summaryrefslogtreecommitdiff
path: root/lisp/emacs-lisp/easymenu.el
diff options
context:
space:
mode:
Diffstat (limited to 'lisp/emacs-lisp/easymenu.el')
-rw-r--r--lisp/emacs-lisp/easymenu.el112
1 files changed, 73 insertions, 39 deletions
diff --git a/lisp/emacs-lisp/easymenu.el b/lisp/emacs-lisp/easymenu.el
index 37083ac800a..ca5151fa984 100644
--- a/lisp/emacs-lisp/easymenu.el
+++ b/lisp/emacs-lisp/easymenu.el
@@ -116,10 +116,15 @@ whenever this expression's value is non-nil.
INCLUDE is an expression; this item is only visible if this
expression has a non-nil value. `:included' is an alias for `:visible'.
+ :label FORM
+
+FORM is an expression that will be dynamically evaluated and whose
+value will be used for the menu entry's text label (the default is NAME).
+
:suffix FORM
FORM is an expression that will be dynamically evaluated and whose
-value will be concatenated to the menu entry's NAME.
+value will be concatenated to the menu entry's label.
:style STYLE
@@ -152,6 +157,21 @@ A menu item can be a list with the same format as MENU. This is a submenu."
,(if symbol `(defvar ,symbol nil ,doc))
(easy-menu-do-define (quote ,symbol) ,maps ,doc ,menu)))
+(defun easy-menu-binding (menu &optional item-name)
+ "Return a binding suitable to pass to `define-key'.
+This is expected to be bound to a mouse event."
+ ;; Under Emacs this is almost trivial, whereas under XEmacs this may
+ ;; involve defining a function that calls popup-menu.
+ (let ((props (if (symbolp menu)
+ (prog1 (get menu 'menu-prop)
+ (setq menu (symbol-function menu))))))
+ (cons 'menu-item
+ (cons (or item-name
+ (if (keymapp menu)
+ (keymap-prompt menu))
+ "")
+ (cons menu props)))))
+
;;;###autoload
(defun easy-menu-do-define (symbol maps doc menu)
;; We can't do anything that might differ between Emacs dialects in
@@ -173,15 +193,10 @@ A menu item can be a list with the same format as MENU. This is a submenu."
'identity)
(symbol-function ,symbol)))
,symbol)))))
- (mapcar (lambda (map)
- (define-key map (vector 'menu-bar (easy-menu-intern (car menu)))
- (cons 'menu-item
- (cons (car menu)
- (if (not (symbolp keymap))
- (list keymap)
- (cons (symbol-function keymap)
- (get keymap 'menu-prop)))))))
- (if (keymapp maps) (list maps) maps))))
+ (dolist (map (if (keymapp maps) (list maps) maps))
+ (define-key map
+ (vector 'menu-bar (easy-menu-intern (car menu)))
+ (easy-menu-binding keymap (car menu))))))
(defun easy-menu-filter-return (menu &optional name)
"Convert MENU to the right thing to return from a menu filter.
@@ -201,12 +216,18 @@ If NAME is provided, it is used for the keymap."
(setq menu (cdr (easy-menu-convert-item menu)))))
menu)
+(defvar easy-menu-avoid-duplicate-keys t
+ "Dynamically scoped var to register already used keys in a menu.
+If it holds a list, this is expected to be a list of keys already seen in the
+menu we're processing. Else it means we're not processing a menu.")
+
;;;###autoload
(defun easy-menu-create-menu (menu-name menu-items)
"Create a menu called MENU-NAME with items described in MENU-ITEMS.
MENU-NAME is a string, the name of the menu. MENU-ITEMS is a list of items
possibly preceded by keyword pairs as described in `easy-menu-define'."
(let ((menu (make-sparse-keymap menu-name))
+ (easy-menu-avoid-duplicate-keys nil)
prop keyword arg label enable filter visible help)
;; Look for keywords.
(while (and menu-items
@@ -249,10 +270,6 @@ possibly preceded by keyword pairs as described in `easy-menu-define'."
(defvar easy-menu-button-prefix
'((radio . :radio) (toggle . :toggle)))
-(defun easy-menu-do-add-item (menu item &optional before)
- (setq item (easy-menu-convert-item item))
- (easy-menu-define-key menu (easy-menu-intern (car item)) (cdr item) before))
-
(defvar easy-menu-converted-items-table (make-hash-table :test 'equal))
(defun easy-menu-convert-item (item)
@@ -269,7 +286,7 @@ would always fail because the key is `equal' but not `eq'."
(defun easy-menu-convert-item-1 (item)
"Parse an item description and convert it to a menu keymap element.
ITEM defines an item as in `easy-menu-define'."
- (let (name command label prop remove help)
+ (let (name command label prop remove)
(cond
((stringp item) ; An item or separator.
(setq label item))
@@ -330,22 +347,22 @@ ITEM defines an item as in `easy-menu-define'."
(setq prop (cons :button
(cons (cons (cdr style) selected) prop)))))
(when (stringp keys)
- (if (string-match "^[^\\]*\\(\\\\\\[\\([^]]+\\)]\\)[^\\]*$"
- keys)
- (let ((prefix
- (if (< (match-beginning 0) (match-beginning 1))
- (substring keys 0 (match-beginning 1))))
- (postfix
- (if (< (match-end 1) (match-end 0))
- (substring keys (match-end 1))))
- (cmd (intern (match-string 2 keys))))
- (setq keys (and (or prefix postfix)
- (cons prefix postfix)))
- (setq keys
- (and (or keys (not (eq command cmd)))
- (cons cmd keys))))
- (setq cache-specified nil))
- (if keys (setq prop (cons :keys (cons keys prop)))))
+ (if (string-match "^[^\\]*\\(\\\\\\[\\([^]]+\\)]\\)[^\\]*$"
+ keys)
+ (let ((prefix
+ (if (< (match-beginning 0) (match-beginning 1))
+ (substring keys 0 (match-beginning 1))))
+ (postfix
+ (if (< (match-end 1) (match-end 0))
+ (substring keys (match-end 1))))
+ (cmd (intern (match-string 2 keys))))
+ (setq keys (and (or prefix postfix)
+ (cons prefix postfix)))
+ (setq keys
+ (and (or keys (not (eq command cmd)))
+ (cons cmd keys))))
+ (setq cache-specified nil))
+ (if keys (setq prop (cons :keys (cons keys prop)))))
(if (and visible (not (easy-menu-always-true-p visible)))
(if (equal visible ''nil)
;; Invisible menu item. Don't insert into keymap.
@@ -360,12 +377,27 @@ ITEM defines an item as in `easy-menu-define'."
;; `intern' the name so as to merge multiple entries with the same name.
;; It also makes it easier/possible to lookup/change menu bindings
;; via keymap functions.
- (cons (easy-menu-intern name)
- (and (not remove)
- (cons 'menu-item
- (cons label
- (and name
- (cons command prop))))))))
+ (let ((key (easy-menu-intern name)))
+ (when (listp easy-menu-avoid-duplicate-keys)
+ ;; Merging multiple entries with the same name is sometimes what we
+ ;; want, but not when the entries are actually different (e.g. same
+ ;; name but different :suffix as seen in cal-menu.el) and appear in
+ ;; the same menu. So we try to detect and resolve conflicts.
+ (while (and (stringp name)
+ (memq key easy-menu-avoid-duplicate-keys))
+ ;; We need to use some distinct object, ideally a symbol, ideally
+ ;; related to the `name'. Uninterned symbols do not work (they
+ ;; are apparently turned into strings and re-interned later on).
+ (setq key (intern (format "%s (%d)" (symbol-name key)
+ (length easy-menu-avoid-duplicate-keys)))))
+ (push key easy-menu-avoid-duplicate-keys))
+
+ (cons key
+ (and (not remove)
+ (cons 'menu-item
+ (cons label
+ (and name
+ (cons command prop)))))))))
(defun easy-menu-define-key (menu key item &optional before)
"Add binding in MENU for KEY => ITEM. Similar to `define-key-after'.
@@ -536,7 +568,8 @@ earlier by `easy-menu-define' or `easy-menu-create-menu'."
(setq item (symbol-value item))))
;; Item is a keymap, find the prompt string and use as item name.
(setq item (cons (keymap-prompt item) item)))
- (easy-menu-do-add-item map item before)))
+ (setq item (easy-menu-convert-item item))
+ (easy-menu-define-key map (easy-menu-intern (car item)) (cdr item) before)))
(defun easy-menu-item-present-p (map path name)
"In submenu of MAP with path PATH, return non-nil if item NAME is present.
@@ -615,7 +648,8 @@ In some cases we use that to select between the local and global maps."
(catch 'found
(if (and map (symbolp map) (not (keymapp map)))
(setq map (symbol-value map)))
- (let ((maps (if map (list map) (current-active-maps))))
+ (let ((maps (if map (if (keymapp map) (list map) map)
+ (current-active-maps))))
;; Look for PATH in each map.
(unless map (push 'menu-bar path))
(dolist (name path)