*** empty log message ***
[bpt/emacs.git] / src / xmenu.c
index 75584af..4724b2e 100644 (file)
@@ -112,7 +112,7 @@ extern void process_expose_from_menu ();
 extern XtAppContext Xt_app_con;
 
 static Lisp_Object xdialog_show ();
-void popup_get_selection ();
+static void popup_get_selection ();
 
 /* Define HAVE_BOXES if menus can handle radio and toggle buttons.  */
 
@@ -132,6 +132,8 @@ static void single_menu_item P_ ((Lisp_Object, Lisp_Object, Lisp_Object *,
                                  int, int, int *));
 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.
@@ -174,6 +176,10 @@ enum menu_item_idx
 
 static Lisp_Object menu_items;
 
+/* If non-nil, means that the global vars defined here are already in use.
+   Used to detect cases where we try to re-enter this non-reentrant code.  */
+static Lisp_Object menu_items_inuse;
+
 /* Number of slots currently allocated in menu_items.  */
 static int menu_items_allocated;
 
@@ -241,6 +247,9 @@ init_menu_items ()
       menu_items = Fmake_vector (make_number (menu_items_allocated), Qnil);
     }
 
+  if (!NILP (menu_items_inuse))
+    error ("Trying to use a menu from within a menu-entry");
+  menu_items_inuse = Qt;
   menu_items_used = 0;
   menu_items_n_panes = 0;
   menu_items_submenu_depth = 0;
@@ -253,6 +262,13 @@ finish_menu_items ()
 {
 }
 
+static Lisp_Object
+unuse_menu_items (dummy)
+     int dummy;
+{
+  return menu_items_inuse = Qnil;
+}
+
 /* Call when finished using the data for the current menu
    in menu_items.  */
 
@@ -266,6 +282,7 @@ discard_menu_items ()
       menu_items = Qnil;
       menu_items_allocated = 0;
     }
+  xassert (NILP (menu_items_inuse));
 }
 
 /* Make the menu_items vector twice as large.  */
@@ -699,6 +716,7 @@ cached information about equivalent key sequences.  */)
   Lisp_Object x, y, window;
   int keymaps = 0;
   int for_click = 0;
+  int specpdl_count = SPECPDL_INDEX ();
   struct gcpro gcpro1;
 
 #ifdef HAVE_MENUS
@@ -777,12 +795,11 @@ cached information about equivalent key sequences.  */)
 
       xpos += XINT (x);
       ypos += XINT (y);
-
-      XSETFRAME (Vmenu_updating_frame, f);
     }
   Vmenu_updating_frame = Qnil;
 #endif /* HAVE_MENUS */
 
+  record_unwind_protect (unuse_menu_items, Qnil);
   title = Qnil;
   GCPRO1 (title);
 
@@ -852,6 +869,8 @@ cached information about equivalent key sequences.  */)
       keymaps = 0;
     }
   
+  unbind_to (specpdl_count, Qnil);
+
   if (NILP (position))
     {
       discard_menu_items ();
@@ -973,10 +992,12 @@ on the left of the dialog box and all following items on the right.
     Lisp_Object title;
     char *error_name;
     Lisp_Object selection;
+    int specpdl_count = SPECPDL_INDEX ();
 
     /* Decode the dialog items from what was specified.  */
     title = Fcar (contents);
     CHECK_STRING (title);
+    record_unwind_protect (unuse_menu_items, Qnil);
 
     list_of_panes (Fcons (contents, Qnil));
 
@@ -985,6 +1006,7 @@ on the left of the dialog box and all following items on the right.
     selection = xdialog_show (f, 0, title, &error_name);
     UNBLOCK_INPUT;
 
+    unbind_to (specpdl_count, Qnil);
     discard_menu_items ();
 
     if (error_name) error (error_name);
@@ -995,31 +1017,44 @@ on the left of the dialog box and all following items on the right.
 \f
 #ifdef USE_X_TOOLKIT
 
+/* Define a queue to save up for later unreading
+   all X events that don't pertain to the menu.  */
+struct event_queue
+  {
+    XEvent event;
+    struct event_queue *next;
+  };
+
+/* It is ok that this queue is a static variable,
+   because init_menu_items won't allow the menu mechanism
+   to be entered recursively.  */
+static struct event_queue *popup_get_selection_queue;
+
+static Lisp_Object popup_get_selection_unwind ();
+
 /* 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 any more.
+   and x-popup-dialog; it is not used for the menu bar.
+
+   If DO_TIMERS is nonzero, run timers.
 
    NOTE: All calls to popup_get_selection should be protected
    with BLOCK_INPUT, UNBLOCK_INPUT wrappers.  */
 
-void
-popup_get_selection (initial_event, dpyinfo, id)
+static void
+popup_get_selection (initial_event, dpyinfo, id, do_timers)
      XEvent *initial_event;
      struct x_display_info *dpyinfo;
      LWLIB_ID id;
+     int do_timers;
 {
   XEvent event;
-
-  /* Define a queue to save up for later unreading
-     all X events that don't pertain to the menu.  */
-  struct event_queue
-    {
-      XEvent event;
-      struct event_queue *next;
-    };
-  
-  struct event_queue *queue = NULL;
   struct event_queue *queue_tmp;
+  int count = SPECPDL_INDEX ();
+
+  popup_get_selection_queue = NULL;
+
+  record_unwind_protect (popup_get_selection_unwind, Qnil);
 
   if (initial_event)
     event = *initial_event;
@@ -1080,23 +1115,38 @@ popup_get_selection (initial_event, dpyinfo, id)
        {
          queue_tmp = (struct event_queue *) xmalloc (sizeof *queue_tmp);
          queue_tmp->event = event;
-         queue_tmp->next = queue;
-         queue = queue_tmp;
+         queue_tmp->next = popup_get_selection_queue;
+         popup_get_selection_queue = queue_tmp;
        }
       else
        XtDispatchEvent (&event);
 
-      if (!popup_activated ())
+      /* If the event deactivated the menu, we are finished.  */
+      if (!popup_activated_flag)
        break;
+
+      /* If we have no events to run, consider timers.  */
+      if (do_timers && !XtAppPending (Xt_app_con))
+       timer_check (1);
+
       XtAppNextEvent (Xt_app_con, &event);
     }
 
-  /* Unread any events that we got but did not handle.  */
-  while (queue != NULL) 
+  unbind_to (count, Qnil);
+}
+
+/* Unread any events that popup_get_selection read but did not handle.  */
+
+static Lisp_Object
+popup_get_selection_unwind (ignore)
+     Lisp_Object ignore;
+{
+  while (popup_get_selection_queue != NULL) 
     {
-      queue_tmp = queue;
+      struct event_queue *queue_tmp;
+      queue_tmp = popup_get_selection_queue;
       XPutBackEvent (queue_tmp->event.xany.display, &queue_tmp->event);
-      queue = queue_tmp->next;
+      popup_get_selection_queue = queue_tmp->next;
       xfree ((char *)queue_tmp);
       /* Cause these events to get read as soon as we UNBLOCK_INPUT.  */
       interrupt_input_pending = 1;
@@ -1126,7 +1176,7 @@ x_activate_menubar (f)
 
   set_frame_menubar (f, 0, 1);
   BLOCK_INPUT;
-  XtDispatchEvent ((XEvent *) f->output_data.x->saved_menu_event);
+  XtDispatchEvent (f->output_data.x->saved_menu_event);
   UNBLOCK_INPUT;
 #ifdef USE_MOTIF
   if (f->output_data.x->saved_menu_event->type == ButtonRelease)
@@ -1345,7 +1395,7 @@ free_menubar_widget_value_tree (wv)
   UNBLOCK_INPUT;
 }
 \f
-/* Set up data i menu_items for a menu bar item
+/* Set up data in menu_items for a menu bar item
    whose event type is ITEM_KEY (with string ITEM_NAME)
    and whose contents come from the list of keymaps MAPS.  */
 
@@ -1374,8 +1424,7 @@ parse_single_submenu (item_key, item_name, maps)
      But don't make a pane that is empty--ignore that map instead.  */
   for (i = 0; i < len; i++)
     {
-      if (SYMBOLP (mapvec[i])
-         || (CONSP (mapvec[i]) && !KEYMAPP (mapvec[i])))
+      if (!KEYMAPP (mapvec[i]))
        {
          /* Here we have a command at top level in the menu bar
             as opposed to a submenu.  */
@@ -1385,7 +1434,13 @@ parse_single_submenu (item_key, item_name, maps)
                          Qnil, Qnil, Qnil, Qnil);
        }
       else
-       single_keymap_panes (mapvec[i], item_name, item_key, 0, 10);
+       {
+         Lisp_Object prompt;
+         prompt = Fkeymap_prompt (mapvec[i]);
+         single_keymap_panes (mapvec[i],
+                              !NILP (prompt) ? prompt : item_name,
+                              item_key, 0, 10);
+       }
     }
 
   return top_level_items;
@@ -1397,7 +1452,7 @@ parse_single_submenu (item_key, item_name, maps)
 
 static widget_value *
 digest_single_submenu (start, end, top_level_items)
-     int start, end;
+     int start, end, top_level_items;
 {
   widget_value *wv, *prev_wv, *save_wv, *first_wv;
   int i;
@@ -1416,10 +1471,10 @@ digest_single_submenu (start, end, top_level_items)
   save_wv = 0;
   prev_wv = 0;
  
-  /* Loop over all panes and items made during this call
-     and construct a tree of widget_value objects.
-     Ignore the panes and items made by previous calls to
-     single_submenu, even though those are also in menu_items.  */
+  /* Loop over all panes and items made by the preceding call
+     to parse_single_submenu and construct a tree of widget_value objects.
+     Ignore the panes and items used by previous calls to
+     digest_single_submenu, even though those are also in menu_items.  */
   i = start;
   while (i < end)
     {
@@ -1625,7 +1680,7 @@ set_frame_menubar (f, first_time, deep_p)
   widget_value *wv, *first_wv, *prev_wv = 0;
   int i, last_i;
   int *submenu_start, *submenu_end;
-  int *submenu_top_level_items;
+  int *submenu_top_level_items, *submenu_n_panes;
 
   LWLIB_ID id;
 
@@ -1671,6 +1726,7 @@ set_frame_menubar (f, first_time, deep_p)
       specbind (Qdebug_on_next_call, Qnil);
 
       record_unwind_protect (Fset_match_data, Fmatch_data (Qnil, Qnil));
+      record_unwind_protect (unuse_menu_items, Qnil);
       if (NILP (Voverriding_local_map_menu_flag))
        {
          specbind (Qoverriding_terminal_local_map, Qnil);
@@ -1702,6 +1758,7 @@ set_frame_menubar (f, first_time, deep_p)
       menu_items_allocated = VECTORP (menu_items) ? ASIZE (menu_items) : 0;
       submenu_start = (int *) alloca (XVECTOR (items)->size * sizeof (int *));
       submenu_end = (int *) alloca (XVECTOR (items)->size * sizeof (int *));
+      submenu_n_panes = (int *) alloca (XVECTOR (items)->size * sizeof (int));
       submenu_top_level_items
        = (int *) alloca (XVECTOR (items)->size * sizeof (int *));
       init_menu_items ();
@@ -1722,6 +1779,7 @@ set_frame_menubar (f, first_time, deep_p)
          menu_items_n_panes = 0;
          submenu_top_level_items[i]
            = parse_single_submenu (key, string, maps);
+         submenu_n_panes[i] = menu_items_n_panes;
 
          submenu_end[i] = menu_items_used;
        }
@@ -1741,6 +1799,7 @@ set_frame_menubar (f, first_time, deep_p)
 
       for (i = 0; i < last_i; i += 4)
        {
+         menu_items_n_panes = submenu_n_panes[i];
          wv = digest_single_submenu (submenu_start[i], submenu_end[i],
                                      submenu_top_level_items[i]);
          if (prev_wv) 
@@ -1766,7 +1825,7 @@ set_frame_menubar (f, first_time, deep_p)
       if (i == menu_items_used && i == previous_menu_items_used && i != 0)
        {
          free_menubar_widget_value_tree (first_wv);
-         menu_items = Qnil;
+         discard_menu_items ();
 
          return;
        }
@@ -1786,7 +1845,7 @@ set_frame_menubar (f, first_time, deep_p)
 
       f->menu_bar_vector = menu_items;
       f->menu_bar_items_used = menu_items_used;
-      menu_items = Qnil;
+      discard_menu_items ();
     }
   else
     {
@@ -2284,7 +2343,7 @@ xmenu_show (f, x, y, for_click, keymaps, title, error)
   popup_activated_flag = 1;
 
   /* Process events that apply to the menu.  */
-  popup_get_selection ((XEvent *) 0, FRAME_X_DISPLAY_INFO (f), menu_id);
+  popup_get_selection ((XEvent *) 0, FRAME_X_DISPLAY_INFO (f), menu_id, 0);
 
   /* fp turned off the following statement and wrote a comment
      that it is unnecessary--that the menu has already disappeared.
@@ -2366,6 +2425,22 @@ dialog_selection_callback (widget, id, client_data)
   popup_activated_flag = 0;
 }
 
+/* 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;
+}
+
 static char * button_names [] = {
   "button1", "button2", "button3", "button4", "button5",
   "button6", "button7", "button8", "button9", "button10" };
@@ -2379,7 +2454,6 @@ xdialog_show (f, keymaps, title, error)
 {
   int i, nb_buttons=0;
   LWLIB_ID dialog_id;
-  Widget menu;
   char dialog_name[6];
 
   widget_value *wv, *first_wv = 0, *prev_wv = 0;
@@ -2491,9 +2565,9 @@ xdialog_show (f, keymaps, title, error)
 
   /* Actually create the dialog.  */
   dialog_id = widget_id_tick++;
-  menu = lw_create_widget (first_wv->name, "dialog", dialog_id, first_wv,
-                          f->output_data.x->widget, 1, 0,
-                          dialog_selection_callback, 0, 0);
+  lw_create_widget (first_wv->name, "dialog", dialog_id, first_wv,
+                   f->output_data.x->widget, 1, 0,
+                   dialog_selection_callback, 0, 0);
   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);
@@ -2501,17 +2575,26 @@ xdialog_show (f, keymaps, title, error)
   /* No selection has been chosen yet.  */
   menu_item_selection = 0;
 
-  /* Display the menu.  */
+  /* Display the dialog box.  */
   lw_pop_up_all_widgets (dialog_id);
   popup_activated_flag = 1;
 
-  /* Process events that apply to the menu.  */
-  popup_get_selection ((XEvent *) 0, FRAME_X_DISPLAY_INFO (f), dialog_id);
+  /* Process events that apply to the dialog box.
+     Also handle timers.  */
+  {
+    int count = SPECPDL_INDEX ();
 
-  lw_destroy_all_widgets (dialog_id); 
+    /* xdialog_show_unwind is responsible for popping the dialog box down.  */
+    record_unwind_protect (xdialog_show_unwind,
+                          Fcons (make_number (dialog_id >> (4 * sizeof (LWLIB_ID))),
+                                 make_number (dialog_id & ~(-1 << (4 * sizeof (LWLIB_ID))))));
 
-  /* Find the selected item, and its pane, to return
-     the proper value.  */
+    popup_get_selection ((XEvent *) 0, FRAME_X_DISPLAY_INFO (f), dialog_id, 1);
+
+    unbind_to (count, Qnil);
+  }
+
+  /* Find the selected item and pane, and return the corresponding value.  */
   if (menu_item_selection != 0)
     {
       Lisp_Object prefix;
@@ -2895,6 +2978,7 @@ syms_of_xmenu ()
 {
   staticpro (&menu_items);
   menu_items = Qnil;
+  menu_items_inuse = Qnil;
 
   Qdebug_on_next_call = intern ("debug-on-next-call");
   staticpro (&Qdebug_on_next_call);