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


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

51
#define DEFAULT_POPUP_DELAY    225
52 53
#define DEFAULT_POPDOWN_DELAY  1000

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

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

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
struct _GtkMenuPrivate 
{
78 79
  gboolean seen_item_enter;

80 81 82
  gboolean have_position;
  gint x;
  gint y;
83 84 85 86

  /* info used for the table */
  guint *heights;
  gint heights_length;
87 88

  gint monitor_num;
89

90 91 92 93
  /* Cached layout information */
  gboolean have_layout;
  gint n_rows;
  gint n_columns;
94 95

  gchar *title;
96 97 98 99 100 101 102

  /* Arrow states */
  GtkStateType lower_arrow_state;
  GtkStateType upper_arrow_state;

  gboolean ignore_button_release;
  gboolean initially_pushed_in;
103 104
};

105 106
typedef struct
{
107 108 109 110 111 112 113 114 115
  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;
116

117 118 119 120 121
enum {
  MOVE_SCROLL,
  LAST_SIGNAL
};

122 123
enum {
  PROP_0,
124
  PROP_TEAROFF_STATE,
125 126
  PROP_TEAROFF_TITLE
};
Tim Janik's avatar
Tim Janik committed
127

128
enum {
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153
  CHILD_PROP_0,
  CHILD_PROP_LEFT_ATTACH,
  CHILD_PROP_RIGHT_ATTACH,
  CHILD_PROP_TOP_ATTACH,
  CHILD_PROP_BOTTOM_ATTACH
};

static void     gtk_menu_set_property      (GObject          *object,
					    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);
154
static void     gtk_menu_destroy           (GtkObject        *object);
155
static void     gtk_menu_finalize          (GObject          *object);
156 157 158 159 160 161
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);
162 163
static void     gtk_menu_paint             (GtkWidget        *widget,
					    GdkEventExpose   *expose);
164
static void     gtk_menu_show              (GtkWidget        *widget);
165 166 167 168
static gboolean gtk_menu_expose            (GtkWidget        *widget,
					    GdkEventExpose   *event);
static gboolean gtk_menu_key_press         (GtkWidget        *widget,
					    GdkEventKey      *event);
169 170
static gboolean gtk_menu_scroll            (GtkWidget        *widget,
					    GdkEventScroll   *event);
171 172 173 174
static gboolean gtk_menu_button_press      (GtkWidget        *widget,
					    GdkEventButton   *event);
static gboolean gtk_menu_button_release    (GtkWidget        *widget,
					    GdkEventButton   *event);
175 176 177 178 179 180 181 182
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);
183 184
static void     gtk_menu_grab_notify       (GtkWidget        *widget,
					    gboolean          was_grabbed);
185

186 187 188 189 190
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);
static gboolean gtk_menu_scroll_timeout_initial (gpointer  data);
static void     gtk_menu_start_scrolling        (GtkMenu  *menu);
191

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

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
227
static void gtk_menu_deactivate	    (GtkMenuShell      *menu_shell);
228 229
static void gtk_menu_show_all       (GtkWidget         *widget);
static void gtk_menu_hide_all       (GtkWidget         *widget);
230
static void gtk_menu_position       (GtkMenu           *menu);
Owen Taylor's avatar
Owen Taylor committed
231 232 233
static void gtk_menu_reparent       (GtkMenu           *menu, 
				     GtkWidget         *new_parent, 
				     gboolean           unrealize);
234 235
static void gtk_menu_remove         (GtkContainer      *menu,
				     GtkWidget         *widget);
Elliot Lee's avatar
Elliot Lee committed
236

237 238
static void gtk_menu_update_title   (GtkMenu           *menu);

239 240 241
static void       menu_grab_transfer_window_destroy (GtkMenu *menu);
static GdkWindow *menu_grab_transfer_window_get     (GtkMenu *menu);

242 243
static gboolean gtk_menu_real_can_activate_accel (GtkWidget *widget,
                                                  guint      signal_id);
244 245 246
static void _gtk_menu_refresh_accel_paths (GtkMenu *menu,
					   gboolean group_changed);

247
static const gchar	  attach_data_key[] = "gtk-menu-attach-data";
Tim Janik's avatar
Tim Janik committed
248

249 250
static guint menu_signals[LAST_SIGNAL] = { 0 };

251
static GtkMenuPrivate *
252 253
gtk_menu_get_private (GtkMenu *menu)
{
254
  return G_TYPE_INSTANCE_GET_PRIVATE (menu, GTK_TYPE_MENU, GtkMenuPrivate);
255 256
}

Matthias Clasen's avatar
Matthias Clasen committed
257
G_DEFINE_TYPE (GtkMenu, gtk_menu, GTK_TYPE_MENU_SHELL);
Elliot Lee's avatar
Elliot Lee committed
258

259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276
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);
Matthias Clasen's avatar
Matthias Clasen committed
277
      g_object_set_data_full (object, I_(ATTACH_INFO_KEY), ai, g_free);
278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 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
    }

  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
424 425 426
static void
gtk_menu_class_init (GtkMenuClass *class)
{
427 428 429 430 431
  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);
432
  GtkBindingSet *binding_set;
Tim Janik's avatar
Tim Janik committed
433
  
434
  gobject_class->finalize = gtk_menu_finalize;
435 436 437
  gobject_class->set_property = gtk_menu_set_property;
  gobject_class->get_property = gtk_menu_get_property;

438 439 440 441 442 443 444 445
  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;
446
  widget_class->scroll_event = gtk_menu_scroll;
447 448 449 450 451 452 453 454 455 456
  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->style_set = gtk_menu_style_set;
  widget_class->focus = gtk_menu_focus;
457
  widget_class->can_activate_accel = gtk_menu_real_can_activate_accel;
458
  widget_class->grab_notify = gtk_menu_grab_notify;
459 460 461 462 463 464 465 466 467 468 469 470

  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;

471
  menu_signals[MOVE_SCROLL] =
Matthias Clasen's avatar
Matthias Clasen committed
472
    _gtk_binding_signal_new (I_("move_scroll"),
473 474 475 476 477 478 479 480
			     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);
  
481 482 483
  g_object_class_install_property (gobject_class,
                                   PROP_TEAROFF_TITLE,
                                   g_param_spec_string ("tearoff-title",
484 485
                                                        P_("Tearoff Title"),
                                                        P_("A title that may be displayed by the window manager when this menu is torn-off"),
486
                                                        "",
487
                                                        GTK_PARAM_READWRITE));
488 489 490 491 492 493 494 495 496 497 498 499 500 501

  /**
   * 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,
502
							 GTK_PARAM_READWRITE));
Manish Singh's avatar
Manish Singh committed
503

504 505
  gtk_widget_class_install_style_property (widget_class,
					   g_param_spec_int ("vertical-padding",
506 507
							     P_("Vertical Padding"),
							     P_("Extra space at the top and bottom of the menu"),
508 509 510
							     0,
							     G_MAXINT,
							     1,
511
							     GTK_PARAM_READABLE));
512

513 514 515 516 517 518 519 520 521
  gtk_widget_class_install_style_property (widget_class,
                                           g_param_spec_int ("horizontal-padding",
                                                             P_("Horizontal Padding"),
                                                             P_("Extra space at the left and right edges of the menu"),
                                                             0,
                                                             G_MAXINT,
                                                             0,
                                                             GTK_PARAM_READABLE));

522 523
  gtk_widget_class_install_style_property (widget_class,
					   g_param_spec_int ("vertical-offset",
524 525
							     P_("Vertical Offset"),
							     P_("When the menu is a submenu, position it this number of pixels offset vertically"),
526 527 528
							     G_MININT,
							     G_MAXINT,
							     0,
529
							     GTK_PARAM_READABLE));
530 531 532

  gtk_widget_class_install_style_property (widget_class,
					   g_param_spec_int ("horizontal-offset",
533 534
							     P_("Horizontal Offset"),
							     P_("When the menu is a submenu, position it this number of pixels offset horizontally"),
535 536 537
							     G_MININT,
							     G_MAXINT,
							     -2,
538
							     GTK_PARAM_READABLE));
539

540 541 542 543 544 545 546
  gtk_widget_class_install_style_property (widget_class,
                                           g_param_spec_boolean ("double-arrows",
                                                                 P_("Double Arrows"),
                                                                 P_("When scrolling, always show both arrows."),
                                                                 TRUE,
                                                                 GTK_PARAM_READABLE));

547

548 549
 gtk_container_class_install_child_property (container_class,
                                             CHILD_PROP_LEFT_ATTACH,
550
					      g_param_spec_int ("left-attach",
551 552
                                                               P_("Left Attach"),
                                                               P_("The column number to attach the left side of the child to"),
553
								-1, INT_MAX, -1,
554
                                                               GTK_PARAM_READWRITE));
555 556 557

 gtk_container_class_install_child_property (container_class,
                                             CHILD_PROP_RIGHT_ATTACH,
558
					      g_param_spec_int ("right-attach",
559 560
                                                               P_("Right Attach"),
                                                               P_("The column number to attach the right side of the child to"),
561
								-1, INT_MAX, -1,
562
                                                               GTK_PARAM_READWRITE));
563 564 565

 gtk_container_class_install_child_property (container_class,
                                             CHILD_PROP_TOP_ATTACH,
566
					      g_param_spec_int ("top-attach",
567 568
                                                               P_("Top Attach"),
                                                               P_("The row number to attach the top of the child to"),
569
								-1, INT_MAX, -1,
570
                                                               GTK_PARAM_READWRITE));
571 572 573

 gtk_container_class_install_child_property (container_class,
                                             CHILD_PROP_BOTTOM_ATTACH,
574
					      g_param_spec_int ("bottom-attach",
575 576
                                                               P_("Bottom Attach"),
                                                               P_("The row number to attach the bottom of the child to"),
577
								-1, INT_MAX, -1,
578
                                                               GTK_PARAM_READWRITE));
579 580 581 582

  binding_set = gtk_binding_set_by_class (class);
  gtk_binding_entry_add_signal (binding_set,
				GDK_Up, 0,
Matthias Clasen's avatar
Matthias Clasen committed
583
				I_("move_current"), 1,
584 585
				GTK_TYPE_MENU_DIRECTION_TYPE,
				GTK_MENU_DIR_PREV);
586 587 588 589 590
  gtk_binding_entry_add_signal (binding_set,
				GDK_KP_Up, 0,
				"move_current", 1,
				GTK_TYPE_MENU_DIRECTION_TYPE,
				GTK_MENU_DIR_PREV);
591 592 593 594 595
  gtk_binding_entry_add_signal (binding_set,
				GDK_Down, 0,
				"move_current", 1,
				GTK_TYPE_MENU_DIRECTION_TYPE,
				GTK_MENU_DIR_NEXT);
596 597 598 599 600
  gtk_binding_entry_add_signal (binding_set,
				GDK_KP_Down, 0,
				"move_current", 1,
				GTK_TYPE_MENU_DIRECTION_TYPE,
				GTK_MENU_DIR_NEXT);
601 602 603 604 605
  gtk_binding_entry_add_signal (binding_set,
				GDK_Left, 0,
				"move_current", 1,
				GTK_TYPE_MENU_DIRECTION_TYPE,
				GTK_MENU_DIR_PARENT);
606 607 608 609 610
  gtk_binding_entry_add_signal (binding_set,
				GDK_KP_Left, 0,
				"move_current", 1,
				GTK_TYPE_MENU_DIRECTION_TYPE,
				GTK_MENU_DIR_PARENT);
611 612 613 614 615
  gtk_binding_entry_add_signal (binding_set,
				GDK_Right, 0,
				"move_current", 1,
				GTK_TYPE_MENU_DIRECTION_TYPE,
				GTK_MENU_DIR_CHILD);
616 617 618 619 620
  gtk_binding_entry_add_signal (binding_set,
				GDK_KP_Right, 0,
				"move_current", 1,
				GTK_TYPE_MENU_DIRECTION_TYPE,
				GTK_MENU_DIR_CHILD);
621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660
  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);
661 662

  gtk_settings_install_property (g_param_spec_boolean ("gtk-can-change-accels",
663 664
						       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
665
						       FALSE,
666
						       GTK_PARAM_READWRITE));
667 668

  gtk_settings_install_property (g_param_spec_int ("gtk-menu-popup-delay",
669 670
						   P_("Delay before submenus appear"),
						   P_("Minimum time the pointer must stay over a menu item before the submenu appear"),
671 672 673
						   0,
						   G_MAXINT,
						   DEFAULT_POPUP_DELAY,
674
						   GTK_PARAM_READWRITE));
675 676

  gtk_settings_install_property (g_param_spec_int ("gtk-menu-popdown-delay",
677 678
						   P_("Delay before hiding a submenu"),
						   P_("The time before hiding a submenu when the pointer is moving towards the submenu"),
679 680 681
						   0,
						   G_MAXINT,
						   DEFAULT_POPDOWN_DELAY,
682
						   GTK_PARAM_READWRITE));
683 684

  g_type_class_add_private (gobject_class, sizeof (GtkMenuPrivate));
Elliot Lee's avatar
Elliot Lee committed
685 686
}

687 688 689 690 691 692 693 694 695 696 697 698 699

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)
    {
700 701 702
    case PROP_TEAROFF_STATE:
      gtk_menu_set_tearoff_state (menu, g_value_get_boolean (value));
      break;
703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723
    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)
    {
724 725 726
    case PROP_TEAROFF_STATE:
      g_value_set_boolean (value, gtk_menu_get_tearoff_state (menu));
      break;
727
    case PROP_TEAROFF_TITLE:
728
      g_value_set_string (value, gtk_menu_get_title (menu));
729 730 731 732 733 734 735
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}

736 737 738 739 740 741 742 743
static void
gtk_menu_set_child_property (GtkContainer *container,
                             GtkWidget    *child,
                             guint         property_id,
                             const GValue *value,
                             GParamSpec   *pspec)
{
  GtkMenu *menu = GTK_MENU (container);
744
  AttachInfo *ai = get_attach_info (child);
745 746 747

  switch (property_id)
    {
748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763
    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;
764 765
    }

766
  menu_queue_resize (menu);
767 768 769 770 771 772 773 774 775
}

static void
gtk_menu_get_child_property (GtkContainer *container,
                             GtkWidget    *child,
                             guint         property_id,
                             GValue       *value,
                             GParamSpec   *pspec)
{
776
  AttachInfo *ai = get_attach_info (child);
777 778 779

  switch (property_id)
    {
780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795
    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;
796 797 798
    }
}

799
static gboolean
800 801 802 803 804 805
gtk_menu_window_event (GtkWidget *window,
		       GdkEvent  *event,
		       GtkWidget *menu)
{
  gboolean handled = FALSE;

Manish Singh's avatar
Manish Singh committed
806 807
  g_object_ref (window);
  g_object_ref (menu);
808 809 810 811 812

  switch (event->type)
    {
    case GDK_KEY_PRESS:
    case GDK_KEY_RELEASE:
813
      handled = gtk_widget_event (menu, event);
814 815 816 817 818
      break;
    default:
      break;
    }

Manish Singh's avatar
Manish Singh committed
819 820
  g_object_unref (window);
  g_object_unref (menu);
821 822 823 824

  return handled;
}

825 826 827 828 829 830 831 832 833
static void
gtk_menu_window_size_request (GtkWidget      *window,
			      GtkRequisition *requisition,
			      GtkMenu        *menu)
{
  GtkMenuPrivate *private = gtk_menu_get_private (menu);

  if (private->have_position)
    {
834
      GdkScreen *screen = gtk_widget_get_screen (window);
835 836
      GdkRectangle monitor;
      
837
      gdk_screen_get_monitor_geometry (screen, private->monitor_num, &monitor);
838 839 840

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

842 843
      if (private->y < monitor.y)
	requisition->height -= monitor.y - private->y;
844 845 846
    }
}

Elliot Lee's avatar
Elliot Lee committed
847 848 849
static void
gtk_menu_init (GtkMenu *menu)
{
850 851
  GtkMenuPrivate *priv = gtk_menu_get_private (menu);

Elliot Lee's avatar
Elliot Lee committed
852 853
  menu->parent_menu_item = NULL;
  menu->old_active_menu_item = NULL;
Tim Janik's avatar
Tim Janik committed
854
  menu->accel_group = NULL;
Elliot Lee's avatar
Elliot Lee committed
855 856
  menu->position_func = NULL;
  menu->position_func_data = NULL;
857
  menu->toggle_size = 0;
Tim Janik's avatar
Tim Janik committed
858

Manish Singh's avatar
Manish Singh committed
859 860 861 862
  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
863
				     "signal::event", gtk_menu_window_event, menu,
864
				     "signal::size_request", gtk_menu_window_size_request, menu,
Tim Janik's avatar
Tim Janik committed
865 866
				     "signal::destroy", gtk_widget_destroyed, &menu->toplevel,
				     NULL);
Manish Singh's avatar
Manish Singh committed
867
  gtk_window_set_resizable (GTK_WINDOW (menu->toplevel), FALSE);
868
  gtk_window_set_mnemonic_modifier (GTK_WINDOW (menu->toplevel), 0);
869

Owen Taylor's avatar
Owen Taylor committed
870 871 872
  /* Refloat the menu, so that reference counting for the menu isn't
   * affected by it being a child of the toplevel
   */
873
  g_object_force_floating (G_OBJECT (menu));
874
  menu->needs_destruction_ref_count = TRUE;
Owen Taylor's avatar
Owen Taylor committed
875

876 877 878 879 880 881 882 883
  menu->view_window = NULL;
  menu->bin_window = NULL;

  menu->scroll_offset = 0;
  menu->scroll_step  = 0;
  menu->timeout_id = 0;
  menu->scroll_fast = FALSE;
  
884
  menu->tearoff_window = NULL;
885
  menu->tearoff_hbox = NULL;
886
  menu->torn_off = FALSE;
887 888 889
  menu->tearoff_active = FALSE;
  menu->tearoff_adjustment = NULL;
  menu->tearoff_scrollbar = NULL;
890

891 892 893 894
  menu->upper_arrow_visible = FALSE;
  menu->lower_arrow_visible = FALSE;
  menu->upper_arrow_prelight = FALSE;
  menu->lower_arrow_prelight = FALSE;
895

896 897 898
  priv->upper_arrow_state = GTK_STATE_NORMAL;
  priv->lower_arrow_state = GTK_STATE_NORMAL;

899
  priv->have_layout = FALSE;
900 901 902
}

static void
903
gtk_menu_destroy (GtkObject *object)
904
{
905
  GtkMenu *menu;
Tim Janik's avatar
Tim Janik committed
906
  GtkMenuAttachData *data;
907
  GtkMenuPrivate *priv; 
908

909
  g_return_if_fail (GTK_IS_MENU (object));
910 911

  menu = GTK_MENU (object);
912 913

  gtk_menu_stop_scrolling (menu);
Tim Janik's avatar
Tim Janik committed
914
  
Manish Singh's avatar
Manish Singh committed
915
  data = g_object_get_data (G_OBJECT (object), attach_data_key);
Tim Janik's avatar
Tim Janik committed
916
  if (data)
917
    gtk_menu_detach (menu);
Tim Janik's avatar
Tim Janik committed
918
  
919 920
  gtk_menu_stop_navigating_submenu (menu);

921 922
  if (menu->old_active_menu_item)
    {
Manish Singh's avatar
Manish Singh committed
923
      g_object_unref (menu->old_active_menu_item);
924 925 926
      menu->old_active_menu_item = NULL;
    }

Owen Taylor's avatar
Owen Taylor committed
927
  /* Add back the reference count for being a child */
928 929 930
  if (menu->needs_destruction_ref_count)
    {
      menu->needs_destruction_ref_count = FALSE;
Manish Singh's avatar
Manish Singh committed
931
      g_object_ref (object);
932
    }
Tim Janik's avatar
Tim Janik committed
933
  
934 935 936 937 938 939
  if (menu->accel_group)
    {
      g_object_unref (menu->accel_group);
      menu->accel_group = NULL;
    }

940 941
  if (menu->toplevel)
    gtk_widget_destroy (menu->toplevel);
942

943 944 945
  if (menu->tearoff_window)
    gtk_widget_destroy (menu->tearoff_window);

946 947 948 949 950 951 952 953 954 955 956 957 958 959
  priv = gtk_menu_get_private (menu);

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

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

Matthias Clasen's avatar
Matthias Clasen committed
960
  GTK_OBJECT_CLASS (gtk_menu_parent_class)->destroy (object);
Tim Janik's avatar
Tim Janik committed
961 962
}

963 964 965 966 967 968 969
static void
gtk_menu_finalize (GObject *object)
{
  GtkMenu *menu = GTK_MENU (object);

  g_free (menu->accel_path);
  
Matthias Clasen's avatar
Matthias Clasen committed
970
  G_OBJECT_CLASS (gtk_menu_parent_class)->finalize (object);
971
}
Tim Janik's avatar
Tim Janik committed
972

973 974 975 976
static void
menu_change_screen (GtkMenu   *menu,
		    GdkScreen *new_screen)
{
977 978
  GtkMenuPrivate *private = gtk_menu_get_private (menu);

Matthias Clasen's avatar
Matthias Clasen committed
979
  if (gtk_widget_has_screen (GTK_WIDGET (menu)))
980
    {
Matthias Clasen's avatar
Matthias Clasen committed
981
      if (new_screen == gtk_widget_get_screen (GTK_WIDGET (menu)))
982 983 984
	return;
    }

985 986 987 988 989 990 991
  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);
992
  private->monitor_num = -1;
993 994
}

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

Søren Sandmann's avatar
Søren Sandmann committed
1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033
static void
attach_widget_hierarchy_changed (GtkWidget *attach_widget,
				 GtkWidget *previous_toplevel,
				 gpointer data)
{
  GtkMenu *menu = GTK_MENU (data);
  GtkWidget *new_toplevel = gtk_widget_get_toplevel (attach_widget);

  if (g_object_get_data (G_OBJECT (menu), "gtk-menu-explicit-screen"))
    {
      /* If there is an explicit screen set, then don't set WM_TRANSIENT_FOR.
       * Because, what would happen if the attach widget moved to a different
       * screen on a different display? The menu wouldn't move along with it,
       * so we just make it the responsibility of whoever set the screen to
       * also set WM_TRANSIENT_FOR.
       */
      return;
    }
  
  if (menu->toplevel && !g_object_get_data (G_OBJECT (menu), "gtk-menu-explicit-screen") &&
      (!new_toplevel || GTK_IS_WINDOW (new_toplevel)))
    {
      gtk_window_set_transient_for (GTK_WINDOW (menu->toplevel),
				    GTK_WINDOW (new_toplevel));
    }
}

Tim Janik's avatar
Tim Janik committed
1034
void
Tim Janik's avatar
Tim Janik committed
1035 1036 1037
gtk_menu_attach_to_widget (GtkMenu	       *menu,
			   GtkWidget	       *attach_widget,
			   GtkMenuDetachFunc	detacher)
Tim Janik's avatar
Tim Janik committed
1038 1039
{
  GtkMenuAttachData *data;
1040
  GList *list;
Tim Janik's avatar
Tim Janik committed
1041
  
Tim Janik's avatar
Tim Janik committed
1042 1043
  g_return_if_fail (GTK_IS_MENU (menu));
  g_return_if_fail (GTK_IS_WIDGET (attach_widget));
Tim Janik's avatar
Tim Janik committed
1044
  
Tim Janik's avatar
Tim Janik committed
1045 1046
  /* keep this function in sync with gtk_widget_set_parent()
   */
Tim Janik's avatar
Tim Janik committed
1047
  
Manish Singh's avatar
Manish Singh committed
1048
  data = g_object_get_data (G_OBJECT (menu), attach_data_key);
Tim Janik's avatar
Tim Janik committed
1049 1050 1051
  if (data)
    {
      g_warning ("gtk_menu_attach_to_widget(): menu already attached to %s",
Manish Singh's avatar
Manish Singh committed
1052
		 g_type_name (G_TYPE_FROM_INSTANCE (data->attach_widget)));
1053
     return;
Tim Janik's avatar
Tim Janik committed
1054
    }
Tim Janik's avatar
Tim Janik committed
1055
  
1056
  g_object_ref_sink (menu);
Tim Janik's avatar
Tim Janik committed
1057
  
Tim Janik's avatar
Tim Janik committed
1058 1059
  data = g_new (GtkMenuAttachData, 1);
  data->attach_widget = attach_widget;
1060 1061 1062 1063 1064
  
  g_signal_connect (attach_widget, "screen_changed",
		    G_CALLBACK (attach_widget_screen_changed), menu);
  attach_widget_screen_changed (attach_widget, NULL, menu);
  
Søren Sandmann's avatar
Søren Sandmann committed
1065 1066 1067 1068
  g_signal_connect (attach_widget, "hierarchy_changed",
		    G_CALLBACK (attach_widget_hierarchy_changed), menu);
  attach_widget_hierarchy_changed (attach_widget, NULL, menu);
  
Tim Janik's avatar
Tim Janik committed
1069
  data->detacher = detacher;
Matthias Clasen's avatar
Matthias Clasen committed
1070
  g_object_set_data (G_OBJECT (menu), I_(attach_data_key), data);
1071
  list = g_object_steal_data (G_OBJECT (attach_widget), ATTACHED_MENUS);
1072 1073 1074 1075
  if (!g_list_find (list, menu))
    {
      list = g_list_prepend (list, menu);
    }
Matthias Clasen's avatar
Matthias Clasen committed
1076
  g_object_set_data_full (G_OBJECT (attach_widget), I_(ATTACHED_MENUS), list, (GtkDestroyNotify) g_list_free);
Tim Janik's avatar
Tim Janik committed
1077
  
Tim Janik's avatar
Tim Janik committed
1078 1079
  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
1080