gtkmenu.c 122 KB
Newer Older
Elliot Lee's avatar
Elliot Lee committed
1 2 3 4
/* GTK - The GIMP Toolkit
 * 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
Tim Janik's avatar
Tim Janik committed
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
15 16 17
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
Elliot Lee's avatar
Elliot Lee committed
18
 */
19 20

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

Manish Singh's avatar
Manish Singh committed
27 28
#define GTK_MENU_INTERNALS

29
#include <config.h>
30
#include <string.h> /* memset */
Elliot Lee's avatar
Elliot Lee committed
31
#include "gdk/gdkkeysyms.h"
Matthias Clasen's avatar
Matthias Clasen committed
32
#include "gtkaccellabel.h"
33
#include "gtkaccelmap.h"
34 35
#include "gtkbindings.h"
#include "gtklabel.h"
Elliot Lee's avatar
Elliot Lee committed
36
#include "gtkmain.h"
37
#include "gtkmarshalers.h"
Elliot Lee's avatar
Elliot Lee committed
38 39
#include "gtkmenu.h"
#include "gtkmenuitem.h"
40
#include "gtktearoffmenuitem.h"
41
#include "gtkwindow.h"
42 43
#include "gtkhbox.h"
#include "gtkvscrollbar.h"
Havoc Pennington's avatar
Havoc Pennington committed
44
#include "gtksettings.h"
45
#include "gtkprivate.h"
46
#include "gtkintl.h"
47
#include "gtkalias.h"
Elliot Lee's avatar
Elliot Lee committed
48 49


50
#define MENU_ITEM_CLASS(w)   GTK_MENU_ITEM_GET_CLASS (w)
Elliot Lee's avatar
Elliot Lee committed
51

52
#define DEFAULT_POPUP_DELAY    225
53 54
#define DEFAULT_POPDOWN_DELAY  1000

55 56 57
#define NAVIGATION_REGION_OVERSHOOT 50  /* How much the navigation region
					 * extends below the submenu
					 */
58

59 60
#define MENU_SCROLL_STEP1 8
#define MENU_SCROLL_STEP2 15
61
#define MENU_SCROLL_ARROW_HEIGHT 16
62 63
#define MENU_SCROLL_FAST_ZONE 8
#define MENU_SCROLL_TIMEOUT1 50
64
#define MENU_SCROLL_TIMEOUT2 20
65

66
#define ATTACH_INFO_KEY "gtk-menu-child-attach-info-key"
67
#define ATTACHED_MENUS "gtk-attached-menus"
68

69
typedef struct _GtkMenuAttachData	GtkMenuAttachData;
70
typedef struct _GtkMenuPrivate  	GtkMenuPrivate;
71 72 73 74 75 76 77

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

78 79
struct _GtkMenuPrivate 
{
80 81
  gboolean seen_item_enter;

82 83 84
  gboolean have_position;
  gint x;
  gint y;
85 86 87 88

  /* info used for the table */
  guint *heights;
  gint heights_length;
89 90

  gint monitor_num;
91

92 93 94 95
  /* Cached layout information */
  gboolean have_layout;
  gint n_rows;
  gint n_columns;
96 97
};

98 99
typedef struct
{
100 101 102 103 104 105 106 107 108
  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;
109

110 111 112 113 114
enum {
  MOVE_SCROLL,
  LAST_SIGNAL
};

115 116
enum {
  PROP_0,
117
  PROP_TEAROFF_STATE,
118 119
  PROP_TEAROFF_TITLE
};
120

121
enum {
122 123 124 125 126 127 128
  CHILD_PROP_0,
  CHILD_PROP_LEFT_ATTACH,
  CHILD_PROP_RIGHT_ATTACH,
  CHILD_PROP_TOP_ATTACH,
  CHILD_PROP_BOTTOM_ATTACH
};

129 130
static void     gtk_menu_class_init        (GtkMenuClass     *klass);
static void     gtk_menu_init              (GtkMenu          *menu);
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
static void     gtk_menu_set_property      (GObject          *object,
					    guint             prop_id,
					    const GValue     *value,
					    GParamSpec       *pspec);
static void     gtk_menu_get_property      (GObject          *object,
					    guint             prop_id,
					    GValue           *value,
					    GParamSpec       *pspec);
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);
149
static void     gtk_menu_destroy           (GtkObject        *object);
150
static void     gtk_menu_finalize          (GObject          *object);
151 152 153 154 155 156
static void     gtk_menu_realize           (GtkWidget        *widget);
static void     gtk_menu_unrealize         (GtkWidget        *widget);
static void     gtk_menu_size_request      (GtkWidget        *widget,
					    GtkRequisition   *requisition);
static void     gtk_menu_size_allocate     (GtkWidget        *widget,
					    GtkAllocation    *allocation);
157 158
static void     gtk_menu_paint             (GtkWidget        *widget,
					    GdkEventExpose   *expose);
159
static void     gtk_menu_show              (GtkWidget        *widget);
160 161 162 163
static gboolean gtk_menu_expose            (GtkWidget        *widget,
					    GdkEventExpose   *event);
static gboolean gtk_menu_key_press         (GtkWidget        *widget,
					    GdkEventKey      *event);
164 165
static gboolean gtk_menu_scroll            (GtkWidget        *widget,
					    GdkEventScroll   *event);
166 167 168 169
static gboolean gtk_menu_button_press      (GtkWidget        *widget,
					    GdkEventButton   *event);
static gboolean gtk_menu_button_release    (GtkWidget        *widget,
					    GdkEventButton   *event);
170 171 172 173 174 175 176 177
static gboolean gtk_menu_motion_notify     (GtkWidget        *widget,
					    GdkEventMotion   *event);
static gboolean gtk_menu_enter_notify      (GtkWidget        *widget,
					    GdkEventCrossing *event);
static gboolean gtk_menu_leave_notify      (GtkWidget        *widget,
					    GdkEventCrossing *event);
static void     gtk_menu_scroll_to         (GtkMenu          *menu,
					    gint              offset);
178 179
static void     gtk_menu_grab_notify       (GtkWidget        *widget,
					    gboolean          was_grabbed);
180 181 182 183 184

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

185 186
static void     gtk_menu_scroll_item_visible (GtkMenuShell    *menu_shell,
					      GtkWidget       *menu_item);
187 188
static void     gtk_menu_select_item       (GtkMenuShell     *menu_shell,
					    GtkWidget        *menu_item);
189
static void     gtk_menu_real_insert       (GtkMenuShell     *menu_shell,
190 191 192 193 194
					    GtkWidget        *child,
					    gint              position);
static void     gtk_menu_scrollbar_changed (GtkAdjustment    *adjustment,
					    GtkMenu          *menu);
static void     gtk_menu_handle_scrolling  (GtkMenu          *menu,
195 196 197
					    gint	      event_x,
					    gint	      event_y,
					    gboolean          enter);
198 199
static void     gtk_menu_set_tearoff_hints (GtkMenu          *menu,
					    gint             width);
200 201
static void     gtk_menu_style_set         (GtkWidget        *widget,
					    GtkStyle         *previous_style);
202 203
static gboolean gtk_menu_focus             (GtkWidget        *widget,
					    GtkDirectionType direction);
204
static gint     gtk_menu_get_popup_delay   (GtkMenuShell     *menu_shell);
205 206
static void     gtk_menu_move_current      (GtkMenuShell     *menu_shell,
                                            GtkMenuDirectionType direction);
207 208
static void     gtk_menu_real_move_scroll  (GtkMenu          *menu,
					    GtkScrollType     type);
209 210 211 212 213 214 215 216 217 218

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,
							gint              event_x,
							gint              event_y);
static void     gtk_menu_set_submenu_navigation_region (GtkMenu          *menu,
							GtkMenuItem      *menu_item,
							GdkEventCrossing *event);
 
Tim Janik's avatar
Tim Janik committed
219
static void gtk_menu_deactivate	    (GtkMenuShell      *menu_shell);
220 221
static void gtk_menu_show_all       (GtkWidget         *widget);
static void gtk_menu_hide_all       (GtkWidget         *widget);
222
static void gtk_menu_position       (GtkMenu           *menu);
Owen Taylor's avatar
Owen Taylor committed
223 224 225
static void gtk_menu_reparent       (GtkMenu           *menu, 
				     GtkWidget         *new_parent, 
				     gboolean           unrealize);
226 227
static void gtk_menu_remove         (GtkContainer      *menu,
				     GtkWidget         *widget);
Elliot Lee's avatar
Elliot Lee committed
228

229 230
static void gtk_menu_update_title   (GtkMenu           *menu);

231 232 233
static void       menu_grab_transfer_window_destroy (GtkMenu *menu);
static GdkWindow *menu_grab_transfer_window_get     (GtkMenu *menu);

234 235
static gboolean gtk_menu_real_can_activate_accel (GtkWidget *widget,
                                                  guint      signal_id);
236 237 238
static void _gtk_menu_refresh_accel_paths (GtkMenu *menu,
					   gboolean group_changed);

239
static GtkMenuShellClass *parent_class = NULL;
240
static const gchar	  attach_data_key[] = "gtk-menu-attach-data";
241

242 243
static guint menu_signals[LAST_SIGNAL] = { 0 };

244 245 246 247 248 249
static void
gtk_menu_free_private (gpointer data)
{
  GtkMenuPrivate *priv = (GtkMenuPrivate *)data;

  g_free (priv->heights);
Matthias Clasen's avatar
Matthias Clasen committed
250 251

  g_free (priv);
252 253
}

254
static GtkMenuPrivate *
255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270
gtk_menu_get_private (GtkMenu *menu)
{
  GtkMenuPrivate *private;
  static GQuark private_quark = 0;

  if (!private_quark)
    private_quark = g_quark_from_static_string ("gtk-menu-private");

  private = g_object_get_qdata (G_OBJECT (menu), private_quark);

  if (!private)
    {
      private = g_new0 (GtkMenuPrivate, 1);
      private->have_position = FALSE;
      
      g_object_set_qdata_full (G_OBJECT (menu), private_quark,
271
			       private, gtk_menu_free_private);
272 273 274 275 276
    }

  return private;
}

Manish Singh's avatar
Manish Singh committed
277
GType
278
gtk_menu_get_type (void)
Elliot Lee's avatar
Elliot Lee committed
279
{
Manish Singh's avatar
Manish Singh committed
280
  static GType menu_type = 0;
Tim Janik's avatar
Tim Janik committed
281
  
Elliot Lee's avatar
Elliot Lee committed
282 283
  if (!menu_type)
    {
Manish Singh's avatar
Manish Singh committed
284
      static const GTypeInfo menu_info =
Elliot Lee's avatar
Elliot Lee committed
285 286
      {
	sizeof (GtkMenuClass),
Manish Singh's avatar
Manish Singh committed
287 288 289 290 291 292 293 294
	NULL,		/* base_init */
	NULL,		/* base_finalize */
	(GClassInitFunc) gtk_menu_class_init,
	NULL,		/* class_finalize */
	NULL,		/* class_data */
	sizeof (GtkMenu),
	0,		/* n_preallocs */
	(GInstanceInitFunc) gtk_menu_init,
Elliot Lee's avatar
Elliot Lee committed
295
      };
Tim Janik's avatar
Tim Janik committed
296
      
297
      menu_type = g_type_register_static (GTK_TYPE_MENU_SHELL, I_("GtkMenu"),
Manish Singh's avatar
Manish Singh committed
298
					  &menu_info, 0);
Elliot Lee's avatar
Elliot Lee committed
299
    }
Tim Janik's avatar
Tim Janik committed
300
  
Elliot Lee's avatar
Elliot Lee committed
301 302 303
  return menu_type;
}

304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321
static void
menu_queue_resize (GtkMenu *menu)
{
  GtkMenuPrivate *priv = gtk_menu_get_private (menu);

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

static AttachInfo *
get_attach_info (GtkWidget *child)
{
  GObject *object = G_OBJECT (child);
  AttachInfo *ai = g_object_get_data (object, ATTACH_INFO_KEY);

  if (!ai)
    {
      ai = g_new0 (AttachInfo, 1);
322
      g_object_set_data_full (object, I_(ATTACH_INFO_KEY), ai, g_free);
323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468
    }

  return ai;
}

static gboolean
is_grid_attached (AttachInfo *ai)
{
  return (ai->left_attach >= 0 &&
	  ai->right_attach >= 0 &&
	  ai->top_attach >= 0 &&
	  ai->bottom_attach >= 0);
}

static void
menu_ensure_layout (GtkMenu *menu)
{
  GtkMenuPrivate *priv = gtk_menu_get_private (menu);

  if (!priv->have_layout)
    {
      GtkMenuShell *menu_shell = GTK_MENU_SHELL (menu);
      GList *l;
      gchar *row_occupied;
      gint current_row;
      gint max_right_attach;      
      gint max_bottom_attach;

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

      for (l = menu_shell->children; l; l = l->next)
	{
	  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
       */
      row_occupied = g_malloc0 (max_bottom_attach);

      for (l = menu_shell->children; l; l = l->next)
	{
	  GtkWidget *child = l->data;
	  AttachInfo *ai = get_attach_info (child);

	  if (is_grid_attached (ai))
	    {
	      gint i;

	      for (i = ai->top_attach; i < ai->bottom_attach; i++)
		row_occupied[i] = TRUE;
	    }
	}

      /* Lay non-grid-items out in those rows
       */
      current_row = 0;
      for (l = menu_shell->children; l; l = l->next)
	{
	  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;
	    }
	}

      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)
{
  GtkMenuPrivate *priv = gtk_menu_get_private (menu);

  menu_ensure_layout (menu);

  return priv->n_columns;
}

static gint
gtk_menu_get_n_rows (GtkMenu *menu)
{
  GtkMenuPrivate *priv = gtk_menu_get_private (menu);

  menu_ensure_layout (menu);

  return priv->n_rows;
}

static void
get_effective_child_attach (GtkWidget *child,
			    int       *l,
			    int       *r,
			    int       *t,
			    int       *b)
{
  GtkMenu *menu = GTK_MENU (child->parent);
  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
469 470 471
static void
gtk_menu_class_init (GtkMenuClass *class)
{
472 473 474 475 476
  GObjectClass *gobject_class = G_OBJECT_CLASS (class);
  GtkObjectClass *object_class = GTK_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);
477
  GtkBindingSet *binding_set;
Tim Janik's avatar
Tim Janik committed
478
  
479
  parent_class = g_type_class_peek_parent (class);
Tim Janik's avatar
Tim Janik committed
480
  
481
  gobject_class->finalize = gtk_menu_finalize;
482 483 484
  gobject_class->set_property = gtk_menu_set_property;
  gobject_class->get_property = gtk_menu_get_property;

485 486 487 488 489 490 491 492
  object_class->destroy = gtk_menu_destroy;
  
  widget_class->realize = gtk_menu_realize;
  widget_class->unrealize = gtk_menu_unrealize;
  widget_class->size_request = gtk_menu_size_request;
  widget_class->size_allocate = gtk_menu_size_allocate;
  widget_class->show = gtk_menu_show;
  widget_class->expose_event = gtk_menu_expose;
493
  widget_class->scroll_event = gtk_menu_scroll;
494 495 496 497 498 499 500 501 502 503 504
  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->hide_all = gtk_menu_hide_all;
  widget_class->enter_notify_event = gtk_menu_enter_notify;
  widget_class->leave_notify_event = gtk_menu_leave_notify;
  widget_class->motion_notify_event = gtk_menu_motion_notify;
  widget_class->style_set = gtk_menu_style_set;
  widget_class->focus = gtk_menu_focus;
505
  widget_class->can_activate_accel = gtk_menu_real_can_activate_accel;
506
  widget_class->grab_notify = gtk_menu_grab_notify;
507 508 509 510 511 512 513 514 515 516 517 518

  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;

519
  menu_signals[MOVE_SCROLL] =
520
    _gtk_binding_signal_new (I_("move_scroll"),
521 522 523 524 525 526 527 528
			     G_OBJECT_CLASS_TYPE (object_class),
			     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);
  
529 530 531
  g_object_class_install_property (gobject_class,
                                   PROP_TEAROFF_TITLE,
                                   g_param_spec_string ("tearoff-title",
532 533
                                                        P_("Tearoff Title"),
                                                        P_("A title that may be displayed by the window manager when this menu is torn-off"),
534
                                                        "",
535
                                                        GTK_PARAM_READWRITE));
536 537 538 539 540 541 542 543 544 545 546 547 548 549

  /**
   * 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",
							 P_("Tearoff State"),
							 P_("A boolean that indicates whether the menu is torn-off"),
							 FALSE,
550
							 GTK_PARAM_READWRITE));
Manish Singh's avatar
Manish Singh committed
551

552 553
  gtk_widget_class_install_style_property (widget_class,
					   g_param_spec_int ("vertical-padding",
554 555
							     P_("Vertical Padding"),
							     P_("Extra space at the top and bottom of the menu"),
556 557 558
							     0,
							     G_MAXINT,
							     1,
559
							     GTK_PARAM_READABLE));
560

561 562
  gtk_widget_class_install_style_property (widget_class,
					   g_param_spec_int ("vertical-offset",
563 564
							     P_("Vertical Offset"),
							     P_("When the menu is a submenu, position it this number of pixels offset vertically"),
565 566 567
							     G_MININT,
							     G_MAXINT,
							     0,
568
							     GTK_PARAM_READABLE));
569 570 571

  gtk_widget_class_install_style_property (widget_class,
					   g_param_spec_int ("horizontal-offset",
572 573
							     P_("Horizontal Offset"),
							     P_("When the menu is a submenu, position it this number of pixels offset horizontally"),
574 575 576
							     G_MININT,
							     G_MAXINT,
							     -2,
577
							     GTK_PARAM_READABLE));
578

579

580 581
 gtk_container_class_install_child_property (container_class,
                                             CHILD_PROP_LEFT_ATTACH,
582
					      g_param_spec_int ("left-attach",
583 584
                                                               P_("Left Attach"),
                                                               P_("The column number to attach the left side of the child to"),
585
								-1, INT_MAX, -1,
586
                                                               GTK_PARAM_READWRITE));
587 588 589

 gtk_container_class_install_child_property (container_class,
                                             CHILD_PROP_RIGHT_ATTACH,
590
					      g_param_spec_int ("right-attach",
591 592
                                                               P_("Right Attach"),
                                                               P_("The column number to attach the right side of the child to"),
593
								-1, INT_MAX, -1,
594
                                                               GTK_PARAM_READWRITE));
595 596 597

 gtk_container_class_install_child_property (container_class,
                                             CHILD_PROP_TOP_ATTACH,
598
					      g_param_spec_int ("top-attach",
599 600
                                                               P_("Top Attach"),
                                                               P_("The row number to attach the top of the child to"),
601
								-1, INT_MAX, -1,
602
                                                               GTK_PARAM_READWRITE));
603 604 605

 gtk_container_class_install_child_property (container_class,
                                             CHILD_PROP_BOTTOM_ATTACH,
606
					      g_param_spec_int ("bottom-attach",
607 608
                                                               P_("Bottom Attach"),
                                                               P_("The row number to attach the bottom of the child to"),
609
								-1, INT_MAX, -1,
610
                                                               GTK_PARAM_READWRITE));
611 612 613 614

  binding_set = gtk_binding_set_by_class (class);
  gtk_binding_entry_add_signal (binding_set,
				GDK_Up, 0,
615
				I_("move_current"), 1,
616 617
				GTK_TYPE_MENU_DIRECTION_TYPE,
				GTK_MENU_DIR_PREV);
618 619 620 621 622
  gtk_binding_entry_add_signal (binding_set,
				GDK_KP_Up, 0,
				"move_current", 1,
				GTK_TYPE_MENU_DIRECTION_TYPE,
				GTK_MENU_DIR_PREV);
623 624 625 626 627
  gtk_binding_entry_add_signal (binding_set,
				GDK_Down, 0,
				"move_current", 1,
				GTK_TYPE_MENU_DIRECTION_TYPE,
				GTK_MENU_DIR_NEXT);
628 629 630 631 632
  gtk_binding_entry_add_signal (binding_set,
				GDK_KP_Down, 0,
				"move_current", 1,
				GTK_TYPE_MENU_DIRECTION_TYPE,
				GTK_MENU_DIR_NEXT);
633 634 635 636 637
  gtk_binding_entry_add_signal (binding_set,
				GDK_Left, 0,
				"move_current", 1,
				GTK_TYPE_MENU_DIRECTION_TYPE,
				GTK_MENU_DIR_PARENT);
638 639 640 641 642
  gtk_binding_entry_add_signal (binding_set,
				GDK_KP_Left, 0,
				"move_current", 1,
				GTK_TYPE_MENU_DIRECTION_TYPE,
				GTK_MENU_DIR_PARENT);
643 644 645 646 647
  gtk_binding_entry_add_signal (binding_set,
				GDK_Right, 0,
				"move_current", 1,
				GTK_TYPE_MENU_DIRECTION_TYPE,
				GTK_MENU_DIR_CHILD);
648 649 650 651 652
  gtk_binding_entry_add_signal (binding_set,
				GDK_KP_Right, 0,
				"move_current", 1,
				GTK_TYPE_MENU_DIRECTION_TYPE,
				GTK_MENU_DIR_CHILD);
653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692
  gtk_binding_entry_add_signal (binding_set,
				GDK_Home, 0,
				"move_scroll", 1,
				GTK_TYPE_SCROLL_TYPE,
				GTK_SCROLL_START);
  gtk_binding_entry_add_signal (binding_set,
				GDK_KP_Home, 0,
				"move_scroll", 1,
				GTK_TYPE_SCROLL_TYPE,
				GTK_SCROLL_START);
  gtk_binding_entry_add_signal (binding_set,
				GDK_End, 0,
				"move_scroll", 1,
				GTK_TYPE_SCROLL_TYPE,
				GTK_SCROLL_END);
  gtk_binding_entry_add_signal (binding_set,
				GDK_KP_End, 0,
				"move_scroll", 1,
				GTK_TYPE_SCROLL_TYPE,
				GTK_SCROLL_END);
  gtk_binding_entry_add_signal (binding_set,
				GDK_Page_Up, 0,
				"move_scroll", 1,
				GTK_TYPE_SCROLL_TYPE,
				GTK_SCROLL_PAGE_UP);
  gtk_binding_entry_add_signal (binding_set,
				GDK_KP_Page_Up, 0,
				"move_scroll", 1,
				GTK_TYPE_SCROLL_TYPE,
				GTK_SCROLL_PAGE_UP);
  gtk_binding_entry_add_signal (binding_set,
				GDK_Page_Down, 0,
				"move_scroll", 1,
				GTK_TYPE_SCROLL_TYPE,
				GTK_SCROLL_PAGE_DOWN);
  gtk_binding_entry_add_signal (binding_set,
				GDK_KP_Page_Down, 0,
				"move_scroll", 1,
				GTK_TYPE_SCROLL_TYPE,
				GTK_SCROLL_PAGE_DOWN);
693 694

  gtk_settings_install_property (g_param_spec_boolean ("gtk-can-change-accels",
695 696
						       P_("Can change accelerators"),
						       P_("Whether menu accelerators can be changed by pressing a key over the menu item"),
Owen Taylor's avatar
Owen Taylor committed
697
						       FALSE,
698
						       GTK_PARAM_READWRITE));
699 700

  gtk_settings_install_property (g_param_spec_int ("gtk-menu-popup-delay",
701 702
						   P_("Delay before submenus appear"),
						   P_("Minimum time the pointer must stay over a menu item before the submenu appear"),
703 704 705
						   0,
						   G_MAXINT,
						   DEFAULT_POPUP_DELAY,
706
						   GTK_PARAM_READWRITE));
707 708

  gtk_settings_install_property (g_param_spec_int ("gtk-menu-popdown-delay",
709 710
						   P_("Delay before hiding a submenu"),
						   P_("The time before hiding a submenu when the pointer is moving towards the submenu"),
711 712 713
						   0,
						   G_MAXINT,
						   DEFAULT_POPDOWN_DELAY,
714
						   GTK_PARAM_READWRITE));
715
						   
Elliot Lee's avatar
Elliot Lee committed
716 717
}

718 719 720 721 722 723 724 725 726 727 728 729 730

static void 
gtk_menu_set_property (GObject      *object,
		       guint         prop_id,
		       const GValue *value,
		       GParamSpec   *pspec)
{
  GtkMenu *menu;
  
  menu = GTK_MENU (object);
  
  switch (prop_id)
    {
731 732 733
    case PROP_TEAROFF_STATE:
      gtk_menu_set_tearoff_state (menu, g_value_get_boolean (value));
      break;
734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754
    case PROP_TEAROFF_TITLE:
      gtk_menu_set_title (menu, g_value_get_string (value));
      break;	  
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}

static void 
gtk_menu_get_property (GObject     *object,
		       guint        prop_id,
		       GValue      *value,
		       GParamSpec  *pspec)
{
  GtkMenu *menu;
  
  menu = GTK_MENU (object);
  
  switch (prop_id)
    {
755 756 757
    case PROP_TEAROFF_STATE:
      g_value_set_boolean (value, gtk_menu_get_tearoff_state (menu));
      break;
758
    case PROP_TEAROFF_TITLE:
759
      g_value_set_string (value, gtk_menu_get_title (menu));
760 761 762 763 764 765 766
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}

767 768 769 770 771 772 773 774
static void
gtk_menu_set_child_property (GtkContainer *container,
                             GtkWidget    *child,
                             guint         property_id,
                             const GValue *value,
                             GParamSpec   *pspec)
{
  GtkMenu *menu = GTK_MENU (container);
775
  AttachInfo *ai = get_attach_info (child);
776 777 778

  switch (property_id)
    {
779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794
    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:
      ai->top_attach = g_value_get_int (value);	
      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;
795 796
    }

797
  menu_queue_resize (menu);
798 799 800 801 802 803 804 805 806
}

static void
gtk_menu_get_child_property (GtkContainer *container,
                             GtkWidget    *child,
                             guint         property_id,
                             GValue       *value,
                             GParamSpec   *pspec)
{
807
  AttachInfo *ai = get_attach_info (child);
808 809 810

  switch (property_id)
    {
811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826
    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;
827 828 829
    }
}

830
static gboolean
831 832 833 834 835 836
gtk_menu_window_event (GtkWidget *window,
		       GdkEvent  *event,
		       GtkWidget *menu)
{
  gboolean handled = FALSE;

Manish Singh's avatar
Manish Singh committed
837 838
  g_object_ref (window);
  g_object_ref (menu);
839 840 841 842 843

  switch (event->type)
    {
    case GDK_KEY_PRESS:
    case GDK_KEY_RELEASE:
844
      handled = gtk_widget_event (menu, event);
845 846 847 848 849
      break;
    default:
      break;
    }

Manish Singh's avatar
Manish Singh committed
850 851
  g_object_unref (window);
  g_object_unref (menu);
852 853 854 855

  return handled;
}

856 857 858 859 860 861 862 863 864
static void
gtk_menu_window_size_request (GtkWidget      *window,
			      GtkRequisition *requisition,
			      GtkMenu        *menu)
{
  GtkMenuPrivate *private = gtk_menu_get_private (menu);

  if (private->have_position)
    {
865
      GdkScreen *screen = gtk_widget_get_screen (window);
866 867
      GdkRectangle monitor;
      
868
      gdk_screen_get_monitor_geometry (screen, private->monitor_num, &monitor);
869 870 871

      if (private->y + requisition->height > monitor.y + monitor.height)
	requisition->height = monitor.y + monitor.height - private->y;
872

873 874
      if (private->y < monitor.y)
	requisition->height -= monitor.y - private->y;
875 876 877
    }
}

Elliot Lee's avatar
Elliot Lee committed
878 879 880
static void
gtk_menu_init (GtkMenu *menu)
{
881 882
  GtkMenuPrivate *priv = gtk_menu_get_private (menu);

Elliot Lee's avatar
Elliot Lee committed
883 884
  menu->parent_menu_item = NULL;
  menu->old_active_menu_item = NULL;
Tim Janik's avatar
Tim Janik committed
885
  menu->accel_group = NULL;
Elliot Lee's avatar
Elliot Lee committed
886 887
  menu->position_func = NULL;
  menu->position_func_data = NULL;
888
  menu->toggle_size = 0;
Tim Janik's avatar
Tim Janik committed
889

Manish Singh's avatar
Manish Singh committed
890 891 892 893
  menu->toplevel = g_object_connect (g_object_new (GTK_TYPE_WINDOW,
						   "type", GTK_WINDOW_POPUP,
						   "child", menu,
						   NULL),
Tim Janik's avatar
Tim Janik committed
894
				     "signal::event", gtk_menu_window_event, menu,
895
				     "signal::size_request", gtk_menu_window_size_request, menu,
Tim Janik's avatar
Tim Janik committed
896 897
				     "signal::destroy", gtk_widget_destroyed, &menu->toplevel,
				     NULL);
Manish Singh's avatar
Manish Singh committed
898
  gtk_window_set_resizable (GTK_WINDOW (menu->toplevel), FALSE);
899
  gtk_window_set_mnemonic_modifier (GTK_WINDOW (menu->toplevel), 0);
900

Owen Taylor's avatar
Owen Taylor committed
901 902 903 904
  /* Refloat the menu, so that reference counting for the menu isn't
   * affected by it being a child of the toplevel
   */
  GTK_WIDGET_SET_FLAGS (menu, GTK_FLOATING);
905
  menu->needs_destruction_ref_count = TRUE;
Owen Taylor's avatar
Owen Taylor committed
906

907 908 909 910 911 912 913 914
  menu->view_window = NULL;
  menu->bin_window = NULL;

  menu->scroll_offset = 0;
  menu->scroll_step  = 0;
  menu->timeout_id = 0;
  menu->scroll_fast = FALSE;
  
915
  menu->tearoff_window = NULL;
916
  menu->tearoff_hbox = NULL;
917
  menu->torn_off = FALSE;
918 919 920
  menu->tearoff_active = FALSE;
  menu->tearoff_adjustment = NULL;
  menu->tearoff_scrollbar = NULL;
921

922 923 924 925
  menu->upper_arrow_visible = FALSE;
  menu->lower_arrow_visible = FALSE;
  menu->upper_arrow_prelight = FALSE;
  menu->lower_arrow_prelight = FALSE;
926

927
  priv->have_layout = FALSE;
928 929 930
}

static void
931
gtk_menu_destroy (GtkObject *object)
932
{
933
  GtkMenu *menu;
934
  GtkMenuAttachData *data;
935
  GtkMenuPrivate *priv;
936

937
  g_return_if_fail (GTK_IS_MENU (object));
938 939

  menu = GTK_MENU (object);
940 941

  gtk_menu_stop_scrolling (menu);
Tim Janik's avatar
Tim Janik committed
942
  
Manish Singh's avatar
Manish Singh committed
943
  data = g_object_get_data (G_OBJECT (object), attach_data_key);
944
  if (data)
945
    gtk_menu_detach (menu);
Tim Janik's avatar
Tim Janik committed
946
  
947 948
  gtk_menu_stop_navigating_submenu (menu);

949 950
  if (menu->old_active_menu_item)
    {
Manish Singh's avatar
Manish Singh committed
951
      g_object_unref (menu->old_active_menu_item);
952 953 954
      menu->old_active_menu_item = NULL;
    }

Owen Taylor's avatar
Owen Taylor committed
955
  /* Add back the reference count for being a child */
956 957 958
  if (menu->needs_destruction_ref_count)
    {
      menu->needs_destruction_ref_count = FALSE;
Manish Singh's avatar
Manish Singh committed
959
      g_object_ref (object);
960
    }
Tim Janik's avatar
Tim Janik committed
961
  
962 963 964 965 966 967
  if (menu->accel_group)
    {
      g_object_unref (menu->accel_group);
      menu->accel_group = NULL;
    }

968 969
  if (menu->toplevel)
    gtk_widget_destroy (menu->toplevel);
970 971 972
  if (menu->tearoff_window)
    gtk_widget_destroy (menu->tearoff_window);

973 974
  priv = gtk_menu_get_private (menu);

975
  GTK_OBJECT_CLASS (parent_class)->destroy (object);
976 977
}

978 979 980 981 982 983 984 985 986
static void
gtk_menu_finalize (GObject *object)
{
  GtkMenu *menu = GTK_MENU (object);

  g_free (menu->accel_path);
  
  G_OBJECT_CLASS (parent_class)->finalize (object);
}
987

988 989 990 991
static void
menu_change_screen (GtkMenu   *menu,
		    GdkScreen *new_screen)
{
992 993
  GtkMenuPrivate *private = gtk_menu_get_private (menu);

Matthias Clasen's avatar
Matthias Clasen committed
994
  if (gtk_widget_has_screen (GTK_WIDGET (menu)))
995
    {
Matthias Clasen's avatar
Matthias Clasen committed
996
      if (new_screen == gtk_widget_get_screen (GTK_WIDGET (menu)))
997 998 999
	return;
    }

1000 1001 1002 1003 1004 1005 1006
  if (menu->torn_off)
    {
      gtk_window_set_screen (GTK_WINDOW (menu->tearoff_window), new_screen);
      gtk_menu_position (menu);
    }

  gtk_window_set_screen (GTK_WINDOW (menu->toplevel), new_screen);
1007
  private->monitor_num = -1;
1008 1009
}

1010 1011 1012 1013 1014 1015 1016 1017
static void
attach_widget_screen_changed (GtkWidget *attach_widget,
			      GdkScreen *previous_screen,
			      GtkMenu   *menu)
{
  if (gtk_widget_has_screen (attach_widget) &&
      !g_object_get_data (G_OBJECT (menu), "gtk-menu-explicit-screen"))
    {
1018
      menu_change_screen (menu, gtk_widget_get_screen (attach_widget));
1019 1020 1021
    }
}

1022
void
Tim Janik's avatar
Tim Janik committed
1023 1024 1025
gtk_menu_attach_to_widget (GtkMenu	       *menu,
			   GtkWidget	       *attach_widget,
			   GtkMenuDetachFunc	detacher)
1026 1027
{
  GtkMenuAttachData *data;
1028
  GList *list;
Tim Janik's avatar
Tim Janik committed
1029
  
1030 1031 1032
  g_return_if_fail (GTK_IS_MENU (menu));
  g_return_if_fail (GTK_IS_WIDGET (attach_widget));
  g_return_if_fail (detacher != NULL);
Tim Janik's avatar
Tim Janik committed
1033
  
1034 1035
  /* keep this function in sync with gtk_widget_set_parent()
   */
Tim Janik's avatar
Tim Janik committed
1036
  
Manish Singh's avatar
Manish Singh committed
1037
  data = g_object_get_data (G_OBJECT (menu), attach_data_key);
1038 1039 1040
  if (data)
    {
      g_warning ("gtk_menu_attach_to_widget(): menu already attached to %s",
Manish Singh's avatar
Manish Singh committed
1041
		 g_type_name (G_TYPE_FROM_INSTANCE (data->attach_widget)));
1042
     return;
1043
    }
Tim Janik's avatar
Tim Janik committed
1044
  
Manish Singh's avatar
Manish Singh committed
1045
  g_object_ref (menu);
1046
  gtk_object_sink (GTK_OBJECT (menu));
Tim Janik's avatar
Tim Janik committed
1047
  
1048 1049
  data = g_new (GtkMenuAttachData, 1);
  data->attach_widget = attach_widget;
1050 1051 1052 1053 1054
  
  g_signal_connect (attach_widget, "screen_changed",
		    G_CALLBACK (attach_widget_screen_changed), menu);
  attach_widget_screen_changed (attach_widget, NULL, menu);
  
1055
  data->detacher = detacher;
1056
  g_object_set_data (G_OBJECT (menu), I_(attach_data_key), data);
1057
  list = g_object_steal_data (G_OBJECT (attach_widget), ATTACHED_MENUS);
1058 1059 1060 1061
  if (!g_list_find (list, menu))
    {
      list = g_list_prepend (list, menu);
    }
1062
  g_object_set_data_full (G_OBJECT (attach_widget), I_(ATTACHED_MENUS), list, (GtkDestroyNotify) g_list_free);
Tim Janik's avatar
Tim Janik committed
1063
  
1064 1065
  if (GTK_WIDGET_STATE (menu) != GTK_STATE_NORMAL)
    gtk_widget_set_state (GTK_WIDGET (menu), GTK_STATE_NORMAL);
Tim Janik's avatar
Tim Janik committed
1066
  
1067 1068 1069
  /* we don't need to set the style here, since
   * we are a toplevel widget.
   */
1070 1071 1072

  /* Fallback title for menu comes from attach widget */
  gtk_menu_update_title (menu);
1073 1074
}

1075
GtkWidget*
1076
gtk_menu_get_attach_widget (GtkMenu *menu)
1077 1078
{
  GtkMenuAttachData *data;
Tim Janik's avatar
Tim Janik committed
1079
  
1080
  g_return_val_if_fail (GTK_IS_MENU (menu), NULL);
Tim Janik's avatar
Tim Janik committed
1081
  
Manish Singh's avatar
Manish Singh committed
1082
  data = g_object_get_data (G_OBJECT (menu), attach_data_key);
1083 1084 1085 1086 1087
  if (data)
    return data->attach_widget;
  return NULL;
}

1088
void
1089
gtk_menu_detach (GtkMenu *menu)
1090 1091
{
  GtkMenuAttachData *data;
1092
  GList *list;
Tim Janik's avatar
Tim Janik committed
1093
  
1094
  g_return_if_fail (GTK_IS_MENU (menu));
Tim Janik's avatar
Tim Janik committed
1095
  
1096 1097
  /* keep this function in sync with gtk_widget_unparent()
   */
Manish Singh's avatar
Manish Singh committed
1098
  data = g_object_get_data (G_OBJECT (menu), attach_data_key);
1099 1100 1101 1102 1103
  if (!data)
    {
      g_warning ("gtk_menu_detach(): menu is not attached");
      return;
    }
1104
  g_object_set_data (G_OBJECT (menu), I_(attach_data_key), NULL);
Tim Janik's avatar
Tim Janik committed
1105
  
1106 1107 1108 1109
  g_signal_handlers_disconnect_by_func (data->attach_widget,
					(gpointer) attach_widget_screen_changed,
					menu);

1110
  data->detacher (data->attach_widget, menu);
1111 1112 1113
  list = g_object_steal_data (G_OBJECT (data->attach_widget), ATTACHED_MENUS);
  list = g_list_remove (list, menu);
  if (list)
1114
    g_object_set_data_full (G_OBJECT (data->attach_widget), I_(ATTACHED_MENUS), list, (GtkDestroyNotify) g_list_free);
1115
  else
1116
    g_object_set_data (G_OBJECT (data->attach_widget), I_(ATTACHED_MENUS), NULL);
Tim Janik's avatar
Tim Janik committed
1117
  
1118 1119
  if (GTK_WIDGET_REALIZED (menu))
    gtk_widget_unrealize (GTK_WIDGET (menu));
Tim Janik's avatar
Tim Janik committed
1120
  
1121
  g_free (data);
Tim Janik's avatar
Tim Janik committed
1122
  
1123 1124 1125
  /* Fallback title for menu comes from attach widget */
  gtk_menu_update_title (menu);

Manish Singh's avatar
Manish Singh committed
1126
  g_object_unref (menu);
Elliot Lee's avatar
Elliot Lee committed
1127 1128
}

1129
static void
1130 1131
gtk_menu_remove (GtkContainer *container,
		 GtkWidget    *widget)
1132 1133
{
  GtkMenu *menu;
1134
  GtkMenuPrivate *priv;
1135

1136 1137 1138 1139
  g_return_if_fail (GTK_IS_MENU (container));
  g_return_if_fail (GTK_IS_MENU_ITEM (widget));

  menu = GTK_MENU (container);
1140
  priv = gtk_menu_get_private (menu);
1141 1142 1143 1144 1145

  /* Clear out old_active_menu_item if it matches the item we are removing
   */
  if (menu->old_active_menu_item == widget)
    {
Manish Singh's avatar
Manish Singh committed
1146
      g_object_unref (menu->old_active_menu_item);
1147 1148 1149 1150
      menu->old_active_menu_item = NULL;
    }

  GTK_CONTAINER_CLASS (parent_class)->remove (container, widget);
1151
  g_object_set_data (G_OBJECT (widget), I_(ATTACH_INFO_KEY), NULL);
1152

1153 1154
  menu_queue_resize (menu);
}
1155

Elliot Lee's avatar
Elliot Lee committed
1156
GtkWidget*
1157
gtk_menu_new (void)
Elliot Lee's avatar
Elliot Lee committed
1158
{
Manish Singh's avatar
Manish Singh committed
1159
  return g_object_new (GTK_TYPE_MENU, NULL);
Elliot Lee's avatar
Elliot Lee committed
1160 1161
}

1162
static void
1163 1164 1165
gtk_menu_real_insert (GtkMenuShell *menu_shell,
		      GtkWidget    *child,
		      gint          position)
Elliot Lee's avatar
Elliot Lee committed
1166
{
1167 1168
  GtkMenu *menu = GTK_MENU (menu_shell);
  AttachInfo *ai = get_attach_info (child);
1169

1170 1171 1172 1173
  ai->left_attach = -1;
  ai->right_attach = -1;
  ai->top_attach = -1;
  ai->bottom_attach = -1;
1174

1175
  if (GTK_WIDGET_REALIZED (menu_shell))
1176
    gtk_widget_set_parent_window (child, menu->bin_window);
1177 1178 1179

  GTK_MENU_SHELL_CLASS (parent_class)->insert (menu_shell, child, position);

1180
  menu_queue_resize (menu);
1181 1182
}

1183 1184 1185 1186
static void
gtk_menu_tearoff_bg_copy (GtkMenu *menu)
{
  GtkWidget *widget;
1187
  gint width, height;
1188 1189 1190 1191 1192 1193 1194 1195

  widget = GTK_WIDGET (menu);

  if (menu->torn_off)
    {
      GdkPixmap *pixmap;
      GdkGC *gc;
      GdkGCValues gc_values;
1196 1197 1198

      menu->tearoff_active = FALSE;
      menu->saved_scroll_offset = menu->scroll_offset;
1199 1200 1201 1202 1203
      
      gc_values.subwindow_mode = GDK_INCLUDE_INFERIORS;
      gc = gdk_gc_new_with_values (widget->window,
				   &gc_values, GDK_GC_SUBWINDOW);
      
Manish Singh's avatar
Manish Singh committed
1204
      gdk_drawable_get_size (menu->tearoff_window->window, &width, &height);
1205 1206 1207 1208
      
      pixmap = gdk_pixmap_new (menu->tearoff_window->window,
			       width,
			       height,
1209 1210
			       -1);

Manish Singh's avatar
Manish Singh committed
1211 1212 1213 1214
      gdk_draw_drawable (pixmap, gc,
			 menu->tearoff_window->window,
			 0, 0, 0, 0, -1, -1);
      g_object_unref (gc);
1215

Manish Singh's avatar
Manish Singh committed
1216 1217 1218
      gtk_widget_set_size_request (menu->tearoff_window,
				   width,
				   height);
1219

1220
      gdk_window_set_back_pixmap (menu->tearoff_window->window, pixmap, FALSE);
Manish Singh's avatar
Manish Singh committed
1221
      g_object_unref (pixmap);
1222 1223 1224
    }
}

1225 1226
static gboolean
popup_grab_on_window (GdkWindow *window,
1227 1228
		      guint32    activate_time,
		      gboolean   grab_keyboard)
1229 1230 1231 1232 1233 1234 1235
{
  if ((gdk_pointer_grab (window, TRUE,
			 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
			 GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK |
			 GDK_POINTER_MOTION_MASK,
			 NULL, NULL, activate_time) == 0))
    {
1236 1237
      if (!grab_keyboard ||
	  gdk_keyboard_grab (window, TRUE,
1238 1239 1240 1241
			     activate_time) == 0)
	return TRUE;
      else
	{
1242 1243
	  gdk_display_pointer_ungrab (gdk_drawable_get_display (window),
				      activate_time);
1244 1245 1246 1247 1248 1249 1250
	  return FALSE;
	}
    }

  return FALSE;
}

Soeren Sandmann's avatar
Soeren Sandmann committed
1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276
/**
 * gtk_menu_popup:
 * @menu: a #GtkMenu.
 * @parent_menu_shell: the menu shell containing the triggering menu item, or %NULL
 * @parent_menu_item: the menu item whose activation triggered the popup, or %NULL
 * @func: a user supplied function used to position the menu, or %NULL
 * @data: user supplied data to be passed to @func.
 * @button: the mouse button which was pressed to initiate the event.
 * @activate_time: the time at which the activation event occurred.
 *
 * Displays a menu and makes it available for selection.  Applications can use
 * this function to display context-sensitive menus, and will typically supply
 * %NULL for the @parent_menu_shell, @parent_menu_item, @func and @data 
 * parameters. The default menu positioning function will position the menu
 * at the current mouse cursor position.
 *
 * The @button parameter should be the mouse button pressed to initiate
 * the menu popup. If the menu popup was initiated by something other than
 * a mouse button press, such as a mouse button release or a keypress,
 * @button should be 0.
 *
 * The @activate_time parameter should be the time stamp of the event that
 * initiated the popup. If such an event is not available, use
 * gtk_get_current_event_time() instead.
 *
 */
Elliot Lee's avatar
Elliot Lee committed
1277
void
Tim Janik's avatar
Tim Janik committed
1278 1279 1280
gtk_menu_popup (GtkMenu		    *menu,
		GtkWidget	    *parent_menu_shell,
		GtkWidget	    *parent_menu_item,
Elliot Lee's avatar
Elliot Lee committed
1281
		GtkMenuPositionFunc  func,
Tim Janik's avatar
Tim Janik committed
1282 1283 1284
		gpointer	     data,
		guint		     button,
		guint32		     activate_time)
Elliot Lee's avatar
Elliot Lee committed
1285
{
1286
  GtkWidget *widget;
Owen Taylor's avatar
Owen Taylor committed
1287 1288
  GtkWidget *xgrab_shell;
  GtkWidget *parent;
1289 1290
  GdkEvent *current_event;
  GtkMenuShell *menu_shell;
1291 1292
  gboolean grab_keyboard;
  GtkMenuPrivate *priv;
1293

Elliot Lee's avatar
Elliot Lee committed
1294
  g_return_if_fail (GTK_IS_MENU (menu));
1295

1296 1297
  widget = GTK_WIDGET (menu);
  menu_shell = GTK_MENU_SHELL (menu);
1298 1299
  priv = gtk_menu_get_private (menu);

1300
  menu_shell->parent_menu_shell = parent_menu_shell;
1301 1302

  priv->seen_item_enter = FALSE;
1303
  
1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343
  /* Find the last viewable ancestor, and make an X grab on it
   */
  parent = GTK_WIDGET (menu);
  xgrab_shell = NULL;
  while (parent)
    {
      gboolean viewable = TRUE;
      GtkWidget *tmp = parent;
      
      while (tmp)
	{
	  if (!GTK_WIDGET_MAPPED (tmp))
	    {
	      viewable = FALSE;
	      break;
	    }
	  tmp = tmp->parent;
	}
      
      if (viewable)
	xgrab_shell = parent;
      
      parent = GTK_MENU_SHELL (parent)->parent_menu_shell;
    }

  /* We want to receive events generated when we map the menu; unfortunately,
   * since there is probably already an implicit grab in place from the
   * button that the user used to pop up the menu, we won't receive then --
   * in particular, the EnterNotify when the menu pops up under the pointer.
   *
   * If we are grabbing on a parent menu shell, no problem; just grab on
   * that menu shell first before popping up the window with owner_events = TRUE.
   *
   * When grabbing on the menu itself, things get more convuluted - we
   * we do an explicit grab on a specially created window with
   * owner_events = TRUE, which we override further down with a grab
   * on the menu. (We can't grab on the menu until it is mapped; we
   * probably could just leave the grab on the other window, with a
   * little reorganization of the code in gtkmenu*).
   */
1344 1345 1346
  grab_keyboard = gtk_menu_shell_get_take_focus (menu_shell);
  gtk_window_set_accept_focus (GTK_WINDOW (menu->toplevel), grab_keyboard);

1347
  if (xgrab_shell && xgrab_shell != widget)
1348
    {
1349
      if (popup_grab_on_window (xgrab_shell->window, activate_time, grab_keyboard))
1350 1351 1352 1353 1354 1355 1356 1357
	GTK_MENU_SHELL (xgrab_shell)->have_xgrab = TRUE;
    }
  else
    {
      GdkWindow *transfer_window;

      xgrab_shell = widget;
      transfer_window = menu_grab_transfer_window_get (menu);
1358
      if (popup_grab_on_window (transfer_window, activate_time, grab_keyboard))
1359 1360 1361 1362 1363 1364 1365 1366 1367 1368
	GTK_MENU_SHELL (xgrab_shell)->have_xgrab = TRUE;
    }

  if (!GTK_MENU_SHELL (xgrab_shell)->have_xgrab)
    {
      /* We failed to make our pointer/keyboard grab. Rather than leaving the user
       * with a stuck up window, we just abort here. Presumably the user will
       * try again.
       */
      menu_shell->parent_menu_shell = NULL;
1369
      menu_grab_transfer_window_destroy (menu);
1370 1371 1372
      return;
    }

1373 1374 1375 1376 1377 1378 1379 1380
  menu_shell->active = TRUE;
  menu_shell->button = button;

  /* If we are popping up the menu from something other than, a button
   * press then, as a heuristic, we ignore enter events for the menu
   * until we get a MOTION_NOTIFY.  
   */

1381
  current_event = gtk_get_current_event ();
1382 1383 1384 1385 1386
  if (current_event)
    {
      if ((current_event->type != GDK_BUTTON_PRESS) &&
	  (current_event->type != GDK_ENTER_NOTIFY))
	menu_shell->ignore_enter = TRUE;
1387 1388

      gdk_event_free (current_event);
1389
    }
1390 1391
  else
    menu_shell->ignore_enter = TRUE;
1392 1393 1394

  if (menu->torn_off)
    {
1395
      gtk_menu_tearoff_bg_copy (menu);
1396

1397
      gtk_menu_reparent (menu, menu->toplevel, FALSE);
1398
    }
1399 1400 1401 1402 1403 1404 1405 1406 1407 1408
 
  if (parent_menu_shell) 
    {
      GtkWidget *toplevel;

      toplevel = gtk_widget_get_toplevel (parent_menu_shell);
      if (GTK_IS_WINDOW (toplevel))
	gtk_window_group_add_window (_gtk_window_get_group (GTK_WINDOW (toplevel)), 
				     GTK_WINDOW (menu->toplevel));
    }
Tim Janik's avatar
Tim Janik committed
1409
  
Elliot Lee's avatar
Elliot Lee committed
1410 1411 1412
  menu->parent_menu_item = parent_menu_item;
  menu->position_func = func;
  menu->position_func_data = data;
1413 1414
  menu_shell->activate_time = activate_time;

1415 1416 1417
  /* We need to show the menu here rather in the init function because
   * code expects to be able to tell if the menu is onscreen by
   * looking at the GTK_WIDGET_VISIBLE (menu)
1418
   */
Elliot Lee's avatar
Elliot Lee committed
1419
  gtk_widget_show (GTK_WIDGET (menu));
1420

1421 1422 1423 1424
  /* Position the menu, possibly changing the size request
   */
  gtk_menu_position (menu);

1425
  /* Compute the size of the toplevel and realize it so we
1426
   * can scroll correctly.
1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441
   */
  {
    GtkRequisition tmp_request;
    GtkAllocation tmp_allocation = { 0, };

    gtk_widget_size_request (menu->toplevel, &tmp_request);
    
    tmp_allocation.width = tmp_request.width;
    tmp_allocation.height = tmp_request.height;

    gtk_widget_size_allocate (menu->toplevel, &tmp_allocation);
    
    gtk_widget_realize (GTK_WIDGET (menu));
  }

1442 1443
  gtk_menu_scroll_to (menu, menu->scroll_offset);

1444 1445 1446 1447 1448
  /* Once everything is set up correctly, map the toplevel window on
     the screen.
   */
  gtk_widget_show (menu->toplevel);

1449
  if (xgrab_shell == widget)
1450
    popup_grab_on_window (widget->window, activate_time, grab_keyboard); /* Should always succeed */
Elliot Lee's avatar
Elliot Lee committed
1451 1452 1453 1454 1455 1456
  gtk_grab_add (GTK_WIDGET (menu));
}

void
gtk_menu_popdown (GtkMenu *menu)
{
1457
  GtkMenuPrivate *private;
Elliot Lee's avatar
Elliot Lee committed
1458
  GtkMenuShell *menu_shell;
1459

Elliot Lee's avatar
Elliot Lee committed
1460
  g_return_if_fail (GTK_IS_MENU (menu));
Tim Janik's avatar
Tim Janik committed
1461
  
Elliot Lee's avatar
Elliot Lee committed
1462
  menu_shell = GTK_MENU_SHELL (menu);
1463
  private = gtk_menu_get_private (menu);
Tim Janik's avatar
Tim Janik committed
1464
  
Elliot Lee's avatar
Elliot Lee committed
1465 1466
  menu_shell->parent_menu_shell = NULL;
  menu_shell->active = FALSE;
Owen Taylor's avatar