Mercurial > hg > xemacs-beta
annotate lisp/menubar.el @ 5067:7d7ae8db0341
add functions `stable-union' and `stable-intersection' to do stable set operations
-------------------- ChangeLog entries follow: --------------------
lisp/ChangeLog addition:
2010-02-22 Ben Wing <ben@xemacs.org>
* cl-seq.el:
* cl-seq.el (stable-union): New.
* cl-seq.el (stable-intersection): New.
New functions to do stable set operations, i.e. preserve the order
of the elements in the argument lists, and prefer LIST1 over LIST2
when ordering the combined result. The result looks as much like
LIST1 as possible, followed (in the case of `stable-union') by
any necessary elements from LIST2, in order. This is contrary to
`union' and `intersection', which are not required to be order-
preserving and are not -- they prefer LIST2 and output results in
backwards order.
author | Ben Wing <ben@xemacs.org> |
---|---|
date | Mon, 22 Feb 2010 21:23:02 -0600 |
parents | a6d7e031a10b |
children | 308d34e9f07d |
rev | line source |
---|---|
428 | 1 ;;; menubar.el --- Menubar support for XEmacs |
2 | |
3 ;; Copyright (C) 1991-4, 1997-1998 Free Software Foundation, Inc. | |
4 ;; Copyright (C) 1995 Tinker Systems and INS Engineering Corp. | |
2545 | 5 ;; Copyright (C) 1995, 1996, 2003 Ben Wing. |
428 | 6 |
7 ;; Maintainer: XEmacs Development Team | |
8 ;; Keywords: internal, extensions, dumped | |
9 | |
10 ;; This file is part of XEmacs. | |
11 | |
12 ;; XEmacs is free software; you can redistribute it and/or modify it | |
13 ;; under the terms of the GNU General Public License as published by | |
14 ;; the Free Software Foundation; either version 2, or (at your option) | |
15 ;; any later version. | |
16 | |
17 ;; XEmacs is distributed in the hope that it will be useful, but | |
18 ;; WITHOUT ANY WARRANTY; without even the implied warranty of | |
19 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
20 ;; General Public License for more details. | |
21 | |
22 ;; You should have received a copy of the GNU General Public License | |
444 | 23 ;; along with XEmacs; see the file COPYING. If not, write to the |
428 | 24 ;; Free Software Foundation, 59 Temple Place - Suite 330, |
25 ;; Boston, MA 02111-1307, USA. | |
26 | |
27 ;;; Synched up with: Not in FSF. (Completely divergent from FSF menu-bar.el) | |
28 | |
29 ;;; Commentary: | |
30 | |
31 ;; This file is dumped with XEmacs (when menubar support is compiled in). | |
32 | |
442 | 33 ;; Some stuff in FSF menu-bar.el is in menubar-items.el |
428 | 34 |
35 ;;; Code: | |
36 | |
37 (defgroup menu nil | |
38 "Input from the menus." | |
39 :group 'environment) | |
40 | |
41 (defvar default-menubar nil) | |
42 | |
43 ;; this function is considered "part of the lexicon" by many, | |
44 ;; so we'll leave it here. | |
45 (defun kill-this-buffer () ; for the menubar | |
46 "Kill the current buffer." | |
47 (interactive) | |
48 (kill-buffer (current-buffer))) | |
49 | |
50 (defun set-menubar-dirty-flag () | |
51 "Tell XEmacs that the menubar has to be updated. | |
52 NOTE: XEmacs now recognizes when you set a different value for | |
53 `current-menubar'. You *only* need to call this function if you | |
54 destructively modify a part of the menubar and don't set `current-menubar'. | |
55 Note that all the functions that modify a menu call this automatically." | |
56 (setq-default current-menubar (default-value 'current-menubar))) | |
57 | |
58 ;; #### shouldn't this perhaps be `copy-tree'? | |
59 (defun set-menubar (menubar) | |
60 "Set the default menubar to be MENUBAR. | |
61 See `current-menubar' for a description of the syntax of a menubar." | |
62 (check-menu-syntax menubar t) | |
63 (setq-default current-menubar (copy-sequence menubar))) | |
64 | |
65 (defun set-buffer-menubar (menubar) | |
66 "Set the buffer-local menubar to be MENUBAR. | |
67 See `current-menubar' for a description of the syntax of a menubar." | |
68 (check-menu-syntax menubar t) | |
69 (make-local-variable 'current-menubar) | |
70 (setq current-menubar (copy-sequence menubar))) | |
71 | |
72 (defun check-menu-syntax (menu &optional menubar-p) | |
73 ;; The C code does syntax checking on the value of `current-menubar', | |
74 ;; but it's better to do it early, before things have gotten messed up. | |
75 (if menubar-p | |
76 nil | |
77 (or (stringp (car menu)) | |
78 (signal 'error | |
79 (list "menu name (first element) must be a string" menu))) | |
80 ;;(or (cdr menu) (signal 'error (list "menu is empty" menu))) | |
81 (setq menu (cdr menu))) | |
82 (let (menuitem item) | |
83 (while (keywordp (setq item (car menu))) | |
84 (or (memq item '(:config :included :filter :accelerator)) | |
85 (signal 'error | |
86 (list "menu keyword must be :config, :included, :accelerator or :filter" | |
87 item))) | |
88 (if (or (not (cdr menu)) | |
89 (vectorp (nth 1 menu)) | |
90 (keywordp (nth 1 menu))) | |
91 (signal 'error (list "strange keyword value" item (nth 1 menu)))) | |
92 (setq menu (nthcdr 2 menu))) | |
93 (while menu | |
94 (setq menuitem (car menu)) | |
95 (cond | |
96 ((stringp menuitem) | |
97 (and (string-match "^\\(-+\\|=+\\):\\(.*\\)" menuitem) | |
98 (setq item (match-string 2 menuitem)) | |
444 | 99 (or (member item '(;; Motif-compatible |
428 | 100 "singleLine" |
101 "doubleLine" | |
102 "singleDashedLine" | |
103 "doubleDashedLine" | |
104 "noLine" | |
105 "shadowEtchedIn" | |
106 "shadowEtchedOut" | |
107 "shadowEtchedInDash" | |
108 "shadowEtchedOutDash" | |
109 ;; non-Motif (Lucid menubar widget only) | |
110 "shadowDoubleEtchedIn" | |
111 "shadowDoubleEtchedOut" | |
112 "shadowDoubleEtchedInDash" | |
113 "shadowDoubleEtchedOutDash" | |
114 )) | |
115 (signal 'error (list "bogus separator style in menu item" item))) | |
116 )) | |
117 ((null menuitem) | |
118 (or menubar-p | |
119 (signal 'error (list "nil is only permitted in the top level of menubars")))) | |
120 ((consp menuitem) | |
121 (check-menu-syntax menuitem)) | |
122 ((vectorp menuitem) | |
123 (let ((L (length menuitem)) | |
124 plistp) | |
125 (and (< L 2) | |
126 (signal 'error | |
127 (list "button descriptors must be at least 2 long" | |
128 menuitem))) | |
129 (setq plistp (or (>= L 5) | |
130 (and (> L 2) (keywordp (aref menuitem 2))))) | |
131 (if plistp | |
132 (let ((i 2) | |
133 selp | |
134 style | |
135 item) | |
136 (while (< i L) | |
137 (setq item (aref menuitem i)) | |
138 (cond ((not (memq item '(:active :suffix :keys :style | |
139 :full :included :selected | |
140 :accelerator))) | |
141 (signal 'error | |
142 (list (if (keywordp item) | |
143 "unknown menu item keyword" | |
144 "not a keyword") | |
145 item menuitem))) | |
146 ((eq item :style) | |
147 (setq style (aref menuitem (1+ i))) | |
148 (or (memq style '(nil toggle radio button text)) | |
149 (signal 'error (list "unknown style" style | |
150 menuitem)))) | |
151 ((eq item :selected) (setq selp t)) | |
152 ) | |
153 (setq i (+ i (if (eq item :full) 1 2)))) | |
154 (if (and selp (not (memq style '(toggle button radio)))) | |
155 (signal 'error | |
156 (list | |
157 ":selected only makes sense with :style toggle, radio, or button" | |
158 menuitem))) | |
159 ))) | |
160 ) | |
161 ;; (t (signal 'error (list "unrecognized menu descriptor" menuitem)))) | |
162 (t (message "unrecognized menu descriptor %s" (prin1-to-string menuitem)))) | |
163 (setq menu (cdr menu))))) | |
164 | |
165 | |
2545 | 166 ;;; basic menu manipulation functions |
428 | 167 |
2545 | 168 (defun menu-item-text (item &optional normalize) |
169 "Return the text that is displayed for a menu item. | |
170 If ITEM is a string (unselectable text), it is returned; otherwise, | |
171 the first element of the cons or vector is returned. | |
172 If NORMALIZE is non-nil, pass the text through `normalize-menu-text' | |
173 before being returned, to remove accelerator specs and convert %% to %." | |
174 (let ((val (if (stringp item) item (elt item 0)))) | |
175 (if normalize (normalize-menu-text val) val))) | |
176 | |
177 (defun find-menu-item (menubar item-path-list) | |
178 "Search MENUBAR for item given by ITEM-PATH-LIST. | |
428 | 179 Returns (ITEM . PARENT), where PARENT is the immediate parent of |
180 the item found. | |
181 If the item does not exist, the car of the returned value is nil. | |
182 If some menu in the ITEM-PATH-LIST does not exist, an error is signalled." | |
2545 | 183 (find-menu-item-1 menubar item-path-list)) |
184 | |
185 (defun find-menu-item-1 (menubar item-path-list &optional parent) | |
428 | 186 (check-argument-type 'listp item-path-list) |
187 (if (not (consp menubar)) | |
188 nil | |
189 (let ((rest menubar) | |
190 result) | |
191 (when (stringp (car rest)) | |
192 (setq rest (cdr rest))) | |
193 (while (keywordp (car rest)) | |
194 (setq rest (cddr rest))) | |
195 (while rest | |
196 (if (and (car rest) | |
2545 | 197 (stringp (car item-path-list)) |
198 (= 0 (compare-menu-text (car item-path-list) | |
199 (menu-item-text (car rest))))) | |
428 | 200 (setq result (car rest) |
201 rest nil) | |
202 (setq rest (cdr rest)))) | |
203 (if (cdr item-path-list) | |
204 (cond ((consp result) | |
2571 | 205 (find-menu-item-1 (cdr result) (cdr item-path-list) result)) |
428 | 206 (result |
207 (signal 'error (list (gettext "not a submenu") result))) | |
208 (t | |
209 (signal 'error (list (gettext "no such submenu") | |
210 (car item-path-list))))) | |
211 (cons result parent))))) | |
212 | |
4310
a6d7e031a10b
Fix two Tailor glitches.
Mike Sperber <sperber@deinprogramm.de>
parents:
4164
diff
changeset
|
213 (defun add-menu-item-1 (leaf-p menu-path new-item before in-menu) |
428 | 214 ;; This code looks like it could be cleaned up some more |
215 ;; Do we really need 6 calls to find-menu-item? | |
216 (let* ((item-name | |
217 (cond ((vectorp new-item) (aref new-item 0)) | |
218 ((consp new-item) (car new-item)) | |
219 (t nil))) | |
220 (menubar (or in-menu current-menubar)) | |
221 (menu (condition-case () | |
222 (car (find-menu-item menubar menu-path)) | |
223 (error nil))) | |
224 (item-found (cond | |
225 ((null item-name) | |
226 nil) | |
227 ((not (listp menu)) | |
228 (signal 'error (list (gettext "not a submenu") | |
229 menu-path))) | |
230 (menu | |
231 (find-menu-item (cdr menu) (list item-name))) | |
232 (t | |
233 (find-menu-item menubar (list item-name))) | |
234 ))) | |
235 (unless menubar | |
236 (error "`current-menubar' is nil: can't add menus to it.")) | |
237 (unless menu | |
238 (let ((rest menu-path) | |
239 (so-far menubar)) | |
240 (while rest | |
241 ;;; (setq menu (car (find-menu-item (cdr so-far) (list (car rest))))) | |
242 (setq menu | |
243 (if (eq so-far menubar) | |
244 (car (find-menu-item so-far (list (car rest)))) | |
245 (car (find-menu-item (cdr so-far) (list (car rest)))))) | |
246 (unless menu | |
247 (let ((rest2 so-far)) | |
248 (while (and (cdr rest2) (car (cdr rest2))) | |
249 (setq rest2 (cdr rest2))) | |
250 (setcdr rest2 | |
251 (nconc (list (setq menu (list (car rest)))) | |
252 (cdr rest2))))) | |
253 (setq so-far menu) | |
254 (setq rest (cdr rest))))) | |
255 (if (and item-found (car item-found)) | |
256 ;; hack the item in place. | |
257 (if menu | |
258 ;; Isn't it very bad form to use nsubstitute for side effects? | |
259 (nsubstitute new-item (car item-found) menu) | |
260 (setq current-menubar (nsubstitute new-item | |
261 (car item-found) | |
262 current-menubar))) | |
263 ;; OK, we have to add the whole thing... | |
264 ;; if BEFORE is specified, try to add it there. | |
265 (unless menu (setq menu current-menubar)) | |
266 (when before | |
267 (setq before (car (find-menu-item menu (list before))))) | |
268 (let ((rest menu) | |
269 (added-before nil)) | |
270 (while rest | |
271 (if (eq before (car (cdr rest))) | |
272 (progn | |
273 (setcdr rest (cons new-item (cdr rest))) | |
274 (setq rest nil added-before t)) | |
275 (setq rest (cdr rest)))) | |
276 (when (not added-before) | |
277 ;; adding before the first item on the menubar itself is harder | |
278 (if (and (eq menu menubar) (eq before (car menu))) | |
279 (setq menu (cons new-item menu) | |
280 current-menubar menu) | |
281 ;; otherwise, add the item to the end. | |
282 (nconc menu (list new-item)))))) | |
283 (set-menubar-dirty-flag) | |
284 new-item)) | |
285 | |
4310
a6d7e031a10b
Fix two Tailor glitches.
Mike Sperber <sperber@deinprogramm.de>
parents:
4164
diff
changeset
|
286 (defun add-menu-button (menu-path menu-leaf &optional before in-menu) |
428 | 287 "Add a menu item to some menu, creating the menu first if necessary. |
288 If the named item exists already, it is changed. | |
289 MENU-PATH identifies the menu under which the new menu item should be inserted. | |
290 It is a list of strings; for example, (\"File\") names the top-level \"File\" | |
291 menu. (\"File\" \"Foo\") names a hypothetical submenu of \"File\". | |
4310
a6d7e031a10b
Fix two Tailor glitches.
Mike Sperber <sperber@deinprogramm.de>
parents:
4164
diff
changeset
|
292 MENU-LEAF is a menubar leaf node. See the documentation of `current-menubar'. |
428 | 293 BEFORE, if provided, is the name of a menu item before which this item should |
294 be added, if this item is not on the menu already. If the item is already | |
295 present, it will not be moved. | |
444 | 296 IN-MENU, if provided, means use that instead of `current-menubar' as the |
297 menu to change." | |
428 | 298 ;; Note easymenu.el uses the fact that menu-leaf can be a submenu. |
4310
a6d7e031a10b
Fix two Tailor glitches.
Mike Sperber <sperber@deinprogramm.de>
parents:
4164
diff
changeset
|
299 (add-menu-item-1 t menu-path menu-leaf before in-menu)) |
428 | 300 |
301 ;; I actually liked the old name better, but the interface has changed too | |
444 | 302 ;; drastically to keep it. --Stig |
428 | 303 (defun add-submenu (menu-path submenu &optional before in-menu) |
304 "Add a menu to the menubar or one of its submenus. | |
305 If the named menu exists already, it is changed. | |
306 MENU-PATH identifies the menu under which the new menu should be inserted. | |
307 It is a list of strings; for example, (\"File\") names the top-level \"File\" | |
308 menu. (\"File\" \"Foo\") names a hypothetical submenu of \"File\". | |
309 If MENU-PATH is nil, then the menu will be added to the menubar itself. | |
310 SUBMENU is the new menu to add. | |
311 See the documentation of `current-menubar' for the syntax. | |
312 BEFORE, if provided, is the name of a menu before which this menu should | |
313 be added, if this menu is not on its parent already. If the menu is already | |
444 | 314 present, it will not be moved. |
315 IN-MENU, if provided, means use that instead of `current-menubar' as the | |
316 menu to change." | |
428 | 317 (check-menu-syntax submenu nil) |
4310
a6d7e031a10b
Fix two Tailor glitches.
Mike Sperber <sperber@deinprogramm.de>
parents:
4164
diff
changeset
|
318 (add-menu-item-1 nil menu-path submenu before in-menu)) |
444 | 319 ;; purespace is no more, so this function is unnecessary |
320 ;(defun purecopy-menubar (x) | |
321 ; ;; this calls purecopy on the strings, and the contents of the vectors, | |
322 ; ;; but not on the vectors themselves, or the conses - those must be | |
323 ; ;; writable. | |
324 ; (cond ((vectorp x) | |
325 ; (let ((i (length x))) | |
326 ; (while (> i 0) | |
327 ; (aset x (1- i) (purecopy (aref x (1- i)))) | |
328 ; (setq i (1- i)))) | |
329 ; x) | |
330 ; ((consp x) | |
331 ; (let ((rest x)) | |
332 ; (while rest | |
333 ; (setcar rest (purecopy-menubar (car rest))) | |
334 ; (setq rest (cdr rest)))) | |
335 ; x) | |
336 ; (t | |
337 ; (purecopy x)))) | |
428 | 338 |
339 (defun delete-menu-item (path &optional from-menu) | |
340 "Remove the named menu item from the menu hierarchy. | |
444 | 341 PATH is a list of strings which identify the position of the menu item |
342 in the menu hierarchy. The documentation of `add-submenu' describes | |
343 menu paths. | |
344 FROM-MENU, if provided, means use that instead of `current-menubar' | |
345 as the menu to change." | |
428 | 346 (let* ((pair (condition-case nil (find-menu-item (or from-menu |
347 current-menubar) path) | |
348 (error nil))) | |
349 (item (car pair)) | |
350 (parent (or (cdr pair) current-menubar))) | |
351 (if (not item) | |
352 nil | |
353 ;; the menubar is the only special case, because other menus begin | |
354 ;; with their name. | |
355 (if (eq parent current-menubar) | |
356 (setq current-menubar (delq item parent)) | |
357 (delq item parent)) | |
358 (set-menubar-dirty-flag) | |
359 item))) | |
360 | |
361 (defun relabel-menu-item (path new-name) | |
362 "Change the string of the specified menu item. | |
444 | 363 PATH is a list of strings which identify the position of the menu item in |
428 | 364 the menu hierarchy. (\"File\" \"Save\") means the menu item called \"Save\" |
444 | 365 under the toplevel \"File\" menu. (\"Menu\" \"Foo\" \"Item\") means the |
428 | 366 menu item called \"Item\" under the \"Foo\" submenu of \"Menu\". |
367 NEW-NAME is the string that the menu item will be printed as from now on." | |
446 | 368 (check-type new-name string) |
428 | 369 (let* ((menubar current-menubar) |
370 (pair (find-menu-item menubar path)) | |
371 (item (car pair)) | |
372 (menu (cdr pair))) | |
373 (or item | |
374 (signal 'error (list (if menu (gettext "No such menu item") | |
375 (gettext "No such menu")) | |
376 path))) | |
377 (if (and (consp item) | |
378 (stringp (car item))) | |
379 (setcar item new-name) | |
380 (aset item 0 new-name)) | |
381 (set-menubar-dirty-flag) | |
382 item)) | |
383 | |
384 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
385 ;; | |
386 ;; these are all bad style. Why in the world would we put evaluable forms | |
387 ;; into the menubar if we didn't want people to use 'em? | |
388 ;; x-font-menu.el is the only known offender right now and that ought to be | |
389 ;; rehashed a bit. | |
444 | 390 ;; |
428 | 391 |
392 (defun enable-menu-item-1 (path toggle-p on-p) | |
393 (let (menu item) | |
394 (if (and (vectorp path) (> (length path) 2)) ; limited syntax checking... | |
395 (setq item path) | |
396 (let* ((menubar current-menubar) | |
397 (pair (find-menu-item menubar path))) | |
398 (setq item (car pair) | |
399 menu (cdr pair)) | |
400 (or item | |
401 (signal 'error (list (if menu | |
402 "No such menu item" | |
403 "No such menu") | |
404 path))) | |
405 (if (consp item) | |
406 (error "%S is a menu, not a menu item" path)))) | |
407 (if (or (> (length item) 4) | |
408 (and (symbolp (aref item 2)) | |
409 (= ?: (aref (symbol-name (aref item 2)) 0)))) | |
410 ;; plist-like syntax | |
411 (let ((i 2) | |
412 (keyword (if toggle-p :selected :active)) | |
413 (ok nil)) | |
414 (while (< i (length item)) | |
415 (cond ((eq (aref item i) keyword) | |
416 (aset item (1+ i) on-p) | |
417 (setq ok t))) | |
418 (setq i (+ i 2))) | |
419 (cond (ok nil) | |
420 (toggle-p | |
421 (signal 'error (list "not a toggle menu item" item))) | |
422 (t | |
423 ;; Need to copy the item to extend it, sigh... | |
424 (let ((cons (memq item menu)) | |
425 (new-item (vconcat item (list keyword on-p)))) | |
426 (if cons | |
427 (setcar cons (setq item new-item)) | |
428 (if menu | |
429 (error "couldn't find %S on its parent?" item) | |
430 (error "no %S slot to set: %S" keyword item))))))) | |
431 ;; positional syntax | |
432 (if toggle-p | |
433 (signal 'error (list "not a toggle menu item" item)) | |
434 (aset item 2 on-p))) | |
435 (set-menubar-dirty-flag) | |
436 item)) | |
437 | |
438 (defun enable-menu-item (path) | |
439 "Make the named menu item be selectable. | |
444 | 440 PATH is a list of strings which identify the position of the menu item in |
428 | 441 the menu hierarchy. (\"File\" \"Save\") means the menu item called \"Save\" |
444 | 442 under the toplevel \"File\" menu. (\"Menu\" \"Foo\" \"Item\") means the |
428 | 443 menu item called \"Item\" under the \"Foo\" submenu of \"Menu\"." |
444 (enable-menu-item-1 path nil t)) | |
445 | |
446 (defun disable-menu-item (path) | |
447 "Make the named menu item be unselectable. | |
444 | 448 PATH is a list of strings which identify the position of the menu item in |
428 | 449 the menu hierarchy. (\"File\" \"Save\") means the menu item called \"Save\" |
444 | 450 under the toplevel \"File\" menu. (\"Menu\" \"Foo\" \"Item\") means the |
428 | 451 menu item called \"Item\" under the \"Foo\" submenu of \"Menu\"." |
452 (enable-menu-item-1 path nil nil)) | |
453 | |
454 (defun select-toggle-menu-item (path) | |
455 "Make the named toggle- or radio-style menu item be in the `selected' state. | |
444 | 456 PATH is a list of strings which identify the position of the menu item in |
428 | 457 the menu hierarchy. (\"File\" \"Save\") means the menu item called \"Save\" |
444 | 458 under the toplevel \"File\" menu. (\"Menu\" \"Foo\" \"Item\") means the |
428 | 459 menu item called \"Item\" under the \"Foo\" submenu of \"Menu\"." |
460 (enable-menu-item-1 path t t)) | |
461 | |
462 (defun deselect-toggle-menu-item (path) | |
463 "Make the named toggle- or radio-style menu item be in the `unselected' state. | |
444 | 464 PATH is a list of strings which identify the position of the menu item in |
428 | 465 the menu hierarchy. (\"File\" \"Save\") means the menu item called \"Save\" |
444 | 466 under the toplevel \"File\" menu. (\"Menu\" \"Foo\" \"Item\") means the |
428 | 467 menu item called \"Item\" under the \"Foo\" submenu of \"Menu\"." |
468 (enable-menu-item-1 path t nil)) | |
469 | |
470 | |
2545 | 471 ;;; functions for manipulating whole menus -- adding accelerators, sorting, |
472 ;;; splitting long menus, etc. | |
473 | |
474 (defun submenu-generate-accelerator-spec (list &optional omit-chars-list) | |
475 "Add auto-generated accelerator specifications to a submenu. | |
476 This can be used to add accelerators to the return value of a menu filter | |
477 function. It correctly ignores unselectable items. It will destructively | |
478 modify the list passed to it. If an item already has an auto-generated | |
479 accelerator spec, this will be removed before the new one is added, making | |
480 this function idempotent. | |
481 | |
482 If OMIT-CHARS-LIST is given, it should be a list of lowercase characters, | |
483 which will not be used as accelerators." | |
484 (let ((n 0)) | |
485 (dolist (item list list) | |
486 (cond | |
487 ((or (vectorp item) (consp item)) | |
488 (incf n) | |
489 (setf (elt item 0) | |
490 (concat | |
491 (menu-item-generate-accelerator-spec n omit-chars-list) | |
492 (menu-item-strip-accelerator-spec (elt item 0))))))))) | |
493 | |
494 (defun menu-item-strip-accelerator-spec (item) | |
495 "Strip an auto-generated accelerator spec off of ITEM. | |
496 ITEM should be a string. This removes specs added by | |
497 `menu-item-generate-accelerator-spec' and `submenu-generate-accelerator-spec'." | |
498 (if (string-match "%_. " item) | |
499 (substring item 4) | |
500 item)) | |
501 | |
502 (defun menu-item-generate-accelerator-spec (n &optional omit-chars-list) | |
503 "Return an accelerator specification for use with auto-generated menus. | |
504 This should be concat'd onto the beginning of each menu line. The spec | |
505 allows the Nth line to be selected by the number N. '0' is used for the | |
506 10th line, and 'a' through 'z' are used for the following 26 lines. | |
507 | |
508 If OMIT-CHARS-LIST is given, it should be a list of lowercase characters, | |
509 which will not be used as accelerators." | |
510 (cond ((< n 10) (concat "%_" (int-to-string n) " ")) | |
511 ((= n 10) "%_0 ") | |
512 ((<= n 36) | |
513 (setq n (- n 10)) | |
514 (let ((m 0)) | |
515 (while (> n 0) | |
516 (setq m (1+ m)) | |
517 (while (memq (int-to-char (+ m (- (char-to-int ?a) 1))) | |
518 omit-chars-list) | |
519 (setq m (1+ m))) | |
520 (setq n (1- n))) | |
521 (if (<= m 26) | |
522 (concat | |
523 "%_" | |
524 (char-to-string (int-to-char (+ m (- (char-to-int ?a) 1)))) | |
525 " ") | |
526 ""))) | |
527 (t ""))) | |
528 | |
529 (defcustom menu-max-items 25 | |
530 "*Maximum number of items in generated menus. | |
531 If number of entries in such a menu is larger than this value, split menu | |
532 into submenus of nearly equal length (see `menu-submenu-max-items'). If | |
533 nil, never split menu into submenus." | |
534 :group 'menu | |
535 :type '(choice (const :tag "no submenus" nil) | |
536 (integer))) | |
537 | |
538 (defcustom menu-submenu-max-items 20 | |
539 "*Maximum number of items in submenus when splitting menus. | |
540 We split large menus into submenus of this many items, and then balance | |
541 them out as much as possible (otherwise the last submenu may have very few | |
542 items)." | |
543 :group 'menu | |
544 :type 'integer) | |
545 | |
546 (defcustom menu-submenu-name-format "%-12.12s ... %.12s" | |
547 "*Format specification of the submenu name when splitting menus. | |
548 Used by `menu-split-long-menu' if the number of entries in a menu is | |
549 larger than `menu-menu-max-items'. | |
550 This string should contain one %s for the name of the first entry and | |
551 one %s for the name of the last entry in the submenu. | |
552 If the value is a function, it should return the submenu name. The | |
553 function is be called with two arguments, the names of the first and | |
554 the last entry in the menu." | |
555 :group 'menu | |
556 :type '(choice (string :tag "Format string") | |
557 (function))) | |
558 | |
559 (defun menu-split-long-menu-and-sort (menu) | |
560 "Sort MENU, split according to `menu-max-items' and add accelerator specs. | |
561 This is useful for menus generated by filter functions, to make them look | |
562 nice. This is equivalent to | |
563 | |
564 \(menu-split-long-menu (menu-sort-menu menu)) | |
565 | |
566 and you can call those functions individually if necessary. | |
567 You can also call `submenu-generate-accelerator-spec' yourself to add | |
568 accelerator specs -- this works even if the specs have already been added." | |
569 (menu-split-long-menu (menu-sort-menu menu))) | |
570 | |
4310
a6d7e031a10b
Fix two Tailor glitches.
Mike Sperber <sperber@deinprogramm.de>
parents:
4164
diff
changeset
|
571 ;;;###autoload |
2545 | 572 (defun menu-split-long-menu (menu) |
573 "Split MENU according to `menu-max-items' and add accelerator specs. | |
574 If MENU already has accelerator specs, they will be removed and new ones | |
575 generated. You should normally use `menu-split-long-menu-and-sort' instead. | |
576 The menu should already be sorted to get meaningful results when it is | |
577 split, since the outer menus are of the format `FROM ... TO'." | |
578 (let ((len (length menu))) | |
579 (if (or (null menu-max-items) | |
580 (<= len menu-max-items)) | |
581 (submenu-generate-accelerator-spec menu) | |
582 (let* ((outer (/ (+ len (1- menu-submenu-max-items)) | |
583 menu-submenu-max-items)) | |
584 (inner (/ (+ len (1- outer)) outer)) | |
585 (result nil)) | |
586 (while menu | |
587 (let ((sub nil) | |
588 (from (car menu))) | |
589 (dotimes (foo (min inner len)) | |
590 (setq sub (cons (car menu) sub) | |
591 menu (cdr menu))) | |
592 (setq len (- len inner)) | |
593 (let* ((to (car sub)) | |
594 (ftext (menu-item-strip-accelerator-spec | |
595 (menu-item-text from))) | |
596 (ttext (menu-item-strip-accelerator-spec | |
597 (menu-item-text to)))) | |
598 (setq sub (nreverse sub)) | |
599 (setq result | |
600 (cons (cons (if (stringp menu-submenu-name-format) | |
601 (format menu-submenu-name-format | |
602 ftext ttext) | |
603 (funcall menu-submenu-name-format | |
604 ftext ttext)) | |
605 (submenu-generate-accelerator-spec sub)) | |
606 result))))) | |
607 (submenu-generate-accelerator-spec (nreverse result)))))) | |
608 | |
609 (defun menu-sort-menu (menu) | |
610 "Sort MENU alphabetically. | |
611 You should normally use `menu-split-long-menu-and-sort' instead." | |
612 (sort menu | |
613 #'(lambda (a b) (< (compare-menu-text | |
614 (menu-item-text a) (menu-item-text b)) | |
615 0)))) | |
616 | |
442 | 617 |
618 ;;;;;;; popup menus | |
619 | |
620 (defvar global-popup-menu nil | |
621 "The global popup menu. This is present in all modes. | |
622 See the function `popup-menu' for a description of menu syntax.") | |
623 | |
624 (defvar mode-popup-menu nil | |
625 "The mode-specific popup menu. Automatically buffer local. | |
626 This is appended to the default items in `global-popup-menu'. | |
627 See the function `popup-menu' for a description of menu syntax.") | |
628 (make-variable-buffer-local 'mode-popup-menu) | |
629 | |
630 (defvar activate-popup-menu-hook nil | |
631 "Function or functions run before a mode-specific popup menu is made visible. | |
632 These functions are called with no arguments, and should interrogate and | |
633 modify the value of `global-popup-menu' or `mode-popup-menu' as desired. | |
634 Note: this hook is only run if you use `popup-mode-menu' for activating the | |
635 global and mode-specific commands; if you have your own binding for button3, | |
636 this hook won't be run.") | |
637 | |
638 (defvar last-popup-menu-event nil | |
639 "The mouse event that invoked the last popup menu. | |
640 NOTE: This is EXPERIMENTAL and may change at any time.") | |
641 | |
642 (defun popup-mode-menu (&optional event) | |
643 "Pop up a menu of global and mode-specific commands. | |
644 The menu is computed by combining `global-popup-menu' and `mode-popup-menu' | |
645 with any items derived from the `context-menu' property of the extent where the | |
646 button was clicked." | |
647 (interactive "_e") | |
648 (setq last-popup-menu-event | |
649 (or (and event (button-event-p event) event) | |
650 (let* ((mouse-pos (mouse-position)) | |
651 (win (car mouse-pos)) | |
652 (x (cadr mouse-pos)) | |
653 (y (cddr mouse-pos)) | |
654 (edges (window-pixel-edges win)) | |
655 (winx (first edges)) | |
656 (winy (second edges)) | |
657 (x (+ x winx)) | |
658 (y (+ y winy))) | |
659 (make-event 'button-press | |
660 `(button 3 x ,x y ,y channel ,(window-frame win) | |
661 timestamp ,(current-event-timestamp | |
662 (cdfw-console win))))))) | |
663 (run-hooks 'activate-popup-menu-hook) | |
664 (let* ((context-window (and event (event-window event))) | |
665 (context-point (and event (event-point event))) | |
666 (context-extents (and context-window | |
667 context-point | |
668 (extents-at context-point | |
669 (window-buffer context-window) | |
670 'context-menu))) | |
671 (context-menu-items | |
672 (apply 'append (mapcar #'(lambda (extent) | |
673 (extent-property extent 'context-menu)) | |
674 context-extents)))) | |
675 (popup-menu | |
462 | 676 (progn |
442 | 677 ;; Merge global-popup-menu and mode-popup-menu |
462 | 678 (and mode-popup-menu (check-menu-syntax mode-popup-menu)) |
679 (let* ((mode-title (and (stringp (car mode-popup-menu)) | |
680 (car mode-popup-menu))) | |
681 (mode-items (if mode-title (cdr mode-popup-menu) | |
682 mode-popup-menu)) | |
683 (global-title (and (stringp (car global-popup-menu)) | |
684 (car global-popup-menu))) | |
685 (global-items (if global-title (cdr global-popup-menu) | |
686 global-popup-menu)) | |
442 | 687 mode-filters) |
688 ;; Strip keywords from local menu for attaching them at the top | |
462 | 689 (while (and mode-items |
690 (keywordp (car mode-items))) | |
442 | 691 ;; Push both keyword and its argument. |
462 | 692 (push (pop mode-items) mode-filters) |
693 (push (pop mode-items) mode-filters)) | |
442 | 694 (setq mode-filters (nreverse mode-filters)) |
695 ;; If mode-filters contains a keyword already present in | |
696 ;; `global-popup-menu', you will probably lose. | |
462 | 697 (append (and popup-menu-titles |
698 (cond (mode-title (list mode-title)) | |
699 (global-title (list global-title)) | |
700 (t ""))) | |
442 | 701 mode-filters |
462 | 702 context-menu-items |
703 (and context-menu-items mode-items '("---")) | |
704 mode-items | |
705 (and (or context-menu-items mode-items) | |
706 global-items '("---" "---")) | |
707 (and global-title (list global-title)) | |
708 global-items | |
709 )))) | |
442 | 710 |
711 (while (popup-up-p) | |
712 (dispatch-event (next-event))) | |
713 | |
714 )) | |
444 | 715 |
442 | 716 (defun popup-buffer-menu (event) |
502 | 717 "Pop up a copy of the menubar Buffers menu where the mouse is clicked." |
442 | 718 (interactive "e") |
719 (let ((window (and (event-over-text-area-p event) (event-window event))) | |
720 (bmenu nil)) | |
721 (or window | |
722 (error "Pointer must be in a normal window")) | |
723 (select-window window) | |
724 (if current-menubar | |
725 (setq bmenu (assoc "%_Buffers" current-menubar))) | |
726 (if (null bmenu) | |
727 (setq bmenu (assoc "%_Buffers" default-menubar))) | |
728 (if (null bmenu) | |
729 (error "Can't find the Buffers menu")) | |
730 (popup-menu bmenu))) | |
731 | |
732 (defun popup-menubar-menu (event) | |
733 "Pop up a copy of menu that also appears in the menubar." | |
734 (interactive "e") | |
735 (let ((window (and (event-over-text-area-p event) (event-window event))) | |
736 popup-menubar) | |
737 (or window | |
738 (error "Pointer must be in a normal window")) | |
739 (select-window window) | |
740 (and current-menubar (run-hooks 'activate-menubar-hook)) | |
741 ;; #### Instead of having to copy this just to safely get rid of | |
742 ;; any nil what we should really do is fix up the internal menubar | |
743 ;; code to just ignore nil if generating a popup menu | |
744 (setq popup-menubar (delete nil (copy-sequence (or current-menubar | |
745 default-menubar)))) | |
746 (popup-menu (cons "%_Menubar Menu" popup-menubar)) | |
747 )) | |
748 | |
749 (defun menu-call-at-event (form &optional event default-behavior-fallback) | |
750 "Call FORM while temporarily setting point to the position in EVENT. | |
751 NOTE: This is EXPERIMENTAL and may change at any time. | |
752 | |
753 FORM is called the way forms in menu specs are: i.e. if a symbol, it's called | |
754 with `call-interactively', otherwise with `eval'. EVENT defaults to | |
755 `last-popup-menu-event', making this function especially useful in popup | |
756 menus. The buffer and point are set temporarily within a `save-excursion'. | |
757 If EVENT is not a mouse event, or was not over a buffer, nothing | |
758 happens unless DEFAULT-BEHAVIOR-FALLBACK is non-nil, in which case the | |
759 FORM is called normally." | |
760 (or event (setq event last-popup-menu-event)) | |
761 (let ((buf (event-buffer event)) | |
762 (p (event-closest-point event))) | |
763 (cond ((and buf p (> p 0)) | |
764 (save-excursion | |
765 (set-buffer buf) | |
766 (goto-char p) | |
767 (if (symbolp form) | |
768 (call-interactively form) | |
769 (eval form)))) | |
770 (default-behavior-fallback | |
771 (if (symbolp form) | |
772 (call-interactively form) | |
773 (eval form)))))) | |
774 | |
775 (global-set-key 'button3 'popup-mode-menu) | |
776 ;; shift button3 and shift button2 are reserved for Hyperbole | |
777 (global-set-key '(meta control button3) 'popup-buffer-menu) | |
778 ;; The following command is way too dangerous with Custom. | |
779 ;; (global-set-key '(meta shift button3) 'popup-menubar-menu) | |
780 | |
781 ;; Here's a test of the cool new menu features (from Stig). | |
782 | |
783 ;;(setq mode-popup-menu | |
784 ;; '("Test Popup Menu" | |
785 ;; :filter cdr | |
786 ;; ["this item won't appear because of the menu filter" ding t] | |
787 ;; "--:singleLine" | |
788 ;; "singleLine" | |
789 ;; "--:doubleLine" | |
790 ;; "doubleLine" | |
791 ;; "--:singleDashedLine" | |
792 ;; "singleDashedLine" | |
793 ;; "--:doubleDashedLine" | |
794 ;; "doubleDashedLine" | |
795 ;; "--:noLine" | |
796 ;; "noLine" | |
797 ;; "--:shadowEtchedIn" | |
798 ;; "shadowEtchedIn" | |
799 ;; "--:shadowEtchedOut" | |
800 ;; "shadowEtchedOut" | |
801 ;; "--:shadowDoubleEtchedIn" | |
802 ;; "shadowDoubleEtchedIn" | |
803 ;; "--:shadowDoubleEtchedOut" | |
804 ;; "shadowDoubleEtchedOut" | |
805 ;; "--:shadowEtchedInDash" | |
806 ;; "shadowEtchedInDash" | |
807 ;; "--:shadowEtchedOutDash" | |
808 ;; "shadowEtchedOutDash" | |
809 ;; "--:shadowDoubleEtchedInDash" | |
810 ;; "shadowDoubleEtchedInDash" | |
811 ;; "--:shadowDoubleEtchedOutDash" | |
812 ;; "shadowDoubleEtchedOutDash" | |
813 ;; )) | |
814 | |
428 | 815 (defun get-popup-menu-response (menu-desc &optional event) |
816 "Pop up the given menu and wait for a response. | |
817 This blocks until the response is received, and returns the misc-user | |
818 event that encapsulates the response. To execute it, you can do | |
819 (funcall (event-function response) (event-object response)) | |
820 If no response was received, nil is returned. | |
821 | |
822 MENU-DESC and EVENT are as in the call to `popup-menu'." | |
823 ;; partially stolen from w3 | |
707 | 824 |
825 ;; This function is way gross and assumes to much about menu | |
826 ;; processing that is X specific. Under mswindows popup menus behave | |
827 ;; in reasonable ways that you can't obstruct. | |
428 | 828 (let ((echo-keystrokes 0) |
829 new-event) | |
830 (popup-menu menu-desc event) | |
831 (catch 'popup-done | |
832 (while t | |
833 (setq new-event (next-command-event new-event)) | |
834 (cond ((misc-user-event-p new-event) | |
835 (throw 'popup-done new-event)) | |
707 | 836 ((button-release-event-p new-event);; don't beep twice |
837 nil) | |
838 ;; It shows how bogus this function is that the event | |
839 ;; arg could be missing and no-one noticed ... | |
840 ((event-matches-key-specifier-p new-event (quit-char)) | |
841 (signal 'quit nil)) | |
842 ;; mswindows has no pop-down processing (selection is | |
843 ;; atomic) so doing anything more makes no sense. Since | |
844 ;; popup-up-p is always false under mswindows, this | |
845 ;; function has been ordered to do essentially X-specifc | |
846 ;; processing after this check. | |
847 ((not (popup-up-p)) | |
428 | 848 (setq unread-command-events (cons new-event |
849 unread-command-events)) | |
850 (throw 'popup-done nil)) | |
707 | 851 ;; mswindows never gets here |
428 | 852 (t |
853 (beep) | |
854 (message "please make a choice from the menu."))))))) | |
855 | |
856 (defun popup-menu-and-execute-in-window (menu-desc event) | |
857 "Pop up the given menu and execute its response in EVENT's window. | |
858 This blocks until the response is received, temporarily selects | |
859 EVENT's window, and executes the command specified in the response. | |
860 EVENT can also be a window. See `popup-menu' for the semantics of | |
861 MENU-DESC." | |
862 (let ((response | |
863 (get-popup-menu-response menu-desc | |
864 (and (eventp event) event)))) | |
865 (and (misc-user-event-p response) | |
866 (save-selected-window | |
867 (select-window (if (windowp event) event | |
868 (event-window event))) | |
869 (funcall (event-function response) | |
870 (event-object response)))))) | |
871 | |
872 ;; provide default bindings for menu accelerator map | |
873 (and (boundp 'menu-accelerator-map) | |
874 (keymapp menu-accelerator-map) | |
875 (progn | |
876 (define-key menu-accelerator-map "\e" 'menu-escape) | |
877 (define-key menu-accelerator-map [left] 'menu-left) | |
878 (define-key menu-accelerator-map [right] 'menu-right) | |
879 (define-key menu-accelerator-map [up] 'menu-up) | |
880 (define-key menu-accelerator-map [down] 'menu-down) | |
881 (define-key menu-accelerator-map [return] 'menu-select) | |
502 | 882 (define-key menu-accelerator-map [kp-down] 'menu-down) |
883 (define-key menu-accelerator-map [kp-up] 'menu-down) | |
884 (define-key menu-accelerator-map [kp-left] 'menu-left) | |
885 (define-key menu-accelerator-map [kp-right] 'menu-right) | |
886 (define-key menu-accelerator-map [kp-enter] 'menu-select) | |
428 | 887 (define-key menu-accelerator-map "\C-g" 'menu-quit))) |
888 | |
889 | |
890 (provide 'menubar) | |
891 | |
892 ;;; menubar.el ends here |