]> Pileus Git - ~andy/gtk/blob - gtk/gtkmenushell.c
gpointer gtk_object_get_user_data (GtkObject *object) { if
[~andy/gtk] / gtk / gtkmenushell.c
1 /* GTK - The GIMP Toolkit
2  * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
3  *
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.
8  *
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.
13  *
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.
17  */
18 #include "gtkmain.h"
19 #include "gtkmenuitem.h"
20 #include "gtkmenushell.h"
21 #include "gtksignal.h"
22
23
24 #define MENU_SHELL_TIMEOUT   500
25 #define MENU_SHELL_CLASS(w)  GTK_MENU_SHELL_CLASS (GTK_OBJECT (w)->klass)
26
27
28 enum {
29   DEACTIVATE,
30   LAST_SIGNAL
31 };
32
33
34 static void gtk_menu_shell_class_init        (GtkMenuShellClass *klass);
35 static void gtk_menu_shell_init              (GtkMenuShell      *menu_shell);
36 static void gtk_menu_shell_map               (GtkWidget         *widget);
37 static void gtk_menu_shell_realize           (GtkWidget         *widget);
38 static gint gtk_menu_shell_button_press      (GtkWidget         *widget,
39                                               GdkEventButton    *event);
40 static gint gtk_menu_shell_button_release    (GtkWidget         *widget,
41                                               GdkEventButton    *event);
42 static gint gtk_menu_shell_enter_notify      (GtkWidget         *widget,
43                                               GdkEventCrossing  *event);
44 static gint gtk_menu_shell_leave_notify      (GtkWidget         *widget,
45                                               GdkEventCrossing  *event);
46 static void gtk_menu_shell_add               (GtkContainer      *container,
47                                               GtkWidget         *widget);
48 static void gtk_menu_shell_remove            (GtkContainer      *container,
49                                               GtkWidget         *widget);
50 static void gtk_menu_shell_foreach           (GtkContainer      *container,
51                                               GtkCallback        callback,
52                                               gpointer           callback_data);
53 static void gtk_real_menu_shell_deactivate   (GtkMenuShell      *menu_shell);
54 static gint gtk_menu_shell_is_item           (GtkMenuShell      *menu_shell,
55                                               GtkWidget         *child);
56
57
58 static GtkContainerClass *parent_class = NULL;
59 static guint menu_shell_signals[LAST_SIGNAL] = { 0 };
60
61
62 guint
63 gtk_menu_shell_get_type ()
64 {
65   static guint menu_shell_type = 0;
66
67   if (!menu_shell_type)
68     {
69       GtkTypeInfo menu_shell_info =
70       {
71         "GtkMenuShell",
72         sizeof (GtkMenuShell),
73         sizeof (GtkMenuShellClass),
74         (GtkClassInitFunc) gtk_menu_shell_class_init,
75         (GtkObjectInitFunc) gtk_menu_shell_init,
76         (GtkArgSetFunc) NULL,
77         (GtkArgGetFunc) NULL,
78       };
79
80       menu_shell_type = gtk_type_unique (gtk_container_get_type (), &menu_shell_info);
81     }
82
83   return menu_shell_type;
84 }
85
86 static void
87 gtk_menu_shell_class_init (GtkMenuShellClass *klass)
88 {
89   GtkObjectClass *object_class;
90   GtkWidgetClass *widget_class;
91   GtkContainerClass *container_class;
92
93   object_class = (GtkObjectClass*) klass;
94   widget_class = (GtkWidgetClass*) klass;
95   container_class = (GtkContainerClass*) klass;
96
97   parent_class = gtk_type_class (gtk_container_get_type ());
98
99   menu_shell_signals[DEACTIVATE] =
100     gtk_signal_new ("deactivate",
101                     GTK_RUN_FIRST,
102                     object_class->type,
103                     GTK_SIGNAL_OFFSET (GtkMenuShellClass, deactivate),
104                     gtk_signal_default_marshaller,
105                     GTK_TYPE_NONE, 0);
106
107   gtk_object_class_add_signals (object_class, menu_shell_signals, LAST_SIGNAL);
108
109   widget_class->map = gtk_menu_shell_map;
110   widget_class->realize = gtk_menu_shell_realize;
111   widget_class->button_press_event = gtk_menu_shell_button_press;
112   widget_class->button_release_event = gtk_menu_shell_button_release;
113   widget_class->enter_notify_event = gtk_menu_shell_enter_notify;
114   widget_class->leave_notify_event = gtk_menu_shell_leave_notify;
115
116   container_class->add = gtk_menu_shell_add;
117   container_class->remove = gtk_menu_shell_remove;
118   container_class->foreach = gtk_menu_shell_foreach;
119
120   klass->submenu_placement = GTK_TOP_BOTTOM;
121   klass->deactivate = gtk_real_menu_shell_deactivate;
122 }
123
124 static void
125 gtk_menu_shell_init (GtkMenuShell *menu_shell)
126 {
127   menu_shell->children = NULL;
128   menu_shell->active_menu_item = NULL;
129   menu_shell->parent_menu_shell = NULL;
130   menu_shell->active = FALSE;
131   menu_shell->have_grab = FALSE;
132   menu_shell->have_xgrab = FALSE;
133   menu_shell->ignore_leave = FALSE;
134   menu_shell->button = 0;
135   menu_shell->menu_flag = 0;
136   menu_shell->activate_time = 0;
137 }
138
139 void
140 gtk_menu_shell_append (GtkMenuShell *menu_shell,
141                        GtkWidget    *child)
142 {
143   gtk_menu_shell_insert (menu_shell, child, -1);
144 }
145
146 void
147 gtk_menu_shell_prepend (GtkMenuShell *menu_shell,
148                         GtkWidget    *child)
149 {
150   gtk_menu_shell_insert (menu_shell, child, 0);
151 }
152
153 void
154 gtk_menu_shell_insert (GtkMenuShell *menu_shell,
155                        GtkWidget    *child,
156                        gint          position)
157 {
158   GList *tmp_list;
159   GList *new_list;
160   gint nchildren;
161
162   g_return_if_fail (menu_shell != NULL);
163   g_return_if_fail (GTK_IS_MENU_SHELL (menu_shell));
164   g_return_if_fail (child != NULL);
165   g_return_if_fail (GTK_IS_MENU_ITEM (child));
166
167   gtk_widget_set_parent (child, GTK_WIDGET (menu_shell));
168
169   if (GTK_WIDGET_VISIBLE (child->parent))
170     {
171       if (GTK_WIDGET_REALIZED (child->parent) &&
172           !GTK_WIDGET_REALIZED (child))
173         gtk_widget_realize (child);
174
175       if (GTK_WIDGET_MAPPED (child->parent) &&
176           !GTK_WIDGET_MAPPED (child))
177         gtk_widget_map (child);
178     }
179
180   nchildren = g_list_length (menu_shell->children);
181   if ((position < 0) || (position > nchildren))
182     position = nchildren;
183
184   if (position == nchildren)
185     {
186       menu_shell->children = g_list_append (menu_shell->children, child);
187     }
188   else
189     {
190       tmp_list = g_list_nth (menu_shell->children, position);
191       new_list = g_list_alloc ();
192       new_list->data = child;
193
194       if (tmp_list->prev)
195         tmp_list->prev->next = new_list;
196       new_list->next = tmp_list;
197       new_list->prev = tmp_list->prev;
198       tmp_list->prev = new_list;
199
200       if (tmp_list == menu_shell->children)
201         menu_shell->children = new_list;
202     }
203
204   if (GTK_WIDGET_VISIBLE (menu_shell))
205     gtk_widget_queue_resize (GTK_WIDGET (menu_shell));
206 }
207
208 void
209 gtk_menu_shell_deactivate (GtkMenuShell *menu_shell)
210 {
211   g_return_if_fail (menu_shell != NULL);
212   g_return_if_fail (GTK_IS_MENU_SHELL (menu_shell));
213
214   gtk_signal_emit (GTK_OBJECT (menu_shell), menu_shell_signals[DEACTIVATE]);
215 }
216
217 static void
218 gtk_menu_shell_map (GtkWidget *widget)
219 {
220   GtkMenuShell *menu_shell;
221   GtkWidget *child;
222   GList *children;
223
224   g_return_if_fail (widget != NULL);
225   g_return_if_fail (GTK_IS_MENU_SHELL (widget));
226
227   menu_shell = GTK_MENU_SHELL (widget);
228   GTK_WIDGET_SET_FLAGS (menu_shell, GTK_MAPPED);
229   gdk_window_show (widget->window);
230
231   children = menu_shell->children;
232   while (children)
233     {
234       child = children->data;
235       children = children->next;
236
237       if (GTK_WIDGET_VISIBLE (child) && !GTK_WIDGET_MAPPED (child))
238         gtk_widget_map (child);
239     }
240 }
241
242 static void
243 gtk_menu_shell_realize (GtkWidget *widget)
244 {
245   GdkWindowAttr attributes;
246   gint attributes_mask;
247
248   g_return_if_fail (widget != NULL);
249   g_return_if_fail (GTK_IS_MENU_SHELL (widget));
250
251   GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
252
253   attributes.x = widget->allocation.x;
254   attributes.y = widget->allocation.y;
255   attributes.width = widget->allocation.width;
256   attributes.height = widget->allocation.height;
257   attributes.window_type = GDK_WINDOW_CHILD;
258   attributes.wclass = GDK_INPUT_OUTPUT;
259   attributes.visual = gtk_widget_get_visual (widget);
260   attributes.colormap = gtk_widget_get_colormap (widget);
261   attributes.event_mask = gtk_widget_get_events (widget);
262   attributes.event_mask |= (GDK_EXPOSURE_MASK |
263                             GDK_BUTTON_PRESS_MASK |
264                             GDK_BUTTON_RELEASE_MASK |
265                             GDK_ENTER_NOTIFY_MASK |
266                             GDK_LEAVE_NOTIFY_MASK);
267
268   attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
269   widget->window = gdk_window_new (gtk_widget_get_parent_window (widget), &attributes, attributes_mask);
270   gdk_window_set_user_data (widget->window, widget);
271
272   widget->style = gtk_style_attach (widget->style, widget->window);
273   gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL);
274 }
275
276 static gint
277 gtk_menu_shell_button_press (GtkWidget      *widget,
278                              GdkEventButton *event)
279 {
280   GtkMenuShell *menu_shell;
281   GtkWidget *menu_item;
282
283   g_return_val_if_fail (widget != NULL, FALSE);
284   g_return_val_if_fail (GTK_IS_MENU_SHELL (widget), FALSE);
285   g_return_val_if_fail (event != NULL, FALSE);
286
287   if (event->type != GDK_BUTTON_PRESS)
288     return FALSE;
289
290   menu_shell = GTK_MENU_SHELL (widget);
291
292   if (menu_shell->parent_menu_shell)
293     {
294       gtk_widget_event (menu_shell->parent_menu_shell, (GdkEvent*) event);
295     }
296   else if (!menu_shell->active || !menu_shell->button)
297     {
298       if (!menu_shell->active)
299         {
300           gtk_grab_add (GTK_WIDGET (widget));
301           menu_shell->have_grab = TRUE;
302         }
303       menu_shell->active = TRUE;
304
305       menu_item = gtk_get_event_widget ((GdkEvent*) event);
306       if (menu_item && GTK_IS_MENU_ITEM (menu_item) &&
307           gtk_menu_shell_is_item (menu_shell, menu_item))
308         {
309           if ((menu_item->parent == widget) &&
310               (menu_item != menu_shell->active_menu_item))
311             {
312               if (menu_shell->active_menu_item)
313                 gtk_menu_item_deselect (GTK_MENU_ITEM (menu_shell->active_menu_item));
314               
315               menu_shell->active_menu_item = menu_item;
316               gtk_menu_item_set_placement (GTK_MENU_ITEM (menu_shell->active_menu_item),
317                                            MENU_SHELL_CLASS (menu_shell)->submenu_placement);
318               gtk_menu_item_select (GTK_MENU_ITEM (menu_shell->active_menu_item));
319             }
320         }
321       else if (!menu_shell->button)
322         {
323           gtk_menu_shell_deactivate (menu_shell);
324         }
325       
326       if (menu_shell->active)
327         menu_shell->button = event->button;
328     }
329   else
330     {
331       widget = gtk_get_event_widget ((GdkEvent*) event);
332       if (widget == GTK_WIDGET (menu_shell))
333         gtk_menu_shell_deactivate (menu_shell);
334     }
335
336   return TRUE;
337 }
338
339 static gint
340 gtk_menu_shell_button_release (GtkWidget      *widget,
341                                GdkEventButton *event)
342 {
343   GtkMenuShell *menu_shell;
344   GtkWidget *menu_item;
345   gint deactivate;
346
347   g_return_val_if_fail (widget != NULL, FALSE);
348   g_return_val_if_fail (GTK_IS_MENU_SHELL (widget), FALSE);
349   g_return_val_if_fail (event != NULL, FALSE);
350
351   menu_shell = GTK_MENU_SHELL (widget);
352   if (menu_shell->active)
353     {
354       if (menu_shell->button && (event->button != menu_shell->button))
355         {
356           menu_shell->button = 0;
357           if (menu_shell->parent_menu_shell)
358             gtk_widget_event (menu_shell->parent_menu_shell, (GdkEvent*) event);
359           return TRUE;
360         }
361
362       menu_shell->button = 0;
363       menu_item = gtk_get_event_widget ((GdkEvent*) event);
364       deactivate = TRUE;
365
366       if ((event->time - menu_shell->activate_time) > MENU_SHELL_TIMEOUT)
367         {
368           if (menu_shell->active_menu_item == menu_item)
369             {
370               if (GTK_MENU_ITEM (menu_item)->submenu == NULL)
371                 {
372                   gtk_menu_shell_deactivate (menu_shell);
373                   gtk_widget_activate (menu_item);
374                   return TRUE;
375                 }
376             }
377           else if (menu_shell->parent_menu_shell)
378             {
379               menu_shell->active = TRUE;
380               gtk_widget_event (menu_shell->parent_menu_shell, (GdkEvent*) event);
381               return TRUE;
382             }
383         }
384       else
385         deactivate = FALSE;
386
387       if ((!deactivate || (menu_shell->active_menu_item == menu_item)) &&
388           (gdk_pointer_grab (widget->window, TRUE,
389                              GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
390                              GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK,
391                              NULL, NULL, event->time) == 0))
392         {
393           deactivate = FALSE;
394           menu_shell->have_xgrab = TRUE;
395           menu_shell->ignore_leave = TRUE;
396         }
397       else
398         deactivate = TRUE;
399
400       if (deactivate)
401         gtk_menu_shell_deactivate (menu_shell);
402     }
403
404   return TRUE;
405 }
406
407 static gint
408 gtk_menu_shell_enter_notify (GtkWidget        *widget,
409                              GdkEventCrossing *event)
410 {
411   GtkMenuShell *menu_shell;
412   GtkWidget *menu_item;
413
414   g_return_val_if_fail (widget != NULL, FALSE);
415   g_return_val_if_fail (GTK_IS_MENU_SHELL (widget), FALSE);
416   g_return_val_if_fail (event != NULL, FALSE);
417
418   menu_shell = GTK_MENU_SHELL (widget);
419   if (menu_shell->active)
420     {
421       menu_item = gtk_get_event_widget ((GdkEvent*) event);
422
423       if (!menu_item || !GTK_WIDGET_IS_SENSITIVE (menu_item))
424         return TRUE;
425
426       if ((menu_item->parent == widget) &&
427           (menu_shell->active_menu_item != menu_item) &&
428           GTK_IS_MENU_ITEM (menu_item))
429         {
430           if ((event->detail != GDK_NOTIFY_INFERIOR) &&
431               (GTK_WIDGET_STATE (menu_item) != GTK_STATE_PRELIGHT))
432             {
433               if (menu_shell->active_menu_item)
434                 gtk_menu_item_deselect (GTK_MENU_ITEM (menu_shell->active_menu_item));
435
436               menu_shell->active_menu_item = menu_item;
437               gtk_menu_item_set_placement (GTK_MENU_ITEM (menu_shell->active_menu_item),
438                                            MENU_SHELL_CLASS (menu_shell)->submenu_placement);
439               gtk_menu_item_select (GTK_MENU_ITEM (menu_shell->active_menu_item));
440             }
441         }
442       else if (menu_shell->parent_menu_shell)
443         {
444           gtk_widget_event (menu_shell->parent_menu_shell, (GdkEvent*) event);
445         }
446     }
447
448   return TRUE;
449 }
450
451 static gint
452 gtk_menu_shell_leave_notify (GtkWidget        *widget,
453                              GdkEventCrossing *event)
454 {
455   GtkMenuShell *menu_shell;
456   GtkMenuItem *menu_item;
457   GtkWidget *event_widget;
458
459   g_return_val_if_fail (widget != NULL, FALSE);
460   g_return_val_if_fail (GTK_IS_MENU_SHELL (widget), FALSE);
461   g_return_val_if_fail (event != NULL, FALSE);
462
463   if (GTK_WIDGET_VISIBLE (widget))
464     {
465       menu_shell = GTK_MENU_SHELL (widget);
466       event_widget = gtk_get_event_widget ((GdkEvent*) event);
467
468       if (!event_widget || !GTK_IS_MENU_ITEM (event_widget))
469         return TRUE;
470
471       menu_item = GTK_MENU_ITEM (event_widget);
472
473       if (!GTK_WIDGET_IS_SENSITIVE (menu_item))
474         return TRUE;
475
476       if (menu_shell->ignore_leave)
477         {
478           menu_shell->ignore_leave = FALSE;
479           return TRUE;
480         }
481
482       if ((menu_shell->active_menu_item == event_widget) &&
483           (menu_item->submenu == NULL))
484         {
485           if ((event->detail != GDK_NOTIFY_INFERIOR) &&
486               (GTK_WIDGET_STATE (menu_item) != GTK_STATE_NORMAL))
487             {
488               gtk_menu_item_deselect (GTK_MENU_ITEM (menu_shell->active_menu_item));
489               menu_shell->active_menu_item = NULL;
490             }
491         }
492       else if (menu_shell->parent_menu_shell)
493         {
494           gtk_widget_event (menu_shell->parent_menu_shell, (GdkEvent*) event);
495         }
496     }
497
498   return TRUE;
499 }
500
501 static void
502 gtk_menu_shell_add (GtkContainer *container,
503                     GtkWidget    *widget)
504 {
505   gtk_menu_shell_append (GTK_MENU_SHELL (container), widget);
506 }
507
508 static void
509 gtk_menu_shell_remove (GtkContainer *container,
510                        GtkWidget    *widget)
511 {
512   GtkMenuShell *menu_shell;
513   gint was_visible;
514   
515   g_return_if_fail (container != NULL);
516   g_return_if_fail (GTK_IS_MENU_SHELL (container));
517   g_return_if_fail (widget != NULL);
518   g_return_if_fail (GTK_IS_MENU_ITEM (widget));
519   
520   was_visible = GTK_WIDGET_VISIBLE (widget);
521   menu_shell = GTK_MENU_SHELL (container);
522   menu_shell->children = g_list_remove (menu_shell->children, widget);
523   
524   gtk_widget_unparent (widget);
525   
526   if (was_visible && GTK_WIDGET_VISIBLE (container))
527     gtk_widget_queue_resize (GTK_WIDGET (container));
528 }
529
530 static void
531 gtk_menu_shell_foreach (GtkContainer *container,
532                         GtkCallback   callback,
533                         gpointer      callback_data)
534 {
535   GtkMenuShell *menu_shell;
536   GtkWidget *child;
537   GList *children;
538
539   g_return_if_fail (container != NULL);
540   g_return_if_fail (GTK_IS_MENU_SHELL (container));
541   g_return_if_fail (callback != NULL);
542
543   menu_shell = GTK_MENU_SHELL (container);
544
545   children = menu_shell->children;
546   while (children)
547     {
548       child = children->data;
549       children = children->next;
550
551       (* callback) (child, callback_data);
552     }
553 }
554
555
556 static void
557 gtk_real_menu_shell_deactivate (GtkMenuShell *menu_shell)
558 {
559   g_return_if_fail (menu_shell != NULL);
560   g_return_if_fail (GTK_IS_MENU_SHELL (menu_shell));
561
562   if (menu_shell->active)
563     {
564       menu_shell->button = 0;
565       menu_shell->active = FALSE;
566
567       if (menu_shell->active_menu_item)
568         {
569           gtk_menu_item_deselect (GTK_MENU_ITEM (menu_shell->active_menu_item));
570           menu_shell->active_menu_item = NULL;
571         }
572
573       if (menu_shell->have_grab)
574         {
575           menu_shell->have_grab = FALSE;
576           gtk_grab_remove (GTK_WIDGET (menu_shell));
577         }
578       if (menu_shell->have_xgrab)
579         {
580           menu_shell->have_xgrab = FALSE;
581           gdk_pointer_ungrab (GDK_CURRENT_TIME);
582         }
583     }
584 }
585
586 static gint
587 gtk_menu_shell_is_item (GtkMenuShell *menu_shell,
588                         GtkWidget    *child)
589 {
590   GtkWidget *parent;
591
592   g_return_val_if_fail (menu_shell != NULL, FALSE);
593   g_return_val_if_fail (GTK_IS_MENU_SHELL (menu_shell), FALSE);
594   g_return_val_if_fail (child != NULL, FALSE);
595
596   parent = child->parent;
597   while (parent && GTK_IS_MENU_SHELL (parent))
598     {
599       if (parent == (GtkWidget*) menu_shell)
600         return TRUE;
601       parent = GTK_MENU_SHELL (parent)->parent_menu_shell;
602     }
603
604   return FALSE;
605 }