source: trunk/third/gtk/gtk/gtkmenushell.c @ 15781

Revision 15781, 29.2 KB checked in by ghudson, 24 years ago (diff)
This commit was generated by cvs2svn to compensate for changes in r15780, which included commits to RCS files with non-trunk default branches.
Line 
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
20/*
21 * Modified by the GTK+ Team and others 1997-1999.  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/.
25 */
26
27#include "gdk/gdkkeysyms.h"
28#include "gtkbindings.h"
29#include "gtkmain.h"
30#include "gtkmenuitem.h"
31#include "gtktearoffmenuitem.h" /* FIXME */
32#include "gtkmenushell.h"
33#include "gtksignal.h"
34
35
36#define MENU_SHELL_TIMEOUT   500
37#define MENU_SHELL_CLASS(w)  GTK_MENU_SHELL_CLASS (GTK_OBJECT (w)->klass)
38
39
40enum {
41  DEACTIVATE,
42  SELECTION_DONE,
43  MOVE_CURRENT,
44  ACTIVATE_CURRENT,
45  CANCEL,
46  LAST_SIGNAL
47};
48
49typedef void (*GtkMenuShellSignal1) (GtkObject           *object,
50                                     GtkMenuDirectionType arg1,
51                                     gpointer             data);
52typedef void (*GtkMenuShellSignal2) (GtkObject *object,
53                                     gboolean   arg1,
54                                     gpointer   data);
55
56/* Terminology:
57 *
58 * A menu item can be "selected", this means that it is displayed
59 * in the prelight state, and if it has a submenu, that submenu
60 * will be popped up.
61 *
62 * A menu is "active" when it is visible onscreen and the user
63 * is selecting from it. A menubar is not active until the user
64 * clicks on one of its menuitems. When a menu is active,
65 * passing the mouse over a submenu will pop it up.
66 *
67 * menu_shell->active_menu_item, is however, not an "active"
68 * menu item (there is no such thing) but rather, the selected
69 * menu item in that MenuShell, if there is one.
70 *
71 * There is also is a concept of the current menu and a current
72 * menu item. The current menu item is the selected menu item
73 * that is furthest down in the heirarchy. (Every active menu_shell
74 * does not necessarily contain a selected menu item, but if
75 * it does, then menu_shell->parent_menu_shell must also contain
76 * a selected menu item. The current menu is the menu that
77 * contains the current menu_item. It will always have a GTK
78 * grab and receive all key presses.
79 *
80 *
81 * Action signals:
82 *
83 *  ::move_current (GtkMenuDirection *dir)
84 *     Moves the current menu item in direction 'dir':
85 *
86 *       GTK_MENU_DIR_PARENT: To the parent menu shell
87 *       GTK_MENU_DIR_CHILD: To the child menu shell (if this item has
88 *          a submenu.
89 *       GTK_MENU_DIR_NEXT/PREV: To the next or previous item
90 *          in this menu.
91 *
92 *     As a a bit of a hack to get movement between menus and
93 *     menubars working, if submenu_placement is different for
94 *     the menu and its MenuShell then the following apply:
95 *
96 *       - For 'parent' the current menu is not just moved to
97 *         the parent, but moved to the previous entry in the parent
98 *       - For 'child', if there is no child, then current is
99 *         moved to the next item in the parent.
100 *
101 *
102 *  ::activate_current (GBoolean *force_hide)
103 *     Activate the current item. If 'force_hide' is true, hide
104 *     the current menu item always. Otherwise, only hide
105 *     it if menu_item->klass->hide_on_activate is true.
106 *
107 *  ::cancel ()
108 *     Cancels the current selection
109 */
110
111static void gtk_menu_shell_class_init        (GtkMenuShellClass *klass);
112static void gtk_menu_shell_init              (GtkMenuShell      *menu_shell);
113static void gtk_menu_shell_map               (GtkWidget         *widget);
114static void gtk_menu_shell_realize           (GtkWidget         *widget);
115static gint gtk_menu_shell_button_press      (GtkWidget         *widget,
116                                              GdkEventButton    *event);
117static gint gtk_menu_shell_button_release    (GtkWidget         *widget,
118                                              GdkEventButton    *event);
119static gint gtk_menu_shell_key_press         (GtkWidget         *widget,
120                                              GdkEventKey       *event);
121static gint gtk_menu_shell_enter_notify      (GtkWidget         *widget,
122                                              GdkEventCrossing  *event);
123static gint gtk_menu_shell_leave_notify      (GtkWidget         *widget,
124                                              GdkEventCrossing  *event);
125static void gtk_menu_shell_add               (GtkContainer      *container,
126                                              GtkWidget         *widget);
127static void gtk_menu_shell_remove            (GtkContainer      *container,
128                                              GtkWidget         *widget);
129static void gtk_menu_shell_forall            (GtkContainer      *container,
130                                              gboolean           include_internals,
131                                              GtkCallback        callback,
132                                              gpointer           callback_data);
133static void gtk_real_menu_shell_deactivate   (GtkMenuShell      *menu_shell);
134static gint gtk_menu_shell_is_item           (GtkMenuShell      *menu_shell,
135                                              GtkWidget         *child);
136static GtkWidget *gtk_menu_shell_get_item    (GtkMenuShell      *menu_shell,
137                                              GdkEvent          *event);
138static GtkType    gtk_menu_shell_child_type  (GtkContainer      *container);
139static void gtk_menu_shell_select_submenu_first (GtkMenuShell   *menu_shell);
140
141static void gtk_real_menu_shell_move_current (GtkMenuShell      *menu_shell,
142                                              GtkMenuDirectionType direction);
143static void gtk_real_menu_shell_activate_current (GtkMenuShell      *menu_shell,
144                                                  gboolean           force_hide);
145static void gtk_real_menu_shell_cancel           (GtkMenuShell      *menu_shell);
146
147static GtkContainerClass *parent_class = NULL;
148static guint menu_shell_signals[LAST_SIGNAL] = { 0 };
149
150
151GtkType
152gtk_menu_shell_get_type (void)
153{
154  static GtkType menu_shell_type = 0;
155
156  if (!menu_shell_type)
157    {
158      static const GtkTypeInfo menu_shell_info =
159      {
160        "GtkMenuShell",
161        sizeof (GtkMenuShell),
162        sizeof (GtkMenuShellClass),
163        (GtkClassInitFunc) gtk_menu_shell_class_init,
164        (GtkObjectInitFunc) gtk_menu_shell_init,
165        /* reserved_1 */ NULL,
166        /* reserved_2 */ NULL,
167        (GtkClassInitFunc) NULL,
168      };
169
170      menu_shell_type = gtk_type_unique (gtk_container_get_type (), &menu_shell_info);
171    }
172
173  return menu_shell_type;
174}
175
176static void
177gtk_menu_shell_class_init (GtkMenuShellClass *klass)
178{
179  GtkObjectClass *object_class;
180  GtkWidgetClass *widget_class;
181  GtkContainerClass *container_class;
182
183  GtkBindingSet *binding_set;
184
185  object_class = (GtkObjectClass*) klass;
186  widget_class = (GtkWidgetClass*) klass;
187  container_class = (GtkContainerClass*) klass;
188
189  parent_class = gtk_type_class (gtk_container_get_type ());
190
191  menu_shell_signals[DEACTIVATE] =
192    gtk_signal_new ("deactivate",
193                    GTK_RUN_FIRST,
194                    object_class->type,
195                    GTK_SIGNAL_OFFSET (GtkMenuShellClass, deactivate),
196                    gtk_marshal_NONE__NONE,
197                    GTK_TYPE_NONE, 0);
198  menu_shell_signals[SELECTION_DONE] =
199    gtk_signal_new ("selection-done",
200                    GTK_RUN_FIRST,
201                    object_class->type,
202                    GTK_SIGNAL_OFFSET (GtkMenuShellClass, selection_done),
203                    gtk_marshal_NONE__NONE,
204                    GTK_TYPE_NONE, 0);
205  menu_shell_signals[MOVE_CURRENT] =
206    gtk_signal_new ("move_current",
207                    GTK_RUN_LAST | GTK_RUN_ACTION,
208                    object_class->type,
209                    GTK_SIGNAL_OFFSET (GtkMenuShellClass, move_current),
210                    gtk_marshal_NONE__ENUM,
211                    GTK_TYPE_NONE, 1,
212                    GTK_TYPE_MENU_DIRECTION_TYPE);
213  menu_shell_signals[ACTIVATE_CURRENT] =
214    gtk_signal_new ("activate_current",
215                    GTK_RUN_LAST | GTK_RUN_ACTION,
216                    object_class->type,
217                    GTK_SIGNAL_OFFSET (GtkMenuShellClass, activate_current),
218                    gtk_marshal_NONE__BOOL,
219                    GTK_TYPE_NONE, 1,
220                    GTK_TYPE_BOOL);
221  menu_shell_signals[CANCEL] =
222    gtk_signal_new ("cancel",
223                    GTK_RUN_LAST | GTK_RUN_ACTION,
224                    object_class->type,
225                    GTK_SIGNAL_OFFSET (GtkMenuShellClass, cancel),
226                    gtk_marshal_NONE__NONE,
227                    GTK_TYPE_NONE, 0);
228 
229  gtk_object_class_add_signals (object_class, menu_shell_signals, LAST_SIGNAL);
230
231  widget_class->map = gtk_menu_shell_map;
232  widget_class->realize = gtk_menu_shell_realize;
233  widget_class->button_press_event = gtk_menu_shell_button_press;
234  widget_class->button_release_event = gtk_menu_shell_button_release;
235  widget_class->key_press_event = gtk_menu_shell_key_press;
236  widget_class->enter_notify_event = gtk_menu_shell_enter_notify;
237  widget_class->leave_notify_event = gtk_menu_shell_leave_notify;
238
239  container_class->add = gtk_menu_shell_add;
240  container_class->remove = gtk_menu_shell_remove;
241  container_class->forall = gtk_menu_shell_forall;
242  container_class->child_type = gtk_menu_shell_child_type;
243
244  klass->submenu_placement = GTK_TOP_BOTTOM;
245  klass->deactivate = gtk_real_menu_shell_deactivate;
246  klass->selection_done = NULL;
247  klass->move_current = gtk_real_menu_shell_move_current;
248  klass->activate_current = gtk_real_menu_shell_activate_current;
249  klass->cancel = gtk_real_menu_shell_cancel;
250
251  binding_set = gtk_binding_set_by_class (klass);
252  gtk_binding_entry_add_signal (binding_set,
253                                GDK_Escape, 0,
254                                "cancel", 0);
255  gtk_binding_entry_add_signal (binding_set,
256                                GDK_Return, 0,
257                                "activate_current", 1,
258                                GTK_TYPE_BOOL,
259                                TRUE);
260  gtk_binding_entry_add_signal (binding_set,
261                                GDK_space, 0,
262                                "activate_current", 1,
263                                GTK_TYPE_BOOL,
264                                FALSE);
265}
266
267static GtkType
268gtk_menu_shell_child_type (GtkContainer     *container)
269{
270  return GTK_TYPE_MENU_ITEM;
271}
272
273static void
274gtk_menu_shell_init (GtkMenuShell *menu_shell)
275{
276  menu_shell->children = NULL;
277  menu_shell->active_menu_item = NULL;
278  menu_shell->parent_menu_shell = NULL;
279  menu_shell->active = FALSE;
280  menu_shell->have_grab = FALSE;
281  menu_shell->have_xgrab = FALSE;
282  menu_shell->ignore_leave = FALSE;
283  menu_shell->button = 0;
284  menu_shell->menu_flag = 0;
285  menu_shell->activate_time = 0;
286}
287
288void
289gtk_menu_shell_append (GtkMenuShell *menu_shell,
290                       GtkWidget    *child)
291{
292  gtk_menu_shell_insert (menu_shell, child, -1);
293}
294
295void
296gtk_menu_shell_prepend (GtkMenuShell *menu_shell,
297                        GtkWidget    *child)
298{
299  gtk_menu_shell_insert (menu_shell, child, 0);
300}
301
302void
303gtk_menu_shell_insert (GtkMenuShell *menu_shell,
304                       GtkWidget    *child,
305                       gint          position)
306{
307  g_return_if_fail (menu_shell != NULL);
308  g_return_if_fail (GTK_IS_MENU_SHELL (menu_shell));
309  g_return_if_fail (child != NULL);
310  g_return_if_fail (GTK_IS_MENU_ITEM (child));
311
312  menu_shell->children = g_list_insert (menu_shell->children, child, position);
313
314  gtk_widget_set_parent (child, GTK_WIDGET (menu_shell));
315
316  if (GTK_WIDGET_REALIZED (child->parent))
317    gtk_widget_realize (child);
318
319  if (GTK_WIDGET_VISIBLE (child->parent) && GTK_WIDGET_VISIBLE (child))
320    {
321      if (GTK_WIDGET_MAPPED (child->parent))
322        gtk_widget_map (child);
323
324      gtk_widget_queue_resize (child);
325    }
326}
327
328void
329gtk_menu_shell_deactivate (GtkMenuShell *menu_shell)
330{
331  g_return_if_fail (menu_shell != NULL);
332  g_return_if_fail (GTK_IS_MENU_SHELL (menu_shell));
333
334  gtk_signal_emit (GTK_OBJECT (menu_shell), menu_shell_signals[DEACTIVATE]);
335}
336
337static void
338gtk_menu_shell_map (GtkWidget *widget)
339{
340  GtkMenuShell *menu_shell;
341  GtkWidget *child;
342  GList *children;
343
344  g_return_if_fail (widget != NULL);
345  g_return_if_fail (GTK_IS_MENU_SHELL (widget));
346
347  menu_shell = GTK_MENU_SHELL (widget);
348  GTK_WIDGET_SET_FLAGS (menu_shell, GTK_MAPPED);
349
350  children = menu_shell->children;
351  while (children)
352    {
353      child = children->data;
354      children = children->next;
355
356      if (GTK_WIDGET_VISIBLE (child) && !GTK_WIDGET_MAPPED (child))
357        gtk_widget_map (child);
358    }
359
360  gdk_window_show (widget->window);
361}
362
363static void
364gtk_menu_shell_realize (GtkWidget *widget)
365{
366  GdkWindowAttr attributes;
367  gint attributes_mask;
368
369  g_return_if_fail (widget != NULL);
370  g_return_if_fail (GTK_IS_MENU_SHELL (widget));
371
372  GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
373
374  attributes.x = widget->allocation.x;
375  attributes.y = widget->allocation.y;
376  attributes.width = widget->allocation.width;
377  attributes.height = widget->allocation.height;
378  attributes.window_type = GDK_WINDOW_CHILD;
379  attributes.wclass = GDK_INPUT_OUTPUT;
380  attributes.visual = gtk_widget_get_visual (widget);
381  attributes.colormap = gtk_widget_get_colormap (widget);
382  attributes.event_mask = gtk_widget_get_events (widget);
383  attributes.event_mask |= (GDK_EXPOSURE_MASK |
384                            GDK_BUTTON_PRESS_MASK |
385                            GDK_BUTTON_RELEASE_MASK |
386                            GDK_KEY_PRESS_MASK |
387                            GDK_ENTER_NOTIFY_MASK |
388                            GDK_LEAVE_NOTIFY_MASK);
389
390  attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
391  widget->window = gdk_window_new (gtk_widget_get_parent_window (widget), &attributes, attributes_mask);
392  gdk_window_set_user_data (widget->window, widget);
393
394  widget->style = gtk_style_attach (widget->style, widget->window);
395  gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL);
396}
397
398static gint
399gtk_menu_shell_button_press (GtkWidget      *widget,
400                             GdkEventButton *event)
401{
402  GtkMenuShell *menu_shell;
403  GtkWidget *menu_item;
404
405  g_return_val_if_fail (widget != NULL, FALSE);
406  g_return_val_if_fail (GTK_IS_MENU_SHELL (widget), FALSE);
407  g_return_val_if_fail (event != NULL, FALSE);
408
409  if (event->type != GDK_BUTTON_PRESS)
410    return FALSE;
411
412  menu_shell = GTK_MENU_SHELL (widget);
413
414  if (menu_shell->parent_menu_shell)
415    {
416      gtk_widget_event (menu_shell->parent_menu_shell, (GdkEvent*) event);
417    }
418  else if (!menu_shell->active || !menu_shell->button)
419    {
420      if (!menu_shell->active)
421        {
422          gtk_grab_add (GTK_WIDGET (widget));
423          menu_shell->have_grab = TRUE;
424          menu_shell->active = TRUE;
425        }
426      menu_shell->button = event->button;
427
428      menu_item = gtk_menu_shell_get_item (menu_shell, (GdkEvent *)event);
429
430      if (menu_item &&
431          GTK_WIDGET_IS_SENSITIVE (menu_item))
432        {
433          if ((menu_item->parent == widget) &&
434              (menu_item != menu_shell->active_menu_item))
435            gtk_menu_shell_select_item (menu_shell, menu_item);
436        }
437    }
438  else
439    {
440      widget = gtk_get_event_widget ((GdkEvent*) event);
441      if (widget == GTK_WIDGET (menu_shell))
442        {
443          gtk_menu_shell_deactivate (menu_shell);
444          gtk_signal_emit (GTK_OBJECT (menu_shell), menu_shell_signals[SELECTION_DONE]);
445        }
446    }
447
448  return TRUE;
449}
450
451static gint
452gtk_menu_shell_button_release (GtkWidget      *widget,
453                               GdkEventButton *event)
454{
455  GtkMenuShell *menu_shell;
456  GtkWidget *menu_item;
457  gint deactivate;
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  menu_shell = GTK_MENU_SHELL (widget);
464  if (menu_shell->active)
465    {
466      if (menu_shell->button && (event->button != menu_shell->button))
467        {
468          menu_shell->button = 0;
469          if (menu_shell->parent_menu_shell)
470            gtk_widget_event (menu_shell->parent_menu_shell, (GdkEvent*) event);
471          return TRUE;
472        }
473     
474      menu_shell->button = 0;
475      menu_item = gtk_menu_shell_get_item (menu_shell, (GdkEvent*) event);
476
477      deactivate = TRUE;
478
479      if ((event->time - menu_shell->activate_time) > MENU_SHELL_TIMEOUT)
480        {
481          if (menu_item && (menu_shell->active_menu_item == menu_item) &&
482              GTK_WIDGET_IS_SENSITIVE (menu_item))
483            {
484              if (GTK_MENU_ITEM (menu_item)->submenu == NULL)
485                {
486                  gtk_menu_shell_activate_item (menu_shell, menu_item, TRUE);
487                  return TRUE;
488                }
489            }
490          else if (menu_shell->parent_menu_shell)
491            {
492              menu_shell->active = TRUE;
493              gtk_widget_event (menu_shell->parent_menu_shell, (GdkEvent*) event);
494              return TRUE;
495            }
496        }
497      else
498        {
499          /* We only ever want to prevent deactivation on the first
500           * press/release. Setting the time to zero is a bit of a
501           * hack, since we could be being triggered in the first
502           * few fractions of a second after a server time wraparound.
503           * the chances of that happening are ~1/10^6, without
504           * serious harm if we lose.
505           */
506          menu_shell->activate_time = 0;
507          deactivate = FALSE;
508        }
509     
510      /* If the button click was very fast, or we ended up on a submenu,
511       * leave the menu up
512       */
513      if (!deactivate ||
514          (menu_item && (menu_shell->active_menu_item == menu_item)))
515        {
516          deactivate = FALSE;
517          menu_shell->ignore_leave = TRUE;
518        }
519      else
520        deactivate = TRUE;
521
522      if (deactivate)
523        {
524          gtk_menu_shell_deactivate (menu_shell);
525          gtk_signal_emit (GTK_OBJECT (menu_shell), menu_shell_signals[SELECTION_DONE]);
526        }
527    }
528
529  return TRUE;
530}
531
532static gint
533gtk_menu_shell_key_press (GtkWidget     *widget,
534                          GdkEventKey *event)
535{
536  GtkMenuShell *menu_shell;
537 
538  g_return_val_if_fail (widget != NULL, FALSE);
539  g_return_val_if_fail (GTK_IS_MENU_SHELL (widget), FALSE);
540  g_return_val_if_fail (event != NULL, FALSE);
541     
542  menu_shell = GTK_MENU_SHELL (widget);
543
544  if (!menu_shell->active_menu_item && menu_shell->parent_menu_shell)
545    return gtk_widget_event (menu_shell->parent_menu_shell, (GdkEvent *)event);
546 
547  if (gtk_bindings_activate (GTK_OBJECT (widget),
548                             event->keyval,
549                             event->state))
550    return TRUE;
551
552  if (gtk_accel_groups_activate (GTK_OBJECT (widget), event->keyval, event->state))
553    return TRUE;
554
555  return FALSE;
556}
557
558static gint
559gtk_menu_shell_enter_notify (GtkWidget        *widget,
560                             GdkEventCrossing *event)
561{
562  GtkMenuShell *menu_shell;
563  GtkWidget *menu_item;
564
565  g_return_val_if_fail (widget != NULL, FALSE);
566  g_return_val_if_fail (GTK_IS_MENU_SHELL (widget), FALSE);
567  g_return_val_if_fail (event != NULL, FALSE);
568
569  menu_shell = GTK_MENU_SHELL (widget);
570
571  if (menu_shell->active && !menu_shell->ignore_enter)
572    {
573      menu_item = gtk_get_event_widget ((GdkEvent*) event);
574
575      if (!menu_item || !GTK_WIDGET_IS_SENSITIVE (menu_item))
576        return TRUE;
577
578      if ((menu_item->parent == widget) &&
579          (menu_shell->active_menu_item != menu_item) &&
580          GTK_IS_MENU_ITEM (menu_item))
581        {
582          if ((event->detail != GDK_NOTIFY_INFERIOR) &&
583              (GTK_WIDGET_STATE (menu_item) != GTK_STATE_PRELIGHT))
584            {
585              gtk_menu_shell_select_item (menu_shell, menu_item);
586            }
587        }
588      else if (menu_shell->parent_menu_shell)
589        {
590          gtk_widget_event (menu_shell->parent_menu_shell, (GdkEvent*) event);
591        }
592    }
593
594  return TRUE;
595}
596
597static gint
598gtk_menu_shell_leave_notify (GtkWidget        *widget,
599                             GdkEventCrossing *event)
600{
601  GtkMenuShell *menu_shell;
602  GtkMenuItem *menu_item;
603  GtkWidget *event_widget;
604
605  g_return_val_if_fail (widget != NULL, FALSE);
606  g_return_val_if_fail (GTK_IS_MENU_SHELL (widget), FALSE);
607  g_return_val_if_fail (event != NULL, FALSE);
608
609  if (GTK_WIDGET_VISIBLE (widget))
610    {
611      menu_shell = GTK_MENU_SHELL (widget);
612      event_widget = gtk_get_event_widget ((GdkEvent*) event);
613
614      if (!event_widget || !GTK_IS_MENU_ITEM (event_widget))
615        return TRUE;
616
617      menu_item = GTK_MENU_ITEM (event_widget);
618
619      if (menu_shell->ignore_leave)
620        {
621          menu_shell->ignore_leave = FALSE;
622          return TRUE;
623        }
624
625      if (!GTK_WIDGET_IS_SENSITIVE (menu_item))
626        return TRUE;
627
628      if ((menu_shell->active_menu_item == event_widget) &&
629          (menu_item->submenu == NULL))
630        {
631          if ((event->detail != GDK_NOTIFY_INFERIOR) &&
632              (GTK_WIDGET_STATE (menu_item) != GTK_STATE_NORMAL))
633            {
634              gtk_menu_shell_deselect (menu_shell);
635            }
636        }
637      else if (menu_shell->parent_menu_shell)
638        {
639          gtk_widget_event (menu_shell->parent_menu_shell, (GdkEvent*) event);
640        }
641    }
642
643  return TRUE;
644}
645
646static void
647gtk_menu_shell_add (GtkContainer *container,
648                    GtkWidget    *widget)
649{
650  gtk_menu_shell_append (GTK_MENU_SHELL (container), widget);
651}
652
653static void
654gtk_menu_shell_remove (GtkContainer *container,
655                       GtkWidget    *widget)
656{
657  GtkMenuShell *menu_shell;
658  gint was_visible;
659 
660  g_return_if_fail (container != NULL);
661  g_return_if_fail (GTK_IS_MENU_SHELL (container));
662  g_return_if_fail (widget != NULL);
663  g_return_if_fail (GTK_IS_MENU_ITEM (widget));
664 
665  was_visible = GTK_WIDGET_VISIBLE (widget);
666  menu_shell = GTK_MENU_SHELL (container);
667  menu_shell->children = g_list_remove (menu_shell->children, widget);
668 
669  if (widget == menu_shell->active_menu_item)
670    {
671      gtk_item_deselect (GTK_ITEM (menu_shell->active_menu_item));
672      menu_shell->active_menu_item = NULL;
673    }
674
675  gtk_widget_unparent (widget);
676 
677  /* queue resize regardless of GTK_WIDGET_VISIBLE (container),
678   * since that's what is needed by toplevels.
679   */
680  if (was_visible)
681    gtk_widget_queue_resize (GTK_WIDGET (container));
682}
683
684static void
685gtk_menu_shell_forall (GtkContainer *container,
686                       gboolean      include_internals,
687                       GtkCallback   callback,
688                       gpointer      callback_data)
689{
690  GtkMenuShell *menu_shell;
691  GtkWidget *child;
692  GList *children;
693
694  g_return_if_fail (container != NULL);
695  g_return_if_fail (GTK_IS_MENU_SHELL (container));
696  g_return_if_fail (callback != NULL);
697
698  menu_shell = GTK_MENU_SHELL (container);
699
700  children = menu_shell->children;
701  while (children)
702    {
703      child = children->data;
704      children = children->next;
705
706      (* callback) (child, callback_data);
707    }
708}
709
710
711static void
712gtk_real_menu_shell_deactivate (GtkMenuShell *menu_shell)
713{
714  g_return_if_fail (menu_shell != NULL);
715  g_return_if_fail (GTK_IS_MENU_SHELL (menu_shell));
716
717  if (menu_shell->active)
718    {
719      menu_shell->button = 0;
720      menu_shell->active = FALSE;
721
722      if (menu_shell->active_menu_item)
723        {
724          gtk_menu_item_deselect (GTK_MENU_ITEM (menu_shell->active_menu_item));
725          menu_shell->active_menu_item = NULL;
726        }
727
728      if (menu_shell->have_grab)
729        {
730          menu_shell->have_grab = FALSE;
731          gtk_grab_remove (GTK_WIDGET (menu_shell));
732        }
733      if (menu_shell->have_xgrab)
734        {
735          menu_shell->have_xgrab = FALSE;
736          gdk_pointer_ungrab (GDK_CURRENT_TIME);
737          gdk_keyboard_ungrab (GDK_CURRENT_TIME);
738        }
739    }
740}
741
742static gint
743gtk_menu_shell_is_item (GtkMenuShell *menu_shell,
744                        GtkWidget    *child)
745{
746  GtkWidget *parent;
747
748  g_return_val_if_fail (menu_shell != NULL, FALSE);
749  g_return_val_if_fail (GTK_IS_MENU_SHELL (menu_shell), FALSE);
750  g_return_val_if_fail (child != NULL, FALSE);
751
752  parent = child->parent;
753  while (parent && GTK_IS_MENU_SHELL (parent))
754    {
755      if (parent == (GtkWidget*) menu_shell)
756        return TRUE;
757      parent = GTK_MENU_SHELL (parent)->parent_menu_shell;
758    }
759
760  return FALSE;
761}
762
763static GtkWidget*
764gtk_menu_shell_get_item (GtkMenuShell *menu_shell,
765                         GdkEvent     *event)
766{
767  GtkWidget *menu_item;
768
769  menu_item = gtk_get_event_widget ((GdkEvent*) event);
770 
771  while (menu_item && !GTK_IS_MENU_ITEM (menu_item))
772    menu_item = menu_item->parent;
773
774  if (menu_item && gtk_menu_shell_is_item (menu_shell, menu_item))
775    return menu_item;
776  else
777    return NULL;
778}
779
780/* Handlers for action signals */
781
782void
783gtk_menu_shell_select_item (GtkMenuShell *menu_shell,
784                            GtkWidget    *menu_item)
785{
786  g_return_if_fail (menu_shell != NULL);
787  g_return_if_fail (GTK_IS_MENU_SHELL (menu_shell));
788  g_return_if_fail (menu_item != NULL);
789  g_return_if_fail (GTK_IS_MENU_ITEM (menu_item));
790
791  gtk_menu_shell_deselect (menu_shell);
792 
793  menu_shell->active_menu_item = menu_item;
794  gtk_menu_item_set_placement (GTK_MENU_ITEM (menu_shell->active_menu_item),
795                               MENU_SHELL_CLASS (menu_shell)->submenu_placement);
796  gtk_menu_item_select (GTK_MENU_ITEM (menu_shell->active_menu_item));
797
798  /* This allows the bizarre radio buttons-with-submenus-display-history
799   * behavior
800   */
801  if (GTK_MENU_ITEM (menu_shell->active_menu_item)->submenu)
802    gtk_widget_activate (menu_shell->active_menu_item);
803}
804
805void
806gtk_menu_shell_deselect (GtkMenuShell *menu_shell)
807{
808  g_return_if_fail (GTK_IS_MENU_SHELL (menu_shell));
809
810  if (menu_shell->active_menu_item)
811    {
812      gtk_menu_item_deselect (GTK_MENU_ITEM (menu_shell->active_menu_item));
813      menu_shell->active_menu_item = NULL;
814    }
815}
816
817void
818gtk_menu_shell_activate_item (GtkMenuShell      *menu_shell,
819                              GtkWidget         *menu_item,
820                              gboolean           force_deactivate)
821{
822  GSList *slist, *shells = NULL;
823  gboolean deactivate = force_deactivate;
824
825  g_return_if_fail (menu_shell != NULL);
826  g_return_if_fail (GTK_IS_MENU_SHELL (menu_shell));
827  g_return_if_fail (menu_item != NULL);
828  g_return_if_fail (GTK_IS_MENU_ITEM (menu_item));
829
830  if (!deactivate)
831    deactivate = GTK_MENU_ITEM_CLASS (GTK_OBJECT (menu_item)->klass)->hide_on_activate;
832
833  gtk_widget_ref (GTK_WIDGET (menu_shell));
834
835  if (deactivate)
836    {
837      GtkMenuShell *parent_menu_shell = menu_shell;
838
839      do
840        {
841          gtk_widget_ref (GTK_WIDGET (parent_menu_shell));
842          shells = g_slist_prepend (shells, parent_menu_shell);
843          parent_menu_shell = (GtkMenuShell*) parent_menu_shell->parent_menu_shell;
844        }
845      while (parent_menu_shell);
846      shells = g_slist_reverse (shells);
847
848      gtk_menu_shell_deactivate (menu_shell);
849 
850      /* flush the x-queue, so any grabs are removed and
851       * the menu is actually taken down
852       */
853      gdk_flush ();
854    }
855
856  gtk_widget_activate (menu_item);
857
858  for (slist = shells; slist; slist = slist->next)
859    {
860      gtk_signal_emit (slist->data, menu_shell_signals[SELECTION_DONE]);
861      gtk_widget_unref (slist->data);
862    }
863  g_slist_free (shells);
864
865  gtk_widget_unref (GTK_WIDGET (menu_shell));
866}
867
868/* Distance should be +/- 1 */
869static void
870gtk_menu_shell_move_selected (GtkMenuShell  *menu_shell,
871                              gint           distance)
872{
873  if (menu_shell->active_menu_item)
874    {
875      GList *node = g_list_find (menu_shell->children,
876                                 menu_shell->active_menu_item);
877      GList *start_node = node;
878     
879      if (distance > 0)
880        {
881          node = node->next;
882          while (node != start_node &&
883                 (!node ||
884                  !GTK_WIDGET_IS_SENSITIVE (node->data) ||
885                  !GTK_WIDGET_VISIBLE (node->data) ))
886            {
887              if (!node)
888                node = menu_shell->children;
889              else
890                node = node->next;
891            }
892        }
893      else
894        {
895          node = node->prev;
896          while (node != start_node &&
897                 (!node ||
898                  !GTK_WIDGET_IS_SENSITIVE (node->data) ||
899                  !GTK_WIDGET_VISIBLE (node->data) ))
900            {
901              if (!node)
902                node = g_list_last (menu_shell->children);
903              else
904                node = node->prev;
905            }
906        }
907     
908      if (node)
909        gtk_menu_shell_select_item (menu_shell, node->data);
910    }
911}
912
913static void
914gtk_menu_shell_select_submenu_first (GtkMenuShell     *menu_shell)
915{
916  GtkMenuItem *menu_item;
917
918  menu_item = GTK_MENU_ITEM (menu_shell->active_menu_item);
919 
920  if (menu_item->submenu)
921    {
922      GtkMenuShell *submenu = GTK_MENU_SHELL (menu_item->submenu);
923      if (submenu->children)
924        gtk_menu_shell_select_item (submenu, submenu->children->data);
925    }
926}
927
928static void
929gtk_real_menu_shell_move_current (GtkMenuShell      *menu_shell,
930                                  GtkMenuDirectionType direction)
931{
932  GtkMenuShell *parent_menu_shell = NULL;
933  gboolean had_selection;
934
935  had_selection = menu_shell->active_menu_item != NULL;
936
937  if (menu_shell->parent_menu_shell)
938    parent_menu_shell = GTK_MENU_SHELL (menu_shell->parent_menu_shell);
939 
940  switch (direction)
941    {
942    case GTK_MENU_DIR_PARENT:
943      if (parent_menu_shell)
944        {
945          if (GTK_MENU_SHELL_CLASS (GTK_OBJECT (parent_menu_shell)->klass)->submenu_placement ==
946                       GTK_MENU_SHELL_CLASS (GTK_OBJECT (menu_shell)->klass)->submenu_placement)
947            gtk_menu_shell_deselect (menu_shell);
948          else
949            {
950              gtk_menu_shell_move_selected (parent_menu_shell, -1);
951              gtk_menu_shell_select_submenu_first (parent_menu_shell);
952            }
953        }
954      break;
955     
956    case GTK_MENU_DIR_CHILD:
957      if (menu_shell->active_menu_item &&
958          GTK_BIN (menu_shell->active_menu_item)->child &&
959          GTK_MENU_ITEM (menu_shell->active_menu_item)->submenu)
960        {
961          menu_shell = GTK_MENU_SHELL (GTK_MENU_ITEM (menu_shell->active_menu_item)->submenu);
962          if (menu_shell->children)
963            gtk_menu_shell_select_item (menu_shell, menu_shell->children->data);
964        }
965      else
966        {
967          /* Try to find a menu running the opposite direction */
968          while (parent_menu_shell &&
969                 (GTK_MENU_SHELL_CLASS (GTK_OBJECT (parent_menu_shell)->klass)->submenu_placement ==
970                  GTK_MENU_SHELL_CLASS (GTK_OBJECT (menu_shell)->klass)->submenu_placement))
971            parent_menu_shell = GTK_MENU_SHELL (parent_menu_shell->parent_menu_shell);
972         
973          if (parent_menu_shell)
974            {
975              gtk_menu_shell_move_selected (parent_menu_shell, 1);
976              gtk_menu_shell_select_submenu_first (parent_menu_shell);
977            }
978        }
979      break;
980     
981    case GTK_MENU_DIR_PREV:
982      gtk_menu_shell_move_selected (menu_shell, -1);
983      if (!had_selection &&
984          !menu_shell->active_menu_item &&
985          menu_shell->children)
986        gtk_menu_shell_select_item (menu_shell, g_list_last (menu_shell->children)->data);
987      break;
988    case GTK_MENU_DIR_NEXT:
989      gtk_menu_shell_move_selected (menu_shell, 1);
990      if (!had_selection &&
991          !menu_shell->active_menu_item &&
992          menu_shell->children)
993        gtk_menu_shell_select_item (menu_shell, menu_shell->children->data);
994      break;
995    }
996 
997}
998
999static void
1000gtk_real_menu_shell_activate_current (GtkMenuShell      *menu_shell,
1001                                      gboolean           force_hide)
1002{
1003  if (menu_shell->active_menu_item &&
1004      GTK_WIDGET_IS_SENSITIVE (menu_shell->active_menu_item) &&
1005      GTK_MENU_ITEM (menu_shell->active_menu_item)->submenu == NULL)
1006    {
1007      gtk_menu_shell_activate_item (menu_shell,
1008                                    menu_shell->active_menu_item,
1009                                    force_hide);
1010    }
1011}
1012
1013static void
1014gtk_real_menu_shell_cancel (GtkMenuShell      *menu_shell)
1015{
1016  /* Unset the active menu item so gtk_menu_popdown() doesn't see it.
1017   */
1018  gtk_menu_shell_deselect (menu_shell);
1019 
1020  gtk_menu_shell_deactivate (menu_shell);
1021  gtk_signal_emit (GTK_OBJECT (menu_shell), menu_shell_signals[SELECTION_DONE]);
1022}
1023
Note: See TracBrowser for help on using the repository browser.