(menu_item_enabled_p): Fix declaration.
[bpt/emacs.git] / src / xmenu.c
index 3b48ad9..ec0695a 100644 (file)
@@ -29,17 +29,18 @@ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */
 
 /* Rewritten for clarity and GC protection by rms in Feb 94.  */
 
-#include <stdio.h>
-
 /* On 4.3 this loses if it comes after xterm.h.  */
 #include <signal.h>
 #include <config.h>
+
+#include <stdio.h>
 #include "lisp.h"
 #include "termhooks.h"
 #include "frame.h"
 #include "window.h"
 #include "keyboard.h"
 #include "blockinput.h"
+#include "puresize.h"
 
 /* This may include sys/types.h, and that somehow loses
    if this is not done before the other system files.  */
@@ -64,6 +65,7 @@ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */
 #include <X11/IntrinsicP.h>
 #include <X11/CoreP.h>
 #include <X11/StringDefs.h>
+#include <X11/Shell.h>
 #include <X11/Xaw/Paned.h>
 #include "../lwlib/lwlib.h"
 #include "../lwlib/xlwmenuP.h"
@@ -298,6 +300,7 @@ menu_item_equiv_key (item_string, item1, descrip_ptr)
   Lisp_Object savedkey, descrip;
   Lisp_Object def1;
   int changed = 0;
+  struct gcpro gcpro1, gcpro2, gcpro3, gcpro4;
 
   /* If a help string follows the item string, skip it.  */
   if (CONSP (XCONS (item1)->cdr)
@@ -318,12 +321,19 @@ menu_item_equiv_key (item_string, item1, descrip_ptr)
       descrip = XCONS (cachelist)->cdr;
     }
 
+  GCPRO4 (def, def1, savedkey, descrip);
+
   /* Is it still valid?  */
   def1 = Qnil;
   if (!NILP (savedkey))
     def1 = Fkey_binding (savedkey, Qnil);
   /* If not, update it.  */
   if (! EQ (def1, def)
+      /* If the command is an alias for another
+         (such as easymenu.el and lmenu.el set it up),
+         check if the original command matches the cached command. */
+      && !(SYMBOLP (def) && SYMBOLP (XSYMBOL (def)->function)
+           && EQ (def1, XSYMBOL (def)->function))
       /* If something had no key binding before, don't recheck it--
         doing that takes too much time and makes menus too slow.  */
       && !(!NILP (cachelist) && NILP (savedkey)))
@@ -331,6 +341,13 @@ menu_item_equiv_key (item_string, item1, descrip_ptr)
       changed = 1;
       descrip = Qnil;
       savedkey = Fwhere_is_internal (def, Qnil, Qt, Qnil);
+      /* If the command is an alias for another
+        (such as easymenu.el and lmenu.el set it up),
+        see if the original command name has equivalent keys.  */
+      if (SYMBOLP (def) && SYMBOLP (XSYMBOL (def)->function))
+       savedkey = Fwhere_is_internal (XSYMBOL (def)->function,
+                                      Qnil, Qt, Qnil);
+
       if (VECTORP (savedkey)
          && EQ (XVECTOR (savedkey)->contents[0], Qmenu_bar))
        savedkey = Qnil;
@@ -344,13 +361,17 @@ menu_item_equiv_key (item_string, item1, descrip_ptr)
 
   /* Cache the data we just got in a sublist of the menu binding.  */
   if (NILP (cachelist))
-    XCONS (item1)->cdr = Fcons (Fcons (savedkey, descrip), def);
+    {
+      CHECK_IMPURE (item1);
+      XCONS (item1)->cdr = Fcons (Fcons (savedkey, descrip), def);
+    }
   else if (changed)
     {
       XCONS (cachelist)->car = savedkey;
       XCONS (cachelist)->cdr = descrip;
     }
 
+  UNGCPRO;
   *descrip_ptr = descrip;
   return def;
 }
@@ -371,6 +392,7 @@ menu_item_enabled_p_1 (arg)
 static Lisp_Object
 menu_item_enabled_p (def, notreal)
      Lisp_Object def;
+     int notreal;
 {
   Lisp_Object enabled, tem;
 
@@ -457,14 +479,16 @@ single_keymap_panes (keymap, pane_name, prefix, notreal)
                  Lisp_Object descrip;
                  Lisp_Object tem, enabled;
 
-                 def = menu_item_equiv_key (item_string, item1, &descrip);
-
-                 /* GCPRO because we will call eval.
+                 /* GCPRO because ...enabled_p will call eval
+                    and ..._equiv_key may autoload something.
                     Protecting KEYMAP preserves everything we use;
                     aside from that, must protect whatever might be
                     a string.  Since there's no GCPRO5, we refetch
                     item_string instead of protecting it.  */
+                 descrip = def = Qnil;
                  GCPRO4 (keymap, pending_maps, def, descrip);
+
+                 def = menu_item_equiv_key (item_string, item1, &descrip);
                  enabled = menu_item_enabled_p (def, notreal);
 
                  UNGCPRO;
@@ -478,7 +502,9 @@ single_keymap_panes (keymap, pane_name, prefix, notreal)
                  else
                    {
                      Lisp_Object submap;
+                     GCPRO4 (keymap, pending_maps, descrip, item_string);
                      submap = get_keymap_1 (def, 0, 1);
+                     UNGCPRO;
 #ifndef USE_X_TOOLKIT
                      /* Indicate visually that this is a submenu.  */
                      if (!NILP (submap))
@@ -523,15 +549,18 @@ single_keymap_panes (keymap, pane_name, prefix, notreal)
                      Lisp_Object descrip;
                      Lisp_Object tem, enabled;
 
-                     def = menu_item_equiv_key (item_string, item1, &descrip);
-
-                     /* GCPRO because we will call eval.
+                     /* GCPRO because ...enabled_p will call eval
+                        and ..._equiv_key may autoload something.
                         Protecting KEYMAP preserves everything we use;
                         aside from that, must protect whatever might be
                         a string.  Since there's no GCPRO5, we refetch
                         item_string instead of protecting it.  */
                      GCPRO4 (keymap, pending_maps, def, descrip);
+                     descrip = def = Qnil;
+
+                     def = menu_item_equiv_key (item_string, item1, &descrip);
                      enabled = menu_item_enabled_p (def, notreal);
+
                      UNGCPRO;
 
                      item_string = XCONS (item1)->car;
@@ -543,7 +572,9 @@ single_keymap_panes (keymap, pane_name, prefix, notreal)
                      else
                        {
                          Lisp_Object submap;
+                         GCPRO4 (keymap, pending_maps, descrip, item_string);
                          submap = get_keymap_1 (def, 0, 1);
+                         UNGCPRO;
 #ifndef USE_X_TOOLKIT
                          if (!NILP (submap))
                            item_string = concat2 (item_string,
@@ -1039,7 +1070,11 @@ map_event_to_object (event, f)
   return Qnil;
 }
 
+#ifdef __STDC__
+static Lisp_Object *volatile menu_item_selection;
+#else
 static Lisp_Object *menu_item_selection;
+#endif
 
 static void
 popup_selection_callback (widget, id, client_data)
@@ -1269,6 +1304,7 @@ other_menu_bar_item_p (f, x, y)
      int x, y;
 {
   return (y >= 0
+         && f->display.x->menubar_widget != 0
          && y < f->display.x->menubar_widget->core.height
          && x >= 0
          && x < last_menu_bar_item_end
@@ -1341,6 +1377,9 @@ check_mouse_other_menu_bar (f)
 
 #ifdef USE_X_TOOLKIT
 
+extern unsigned last_event_timestamp;
+extern Lisp_Object Vdouble_click_time;
+
 extern unsigned int x_mouse_grabbed;
 extern Lisp_Object Vmouse_depressed;
 
@@ -1358,6 +1397,8 @@ xmenu_show (f, x, y, menubarp, keymaps, title, error)
   int menu_id;
   Widget menu;
   XlwMenuWidget menubar = (XlwMenuWidget) f->display.x->menubar_widget;
+  Arg av [2];
+  int ac = 0;
 
   /* This is the menu bar item (if any) that led to this menu.  */
   widget_value *menubar_item = 0;
@@ -1382,6 +1423,9 @@ xmenu_show (f, x, y, menubarp, keymaps, title, error)
 
   Position root_x, root_y;
 
+  int first_pane;
+  int next_release_must_exit = 0;
+
   *error = NULL;
 
   if (menu_items_used <= MENU_ITEMS_PANE_LENGTH)
@@ -1425,9 +1469,10 @@ xmenu_show (f, x, y, menubarp, keymaps, title, error)
     menubarp = 0;
 
   /* Offset the coordinates to root-relative.  */
+  if (f->display.x->menubar_widget != 0)
+    y += f->display.x->menubar_widget->core.height;
   XtTranslateCoords (f->display.x->widget,
-                    x, y + f->display.x->menubar_widget->core.height,
-                    &root_x, &root_y);
+                    x, y, &root_x, &root_y);
   x = root_x;
   y = root_y;
 
@@ -1438,6 +1483,7 @@ xmenu_show (f, x, y, menubarp, keymaps, title, error)
   wv->value = 0;
   wv->enabled = 1;
   first_wv = wv;
+  first_pane = 1;
  
   /* Loop over all panes and items, filling in the tree.  */
   i = 0;
@@ -1448,12 +1494,14 @@ xmenu_show (f, x, y, menubarp, keymaps, title, error)
          submenu_stack[submenu_depth++] = save_wv;
          save_wv = prev_wv;
          prev_wv = 0;
+         first_pane = 1;
          i++;
        }
       else if (EQ (XVECTOR (menu_items)->contents[i], Qlambda))
        {
          prev_wv = save_wv;
          save_wv = submenu_stack[--submenu_depth];
+         first_pane = 0;
          i++;
        }
       else if (EQ (XVECTOR (menu_items)->contents[i], Qt)
@@ -1480,7 +1528,7 @@ xmenu_show (f, x, y, menubarp, keymaps, title, error)
          /* If the pane has a meaningful name,
             make the pane a top-level menu item
             with its items as a submenu beneath it.  */
-         if (strcmp (pane_string, ""))
+         if (!keymaps && strcmp (pane_string, ""))
            {
              wv = malloc_widget_value ();
              if (save_wv)
@@ -1492,9 +1540,15 @@ xmenu_show (f, x, y, menubarp, keymaps, title, error)
                wv->name++;
              wv->value = 0;
              wv->enabled = 1;
+             save_wv = wv;
+             prev_wv = 0;
            }
-         save_wv = wv;
-         prev_wv = 0;
+         else if (first_pane)
+           {
+             save_wv = wv;
+             prev_wv = 0;
+           }
+         first_pane = 0;
          i += MENU_ITEMS_PANE_LENGTH;
        }
       else
@@ -1528,6 +1582,11 @@ xmenu_show (f, x, y, menubarp, keymaps, title, error)
   menu = lw_create_widget ("popup", first_wv->name, menu_id, first_wv,
                           f->display.x->widget, 1, 0,
                           popup_selection_callback, popup_down_callback);
+
+  /* Don't allow any geometry request from the user.  */
+  XtSetArg (av[ac], XtNgeometry, 0); ac++;
+  XtSetValues (menu, av, ac);
+
   /* Free the widget_value objects we used to specify the contents.  */
   free_menubar_widget_value_tree (first_wv);
 
@@ -1573,17 +1632,19 @@ xmenu_show (f, x, y, menubarp, keymaps, title, error)
 
   /* No need to check a second time since this is done in the XEvent loop.
      This slows done the execution.  */
-#if 0
+#ifdef XMENU_FOO
   /* Check again whether the mouse has moved to another menu bar item.  */
   if (check_mouse_other_menu_bar (f))
     {
       /* The mouse moved into a different menu bar item. 
         We should bring up that item's menu instead.
         First pop down this menu.  */
+#if 0 /* xlwmenu.c now does this.  */
       XtUngrabPointer ((Widget)
                       ((XlwMenuWidget)
                        ((CompositeWidget)menu)->composite.children[0]),
                       CurrentTime);
+#endif
       lw_destroy_all_widgets (menu_id); 
       goto pop_down;
     }
@@ -1593,26 +1654,66 @@ xmenu_show (f, x, y, menubarp, keymaps, title, error)
   while (1)
     {
       XEvent event;
+      int queue_and_exit = 0;
+      int in_this_menu = 0, in_menu_bar = 0;
+      Widget widget;
 
       XtAppNextEvent (Xt_app_con, &event);
-      if (event.type == ButtonRelease)
+
+      /* Check whether the event happened in the menu
+        or any child of it.  */
+      widget = XtWindowToWidget (XDISPLAY event.xany.window);
+
+      while (widget)
        {
-         XtDispatchEvent (&event);
-         if (! menubarp)
+         if (widget == menu)
            {
-             /* Do the work of construct_mouse_click since it can't
-                be called. Initially, the popup menu has been called
-                from a ButtonPress in the edit_widget. Then the mouse
-                has been set to grabbed. Reset it now.  */
-             x_mouse_grabbed &= ~(1 << event.xbutton.button);
-             if (!x_mouse_grabbed)
-               Vmouse_depressed = Qnil;
+             in_this_menu = 1;
+             break;
            }
-         break;
+         if (widget == f->display.x->menubar_widget)
+           {
+             in_menu_bar = 1;
+             break;
+           }
+         widget = XtParent (widget);
+       }
+
+      if (event.type == ButtonRelease)
+       {
+         /* Do the work of construct_mouse_click since it can't
+            be called. Initially, the popup menu has been called
+            from a ButtonPress in the edit_widget. Then the mouse
+            has been set to grabbed. Reset it now.  */
+         x_mouse_grabbed &= ~(1 << event.xbutton.button);
+         if (!x_mouse_grabbed)
+           Vmouse_depressed = Qnil;
+
+         /* If we release the button soon without selecting anything,
+            stay in the loop--that is, leave the menu posted.
+            Otherwise, exit this loop and thus pop down the menu.  */
+         if (! in_this_menu
+             && (next_release_must_exit
+                 || !(((XButtonEvent *) (&event))->time - last_event_timestamp
+                      < XINT (Vdouble_click_time))))
+           break;
+       }
+      /* A button press outside the menu => pop it down.  */
+      else if (event.type == ButtonPress && !in_this_menu)
+       break;
+      else if (event.type == ButtonPress)
+       next_release_must_exit = 1;
+      else if (event.type == KeyPress)
+       {
+         /* Exit the loop, but first queue this event for reuse.  */
+         queue_and_exit = 1;
        }
       else if (event.type == Expose)
        process_expose_from_menu (event);
-      else if (event.type == MotionNotify)
+      /* If the mouse moves to a different menu bar item, switch to
+        that item's menu.  But only if the button is still held down.  */
+      else if (event.type == MotionNotify
+              && x_mouse_grabbed)
        {
          int event_x = (event.xmotion.x_root
                         - (f->display.x->widget->core.x
@@ -1626,10 +1727,12 @@ xmenu_show (f, x, y, menubarp, keymaps, title, error)
              /* The mouse moved into a different menu bar item. 
                 We should bring up that item's menu instead.
                 First pop down this menu.  */
+#if 0 /* xlwmenu.c now does this.  */
              XtUngrabPointer ((Widget)
                               ((XlwMenuWidget)
                                ((CompositeWidget)menu)->composite.children[0]),
                               event.xbutton.time);
+#endif
              lw_destroy_all_widgets (menu_id); 
 
              /* Put back an event that will bring up the other item's menu.  */
@@ -1639,9 +1742,17 @@ xmenu_show (f, x, y, menubarp, keymaps, title, error)
              break;
            }
        }
+      else if (event.type == UnmapNotify)
+       {
+         /* If the menu disappears, there is no need to stay in the
+             loop.  */
+         if (event.xunmap.window == menu->core.window)
+           break;
+       }
 
       XtDispatchEvent (&event);
-      if (XtWindowToWidget(XDISPLAY event.xany.window) != menu)
+
+      if (queue_and_exit || (!in_this_menu && !in_menu_bar))
        {
          queue_tmp
            = (struct event_queue *) malloc (sizeof (struct event_queue));
@@ -1653,6 +1764,8 @@ xmenu_show (f, x, y, menubarp, keymaps, title, error)
              queue = queue_tmp;
            }
        }
+      if (queue_and_exit)
+       break;
     }
 
  pop_down:
@@ -1881,7 +1994,7 @@ xdialog_show (f, menubarp, keymaps, title, error)
 #if 0 /* This causes crashes, and seems to be redundant -- rms.  */
   lw_modify_all_widgets (dialog_id, first_wv, True);
 #endif
-  lw_modify_all_widgets (dialog_id, first_wv->contents->next, True);
+  lw_modify_all_widgets (dialog_id, first_wv->contents, True);
   /* Free the widget_value objects we used to specify the contents.  */
   free_menubar_widget_value_tree (first_wv);
 
@@ -2226,7 +2339,6 @@ xmenu_show (f, x, y, menubarp, keymaps, title, error)
       break;
 
     case XM_FAILURE:
-      XMenuDestroy (XDISPLAY menu);
       *error = "Can't activate menu";
     case XM_IA_SELECT:
     case XM_NO_SELECT: