gtkmenu.c 180 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
 * 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
11
 * 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
 * file for a list of people on the GTK+ Team.  See the ChangeLog
 * files for a list of changes.  These files are distributed with
22
 * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
23 24
 */

25 26 27 28 29 30 31 32 33 34 35 36 37
/**
 * SECTION:gtkmenu
 * @Short_description: A menu widget
 * @Title: GtkMenu
 *
 * A #GtkMenu is a #GtkMenuShell that implements a drop down menu
 * consisting of a list of #GtkMenuItem objects which can be navigated
 * and activated by the user to perform application functions.
 *
 * A #GtkMenu is most commonly dropped down by activating a
 * #GtkMenuItem in a #GtkMenuBar or popped up by activating a
 * #GtkMenuItem in another #GtkMenu.
 *
38
 * A #GtkMenu can also be popped up by activating a #GtkComboBox.
39 40 41 42 43 44 45
 * Other composite widgets such as the #GtkNotebook can pop up a
 * #GtkMenu as well.
 *
 * Applications can display a #GtkMenu as a popup menu by calling the 
 * gtk_menu_popup() function.  The example below shows how an application
 * can pop up a menu when the 3rd mouse button is pressed.  
 *
46 47
 * ## Connecting the popup signal handler.
 *
48
 * |[<!-- language="C" -->
49
 *   // connect our handler which will popup the menu
50 51
 *   g_signal_connect_swapped (window, "button_press_event",
 *	G_CALLBACK (my_popup_handler), menu);
52
 * ]|
53
 *
54 55
 * ## Signal handler which displays a popup menu.
 *
56
 * |[<!-- language="C" -->
57 58 59 60 61 62 63 64 65 66
 * static gint
 * my_popup_handler (GtkWidget *widget, GdkEvent *event)
 * {
 *   GtkMenu *menu;
 *   GdkEventButton *event_button;
 *
 *   g_return_val_if_fail (widget != NULL, FALSE);
 *   g_return_val_if_fail (GTK_IS_MENU (widget), FALSE);
 *   g_return_val_if_fail (event != NULL, FALSE);
 *
67 68
 *   // The "widget" is the menu that was supplied when 
 *   // g_signal_connect_swapped() was called.
69 70 71 72 73
 *   menu = GTK_MENU (widget);
 *
 *   if (event->type == GDK_BUTTON_PRESS)
 *     {
 *       event_button = (GdkEventButton *) event;
74
 *       if (event_button->button == GDK_BUTTON_SECONDARY)
75 76 77 78 79 80
 *         {
 *           gtk_menu_popup (menu, NULL, NULL, NULL, NULL, 
 *                           event_button->button, event_button->time);
 *           return TRUE;
 *         }
 *     }
81
 *
82 83
 *   return FALSE;
 * }
84
 * ]|
85 86 87
 *
 * # CSS nodes
 *
88 89 90
 * |[<!-- language="plain" -->
 * menu
 * ├── arrow.top
91 92 93
 * ├── <child>
 * ┊
 * ├── <child>
94 95 96
 * ╰── arrow.bottom
 * ]|
 *
97 98 99
 * The main CSS node of GtkMenu has name menu, and there are two subnodes
 * with name arrow, for scrolling menu arrows. These subnodes get the
 * .top and .bottom style classes.
100 101
 */

102
#include "config.h"
103

104
#include <string.h>
105 106 107

#include  <gobject/gvaluecollector.h>

Matthias Clasen's avatar
Matthias Clasen committed
108
#include "gtkaccellabel.h"
109
#include "gtkaccelmap.h"
110
#include "gtkadjustment.h"
111
#include "gtkbindings.h"
112
#include "gtkcheckmenuitem.h"
113
#include "gtkcheckmenuitemprivate.h"
Elliot Lee's avatar
Elliot Lee committed
114
#include "gtkmain.h"
115
#include "gtkmarshalers.h"
116
#include "gtkmenuprivate.h"
117
#include "gtkmenuitemprivate.h"
118
#include "gtkmenushellprivate.h"
119
#include "gtkwindow.h"
Matthias Clasen's avatar
Matthias Clasen committed
120
#include "gtkbox.h"
121
#include "gtkscrollbar.h"
Havoc Pennington's avatar
Havoc Pennington committed
122
#include "gtksettings.h"
123
#include "gtkprivate.h"
124
#include "gtkwidgetpath.h"
125
#include "gtkwidgetprivate.h"
126
#include "gtkdnd.h"
127
#include "gtkintl.h"
128
#include "gtktypebuiltins.h"
129
#include "gtkwidgetprivate.h"
130
#include "gtkwindowprivate.h"
131 132 133
#include "gtkcssnodeprivate.h"
#include "gtkstylecontextprivate.h"
#include "gtkcssstylepropertyprivate.h"
134

135
#include "deprecated/gtktearoffmenuitem.h"
136 137


138
#include "a11y/gtkmenuaccessible.h"
Elliot Lee's avatar
Elliot Lee committed
139

140
#define NAVIGATION_REGION_OVERSHOOT 50  /* How much the navigation region
141 142
                                         * extends below the submenu
                                         */
143

144 145 146 147 148
#define MENU_SCROLL_STEP1      8
#define MENU_SCROLL_STEP2     15
#define MENU_SCROLL_FAST_ZONE  8
#define MENU_SCROLL_TIMEOUT1  50
#define MENU_SCROLL_TIMEOUT2  20
149

150 151 152
#define MENU_POPUP_DELAY     225
#define MENU_POPDOWN_DELAY  1000

153
#define ATTACH_INFO_KEY "gtk-menu-child-attach-info-key"
154
#define ATTACHED_MENUS "gtk-attached-menus"
155

156 157
typedef struct _GtkMenuAttachData  GtkMenuAttachData;
typedef struct _GtkMenuPopdownData GtkMenuPopdownData;
158 159 160 161 162 163 164

struct _GtkMenuAttachData
{
  GtkWidget *attach_widget;
  GtkMenuDetachFunc detacher;
};

165 166 167 168 169 170
struct _GtkMenuPopdownData
{
  GtkMenu *menu;
  GdkDevice *device;
};

171 172
typedef struct
{
173 174 175 176 177 178 179 180 181
  gint left_attach;
  gint right_attach;
  gint top_attach;
  gint bottom_attach;
  gint effective_left_attach;
  gint effective_right_attach;
  gint effective_top_attach;
  gint effective_bottom_attach;
} AttachInfo;
182

183 184 185 186 187
enum {
  MOVE_SCROLL,
  LAST_SIGNAL
};

188 189
enum {
  PROP_0,
Tim Janik's avatar
Tim Janik committed
190 191 192 193
  PROP_ACTIVE,
  PROP_ACCEL_GROUP,
  PROP_ACCEL_PATH,
  PROP_ATTACH_WIDGET,
194
  PROP_TEAROFF_STATE,
Tim Janik's avatar
Tim Janik committed
195
  PROP_TEAROFF_TITLE,
196 197
  PROP_MONITOR,
  PROP_RESERVE_TOGGLE_SIZE
198
};
199

200
enum {
201 202 203 204 205 206 207 208
  CHILD_PROP_0,
  CHILD_PROP_LEFT_ATTACH,
  CHILD_PROP_RIGHT_ATTACH,
  CHILD_PROP_TOP_ATTACH,
  CHILD_PROP_BOTTOM_ATTACH
};

static void     gtk_menu_set_property      (GObject          *object,
209 210 211
                                            guint             prop_id,
                                            const GValue     *value,
                                            GParamSpec       *pspec);
212
static void     gtk_menu_get_property      (GObject          *object,
213 214 215
                                            guint             prop_id,
                                            GValue           *value,
                                            GParamSpec       *pspec);
216 217 218 219 220 221 222 223 224 225
static void     gtk_menu_set_child_property(GtkContainer     *container,
                                            GtkWidget        *child,
                                            guint             property_id,
                                            const GValue     *value,
                                            GParamSpec       *pspec);
static void     gtk_menu_get_child_property(GtkContainer     *container,
                                            GtkWidget        *child,
                                            guint             property_id,
                                            GValue           *value,
                                            GParamSpec       *pspec);
226
static void     gtk_menu_destroy           (GtkWidget        *widget);
227 228 229
static void     gtk_menu_realize           (GtkWidget        *widget);
static void     gtk_menu_unrealize         (GtkWidget        *widget);
static void     gtk_menu_size_allocate     (GtkWidget        *widget,
230
                                            GtkAllocation    *allocation);
231
static void     gtk_menu_show              (GtkWidget        *widget);
Benjamin Otte's avatar
Benjamin Otte committed
232 233
static gboolean gtk_menu_draw              (GtkWidget        *widget,
                                            cairo_t          *cr);
234
static gboolean gtk_menu_key_press         (GtkWidget        *widget,
235
                                            GdkEventKey      *event);
236
static gboolean gtk_menu_scroll            (GtkWidget        *widget,
237
                                            GdkEventScroll   *event);
238
static gboolean gtk_menu_button_press      (GtkWidget        *widget,
239
                                            GdkEventButton   *event);
240
static gboolean gtk_menu_button_release    (GtkWidget        *widget,
241
                                            GdkEventButton   *event);
242
static gboolean gtk_menu_motion_notify     (GtkWidget        *widget,
243
                                            GdkEventMotion   *event);
244
static gboolean gtk_menu_enter_notify      (GtkWidget        *widget,
245
                                            GdkEventCrossing *event);
246
static gboolean gtk_menu_leave_notify      (GtkWidget        *widget,
247
                                            GdkEventCrossing *event);
248
static void     gtk_menu_scroll_to         (GtkMenu          *menu,
249
                                            gint              offset);
250
static void     gtk_menu_grab_notify       (GtkWidget        *widget,
251
                                            gboolean          was_grabbed);
252 253 254
static gboolean gtk_menu_captured_event    (GtkWidget        *widget,
                                            GdkEvent         *event);

255

256 257 258
static void     gtk_menu_stop_scrolling         (GtkMenu  *menu);
static void     gtk_menu_remove_scroll_timeout  (GtkMenu  *menu);
static gboolean gtk_menu_scroll_timeout         (gpointer  data);
259

260
static void     gtk_menu_scroll_item_visible (GtkMenuShell    *menu_shell,
261
                                              GtkWidget       *menu_item);
262
static void     gtk_menu_select_item       (GtkMenuShell     *menu_shell,
263
                                            GtkWidget        *menu_item);
264
static void     gtk_menu_real_insert       (GtkMenuShell     *menu_shell,
265 266
                                            GtkWidget        *child,
                                            gint              position);
267
static void     gtk_menu_scrollbar_changed (GtkAdjustment    *adjustment,
268
                                            GtkMenu          *menu);
269
static void     gtk_menu_handle_scrolling  (GtkMenu          *menu,
270 271 272
                                            gint              event_x,
                                            gint              event_y,
                                            gboolean          enter,
273
                                            gboolean          motion);
274
static void     gtk_menu_set_tearoff_hints (GtkMenu          *menu,
275
                                            gint             width);
276
static gboolean gtk_menu_focus             (GtkWidget        *widget,
277
                                            GtkDirectionType direction);
278
static gint     gtk_menu_get_popup_delay   (GtkMenuShell     *menu_shell);
279 280
static void     gtk_menu_move_current      (GtkMenuShell     *menu_shell,
                                            GtkMenuDirectionType direction);
281
static void     gtk_menu_real_move_scroll  (GtkMenu          *menu,
282
                                            GtkScrollType     type);
283 284 285 286

static void     gtk_menu_stop_navigating_submenu       (GtkMenu          *menu);
static gboolean gtk_menu_stop_navigating_submenu_cb    (gpointer          user_data);
static gboolean gtk_menu_navigating_submenu            (GtkMenu          *menu,
287 288
                                                        gint              event_x,
                                                        gint              event_y);
289
static void     gtk_menu_set_submenu_navigation_region (GtkMenu          *menu,
290 291
                                                        GtkMenuItem      *menu_item,
                                                        GdkEventCrossing *event);
292
 
293
static void gtk_menu_deactivate     (GtkMenuShell      *menu_shell);
294
static void gtk_menu_show_all       (GtkWidget         *widget);
295 296
static void gtk_menu_position       (GtkMenu           *menu,
                                     gboolean           set_scroll_offset);
297 298 299
static void gtk_menu_reparent       (GtkMenu           *menu,
                                     GtkWidget         *new_parent,
                                     gboolean           unrealize);
300
static void gtk_menu_remove         (GtkContainer      *menu,
301
                                     GtkWidget         *widget);
Elliot Lee's avatar
Elliot Lee committed
302

303 304
static void gtk_menu_update_title   (GtkMenu           *menu);

305 306 307
static void       menu_grab_transfer_window_destroy (GtkMenu *menu);
static GdkWindow *menu_grab_transfer_window_get     (GtkMenu *menu);

308 309
static gboolean gtk_menu_real_can_activate_accel (GtkWidget *widget,
                                                  guint      signal_id);
310
static void _gtk_menu_refresh_accel_paths (GtkMenu *menu,
311
                                           gboolean group_changed);
312

313 314 315 316 317 318 319 320 321 322
static void gtk_menu_get_preferred_width            (GtkWidget           *widget,
                                                     gint                *minimum_size,
                                                     gint                *natural_size);
static void gtk_menu_get_preferred_height           (GtkWidget           *widget,
                                                     gint                *minimum_size,
                                                     gint                *natural_size);
static void gtk_menu_get_preferred_height_for_width (GtkWidget           *widget,
                                                     gint                 for_size,
                                                     gint                *minimum_size,
                                                     gint                *natural_size);
323 324


325
static const gchar attach_data_key[] = "gtk-menu-attach-data";
326

327 328
static guint menu_signals[LAST_SIGNAL] = { 0 };

329
G_DEFINE_TYPE_WITH_PRIVATE (GtkMenu, gtk_menu, GTK_TYPE_MENU_SHELL)
Elliot Lee's avatar
Elliot Lee committed
330

331 332 333
static void
menu_queue_resize (GtkMenu *menu)
{
334
  GtkMenuPrivate *priv = menu->priv;
335 336 337 338 339

  priv->have_layout = FALSE;
  gtk_widget_queue_resize (GTK_WIDGET (menu));
}

340 341 342 343 344 345
static void
attach_info_free (AttachInfo *info)
{
  g_slice_free (AttachInfo, info);
}

346 347 348 349 350 351 352 353
static AttachInfo *
get_attach_info (GtkWidget *child)
{
  GObject *object = G_OBJECT (child);
  AttachInfo *ai = g_object_get_data (object, ATTACH_INFO_KEY);

  if (!ai)
    {
354
      ai = g_slice_new0 (AttachInfo);
355 356
      g_object_set_data_full (object, I_(ATTACH_INFO_KEY), ai,
                              (GDestroyNotify) attach_info_free);
357 358 359 360 361 362 363 364 365
    }

  return ai;
}

static gboolean
is_grid_attached (AttachInfo *ai)
{
  return (ai->left_attach >= 0 &&
366 367 368
          ai->right_attach >= 0 &&
          ai->top_attach >= 0 &&
          ai->bottom_attach >= 0);
369 370 371 372 373
}

static void
menu_ensure_layout (GtkMenu *menu)
{
374
  GtkMenuPrivate *priv = menu->priv;
375 376 377 378 379 380 381

  if (!priv->have_layout)
    {
      GtkMenuShell *menu_shell = GTK_MENU_SHELL (menu);
      GList *l;
      gchar *row_occupied;
      gint current_row;
382
      gint max_right_attach;
383 384 385 386 387 388 389
      gint max_bottom_attach;

      /* Find extents of gridded portion
       */
      max_right_attach = 1;
      max_bottom_attach = 0;

390
      for (l = menu_shell->priv->children; l; l = l->next)
391 392 393 394 395 396 397 398 399 400 401 402
        {
          GtkWidget *child = l->data;
          AttachInfo *ai = get_attach_info (child);

          if (is_grid_attached (ai))
            {
              max_bottom_attach = MAX (max_bottom_attach, ai->bottom_attach);
              max_right_attach = MAX (max_right_attach, ai->right_attach);
            }
        }

      /* Find empty rows */
403 404
      row_occupied = g_malloc0 (max_bottom_attach);

405
      for (l = menu_shell->priv->children; l; l = l->next)
406 407 408
        {
          GtkWidget *child = l->data;
          AttachInfo *ai = get_attach_info (child);
409

410 411 412
          if (is_grid_attached (ai))
            {
              gint i;
413

414 415 416 417
              for (i = ai->top_attach; i < ai->bottom_attach; i++)
                row_occupied[i] = TRUE;
            }
        }
418 419 420 421

      /* Lay non-grid-items out in those rows
       */
      current_row = 0;
422
      for (l = menu_shell->priv->children; l; l = l->next)
423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446
        {
          GtkWidget *child = l->data;
          AttachInfo *ai = get_attach_info (child);

          if (!is_grid_attached (ai))
            {
              while (current_row < max_bottom_attach && row_occupied[current_row])
                current_row++;

              ai->effective_left_attach = 0;
              ai->effective_right_attach = max_right_attach;
              ai->effective_top_attach = current_row;
              ai->effective_bottom_attach = current_row + 1;

              current_row++;
            }
          else
            {
              ai->effective_left_attach = ai->left_attach;
              ai->effective_right_attach = ai->right_attach;
              ai->effective_top_attach = ai->top_attach;
              ai->effective_bottom_attach = ai->bottom_attach;
            }
        }
447 448 449 450 451 452 453 454 455 456 457 458 459

      g_free (row_occupied);

      priv->n_rows = MAX (current_row, max_bottom_attach);
      priv->n_columns = max_right_attach;
      priv->have_layout = TRUE;
    }
}


static gint
gtk_menu_get_n_columns (GtkMenu *menu)
{
460
  GtkMenuPrivate *priv = menu->priv;
461 462 463 464 465 466 467 468 469

  menu_ensure_layout (menu);

  return priv->n_columns;
}

static gint
gtk_menu_get_n_rows (GtkMenu *menu)
{
470
  GtkMenuPrivate *priv = menu->priv;
471 472 473 474 475 476 477 478

  menu_ensure_layout (menu);

  return priv->n_rows;
}

static void
get_effective_child_attach (GtkWidget *child,
479 480 481 482
                            int       *l,
                            int       *r,
                            int       *t,
                            int       *b)
483
{
484
  GtkMenu *menu = GTK_MENU (gtk_widget_get_parent (child));
485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501
  AttachInfo *ai;
  
  menu_ensure_layout (menu);

  ai = get_attach_info (child);

  if (l)
    *l = ai->effective_left_attach;
  if (r)
    *r = ai->effective_right_attach;
  if (t)
    *t = ai->effective_top_attach;
  if (b)
    *b = ai->effective_bottom_attach;

}

Elliot Lee's avatar
Elliot Lee committed
502 503 504
static void
gtk_menu_class_init (GtkMenuClass *class)
{
505 506 507 508
  GObjectClass *gobject_class = G_OBJECT_CLASS (class);
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
  GtkContainerClass *container_class = GTK_CONTAINER_CLASS (class);
  GtkMenuShellClass *menu_shell_class = GTK_MENU_SHELL_CLASS (class);
509
  GtkBindingSet *binding_set;
Tim Janik's avatar
Tim Janik committed
510
  
511 512 513
  gobject_class->set_property = gtk_menu_set_property;
  gobject_class->get_property = gtk_menu_get_property;

514
  widget_class->destroy = gtk_menu_destroy;
515 516 517 518
  widget_class->realize = gtk_menu_realize;
  widget_class->unrealize = gtk_menu_unrealize;
  widget_class->size_allocate = gtk_menu_size_allocate;
  widget_class->show = gtk_menu_show;
Benjamin Otte's avatar
Benjamin Otte committed
519
  widget_class->draw = gtk_menu_draw;
520
  widget_class->scroll_event = gtk_menu_scroll;
521 522 523 524 525 526 527 528
  widget_class->key_press_event = gtk_menu_key_press;
  widget_class->button_press_event = gtk_menu_button_press;
  widget_class->button_release_event = gtk_menu_button_release;
  widget_class->motion_notify_event = gtk_menu_motion_notify;
  widget_class->show_all = gtk_menu_show_all;
  widget_class->enter_notify_event = gtk_menu_enter_notify;
  widget_class->leave_notify_event = gtk_menu_leave_notify;
  widget_class->focus = gtk_menu_focus;
529
  widget_class->can_activate_accel = gtk_menu_real_can_activate_accel;
530
  widget_class->grab_notify = gtk_menu_grab_notify;
531 532 533
  widget_class->get_preferred_width = gtk_menu_get_preferred_width;
  widget_class->get_preferred_height = gtk_menu_get_preferred_height;
  widget_class->get_preferred_height_for_width = gtk_menu_get_preferred_height_for_width;
534 535 536 537 538 539 540 541 542 543 544 545

  container_class->remove = gtk_menu_remove;
  container_class->get_child_property = gtk_menu_get_child_property;
  container_class->set_child_property = gtk_menu_set_child_property;
  
  menu_shell_class->submenu_placement = GTK_LEFT_RIGHT;
  menu_shell_class->deactivate = gtk_menu_deactivate;
  menu_shell_class->select_item = gtk_menu_select_item;
  menu_shell_class->insert = gtk_menu_real_insert;
  menu_shell_class->get_popup_delay = gtk_menu_get_popup_delay;
  menu_shell_class->move_current = gtk_menu_move_current;

546 547 548 549 550
  /**
   * GtkMenu::move-scroll:
   * @menu: a #GtkMenu
   * @scroll_type: a #GtkScrollType
   */
551
  menu_signals[MOVE_SCROLL] =
552
    g_signal_new_class_handler (I_("move-scroll"),
553
                                G_OBJECT_CLASS_TYPE (gobject_class),
554 555 556 557 558 559
                                G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
                                G_CALLBACK (gtk_menu_real_move_scroll),
                                NULL, NULL,
                                _gtk_marshal_VOID__ENUM,
                                G_TYPE_NONE, 1,
                                GTK_TYPE_SCROLL_TYPE);
Tim Janik's avatar
Tim Janik committed
560 561 562 563

  /**
   * GtkMenu:active:
   *
564 565
   * The index of the currently selected menu item, or -1 if no
   * menu item is selected.
Tim Janik's avatar
Tim Janik committed
566
   *
567
   * Since: 2.14
Tim Janik's avatar
Tim Janik committed
568 569 570
   **/
  g_object_class_install_property (gobject_class,
                                   PROP_ACTIVE,
571
                                   g_param_spec_int ("active",
572 573 574 575
                                                     P_("Active"),
                                                     P_("The currently selected menu item"),
                                                     -1, G_MAXINT, -1,
                                                     GTK_PARAM_READWRITE));
Tim Janik's avatar
Tim Janik committed
576 577 578 579 580 581

  /**
   * GtkMenu:accel-group:
   *
   * The accel group holding accelerators for the menu.
   *
582
   * Since: 2.14
Tim Janik's avatar
Tim Janik committed
583 584 585 586
   **/
  g_object_class_install_property (gobject_class,
                                   PROP_ACCEL_GROUP,
                                   g_param_spec_object ("accel-group",
587 588 589 590
                                                        P_("Accel Group"),
                                                        P_("The accel group holding accelerators for the menu"),
                                                        GTK_TYPE_ACCEL_GROUP,
                                                        GTK_PARAM_READWRITE));
Tim Janik's avatar
Tim Janik committed
591 592 593 594 595 596

  /**
   * GtkMenu:accel-path:
   *
   * An accel path used to conveniently construct accel paths of child items.
   *
597
   * Since: 2.14
Tim Janik's avatar
Tim Janik committed
598 599 600 601
   **/
  g_object_class_install_property (gobject_class,
                                   PROP_ACCEL_PATH,
                                   g_param_spec_string ("accel-path",
602 603 604 605
                                                        P_("Accel Path"),
                                                        P_("An accel path used to conveniently construct accel paths of child items"),
                                                        NULL,
                                                        GTK_PARAM_READWRITE));
Tim Janik's avatar
Tim Janik committed
606 607 608 609

  /**
   * GtkMenu:attach-widget:
   *
Matthias Clasen's avatar
Matthias Clasen committed
610 611 612
   * The widget the menu is attached to. Setting this property attaches
   * the menu without a #GtkMenuDetachFunc. If you need to use a detacher,
   * use gtk_menu_attach_to_widget() directly.
Tim Janik's avatar
Tim Janik committed
613
   *
614
   * Since: 2.14
Tim Janik's avatar
Tim Janik committed
615 616
   **/
  g_object_class_install_property (gobject_class,
Matthias Clasen's avatar
Matthias Clasen committed
617 618
                                   PROP_ATTACH_WIDGET,
                                   g_param_spec_object ("attach-widget",
619 620 621 622
                                                        P_("Attach Widget"),
                                                        P_("The widget the menu is attached to"),
                                                        GTK_TYPE_WIDGET,
                                                        GTK_PARAM_READWRITE));
Tim Janik's avatar
Tim Janik committed
623

624 625 626 627 628 629 630 631
  /**
   * GtkMenu:tearoff-title:
   *
   * A title that may be displayed by the window manager when this
   * menu is torn-off.
   *
   * Deprecated: 3.10
   **/
632 633 634
  g_object_class_install_property (gobject_class,
                                   PROP_TEAROFF_TITLE,
                                   g_param_spec_string ("tearoff-title",
635 636
                                                        P_("Tearoff Title"),
                                                        P_("A title that may be displayed by the window manager when this menu is torn-off"),
637
                                                        NULL,
638
                                                        GTK_PARAM_READWRITE | G_PARAM_DEPRECATED));
639 640 641 642 643 644 645

  /**
   * GtkMenu:tearoff-state:
   *
   * A boolean that indicates whether the menu is torn-off.
   *
   * Since: 2.6
646 647
   *
   * Deprecated: 3.10
648 649 650 651
   **/
  g_object_class_install_property (gobject_class,
                                   PROP_TEAROFF_STATE,
                                   g_param_spec_boolean ("tearoff-state",
652 653 654
                                                         P_("Tearoff State"),
                                                         P_("A boolean that indicates whether the menu is torn-off"),
                                                         FALSE,
655
                                                         GTK_PARAM_READWRITE | G_PARAM_DEPRECATED));
Manish Singh's avatar
Manish Singh committed
656

Tim Janik's avatar
Tim Janik committed
657 658 659 660 661
  /**
   * GtkMenu:monitor:
   *
   * The monitor the menu will be popped up on.
   *
662
   * Since: 2.14
Tim Janik's avatar
Tim Janik committed
663 664 665 666
   **/
  g_object_class_install_property (gobject_class,
                                   PROP_MONITOR,
                                   g_param_spec_int ("monitor",
667 668 669
                                                     P_("Monitor"),
                                                     P_("The monitor the menu will be popped up on"),
                                                     -1, G_MAXINT, -1,
670
                                                     GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
Tim Janik's avatar
Tim Janik committed
671

672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687
  /**
   * GtkMenu:reserve-toggle-size:
   *
   * A boolean that indicates whether the menu reserves space for
   * toggles and icons, regardless of their actual presence.
   *
   * This property should only be changed from its default value
   * for special-purposes such as tabular menus. Regular menus that
   * are connected to a menu bar or context menus should reserve
   * toggle space for consistency.
   *
   * Since: 2.18
   */
  g_object_class_install_property (gobject_class,
                                   PROP_RESERVE_TOGGLE_SIZE,
                                   g_param_spec_boolean ("reserve-toggle-size",
688 689 690
                                                         P_("Reserve Toggle Size"),
                                                         P_("A boolean that indicates whether the menu reserves space for toggles and icons"),
                                                         TRUE,
691
                                                         GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
692

693 694 695 696 697 698 699 700 701
  /**
   * GtkMenu:horizontal-padding:
   *
   * Extra space at the left and right edges of the menu.
   *
   * Deprecated: 3.8: use the standard padding CSS property (through objects
   *   like #GtkStyleContext and #GtkCssProvider); the value of this style
   *   property is ignored.
   */
702 703 704 705 706 707 708
  gtk_widget_class_install_style_property (widget_class,
                                           g_param_spec_int ("horizontal-padding",
                                                             P_("Horizontal Padding"),
                                                             P_("Extra space at the left and right edges of the menu"),
                                                             0,
                                                             G_MAXINT,
                                                             0,
709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729
                                                             GTK_PARAM_READABLE |
                                                             G_PARAM_DEPRECATED));

  /**
   * GtkMenu:vertical-padding:
   *
   * Extra space at the top and bottom of the menu.
   *
   * Deprecated: 3.8: use the standard padding CSS property (through objects
   *   like #GtkStyleContext and #GtkCssProvider); the value of this style
   *   property is ignored.
   */
  gtk_widget_class_install_style_property (widget_class,
                                           g_param_spec_int ("vertical-padding",
                                                             P_("Vertical Padding"),
                                                             P_("Extra space at the top and bottom of the menu"),
                                                             0,
                                                             G_MAXINT,
                                                             1,
                                                             GTK_PARAM_READABLE |
                                                             G_PARAM_DEPRECATED));
730

731
  gtk_widget_class_install_style_property (widget_class,
732 733 734 735 736 737 738
                                           g_param_spec_int ("vertical-offset",
                                                             P_("Vertical Offset"),
                                                             P_("When the menu is a submenu, position it this number of pixels offset vertically"),
                                                             G_MININT,
                                                             G_MAXINT,
                                                             0,
                                                             GTK_PARAM_READABLE));
739 740

  gtk_widget_class_install_style_property (widget_class,
741 742 743 744 745 746 747
                                           g_param_spec_int ("horizontal-offset",
                                                             P_("Horizontal Offset"),
                                                             P_("When the menu is a submenu, position it this number of pixels offset horizontally"),
                                                             G_MININT,
                                                             G_MAXINT,
                                                             -2,
                                                             GTK_PARAM_READABLE));
748

749 750 751 752 753 754 755
  gtk_widget_class_install_style_property (widget_class,
                                           g_param_spec_boolean ("double-arrows",
                                                                 P_("Double Arrows"),
                                                                 P_("When scrolling, always show both arrows."),
                                                                 TRUE,
                                                                 GTK_PARAM_READABLE));

756 757 758 759 760 761 762 763 764 765 766 767 768 769
  /**
   * GtkMenu:arrow-placement:
   *
   * Indicates where scroll arrows should be placed.
   *
   * Since: 2.16
   **/
  gtk_widget_class_install_style_property (widget_class,
                                           g_param_spec_enum ("arrow-placement",
                                                              P_("Arrow Placement"),
                                                              P_("Indicates where scroll arrows should be placed"),
                                                              GTK_TYPE_ARROW_PLACEMENT,
                                                              GTK_ARROWS_BOTH,
                                                              GTK_PARAM_READABLE));
770

771 772
 gtk_container_class_install_child_property (container_class,
                                             CHILD_PROP_LEFT_ATTACH,
773
                                              g_param_spec_int ("left-attach",
774 775
                                                               P_("Left Attach"),
                                                               P_("The column number to attach the left side of the child to"),
776
                                                                -1, INT_MAX, -1,
777
                                                               GTK_PARAM_READWRITE));
778 779 780

 gtk_container_class_install_child_property (container_class,
                                             CHILD_PROP_RIGHT_ATTACH,
781
                                              g_param_spec_int ("right-attach",
782 783
                                                               P_("Right Attach"),
                                                               P_("The column number to attach the right side of the child to"),
784
                                                                -1, INT_MAX, -1,
785
                                                               GTK_PARAM_READWRITE));
786 787 788

 gtk_container_class_install_child_property (container_class,
                                             CHILD_PROP_TOP_ATTACH,
789
                                              g_param_spec_int ("top-attach",
790 791
                                                               P_("Top Attach"),
                                                               P_("The row number to attach the top of the child to"),
792
                                                                -1, INT_MAX, -1,
793
                                                               GTK_PARAM_READWRITE));
794 795 796

 gtk_container_class_install_child_property (container_class,
                                             CHILD_PROP_BOTTOM_ATTACH,
797
                                              g_param_spec_int ("bottom-attach",
798 799
                                                               P_("Bottom Attach"),
                                                               P_("The row number to attach the bottom of the child to"),
800
                                                                -1, INT_MAX, -1,
801
                                                               GTK_PARAM_READWRITE));
802

803
 /**
804
  * GtkMenu:arrow-scaling:
805 806 807 808 809 810 811 812 813 814 815 816
  *
  * Arbitrary constant to scale down the size of the scroll arrow.
  *
  * Since: 2.16
  */
  gtk_widget_class_install_style_property (widget_class,
                                           g_param_spec_float ("arrow-scaling",
                                                               P_("Arrow Scaling"),
                                                               P_("Arbitrary constant to scale down the size of the scroll arrow"),
                                                               0.0, 1.0, 0.7,
                                                               GTK_PARAM_READABLE));

817 818
  binding_set = gtk_binding_set_by_class (class);
  gtk_binding_entry_add_signal (binding_set,
819 820 821 822
                                GDK_KEY_Up, 0,
                                I_("move-current"), 1,
                                GTK_TYPE_MENU_DIRECTION_TYPE,
                                GTK_MENU_DIR_PREV);
823
  gtk_binding_entry_add_signal (binding_set,
824 825 826 827
                                GDK_KEY_KP_Up, 0,
                                "move-current", 1,
                                GTK_TYPE_MENU_DIRECTION_TYPE,
                                GTK_MENU_DIR_PREV);
828
  gtk_binding_entry_add_signal (binding_set,
829 830 831 832
                                GDK_KEY_Down, 0,
                                "move-current", 1,
                                GTK_TYPE_MENU_DIRECTION_TYPE,
                                GTK_MENU_DIR_NEXT);
833
  gtk_binding_entry_add_signal (binding_set,
834 835 836 837
                                GDK_KEY_KP_Down, 0,
                                "move-current", 1,
                                GTK_TYPE_MENU_DIRECTION_TYPE,
                                GTK_MENU_DIR_NEXT);
838
  gtk_binding_entry_add_signal (binding_set,
839 840 841 842
                                GDK_KEY_Left, 0,
                                "move-current", 1,
                                GTK_TYPE_MENU_DIRECTION_TYPE,
                                GTK_MENU_DIR_PARENT);
843
  gtk_binding_entry_add_signal (binding_set,
844 845 846 847
                                GDK_KEY_KP_Left, 0,
                                "move-current", 1,
                                GTK_TYPE_MENU_DIRECTION_TYPE,
                                GTK_MENU_DIR_PARENT);
848
  gtk_binding_entry_add_signal (binding_set,
849 850 851 852
                                GDK_KEY_Right, 0,
                                "move-current", 1,
                                GTK_TYPE_MENU_DIRECTION_TYPE,
                                GTK_MENU_DIR_CHILD);
853
  gtk_binding_entry_add_signal (binding_set,
854 855 856 857
                                GDK_KEY_KP_Right, 0,
                                "move-current", 1,
                                GTK_TYPE_MENU_DIRECTION_TYPE,
                                GTK_MENU_DIR_CHILD);
858
  gtk_binding_entry_add_signal (binding_set,
859 860 861 862
                                GDK_KEY_Home, 0,
                                "move-scroll", 1,
                                GTK_TYPE_SCROLL_TYPE,
                                GTK_SCROLL_START);
863
  gtk_binding_entry_add_signal (binding_set,
864 865 866 867
                                GDK_KEY_KP_Home, 0,
                                "move-scroll", 1,
                                GTK_TYPE_SCROLL_TYPE,
                                GTK_SCROLL_START);
868
  gtk_binding_entry_add_signal (binding_set,
869 870 871 872
                                GDK_KEY_End, 0,
                                "move-scroll", 1,
                                GTK_TYPE_SCROLL_TYPE,
                                GTK_SCROLL_END);
873
  gtk_binding_entry_add_signal (binding_set,
874 875 876 877
                                GDK_KEY_KP_End, 0,
                                "move-scroll", 1,
                                GTK_TYPE_SCROLL_TYPE,
                                GTK_SCROLL_END);
878
  gtk_binding_entry_add_signal (binding_set,
879 880 881 882
                                GDK_KEY_Page_Up, 0,
                                "move-scroll", 1,
                                GTK_TYPE_SCROLL_TYPE,
                                GTK_SCROLL_PAGE_UP);
883
  gtk_binding_entry_add_signal (binding_set,
884 885 886 887
                                GDK_KEY_KP_Page_Up, 0,
                                "move-scroll", 1,
                                GTK_TYPE_SCROLL_TYPE,
                                GTK_SCROLL_PAGE_UP);
888
  gtk_binding_entry_add_signal (binding_set,
889 890 891 892
                                GDK_KEY_Page_Down, 0,
                                "move-scroll", 1,
                                GTK_TYPE_SCROLL_TYPE,
                                GTK_SCROLL_PAGE_DOWN);
893
  gtk_binding_entry_add_signal (binding_set,
894 895 896 897
                                GDK_KEY_KP_Page_Down, 0,
                                "move-scroll", 1,
                                GTK_TYPE_SCROLL_TYPE,
                                GTK_SCROLL_PAGE_DOWN);
898

899
  gtk_widget_class_set_accessible_type (widget_class, GTK_TYPE_MENU_ACCESSIBLE);
900
  gtk_widget_class_set_css_name (widget_class, "menu");
Elliot Lee's avatar
Elliot Lee committed
901 902
}

903

904
static void
905
gtk_menu_set_property (GObject      *object,
906 907 908
                       guint         prop_id,
                       const GValue *value,
                       GParamSpec   *pspec)
909
{
910 911
  GtkMenu *menu = GTK_MENU (object);

912 913
  switch (prop_id)
    {
Tim Janik's avatar
Tim Janik committed
914
    case PROP_ACTIVE:
915
      gtk_menu_set_active (menu, g_value_get_int (value));
Tim Janik's avatar
Tim Janik committed
916 917 918 919 920 921 922 923
      break;
    case PROP_ACCEL_GROUP:
      gtk_menu_set_accel_group (menu, g_value_get_object (value));
      break;
    case PROP_ACCEL_PATH:
      gtk_menu_set_accel_path (menu, g_value_get_string (value));
      break;
    case PROP_ATTACH_WIDGET:
Matthias Clasen's avatar
Matthias Clasen committed
924 925 926 927 928 929 930 931 932 933 934
      {
        GtkWidget *widget;

        widget = gtk_menu_get_attach_widget (menu);
        if (widget)
          gtk_menu_detach (menu);

        widget = (GtkWidget*) g_value_get_object (value); 
        if (widget)
          gtk_menu_attach_to_widget (menu, widget, NULL);
      }
Tim Janik's avatar
Tim Janik committed
935
      break;
936
    case PROP_TEAROFF_STATE:
937
G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
938
      gtk_menu_set_tearoff_state (menu, g_value_get_boolean (value));
939
G_GNUC_END_IGNORE_DEPRECATIONS;
940
      break;
941
    case PROP_TEAROFF_TITLE:
942
G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
943
      gtk_menu_set_title (menu, g_value_get_string (value));
944
G_GNUC_END_IGNORE_DEPRECATIONS;
Tim Janik's avatar
Tim Janik committed
945 946 947 948
      break;
    case PROP_MONITOR:
      gtk_menu_set_monitor (menu, g_value_get_int (value));
      break;
949 950 951
    case PROP_RESERVE_TOGGLE_SIZE:
      gtk_menu_set_reserve_toggle_size (menu, g_value_get_boolean (value));
      break;
952 953 954 955 956 957
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}

958
static void
959
gtk_menu_get_property (GObject     *object,
960 961 962
                       guint        prop_id,
                       GValue      *value,
                       GParamSpec  *pspec)
963
{
964 965
  GtkMenu *menu = GTK_MENU (object);

966 967
  switch (prop_id)
    {
Tim Janik's avatar
Tim Janik committed
968
    case PROP_ACTIVE:
969
      g_value_set_int (value, g_list_index (GTK_MENU_SHELL (menu)->priv->children, gtk_menu_get_active (menu)));
Tim Janik's avatar
Tim Janik committed
970 971 972 973 974 975 976 977 978 979
      break;
    case PROP_ACCEL_GROUP:
      g_value_set_object (value, gtk_menu_get_accel_group (menu));
      break;
    case PROP_ACCEL_PATH:
      g_value_set_string (value, gtk_menu_get_accel_path (menu));
      break;
    case PROP_ATTACH_WIDGET:
      g_value_set_object (value, gtk_menu_get_attach_widget (menu));
      break;
980
    case PROP_TEAROFF_STATE:
981
G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
982
      g_value_set_boolean (value, gtk_menu_get_tearoff_state (menu));
983
G_GNUC_END_IGNORE_DEPRECATIONS;
984
      break;
985
    case PROP_TEAROFF_TITLE:
986
G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
987
      g_value_set_string (value, gtk_menu_get_title (menu));
988
G_GNUC_END_IGNORE_DEPRECATIONS;
989
      break;
Tim Janik's avatar
Tim Janik committed
990 991 992
    case PROP_MONITOR:
      g_value_set_int (value, gtk_menu_get_monitor (menu));
      break;
993 994 995
    case PROP_RESERVE_TOGGLE_SIZE:
      g_value_set_boolean (value, gtk_menu_get_reserve_toggle_size (menu));
      break;
996 997 998 999 1000 1001
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}

1002 1003 1004 1005 1006 1007 1008 1009
static void
gtk_menu_set_child_property (GtkContainer *container,
                             GtkWidget    *child,
                             guint         property_id,
                             const GValue *value,
                             GParamSpec   *pspec)
{
  GtkMenu *menu = GTK_MENU (container);
1010
  AttachInfo *ai = get_attach_info (child);
1011 1012 1013

  switch (property_id)
    {
1014 1015 1016 1017 1018 1019 1020
    case CHILD_PROP_LEFT_ATTACH:
      ai->left_attach = g_value_get_int (value);
      break;
    case CHILD_PROP_RIGHT_ATTACH:
      ai->right_attach = g_value_get_int (value);
      break;
    case CHILD_PROP_TOP_ATTACH:
1021
      ai->top_attach = g_value_get_int (value);
1022 1023 1024 1025 1026 1027 1028 1029
      break;
    case CHILD_PROP_BOTTOM_ATTACH:
      ai->bottom_attach = g_value_get_int (value);
      break;

    default:
      GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec);
      return;
1030 1031
    }

1032
  menu_queue_resize (menu);
1033 1034 1035 1036 1037 1038 1039 1040 1041
}

static void
gtk_menu_get_child_property (GtkContainer *container,
                             GtkWidget    *child,
                             guint         property_id,
                             GValue       *value,
                             GParamSpec   *pspec)
{
1042
  AttachInfo *ai = get_attach_info (child);
1043 1044 1045

  switch (property_id)
    {
1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061
    case CHILD_PROP_LEFT_ATTACH:
      g_value_set_int (value, ai->left_attach);
      break;
    case CHILD_PROP_RIGHT_ATTACH:
      g_value_set_int (value, ai->right_attach);
      break;
    case CHILD_PROP_TOP_ATTACH:
      g_value_set_int (value, ai->top_attach);
      break;
    case CHILD_PROP_BOTTOM_ATTACH:
      g_value_set_int (value, ai->bottom_attach);
      break;
      
    default:
      GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec);
      return;
1062 1063 1064
    }
}

1065
static gboolean
1066
gtk_menu_window_event (GtkWidget *window,
1067 1068
                       GdkEvent  *event,
                       GtkWidget *menu)
1069 1070 1071
{
  gboolean handled = FALSE;

Manish Singh's avatar
Manish Singh committed
1072 1073
  g_object_ref (window);
  g_object_ref (menu);
1074 1075 1076 1077 1078

  switch (event->type)
    {
    case GDK_KEY_PRESS:
    case GDK_KEY_RELEASE:
1079
      handled = gtk_widget_event (menu, event);
1080
      break;
1081 1082 1083 1084 1085 1086 1087 1088 1089
    case GDK_WINDOW_STATE:
      /* Window for the menu has been closed by the display server or by GDK.
       * Update the internal state as if the user had clicked outside the
       * menu
       */
      if (event->window_state.new_window_state & GDK_WINDOW_STATE_WITHDRAWN &&
          event->window_state.changed_mask & GDK_WINDOW_STATE_WITHDRAWN)
        gtk_menu_shell_deactivate (GTK_MENU_SHELL(menu));
      break;
1090 1091 1092 1093
    default:
      break;
    }

Manish Singh's avatar
Manish Singh committed
1094 1095
  g_object_unref (window);
  g_object_unref (menu);
1096 1097 1098 1099

  return handled;
}

1100
static void
1101 1102 1103
node_style_changed_cb (GtkCssNode        *node,
                       GtkCssStyleChange *change,
                       GtkWidget         *widget)
1104
{
1105
  if (gtk_css_style_change_affects (change, GTK_CSS_AFFECTS_SIZE | GTK_CSS_AFFECTS_CLIP))
1106 1107 1108 1109 1110
    gtk_widget_queue_resize (widget);
  else
    gtk_widget_queue_draw (widget);
}

Elliot Lee's avatar
Elliot Lee committed
1111 1112 1113
static void
gtk_menu_init (GtkMenu *menu)
{
1114
  GtkMenuPrivate *priv;
1115
  GtkCssNode *widget_node;
1116

1117
  priv = gtk_menu_get_instance_private (menu);
1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129

  menu->priv = priv;

  priv->toplevel = g_object_connect (g_object_new (GTK_TYPE_WINDOW,
                                                   "type", GTK_WINDOW_POPUP,
                                                   "child", menu,
                                                   NULL),
                                     "signal::event", gtk_menu_window_event, menu,
                                     "signal::destroy", gtk_widget_destroyed, &priv->toplevel,
                                     NULL);
  gtk_window_set_resizable (GTK_WINDOW (priv->toplevel), FALSE);
  gtk_window_set_mnemonic_modifier (GTK_WINDOW (priv->toplevel), 0);
1130

Matthias Clasen's avatar
Matthias Clasen committed
1131
  _gtk_window_request_csd (GTK_WINDOW (priv->toplevel));
1132 1133
  gtk_style_context_add_class (gtk_widget_get_style_context (priv->toplevel),
                               GTK_STYLE_CLASS_POPUP);
1134

Owen Taylor's avatar
Owen Taylor committed
1135 1136 1137
  /* Refloat the menu, so that reference counting for the menu isn't
   * affected by it being a child of the toplevel
   */
1138
  g_object_force_floating (G_OBJECT (menu));
1139
  priv->needs_destruction_ref = TRUE;
Owen Taylor's avatar
Owen Taylor committed
1140

1141
  priv->monitor_num = -1;
1142
  priv->drag_start_y = -1;
1143

1144
  _gtk_widget_set_captured_event_handler (GTK_WIDGET (menu), gtk_menu_captured_event);
1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163

  widget_node = gtk_widget_get_css_node (GTK_WIDGET (menu));
  priv->top_arrow = gtk_css_node_new ();
  gtk_css_node_set_name (priv->top_arrow, I_("arrow"));
  gtk_css_node_add_class (priv->top_arrow, g_quark_from_static_string (GTK_STYLE_CLASS_TOP));
  gtk_css_node_set_parent (priv->top_arrow, widget_node);
  gtk_css_node_set_visible (priv->top_arrow, FALSE);
  gtk_css_node_set_state (priv->top_arrow, gtk_css_node_get_state (widget_node));
  g_signal_connect_object (priv->top_arrow, "style-changed", G_CALLBACK (node_style_changed_cb), menu, 0);
  g_object_unref (priv->top_arrow);

  priv->bottom_arrow = gtk_css_node_new ();
  gtk_css_node_set_name (priv->bottom_arrow, I_("arrow"));
  gtk_css_node_add_class (priv->bottom_arrow, g_quark_from_static_string (GTK_STYLE_CLASS_BOTTOM));
  gtk_css_node_set_parent (priv->bottom_arrow, widget_node);
  gtk_css_node_set_visible (priv->bottom_arrow, FALSE);
  gtk_css_node_set_state (priv->bottom_arrow, gtk_css_node_get_state (widget_node));
  g_signal_connect_object (priv->bottom_arrow, "style-changed", G_CALLBACK (node_style_changed_cb), menu, 0);
  g_object_unref (priv->bottom_arrow);
1164 1165 1166
}

static void
1167
gtk_menu_destroy (GtkWidget *widget)
1168
{
1169
  GtkMenu *menu = GTK_MENU (widget);
1170
  GtkMenuPrivate *priv = menu->priv;
1171
  GtkMenuAttachData *data;
1172

1173
  gtk_menu_remove_scroll_timeout (menu);
1174

1175
  data = g_object_get_data (G_OBJECT (widget), attach_data_key);
1176
  if (data)
1177
    gtk_menu_detach (menu);
1178

1179 1180
  gtk_menu_stop_navigating_submenu (menu);

1181
  g_clear_object (&priv->old_active_menu_item);
1182

Owen Taylor's avatar
Owen Taylor committed
1183
  /* Add back the reference count for being a child */
1184
  if (priv->needs_destruction_ref)
1185
    {
1186
      priv->needs_destruction_ref = FALSE;
1187
      g_object_ref (widget);
1188
    }
1189

1190
  g_clear_object (&priv->accel_group);
1191

1192 1193
  if (priv->toplevel)
    gtk_widget_destroy (priv->toplevel);
1194

1195 1196
  if (priv->tearoff_window)
    gtk_widget_destroy (priv->tearoff_window);
1197

1198
  g_clear_pointer (&priv->heights, g_free);
1199

1200
  g_clear_pointer (&priv->title, g_free);
1201

1202 1203
  if (priv->position_func_data_destroy)
    {
1204 1205
      priv->position_func_data_destroy (priv->position_func_data);
      priv->position_func_data = NULL;
1206 1207 1208
      priv->position_func_data_destroy = NULL;
    }

1209
  GTK_WIDGET_CLASS (gtk_menu_parent_class)->destroy (widget);
1210 1211
}

1212 1213
static void
menu_change_screen (GtkMenu   *menu,
1214
                    GdkScreen *new_screen)
1215
{
1216
  GtkMenuPrivate *priv = menu->priv;
1217

Matthias Clasen's avatar
Matthias Clasen committed
1218
  if (gtk_widget_has_screen (GTK_WIDGET (menu)))
1219
    {
Matthias Clasen's avatar
Matthias Clasen committed
1220
      if (new_screen == gtk_widget_get_screen (GTK_WIDGET (menu)))
1221
        return;
1222 1223
    }

1224
  if (priv->torn_off)
1225
    {
1226
      gtk_window_set_screen (GTK_WINDOW (priv->tearoff_window), new_screen);
1227
      gtk_menu_position (menu, TRUE);
1228 1229
    }

1230 1231
  gtk_window_set_screen (GTK_WINDOW (priv->toplevel), new_screen);
  priv->monitor_num = -1;
1232 1233
}

1234 1235
static void
attach_widget_screen_changed (GtkWidget *attach_widget,
1236 1237
                              GdkScreen *previous_screen,
                              GtkMenu   *menu)
1238 1239 1240
{
  if (gtk_widget_has_screen (attach_widget) &&
      !g_object_get_data (G_OBJECT (menu), "gtk-menu-explicit-screen"))
1241
    menu_change_screen (menu, gtk_widget_get_screen (attach_widget));
1242 1243
}

1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255
static void
menu_toplevel_attached_to (GtkWindow *toplevel, GParamSpec *pspec, GtkMenu *menu)
{
  GtkMenuAttachData *data;

  data = g_object_get_data (G_OBJECT (menu), attach_data_key);

  g_return_if_fail (data);

  gtk_menu_detach (menu);
}

1256
/**
1257
 * gtk_menu_attach_to_widget:
1258 1259
 * @menu: a #GtkMenu
 * @attach_widget: the #GtkWidget that the menu will be attached to
1260 1261
 * @detacher: (scope async)(allow-none): the user supplied callback function
 *             that will be called when the menu calls gtk_menu_detach()
1262 1263 1264 1265
 *
 * Attaches the menu to the widget and provides a callback function
 * that will be invoked when the menu calls gtk_menu_detach() during
 * its destruction.
1266 1267 1268 1269 1270
 *
 * If the menu is attached to the widget then it will be destroyed
 * when the widget is destroyed, as if it was a child widget.
 * An attached menu will also move between screens correctly if the
 * widgets moves between screens.
1271
 */
1272
void
1273 1274 1275
gtk_menu_attach_to_widget (GtkMenu           *menu,
                           GtkWidget         *attach_widget,
                           GtkMenuDetachFunc  detacher)
1276 1277
{
  GtkMenuAttachData *data;
1278
  GList *list;
1279

1280 1281
  g_return_if_fail (GTK_IS_MENU (menu));
  g_return_if_fail (GTK_IS_WIDGET (attach_widget));
1282 1283

  /* keep this function in sync with gtk_widget_set_parent() */
Manish Singh's avatar
Manish Singh committed
1284
  data = g_object_get_data (G_OBJECT (menu), attach_data_key);
1285 1286 1287
  if (data)
    {
      g_warning ("gtk_menu_attach_to_widget(): menu already attached to %s",
1288
                 g_type_name (G_TYPE_FROM_INSTANCE (data->attach_widget)));
1289
     return;
1290
    }
1291

1292
  g_object_ref_sink (menu);
1293

1294
  data = g_slice_new (GtkMenuAttachData);
1295
  data->attach_widget = attach_widget;
1296

1297
  g_signal_connect (attach_widget, "screen-changed",
1298
                    G_CALLBACK (attach_widget_screen_changed), menu);
1299
  attach_widget_screen_changed (attach_widget, NULL, menu);
1300

1301
  data->detacher = detacher;
1302
  g_object_set_data (G_OBJECT (menu), I_(attach_data_key), data);
1303
  list = g_object_steal_data (G_OBJECT (attach_widget), ATTACHED_MENUS);
1304
  if (!g_list_find (list, menu))
1305 1306
    list = g_list_prepend (list, menu);

1307 1308 1309
  g_object_set_data_full (G_OBJECT (attach_widget), I_(ATTACHED_MENUS), list,
                          (GDestroyNotify) g_list_free);

1310 1311
  /* Attach the widget to the toplevel window. */
  gtk_window_set_attached_to (GTK_WINDOW (menu->priv->toplevel), attach_widget);
1312 1313
  g_signal_connect (GTK_WINDOW (menu->priv->toplevel), "notify::attached-to",
                    G_CALLBACK (menu_toplevel_attached_to), menu);
1314

1315 1316
  _gtk_widget_update_parent_muxer (GTK_WIDGET (menu));

1317 1318
  /* Fallback title for menu comes from attach widget */
  gtk_menu_update_title (menu);
1319 1320

  g_object_notify (G_OBJECT (menu), "attach-widget");
1321 1322
}

1323 1324 1325 1326 1327 1328
/**
 * gtk_menu_get_attach_widget:
 * @menu: a #GtkMenu
 *
 * Returns the #GtkWidget that the menu is attached to.
 *
1329
 * Returns: (transfer none): the #GtkWidget that the menu is attached to