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/.
28 #include "gdk/gdkkeysyms.h"
29 #include "gtkbindings.h"
33 #include "gtkmenuitem.h"
34 #include "gtksignal.h"
35 #include "gtkwindow.h"
38 #define MENU_ITEM_CLASS(w) GTK_MENU_ITEM_GET_CLASS (w)
39 #define MENU_NEEDS_RESIZE(m) GTK_MENU_SHELL (m)->menu_flag
41 #define SUBMENU_NAV_REGION_PADDING 2
42 #define SUBMENU_NAV_HYSTERESIS_TIMEOUT 333
44 typedef struct _GtkMenuAttachData GtkMenuAttachData;
46 struct _GtkMenuAttachData
48 GtkWidget *attach_widget;
49 GtkMenuDetachFunc detacher;
53 static void gtk_menu_class_init (GtkMenuClass *klass);
54 static void gtk_menu_init (GtkMenu *menu);
55 static void gtk_menu_destroy (GtkObject *object);
56 static void gtk_menu_realize (GtkWidget *widget);
57 static void gtk_menu_size_request (GtkWidget *widget,
58 GtkRequisition *requisition);
59 static void gtk_menu_size_allocate (GtkWidget *widget,
60 GtkAllocation *allocation);
61 static void gtk_menu_paint (GtkWidget *widget);
62 static void gtk_menu_draw (GtkWidget *widget,
64 static gboolean gtk_menu_expose (GtkWidget *widget,
65 GdkEventExpose *event);
66 static gboolean gtk_menu_key_press (GtkWidget *widget,
68 static gboolean gtk_menu_motion_notify (GtkWidget *widget,
69 GdkEventMotion *event);
70 static gboolean gtk_menu_enter_notify (GtkWidget *widget,
71 GdkEventCrossing *event);
72 static gboolean gtk_menu_leave_notify (GtkWidget *widget,
73 GdkEventCrossing *event);
75 static void gtk_menu_stop_navigating_submenu (GtkMenu *menu);
76 static gboolean gtk_menu_stop_navigating_submenu_cb (gpointer user_data);
77 static gboolean gtk_menu_navigating_submenu (GtkMenu *menu,
80 static void gtk_menu_set_submenu_navigation_region (GtkMenu *menu,
81 GtkMenuItem *menu_item,
82 GdkEventCrossing *event);
84 static void gtk_menu_deactivate (GtkMenuShell *menu_shell);
85 static void gtk_menu_show_all (GtkWidget *widget);
86 static void gtk_menu_hide_all (GtkWidget *widget);
87 static void gtk_menu_position (GtkMenu *menu);
88 static void gtk_menu_reparent (GtkMenu *menu,
89 GtkWidget *new_parent,
92 static GtkMenuShellClass *parent_class = NULL;
93 static const gchar *attach_data_key = "gtk-menu-attach-data";
94 static GQuark quark_uline_accel_group = 0;
97 gtk_menu_get_type (void)
99 static GtkType menu_type = 0;
103 static const GtkTypeInfo menu_info =
107 sizeof (GtkMenuClass),
108 (GtkClassInitFunc) gtk_menu_class_init,
109 (GtkObjectInitFunc) gtk_menu_init,
110 /* reserved_1 */ NULL,
111 /* reserved_2 */ NULL,
112 (GtkClassInitFunc) NULL,
115 menu_type = gtk_type_unique (gtk_menu_shell_get_type (), &menu_info);
122 gtk_menu_class_init (GtkMenuClass *class)
124 GtkObjectClass *object_class;
125 GtkWidgetClass *widget_class;
126 GtkContainerClass *container_class;
127 GtkMenuShellClass *menu_shell_class;
129 GtkBindingSet *binding_set;
131 object_class = (GtkObjectClass*) class;
132 widget_class = (GtkWidgetClass*) class;
133 container_class = (GtkContainerClass*) class;
134 menu_shell_class = (GtkMenuShellClass*) class;
135 parent_class = gtk_type_class (gtk_menu_shell_get_type ());
137 object_class->destroy = gtk_menu_destroy;
139 widget_class->realize = gtk_menu_realize;
140 widget_class->draw = gtk_menu_draw;
141 widget_class->size_request = gtk_menu_size_request;
142 widget_class->size_allocate = gtk_menu_size_allocate;
143 widget_class->expose_event = gtk_menu_expose;
144 widget_class->key_press_event = gtk_menu_key_press;
145 widget_class->motion_notify_event = gtk_menu_motion_notify;
146 widget_class->show_all = gtk_menu_show_all;
147 widget_class->hide_all = gtk_menu_hide_all;
148 widget_class->enter_notify_event = gtk_menu_enter_notify;
149 widget_class->leave_notify_event = gtk_menu_leave_notify;
151 menu_shell_class->submenu_placement = GTK_LEFT_RIGHT;
152 menu_shell_class->deactivate = gtk_menu_deactivate;
154 binding_set = gtk_binding_set_by_class (class);
155 gtk_binding_entry_add_signal (binding_set,
158 GTK_TYPE_MENU_DIRECTION_TYPE,
160 gtk_binding_entry_add_signal (binding_set,
163 GTK_TYPE_MENU_DIRECTION_TYPE,
165 gtk_binding_entry_add_signal (binding_set,
168 GTK_TYPE_MENU_DIRECTION_TYPE,
169 GTK_MENU_DIR_PARENT);
170 gtk_binding_entry_add_signal (binding_set,
173 GTK_TYPE_MENU_DIRECTION_TYPE,
178 gtk_menu_window_event (GtkWidget *window,
182 gboolean handled = FALSE;
184 gtk_widget_ref (window);
185 gtk_widget_ref (menu);
190 case GDK_KEY_RELEASE:
191 gtk_widget_event (menu, event);
198 gtk_widget_unref (window);
199 gtk_widget_unref (menu);
205 gtk_menu_init (GtkMenu *menu)
207 menu->parent_menu_item = NULL;
208 menu->old_active_menu_item = NULL;
209 menu->accel_group = NULL;
210 menu->position_func = NULL;
211 menu->position_func_data = NULL;
213 menu->toplevel = gtk_widget_new (GTK_TYPE_WINDOW,
214 "type", GTK_WINDOW_POPUP,
215 "signal::event", gtk_menu_window_event, menu,
216 "signal::destroy", gtk_widget_destroyed, &menu->toplevel,
219 gtk_window_set_policy (GTK_WINDOW (menu->toplevel),
222 /* Refloat the menu, so that reference counting for the menu isn't
223 * affected by it being a child of the toplevel
225 GTK_WIDGET_SET_FLAGS (menu, GTK_FLOATING);
226 menu->needs_destruction_ref_count = TRUE;
228 menu->tearoff_window = NULL;
229 menu->torn_off = FALSE;
231 MENU_NEEDS_RESIZE (menu) = TRUE;
235 gtk_menu_destroy (GtkObject *object)
238 GtkMenuAttachData *data;
240 g_return_if_fail (GTK_IS_MENU (object));
242 menu = GTK_MENU (object);
244 data = gtk_object_get_data (object, attach_data_key);
246 gtk_menu_detach (menu);
248 gtk_menu_stop_navigating_submenu (menu);
250 gtk_menu_set_accel_group (menu, NULL);
252 if (menu->old_active_menu_item)
254 gtk_widget_unref (menu->old_active_menu_item);
255 menu->old_active_menu_item = NULL;
258 /* Add back the reference count for being a child */
259 if (menu->needs_destruction_ref_count)
261 menu->needs_destruction_ref_count = FALSE;
262 gtk_object_ref (object);
266 gtk_widget_destroy (menu->toplevel);
267 if (menu->tearoff_window)
268 gtk_widget_destroy (menu->tearoff_window);
270 GTK_OBJECT_CLASS (parent_class)->destroy (object);
275 gtk_menu_attach_to_widget (GtkMenu *menu,
276 GtkWidget *attach_widget,
277 GtkMenuDetachFunc detacher)
279 GtkMenuAttachData *data;
281 g_return_if_fail (menu != NULL);
282 g_return_if_fail (GTK_IS_MENU (menu));
283 g_return_if_fail (attach_widget != NULL);
284 g_return_if_fail (GTK_IS_WIDGET (attach_widget));
285 g_return_if_fail (detacher != NULL);
287 /* keep this function in sync with gtk_widget_set_parent()
290 data = gtk_object_get_data (GTK_OBJECT (menu), attach_data_key);
293 g_warning ("gtk_menu_attach_to_widget(): menu already attached to %s",
294 gtk_type_name (GTK_OBJECT_TYPE (data->attach_widget)));
298 gtk_object_ref (GTK_OBJECT (menu));
299 gtk_object_sink (GTK_OBJECT (menu));
301 data = g_new (GtkMenuAttachData, 1);
302 data->attach_widget = attach_widget;
303 data->detacher = detacher;
304 gtk_object_set_data (GTK_OBJECT (menu), attach_data_key, data);
306 if (GTK_WIDGET_STATE (menu) != GTK_STATE_NORMAL)
307 gtk_widget_set_state (GTK_WIDGET (menu), GTK_STATE_NORMAL);
309 /* we don't need to set the style here, since
310 * we are a toplevel widget.
315 gtk_menu_get_attach_widget (GtkMenu *menu)
317 GtkMenuAttachData *data;
319 g_return_val_if_fail (menu != NULL, NULL);
320 g_return_val_if_fail (GTK_IS_MENU (menu), NULL);
322 data = gtk_object_get_data (GTK_OBJECT (menu), attach_data_key);
324 return data->attach_widget;
329 gtk_menu_detach (GtkMenu *menu)
331 GtkMenuAttachData *data;
333 g_return_if_fail (menu != NULL);
334 g_return_if_fail (GTK_IS_MENU (menu));
336 /* keep this function in sync with gtk_widget_unparent()
338 data = gtk_object_get_data (GTK_OBJECT (menu), attach_data_key);
341 g_warning ("gtk_menu_detach(): menu is not attached");
344 gtk_object_remove_data (GTK_OBJECT (menu), attach_data_key);
346 data->detacher (data->attach_widget, menu);
348 if (GTK_WIDGET_REALIZED (menu))
349 gtk_widget_unrealize (GTK_WIDGET (menu));
353 gtk_widget_unref (GTK_WIDGET (menu));
359 return GTK_WIDGET (gtk_type_new (gtk_menu_get_type ()));
363 gtk_menu_append (GtkMenu *menu,
366 gtk_menu_shell_append (GTK_MENU_SHELL (menu), child);
370 gtk_menu_prepend (GtkMenu *menu,
373 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), child);
377 gtk_menu_insert (GtkMenu *menu,
381 gtk_menu_shell_insert (GTK_MENU_SHELL (menu), child, position);
385 gtk_menu_tearoff_bg_copy (GtkMenu *menu)
389 widget = GTK_WIDGET (menu);
395 GdkGCValues gc_values;
397 gc_values.subwindow_mode = GDK_INCLUDE_INFERIORS;
398 gc = gdk_gc_new_with_values (widget->window,
399 &gc_values, GDK_GC_SUBWINDOW);
401 pixmap = gdk_pixmap_new (widget->window,
402 widget->requisition.width,
403 widget->requisition.height,
406 gdk_draw_pixmap (pixmap, gc,
411 gtk_widget_set_usize (menu->tearoff_window,
412 widget->requisition.width,
413 widget->requisition.height);
415 gdk_window_set_back_pixmap (menu->tearoff_window->window, pixmap, FALSE);
416 gdk_pixmap_unref (pixmap);
421 gtk_menu_popup (GtkMenu *menu,
422 GtkWidget *parent_menu_shell,
423 GtkWidget *parent_menu_item,
424 GtkMenuPositionFunc func,
427 guint32 activate_time)
430 GtkWidget *xgrab_shell;
432 GdkEvent *current_event;
433 GtkMenuShell *menu_shell;
435 g_return_if_fail (menu != NULL);
436 g_return_if_fail (GTK_IS_MENU (menu));
438 widget = GTK_WIDGET (menu);
439 menu_shell = GTK_MENU_SHELL (menu);
441 menu_shell->parent_menu_shell = parent_menu_shell;
442 menu_shell->active = TRUE;
443 menu_shell->button = button;
445 /* If we are popping up the menu from something other than, a button
446 * press then, as a heuristic, we ignore enter events for the menu
447 * until we get a MOTION_NOTIFY.
450 current_event = gtk_get_current_event();
453 if ((current_event->type != GDK_BUTTON_PRESS) &&
454 (current_event->type != GDK_ENTER_NOTIFY))
455 menu_shell->ignore_enter = TRUE;
456 gdk_event_free (current_event);
461 gtk_menu_tearoff_bg_copy (menu);
463 /* We force an unrealize here so that we don't trigger redrawing/
464 * clearing code - we just want to reveal our backing pixmap.
466 gtk_menu_reparent (menu, menu->toplevel, TRUE);
469 menu->parent_menu_item = parent_menu_item;
470 menu->position_func = func;
471 menu->position_func_data = data;
472 menu_shell->activate_time = activate_time;
474 gtk_menu_position (menu);
476 /* We need to show the menu _here_ because code expects to be
477 * able to tell if the menu is onscreen by looking at the
478 * GTK_WIDGET_VISIBLE (menu)
480 gtk_widget_show (GTK_WIDGET (menu));
481 gtk_widget_show (menu->toplevel);
483 /* Find the last viewable ancestor, and make an X grab on it
485 parent = GTK_WIDGET (menu);
489 gboolean viewable = TRUE;
490 GtkWidget *tmp = parent;
494 if (!GTK_WIDGET_MAPPED (tmp))
503 xgrab_shell = parent;
505 parent = GTK_MENU_SHELL (parent)->parent_menu_shell;
508 if (xgrab_shell && (!GTK_MENU_SHELL (xgrab_shell)->have_xgrab))
510 GdkCursor *cursor = gdk_cursor_new (GDK_ARROW);
512 if ((gdk_pointer_grab (xgrab_shell->window, TRUE,
513 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
514 GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK |
515 GDK_POINTER_MOTION_MASK,
516 NULL, cursor, activate_time) == 0))
518 if (gdk_keyboard_grab (xgrab_shell->window, TRUE,
520 GTK_MENU_SHELL (xgrab_shell)->have_xgrab = TRUE;
523 gdk_pointer_ungrab (activate_time);
527 gdk_cursor_destroy (cursor);
530 gtk_grab_add (GTK_WIDGET (menu));
534 gtk_menu_popdown (GtkMenu *menu)
536 GtkMenuShell *menu_shell;
538 g_return_if_fail (menu != NULL);
539 g_return_if_fail (GTK_IS_MENU (menu));
541 menu_shell = GTK_MENU_SHELL (menu);
543 menu_shell->parent_menu_shell = NULL;
544 menu_shell->active = FALSE;
545 menu_shell->ignore_enter = FALSE;
547 gtk_menu_stop_navigating_submenu (menu);
549 if (menu_shell->active_menu_item)
551 if (menu->old_active_menu_item)
552 gtk_widget_unref (menu->old_active_menu_item);
553 menu->old_active_menu_item = menu_shell->active_menu_item;
554 gtk_widget_ref (menu->old_active_menu_item);
557 gtk_menu_shell_deselect (menu_shell);
559 /* The X Grab, if present, will automatically be removed when we hide
561 gtk_widget_hide (menu->toplevel);
565 if (GTK_BIN (menu->toplevel)->child)
567 gtk_menu_reparent (menu, menu->tearoff_window, FALSE);
571 /* We popped up the menu from the tearoff, so we need to
572 * release the grab - we aren't actually hiding the menu.
574 if (menu_shell->have_xgrab)
576 gdk_pointer_ungrab (GDK_CURRENT_TIME);
577 gdk_keyboard_ungrab (GDK_CURRENT_TIME);
582 gtk_widget_hide (GTK_WIDGET (menu));
584 menu_shell->have_xgrab = FALSE;
585 gtk_grab_remove (GTK_WIDGET (menu));
589 gtk_menu_get_active (GtkMenu *menu)
594 g_return_val_if_fail (menu != NULL, NULL);
595 g_return_val_if_fail (GTK_IS_MENU (menu), NULL);
597 if (!menu->old_active_menu_item)
600 children = GTK_MENU_SHELL (menu)->children;
604 child = children->data;
605 children = children->next;
607 if (GTK_BIN (child)->child)
612 menu->old_active_menu_item = child;
613 if (menu->old_active_menu_item)
614 gtk_widget_ref (menu->old_active_menu_item);
617 return menu->old_active_menu_item;
621 gtk_menu_set_active (GtkMenu *menu,
627 g_return_if_fail (menu != NULL);
628 g_return_if_fail (GTK_IS_MENU (menu));
630 tmp_list = g_list_nth (GTK_MENU_SHELL (menu)->children, index);
633 child = tmp_list->data;
634 if (GTK_BIN (child)->child)
636 if (menu->old_active_menu_item)
637 gtk_widget_unref (menu->old_active_menu_item);
638 menu->old_active_menu_item = child;
639 gtk_widget_ref (menu->old_active_menu_item);
645 gtk_menu_set_accel_group (GtkMenu *menu,
646 GtkAccelGroup *accel_group)
648 g_return_if_fail (GTK_IS_MENU (menu));
650 if (menu->accel_group != accel_group)
652 if (menu->accel_group)
653 gtk_accel_group_unref (menu->accel_group);
654 menu->accel_group = accel_group;
655 if (menu->accel_group)
656 gtk_accel_group_ref (menu->accel_group);
661 gtk_menu_get_accel_group (GtkMenu *menu)
663 g_return_val_if_fail (GTK_IS_MENU (menu), NULL);
665 return menu->accel_group;
669 gtk_menu_ensure_uline_accel_group (GtkMenu *menu)
671 GtkAccelGroup *accel_group;
673 g_return_val_if_fail (GTK_IS_MENU (menu), NULL);
675 if (!quark_uline_accel_group)
676 quark_uline_accel_group = g_quark_from_static_string ("GtkMenu-uline-accel-group");
678 accel_group = gtk_object_get_data_by_id (GTK_OBJECT (menu), quark_uline_accel_group);
681 accel_group = gtk_accel_group_new ();
682 gtk_accel_group_attach (accel_group, GTK_OBJECT (menu));
683 gtk_object_set_data_by_id_full (GTK_OBJECT (menu),
684 quark_uline_accel_group,
686 (GtkDestroyNotify) gtk_accel_group_unref);
693 gtk_menu_get_uline_accel_group (GtkMenu *menu)
695 g_return_val_if_fail (GTK_IS_MENU (menu), NULL);
697 return gtk_object_get_data_by_id (GTK_OBJECT (menu), quark_uline_accel_group);
701 gtk_menu_reposition (GtkMenu *menu)
703 g_return_if_fail (menu != NULL);
704 g_return_if_fail (GTK_IS_MENU (menu));
706 if (GTK_WIDGET_DRAWABLE (menu) && !menu->torn_off)
707 gtk_menu_position (menu);
712 gtk_menu_set_tearoff_state (GtkMenu *menu,
715 g_return_if_fail (menu != NULL);
716 g_return_if_fail (GTK_IS_MENU (menu));
718 if (menu->torn_off != torn_off)
720 menu->torn_off = torn_off;
724 if (GTK_WIDGET_VISIBLE (menu))
725 gtk_menu_popdown (menu);
727 if (!menu->tearoff_window)
729 GtkWidget *attach_widget;
732 menu->tearoff_window = gtk_widget_new (GTK_TYPE_WINDOW,
733 "type", GTK_WINDOW_TOPLEVEL,
734 "signal::destroy", gtk_widget_destroyed, &menu->tearoff_window,
736 gtk_widget_set_app_paintable (menu->tearoff_window, TRUE);
737 gtk_signal_connect (GTK_OBJECT (menu->tearoff_window),
739 GTK_SIGNAL_FUNC (gtk_menu_window_event),
741 gtk_widget_realize (menu->tearoff_window);
743 title = gtk_object_get_data (GTK_OBJECT (menu), "gtk-menu-title");
746 attach_widget = gtk_menu_get_attach_widget (menu);
747 if (GTK_IS_MENU_ITEM (attach_widget))
749 GtkWidget *child = GTK_BIN (attach_widget)->child;
750 if (GTK_IS_LABEL (child))
751 gtk_label_get (GTK_LABEL (child), &title);
756 gdk_window_set_title (menu->tearoff_window->window, title);
758 gdk_window_set_decorations (menu->tearoff_window->window,
763 gtk_window_set_policy (GTK_WINDOW (menu->tearoff_window),
766 gtk_menu_reparent (menu, menu->tearoff_window, FALSE);
768 gtk_menu_position (menu);
770 gtk_widget_show (GTK_WIDGET (menu));
771 gtk_widget_show (menu->tearoff_window);
775 gtk_widget_hide (menu->tearoff_window);
776 gtk_menu_reparent (menu, menu->toplevel, FALSE);
782 gtk_menu_set_title (GtkMenu *menu,
785 g_return_if_fail (menu != NULL);
786 g_return_if_fail (GTK_IS_MENU (menu));
788 gtk_object_set_data_full (GTK_OBJECT (menu), "gtk-menu-title",
789 g_strdup (title), (GtkDestroyNotify) g_free);
793 gtk_menu_reorder_child (GtkMenu *menu,
797 GtkMenuShell *menu_shell;
798 g_return_if_fail (GTK_IS_MENU (menu));
799 g_return_if_fail (GTK_IS_MENU_ITEM (child));
800 menu_shell = GTK_MENU_SHELL (menu);
801 if (g_list_find (menu_shell->children, child))
803 menu_shell->children = g_list_remove (menu_shell->children, child);
804 menu_shell->children = g_list_insert (menu_shell->children, child, position);
805 if (GTK_WIDGET_VISIBLE (menu_shell))
806 gtk_widget_queue_resize (GTK_WIDGET (menu_shell));
811 gtk_menu_realize (GtkWidget *widget)
813 GdkWindowAttr attributes;
814 gint attributes_mask;
816 g_return_if_fail (widget != NULL);
817 g_return_if_fail (GTK_IS_MENU (widget));
819 GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
821 attributes.window_type = GDK_WINDOW_CHILD;
822 attributes.x = widget->allocation.x;
823 attributes.y = widget->allocation.y;
824 attributes.width = widget->allocation.width;
825 attributes.height = widget->allocation.height;
826 attributes.wclass = GDK_INPUT_OUTPUT;
827 attributes.visual = gtk_widget_get_visual (widget);
828 attributes.colormap = gtk_widget_get_colormap (widget);
829 attributes.event_mask = gtk_widget_get_events (widget);
830 attributes.event_mask |= (GDK_EXPOSURE_MASK | GDK_KEY_PRESS_MASK);
832 attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
833 widget->window = gdk_window_new (gtk_widget_get_parent_window (widget), &attributes, attributes_mask);
834 gdk_window_set_user_data (widget->window, widget);
836 widget->style = gtk_style_attach (widget->style, widget->window);
837 gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL);
838 gtk_menu_paint(widget);
842 gtk_menu_size_request (GtkWidget *widget,
843 GtkRequisition *requisition)
846 GtkMenuShell *menu_shell;
849 guint max_toggle_size;
850 guint max_accel_width;
851 GtkRequisition child_requisition;
853 g_return_if_fail (widget != NULL);
854 g_return_if_fail (GTK_IS_MENU (widget));
855 g_return_if_fail (requisition != NULL);
857 menu = GTK_MENU (widget);
858 menu_shell = GTK_MENU_SHELL (widget);
860 requisition->width = 0;
861 requisition->height = 0;
866 children = menu_shell->children;
869 child = children->data;
870 children = children->next;
872 if (GTK_WIDGET_VISIBLE (child))
874 GTK_MENU_ITEM (child)->show_submenu_indicator = TRUE;
875 gtk_widget_size_request (child, &child_requisition);
877 requisition->width = MAX (requisition->width, child_requisition.width);
878 requisition->height += child_requisition.height;
880 max_toggle_size = MAX (max_toggle_size, MENU_ITEM_CLASS (child)->toggle_size);
881 max_accel_width = MAX (max_accel_width, GTK_MENU_ITEM (child)->accelerator_width);
885 requisition->width += max_toggle_size + max_accel_width;
886 requisition->width += (GTK_CONTAINER (menu)->border_width +
887 widget->style->xthickness) * 2;
888 requisition->height += (GTK_CONTAINER (menu)->border_width +
889 widget->style->ythickness) * 2;
891 children = menu_shell->children;
894 child = children->data;
895 children = children->next;
897 GTK_MENU_ITEM (child)->toggle_size = max_toggle_size;
902 gtk_menu_size_allocate (GtkWidget *widget,
903 GtkAllocation *allocation)
906 GtkMenuShell *menu_shell;
908 GtkAllocation child_allocation;
911 g_return_if_fail (widget != NULL);
912 g_return_if_fail (GTK_IS_MENU (widget));
913 g_return_if_fail (allocation != NULL);
915 menu = GTK_MENU (widget);
916 menu_shell = GTK_MENU_SHELL (widget);
918 widget->allocation = *allocation;
919 if (GTK_WIDGET_REALIZED (widget))
920 gdk_window_move_resize (widget->window,
921 allocation->x, allocation->y,
922 allocation->width, allocation->height);
925 if (menu_shell->children)
927 child_allocation.x = (GTK_CONTAINER (menu)->border_width +
928 widget->style->xthickness);
929 child_allocation.y = (GTK_CONTAINER (menu)->border_width +
930 widget->style->ythickness);
931 child_allocation.width = MAX (1, (gint)allocation->width - child_allocation.x * 2);
933 children = menu_shell->children;
936 child = children->data;
937 children = children->next;
939 if (GTK_WIDGET_VISIBLE (child))
941 GtkRequisition child_requisition;
942 gtk_widget_get_child_requisition (child, &child_requisition);
944 child_allocation.height = child_requisition.height;
946 gtk_widget_size_allocate (child, &child_allocation);
947 gtk_widget_queue_draw (child);
949 child_allocation.y += child_allocation.height;
956 gtk_menu_paint (GtkWidget *widget)
958 g_return_if_fail (widget != NULL);
959 g_return_if_fail (GTK_IS_MENU (widget));
961 if (GTK_WIDGET_DRAWABLE (widget))
963 gtk_paint_box (widget->style,
967 NULL, widget, "menu",
973 gtk_menu_draw (GtkWidget *widget,
976 GtkMenuShell *menu_shell;
978 GdkRectangle child_area;
981 g_return_if_fail (widget != NULL);
982 g_return_if_fail (GTK_IS_MENU (widget));
983 g_return_if_fail (area != NULL);
985 if (GTK_WIDGET_DRAWABLE (widget))
987 gtk_menu_paint (widget);
989 menu_shell = GTK_MENU_SHELL (widget);
991 children = menu_shell->children;
994 child = children->data;
995 children = children->next;
997 if (gtk_widget_intersect (child, area, &child_area))
998 gtk_widget_draw (child, &child_area);
1004 gtk_menu_expose (GtkWidget *widget,
1005 GdkEventExpose *event)
1007 GtkMenuShell *menu_shell;
1009 GdkEventExpose child_event;
1013 g_return_val_if_fail (widget != NULL, FALSE);
1014 g_return_val_if_fail (GTK_IS_MENU (widget), FALSE);
1015 g_return_val_if_fail (event != NULL, FALSE);
1017 menu_shell = GTK_MENU_SHELL (widget);
1018 menu = GTK_MENU (widget);
1020 if (GTK_WIDGET_DRAWABLE (widget))
1022 gtk_menu_paint (widget);
1024 child_event = *event;
1026 children = menu_shell->children;
1029 child = children->data;
1030 children = children->next;
1032 if (GTK_WIDGET_NO_WINDOW (child) &&
1033 gtk_widget_intersect (child, &event->area, &child_event.area))
1034 gtk_widget_event (child, (GdkEvent*) &child_event);
1042 gtk_menu_key_press (GtkWidget *widget,
1045 GtkMenuShell *menu_shell;
1046 gboolean delete = FALSE;
1048 g_return_val_if_fail (widget != NULL, FALSE);
1049 g_return_val_if_fail (GTK_IS_MENU (widget), FALSE);
1050 g_return_val_if_fail (event != NULL, FALSE);
1052 menu_shell = GTK_MENU_SHELL (widget);
1054 gtk_menu_stop_navigating_submenu (GTK_MENU (widget));
1056 if (GTK_WIDGET_CLASS (parent_class)->key_press_event (widget, event))
1059 switch (event->keyval)
1070 /* Modify the accelerators */
1071 if (menu_shell->active_menu_item &&
1072 GTK_BIN (menu_shell->active_menu_item)->child &&
1073 GTK_MENU_ITEM (menu_shell->active_menu_item)->submenu == NULL &&
1074 !gtk_widget_accelerators_locked (menu_shell->active_menu_item) &&
1076 (gtk_accelerator_valid (event->keyval, event->state) &&
1078 !gtk_menu_get_uline_accel_group (GTK_MENU (menu_shell)) ||
1079 (event->keyval >= GDK_F1 && event->keyval <= GDK_F35)))))
1081 GtkMenuItem *menu_item;
1082 GtkAccelGroup *accel_group;
1084 menu_item = GTK_MENU_ITEM (menu_shell->active_menu_item);
1086 if (!GTK_MENU (widget)->accel_group)
1087 accel_group = gtk_accel_group_get_default ();
1089 accel_group = GTK_MENU (widget)->accel_group;
1091 gtk_widget_remove_accelerators (GTK_WIDGET (menu_item),
1092 gtk_signal_name (menu_item->accelerator_signal),
1096 0 == gtk_widget_accelerator_signal (GTK_WIDGET (menu_item),
1103 slist = gtk_accel_group_entries_from_object (GTK_OBJECT (menu_item));
1106 GtkAccelEntry *ac_entry;
1108 ac_entry = slist->data;
1110 if (ac_entry->signal_id == menu_item->accelerator_signal)
1113 slist = slist->next;
1117 gtk_widget_add_accelerator (GTK_WIDGET (menu_item),
1118 gtk_signal_name (menu_item->accelerator_signal),
1130 gtk_menu_motion_notify (GtkWidget *widget,
1131 GdkEventMotion *event)
1133 GtkWidget *menu_item;
1135 GtkMenuShell *menu_shell;
1137 gboolean need_enter;
1139 /* We received the event for one of two reasons:
1141 * a) We are the active menu, and did gtk_grab_add()
1142 * b) The widget is a child of ours, and the event was propagated
1144 * Since for computation of navigation regions, we want the menu which
1145 * is the parent of the menu item, for a), we need to find that menu,
1146 * which may be different from 'widget'.
1149 menu_item = gtk_get_event_widget ((GdkEvent*) event);
1150 if (!menu_item || !GTK_IS_MENU_ITEM (menu_item) || !GTK_WIDGET_IS_SENSITIVE (menu_item) ||
1151 !GTK_IS_MENU (menu_item->parent))
1154 menu_shell = GTK_MENU_SHELL (menu_item->parent);
1155 menu = GTK_MENU (menu_shell);
1157 need_enter = (menu->navigation_region != NULL || menu_shell->ignore_enter);
1159 /* Check to see if we are within an active submenu's navigation region
1161 if (gtk_menu_navigating_submenu (menu, event->x_root, event->y_root))
1166 /* The menu is now sensitive to enter events on its items, but
1167 * was previously sensitive. So we fake an enter event.
1171 menu_shell->ignore_enter = FALSE;
1173 gdk_window_get_size (event->window, &width, &height);
1174 if (event->x >= 0 && event->x < width &&
1175 event->y >= 0 && event->y < height)
1177 GdkEvent send_event;
1179 send_event.crossing.type = GDK_ENTER_NOTIFY;
1180 send_event.crossing.window = event->window;
1181 send_event.crossing.time = event->time;
1182 send_event.crossing.send_event = TRUE;
1183 send_event.crossing.x_root = event->x_root;
1184 send_event.crossing.y_root = event->y_root;
1185 send_event.crossing.x = event->x;
1186 send_event.crossing.y = event->y;
1188 /* We send the event to 'widget', the currently active menu,
1189 * instead of 'menu', the menu that the pointer is in. This
1190 * will ensure that the event will be ignored unless the
1191 * menuitem is a child of the active menu or some parent
1192 * menu of the active menu.
1194 return gtk_widget_event (widget, &send_event);
1202 gtk_menu_enter_notify (GtkWidget *widget,
1203 GdkEventCrossing *event)
1205 GtkWidget *menu_item;
1207 /* If this is a faked enter (see gtk_menu_motion_notify), 'widget'
1208 * will not correspond to the event widget's parent. Check to see
1209 * if we are in the parent's navigation region.
1211 menu_item = gtk_get_event_widget ((GdkEvent*) event);
1212 if (menu_item && GTK_IS_MENU_ITEM (menu_item) && GTK_IS_MENU (menu_item->parent) &&
1213 gtk_menu_navigating_submenu (GTK_MENU (menu_item->parent), event->x_root, event->y_root))
1216 return GTK_WIDGET_CLASS (parent_class)->enter_notify_event (widget, event);
1220 gtk_menu_leave_notify (GtkWidget *widget,
1221 GdkEventCrossing *event)
1223 GtkMenuShell *menu_shell;
1225 GtkMenuItem *menu_item;
1226 GtkWidget *event_widget;
1228 menu = GTK_MENU (widget);
1229 menu_shell = GTK_MENU_SHELL (widget);
1231 if (gtk_menu_navigating_submenu (menu, event->x_root, event->y_root))
1234 event_widget = gtk_get_event_widget ((GdkEvent*) event);
1236 if (!event_widget || !GTK_IS_MENU_ITEM (event_widget))
1239 menu_item = GTK_MENU_ITEM (event_widget);
1241 /* Here we check to see if we're leaving an active menu item with a submenu,
1242 * in which case we enter submenu navigation mode.
1244 if (menu_shell->active_menu_item != NULL
1245 && menu_item->submenu != NULL
1246 && menu_item->submenu_placement == GTK_LEFT_RIGHT)
1248 if (menu_item->submenu->window != NULL)
1250 gtk_menu_set_submenu_navigation_region (menu, menu_item, event);
1255 return GTK_WIDGET_CLASS (parent_class)->leave_notify_event (widget, event);
1259 gtk_menu_stop_navigating_submenu (GtkMenu *menu)
1261 if (menu->navigation_region)
1263 gdk_region_destroy (menu->navigation_region);
1264 menu->navigation_region = NULL;
1267 if (menu->navigation_timeout)
1269 gtk_timeout_remove (menu->navigation_timeout);
1270 menu->navigation_timeout = 0;
1274 /* When the timeout is elapsed, the navigation region is destroyed
1275 * and the menuitem under the pointer (if any) is selected.
1278 gtk_menu_stop_navigating_submenu_cb (gpointer user_data)
1280 GdkEventCrossing send_event;
1282 GtkMenu *menu = user_data;
1283 GdkWindow *child_window;
1285 gtk_menu_stop_navigating_submenu (menu);
1287 if (GTK_WIDGET_REALIZED (menu))
1289 child_window = gdk_window_get_pointer (GTK_WIDGET (menu)->window, NULL, NULL, NULL);
1293 send_event.window = child_window;
1294 send_event.type = GDK_ENTER_NOTIFY;
1295 send_event.time = GDK_CURRENT_TIME; /* Bogus */
1296 send_event.send_event = TRUE;
1298 GTK_WIDGET_CLASS (parent_class)->enter_notify_event (GTK_WIDGET (menu), &send_event);
1306 gtk_menu_navigating_submenu (GtkMenu *menu,
1310 if (menu->navigation_region)
1312 if (gdk_region_point_in (menu->navigation_region, event_x, event_y))
1316 gtk_menu_stop_navigating_submenu (menu);
1324 gtk_menu_set_submenu_navigation_region (GtkMenu *menu,
1325 GtkMenuItem *menu_item,
1326 GdkEventCrossing *event)
1328 gint submenu_left = 0;
1329 gint submenu_right = 0;
1330 gint submenu_top = 0;
1331 gint submenu_bottom = 0;
1335 GtkWidget *event_widget;
1337 g_return_if_fail (menu_item->submenu != NULL);
1338 g_return_if_fail (event != NULL);
1340 event_widget = gtk_get_event_widget ((GdkEvent*) event);
1342 gdk_window_get_origin (menu_item->submenu->window, &submenu_left, &submenu_top);
1343 gdk_window_get_size (menu_item->submenu->window, &width, &height);
1344 submenu_right = submenu_left + width;
1345 submenu_bottom = submenu_top + height;
1347 gdk_window_get_size (event_widget->window, &width, &height);
1349 if (event->x >= 0 && event->x < width)
1351 /* Set navigation region */
1352 /* We fudge/give a little padding in case the user
1353 * ``misses the vertex'' of the triangle/is off by a pixel or two.
1355 if (menu_item->submenu_direction == GTK_DIRECTION_RIGHT)
1356 point[0].x = event->x_root - SUBMENU_NAV_REGION_PADDING;
1358 point[0].x = event->x_root + SUBMENU_NAV_REGION_PADDING;
1360 /* Exiting the top or bottom? */
1363 point[0].y = event->y_root + SUBMENU_NAV_REGION_PADDING;
1364 point[1].y = submenu_top;
1366 if (point[0].y <= point[1].y)
1371 point[0].y = event->y_root - SUBMENU_NAV_REGION_PADDING;
1372 point[1].y = submenu_bottom;
1374 if (point[0].y >= point[1].y)
1378 /* Submenu is to the left or right? */
1379 if (menu_item->submenu_direction == GTK_DIRECTION_RIGHT)
1380 point[1].x = submenu_left; /* right */
1382 point[1].x = submenu_right; /* left */
1384 point[2].x = point[1].x;
1385 point[2].y = point[0].y;
1387 gtk_menu_stop_navigating_submenu (menu);
1389 menu->navigation_region = gdk_region_polygon (point, 3, GDK_WINDING_RULE);
1391 menu->navigation_timeout = gtk_timeout_add (SUBMENU_NAV_HYSTERESIS_TIMEOUT,
1392 gtk_menu_stop_navigating_submenu_cb, menu);
1397 gtk_menu_deactivate (GtkMenuShell *menu_shell)
1401 g_return_if_fail (menu_shell != NULL);
1402 g_return_if_fail (GTK_IS_MENU (menu_shell));
1404 parent = menu_shell->parent_menu_shell;
1406 menu_shell->activate_time = 0;
1407 gtk_menu_popdown (GTK_MENU (menu_shell));
1410 gtk_menu_shell_deactivate (GTK_MENU_SHELL (parent));
1414 gtk_menu_position (GtkMenu *menu)
1417 GtkRequisition requisition;
1420 g_return_if_fail (menu != NULL);
1421 g_return_if_fail (GTK_IS_MENU (menu));
1423 widget = GTK_WIDGET (menu);
1425 gdk_window_get_pointer (NULL, &x, &y, NULL);
1427 /* We need the requisition to figure out the right place to
1428 * popup the menu. In fact, we always need to ask here, since
1429 * if a size_request was queued while we weren't popped up,
1430 * the requisition won't have been recomputed yet.
1432 gtk_widget_size_request (widget, &requisition);
1434 if (menu->position_func)
1435 (* menu->position_func) (menu, &x, &y, menu->position_func_data);
1441 screen_width = gdk_screen_width ();
1442 screen_height = gdk_screen_height ();
1444 x = CLAMP (x - 2, 0, MAX (0, screen_width - requisition.width));
1445 y = CLAMP (y - 2, 0, MAX (0, screen_height - requisition.height));
1448 /* FIXME: The MAX() here is because gtk_widget_set_uposition
1449 * is broken. Once we provide an alternate interface that
1450 * allows negative values, then we can remove them.
1452 gtk_widget_set_uposition (GTK_MENU_SHELL (menu)->active ?
1453 menu->toplevel : menu->tearoff_window,
1454 MAX (x, 0), MAX (y, 0));
1457 /* Reparent the menu, taking care of the refcounting
1460 gtk_menu_reparent (GtkMenu *menu,
1461 GtkWidget *new_parent,
1464 GtkObject *object = GTK_OBJECT (menu);
1465 GtkWidget *widget = GTK_WIDGET (menu);
1466 gboolean was_floating = GTK_OBJECT_FLOATING (object);
1468 gtk_object_ref (object);
1469 gtk_object_sink (object);
1473 gtk_object_ref (object);
1474 gtk_container_remove (GTK_CONTAINER (widget->parent), widget);
1475 gtk_container_add (GTK_CONTAINER (new_parent), widget);
1476 gtk_object_unref (object);
1479 gtk_widget_reparent (GTK_WIDGET (menu), new_parent);
1480 gtk_widget_set_usize (new_parent, -1, -1);
1483 GTK_OBJECT_SET_FLAGS (object, GTK_FLOATING);
1485 gtk_object_unref (object);
1489 gtk_menu_show_all (GtkWidget *widget)
1491 g_return_if_fail (widget != NULL);
1492 g_return_if_fail (GTK_IS_MENU (widget));
1494 /* Show children, but not self. */
1495 gtk_container_foreach (GTK_CONTAINER (widget), (GtkCallback) gtk_widget_show_all, NULL);
1500 gtk_menu_hide_all (GtkWidget *widget)
1502 g_return_if_fail (widget != NULL);
1503 g_return_if_fail (GTK_IS_MENU (widget));
1505 /* Hide children, but not self. */
1506 gtk_container_foreach (GTK_CONTAINER (widget), (GtkCallback) gtk_widget_hide_all, NULL);