gtkmenushell.c 71.5 KB
Newer Older
Cody Russell's avatar
Cody Russell committed
1
/* GTK - The GIMP Toolkit
Elliot Lee's avatar
Elliot Lee committed
2 3 4
 * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
 *
 * This library is free software; you can redistribute it and/or
5
 * modify it under the terms of the GNU Lesser General Public
Elliot Lee's avatar
Elliot Lee committed
6 7 8 9 10 11
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12
 * Lesser General Public License for more details.
Elliot Lee's avatar
Elliot Lee committed
13
 *
14
 * You should have received a copy of the GNU Lesser General Public
Javier Jardón's avatar
Javier Jardón committed
15
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
Elliot Lee's avatar
Elliot Lee committed
16
 */
17 18

/*
19
 * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
20 21 22 23 24
 * file for a list of people on the GTK+ Team.  See the ChangeLog
 * files for a list of changes.  These files are distributed with
 * GTK+ at ftp://ftp.gtk.org/pub/gtk/. 
 */

25 26 27 28 29 30 31 32 33 34 35 36
/**
 * SECTION:gtkmenushell
 * @Title: GtkMenuShell
 * @Short_description: A base class for menu objects
 *
 * A #GtkMenuShell is the abstract base class used to derive the
 * #GtkMenu and #GtkMenuBar subclasses.
 *
 * A #GtkMenuShell is a container of #GtkMenuItem objects arranged
 * in a list which can be navigated, selected, and activated by the
 * user to perform application functions. A #GtkMenuItem can have a
 * submenu associated with it, allowing for nested hierarchical menus.
37
 *
38
 * # Terminology
39
 *
40
 * A menu item can be “selected”, this means that it is displayed
41 42 43
 * in the prelight state, and if it has a submenu, that submenu
 * will be popped up.
 *
44
 * A menu is “active” when it is visible onscreen and the user
45 46 47 48 49 50 51 52 53 54 55 56
 * is selecting from it. A menubar is not active until the user
 * clicks on one of its menuitems. When a menu is active,
 * passing the mouse over a submenu will pop it up.
 *
 * There is also is a concept of the current menu and a current
 * menu item. The current menu item is the selected menu item
 * that is furthest down in the hierarchy. (Every active menu shell
 * does not necessarily contain a selected menu item, but if
 * it does, then the parent menu shell must also contain
 * a selected menu item.) The current menu is the menu that
 * contains the current menu item. It will always have a GTK
 * grab and receive all key presses.
57
 */
58
#include "config.h"
59

60
#include "gtkbindings.h"
61
#include "gtkkeyhash.h"
62
#include "gtklabel.h"
63
#include "gtkmain.h"
64
#include "gtkmarshalers.h"
65
#include "gtkmenubar.h"
66
#include "gtkmenuitemprivate.h"
67
#include "gtkmenushellprivate.h"
68
#include "gtkmnemonichash.h"
69
#include "gtkrender.h"
70
#include "gtkwindow.h"
71
#include "gtkwindowprivate.h"
72
#include "gtkprivate.h"
73
#include "gtkmain.h"
74
#include "gtkintl.h"
75
#include "gtktypebuiltins.h"
76
#include "gtkmodelmenuitem.h"
77
#include "gtkwidgetprivate.h"
78
#include "gtklabelprivate.h"
79

80
#include "deprecated/gtktearoffmenuitem.h"
81

82
#include "a11y/gtkmenushellaccessible.h"
Elliot Lee's avatar
Elliot Lee committed
83

84

Elliot Lee's avatar
Elliot Lee committed
85
#define MENU_SHELL_TIMEOUT   500
86 87
#define MENU_POPUP_DELAY     225
#define MENU_POPDOWN_DELAY   1000
Elliot Lee's avatar
Elliot Lee committed
88

89 90 91 92 93
#define PACK_DIRECTION(m)                                 \
   (GTK_IS_MENU_BAR (m)                                   \
     ? gtk_menu_bar_get_pack_direction (GTK_MENU_BAR (m)) \
     : GTK_PACK_DIRECTION_LTR)

Elliot Lee's avatar
Elliot Lee committed
94 95
enum {
  DEACTIVATE,
Tim Janik's avatar
Tim Janik committed
96
  SELECTION_DONE,
97 98 99
  MOVE_CURRENT,
  ACTIVATE_CURRENT,
  CANCEL,
100
  CYCLE_FOCUS,
101
  MOVE_SELECTED,
102
  INSERT,
Elliot Lee's avatar
Elliot Lee committed
103 104 105
  LAST_SIGNAL
};

106 107 108 109 110
enum {
  PROP_0,
  PROP_TAKE_FOCUS
};

111

112 113 114 115 116 117 118 119
static void gtk_menu_shell_set_property      (GObject           *object,
                                              guint              prop_id,
                                              const GValue      *value,
                                              GParamSpec        *pspec);
static void gtk_menu_shell_get_property      (GObject           *object,
                                              guint              prop_id,
                                              GValue            *value,
                                              GParamSpec        *pspec);
Elliot Lee's avatar
Elliot Lee committed
120
static void gtk_menu_shell_realize           (GtkWidget         *widget);
121
static void gtk_menu_shell_finalize          (GObject           *object);
122
static void gtk_menu_shell_dispose           (GObject           *object);
Elliot Lee's avatar
Elliot Lee committed
123
static gint gtk_menu_shell_button_press      (GtkWidget         *widget,
124
                                              GdkEventButton    *event);
Elliot Lee's avatar
Elliot Lee committed
125
static gint gtk_menu_shell_button_release    (GtkWidget         *widget,
126 127 128
                                              GdkEventButton    *event);
static gint gtk_menu_shell_key_press         (GtkWidget         *widget,
                                              GdkEventKey       *event);
Elliot Lee's avatar
Elliot Lee committed
129
static gint gtk_menu_shell_enter_notify      (GtkWidget         *widget,
130
                                              GdkEventCrossing  *event);
Elliot Lee's avatar
Elliot Lee committed
131
static gint gtk_menu_shell_leave_notify      (GtkWidget         *widget,
132
                                              GdkEventCrossing  *event);
133
static void gtk_menu_shell_screen_changed    (GtkWidget         *widget,
134
                                              GdkScreen         *previous_screen);
135
static gboolean gtk_menu_shell_grab_broken       (GtkWidget         *widget,
136
                                              GdkEventGrabBroken *event);
Elliot Lee's avatar
Elliot Lee committed
137
static void gtk_menu_shell_add               (GtkContainer      *container,
138
                                              GtkWidget         *widget);
Elliot Lee's avatar
Elliot Lee committed
139
static void gtk_menu_shell_remove            (GtkContainer      *container,
140
                                              GtkWidget         *widget);
141
static void gtk_menu_shell_forall            (GtkContainer      *container,
142 143 144
                                              gboolean           include_internals,
                                              GtkCallback        callback,
                                              gpointer           callback_data);
145
static void gtk_menu_shell_real_insert       (GtkMenuShell *menu_shell,
146 147
                                              GtkWidget    *child,
                                              gint          position);
Elliot Lee's avatar
Elliot Lee committed
148 149
static void gtk_real_menu_shell_deactivate   (GtkMenuShell      *menu_shell);
static gint gtk_menu_shell_is_item           (GtkMenuShell      *menu_shell,
150
                                              GtkWidget         *child);
Owen Taylor's avatar
Owen Taylor committed
151
static GtkWidget *gtk_menu_shell_get_item    (GtkMenuShell      *menu_shell,
152
                                              GdkEvent          *event);
Manish Singh's avatar
Manish Singh committed
153
static GType    gtk_menu_shell_child_type  (GtkContainer      *container);
154
static void gtk_menu_shell_real_select_item  (GtkMenuShell      *menu_shell,
155
                                              GtkWidget         *menu_item);
156
static gboolean gtk_menu_shell_select_submenu_first (GtkMenuShell   *menu_shell); 
Elliot Lee's avatar
Elliot Lee committed
157

158
static void gtk_real_menu_shell_move_current (GtkMenuShell      *menu_shell,
159
                                              GtkMenuDirectionType direction);
160
static void gtk_real_menu_shell_activate_current (GtkMenuShell      *menu_shell,
161
                                                  gboolean           force_hide);
162
static void gtk_real_menu_shell_cancel           (GtkMenuShell      *menu_shell);
163
static void gtk_real_menu_shell_cycle_focus      (GtkMenuShell      *menu_shell,
164
                                                  GtkDirectionType   dir);
Elliot Lee's avatar
Elliot Lee committed
165

166 167
static void     gtk_menu_shell_reset_key_hash    (GtkMenuShell *menu_shell);
static gboolean gtk_menu_shell_activate_mnemonic (GtkMenuShell *menu_shell,
168
                                                  GdkEventKey  *event);
169
static gboolean gtk_menu_shell_real_move_selected (GtkMenuShell  *menu_shell, 
170
                                                   gint           distance);
171

172
static guint menu_shell_signals[LAST_SIGNAL] = { 0 };
Elliot Lee's avatar
Elliot Lee committed
173

174
G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GtkMenuShell, gtk_menu_shell, GTK_TYPE_CONTAINER)
Elliot Lee's avatar
Elliot Lee committed
175 176 177 178

static void
gtk_menu_shell_class_init (GtkMenuShellClass *klass)
{
Manish Singh's avatar
Manish Singh committed
179
  GObjectClass *object_class;
Elliot Lee's avatar
Elliot Lee committed
180 181 182
  GtkWidgetClass *widget_class;
  GtkContainerClass *container_class;

183 184
  GtkBindingSet *binding_set;

Manish Singh's avatar
Manish Singh committed
185
  object_class = (GObjectClass*) klass;
Elliot Lee's avatar
Elliot Lee committed
186 187 188
  widget_class = (GtkWidgetClass*) klass;
  container_class = (GtkContainerClass*) klass;

189 190
  object_class->set_property = gtk_menu_shell_set_property;
  object_class->get_property = gtk_menu_shell_get_property;
191
  object_class->finalize = gtk_menu_shell_finalize;
192
  object_class->dispose = gtk_menu_shell_dispose;
193

194 195 196
  widget_class->realize = gtk_menu_shell_realize;
  widget_class->button_press_event = gtk_menu_shell_button_press;
  widget_class->button_release_event = gtk_menu_shell_button_release;
197
  widget_class->grab_broken_event = gtk_menu_shell_grab_broken;
198 199 200
  widget_class->key_press_event = gtk_menu_shell_key_press;
  widget_class->enter_notify_event = gtk_menu_shell_enter_notify;
  widget_class->leave_notify_event = gtk_menu_shell_leave_notify;
201
  widget_class->screen_changed = gtk_menu_shell_screen_changed;
202 203 204 205 206 207 208 209 210 211 212 213 214 215

  container_class->add = gtk_menu_shell_add;
  container_class->remove = gtk_menu_shell_remove;
  container_class->forall = gtk_menu_shell_forall;
  container_class->child_type = gtk_menu_shell_child_type;

  klass->submenu_placement = GTK_TOP_BOTTOM;
  klass->deactivate = gtk_real_menu_shell_deactivate;
  klass->selection_done = NULL;
  klass->move_current = gtk_real_menu_shell_move_current;
  klass->activate_current = gtk_real_menu_shell_activate_current;
  klass->cancel = gtk_real_menu_shell_cancel;
  klass->select_item = gtk_menu_shell_real_select_item;
  klass->insert = gtk_menu_shell_real_insert;
216
  klass->move_selected = gtk_menu_shell_real_move_selected;
217

218 219 220 221 222 223
  /**
   * GtkMenuShell::deactivate:
   * @menushell: the object which received the signal
   *
   * This signal is emitted when a menu shell is deactivated.
   */
Elliot Lee's avatar
Elliot Lee committed
224
  menu_shell_signals[DEACTIVATE] =
225
    g_signal_new (I_("deactivate"),
226 227 228 229
                  G_OBJECT_CLASS_TYPE (object_class),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (GtkMenuShellClass, deactivate),
                  NULL, NULL,
230
                  NULL,
231
                  G_TYPE_NONE, 0);
232

233 234 235 236 237 238 239
  /**
   * GtkMenuShell::selection-done:
   * @menushell: the object which received the signal
   *
   * This signal is emitted when a selection has been
   * completed within a menu shell.
   */
Tim Janik's avatar
Tim Janik committed
240
  menu_shell_signals[SELECTION_DONE] =
241
    g_signal_new (I_("selection-done"),
242 243 244 245
                  G_OBJECT_CLASS_TYPE (object_class),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (GtkMenuShellClass, selection_done),
                  NULL, NULL,
246
                  NULL,
247
                  G_TYPE_NONE, 0);
248

249 250 251 252 253 254 255 256
  /**
   * GtkMenuShell::move-current:
   * @menushell: the object which received the signal
   * @direction: the direction to move
   *
   * An keybinding signal which moves the current menu item
   * in the direction specified by @direction.
   */
257
  menu_shell_signals[MOVE_CURRENT] =
258
    g_signal_new (I_("move-current"),
259 260 261 262
                  G_OBJECT_CLASS_TYPE (object_class),
                  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
                  G_STRUCT_OFFSET (GtkMenuShellClass, move_current),
                  NULL, NULL,
263
                  NULL,
264 265
                  G_TYPE_NONE, 1,
                  GTK_TYPE_MENU_DIRECTION_TYPE);
266

267 268 269 270 271 272 273 274
  /**
   * GtkMenuShell::activate-current:
   * @menushell: the object which received the signal
   * @force_hide: if %TRUE, hide the menu after activating the menu item
   *
   * An action signal that activates the current menu item within
   * the menu shell.
   */
275
  menu_shell_signals[ACTIVATE_CURRENT] =
276
    g_signal_new (I_("activate-current"),
277 278 279 280
                  G_OBJECT_CLASS_TYPE (object_class),
                  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
                  G_STRUCT_OFFSET (GtkMenuShellClass, activate_current),
                  NULL, NULL,
281
                  NULL,
282 283
                  G_TYPE_NONE, 1,
                  G_TYPE_BOOLEAN);
284

285 286 287 288 289 290 291
  /**
   * GtkMenuShell::cancel:
   * @menushell: the object which received the signal
   *
   * An action signal which cancels the selection within the menu shell.
   * Causes the #GtkMenuShell::selection-done signal to be emitted.
   */
292
  menu_shell_signals[CANCEL] =
293
    g_signal_new (I_("cancel"),
294 295 296 297
                  G_OBJECT_CLASS_TYPE (object_class),
                  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
                  G_STRUCT_OFFSET (GtkMenuShellClass, cancel),
                  NULL, NULL,
298
                  NULL,
299
                  G_TYPE_NONE, 0);
300

301 302 303 304 305 306 307 308
  /**
   * GtkMenuShell::cycle-focus:
   * @menushell: the object which received the signal
   * @direction: the direction to cycle in
   *
   * A keybinding signal which moves the focus in the
   * given @direction.
   */
309
  menu_shell_signals[CYCLE_FOCUS] =
310
    g_signal_new_class_handler (I_("cycle-focus"),
311 312 313 314
                                G_OBJECT_CLASS_TYPE (object_class),
                                G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
                                G_CALLBACK (gtk_real_menu_shell_cycle_focus),
                                NULL, NULL,
315
                                NULL,
316 317 318
                                G_TYPE_NONE, 1,
                                GTK_TYPE_DIRECTION_TYPE);

Matthias Clasen's avatar
Matthias Clasen committed
319 320 321 322 323 324
  /**
   * GtkMenuShell::move-selected:
   * @menu_shell: the object on which the signal is emitted
   * @distance: +1 to move to the next item, -1 to move to the previous
   *
   * The ::move-selected signal is emitted to move the selection to
325 326
   * another item.
   *
Matthias Clasen's avatar
Matthias Clasen committed
327 328 329
   * Returns: %TRUE to stop the signal emission, %FALSE to continue
   *
   * Since: 2.12
330
   */
331
  menu_shell_signals[MOVE_SELECTED] =
332
    g_signal_new (I_("move-selected"),
333 334 335 336 337 338 339
                  G_OBJECT_CLASS_TYPE (object_class),
                  G_SIGNAL_RUN_LAST,
                  G_STRUCT_OFFSET (GtkMenuShellClass, move_selected),
                  _gtk_boolean_handled_accumulator, NULL,
                  _gtk_marshal_BOOLEAN__INT,
                  G_TYPE_BOOLEAN, 1,
                  G_TYPE_INT);
340

341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363
  /**
   * GtkMenuShell::insert:
   * @menu_shell: the object on which the signal is emitted
   * @child: the #GtkMenuItem that is being inserted
   * @position: the position at which the insert occurs
   *
   * The ::insert signal is emitted when a new #GtkMenuItem is added to
   * a #GtkMenuShell.  A separate signal is used instead of
   * GtkContainer::add because of the need for an additional position
   * parameter.
   *
   * The inverse of this signal is the GtkContainer::removed signal.
   *
   * Since: 3.2
   **/
  menu_shell_signals[INSERT] =
    g_signal_new (I_("insert"),
                  G_OBJECT_CLASS_TYPE (object_class),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (GtkMenuShellClass, insert),
                  NULL, NULL,
                  _gtk_marshal_VOID__OBJECT_INT,
                  G_TYPE_NONE, 2, GTK_TYPE_WIDGET, G_TYPE_INT);
364 365 366
  g_signal_set_va_marshaller (menu_shell_signals[INSERT],
                              G_OBJECT_CLASS_TYPE (object_class),
                              _gtk_marshal_VOID__OBJECT_INTv);
367 368


369 370
  binding_set = gtk_binding_set_by_class (klass);
  gtk_binding_entry_add_signal (binding_set,
371 372
                                GDK_KEY_Escape, 0,
                                "cancel", 0);
373
  gtk_binding_entry_add_signal (binding_set,
374 375 376 377
                                GDK_KEY_Return, 0,
                                "activate-current", 1,
                                G_TYPE_BOOLEAN,
                                TRUE);
378
  gtk_binding_entry_add_signal (binding_set,
379 380 381 382
                                GDK_KEY_ISO_Enter, 0,
                                "activate-current", 1,
                                G_TYPE_BOOLEAN,
                                TRUE);
383
  gtk_binding_entry_add_signal (binding_set,
384 385 386 387
                                GDK_KEY_KP_Enter, 0,
                                "activate-current", 1,
                                G_TYPE_BOOLEAN,
                                TRUE);
388
  gtk_binding_entry_add_signal (binding_set,
389 390 391 392
                                GDK_KEY_space, 0,
                                "activate-current", 1,
                                G_TYPE_BOOLEAN,
                                FALSE);
393
  gtk_binding_entry_add_signal (binding_set,
394 395 396 397
                                GDK_KEY_KP_Space, 0,
                                "activate-current", 1,
                                G_TYPE_BOOLEAN,
                                FALSE);
398
  gtk_binding_entry_add_signal (binding_set,
399 400
                                GDK_KEY_F10, 0,
                                "cycle-focus", 1,
401 402
                                GTK_TYPE_DIRECTION_TYPE, GTK_DIR_TAB_FORWARD);
  gtk_binding_entry_add_signal (binding_set,
403 404
                                GDK_KEY_F10, GDK_SHIFT_MASK,
                                "cycle-focus", 1,
405
                                GTK_TYPE_DIRECTION_TYPE, GTK_DIR_TAB_BACKWARD);
406

407 408 409 410 411 412 413 414 415 416 417 418
  /**
   * GtkMenuShell:take-focus:
   *
   * A boolean that determines whether the menu and its submenus grab the
   * keyboard focus. See gtk_menu_shell_set_take_focus() and
   * gtk_menu_shell_get_take_focus().
   *
   * Since: 2.8
   **/
  g_object_class_install_property (object_class,
                                   PROP_TAKE_FOCUS,
                                   g_param_spec_boolean ("take-focus",
419 420 421
                                                         P_("Take Focus"),
                                                         P_("A boolean that determines whether the menu grabs the keyboard focus"),
                                                         TRUE,
422
                                                         GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
423

424
  gtk_widget_class_set_accessible_type (widget_class, GTK_TYPE_MENU_SHELL_ACCESSIBLE);
Elliot Lee's avatar
Elliot Lee committed
425 426
}

Manish Singh's avatar
Manish Singh committed
427
static GType
428
gtk_menu_shell_child_type (GtkContainer *container)
429 430 431 432
{
  return GTK_TYPE_MENU_ITEM;
}

Elliot Lee's avatar
Elliot Lee committed
433 434 435
static void
gtk_menu_shell_init (GtkMenuShell *menu_shell)
{
436 437
  menu_shell->priv = gtk_menu_shell_get_instance_private (menu_shell);
  menu_shell->priv->take_focus = TRUE;
438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459
}

static void
gtk_menu_shell_set_property (GObject      *object,
                             guint         prop_id,
                             const GValue *value,
                             GParamSpec   *pspec)
{
  GtkMenuShell *menu_shell = GTK_MENU_SHELL (object);

  switch (prop_id)
    {
    case PROP_TAKE_FOCUS:
      gtk_menu_shell_set_take_focus (menu_shell, g_value_get_boolean (value));
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}

static void
460 461 462 463
gtk_menu_shell_get_property (GObject    *object,
                             guint       prop_id,
                             GValue     *value,
                             GParamSpec *pspec)
464 465 466 467 468 469 470 471 472 473 474 475
{
  GtkMenuShell *menu_shell = GTK_MENU_SHELL (object);

  switch (prop_id)
    {
    case PROP_TAKE_FOCUS:
      g_value_set_boolean (value, gtk_menu_shell_get_take_focus (menu_shell));
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
Elliot Lee's avatar
Elliot Lee committed
476 477
}

478 479 480 481
static void
gtk_menu_shell_finalize (GObject *object)
{
  GtkMenuShell *menu_shell = GTK_MENU_SHELL (object);
482
  GtkMenuShellPrivate *priv = menu_shell->priv;
483 484 485 486 487 488

  if (priv->mnemonic_hash)
    _gtk_mnemonic_hash_free (priv->mnemonic_hash);
  if (priv->key_hash)
    _gtk_key_hash_free (priv->key_hash);

Matthias Clasen's avatar
Matthias Clasen committed
489
  G_OBJECT_CLASS (gtk_menu_shell_parent_class)->finalize (object);
490 491 492
}


493 494
static void
gtk_menu_shell_dispose (GObject *object)
495
{
496 497 498 499
  GtkMenuShell *menu_shell = GTK_MENU_SHELL (object);

  g_clear_pointer (&menu_shell->priv->tracker, gtk_menu_tracker_free);
  gtk_menu_shell_deactivate (menu_shell);
500 501 502 503

  G_OBJECT_CLASS (gtk_menu_shell_parent_class)->dispose (object);
}

504 505 506
/**
 * gtk_menu_shell_append:
 * @menu_shell: a #GtkMenuShell
507
 * @child: (type Gtk.MenuItem): The #GtkMenuItem to add
508 509 510 511
 *
 * Adds a new #GtkMenuItem to the end of the menu shell's
 * item list.
 */
Elliot Lee's avatar
Elliot Lee committed
512 513
void
gtk_menu_shell_append (GtkMenuShell *menu_shell,
514
                       GtkWidget    *child)
Elliot Lee's avatar
Elliot Lee committed
515 516 517 518
{
  gtk_menu_shell_insert (menu_shell, child, -1);
}

519 520 521 522 523 524 525 526
/**
 * gtk_menu_shell_prepend:
 * @menu_shell: a #GtkMenuShell
 * @child: The #GtkMenuItem to add
 *
 * Adds a new #GtkMenuItem to the beginning of the menu shell's
 * item list.
 */
Elliot Lee's avatar
Elliot Lee committed
527 528
void
gtk_menu_shell_prepend (GtkMenuShell *menu_shell,
529
                        GtkWidget    *child)
Elliot Lee's avatar
Elliot Lee committed
530 531 532 533
{
  gtk_menu_shell_insert (menu_shell, child, 0);
}

534 535 536 537 538 539 540
/**
 * gtk_menu_shell_insert:
 * @menu_shell: a #GtkMenuShell
 * @child: The #GtkMenuItem to add
 * @position: The position in the item list where @child
 *     is added. Positions are numbered from 0 to n-1
 *
541
 * Adds a new #GtkMenuItem to the menu shell’s item list
542 543
 * at the position indicated by @position.
 */
Elliot Lee's avatar
Elliot Lee committed
544 545
void
gtk_menu_shell_insert (GtkMenuShell *menu_shell,
546 547
                       GtkWidget    *child,
                       gint          position)
Elliot Lee's avatar
Elliot Lee committed
548 549 550 551
{
  g_return_if_fail (GTK_IS_MENU_SHELL (menu_shell));
  g_return_if_fail (GTK_IS_MENU_ITEM (child));

552
  g_signal_emit (menu_shell, menu_shell_signals[INSERT], 0, child, position);
553 554 555 556
}

static void
gtk_menu_shell_real_insert (GtkMenuShell *menu_shell,
557 558
                            GtkWidget    *child,
                            gint          position)
559
{
560 561 562
  GtkMenuShellPrivate *priv = menu_shell->priv;

  priv->children = g_list_insert (priv->children, child, position);
563

Elliot Lee's avatar
Elliot Lee committed
564 565 566
  gtk_widget_set_parent (child, GTK_WIDGET (menu_shell));
}

567 568 569 570 571 572 573 574 575
/**
 * gtk_menu_shell_deactivate:
 * @menu_shell: a #GtkMenuShell
 *
 * Deactivates the menu shell.
 *
 * Typically this results in the menu shell being erased
 * from the screen.
 */
Elliot Lee's avatar
Elliot Lee committed
576 577 578 579 580
void
gtk_menu_shell_deactivate (GtkMenuShell *menu_shell)
{
  g_return_if_fail (GTK_IS_MENU_SHELL (menu_shell));

581 582
  if (menu_shell->priv->active)
    g_signal_emit (menu_shell, menu_shell_signals[DEACTIVATE], 0);
Elliot Lee's avatar
Elliot Lee committed
583 584 585 586 587
}

static void
gtk_menu_shell_realize (GtkWidget *widget)
{
588 589
  GtkAllocation allocation;
  GdkWindow *window;
Elliot Lee's avatar
Elliot Lee committed
590 591 592
  GdkWindowAttr attributes;
  gint attributes_mask;

593
  gtk_widget_set_realized (widget, TRUE);
Elliot Lee's avatar
Elliot Lee committed
594

595 596 597 598 599 600
  gtk_widget_get_allocation (widget, &allocation);

  attributes.x = allocation.x;
  attributes.y = allocation.y;
  attributes.width = allocation.width;
  attributes.height = allocation.height;
Elliot Lee's avatar
Elliot Lee committed
601 602 603 604
  attributes.window_type = GDK_WINDOW_CHILD;
  attributes.wclass = GDK_INPUT_OUTPUT;
  attributes.visual = gtk_widget_get_visual (widget);
  attributes.event_mask = gtk_widget_get_events (widget);
605
  attributes.event_mask |= (GDK_BUTTON_PRESS_MASK |
606
                            GDK_BUTTON_RELEASE_MASK |
607
                            GDK_POINTER_MOTION_MASK |
608 609 610
                            GDK_KEY_PRESS_MASK |
                            GDK_ENTER_NOTIFY_MASK |
                            GDK_LEAVE_NOTIFY_MASK);
Elliot Lee's avatar
Elliot Lee committed
611

612
  attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL;
Elliot Lee's avatar
Elliot Lee committed
613

614 615 616
  window = gdk_window_new (gtk_widget_get_parent_window (widget),
                           &attributes, attributes_mask);
  gtk_widget_set_window (widget, window);
617
  gtk_widget_register_window (widget, window);
618 619
}

Matthias Clasen's avatar
Matthias Clasen committed
620 621
static void
gtk_menu_shell_activate (GtkMenuShell *menu_shell)
622
{
623 624 625
  GtkMenuShellPrivate *priv = menu_shell->priv;

  if (!priv->active)
626
    {
627 628 629 630 631
      GdkDevice *device;

      device = gtk_get_current_event_device ();

      _gtk_menu_shell_set_grab_device (menu_shell, device);
632
      gtk_grab_add (GTK_WIDGET (menu_shell));
633

634 635
      priv->have_grab = TRUE;
      priv->active = TRUE;
636 637 638
    }
}

Elliot Lee's avatar
Elliot Lee committed
639 640
static gint
gtk_menu_shell_button_press (GtkWidget      *widget,
641
                             GdkEventButton *event)
Elliot Lee's avatar
Elliot Lee committed
642 643
{
  GtkMenuShell *menu_shell;
644
  GtkMenuShellPrivate *priv;
Elliot Lee's avatar
Elliot Lee committed
645
  GtkWidget *menu_item;
646
  GtkWidget *parent;
Elliot Lee's avatar
Elliot Lee committed
647 648 649 650 651

  if (event->type != GDK_BUTTON_PRESS)
    return FALSE;

  menu_shell = GTK_MENU_SHELL (widget);
652
  priv = menu_shell->priv;
Elliot Lee's avatar
Elliot Lee committed
653

654 655
  if (priv->parent_menu_shell)
    return gtk_widget_event (priv->parent_menu_shell, (GdkEvent*) event);
656 657 658

  menu_item = gtk_menu_shell_get_item (menu_shell, (GdkEvent *)event);

659
  if (menu_item && _gtk_menu_item_is_selectable (menu_item))
Elliot Lee's avatar
Elliot Lee committed
660
    {
661 662
      parent = gtk_widget_get_parent (menu_item);

663
      if (menu_item != GTK_MENU_SHELL (parent)->priv->active_menu_item)
664
        {
665 666 667 668 669 670 671 672 673
          /*  select the menu item *before* activating the shell, so submenus
           *  which might be open are closed the friendly way. If we activate
           *  (and thus grab) this menu shell first, we might get grab_broken
           *  events which will close the entire menu hierarchy. Selecting the
           *  menu item also fixes up the state as if enter_notify() would
           *  have run before (which normally selects the item).
           */
          if (GTK_MENU_SHELL_GET_CLASS (parent)->submenu_placement != GTK_TOP_BOTTOM)
            gtk_menu_shell_select_item (GTK_MENU_SHELL (parent), menu_item);
674
        }
Elliot Lee's avatar
Elliot Lee committed
675
    }
676

677
  if (!priv->active || !priv->button)
Elliot Lee's avatar
Elliot Lee committed
678
    {
679
      gboolean initially_active = priv->active;
680

681
      priv->button = event->button;
682

683
      if (menu_item)
684
        {
685
          if (_gtk_menu_item_is_selectable (menu_item) &&
686
              gtk_widget_get_parent (menu_item) == widget &&
687
              menu_item != priv->active_menu_item)
688
            {
Matthias Clasen's avatar
Matthias Clasen committed
689
              gtk_menu_shell_activate (menu_shell);
690
              priv->button = event->button;
691 692 693

              if (GTK_MENU_SHELL_GET_CLASS (menu_shell)->submenu_placement == GTK_TOP_BOTTOM)
                {
694
                  priv->activate_time = event->time;
695 696 697 698 699 700 701 702
                  gtk_menu_shell_select_item (menu_shell, menu_item);
                }
            }
        }
      else
        {
          if (!initially_active)
            {
703 704
              gtk_menu_shell_deactivate (menu_shell);
              return FALSE;
705 706
            }
        }
Elliot Lee's avatar
Elliot Lee committed
707 708 709 710 711
    }
  else
    {
      widget = gtk_get_event_widget ((GdkEvent*) event);
      if (widget == GTK_WIDGET (menu_shell))
712 713 714 715
        {
          gtk_menu_shell_deactivate (menu_shell);
          g_signal_emit (menu_shell, menu_shell_signals[SELECTION_DONE], 0);
        }
Elliot Lee's avatar
Elliot Lee committed
716 717
    }

718 719 720 721
  if (menu_item &&
      _gtk_menu_item_is_selectable (menu_item) &&
      GTK_MENU_ITEM (menu_item)->priv->submenu != NULL &&
      !gtk_widget_get_visible (GTK_MENU_ITEM (menu_item)->priv->submenu))
722 723 724 725 726
    {
      _gtk_menu_item_popup_submenu (menu_item, FALSE);
      priv->activated_submenu = TRUE;
    }

Elliot Lee's avatar
Elliot Lee committed
727 728 729
  return TRUE;
}

730 731
static gboolean
gtk_menu_shell_grab_broken (GtkWidget          *widget,
732
                            GdkEventGrabBroken *event)
733
{
734
  GtkMenuShell *menu_shell = GTK_MENU_SHELL (widget);
735
  GtkMenuShellPrivate *priv = menu_shell->priv;
736

737
  if (priv->have_xgrab && event->grab_window == NULL)
738
    {
739
      /* Unset the active menu item so gtk_menu_popdown() doesn't see it. */
740
      gtk_menu_shell_deselect (menu_shell);
741 742 743 744 745 746 747
      gtk_menu_shell_deactivate (menu_shell);
      g_signal_emit (menu_shell, menu_shell_signals[SELECTION_DONE], 0);
    }

  return TRUE;
}

Elliot Lee's avatar
Elliot Lee committed
748 749
static gint
gtk_menu_shell_button_release (GtkWidget      *widget,
750
                               GdkEventButton *event)
Elliot Lee's avatar
Elliot Lee committed
751
{
752
  GtkMenuShell *menu_shell = GTK_MENU_SHELL (widget);
753
  GtkMenuShellPrivate *priv = menu_shell->priv;
Elliot Lee's avatar
Elliot Lee committed
754

755 756 757 758 759 760 761 762 763 764 765 766
  if (priv->parent_menu_shell &&
      (event->time - GTK_MENU_SHELL (priv->parent_menu_shell)->priv->activate_time) < MENU_SHELL_TIMEOUT)
    {
      /* The button-press originated in the parent menu bar and we are
       * a pop-up menu. It was a quick press-and-release so we don't want
       * to activate an item but we leave the popup in place instead.
       * https://bugzilla.gnome.org/show_bug.cgi?id=703069
       */
      GTK_MENU_SHELL (priv->parent_menu_shell)->priv->activate_time = 0;
      return TRUE;
    }

767
  if (priv->active)
Elliot Lee's avatar
Elliot Lee committed
768
    {
769 770 771
      GtkWidget *menu_item;
      gboolean   deactivate = TRUE;

772 773 774 775 776 777
      if (priv->button && (event->button != priv->button))
        {
          priv->button = 0;
          if (priv->parent_menu_shell)
            return gtk_widget_event (priv->parent_menu_shell, (GdkEvent*) event);
        }
778

779
      priv->button = 0;
Owen Taylor's avatar
Owen Taylor committed
780
      menu_item = gtk_menu_shell_get_item (menu_shell, (GdkEvent*) event);
781

782
      if ((event->time - priv->activate_time) > MENU_SHELL_TIMEOUT)
783
        {
784
          if (menu_item && (priv->active_menu_item == menu_item) &&
785 786
              _gtk_menu_item_is_selectable (menu_item))
            {
787
              GtkWidget *submenu = GTK_MENU_ITEM (menu_item)->priv->submenu;
788

789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817
              if (submenu == NULL)
                {
                  gtk_menu_shell_activate_item (menu_shell, menu_item, TRUE);
                  deactivate = FALSE;
                }
              else if (GTK_MENU_SHELL_GET_CLASS (menu_shell)->submenu_placement != GTK_TOP_BOTTOM ||
                       priv->activated_submenu)
                {
                  GTimeVal *popup_time;
                  gint64 usec_since_popup = 0;

                  popup_time = g_object_get_data (G_OBJECT (submenu),
                                                  "gtk-menu-exact-popup-time");

                  if (popup_time)
                    {
                      GTimeVal current_time;

                      g_get_current_time (&current_time);

                      usec_since_popup = ((gint64) current_time.tv_sec * 1000 * 1000 +
                                          (gint64) current_time.tv_usec -
                                          (gint64) popup_time->tv_sec * 1000 * 1000 -
                                          (gint64) popup_time->tv_usec);

                      g_object_set_data (G_OBJECT (submenu),
                                         "gtk-menu-exact-popup-time", NULL);
                    }

818
                  /* Only close the submenu on click if we opened the
819
                   * menu explicitly (usec_since_popup == 0) or
820 821
                   * enough time has passed since it was opened by
                   * GtkMenuItem's timeout (usec_since_popup > delay).
822 823 824
                   */
                  if (!priv->activated_submenu &&
                      (usec_since_popup == 0 ||
825
                       usec_since_popup > MENU_POPDOWN_DELAY * 1000))
826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842
                    {
                      _gtk_menu_item_popdown_submenu (menu_item);
                    }
                  else
                    {
                      gtk_menu_item_select (GTK_MENU_ITEM (menu_item));
                    }

                  deactivate = FALSE;
                }
            }
          else if (menu_item &&
                   !_gtk_menu_item_is_selectable (menu_item) &&
                   GTK_MENU_SHELL_GET_CLASS (menu_shell)->submenu_placement != GTK_TOP_BOTTOM)
            {
              deactivate = FALSE;
            }
843
          else if (priv->parent_menu_shell)
844
            {
845 846
              priv->active = TRUE;
              gtk_widget_event (priv->parent_menu_shell, (GdkEvent*) event);
847 848 849
              deactivate = FALSE;
            }

850 851 852
          /* If we ended up on an item with a submenu, leave the menu up. */
          if (menu_item &&
              (priv->active_menu_item == menu_item) &&
853 854 855 856 857
              GTK_MENU_SHELL_GET_CLASS (menu_shell)->submenu_placement != GTK_TOP_BOTTOM)
            {
              deactivate = FALSE;
            }
        }
858
      else /* a very fast press-release */
859 860
        {
          /* We only ever want to prevent deactivation on the first
Owen Taylor's avatar
Owen Taylor committed
861
           * press/release. Setting the time to zero is a bit of a
862 863 864 865 866
           * hack, since we could be being triggered in the first
           * few fractions of a second after a server time wraparound.
           * the chances of that happening are ~1/10^6, without
           * serious harm if we lose.
           */
867
          priv->activate_time = 0;
868 869 870
          deactivate = FALSE;
        }

Elliot Lee's avatar
Elliot Lee committed
871
      if (deactivate)
872 873 874 875 876 877
        {
          gtk_menu_shell_deactivate (menu_shell);
          g_signal_emit (menu_shell, menu_shell_signals[SELECTION_DONE], 0);
        }

      priv->activated_submenu = FALSE;
Elliot Lee's avatar
Elliot Lee committed
878 879 880 881 882
    }

  return TRUE;
}

883 884 885 886
void
_gtk_menu_shell_set_keyboard_mode (GtkMenuShell *menu_shell,
                                   gboolean      keyboard_mode)
{
887
  menu_shell->priv->keyboard_mode = keyboard_mode;
888 889 890 891 892
}

gboolean
_gtk_menu_shell_get_keyboard_mode (GtkMenuShell *menu_shell)
{
893
  return menu_shell->priv->keyboard_mode;
894 895 896 897 898 899 900 901 902 903 904 905 906
}

void
_gtk_menu_shell_update_mnemonics (GtkMenuShell *menu_shell)
{
  GtkMenuShell *target;
  gboolean found;
  gboolean mnemonics_visible;

  target = menu_shell;
  found = FALSE;
  while (target)
    {
907
      GtkMenuShellPrivate *priv = target->priv;
908 909
      GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (target));

910 911 912 913 914 915
      /* The idea with keyboard mode is that once you start using
       * the keyboard to navigate the menus, we show mnemonics
       * until the menu navigation is over. To that end, we spread
       * the keyboard mode upwards in the menu hierarchy here.
       * Also see gtk_menu_popup, where we inherit it downwards.
       */
916 917
      if (menu_shell->priv->keyboard_mode)
        target->priv->keyboard_mode = TRUE;
918 919 920 921 922 923 924 925

      /* While navigating menus, the first parent menu with an active
       * item is the one where mnemonics are effective, as can be seen
       * in gtk_menu_shell_key_press below.
       * We also show mnemonics in context menus. The grab condition is
       * necessary to ensure we remove underlines from menu bars when
       * dismissing menus.
       */
926 927
      mnemonics_visible = target->priv->keyboard_mode &&
                          (((target->priv->active_menu_item || priv->in_unselectable_item) && !found) ||
928
                           (target == menu_shell &&
929
                            !target->priv->parent_menu_shell &&
930
                            gtk_widget_has_grab (GTK_WIDGET (target))));
931 932 933 934 935

      /* While menus are up, only show underlines inside the menubar,
       * not in the entire window.
       */
      if (GTK_IS_MENU_BAR (target))
936
        {
937
          gtk_window_set_mnemonics_visible (GTK_WINDOW (toplevel), FALSE);
938 939 940
          _gtk_label_mnemonics_visible_apply_recursively (GTK_WIDGET (target),
                                                          mnemonics_visible);
        }
941
      else
942
        gtk_window_set_mnemonics_visible (GTK_WINDOW (toplevel), mnemonics_visible);
943

944
      if (target->priv->active_menu_item || priv->in_unselectable_item)
945 946
        found = TRUE;

947
      target = GTK_MENU_SHELL (target->priv->parent_menu_shell);
948 949 950
    }
}

951
static gint
952
gtk_menu_shell_key_press (GtkWidget   *widget,
953
                          GdkEventKey *event)
954
{
955
  GtkMenuShell *menu_shell = GTK_MENU_SHELL (widget);
956
  GtkMenuShellPrivate *priv = menu_shell->priv;
957
  gboolean enable_mnemonics;
958

959
  priv->keyboard_mode = TRUE;
960

961 962 963
  if (!(priv->active_menu_item || priv->in_unselectable_item) &&
      priv->parent_menu_shell)
    return gtk_widget_event (priv->parent_menu_shell, (GdkEvent *)event);
964

965
  if (gtk_bindings_activate_event (G_OBJECT (widget), event))
966 967
    return TRUE;

968 969 970 971 972 973 974 975
  g_object_get (gtk_widget_get_settings (widget),
                "gtk-enable-mnemonics", &enable_mnemonics,
                NULL);

  if (enable_mnemonics)
    return gtk_menu_shell_activate_mnemonic (menu_shell, event);

  return FALSE;
976 977
}

Elliot Lee's avatar
Elliot Lee committed
978 979
static gint
gtk_menu_shell_enter_notify (GtkWidget        *widget,
980
                             GdkEventCrossing *event)
Elliot Lee's avatar
Elliot Lee committed
981
{
982
  GtkMenuShell *menu_shell = GTK_MENU_SHELL (widget);
983
  GtkMenuShellPrivate *priv = menu_shell->priv;
984

985 986 987 988 989
  if (event->mode == GDK_CROSSING_GTK_GRAB ||
      event->mode == GDK_CROSSING_GTK_UNGRAB ||
      event->mode == GDK_CROSSING_STATE_CHANGED)
    return TRUE;

990
  if (priv->active)
Elliot Lee's avatar
Elliot Lee committed
991
    {
992
      GtkWidget *menu_item;
993
      GtkWidget *parent;
994

Elliot Lee's avatar
Elliot Lee committed
995 996
      menu_item = gtk_get_event_widget ((GdkEvent*) event);

997 998 999 1000 1001 1002 1003 1004 1005
      if (!menu_item)
        return TRUE;

      if (GTK_IS_MENU_ITEM (menu_item) &&
          !_gtk_menu_item_is_selectable (menu_item))
        {
          priv->in_unselectable_item = TRUE;
          return TRUE;
        }
1006

1007 1008
      parent = gtk_widget_get_parent (menu_item);
      if (parent == widget &&
1009 1010 1011 1012
          GTK_IS_MENU_ITEM (menu_item))
        {
          if (priv->ignore_enter)
            return TRUE;
1013

1014
          if (event->detail != GDK_NOTIFY_INFERIOR)
1015
            {
1016
              if ((gtk_widget_get_state_flags (menu_item) & GTK_STATE_FLAG_PRELIGHT) == 0)
1017
                gtk_menu_shell_select_item (menu_shell, menu_item);
1018 1019 1020 1021 1022 1023 1024 1025 1026

              /* If any mouse button is down, and there is a submenu
               * that is not yet visible, activate it. It's sufficient
               * to check for any button's mask (not only the one
               * matching menu_shell->button), because there is no
               * situation a mouse button could be pressed while
               * entering a menu item where we wouldn't want to show
               * its submenu.
               */
1027
              if ((event->state & (GDK_BUTTON1_MASK|GDK_BUTTON2_MASK|GDK_BUTTON3_MASK)) &&
1028
                  GTK_MENU_ITEM (menu_item)->priv->submenu != NULL)
1029
                {
1030
                  GTK_MENU_SHELL (parent)->priv->activated_submenu = TRUE;
1031

1032
                  if (!gtk_widget_get_visible (GTK_MENU_ITEM (menu_item)->priv->submenu))
1033
                    {
1034
                      GdkDevice *source_device;
1035

1036
                      source_device = gdk_event_get_source_device ((GdkEvent *) event);
1037

1038
                      if (gdk_device_get_source (source_device) == GDK_SOURCE_TOUCHSCREEN)
1039 1040
                        _gtk_menu_item_popup_submenu (menu_item, TRUE);
                    }
1041
                }
1042 1043 1044 1045 1046 1047
            }
        }
      else if (priv->parent_menu_shell)
        {
          gtk_widget_event (priv->parent_menu_shell, (GdkEvent*) event);
        }
Elliot Lee's avatar
Elliot Lee committed
1048 1049 1050 1051 1052 1053 1054
    }

  return TRUE;
}

static gint
gtk_menu_shell_leave_notify (GtkWidget        *widget,
1055
                             GdkEventCrossing *event)
Elliot Lee's avatar
Elliot Lee committed
1056
{
1057
  if (event->mode == GDK_CROSSING_GTK_GRAB ||
1058
      event->mode == GDK_CROSSING_GTK_UNGRAB ||
1059 1060 1061
      event->mode == GDK_CROSSING_STATE_CHANGED)
    return TRUE;

1062
  if (gtk_widget_get_visible (widget))
Elliot Lee's avatar
Elliot Lee committed
1063
    {
1064
      GtkMenuShell *menu_shell = GTK_MENU_SHELL (widget);
1065
      GtkMenuShellPrivate *priv = menu_shell->priv;
1066 1067
      GtkWidget *event_widget = gtk_get_event_widget ((GdkEvent*) event);
      GtkMenuItem *menu_item;
Elliot Lee's avatar
Elliot Lee committed
1068

1069
      if (!event_widget || !GTK_IS_MENU_ITEM (event_widget))
1070
        return TRUE;
Elliot Lee's avatar
Elliot Lee committed
1071 1072 1073

      menu_item = GTK_MENU_ITEM (event_widget);

1074
      if (!_gtk_menu_item_is_selectable (event_widget))
1075 1076 1077 1078
        {
          priv->in_unselectable_item = TRUE;
          return TRUE;
        }
Tim Janik's avatar
Tim Janik committed
1079

1080
      if ((priv->active_menu_item == event_widget) &&
1081
          (menu_item->priv->submenu == NULL))
1082 1083
        {
          if ((event->detail != GDK_NOTIFY_INFERIOR) &&
1084
              (gtk_widget_get_state_flags (GTK_WIDGET (menu_item)) & GTK_STATE_FLAG_PRELIGHT) != 0)
1085 1086 1087 1088 1089 1090 1091 1092
            {
              gtk_menu_shell_deselect (menu_shell);
            }
        }
      else if (priv->parent_menu_shell)
        {
          gtk_widget_event (priv->parent_menu_shell, (GdkEvent*) event);
        }
Elliot Lee's avatar
Elliot Lee committed
1093 1094 1095 1096 1097
    }

  return TRUE;
}

1098 1099
static void
gtk_menu_shell_screen_changed (GtkWidget *widget,
1100
                               GdkScreen *previous_screen)
1101 1102 1103 1104
{
  gtk_menu_shell_reset_key_hash (GTK_MENU_SHELL (widget));
}

Elliot Lee's avatar
Elliot Lee committed
1105 1106
static void
gtk_menu_shell_add (GtkContainer *container,
1107
                    GtkWidget    *widget)
Elliot Lee's avatar
Elliot Lee committed
1108 1109 1110 1111 1112 1113
{
  gtk_menu_shell_append (GTK_MENU_SHELL (container), widget);
}

static void
gtk_menu_shell_remove (GtkContainer *container,
1114
                       GtkWidget    *widget)
Elliot Lee's avatar
Elliot Lee committed
1115
{
1116
  GtkMenuShell *menu_shell = GTK_MENU_SHELL (container);
1117
  GtkMenuShellPrivate *priv = menu_shell->priv;
1118
  gint was_visible;
1119

1120
  was_visible = gtk_widget_get_visible (widget);
1121 1122 1123
  priv->children = g_list_remove (priv->children, widget);

  if (widget == priv->active_menu_item)
1124
    {
1125 1126
      g_signal_emit_by_name (priv->active_menu_item, "deselect");
      priv->active_menu_item = NULL;
1127 1128
    }

1129
  gtk_widget_unparent (widget);
1130 1131

  /* Queue resize regardless of gtk_widget_get_visible (container),
1132 1133 1134
   * since that's what is needed by toplevels.
   */
  if (was_visible)
Elliot Lee's avatar
Elliot Lee committed
1135 1136 1137 1138
    gtk_widget_queue_resize (GTK_WIDGET (container));
}

static void
1139
gtk_menu_shell_forall (GtkContainer *container,
1140 1141 1142
                       gboolean      include_internals,
                       GtkCallback   callback,
                       gpointer      callback_data)
Elliot Lee's avatar
Elliot Lee committed
1143
{
1144
  GtkMenuShell *menu_shell = GTK_MENU_SHELL (container);
Elliot Lee's avatar
Elliot Lee committed
1145 1146 1147
  GtkWidget *child;
  GList *children;

1148
  children = menu_shell->priv->children;
Elliot Lee's avatar
Elliot Lee committed
1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161
  while (children)
    {
      child = children->data;
      children = children->next;

      (* callback) (child, callback_data);
    }
}


static void
gtk_real_menu_shell_deactivate (GtkMenuShell *menu_shell)
{
1162 1163 1164
  GtkMenuShellPrivate *priv = menu_shell->priv;

  if (priv->active)
Elliot Lee's avatar
Elliot Lee committed
1165
    {
1166 1167 1168
      priv->button = 0;
      priv->active = FALSE;
      priv->activate_time = 0;
Elliot Lee's avatar
Elliot Lee committed
1169

1170 1171 1172 1173 1174
      if (priv->active_menu_item)
        {
          gtk_menu_item_deselect (GTK_MENU_ITEM (priv->active_menu_item));
          priv->active_menu_item = NULL;
        }
Elliot Lee's avatar
Elliot Lee committed
1175

1176 1177 1178
      if (priv->have_grab)
        {
          priv->have_grab = FALSE;
1179
          gtk_grab_remove (GTK_WIDGET (menu_shell));
1180 1181 1182
        }
      if (priv->have_xgrab)
        {
1183
          gdk_seat_ungrab (gdk_device_get_seat (priv->grab_pointer));
1184 1185
          priv->have_xgrab = FALSE;
        }
1186

1187
      priv->keyboard_mode = FALSE;
1188
      _gtk_menu_shell_set_grab_device (menu_shell, NULL);
1189 1190

      _gtk_menu_shell_update_mnemonics (menu_shell);
Elliot Lee's avatar
Elliot Lee committed
1191 1192 1193 1194 1195
    }
}

static gint
gtk_menu_shell_is_item (GtkMenuShell *menu_shell,
1196
                        GtkWidget    *child)
Elliot Lee's avatar
Elliot Lee committed
1197
{
1198
  GtkWidget *parent;
Elliot Lee's avatar
Elliot Lee committed
1199 1200 1201 1202

  g_return_val_if_fail (GTK_IS_MENU_SHELL (menu_shell), FALSE);
  g_return_val_if_fail (child != NULL, FALSE);

1203
  parent = gtk_widget_get_parent (child);
1204
  while (GTK_IS_MENU_SHELL (parent))
Elliot Lee's avatar
Elliot Lee committed
1205
    {
1206
      if (parent == (GtkWidget*) menu_shell)
1207 1208
        return TRUE;
      parent = GTK_MENU_SHELL (parent)->priv->parent_menu_shell;
Elliot Lee's avatar
Elliot Lee committed
1209 1210 1211 1212
    }

  return FALSE;
}
Owen Taylor's avatar
Owen Taylor committed
1213

1214
static GtkWidget*
Owen Taylor's avatar
Owen Taylor committed
1215
gtk_menu_shell_get_item (GtkMenuShell *menu_shell,
1216
                         GdkEvent     *event)
Owen Taylor's avatar
Owen Taylor committed
1217 1218 1219 1220
{
  GtkWidget *menu_item;

  menu_item = gtk_get_event_widget ((GdkEvent*) event);
1221

Owen Taylor's avatar
Owen Taylor committed
1222
  while (menu_item && !GTK_IS_MENU_ITEM (menu_item))
1223
    menu_item = gtk_widget_get_parent (menu_item);
Owen Taylor's avatar
Owen Taylor committed
1224 1225 1226 1227 1228 1229 1230

  if (menu_item && gtk_menu_shell_is_item (menu_shell, menu_item))
    return menu_item;
  else
    return NULL;
}

1231 1232
/* Handlers for action signals */

1233 1234 1235 1236 1237 1238 1239
/**
 * gtk_menu_shell_select_item:
 * @menu_shell: a #GtkMenuShell
 * @menu_item: The #GtkMenuItem to select
 *
 * Selects the menu item from the menu shell.
 */
1240
void
1241
gtk_menu_shell_select_item (GtkMenuShell *menu_shell,
1242
                            GtkWidget    *menu_item)
1243
{
1244
  GtkMenuShellPrivate *priv;
1245 1246
  GtkMenuShellClass *class;

1247 1248 1249
  g_return_if_fail (GTK_IS_MENU_SHELL (menu_shell));
  g_return_if_fail (GTK_IS_MENU_ITEM (menu_item));

1250
  priv = menu_shell->priv;
1251 1252
  class = GTK_MENU_SHELL_GET_CLASS (menu_shell);

1253
  if (class->select_item &&
1254 1255
      !(priv->active &&
        priv->active_menu_item == menu_item))
1256 1257 1258
    class->select_item (menu_shell, menu_item);
}

1259
void _gtk_menu_item_set_placement (GtkMenuItem         *menu_item,
1260
                                   GtkSubmenuPlacement  placement);
1261 1262 1263

static void
gtk_menu_shell_real_select_item (GtkMenuShell *menu_shell,
1264
                                 GtkWidget    *menu_item)
1265
{
1266
  GtkMenuShellPrivate *priv = menu_shell->priv;
1267 1268
  GtkPackDirection pack_dir = PACK_DIRECTION (menu_shell);

1269
  if (priv->active_menu_item)
1270
    {
1271 1272
      gtk_menu_item_deselect (GTK_MENU_ITEM (priv->active_menu_item));
      priv->active_menu_item = NULL;
1273
    }