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 Library 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 * Library General Public License for more details.
14 * You should have received a copy of the GNU Library General Public
15 * License along with this library; if not, write to the Free
16 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19 #include "gdk/gdkkeysyms.h"
22 #include "gtkmenuitem.h"
23 #include "gtksignal.h"
26 #define MENU_ITEM_CLASS(w) GTK_MENU_ITEM_CLASS (GTK_OBJECT (w)->klass)
29 typedef struct _GtkMenuAttachData GtkMenuAttachData;
31 struct _GtkMenuAttachData
33 GtkWidget *attach_widget;
34 GtkMenuDetachFunc detacher;
38 static void gtk_menu_class_init (GtkMenuClass *klass);
39 static void gtk_menu_init (GtkMenu *menu);
40 static void gtk_menu_destroy (GtkObject *object);
41 static void gtk_menu_show (GtkWidget *widget);
42 static void gtk_menu_map (GtkWidget *widget);
43 static void gtk_menu_unmap (GtkWidget *widget);
44 static void gtk_menu_realize (GtkWidget *widget);
45 static void gtk_menu_size_request (GtkWidget *widget,
46 GtkRequisition *requisition);
47 static void gtk_menu_size_allocate (GtkWidget *widget,
48 GtkAllocation *allocation);
49 static void gtk_menu_paint (GtkWidget *widget);
50 static void gtk_menu_draw (GtkWidget *widget,
52 static gint gtk_menu_expose (GtkWidget *widget,
53 GdkEventExpose *event);
54 static gint gtk_menu_configure (GtkWidget *widget,
55 GdkEventConfigure *event);
56 static gint gtk_menu_key_press (GtkWidget *widget,
58 static gint gtk_menu_need_resize (GtkContainer *container);
59 static void gtk_menu_deactivate (GtkMenuShell *menu_shell);
60 static void gtk_menu_show_all (GtkWidget *widget);
61 static void gtk_menu_hide_all (GtkWidget *widget);
63 static GtkMenuShellClass *parent_class = NULL;
64 static const gchar *attach_data_key = "gtk-menu-attach-data";
70 static guint menu_type = 0;
74 GtkTypeInfo menu_info =
78 sizeof (GtkMenuClass),
79 (GtkClassInitFunc) gtk_menu_class_init,
80 (GtkObjectInitFunc) gtk_menu_init,
85 menu_type = gtk_type_unique (gtk_menu_shell_get_type (), &menu_info);
92 gtk_menu_class_init (GtkMenuClass *class)
94 GtkObjectClass *object_class;
95 GtkWidgetClass *widget_class;
96 GtkContainerClass *container_class;
97 GtkMenuShellClass *menu_shell_class;
99 object_class = (GtkObjectClass*) class;
100 widget_class = (GtkWidgetClass*) class;
101 container_class = (GtkContainerClass*) class;
102 menu_shell_class = (GtkMenuShellClass*) class;
103 parent_class = gtk_type_class (gtk_menu_shell_get_type ());
105 object_class->destroy = gtk_menu_destroy;
107 widget_class->show = gtk_menu_show;
108 widget_class->map = gtk_menu_map;
109 widget_class->unmap = gtk_menu_unmap;
110 widget_class->realize = gtk_menu_realize;
111 widget_class->draw = gtk_menu_draw;
112 widget_class->size_request = gtk_menu_size_request;
113 widget_class->size_allocate = gtk_menu_size_allocate;
114 widget_class->expose_event = gtk_menu_expose;
115 widget_class->configure_event = gtk_menu_configure;
116 widget_class->key_press_event = gtk_menu_key_press;
117 widget_class->show_all = gtk_menu_show_all;
118 widget_class->hide_all = gtk_menu_hide_all;
120 container_class->need_resize = gtk_menu_need_resize;
122 menu_shell_class->submenu_placement = GTK_LEFT_RIGHT;
123 menu_shell_class->deactivate = gtk_menu_deactivate;
127 gtk_menu_init (GtkMenu *menu)
129 GTK_WIDGET_SET_FLAGS (menu, GTK_TOPLEVEL);
131 menu->parent_menu_item = NULL;
132 menu->old_active_menu_item = NULL;
133 menu->accelerator_table = NULL;
134 menu->position_func = NULL;
135 menu->position_func_data = NULL;
137 GTK_MENU_SHELL (menu)->menu_flag = TRUE;
139 /* we don't need to register as toplevel anymore,
140 * since there is the attach/detach functionality in place.
141 * gtk_container_register_toplevel (GTK_CONTAINER (menu));
146 gtk_menu_destroy (GtkObject *object)
148 GtkMenuAttachData *data;
150 g_return_if_fail (object != NULL);
151 g_return_if_fail (GTK_IS_MENU (object));
153 gtk_widget_ref (GTK_WIDGET (object));
155 data = gtk_object_get_data (object, attach_data_key);
157 gtk_menu_detach (GTK_MENU (object));
159 /* we don't need this:
160 * gtk_container_unregister_toplevel (GTK_CONTAINER (object));
163 if (GTK_OBJECT_CLASS (parent_class)->destroy)
164 (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
166 gtk_widget_unref (GTK_WIDGET (object));
171 gtk_menu_attach_to_widget (GtkMenu *menu,
172 GtkWidget *attach_widget,
173 GtkMenuDetachFunc detacher)
175 GtkMenuAttachData *data;
177 g_return_if_fail (menu != NULL);
178 g_return_if_fail (GTK_IS_MENU (menu));
179 g_return_if_fail (attach_widget != NULL);
180 g_return_if_fail (GTK_IS_WIDGET (attach_widget));
181 g_return_if_fail (detacher != NULL);
183 /* keep this function in sync with gtk_widget_set_parent()
186 data = gtk_object_get_data (GTK_OBJECT (menu), attach_data_key);
189 g_warning ("gtk_menu_attach_to_widget(): menu already attached to %s",
190 gtk_type_name (GTK_OBJECT_TYPE (data->attach_widget)));
194 gtk_widget_ref (menu);
195 gtk_object_sink (GTK_OBJECT (menu));
197 data = g_new (GtkMenuAttachData, 1);
198 data->attach_widget = attach_widget;
199 data->detacher = detacher;
200 gtk_object_set_data (GTK_OBJECT (menu), attach_data_key, data);
202 if (GTK_WIDGET_STATE (menu) != GTK_STATE_NORMAL)
203 gtk_widget_set_state (GTK_WIDGET (menu), GTK_STATE_NORMAL);
205 /* we don't need to set the style here, since
206 * we are a toplevel widget.
211 gtk_menu_get_attach_widget (GtkMenu *menu)
213 GtkMenuAttachData *data;
215 g_return_val_if_fail (menu != NULL, NULL);
216 g_return_val_if_fail (GTK_IS_MENU (menu), NULL);
218 data = gtk_object_get_data (GTK_OBJECT (menu), attach_data_key);
220 return data->attach_widget;
225 gtk_menu_detach (GtkMenu *menu)
227 GtkMenuAttachData *data;
229 g_return_if_fail (menu != NULL);
230 g_return_if_fail (GTK_IS_MENU (menu));
232 /* keep this function in sync with gtk_widget_unparent()
234 data = gtk_object_get_data (GTK_OBJECT (menu), attach_data_key);
237 g_warning ("gtk_menu_detach(): menu is not attached");
240 gtk_object_remove_data (GTK_OBJECT (menu), attach_data_key);
242 data->detacher (data->attach_widget, menu);
244 if (GTK_WIDGET_REALIZED (menu))
245 gtk_widget_unrealize (GTK_WIDGET (menu));
249 gtk_widget_unref (GTK_WIDGET (menu));
255 return GTK_WIDGET (gtk_type_new (gtk_menu_get_type ()));
259 gtk_menu_append (GtkMenu *menu,
262 gtk_menu_shell_append (GTK_MENU_SHELL (menu), child);
266 gtk_menu_prepend (GtkMenu *menu,
269 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), child);
273 gtk_menu_insert (GtkMenu *menu,
277 gtk_menu_shell_insert (GTK_MENU_SHELL (menu), child, position);
281 gtk_menu_popup (GtkMenu *menu,
282 GtkWidget *parent_menu_shell,
283 GtkWidget *parent_menu_item,
284 GtkMenuPositionFunc func,
287 guint32 activate_time)
289 g_return_if_fail (menu != NULL);
290 g_return_if_fail (GTK_IS_MENU (menu));
292 GTK_MENU_SHELL (menu)->parent_menu_shell = parent_menu_shell;
293 GTK_MENU_SHELL (menu)->active = TRUE;
294 GTK_MENU_SHELL (menu)->button = button;
296 menu->parent_menu_item = parent_menu_item;
297 menu->position_func = func;
298 menu->position_func_data = data;
299 GTK_MENU_SHELL (menu)->activate_time = activate_time;
301 gtk_widget_show (GTK_WIDGET (menu));
302 gtk_grab_add (GTK_WIDGET (menu));
306 gtk_menu_popdown (GtkMenu *menu)
308 GtkMenuShell *menu_shell;
310 g_return_if_fail (menu != NULL);
311 g_return_if_fail (GTK_IS_MENU (menu));
313 menu_shell = GTK_MENU_SHELL (menu);
315 menu_shell->parent_menu_shell = NULL;
316 menu_shell->active = FALSE;
318 if (menu_shell->active_menu_item)
320 menu->old_active_menu_item = menu_shell->active_menu_item;
321 gtk_menu_item_deselect (GTK_MENU_ITEM (menu_shell->active_menu_item));
322 menu_shell->active_menu_item = NULL;
325 gtk_widget_hide (GTK_WIDGET (menu));
326 gtk_grab_remove (GTK_WIDGET (menu));
330 gtk_menu_get_active (GtkMenu *menu)
335 g_return_val_if_fail (menu != NULL, NULL);
336 g_return_val_if_fail (GTK_IS_MENU (menu), NULL);
338 if (!menu->old_active_menu_item)
341 children = GTK_MENU_SHELL (menu)->children;
345 child = children->data;
346 children = children->next;
348 if (GTK_BIN (child)->child)
353 menu->old_active_menu_item = child;
356 return menu->old_active_menu_item;
360 gtk_menu_set_active (GtkMenu *menu,
366 g_return_if_fail (menu != NULL);
367 g_return_if_fail (GTK_IS_MENU (menu));
369 tmp_list = g_list_nth (GTK_MENU_SHELL (menu)->children, index);
372 child = tmp_list->data;
373 if (GTK_BIN (child)->child)
374 menu->old_active_menu_item = child;
379 gtk_menu_set_accelerator_table (GtkMenu *menu,
380 GtkAcceleratorTable *table)
382 g_return_if_fail (menu != NULL);
383 g_return_if_fail (GTK_IS_MENU (menu));
385 if (menu->accelerator_table != table)
387 if (menu->accelerator_table)
388 gtk_accelerator_table_unref (menu->accelerator_table);
389 menu->accelerator_table = table;
390 if (menu->accelerator_table)
391 gtk_accelerator_table_ref (menu->accelerator_table);
397 gtk_menu_show (GtkWidget *widget)
399 g_return_if_fail (widget != NULL);
400 g_return_if_fail (GTK_IS_MENU (widget));
402 GTK_WIDGET_SET_FLAGS (widget, GTK_VISIBLE);
403 gtk_widget_map (widget);
407 gtk_menu_map (GtkWidget *widget)
410 GtkMenuShell *menu_shell;
413 GtkAllocation allocation;
416 g_return_if_fail (widget != NULL);
417 g_return_if_fail (GTK_IS_MENU (widget));
419 menu = GTK_MENU (widget);
420 menu_shell = GTK_MENU_SHELL (widget);
421 GTK_WIDGET_SET_FLAGS (menu_shell, GTK_MAPPED);
423 gtk_widget_size_request (widget, &widget->requisition);
425 if (menu_shell->menu_flag)
427 menu_shell->menu_flag = FALSE;
429 allocation.x = widget->allocation.x;
430 allocation.y = widget->allocation.y;
431 allocation.width = widget->requisition.width;
432 allocation.height = widget->requisition.height;
434 gtk_widget_size_allocate (widget, &allocation);
437 gdk_window_get_pointer (NULL, &x, &y, NULL);
439 if (menu->position_func)
440 (* menu->position_func) (menu, &x, &y, menu->position_func_data);
446 screen_width = gdk_screen_width ();
447 screen_height = gdk_screen_height ();
452 if ((x + widget->requisition.width) > screen_width)
453 x -= ((x + widget->requisition.width) - screen_width);
456 if ((y + widget->requisition.height) > screen_height)
457 y -= ((y + widget->requisition.height) - screen_height);
462 gdk_window_move_resize (widget->window, x, y,
463 widget->requisition.width,
464 widget->requisition.height);
466 children = menu_shell->children;
469 child = children->data;
470 children = children->next;
472 if (GTK_WIDGET_VISIBLE (child) && !GTK_WIDGET_MAPPED (child))
473 gtk_widget_map (child);
476 gdk_window_show (widget->window);
480 gtk_menu_unmap (GtkWidget *widget)
482 g_return_if_fail (widget != NULL);
483 g_return_if_fail (GTK_IS_MENU (widget));
485 GTK_WIDGET_UNSET_FLAGS (widget, GTK_MAPPED);
486 gdk_window_hide (widget->window);
490 gtk_menu_realize (GtkWidget *widget)
492 GdkWindowAttr attributes;
493 gint attributes_mask;
495 g_return_if_fail (widget != NULL);
496 g_return_if_fail (GTK_IS_MENU (widget));
498 GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
500 attributes.x = widget->allocation.x;
501 attributes.y = widget->allocation.y;
502 attributes.width = widget->allocation.width;
503 attributes.height = widget->allocation.height;
504 attributes.wclass = GDK_INPUT_OUTPUT;
505 attributes.visual = gtk_widget_get_visual (widget);
506 attributes.colormap = gtk_widget_get_colormap (widget);
507 attributes.window_type = GDK_WINDOW_TEMP;
508 attributes.event_mask = gtk_widget_get_events (widget);
509 attributes.event_mask |= (GDK_EXPOSURE_MASK |
513 attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
514 widget->window = gdk_window_new (NULL, &attributes, attributes_mask);
515 gdk_window_set_user_data (widget->window, widget);
517 widget->style = gtk_style_attach (widget->style, widget->window);
518 gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL);
522 gtk_menu_size_request (GtkWidget *widget,
523 GtkRequisition *requisition)
526 GtkMenuShell *menu_shell;
529 gint max_accelerator_size;
530 gint max_toggle_size;
532 g_return_if_fail (widget != NULL);
533 g_return_if_fail (GTK_IS_MENU (widget));
534 g_return_if_fail (requisition != NULL);
536 menu = GTK_MENU (widget);
537 menu_shell = GTK_MENU_SHELL (widget);
539 requisition->width = 0;
540 requisition->height = 0;
542 max_accelerator_size = 0;
545 children = menu_shell->children;
548 child = children->data;
549 children = children->next;
551 if (GTK_WIDGET_VISIBLE (child))
553 GTK_MENU_ITEM (child)->show_submenu_indicator = TRUE;
554 gtk_widget_size_request (child, &child->requisition);
556 requisition->width = MAX (requisition->width, child->requisition.width);
557 requisition->height += child->requisition.height;
559 max_accelerator_size = MAX (max_accelerator_size, GTK_MENU_ITEM (child)->accelerator_size);
560 max_toggle_size = MAX (max_toggle_size, MENU_ITEM_CLASS (child)->toggle_size);
564 requisition->width += max_toggle_size + max_accelerator_size;
565 requisition->width += (GTK_CONTAINER (menu)->border_width +
566 widget->style->klass->xthickness) * 2;
567 requisition->height += (GTK_CONTAINER (menu)->border_width +
568 widget->style->klass->ythickness) * 2;
570 children = menu_shell->children;
573 child = children->data;
574 children = children->next;
576 GTK_MENU_ITEM (child)->accelerator_size = max_accelerator_size;
577 GTK_MENU_ITEM (child)->toggle_size = max_toggle_size;
582 gtk_menu_size_allocate (GtkWidget *widget,
583 GtkAllocation *allocation)
586 GtkMenuShell *menu_shell;
588 GtkAllocation child_allocation;
591 g_return_if_fail (widget != NULL);
592 g_return_if_fail (GTK_IS_MENU (widget));
593 g_return_if_fail (allocation != NULL);
595 menu = GTK_MENU (widget);
596 menu_shell = GTK_MENU_SHELL (widget);
598 widget->allocation = *allocation;
600 if (menu_shell->children)
602 child_allocation.x = (GTK_CONTAINER (menu)->border_width +
603 widget->style->klass->xthickness);
604 child_allocation.y = (GTK_CONTAINER (menu)->border_width +
605 widget->style->klass->ythickness);
606 child_allocation.width = allocation->width - child_allocation.x * 2;
608 children = menu_shell->children;
611 child = children->data;
612 children = children->next;
614 if (GTK_WIDGET_VISIBLE (child))
616 child_allocation.height = child->requisition.height;
618 gtk_widget_size_allocate (child, &child_allocation);
620 child_allocation.y += child_allocation.height;
627 gtk_menu_paint (GtkWidget *widget)
629 g_return_if_fail (widget != NULL);
630 g_return_if_fail (GTK_IS_MENU (widget));
632 if (GTK_WIDGET_DRAWABLE (widget))
634 gtk_draw_shadow (widget->style,
639 widget->allocation.width,
640 widget->allocation.height);
645 gtk_menu_draw (GtkWidget *widget,
648 GtkMenuShell *menu_shell;
650 GdkRectangle child_area;
653 g_return_if_fail (widget != NULL);
654 g_return_if_fail (GTK_IS_MENU (widget));
655 g_return_if_fail (area != NULL);
657 if (GTK_WIDGET_DRAWABLE (widget))
659 gtk_menu_paint (widget);
661 menu_shell = GTK_MENU_SHELL (widget);
663 children = menu_shell->children;
666 child = children->data;
667 children = children->next;
669 if (gtk_widget_intersect (child, area, &child_area))
670 gtk_widget_draw (child, &child_area);
676 gtk_menu_expose (GtkWidget *widget,
677 GdkEventExpose *event)
679 GtkMenuShell *menu_shell;
681 GdkEventExpose child_event;
684 g_return_val_if_fail (widget != NULL, FALSE);
685 g_return_val_if_fail (GTK_IS_MENU (widget), FALSE);
686 g_return_val_if_fail (event != NULL, FALSE);
688 if (GTK_WIDGET_DRAWABLE (widget))
690 gtk_menu_paint (widget);
692 menu_shell = GTK_MENU_SHELL (widget);
693 child_event = *event;
695 children = menu_shell->children;
698 child = children->data;
699 children = children->next;
701 if (GTK_WIDGET_NO_WINDOW (child) &&
702 gtk_widget_intersect (child, &event->area, &child_event.area))
703 gtk_widget_event (child, (GdkEvent*) &child_event);
711 gtk_menu_configure (GtkWidget *widget,
712 GdkEventConfigure *event)
714 GtkAllocation allocation;
716 g_return_val_if_fail (widget != NULL, FALSE);
717 g_return_val_if_fail (GTK_IS_MENU (widget), FALSE);
718 g_return_val_if_fail (event != NULL, FALSE);
720 if (GTK_MENU_SHELL (widget)->menu_flag)
722 GTK_MENU_SHELL (widget)->menu_flag = FALSE;
726 allocation.width = event->width;
727 allocation.height = event->height;
729 gtk_widget_size_allocate (widget, &allocation);
736 gtk_menu_key_press (GtkWidget *widget,
739 GtkAllocation allocation;
740 GtkAcceleratorTable *table;
741 GtkMenuShell *menu_shell;
742 GtkMenuItem *menu_item;
746 g_return_val_if_fail (widget != NULL, FALSE);
747 g_return_val_if_fail (GTK_IS_MENU (widget), FALSE);
748 g_return_val_if_fail (event != NULL, FALSE);
750 delete = ((event->keyval == GDK_Delete) ||
751 (event->keyval == GDK_BackSpace));
753 if (delete || ((event->keyval >= 0x20) && (event->keyval <= 0xFF)))
755 menu_shell = GTK_MENU_SHELL (widget);
756 menu_item = GTK_MENU_ITEM (menu_shell->active_menu_item);
758 if (menu_item && GTK_BIN (menu_item)->child)
761 gtk_container_block_resize (GTK_CONTAINER (widget));
764 /* if the menu item currently has an accelerator then we'll
765 * remove it before we do anything else.
767 if (menu_item->accelerator_signal)
769 signame = gtk_signal_name (menu_item->accelerator_signal);
770 table = gtk_accelerator_table_find (GTK_OBJECT (widget),
772 menu_item->accelerator_key,
773 menu_item->accelerator_mods);
775 table = GTK_MENU (widget)->accelerator_table;
776 gtk_widget_remove_accelerator (GTK_WIDGET (menu_item),
781 table = GTK_MENU (widget)->accelerator_table;
783 /* if we aren't simply deleting the accelerator, then we'll install
787 gtk_widget_install_accelerator (GTK_WIDGET (menu_item),
789 toupper (event->keyval),
792 /* check and see if the menu has changed size. */
793 gtk_widget_size_request (widget, &widget->requisition);
795 allocation.x = widget->allocation.x;
796 allocation.y = widget->allocation.y;
797 allocation.width = widget->requisition.width;
798 allocation.height = widget->requisition.height;
800 if ((allocation.width == widget->allocation.width) &&
801 (allocation.height == widget->allocation.height))
803 gtk_widget_queue_draw (widget);
807 gtk_widget_size_allocate (GTK_WIDGET (widget), &allocation);
808 gtk_menu_map (widget);
811 /* unblock resizes */
812 gtk_container_unblock_resize (GTK_CONTAINER (widget));
820 gtk_menu_need_resize (GtkContainer *container)
822 GtkAllocation allocation;
824 g_return_val_if_fail (container != NULL, FALSE);
825 g_return_val_if_fail (GTK_IS_MENU (container), FALSE);
827 if (GTK_WIDGET_VISIBLE (container))
829 GTK_MENU_SHELL (container)->menu_flag = FALSE;
831 gtk_widget_size_request (GTK_WIDGET (container),
832 >K_WIDGET (container)->requisition);
834 allocation.x = GTK_WIDGET (container)->allocation.x;
835 allocation.y = GTK_WIDGET (container)->allocation.y;
836 allocation.width = GTK_WIDGET (container)->requisition.width;
837 allocation.height = GTK_WIDGET (container)->requisition.height;
839 gtk_widget_size_allocate (GTK_WIDGET (container), &allocation);
843 GTK_MENU_SHELL (container)->menu_flag = TRUE;
850 gtk_menu_deactivate (GtkMenuShell *menu_shell)
852 GtkMenuShell *parent;
854 g_return_if_fail (menu_shell != NULL);
855 g_return_if_fail (GTK_IS_MENU (menu_shell));
857 parent = GTK_MENU_SHELL (menu_shell->parent_menu_shell);
859 menu_shell->activate_time = 0;
860 gtk_menu_popdown (GTK_MENU (menu_shell));
863 gtk_menu_shell_deactivate (parent);
868 gtk_menu_show_all (GtkWidget *widget)
870 GtkContainer *container;
872 g_return_if_fail (widget != NULL);
873 g_return_if_fail (GTK_IS_MENU (widget));
874 container = GTK_CONTAINER (widget);
876 /* Show children, but not self. */
877 gtk_container_foreach (container, (GtkCallback) gtk_widget_show_all, NULL);
882 gtk_menu_hide_all (GtkWidget *widget)
884 GtkContainer *container;
886 g_return_if_fail (widget != NULL);
887 g_return_if_fail (GTK_IS_MENU (widget));
888 container = GTK_CONTAINER (widget);
890 /* Hide children, but not self. */
891 gtk_container_foreach (container, (GtkCallback) gtk_widget_hide_all, NULL);