gtkoptionmenu.c 25.9 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 11
 * 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
 * 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/. 
 */

27
#include "gtkintl.h"
Elliot Lee's avatar
Elliot Lee committed
28 29 30 31
#include "gtkmenu.h"
#include "gtkmenuitem.h"
#include "gtkoptionmenu.h"
#include "gtksignal.h"
32
#include "gdk/gdkkeysyms.h"
Elliot Lee's avatar
Elliot Lee committed
33 34 35 36 37 38 39


#define CHILD_LEFT_SPACING        5
#define CHILD_RIGHT_SPACING       1
#define CHILD_TOP_SPACING         1
#define CHILD_BOTTOM_SPACING      1

40 41 42 43
typedef struct _GtkOptionMenuProps GtkOptionMenuProps;

struct _GtkOptionMenuProps
{
44
  gboolean interior_focus;
45 46 47 48 49
  GtkRequisition indicator_size;
  GtkBorder indicator_spacing;
};

static GtkOptionMenuProps default_props = {
50
  FALSE,
51 52
  { 7, 13 },
  { 7, 5, 2, 2 }		/* Left, right, top, bottom */
53
};
Elliot Lee's avatar
Elliot Lee committed
54 55 56 57 58 59 60 61 62 63 64 65 66 67

static void gtk_option_menu_class_init      (GtkOptionMenuClass *klass);
static void gtk_option_menu_init            (GtkOptionMenu      *option_menu);
static void gtk_option_menu_destroy         (GtkObject          *object);
static void gtk_option_menu_size_request    (GtkWidget          *widget,
					     GtkRequisition     *requisition);
static void gtk_option_menu_size_allocate   (GtkWidget          *widget,
					     GtkAllocation      *allocation);
static void gtk_option_menu_paint           (GtkWidget          *widget,
					     GdkRectangle       *area);
static gint gtk_option_menu_expose          (GtkWidget          *widget,
					     GdkEventExpose     *event);
static gint gtk_option_menu_button_press    (GtkWidget          *widget,
					     GdkEventButton     *event);
68 69
static gint gtk_option_menu_key_press	    (GtkWidget          *widget,
					     GdkEventKey        *event);
Elliot Lee's avatar
Elliot Lee committed
70 71 72 73 74 75 76 77
static void gtk_option_menu_deactivate      (GtkMenuShell       *menu_shell,
					     GtkOptionMenu      *option_menu);
static void gtk_option_menu_update_contents (GtkOptionMenu      *option_menu);
static void gtk_option_menu_remove_contents (GtkOptionMenu      *option_menu);
static void gtk_option_menu_calc_size       (GtkOptionMenu      *option_menu);
static void gtk_option_menu_position        (GtkMenu            *menu,
					     gint               *x,
					     gint               *y,
78
					     gint               *scroll_offet,
Elliot Lee's avatar
Elliot Lee committed
79
					     gpointer            user_data);
80 81
static void gtk_option_menu_show_all        (GtkWidget          *widget);
static void gtk_option_menu_hide_all        (GtkWidget          *widget);
82
static GtkType gtk_option_menu_child_type   (GtkContainer       *container);
Elliot Lee's avatar
Elliot Lee committed
83

84 85 86 87 88
enum
{
  CHANGED,
  LAST_SIGNAL
};
Elliot Lee's avatar
Elliot Lee committed
89 90

static GtkButtonClass *parent_class = NULL;
91
static guint           signals[LAST_SIGNAL] = { 0 };
Elliot Lee's avatar
Elliot Lee committed
92 93


94
GtkType
95
gtk_option_menu_get_type (void)
Elliot Lee's avatar
Elliot Lee committed
96
{
97
  static GtkType option_menu_type = 0;
Elliot Lee's avatar
Elliot Lee committed
98 99 100

  if (!option_menu_type)
    {
101
      static const GtkTypeInfo option_menu_info =
Elliot Lee's avatar
Elliot Lee committed
102 103 104 105 106 107
      {
	"GtkOptionMenu",
	sizeof (GtkOptionMenu),
	sizeof (GtkOptionMenuClass),
	(GtkClassInitFunc) gtk_option_menu_class_init,
	(GtkObjectInitFunc) gtk_option_menu_init,
108 109
	/* reserved_1 */ NULL,
        /* reserved_2 */ NULL,
110
        (GtkClassInitFunc) NULL,
Elliot Lee's avatar
Elliot Lee committed
111 112 113 114 115 116 117 118 119 120 121 122 123 124
      };

      option_menu_type = gtk_type_unique (gtk_button_get_type (), &option_menu_info);
    }

  return option_menu_type;
}

static void
gtk_option_menu_class_init (GtkOptionMenuClass *class)
{
  GtkObjectClass *object_class;
  GtkWidgetClass *widget_class;
  GtkButtonClass *button_class;
125
  GtkContainerClass *container_class;
Elliot Lee's avatar
Elliot Lee committed
126 127 128 129

  object_class = (GtkObjectClass*) class;
  widget_class = (GtkWidgetClass*) class;
  button_class = (GtkButtonClass*) class;
130
  container_class = (GtkContainerClass*) class;
Elliot Lee's avatar
Elliot Lee committed
131 132 133

  parent_class = gtk_type_class (gtk_button_get_type ());

134
  signals[CHANGED] =
135 136 137 138 139 140 141
    g_signal_new ("changed",
                  G_OBJECT_CLASS_TYPE (class),
                  G_SIGNAL_RUN_LAST,
                  G_STRUCT_OFFSET (GtkOptionMenuClass, changed),
                  NULL, NULL,
                  gtk_marshal_VOID__VOID,
                  G_TYPE_NONE, 0);
142
  
Elliot Lee's avatar
Elliot Lee committed
143
  object_class->destroy = gtk_option_menu_destroy;
144
  
Elliot Lee's avatar
Elliot Lee committed
145 146 147 148
  widget_class->size_request = gtk_option_menu_size_request;
  widget_class->size_allocate = gtk_option_menu_size_allocate;
  widget_class->expose_event = gtk_option_menu_expose;
  widget_class->button_press_event = gtk_option_menu_button_press;
149
  widget_class->key_press_event = gtk_option_menu_key_press;
150 151
  widget_class->show_all = gtk_option_menu_show_all;
  widget_class->hide_all = gtk_option_menu_hide_all;
152 153

  container_class->child_type = gtk_option_menu_child_type;
154 155 156 157 158 159 160 161 162 163 164 165 166

  gtk_widget_class_install_style_property (widget_class,
					   g_param_spec_boxed ("indicator_size",
							       _("Indicator Size"),
							       _("Size of dropdown indicator"),
							       GTK_TYPE_REQUISITION,
							       G_PARAM_READABLE));
  gtk_widget_class_install_style_property (widget_class,
					   g_param_spec_boxed ("indicator_spacing",
							       _("Indicator Spacing"),
							       _("Spacing around indicator"),
							       GTK_TYPE_BORDER,
							       G_PARAM_READABLE));
167 168 169 170 171 172
}

static GtkType
gtk_option_menu_child_type (GtkContainer       *container)
{
  return GTK_TYPE_NONE;
Elliot Lee's avatar
Elliot Lee committed
173 174 175 176 177
}

static void
gtk_option_menu_init (GtkOptionMenu *option_menu)
{
178
  GTK_WIDGET_SET_FLAGS (option_menu, GTK_CAN_FOCUS);
179
  GTK_WIDGET_UNSET_FLAGS (option_menu, GTK_CAN_DEFAULT | GTK_RECEIVES_DEFAULT);
Elliot Lee's avatar
Elliot Lee committed
180 181 182 183 184 185 186 187

  option_menu->menu = NULL;
  option_menu->menu_item = NULL;
  option_menu->width = 0;
  option_menu->height = 0;
}

GtkWidget*
188
gtk_option_menu_new (void)
Elliot Lee's avatar
Elliot Lee committed
189 190 191 192 193 194 195 196 197 198 199 200 201
{
  return GTK_WIDGET (gtk_type_new (gtk_option_menu_get_type ()));
}

GtkWidget*
gtk_option_menu_get_menu (GtkOptionMenu *option_menu)
{
  g_return_val_if_fail (option_menu != NULL, NULL);
  g_return_val_if_fail (GTK_IS_OPTION_MENU (option_menu), NULL);

  return option_menu->menu;
}

Tim Janik's avatar
Tim Janik committed
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220
static void
gtk_option_menu_detacher (GtkWidget     *widget,
			  GtkMenu	*menu)
{
  GtkOptionMenu *option_menu;

  g_return_if_fail (widget != NULL);
  g_return_if_fail (GTK_IS_OPTION_MENU (widget));

  option_menu = GTK_OPTION_MENU (widget);
  g_return_if_fail (option_menu->menu == (GtkWidget*) menu);

  gtk_option_menu_remove_contents (option_menu);
  gtk_signal_disconnect_by_data (GTK_OBJECT (option_menu->menu),
				 option_menu);
  
  option_menu->menu = NULL;
}

Elliot Lee's avatar
Elliot Lee committed
221 222 223 224 225 226 227 228 229
void
gtk_option_menu_set_menu (GtkOptionMenu *option_menu,
			  GtkWidget     *menu)
{
  g_return_if_fail (option_menu != NULL);
  g_return_if_fail (GTK_IS_OPTION_MENU (option_menu));
  g_return_if_fail (menu != NULL);
  g_return_if_fail (GTK_IS_MENU (menu));

230 231 232
  if (option_menu->menu != menu)
    {
      gtk_option_menu_remove_menu (option_menu);
Tim Janik's avatar
Tim Janik committed
233

234
      option_menu->menu = menu;
Tim Janik's avatar
Tim Janik committed
235 236 237
      gtk_menu_attach_to_widget (GTK_MENU (menu),
				 GTK_WIDGET (option_menu),
				 gtk_option_menu_detacher);
Elliot Lee's avatar
Elliot Lee committed
238

239
      gtk_option_menu_calc_size (option_menu);
Elliot Lee's avatar
Elliot Lee committed
240

241 242 243
      gtk_signal_connect (GTK_OBJECT (option_menu->menu), "deactivate",
			  (GtkSignalFunc) gtk_option_menu_deactivate,
			  option_menu);
244 245 246
      gtk_signal_connect_object (GTK_OBJECT (option_menu->menu), "size_request",
				 (GtkSignalFunc) gtk_option_menu_calc_size,
				 GTK_OBJECT (option_menu));
Elliot Lee's avatar
Elliot Lee committed
247

248 249
      if (GTK_WIDGET (option_menu)->parent)
	gtk_widget_queue_resize (GTK_WIDGET (option_menu));
Elliot Lee's avatar
Elliot Lee committed
250

251 252
      gtk_option_menu_update_contents (option_menu);
    }
Elliot Lee's avatar
Elliot Lee committed
253 254 255 256 257 258 259 260 261
}

void
gtk_option_menu_remove_menu (GtkOptionMenu *option_menu)
{
  g_return_if_fail (option_menu != NULL);
  g_return_if_fail (GTK_IS_OPTION_MENU (option_menu));

  if (option_menu->menu)
Tim Janik's avatar
Tim Janik committed
262
    gtk_menu_detach (GTK_MENU (option_menu->menu));
Elliot Lee's avatar
Elliot Lee committed
263 264 265 266
}

void
gtk_option_menu_set_history (GtkOptionMenu *option_menu,
267
			     guint          index)
Elliot Lee's avatar
Elliot Lee committed
268 269 270 271 272 273 274 275 276 277 278 279
{
  GtkWidget *menu_item;

  g_return_if_fail (option_menu != NULL);
  g_return_if_fail (GTK_IS_OPTION_MENU (option_menu));

  if (option_menu->menu)
    {
      gtk_menu_set_active (GTK_MENU (option_menu->menu), index);
      menu_item = gtk_menu_get_active (GTK_MENU (option_menu->menu));

      if (menu_item != option_menu->menu_item)
280
        gtk_option_menu_update_contents (option_menu);
Elliot Lee's avatar
Elliot Lee committed
281 282 283
    }
}

284 285 286 287 288 289 290 291 292
/**
 * gtk_option_menu_get_history:
 * @option_menu: a #GtkOptionMenu
 * 
 * Retrieves the index of the currently selected menu item. The menu
 * items are numbered from top to bottom, starting with 0. 
 * 
 * Return value: index of the selected menu item, or -1 if there are no menu items
 **/
Owen Taylor's avatar
Owen Taylor committed
293 294 295 296 297 298 299
gint
gtk_option_menu_get_history (GtkOptionMenu *option_menu)
{
  GtkWidget *active_widget;
  
  g_return_val_if_fail (GTK_IS_OPTION_MENU (option_menu), -1);

300 301 302 303 304 305
  if (option_menu->menu)
    {
      active_widget = gtk_menu_get_active (GTK_MENU (option_menu->menu));

      if (active_widget)
	return g_list_index (GTK_MENU_SHELL (option_menu->menu)->children,
306
                             active_widget);
307 308 309
      else
	return -1;
    }
Owen Taylor's avatar
Owen Taylor committed
310 311 312
  else
    return -1;
}
Elliot Lee's avatar
Elliot Lee committed
313 314 315 316 317 318 319 320 321 322 323 324

static void
gtk_option_menu_destroy (GtkObject *object)
{
  GtkOptionMenu *option_menu;

  g_return_if_fail (object != NULL);
  g_return_if_fail (GTK_IS_OPTION_MENU (object));

  option_menu = GTK_OPTION_MENU (object);

  if (option_menu->menu)
325
    gtk_widget_destroy (option_menu->menu);
Elliot Lee's avatar
Elliot Lee committed
326 327 328 329 330

  if (GTK_OBJECT_CLASS (parent_class)->destroy)
    (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
}

331 332 333 334 335 336 337 338 339 340
static void
gtk_option_menu_get_props (GtkOptionMenu       *option_menu,
			   GtkOptionMenuProps  *props)
{
  GtkRequisition *indicator_size;
  GtkBorder *indicator_spacing;
  
  gtk_widget_style_get (GTK_WIDGET (option_menu),
			"indicator_size", &indicator_size,
			"indicator_spacing", &indicator_spacing,
341
			"interior_focus", &props->interior_focus,
342 343 344
			NULL);

  if (indicator_size)
345 346 347 348
    props->indicator_size = *indicator_size;
  else
    props->indicator_size = default_props.indicator_size;

349
  if (indicator_spacing)
350 351 352
    props->indicator_spacing = *indicator_spacing;
  else
    props->indicator_spacing = default_props.indicator_spacing;
Havoc Pennington's avatar
Havoc Pennington committed
353 354 355

  g_free (indicator_size);
  g_free (indicator_spacing);
356 357
}

Elliot Lee's avatar
Elliot Lee committed
358 359 360 361
static void
gtk_option_menu_size_request (GtkWidget      *widget,
			      GtkRequisition *requisition)
{
362 363
  GtkOptionMenu *option_menu = GTK_OPTION_MENU (widget);
  GtkOptionMenuProps props;
Elliot Lee's avatar
Elliot Lee committed
364
  gint tmp;
365 366
  GtkRequisition child_requisition = { 0, 0 };
      
367 368
  gtk_option_menu_get_props (option_menu, &props);
 
369 370 371 372 373 374 375 376
  if (GTK_BIN (option_menu)->child && GTK_WIDGET_VISIBLE (GTK_BIN (option_menu)->child))
    {
      gtk_widget_size_request (GTK_BIN (option_menu)->child, &child_requisition);

      requisition->width += child_requisition.width;
      requisition->height += child_requisition.height;
    }
  
Elliot Lee's avatar
Elliot Lee committed
377
  requisition->width = ((GTK_CONTAINER (widget)->border_width +
378
			 GTK_WIDGET (widget)->style->xthickness) * 2 +
379
			MAX (child_requisition.width, option_menu->width) +
380 381
 			props.indicator_size.width +
 			props.indicator_spacing.left + props.indicator_spacing.right +
382
			CHILD_LEFT_SPACING + CHILD_RIGHT_SPACING + 2);
Elliot Lee's avatar
Elliot Lee committed
383
  requisition->height = ((GTK_CONTAINER (widget)->border_width +
384
			  GTK_WIDGET (widget)->style->ythickness) * 2 +
385
			 MAX (child_requisition.height, option_menu->height) +
386
			 CHILD_TOP_SPACING + CHILD_BOTTOM_SPACING + 2);
Elliot Lee's avatar
Elliot Lee committed
387 388

  tmp = (requisition->height - option_menu->height +
389
	 props.indicator_size.height + props.indicator_spacing.top + props.indicator_spacing.bottom);
Elliot Lee's avatar
Elliot Lee committed
390 391 392 393 394 395 396 397 398
  requisition->height = MAX (requisition->height, tmp);
}

static void
gtk_option_menu_size_allocate (GtkWidget     *widget,
			       GtkAllocation *allocation)
{
  GtkWidget *child;
  GtkAllocation child_allocation;
399
  GtkOptionMenuProps props;
400
  gint border_width;
401 402
    
  gtk_option_menu_get_props (GTK_OPTION_MENU (widget), &props);
403
  border_width = GTK_CONTAINER (widget)->border_width;
404

Elliot Lee's avatar
Elliot Lee committed
405 406 407
  widget->allocation = *allocation;
  if (GTK_WIDGET_REALIZED (widget))
    gdk_window_move_resize (widget->window,
408 409
			    allocation->x + border_width, allocation->y + border_width,
			    allocation->width - border_width * 2, allocation->height - border_width * 2);
Elliot Lee's avatar
Elliot Lee committed
410

411
  child = GTK_BIN (widget)->child;
Elliot Lee's avatar
Elliot Lee committed
412 413
  if (child && GTK_WIDGET_VISIBLE (child))
    {
414 415 416
      child_allocation.x = GTK_WIDGET (widget)->style->xthickness + 1;
      child_allocation.y = GTK_CONTAINER (widget)->border_width + 1;
      child_allocation.width = MAX (1, (gint)allocation->width - child_allocation.x * 2 - border_width * 2 -
417
				    props.indicator_size.width - props.indicator_spacing.left - props.indicator_spacing.right -
418
				    CHILD_LEFT_SPACING - CHILD_RIGHT_SPACING - 2);
419
      child_allocation.height = MAX (1, (gint)allocation->height - child_allocation.y * 2 - border_width * 2 -
420
				     CHILD_TOP_SPACING - CHILD_BOTTOM_SPACING - 2);
Elliot Lee's avatar
Elliot Lee committed
421
      child_allocation.x += CHILD_LEFT_SPACING;
422
      child_allocation.y += CHILD_TOP_SPACING;
Elliot Lee's avatar
Elliot Lee committed
423 424 425 426 427 428 429 430 431

      gtk_widget_size_allocate (child, &child_allocation);
    }
}

static void
gtk_option_menu_paint (GtkWidget    *widget,
		       GdkRectangle *area)
{
432
  GdkRectangle button_area;
433
  GtkOptionMenuProps props;
434
  gint border_width;
Elliot Lee's avatar
Elliot Lee committed
435 436 437 438 439 440 441

  g_return_if_fail (widget != NULL);
  g_return_if_fail (GTK_IS_OPTION_MENU (widget));
  g_return_if_fail (area != NULL);

  if (GTK_WIDGET_DRAWABLE (widget))
    {
442
      border_width = GTK_CONTAINER (widget)->border_width;
443 444
      gtk_option_menu_get_props (GTK_OPTION_MENU (widget), &props);

445 446
      button_area.x = 0;
      button_area.y = 0;
447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463
      button_area.width = widget->allocation.width - 2 * border_width;
      button_area.height = widget->allocation.height - 2 * border_width;

      if (!props.interior_focus)
	{
	  button_area.x += 1;
	  button_area.y += 1;
	  button_area.width -= 2;
	  button_area.height -= 2;

	  /* This is evil, and should be elimated here and in the button
	   * code. The point is to clear the focus, and make it
	   * sort of transparent if it isn't there.
	   */
	  gdk_window_set_back_pixmap (widget->window, NULL, TRUE);
	  gdk_window_clear_area (widget->window, area->x, area->y, area->width, area->height);
	}
464 465 466 467 468 469 470 471 472 473

      gtk_paint_box(widget->style, widget->window,
		    GTK_WIDGET_STATE (widget), GTK_SHADOW_OUT,
		    area, widget, "optionmenu",
		    button_area.x, button_area.y,
		    button_area.width, button_area.height);
      
      gtk_paint_tab (widget->style, widget->window,
		     GTK_WIDGET_STATE (widget), GTK_SHADOW_OUT,
		     area, widget, "optionmenutab",
474 475 476 477 478
		     button_area.x + button_area.width - 
		     props.indicator_size.width - props.indicator_spacing.right -
		     widget->style->xthickness,
		     button_area.y + (button_area.height - props.indicator_size.height) / 2,
		     props.indicator_size.width, props.indicator_size.height);
479 480
      
      if (GTK_WIDGET_HAS_FOCUS (widget))
481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504
	{
	  if (props.interior_focus)
	    {
	      button_area.x += widget->style->xthickness + 1;
	      button_area.y += widget->style->ythickness + 1;
	      button_area.width -= 2 * (widget->style->xthickness + 1)
		+ props.indicator_spacing.left + props.indicator_spacing.right + props.indicator_size.width;
	      button_area.height -= 2 * (widget->style->ythickness + 1);
	    }
	  else
	    {
	      button_area.x -= 1;
	      button_area.y -= 1;
	      button_area.width += 2;
	      button_area.height += 2;
	    }
	    
	  gtk_paint_focus (widget->style, widget->window,
			   area, widget, "button",
			   button_area.x, 
			   button_area.y, 
			   button_area.width - 1,
			   button_area.height - 1);
	}
Elliot Lee's avatar
Elliot Lee committed
505 506 507 508 509 510 511 512 513 514 515 516 517 518 519
    }
}

static gint
gtk_option_menu_expose (GtkWidget      *widget,
			GdkEventExpose *event)
{
  g_return_val_if_fail (widget != NULL, FALSE);
  g_return_val_if_fail (GTK_IS_OPTION_MENU (widget), FALSE);
  g_return_val_if_fail (event != NULL, FALSE);

  if (GTK_WIDGET_DRAWABLE (widget))
    {
      gtk_option_menu_paint (widget, &event->area);

520 521 522 523 524 525 526 527 528 529 530 531

      /* The following code tries to draw the child in two places at
       * once. It fails miserably for several reasons
       *
       * - If the child is not no-window, removing generates
       *   more expose events. Bad, bad, bad.
       * 
       * - Even if the child is no-window, removing it now (properly)
       *   clears the space where it was, so it does no good
       */
      
#if 0
Elliot Lee's avatar
Elliot Lee committed
532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553
      remove_child = FALSE;
      child = GTK_BUTTON (widget)->child;

      if (!child)
	{
	  if (!GTK_OPTION_MENU (widget)->menu)
	    return FALSE;
	  gtk_option_menu_update_contents (GTK_OPTION_MENU (widget));
	  child = GTK_BUTTON (widget)->child;
	  if (!child)
	    return FALSE;
	  remove_child = TRUE;
	}

      child_event = *event;

      if (GTK_WIDGET_NO_WINDOW (child) &&
	  gtk_widget_intersect (child, &event->area, &child_event.area))
	gtk_widget_event (child, (GdkEvent*) &child_event);

      if (remove_child)
	gtk_option_menu_remove_contents (GTK_OPTION_MENU (widget));
554
#else
555 556 557 558
      if (GTK_BIN (widget)->child)
	gtk_container_propagate_expose (GTK_CONTAINER (widget),
					GTK_BIN (widget)->child,
					event);
559
#endif /* 0 */
Elliot Lee's avatar
Elliot Lee committed
560 561 562 563 564 565 566 567 568 569
    }

  return FALSE;
}

static gint
gtk_option_menu_button_press (GtkWidget      *widget,
			      GdkEventButton *event)
{
  GtkOptionMenu *option_menu;
570
  GtkWidget *menu_item;
Elliot Lee's avatar
Elliot Lee committed
571 572 573 574 575

  g_return_val_if_fail (widget != NULL, FALSE);
  g_return_val_if_fail (GTK_IS_OPTION_MENU (widget), FALSE);
  g_return_val_if_fail (event != NULL, FALSE);

576 577
  option_menu = GTK_OPTION_MENU (widget);

Elliot Lee's avatar
Elliot Lee committed
578 579 580 581 582 583 584
  if ((event->type == GDK_BUTTON_PRESS) &&
      (event->button == 1))
    {
      gtk_option_menu_remove_contents (option_menu);
      gtk_menu_popup (GTK_MENU (option_menu->menu), NULL, NULL,
		      gtk_option_menu_position, option_menu,
		      event->button, event->time);
585 586 587
      menu_item = gtk_menu_get_active (GTK_MENU (option_menu->menu));
      if (menu_item)
	gtk_menu_shell_select_item (GTK_MENU_SHELL (option_menu->menu), menu_item);
588
      return TRUE;
Elliot Lee's avatar
Elliot Lee committed
589 590 591 592 593
    }

  return FALSE;
}

594 595 596 597 598
static gint
gtk_option_menu_key_press (GtkWidget   *widget,
			   GdkEventKey *event)
{
  GtkOptionMenu *option_menu;
599
  GtkWidget *menu_item;
600 601 602 603 604 605 606 607 608

  g_return_val_if_fail (widget != NULL, FALSE);
  g_return_val_if_fail (GTK_IS_OPTION_MENU (widget), FALSE);
  g_return_val_if_fail (event != NULL, FALSE);

  option_menu = GTK_OPTION_MENU (widget);

  switch (event->keyval)
    {
609
    case GDK_KP_Space:
610 611 612 613 614
    case GDK_space:
      gtk_option_menu_remove_contents (option_menu);
      gtk_menu_popup (GTK_MENU (option_menu->menu), NULL, NULL,
		      gtk_option_menu_position, option_menu,
		      0, event->time);
615 616 617
      menu_item = gtk_menu_get_active (GTK_MENU (option_menu->menu));
      if (menu_item)
	gtk_menu_shell_select_item (GTK_MENU_SHELL (option_menu->menu), menu_item);
618
      return TRUE;
619 620 621 622 623
    }
  
  return FALSE;
}

Elliot Lee's avatar
Elliot Lee committed
624 625 626 627 628 629 630 631 632 633 634
static void
gtk_option_menu_deactivate (GtkMenuShell  *menu_shell,
			    GtkOptionMenu *option_menu)
{
  g_return_if_fail (menu_shell != NULL);
  g_return_if_fail (option_menu != NULL);
  g_return_if_fail (GTK_IS_OPTION_MENU (option_menu));

  gtk_option_menu_update_contents (option_menu);
}

635 636 637 638 639 640 641 642
static void
gtk_option_menu_changed (GtkOptionMenu *option_menu)
{
  g_return_if_fail (GTK_IS_OPTION_MENU (option_menu));

  g_signal_emit (G_OBJECT (option_menu), signals[CHANGED], 0);
}

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 681 682 683 684 685 686 687 688 689 690 691 692
static void
gtk_option_menu_select_first_sensitive (GtkOptionMenu *option_menu)
{
  if (option_menu->menu)
    {
      GList *children = GTK_MENU_SHELL (option_menu->menu)->children;
      gint index = 0;

      while (children)
	{
	  if (GTK_WIDGET_SENSITIVE (children->data))
	    {
	      gtk_option_menu_set_history (option_menu, index);
	      return;
	    }
	  
	  children = children->next;
	  index++;
	}
    }
}

static void
gtk_option_menu_item_state_changed_cb (GtkWidget      *widget,
				       GtkStateType    previous_state,
				       GtkOptionMenu  *option_menu)
{
  GtkWidget *child = GTK_BIN (option_menu)->child;

  if (child && GTK_WIDGET_SENSITIVE (child) != GTK_WIDGET_IS_SENSITIVE (widget))
    gtk_widget_set_sensitive (child, GTK_WIDGET_IS_SENSITIVE (widget));
}

static void
gtk_option_menu_item_destroy_cb (GtkWidget     *widget,
				 GtkOptionMenu *option_menu)
{
  GtkWidget *child = GTK_BIN (option_menu)->child;

  if (child)
    {
      gtk_widget_ref (child);
      gtk_option_menu_remove_contents (option_menu);
      gtk_widget_destroy (child);
      gtk_widget_unref (child);

      gtk_option_menu_select_first_sensitive (option_menu);
    }
}

Elliot Lee's avatar
Elliot Lee committed
693 694 695 696
static void
gtk_option_menu_update_contents (GtkOptionMenu *option_menu)
{
  GtkWidget *child;
697
  GtkRequisition child_requisition;
Elliot Lee's avatar
Elliot Lee committed
698 699 700 701 702 703

  g_return_if_fail (option_menu != NULL);
  g_return_if_fail (GTK_IS_OPTION_MENU (option_menu));

  if (option_menu->menu)
    {
704 705
      GtkWidget *old_item = option_menu->menu_item;
      
Elliot Lee's avatar
Elliot Lee committed
706 707 708 709 710
      gtk_option_menu_remove_contents (option_menu);

      option_menu->menu_item = gtk_menu_get_active (GTK_MENU (option_menu->menu));
      if (option_menu->menu_item)
	{
Tim Janik's avatar
Tim Janik committed
711
	  gtk_widget_ref (option_menu->menu_item);
Elliot Lee's avatar
Elliot Lee committed
712 713 714
	  child = GTK_BIN (option_menu->menu_item)->child;
	  if (child)
	    {
715 716
	      if (!GTK_WIDGET_IS_SENSITIVE (option_menu->menu_item))
		gtk_widget_set_sensitive (child, FALSE);
Elliot Lee's avatar
Elliot Lee committed
717 718 719
	      gtk_widget_reparent (child, GTK_WIDGET (option_menu));
	    }

720 721 722 723 724
	  gtk_signal_connect (GTK_OBJECT (option_menu->menu_item), "state_changed",
			      GTK_SIGNAL_FUNC (gtk_option_menu_item_state_changed_cb), option_menu);
	  gtk_signal_connect (GTK_OBJECT (option_menu->menu_item), "destroy",
			      GTK_SIGNAL_FUNC (gtk_option_menu_item_destroy_cb), option_menu);

725
	  gtk_widget_size_request (child, &child_requisition);
Elliot Lee's avatar
Elliot Lee committed
726 727 728 729 730 731
	  gtk_widget_size_allocate (GTK_WIDGET (option_menu),
				    &(GTK_WIDGET (option_menu)->allocation));

	  if (GTK_WIDGET_DRAWABLE (option_menu))
	    gtk_widget_queue_draw (GTK_WIDGET (option_menu));
	}
732 733 734

      if (old_item != option_menu->menu_item)
        gtk_option_menu_changed (option_menu);
Elliot Lee's avatar
Elliot Lee committed
735 736 737 738 739 740
    }
}

static void
gtk_option_menu_remove_contents (GtkOptionMenu *option_menu)
{
741 742
  GtkWidget *child;
  
Elliot Lee's avatar
Elliot Lee committed
743 744 745
  g_return_if_fail (option_menu != NULL);
  g_return_if_fail (GTK_IS_OPTION_MENU (option_menu));

746
  if (option_menu->menu_item)
Elliot Lee's avatar
Elliot Lee committed
747
    {
748 749 750 751 752 753 754 755 756 757 758 759 760 761 762
      child = GTK_BIN (option_menu)->child;
  
      if (child)
	{
	  gtk_widget_set_sensitive (child, TRUE);
	  gtk_widget_reparent (child, option_menu->menu_item);
	}

      gtk_signal_disconnect_by_func (GTK_OBJECT (option_menu->menu_item),
				     GTK_SIGNAL_FUNC (gtk_option_menu_item_state_changed_cb),
				     option_menu);				     
      gtk_signal_disconnect_by_func (GTK_OBJECT (option_menu->menu_item),
				     GTK_SIGNAL_FUNC (gtk_option_menu_item_destroy_cb),
				     option_menu);   
      
Tim Janik's avatar
Tim Janik committed
763
      gtk_widget_unref (option_menu->menu_item);
Elliot Lee's avatar
Elliot Lee committed
764 765 766 767 768 769 770 771 772
      option_menu->menu_item = NULL;
    }
}

static void
gtk_option_menu_calc_size (GtkOptionMenu *option_menu)
{
  GtkWidget *child;
  GList *children;
773
  GtkRequisition child_requisition;
774 775
  gint old_width = option_menu->width;
  gint old_height = option_menu->height;
Elliot Lee's avatar
Elliot Lee committed
776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792

  g_return_if_fail (option_menu != NULL);
  g_return_if_fail (GTK_IS_OPTION_MENU (option_menu));

  option_menu->width = 0;
  option_menu->height = 0;

  if (option_menu->menu)
    {
      children = GTK_MENU_SHELL (option_menu->menu)->children;
      while (children)
	{
	  child = children->data;
	  children = children->next;

	  if (GTK_WIDGET_VISIBLE (child))
	    {
793
	      gtk_widget_size_request (child, &child_requisition);
Elliot Lee's avatar
Elliot Lee committed
794

795 796
	      option_menu->width = MAX (option_menu->width, child_requisition.width);
	      option_menu->height = MAX (option_menu->height, child_requisition.height);
Elliot Lee's avatar
Elliot Lee committed
797 798 799
	    }
	}
    }
800 801 802

  if (old_width != option_menu->width || old_height != option_menu->height)
    gtk_widget_queue_resize (GTK_WIDGET (option_menu));
Elliot Lee's avatar
Elliot Lee committed
803 804 805 806 807 808
}

static void
gtk_option_menu_position (GtkMenu  *menu,
			  gint     *x,
			  gint     *y,
809
			  gboolean *push_in,
Elliot Lee's avatar
Elliot Lee committed
810 811 812 813 814
			  gpointer  user_data)
{
  GtkOptionMenu *option_menu;
  GtkWidget *active;
  GtkWidget *child;
Owen Taylor's avatar
Owen Taylor committed
815
  GtkRequisition requisition;
Elliot Lee's avatar
Elliot Lee committed
816 817 818 819
  GList *children;
  gint screen_width;
  gint menu_xpos;
  gint menu_ypos;
820
  gint menu_width;
Elliot Lee's avatar
Elliot Lee committed
821 822 823 824 825 826

  g_return_if_fail (user_data != NULL);
  g_return_if_fail (GTK_IS_OPTION_MENU (user_data));

  option_menu = GTK_OPTION_MENU (user_data);

Owen Taylor's avatar
Owen Taylor committed
827
  gtk_widget_get_child_requisition (GTK_WIDGET (menu), &requisition);
828
  menu_width = requisition.width;
Elliot Lee's avatar
Elliot Lee committed
829 830 831 832 833 834 835

  active = gtk_menu_get_active (GTK_MENU (option_menu->menu));
  gdk_window_get_origin (GTK_WIDGET (option_menu)->window, &menu_xpos, &menu_ypos);

  menu_ypos += GTK_WIDGET (option_menu)->allocation.height / 2 - 2;

  if (active != NULL)
Owen Taylor's avatar
Owen Taylor committed
836 837 838 839
    {
      gtk_widget_get_child_requisition (active, &requisition);
      menu_ypos -= requisition.height / 2;
    }
Elliot Lee's avatar
Elliot Lee committed
840

841
  children = GTK_MENU_SHELL (option_menu->menu)->children;
Elliot Lee's avatar
Elliot Lee committed
842 843 844 845 846 847 848
  while (children)
    {
      child = children->data;

      if (active == child)
	break;

849
      if (GTK_WIDGET_VISIBLE (child))
Owen Taylor's avatar
Owen Taylor committed
850 851 852 853
	{
	  gtk_widget_get_child_requisition (child, &requisition);
	  menu_ypos -= requisition.height;
	}
854

Elliot Lee's avatar
Elliot Lee committed
855 856 857 858
      children = children->next;
    }

  screen_width = gdk_screen_width ();
859
  
Elliot Lee's avatar
Elliot Lee committed
860 861
  if (menu_xpos < 0)
    menu_xpos = 0;
862 863
  else if ((menu_xpos + menu_width) > screen_width)
    menu_xpos -= ((menu_xpos + menu_width) - screen_width);
Elliot Lee's avatar
Elliot Lee committed
864 865 866

  *x = menu_xpos;
  *y = menu_ypos;
867
  *push_in = TRUE;
Elliot Lee's avatar
Elliot Lee committed
868
}
869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903


static void
gtk_option_menu_show_all (GtkWidget *widget)
{
  GtkContainer *container;
  GtkOptionMenu *option_menu;
  
  g_return_if_fail (widget != NULL);
  g_return_if_fail (GTK_IS_OPTION_MENU (widget));
  container = GTK_CONTAINER (widget);
  option_menu = GTK_OPTION_MENU (widget);

  gtk_widget_show (widget);
  gtk_container_foreach (container, (GtkCallback) gtk_widget_show_all, NULL);
  if (option_menu->menu)
    gtk_widget_show_all (option_menu->menu);
  if (option_menu->menu_item)
    gtk_widget_show_all (option_menu->menu_item);
}


static void
gtk_option_menu_hide_all (GtkWidget *widget)
{
  GtkContainer *container;

  g_return_if_fail (widget != NULL);
  g_return_if_fail (GTK_IS_OPTION_MENU (widget));
  container = GTK_CONTAINER (widget);

  gtk_widget_hide (widget);
  gtk_container_foreach (container, (GtkCallback) gtk_widget_hide_all, NULL);
}