X-Git-Url: https://code.delx.au/gnu-emacs/blobdiff_plain/cc45fb4031a234b9c99549e54c4b0612af3d28cd..8ea5080e3587261d4188dbdeac4ed6ddf352f706:/src/xmenu.c diff --git a/src/xmenu.c b/src/xmenu.c index e974dfe312..f9c2dc4bd1 100644 --- a/src/xmenu.c +++ b/src/xmenu.c @@ -1,5 +1,5 @@ /* X Communication module for terminals which understand the X protocol. - Copyright (C) 1986, 88, 93, 94, 96, 99, 2000, 2001 + 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" @@ -68,6 +69,8 @@ Boston, MA 02111-1307, USA. */ #include "dispextern.h" #ifdef HAVE_X_WINDOWS +/* Defining HAVE_MULTILINGUAL_MENU would mean that the toolkit menu + code accepts the Emacs internal encoding. */ #undef HAVE_MULTILINGUAL_MENU #ifdef USE_X_TOOLKIT #include "widget.h" @@ -81,7 +84,9 @@ Boston, MA 02111-1307, USA. */ #endif /* USE_LUCID */ #include "../lwlib/lwlib.h" #else /* not USE_X_TOOLKIT */ +#ifndef USE_GTK #include "../oldXMenu/XMenu.h" +#endif #endif /* not USE_X_TOOLKIT */ #endif /* HAVE_X_WINDOWS */ @@ -95,7 +100,6 @@ Lisp_Object Vmenu_updating_frame; Lisp_Object Qdebug_on_next_call; extern Lisp_Object Qmenu_bar; -extern Lisp_Object Qmouse_click, Qevent_kind; extern Lisp_Object QCtoggle, QCradio; @@ -107,16 +111,39 @@ 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 process_expose_from_menu (); +extern void set_frame_menubar P_ ((FRAME_PTR, int, int)); extern XtAppContext Xt_app_con; -static Lisp_Object xdialog_show (); -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. */ #define HAVE_BOXES 1 +#endif /* USE_X_TOOLKIT */ + +#ifdef USE_GTK +#include "gtkutil.h" +#define HAVE_BOXES 1 +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 + isn't defined. The use of HAVE_MULTILINGUAL_MENU could probably be + confined to an extended version of this with sections of code below + using it unconditionally. */ +#ifdef USE_GTK +/* gtk just uses utf-8. */ +# define ENCODE_MENU_STRING(str) ENCODE_UTF_8 (str) +#else +/* I'm not convinced ENCODE_SYSTEM is defined correctly, or maybe + something else should be used here. Except under MS-Windows it + just converts to unibyte, but encoding with `locale-coding-system' + seems better -- X may actually display the result correctly, and + it's not necessarily equivalent to the unibyte text. -- fx */ +# define ENCODE_MENU_STRING(str) ENCODE_SYSTEM (str) #endif static void push_menu_item P_ ((Lisp_Object, Lisp_Object, Lisp_Object, @@ -128,10 +155,9 @@ static Lisp_Object xmenu_show P_ ((struct frame *, int, int, int, int, static void keymap_panes P_ ((Lisp_Object *, int, int)); static void single_keymap_panes P_ ((Lisp_Object, Lisp_Object, Lisp_Object, int, int)); -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)); + /* This holds a Lisp vector that holds the results of decoding the keymaps or alist-of-alists that specify a menu. @@ -174,6 +200,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; @@ -189,7 +219,7 @@ static int menu_items_submenu_depth; /* Flag which when set indicates a dialog or menu has been posted by Xt on behalf of one of the widget sets. */ -int popup_activated_flag; +static int popup_activated_flag; static int next_menubar_widget_id; @@ -241,19 +271,28 @@ 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; } -/* Call at the end of generating the data in menu_items. - This fills in the number of items in the last pane. */ +/* Call at the end of generating the data in menu_items. */ static void finish_menu_items () { } +static Lisp_Object +unuse_menu_items (dummy) + Lisp_Object dummy; +{ + return menu_items_inuse = Qnil; +} + /* Call when finished using the data for the current menu in menu_items. */ @@ -267,6 +306,7 @@ discard_menu_items () menu_items = Qnil; menu_items_allocated = 0; } + xassert (NILP (menu_items_inuse)); } /* Make the menu_items vector twice as large. */ @@ -386,6 +426,17 @@ keymap_panes (keymaps, nmaps, notreal) finish_menu_items (); } +/* Args passed between single_keymap_panes and single_menu_item. */ +struct skp + { + Lisp_Object pending_maps; + int maxdepth, notreal; + int notbuttons; + }; + +static void single_menu_item P_ ((Lisp_Object, Lisp_Object, Lisp_Object, + void *)); + /* This is a recursive subroutine of keymap_panes. It handles one keymap, KEYMAP. The other arguments are passed along @@ -403,10 +454,13 @@ single_keymap_panes (keymap, pane_name, prefix, notreal, maxdepth) int notreal; int maxdepth; { - Lisp_Object pending_maps = Qnil; - Lisp_Object tail, item; - struct gcpro gcpro1, gcpro2; - int notbuttons = 0; + struct skp skp; + struct gcpro gcpro1; + + skp.pending_maps = Qnil; + skp.maxdepth = maxdepth; + skp.notreal = notreal; + skp.notbuttons = 0; if (maxdepth <= 0) return; @@ -418,100 +472,77 @@ single_keymap_panes (keymap, pane_name, prefix, notreal, maxdepth) add a prefix when (if) we see the first button. After that, notbuttons is set to 0, to mark that we have seen a button and all non button items need a prefix. */ - notbuttons = menu_items_used; + skp.notbuttons = menu_items_used; #endif - for (tail = keymap; CONSP (tail); tail = XCDR (tail)) - { - GCPRO2 (keymap, pending_maps); - /* Look at each key binding, and if it is a menu item add it - to this menu. */ - item = XCAR (tail); - if (CONSP (item)) - single_menu_item (XCAR (item), XCDR (item), - &pending_maps, notreal, maxdepth, ¬buttons); - else if (VECTORP (item)) - { - /* Loop over the char values represented in the vector. */ - int len = XVECTOR (item)->size; - int c; - for (c = 0; c < len; c++) - { - Lisp_Object character; - XSETFASTINT (character, c); - single_menu_item (character, XVECTOR (item)->contents[c], - &pending_maps, notreal, maxdepth, ¬buttons); - } - } - UNGCPRO; - } + GCPRO1 (skp.pending_maps); + map_keymap (keymap, single_menu_item, Qnil, &skp, 1); + UNGCPRO; /* Process now any submenus which want to be panes at this level. */ - while (!NILP (pending_maps)) + while (CONSP (skp.pending_maps)) { Lisp_Object elt, eltcdr, string; - elt = Fcar (pending_maps); + elt = XCAR (skp.pending_maps); eltcdr = XCDR (elt); string = XCAR (eltcdr); /* We no longer discard the @ from the beginning of the string here. Instead, we do this in xmenu_show. */ single_keymap_panes (Fcar (elt), string, XCDR (eltcdr), notreal, maxdepth - 1); - pending_maps = Fcdr (pending_maps); + skp.pending_maps = XCDR (skp.pending_maps); } } /* This is a subroutine of single_keymap_panes that handles one keymap entry. - KEY is a key in a keymap and ITEM is its binding. - PENDING_MAPS_PTR points to a list of keymaps waiting to be made into + KEY is a key in a keymap and ITEM is its binding. + SKP->PENDING_MAPS_PTR is a list of keymaps waiting to be made into separate panes. - If NOTREAL is nonzero, only check for equivalent key bindings, don't + If SKP->NOTREAL is nonzero, only check for equivalent key bindings, don't evaluate expressions in menu items and don't make any menu. - If we encounter submenus deeper than MAXDEPTH levels, ignore them. - NOTBUTTONS_PTR is only used when simulating toggle boxes and radio - buttons. It points to variable notbuttons in single_keymap_panes, - which keeps track of if we have seen a button in this menu or not. */ + If we encounter submenus deeper than SKP->MAXDEPTH levels, ignore them. + SKP->NOTBUTTONS is only used when simulating toggle boxes and radio + buttons. It keeps track of if we have seen a button in this menu or + not. */ static void -single_menu_item (key, item, pending_maps_ptr, notreal, maxdepth, - notbuttons_ptr) - Lisp_Object key, item; - Lisp_Object *pending_maps_ptr; - int maxdepth, notreal; - int *notbuttons_ptr; +single_menu_item (key, item, dummy, skp_v) + Lisp_Object key, item, dummy; + void *skp_v; { Lisp_Object map, item_string, enabled; struct gcpro gcpro1, gcpro2; int res; - + struct skp *skp = skp_v; + /* Parse the menu item and leave the result in item_properties. */ GCPRO2 (key, item); - res = parse_menu_item (item, notreal, 0); + res = parse_menu_item (item, skp->notreal, 0); UNGCPRO; if (!res) return; /* Not a menu item. */ map = XVECTOR (item_properties)->contents[ITEM_PROPERTY_MAP]; - - if (notreal) + + if (skp->notreal) { /* We don't want to make a menu, just traverse the keymaps to precompute equivalent key bindings. */ if (!NILP (map)) - single_keymap_panes (map, Qnil, key, 1, maxdepth - 1); + single_keymap_panes (map, Qnil, key, 1, skp->maxdepth - 1); return; } enabled = XVECTOR (item_properties)->contents[ITEM_PROPERTY_ENABLE]; - item_string = XVECTOR (item_properties)->contents[ITEM_PROPERTY_NAME]; + item_string = XVECTOR (item_properties)->contents[ITEM_PROPERTY_NAME]; - if (!NILP (map) && XSTRING (item_string)->data[0] == '@') + if (!NILP (map) && SREF (item_string, 0) == '@') { if (!NILP (enabled)) /* An enabled separate pane. Remember this to handle it later. */ - *pending_maps_ptr = Fcons (Fcons (map, Fcons (item_string, key)), - *pending_maps_ptr); + skp->pending_maps = Fcons (Fcons (map, Fcons (item_string, key)), + skp->pending_maps); return; } @@ -526,10 +557,10 @@ single_menu_item (key, item, pending_maps_ptr, notreal, maxdepth, Lisp_Object selected = XVECTOR (item_properties)->contents[ITEM_PROPERTY_SELECTED]; - if (*notbuttons_ptr) + if (skp->notbuttons) /* The first button. Line up previous items in this menu. */ { - int index = *notbuttons_ptr; /* Index for first item this menu. */ + int index = skp->notbuttons; /* Index for first item this menu. */ int submenu = 0; Lisp_Object tem; while (index < menu_items_used) @@ -552,14 +583,14 @@ single_menu_item (key, item, pending_maps_ptr, notreal, maxdepth, index++; /* Skip a left, right divider. */ else { - if (!submenu && XSTRING (tem)->data[0] != '\0' - && XSTRING (tem)->data[0] != '-') + if (!submenu && SREF (tem, 0) != '\0' + && SREF (tem, 0) != '-') XVECTOR (menu_items)->contents[index + MENU_ITEMS_ITEM_NAME] = concat2 (build_string (" "), tem); index += MENU_ITEMS_ITEM_LENGTH; } } - *notbuttons_ptr = 0; + skp->notbuttons = 0; } /* Calculate prefix, if any, for this item. */ @@ -569,17 +600,17 @@ single_menu_item (key, item, pending_maps_ptr, notreal, maxdepth, prefix = build_string (NILP (selected) ? "( ) " : "(*) "); } /* Not a button. If we have earlier buttons, then we need a prefix. */ - else if (!*notbuttons_ptr && XSTRING (item_string)->data[0] != '\0' - && XSTRING (item_string)->data[0] != '-') + else if (!skp->notbuttons && SREF (item_string, 0) != '\0' + && SREF (item_string, 0) != '-') prefix = build_string (" "); if (!NILP (prefix)) item_string = concat2 (prefix, item_string); } #endif /* not HAVE_BOXES */ - -#ifndef USE_X_TOOLKIT - if (!NILP(map)) + +#if ! defined (USE_X_TOOLKIT) && ! defined (USE_GTK) + if (!NILP (map)) /* Indicate visually that this is a submenu. */ item_string = concat2 (item_string, build_string (" >")); #endif @@ -591,12 +622,12 @@ single_menu_item (key, item, pending_maps_ptr, notreal, maxdepth, XVECTOR (item_properties)->contents[ITEM_PROPERTY_SELECTED], XVECTOR (item_properties)->contents[ITEM_PROPERTY_HELP]); -#ifdef USE_X_TOOLKIT +#if defined (USE_X_TOOLKIT) || defined (USE_GTK) /* Display a submenu using the toolkit. */ if (! (NILP (map) || NILP (enabled))) { push_submenu_start (); - single_keymap_panes (map, Qnil, key, 0, maxdepth - 1); + single_keymap_panes (map, Qnil, key, 0, skp->maxdepth - 1); push_submenu_end (); } #endif @@ -619,10 +650,10 @@ list_of_panes (menu) Lisp_Object elt, pane_name, pane_data; elt = Fcar (tail); pane_name = Fcar (elt); - CHECK_STRING (pane_name, 0); - push_menu_pane (pane_name, Qnil); + CHECK_STRING (pane_name); + push_menu_pane (ENCODE_MENU_STRING (pane_name), Qnil); pane_data = Fcdr (elt); - CHECK_CONS (pane_data, 0); + CHECK_CONS (pane_data); list_of_items (pane_data); } @@ -641,27 +672,76 @@ list_of_items (pane) { item = Fcar (tail); if (STRINGP (item)) - push_menu_item (item, Qnil, Qnil, Qt, Qnil, Qnil, Qnil, Qnil); + push_menu_item (ENCODE_MENU_STRING (item), Qnil, Qnil, Qt, + Qnil, Qnil, Qnil, Qnil); else if (NILP (item)) push_left_right_boundary (); else { - CHECK_CONS (item, 0); + CHECK_CONS (item); item1 = Fcar (item); - CHECK_STRING (item1, 1); - push_menu_item (item1, Qt, Fcdr (item), Qt, Qnil, Qnil, Qnil, Qnil); + CHECK_STRING (item1); + push_menu_item (ENCODE_MENU_STRING (item1), Qt, Fcdr (item), + Qt, Qnil, Qnil, Qnil, Qnil); } } } +#ifdef HAVE_X_WINDOWS +/* Return the mouse position in *X and *Y. The coordinates are window + relative for the edit window in frame F. + This is for Fx_popup_menu. The mouse_position_hook can not + be used for X, as it returns window relative coordinates + for the window where the mouse is in. This could be the menu bar, + the scroll bar or the edit window. Fx_popup_menu needs to be + sure it is the edit window. */ +static void +mouse_position_for_popup (f, x, y) + FRAME_PTR f; + int *x; + int *y; +{ + Window root, dummy_window; + int dummy; + + BLOCK_INPUT; + + XQueryPointer (FRAME_X_DISPLAY (f), + DefaultRootWindow (FRAME_X_DISPLAY (f)), + + /* The root window which contains the pointer. */ + &root, + + /* Window pointer is on, not used */ + &dummy_window, + + /* The position on that root window. */ + x, y, + + /* x/y in dummy_window coordinates, not used. */ + &dummy, &dummy, + + /* Modifier keys and pointer buttons, about which + we don't care. */ + (unsigned int *) &dummy); + + UNBLOCK_INPUT; + + /* xmenu_show expects window coordinates, not root window + coordinates. Translate. */ + *x -= f->left_pos + FRAME_OUTER_TO_INNER_DIFF_X (f); + *y -= f->top_pos + FRAME_OUTER_TO_INNER_DIFF_Y (f); +} + +#endif /* HAVE_X_WINDOWS */ + DEFUN ("x-popup-menu", Fx_popup_menu, Sx_popup_menu, 2, 2, 0, - doc: /* Pop up a deck-of-cards menu and return user's selection. + doc: /* Pop up a deck-of-cards menu and return user's selection. 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. @@ -700,11 +780,13 @@ 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 if (! NILP (position)) { + int get_current_pos_p = 0; check_x (); /* Decode the first argument: find the window and the coordinates. */ @@ -712,24 +794,8 @@ cached information about equivalent key sequences. */) || (CONSP (position) && (EQ (XCAR (position), Qmenu_bar) || EQ (XCAR (position), Qtool_bar)))) { - /* Use the mouse's current position. */ - FRAME_PTR new_f = SELECTED_FRAME (); - Lisp_Object bar_window; - enum scroll_bar_part part; - unsigned long time; - - if (mouse_position_hook) - (*mouse_position_hook) (&new_f, 1, &bar_window, - &part, &x, &y, &time); - if (new_f != 0) - XSETFRAME (window, new_f); - else - { - window = selected_window; - XSETFASTINT (x, 0); - XSETFASTINT (y, 0); - } - } + get_current_pos_p = 1; + } else { tem = Fcar (position); @@ -748,10 +814,56 @@ cached information about equivalent key sequences. */) x = Fcar (tem); y = Fcdr (tem); } + + /* If a click happens in an external tool bar or a detached + tool bar, x and y is NIL. In that case, use the current + mouse position. This happens for the help button in the + tool bar. Ideally popup-menu should pass NIL to + this function, but it doesn't. */ + if (NILP (x) && NILP (y)) + get_current_pos_p = 1; + } + + if (get_current_pos_p) + { + /* Use the mouse's current position. */ + FRAME_PTR new_f = SELECTED_FRAME (); +#ifdef HAVE_X_WINDOWS + /* Can't use mouse_position_hook for X since it returns + coordinates relative to the window the mouse is in, + we need coordinates relative to the edit widget always. */ + if (new_f != 0) + { + int cur_x, cur_y; + + mouse_position_for_popup (new_f, &cur_x, &cur_y); + /* cur_x/y may be negative, so use make_number. */ + x = make_number (cur_x); + y = make_number (cur_y); + } + +#else /* not HAVE_X_WINDOWS */ + Lisp_Object bar_window; + enum scroll_bar_part part; + unsigned long time; + + if (mouse_position_hook) + (*mouse_position_hook) (&new_f, 1, &bar_window, + &part, &x, &y, &time); +#endif /* not HAVE_X_WINDOWS */ + + if (new_f != 0) + XSETFRAME (window, new_f); + else + { + window = selected_window; + XSETFASTINT (x, 0); + XSETFASTINT (y, 0); + } } - CHECK_NUMBER (x, 0); - CHECK_NUMBER (y, 0); + CHECK_NUMBER (x); + CHECK_NUMBER (y); /* Decode where to put the menu. */ @@ -763,27 +875,24 @@ cached information about equivalent key sequences. */) } else if (WINDOWP (window)) { - CHECK_LIVE_WINDOW (window, 0); + CHECK_LIVE_WINDOW (window); f = XFRAME (WINDOW_FRAME (XWINDOW (window))); - xpos = (FONT_WIDTH (FRAME_FONT (f)) - * XFASTINT (XWINDOW (window)->left)); - ypos = (FRAME_LINE_HEIGHT (f) - * XFASTINT (XWINDOW (window)->top)); + xpos = WINDOW_LEFT_EDGE_X (XWINDOW (window)); + ypos = WINDOW_TOP_EDGE_Y (XWINDOW (window)); } else /* ??? Not really clean; should be CHECK_WINDOW_OR_FRAME, but I don't want to make one now. */ - CHECK_WINDOW (window, 0); + CHECK_WINDOW (window); 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); @@ -846,13 +955,15 @@ cached information about equivalent key sequences. */) { /* We were given an old-fashioned menu. */ title = Fcar (menu); - CHECK_STRING (title, 1); + CHECK_STRING (title); list_of_panes (Fcdr (menu)); keymaps = 0; } - + + unbind_to (specpdl_count, Qnil); + if (NILP (position)) { discard_menu_items (); @@ -880,7 +991,7 @@ cached information about equivalent key sequences. */) #ifdef HAVE_MENUS DEFUN ("x-popup-dialog", Fx_popup_dialog, Sx_popup_dialog, 2, 2, 0, - doc: /* Pop up a dialog box and return user's selection. + doc: /* Pop up a dialog box and return user's selection. POSITION specifies which frame to use. This is normally a mouse button event or a window or frame. If POSITION is t, it means to use the frame the mouse is on. @@ -948,15 +1059,15 @@ on the left of the dialog box and all following items on the right. f = XFRAME (window); else if (WINDOWP (window)) { - CHECK_LIVE_WINDOW (window, 0); + CHECK_LIVE_WINDOW (window); f = XFRAME (WINDOW_FRAME (XWINDOW (window))); } else /* ??? Not really clean; should be CHECK_WINDOW_OR_FRAME, but I don't want to make one now. */ - CHECK_WINDOW (window, 0); + CHECK_WINDOW (window); -#ifndef USE_X_TOOLKIT +#if ! defined (USE_X_TOOLKIT) && ! defined (USE_GTK) /* Display a menu with these alternatives in the middle of frame F. */ { @@ -974,10 +1085,18 @@ 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, 1); + CHECK_STRING (title); + record_unwind_protect (unuse_menu_items, Qnil); + + if (NILP (Fcar (Fcdr (contents)))) + /* No buttons specified, add an "Ok" button so users can pop down + 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)); @@ -986,6 +1105,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); @@ -993,120 +1113,162 @@ 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 */ + +#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 any more. + and x-popup-dialog; it is not used for the menu bar. 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 + while (popup_activated_flag) { - XEvent event; - struct event_queue *next; - }; - - struct event_queue *queue = NULL; - struct event_queue *queue_tmp; - - if (initial_event) - event = *initial_event; - else - XtAppNextEvent (Xt_app_con, &event); + if (initial_event) + { + event = *initial_event; + initial_event = 0; + } + else + { + if (do_timers) x_menu_wait_for_event (0); + XtAppNextEvent (Xt_app_con, &event); + } - while (1) - { - /* Handle expose events for editor frames right away. */ - if (event.type == Expose) - process_expose_from_menu (event); /* Make sure we don't consider buttons grabbed after menu goes. - And make sure to deactivate for any ButtonRelease, - even if XtDispatchEvent doesn't do that. */ - else if (event.type == ButtonRelease - && dpyinfo->display == event.xbutton.display) + And make sure to deactivate for any ButtonRelease, + even if XtDispatchEvent doesn't do that. */ + if (event.type == ButtonRelease + && dpyinfo->display == event.xbutton.display) { dpyinfo->grabbed &= ~(1 << event.xbutton.button); - popup_activated_flag = 0; -#ifdef USE_MOTIF /* Pretending that the event came from a - Btn1Down seems the only way to convince Motif to - activate its callbacks; setting the XmNmenuPost - isn't working. --marcus@sysc.pdx.edu. */ - event.xbutton.button = 1; +#ifdef USE_MOTIF /* Pretending that the event came from a + Btn1Down seems the only way to convince Motif to + activate its callbacks; setting the XmNmenuPost + isn't working. --marcus@sysc.pdx.edu. */ + event.xbutton.button = 1; + /* Motif only pops down menus when no Ctrl, Alt or Mod + key is pressed and the button is released. So reset key state + so Motif thinks this is the case. */ + event.xbutton.state = 0; #endif } - /* If the user presses a key, deactivate the menu. - The user is likely to do that if we get wedged. */ + /* Pop down on C-g and Escape. */ else if (event.type == KeyPress - && dpyinfo->display == event.xbutton.display) - { - KeySym keysym = XLookupKeysym (&event.xkey, 0); - if (!IsModifierKey (keysym)) - { - popup_activated_flag = 0; - break; - } - } - /* Button presses outside the menu also pop it down. */ - else if (event.type == ButtonPress - && event.xany.display == dpyinfo->display - && x_any_window_to_frame (dpyinfo, event.xany.window)) - { - popup_activated_flag = 0; - break; - } - - /* Queue all events not for this popup, - except for Expose, which we've already handled, and ButtonRelease. - Note that the X window is associated with the frame if this - is a menu bar popup, but not if it's a dialog box. So we use - x_non_menubar_window_to_frame, not x_any_window_to_frame. */ - if (event.type != Expose - && !(event.type == ButtonRelease && dpyinfo->display == event.xbutton.display) - && (event.xany.display != dpyinfo->display - || x_non_menubar_window_to_frame (dpyinfo, event.xany.window))) - { - queue_tmp = (struct event_queue *) xmalloc (sizeof *queue_tmp); - queue_tmp->event = event; - queue_tmp->next = queue; - queue = queue_tmp; - } - else - XtDispatchEvent (&event); + { + KeySym keysym = XLookupKeysym (&event.xkey, 0); - if (!popup_activated ()) - break; - XtAppNextEvent (Xt_app_con, &event); + 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); } +} + +#endif /* USE_X_TOOLKIT */ + +#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 (do_timers, widget) + int do_timers; + GtkWidget *widget; +{ + ++popup_activated_flag; - /* Unread any events that we got but did not handle. */ - while (queue != NULL) + /* Process events in the Gtk event loop until done. */ + while (popup_activated_flag) { - queue_tmp = queue; - XPutBackEvent (queue_tmp->event.xany.display, &queue_tmp->event); - queue = queue_tmp->next; - xfree ((char *)queue_tmp); - /* Cause these events to get read as soon as we UNBLOCK_INPUT. */ - interrupt_input_pending = 1; + if (do_timers) x_menu_wait_for_event (0); + gtk_main_iteration (); } } +#endif /* Activate the menu bar of frame F. This is called from keyboard.c when it gets the - menu_bar_activate_event out of the Emacs event queue. + MENU_BAR_ACTIVATE_EVENT out of the Emacs event queue. To activate the menu bar, we use the X button-press event that was saved in saved_menu_event. @@ -1117,7 +1279,7 @@ popup_get_selection (initial_event, dpyinfo, id) The reason for saving the button event until here, instead of passing it to the toolkit right away, is that we can safely execute Lisp code. */ - + void x_activate_menubar (f) FRAME_PTR f; @@ -1125,15 +1287,27 @@ x_activate_menubar (f) if (!f->output_data.x->saved_menu_event->type) return; +#ifdef USE_GTK + if (! xg_win_to_widget (FRAME_X_DISPLAY (f), + f->output_data.x->saved_menu_event->xany.window)) + return; +#endif + set_frame_menubar (f, 0, 1); BLOCK_INPUT; - XtDispatchEvent ((XEvent *) f->output_data.x->saved_menu_event); +#ifdef USE_GTK + XPutBackEvent (f->output_data.x->display_info->display, + f->output_data.x->saved_menu_event); + popup_activated_flag = 1; +#else + XtDispatchEvent (f->output_data.x->saved_menu_event); +#endif UNBLOCK_INPUT; #ifdef USE_MOTIF if (f->output_data.x->saved_menu_event->type == ButtonRelease) pending_menu_activation = 1; #endif - + /* Ignore this if we get it a second time. */ f->output_data.x->saved_menu_event->type = 0; } @@ -1149,6 +1323,7 @@ popup_activated () /* This callback is invoked when the user selects a menubar cascade pushbutton, but before the pulldown menu is posted. */ +#ifndef USE_GTK static void popup_activate_callback (widget, id, client_data) Widget widget; @@ -1157,10 +1332,20 @@ popup_activate_callback (widget, id, client_data) { popup_activated_flag = 1; } +#endif /* This callback is invoked when a dialog or menu is finished being used and has been unposted. */ +#ifdef USE_GTK +static void +popup_deactivate_callback (widget, client_data) + GtkWidget *widget; + gpointer client_data; +{ + popup_activated_flag = 0; +} +#else static void popup_deactivate_callback (widget, id, client_data) Widget widget; @@ -1169,27 +1354,20 @@ popup_deactivate_callback (widget, id, client_data) { popup_activated_flag = 0; } +#endif -/* Lwlib callback called when menu items are highlighted/unhighlighted - while moving the mouse over them. WIDGET is the menu bar or menu - popup widget. ID is its LWLIB_ID. CALL_DATA contains a pointer to - the widget_value structure for the menu item, or null in case of - unhighlighting. */ -void -menu_highlight_callback (widget, id, call_data) - Widget widget; - LWLIB_ID id; - void *call_data; +/* Function that finds the frame for WIDGET and shows the HELP text + for that widget. + F is the frame if known, or NULL if not known. */ +static void +show_help_event (f, widget, help) + FRAME_PTR f; + xt_or_gtk_widget widget; + Lisp_Object help; { - widget_value *wv = (widget_value *) call_data; - struct frame *f; - Lisp_Object frame, help; + Lisp_Object frame; - help = wv && wv->help ? build_string (wv->help) : Qnil; - - /* Determine the frame for the help event. */ - f = menubar_id_to_frame (id); if (f) { XSETFRAME (frame, f); @@ -1197,9 +1375,10 @@ menu_highlight_callback (widget, id, call_data) } else { - /* WIDGET is the popup menu. It's parent is the frame's +#if 0 /* This code doesn't do anything useful. ++kfs */ + /* WIDGET is the popup menu. It's parent is the frame's widget. See which frame that is. */ - Widget frame_widget = XtParent (widget); + xt_or_gtk_widget frame_widget = XtParent (widget); Lisp_Object tail; for (tail = Vframe_list; GC_CONSP (tail); tail = XCDR (tail)) @@ -1210,37 +1389,82 @@ menu_highlight_callback (widget, id, call_data) FRAME_X_P (f) && f->output_data.x->widget == frame_widget)) break; } - +#endif show_help_echo (help, Qnil, Qnil, Qnil, 1); } } -/* This callback is called from the menu bar pulldown menu - when the user makes a selection. - Figure out what the user chose - and put the appropriate events into the keyboard buffer. */ +/* Callback called when menu items are highlighted/unhighlighted + while moving the mouse over them. WIDGET is the menu bar or menu + popup widget. ID is its LWLIB_ID. CALL_DATA contains a pointer to + the data structure for the menu item, or null in case of + unhighlighting. */ -static void -menubar_selection_callback (widget, id, client_data) +#ifdef USE_GTK +void +menu_highlight_callback (widget, call_data) + GtkWidget *widget; + gpointer call_data; +{ + xg_menu_item_cb_data *cb_data; + Lisp_Object help; + + cb_data = (xg_menu_item_cb_data*) g_object_get_data (G_OBJECT (widget), + XG_ITEM_DATA); + if (! cb_data) return; + + help = call_data ? cb_data->help : Qnil; + + /* If popup_activated_flag is greater than 1 we are in a popup menu. + Don't show help for them, they won't appear before the + popup is popped down. */ + if (popup_activated_flag <= 1) + show_help_event (cb_data->cl_data->f, widget, help); +} +#else +void +menu_highlight_callback (widget, id, call_data) Widget widget; LWLIB_ID id; - XtPointer client_data; + void *call_data; +{ + struct frame *f; + Lisp_Object help; + + widget_value *wv = (widget_value *) call_data; + + help = wv ? wv->help : Qnil; + + /* Determine the frame for the help event. */ + f = menubar_id_to_frame (id); + + show_help_event (f, widget, help); +} +#endif + +/* 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 +find_and_call_menu_selection (f, menu_bar_items_used, vector, client_data) + FRAME_PTR f; + int menu_bar_items_used; + Lisp_Object vector; + void *client_data; { Lisp_Object prefix, entry; - FRAME_PTR f = menubar_id_to_frame (id); - Lisp_Object vector; Lisp_Object *subprefix_stack; int submenu_depth = 0; int i; - if (!f) - return; entry = Qnil; - subprefix_stack = (Lisp_Object *) alloca (f->menu_bar_items_used * sizeof (Lisp_Object)); - vector = f->menu_bar_vector; + subprefix_stack = (Lisp_Object *) alloca (menu_bar_items_used * sizeof (Lisp_Object)); prefix = Qnil; i = 0; - while (i < f->menu_bar_items_used) + + while (i < menu_bar_items_used) { if (EQ (XVECTOR (vector)->contents[i], Qnil)) { @@ -1268,6 +1492,7 @@ menubar_selection_callback (widget, id, client_data) int j; struct input_event buf; Lisp_Object frame; + EVENT_INIT (buf); XSETFRAME (frame, f); buf.kind = MENU_BAR_EVENT; @@ -1304,27 +1529,80 @@ menubar_selection_callback (widget, id, client_data) } } -/* Allocate a widget_value, blocking input. */ -widget_value * -xmalloc_widget_value () +#ifdef USE_GTK +/* Gtk calls callbacks just because we tell it what item should be + selected in a radio group. If this variable is set to a non-zero + value, we are creating menus and don't want callbacks right now. +*/ +static int xg_crazy_callback_abort; + +/* This callback is called from the menu bar pulldown menu + when the user makes a selection. + Figure out what the user chose + and put the appropriate events into the keyboard buffer. */ +static void +menubar_selection_callback (widget, client_data) + GtkWidget *widget; + gpointer client_data; { - widget_value *value; + xg_menu_item_cb_data *cb_data = (xg_menu_item_cb_data*) client_data; - BLOCK_INPUT; - value = malloc_widget_value (); - UNBLOCK_INPUT; + if (xg_crazy_callback_abort) + return; - return value; + if (! cb_data || ! cb_data->cl_data || ! cb_data->cl_data->f) + return; + + 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, + cb_data->call_data); } -/* This recursively calls free_widget_value on the tree of widgets. - It must free all data that was malloc'ed for these widget_values. - In Emacs, many slots are pointers into the data of Lisp_Strings, and - must be left alone. */ +#else /* not USE_GTK */ -void -free_menubar_widget_value_tree (wv) +/* This callback is called from the menu bar pulldown menu + when the user makes a selection. + Figure out what the user chose + and put the appropriate events into the keyboard buffer. */ +static void +menubar_selection_callback (widget, id, client_data) + Widget widget; + LWLIB_ID id; + XtPointer client_data; +{ + FRAME_PTR f; + + f = menubar_id_to_frame (id); + if (!f) + return; + find_and_call_menu_selection (f, f->menu_bar_items_used, + f->menu_bar_vector, client_data); +} +#endif /* not USE_GTK */ + +/* Allocate a widget_value, blocking input. */ + +widget_value * +xmalloc_widget_value () +{ + widget_value *value; + + BLOCK_INPUT; + value = malloc_widget_value (); + UNBLOCK_INPUT; + + return value; +} + +/* This recursively calls free_widget_value on the tree of widgets. + It must free all data that was malloc'ed for these widget_values. + In Emacs, many slots are pointers into the data of Lisp_Strings, and + must be left alone. */ + +void +free_menubar_widget_value_tree (wv) widget_value *wv; { if (! wv) return; @@ -1346,22 +1624,18 @@ free_menubar_widget_value_tree (wv) UNBLOCK_INPUT; } -/* Return a tree of widget_value structures 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. */ -static widget_value * -single_submenu (item_key, item_name, maps) +static int +parse_single_submenu (item_key, item_name, maps) Lisp_Object item_key, item_name, maps; { - widget_value *wv, *prev_wv, *save_wv, *first_wv; - int i; - int submenu_depth = 0; Lisp_Object length; int len; Lisp_Object *mapvec; - widget_value **submenu_stack; - int previous_items = menu_items_used; + int i; int top_level_items = 0; length = Flength (maps); @@ -1375,14 +1649,11 @@ single_submenu (item_key, item_name, maps) maps = Fcdr (maps); } - menu_items_n_panes = 0; - /* Loop over the given keymaps, making a pane for each map. 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. */ @@ -1392,11 +1663,30 @@ 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); + } } - /* Create a tree of widget_value objects - representing the panes and their items. */ + return top_level_items; +} + +/* Create a tree of widget_value objects + representing the panes and items + in menu_items starting at index START, up to index END. */ + +static widget_value * +digest_single_submenu (start, end, top_level_items) + int start, end, top_level_items; +{ + widget_value *wv, *prev_wv, *save_wv, *first_wv; + int i; + int submenu_depth = 0; + widget_value **submenu_stack; submenu_stack = (widget_value **) alloca (menu_items_used * sizeof (widget_value *)); @@ -1405,16 +1695,17 @@ single_submenu (item_key, item_name, maps) wv->value = 0; wv->enabled = 1; wv->button_type = BUTTON_TYPE_NONE; + wv->help = Qnil; first_wv = wv; 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. */ - i = previous_items; - while (i < menu_items_used) + + /* 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) { if (EQ (XVECTOR (menu_items)->contents[i], Qnil)) { @@ -1441,10 +1732,10 @@ single_submenu (item_key, item_name, maps) /* Create a new pane. */ Lisp_Object pane_name, prefix; char *pane_string; - + pane_name = XVECTOR (menu_items)->contents[i + MENU_ITEMS_PANE_NAME]; prefix = XVECTOR (menu_items)->contents[i + MENU_ITEMS_PANE_PREFIX]; - + #ifndef HAVE_MULTILINGUAL_MENU if (STRINGP (pane_name) && STRING_MULTIBYTE (pane_name)) { @@ -1453,7 +1744,7 @@ single_submenu (item_key, item_name, maps) } #endif pane_string = (NILP (pane_name) - ? "" : (char *) XSTRING (pane_name)->data); + ? "" : (char *) SDATA (pane_name)); /* If there is just one top-level pane, put all its items directly under the top-level menu. */ if (menu_items_n_panes == 1) @@ -1469,14 +1760,12 @@ single_submenu (item_key, item_name, maps) save_wv->next = wv; else first_wv->contents = wv; - wv->name = pane_string; - /* Ignore the @ that means "separate pane". - This is a kludge, but this isn't worth more time. */ - if (!NILP (prefix) && wv->name[0] == '@') - wv->name++; - wv->value = 0; + wv->lname = pane_name; + /* Set value to 1 so update_submenu_strings can handle '@' */ + wv->value = (char *)1; wv->enabled = 1; wv->button_type = BUTTON_TYPE_NONE; + wv->help = Qnil; } save_wv = wv; prev_wv = 0; @@ -1487,7 +1776,7 @@ single_submenu (item_key, item_name, maps) /* Create a new item within current pane. */ Lisp_Object item_name, enable, descrip, def, type, selected; Lisp_Object help; - + item_name = AREF (menu_items, i + MENU_ITEMS_ITEM_NAME); enable = AREF (menu_items, i + MENU_ITEMS_ITEM_ENABLE); descrip = AREF (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY); @@ -1499,32 +1788,32 @@ single_submenu (item_key, item_name, maps) #ifndef HAVE_MULTILINGUAL_MENU if (STRING_MULTIBYTE (item_name)) { - item_name = ENCODE_SYSTEM (item_name); + item_name = ENCODE_MENU_STRING (item_name); AREF (menu_items, i + MENU_ITEMS_ITEM_NAME) = item_name; } - + if (STRINGP (descrip) && STRING_MULTIBYTE (descrip)) { - descrip = ENCODE_SYSTEM (descrip); + descrip = ENCODE_MENU_STRING (descrip); AREF (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY) = descrip; } #endif /* not HAVE_MULTILINGUAL_MENU */ wv = xmalloc_widget_value (); - if (prev_wv) + if (prev_wv) prev_wv->next = wv; else save_wv->contents = wv; - wv->name = (char *) XSTRING (item_name)->data; + wv->lname = item_name; if (!NILP (descrip)) - wv->key = (char *) XSTRING (descrip)->data; + wv->lkey = descrip; wv->value = 0; /* The EMACS_INT cast avoids a warning. There's no problem as long as pointers have enough bits to hold small integers. */ wv->call_data = (!NILP (def) ? (void *) (EMACS_INT) i : 0); wv->enabled = !NILP (enable); - + if (NILP (type)) wv->button_type = BUTTON_TYPE_NONE; else if (EQ (type, QCradio)) @@ -1535,10 +1824,10 @@ single_submenu (item_key, item_name, maps) abort (); wv->selected = !NILP (selected); - if (STRINGP (help)) - wv->help = (char *) XSTRING (help)->data; - else - wv->help = NULL; + if (! STRINGP (help)) + help = Qnil; + + wv->help = help; prev_wv = wv; @@ -1558,6 +1847,40 @@ single_submenu (item_key, item_name, maps) return first_wv; } +/* 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 + until no GC can happen before storing pointers into lisp values. */ +static void +update_submenu_strings (first_wv) + widget_value *first_wv; +{ + widget_value *wv; + + for (wv = first_wv; wv; wv = wv->next) + { + if (STRINGP (wv->lname)) + { + wv->name = SDATA (wv->lname); + + /* Ignore the @ that means "separate pane". + This is a kludge, but this isn't worth more time. */ + if (wv->value == (char *)1) + { + if (wv->name[0] == '@') + wv->name++; + wv->value = 0; + } + } + + if (STRINGP (wv->lkey)) + wv->key = SDATA (wv->lkey); + + if (wv->contents) + update_submenu_strings (wv->contents); + } +} /* Recompute all the widgets of frame F, when the menu bar has been @@ -1567,17 +1890,20 @@ static int update_frame_menubar (f) FRAME_PTR f; { +#ifdef USE_GTK + return xg_update_frame_menubar (f); +#else struct x_output *x = f->output_data.x; int columns, rows; - + if (!x->menubar_widget || XtIsManaged (x->menubar_widget)) return 0; BLOCK_INPUT; /* Save the size of the frame because the pane widget doesn't accept to resize itself. So force it. */ - columns = f->width; - rows = f->height; + columns = FRAME_COLS (f); + rows = FRAME_LINES (f); /* Do the voodoo which means "I'm changing lots of things, don't try to refigure sizes until I'm done." */ @@ -1601,6 +1927,7 @@ update_frame_menubar (f) /* Force the pane widget to resize itself with the right values. */ EmacsFrameSetCharSize (x->edit_widget, columns, rows); UNBLOCK_INPUT; +#endif return 1; } @@ -1614,18 +1941,24 @@ set_frame_menubar (f, first_time, deep_p) int first_time; int deep_p; { - Widget menubar_widget = f->output_data.x->menubar_widget; + xt_or_gtk_widget menubar_widget = f->output_data.x->menubar_widget; +#ifdef USE_X_TOOLKIT + LWLIB_ID id; +#endif Lisp_Object items; widget_value *wv, *first_wv, *prev_wv = 0; - int i; + int i, last_i = 0; + int *submenu_start, *submenu_end; + int *submenu_top_level_items, *submenu_n_panes; - LWLIB_ID id; XSETFRAME (Vmenu_updating_frame, f); +#ifdef USE_X_TOOLKIT if (f->output_data.x->id == 0) f->output_data.x->id = next_menubar_widget_id++; id = f->output_data.x->id; +#endif if (! menubar_widget) deep_p = 1; @@ -1639,12 +1972,11 @@ set_frame_menubar (f, first_time, deep_p) f->output_data.x->saved_menu_event->type = 0; } - wv = xmalloc_widget_value (); - wv->name = "menubar"; - wv->value = 0; - wv->enabled = 1; - wv->button_type = BUTTON_TYPE_NONE; - first_wv = wv; +#ifdef USE_GTK + /* If we have detached menus, we must update deep so detached menus + also gets updated. */ + deep_p = deep_p || xg_have_tear_offs (); +#endif if (deep_p) { @@ -1652,7 +1984,7 @@ set_frame_menubar (f, first_time, deep_p) struct buffer *prev = current_buffer; Lisp_Object buffer; - int specpdl_count = specpdl_ptr - specpdl; + int specpdl_count = SPECPDL_INDEX (); int previous_menu_items_used = f->menu_bar_items_used; Lisp_Object *previous_items = (Lisp_Object *) alloca (previous_menu_items_used @@ -1670,6 +2002,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); @@ -1680,7 +2013,7 @@ set_frame_menubar (f, first_time, deep_p) /* Run the Lucid hook. */ safe_run_hooks (Qactivate_menubar_hook); - + /* If it has changed current-menubar from previous value, really recompute the menubar from the value. */ if (! NILP (Vlucid_menu_bar_dirty_flag)) @@ -1690,29 +2023,62 @@ set_frame_menubar (f, first_time, deep_p) items = FRAME_MENU_BAR_ITEMS (f); - inhibit_garbage_collection (); - /* Save the frame's previous menu bar contents data. */ if (previous_menu_items_used) bcopy (XVECTOR (f->menu_bar_vector)->contents, previous_items, previous_menu_items_used * sizeof (Lisp_Object)); - /* Fill in the current menu bar contents. */ + /* Fill in menu_items with the current menu bar contents. + This can evaluate Lisp code. */ menu_items = f->menu_bar_vector; 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 (); for (i = 0; i < XVECTOR (items)->size; i += 4) { Lisp_Object key, string, maps; + last_i = i; + key = XVECTOR (items)->contents[i]; string = XVECTOR (items)->contents[i + 1]; maps = XVECTOR (items)->contents[i + 2]; if (NILP (string)) break; - wv = single_submenu (key, string, maps); - if (prev_wv) + submenu_start[i] = menu_items_used; + + 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; + } + + finish_menu_items (); + + /* Convert menu_items into widget_value trees + to display the menu. This cannot evaluate Lisp code. */ + + wv = xmalloc_widget_value (); + wv->name = "menubar"; + wv->value = 0; + wv->enabled = 1; + wv->button_type = BUTTON_TYPE_NONE; + wv->help = Qnil; + first_wv = wv; + + 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) prev_wv->next = wv; else first_wv->contents = wv; @@ -1722,8 +2088,6 @@ set_frame_menubar (f, first_time, deep_p) prev_wv = wv; } - finish_menu_items (); - set_buffer_internal_1 (prev); unbind_to (specpdl_count, Qnil); @@ -1737,7 +2101,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; } @@ -1750,20 +2114,29 @@ set_frame_menubar (f, first_time, deep_p) Lisp_Object string; string = XVECTOR (items)->contents[i + 1]; if (NILP (string)) - break; - wv->name = (char *) XSTRING (string)->data; - wv = wv->next; + break; + wv->name = (char *) SDATA (string); + update_submenu_strings (wv->contents); + wv = wv->next; } f->menu_bar_vector = menu_items; f->menu_bar_items_used = menu_items_used; - menu_items = Qnil; + discard_menu_items (); } else { /* Make a widget-value tree containing just the top level menu bar strings. */ + wv = xmalloc_widget_value (); + wv->name = "menubar"; + wv->value = 0; + wv->enabled = 1; + wv->button_type = BUTTON_TYPE_NONE; + wv->help = Qnil; + first_wv = wv; + items = FRAME_MENU_BAR_ITEMS (f); for (i = 0; i < XVECTOR (items)->size; i += 4) { @@ -1774,17 +2147,18 @@ set_frame_menubar (f, first_time, deep_p) break; wv = xmalloc_widget_value (); - wv->name = (char *) XSTRING (string)->data; + wv->name = (char *) SDATA (string); wv->value = 0; wv->enabled = 1; wv->button_type = BUTTON_TYPE_NONE; + wv->help = Qnil; /* This prevents lwlib from assuming this menu item is really supposed to be empty. */ /* The EMACS_INT cast avoids a warning. This value just has to be different from small integers. */ wv->call_data = (void *) (EMACS_INT) (-1); - if (prev_wv) + if (prev_wv) prev_wv->next = wv; else first_wv->contents = wv; @@ -1801,6 +2175,35 @@ set_frame_menubar (f, first_time, deep_p) BLOCK_INPUT; +#ifdef USE_GTK + xg_crazy_callback_abort = 1; + if (menubar_widget) + { + /* The fourth arg is DEEP_P, which says to consider the entire + menu trees we supply, rather than just the menu bar item names. */ + xg_modify_menubar_widgets (menubar_widget, + f, + first_wv, + deep_p, + G_CALLBACK (menubar_selection_callback), + G_CALLBACK (popup_deactivate_callback), + G_CALLBACK (menu_highlight_callback)); + } + else + { + GtkWidget *wvbox = f->output_data.x->vbox_widget; + + menubar_widget + = xg_create_widget ("menubar", "menubar", f, first_wv, + G_CALLBACK (menubar_selection_callback), + G_CALLBACK (popup_deactivate_callback), + G_CALLBACK (menu_highlight_callback)); + + f->output_data.x->menubar_widget = menubar_widget; + } + + +#else /* not USE_GTK */ if (menubar_widget) { /* Disable resizing (done for Motif!) */ @@ -1815,7 +2218,10 @@ set_frame_menubar (f, first_time, deep_p) } else { - menubar_widget = lw_create_widget ("menubar", "menubar", id, first_wv, + char menuOverride[] = "Ctrlg: MenuGadgetEscape()"; + XtTranslations override = XtParseTranslationTable (menuOverride); + + menubar_widget = lw_create_widget ("menubar", "menubar", id, first_wv, f->output_data.x->column_widget, 0, popup_activate_callback, @@ -1823,10 +2229,13 @@ 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); } { - int menubar_size + int menubar_size = (f->output_data.x->menubar_widget ? (f->output_data.x->menubar_widget->core.height + f->output_data.x->menubar_widget->core.border_width) @@ -1847,10 +2256,15 @@ set_frame_menubar (f, first_time, deep_p) f->output_data.x->menubar_height = menubar_size; } - +#endif /* not USE_GTK */ + free_menubar_widget_value_tree (first_wv); update_frame_menubar (f); +#ifdef USE_GTK + xg_crazy_callback_abort = 0; +#endif + UNBLOCK_INPUT; } @@ -1871,8 +2285,10 @@ initialize_frame_menubar (f) /* Get rid of the menu bar of frame F, and free its storage. - This is used when deleting a frame, and when turning off the menu bar. */ + This is used when deleting a frame, and when turning off the menu bar. + For GTK this function is in gtkutil.c. */ +#ifndef USE_GTK void free_frame_menubar (f) FRAME_PTR f; @@ -1882,7 +2298,7 @@ free_frame_menubar (f) menubar_widget = f->output_data.x->menubar_widget; f->output_data.x->menubar_height = 0; - + if (menubar_widget) { #ifdef USE_MOTIF @@ -1896,14 +2312,14 @@ free_frame_menubar (f) is switched off, which fixes the shell position. */ Position x0, y0, x1, y1; #endif - + BLOCK_INPUT; #ifdef USE_MOTIF if (f->output_data.x->widget) XtVaGetValues (f->output_data.x->widget, XtNx, &x0, XtNy, &y0, NULL); #endif - + lw_destroy_all_widgets ((LWLIB_ID) f->output_data.x->id); f->output_data.x->menubar_widget = NULL; @@ -1915,12 +2331,13 @@ free_frame_menubar (f) XtVaSetValues (f->output_data.x->widget, XtNx, x0, XtNy, y0, NULL); } #endif - + UNBLOCK_INPUT; } } +#endif /* not USE_GTK */ -#endif /* USE_X_TOOLKIT */ +#endif /* USE_X_TOOLKIT || USE_GTK */ /* xmenu_show actually displays a menu using the panes and items in menu_items and returns the value selected from it. @@ -1938,7 +2355,143 @@ free_frame_menubar (f) ERROR is a place to store an error message string in case of failure. (We return nil on failure, but the value doesn't actually matter.) */ -#ifdef USE_X_TOOLKIT +#if defined (USE_X_TOOLKIT) || defined (USE_GTK) + +/* The item selected in the popup menu. */ +static Lisp_Object *volatile menu_item_selection; + +#ifdef USE_GTK + +/* Used when position a popup menu. See menu_position_func and + create_and_show_popup_menu below. */ +struct next_popup_x_y +{ + FRAME_PTR f; + int x; + int y; +}; + +/* The menu position function to use if we are not putting a popup + menu where the pointer is. + MENU is the menu to pop up. + X and Y shall on exit contain x/y where the menu shall pop up. + PUSH_IN is not documented in the GTK manual. + USER_DATA is any data passed in when calling gtk_menu_popup. + Here it points to a struct next_popup_x_y where the coordinates + to store in *X and *Y are as well as the frame for the popup. + + Here only X and Y are used. */ +static void +menu_position_func (menu, x, y, push_in, user_data) + GtkMenu *menu; + gint *x; + gint *y; + gboolean *push_in; + gpointer user_data; +{ + struct next_popup_x_y* data = (struct next_popup_x_y*)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; + + /* Check if there is room for the menu. If not, adjust x/y so that + the menu is fully visible. */ + gtk_widget_size_request (GTK_WIDGET (menu), &req); + if (data->x + req.width > disp_width) + *x -= data->x + req.width - disp_width; + if (data->y + req.height > disp_height) + *y -= data->y + req.height - disp_height; +} + +static void +popup_selection_callback (widget, client_data) + GtkWidget *widget; + gpointer client_data; +{ + xg_menu_item_cb_data *cb_data = (xg_menu_item_cb_data*) client_data; + + if (xg_crazy_callback_abort) return; + 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. */ +static void +create_and_show_popup_menu (f, first_wv, x, y, for_click) + FRAME_PTR f; + widget_value *first_wv; + int x; + int y; + int for_click; +{ + int i; + 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, + G_CALLBACK (popup_selection_callback), + G_CALLBACK (popup_deactivate_callback), + 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. */ + pos_func = menu_position_func; + + /* Adjust coordinates to be root-window-relative. */ + x += f->left_pos + FRAME_OUTER_TO_INNER_DIFF_X (f); + y += f->top_pos + FRAME_OUTER_TO_INNER_DIFF_Y (f); + + popup_x_y.x = x; + popup_x_y.y = y; + popup_x_y.f = f; + } + + /* 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 (1, menu); + + unbind_to (specpdl_count, Qnil); + + /* Must reset this manually because the button release event is not passed + to Emacs event loop. */ + FRAME_X_DISPLAY_INFO (f)->grabbed = 0; +} + +#else /* not USE_GTK */ /* We need a unique id for each widget handled by the Lucid Widget library. @@ -1950,8 +2503,6 @@ free_frame_menubar (f) next_menubar_widget_id. */ LWLIB_ID widget_id_tick; -static Lisp_Object *volatile menu_item_selection; - static void popup_selection_callback (widget, id, client_data) Widget widget; @@ -1961,6 +2512,97 @@ 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. */ +static void +create_and_show_popup_menu (f, first_wv, x, y, for_click) + FRAME_PTR f; + widget_value *first_wv; + int x; + int y; + int for_click; +{ + int i; + Arg av[2]; + int ac = 0; + XButtonPressedEvent dummy; + LWLIB_ID menu_id; + Widget menu; + + menu_id = widget_id_tick++; + menu = lw_create_widget ("popup", first_wv->name, menu_id, first_wv, + f->output_data.x->widget, 1, 0, + popup_selection_callback, + popup_deactivate_callback, + menu_highlight_callback); + + dummy.type = ButtonPress; + dummy.serial = 0; + dummy.send_event = 0; + dummy.display = FRAME_X_DISPLAY (f); + dummy.time = CurrentTime; + dummy.root = FRAME_X_DISPLAY_INFO (f)->root_window; + dummy.window = dummy.root; + dummy.subwindow = dummy.root; + dummy.x = x; + dummy.y = y; + + /* Adjust coordinates to be root-window-relative. */ + x += f->left_pos + FRAME_OUTER_TO_INNER_DIFF_X (f); + y += f->top_pos + FRAME_OUTER_TO_INNER_DIFF_Y (f); + + dummy.x_root = x; + dummy.y_root = y; + + dummy.state = 0; + dummy.button = 0; + for (i = 0; i < 5; i++) + if (FRAME_X_DISPLAY_INFO (f)->grabbed & (1 << i)) + dummy.button = i; + + /* Don't allow any geometry request from the user. */ + XtSetArg (av[ac], XtNgeometry, 0); ac++; + XtSetValues (menu, av, ac); + + /* 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, 1); + + unbind_to (specpdl_count, Qnil); + } +} + +#endif /* not USE_GTK */ + static Lisp_Object xmenu_show (f, x, y, for_click, keymaps, title, error) FRAME_PTR f; @@ -1972,17 +2614,12 @@ xmenu_show (f, x, y, for_click, keymaps, title, error) char **error; { int i; - LWLIB_ID menu_id; - Widget menu; - Arg av[2]; - int ac = 0; widget_value *wv, *save_wv = 0, *first_wv = 0, *prev_wv = 0; widget_value **submenu_stack = (widget_value **) alloca (menu_items_used * sizeof (widget_value *)); Lisp_Object *subprefix_stack = (Lisp_Object *) alloca (menu_items_used * sizeof (Lisp_Object)); int submenu_depth = 0; - XButtonPressedEvent dummy; int first_pane; @@ -2001,9 +2638,10 @@ xmenu_show (f, x, y, for_click, keymaps, title, error) wv->value = 0; wv->enabled = 1; wv->button_type = BUTTON_TYPE_NONE; + wv->help =Qnil; first_wv = wv; first_pane = 1; - + /* Loop over all panes and items, filling in the tree. */ i = 0; while (i < menu_items_used) @@ -2035,10 +2673,10 @@ xmenu_show (f, x, y, for_click, keymaps, title, error) /* Create a new pane. */ Lisp_Object pane_name, prefix; char *pane_string; - + pane_name = AREF (menu_items, i + MENU_ITEMS_PANE_NAME); prefix = AREF (menu_items, i + MENU_ITEMS_PANE_PREFIX); - + #ifndef HAVE_MULTILINGUAL_MENU if (STRINGP (pane_name) && STRING_MULTIBYTE (pane_name)) { @@ -2047,7 +2685,7 @@ xmenu_show (f, x, y, for_click, keymaps, title, error) } #endif pane_string = (NILP (pane_name) - ? "" : (char *) XSTRING (pane_name)->data); + ? "" : (char *) SDATA (pane_name)); /* If there is just one top-level pane, put all its items directly under the top-level menu. */ if (menu_items_n_panes == 1) @@ -2069,6 +2707,7 @@ xmenu_show (f, x, y, for_click, keymaps, title, error) wv->value = 0; wv->enabled = 1; wv->button_type = BUTTON_TYPE_NONE; + wv->help = Qnil; save_wv = wv; prev_wv = 0; } @@ -2095,25 +2734,25 @@ xmenu_show (f, x, y, for_click, keymaps, title, error) #ifndef HAVE_MULTILINGUAL_MENU if (STRINGP (item_name) && STRING_MULTIBYTE (item_name)) { - item_name = ENCODE_SYSTEM (item_name); + item_name = ENCODE_MENU_STRING (item_name); AREF (menu_items, i + MENU_ITEMS_ITEM_NAME) = item_name; } - + if (STRINGP (descrip) && STRING_MULTIBYTE (descrip)) { - descrip = ENCODE_SYSTEM (descrip); + descrip = ENCODE_MENU_STRING (descrip); AREF (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY) = descrip; } #endif /* not HAVE_MULTILINGUAL_MENU */ - + wv = xmalloc_widget_value (); - if (prev_wv) + if (prev_wv) prev_wv->next = wv; - else + else save_wv->contents = wv; - wv->name = (char *) XSTRING (item_name)->data; + wv->name = (char *) SDATA (item_name); if (!NILP (descrip)) - wv->key = (char *) XSTRING (descrip)->data; + wv->key = (char *) SDATA (descrip); wv->value = 0; /* If this item has a null value, make the call_data null so that it won't display a box @@ -2133,10 +2772,10 @@ xmenu_show (f, x, y, for_click, keymaps, title, error) wv->selected = !NILP (selected); - if (STRINGP (help)) - wv->help = (char *) XSTRING (help)->data; - else - wv->help = NULL; + if (! STRINGP (help)) + help = Qnil; + + wv->help = help; prev_wv = wv; @@ -2153,101 +2792,33 @@ xmenu_show (f, x, y, for_click, keymaps, title, error) wv_sep2->name = "--"; wv_sep2->next = first_wv->contents; + wv_sep2->help = Qnil; wv_sep1->name = "--"; wv_sep1->next = wv_sep2; + wv_sep1->help = Qnil; #ifndef HAVE_MULTILINGUAL_MENU if (STRING_MULTIBYTE (title)) - title = ENCODE_SYSTEM (title); + title = ENCODE_MENU_STRING (title); #endif - - wv_title->name = (char *) XSTRING (title)->data; + + wv_title->name = (char *) SDATA (title); wv_title->enabled = TRUE; wv_title->button_type = BUTTON_TYPE_NONE; wv_title->next = wv_sep1; + wv_title->help = Qnil; first_wv->contents = wv_title; } - /* Actually create the menu. */ - menu_id = widget_id_tick++; - menu = lw_create_widget ("popup", first_wv->name, menu_id, first_wv, - f->output_data.x->widget, 1, 0, - popup_selection_callback, - popup_deactivate_callback, - menu_highlight_callback); - - /* 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; - } - } - - /* Adjust coordinates to be root-window-relative. */ - x += f->output_data.x->left_pos; - y += f->output_data.x->top_pos; - - dummy.type = ButtonPress; - dummy.serial = 0; - dummy.send_event = 0; - dummy.display = FRAME_X_DISPLAY (f); - dummy.time = CurrentTime; - dummy.root = FRAME_X_DISPLAY_INFO (f)->root_window; - dummy.window = dummy.root; - dummy.subwindow = dummy.root; - dummy.x_root = x; - dummy.y_root = y; - dummy.x = x; - dummy.y = y; - dummy.state = (FRAME_X_DISPLAY_INFO (f)->grabbed >> 1) * Button1Mask; - dummy.button = 0; - for (i = 0; i < 5; i++) - if (FRAME_X_DISPLAY_INFO (f)->grabbed & (1 << i)) - dummy.button = i; - - /* 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); - /* No selection has been chosen yet. */ menu_item_selection = 0; - /* Display the menu. */ - lw_popup_menu (menu, (XEvent *) &dummy); - popup_activated_flag = 1; - - /* Process events that apply to the menu. */ - popup_get_selection ((XEvent *) 0, FRAME_X_DISPLAY_INFO (f), menu_id); + /* Actually create and show the menu until popped down. */ + create_and_show_popup_menu (f, first_wv, x, y, for_click); - /* 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); + /* Free the widget_value objects we used to specify the contents. */ + free_menubar_widget_value_tree (first_wv); /* Find the selected item, and its pane, to return the proper value. */ @@ -2307,6 +2878,51 @@ xmenu_show (f, x, y, for_click, keymaps, title, error) return Qnil; } +#ifdef USE_GTK +static void +dialog_selection_callback (widget, client_data) + GtkWidget *widget; + gpointer client_data; +{ + /* The EMACS_INT cast avoids a warning. There's no problem + as long as pointers have enough bits to hold small integers. */ + if ((int) (EMACS_INT) client_data != -1) + menu_item_selection = (Lisp_Object *) client_data; + + popup_activated_flag = 0; +} + +/* 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. */ +static void +create_and_show_dialog (f, first_wv) + FRAME_PTR f; + widget_value *first_wv; +{ + GtkWidget *menu; + + menu = xg_create_widget ("dialog", first_wv->name, f, first_wv, + G_CALLBACK (dialog_selection_callback), + G_CALLBACK (popup_deactivate_callback), + 0); + + 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 (1, menu); + + unbind_to (specpdl_count, Qnil); + } +} + +#else /* not USE_GTK */ static void dialog_selection_callback (widget, id, client_data) Widget widget; @@ -2317,12 +2933,54 @@ dialog_selection_callback (widget, id, client_data) as long as pointers have enough bits to hold small integers. */ if ((int) (EMACS_INT) client_data != -1) menu_item_selection = (Lisp_Object *) client_data; + BLOCK_INPUT; lw_destroy_all_widgets (id); UNBLOCK_INPUT; popup_activated_flag = 0; } + +/* 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. */ +static void +create_and_show_dialog (f, first_wv) + FRAME_PTR f; + widget_value *first_wv; +{ + LWLIB_ID dialog_id; + + dialog_id = widget_id_tick++; + 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); + + /* Display the dialog box. */ + lw_pop_up_all_widgets (dialog_id); + popup_activated_flag = 1; + + /* Process events that apply to the dialog box. + Also handle timers. */ + { + int count = SPECPDL_INDEX (); + int fact = 4 * sizeof (LWLIB_ID); + + /* xdialog_show_unwind is responsible for popping the dialog box down. */ + 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); + + unbind_to (count, Qnil); + } +} + +#endif /* not USE_GTK */ + static char * button_names [] = { "button1", "button2", "button3", "button4", "button5", "button6", "button7", "button8", "button9", "button10" }; @@ -2335,8 +2993,6 @@ xdialog_show (f, keymaps, title, error) char **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; @@ -2362,27 +3018,28 @@ xdialog_show (f, keymaps, title, error) pane_name = XVECTOR (menu_items)->contents[MENU_ITEMS_PANE_NAME]; prefix = XVECTOR (menu_items)->contents[MENU_ITEMS_PANE_PREFIX]; pane_string = (NILP (pane_name) - ? "" : (char *) XSTRING (pane_name)->data); + ? "" : (char *) SDATA (pane_name)); prev_wv = xmalloc_widget_value (); prev_wv->value = pane_string; if (keymaps && !NILP (prefix)) prev_wv->name++; prev_wv->enabled = 1; prev_wv->name = "message"; + prev_wv->help = Qnil; first_wv = prev_wv; - + /* Loop over all panes and items, filling in the tree. */ i = MENU_ITEMS_PANE_LENGTH; while (i < menu_items_used) { - + /* Create a new item within current pane. */ Lisp_Object item_name, enable, descrip; item_name = XVECTOR (menu_items)->contents[i + MENU_ITEMS_ITEM_NAME]; enable = XVECTOR (menu_items)->contents[i + MENU_ITEMS_ITEM_ENABLE]; descrip = XVECTOR (menu_items)->contents[i + MENU_ITEMS_ITEM_EQUIV_KEY]; - + if (NILP (item_name)) { free_menubar_widget_value_tree (first_wv); @@ -2408,10 +3065,11 @@ xdialog_show (f, keymaps, title, error) prev_wv->next = wv; wv->name = (char *) button_names[nb_buttons]; if (!NILP (descrip)) - wv->key = (char *) XSTRING (descrip)->data; - wv->value = (char *) XSTRING (item_name)->data; + wv->key = (char *) SDATA (descrip); + wv->value = (char *) SDATA (item_name); wv->call_data = (void *) &XVECTOR (menu_items)->contents[i]; wv->enabled = !NILP (enable); + wv->help = Qnil; prev_wv = wv; if (! boundary_seen) @@ -2428,7 +3086,7 @@ xdialog_show (f, keymaps, title, error) wv = xmalloc_widget_value (); wv->name = dialog_name; - + wv->help = Qnil; /* Dialog boxes use a really stupid name encoding which specifies how many buttons to use and how many buttons are on the right. @@ -2444,26 +3102,14 @@ xdialog_show (f, keymaps, title, error) first_wv = wv; } - /* 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_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); - /* No selection has been chosen yet. */ menu_item_selection = 0; - /* Display the menu. */ - 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); + /* Actually create and show the dialog. */ + create_and_show_dialog (f, first_wv); - lw_destroy_all_widgets (dialog_id); + /* Free the widget_value objects we used to specify the contents. */ + free_menubar_widget_value_tree (first_wv); /* Find the selected item, and its pane, to return the proper value. */ @@ -2507,11 +3153,14 @@ xdialog_show (f, keymaps, title, error) } } } + else + /* Make "Cancel" equivalent to C-g. */ + Fsignal (Qquit, Qnil); return Qnil; } -#else /* not USE_X_TOOLKIT */ +#else /* not USE_X_TOOLKIT && not USE_GTK */ /* The frame of the last activated non-toolkit menu bar. Used to generate menu help events. */ @@ -2523,7 +3172,7 @@ static struct frame *menu_help_frame; PANE is the pane number, and ITEM is the menu item number in the menu (currently not used). - + This cannot be done with generating a HELP_EVENT because XMenuActivate contains a loop that doesn't let Emacs process keyboard events. */ @@ -2537,7 +3186,7 @@ menu_help_callback (help_string, pane, item) Lisp_Object *first_item; Lisp_Object pane_name; Lisp_Object menu_object; - + first_item = XVECTOR (menu_items)->contents; if (EQ (first_item[0], Qt)) pane_name = first_item[MENU_ITEMS_PANE_NAME]; @@ -2546,7 +3195,7 @@ menu_help_callback (help_string, pane, item) pane_name = empty_string; else pane_name = first_item[MENU_ITEMS_ITEM_NAME]; - + /* (menu-item MENU-NAME PANE-NUMBER) */ menu_object = Fcons (Qmenu_item, Fcons (pane_name, @@ -2554,7 +3203,42 @@ menu_help_callback (help_string, pane, item) show_help_echo (help_string ? build_string (help_string) : Qnil, 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) @@ -2565,17 +3249,18 @@ xmenu_show (f, x, y, for_click, keymaps, title, error) Lisp_Object title; char **error; { - Window root; + Window root; XMenu *menu; int pane, selidx, lpane, status; Lisp_Object entry, pane_prefix; 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) @@ -2602,39 +3287,16 @@ 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. */ - x += f->output_data.x->left_pos; - y += f->output_data.x->top_pos; - + x += f->left_pos; + 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)) @@ -2643,10 +3305,12 @@ 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) - ? "" : (char *) XSTRING (pane_name)->data); + ? "" : (char *) SDATA (pane_name)); if (keymaps && !NILP (prefix)) pane_string++; @@ -2673,7 +3337,7 @@ xmenu_show (f, x, y, for_click, keymaps, title, error) j++; continue; } - width = STRING_BYTES (XSTRING (item)); + width = SBYTES (item); if (width > maxwidth) maxwidth = width; @@ -2696,34 +3360,34 @@ xmenu_show (f, x, y, for_click, keymaps, title, error) descrip = XVECTOR (menu_items)->contents[i + MENU_ITEMS_ITEM_EQUIV_KEY]; help = XVECTOR (menu_items)->contents[i + MENU_ITEMS_ITEM_HELP]; - help_string = STRINGP (help) ? XSTRING (help)->data : NULL; - + help_string = STRINGP (help) ? SDATA (help) : NULL; + if (!NILP (descrip)) { - int gap = maxwidth - STRING_BYTES (XSTRING (item_name)); + int gap = maxwidth - SBYTES (item_name); #ifdef C_ALLOCA Lisp_Object spacer; spacer = Fmake_string (make_number (gap), make_number (' ')); item_name = concat2 (item_name, spacer); item_name = concat2 (item_name, descrip); - item_data = XSTRING (item_name)->data; + item_data = SDATA (item_name); #else /* if alloca is fast, use that to make the space, to reduce gc needs. */ item_data = (unsigned char *) alloca (maxwidth - + STRING_BYTES (XSTRING (descrip)) + 1); - bcopy (XSTRING (item_name)->data, item_data, - STRING_BYTES (XSTRING (item_name))); - for (j = XSTRING (item_name)->size; j < maxwidth; j++) + + SBYTES (descrip) + 1); + bcopy (SDATA (item_name), item_data, + SBYTES (item_name)); + for (j = SCHARS (item_name); j < maxwidth; j++) item_data[j] = ' '; - bcopy (XSTRING (descrip)->data, item_data + j, - STRING_BYTES (XSTRING (descrip))); - item_data[j + STRING_BYTES (XSTRING (descrip))] = 0; + bcopy (SDATA (descrip), item_data + j, + SBYTES (descrip)); + item_data[j + SBYTES (descrip)] = 0; #endif } else - item_data = XSTRING (item_name)->data; + item_data = SDATA (item_name); if (XMenuAddSelection (FRAME_X_DISPLAY (f), menu, lpane, 0, item_data, @@ -2735,9 +3399,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)); @@ -2761,23 +3428,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) { @@ -2824,19 +3501,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; } @@ -2850,6 +3526,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); @@ -2860,7 +3537,7 @@ The enable predicate for a menu command should check this variable. */); Vmenu_updating_frame = Qnil; #ifdef USE_X_TOOLKIT - widget_id_tick = (1<<16); + widget_id_tick = (1<<16); next_menubar_widget_id = 1; #endif @@ -2869,3 +3546,6 @@ The enable predicate for a menu command should check this variable. */); defsubr (&Sx_popup_dialog); #endif } + +/* arch-tag: 92ea573c-398e-496e-ac73-2436f7d63242 + (do not change this comment) */