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