X-Git-Url: http://git.hcoop.net/bpt/emacs.git/blobdiff_plain/159cf431a94443d05bd2f4bbd880975aabbdb17e..c04d9e704746e6914389ee11c1ade991f66031d5:/src/xmenu.c diff --git a/src/xmenu.c b/src/xmenu.c index 004458aded..bcc0e57bcd 100644 --- a/src/xmenu.c +++ b/src/xmenu.c @@ -1,6 +1,6 @@ /* X Communication module for terminals which understand the X protocol. - Copyright (C) 1986, 88, 93, 94, 96, 99, 2000, 2001 - Free Software Foundation, Inc. + Copyright (C) 1986, 1988, 1993, 1994, 1996, 1999, 2000, 2001, 2003, 2004, + 2005 Free Software Foundation, Inc. This file is part of GNU Emacs. @@ -16,10 +16,10 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GNU Emacs; see the file COPYING. If not, write to -the Free Software Foundation, Inc., 59 Temple Place - Suite 330, -Boston, MA 02111-1307, USA. */ +the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +Boston, MA 02110-1301, USA. */ -/* X pop-up deck-of-cards menu facility for gnuemacs. +/* X pop-up deck-of-cards menu facility for GNU Emacs. * * Written by Jon Arnold and Roman Budzianowski * Mods and rewrite by Robert Krawitz @@ -33,17 +33,24 @@ Boston, MA 02111-1307, USA. */ #include +#if 0 /* Why was this included? And without syssignal.h? */ /* On 4.3 this loses if it comes after xterm.h. */ #include +#endif #include + #include "lisp.h" #include "termhooks.h" #include "keyboard.h" +#include "keymap.h" #include "frame.h" #include "window.h" #include "blockinput.h" #include "buffer.h" +#include "charset.h" +#include "coding.h" +#include "sysselect.h" #ifdef MSDOS #include "msdos.h" @@ -64,8 +71,11 @@ 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" #include #include #include @@ -76,17 +86,12 @@ 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 */ -#ifdef USE_MOTIF -#include /* for LESSTIF_VERSION */ -#endif - -#define min(x,y) (((x) < (y)) ? (x) : (y)) -#define max(x,y) (((x) > (y)) ? (x) : (y)) - #ifndef TRUE #define TRUE 1 #define FALSE 0 @@ -97,7 +102,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; @@ -109,30 +113,52 @@ 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 (); -#endif +static Lisp_Object xdialog_show P_ ((FRAME_PTR, int, Lisp_Object, Lisp_Object, + char **)); +static void popup_get_selection P_ ((XEvent *, struct x_display_info *, + LWLIB_ID, int)); -#ifdef USE_X_TOOLKIT +/* Define HAVE_BOXES if menus can handle radio and toggle buttons. */ -/* Define HAVE_BOXES if meus 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, 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) +#elif defined HAVE_X_I18N +# define ENCODE_MENU_STRING(str) ENCODE_SYSTEM (str) +#else +# define ENCODE_MENU_STRING(str) string_make_unibyte (str) #endif static void push_menu_item P_ ((Lisp_Object, Lisp_Object, Lisp_Object, Lisp_Object, Lisp_Object, Lisp_Object, Lisp_Object, Lisp_Object)); -static Lisp_Object xmenu_show (); -static void keymap_panes (); -static void single_keymap_panes (); -static void single_menu_item (); -static void list_of_panes (); -static void list_of_items (); +static int update_frame_menubar P_ ((struct frame *)); +static Lisp_Object xmenu_show P_ ((struct frame *, int, int, int, int, + Lisp_Object, char **)); +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 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. @@ -175,6 +201,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; @@ -190,7 +220,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; @@ -242,19 +272,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. */ @@ -268,6 +307,7 @@ discard_menu_items () menu_items = Qnil; menu_items_allocated = 0; } + xassert (NILP (menu_items_inuse)); } /* Make the menu_items vector twice as large. */ @@ -320,7 +360,7 @@ push_left_right_boundary () XVECTOR (menu_items)->contents[menu_items_used++] = Qquote; } -/* Start a new menu pane in menu_items.. +/* Start a new menu pane in menu_items. NAME is the pane name. PREFIX_VEC is a prefix key for this pane. */ static void @@ -382,11 +422,22 @@ keymap_panes (keymaps, nmaps, notreal) P is the number of panes we have made so far. */ for (mapno = 0; mapno < nmaps; mapno++) single_keymap_panes (keymaps[mapno], - map_prompt (keymaps[mapno]), Qnil, notreal, 10); + Fkeymap_prompt (keymaps[mapno]), Qnil, notreal, 10); 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 @@ -404,10 +455,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; @@ -419,100 +473,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; } @@ -527,10 +558,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) @@ -553,14 +584,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. */ @@ -570,17 +601,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 @@ -592,12 +623,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 @@ -615,15 +646,15 @@ list_of_panes (menu) init_menu_items (); - for (tail = menu; !NILP (tail); tail = Fcdr (tail)) + for (tail = menu; CONSP (tail); tail = XCDR (tail)) { Lisp_Object elt, pane_name, pane_data; - elt = Fcar (tail); + elt = XCAR (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); } @@ -638,73 +669,135 @@ list_of_items (pane) { Lisp_Object tail, item, item1; - for (tail = pane; !NILP (tail); tail = Fcdr (tail)) + for (tail = pane; CONSP (tail); tail = XCDR (tail)) { - item = Fcar (tail); + item = XCAR (tail); if (STRINGP (item)) - push_menu_item (item, Qnil, Qnil, Qt, Qnil, Qnil, Qnil, Qnil); - else if (NILP (item)) - push_left_right_boundary (); - else + push_menu_item (ENCODE_MENU_STRING (item), Qnil, Qnil, Qt, + Qnil, Qnil, Qnil, Qnil); + else if (CONSP (item)) { - CHECK_CONS (item, 0); - item1 = Fcar (item); - CHECK_STRING (item1, 1); - push_menu_item (item1, Qt, Fcdr (item), Qt, Qnil, Qnil, Qnil, Qnil); + item1 = XCAR (item); + CHECK_STRING (item1); + push_menu_item (ENCODE_MENU_STRING (item1), Qt, XCDR (item), + Qt, Qnil, Qnil, Qnil, Qnil); } + else + push_left_right_boundary (); + } } +#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, - "Pop up a deck-of-cards menu and return user's selection.\n\ -POSITION is a position specification. This is either a mouse button event\n\ -or a list ((XOFFSET YOFFSET) WINDOW)\n\ -where XOFFSET and YOFFSET are positions in pixels from the top left\n\ -corner of WINDOW's frame. (WINDOW may be a frame object instead of a window.)\n\ -This controls the position of the center of the first line\n\ -in the first pane of the menu, not the top left of the menu as a whole.\n\ -If POSITION is t, it means to use the current mouse position.\n\ -\n\ -MENU is a specifier for a menu. For the simplest case, MENU is a keymap.\n\ -The menu items come from key bindings that have a menu string as well as\n\ -a definition; actually, the \"definition\" in such a key binding looks like\n\ -\(STRING . REAL-DEFINITION). To give the menu a title, put a string into\n\ -the keymap as a top-level element.\n\n\ -If REAL-DEFINITION is nil, that puts a nonselectable string in the menu.\n\ -Otherwise, REAL-DEFINITION should be a valid key binding definition.\n\ -\n\ -You can also use a list of keymaps as MENU.\n\ - Then each keymap makes a separate pane.\n\ -When MENU is a keymap or a list of keymaps, the return value\n\ -is a list of events.\n\n\ -\n\ -Alternatively, you can specify a menu of multiple panes\n\ - with a list of the form (TITLE PANE1 PANE2...),\n\ -where each pane is a list of form (TITLE ITEM1 ITEM2...).\n\ -Each ITEM is normally a cons cell (STRING . VALUE);\n\ -but a string can appear as an item--that makes a nonselectable line\n\ -in the menu.\n\ -With this form of menu, the return value is VALUE from the chosen item.\n\ -\n\ -If POSITION is nil, don't display the menu at all, just precalculate the\n\ -cached information about equivalent key sequences.") - (position, menu) + 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. (WINDOW may be a window or a frame object.) +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. +The menu items come from key bindings that have a menu string as well as +a definition; actually, the "definition" in such a key binding looks like +\(STRING . REAL-DEFINITION). To give the menu a title, put a string into +the keymap as a top-level element. + +If REAL-DEFINITION is nil, that puts a nonselectable string in the menu. +Otherwise, REAL-DEFINITION should be a valid key binding definition. + +You can also use a list of keymaps as MENU. + Then each keymap makes a separate pane. + +When MENU is a keymap or a list of keymaps, the return value is the +list of events corresponding to the user's choice. Note that +`x-popup-menu' does not actually execute the command bound to that +sequence of events. + +Alternatively, you can specify a menu of multiple panes + with a list of the form (TITLE PANE1 PANE2...), +where each pane is a list of form (TITLE ITEM1 ITEM2...). +Each ITEM is normally a cons cell (STRING . VALUE); +but a string can appear as an item--that makes a nonselectable line +in the menu. +With this form of menu, the return value is VALUE from the chosen item. + +If POSITION is nil, don't display the menu at all, just precalculate the +cached information about equivalent key sequences. + +If the user gets rid of the menu without making a valid choice, for +instance by clicking the mouse away from a valid choice or by typing +keyboard input, then this normally results in a quit and +`x-popup-menu' does not return. But if POSITION is a mouse button +event (indicating that the user invoked the menu with the mouse) then +no quit occurs and `x-popup-menu' returns nil. */) + (position, menu) Lisp_Object position, menu; { Lisp_Object keymap, tem; int xpos = 0, ypos = 0; Lisp_Object title; - char *error_name; + char *error_name = NULL; Lisp_Object selection; - struct frame *f = NULL; + FRAME_PTR f = NULL; 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,8 +805,55 @@ cached information about equivalent key sequences.") || (CONSP (position) && (EQ (XCAR (position), Qmenu_bar) || EQ (XCAR (position), Qtool_bar)))) { + get_current_pos_p = 1; + } + else + { + tem = Fcar (position); + if (CONSP (tem)) + { + window = Fcar (Fcdr (position)); + x = XCAR (tem); + y = Fcar (XCDR (tem)); + } + else + { + for_click = 1; + tem = Fcar (Fcdr (position)); /* EVENT_START (position) */ + window = Fcar (tem); /* POSN_WINDOW (tem) */ + tem = Fcar (Fcdr (Fcdr (tem))); /* POSN_WINDOW_POSN (tem) */ + 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; @@ -721,6 +861,8 @@ cached information about equivalent key sequences.") 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 @@ -730,28 +872,9 @@ cached information about equivalent key sequences.") XSETFASTINT (y, 0); } } - else - { - tem = Fcar (position); - if (CONSP (tem)) - { - window = Fcar (Fcdr (position)); - x = Fcar (tem); - y = Fcar (Fcdr (tem)); - } - else - { - for_click = 1; - tem = Fcar (Fcdr (position)); /* EVENT_START (position) */ - window = Fcar (tem); /* POSN_WINDOW (tem) */ - tem = Fcar (Fcdr (Fcdr (tem))); /* POSN_WINDOW_POSN (tem) */ - x = Fcar (tem); - y = Fcdr (tem); - } - } - CHECK_NUMBER (x, 0); - CHECK_NUMBER (y, 0); + CHECK_NUMBER (x); + CHECK_NUMBER (y); /* Decode where to put the menu. */ @@ -763,27 +886,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 (f->output_data.x->font) - * XFASTINT (XWINDOW (window)->left)); - ypos = (f->output_data.x->line_height - * 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); @@ -800,7 +920,7 @@ cached information about equivalent key sequences.") /* Search for a string appearing directly as an element of the keymap. That string is the title of the menu. */ - prompt = map_prompt (keymap); + prompt = Fkeymap_prompt (keymap); if (NILP (title) && !NILP (prompt)) title = prompt; @@ -822,13 +942,13 @@ cached information about equivalent key sequences.") /* The first keymap that has a prompt string supplies the menu title. */ - for (tem = menu, i = 0; CONSP (tem); tem = Fcdr (tem)) + for (tem = menu, i = 0; CONSP (tem); tem = XCDR (tem)) { Lisp_Object prompt; - maps[i++] = keymap = get_keymap (Fcar (tem), 1, 0); + maps[i++] = keymap = get_keymap (XCAR (tem), 1, 0); - prompt = map_prompt (keymap); + prompt = Fkeymap_prompt (keymap); if (NILP (title) && !NILP (prompt)) title = prompt; } @@ -846,13 +966,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 (); @@ -879,25 +1001,33 @@ cached information about equivalent key sequences.") #ifdef HAVE_MENUS -DEFUN ("x-popup-dialog", Fx_popup_dialog, Sx_popup_dialog, 2, 2, 0, - "Pop up a dialog box and return user's selection.\n\ -POSITION specifies which frame to use.\n\ -This is normally a mouse button event or a window or frame.\n\ -If POSITION is t, it means to use the frame the mouse is on.\n\ -The dialog box appears in the middle of the specified frame.\n\ -\n\ -CONTENTS specifies the alternatives to display in the dialog box.\n\ -It is a list of the form (TITLE ITEM1 ITEM2...).\n\ -Each ITEM is a cons cell (STRING . VALUE).\n\ -The return value is VALUE from the chosen item.\n\n\ -An ITEM may also be just a string--that makes a nonselectable item.\n\ -An ITEM may also be nil--that means to put all preceding items\n\ -on the left of the dialog box and all following items on the right.\n\ -\(By default, approximately half appear on each side.)") - (position, contents) - Lisp_Object position, contents; +DEFUN ("x-popup-dialog", Fx_popup_dialog, Sx_popup_dialog, 2, 3, 0, + 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. +The dialog box appears in the middle of the specified frame. + +CONTENTS specifies the alternatives to display in the dialog box. +It is a list of the form (DIALOG ITEM1 ITEM2...). +Each ITEM is a cons cell (STRING . VALUE). +The return value is VALUE from the chosen item. + +An ITEM may also be just a string--that makes a nonselectable item. +An ITEM may also be nil--that means to put all preceding items +on the left of the dialog box and all following items on the right. +\(By default, approximately half appear on each side.) + +If HEADER is non-nil, the frame title for the box is "Information", +otherwise it is "Question". + +If the user gets rid of the dialog box without making a valid choice, +for instance using the window manager, then this produces a quit and +`x-popup-dialog' does not return. */) + (position, contents, header) + Lisp_Object position, contents, header; { - struct frame * f = NULL; + FRAME_PTR f = NULL; Lisp_Object window; check_x (); @@ -911,7 +1041,7 @@ on the left of the dialog box and all following items on the right.\n\ /* Use the mouse's current position. */ FRAME_PTR new_f = SELECTED_FRAME (); Lisp_Object bar_window; - int part; + enum scroll_bar_part part; unsigned long time; Lisp_Object x, y; @@ -947,15 +1077,15 @@ on the left of the dialog box and all following items on the right.\n\ 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. */ { @@ -973,18 +1103,27 @@ on the left of the dialog box and all following items on the right.\n\ 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)); /* Display them in a dialog box. */ BLOCK_INPUT; - selection = xdialog_show (f, 0, title, &error_name); + selection = xdialog_show (f, 0, title, header, &error_name); UNBLOCK_INPUT; + unbind_to (specpdl_count, Qnil); discard_menu_items (); if (error_name) error (error_name); @@ -992,120 +1131,162 @@ on the left of the dialog box and all following items on the right.\n\ } #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. @@ -1116,7 +1297,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; @@ -1124,15 +1305,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; } @@ -1148,55 +1341,51 @@ 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; LWLIB_ID id; XtPointer client_data; { -#ifdef USE_MOTIF - ++popup_activated_flag; -#else popup_activated_flag = 1; -#endif } +#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; LWLIB_ID id; XtPointer client_data; { -#ifdef USE_MOTIF - --popup_activated_flag; -#else popup_activated_flag = 0; -#endif } +#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); @@ -1204,9 +1393,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)) @@ -1217,37 +1407,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. */ + +static 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)) { @@ -1275,6 +1510,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; @@ -1311,16 +1547,80 @@ menubar_selection_callback (widget, id, client_data) } } -/* Allocate a widget_value, blocking input. */ - -widget_value * -xmalloc_widget_value () -{ - widget_value *value; - BLOCK_INPUT; - value = malloc_widget_value (); - UNBLOCK_INPUT; +#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; +{ + xg_menu_item_cb_data *cb_data = (xg_menu_item_cb_data*) client_data; + + if (xg_crazy_callback_abort) + return; + + if (! cb_data || ! cb_data->cl_data || ! cb_data->cl_data->f) + return; + + /* When a menu is popped down, X generates a focus event (i.e. focus + goes back to the frame below the menu). Since GTK buffers events, + we force it out here before the menu selection event. Otherwise + sit-for will exit at once if the focus event follows the menu selection + event. */ + + BLOCK_INPUT; + while (gtk_events_pending ()) + gtk_main_iteration (); + UNBLOCK_INPUT; + + find_and_call_menu_selection (cb_data->cl_data->f, + cb_data->cl_data->menu_bar_items_used, + cb_data->cl_data->menu_bar_vector, + cb_data->call_data); +} + +#else /* not USE_GTK */ + +/* 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; } @@ -1353,22 +1653,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); @@ -1382,14 +1678,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. */ @@ -1399,11 +1692,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 *)); @@ -1412,16 +1724,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)) { @@ -1448,14 +1761,19 @@ 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)) - pane_name = string_make_unibyte (pane_name); + { + pane_name = ENCODE_MENU_STRING (pane_name); + AREF (menu_items, i + MENU_ITEMS_PANE_NAME) = pane_name; + } #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) @@ -1471,14 +1789,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; @@ -1489,38 +1805,44 @@ 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 = 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]; - def = XVECTOR (menu_items)->contents[i + MENU_ITEMS_ITEM_DEFINITION]; - type = XVECTOR (menu_items)->contents[i + MENU_ITEMS_ITEM_TYPE]; - selected = XVECTOR (menu_items)->contents[i + MENU_ITEMS_ITEM_SELECTED]; - help = XVECTOR (menu_items)->contents[i + MENU_ITEMS_ITEM_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); + def = AREF (menu_items, i + MENU_ITEMS_ITEM_DEFINITION); + type = AREF (menu_items, i + MENU_ITEMS_ITEM_TYPE); + selected = AREF (menu_items, i + MENU_ITEMS_ITEM_SELECTED); + help = AREF (menu_items, i + MENU_ITEMS_ITEM_HELP); #ifndef HAVE_MULTILINGUAL_MENU if (STRING_MULTIBYTE (item_name)) - item_name = string_make_unibyte (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 = string_make_unibyte (descrip); -#endif + { + 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)) @@ -1531,9 +1853,11 @@ single_submenu (item_key, item_name, maps) abort (); wv->selected = !NILP (selected); - if (STRINGP (help)) - wv->help = XSTRING (help)->data; - + if (! STRINGP (help)) + help = Qnil; + + wv->help = help; + prev_wv = wv; i += MENU_ITEMS_ITEM_LENGTH; @@ -1551,64 +1875,89 @@ single_submenu (item_key, item_name, maps) return first_wv; } - -extern void EmacsFrameSetCharSize (); - -/* Recompute all the widgets of frame F, when the menu bar - has been changed. */ +/* 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 + changed. Value is non-zero if widgets were updated. */ + +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; - int menubar_changed; - - /* We assume the menubar contents has changed if the global flag is set, - or if the current buffer has changed, or if the menubar has never - been updated before. - */ - menubar_changed = (x->menubar_widget - && !XtIsManaged (x->menubar_widget)); - - if (! (menubar_changed)) - return; + + 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; + /* Save the size of the frame because the pane widget doesn't accept + to resize itself. So force it. */ + 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." */ + /* Do the voodoo which means "I'm changing lots of things, don't try + to refigure sizes until I'm done." */ lw_refigure_widget (x->column_widget, False); - /* the order in which children are managed is the top to - bottom order in which they are displayed in the paned window. - First, remove the text-area widget. - */ + /* The order in which children are managed is the top to bottom + order in which they are displayed in the paned window. First, + remove the text-area widget. */ XtUnmanageChild (x->edit_widget); - /* remove the menubar that is there now, and put up the menubar that - should be there. - */ - if (menubar_changed) - { - XtManageChild (x->menubar_widget); - XtMapWidget (x->menubar_widget); - XtVaSetValues (x->menubar_widget, XtNmappedWhenManaged, 1, NULL); - } + /* Remove the menubar that is there now, and put up the menubar that + should be there. */ + XtManageChild (x->menubar_widget); + XtMapWidget (x->menubar_widget); + XtVaSetValues (x->menubar_widget, XtNmappedWhenManaged, 1, NULL); /* Re-manage the text-area widget, and then thrash the sizes. */ XtManageChild (x->edit_widget); - x_set_menu_resources_from_menu_face (f, x->menubar_widget); lw_refigure_widget (x->column_widget, True); /* Force the pane widget to resize itself with the right values. */ EmacsFrameSetCharSize (x->edit_widget, columns, rows); UNBLOCK_INPUT; +#endif + return 1; } /* Set the contents of the menubar widgets of frame F. @@ -1621,17 +1970,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; - LWLIB_ID id; + int i, last_i = 0; + int *submenu_start, *submenu_end; + int *submenu_top_level_items, *submenu_n_panes; + 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; @@ -1645,12 +2001,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) { @@ -1658,7 +2013,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 @@ -1675,7 +2030,8 @@ set_frame_menubar (f, first_time, deep_p) because it is not reentrant. */ specbind (Qdebug_on_next_call, Qnil); - record_unwind_protect (Fset_match_data, Fmatch_data (Qnil, Qnil)); + record_unwind_save_match_data (); + record_unwind_protect (unuse_menu_items, Qnil); if (NILP (Voverriding_local_map_menu_flag)) { specbind (Qoverriding_terminal_local_map, Qnil); @@ -1686,7 +2042,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)) @@ -1696,28 +2052,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. */ - bcopy (XVECTOR (f->menu_bar_vector)->contents, previous_items, - previous_menu_items_used * sizeof (Lisp_Object)); + 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 = XVECTOR (menu_items)->size; + 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; @@ -1727,8 +2117,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); @@ -1742,7 +2130,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; } @@ -1755,20 +2143,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) { @@ -1779,17 +2176,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; @@ -1806,6 +2204,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!) */ @@ -1820,7 +2247,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, @@ -1828,10 +2258,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) @@ -1852,10 +2285,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; } @@ -1874,9 +2312,12 @@ initialize_frame_menubar (f) set_frame_menubar (f, 1, 1); } + /* 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; @@ -1886,17 +2327,46 @@ 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 + /* Removing the menu bar magically changes the shell widget's x + and y position of (0, 0) which, when the menu bar is turned + on again, leads to pull-down menuss appearing in strange + positions near the upper-left corner of the display. This + happens only with some window managers like twm and ctwm, + but not with other like Motif's mwm or kwm, because the + latter generate ConfigureNotify events when the menu bar + 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; + +#ifdef USE_MOTIF + if (f->output_data.x->widget) + { + XtVaGetValues (f->output_data.x->widget, XtNx, &x1, XtNy, &y1, NULL); + if (x1 == 0 && y1 == 0) + 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. @@ -1914,7 +2384,150 @@ 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; + + 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; + + i = 0; /* gtk_menu_popup needs this to be 0 for a non-button popup. */ + } + else + { + for (i = 0; i < 5; i++) + if (FRAME_X_DISPLAY_INFO (f)->grabbed & (1 << i)) + break; + } + + /* Display the menu. */ + gtk_widget_show_all (menu); + gtk_menu_popup (GTK_MENU (menu), 0, 0, pos_func, &popup_x_y, i, 0); + + record_unwind_protect (pop_down_menu, make_save_value (menu, 0)); + + if (GTK_WIDGET_MAPPED (menu)) + { + /* 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. @@ -1926,8 +2539,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; @@ -1937,6 +2548,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; @@ -1948,17 +2650,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; @@ -1977,9 +2674,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) @@ -2011,14 +2709,19 @@ xmenu_show (f, x, y, for_click, keymaps, title, error) /* 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]; + + pane_name = AREF (menu_items, i + MENU_ITEMS_PANE_NAME); + prefix = AREF (menu_items, i + MENU_ITEMS_PANE_PREFIX); + #ifndef HAVE_MULTILINGUAL_MENU - if (!NILP (pane_name) && STRING_MULTIBYTE (pane_name)) - pane_name = string_make_unibyte (pane_name); + if (STRINGP (pane_name) && STRING_MULTIBYTE (pane_name)) + { + pane_name = ENCODE_MENU_STRING (pane_name); + AREF (menu_items, i + MENU_ITEMS_PANE_NAME) = pane_name; + } #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) @@ -2040,6 +2743,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; } @@ -2055,30 +2759,36 @@ xmenu_show (f, x, y, for_click, keymaps, title, error) { /* Create a new item within current pane. */ Lisp_Object item_name, enable, descrip, def, type, selected, help; - 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]; - def = XVECTOR (menu_items)->contents[i + MENU_ITEMS_ITEM_DEFINITION]; - type = XVECTOR (menu_items)->contents[i + MENU_ITEMS_ITEM_TYPE]; - selected = XVECTOR (menu_items)->contents[i + MENU_ITEMS_ITEM_SELECTED]; - help = XVECTOR (menu_items)->contents[i + MENU_ITEMS_ITEM_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); + def = AREF (menu_items, i + MENU_ITEMS_ITEM_DEFINITION); + type = AREF (menu_items, i + MENU_ITEMS_ITEM_TYPE); + selected = AREF (menu_items, i + MENU_ITEMS_ITEM_SELECTED); + help = AREF (menu_items, i + MENU_ITEMS_ITEM_HELP); #ifndef HAVE_MULTILINGUAL_MENU if (STRINGP (item_name) && STRING_MULTIBYTE (item_name)) - item_name = string_make_unibyte (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)) - item_name = string_make_unibyte (descrip); -#endif - + { + 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 @@ -2097,9 +2807,12 @@ xmenu_show (f, x, y, for_click, keymaps, title, error) abort (); wv->selected = !NILP (selected); - if (STRINGP (help)) - wv->help = XSTRING (help)->data; - + + if (! STRINGP (help)) + help = Qnil; + + wv->help = help; + prev_wv = wv; i += MENU_ITEMS_ITEM_LENGTH; @@ -2115,103 +2828,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 = string_make_unibyte (title); + title = ENCODE_MENU_STRING (title); #endif - wv_title->name = (char *) XSTRING (title)->data; - wv_title->enabled = True; + + 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); - - /* Override any default settings with ones from the `menu' face. */ - x_set_menu_resources_from_menu_face (f, menu); - /* 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. */ @@ -2267,10 +2910,58 @@ xmenu_show (f, x, y, for_click, keymaps, title, error) } } } + else if (!for_click) + /* Make "Cancel" equivalent to C-g. */ + Fsignal (Qquit, Qnil); 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; @@ -2281,26 +2972,66 @@ 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" }; static Lisp_Object -xdialog_show (f, keymaps, title, error) +xdialog_show (f, keymaps, title, header, error_name) FRAME_PTR f; int keymaps; - Lisp_Object title; - char **error; + Lisp_Object title, header; + char **error_name; { int i, nb_buttons=0; - LWLIB_ID dialog_id; - Widget menu; char dialog_name[6]; widget_value *wv, *first_wv = 0, *prev_wv = 0; @@ -2310,11 +3041,11 @@ xdialog_show (f, keymaps, title, error) /* 1 means we've seen the boundary between left-hand elts and right-hand. */ int boundary_seen = 0; - *error = NULL; + *error_name = NULL; if (menu_items_n_panes > 1) { - *error = "Multiple panes in dialog box"; + *error_name = "Multiple panes in dialog box"; return Qnil; } @@ -2326,31 +3057,32 @@ 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); - *error = "Submenu in dialog items"; + *error_name = "Submenu in dialog items"; return Qnil; } if (EQ (item_name, Qquote)) @@ -2364,7 +3096,7 @@ xdialog_show (f, keymaps, title, error) if (nb_buttons >= 9) { free_menubar_widget_value_tree (first_wv); - *error = "Too many dialog items"; + *error_name = "Too many dialog items"; return Qnil; } @@ -2372,10 +3104,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) @@ -2392,12 +3125,19 @@ xdialog_show (f, keymaps, title, error) wv = xmalloc_widget_value (); wv->name = dialog_name; + wv->help = Qnil; + + /* Frame title: 'Q' = Question, 'I' = Information. + Can also have 'E' = Error if, one day, we want + a popup for errors. */ + if (NILP(header)) + dialog_name[0] = 'Q'; + else + dialog_name[0] = 'I'; /* Dialog boxes use a really stupid name encoding which specifies how many buttons to use - and how many buttons are on the right. - The Q means something also. */ - dialog_name[0] = 'Q'; + and how many buttons are on the right. */ dialog_name[1] = '0' + nb_buttons; dialog_name[2] = 'B'; dialog_name[3] = 'R'; @@ -2408,26 +3148,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. */ @@ -2471,11 +3199,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. */ @@ -2487,7 +3218,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. */ @@ -2501,16 +3232,16 @@ 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]; else if (EQ (first_item[0], Qquote)) /* This shouldn't happen, see xmenu_show. */ - pane_name = build_string (""); + 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, @@ -2518,7 +3249,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) @@ -2529,17 +3295,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) @@ -2566,39 +3333,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)) @@ -2607,10 +3351,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++; @@ -2637,7 +3383,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; @@ -2660,34 +3406,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, @@ -2699,15 +3445,16 @@ 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), - XScreenNumberOfScreen (FRAME_X_SCREEN (f))); - dispheight = DisplayHeight (FRAME_X_DISPLAY (f), - XScreenNumberOfScreen (FRAME_X_SCREEN (f))); + dispwidth = DisplayWidth (FRAME_X_DISPLAY (f), FRAME_X_SCREEN_NUMBER (f)); + dispheight = DisplayHeight (FRAME_X_DISPLAY (f), FRAME_X_SCREEN_NUMBER (f)); x = min (x, dispwidth); y = min (y, dispheight); x = max (x, 1); @@ -2727,23 +3474,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) { @@ -2790,19 +3547,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 FOR_CLICK (which means + the menu was invoked with a mouse event as POSITION). */ + 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; } @@ -2816,17 +3572,18 @@ 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); DEFVAR_LISP ("menu-updating-frame", &Vmenu_updating_frame, - "Frame for which we are updating a menu.\n\ -The enable predicate for a menu command should check this variable."); + doc: /* Frame for which we are updating a menu. +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 @@ -2835,3 +3592,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) */