]> code.delx.au - gnu-emacs/blobdiff - lisp/emacs-lisp/easymenu.el
Merge from emacs--rel--22
[gnu-emacs] / lisp / emacs-lisp / easymenu.el
index ec8c8cd488f4e5e9c2c7a90948628d8afbd1c8f3..b802d8acd43c19d5ae1bfd229b61a73e5816dcd0 100644 (file)
@@ -1,15 +1,16 @@
-;;; easymenu.el --- support the easymenu interface for defining a menu.
+;;; easymenu.el --- support the easymenu interface for defining a menu
 
-;; Copyright (C) 1994, 1996, 1998 Free Software Foundation, Inc.
+;; Copyright (C) 1994, 1996, 1998, 1999, 2000, 2001, 2002, 2003,
+;;   2004, 2005, 2006, 2007 Free Software Foundation, Inc.
 
 ;; Keywords: emulations
-;; Author: rms
+;; Author: Richard Stallman <rms@gnu.org>
 
 ;; This file is part of GNU Emacs.
 
 ;; GNU Emacs is free software; you can redistribute it and/or modify
 ;; it under the terms of the GNU General Public License as published by
-;; the Free Software Foundation; either version 2, or (at your option)
+;; the Free Software Foundation; either version 3, or (at your option)
 ;; any later version.
 
 ;; GNU Emacs is distributed in the hope that it will be useful,
@@ -19,8 +20,8 @@
 
 ;; You should have received a copy of the GNU General Public License
 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
-;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
-;; Boston, MA 02111-1307, USA.
+;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+;; Boston, MA 02110-1301, USA.
 
 ;;; Commentary:
 
 
 ;;; Code:
 
+(defcustom easy-menu-precalculate-equivalent-keybindings t
+  "Determine when equivalent key bindings are computed for easy-menu menus.
+It can take some time to calculate the equivalent key bindings that are shown
+in a menu.  If the variable is on, then this calculation gives a (maybe
+noticeable) delay when a mode is first entered.  If the variable is off, then
+this delay will come when a menu is displayed the first time.  If you never use
+menus, turn this variable off, otherwise it is probably better to keep it on."
+  :type 'boolean
+  :group 'menu
+  :version "20.3")
+
+(defsubst easy-menu-intern (s)
+  (if (stringp s) (intern s) s))
+
+;;;###autoload
+(put 'easy-menu-define 'lisp-indent-function 'defun)
 ;;;###autoload
 (defmacro easy-menu-define (symbol maps doc menu)
   "Define a menu bar submenu in maps MAPS, according to MENU.
-The menu keymap is stored in symbol SYMBOL, both as its value
-and as its function definition.   DOC is used as the doc string for SYMBOL.
+
+If SYMBOL is non-nil, store the menu keymap in the value of SYMBOL,
+and define SYMBOL as a function to pop up the menu, with DOC as its doc string.
+If SYMBOL is nil, just store the menu keymap into MAPS.
 
 The first element of MENU must be a string.  It is the menu bar item name.
-It may be followed by the keyword argument pair
+It may be followed by the following keyword argument pairs
+
    :filter FUNCTION
-FUNCTION is a function with one argument, the menu.  It returns the actual
-menu displayed.
 
-The rest of the elements are menu items.
+FUNCTION is a function with one argument, the rest of menu items.
+It returns the remaining items of the displayed menu.
+
+   :visible INCLUDE
+
+INCLUDE is an expression; this menu is only visible if this
+expression has a non-nil value.  `:included' is an alias for `:visible'.
+
+   :active ENABLE
+
+ENABLE is an expression; the menu is enabled for selection
+whenever this expression's value is non-nil.
+
+The rest of the elements in MENU, are menu items.
 
 A menu item is usually a vector of three elements:  [NAME CALLBACK ENABLE]
 
@@ -54,7 +85,7 @@ or a list to evaluate when the item is chosen.
 ENABLE is an expression; the item is enabled for selection
 whenever this expression's value is non-nil.
 
-Alternatively, a menu item may have the form: 
+Alternatively, a menu item may have the form:
 
    [ NAME CALLBACK [ KEYWORD ARG ] ... ]
 
@@ -65,67 +96,120 @@ Where KEYWORD is one of the symbols defined below.
 KEYS is a string; a complex keyboard equivalent to this menu item.
 This is normally not needed because keyboard equivalents are usually
 computed automatically.
+KEYS is expanded with `substitute-command-keys' before it is used.
+
+   :key-sequence KEYS
+
+KEYS is nil, a string or a vector; nil or a keyboard equivalent to this
+menu item.
+This is a hint that will considerably speed up Emacs' first display of
+a menu.  Use `:key-sequence nil' when you know that this menu item has no
+keyboard equivalent.
 
    :active ENABLE
 
 ENABLE is an expression; the item is enabled for selection
 whenever this expression's value is non-nil.
 
-   :suffix NAME
+   :visible INCLUDE
+
+INCLUDE is an expression; this item is only visible if this
+expression has a non-nil value.  `:included' is an alias for `:visible'.
+
+   :suffix FORM
 
-NAME is a string; the name of an argument to CALLBACK.
+FORM is an expression that will be dynamically evaluated and whose
+value will be concatenated to the menu entry's NAME.
 
    :style STYLE
-   
+
 STYLE is a symbol describing the type of menu item.  The following are
-defined:  
+defined:
 
 toggle: A checkbox.
-        Prepend the name with '(*) ' or '( ) ' depending on if selected or not.
+        Prepend the name with `(*) ' or `( ) ' depending on if selected or not.
 radio: A radio button.
-       Prepend the name with '[X] ' or '[ ] ' depending on if selected or not.
-nil: An ordinary menu item.
+       Prepend the name with `[X] ' or `[ ] ' depending on if selected or not.
+button: Surround the name with `[' and `]'.  Use this for an item in the
+        menu bar itself.
+anything else means an ordinary menu item.
 
    :selected SELECTED
 
 SELECTED is an expression; the checkbox or radio button is selected
 whenever this expression's value is non-nil.
 
+   :help HELP
+
+HELP is a string, the help to display for the menu item.
+
 A menu item can be a string.  Then that string appears in the menu as
 unselectable text.  A string consisting solely of hyphens is displayed
 as a solid horizontal line.
 
-A menu item can be a list.  It is treated as a submenu.
-The first element should be the submenu name.  That's used as the
-menu item name in the top-level menu.  It may be followed by the :filter
-FUNCTION keyword argument pair.  The rest of the submenu list are menu items,
-as above."
+A menu item can be a list with the same format as MENU.  This is a submenu."
   `(progn
-     (defvar ,symbol nil ,doc)
+     ,(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
   ;; `easy-menu-define' in order to make byte compiled files
   ;; compatible.  Therefore everything interesting is done in this
-  ;; function. 
-  (set symbol (easy-menu-create-menu (car menu) (cdr menu)))
-  (fset symbol (` (lambda (event) (, doc) (interactive "@e")
-                   (x-popup-menu event (, symbol)))))
-  (mapcar (function (lambda (map) 
-           (define-key map (vector 'menu-bar (intern (car menu)))
-             (cons (car menu) (symbol-value symbol)))))
-         (if (keymapp maps) (list maps) maps)))
-
-(defun easy-menu-filter-return (menu)
+  ;; function.
+  (let ((keymap (easy-menu-create-menu (car menu) (cdr menu))))
+    (when symbol
+      (set symbol keymap)
+      (defalias symbol
+       `(lambda (event) ,doc (interactive "@e")
+          ;; FIXME: XEmacs uses popup-menu which calls the binding
+          ;; while x-popup-menu only returns the selection.
+          (x-popup-menu event
+                        (or (and (symbolp ,symbol)
+                                 (funcall
+                                  (or (plist-get (get ,symbol 'menu-prop)
+                                                 :filter)
+                                      'identity)
+                                  (symbol-function ,symbol)))
+                            ,symbol)))))
+    (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.
 MENU is a menu as computed by `easy-menu-define' or `easy-menu-create-menu' or
 a symbol whose value is such a menu.
 In Emacs a menu filter must return a menu (a keymap), in XEmacs a filter must
-return a menu items list (without menu name and keywords). This function
-returns the right thing in the two cases."
- (easy-menu-get-map menu nil))         ; Get past indirections.
+return a menu items list (without menu name and keywords).
+This function returns the right thing in the two cases.
+If NAME is provided, it is used for the keymap."
+ (cond
+  ((and (not (keymapp menu)) (consp menu))
+   ;; If it's a cons but not a keymap, then it can't be right
+   ;; unless it's an XEmacs menu.
+   (setq menu (easy-menu-create-menu (or name "") menu)))
+  ((vectorp menu)
+   ;; It's just a menu entry.
+   (setq menu (cdr (easy-menu-convert-item menu)))))
+ menu)
 
 ;;;###autoload
 (defun easy-menu-create-menu (menu-name menu-items)
@@ -133,314 +217,444 @@ returns the right thing in the two cases."
 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))
-       keyword filter have-buttons)
+       prop keyword arg label enable filter visible help)
     ;; Look for keywords.
-    (while (and menu-items (cdr menu-items)
-               (symbolp (setq keyword (car menu-items)))
-               (= ?: (aref (symbol-name keyword) 0)))
-      (if (eq keyword ':filter) (setq filter (cadr menu-items)))
-      (setq menu-items (cddr menu-items)))
-    (while menu-items
-      (setq have-buttons
-           (easy-menu-do-add-item menu (car menu-items) have-buttons))
-      (setq menu-items (cdr menu-items)))
-    (when filter
-      (setq menu (easy-menu-make-symbol menu))
-      (put menu 'menu-enable
-          `(easy-menu-filter (quote ,menu) (quote ,filter))))
-    menu))
-
-
-;; Button prefixes.
+    (while (and menu-items
+               (cdr menu-items)
+               (keywordp (setq keyword (car menu-items))))
+      (setq arg (cadr menu-items))
+      (setq menu-items (cddr menu-items))
+      (cond
+       ((eq keyword :filter)
+       (setq filter `(lambda (menu)
+                       (easy-menu-filter-return (,arg menu) ,menu-name))))
+       ((eq keyword :active) (setq enable (or arg ''nil)))
+       ((eq keyword :label) (setq label arg))
+       ((eq keyword :help) (setq help arg))
+       ((or (eq keyword :included) (eq keyword :visible))
+       (setq visible (or arg ''nil)))))
+    (if (equal visible ''nil)
+       nil                             ; Invisible menu entry, return nil.
+      (if (and visible (not (easy-menu-always-true-p visible)))
+         (setq prop (cons :visible (cons visible prop))))
+      (if (and enable (not (easy-menu-always-true-p enable)))
+         (setq prop (cons :enable (cons enable prop))))
+      (if filter (setq prop (cons :filter (cons filter prop))))
+      (if help (setq prop (cons :help (cons help prop))))
+      (if label (setq prop (cons nil (cons label prop))))
+      (if filter
+         ;; The filter expects the menu in its XEmacs form and the pre-filter
+         ;; form will only be passed to the filter anyway, so we'd better
+         ;; not convert it at all (it will be converted on the fly by
+         ;; easy-menu-filter-return).
+         (setq menu menu-items)
+       (setq menu (append menu (mapcar 'easy-menu-convert-item menu-items))))
+      (when prop
+       (setq menu (easy-menu-make-symbol menu 'noexp))
+       (put menu 'menu-prop prop))
+      menu)))
+
+
+;; Known button types.
 (defvar easy-menu-button-prefix
-  '((radio ?* . "( ) ") (toggle ?X . "[ ] ")))
-
-(defun easy-menu-do-add-item (menu item have-buttons &optional before top)
-  ;; Parse an item description and add the item to a keymap.  This is
-  ;; the function that is used for item definition by the other easy-menu
-  ;; functions.
-  ;; MENU is a sparse keymap i.e. a list starting with the symbol `keymap'.
-  ;; ITEM defines an item as in `easy-menu-define'.
-  ;; HAVE-BUTTONS is a string or nil.  If not nil, use as item prefix for
-  ;; items that are not toggle or radio buttons to compensate for the
-  ;; button prefix.
-  ;; Optional argument BEFORE is nil or a symbol used as a key in MENU. If
-  ;; BEFORE is not nil put item before BEFORE in MENU, otherwise if item is
-  ;; already present in MENU, just change it, otherwise put it last in MENU.
-  ;; If optional TOP is true, this is an item in the menu bar itself so
-  ;; don't use prefix.  In this case HAVE-BUTTONS will be nil.
-  (let (command name item-string is-button done inserted)
+  '((radio . :radio) (toggle . :toggle)))
+
+(defvar easy-menu-converted-items-table (make-hash-table :test 'equal))
+
+(defun easy-menu-convert-item (item)
+  "Memoize the value returned by `easy-menu-convert-item-1' called on ITEM.
+This makes key-shortcut-caching work a *lot* better when this
+conversion is done from within a filter.
+This also helps when the NAME of the entry is recreated each time:
+since the menu is built and traversed separately, the lookup
+would always fail because the key is `equal' but not `eq'."
+  (or (gethash item easy-menu-converted-items-table)
+      (puthash item (easy-menu-convert-item-1 item)
+              easy-menu-converted-items-table)))
+
+(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)
     (cond
-     ((stringp item)
-      (setq item-string
-           (if (string-match   ; If an XEmacs separator
-                "^\\(-+\\|\
---:\\(\\(no\\|\\(sing\\|doub\\)le\\(Dashed\\)?\\)Line\\|\
-shadow\\(Double\\)?Etched\\(In\\|Out\\)\\(Dash\\)?\\)\\)$"
-                item) ""               ; use a single line separator.
-             (concat have-buttons item))))
-     ((consp item)
-      (setq name (setq item-string (car item)))
-      (setq command (if (keymapp (setq item (cdr item))) item
-                     (easy-menu-create-menu name item))))
-     ((vectorp item)
-      (setq name (setq item-string (aref item 0)))
-      (setq command (easy-menu-make-symbol (aref item 1) t))
-      (let ((active (aref item 2))
-           (count 2)
-           style selected)
-       (if (and (symbolp active) (= ?: (aref (symbol-name active) 0)))
-           (let ((count 2) keyword arg suffix keys)
+     ((stringp item)                   ; An item or separator.
+      (setq label item))
+     ((consp item)                     ; A sub-menu
+      (setq label (setq name (car item)))
+      (setq command (cdr item))
+      (if (not (keymapp command))
+         (setq command (easy-menu-create-menu name command)))
+      (if (null command)
+         ;; Invisible menu item. Don't insert into keymap.
+         (setq remove t)
+       (when (and (symbolp command) (setq prop (get command 'menu-prop)))
+         (when (null (car prop))
+           (setq label (cadr prop))
+           (setq prop (cddr prop)))
+         (setq command (symbol-function command)))))
+     ((vectorp item)                   ; An item.
+      (let* ((ilen (length item))
+            (active (if (> ilen 2) (or (aref item 2) ''nil) t))
+            (no-name (not (symbolp (setq command (aref item 1)))))
+            cache cache-specified)
+       (setq label (setq name (aref item 0)))
+       (if no-name (setq command (easy-menu-make-symbol command)))
+       (if (keywordp active)
+           (let ((count 2)
+                 keyword arg suffix visible style selected keys)
              (setq active nil)
-             (while (> (length item) count)
+             (while (> ilen count)
                (setq keyword (aref item count))
                (setq arg (aref item (1+ count)))
                (setq count (+ 2 count))
                (cond
-                ((eq keyword ':keys) (setq keys arg))
-                ((eq keyword ':active) (setq active arg))
-                ((eq keyword ':suffix) (setq suffix (concat " " arg)))
-                ((eq keyword ':style) (setq style arg))
-                ((eq keyword ':selected) (setq selected arg))))
-             (if keys (setq suffix (concat suffix "  (" keys ")")))
-             (if suffix (setq item-string (concat item-string " " suffix)))
-             (when (and selected
-                        (setq style (assq style easy-menu-button-prefix)))
-               ;; Simulate checkboxes and radio buttons.
-               (setq item-string (concat (cddr style) item-string))
-               (put command 'menu-enable
-                    `(easy-menu-update-button ,item-string
-                                              ,(cadr style)
-                                              ,selected
-                                              ,(or active t)))
-               (setq is-button t)
-               (setq active nil)       ; Already taken care of active.
-               (when (not (or have-buttons top))
-                 (setq have-buttons "    ")
-                 ;; Add prefix to menu items defined so far.
-                 (easy-menu-change-prefix menu t)))))
-       (if active (put command 'menu-enable active))))
-     (t "Illegal menu item in easy menu."))
-    (when name
-      (and (not is-button) have-buttons
-          (setq item-string (concat have-buttons item-string)))
-      (setq name (intern name)))
-    (setq item (cons item-string command))
-    (if before (setq before (intern before)))
-    ;; The following loop is simlar to `define-key-after'. It
-    ;; inserts (name . item) in keymap menu.
-    ;; If name is not nil then delete any duplications.
-    ;; If before is not nil, insert before before. Otherwise
-    ;; if name is not nil and it is found in menu, insert there, else
-    ;; insert at end.
+                ((or (eq keyword :included) (eq keyword :visible))
+                 (setq visible (or arg ''nil)))
+                ((eq keyword :key-sequence)
+                 (setq cache arg cache-specified t))
+                ((eq keyword :keys) (setq keys arg no-name nil))
+                ((eq keyword :label) (setq label arg))
+                ((eq keyword :active) (setq active (or arg ''nil)))
+                ((eq keyword :help) (setq prop (cons :help (cons arg prop))))
+                ((eq keyword :suffix) (setq suffix arg))
+                ((eq keyword :style) (setq style arg))
+                ((eq keyword :selected) (setq selected (or arg ''nil)))))
+             (if suffix
+                 (setq label
+                       (if (stringp suffix)
+                           (if (stringp label) (concat label " " suffix)
+                             (list 'concat label (concat " " suffix)))
+                         (if (stringp label)
+                             (list 'concat (concat label " ") suffix)
+                           (list 'concat label " " suffix)))))
+             (cond
+              ((eq style 'button)
+               (setq label (if (stringp label) (concat "[" label "]")
+                             (list 'concat "[" label "]"))))
+              ((and selected
+                    (setq style (assq style easy-menu-button-prefix)))
+               (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 (and visible (not (easy-menu-always-true-p visible)))
+                 (if (equal visible ''nil)
+                     ;; Invisible menu item. Don't insert into keymap.
+                     (setq remove t)
+                   (setq prop (cons :visible (cons visible prop)))))))
+       (if (and active (not (easy-menu-always-true-p active)))
+           (setq prop (cons :enable (cons active prop))))
+       (if (and (or no-name cache-specified)
+                (or (null cache) (stringp cache) (vectorp cache)))
+           (setq prop (cons :key-sequence (cons cache prop))))))
+     (t (error "Invalid menu item in easymenu")))
+    ;; `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))))))))
+
+(defun easy-menu-define-key (menu key item &optional before)
+  "Add binding in MENU for KEY => ITEM.  Similar to `define-key-after'.
+If KEY is not nil then delete any duplications.
+If ITEM is nil, then delete the definition of KEY.
+
+Optional argument BEFORE is nil or a key in MENU.  If BEFORE is not nil,
+put binding before the item in MENU named BEFORE; otherwise,
+if a binding for KEY is already present in MENU, just change it;
+otherwise put the new binding last in MENU.
+BEFORE can be either a string (menu item name) or a symbol
+\(the fake function key for the menu item).
+KEY does not have to be a symbol, and comparison is done with equal."
+  (if (symbolp menu) (setq menu (indirect-function menu)))
+  (let ((inserted (null item))         ; Fake already inserted.
+       tail done)
     (while (not done)
       (cond
        ((or (setq done (or (null (cdr menu)) (keymapp (cdr menu))))
-           (and before (eq (car-safe (cadr menu)) before)))
-       ;; If name is nil, stop here, otherwise keep going past the
+           (and before (easy-menu-name-match before (cadr menu))))
+       ;; If key is nil, stop here, otherwise keep going past the
        ;; inserted element so we can delete any duplications that come
        ;; later.
-       (if (null name) (setq done t))
+       (if (null key) (setq done t))
        (unless inserted                ; Don't insert more than once.
-         (setcdr menu (cons (cons name item) (cdr menu)))
+         (setcdr menu (cons (cons key item) (cdr menu)))
+         (setq inserted t)
+         (setq menu (cdr menu)))
+       (setq menu (cdr menu)))
+       ((and key (equal (car-safe (cadr menu)) key))
+       (if (or inserted                ; Already inserted or
+               (and before             ;  wanted elsewhere and
+                    (setq tail (cddr menu)) ; not last item and not
+                    (not (keymapp tail))
+                    (not (easy-menu-name-match
+                          before (car tail))))) ; in position
+           (setcdr menu (cddr menu))   ; Remove item.
+         (setcdr (cadr menu) item)     ; Change item.
          (setq inserted t)
          (setq menu (cdr menu))))
-       ((and name (eq (car-safe (cadr menu)) name))
-       (if (and before                 ; Wanted elsewere and
-                (not (setq done        ; not the last in this keymap.
-                           (or (null (cddr menu)) (keymapp (cddr menu))))))
-             (setcdr menu (cddr menu))
-         (setcdr (cadr menu) item) ; Change item.
-         (setq inserted t))))
-      (setq menu (cdr menu)))
-    have-buttons))
+       (t (setq menu (cdr menu)))))))
+
+(defun easy-menu-name-match (name item)
+  "Return t if NAME is the name of menu item ITEM.
+NAME can be either a string, or a symbol.
+ITEM should be a keymap binding of the form (KEY . MENU-ITEM)."
+  (if (consp item)
+      (if (symbolp name)
+         (eq (car-safe item) name)
+       (if (stringp name)
+           ;; Match against the text that is displayed to the user.
+           (or (condition-case nil (member-ignore-case name item)
+                 (error nil))          ;`item' might not be a proper list.
+               ;; Also check the string version of the symbol name,
+               ;; for backwards compatibility.
+               (eq (car-safe item) (intern name)))))))
+
+(defun easy-menu-always-true-p (x)
+  "Return true if form X never evaluates to nil."
+  (if (consp x) (and (eq (car x) 'quote) (cadr x))
+    (or (eq x t) (not (symbolp x)))))
 
 (defvar easy-menu-item-count 0)
 
-(defun easy-menu-make-symbol (callback &optional call)
-  ;; Return a unique symbol with CALLBACK as function value.
-  ;; If CALL is false then this is a keymap, not a function.
-  ;; Else if CALLBACK is a symbol, avoid the indirection when looking for
-  ;; key-bindings in menu.
-  ;; Else make a lambda expression of CALLBACK.
+(defun easy-menu-make-symbol (callback &optional noexp)
+  "Return a unique symbol with CALLBACK as function value.
+When non-nil, NOEXP indicates that CALLBACK cannot be an expression
+\(i.e. does not need to be turned into a function)."
   (let ((command
         (make-symbol (format "menu-function-%d" easy-menu-item-count))))
     (setq easy-menu-item-count (1+ easy-menu-item-count))
     (fset command
-         (cond
-          ((not call) callback)
-          ((symbolp callback)
-           ;; Try find key-bindings for callback instead of for command
-           (put command 'menu-alias t) ; when displaying menu.
-           callback)
-          (t `(lambda () (interactive) ,callback))))
+         (if (or (keymapp callback) (functionp callback) noexp) callback
+           `(lambda () (interactive) ,callback)))
     command))
 
-(defun easy-menu-filter (name filter)
-  "Used as menu-enable property to filter menus.
-A call to this function is used as the menu-enable property for a menu with
-a filter function.
-NAME is a symbol with a keymap as function value.  Call the function FILTER
-with this keymap as argument.  FILTER must return a keymap which becomes the
-new function value for NAME.  Use `easy-menu-filter-return' to return the
-correct value in a way portable to XEmacs. If the new keymap is `eq' the old,
-then the menu is not updated."
-  (let* ((old (symbol-function name))
-        (new (funcall filter old)))
-    (or (eq old new)                   ; No change
-       (and (fset name new)
-            ;; Make sure the menu gets updated by returning a
-            ;; different value than last time to cheat the cache. 
-            (random)))))
-
-(defun easy-menu-update-button (item ch selected active)
-  "Used as menu-enable property to update buttons.
-A call to this function is used as the menu-enable property for buttons.
-ITEM is the item-string into which CH or ` ' is inserted depending on if
-SELECTED is true or not.  The menu entry in enabled iff ACTIVE is true."
-  (let ((new (if selected ch ? ))
-       (old (aref item 1)))
-    (if (eq new old)
-       ;; No change, just use the active value.
-       active
-      ;; It has changed.  Update the entry.
-      (aset item 1 new)
-      ;; If the entry is active, make sure the menu gets updated by
-      ;; returning a different value than last time to cheat the cache. 
-      (and active
-          (random)))))
-
-(defun easy-menu-change (path name items &optional before)
+;;;###autoload
+(defun easy-menu-change (path name items &optional before map)
   "Change menu found at PATH as item NAME to contain ITEMS.
-PATH is a list of strings for locating the menu containing NAME in the
-menu bar.  ITEMS is a list of menu items, as in `easy-menu-define'.
-These items entirely replace the previous items in that map.
-If NAME is not present in the menu located by PATH, then add item NAME to
-that menu. If the optional argument BEFORE is present add NAME in menu
-just before BEFORE, otherwise add at end of menu.
+PATH is a list of strings for locating the menu that
+should contain a submenu named NAME.
+ITEMS is a list of menu items, as in `easy-menu-define'.
+These items entirely replace the previous items in that submenu.
+
+If MAP is specified, it should normally be a keymap; nil stands for the local
+menu-bar keymap.  It can also be a symbol, which has earlier been used as the
+first argument in a call to `easy-menu-define', or the value of such a symbol.
 
-Either call this from `menu-bar-update-hook' or use a menu filter,
-to implement dynamic menus."
-  (easy-menu-add-item nil path (cons name items) before))
+If the menu located by PATH has no submenu named NAME, add one.
+If the optional argument BEFORE is present, add it just before
+the submenu named BEFORE, otherwise add it at the end of the menu.
+
+To implement dynamic menus, either call this from
+`menu-bar-update-hook' or use a menu filter."
+  (easy-menu-add-item map path (easy-menu-create-menu name items) before))
 
 ;; XEmacs needs the following two functions to add and remove menus.
 ;; In Emacs this is done automatically when switching keymaps, so
-;; here these functions are noops.
-(defun easy-menu-remove (menu))
-
-(defun easy-menu-add (menu &optional map))
-
-(defun easy-menu-add-item (menu path item &optional before)
-  "At the end of the submenu of MENU with path PATH add ITEM.
-If ITEM is already present in this submenu, then this item will be changed.
-otherwise ITEM will be added at the end of the submenu, unless the optional
-argument BEFORE is present, in which case ITEM will instead be added
-before the item named BEFORE.
-MENU is either a symbol, which have earlier been used as the first
-argument in a call to `easy-menu-define', or the value of such a symbol
-i.e. a menu, or nil which stands for the menu-bar itself.
+;; here easy-menu-remove is a noop and easy-menu-add only precalculates
+;; equivalent keybindings (if easy-menu-precalculate-equivalent-keybindings
+;; is on).
+(defalias 'easy-menu-remove 'ignore
+  "Remove MENU from the current menu bar.
+Contrary to XEmacs, this is a nop on Emacs since menus are automatically
+\(de)activated when the corresponding keymap is (de)activated.
+
+\(fn MENU)")
+
+(defun easy-menu-add (menu &optional map)
+  "Add the menu to the menubar.
+On Emacs, menus are already automatically activated when the
+corresponding keymap is activated.  On XEmacs this is needed to
+actually add the menu to the current menubar.
+
+This also precalculates equivalent key bindings when
+`easy-menu-precalculate-equivalent-keybindings' is on.
+
+You should call this once the menu and keybindings are set up
+completely and menu filter functions can be expected to work."
+  (when easy-menu-precalculate-equivalent-keybindings
+    (if (and (symbolp menu) (not (keymapp menu)) (boundp menu))
+       (setq menu (symbol-value menu)))
+    (and (keymapp menu) (fboundp 'x-popup-menu)
+        (x-popup-menu nil menu))
+    ))
+
+(defun add-submenu (menu-path submenu &optional before in-menu)
+  "Add submenu SUBMENU in the menu at MENU-PATH.
+If BEFORE is non-nil, add before the item named BEFORE.
+If IN-MENU is non-nil, follow MENU-PATH in IN-MENU.
+This is a compatibility function; use `easy-menu-add-item'."
+  (easy-menu-add-item (or in-menu (current-global-map))
+                     (cons "menu-bar" menu-path)
+                     submenu before))
+
+(defun easy-menu-add-item (map path item &optional before)
+  "To the submenu of MAP with path PATH, add ITEM.
+
+If an item with the same name is already present in this submenu,
+then ITEM replaces it.  Otherwise, ITEM is added to this submenu.
+In the latter case, ITEM is normally added at the end of the submenu.
+However, if BEFORE is a string and there is an item in the submenu
+with that name, then ITEM is added before that item.
+
+MAP should normally be a keymap; nil stands for the local menu-bar keymap.
+It can also be a symbol, which has earlier been used as the first
+argument in a call to `easy-menu-define', or the value of such a symbol.
+
 PATH is a list of strings for locating the submenu where ITEM is to be
-added.  If PATH is nil, MENU itself is used.  Otherwise, the first
-element should be the name of a submenu directly under MENU.  This
+added.  If PATH is nil, MAP itself is used.  Otherwise, the first
+element should be the name of a submenu directly under MAP.  This
 submenu is then traversed recursively with the remaining elements of PATH.
-ITEM is either defined as in `easy-menu-define' or a menu defined earlier
-by `easy-menu-define' or `easy-menu-create-menu'."
-  (let ((top (not (or menu path))))
-    (setq menu (easy-menu-get-map menu path))
+
+ITEM is either defined as in `easy-menu-define' or a non-nil value returned
+by `easy-menu-item-present-p' or `easy-menu-remove-item' or a menu defined
+earlier by `easy-menu-define' or `easy-menu-create-menu'."
+  (setq map (easy-menu-get-map map path
+                              (and (null map) (null path)
+                                   (stringp (car-safe item))
+                                   (car item))))
+  (if (and (consp item) (consp (cdr item)) (eq (cadr item) 'menu-item))
+      ;; This is a value returned by `easy-menu-item-present-p' or
+      ;; `easy-menu-remove-item'.
+      (easy-menu-define-key map (easy-menu-intern (car item))
+                           (cdr item) before)
     (if (or (keymapp item)
-           (and (symbolp item) (keymapp (symbol-value item))))
+           (and (symbolp item) (keymapp (symbol-value item))
+                (setq item (symbol-value item))))
        ;; Item is a keymap, find the prompt string and use as item name.
-       (let ((tail (easy-menu-get-map item nil)) name)
-         (if (not (keymapp item)) (setq item tail))
-         (while (and (null name) (consp (setq tail (cdr tail)))
-                     (not (keymapp tail)))
-           (if (stringp (car tail)) (setq name (car tail)) ; Got a name.
-             (setq tail (cdr tail))))
-         (setq item (cons name item))))
-    (easy-menu-do-add-item menu item
-                          (and (not top) (easy-menu-have-button menu)
-                               "    ")
-                          before top)))
-
-(defun easy-menu-item-present-p (menu path name)
-  "In submenu of MENU with path PATH, return true iff item NAME is present.
-MENU and PATH are defined as in `easy-menu-add-item'.
+       (setq item (cons (keymap-prompt item) item)))
+    (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.
+MAP and PATH are defined as in `easy-menu-add-item'.
 NAME should be a string, the name of the element to be looked for."
-  (lookup-key (easy-menu-get-map menu path) (vector (intern name))))
+  (easy-menu-return-item (easy-menu-get-map map path) name))
 
-(defun easy-menu-remove-item (menu path name)
-  "From submenu of MENU with path PATH remove item NAME.
-MENU and PATH are defined as in `easy-menu-add-item'.
+(defun easy-menu-remove-item (map path name)
+  "From submenu of MAP with path PATH remove item NAME.
+MAP and PATH are defined as in `easy-menu-add-item'.
 NAME should be a string, the name of the element to be removed."
-  (let ((item (vector (intern name)))
-       (top (not (or menu path)))
-       tmp)
-    (setq menu (easy-menu-get-map menu path))
-    (when (setq tmp (lookup-key menu item))
-      (define-key menu item nil)
-      (and (not top)
-          (easy-menu-is-button tmp)    ; Removed item was a button and
-          (not (easy-menu-have-button menu)) ; no buttons left then
-          ;; remove prefix from items in menu
-          (easy-menu-change-prefix menu nil)))))
-
-(defun easy-menu-get-map (menu path)
-  ;; Return a sparse keymap in which to add or remove an item.
-  ;; MENU and PATH are as defined in `easy-menu-remove-item'.
-  (if (null menu)
-      (setq menu (key-binding (vconcat '(menu-bar) (mapcar 'intern path))))
-    (if (and (symbolp menu) (not (keymapp menu)))
-       (setq menu (symbol-value menu)))
-    (if path (setq menu (lookup-key menu (vconcat (mapcar 'intern path))))))
-  (while (and (symbolp menu) (keymapp menu))
-    (setq menu (symbol-function menu)))
-  (or (keymapp menu) (error "Malformed menu in easy-menu: (%s)" menu))
-  menu)
-
-(defun easy-menu-is-button (val)
-  ;; VAL is a real menu binding.  Return true iff it is a toggle or
-  ;; radio button.
-  (and (symbolp val)
-       (consp (setq val (get val 'menu-enable)))
-       (eq (car val) 'easy-menu-update-button)))
-
-(defun easy-menu-have-button (map)
-  ;; MAP is a sparse keymap.  Return true iff there is any toggle or radio
-  ;; button in MAP.
-  (let ((have nil) tmp)
-    (while (and (consp map) (not have))
-      (and (consp (setq tmp (car map)))
-          (consp (setq tmp (cdr tmp)))
-          (stringp (car tmp))
-          (setq have (easy-menu-is-button (easy-menu-real-binding tmp))))
-      (setq map (cdr map)))
-    have))
-
-(defun easy-menu-real-binding (val)
-  ;; Val is a menu keymap binding.  Skip item string.
-  ;; Also skip a possible help string and/or key-binding cache.
-  (if (and (consp (setq val (cdr val))) (stringp (car val)))
-      (setq val (cdr val)))            ; Skip help string.
-  (if (and (consp val) (consp (car val))
-          (or (null (caar val)) (vectorp (caar val))))
-      (setq val (cdr val)))            ; Skip key-binding cache.
-  val)
-
-(defun easy-menu-change-prefix (map add)
-  ;; MAP is a sparse keymap.
-  ;; If ADD is true add a button compensating prefix to each menu item in MAP.
-  ;; Else remove prefix instead.
-  (let (tmp val)
-    (while (consp map)
-      (when (and (consp (setq tmp (car map)))
-                (consp (setq tmp (cdr tmp)))
-                (stringp (car tmp)))
-       (cond
-        (add (setcar tmp (concat "    " (car tmp))))
-        ((string-match "$    " (car tmp))
-         (setcar tmp (substring (car tmp) (match-end 0))))))
-      (setq map (cdr map)))))
+  (setq map (easy-menu-get-map map path))
+  (let ((ret (easy-menu-return-item map name)))
+    (if ret (easy-menu-define-key map (easy-menu-intern name) nil))
+    ret))
+
+(defun easy-menu-return-item (menu name)
+  "In menu MENU try to look for menu item with name NAME.
+If a menu item is found, return (NAME . item), otherwise return nil.
+If item is an old format item, a new format item is returned."
+  ;; The call to `lookup-key' also calls the C function `get_keyelt' which
+  ;; looks inside a menu-item to only return the actual command.  This is
+  ;; not what we want here.  We should either add an arg to lookup-key to be
+  ;; able to turn off this "feature", or else we could use map-keymap here.
+  ;; In the mean time, I just use `assq' which is an OK approximation since
+  ;; menus are rarely built from vectors or char-tables.
+  (let ((item (or (cdr (assq name menu))
+                  (lookup-key menu (vector (easy-menu-intern name)))))
+       ret enable cache label)
+    (cond
+     ((stringp (car-safe item))
+      ;; This is the old menu format. Convert it to new format.
+      (setq label (car item))
+      (when (stringp (car (setq item (cdr item)))) ; Got help string
+       (setq ret (list :help (car item)))
+       (setq item (cdr item)))
+      (when (and (consp item) (consp (car item))
+                (or (null (caar item)) (numberp (caar item))))
+       (setq cache (car item))         ; Got cache
+       (setq item (cdr item)))
+      (and (symbolp item) (setq enable (get item 'menu-enable))        ; Got enable
+          (setq ret (cons :enable (cons enable ret))))
+      (if cache (setq ret (cons cache ret)))
+      (cons name (cons 'menu-enable (cons label (cons item ret)))))
+     (item ; (or (symbolp item) (keymapp item) (eq (car-safe item) 'menu-item))
+      (cons name item))                        ; Keymap or new menu format
+     )))
+
+(defun easy-menu-lookup-name (map name)
+  "Lookup menu item NAME in keymap MAP.
+Like `lookup-key' except that NAME is not an array but just a single key
+and that NAME can be a string representing the menu item's name."
+  (or (lookup-key map (vector (easy-menu-intern name)))
+      (when (stringp name)
+       ;; `lookup-key' failed and we have a menu item name: look at the
+       ;; actual menu entries's names.
+       (catch 'found
+         (map-keymap (lambda (key item)
+                       (if (condition-case nil (member name item)
+                             (error nil))
+                           ;; Found it!!  Look for it again with
+                           ;; `lookup-key' so as to handle inheritance and
+                           ;; to extract the actual command/keymap bound to
+                           ;; `name' from the item (via get_keyelt).
+                           (throw 'found (lookup-key map (vector key)))))
+                     map)))))
+
+(defun easy-menu-get-map (map path &optional to-modify)
+  "Return a sparse keymap in which to add or remove an item.
+MAP and PATH are as defined in `easy-menu-add-item'.
+
+TO-MODIFY, if non-nil, is the name of the item the caller
+wants to modify in the map that we return.
+In some cases we use that to select between the local and global maps."
+  (setq map
+       (catch 'found
+         (if (and map (symbolp map) (not (keymapp map)))
+             (setq map (symbol-value map)))
+         (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)
+             (setq maps
+                   (delq nil (mapcar (lambda (map)
+                                       (setq map (easy-menu-lookup-name
+                                                  map name))
+                                       (and (keymapp map) map))
+                                     maps))))
+
+           ;; Prefer a map that already contains the to-be-modified entry.
+           (when to-modify
+             (dolist (map maps)
+               (when (easy-menu-lookup-name map to-modify)
+                 (throw 'found map))))
+           ;; Use the first valid map.
+           (when maps (throw 'found (car maps)))
+
+           ;; Otherwise, make one up.
+           ;; Hardcoding current-local-map is lame, but it's difficult
+           ;; to know what the caller intended for us to do ;-(
+           (let* ((name (if path (format "%s" (car (reverse path)))))
+                  (newmap (make-sparse-keymap name)))
+             (define-key (or map (current-local-map))
+               (apply 'vector (mapcar 'easy-menu-intern path))
+               (if name (cons name newmap) newmap))
+             newmap))))
+  (or (keymapp map) (error "Malformed menu in easy-menu: (%s)" map))
+  map)
 
 (provide 'easymenu)
 
+;; arch-tag: 2a04020d-90d2-476d-a7c6-71e072007a4a
 ;;; easymenu.el ends here