/* X Communication module for terminals which understand the X protocol.
- Copyright (C) 1986, 88, 93, 94, 96, 99, 2000, 2001, 2003
+ Copyright (C) 1986, 1988, 1993, 1994, 1996, 1999, 2000, 2001, 2003, 2004
Free Software Foundation, Inc.
This file is part of GNU Emacs.
#include "buffer.h"
#include "charset.h"
#include "coding.h"
+#include "sysselect.h"
#ifdef MSDOS
#include "msdos.h"
Lisp_Object Qdebug_on_next_call;
extern Lisp_Object Qmenu_bar;
-extern Lisp_Object Qmouse_click, Qevent_kind;
extern Lisp_Object QCtoggle, QCradio;
extern Lisp_Object Qmenu_bar_update_hook;
#ifdef USE_X_TOOLKIT
-extern void set_frame_menubar ();
+extern void set_frame_menubar P_ ((FRAME_PTR, int, int));
extern XtAppContext Xt_app_con;
-static Lisp_Object xdialog_show ();
-static void popup_get_selection ();
+static Lisp_Object xdialog_show P_ ((FRAME_PTR, int, Lisp_Object, char **));
+static void popup_get_selection P_ ((XEvent *, struct x_display_info *,
+ LWLIB_ID, int, int));
/* Define HAVE_BOXES if menus can handle radio and toggle buttons. */
#ifdef USE_GTK
#include "gtkutil.h"
#define HAVE_BOXES 1
-extern void set_frame_menubar ();
-static Lisp_Object xdialog_show ();
+extern void set_frame_menubar P_ ((FRAME_PTR, int, int));
+static Lisp_Object xdialog_show P_ ((FRAME_PTR, int, Lisp_Object, char **));
#endif
/* This is how to deal with multibyte text if HAVE_MULTILINGUAL_MENU
static void list_of_panes P_ ((Lisp_Object));
static void list_of_items P_ ((Lisp_Object));
-extern EMACS_TIME timer_check P_ ((int));
\f
/* This holds a Lisp vector that holds the results of decoding
the keymaps or alist-of-alists that specify a menu.
static Lisp_Object
unuse_menu_items (dummy)
- int dummy;
+ Lisp_Object dummy;
{
return menu_items_inuse = Qnil;
}
return; /* Not a menu item. */
map = XVECTOR (item_properties)->contents[ITEM_PROPERTY_MAP];
-
+
if (skp->notreal)
{
/* We don't want to make a menu, just traverse the keymaps to
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. */
}
#endif
}
+
+
+#ifndef MSDOS
+
+/* Return non-zero if a dialog or popup menu is already popped up. */
+
+int
+x_menu_in_use ()
+{
+ return ! NILP (menu_items_inuse);
+}
+
+/* Set menu_items_inuse so no other popup menu or dialog is created. */
+
+void
+x_menu_set_in_use (in_use)
+ int in_use;
+{
+ menu_items_inuse = in_use ? Qt : Qnil;
+}
+
+/* Wait for an X event to arrive or for a timer to expire. */
+
+void
+x_menu_wait_for_event (void *data)
+{
+ extern EMACS_TIME timer_check P_ ((int));
+
+ /* Another way to do this is to register a timer callback, that can be
+ done in GTK and Xt. But we have to do it like this when using only X
+ anyway, and with callbacks we would have three variants for timer handling
+ instead of the small ifdefs below. */
+
+ while (
+#ifdef USE_X_TOOLKIT
+ ! XtAppPending (Xt_app_con)
+#elif defined USE_GTK
+ ! gtk_events_pending ()
+#else
+ ! XPending ((Display*) data)
+#endif
+ )
+ {
+ EMACS_TIME next_time = timer_check (1);
+ long secs = EMACS_SECS (next_time);
+ long usecs = EMACS_USECS (next_time);
+ SELECT_TYPE read_fds;
+ struct x_display_info *dpyinfo;
+ int n = 0;
+
+ FD_ZERO (&read_fds);
+ for (dpyinfo = x_display_list; dpyinfo; dpyinfo = dpyinfo->next)
+ {
+ int fd = ConnectionNumber (dpyinfo->display);
+ FD_SET (fd, &read_fds);
+ if (fd > n) n = fd;
+ }
+
+ if (secs < 0 || (secs == 0 && usecs == 0))
+ {
+ /* Sometimes timer_check returns -1 (no timers) even if there are
+ timers. So do a timeout anyway. */
+ EMACS_SET_SECS (next_time, 1);
+ EMACS_SET_USECS (next_time, 0);
+ }
+
+ select (n + 1, &read_fds, (SELECT_TYPE *)0, (SELECT_TYPE *)0, &next_time);
+ }
+}
+#endif /* ! MSDOS */
+
\f
#if defined (USE_X_TOOLKIT) || defined (USE_GTK)
+#ifdef USE_X_TOOLKIT
+
/* Loop in Xt until the menu pulldown or dialog popup has been
popped down (deactivated). This is used for x-popup-menu
and x-popup-dialog; it is not used for the menu bar.
- If DO_TIMERS is nonzero, run timers.
+ If DOWN_ON_KEYPRESS is nonzero, pop down if a key is pressed.
NOTE: All calls to popup_get_selection should be protected
with BLOCK_INPUT, UNBLOCK_INPUT wrappers. */
-#ifdef USE_X_TOOLKIT
static void
-popup_get_selection (initial_event, dpyinfo, id, do_timers)
+popup_get_selection (initial_event, dpyinfo, id, do_timers, down_on_keypress)
XEvent *initial_event;
struct x_display_info *dpyinfo;
LWLIB_ID id;
int do_timers;
+ int down_on_keypress;
{
XEvent event;
while (popup_activated_flag)
{
- /* If we have no events to run, consider timers. */
- if (do_timers && !XtAppPending (Xt_app_con))
- timer_check (1);
-
- if (initial_event)
+ if (initial_event)
{
event = *initial_event;
initial_event = 0;
}
else
- XtAppNextEvent (Xt_app_con, &event);
+ {
+ if (do_timers) x_menu_wait_for_event (0);
+ XtAppNextEvent (Xt_app_con, &event);
+ }
/* Make sure we don't consider buttons grabbed after menu goes.
And make sure to deactivate for any ButtonRelease,
event.xbutton.state = 0;
#endif
}
- /* If the user presses a key, deactivate the menu.
+ /* If the user presses a key that doesn't go to the menu,
+ deactivate the menu.
The user is likely to do that if we get wedged.
- This is mostly for Lucid, Motif pops down the menu on ESC. */
+ All toolkits now pop down menus on ESC.
+ For dialogs however, the focus may not be on the dialog, so
+ in that case, we pop down. */
else if (event.type == KeyPress
+ && down_on_keypress
&& dpyinfo->display == event.xbutton.display)
{
KeySym keysym = XLookupKeysym (&event.xkey, 0);
- if (!IsModifierKey (keysym))
- popup_activated_flag = 0;
+ if (!IsModifierKey (keysym)
+ && x_any_window_to_frame (dpyinfo, event.xany.window) != NULL)
+ popup_activated_flag = 0;
}
x_dispatch_event (&event, event.xany.display);
#ifdef USE_GTK
/* Loop util popup_activated_flag is set to zero in a callback.
Used for popup menus and dialogs. */
+
static void
-popup_widget_loop ()
+popup_widget_loop (do_timers, widget)
+ int do_timers;
+ GtkWidget *widget;
{
++popup_activated_flag;
/* Process events in the Gtk event loop until done. */
while (popup_activated_flag)
{
+ if (do_timers) x_menu_wait_for_event (0);
gtk_main_iteration ();
}
}
}
else
{
+#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. */
xt_or_gtk_widget frame_widget = XtParent (widget);
FRAME_X_P (f) && f->output_data.x->widget == frame_widget))
break;
}
-
+#endif
show_help_echo (help, Qnil, Qnil, Qnil, 1);
}
}
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;
else
save_wv->contents = wv;
- wv->name = (char *) SDATA (item_name);
+ wv->lname = item_name;
if (!NILP (descrip))
- wv->key = (char *) SDATA (descrip);
+ 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. */
return first_wv;
}
+
+/* Walk through the widget_value tree starting at FIRST_WV and update
+ the char * pointers from the corresponding lisp values.
+ We do this after building the whole tree, since GC may happen while the
+ tree is constructed, and small strings are relocated. So we must wait
+ until no GC can happen before storing pointers into lisp values. */
+static void
+update_submenu_strings (first_wv)
+ widget_value *first_wv;
+{
+ widget_value *wv;
+
+ for (wv = first_wv; wv; wv = wv->next)
+ {
+ if (STRINGP (wv->lname))
+ {
+ wv->name = SDATA (wv->lname);
+
+ /* Ignore the @ that means "separate pane".
+ This is a kludge, but this isn't worth more time. */
+ if (wv->value == (char *)1)
+ {
+ if (wv->name[0] == '@')
+ wv->name++;
+ wv->value = 0;
+ }
+ }
+
+ if (STRINGP (wv->lkey))
+ wv->key = SDATA (wv->lkey);
+
+ if (wv->contents)
+ update_submenu_strings (wv->contents);
+ }
+}
+
\f
/* Recompute all the widgets of frame F, when the menu bar has been
changed. Value is non-zero if widgets were updated. */
#endif
Lisp_Object items;
widget_value *wv, *first_wv, *prev_wv = 0;
- int i, last_i;
+ int i, last_i = 0;
int *submenu_start, *submenu_end;
int *submenu_top_level_items, *submenu_n_panes;
Lisp_Object string;
string = XVECTOR (items)->contents[i + 1];
if (NILP (string))
- break;
- wv->name = (char *) SDATA (string);
- wv = wv->next;
+ break;
+ wv->name = (char *) SDATA (string);
+ update_submenu_strings (wv->contents);
+ wv = wv->next;
}
f->menu_bar_vector = menu_items;
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;
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. */
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,
gtk_widget_show_all (menu);
gtk_menu_popup (GTK_MENU (menu), 0, 0, pos_func, &popup_x_y, i, 0);
+ fprintf (stderr, "Unwind: %p\n", menu);
+ record_unwind_protect (pop_down_menu, make_save_value (menu, 0));
+
/* Set this to one. popup_widget_loop increases it by one, so it becomes
two. show_help_echo uses this to detect popup menus. */
popup_activated_flag = 1;
/* Process events that apply to the menu. */
- popup_widget_loop ();
+ popup_widget_loop (1, menu);
- gtk_widget_destroy (menu);
+ unbind_to (specpdl_count, Qnil);
/* Must reset this manually because the button release event is not passed
to Emacs event loop. */
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. */
/* Display the menu. */
lw_popup_menu (menu, (XEvent *) &dummy);
popup_activated_flag = 1;
+
+ {
+ int fact = 4 * sizeof (LWLIB_ID);
+ int specpdl_count = SPECPDL_INDEX ();
+ record_unwind_protect (pop_down_menu,
+ Fcons (make_number (menu_id >> (fact)),
+ make_number (menu_id & ~(-1 << (fact)))));
- /* Process events that apply to the menu. */
- popup_get_selection ((XEvent *) 0, FRAME_X_DISPLAY_INFO (f), menu_id, 0);
+ /* Process events that apply to the menu. */
+ popup_get_selection ((XEvent *) 0, FRAME_X_DISPLAY_INFO (f), menu_id, 1, 0);
- /* fp turned off the following statement and wrote a comment
- that it is unnecessary--that the menu has already disappeared.
- Nowadays the menu disappears ok, all right, but
- we need to delete the widgets or multiple ones will pile up. */
- lw_destroy_all_widgets (menu_id);
+ unbind_to (specpdl_count, Qnil);
+ }
}
#endif /* not USE_GTK */
if (menu)
{
+ int specpdl_count = SPECPDL_INDEX ();
+ record_unwind_protect (pop_down_menu, make_save_value (menu, 0));
+
/* Display the menu. */
gtk_widget_show_all (menu);
/* Process events that apply to the menu. */
- popup_widget_loop ();
+ popup_widget_loop (1, menu);
- gtk_widget_destroy (menu);
+ unbind_to (specpdl_count, Qnil);
}
}
}
-/* ARG is the LWLIB ID of the dialog box, represented
- as a Lisp object as (HIGHPART . LOWPART). */
-
-Lisp_Object
-xdialog_show_unwind (arg)
- Lisp_Object arg;
-{
- LWLIB_ID id = (XINT (XCAR (arg)) << 4 * sizeof (LWLIB_ID)
- | XINT (XCDR (arg)));
- BLOCK_INPUT;
- lw_destroy_all_widgets (id);
- UNBLOCK_INPUT;
- popup_activated_flag = 0;
- return Qnil;
-}
-
-
/* Pop up the dialog for frame F defined by FIRST_WV and loop until the
dialog pops down.
menu_item_selection will be set to the selection. */
int fact = 4 * sizeof (LWLIB_ID);
/* xdialog_show_unwind is responsible for popping the dialog box down. */
- record_unwind_protect (xdialog_show_unwind,
+ record_unwind_protect (pop_down_menu,
Fcons (make_number (dialog_id >> (fact)),
make_number (dialog_id & ~(-1 << (fact)))));
- popup_get_selection ((XEvent *) 0, FRAME_X_DISPLAY_INFO (f), dialog_id, 1);
+ popup_get_selection ((XEvent *) 0, FRAME_X_DISPLAY_INFO (f),
+ dialog_id, 1, 1);
unbind_to (count, 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)
int maxwidth;
int dummy_int;
unsigned int dummy_uint;
+ int specpdl_count = SPECPDL_INDEX ();
*error = 0;
if (menu_items_n_panes == 0)
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)
{
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;
}