* xmenu.c (pop_down_menu): Remove global variable current_menu,
[bpt/emacs.git] / src / xmenu.c
index 2eed2b4..b0d4517 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, 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
@@ -1097,7 +1098,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,20 +1114,91 @@ on the left of the dialog box and all following items on the right.
   }
 #endif
 }
+
+
+#ifndef MSDOS
+
+/* Return non-zero if a dialog or popup menu is already popped up.  */
+
+int
+x_menu_in_use ()
+{
+  return ! NILP (menu_items_inuse);
+}
+
+/* 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;
+}
+
+/* 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)
      XEvent *initial_event;
@@ -1139,17 +1211,16 @@ popup_get_selection (initial_event, dpyinfo, id, do_timers, down_on_keypress)
 
   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)
+       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,
@@ -1194,14 +1265,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 ();
     }
 }
@@ -1788,7 +1863,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 +1876,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 +1890,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 +1963,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;
 
@@ -2056,7 +2131,7 @@ set_frame_menubar (f, first_time, deep_p)
          string = XVECTOR (items)->contents[i + 1];
          if (NILP (string))
             break;
-          wv->name = (char *) SDATA (ENCODE_MENU_STRING (string));
+          wv->name = (char *) SDATA (string);
           update_submenu_strings (wv->contents);
           wv = wv->next;
        }
@@ -2088,7 +2163,7 @@ set_frame_menubar (f, first_time, deep_p)
            break;
 
          wv = xmalloc_widget_value ();
-         wv->name = (char *) SDATA (ENCODE_MENU_STRING (string));
+         wv->name = (char *) SDATA (string);
          wv->value = 0;
          wv->enabled = 1;
          wv->button_type = BUTTON_TYPE_NONE;
@@ -2328,7 +2403,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 +2427,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 +2455,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,
@@ -2397,13 +2486,16 @@ create_and_show_popup_menu (f, first_wv, x, y, for_click)
   gtk_widget_show_all (menu);
   gtk_menu_popup (GTK_MENU (menu), 0, 0, pos_func, &popup_x_y, i, 0);
 
+  fprintf (stderr, "Unwind: %p\n", menu);
+  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 +2523,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 +2597,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, 0);
 
-  /* 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 +2920,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 +2952,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,7 +2979,7 @@ 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)))));
 
@@ -3112,6 +3212,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)
@@ -3133,6 +3268,7 @@ xmenu_show (f, x, y, for_click, keymaps, title, error)
   int maxwidth;
   int dummy_int;
   unsigned int dummy_uint;
+  int specpdl_count = SPECPDL_INDEX ();
 
   *error = 0;
   if (menu_items_n_panes == 0)
@@ -3322,19 +3458,20 @@ xmenu_show (f, x, y, for_click, keymaps, title, error)
   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)
     {
@@ -3385,15 +3522,8 @@ xmenu_show (f, x, y, for_click, keymaps, title, error)
       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;
 }