gtkmenu.c 183 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 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
 * 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.  
 *
 * <example>
 * <title>Connecting the popup signal handler.</title>
 * <programlisting>
 *   /<!---->* connect our handler which will popup the menu *<!---->/
 *   g_signal_connect_swapped (window, "button_press_event",
 *	G_CALLBACK (my_popup_handler), menu);
 * </programlisting>
 * </example>
 *
 * <example>
 * <title>Signal handler which displays a popup menu.</title>
 * <programlisting>
 * 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);
 *
 *   /<!---->* The "widget" is the menu that was supplied when 
 *    * g_signal_connect_swapped() was called.
 *    *<!---->/
 *   menu = GTK_MENU (widget);
 *
 *   if (event->type == GDK_BUTTON_PRESS)
 *     {
 *       event_button = (GdkEventButton *) event;
76
 *       if (event_button->button == GDK_BUTTON_SECONDARY)
77 78 79 80 81 82 83 84 85 86 87 88 89
 *         {
 *           gtk_menu_popup (menu, NULL, NULL, NULL, NULL, 
 *                           event_button->button, event_button->time);
 *           return TRUE;
 *         }
 *     }
 * 
 *   return FALSE;
 * }
 * </programlisting>
 * </example>
 */

90
#include "config.h"
91

92
#include <string.h>
93 94 95

#include  <gobject/gvaluecollector.h>

Matthias Clasen's avatar
Matthias Clasen committed
96
#include "gtkaccellabel.h"
97
#include "gtkaccelmap.h"
98
#include "gtkadjustment.h"
99
#include "gtkbindings.h"
100
#include "gtkcheckmenuitem.h"
Elliot Lee's avatar
Elliot Lee committed
101
#include "gtkmain.h"
102
#include "gtkmarshalers.h"
103
#include "gtkmenuprivate.h"
104
#include "gtkmenuitemprivate.h"
105
#include "gtkmenushellprivate.h"
106
#include "gtkwindow.h"
Matthias Clasen's avatar
Matthias Clasen committed
107
#include "gtkbox.h"
108
#include "gtkscrollbar.h"
Havoc Pennington's avatar
Havoc Pennington committed
109
#include "gtksettings.h"
110
#include "gtkprivate.h"
111
#include "gtkwidgetpath.h"
112
#include "gtkwidgetprivate.h"
113
#include "gtkdnd.h"
114
#include "gtkintl.h"
115
#include "gtktypebuiltins.h"
116
#include "gtkwidgetprivate.h"
117

118
#include "deprecated/gtktearoffmenuitem.h"
119 120


121
#include "a11y/gtkmenuaccessible.h"
Elliot Lee's avatar
Elliot Lee committed
122

123
#define NAVIGATION_REGION_OVERSHOOT 50  /* How much the navigation region
124 125
                                         * extends below the submenu
                                         */
126

127 128 129 130 131
#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
132

133
#define ATTACH_INFO_KEY "gtk-menu-child-attach-info-key"
134
#define ATTACHED_MENUS "gtk-attached-menus"
135

136 137
typedef struct _GtkMenuAttachData  GtkMenuAttachData;
typedef struct _GtkMenuPopdownData GtkMenuPopdownData;
138 139 140 141 142 143 144

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

145 146 147 148 149 150
struct _GtkMenuPopdownData
{
  GtkMenu *menu;
  GdkDevice *device;
};

151 152
typedef struct
{
153 154 155 156 157 158 159 160 161
  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;
162

163 164 165 166 167
enum {
  MOVE_SCROLL,
  LAST_SIGNAL
};

168 169
enum {
  PROP_0,
Tim Janik's avatar
Tim Janik committed
170 171 172 173
  PROP_ACTIVE,
  PROP_ACCEL_GROUP,
  PROP_ACCEL_PATH,
  PROP_ATTACH_WIDGET,
174
  PROP_TEAROFF_STATE,
Tim Janik's avatar
Tim Janik committed
175
  PROP_TEAROFF_TITLE,
176 177
  PROP_MONITOR,
  PROP_RESERVE_TOGGLE_SIZE
178
};
179

180
enum {
181 182 183 184 185 186 187 188
  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,
189 190 191
                                            guint             prop_id,
                                            const GValue     *value,
                                            GParamSpec       *pspec);
192
static void     gtk_menu_get_property      (GObject          *object,
193 194 195
                                            guint             prop_id,
                                            GValue           *value,
                                            GParamSpec       *pspec);
196 197 198 199 200 201 202 203 204 205
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);
206
static void     gtk_menu_destroy           (GtkWidget        *widget);
207 208 209
static void     gtk_menu_realize           (GtkWidget        *widget);
static void     gtk_menu_unrealize         (GtkWidget        *widget);
static void     gtk_menu_size_allocate     (GtkWidget        *widget,
210
                                            GtkAllocation    *allocation);
211
static void     gtk_menu_show              (GtkWidget        *widget);
Benjamin Otte's avatar
Benjamin Otte committed
212 213
static gboolean gtk_menu_draw              (GtkWidget        *widget,
                                            cairo_t          *cr);
214
static gboolean gtk_menu_key_press         (GtkWidget        *widget,
215
                                            GdkEventKey      *event);
216
static gboolean gtk_menu_scroll            (GtkWidget        *widget,
217
                                            GdkEventScroll   *event);
218
static gboolean gtk_menu_button_press      (GtkWidget        *widget,
219
                                            GdkEventButton   *event);
220
static gboolean gtk_menu_button_release    (GtkWidget        *widget,
221
                                            GdkEventButton   *event);
222
static gboolean gtk_menu_motion_notify     (GtkWidget        *widget,
223
                                            GdkEventMotion   *event);
224
static gboolean gtk_menu_enter_notify      (GtkWidget        *widget,
225
                                            GdkEventCrossing *event);
226
static gboolean gtk_menu_leave_notify      (GtkWidget        *widget,
227
                                            GdkEventCrossing *event);
228
static void     gtk_menu_scroll_to         (GtkMenu          *menu,
229
                                            gint              offset);
230
static void     gtk_menu_grab_notify       (GtkWidget        *widget,
231
                                            gboolean          was_grabbed);
232 233 234
static gboolean gtk_menu_captured_event    (GtkWidget        *widget,
                                            GdkEvent         *event);

235

236 237 238
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);
239

240
static void     gtk_menu_scroll_item_visible (GtkMenuShell    *menu_shell,
241
                                              GtkWidget       *menu_item);
242
static void     gtk_menu_select_item       (GtkMenuShell     *menu_shell,
243
                                            GtkWidget        *menu_item);
244
static void     gtk_menu_real_insert       (GtkMenuShell     *menu_shell,
245 246
                                            GtkWidget        *child,
                                            gint              position);
247
static void     gtk_menu_scrollbar_changed (GtkAdjustment    *adjustment,
248
                                            GtkMenu          *menu);
249
static void     gtk_menu_handle_scrolling  (GtkMenu          *menu,
250 251 252
                                            gint              event_x,
                                            gint              event_y,
                                            gboolean          enter,
253
                                            gboolean          motion);
254
static void     gtk_menu_set_tearoff_hints (GtkMenu          *menu,
255
                                            gint             width);
256
static void     gtk_menu_style_updated     (GtkWidget        *widget);
257
static gboolean gtk_menu_focus             (GtkWidget        *widget,
258
                                            GtkDirectionType direction);
259
static gint     gtk_menu_get_popup_delay   (GtkMenuShell     *menu_shell);
260 261
static void     gtk_menu_move_current      (GtkMenuShell     *menu_shell,
                                            GtkMenuDirectionType direction);
262
static void     gtk_menu_real_move_scroll  (GtkMenu          *menu,
263
                                            GtkScrollType     type);
264 265 266 267

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,
268 269
                                                        gint              event_x,
                                                        gint              event_y);
270
static void     gtk_menu_set_submenu_navigation_region (GtkMenu          *menu,
271 272
                                                        GtkMenuItem      *menu_item,
                                                        GdkEventCrossing *event);
273
 
274
static void gtk_menu_deactivate     (GtkMenuShell      *menu_shell);
275
static void gtk_menu_show_all       (GtkWidget         *widget);
276 277
static void gtk_menu_position       (GtkMenu           *menu,
                                     gboolean           set_scroll_offset);
278 279 280
static void gtk_menu_reparent       (GtkMenu           *menu,
                                     GtkWidget         *new_parent,
                                     gboolean           unrealize);
281
static void gtk_menu_remove         (GtkContainer      *menu,
282
                                     GtkWidget         *widget);
Elliot Lee's avatar
Elliot Lee committed
283

284 285
static void gtk_menu_update_title   (GtkMenu           *menu);

286 287 288
static void       menu_grab_transfer_window_destroy (GtkMenu *menu);
static GdkWindow *menu_grab_transfer_window_get     (GtkMenu *menu);

289 290
static gboolean gtk_menu_real_can_activate_accel (GtkWidget *widget,
                                                  guint      signal_id);
291
static void _gtk_menu_refresh_accel_paths (GtkMenu *menu,
292
                                           gboolean group_changed);
293

294 295 296 297 298 299 300 301 302 303
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);
304 305


306
static const gchar attach_data_key[] = "gtk-menu-attach-data";
307

308 309
static guint menu_signals[LAST_SIGNAL] = { 0 };

310
G_DEFINE_TYPE (GtkMenu, gtk_menu, GTK_TYPE_MENU_SHELL)
Elliot Lee's avatar
Elliot Lee committed
311

312 313 314
static void
menu_queue_resize (GtkMenu *menu)
{
315
  GtkMenuPrivate *priv = menu->priv;
316 317 318 319 320

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

321 322 323 324 325 326
static void
attach_info_free (AttachInfo *info)
{
  g_slice_free (AttachInfo, info);
}

327 328 329 330 331 332 333 334
static AttachInfo *
get_attach_info (GtkWidget *child)
{
  GObject *object = G_OBJECT (child);
  AttachInfo *ai = g_object_get_data (object, ATTACH_INFO_KEY);

  if (!ai)
    {
335
      ai = g_slice_new0 (AttachInfo);
336 337
      g_object_set_data_full (object, I_(ATTACH_INFO_KEY), ai,
                              (GDestroyNotify) attach_info_free);
338 339 340 341 342 343 344 345 346
    }

  return ai;
}

static gboolean
is_grid_attached (AttachInfo *ai)
{
  return (ai->left_attach >= 0 &&
347 348 349
          ai->right_attach >= 0 &&
          ai->top_attach >= 0 &&
          ai->bottom_attach >= 0);
350 351 352 353 354
}

static void
menu_ensure_layout (GtkMenu *menu)
{
355
  GtkMenuPrivate *priv = menu->priv;
356 357 358 359 360 361 362

  if (!priv->have_layout)
    {
      GtkMenuShell *menu_shell = GTK_MENU_SHELL (menu);
      GList *l;
      gchar *row_occupied;
      gint current_row;
363
      gint max_right_attach;
364 365 366 367 368 369 370
      gint max_bottom_attach;

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

371
      for (l = menu_shell->priv->children; l; l = l->next)
372 373 374 375 376 377 378 379 380 381 382 383
        {
          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 */
384 385
      row_occupied = g_malloc0 (max_bottom_attach);

386
      for (l = menu_shell->priv->children; l; l = l->next)
387 388 389
        {
          GtkWidget *child = l->data;
          AttachInfo *ai = get_attach_info (child);
390

391 392 393
          if (is_grid_attached (ai))
            {
              gint i;
394

395 396 397 398
              for (i = ai->top_attach; i < ai->bottom_attach; i++)
                row_occupied[i] = TRUE;
            }
        }
399 400 401 402

      /* Lay non-grid-items out in those rows
       */
      current_row = 0;
403
      for (l = menu_shell->priv->children; l; l = l->next)
404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427
        {
          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;
            }
        }
428 429 430 431 432 433 434 435 436 437 438 439 440

      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)
{
441
  GtkMenuPrivate *priv = menu->priv;
442 443 444 445 446 447 448 449 450

  menu_ensure_layout (menu);

  return priv->n_columns;
}

static gint
gtk_menu_get_n_rows (GtkMenu *menu)
{
451
  GtkMenuPrivate *priv = menu->priv;
452 453 454 455 456 457 458 459

  menu_ensure_layout (menu);

  return priv->n_rows;
}

static void
get_effective_child_attach (GtkWidget *child,
460 461 462 463
                            int       *l,
                            int       *r,
                            int       *t,
                            int       *b)
464
{
465
  GtkMenu *menu = GTK_MENU (gtk_widget_get_parent (child));
466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482
  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
483 484 485
static void
gtk_menu_class_init (GtkMenuClass *class)
{
486 487 488 489
  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);
490
  GtkBindingSet *binding_set;
Tim Janik's avatar
Tim Janik committed
491
  
492 493 494
  gobject_class->set_property = gtk_menu_set_property;
  gobject_class->get_property = gtk_menu_get_property;

495
  widget_class->destroy = gtk_menu_destroy;
496 497 498 499
  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
500
  widget_class->draw = gtk_menu_draw;
501
  widget_class->scroll_event = gtk_menu_scroll;
502 503 504 505 506 507 508
  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;
509
  widget_class->style_updated = gtk_menu_style_updated;
510
  widget_class->focus = gtk_menu_focus;
511
  widget_class->can_activate_accel = gtk_menu_real_can_activate_accel;
512
  widget_class->grab_notify = gtk_menu_grab_notify;
513 514 515
  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;
516 517 518 519 520 521 522 523 524 525 526 527

  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;

528 529 530 531 532
  /**
   * GtkMenu::move-scroll:
   * @menu: a #GtkMenu
   * @scroll_type: a #GtkScrollType
   */
533
  menu_signals[MOVE_SCROLL] =
534
    g_signal_new_class_handler (I_("move-scroll"),
535
                                G_OBJECT_CLASS_TYPE (gobject_class),
536 537 538 539 540 541
                                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
542 543 544 545

  /**
   * GtkMenu:active:
   *
546 547
   * The index of the currently selected menu item, or -1 if no
   * menu item is selected.
Tim Janik's avatar
Tim Janik committed
548
   *
549
   * Since: 2.14
Tim Janik's avatar
Tim Janik committed
550 551 552
   **/
  g_object_class_install_property (gobject_class,
                                   PROP_ACTIVE,
553
                                   g_param_spec_int ("active",
554 555 556 557
                                                     P_("Active"),
                                                     P_("The currently selected menu item"),
                                                     -1, G_MAXINT, -1,
                                                     GTK_PARAM_READWRITE));
Tim Janik's avatar
Tim Janik committed
558 559 560 561 562 563

  /**
   * GtkMenu:accel-group:
   *
   * The accel group holding accelerators for the menu.
   *
564
   * Since: 2.14
Tim Janik's avatar
Tim Janik committed
565 566 567 568
   **/
  g_object_class_install_property (gobject_class,
                                   PROP_ACCEL_GROUP,
                                   g_param_spec_object ("accel-group",
569 570 571 572
                                                        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
573 574 575 576 577 578

  /**
   * GtkMenu:accel-path:
   *
   * An accel path used to conveniently construct accel paths of child items.
   *
579
   * Since: 2.14
Tim Janik's avatar
Tim Janik committed
580 581 582 583
   **/
  g_object_class_install_property (gobject_class,
                                   PROP_ACCEL_PATH,
                                   g_param_spec_string ("accel-path",
584 585 586 587
                                                        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
588 589 590 591

  /**
   * GtkMenu:attach-widget:
   *
Matthias Clasen's avatar
Matthias Clasen committed
592 593 594
   * 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
595
   *
596
   * Since: 2.14
Tim Janik's avatar
Tim Janik committed
597 598
   **/
  g_object_class_install_property (gobject_class,
Matthias Clasen's avatar
Matthias Clasen committed
599 600
                                   PROP_ATTACH_WIDGET,
                                   g_param_spec_object ("attach-widget",
601 602 603 604
                                                        P_("Attach Widget"),
                                                        P_("The widget the menu is attached to"),
                                                        GTK_TYPE_WIDGET,
                                                        GTK_PARAM_READWRITE));
Tim Janik's avatar
Tim Janik committed
605

606 607 608
  g_object_class_install_property (gobject_class,
                                   PROP_TEAROFF_TITLE,
                                   g_param_spec_string ("tearoff-title",
609 610
                                                        P_("Tearoff Title"),
                                                        P_("A title that may be displayed by the window manager when this menu is torn-off"),
611
                                                        NULL,
612
                                                        GTK_PARAM_READWRITE));
613 614 615 616 617 618 619 620 621 622 623

  /**
   * GtkMenu:tearoff-state:
   *
   * A boolean that indicates whether the menu is torn-off.
   *
   * Since: 2.6
   **/
  g_object_class_install_property (gobject_class,
                                   PROP_TEAROFF_STATE,
                                   g_param_spec_boolean ("tearoff-state",
624 625 626 627
                                                         P_("Tearoff State"),
                                                         P_("A boolean that indicates whether the menu is torn-off"),
                                                         FALSE,
                                                         GTK_PARAM_READWRITE));
Manish Singh's avatar
Manish Singh committed
628

Tim Janik's avatar
Tim Janik committed
629 630 631 632 633
  /**
   * GtkMenu:monitor:
   *
   * The monitor the menu will be popped up on.
   *
634
   * Since: 2.14
Tim Janik's avatar
Tim Janik committed
635 636 637 638
   **/
  g_object_class_install_property (gobject_class,
                                   PROP_MONITOR,
                                   g_param_spec_int ("monitor",
639 640 641 642
                                                     P_("Monitor"),
                                                     P_("The monitor the menu will be popped up on"),
                                                     -1, G_MAXINT, -1,
                                                     GTK_PARAM_READWRITE));
Tim Janik's avatar
Tim Janik committed
643

644
  gtk_widget_class_install_style_property (widget_class,
645 646 647 648 649 650 651
                                           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));
652

653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668
  /**
   * 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",
669 670 671 672
                                                         P_("Reserve Toggle Size"),
                                                         P_("A boolean that indicates whether the menu reserves space for toggles and icons"),
                                                         TRUE,
                                                         GTK_PARAM_READWRITE));
673

674 675 676 677 678 679 680 681 682
  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,
                                                             GTK_PARAM_READABLE));

683
  gtk_widget_class_install_style_property (widget_class,
684 685 686 687 688 689 690
                                           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));
691 692

  gtk_widget_class_install_style_property (widget_class,
693 694 695 696 697 698 699
                                           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));
700

701 702 703 704 705 706 707
  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));

708 709 710 711 712 713 714 715 716 717 718 719 720 721
  /**
   * 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));
722

723 724
 gtk_container_class_install_child_property (container_class,
                                             CHILD_PROP_LEFT_ATTACH,
725
                                              g_param_spec_int ("left-attach",
726 727
                                                               P_("Left Attach"),
                                                               P_("The column number to attach the left side of the child to"),
728
                                                                -1, INT_MAX, -1,
729
                                                               GTK_PARAM_READWRITE));
730 731 732

 gtk_container_class_install_child_property (container_class,
                                             CHILD_PROP_RIGHT_ATTACH,
733
                                              g_param_spec_int ("right-attach",
734 735
                                                               P_("Right Attach"),
                                                               P_("The column number to attach the right side of the child to"),
736
                                                                -1, INT_MAX, -1,
737
                                                               GTK_PARAM_READWRITE));
738 739 740

 gtk_container_class_install_child_property (container_class,
                                             CHILD_PROP_TOP_ATTACH,
741
                                              g_param_spec_int ("top-attach",
742 743
                                                               P_("Top Attach"),
                                                               P_("The row number to attach the top of the child to"),
744
                                                                -1, INT_MAX, -1,
745
                                                               GTK_PARAM_READWRITE));
746 747 748

 gtk_container_class_install_child_property (container_class,
                                             CHILD_PROP_BOTTOM_ATTACH,
749
                                              g_param_spec_int ("bottom-attach",
750 751
                                                               P_("Bottom Attach"),
                                                               P_("The row number to attach the bottom of the child to"),
752
                                                                -1, INT_MAX, -1,
753
                                                               GTK_PARAM_READWRITE));
754

755
 /**
756
  * GtkMenu:arrow-scaling:
757 758 759 760 761 762 763 764 765 766 767 768
  *
  * 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));

769 770
  binding_set = gtk_binding_set_by_class (class);
  gtk_binding_entry_add_signal (binding_set,
771 772 773 774
                                GDK_KEY_Up, 0,
                                I_("move-current"), 1,
                                GTK_TYPE_MENU_DIRECTION_TYPE,
                                GTK_MENU_DIR_PREV);
775
  gtk_binding_entry_add_signal (binding_set,
776 777 778 779
                                GDK_KEY_KP_Up, 0,
                                "move-current", 1,
                                GTK_TYPE_MENU_DIRECTION_TYPE,
                                GTK_MENU_DIR_PREV);
780
  gtk_binding_entry_add_signal (binding_set,
781 782 783 784
                                GDK_KEY_Down, 0,
                                "move-current", 1,
                                GTK_TYPE_MENU_DIRECTION_TYPE,
                                GTK_MENU_DIR_NEXT);
785
  gtk_binding_entry_add_signal (binding_set,
786 787 788 789
                                GDK_KEY_KP_Down, 0,
                                "move-current", 1,
                                GTK_TYPE_MENU_DIRECTION_TYPE,
                                GTK_MENU_DIR_NEXT);
790
  gtk_binding_entry_add_signal (binding_set,
791 792 793 794
                                GDK_KEY_Left, 0,
                                "move-current", 1,
                                GTK_TYPE_MENU_DIRECTION_TYPE,
                                GTK_MENU_DIR_PARENT);
795
  gtk_binding_entry_add_signal (binding_set,
796 797 798 799
                                GDK_KEY_KP_Left, 0,
                                "move-current", 1,
                                GTK_TYPE_MENU_DIRECTION_TYPE,
                                GTK_MENU_DIR_PARENT);
800
  gtk_binding_entry_add_signal (binding_set,
801 802 803 804
                                GDK_KEY_Right, 0,
                                "move-current", 1,
                                GTK_TYPE_MENU_DIRECTION_TYPE,
                                GTK_MENU_DIR_CHILD);
805
  gtk_binding_entry_add_signal (binding_set,
806 807 808 809
                                GDK_KEY_KP_Right, 0,
                                "move-current", 1,
                                GTK_TYPE_MENU_DIRECTION_TYPE,
                                GTK_MENU_DIR_CHILD);
810
  gtk_binding_entry_add_signal (binding_set,
811 812 813 814
                                GDK_KEY_Home, 0,
                                "move-scroll", 1,
                                GTK_TYPE_SCROLL_TYPE,
                                GTK_SCROLL_START);
815
  gtk_binding_entry_add_signal (binding_set,
816 817 818 819
                                GDK_KEY_KP_Home, 0,
                                "move-scroll", 1,
                                GTK_TYPE_SCROLL_TYPE,
                                GTK_SCROLL_START);
820
  gtk_binding_entry_add_signal (binding_set,
821 822 823 824
                                GDK_KEY_End, 0,
                                "move-scroll", 1,
                                GTK_TYPE_SCROLL_TYPE,
                                GTK_SCROLL_END);
825
  gtk_binding_entry_add_signal (binding_set,
826 827 828 829
                                GDK_KEY_KP_End, 0,
                                "move-scroll", 1,
                                GTK_TYPE_SCROLL_TYPE,
                                GTK_SCROLL_END);
830
  gtk_binding_entry_add_signal (binding_set,
831 832 833 834
                                GDK_KEY_Page_Up, 0,
                                "move-scroll", 1,
                                GTK_TYPE_SCROLL_TYPE,
                                GTK_SCROLL_PAGE_UP);
835
  gtk_binding_entry_add_signal (binding_set,
836 837 838 839
                                GDK_KEY_KP_Page_Up, 0,
                                "move-scroll", 1,
                                GTK_TYPE_SCROLL_TYPE,
                                GTK_SCROLL_PAGE_UP);
840
  gtk_binding_entry_add_signal (binding_set,
841 842 843 844
                                GDK_KEY_Page_Down, 0,
                                "move-scroll", 1,
                                GTK_TYPE_SCROLL_TYPE,
                                GTK_SCROLL_PAGE_DOWN);
845
  gtk_binding_entry_add_signal (binding_set,
846 847 848 849
                                GDK_KEY_KP_Page_Down, 0,
                                "move-scroll", 1,
                                GTK_TYPE_SCROLL_TYPE,
                                GTK_SCROLL_PAGE_DOWN);
850

851
  g_type_class_add_private (gobject_class, sizeof (GtkMenuPrivate));
852 853

  gtk_widget_class_set_accessible_type (widget_class, GTK_TYPE_MENU_ACCESSIBLE);
Elliot Lee's avatar
Elliot Lee committed
854 855
}

856

857
static void
858
gtk_menu_set_property (GObject      *object,
859 860 861
                       guint         prop_id,
                       const GValue *value,
                       GParamSpec   *pspec)
862
{
863 864
  GtkMenu *menu = GTK_MENU (object);

865 866
  switch (prop_id)
    {
Tim Janik's avatar
Tim Janik committed
867
    case PROP_ACTIVE:
868
      gtk_menu_set_active (menu, g_value_get_int (value));
Tim Janik's avatar
Tim Janik committed
869 870 871 872 873 874 875 876
      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
877 878 879 880 881 882 883 884 885 886 887
      {
        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
888
      break;
889 890 891
    case PROP_TEAROFF_STATE:
      gtk_menu_set_tearoff_state (menu, g_value_get_boolean (value));
      break;
892 893
    case PROP_TEAROFF_TITLE:
      gtk_menu_set_title (menu, g_value_get_string (value));
Tim Janik's avatar
Tim Janik committed
894 895 896 897
      break;
    case PROP_MONITOR:
      gtk_menu_set_monitor (menu, g_value_get_int (value));
      break;
898 899 900
    case PROP_RESERVE_TOGGLE_SIZE:
      gtk_menu_set_reserve_toggle_size (menu, g_value_get_boolean (value));
      break;
901 902 903 904 905 906
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}

907
static void
908
gtk_menu_get_property (GObject     *object,
909 910 911
                       guint        prop_id,
                       GValue      *value,
                       GParamSpec  *pspec)
912
{
913 914
  GtkMenu *menu = GTK_MENU (object);

915 916
  switch (prop_id)
    {
Tim Janik's avatar
Tim Janik committed
917
    case PROP_ACTIVE:
918
      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
919 920 921 922 923 924 925 926 927 928
      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;
929 930 931
    case PROP_TEAROFF_STATE:
      g_value_set_boolean (value, gtk_menu_get_tearoff_state (menu));
      break;
932
    case PROP_TEAROFF_TITLE:
933
      g_value_set_string (value, gtk_menu_get_title (menu));
934
      break;
Tim Janik's avatar
Tim Janik committed
935 936 937
    case PROP_MONITOR:
      g_value_set_int (value, gtk_menu_get_monitor (menu));
      break;
938 939 940
    case PROP_RESERVE_TOGGLE_SIZE:
      g_value_set_boolean (value, gtk_menu_get_reserve_toggle_size (menu));
      break;
941 942 943 944 945 946
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}

947 948 949 950 951 952 953 954
static void
gtk_menu_set_child_property (GtkContainer *container,
                             GtkWidget    *child,
                             guint         property_id,
                             const GValue *value,
                             GParamSpec   *pspec)
{
  GtkMenu *menu = GTK_MENU (container);
955
  AttachInfo *ai = get_attach_info (child);
956 957 958

  switch (property_id)
    {
959 960 961 962 963 964 965
    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:
966
      ai->top_attach = g_value_get_int (value);
967 968 969 970 971 972 973 974
      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;
975 976
    }

977
  menu_queue_resize (menu);
978 979 980 981 982 983 984 985 986
}

static void
gtk_menu_get_child_property (GtkContainer *container,
                             GtkWidget    *child,
                             guint         property_id,
                             GValue       *value,
                             GParamSpec   *pspec)
{
987
  AttachInfo *ai = get_attach_info (child);
988 989 990

  switch (property_id)
    {
991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006
    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;
1007 1008 1009
    }
}

1010
static gboolean
1011
gtk_menu_window_event (GtkWidget *window,
1012 1013
                       GdkEvent  *event,
                       GtkWidget *menu)
1014 1015 1016
{
  gboolean handled = FALSE;

Manish Singh's avatar
Manish Singh committed
1017 1018
  g_object_ref (window);
  g_object_ref (menu);
1019 1020 1021 1022 1023

  switch (event->type)
    {
    case GDK_KEY_PRESS:
    case GDK_KEY_RELEASE:
1024
      handled = gtk_widget_event (menu, event);
1025
      break;
1026 1027 1028 1029 1030 1031 1032 1033 1034
    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;
1035 1036 1037 1038
    default:
      break;
    }

Manish Singh's avatar
Manish Singh committed
1039 1040
  g_object_unref (window);
  g_object_unref (menu);
1041 1042 1043 1044

  return handled;
}

Elliot Lee's avatar
Elliot Lee committed
1045 1046 1047
static void
gtk_menu_init (GtkMenu *menu)
{
1048
  GtkMenuPrivate *priv;
1049
  GtkStyleContext *context;
1050

1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063
  priv = G_TYPE_INSTANCE_GET_PRIVATE (menu, GTK_TYPE_MENU, GtkMenuPrivate);

  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);
1064

Owen Taylor's avatar
Owen Taylor committed
1065 1066 1067
  /* Refloat the menu, so that reference counting for the menu isn't
   * affected by it being a child of the toplevel
   */
1068
  g_object_force_floating (G_OBJECT (menu));
1069
  priv->needs_destruction_ref = TRUE;
Owen Taylor's avatar
Owen Taylor committed
1070

1071
  priv->monitor_num = -1;
1072
  priv->drag_start_y = -1;
1073 1074 1075

  context = gtk_widget_get_style_context (GTK_WIDGET (menu));
  gtk_style_context_add_class (context, GTK_STYLE_CLASS_MENU);
1076 1077

  _gtk_widget_set_captured_event_handler (GTK_WIDGET (menu), gtk_menu_captured_event);
1078 1079 1080
}

static void
1081
gtk_menu_destroy (GtkWidget *widget)
1082
{
1083
  GtkMenu *menu = GTK_MENU (widget);
1084
  GtkMenuPrivate *priv = menu->priv;
1085
  GtkMenuAttachData *data;
1086

1087
  gtk_menu_remove_scroll_timeout (menu);
1088

1089
  data = g_object_get_data (G_OBJECT (widget), attach_data_key);
1090
  if (data)
1091
    gtk_menu_detach (menu);
1092

1093 1094
  gtk_menu_stop_navigating_submenu (menu);

1095 1096
  if (priv->old_active_menu_item)
    g_clear_object (&priv->old_active_menu_item);
1097

Owen Taylor's avatar
Owen Taylor committed
1098
  /* Add back the reference count for being a child */
1099
  if (priv->needs_destruction_ref)
1100
    {
1101
      priv->needs_destruction_ref = FALSE;
1102
      g_object_ref (widget);
1103
    }
1104

1105 1106
  if (priv->accel_group)
    g_clear_object (&priv->accel_group);
1107

1108 1109
  if (priv->toplevel)
    gtk_widget_destroy (priv->toplevel);
1110

1111 1112
  if (priv->tearoff_window)
    gtk_widget_destroy (priv->tearoff_window);
1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125

  if (priv->heights)
    {
      g_free (priv->heights);
      priv->heights = NULL;
    }

  if (priv->title)
    {
      g_free (priv->title);
      priv->title = NULL;
    }

1126 1127
  if (priv->position_func_data_destroy)
    {
1128 1129
      priv->position_func_data_destroy (priv->position_func_data);
      priv->position_func_data = NULL;
1130 1131 1132
      priv->position_func_data_destroy = NULL;
    }

1133
  GTK_WIDGET_CLASS (gtk_menu_parent_class)->destroy (widget);
1134 1135
}

1136 1137
static void
menu_change_screen (GtkMenu   *menu,
1138
                    GdkScreen *new_screen)
1139
{
1140
  GtkMenuPrivate *priv = menu->priv;
1141

Matthias Clasen's avatar
Matthias Clasen committed
1142
  if (gtk_widget_has_screen (GTK_WIDGET (menu)))
1143
    {
Matthias Clasen's avatar
Matthias Clasen committed
1144
      if (new_screen == gtk_widget_get_screen (GTK_WIDGET (menu)))
1145
        return;
1146 1147
    }

1148
  if (priv->torn_off)
1149
    {
1150
      gtk_window_set_screen (GTK_WINDOW (priv->tearoff_window), new_screen);
1151
      gtk_menu_position (menu, TRUE);
1152 1153
    }

1154 1155
  gtk_window_set_screen (GTK_WINDOW (priv->toplevel), new_screen);
  priv->monitor_num = -1;
1156 1157
}

1158 1159
static void
attach_widget_screen_changed (GtkWidget *attach_widget,
1160 1161
                              GdkScreen *previous_screen,
                              GtkMenu   *menu)
1162 1163 1164
{
  if (gtk_widget_has_screen (attach_widget) &&
      !g_object_get_data (G_OBJECT (menu), "gtk-menu-explicit-screen"))
1165
    menu_change_screen (menu, gtk_widget_get_screen (attach_widget));
1166 1167
}

1168
/**
1169
 * gtk_menu_attach_to_widget:
1170 1171
 * @menu: a #GtkMenu
 * @attach_widget: the #GtkWidget that the menu will be attached to
1172 1173
 * @detacher: (scope async)(allow-none): the user supplied callback function
 *             that will be called when the menu calls gtk_menu_detach()
1174 1175 1176 1177 1178
 *
 * 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.
 */
1179
void
1180 1181 1182
gtk_menu_attach_to_widget (GtkMenu           *menu,
                           GtkWidget         *attach_widget,
                           GtkMenuDetachFunc  detacher)
1183 1184
{
  GtkMenuAttachData *data;
1185
  GList *list;
1186

1187 1188
  g_return_if_fail (GTK_IS_MENU (menu));
  g_return_if_fail (GTK_IS_WIDGET (attach_widget));
1189 1190

  /* keep this function in sync with gtk_widget_set_parent() */
Manish Singh's avatar
Manish Singh committed
1191
  data = g_object_get_data (G_OBJECT (menu), attach_data_key);
1192 1193 1194
  if (data)
    {
      g_warning ("gtk_menu_attach_to_widget(): menu already attached to %s",
1195
                 g_type_name (G_TYPE_FROM_INSTANCE (data->attach_widget)));
1196
     return;
1197
    }
1198

1199
  g_object_ref_sink (menu);
1200

1201
  data = g_slice_new (GtkMenuAttachData);
1202
  data->attach_widget = attach_widget;
1203

1204
  g_signal_connect (attach_widget, "screen-changed",
1205
                    G_CALLBACK (attach_widget_screen_changed), menu);
1206
  attach_widget_screen_changed (attach_widget, NULL, menu);
1207

1208
  data->detacher = detacher;
1209
  g_object_set_data (G_OBJECT (menu), I_(attach_data_key), data);
1210
  list = g_object_steal_data (G_OBJECT (attach_widget), ATTACHED_MENUS);
1211
  if (!g_list_find (list, menu))
1212 1213
    list = g_list_prepend (list, menu);

1214 1215 1216
  g_object_set_data_full (G_OBJECT (attach_widget), I_(ATTACHED_MENUS), list,
                          (GDestroyNotify) g_list_free);

1217 1218 1219
  if (gtk_widget_get_state_flags (GTK_WIDGET (menu)) != 0)
    gtk_widget_set_state_flags (GTK_WIDGET (menu), 0, TRUE);

1220 1221
  /* Attach the widget to the toplevel window. */
  gtk_window_set_attached_to (GTK_WINDOW (menu->priv->toplevel), attach_widget);
1222

1223 1224
  _gtk_widget_update_parent_muxer (GTK_WIDGET (menu));

1225 1226
  /* Fallback title for menu comes from attach widget */
  gtk_menu_update_title (menu);
1227 1228

  g_object_notify (G_OBJECT (menu), "attach-widget");
1229 1230
}

1231 1232 1233 1234 1235 1236
/**
 * gtk_menu_get_attach_widget:
 * @menu: a #GtkMenu
 *
 * Returns the #GtkWidget that the menu is attached to.
 *
1237
 * Returns: (transfer none): the #GtkWidget that the menu is attached to
1238
 */
1239
GtkWidget*
1240
gtk_menu_get_attach_widget (GtkMenu *menu)
1241 1242
{
  GtkMenuAttachData *data;
1243

1244
  g_return_val_if_fail (GTK_IS_MENU (menu), NULL);
1245

Manish Singh's avatar
Manish Singh committed
1246
  data = g_object_get_data (G_OBJECT (menu), attach_data_key);
1247 1248 1249 1250 1251
  if (data)
    return data->attach_widget;
  return NULL;
}

1252 1253 1254 1255 1256 1257 1258 1259
/**
 * gtk_menu_detach:
 * @menu: a #GtkMenu
 *
 * Detaches the menu from the widget to which it had been attached.
 * This function will call the callback function, @detacher, provided
 * when the gtk_menu_attach_to_widget() function was called.
 */
1260
void
1261
gtk_menu_detach (GtkMenu *menu)
1262 1263
{
  GtkMenuAttachData *data;
1264
  GList *list;
1265

1266
  g_return_if_fail (GTK_IS_MENU (menu));
1267 1268

  /* keep this function in sync with gtk_widget_unparent() */
Manish Singh's avatar
Manish Singh committed
1269
  data = g_object_get_data (G_OBJECT (menu), attach_data_key);
1270 1271 1272 1273 1274
  if (!data)
    {
      g_warning ("gtk_menu_detach(): menu is not attached");
      return;
    }
1275
  g_object_set_data (G_OBJECT (menu), I_(attach_data_key), NULL);
1276

1277 1278 1279
  /* Detach the toplevel window. */
  gtk_window_set_attached_to (GTK_WINDOW (menu->priv->toplevel), NULL);

1280
  g_signal_handlers_disconnect_by_func (data->attach_widget,
1281 1282
                                        (gpointer) attach_widget_screen_changed,
                                        menu);
1283

1284 1285
  if (data->detacher)
    data->detacher (data->attach_widget, menu);
1286 1287 1288
  list = g_object_steal_data (G_OBJECT (data->attach_widget), ATTACHED_MENUS);
  list = g_list_remove (list, menu);
  if (list)
1289 1290
    g_object_set_data_full (G_OBJECT (data->attach_widget), I_(ATTACHED_MENUS), list,
                            (GDestroyNotify) g_list_free);
1291
  else
1292
    g_object_set_data (G_OBJECT (data->attach_widget), I_(ATTACHED_MENUS), NULL);
1293

1294
  if (gtk_widget_get_realized (GTK_WIDGET (menu)))
1295
    gtk_widget_unrealize (GTK_WIDGET (menu));
1296

1297
  g_slice_free (GtkMenuAttachData, data);
1298

1299 1300
  _gtk_widget_update_parent_muxer (GTK_WIDGET (menu));

1301 1302 1303
  /* Fallback title for menu comes from attach widget */
  gtk_menu_update_title (menu);

1304
  g_object_notify (G_OBJECT (menu), "attach-widget");
Manish Singh's avatar
Manish Singh committed
1305
  g_object_unref (menu);
Elliot Lee's avatar
Elliot Lee committed
1306 1307
}

1308
static void
1309
gtk_menu_remove (GtkContainer *container,
1310
                 GtkWidget    *widget)
1311
{
1312
  GtkMenu *menu = GTK_MENU (container);
1313
  GtkMenuPrivate *priv = menu->priv;
1314

1315 1316 1317
  /* Clear out old_active_menu_item if it matches the item we are removing */
  if (priv->old_active_menu_item == widget)
    g_clear_object (&priv->old_active_menu_item);
1318

Matthias Clasen's avatar
Matthias Clasen committed
1319
  GTK_CONTAINER_CLASS (gtk_menu_parent_class)->remove (container, widget);
1320

1321
  g_object_set_data (G_OBJECT (widget), I_(ATTACH_INFO_KEY), NULL);