gtkmenu.c 117 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 "gtkintl.h"
Elliot Lee's avatar
Elliot Lee committed
46 47


48
#define MENU_ITEM_CLASS(w)   GTK_MENU_ITEM_GET_CLASS (w)
Elliot Lee's avatar
Elliot Lee committed
49

50
#define DEFAULT_POPUP_DELAY    225
51 52
#define DEFAULT_POPDOWN_DELAY  1000

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

57 58
#define MENU_SCROLL_STEP1 8
#define MENU_SCROLL_STEP2 15
59
#define MENU_SCROLL_ARROW_HEIGHT 16
60 61
#define MENU_SCROLL_FAST_ZONE 8
#define MENU_SCROLL_TIMEOUT1 50
62 63
#define MENU_SCROLL_TIMEOUT2 50

64
#define ATTACH_INFO_KEY "gtk-menu-child-attach-info-key"
65
#define ATTACHED_MENUS "gtk-attached-menus"
66

Tim Janik's avatar
Tim Janik committed
67
typedef struct _GtkMenuAttachData	GtkMenuAttachData;
68
typedef struct _GtkMenuPrivate  	GtkMenuPrivate;
Tim Janik's avatar
Tim Janik committed
69 70 71 72 73 74 75

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

76 77 78 79 80
struct _GtkMenuPrivate 
{
  gboolean have_position;
  gint x;
  gint y;
81 82 83 84

  /* info used for the table */
  guint *heights;
  gint heights_length;
85 86

  gint monitor_num;
87

88 89 90 91
  /* Cached layout information */
  gboolean have_layout;
  gint n_rows;
  gint n_columns;
92 93
};

94 95
typedef struct
{
96 97 98 99 100 101 102 103 104
  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;
105

106 107 108 109 110
enum {
  MOVE_SCROLL,
  LAST_SIGNAL
};

111 112
enum {
  PROP_0,
113
  PROP_TEAROFF_STATE,
114 115
  PROP_TEAROFF_TITLE
};
Tim Janik's avatar
Tim Janik committed
116

117
enum {
118 119 120 121 122 123 124
  CHILD_PROP_0,
  CHILD_PROP_LEFT_ATTACH,
  CHILD_PROP_RIGHT_ATTACH,
  CHILD_PROP_TOP_ATTACH,
  CHILD_PROP_BOTTOM_ATTACH
};

125 126
static void     gtk_menu_class_init        (GtkMenuClass     *klass);
static void     gtk_menu_init              (GtkMenu          *menu);
127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
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);
145
static void     gtk_menu_destroy           (GtkObject        *object);
146
static void     gtk_menu_finalize          (GObject          *object);
147 148 149 150 151 152
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);
153 154
static void     gtk_menu_paint             (GtkWidget        *widget,
					    GdkEventExpose   *expose);
155
static void     gtk_menu_show              (GtkWidget        *widget);
156 157 158 159
static gboolean gtk_menu_expose            (GtkWidget        *widget,
					    GdkEventExpose   *event);
static gboolean gtk_menu_key_press         (GtkWidget        *widget,
					    GdkEventKey      *event);
160 161 162 163
static gboolean gtk_menu_button_press      (GtkWidget        *widget,
					    GdkEventButton   *event);
static gboolean gtk_menu_button_release    (GtkWidget        *widget,
					    GdkEventButton   *event);
164 165 166 167 168 169 170 171
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);
172 173 174 175 176

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

177 178
static void     gtk_menu_scroll_item_visible (GtkMenuShell    *menu_shell,
					      GtkWidget       *menu_item);
179 180
static void     gtk_menu_select_item       (GtkMenuShell     *menu_shell,
					    GtkWidget        *menu_item);
181
static void     gtk_menu_real_insert       (GtkMenuShell     *menu_shell,
182 183 184 185 186 187 188 189
					    GtkWidget        *child,
					    gint              position);
static void     gtk_menu_scrollbar_changed (GtkAdjustment    *adjustment,
					    GtkMenu          *menu);
static void     gtk_menu_handle_scrolling  (GtkMenu          *menu,
					    gboolean         enter);
static void     gtk_menu_set_tearoff_hints (GtkMenu          *menu,
					    gint             width);
190 191
static void     gtk_menu_style_set         (GtkWidget        *widget,
					    GtkStyle         *previous_style);
192 193
static gboolean gtk_menu_focus             (GtkWidget        *widget,
					    GtkDirectionType direction);
194
static gint     gtk_menu_get_popup_delay   (GtkMenuShell     *menu_shell);
195 196
static void     gtk_menu_move_current      (GtkMenuShell     *menu_shell,
                                            GtkMenuDirectionType direction);
197 198
static void     gtk_menu_real_move_scroll  (GtkMenu          *menu,
					    GtkScrollType     type);
199 200 201 202 203 204 205 206 207 208

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
209
static void gtk_menu_deactivate	    (GtkMenuShell      *menu_shell);
210 211
static void gtk_menu_show_all       (GtkWidget         *widget);
static void gtk_menu_hide_all       (GtkWidget         *widget);
212
static void gtk_menu_position       (GtkMenu           *menu);
Owen Taylor's avatar
Owen Taylor committed
213 214 215
static void gtk_menu_reparent       (GtkMenu           *menu, 
				     GtkWidget         *new_parent, 
				     gboolean           unrealize);
216 217
static void gtk_menu_remove         (GtkContainer      *menu,
				     GtkWidget         *widget);
Elliot Lee's avatar
Elliot Lee committed
218

219 220
static void gtk_menu_update_title   (GtkMenu           *menu);

221 222 223
static void       menu_grab_transfer_window_destroy (GtkMenu *menu);
static GdkWindow *menu_grab_transfer_window_get     (GtkMenu *menu);

224 225
static gboolean gtk_menu_real_can_activate_accel (GtkWidget *widget,
                                                  guint      signal_id);
226 227 228
static void _gtk_menu_refresh_accel_paths (GtkMenu *menu,
					   gboolean group_changed);

229
static GtkMenuShellClass *parent_class = NULL;
230
static const gchar	 *attach_data_key = "gtk-menu-attach-data";
Tim Janik's avatar
Tim Janik committed
231

232 233
static guint menu_signals[LAST_SIGNAL] = { 0 };

234 235 236 237 238 239
static void
gtk_menu_free_private (gpointer data)
{
  GtkMenuPrivate *priv = (GtkMenuPrivate *)data;

  g_free (priv->heights);
Matthias Clasen's avatar
Matthias Clasen committed
240 241

  g_free (priv);
242 243
}

244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260
GtkMenuPrivate *
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,
261
			       private, gtk_menu_free_private);
262 263 264 265 266
    }

  return private;
}

Manish Singh's avatar
Manish Singh committed
267
GType
268
gtk_menu_get_type (void)
Elliot Lee's avatar
Elliot Lee committed
269
{
Manish Singh's avatar
Manish Singh committed
270
  static GType menu_type = 0;
Tim Janik's avatar
Tim Janik committed
271
  
Elliot Lee's avatar
Elliot Lee committed
272 273
  if (!menu_type)
    {
Manish Singh's avatar
Manish Singh committed
274
      static const GTypeInfo menu_info =
Elliot Lee's avatar
Elliot Lee committed
275 276
      {
	sizeof (GtkMenuClass),
Manish Singh's avatar
Manish Singh committed
277 278 279 280 281 282 283 284
	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
285
      };
Tim Janik's avatar
Tim Janik committed
286
      
Manish Singh's avatar
Manish Singh committed
287 288
      menu_type = g_type_register_static (GTK_TYPE_MENU_SHELL, "GtkMenu",
					  &menu_info, 0);
Elliot Lee's avatar
Elliot Lee committed
289
    }
Tim Janik's avatar
Tim Janik committed
290
  
Elliot Lee's avatar
Elliot Lee committed
291 292 293
  return menu_type;
}

294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 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
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);
      g_object_set_data_full (object, ATTACH_INFO_KEY, ai, g_free);
    }

  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
459 460 461
static void
gtk_menu_class_init (GtkMenuClass *class)
{
462 463 464 465 466
  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);
467
  GtkBindingSet *binding_set;
Tim Janik's avatar
Tim Janik committed
468
  
469
  parent_class = g_type_class_peek_parent (class);
Tim Janik's avatar
Tim Janik committed
470
  
471
  gobject_class->finalize = gtk_menu_finalize;
472 473 474
  gobject_class->set_property = gtk_menu_set_property;
  gobject_class->get_property = gtk_menu_get_property;

475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493
  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;
  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;
494
  widget_class->can_activate_accel = gtk_menu_real_can_activate_accel;
495 496 497 498 499 500 501 502 503 504 505 506

  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;

507 508 509 510 511 512 513 514 515 516
  menu_signals[MOVE_SCROLL] =
    _gtk_binding_signal_new ("move_scroll",
			     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);
  
517 518 519
  g_object_class_install_property (gobject_class,
                                   PROP_TEAROFF_TITLE,
                                   g_param_spec_string ("tearoff-title",
520 521
                                                        P_("Tearoff Title"),
                                                        P_("A title that may be displayed by the window manager when this menu is torn-off"),
522
                                                        "",
523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538
                                                        G_PARAM_READWRITE));

  /**
   * 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,
							 G_PARAM_READWRITE));
Manish Singh's avatar
Manish Singh committed
539

540 541
  gtk_widget_class_install_style_property (widget_class,
					   g_param_spec_int ("vertical-padding",
542 543
							     P_("Vertical Padding"),
							     P_("Extra space at the top and bottom of the menu"),
544 545 546 547 548
							     0,
							     G_MAXINT,
							     1,
							     G_PARAM_READABLE));

549 550
  gtk_widget_class_install_style_property (widget_class,
					   g_param_spec_int ("vertical-offset",
551 552
							     P_("Vertical Offset"),
							     P_("When the menu is a submenu, position it this number of pixels offset vertically"),
553 554 555 556 557 558 559
							     G_MININT,
							     G_MAXINT,
							     0,
							     G_PARAM_READABLE));

  gtk_widget_class_install_style_property (widget_class,
					   g_param_spec_int ("horizontal-offset",
560 561
							     P_("Horizontal Offset"),
							     P_("When the menu is a submenu, position it this number of pixels offset horizontally"),
562 563 564 565 566
							     G_MININT,
							     G_MAXINT,
							     -2,
							     G_PARAM_READABLE));

567

568 569
 gtk_container_class_install_child_property (container_class,
                                             CHILD_PROP_LEFT_ATTACH,
570
					      g_param_spec_int ("left_attach",
571 572
                                                               P_("Left Attach"),
                                                               P_("The column number to attach the left side of the child to"),
573
								-1, INT_MAX, -1,
574 575 576 577
                                                               G_PARAM_READWRITE));

 gtk_container_class_install_child_property (container_class,
                                             CHILD_PROP_RIGHT_ATTACH,
578
					      g_param_spec_int ("right_attach",
579 580
                                                               P_("Right Attach"),
                                                               P_("The column number to attach the right side of the child to"),
581
								-1, INT_MAX, -1,
582 583 584 585
                                                               G_PARAM_READWRITE));

 gtk_container_class_install_child_property (container_class,
                                             CHILD_PROP_TOP_ATTACH,
586
					      g_param_spec_int ("top_attach",
587 588
                                                               P_("Top Attach"),
                                                               P_("The row number to attach the top of the child to"),
589
								-1, INT_MAX, -1,
590 591 592 593
                                                               G_PARAM_READWRITE));

 gtk_container_class_install_child_property (container_class,
                                             CHILD_PROP_BOTTOM_ATTACH,
594
					      g_param_spec_int ("bottom_attach",
595 596
                                                               P_("Bottom Attach"),
                                                               P_("The row number to attach the bottom of the child to"),
597
								-1, INT_MAX, -1,
598
                                                               G_PARAM_READWRITE));
599 600 601 602 603 604 605

  binding_set = gtk_binding_set_by_class (class);
  gtk_binding_entry_add_signal (binding_set,
				GDK_Up, 0,
				"move_current", 1,
				GTK_TYPE_MENU_DIRECTION_TYPE,
				GTK_MENU_DIR_PREV);
606 607 608 609 610
  gtk_binding_entry_add_signal (binding_set,
				GDK_KP_Up, 0,
				"move_current", 1,
				GTK_TYPE_MENU_DIRECTION_TYPE,
				GTK_MENU_DIR_PREV);
611 612 613 614 615
  gtk_binding_entry_add_signal (binding_set,
				GDK_Down, 0,
				"move_current", 1,
				GTK_TYPE_MENU_DIRECTION_TYPE,
				GTK_MENU_DIR_NEXT);
616 617 618 619 620
  gtk_binding_entry_add_signal (binding_set,
				GDK_KP_Down, 0,
				"move_current", 1,
				GTK_TYPE_MENU_DIRECTION_TYPE,
				GTK_MENU_DIR_NEXT);
621 622 623 624 625
  gtk_binding_entry_add_signal (binding_set,
				GDK_Left, 0,
				"move_current", 1,
				GTK_TYPE_MENU_DIRECTION_TYPE,
				GTK_MENU_DIR_PARENT);
626 627 628 629 630
  gtk_binding_entry_add_signal (binding_set,
				GDK_KP_Left, 0,
				"move_current", 1,
				GTK_TYPE_MENU_DIRECTION_TYPE,
				GTK_MENU_DIR_PARENT);
631 632 633 634 635
  gtk_binding_entry_add_signal (binding_set,
				GDK_Right, 0,
				"move_current", 1,
				GTK_TYPE_MENU_DIRECTION_TYPE,
				GTK_MENU_DIR_CHILD);
636 637 638 639 640
  gtk_binding_entry_add_signal (binding_set,
				GDK_KP_Right, 0,
				"move_current", 1,
				GTK_TYPE_MENU_DIRECTION_TYPE,
				GTK_MENU_DIR_CHILD);
641 642 643 644 645 646 647 648 649 650 651 652 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
  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);
681 682

  gtk_settings_install_property (g_param_spec_boolean ("gtk-can-change-accels",
683 684
						       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
685
						       FALSE,
686
						       G_PARAM_READWRITE));
687 688

  gtk_settings_install_property (g_param_spec_int ("gtk-menu-popup-delay",
689 690
						   P_("Delay before submenus appear"),
						   P_("Minimum time the pointer must stay over a menu item before the submenu appear"),
691 692 693 694 695 696
						   0,
						   G_MAXINT,
						   DEFAULT_POPUP_DELAY,
						   G_PARAM_READWRITE));

  gtk_settings_install_property (g_param_spec_int ("gtk-menu-popdown-delay",
697 698
						   P_("Delay before hiding a submenu"),
						   P_("The time before hiding a submenu when the pointer is moving towards the submenu"),
699 700 701 702 703
						   0,
						   G_MAXINT,
						   DEFAULT_POPDOWN_DELAY,
						   G_PARAM_READWRITE));
						   
Elliot Lee's avatar
Elliot Lee committed
704 705
}

706 707 708 709 710 711 712 713 714 715 716 717 718

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)
    {
719 720 721
    case PROP_TEAROFF_STATE:
      gtk_menu_set_tearoff_state (menu, g_value_get_boolean (value));
      break;
722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742
    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)
    {
743 744 745
    case PROP_TEAROFF_STATE:
      g_value_set_boolean (value, gtk_menu_get_tearoff_state (menu));
      break;
746
    case PROP_TEAROFF_TITLE:
747
      g_value_set_string (value, gtk_menu_get_title (menu));
748 749 750 751 752 753 754
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}

755 756 757 758 759 760 761 762
static void
gtk_menu_set_child_property (GtkContainer *container,
                             GtkWidget    *child,
                             guint         property_id,
                             const GValue *value,
                             GParamSpec   *pspec)
{
  GtkMenu *menu = GTK_MENU (container);
763
  AttachInfo *ai = get_attach_info (child);
764 765 766

  switch (property_id)
    {
767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782
    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;
783 784
    }

785
  menu_queue_resize (menu);
786 787 788 789 790 791 792 793 794
}

static void
gtk_menu_get_child_property (GtkContainer *container,
                             GtkWidget    *child,
                             guint         property_id,
                             GValue       *value,
                             GParamSpec   *pspec)
{
795
  AttachInfo *ai = get_attach_info (child);
796 797 798

  switch (property_id)
    {
799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814
    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;
815 816 817
    }
}

818
static gboolean
819 820 821 822 823 824
gtk_menu_window_event (GtkWidget *window,
		       GdkEvent  *event,
		       GtkWidget *menu)
{
  gboolean handled = FALSE;

Manish Singh's avatar
Manish Singh committed
825 826
  g_object_ref (window);
  g_object_ref (menu);
827 828 829 830 831

  switch (event->type)
    {
    case GDK_KEY_PRESS:
    case GDK_KEY_RELEASE:
832
      handled = gtk_widget_event (menu, event);
833 834 835 836 837
      break;
    default:
      break;
    }

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

  return handled;
}

844 845 846 847 848 849 850 851 852
static void
gtk_menu_window_size_request (GtkWidget      *window,
			      GtkRequisition *requisition,
			      GtkMenu        *menu)
{
  GtkMenuPrivate *private = gtk_menu_get_private (menu);

  if (private->have_position)
    {
853
      GdkScreen *screen = gtk_widget_get_screen (window);
854 855
      GdkRectangle monitor;
      
856
      gdk_screen_get_monitor_geometry (screen, private->monitor_num, &monitor);
857 858 859

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

861 862
      if (private->y < monitor.y)
	requisition->height -= monitor.y - private->y;
863 864 865
    }
}

Elliot Lee's avatar
Elliot Lee committed
866 867 868
static void
gtk_menu_init (GtkMenu *menu)
{
869 870
  GtkMenuPrivate *priv = gtk_menu_get_private (menu);

Elliot Lee's avatar
Elliot Lee committed
871 872
  menu->parent_menu_item = NULL;
  menu->old_active_menu_item = NULL;
Tim Janik's avatar
Tim Janik committed
873
  menu->accel_group = NULL;
Elliot Lee's avatar
Elliot Lee committed
874 875
  menu->position_func = NULL;
  menu->position_func_data = NULL;
876
  menu->toggle_size = 0;
Tim Janik's avatar
Tim Janik committed
877

Manish Singh's avatar
Manish Singh committed
878 879 880 881
  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
882
				     "signal::event", gtk_menu_window_event, menu,
883
				     "signal::size_request", gtk_menu_window_size_request, menu,
Tim Janik's avatar
Tim Janik committed
884 885
				     "signal::destroy", gtk_widget_destroyed, &menu->toplevel,
				     NULL);
Manish Singh's avatar
Manish Singh committed
886
  gtk_window_set_resizable (GTK_WINDOW (menu->toplevel), FALSE);
887
  gtk_window_set_mnemonic_modifier (GTK_WINDOW (menu->toplevel), 0);
888

Owen Taylor's avatar
Owen Taylor committed
889 890 891 892
  /* 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);
893
  menu->needs_destruction_ref_count = TRUE;
Owen Taylor's avatar
Owen Taylor committed
894

895 896 897 898 899 900 901 902
  menu->view_window = NULL;
  menu->bin_window = NULL;

  menu->scroll_offset = 0;
  menu->scroll_step  = 0;
  menu->timeout_id = 0;
  menu->scroll_fast = FALSE;
  
903
  menu->tearoff_window = NULL;
904
  menu->tearoff_hbox = NULL;
905
  menu->torn_off = FALSE;
906 907 908
  menu->tearoff_active = FALSE;
  menu->tearoff_adjustment = NULL;
  menu->tearoff_scrollbar = NULL;
909

910 911 912 913
  menu->upper_arrow_visible = FALSE;
  menu->lower_arrow_visible = FALSE;
  menu->upper_arrow_prelight = FALSE;
  menu->lower_arrow_prelight = FALSE;
914

915
  priv->have_layout = FALSE;
916 917 918
}

static void
919
gtk_menu_destroy (GtkObject *object)
920
{
921
  GtkMenu *menu;
Tim Janik's avatar
Tim Janik committed
922
  GtkMenuAttachData *data;
923
  GtkMenuPrivate *priv;
924

925
  g_return_if_fail (GTK_IS_MENU (object));
926 927

  menu = GTK_MENU (object);
928 929

  gtk_menu_stop_scrolling (menu);
Tim Janik's avatar
Tim Janik committed
930
  
Manish Singh's avatar
Manish Singh committed
931
  data = g_object_get_data (G_OBJECT (object), attach_data_key);
Tim Janik's avatar
Tim Janik committed
932
  if (data)
933
    gtk_menu_detach (menu);
Tim Janik's avatar
Tim Janik committed
934
  
935 936
  gtk_menu_stop_navigating_submenu (menu);

937 938
  if (menu->old_active_menu_item)
    {
Manish Singh's avatar
Manish Singh committed
939
      g_object_unref (menu->old_active_menu_item);
940 941 942
      menu->old_active_menu_item = NULL;
    }

Owen Taylor's avatar
Owen Taylor committed
943
  /* Add back the reference count for being a child */
944 945 946
  if (menu->needs_destruction_ref_count)
    {
      menu->needs_destruction_ref_count = FALSE;
Manish Singh's avatar
Manish Singh committed
947
      g_object_ref (object);
948
    }
Tim Janik's avatar
Tim Janik committed
949
  
950 951 952 953 954 955
  if (menu->accel_group)
    {
      g_object_unref (menu->accel_group);
      menu->accel_group = NULL;
    }

956 957
  if (menu->toplevel)
    gtk_widget_destroy (menu->toplevel);
958 959 960
  if (menu->tearoff_window)
    gtk_widget_destroy (menu->tearoff_window);

961 962
  priv = gtk_menu_get_private (menu);

963
  GTK_OBJECT_CLASS (parent_class)->destroy (object);
Tim Janik's avatar
Tim Janik committed
964 965
}

966 967 968 969 970 971 972 973 974
static void
gtk_menu_finalize (GObject *object)
{
  GtkMenu *menu = GTK_MENU (object);

  g_free (menu->accel_path);
  
  G_OBJECT_CLASS (parent_class)->finalize (object);
}
Tim Janik's avatar
Tim Janik committed
975

976 977 978 979
static void
menu_change_screen (GtkMenu   *menu,
		    GdkScreen *new_screen)
{
980 981
  GtkMenuPrivate *private = gtk_menu_get_private (menu);

982 983 984 985 986 987 988
  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);
989
  private->monitor_num = -1;
990 991
}

992 993 994 995 996 997 998 999
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"))
    {
1000
      menu_change_screen (menu, gtk_widget_get_screen (attach_widget));
1001 1002 1003
    }
}

Tim Janik's avatar
Tim Janik committed
1004
void
Tim Janik's avatar
Tim Janik committed
1005 1006 1007
gtk_menu_attach_to_widget (GtkMenu	       *menu,
			   GtkWidget	       *attach_widget,
			   GtkMenuDetachFunc	detacher)
Tim Janik's avatar
Tim Janik committed
1008 1009
{
  GtkMenuAttachData *data;
1010
  GList *list;
Tim Janik's avatar
Tim Janik committed
1011
  
Tim Janik's avatar
Tim Janik committed
1012 1013 1014
  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
1015
  
Tim Janik's avatar
Tim Janik committed
1016 1017
  /* keep this function in sync with gtk_widget_set_parent()
   */
Tim Janik's avatar
Tim Janik committed
1018
  
Manish Singh's avatar
Manish Singh committed
1019
  data = g_object_get_data (G_OBJECT (menu), attach_data_key);
Tim Janik's avatar
Tim Janik committed
1020 1021 1022
  if (data)
    {
      g_warning ("gtk_menu_attach_to_widget(): menu already attached to %s",
Manish Singh's avatar
Manish Singh committed
1023
		 g_type_name (G_TYPE_FROM_INSTANCE (data->attach_widget)));
1024
     return;
Tim Janik's avatar
Tim Janik committed
1025
    }
Tim Janik's avatar
Tim Janik committed
1026
  
Manish Singh's avatar
Manish Singh committed
1027
  g_object_ref (menu);
Tim Janik's avatar
Tim Janik committed
1028
  gtk_object_sink (GTK_OBJECT (menu));
Tim Janik's avatar
Tim Janik committed
1029
  
Tim Janik's avatar
Tim Janik committed
1030 1031
  data = g_new (GtkMenuAttachData, 1);
  data->attach_widget = attach_widget;
1032 1033 1034 1035 1036
  
  g_signal_connect (attach_widget, "screen_changed",
		    G_CALLBACK (attach_widget_screen_changed), menu);
  attach_widget_screen_changed (attach_widget, NULL, menu);
  
Tim Janik's avatar
Tim Janik committed
1037
  data->detacher = detacher;
Manish Singh's avatar
Manish Singh committed
1038
  g_object_set_data (G_OBJECT (menu), attach_data_key, data);
1039 1040 1041 1042 1043 1044
  list = g_object_get_data (G_OBJECT (attach_widget), ATTACHED_MENUS);
  if (!g_list_find (list, menu))
    {
      list = g_list_prepend (list, menu);
    }
  g_object_set_data_full (G_OBJECT (attach_widget), ATTACHED_MENUS, list, (GtkDestroyNotify) g_list_free);
Tim Janik's avatar
Tim Janik committed
1045
  
Tim Janik's avatar
Tim Janik committed
1046 1047
  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
1048
  
Tim Janik's avatar
Tim Janik committed
1049 1050 1051
  /* we don't need to set the style here, since
   * we are a toplevel widget.
   */
1052 1053 1054

  /* Fallback title for menu comes from attach widget */
  gtk_menu_update_title (menu);
Tim Janik's avatar
Tim Janik committed
1055 1056
}

1057
GtkWidget*
1058
gtk_menu_get_attach_widget (GtkMenu *menu)
1059 1060
{
  GtkMenuAttachData *data;
Tim Janik's avatar
Tim Janik committed
1061
  
1062
  g_return_val_if_fail (GTK_IS_MENU (menu), NULL);
Tim Janik's avatar
Tim Janik committed
1063
  
Manish Singh's avatar
Manish Singh committed
1064
  data = g_object_get_data (G_OBJECT (menu), attach_data_key);
1065 1066 1067 1068 1069
  if (data)
    return data->attach_widget;
  return NULL;
}

Tim Janik's avatar
Tim Janik committed
1070
void
1071
gtk_menu_detach (GtkMenu *menu)
Tim Janik's avatar
Tim Janik committed
1072 1073
{
  GtkMenuAttachData *data;
1074
  GList *list;
Tim Janik's avatar
Tim Janik committed
1075
  
Tim Janik's avatar
Tim Janik committed
1076
  g_return_if_fail (GTK_IS_MENU (menu));
Tim Janik's avatar
Tim Janik committed
1077
  
Tim Janik's avatar
Tim Janik committed
1078 1079
  /* keep this function in sync with gtk_widget_unparent()
   */
Manish Singh's avatar
Manish Singh committed
1080
  data = g_object_get_data (G_OBJECT (menu), attach_data_key);
Tim Janik's avatar
Tim Janik committed
1081 1082 1083 1084 1085
  if (!data)
    {
      g_warning ("gtk_menu_detach(): menu is not attached");
      return;
    }
Manish Singh's avatar
Manish Singh committed
1086
  g_object_set_data (G_OBJECT (menu), attach_data_key, NULL);
Tim Janik's avatar
Tim Janik committed
1087
  
1088 1089 1090 1091
  g_signal_handlers_disconnect_by_func (data->attach_widget,
					(gpointer) attach_widget_screen_changed,
					menu);

Tim Janik's avatar
Tim Janik committed
1092
  data->detacher (data->attach_widget, menu);
1093 1094 1095 1096 1097 1098
  list = g_object_steal_data (G_OBJECT (data->attach_widget), ATTACHED_MENUS);
  list = g_list_remove (list, menu);
  if (list)
    g_object_set_data_full (G_OBJECT (data->attach_widget), ATTACHED_MENUS, list, (GtkDestroyNotify) g_list_free);
  else
    g_object_set_data (G_OBJECT (data->attach_widget), ATTACHED_MENUS, NULL);
Tim Janik's avatar
Tim Janik committed
1099
  
Tim Janik's avatar
Tim Janik committed
1100 1101
  if (GTK_WIDGET_REALIZED (menu))
    gtk_widget_unrealize (GTK_WIDGET (menu));
Tim Janik's avatar
Tim Janik committed
1102
  
Tim Janik's avatar
Tim Janik committed
1103
  g_free (data);
Tim Janik's avatar
Tim Janik committed
1104
  
1105 1106 1107
  /* Fallback title for menu comes from attach widget */
  gtk_menu_update_title (menu);

Manish Singh's avatar
Manish Singh committed
1108
  g_object_unref (menu);
Elliot Lee's avatar
Elliot Lee committed
1109 1110
}

1111
static void
1112 1113
gtk_menu_remove (GtkContainer *container,
		 GtkWidget    *widget)
1114 1115
{
  GtkMenu *menu;
1116
  GtkMenuPrivate *priv;
1117

1118 1119 1120 1121
  g_return_if_fail (GTK_IS_MENU (container));
  g_return_if_fail (GTK_IS_MENU_ITEM (widget));

  menu = GTK_MENU (container);
1122
  priv = gtk_menu_get_private (menu);
1123 1124 1125 1126 1127

  /* 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
1128
      g_object_unref (menu->old_active_menu_item);
1129 1130 1131 1132
      menu->old_active_menu_item = NULL;
    }

  GTK_CONTAINER_CLASS (parent_class)->remove (container, widget);
1133
  g_object_set_data (G_OBJECT (widget), ATTACH_INFO_KEY, NULL);
1134

1135 1136
  menu_queue_resize (menu);
}
1137

Elliot Lee's avatar
Elliot Lee committed
1138
GtkWidget*
1139
gtk_menu_new (void)
Elliot Lee's avatar
Elliot Lee committed
1140
{
Manish Singh's avatar
Manish Singh committed
1141
  return g_object_new (GTK_TYPE_MENU, NULL);
Elliot Lee's avatar
Elliot Lee committed
1142 1143
}

1144
static void
1145 1146 1147
gtk_menu_real_insert (GtkMenuShell *menu_shell,
		      GtkWidget    *child,
		      gint          position)
Elliot Lee's avatar
Elliot Lee committed
1148
{
1149 1150
  GtkMenu *menu = GTK_MENU (menu_shell);
  AttachInfo *ai = get_attach_info (child);
1151

1152 1153 1154 1155
  ai->left_attach = -1;
  ai->right_attach = -1;
  ai->top_attach = -1;
  ai->bottom_attach = -1;
1156

1157
  if (GTK_WIDGET_REALIZED (menu_shell))
1158
    gtk_widget_set_parent_window (child, menu->bin_window);
1159 1160 1161

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

1162
  menu_queue_resize (menu);
1163 1164
}

1165 1166 1167 1168
static void
gtk_menu_tearoff_bg_copy (GtkMenu *menu)
{
  GtkWidget *widget;
1169
  gint width, height;
1170 1171 1172 1173 1174 1175 1176 1177

  widget = GTK_WIDGET (menu);

  if (menu->torn_off)
    {
      GdkPixmap *pixmap;
      GdkGC *gc;
      GdkGCValues gc_values;
1178 1179 1180

      menu->tearoff_active = FALSE;
      menu->saved_scroll_offset = menu->scroll_offset;
1181 1182 1183 1184 1185
      
      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
1186
      gdk_drawable_get_size (menu->tearoff_window->window, &width, &height);
1187 1188 1189 1190
      
      pixmap = gdk_pixmap_new (menu->tearoff_window->window,
			       width,
			       height,
1191 1192
			       -1);

Manish Singh's avatar
Manish Singh committed
1193 1194 1195 1196
      gdk_draw_drawable (pixmap, gc,
			 menu->tearoff_window->window,
			 0, 0, 0, 0, -1, -1);
      g_object_unref (gc);
1197

Manish Singh's avatar
Manish Singh committed
1198 1199 1200
      gtk_widget_set_size_request (menu->tearoff_window,
				   width,
				   height);
1201

1202
      gdk_window_set_back_pixmap (menu->tearoff_window->window, pixmap, FALSE);
Manish Singh's avatar
Manish Singh committed
1203
      g_object_unref (pixmap);
1204 1205 1206
    }
}

1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221
static gboolean
popup_grab_on_window (GdkWindow *window,
		      guint32    activate_time)
{
  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))
    {
      if (gdk_keyboard_grab (window, TRUE,
			     activate_time) == 0)
	return TRUE;
      else
	{
1222 1223
	  gdk_display_pointer_ungrab (gdk_drawable_get_display (window),
				      activate_time);
1224 1225 1226 1227 1228 1229 1230
	  return FALSE;
	}
    }

  return FALSE;
}

Soeren Sandmann's avatar
Soeren Sandmann committed
1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256
/**
 * 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
1257
void
Tim Janik's avatar
Tim Janik committed
1258 1259 1260
gtk_menu_popup (GtkMenu		    *menu,
		GtkWidget	    *parent_menu_shell,
		GtkWidget	    *parent_menu_item,
Elliot Lee's avatar
Elliot Lee committed
1261
		GtkMenuPositionFunc  func,
Tim Janik's avatar
Tim Janik committed
1262 1263 1264
		gpointer	     data,
		guint		     button,
		guint32		     activate_time)
Elliot Lee's avatar
Elliot Lee committed
1265
{
1266
  GtkWidget *widget;
Owen Taylor's avatar
Owen Taylor committed
1267 1268
  GtkWidget *xgrab_shell;
  GtkWidget *parent;
1269 1270
  GdkEvent *current_event;
  GtkMenuShell *menu_shell;
1271

Elliot Lee's avatar
Elliot Lee committed
1272
  g_return_if_fail (GTK_IS_MENU (menu));
Tim Janik's avatar
Tim Janik committed
1273
  
1274 1275 1276 1277
  widget = GTK_WIDGET (menu);
  menu_shell = GTK_MENU_SHELL (menu);
  
  menu_shell->parent_menu_shell = parent_menu_shell;
1278
  
1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318
  /* 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*).
   */
1319
  if (xgrab_shell && xgrab_shell != widget)
1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340
    {
      if (popup_grab_on_window (xgrab_shell->window, activate_time))
	GTK_MENU_SHELL (xgrab_shell)->have_xgrab = TRUE;
    }
  else
    {
      GdkWindow *transfer_window;

      xgrab_shell = widget;
      transfer_window = menu_grab_transfer_window_get (menu);
      if (popup_grab_on_window (transfer_window, activate_time))
	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;
1341
      menu_grab_transfer_window_destroy (menu);
1342 1343 1344
      return;
    }

1345 1346 1347 1348 1349 1350 1351 1352
  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.  
   */

1353
  current_event = gtk_get_current_event ();
1354 1355 1356 1357 1358
  if (current_event)
    {
      if ((current_event->type != GDK_BUTTON_PRESS) &&
	  (current_event->type != GDK_ENTER_NOTIFY))
	menu_shell->ignore_enter = TRUE;
1359 1360

      gdk_event_free (current_event);
1361
    }
1362 1363
  else
    menu_shell->ignore_enter = TRUE;
1364 1365 1366

  if (menu->torn_off)
    {
1367
      gtk_menu_tearoff_bg_copy (menu);
1368

1369
      gtk_menu_reparent (menu, menu->toplevel, FALSE);
1370
    }
1371 1372 1373 1374 1375 1376 1377 1378 1379 1380
 
  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
1381
  
Elliot Lee's avatar
Elliot Lee committed
1382 1383 1384
  menu->parent_menu_item = parent_menu_item;
  menu->position_func = func;
  menu->position_func_data = data;
1385 1386
  menu_shell->activate_time = activate_time;