]> code.delx.au - gnu-emacs/blobdiff - src/xmenu.c
(CLEAR_IMAGE_CACHE_COUNT): New const.
[gnu-emacs] / src / xmenu.c
index 229f9c37de71ef41ca274b2674498caa5a779db0..b587fcf9e106fba6211b8338aa9c58dd75a6e077 100644 (file)
@@ -1,5 +1,5 @@
 /* X Communication module for terminals which understand the X protocol.
-   Copyright (C) 1986, 88, 93, 94, 96, 99, 2000, 2001, 2003
+   Copyright (C) 1986, 1988, 1993, 1994, 1996, 1999, 2000, 2001, 2003, 2004
    Free Software Foundation, Inc.
 
 This file is part of GNU Emacs.
@@ -48,6 +48,7 @@ Boston, MA 02111-1307, USA.  */
 #include "buffer.h"
 #include "charset.h"
 #include "coding.h"
+#include "sysselect.h"
 
 #ifdef MSDOS
 #include "msdos.h"
@@ -110,11 +111,12 @@ extern Lisp_Object Qoverriding_local_map, Qoverriding_terminal_local_map;
 extern Lisp_Object Qmenu_bar_update_hook;
 
 #ifdef USE_X_TOOLKIT
-extern void set_frame_menubar ();
+extern void set_frame_menubar P_ ((FRAME_PTR, int, int));
 extern XtAppContext Xt_app_con;
 
-static Lisp_Object xdialog_show ();
-static void popup_get_selection ();
+static Lisp_Object xdialog_show P_ ((FRAME_PTR, int, Lisp_Object, char **));
+static void popup_get_selection P_ ((XEvent *, struct x_display_info *,
+                                     LWLIB_ID, int));
 
 /* Define HAVE_BOXES if menus can handle radio and toggle buttons.  */
 
@@ -124,8 +126,8 @@ static void popup_get_selection ();
 #ifdef USE_GTK
 #include "gtkutil.h"
 #define HAVE_BOXES 1
-extern void set_frame_menubar ();
-static Lisp_Object xdialog_show ();
+extern void set_frame_menubar P_ ((FRAME_PTR, int, int));
+static Lisp_Object xdialog_show P_ ((FRAME_PTR, int, Lisp_Object, char **));
 #endif
 
 /* This is how to deal with multibyte text if HAVE_MULTILINGUAL_MENU
@@ -156,7 +158,6 @@ static void single_keymap_panes P_ ((Lisp_Object, Lisp_Object, Lisp_Object,
 static void list_of_panes P_ ((Lisp_Object));
 static void list_of_items P_ ((Lisp_Object));
 
-extern EMACS_TIME timer_check P_ ((int));
 \f
 /* This holds a Lisp vector that holds the results of decoding
    the keymaps or alist-of-alists that specify a menu.
@@ -287,7 +288,7 @@ finish_menu_items ()
 
 static Lisp_Object
 unuse_menu_items (dummy)
-     int dummy;
+     Lisp_Object dummy;
 {
   return menu_items_inuse = Qnil;
 }
@@ -523,7 +524,7 @@ single_menu_item (key, item, dummy, skp_v)
     return;                    /* Not a menu item.  */
 
   map = XVECTOR (item_properties)->contents[ITEM_PROPERTY_MAP];
-  
+
   if (skp->notreal)
     {
       /* We don't want to make a menu, just traverse the keymaps to
@@ -740,8 +741,7 @@ POSITION is a position specification.  This is either a mouse button event
 or a list ((XOFFSET YOFFSET) WINDOW)
 where XOFFSET and YOFFSET are positions in pixels from the top left
 corner of WINDOW's frame.  (WINDOW may be a frame object instead of a window.)
-This controls the position of the center of the first line
-in the first pane of the menu, not the top left of the menu as a whole.
+This controls the position of the top left of the menu as a whole.
 If POSITION is t, it means to use the current mouse position.
 
 MENU is a specifier for a menu.  For the simplest case, MENU is a keymap.
@@ -1097,7 +1097,7 @@ on the left of the dialog box and all following items on the right.
          the dialog.  Also, the lesstif/motif version crashes if there are
          no buttons.  */
       contents = Fcons (title, Fcons (Fcons (build_string ("Ok"), Qt), Qnil));
-    
+
     list_of_panes (Fcons (contents, Qnil));
 
     /* Display them in a dialog box.  */
@@ -1113,43 +1113,103 @@ on the left of the dialog box and all following items on the right.
   }
 #endif
 }
+
+
+#ifndef MSDOS
+
+/* Set menu_items_inuse so no other popup menu or dialog is created.  */
+
+void
+x_menu_set_in_use (in_use)
+     int in_use;
+{
+  menu_items_inuse = in_use ? Qt : Qnil;
+  popup_activated_flag = in_use;
+}
+
+/* Wait for an X event to arrive or for a timer to expire.  */
+
+void
+x_menu_wait_for_event (void *data)
+{
+  extern EMACS_TIME timer_check P_ ((int));
+
+  /* Another way to do this is to register a timer callback, that can be
+     done in GTK and Xt.  But we have to do it like this when using only X
+     anyway, and with callbacks we would have three variants for timer handling
+     instead of the small ifdefs below.  */
+
+  while (
+#ifdef USE_X_TOOLKIT
+         ! XtAppPending (Xt_app_con)
+#elif defined USE_GTK
+         ! gtk_events_pending ()
+#else
+         ! XPending ((Display*) data)
+#endif
+         )
+    {
+      EMACS_TIME next_time = timer_check (1);
+      long secs = EMACS_SECS (next_time);
+      long usecs = EMACS_USECS (next_time);
+      SELECT_TYPE read_fds;
+      struct x_display_info *dpyinfo;
+      int n = 0;
+
+      FD_ZERO (&read_fds);
+      for (dpyinfo = x_display_list; dpyinfo; dpyinfo = dpyinfo->next)
+        {
+          int fd = ConnectionNumber (dpyinfo->display);
+          FD_SET (fd, &read_fds);
+          if (fd > n) n = fd;
+        }
+
+      if (secs < 0 || (secs == 0 && usecs == 0))
+        {
+          /* Sometimes timer_check returns -1 (no timers) even if there are
+             timers.  So do a timeout anyway.  */
+          EMACS_SET_SECS (next_time, 1);
+          EMACS_SET_USECS (next_time, 0);
+        }
+
+      select (n + 1, &read_fds, (SELECT_TYPE *)0, (SELECT_TYPE *)0, &next_time);
+    }
+}
+#endif /* ! MSDOS */
+
 \f
 #if defined (USE_X_TOOLKIT) || defined (USE_GTK)
 
+#ifdef USE_X_TOOLKIT
+
 /* Loop in Xt until the menu pulldown or dialog popup has been
    popped down (deactivated).  This is used for x-popup-menu
    and x-popup-dialog; it is not used for the menu bar.
 
-   If DO_TIMERS is nonzero, run timers.
-   If DOWN_ON_KEYPRESS is nonzero, pop down if a key is pressed.
-
    NOTE: All calls to popup_get_selection should be protected
    with BLOCK_INPUT, UNBLOCK_INPUT wrappers.  */
 
-#ifdef USE_X_TOOLKIT
 static void
-popup_get_selection (initial_event, dpyinfo, id, do_timers, down_on_keypress)
+popup_get_selection (initial_event, dpyinfo, id, do_timers)
      XEvent *initial_event;
      struct x_display_info *dpyinfo;
      LWLIB_ID id;
      int do_timers;
-     int down_on_keypress;
 {
   XEvent event;
 
   while (popup_activated_flag)
     {
-      /* If we have no events to run, consider timers.  */
-      if (do_timers && !XtAppPending (Xt_app_con))
-        timer_check (1);
-
       if (initial_event)
         {
           event = *initial_event;
           initial_event = 0;
         }
       else
-        XtAppNextEvent (Xt_app_con, &event);
+        {
+          if (do_timers) x_menu_wait_for_event (0);
+          XtAppNextEvent (Xt_app_con, &event);
+        }
 
       /* Make sure we don't consider buttons grabbed after menu goes.
          And make sure to deactivate for any ButtonRelease,
@@ -1169,20 +1229,15 @@ popup_get_selection (initial_event, dpyinfo, id, do_timers, down_on_keypress)
           event.xbutton.state = 0;
 #endif
         }
-      /* If the user presses a key that doesn't go to the menu,
-         deactivate the menu.
-         The user is likely to do that if we get wedged.
-         All toolkits now pop down menus on ESC.
-         For dialogs however, the focus may not be on the dialog, so
-         in that case, we pop down. */
+      /* Pop down on C-g and Escape.  */
       else if (event.type == KeyPress
-               && down_on_keypress
                && dpyinfo->display == event.xbutton.display)
         {
           KeySym keysym = XLookupKeysym (&event.xkey, 0);
-          if (!IsModifierKey (keysym)
-              && x_any_window_to_frame (dpyinfo, event.xany.window) != NULL)
-           popup_activated_flag = 0;
+
+          if ((keysym == XK_g && (event.xkey.state & ControlMask) != 0)
+              || keysym == XK_Escape) /* Any escape, ignore modifiers.  */
+            popup_activated_flag = 0;
         }
 
       x_dispatch_event (&event, event.xany.display);
@@ -1194,14 +1249,18 @@ popup_get_selection (initial_event, dpyinfo, id, do_timers, down_on_keypress)
 #ifdef USE_GTK
 /* Loop util popup_activated_flag is set to zero in a callback.
    Used for popup menus and dialogs. */
+
 static void
-popup_widget_loop ()
+popup_widget_loop (do_timers, widget)
+     int do_timers;
+     GtkWidget *widget;
 {
   ++popup_activated_flag;
 
   /* Process events in the Gtk event loop until done.  */
   while (popup_activated_flag)
     {
+      if (do_timers) x_menu_wait_for_event (0);
       gtk_main_iteration ();
     }
 }
@@ -1386,9 +1445,9 @@ menu_highlight_callback (widget, id, call_data)
 /* Find the menu selection and store it in the keyboard buffer.
    F is the frame the menu is on.
    MENU_BAR_ITEMS_USED is the length of VECTOR.
-   VECTOR is an array of menu events for the whole menu.
- */
-void
+   VECTOR is an array of menu events for the whole menu.  */
+
+static void
 find_and_call_menu_selection (f, menu_bar_items_used, vector, client_data)
      FRAME_PTR f;
      int menu_bar_items_used;
@@ -1495,6 +1554,17 @@ menubar_selection_callback (widget, client_data)
   if (! cb_data || ! cb_data->cl_data || ! cb_data->cl_data->f)
     return;
 
+  /* When a menu is popped down, X generates a focus event (i.e. focus
+     goes back to the frame below the menu).  Since GTK buffers events,
+     we force it out here before the menu selection event.  Otherwise
+     sit-for will exit at once if the focus event follows the menu selection
+     event.  */
+
+  BLOCK_INPUT;
+  while (gtk_events_pending ())
+    gtk_main_iteration ();
+  UNBLOCK_INPUT;
+
   find_and_call_menu_selection (cb_data->cl_data->f,
                                 cb_data->cl_data->menu_bar_items_used,
                                 cb_data->cl_data->menu_bar_vector,
@@ -1788,7 +1858,7 @@ digest_single_submenu (start, end, top_level_items)
   return first_wv;
 }
 
-/* Walk through thw widget_value tree starting at FIRST_WV and update
+/* Walk through the widget_value tree starting at FIRST_WV and update
    the char * pointers from the corresponding lisp values.
    We do this after building the whole tree, since GC may happen while the
    tree is constructed, and small strings are relocated.  So we must wait
@@ -1801,7 +1871,7 @@ update_submenu_strings (first_wv)
 
   for (wv = first_wv; wv; wv = wv->next)
     {
-      if (wv->lname && ! NILP (wv->lname))
+      if (STRINGP (wv->lname))
         {
           wv->name = SDATA (wv->lname);
 
@@ -1815,7 +1885,7 @@ update_submenu_strings (first_wv)
             }
         }
 
-      if (wv->lkey && ! NILP (wv->lkey))
+      if (STRINGP (wv->lkey))
         wv->key = SDATA (wv->lkey);
 
       if (wv->contents)
@@ -1888,7 +1958,7 @@ set_frame_menubar (f, first_time, deep_p)
 #endif
   Lisp_Object items;
   widget_value *wv, *first_wv, *prev_wv = 0;
-  int i, last_i;
+  int i, last_i = 0;
   int *submenu_start, *submenu_end;
   int *submenu_top_level_items, *submenu_n_panes;
 
@@ -2159,6 +2229,9 @@ set_frame_menubar (f, first_time, deep_p)
     }
   else
     {
+      char menuOverride[] = "Ctrl<KeyPress>g: MenuGadgetEscape()";
+      XtTranslations  override = XtParseTranslationTable (menuOverride);
+
       menubar_widget = lw_create_widget ("menubar", "menubar", id, first_wv,
                                         f->output_data.x->column_widget,
                                         0,
@@ -2167,6 +2240,9 @@ set_frame_menubar (f, first_time, deep_p)
                                         popup_deactivate_callback,
                                         menu_highlight_callback);
       f->output_data.x->menubar_widget = menubar_widget;
+
+      /* Make menu pop down on C-g.  */
+      XtOverrideTranslations (menubar_widget, override);
     }
 
   {
@@ -2328,7 +2404,7 @@ menu_position_func (menu, x, y, push_in, user_data)
   GtkRequisition req;
   int disp_width = FRAME_X_DISPLAY_INFO (data->f)->width;
   int disp_height = FRAME_X_DISPLAY_INFO (data->f)->height;
-  
+
   *x = data->x;
   *y = data->y;
 
@@ -2352,6 +2428,19 @@ popup_selection_callback (widget, client_data)
   if (cb_data) menu_item_selection = (Lisp_Object *) cb_data->call_data;
 }
 
+static Lisp_Object
+pop_down_menu (arg)
+     Lisp_Object arg;
+{
+  struct Lisp_Save_Value *p = XSAVE_VALUE (arg);
+
+  popup_activated_flag = 0;
+  BLOCK_INPUT;
+  gtk_widget_destroy (GTK_WIDGET (p->pointer));
+  UNBLOCK_INPUT;
+  return Qnil;
+}
+
 /* Pop up the menu for frame F defined by FIRST_WV at X/Y and loop until the
    menu pops down.
    menu_item_selection will be set to the selection.  */
@@ -2367,6 +2456,7 @@ create_and_show_popup_menu (f, first_wv, x, y, for_click)
   GtkWidget *menu;
   GtkMenuPositionFunc pos_func = 0;  /* Pop up at pointer.  */
   struct next_popup_x_y popup_x_y;
+  int specpdl_count = SPECPDL_INDEX ();
 
   xg_crazy_callback_abort = 1;
   menu = xg_create_widget ("popup", first_wv->name, f, first_wv,
@@ -2375,10 +2465,6 @@ create_and_show_popup_menu (f, first_wv, x, y, for_click)
                            G_CALLBACK (menu_highlight_callback));
   xg_crazy_callback_abort = 0;
 
-  for (i = 0; i < 5; i++)
-    if (FRAME_X_DISPLAY_INFO (f)->grabbed & (1 << i))
-      break;
-
   if (! for_click)
     {
       /* Not invoked by a click.  pop up at x/y.  */
@@ -2391,19 +2477,29 @@ create_and_show_popup_menu (f, first_wv, x, y, for_click)
       popup_x_y.x = x;
       popup_x_y.y = y;
       popup_x_y.f = f;
-    }
 
+      i = 0;  /* gtk_menu_popup needs this to be 0 for a non-button popup.  */
+    }
+  else
+    {
+      for (i = 0; i < 5; i++)
+        if (FRAME_X_DISPLAY_INFO (f)->grabbed & (1 << i))
+          break;
+    }
+  
   /* Display the menu.  */
   gtk_widget_show_all (menu);
   gtk_menu_popup (GTK_MENU (menu), 0, 0, pos_func, &popup_x_y, i, 0);
 
+  record_unwind_protect (pop_down_menu, make_save_value (menu, 0));
+
   /* Set this to one.  popup_widget_loop increases it by one, so it becomes
      two.  show_help_echo uses this to detect popup menus.  */
   popup_activated_flag = 1;
   /* Process events that apply to the menu.  */
-  popup_widget_loop ();
+  popup_widget_loop (1, menu);
 
-  gtk_widget_destroy (menu);
+  unbind_to (specpdl_count, Qnil);
 
   /* Must reset this manually because the button release event is not passed
      to Emacs event loop. */
@@ -2431,6 +2527,24 @@ popup_selection_callback (widget, id, client_data)
   menu_item_selection = (Lisp_Object *) client_data;
 }
 
+/* ARG is the LWLIB ID of the dialog box, represented
+   as a Lisp object as (HIGHPART . LOWPART).  */
+
+static Lisp_Object
+pop_down_menu (arg)
+     Lisp_Object arg;
+{
+  LWLIB_ID id = (XINT (XCAR (arg)) << 4 * sizeof (LWLIB_ID)
+                 | XINT (XCDR (arg)));
+
+  BLOCK_INPUT;
+  lw_destroy_all_widgets (id);
+  UNBLOCK_INPUT;
+  popup_activated_flag = 0;
+
+  return Qnil;
+}
+
 /* Pop up the menu for frame F defined by FIRST_WV at X/Y and loop until the
    menu pops down.
    menu_item_selection will be set to the selection.  */
@@ -2487,15 +2601,19 @@ create_and_show_popup_menu (f, first_wv, x, y, for_click)
   /* Display the menu.  */
   lw_popup_menu (menu, (XEvent *) &dummy);
   popup_activated_flag = 1;
+  
+  {
+    int fact = 4 * sizeof (LWLIB_ID);
+    int specpdl_count = SPECPDL_INDEX ();
+    record_unwind_protect (pop_down_menu,
+                           Fcons (make_number (menu_id >> (fact)),
+                                  make_number (menu_id & ~(-1 << (fact)))));
 
-  /* Process events that apply to the menu.  */
-  popup_get_selection ((XEvent *) 0, FRAME_X_DISPLAY_INFO (f), menu_id, 0, 0);
+    /* Process events that apply to the menu.  */
+    popup_get_selection ((XEvent *) 0, FRAME_X_DISPLAY_INFO (f), menu_id, 1);
 
-  /* fp turned off the following statement and wrote a comment
-     that it is unnecessary--that the menu has already disappeared.
-     Nowadays the menu disappears ok, all right, but
-     we need to delete the widgets or multiple ones will pile up.  */
-  lw_destroy_all_widgets (menu_id);
+    unbind_to (specpdl_count, Qnil);
+  }
 }
 
 #endif /* not USE_GTK */
@@ -2806,13 +2924,16 @@ create_and_show_dialog (f, first_wv)
 
   if (menu)
     {
+      int specpdl_count = SPECPDL_INDEX ();
+      record_unwind_protect (pop_down_menu, make_save_value (menu, 0));
+
       /* Display the menu.  */
       gtk_widget_show_all (menu);
 
       /* Process events that apply to the menu.  */
-      popup_widget_loop ();
+      popup_widget_loop (1, menu);
 
-      gtk_widget_destroy (menu);
+      unbind_to (specpdl_count, Qnil);
     }
 }
 
@@ -2835,23 +2956,6 @@ dialog_selection_callback (widget, id, client_data)
 }
 
 
-/* ARG is the LWLIB ID of the dialog box, represented
-   as a Lisp object as (HIGHPART . LOWPART).  */
-
-Lisp_Object
-xdialog_show_unwind (arg)
-     Lisp_Object arg;
-{
-  LWLIB_ID id = (XINT (XCAR (arg)) << 4 * sizeof (LWLIB_ID)
-                | XINT (XCDR (arg)));
-  BLOCK_INPUT;
-  lw_destroy_all_widgets (id);
-  UNBLOCK_INPUT;
-  popup_activated_flag = 0;
-  return Qnil;
-}
-
-
 /* Pop up the dialog for frame F defined by FIRST_WV and loop until the
    dialog pops down.
    menu_item_selection will be set to the selection.  */
@@ -2879,12 +2983,12 @@ create_and_show_dialog (f, first_wv)
     int fact = 4 * sizeof (LWLIB_ID);
 
     /* xdialog_show_unwind is responsible for popping the dialog box down.  */
-    record_unwind_protect (xdialog_show_unwind,
+    record_unwind_protect (pop_down_menu,
                            Fcons (make_number (dialog_id >> (fact)),
                                   make_number (dialog_id & ~(-1 << (fact)))));
 
     popup_get_selection ((XEvent *) 0, FRAME_X_DISPLAY_INFO (f),
-                         dialog_id, 1, 1);
+                         dialog_id, 1);
 
     unbind_to (count, Qnil);
   }
@@ -3064,6 +3168,9 @@ xdialog_show (f, keymaps, title, error)
            }
        }
     }
+  else
+    /* Make "Cancel" equivalent to C-g.  */
+    Fsignal (Qquit, Qnil);
 
   return Qnil;
 }
@@ -3112,6 +3219,41 @@ menu_help_callback (help_string, pane, item)
                  Qnil, menu_object, make_number (item), 1);
 }
 
+static Lisp_Object
+pop_down_menu (arg)
+     Lisp_Object arg;
+{
+  struct Lisp_Save_Value *p1 = XSAVE_VALUE (Fcar (arg));
+  struct Lisp_Save_Value *p2 = XSAVE_VALUE (Fcdr (arg));
+  
+  FRAME_PTR f = p1->pointer;
+  XMenu *menu = p2->pointer;
+
+  BLOCK_INPUT;
+#ifndef MSDOS
+  XUngrabPointer (FRAME_X_DISPLAY (f), CurrentTime);
+  XUngrabKeyboard (FRAME_X_DISPLAY (f), CurrentTime);
+#endif
+  XMenuDestroy (FRAME_X_DISPLAY (f), menu);
+
+#ifdef HAVE_X_WINDOWS
+  /* Assume the mouse has moved out of the X window.
+     If it has actually moved in, we will get an EnterNotify.  */
+  x_mouse_leave (FRAME_X_DISPLAY_INFO (f));
+
+  /* State that no mouse buttons are now held.
+     (The oldXMenu code doesn't track this info for us.)
+     That is not necessarily true, but the fiction leads to reasonable
+     results, and it is a pain to ask which are actually held now.  */
+  FRAME_X_DISPLAY_INFO (f)->grabbed = 0;
+
+#endif /* HAVE_X_WINDOWS */
+
+  UNBLOCK_INPUT;
+
+  return Qnil;
+}
+
 
 static Lisp_Object
 xmenu_show (f, x, y, for_click, keymaps, title, error)
@@ -3129,10 +3271,11 @@ xmenu_show (f, x, y, for_click, keymaps, title, error)
   char *datap;
   int ulx, uly, width, height;
   int dispwidth, dispheight;
-  int i, j;
+  int i, j, lines, maxlines;
   int maxwidth;
   int dummy_int;
   unsigned int dummy_uint;
+  int specpdl_count = SPECPDL_INDEX ();
 
   *error = 0;
   if (menu_items_n_panes == 0)
@@ -3159,31 +3302,8 @@ xmenu_show (f, x, y, for_click, keymaps, title, error)
 
 #ifdef HAVE_X_WINDOWS
   /* Adjust coordinates to relative to the outer (window manager) window.  */
-  {
-    Window child;
-    int win_x = 0, win_y = 0;
-
-    /* Find the position of the outside upper-left corner of
-       the inner window, with respect to the outer window.  */
-    if (f->output_data.x->parent_desc != FRAME_X_DISPLAY_INFO (f)->root_window)
-      {
-       BLOCK_INPUT;
-       XTranslateCoordinates (FRAME_X_DISPLAY (f),
-
-                              /* From-window, to-window.  */
-                              f->output_data.x->window_desc,
-                              f->output_data.x->parent_desc,
-
-                              /* From-position, to-position.  */
-                              0, 0, &win_x, &win_y,
-
-                              /* Child of window.  */
-                              &child);
-       UNBLOCK_INPUT;
-       x += win_x;
-       y += win_y;
-      }
-  }
+  x += FRAME_OUTER_TO_INNER_DIFF_X (f);
+  y += FRAME_OUTER_TO_INNER_DIFF_Y (f);
 #endif /* HAVE_X_WINDOWS */
 
   /* Adjust coordinates to be root-window-relative.  */
@@ -3191,7 +3311,7 @@ xmenu_show (f, x, y, for_click, keymaps, title, error)
   y += f->top_pos;
 
   /* Create all the necessary panes and their items.  */
-  i = 0;
+  maxlines = lines = i = 0;
   while (i < menu_items_used)
     {
       if (EQ (XVECTOR (menu_items)->contents[i], Qt))
@@ -3200,6 +3320,8 @@ xmenu_show (f, x, y, for_click, keymaps, title, error)
          Lisp_Object pane_name, prefix;
          char *pane_string;
 
+          maxlines = max (maxlines, lines);
+          lines = 0;
          pane_name = XVECTOR (menu_items)->contents[i + MENU_ITEMS_PANE_NAME];
          prefix = XVECTOR (menu_items)->contents[i + MENU_ITEMS_PANE_PREFIX];
          pane_string = (NILP (pane_name)
@@ -3292,9 +3414,12 @@ xmenu_show (f, x, y, for_click, keymaps, title, error)
              return Qnil;
            }
          i += MENU_ITEMS_ITEM_LENGTH;
+          lines++;
        }
     }
 
+  maxlines = max (maxlines, lines);
+
   /* All set and ready to fly.  */
   XMenuRecompute (FRAME_X_DISPLAY (f), menu);
   dispwidth = DisplayWidth (FRAME_X_DISPLAY (f), FRAME_X_SCREEN_NUMBER (f));
@@ -3318,23 +3443,33 @@ xmenu_show (f, x, y, for_click, keymaps, title, error)
   if (ulx < 0) x -= ulx;
   if (uly < 0) y -= uly;
 
+  if (! for_click)
+    {
+      /* If position was not given by a mouse click, adjust so upper left
+         corner of the menu as a whole ends up at given coordinates.  This
+         is what x-popup-menu says in its documentation.  */
+      x += width/2;
+      y += 1.5*height/(maxlines+2);
+    }
+
   XMenuSetAEQ (menu, TRUE);
   XMenuSetFreeze (menu, TRUE);
   pane = selidx = 0;
 
+#ifndef MSDOS
+  XMenuActivateSetWaitFunction (x_menu_wait_for_event, FRAME_X_DISPLAY (f));
+#endif
+  
+  record_unwind_protect (pop_down_menu,
+                         Fcons (make_save_value (f, 0),
+                                make_save_value (menu, 0)));
+
   /* Help display under X won't work because XMenuActivate contains
      a loop that doesn't give Emacs a chance to process it.  */
   menu_help_frame = f;
   status = XMenuActivate (FRAME_X_DISPLAY (f), menu, &pane, &selidx,
-                         x, y, ButtonReleaseMask, &datap,
-                         menu_help_callback);
-
-
-#ifdef HAVE_X_WINDOWS
-  /* Assume the mouse has moved out of the X window.
-     If it has actually moved in, we will get an EnterNotify.  */
-  x_mouse_leave (FRAME_X_DISPLAY_INFO (f));
-#endif
+                          x, y, ButtonReleaseMask, &datap,
+                          menu_help_callback);
 
   switch (status)
     {
@@ -3381,19 +3516,18 @@ xmenu_show (f, x, y, for_click, keymaps, title, error)
     case XM_FAILURE:
       *error = "Can't activate menu";
     case XM_IA_SELECT:
+      entry = Qnil;
+      break;
     case XM_NO_SELECT:
+      /* Make "Cancel" equivalent to C-g unless this menu was popped up by
+         a mouse press.  */
+      if (! for_click)
+        Fsignal (Qquit, Qnil);
       entry = Qnil;
       break;
     }
-  XMenuDestroy (FRAME_X_DISPLAY (f), menu);
 
-#ifdef HAVE_X_WINDOWS
-  /* State that no mouse buttons are now held.
-     (The oldXMenu code doesn't track this info for us.)
-     That is not necessarily true, but the fiction leads to reasonable
-     results, and it is a pain to ask which are actually held now.  */
-  FRAME_X_DISPLAY_INFO (f)->grabbed = 0;
-#endif
+  unbind_to (specpdl_count, Qnil);
 
   return entry;
 }