1 /* GTK - The GIMP Toolkit
2 * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17 * Boston, MA 02111-1307, USA.
21 * Modified by the GTK+ Team and others 1997-2000. See the AUTHORS
22 * file for a list of people on the GTK+ Team. See the ChangeLog
23 * files for a list of changes. These files are distributed with
24 * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
27 #define GTK_MENU_INTERNALS
29 #include <string.h> /* memset */
30 #include "gdk/gdkkeysyms.h"
31 #include "gtkaccelmap.h"
32 #include "gtkbindings.h"
35 #include "gtkmarshalers.h"
37 #include "gtkmenuitem.h"
38 #include "gtktearoffmenuitem.h"
39 #include "gtkwindow.h"
41 #include "gtkvscrollbar.h"
42 #include "gtksettings.h"
46 #define MENU_ITEM_CLASS(w) GTK_MENU_ITEM_GET_CLASS (w)
47 #define MENU_NEEDS_RESIZE(m) GTK_MENU_SHELL (m)->menu_flag
49 #define DEFAULT_POPUP_DELAY 225
50 #define DEFAULT_POPDOWN_DELAY 1000
52 #define NAVIGATION_REGION_OVERSHOOT 50 /* How much the navigation region
53 * extends below the submenu
56 #define MENU_SCROLL_STEP1 8
57 #define MENU_SCROLL_STEP2 15
58 #define MENU_SCROLL_ARROW_HEIGHT 16
59 #define MENU_SCROLL_FAST_ZONE 8
60 #define MENU_SCROLL_TIMEOUT1 50
61 #define MENU_SCROLL_TIMEOUT2 50
63 typedef struct _GtkMenuAttachData GtkMenuAttachData;
64 typedef struct _GtkMenuPrivate GtkMenuPrivate;
66 struct _GtkMenuAttachData
68 GtkWidget *attach_widget;
69 GtkMenuDetachFunc detacher;
72 struct _GtkMenuPrivate
74 gboolean have_position;
89 static void gtk_menu_class_init (GtkMenuClass *klass);
90 static void gtk_menu_init (GtkMenu *menu);
91 static void gtk_menu_set_property (GObject *object,
95 static void gtk_menu_get_property (GObject *object,
99 static void gtk_menu_destroy (GtkObject *object);
100 static void gtk_menu_finalize (GObject *object);
101 static void gtk_menu_realize (GtkWidget *widget);
102 static void gtk_menu_unrealize (GtkWidget *widget);
103 static void gtk_menu_size_request (GtkWidget *widget,
104 GtkRequisition *requisition);
105 static void gtk_menu_size_allocate (GtkWidget *widget,
106 GtkAllocation *allocation);
107 static void gtk_menu_paint (GtkWidget *widget,
108 GdkEventExpose *expose);
109 static void gtk_menu_show (GtkWidget *widget);
110 static gboolean gtk_menu_expose (GtkWidget *widget,
111 GdkEventExpose *event);
112 static gboolean gtk_menu_key_press (GtkWidget *widget,
114 static gboolean gtk_menu_button_press (GtkWidget *widget,
115 GdkEventButton *event);
116 static gboolean gtk_menu_button_release (GtkWidget *widget,
117 GdkEventButton *event);
118 static gboolean gtk_menu_motion_notify (GtkWidget *widget,
119 GdkEventMotion *event);
120 static gboolean gtk_menu_enter_notify (GtkWidget *widget,
121 GdkEventCrossing *event);
122 static gboolean gtk_menu_leave_notify (GtkWidget *widget,
123 GdkEventCrossing *event);
124 static void gtk_menu_scroll_to (GtkMenu *menu,
127 static void gtk_menu_stop_scrolling (GtkMenu *menu);
128 static void gtk_menu_remove_scroll_timeout (GtkMenu *menu);
129 static gboolean gtk_menu_scroll_timeout (gpointer data);
131 static void gtk_menu_scroll_item_visible (GtkMenuShell *menu_shell,
132 GtkWidget *menu_item);
133 static void gtk_menu_select_item (GtkMenuShell *menu_shell,
134 GtkWidget *menu_item);
135 static void gtk_menu_real_insert (GtkMenuShell *menu_shell,
138 static void gtk_menu_scrollbar_changed (GtkAdjustment *adjustment,
140 static void gtk_menu_handle_scrolling (GtkMenu *menu,
142 static void gtk_menu_set_tearoff_hints (GtkMenu *menu,
144 static void gtk_menu_style_set (GtkWidget *widget,
145 GtkStyle *previous_style);
146 static gboolean gtk_menu_focus (GtkWidget *widget,
147 GtkDirectionType direction);
148 static gint gtk_menu_get_popup_delay (GtkMenuShell *menu_shell);
149 static void gtk_menu_real_move_scroll (GtkMenu *menu,
152 static void gtk_menu_stop_navigating_submenu (GtkMenu *menu);
153 static gboolean gtk_menu_stop_navigating_submenu_cb (gpointer user_data);
154 static gboolean gtk_menu_navigating_submenu (GtkMenu *menu,
157 static void gtk_menu_set_submenu_navigation_region (GtkMenu *menu,
158 GtkMenuItem *menu_item,
159 GdkEventCrossing *event);
161 static void gtk_menu_deactivate (GtkMenuShell *menu_shell);
162 static void gtk_menu_show_all (GtkWidget *widget);
163 static void gtk_menu_hide_all (GtkWidget *widget);
164 static void gtk_menu_position (GtkMenu *menu);
165 static void gtk_menu_reparent (GtkMenu *menu,
166 GtkWidget *new_parent,
168 static void gtk_menu_remove (GtkContainer *menu,
171 static void gtk_menu_update_title (GtkMenu *menu);
173 static void menu_grab_transfer_window_destroy (GtkMenu *menu);
174 static GdkWindow *menu_grab_transfer_window_get (GtkMenu *menu);
176 static void _gtk_menu_refresh_accel_paths (GtkMenu *menu,
177 gboolean group_changed);
179 static GtkMenuShellClass *parent_class = NULL;
180 static const gchar *attach_data_key = "gtk-menu-attach-data";
182 static guint menu_signals[LAST_SIGNAL] = { 0 };
185 gtk_menu_get_private (GtkMenu *menu)
187 GtkMenuPrivate *private;
188 static GQuark private_quark = 0;
191 private_quark = g_quark_from_static_string ("gtk-menu-private");
193 private = g_object_get_qdata (G_OBJECT (menu), private_quark);
197 private = g_new0 (GtkMenuPrivate, 1);
198 private->have_position = FALSE;
200 g_object_set_qdata_full (G_OBJECT (menu), private_quark,
208 gtk_menu_get_type (void)
210 static GType menu_type = 0;
214 static const GTypeInfo menu_info =
216 sizeof (GtkMenuClass),
217 NULL, /* base_init */
218 NULL, /* base_finalize */
219 (GClassInitFunc) gtk_menu_class_init,
220 NULL, /* class_finalize */
221 NULL, /* class_data */
224 (GInstanceInitFunc) gtk_menu_init,
227 menu_type = g_type_register_static (GTK_TYPE_MENU_SHELL, "GtkMenu",
235 gtk_menu_class_init (GtkMenuClass *class)
237 GObjectClass *gobject_class = G_OBJECT_CLASS (class);
238 GtkObjectClass *object_class = GTK_OBJECT_CLASS (class);
239 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
240 GtkContainerClass *container_class = GTK_CONTAINER_CLASS (class);
241 GtkMenuShellClass *menu_shell_class = GTK_MENU_SHELL_CLASS (class);
242 GtkBindingSet *binding_set;
244 parent_class = g_type_class_peek_parent (class);
246 gobject_class->finalize = gtk_menu_finalize;
247 gobject_class->set_property = gtk_menu_set_property;
248 gobject_class->get_property = gtk_menu_get_property;
250 menu_signals[MOVE_SCROLL] =
251 _gtk_binding_signal_new ("move_scroll",
252 G_OBJECT_CLASS_TYPE (object_class),
253 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
254 G_CALLBACK (gtk_menu_real_move_scroll),
256 _gtk_marshal_VOID__ENUM,
258 GTK_TYPE_SCROLL_TYPE);
260 g_object_class_install_property (gobject_class,
262 g_param_spec_string ("tearoff-title",
264 _("A title that may be displayed by the window manager when this menu is torn-off"),
266 G_PARAM_READABLE | G_PARAM_WRITABLE));
268 gtk_widget_class_install_style_property (widget_class,
269 g_param_spec_int ("vertical-padding",
270 _("Vertical Padding"),
271 _("Extra space at the top and bottom of the menu"),
277 gtk_widget_class_install_style_property (widget_class,
278 g_param_spec_int ("vertical-offset",
279 _("Vertical Offset"),
280 _("When the menu is a submenu, position it this number of pixels offset vertically"),
286 gtk_widget_class_install_style_property (widget_class,
287 g_param_spec_int ("horizontal-offset",
288 _("Horizontal Offset"),
289 _("When the menu is a submenu, position it this number of pixels offset horizontally"),
295 object_class->destroy = gtk_menu_destroy;
297 widget_class->realize = gtk_menu_realize;
298 widget_class->unrealize = gtk_menu_unrealize;
299 widget_class->size_request = gtk_menu_size_request;
300 widget_class->size_allocate = gtk_menu_size_allocate;
301 widget_class->show = gtk_menu_show;
302 widget_class->expose_event = gtk_menu_expose;
303 widget_class->key_press_event = gtk_menu_key_press;
304 widget_class->button_press_event = gtk_menu_button_press;
305 widget_class->button_release_event = gtk_menu_button_release;
306 widget_class->motion_notify_event = gtk_menu_motion_notify;
307 widget_class->show_all = gtk_menu_show_all;
308 widget_class->hide_all = gtk_menu_hide_all;
309 widget_class->enter_notify_event = gtk_menu_enter_notify;
310 widget_class->leave_notify_event = gtk_menu_leave_notify;
311 widget_class->motion_notify_event = gtk_menu_motion_notify;
312 widget_class->style_set = gtk_menu_style_set;
313 widget_class->focus = gtk_menu_focus;
315 container_class->remove = gtk_menu_remove;
317 menu_shell_class->submenu_placement = GTK_LEFT_RIGHT;
318 menu_shell_class->deactivate = gtk_menu_deactivate;
319 menu_shell_class->select_item = gtk_menu_select_item;
320 menu_shell_class->insert = gtk_menu_real_insert;
321 menu_shell_class->get_popup_delay = gtk_menu_get_popup_delay;
323 binding_set = gtk_binding_set_by_class (class);
324 gtk_binding_entry_add_signal (binding_set,
327 GTK_TYPE_MENU_DIRECTION_TYPE,
329 gtk_binding_entry_add_signal (binding_set,
332 GTK_TYPE_MENU_DIRECTION_TYPE,
334 gtk_binding_entry_add_signal (binding_set,
337 GTK_TYPE_MENU_DIRECTION_TYPE,
339 gtk_binding_entry_add_signal (binding_set,
342 GTK_TYPE_MENU_DIRECTION_TYPE,
344 gtk_binding_entry_add_signal (binding_set,
347 GTK_TYPE_MENU_DIRECTION_TYPE,
348 GTK_MENU_DIR_PARENT);
349 gtk_binding_entry_add_signal (binding_set,
352 GTK_TYPE_MENU_DIRECTION_TYPE,
353 GTK_MENU_DIR_PARENT);
354 gtk_binding_entry_add_signal (binding_set,
357 GTK_TYPE_MENU_DIRECTION_TYPE,
359 gtk_binding_entry_add_signal (binding_set,
362 GTK_TYPE_MENU_DIRECTION_TYPE,
364 gtk_binding_entry_add_signal (binding_set,
367 GTK_TYPE_SCROLL_TYPE,
369 gtk_binding_entry_add_signal (binding_set,
372 GTK_TYPE_SCROLL_TYPE,
374 gtk_binding_entry_add_signal (binding_set,
377 GTK_TYPE_SCROLL_TYPE,
379 gtk_binding_entry_add_signal (binding_set,
382 GTK_TYPE_SCROLL_TYPE,
384 gtk_binding_entry_add_signal (binding_set,
387 GTK_TYPE_SCROLL_TYPE,
389 gtk_binding_entry_add_signal (binding_set,
392 GTK_TYPE_SCROLL_TYPE,
394 gtk_binding_entry_add_signal (binding_set,
397 GTK_TYPE_SCROLL_TYPE,
398 GTK_SCROLL_PAGE_DOWN);
399 gtk_binding_entry_add_signal (binding_set,
402 GTK_TYPE_SCROLL_TYPE,
403 GTK_SCROLL_PAGE_DOWN);
405 gtk_settings_install_property (g_param_spec_boolean ("gtk-can-change-accels",
406 _("Can change accelerators"),
407 _("Whether menu accelerators can be changed by pressing a key over the menu item"),
411 gtk_settings_install_property (g_param_spec_int ("gtk-menu-popup-delay",
412 _("Delay before submenus appear"),
413 _("Minimum time the pointer must stay over a menu item before the submenu appear"),
419 gtk_settings_install_property (g_param_spec_int ("gtk-menu-popdown-delay",
420 _("Delay before hiding a submenu"),
421 _("The time before hiding a submenu when the pointer is moving towards the submenu"),
424 DEFAULT_POPDOWN_DELAY,
431 gtk_menu_set_property (GObject *object,
438 menu = GTK_MENU (object);
442 case PROP_TEAROFF_TITLE:
443 gtk_menu_set_title (menu, g_value_get_string (value));
446 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
452 gtk_menu_get_property (GObject *object,
459 menu = GTK_MENU (object);
463 case PROP_TEAROFF_TITLE:
464 g_value_set_string (value, gtk_menu_get_title (menu));
467 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
473 gtk_menu_window_event (GtkWidget *window,
477 gboolean handled = FALSE;
479 g_object_ref (window);
485 case GDK_KEY_RELEASE:
486 handled = gtk_widget_event (menu, event);
492 g_object_unref (window);
493 g_object_unref (menu);
499 gtk_menu_window_size_request (GtkWidget *window,
500 GtkRequisition *requisition,
503 GtkMenuPrivate *private = gtk_menu_get_private (menu);
505 if (private->have_position)
507 GdkScreen *screen = gtk_widget_get_screen (window);
508 gint screen_height = gdk_screen_get_height (screen);
510 if (private->y + requisition->height > screen_height)
511 requisition->height = screen_height - private->y;
516 gtk_menu_init (GtkMenu *menu)
518 menu->parent_menu_item = NULL;
519 menu->old_active_menu_item = NULL;
520 menu->accel_group = NULL;
521 menu->position_func = NULL;
522 menu->position_func_data = NULL;
523 menu->toggle_size = 0;
525 menu->toplevel = g_object_connect (g_object_new (GTK_TYPE_WINDOW,
526 "type", GTK_WINDOW_POPUP,
529 "signal::event", gtk_menu_window_event, menu,
530 "signal::size_request", gtk_menu_window_size_request, menu,
531 "signal::destroy", gtk_widget_destroyed, &menu->toplevel,
533 gtk_window_set_resizable (GTK_WINDOW (menu->toplevel), FALSE);
534 gtk_window_set_mnemonic_modifier (GTK_WINDOW (menu->toplevel), 0);
536 /* Refloat the menu, so that reference counting for the menu isn't
537 * affected by it being a child of the toplevel
539 GTK_WIDGET_SET_FLAGS (menu, GTK_FLOATING);
540 menu->needs_destruction_ref_count = TRUE;
542 menu->view_window = NULL;
543 menu->bin_window = NULL;
545 menu->scroll_offset = 0;
546 menu->scroll_step = 0;
547 menu->timeout_id = 0;
548 menu->scroll_fast = FALSE;
550 menu->tearoff_window = NULL;
551 menu->tearoff_hbox = NULL;
552 menu->torn_off = FALSE;
553 menu->tearoff_active = FALSE;
554 menu->tearoff_adjustment = NULL;
555 menu->tearoff_scrollbar = NULL;
557 menu->upper_arrow_visible = FALSE;
558 menu->lower_arrow_visible = FALSE;
559 menu->upper_arrow_prelight = FALSE;
560 menu->lower_arrow_prelight = FALSE;
562 MENU_NEEDS_RESIZE (menu) = TRUE;
566 gtk_menu_destroy (GtkObject *object)
569 GtkMenuAttachData *data;
571 g_return_if_fail (GTK_IS_MENU (object));
573 menu = GTK_MENU (object);
575 gtk_menu_stop_scrolling (menu);
577 data = g_object_get_data (G_OBJECT (object), attach_data_key);
579 gtk_menu_detach (menu);
581 gtk_menu_stop_navigating_submenu (menu);
583 if (menu->old_active_menu_item)
585 g_object_unref (menu->old_active_menu_item);
586 menu->old_active_menu_item = NULL;
589 /* Add back the reference count for being a child */
590 if (menu->needs_destruction_ref_count)
592 menu->needs_destruction_ref_count = FALSE;
593 g_object_ref (object);
596 if (menu->accel_group)
598 g_object_unref (menu->accel_group);
599 menu->accel_group = NULL;
603 gtk_widget_destroy (menu->toplevel);
604 if (menu->tearoff_window)
605 gtk_widget_destroy (menu->tearoff_window);
607 GTK_OBJECT_CLASS (parent_class)->destroy (object);
611 gtk_menu_finalize (GObject *object)
613 GtkMenu *menu = GTK_MENU (object);
615 g_free (menu->accel_path);
617 G_OBJECT_CLASS (parent_class)->finalize (object);
621 menu_change_screen (GtkMenu *menu,
622 GdkScreen *new_screen)
626 gtk_window_set_screen (GTK_WINDOW (menu->tearoff_window), new_screen);
627 gtk_menu_position (menu);
630 gtk_window_set_screen (GTK_WINDOW (menu->toplevel), new_screen);
634 attach_widget_screen_changed (GtkWidget *attach_widget,
635 GdkScreen *previous_screen,
638 if (gtk_widget_has_screen (attach_widget) &&
639 !g_object_get_data (G_OBJECT (menu), "gtk-menu-explicit-screen"))
641 menu_change_screen (menu, gtk_widget_get_screen (attach_widget));
646 gtk_menu_attach_to_widget (GtkMenu *menu,
647 GtkWidget *attach_widget,
648 GtkMenuDetachFunc detacher)
650 GtkMenuAttachData *data;
652 g_return_if_fail (GTK_IS_MENU (menu));
653 g_return_if_fail (GTK_IS_WIDGET (attach_widget));
654 g_return_if_fail (detacher != NULL);
656 /* keep this function in sync with gtk_widget_set_parent()
659 data = g_object_get_data (G_OBJECT (menu), attach_data_key);
662 g_warning ("gtk_menu_attach_to_widget(): menu already attached to %s",
663 g_type_name (G_TYPE_FROM_INSTANCE (data->attach_widget)));
668 gtk_object_sink (GTK_OBJECT (menu));
670 data = g_new (GtkMenuAttachData, 1);
671 data->attach_widget = attach_widget;
673 g_signal_connect (attach_widget, "screen_changed",
674 G_CALLBACK (attach_widget_screen_changed), menu);
675 attach_widget_screen_changed (attach_widget, NULL, menu);
677 data->detacher = detacher;
678 g_object_set_data (G_OBJECT (menu), attach_data_key, data);
680 if (GTK_WIDGET_STATE (menu) != GTK_STATE_NORMAL)
681 gtk_widget_set_state (GTK_WIDGET (menu), GTK_STATE_NORMAL);
683 /* we don't need to set the style here, since
684 * we are a toplevel widget.
687 /* Fallback title for menu comes from attach widget */
688 gtk_menu_update_title (menu);
692 gtk_menu_get_attach_widget (GtkMenu *menu)
694 GtkMenuAttachData *data;
696 g_return_val_if_fail (GTK_IS_MENU (menu), NULL);
698 data = g_object_get_data (G_OBJECT (menu), attach_data_key);
700 return data->attach_widget;
705 gtk_menu_detach (GtkMenu *menu)
707 GtkMenuAttachData *data;
709 g_return_if_fail (GTK_IS_MENU (menu));
711 /* keep this function in sync with gtk_widget_unparent()
713 data = g_object_get_data (G_OBJECT (menu), attach_data_key);
716 g_warning ("gtk_menu_detach(): menu is not attached");
719 g_object_set_data (G_OBJECT (menu), attach_data_key, NULL);
721 g_signal_handlers_disconnect_by_func (data->attach_widget,
722 (gpointer) attach_widget_screen_changed,
725 data->detacher (data->attach_widget, menu);
727 if (GTK_WIDGET_REALIZED (menu))
728 gtk_widget_unrealize (GTK_WIDGET (menu));
732 /* Fallback title for menu comes from attach widget */
733 gtk_menu_update_title (menu);
735 g_object_unref (menu);
739 gtk_menu_remove (GtkContainer *container,
743 g_return_if_fail (GTK_IS_MENU (container));
744 g_return_if_fail (GTK_IS_MENU_ITEM (widget));
746 menu = GTK_MENU (container);
748 /* Clear out old_active_menu_item if it matches the item we are removing
750 if (menu->old_active_menu_item == widget)
752 g_object_unref (menu->old_active_menu_item);
753 menu->old_active_menu_item = NULL;
756 GTK_CONTAINER_CLASS (parent_class)->remove (container, widget);
763 return g_object_new (GTK_TYPE_MENU, NULL);
767 gtk_menu_real_insert (GtkMenuShell *menu_shell,
771 if (GTK_WIDGET_REALIZED (menu_shell))
772 gtk_widget_set_parent_window (child, GTK_MENU (menu_shell)->bin_window);
774 GTK_MENU_SHELL_CLASS (parent_class)->insert (menu_shell, child, position);
778 gtk_menu_tearoff_bg_copy (GtkMenu *menu)
783 widget = GTK_WIDGET (menu);
789 GdkGCValues gc_values;
791 menu->tearoff_active = FALSE;
792 menu->saved_scroll_offset = menu->scroll_offset;
794 gc_values.subwindow_mode = GDK_INCLUDE_INFERIORS;
795 gc = gdk_gc_new_with_values (widget->window,
796 &gc_values, GDK_GC_SUBWINDOW);
798 gdk_drawable_get_size (menu->tearoff_window->window, &width, &height);
800 pixmap = gdk_pixmap_new (menu->tearoff_window->window,
805 gdk_draw_drawable (pixmap, gc,
806 menu->tearoff_window->window,
810 gtk_widget_set_size_request (menu->tearoff_window,
814 gdk_window_set_back_pixmap (menu->tearoff_window->window, pixmap, FALSE);
815 g_object_unref (pixmap);
820 popup_grab_on_window (GdkWindow *window,
821 guint32 activate_time)
823 if ((gdk_pointer_grab (window, TRUE,
824 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
825 GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK |
826 GDK_POINTER_MOTION_MASK,
827 NULL, NULL, activate_time) == 0))
829 if (gdk_keyboard_grab (window, TRUE,
834 gdk_display_pointer_ungrab (gdk_drawable_get_display (window),
846 * @parent_menu_shell: the menu shell containing the triggering menu item, or %NULL
847 * @parent_menu_item: the menu item whose activation triggered the popup, or %NULL
848 * @func: a user supplied function used to position the menu, or %NULL
849 * @data: user supplied data to be passed to @func.
850 * @button: the mouse button which was pressed to initiate the event.
851 * @activate_time: the time at which the activation event occurred.
853 * Displays a menu and makes it available for selection. Applications can use
854 * this function to display context-sensitive menus, and will typically supply
855 * %NULL for the @parent_menu_shell, @parent_menu_item, @func and @data
856 * parameters. The default menu positioning function will position the menu
857 * at the current mouse cursor position.
859 * The @button parameter should be the mouse button pressed to initiate
860 * the menu popup. If the menu popup was initiated by something other than
861 * a mouse button press, such as a mouse button release or a keypress,
862 * @button should be 0.
864 * The @activate_time parameter should be the time stamp of the event that
865 * initiated the popup. If such an event is not available, use
866 * gtk_get_current_event_time() instead.
870 gtk_menu_popup (GtkMenu *menu,
871 GtkWidget *parent_menu_shell,
872 GtkWidget *parent_menu_item,
873 GtkMenuPositionFunc func,
876 guint32 activate_time)
879 GtkWidget *xgrab_shell;
881 GdkEvent *current_event;
882 GtkMenuShell *menu_shell;
884 g_return_if_fail (GTK_IS_MENU (menu));
886 widget = GTK_WIDGET (menu);
887 menu_shell = GTK_MENU_SHELL (menu);
889 menu_shell->parent_menu_shell = parent_menu_shell;
891 /* Find the last viewable ancestor, and make an X grab on it
893 parent = GTK_WIDGET (menu);
897 gboolean viewable = TRUE;
898 GtkWidget *tmp = parent;
902 if (!GTK_WIDGET_MAPPED (tmp))
911 xgrab_shell = parent;
913 parent = GTK_MENU_SHELL (parent)->parent_menu_shell;
916 /* We want to receive events generated when we map the menu; unfortunately,
917 * since there is probably already an implicit grab in place from the
918 * button that the user used to pop up the menu, we won't receive then --
919 * in particular, the EnterNotify when the menu pops up under the pointer.
921 * If we are grabbing on a parent menu shell, no problem; just grab on
922 * that menu shell first before popping up the window with owner_events = TRUE.
924 * When grabbing on the menu itself, things get more convuluted - we
925 * we do an explicit grab on a specially created window with
926 * owner_events = TRUE, which we override further down with a grab
927 * on the menu. (We can't grab on the menu until it is mapped; we
928 * probably could just leave the grab on the other window, with a
929 * little reorganization of the code in gtkmenu*).
931 if (xgrab_shell && xgrab_shell != widget)
933 if (popup_grab_on_window (xgrab_shell->window, activate_time))
934 GTK_MENU_SHELL (xgrab_shell)->have_xgrab = TRUE;
938 GdkWindow *transfer_window;
940 xgrab_shell = widget;
941 transfer_window = menu_grab_transfer_window_get (menu);
942 if (popup_grab_on_window (transfer_window, activate_time))
943 GTK_MENU_SHELL (xgrab_shell)->have_xgrab = TRUE;
946 if (!GTK_MENU_SHELL (xgrab_shell)->have_xgrab)
948 /* We failed to make our pointer/keyboard grab. Rather than leaving the user
949 * with a stuck up window, we just abort here. Presumably the user will
952 menu_shell->parent_menu_shell = NULL;
953 menu_grab_transfer_window_destroy (menu);
957 menu_shell->active = TRUE;
958 menu_shell->button = button;
960 /* If we are popping up the menu from something other than, a button
961 * press then, as a heuristic, we ignore enter events for the menu
962 * until we get a MOTION_NOTIFY.
965 current_event = gtk_get_current_event ();
968 if ((current_event->type != GDK_BUTTON_PRESS) &&
969 (current_event->type != GDK_ENTER_NOTIFY))
970 menu_shell->ignore_enter = TRUE;
972 gdk_event_free (current_event);
977 gtk_menu_tearoff_bg_copy (menu);
979 gtk_menu_reparent (menu, menu->toplevel, FALSE);
982 menu->parent_menu_item = parent_menu_item;
983 menu->position_func = func;
984 menu->position_func_data = data;
985 menu_shell->activate_time = activate_time;
987 /* We need to show the menu here rather in the init function because
988 * code expects to be able to tell if the menu is onscreen by
989 * looking at the GTK_WIDGET_VISIBLE (menu)
991 gtk_widget_show (GTK_WIDGET (menu));
993 /* Position the menu, possibly changing the size request
995 gtk_menu_position (menu);
997 /* Compute the size of the toplevel and realize it so we
998 * can scroll correctly.
1001 GtkRequisition tmp_request;
1002 GtkAllocation tmp_allocation = { 0, };
1004 gtk_widget_size_request (menu->toplevel, &tmp_request);
1006 tmp_allocation.width = tmp_request.width;
1007 tmp_allocation.height = tmp_request.height;
1009 gtk_widget_size_allocate (menu->toplevel, &tmp_allocation);
1011 gtk_widget_realize (GTK_WIDGET (menu));
1014 gtk_menu_scroll_to (menu, menu->scroll_offset);
1016 /* Once everything is set up correctly, map the toplevel window on
1019 gtk_widget_show (menu->toplevel);
1021 if (xgrab_shell == widget)
1022 popup_grab_on_window (widget->window, activate_time); /* Should always succeed */
1024 gtk_grab_add (GTK_WIDGET (menu));
1028 gtk_menu_popdown (GtkMenu *menu)
1030 GtkMenuPrivate *private;
1031 GtkMenuShell *menu_shell;
1033 g_return_if_fail (GTK_IS_MENU (menu));
1035 menu_shell = GTK_MENU_SHELL (menu);
1036 private = gtk_menu_get_private (menu);
1038 menu_shell->parent_menu_shell = NULL;
1039 menu_shell->active = FALSE;
1040 menu_shell->ignore_enter = FALSE;
1042 private->have_position = FALSE;
1044 gtk_menu_stop_scrolling (menu);
1046 gtk_menu_stop_navigating_submenu (menu);
1048 if (menu_shell->active_menu_item)
1050 if (menu->old_active_menu_item)
1051 g_object_unref (menu->old_active_menu_item);
1052 menu->old_active_menu_item = menu_shell->active_menu_item;
1053 g_object_ref (menu->old_active_menu_item);
1056 gtk_menu_shell_deselect (menu_shell);
1058 /* The X Grab, if present, will automatically be removed when we hide
1060 gtk_widget_hide (menu->toplevel);
1064 gtk_widget_set_size_request (menu->tearoff_window, -1, -1);
1066 if (GTK_BIN (menu->toplevel)->child)
1068 gtk_menu_reparent (menu, menu->tearoff_hbox, TRUE);
1072 /* We popped up the menu from the tearoff, so we need to
1073 * release the grab - we aren't actually hiding the menu.
1075 if (menu_shell->have_xgrab)
1077 GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET (menu));
1079 gdk_display_pointer_ungrab (display, GDK_CURRENT_TIME);
1080 gdk_display_keyboard_ungrab (display, GDK_CURRENT_TIME);
1084 /* gtk_menu_popdown is called each time a menu item is selected from
1085 * a torn off menu. Only scroll back to the saved position if the
1086 * non-tearoff menu was popped down.
1088 if (!menu->tearoff_active)
1089 gtk_menu_scroll_to (menu, menu->saved_scroll_offset);
1090 menu->tearoff_active = TRUE;
1093 gtk_widget_hide (GTK_WIDGET (menu));
1095 menu_shell->have_xgrab = FALSE;
1096 gtk_grab_remove (GTK_WIDGET (menu));
1098 menu_grab_transfer_window_destroy (menu);
1102 gtk_menu_get_active (GtkMenu *menu)
1107 g_return_val_if_fail (GTK_IS_MENU (menu), NULL);
1109 if (!menu->old_active_menu_item)
1112 children = GTK_MENU_SHELL (menu)->children;
1116 child = children->data;
1117 children = children->next;
1119 if (GTK_BIN (child)->child)
1124 menu->old_active_menu_item = child;
1125 if (menu->old_active_menu_item)
1126 g_object_ref (menu->old_active_menu_item);
1129 return menu->old_active_menu_item;
1133 gtk_menu_set_active (GtkMenu *menu,
1139 g_return_if_fail (GTK_IS_MENU (menu));
1141 tmp_list = g_list_nth (GTK_MENU_SHELL (menu)->children, index);
1144 child = tmp_list->data;
1145 if (GTK_BIN (child)->child)
1147 if (menu->old_active_menu_item)
1148 g_object_unref (menu->old_active_menu_item);
1149 menu->old_active_menu_item = child;
1150 g_object_ref (menu->old_active_menu_item);
1156 gtk_menu_set_accel_group (GtkMenu *menu,
1157 GtkAccelGroup *accel_group)
1159 g_return_if_fail (GTK_IS_MENU (menu));
1161 if (menu->accel_group != accel_group)
1163 if (menu->accel_group)
1164 g_object_unref (menu->accel_group);
1165 menu->accel_group = accel_group;
1166 if (menu->accel_group)
1167 g_object_ref (menu->accel_group);
1168 _gtk_menu_refresh_accel_paths (menu, TRUE);
1173 gtk_menu_get_accel_group (GtkMenu *menu)
1175 g_return_val_if_fail (GTK_IS_MENU (menu), NULL);
1177 return menu->accel_group;
1181 * gtk_menu_set_accel_path
1182 * @menu: a valid #GtkMenu
1183 * @accel_path: a valid accelerator path
1185 * Sets an accelerator path for this menu from which accelerator paths
1186 * for its immediate children, its menu items, can be constructed.
1187 * The main purpose of this function is to spare the programmer the
1188 * inconvenience of having to call gtk_menu_item_set_accel_path() on
1189 * each menu item that should support runtime user changable accelerators.
1190 * Instead, by just calling gtk_menu_set_accel_path() on their parent,
1191 * each menu item of this menu, that contains a label describing its purpose,
1192 * automatically gets an accel path assigned. For example, a menu containing
1193 * menu items "New" and "Exit", will, after
1194 * <literal>gtk_menu_set_accel_path (menu, "<Gnumeric-Sheet>/File");</literal>
1195 * has been called, assign its items the accel paths:
1196 * <literal>"<Gnumeric-Sheet>/File/New"</literal> and <literal>"<Gnumeric-Sheet>/File/Exit"</literal>.
1197 * Assigning accel paths to menu items then enables the user to change
1198 * their accelerators at runtime. More details about accelerator paths
1199 * and their default setups can be found at gtk_accel_map_add_entry().
1202 gtk_menu_set_accel_path (GtkMenu *menu,
1203 const gchar *accel_path)
1205 g_return_if_fail (GTK_IS_MENU (menu));
1207 g_return_if_fail (accel_path[0] == '<' && strchr (accel_path, '/')); /* simplistic check */
1209 g_free (menu->accel_path);
1210 menu->accel_path = g_strdup (accel_path);
1211 if (menu->accel_path)
1212 _gtk_menu_refresh_accel_paths (menu, FALSE);
1217 gboolean group_changed;
1221 refresh_accel_paths_foreach (GtkWidget *widget,
1224 AccelPropagation *prop = data;
1226 if (GTK_IS_MENU_ITEM (widget)) /* should always be true */
1227 _gtk_menu_item_refresh_accel_path (GTK_MENU_ITEM (widget),
1228 prop->menu->accel_path,
1229 prop->menu->accel_group,
1230 prop->group_changed);
1234 _gtk_menu_refresh_accel_paths (GtkMenu *menu,
1235 gboolean group_changed)
1237 g_return_if_fail (GTK_IS_MENU (menu));
1239 if (menu->accel_path && menu->accel_group)
1241 AccelPropagation prop;
1244 prop.group_changed = group_changed;
1245 gtk_container_foreach (GTK_CONTAINER (menu),
1246 refresh_accel_paths_foreach,
1252 gtk_menu_reposition (GtkMenu *menu)
1254 g_return_if_fail (GTK_IS_MENU (menu));
1256 if (GTK_WIDGET_DRAWABLE (menu) && !menu->torn_off)
1257 gtk_menu_position (menu);
1261 gtk_menu_scrollbar_changed (GtkAdjustment *adjustment,
1264 g_return_if_fail (GTK_IS_MENU (menu));
1266 if (adjustment->value != menu->scroll_offset)
1267 gtk_menu_scroll_to (menu, adjustment->value);
1271 gtk_menu_set_tearoff_hints (GtkMenu *menu,
1274 GdkGeometry geometry_hints;
1276 if (!menu->tearoff_window)
1279 if (GTK_WIDGET_VISIBLE (menu->tearoff_scrollbar))
1281 gtk_widget_size_request (menu->tearoff_scrollbar, NULL);
1282 width += menu->tearoff_scrollbar->requisition.width;
1285 geometry_hints.min_width = width;
1286 geometry_hints.max_width = width;
1288 geometry_hints.min_height = 0;
1289 geometry_hints.max_height = GTK_WIDGET (menu)->requisition.height;
1291 gtk_window_set_geometry_hints (GTK_WINDOW (menu->tearoff_window),
1294 GDK_HINT_MAX_SIZE|GDK_HINT_MIN_SIZE);
1298 gtk_menu_update_title (GtkMenu *menu)
1300 if (menu->tearoff_window)
1303 GtkWidget *attach_widget;
1305 title = gtk_menu_get_title (menu);
1308 attach_widget = gtk_menu_get_attach_widget (menu);
1309 if (GTK_IS_MENU_ITEM (attach_widget))
1311 GtkWidget *child = GTK_BIN (attach_widget)->child;
1312 if (GTK_IS_LABEL (child))
1313 title = gtk_label_get_text (GTK_LABEL (child));
1318 gtk_window_set_title (GTK_WINDOW (menu->tearoff_window), title);
1323 gtk_menu_set_tearoff_state (GtkMenu *menu,
1328 g_return_if_fail (GTK_IS_MENU (menu));
1330 if (menu->torn_off != torn_off)
1332 menu->torn_off = torn_off;
1333 menu->tearoff_active = torn_off;
1337 if (GTK_WIDGET_VISIBLE (menu))
1338 gtk_menu_popdown (menu);
1340 if (!menu->tearoff_window)
1342 menu->tearoff_window = gtk_widget_new (GTK_TYPE_WINDOW,
1343 "type", GTK_WINDOW_TOPLEVEL,
1344 "screen", gtk_widget_get_screen (menu->toplevel),
1345 "app_paintable", TRUE,
1348 gtk_window_set_type_hint (GTK_WINDOW (menu->tearoff_window),
1349 GDK_WINDOW_TYPE_HINT_MENU);
1350 gtk_window_set_mnemonic_modifier (GTK_WINDOW (menu->tearoff_window), 0);
1351 g_signal_connect (menu->tearoff_window, "destroy",
1352 G_CALLBACK (gtk_widget_destroyed), &menu->tearoff_window);
1353 g_signal_connect (menu->tearoff_window, "event",
1354 G_CALLBACK (gtk_menu_window_event), menu);
1356 gtk_menu_update_title (menu);
1358 gtk_widget_realize (menu->tearoff_window);
1360 menu->tearoff_hbox = gtk_hbox_new (FALSE, FALSE);
1361 gtk_container_add (GTK_CONTAINER (menu->tearoff_window), menu->tearoff_hbox);
1363 gdk_drawable_get_size (GTK_WIDGET (menu)->window, &width, &height);
1364 menu->tearoff_adjustment =
1365 GTK_ADJUSTMENT (gtk_adjustment_new (0,
1367 GTK_WIDGET (menu)->requisition.height,
1371 g_object_connect (menu->tearoff_adjustment,
1372 "signal::value_changed", gtk_menu_scrollbar_changed, menu,
1374 menu->tearoff_scrollbar = gtk_vscrollbar_new (menu->tearoff_adjustment);
1376 gtk_box_pack_end (GTK_BOX (menu->tearoff_hbox),
1377 menu->tearoff_scrollbar,
1380 if (menu->tearoff_adjustment->upper > height)
1381 gtk_widget_show (menu->tearoff_scrollbar);
1383 gtk_widget_show (menu->tearoff_hbox);
1386 gtk_menu_reparent (menu, menu->tearoff_hbox, FALSE);
1388 gdk_drawable_get_size (GTK_WIDGET (menu)->window, &width, NULL);
1390 /* Update menu->requisition
1392 gtk_widget_size_request (GTK_WIDGET (menu), NULL);
1394 gtk_menu_set_tearoff_hints (menu, width);
1396 gtk_widget_realize (menu->tearoff_window);
1397 gtk_menu_position (menu);
1399 gtk_widget_show (GTK_WIDGET (menu));
1400 gtk_widget_show (menu->tearoff_window);
1402 gtk_menu_scroll_to (menu, 0);
1407 gtk_widget_hide (menu->tearoff_window);
1408 gtk_menu_reparent (menu, menu->toplevel, FALSE);
1409 gtk_widget_destroy (menu->tearoff_window);
1411 menu->tearoff_window = NULL;
1412 menu->tearoff_hbox = NULL;
1413 menu->tearoff_scrollbar = NULL;
1414 menu->tearoff_adjustment = NULL;
1420 * gtk_menu_get_tearoff_state:
1423 * Returns whether the menu is torn off. See
1424 * gtk_menu_set_tearoff_state ().
1426 * Return value: %TRUE if the menu is currently torn off.
1429 gtk_menu_get_tearoff_state (GtkMenu *menu)
1431 g_return_val_if_fail (GTK_IS_MENU (menu), FALSE);
1433 return menu->torn_off;
1437 * gtk_menu_set_title:
1439 * @title: a string containing the title for the menu.
1441 * Sets the title string for the menu. The title is displayed when the menu
1442 * is shown as a tearoff menu.
1445 gtk_menu_set_title (GtkMenu *menu,
1448 g_return_if_fail (GTK_IS_MENU (menu));
1451 g_object_set_data_full (G_OBJECT (menu), "gtk-menu-title",
1452 g_strdup (title), (GtkDestroyNotify) g_free);
1454 g_object_set_data (G_OBJECT (menu), "gtk-menu-title", NULL);
1456 gtk_menu_update_title (menu);
1457 g_object_notify (G_OBJECT (menu), "tearoff_title");
1461 * gtk_menu_get_title:
1464 * Returns the title of the menu. See gtk_menu_set_title().
1466 * Return value: the title of the menu, or %NULL if the menu has no
1467 * title set on it. This string is owned by the widget and should
1468 * not be modified or freed.
1470 G_CONST_RETURN gchar *
1471 gtk_menu_get_title (GtkMenu *menu)
1473 g_return_val_if_fail (GTK_IS_MENU (menu), NULL);
1475 return g_object_get_data (G_OBJECT (menu), "gtk-menu-title");
1479 gtk_menu_reorder_child (GtkMenu *menu,
1483 GtkMenuShell *menu_shell;
1484 g_return_if_fail (GTK_IS_MENU (menu));
1485 g_return_if_fail (GTK_IS_MENU_ITEM (child));
1486 menu_shell = GTK_MENU_SHELL (menu);
1487 if (g_list_find (menu_shell->children, child))
1489 menu_shell->children = g_list_remove (menu_shell->children, child);
1490 menu_shell->children = g_list_insert (menu_shell->children, child, position);
1491 if (GTK_WIDGET_VISIBLE (menu_shell))
1492 gtk_widget_queue_resize (GTK_WIDGET (menu_shell));
1497 gtk_menu_style_set (GtkWidget *widget,
1498 GtkStyle *previous_style)
1500 if (GTK_WIDGET_REALIZED (widget))
1502 GtkMenu *menu = GTK_MENU (widget);
1504 gtk_style_set_background (widget->style, menu->bin_window, GTK_STATE_NORMAL);
1505 gtk_style_set_background (widget->style, menu->view_window, GTK_STATE_NORMAL);
1506 gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL);
1511 gtk_menu_realize (GtkWidget *widget)
1513 GdkWindowAttr attributes;
1514 gint attributes_mask;
1519 guint vertical_padding;
1521 g_return_if_fail (GTK_IS_MENU (widget));
1523 menu = GTK_MENU (widget);
1525 GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
1527 attributes.window_type = GDK_WINDOW_CHILD;
1528 attributes.x = widget->allocation.x;
1529 attributes.y = widget->allocation.y;
1530 attributes.width = widget->allocation.width;
1531 attributes.height = widget->allocation.height;
1532 attributes.wclass = GDK_INPUT_OUTPUT;
1533 attributes.visual = gtk_widget_get_visual (widget);
1534 attributes.colormap = gtk_widget_get_colormap (widget);
1536 attributes.event_mask = gtk_widget_get_events (widget);
1537 attributes.event_mask |= (GDK_EXPOSURE_MASK | GDK_KEY_PRESS_MASK |
1538 GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK );
1540 attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
1541 widget->window = gdk_window_new (gtk_widget_get_parent_window (widget), &attributes, attributes_mask);
1542 gdk_window_set_user_data (widget->window, widget);
1544 border_width = GTK_CONTAINER (widget)->border_width;
1546 gtk_widget_style_get (GTK_WIDGET (menu),
1547 "vertical-padding", &vertical_padding,
1550 attributes.x = border_width + widget->style->xthickness;
1551 attributes.y = border_width + widget->style->ythickness + vertical_padding;
1552 attributes.width = MAX (1, widget->allocation.width - attributes.x * 2);
1553 attributes.height = MAX (1, widget->allocation.height - attributes.y * 2);
1555 if (menu->upper_arrow_visible)
1557 attributes.y += MENU_SCROLL_ARROW_HEIGHT;
1558 attributes.height -= MENU_SCROLL_ARROW_HEIGHT;
1560 if (menu->lower_arrow_visible)
1561 attributes.height -= MENU_SCROLL_ARROW_HEIGHT;
1563 menu->view_window = gdk_window_new (widget->window, &attributes, attributes_mask);
1564 gdk_window_set_user_data (menu->view_window, menu);
1568 attributes.height = MAX (1, widget->requisition.height - (border_width + widget->style->ythickness + vertical_padding) * 2);
1570 menu->bin_window = gdk_window_new (menu->view_window, &attributes, attributes_mask);
1571 gdk_window_set_user_data (menu->bin_window, menu);
1573 children = GTK_MENU_SHELL (menu)->children;
1576 child = children->data;
1577 children = children->next;
1579 gtk_widget_set_parent_window (child, menu->bin_window);
1582 widget->style = gtk_style_attach (widget->style, widget->window);
1583 gtk_style_set_background (widget->style, menu->bin_window, GTK_STATE_NORMAL);
1584 gtk_style_set_background (widget->style, menu->view_window, GTK_STATE_NORMAL);
1585 gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL);
1587 if (GTK_MENU_SHELL (widget)->active_menu_item)
1588 gtk_menu_scroll_item_visible (GTK_MENU_SHELL (widget),
1589 GTK_MENU_SHELL (widget)->active_menu_item);
1591 gdk_window_show (menu->bin_window);
1592 gdk_window_show (menu->view_window);
1596 gtk_menu_focus (GtkWidget *widget,
1597 GtkDirectionType direction)
1600 * A menu or its menu items cannot have focus
1605 /* See notes in gtk_menu_popup() for information about the "grab transfer window"
1608 menu_grab_transfer_window_get (GtkMenu *menu)
1610 GdkWindow *window = g_object_get_data (G_OBJECT (menu), "gtk-menu-transfer-window");
1613 GdkWindowAttr attributes;
1614 gint attributes_mask;
1616 attributes.x = -100;
1617 attributes.y = -100;
1618 attributes.width = 10;
1619 attributes.height = 10;
1620 attributes.window_type = GDK_WINDOW_TEMP;
1621 attributes.wclass = GDK_INPUT_ONLY;
1622 attributes.override_redirect = TRUE;
1623 attributes.event_mask = 0;
1625 attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_NOREDIR;
1627 window = gdk_window_new (gtk_widget_get_root_window (GTK_WIDGET (menu)),
1628 &attributes, attributes_mask);
1629 gdk_window_set_user_data (window, menu);
1631 gdk_window_show (window);
1633 g_object_set_data (G_OBJECT (menu), "gtk-menu-transfer-window", window);
1640 menu_grab_transfer_window_destroy (GtkMenu *menu)
1642 GdkWindow *window = g_object_get_data (G_OBJECT (menu), "gtk-menu-transfer-window");
1645 gdk_window_set_user_data (window, NULL);
1646 gdk_window_destroy (window);
1647 g_object_set_data (G_OBJECT (menu), "gtk-menu-transfer-window", NULL);
1652 gtk_menu_unrealize (GtkWidget *widget)
1656 g_return_if_fail (GTK_IS_MENU (widget));
1658 menu = GTK_MENU (widget);
1660 menu_grab_transfer_window_destroy (menu);
1662 gdk_window_set_user_data (menu->view_window, NULL);
1663 gdk_window_destroy (menu->view_window);
1664 menu->view_window = NULL;
1666 gdk_window_set_user_data (menu->bin_window, NULL);
1667 gdk_window_destroy (menu->bin_window);
1668 menu->bin_window = NULL;
1670 (* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget);
1674 gtk_menu_size_request (GtkWidget *widget,
1675 GtkRequisition *requisition)
1678 GtkMenuShell *menu_shell;
1681 guint max_toggle_size;
1682 guint max_accel_width;
1683 guint vertical_padding;
1684 GtkRequisition child_requisition;
1686 g_return_if_fail (GTK_IS_MENU (widget));
1687 g_return_if_fail (requisition != NULL);
1689 menu = GTK_MENU (widget);
1690 menu_shell = GTK_MENU_SHELL (widget);
1692 requisition->width = 0;
1693 requisition->height = 0;
1695 max_toggle_size = 0;
1696 max_accel_width = 0;
1698 children = menu_shell->children;
1701 child = children->data;
1702 children = children->next;
1704 if (GTK_WIDGET_VISIBLE (child))
1708 /* It's important to size_request the child
1709 * before doing the toggle size request, in
1710 * case the toggle size request depends on the size
1711 * request of a child of the child (e.g. for ImageMenuItem)
1714 GTK_MENU_ITEM (child)->show_submenu_indicator = TRUE;
1715 gtk_widget_size_request (child, &child_requisition);
1717 requisition->width = MAX (requisition->width, child_requisition.width);
1718 requisition->height += child_requisition.height;
1720 gtk_menu_item_toggle_size_request (GTK_MENU_ITEM (child), &toggle_size);
1721 max_toggle_size = MAX (max_toggle_size, toggle_size);
1722 max_accel_width = MAX (max_accel_width, GTK_MENU_ITEM (child)->accelerator_width);
1726 requisition->width += max_toggle_size + max_accel_width;
1727 requisition->width += (GTK_CONTAINER (menu)->border_width +
1728 widget->style->xthickness) * 2;
1730 gtk_widget_style_get (GTK_WIDGET (menu),
1731 "vertical-padding", &vertical_padding,
1733 requisition->height += (GTK_CONTAINER (menu)->border_width + vertical_padding +
1734 widget->style->ythickness) * 2;
1736 menu->toggle_size = max_toggle_size;
1738 /* Don't resize the tearoff if it is not active, because it won't redraw (it is only a background pixmap).
1740 if (menu->tearoff_active)
1741 gtk_menu_set_tearoff_hints (menu, requisition->width);
1745 gtk_menu_size_allocate (GtkWidget *widget,
1746 GtkAllocation *allocation)
1749 GtkMenuShell *menu_shell;
1751 GtkAllocation child_allocation;
1755 guint vertical_padding;
1757 g_return_if_fail (GTK_IS_MENU (widget));
1758 g_return_if_fail (allocation != NULL);
1760 menu = GTK_MENU (widget);
1761 menu_shell = GTK_MENU_SHELL (widget);
1763 widget->allocation = *allocation;
1765 gtk_widget_style_get (GTK_WIDGET (menu),
1766 "vertical-padding", &vertical_padding,
1769 x = GTK_CONTAINER (menu)->border_width + widget->style->xthickness;
1770 y = GTK_CONTAINER (menu)->border_width + widget->style->ythickness + vertical_padding;
1772 width = MAX (1, allocation->width - x * 2);
1773 height = MAX (1, allocation->height - y * 2);
1775 if (menu_shell->active)
1776 gtk_menu_scroll_to (menu, menu->scroll_offset);
1778 if (menu->upper_arrow_visible && !menu->tearoff_active)
1780 y += MENU_SCROLL_ARROW_HEIGHT;
1781 height -= MENU_SCROLL_ARROW_HEIGHT;
1784 if (menu->lower_arrow_visible && !menu->tearoff_active)
1785 height -= MENU_SCROLL_ARROW_HEIGHT;
1787 if (GTK_WIDGET_REALIZED (widget))
1789 gdk_window_move_resize (widget->window,
1790 allocation->x, allocation->y,
1791 allocation->width, allocation->height);
1793 gdk_window_move_resize (menu->view_window,
1800 if (menu_shell->children)
1802 child_allocation.x = 0;
1803 child_allocation.y = 0;
1804 child_allocation.width = width;
1806 children = menu_shell->children;
1809 child = children->data;
1810 children = children->next;
1812 if (GTK_WIDGET_VISIBLE (child))
1814 GtkRequisition child_requisition;
1815 gtk_widget_get_child_requisition (child, &child_requisition);
1817 child_allocation.height = child_requisition.height;
1819 gtk_menu_item_toggle_size_allocate (GTK_MENU_ITEM (child),
1822 gtk_widget_size_allocate (child, &child_allocation);
1823 gtk_widget_queue_draw (child);
1825 child_allocation.y += child_allocation.height;
1829 /* Resize the item window */
1830 if (GTK_WIDGET_REALIZED (widget))
1832 gdk_window_resize (menu->bin_window,
1833 child_allocation.width,
1834 child_allocation.y);
1838 if (menu->tearoff_active)
1840 if (allocation->height >= widget->requisition.height)
1842 if (GTK_WIDGET_VISIBLE (menu->tearoff_scrollbar))
1844 gtk_widget_hide (menu->tearoff_scrollbar);
1845 gtk_menu_set_tearoff_hints (menu, allocation->width);
1847 gtk_menu_scroll_to (menu, 0);
1852 menu->tearoff_adjustment->upper = widget->requisition.height;
1853 menu->tearoff_adjustment->page_size = allocation->height;
1855 if (menu->tearoff_adjustment->value + menu->tearoff_adjustment->page_size >
1856 menu->tearoff_adjustment->upper)
1859 value = menu->tearoff_adjustment->upper - menu->tearoff_adjustment->page_size;
1862 gtk_menu_scroll_to (menu, value);
1865 gtk_adjustment_changed (menu->tearoff_adjustment);
1867 if (!GTK_WIDGET_VISIBLE (menu->tearoff_scrollbar))
1869 gtk_widget_show (menu->tearoff_scrollbar);
1870 gtk_menu_set_tearoff_hints (menu, allocation->width);
1878 gtk_menu_paint (GtkWidget *widget,
1879 GdkEventExpose *event)
1883 gint border_x, border_y;
1884 guint vertical_padding;
1886 g_return_if_fail (GTK_IS_MENU (widget));
1888 menu = GTK_MENU (widget);
1890 gtk_widget_style_get (GTK_WIDGET (menu),
1891 "vertical-padding", &vertical_padding,
1894 border_x = GTK_CONTAINER (widget)->border_width + widget->style->xthickness;
1895 border_y = GTK_CONTAINER (widget)->border_width + widget->style->ythickness + vertical_padding;
1896 gdk_drawable_get_size (widget->window, &width, &height);
1898 if (event->window == widget->window)
1900 gint arrow_space = MENU_SCROLL_ARROW_HEIGHT - 2 * widget->style->ythickness;
1901 gint arrow_size = 0.7 * arrow_space;
1903 gtk_paint_box (widget->style,
1907 NULL, widget, "menu",
1909 if (menu->upper_arrow_visible && !menu->tearoff_active)
1911 gtk_paint_box (widget->style,
1913 menu->upper_arrow_prelight ?
1914 GTK_STATE_PRELIGHT : GTK_STATE_NORMAL,
1916 NULL, widget, "menu",
1919 width - 2 * border_x,
1920 MENU_SCROLL_ARROW_HEIGHT);
1922 gtk_paint_arrow (widget->style,
1924 menu->upper_arrow_prelight ?
1925 GTK_STATE_PRELIGHT : GTK_STATE_NORMAL,
1927 NULL, widget, "menu_scroll_arrow_up",
1930 (width - arrow_size ) / 2,
1931 border_y + widget->style->ythickness + (arrow_space - arrow_size)/2,
1932 arrow_size, arrow_size);
1935 if (menu->lower_arrow_visible && !menu->tearoff_active)
1937 gtk_paint_box (widget->style,
1939 menu->lower_arrow_prelight ?
1940 GTK_STATE_PRELIGHT : GTK_STATE_NORMAL,
1942 NULL, widget, "menu",
1944 height - border_y - MENU_SCROLL_ARROW_HEIGHT,
1946 MENU_SCROLL_ARROW_HEIGHT);
1948 gtk_paint_arrow (widget->style,
1950 menu->lower_arrow_prelight ?
1951 GTK_STATE_PRELIGHT : GTK_STATE_NORMAL,
1953 NULL, widget, "menu_scroll_arrow_down",
1956 (width - arrow_size) / 2,
1957 height - border_y - MENU_SCROLL_ARROW_HEIGHT +
1958 widget->style->ythickness + (arrow_space - arrow_size)/2,
1959 arrow_size, arrow_size);
1965 gtk_menu_expose (GtkWidget *widget,
1966 GdkEventExpose *event)
1968 g_return_val_if_fail (GTK_IS_MENU (widget), FALSE);
1969 g_return_val_if_fail (event != NULL, FALSE);
1971 if (GTK_WIDGET_DRAWABLE (widget))
1973 gtk_menu_paint (widget, event);
1975 (* GTK_WIDGET_CLASS (parent_class)->expose_event) (widget, event);
1982 gtk_menu_show (GtkWidget *widget)
1984 GtkMenu *menu = GTK_MENU (widget);
1986 _gtk_menu_refresh_accel_paths (menu, FALSE);
1988 GTK_WIDGET_CLASS (parent_class)->show (widget);
1992 gtk_menu_button_press (GtkWidget *widget,
1993 GdkEventButton *event)
1995 /* Don't pop down the menu for releases over scroll arrows
1997 if (GTK_IS_MENU (widget))
1999 GtkMenu *menu = GTK_MENU (widget);
2001 if (menu->upper_arrow_prelight || menu->lower_arrow_prelight)
2005 return GTK_WIDGET_CLASS (parent_class)->button_press_event (widget, event);
2009 gtk_menu_button_release (GtkWidget *widget,
2010 GdkEventButton *event)
2012 /* Don't pop down the menu for releases over scroll arrows
2014 if (GTK_IS_MENU (widget))
2016 GtkMenu *menu = GTK_MENU (widget);
2018 if (menu->upper_arrow_prelight || menu->lower_arrow_prelight)
2022 return GTK_WIDGET_CLASS (parent_class)->button_release_event (widget, event);
2026 gtk_menu_key_press (GtkWidget *widget,
2029 GtkMenuShell *menu_shell;
2031 gboolean delete = FALSE;
2032 gboolean can_change_accels;
2033 gchar *accel = NULL;
2034 guint accel_key, accel_mods;
2035 GdkModifierType consumed_modifiers;
2036 GdkDisplay *display;
2038 g_return_val_if_fail (GTK_IS_MENU (widget), FALSE);
2039 g_return_val_if_fail (event != NULL, FALSE);
2041 menu_shell = GTK_MENU_SHELL (widget);
2042 menu = GTK_MENU (widget);
2044 gtk_menu_stop_navigating_submenu (menu);
2046 if (GTK_WIDGET_CLASS (parent_class)->key_press_event (widget, event))
2049 display = gtk_widget_get_display (widget);
2051 g_object_get (gtk_widget_get_settings (widget),
2052 "gtk-menu-bar-accel", &accel,
2053 "gtk-can-change-accels", &can_change_accels,
2059 GdkModifierType mods = 0;
2060 gboolean handled = FALSE;
2062 gtk_accelerator_parse (accel, &keyval, &mods);
2065 g_warning ("Failed to parse menu bar accelerator '%s'\n", accel);
2067 /* FIXME this is wrong, needs to be in the global accel resolution
2068 * thing, to properly consider i18n etc., but that probably requires
2069 * AccelGroup changes etc.
2071 if (event->keyval == keyval &&
2072 (mods & event->state) == mods)
2074 g_signal_emit_by_name (menu, "cancel", 0);
2083 switch (event->keyval)
2094 /* Figure out what modifiers went into determining the key symbol */
2095 gdk_keymap_translate_keyboard_state (gdk_keymap_get_for_display (display),
2096 event->hardware_keycode, event->state, event->group,
2097 NULL, NULL, NULL, &consumed_modifiers);
2099 accel_key = gdk_keyval_to_lower (event->keyval);
2100 accel_mods = event->state & gtk_accelerator_get_default_mod_mask () & ~consumed_modifiers;
2102 /* If lowercasing affects the keysym, then we need to include SHIFT in the modifiers,
2103 * We re-upper case when we match against the keyval, but display and save in caseless form.
2105 if (accel_key != event->keyval)
2106 accel_mods |= GDK_SHIFT_MASK;
2108 /* Modify the accelerators */
2109 if (can_change_accels &&
2110 menu_shell->active_menu_item &&
2111 GTK_BIN (menu_shell->active_menu_item)->child && /* no seperators */
2112 GTK_MENU_ITEM (menu_shell->active_menu_item)->submenu == NULL && /* no submenus */
2113 (delete || gtk_accelerator_valid (accel_key, accel_mods)))
2115 GtkWidget *menu_item = menu_shell->active_menu_item;
2116 gboolean locked, replace_accels = TRUE;
2119 path = _gtk_widget_get_accel_path (menu_item, &locked);
2120 if (!path || locked)
2122 /* can't change accelerators on menu_items without paths
2123 * (basically, those items are accelerator-locked).
2125 /* g_print("item has no path or is locked, menu prefix: %s\n", menu->accel_path); */
2126 gdk_display_beep (display);
2132 /* For the keys that act to delete the current setting, we delete
2133 * the current setting if there is one, otherwise, we set the
2134 * key as the accelerator.
2140 if (gtk_accel_map_lookup_entry (path, &key) &&
2141 (key.accel_key || key.accel_mods))
2147 changed = gtk_accel_map_change_entry (path, accel_key, accel_mods, replace_accels);
2151 /* we failed, probably because this key is in use and
2154 /* g_print("failed to change\n"); */
2155 gdk_display_beep (display);
2164 gtk_menu_motion_notify (GtkWidget *widget,
2165 GdkEventMotion *event)
2167 GtkWidget *menu_item;
2169 GtkMenuShell *menu_shell;
2171 gboolean need_enter;
2173 if (GTK_IS_MENU (widget))
2174 gtk_menu_handle_scrolling (GTK_MENU (widget), TRUE);
2176 /* We received the event for one of two reasons:
2178 * a) We are the active menu, and did gtk_grab_add()
2179 * b) The widget is a child of ours, and the event was propagated
2181 * Since for computation of navigation regions, we want the menu which
2182 * is the parent of the menu item, for a), we need to find that menu,
2183 * which may be different from 'widget'.
2185 menu_item = gtk_get_event_widget ((GdkEvent*) event);
2186 if (!menu_item || !GTK_IS_MENU_ITEM (menu_item) ||
2187 !_gtk_menu_item_is_selectable (menu_item) ||
2188 !GTK_IS_MENU (menu_item->parent))
2191 menu_shell = GTK_MENU_SHELL (menu_item->parent);
2192 menu = GTK_MENU (menu_shell);
2194 need_enter = (menu->navigation_region != NULL || menu_shell->ignore_enter);
2196 /* Check to see if we are within an active submenu's navigation region
2198 if (gtk_menu_navigating_submenu (menu, event->x_root, event->y_root))
2203 /* The menu is now sensitive to enter events on its items, but
2204 * was previously sensitive. So we fake an enter event.
2208 menu_shell->ignore_enter = FALSE;
2210 gdk_drawable_get_size (event->window, &width, &height);
2211 if (event->x >= 0 && event->x < width &&
2212 event->y >= 0 && event->y < height)
2214 GdkEvent *send_event = gdk_event_new (GDK_ENTER_NOTIFY);
2217 send_event->crossing.window = g_object_ref (event->window);
2218 send_event->crossing.time = event->time;
2219 send_event->crossing.send_event = TRUE;
2220 send_event->crossing.x_root = event->x_root;
2221 send_event->crossing.y_root = event->y_root;
2222 send_event->crossing.x = event->x;
2223 send_event->crossing.y = event->y;
2225 /* We send the event to 'widget', the currently active menu,
2226 * instead of 'menu', the menu that the pointer is in. This
2227 * will ensure that the event will be ignored unless the
2228 * menuitem is a child of the active menu or some parent
2229 * menu of the active menu.
2231 result = gtk_widget_event (widget, send_event);
2232 gdk_event_free (send_event);
2242 gtk_menu_scroll_timeout (gpointer data)
2247 gint view_width, view_height;
2249 GDK_THREADS_ENTER ();
2251 menu = GTK_MENU (data);
2252 widget = GTK_WIDGET (menu);
2254 offset = menu->scroll_offset + menu->scroll_step;
2256 /* If we scroll upward and the non-visible top part
2257 * is smaller than the scroll arrow it would be
2258 * pretty stupid to show the arrow and taking more
2259 * screen space than just scrolling to the top.
2261 if ((menu->scroll_step < 0) && (offset < MENU_SCROLL_ARROW_HEIGHT))
2264 /* Don't scroll over the top if we weren't before: */
2265 if ((menu->scroll_offset >= 0) && (offset < 0))
2268 gdk_drawable_get_size (widget->window, &view_width, &view_height);
2270 /* Don't scroll past the bottom if we weren't before: */
2271 if (menu->scroll_offset > 0)
2272 view_height -= MENU_SCROLL_ARROW_HEIGHT;
2274 if ((menu->scroll_offset + view_height <= widget->requisition.height) &&
2275 (offset + view_height > widget->requisition.height))
2276 offset = widget->requisition.height - view_height;
2278 gtk_menu_scroll_to (menu, offset);
2280 GDK_THREADS_LEAVE ();
2286 gtk_menu_handle_scrolling (GtkMenu *menu, gboolean enter)
2288 GtkMenuShell *menu_shell;
2294 gboolean scroll_fast = FALSE;
2295 guint vertical_padding;
2297 menu_shell = GTK_MENU_SHELL (menu);
2299 gdk_window_get_pointer (GTK_WIDGET (menu)->window, &x, &y, NULL);
2300 gdk_drawable_get_size (GTK_WIDGET (menu)->window, &width, &height);
2302 gtk_widget_style_get (GTK_WIDGET (menu),
2303 "vertical-padding", &vertical_padding,
2306 border = GTK_CONTAINER (menu)->border_width +
2307 GTK_WIDGET (menu)->style->ythickness + vertical_padding;
2309 if (menu->upper_arrow_visible && !menu->tearoff_active)
2314 rect.height = MENU_SCROLL_ARROW_HEIGHT + border;
2317 if ((x >= rect.x) && (x < rect.x + rect.width) &&
2318 (y >= rect.y) && (y < rect.y + rect.height))
2321 scroll_fast = (y < rect.y + MENU_SCROLL_FAST_ZONE);
2324 if (enter && in_arrow &&
2325 (!menu->upper_arrow_prelight || menu->scroll_fast != scroll_fast))
2327 menu->upper_arrow_prelight = TRUE;
2328 menu->scroll_fast = scroll_fast;
2329 gdk_window_invalidate_rect (GTK_WIDGET (menu)->window, &rect, FALSE);
2331 /* Deselect the active item so that any submenus are poped down */
2332 gtk_menu_shell_deselect (menu_shell);
2334 gtk_menu_remove_scroll_timeout (menu);
2335 menu->scroll_step = (scroll_fast) ? -MENU_SCROLL_STEP2 : -MENU_SCROLL_STEP1;
2336 menu->timeout_id = g_timeout_add ((scroll_fast) ? MENU_SCROLL_TIMEOUT2 : MENU_SCROLL_TIMEOUT1,
2337 gtk_menu_scroll_timeout,
2340 else if (!enter && !in_arrow && menu->upper_arrow_prelight)
2342 gdk_window_invalidate_rect (GTK_WIDGET (menu)->window, &rect, FALSE);
2344 gtk_menu_stop_scrolling (menu);
2348 if (menu->lower_arrow_visible && !menu->tearoff_active)
2351 rect.y = height - border - MENU_SCROLL_ARROW_HEIGHT;
2353 rect.height = MENU_SCROLL_ARROW_HEIGHT + border;
2356 if ((x >= rect.x) && (x < rect.x + rect.width) &&
2357 (y >= rect.y) && (y < rect.y + rect.height))
2360 scroll_fast = (y > rect.y + rect.height - MENU_SCROLL_FAST_ZONE);
2363 if (enter && in_arrow &&
2364 (!menu->lower_arrow_prelight || menu->scroll_fast != scroll_fast))
2366 menu->lower_arrow_prelight = TRUE;
2367 menu->scroll_fast = scroll_fast;
2368 gdk_window_invalidate_rect (GTK_WIDGET (menu)->window, &rect, FALSE);
2370 /* Deselect the active item so that any submenus are poped down */
2371 gtk_menu_shell_deselect (menu_shell);
2373 gtk_menu_remove_scroll_timeout (menu);
2374 menu->scroll_step = (scroll_fast) ? MENU_SCROLL_STEP2 : MENU_SCROLL_STEP1;
2375 menu->timeout_id = g_timeout_add ((scroll_fast) ? MENU_SCROLL_TIMEOUT2 : MENU_SCROLL_TIMEOUT1,
2376 gtk_menu_scroll_timeout,
2379 else if (!enter && !in_arrow && menu->lower_arrow_prelight)
2381 gdk_window_invalidate_rect (GTK_WIDGET (menu)->window, &rect, FALSE);
2383 gtk_menu_stop_scrolling (menu);
2389 gtk_menu_enter_notify (GtkWidget *widget,
2390 GdkEventCrossing *event)
2392 GtkWidget *menu_item;
2394 if (widget && GTK_IS_MENU (widget))
2396 GtkMenuShell *menu_shell = GTK_MENU_SHELL (widget);
2398 if (!menu_shell->ignore_enter)
2399 gtk_menu_handle_scrolling (GTK_MENU (widget), TRUE);
2402 /* If this is a faked enter (see gtk_menu_motion_notify), 'widget'
2403 * will not correspond to the event widget's parent. Check to see
2404 * if we are in the parent's navigation region.
2406 menu_item = gtk_get_event_widget ((GdkEvent*) event);
2407 if (menu_item && GTK_IS_MENU_ITEM (menu_item) && GTK_IS_MENU (menu_item->parent) &&
2408 gtk_menu_navigating_submenu (GTK_MENU (menu_item->parent), event->x_root, event->y_root))
2411 return GTK_WIDGET_CLASS (parent_class)->enter_notify_event (widget, event);
2415 gtk_menu_leave_notify (GtkWidget *widget,
2416 GdkEventCrossing *event)
2418 GtkMenuShell *menu_shell;
2420 GtkMenuItem *menu_item;
2421 GtkWidget *event_widget;
2423 menu = GTK_MENU (widget);
2424 menu_shell = GTK_MENU_SHELL (widget);
2426 if (gtk_menu_navigating_submenu (menu, event->x_root, event->y_root))
2429 gtk_menu_handle_scrolling (menu, FALSE);
2431 event_widget = gtk_get_event_widget ((GdkEvent*) event);
2433 if (!event_widget || !GTK_IS_MENU_ITEM (event_widget))
2436 menu_item = GTK_MENU_ITEM (event_widget);
2438 /* Here we check to see if we're leaving an active menu item with a submenu,
2439 * in which case we enter submenu navigation mode.
2441 if (menu_shell->active_menu_item != NULL
2442 && menu_item->submenu != NULL
2443 && menu_item->submenu_placement == GTK_LEFT_RIGHT)
2445 if (GTK_MENU_SHELL (menu_item->submenu)->active)
2447 gtk_menu_set_submenu_navigation_region (menu, menu_item, event);
2452 return GTK_WIDGET_CLASS (parent_class)->leave_notify_event (widget, event);
2456 gtk_menu_stop_navigating_submenu (GtkMenu *menu)
2458 if (menu->navigation_region)
2460 gdk_region_destroy (menu->navigation_region);
2461 menu->navigation_region = NULL;
2464 if (menu->navigation_timeout)
2466 g_source_remove (menu->navigation_timeout);
2467 menu->navigation_timeout = 0;
2471 /* When the timeout is elapsed, the navigation region is destroyed
2472 * and the menuitem under the pointer (if any) is selected.
2475 gtk_menu_stop_navigating_submenu_cb (gpointer user_data)
2477 GtkMenu *menu = user_data;
2478 GdkWindow *child_window;
2480 GDK_THREADS_ENTER ();
2482 gtk_menu_stop_navigating_submenu (menu);
2484 if (GTK_WIDGET_REALIZED (menu))
2486 child_window = gdk_window_get_pointer (menu->bin_window, NULL, NULL, NULL);
2490 GdkEvent *send_event = gdk_event_new (GDK_ENTER_NOTIFY);
2492 send_event->crossing.window = g_object_ref (child_window);
2493 send_event->crossing.time = GDK_CURRENT_TIME; /* Bogus */
2494 send_event->crossing.send_event = TRUE;
2496 GTK_WIDGET_CLASS (parent_class)->enter_notify_event (GTK_WIDGET (menu), (GdkEventCrossing *)send_event);
2498 gdk_event_free (send_event);
2502 GDK_THREADS_LEAVE ();
2508 gtk_menu_navigating_submenu (GtkMenu *menu,
2512 if (menu->navigation_region)
2514 if (gdk_region_point_in (menu->navigation_region, event_x, event_y))
2518 gtk_menu_stop_navigating_submenu (menu);
2525 #undef DRAW_STAY_UP_TRIANGLE
2527 #ifdef DRAW_STAY_UP_TRIANGLE
2530 draw_stay_up_triangle (GdkWindow *window,
2533 /* Draw ugly color all over the stay-up triangle */
2534 GdkColor ugly_color = { 0, 50000, 10000, 10000 };
2535 GdkGCValues gc_values;
2537 GdkRectangle clipbox;
2539 gc_values.subwindow_mode = GDK_INCLUDE_INFERIORS;
2540 ugly_gc = gdk_gc_new_with_values (window, &gc_values, 0 | GDK_GC_SUBWINDOW);
2541 gdk_gc_set_rgb_fg_color (ugly_gc, &ugly_color);
2542 gdk_gc_set_clip_region (ugly_gc, region);
2544 gdk_region_get_clipbox (region, &clipbox);
2546 gdk_draw_rectangle (window,
2549 clipbox.x, clipbox.y,
2550 clipbox.width, clipbox.height);
2552 g_object_unref (ugly_gc);
2557 flip_region (GdkRegion *region,
2562 GdkRectangle *rectangles;
2563 GdkRectangle clipbox;
2564 GdkRegion *new_region;
2567 new_region = gdk_region_new ();
2569 gdk_region_get_rectangles (region, &rectangles, &n_rectangles);
2570 gdk_region_get_clipbox (region, &clipbox);
2572 for (i = 0; i < n_rectangles; ++i)
2574 GdkRectangle rect = rectangles[i];
2577 rect.y -= 2 * (rect.y - clipbox.y) + rect.height;
2580 rect.x -= 2 * (rect.x - clipbox.x) + rect.width;
2582 gdk_region_union_with_rect (new_region, &rect);
2585 g_free (rectangles);
2591 gtk_menu_set_submenu_navigation_region (GtkMenu *menu,
2592 GtkMenuItem *menu_item,
2593 GdkEventCrossing *event)
2595 gint submenu_left = 0;
2596 gint submenu_right = 0;
2597 gint submenu_top = 0;
2598 gint submenu_bottom = 0;
2602 GtkWidget *event_widget;
2604 g_return_if_fail (menu_item->submenu != NULL);
2605 g_return_if_fail (event != NULL);
2607 event_widget = gtk_get_event_widget ((GdkEvent*) event);
2609 gdk_window_get_origin (menu_item->submenu->window, &submenu_left, &submenu_top);
2610 gdk_drawable_get_size (menu_item->submenu->window, &width, &height);
2612 submenu_right = submenu_left + width;
2613 submenu_bottom = submenu_top + height;
2615 gdk_drawable_get_size (event_widget->window, &width, &height);
2617 if (event->x >= 0 && event->x < width)
2620 gboolean flip_y = FALSE;
2621 gboolean flip_x = FALSE;
2623 gtk_menu_stop_navigating_submenu (menu);
2625 if (menu_item->submenu_direction == GTK_DIRECTION_RIGHT)
2628 point[0].x = event->x_root;
2629 point[1].x = submenu_left;
2634 point[0].x = event->x_root + 1;
2635 point[1].x = 2 * (event->x_root + 1) - submenu_right;
2643 point[0].y = event->y_root + 1;
2644 point[1].y = 2 * (event->y_root + 1) - submenu_top + NAVIGATION_REGION_OVERSHOOT;
2646 if (point[0].y >= point[1].y - NAVIGATION_REGION_OVERSHOOT)
2654 point[0].y = event->y_root;
2655 point[1].y = submenu_bottom + NAVIGATION_REGION_OVERSHOOT;
2657 if (point[0].y >= submenu_bottom)
2661 point[2].x = point[1].x;
2662 point[2].y = point[0].y;
2664 menu->navigation_region = gdk_region_polygon (point, 3, GDK_WINDING_RULE);
2666 if (flip_x || flip_y)
2668 GdkRegion *new_region = flip_region (menu->navigation_region, flip_x, flip_y);
2669 gdk_region_destroy (menu->navigation_region);
2670 menu->navigation_region = new_region;
2673 g_object_get (gtk_widget_get_settings (GTK_WIDGET (menu)),
2674 "gtk-menu-popdown-delay", &popdown_delay,
2677 menu->navigation_timeout = g_timeout_add (popdown_delay,
2678 gtk_menu_stop_navigating_submenu_cb, menu);
2680 #ifdef DRAW_STAY_UP_TRIANGLE
2681 draw_stay_up_triangle (gdk_get_default_root_window(),
2682 menu->navigation_region);
2688 gtk_menu_deactivate (GtkMenuShell *menu_shell)
2692 g_return_if_fail (GTK_IS_MENU (menu_shell));
2694 parent = menu_shell->parent_menu_shell;
2696 menu_shell->activate_time = 0;
2697 gtk_menu_popdown (GTK_MENU (menu_shell));
2700 gtk_menu_shell_deactivate (GTK_MENU_SHELL (parent));
2704 gtk_menu_position (GtkMenu *menu)
2707 GtkRequisition requisition;
2708 GtkMenuPrivate *private;
2714 GdkScreen *pointer_screen;
2715 GdkRectangle monitor;
2718 g_return_if_fail (GTK_IS_MENU (menu));
2720 widget = GTK_WIDGET (menu);
2722 screen = gtk_widget_get_screen (widget);
2723 gdk_display_get_pointer (gdk_screen_get_display (screen),
2724 &pointer_screen, &x, &y, NULL);
2726 /* We need the requisition to figure out the right place to
2727 * popup the menu. In fact, we always need to ask here, since
2728 * if a size_request was queued while we weren't popped up,
2729 * the requisition won't have been recomputed yet.
2731 gtk_widget_size_request (widget, &requisition);
2733 if (pointer_screen != screen)
2735 /* Pointer is on a different screen; roughly center the
2736 * menu on the screen. If someone was using multiscreen
2737 * + Xinerama together they'd probably want something
2738 * fancier; but that is likely to be vanishingly rare.
2740 x = MAX (0, (gdk_screen_get_width (screen) - requisition.width) / 2);
2741 y = MAX (0, (gdk_screen_get_height (screen) - requisition.height) / 2);
2744 monitor_num = gdk_screen_get_monitor_at_point (screen, x, y);
2745 gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor);
2749 if (menu->position_func)
2750 (* menu->position_func) (menu, &x, &y, &push_in, menu->position_func_data);
2753 x = CLAMP (x - 2, monitor.x, MAX (monitor.x, monitor.x + monitor.width - requisition.width));
2754 y = CLAMP (y - 2, monitor.y, MAX (monitor.y, monitor.y + monitor.height - requisition.height));
2761 menu_height = GTK_WIDGET (menu)->requisition.height;
2763 if (y + menu_height > monitor.y + monitor.height)
2765 scroll_offset -= y + menu_height - (monitor.y + monitor.height);
2766 y = (monitor.y + monitor.height) - menu_height;
2776 /* FIXME: should this be done in the various position_funcs ? */
2777 x = CLAMP (x, monitor.x, MAX (monitor.x, monitor.x + monitor.width - requisition.width));
2779 if (y + requisition.height > monitor.y + monitor.height)
2780 requisition.height = (monitor.y + monitor.height) - y;
2785 requisition.height -= -y;
2789 if (scroll_offset > 0)
2790 scroll_offset += MENU_SCROLL_ARROW_HEIGHT;
2792 gtk_window_move (GTK_WINDOW (GTK_MENU_SHELL (menu)->active ? menu->toplevel : menu->tearoff_window),
2795 if (GTK_MENU_SHELL (menu)->active)
2797 private = gtk_menu_get_private (menu);
2798 private->have_position = TRUE;
2802 gtk_widget_queue_resize (menu->toplevel);
2806 gtk_window_resize (GTK_WINDOW (menu->tearoff_window),
2807 requisition.width, requisition.height);
2810 menu->scroll_offset = scroll_offset;
2814 gtk_menu_remove_scroll_timeout (GtkMenu *menu)
2816 if (menu->timeout_id)
2818 g_source_remove (menu->timeout_id);
2819 menu->timeout_id = 0;
2824 gtk_menu_stop_scrolling (GtkMenu *menu)
2826 gtk_menu_remove_scroll_timeout (menu);
2828 menu->upper_arrow_prelight = FALSE;
2829 menu->lower_arrow_prelight = FALSE;
2833 gtk_menu_scroll_to (GtkMenu *menu,
2838 gint view_width, view_height;
2840 gboolean last_visible;
2842 guint vertical_padding;
2844 widget = GTK_WIDGET (menu);
2846 if (menu->tearoff_active &&
2847 menu->tearoff_adjustment &&
2848 (menu->tearoff_adjustment->value != offset))
2850 menu->tearoff_adjustment->value =
2852 0, menu->tearoff_adjustment->upper - menu->tearoff_adjustment->page_size);
2853 gtk_adjustment_value_changed (menu->tearoff_adjustment);
2856 /* Move/resize the viewport according to arrows: */
2857 view_width = widget->allocation.width;
2858 view_height = widget->allocation.height;
2860 gtk_widget_style_get (GTK_WIDGET (menu),
2861 "vertical-padding", &vertical_padding,
2864 border_width = GTK_CONTAINER (menu)->border_width;
2865 view_width -= (border_width + widget->style->xthickness) * 2;
2866 view_height -= (border_width + widget->style->ythickness + vertical_padding) * 2;
2867 menu_height = widget->requisition.height -
2868 (border_width + widget->style->ythickness + vertical_padding) * 2;
2870 x = border_width + widget->style->xthickness;
2871 y = border_width + widget->style->ythickness + vertical_padding;
2873 if (!menu->tearoff_active)
2875 last_visible = menu->upper_arrow_visible;
2876 menu->upper_arrow_visible = (view_height < menu_height && offset > 0);
2878 if (menu->upper_arrow_visible)
2879 view_height -= MENU_SCROLL_ARROW_HEIGHT;
2881 if ( (last_visible != menu->upper_arrow_visible) &&
2882 !menu->upper_arrow_visible)
2884 menu->upper_arrow_prelight = FALSE;
2886 /* If we hid the upper arrow, possibly remove timeout */
2887 if (menu->scroll_step < 0)
2889 gtk_menu_stop_scrolling (menu);
2890 gtk_widget_queue_draw (GTK_WIDGET (menu));
2894 last_visible = menu->lower_arrow_visible;
2895 menu->lower_arrow_visible = (view_height < menu_height && offset < menu_height - view_height);
2897 if (menu->lower_arrow_visible)
2898 view_height -= MENU_SCROLL_ARROW_HEIGHT;
2900 if ( (last_visible != menu->lower_arrow_visible) &&
2901 !menu->lower_arrow_visible)
2903 menu->lower_arrow_prelight = FALSE;
2905 /* If we hid the lower arrow, possibly remove timeout */
2906 if (menu->scroll_step > 0)
2908 gtk_menu_stop_scrolling (menu);
2909 gtk_widget_queue_draw (GTK_WIDGET (menu));
2913 if (menu->upper_arrow_visible)
2914 y += MENU_SCROLL_ARROW_HEIGHT;
2917 offset = CLAMP (offset, 0, menu_height - view_height);
2919 /* Scroll the menu: */
2920 if (GTK_WIDGET_REALIZED (menu))
2921 gdk_window_move (menu->bin_window, 0, -offset);
2923 if (GTK_WIDGET_REALIZED (menu))
2924 gdk_window_move_resize (menu->view_window,
2930 menu->scroll_offset = offset;
2934 compute_child_offset (GtkMenu *menu,
2935 GtkWidget *menu_item,
2938 gboolean *is_last_child)
2940 GtkMenuShell *menu_shell = GTK_MENU_SHELL (menu);
2942 gint child_offset = 0;
2944 for (children = menu_shell->children; children; children = children->next)
2946 GtkWidget *child = children->data;
2947 GtkRequisition child_requisition;
2950 if (GTK_WIDGET_VISIBLE (child))
2952 gtk_widget_size_request (child, &child_requisition);
2953 child_height = child_requisition.height;
2958 if (child == menu_item)
2961 *is_last_child = (children == NULL);
2963 *offset = child_offset;
2965 *height = child_height;
2970 child_offset += child_height;
2977 gtk_menu_scroll_item_visible (GtkMenuShell *menu_shell,
2978 GtkWidget *menu_item)
2981 gint child_offset, child_height;
2985 gboolean last_child = 0;
2987 menu = GTK_MENU (menu_shell);
2989 /* We need to check if the selected item fully visible.
2990 * If not we need to scroll the menu so that it becomes fully
2994 if (compute_child_offset (menu, menu_item,
2995 &child_offset, &child_height, &last_child))
2997 guint vertical_padding;
2999 y = menu->scroll_offset;
3000 gdk_drawable_get_size (GTK_WIDGET (menu)->window, &width, &height);
3002 gtk_widget_style_get (GTK_WIDGET (menu),
3003 "vertical-padding", &vertical_padding,
3006 height -= 2*GTK_CONTAINER (menu)->border_width + 2*GTK_WIDGET (menu)->style->ythickness + 2*vertical_padding;
3008 if (child_offset < y)
3010 /* Ignore the enter event we might get if the pointer is on the menu
3012 menu_shell->ignore_enter = TRUE;
3013 gtk_menu_scroll_to (menu, child_offset);
3018 if (menu->upper_arrow_visible && !menu->tearoff_active)
3019 arrow_height += MENU_SCROLL_ARROW_HEIGHT;
3020 if (menu->lower_arrow_visible && !menu->tearoff_active)
3021 arrow_height += MENU_SCROLL_ARROW_HEIGHT;
3023 if (child_offset + child_height > y + height - arrow_height)
3026 if (!last_child && !menu->tearoff_active)
3027 arrow_height += MENU_SCROLL_ARROW_HEIGHT;
3029 y = child_offset + child_height - height + arrow_height;
3030 if ((y > 0) && !menu->tearoff_active)
3032 /* Need upper arrow */
3033 arrow_height += MENU_SCROLL_ARROW_HEIGHT;
3034 y = child_offset + child_height - height + arrow_height;
3036 /* Ignore the enter event we might get if the pointer is on the menu
3038 menu_shell->ignore_enter = TRUE;
3039 gtk_menu_scroll_to (menu, y);
3047 gtk_menu_select_item (GtkMenuShell *menu_shell,
3048 GtkWidget *menu_item)
3050 GtkMenu *menu = GTK_MENU (menu_shell);
3052 if (GTK_WIDGET_REALIZED (GTK_WIDGET (menu)))
3053 gtk_menu_scroll_item_visible (menu_shell, menu_item);
3055 GTK_MENU_SHELL_CLASS (parent_class)->select_item (menu_shell, menu_item);
3059 /* Reparent the menu, taking care of the refcounting
3061 * If unrealize is true we force a unrealize while reparenting the parent.
3062 * This can help eliminate flicker in some cases.
3064 * What happens is that when the menu is unrealized and then re-realized,
3065 * the allocations are as follows:
3067 * parent - 1x1 at (0,0)
3068 * child1 - 100x20 at (0,0)
3069 * child2 - 100x20 at (0,20)
3070 * child3 - 100x20 at (0,40)
3072 * That is, the parent is small but the children are full sized. Then,
3073 * when the queued_resize gets processed, the parent gets resized to
3076 * But in order to eliminate flicker when scrolling, gdkgeometry-x11.c
3077 * contains the following logic:
3079 * - if a move or resize operation on a window would change the clip
3080 * region on the children, then before the window is resized
3081 * the background for children is temporarily set to None, the
3082 * move/resize done, and the background for the children restored.
3084 * So, at the point where the parent is resized to final size, the
3085 * background for the children is temporarily None, and thus they
3086 * are not cleared to the background color and the previous background
3087 * (the image of the menu) is left in place.
3090 gtk_menu_reparent (GtkMenu *menu,
3091 GtkWidget *new_parent,
3094 GtkObject *object = GTK_OBJECT (menu);
3095 GtkWidget *widget = GTK_WIDGET (menu);
3096 gboolean was_floating = GTK_OBJECT_FLOATING (object);
3098 g_object_ref (object);
3099 gtk_object_sink (object);
3103 g_object_ref (object);
3104 gtk_container_remove (GTK_CONTAINER (widget->parent), widget);
3105 gtk_container_add (GTK_CONTAINER (new_parent), widget);
3106 g_object_unref (object);
3109 gtk_widget_reparent (GTK_WIDGET (menu), new_parent);
3112 GTK_OBJECT_SET_FLAGS (object, GTK_FLOATING);
3114 g_object_unref (object);
3118 gtk_menu_show_all (GtkWidget *widget)
3120 g_return_if_fail (GTK_IS_MENU (widget));
3122 /* Show children, but not self. */
3123 gtk_container_foreach (GTK_CONTAINER (widget), (GtkCallback) gtk_widget_show_all, NULL);
3128 gtk_menu_hide_all (GtkWidget *widget)
3130 g_return_if_fail (GTK_IS_MENU (widget));
3132 /* Hide children, but not self. */
3133 gtk_container_foreach (GTK_CONTAINER (widget), (GtkCallback) gtk_widget_hide_all, NULL);
3137 * gtk_menu_set_screen:
3138 * @menu: a #GtkMenu.
3139 * @screen: a #GdkScreen, or %NULL if the screen should be
3140 * determined by the widget the menu is attached to.
3142 * Sets the #GdkScreen on which the menu will be displayed.
3147 gtk_menu_set_screen (GtkMenu *menu,
3150 g_return_if_fail (GTK_IS_MENU (menu));
3151 g_return_if_fail (!screen || GDK_IS_SCREEN (screen));
3153 g_object_set_data (G_OBJECT (menu), "gtk-menu-explicit-screen", screen);
3157 menu_change_screen (menu, screen);
3161 GtkWidget *attach_widget = gtk_menu_get_attach_widget (menu);
3163 attach_widget_screen_changed (attach_widget, NULL, menu);
3169 gtk_menu_get_popup_delay (GtkMenuShell *menu_shell)
3173 g_object_get (gtk_widget_get_settings (GTK_WIDGET (menu_shell)),
3174 "gtk-menu-popup-delay", &popup_delay,
3181 get_visible_size (GtkMenu *menu)
3183 GtkWidget *widget = GTK_WIDGET (menu);
3184 GtkContainer *container = GTK_CONTAINER (menu);
3186 gint menu_height = (widget->allocation.height
3187 - 2 * (container->border_width
3188 + widget->style->ythickness));
3190 if (menu->upper_arrow_visible && !menu->tearoff_active)
3191 menu_height -= MENU_SCROLL_ARROW_HEIGHT;
3192 if (menu->lower_arrow_visible && !menu->tearoff_active)
3193 menu_height -= MENU_SCROLL_ARROW_HEIGHT;
3198 /* Find the sensitive on-screen child containing @y, or if none,
3199 * the nearest selectable onscreen child. (%NULL if none)
3202 child_at (GtkMenu *menu,
3205 GtkMenuShell *menu_shell = GTK_MENU_SHELL (menu);
3206 GtkWidget *child = NULL;
3207 gint child_offset = 0;
3210 gint lower, upper; /* Onscreen bounds */
3212 menu_height = get_visible_size (menu);
3213 lower = menu->scroll_offset;
3214 upper = menu->scroll_offset + menu_height;
3216 for (children = menu_shell->children; children; children = children->next)
3218 if (GTK_WIDGET_VISIBLE (children->data))
3220 GtkRequisition child_requisition;
3222 gtk_widget_size_request (children->data, &child_requisition);
3224 if (_gtk_menu_item_is_selectable (children->data) &&
3225 child_offset >= lower &&
3226 child_offset + child_requisition.height <= upper)
3228 child = children->data;
3230 if (child_offset + child_requisition.height > y &&
3231 !GTK_IS_TEAROFF_MENU_ITEM (child))
3235 child_offset += child_requisition.height;
3243 gtk_menu_real_move_scroll (GtkMenu *menu,
3246 GtkMenuShell *menu_shell = GTK_MENU_SHELL (menu);
3250 case GTK_SCROLL_PAGE_UP:
3251 case GTK_SCROLL_PAGE_DOWN:
3253 gint page_size = get_visible_size (menu);
3255 gint child_offset = 0;
3256 gboolean old_upper_arrow_visible;
3259 if (type == GTK_SCROLL_PAGE_UP)
3264 if (menu_shell->active_menu_item)
3268 compute_child_offset (menu, menu_shell->active_menu_item,
3269 &child_offset, &child_height, NULL);
3270 child_offset += child_height / 2;
3273 menu_shell->ignore_enter = TRUE;
3274 old_upper_arrow_visible = menu->upper_arrow_visible && !menu->tearoff_active;
3275 old_offset = menu->scroll_offset;
3276 gtk_menu_scroll_to (menu, menu->scroll_offset + step);
3278 if (menu_shell->active_menu_item)
3280 GtkWidget *new_child;
3281 gboolean new_upper_arrow_visible = menu->upper_arrow_visible && !menu->tearoff_active;
3283 if (menu->scroll_offset != old_offset)
3284 step = menu->scroll_offset - old_offset;
3286 step -= (new_upper_arrow_visible - old_upper_arrow_visible) * MENU_SCROLL_ARROW_HEIGHT;
3288 new_child = child_at (menu, child_offset + step);
3290 gtk_menu_shell_select_item (menu_shell, new_child);
3294 case GTK_SCROLL_START:
3295 /* Ignore the enter event we might get if the pointer is on the menu
3297 menu_shell->ignore_enter = TRUE;
3298 gtk_menu_scroll_to (menu, 0);
3299 gtk_menu_shell_select_first (menu_shell, TRUE);
3301 case GTK_SCROLL_END:
3302 /* Ignore the enter event we might get if the pointer is on the menu
3304 menu_shell->ignore_enter = TRUE;
3305 gtk_menu_scroll_to (menu, G_MAXINT);
3306 _gtk_menu_shell_select_last (menu_shell, TRUE);