gtkoptionmenu.c 28.7 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

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);
58
59
60
61
62
63
64
65
static void gtk_option_menu_set_property    (GObject            *object,
					     guint               prop_id,
					     const GValue       *value,
					     GParamSpec         *pspec);
static void gtk_option_menu_get_property    (GObject            *object,
					     guint               prop_id,
					     GValue             *value,
					     GParamSpec         *pspec);
Elliot Lee's avatar
Elliot Lee committed
66
67
68
69
70
71
72
73
74
75
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);
76
77
static gint gtk_option_menu_key_press	    (GtkWidget          *widget,
					     GdkEventKey        *event);
Tim Janik's avatar
Tim Janik committed
78
static void gtk_option_menu_selection_done  (GtkMenuShell       *menu_shell,
Elliot Lee's avatar
Elliot Lee committed
79
80
81
82
83
84
85
					     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,
86
					     gint               *scroll_offet,
Elliot Lee's avatar
Elliot Lee committed
87
					     gpointer            user_data);
88
89
static void gtk_option_menu_show_all        (GtkWidget          *widget);
static void gtk_option_menu_hide_all        (GtkWidget          *widget);
90
91
static gboolean gtk_option_menu_mnemonic_activate (GtkWidget    *widget,
						   gboolean      group_cycling);
92
static GtkType gtk_option_menu_child_type   (GtkContainer       *container);
93
94
static gint gtk_option_menu_scroll_event    (GtkWidget          *widget,
					     GdkEventScroll     *event);
Elliot Lee's avatar
Elliot Lee committed
95

96
97
98
99
100
enum
{
  CHANGED,
  LAST_SIGNAL
};
Elliot Lee's avatar
Elliot Lee committed
101

102
103
104
105
106
107
108
enum
{
  PROP_0,
  PROP_MENU,
  LAST_PROP
};

Elliot Lee's avatar
Elliot Lee committed
109
static GtkButtonClass *parent_class = NULL;
110
static guint           signals[LAST_SIGNAL] = { 0 };
Elliot Lee's avatar
Elliot Lee committed
111
112


113
GtkType
114
gtk_option_menu_get_type (void)
Elliot Lee's avatar
Elliot Lee committed
115
{
116
  static GtkType option_menu_type = 0;
Elliot Lee's avatar
Elliot Lee committed
117
118
119

  if (!option_menu_type)
    {
120
      static const GtkTypeInfo option_menu_info =
Elliot Lee's avatar
Elliot Lee committed
121
122
123
124
125
126
      {
	"GtkOptionMenu",
	sizeof (GtkOptionMenu),
	sizeof (GtkOptionMenuClass),
	(GtkClassInitFunc) gtk_option_menu_class_init,
	(GtkObjectInitFunc) gtk_option_menu_init,
127
128
	/* reserved_1 */ NULL,
        /* reserved_2 */ NULL,
129
        (GtkClassInitFunc) NULL,
Elliot Lee's avatar
Elliot Lee committed
130
131
132
133
134
135
136
137
138
139
140
      };

      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)
{
141
  GObjectClass *gobject_class;
Elliot Lee's avatar
Elliot Lee committed
142
143
144
  GtkObjectClass *object_class;
  GtkWidgetClass *widget_class;
  GtkButtonClass *button_class;
145
  GtkContainerClass *container_class;
Elliot Lee's avatar
Elliot Lee committed
146

147
  gobject_class = (GObjectClass*) class;
Elliot Lee's avatar
Elliot Lee committed
148
149
150
  object_class = (GtkObjectClass*) class;
  widget_class = (GtkWidgetClass*) class;
  button_class = (GtkButtonClass*) class;
151
  container_class = (GtkContainerClass*) class;
Elliot Lee's avatar
Elliot Lee committed
152
153
154

  parent_class = gtk_type_class (gtk_button_get_type ());

155
  signals[CHANGED] =
156
157
158
159
160
161
162
    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);
163
164
165

  gobject_class->set_property = gtk_option_menu_set_property;
  gobject_class->get_property = gtk_option_menu_get_property;
Elliot Lee's avatar
Elliot Lee committed
166
  object_class->destroy = gtk_option_menu_destroy;
167
  
Elliot Lee's avatar
Elliot Lee committed
168
169
170
171
  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;
172
  widget_class->key_press_event = gtk_option_menu_key_press;
173
  widget_class->scroll_event = gtk_option_menu_scroll_event;
174
175
  widget_class->show_all = gtk_option_menu_show_all;
  widget_class->hide_all = gtk_option_menu_hide_all;
176
  widget_class->mnemonic_activate = gtk_option_menu_mnemonic_activate;
177
178

  container_class->child_type = gtk_option_menu_child_type;
179

180
181
182
183
184
185
186
187
  g_object_class_install_property (gobject_class,
                                   PROP_MENU,
                                   g_param_spec_object ("menu",
                                                        _("Menu"),
                                                        _("The menu of options"),
                                                        GTK_TYPE_MENU,
                                                        G_PARAM_READABLE | G_PARAM_WRITABLE));
  
188
189
190
191
192
193
194
195
196
197
198
199
  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));
200
201
202
203
204
205
}

static GtkType
gtk_option_menu_child_type (GtkContainer       *container)
{
  return GTK_TYPE_NONE;
Elliot Lee's avatar
Elliot Lee committed
206
207
208
209
210
}

static void
gtk_option_menu_init (GtkOptionMenu *option_menu)
{
211
  GTK_WIDGET_SET_FLAGS (option_menu, GTK_CAN_FOCUS);
212
  GTK_WIDGET_UNSET_FLAGS (option_menu, GTK_CAN_DEFAULT | GTK_RECEIVES_DEFAULT);
Elliot Lee's avatar
Elliot Lee committed
213
214
215
216
217
218
219
220

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

GtkWidget*
221
gtk_option_menu_new (void)
Elliot Lee's avatar
Elliot Lee committed
222
223
224
225
226
227
228
229
230
231
232
233
{
  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 (GTK_IS_OPTION_MENU (option_menu), NULL);

  return option_menu->menu;
}

Tim Janik's avatar
Tim Janik committed
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
static void
gtk_option_menu_detacher (GtkWidget     *widget,
			  GtkMenu	*menu)
{
  GtkOptionMenu *option_menu;

  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;
250
  g_object_notify (G_OBJECT (option_menu), "menu");
Tim Janik's avatar
Tim Janik committed
251
252
}

Elliot Lee's avatar
Elliot Lee committed
253
254
255
256
257
258
259
void
gtk_option_menu_set_menu (GtkOptionMenu *option_menu,
			  GtkWidget     *menu)
{
  g_return_if_fail (GTK_IS_OPTION_MENU (option_menu));
  g_return_if_fail (GTK_IS_MENU (menu));

260
261
262
  if (option_menu->menu != menu)
    {
      gtk_option_menu_remove_menu (option_menu);
Tim Janik's avatar
Tim Janik committed
263

264
      option_menu->menu = menu;
Tim Janik's avatar
Tim Janik committed
265
266
267
      gtk_menu_attach_to_widget (GTK_MENU (menu),
				 GTK_WIDGET (option_menu),
				 gtk_option_menu_detacher);
Elliot Lee's avatar
Elliot Lee committed
268

269
      gtk_option_menu_calc_size (option_menu);
Elliot Lee's avatar
Elliot Lee committed
270

Tim Janik's avatar
Tim Janik committed
271
272
273
      gtk_signal_connect_after (GTK_OBJECT (option_menu->menu), "selection_done",
				G_CALLBACK (gtk_option_menu_selection_done),
				option_menu);
274
275
276
      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
277

278
279
      if (GTK_WIDGET (option_menu)->parent)
	gtk_widget_queue_resize (GTK_WIDGET (option_menu));
Elliot Lee's avatar
Elliot Lee committed
280

281
      gtk_option_menu_update_contents (option_menu);
282
283
      
      g_object_notify (G_OBJECT (option_menu), "menu");
284
    }
Elliot Lee's avatar
Elliot Lee committed
285
286
287
288
289
290
291
292
}

void
gtk_option_menu_remove_menu (GtkOptionMenu *option_menu)
{
  g_return_if_fail (GTK_IS_OPTION_MENU (option_menu));

  if (option_menu->menu)
Tim Janik's avatar
Tim Janik committed
293
    gtk_menu_detach (GTK_MENU (option_menu->menu));
Elliot Lee's avatar
Elliot Lee committed
294
295
296
297
}

void
gtk_option_menu_set_history (GtkOptionMenu *option_menu,
298
			     guint          index)
Elliot Lee's avatar
Elliot Lee committed
299
300
301
302
303
304
305
306
307
308
309
{
  GtkWidget *menu_item;

  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)
310
        gtk_option_menu_update_contents (option_menu);
Elliot Lee's avatar
Elliot Lee committed
311
312
313
    }
}

314
315
316
317
318
319
320
321
322
/**
 * 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
323
324
325
326
327
328
329
gint
gtk_option_menu_get_history (GtkOptionMenu *option_menu)
{
  GtkWidget *active_widget;
  
  g_return_val_if_fail (GTK_IS_OPTION_MENU (option_menu), -1);

330
331
332
333
334
335
  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,
336
                             active_widget);
337
338
339
      else
	return -1;
    }
Owen Taylor's avatar
Owen Taylor committed
340
341
342
  else
    return -1;
}
Elliot Lee's avatar
Elliot Lee committed
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
static void
gtk_option_menu_set_property (GObject            *object,
			      guint               prop_id,
			      const GValue       *value,
			      GParamSpec         *pspec)
{
  GtkOptionMenu *option_menu = GTK_OPTION_MENU (object);

  switch (prop_id)
    {
    case PROP_MENU:
      gtk_option_menu_set_menu (option_menu, g_value_get_object (value));
      break;
      
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}

static void
gtk_option_menu_get_property (GObject            *object,
			      guint               prop_id,
			      GValue             *value,
			      GParamSpec         *pspec)
{
  GtkOptionMenu *option_menu = GTK_OPTION_MENU (object);

  switch (prop_id)
    {
    case PROP_MENU:
      g_value_set_object (value, option_menu->menu);
      break;
      
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}

Elliot Lee's avatar
Elliot Lee committed
384
385
386
387
388
389
390
391
392
393
static void
gtk_option_menu_destroy (GtkObject *object)
{
  GtkOptionMenu *option_menu;

  g_return_if_fail (GTK_IS_OPTION_MENU (object));

  option_menu = GTK_OPTION_MENU (object);

  if (option_menu->menu)
394
    gtk_widget_destroy (option_menu->menu);
Elliot Lee's avatar
Elliot Lee committed
395
396
397
398
399

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

400
401
402
403
404
405
406
407
408
409
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,
410
			"interior_focus", &props->interior_focus,
411
412
413
			NULL);

  if (indicator_size)
414
415
416
417
    props->indicator_size = *indicator_size;
  else
    props->indicator_size = default_props.indicator_size;

418
  if (indicator_spacing)
419
420
421
    props->indicator_spacing = *indicator_spacing;
  else
    props->indicator_spacing = default_props.indicator_spacing;
Havoc Pennington's avatar
Havoc Pennington committed
422
423
424

  g_free (indicator_size);
  g_free (indicator_spacing);
425
426
}

Elliot Lee's avatar
Elliot Lee committed
427
428
429
430
static void
gtk_option_menu_size_request (GtkWidget      *widget,
			      GtkRequisition *requisition)
{
431
432
  GtkOptionMenu *option_menu = GTK_OPTION_MENU (widget);
  GtkOptionMenuProps props;
Elliot Lee's avatar
Elliot Lee committed
433
  gint tmp;
434
435
  GtkRequisition child_requisition = { 0, 0 };
      
436
437
  gtk_option_menu_get_props (option_menu, &props);
 
438
439
440
441
442
443
444
445
  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
446
  requisition->width = ((GTK_CONTAINER (widget)->border_width +
447
			 GTK_WIDGET (widget)->style->xthickness) * 2 +
448
			MAX (child_requisition.width, option_menu->width) +
449
450
 			props.indicator_size.width +
 			props.indicator_spacing.left + props.indicator_spacing.right +
451
			CHILD_LEFT_SPACING + CHILD_RIGHT_SPACING + 2);
Elliot Lee's avatar
Elliot Lee committed
452
  requisition->height = ((GTK_CONTAINER (widget)->border_width +
453
			  GTK_WIDGET (widget)->style->ythickness) * 2 +
454
			 MAX (child_requisition.height, option_menu->height) +
455
			 CHILD_TOP_SPACING + CHILD_BOTTOM_SPACING + 2);
Elliot Lee's avatar
Elliot Lee committed
456
457

  tmp = (requisition->height - option_menu->height +
458
	 props.indicator_size.height + props.indicator_spacing.top + props.indicator_spacing.bottom);
Elliot Lee's avatar
Elliot Lee committed
459
460
461
462
463
464
465
466
467
  requisition->height = MAX (requisition->height, tmp);
}

static void
gtk_option_menu_size_allocate (GtkWidget     *widget,
			       GtkAllocation *allocation)
{
  GtkWidget *child;
  GtkAllocation child_allocation;
468
  GtkOptionMenuProps props;
469
  gint border_width;
470
471
    
  gtk_option_menu_get_props (GTK_OPTION_MENU (widget), &props);
472
  border_width = GTK_CONTAINER (widget)->border_width;
473

Elliot Lee's avatar
Elliot Lee committed
474
475
476
  widget->allocation = *allocation;
  if (GTK_WIDGET_REALIZED (widget))
    gdk_window_move_resize (widget->window,
477
478
			    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
479

480
  child = GTK_BIN (widget)->child;
Elliot Lee's avatar
Elliot Lee committed
481
482
  if (child && GTK_WIDGET_VISIBLE (child))
    {
483
484
485
      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 -
486
				    props.indicator_size.width - props.indicator_spacing.left - props.indicator_spacing.right -
487
				    CHILD_LEFT_SPACING - CHILD_RIGHT_SPACING - 2);
488
      child_allocation.height = MAX (1, (gint)allocation->height - child_allocation.y * 2 - border_width * 2 -
489
				     CHILD_TOP_SPACING - CHILD_BOTTOM_SPACING - 2);
Elliot Lee's avatar
Elliot Lee committed
490
      child_allocation.x += CHILD_LEFT_SPACING;
491
      child_allocation.y += CHILD_TOP_SPACING;
Elliot Lee's avatar
Elliot Lee committed
492
493
494
495
496
497
498
499
500

      gtk_widget_size_allocate (child, &child_allocation);
    }
}

static void
gtk_option_menu_paint (GtkWidget    *widget,
		       GdkRectangle *area)
{
501
  GdkRectangle button_area;
502
  GtkOptionMenuProps props;
503
  gint border_width;
Elliot Lee's avatar
Elliot Lee committed
504
505
506
507
508
509

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

  if (GTK_WIDGET_DRAWABLE (widget))
    {
510
      border_width = GTK_CONTAINER (widget)->border_width;
511
512
      gtk_option_menu_get_props (GTK_OPTION_MENU (widget), &props);

513
514
      button_area.x = 0;
      button_area.y = 0;
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
      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);
	}
532

533
534
535
536
537
      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);
538
539
540
541
      
      gtk_paint_tab (widget->style, widget->window,
		     GTK_WIDGET_STATE (widget), GTK_SHADOW_OUT,
		     area, widget, "optionmenutab",
542
543
544
545
546
		     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);
547
548
      
      if (GTK_WIDGET_HAS_FOCUS (widget))
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
	{
	  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
573
574
575
576
577
578
579
580
581
582
583
584
585
586
    }
}

static gint
gtk_option_menu_expose (GtkWidget      *widget,
			GdkEventExpose *event)
{
  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);

587
588
589
590
591
592
593
594
595
596
597
598

      /* 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
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
      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));
621
#else
622
623
624
625
      if (GTK_BIN (widget)->child)
	gtk_container_propagate_expose (GTK_CONTAINER (widget),
					GTK_BIN (widget)->child,
					event);
626
#endif /* 0 */
Elliot Lee's avatar
Elliot Lee committed
627
628
629
630
631
632
633
634
635
636
    }

  return FALSE;
}

static gint
gtk_option_menu_button_press (GtkWidget      *widget,
			      GdkEventButton *event)
{
  GtkOptionMenu *option_menu;
637
  GtkWidget *menu_item;
Elliot Lee's avatar
Elliot Lee committed
638
639
640
641

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

642
643
  option_menu = GTK_OPTION_MENU (widget);

Elliot Lee's avatar
Elliot Lee committed
644
645
646
647
648
649
650
  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);
651
652
653
      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);
654
      return TRUE;
Elliot Lee's avatar
Elliot Lee committed
655
656
657
658
659
    }

  return FALSE;
}

660
661
662
663
664
static gint
gtk_option_menu_key_press (GtkWidget   *widget,
			   GdkEventKey *event)
{
  GtkOptionMenu *option_menu;
665
  GtkWidget *menu_item;
666
667
668
669
670
671
672
673

  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)
    {
674
    case GDK_KP_Space:
675
676
677
678
679
    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);
680
681
682
      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);
683
      return TRUE;
684
685
686
687
688
    }
  
  return FALSE;
}

Elliot Lee's avatar
Elliot Lee committed
689
static void
Tim Janik's avatar
Tim Janik committed
690
691
gtk_option_menu_selection_done (GtkMenuShell  *menu_shell,
				GtkOptionMenu *option_menu)
Elliot Lee's avatar
Elliot Lee committed
692
693
694
695
696
697
698
{
  g_return_if_fail (menu_shell != NULL);
  g_return_if_fail (GTK_IS_OPTION_MENU (option_menu));

  gtk_option_menu_update_contents (option_menu);
}

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

707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
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
757
758
759
760
static void
gtk_option_menu_update_contents (GtkOptionMenu *option_menu)
{
  GtkWidget *child;
761
  GtkRequisition child_requisition;
Elliot Lee's avatar
Elliot Lee committed
762
763
764
765
766

  g_return_if_fail (GTK_IS_OPTION_MENU (option_menu));

  if (option_menu->menu)
    {
767
768
      GtkWidget *old_item = option_menu->menu_item;
      
Elliot Lee's avatar
Elliot Lee committed
769
770
771
772
773
      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
774
	  gtk_widget_ref (option_menu->menu_item);
Elliot Lee's avatar
Elliot Lee committed
775
776
777
	  child = GTK_BIN (option_menu->menu_item)->child;
	  if (child)
	    {
778
779
	      if (!GTK_WIDGET_IS_SENSITIVE (option_menu->menu_item))
		gtk_widget_set_sensitive (child, FALSE);
Elliot Lee's avatar
Elliot Lee committed
780
781
782
	      gtk_widget_reparent (child, GTK_WIDGET (option_menu));
	    }

783
784
785
786
787
	  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);

788
	  gtk_widget_size_request (child, &child_requisition);
Elliot Lee's avatar
Elliot Lee committed
789
790
791
792
793
794
	  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));
	}
795
796
797

      if (old_item != option_menu->menu_item)
        gtk_option_menu_changed (option_menu);
Elliot Lee's avatar
Elliot Lee committed
798
799
800
801
802
803
    }
}

static void
gtk_option_menu_remove_contents (GtkOptionMenu *option_menu)
{
804
805
  GtkWidget *child;
  
Elliot Lee's avatar
Elliot Lee committed
806
807
  g_return_if_fail (GTK_IS_OPTION_MENU (option_menu));

808
  if (option_menu->menu_item)
Elliot Lee's avatar
Elliot Lee committed
809
    {
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
      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
825
      gtk_widget_unref (option_menu->menu_item);
Elliot Lee's avatar
Elliot Lee committed
826
827
828
829
830
831
832
833
834
      option_menu->menu_item = NULL;
    }
}

static void
gtk_option_menu_calc_size (GtkOptionMenu *option_menu)
{
  GtkWidget *child;
  GList *children;
835
  GtkRequisition child_requisition;
836
837
  gint old_width = option_menu->width;
  gint old_height = option_menu->height;
Elliot Lee's avatar
Elliot Lee committed
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853

  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))
	    {
854
	      gtk_widget_size_request (child, &child_requisition);
Elliot Lee's avatar
Elliot Lee committed
855

856
857
	      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
858
859
860
	    }
	}
    }
861
862
863

  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
864
865
866
867
868
869
}

static void
gtk_option_menu_position (GtkMenu  *menu,
			  gint     *x,
			  gint     *y,
870
			  gboolean *push_in,
Elliot Lee's avatar
Elliot Lee committed
871
872
873
874
875
			  gpointer  user_data)
{
  GtkOptionMenu *option_menu;
  GtkWidget *active;
  GtkWidget *child;
Owen Taylor's avatar
Owen Taylor committed
876
  GtkRequisition requisition;
Elliot Lee's avatar
Elliot Lee committed
877
878
879
880
  GList *children;
  gint screen_width;
  gint menu_xpos;
  gint menu_ypos;
881
  gint menu_width;
Elliot Lee's avatar
Elliot Lee committed
882
883
884
885
886

  g_return_if_fail (GTK_IS_OPTION_MENU (user_data));

  option_menu = GTK_OPTION_MENU (user_data);

Owen Taylor's avatar
Owen Taylor committed
887
  gtk_widget_get_child_requisition (GTK_WIDGET (menu), &requisition);
888
  menu_width = requisition.width;
Elliot Lee's avatar
Elliot Lee committed
889
890
891
892
893
894
895

  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
896
897
898
899
    {
      gtk_widget_get_child_requisition (active, &requisition);
      menu_ypos -= requisition.height / 2;
    }
Elliot Lee's avatar
Elliot Lee committed
900

901
  children = GTK_MENU_SHELL (option_menu->menu)->children;
Elliot Lee's avatar
Elliot Lee committed
902
903
904
905
906
907
908
  while (children)
    {
      child = children->data;

      if (active == child)
	break;

909
      if (GTK_WIDGET_VISIBLE (child))
Owen Taylor's avatar
Owen Taylor committed
910
911
912
913
	{
	  gtk_widget_get_child_requisition (child, &requisition);
	  menu_ypos -= requisition.height;
	}
914

Elliot Lee's avatar
Elliot Lee committed
915
916
917
918
      children = children->next;
    }

  screen_width = gdk_screen_width ();
919
  
Elliot Lee's avatar
Elliot Lee committed
920
921
  if (menu_xpos < 0)
    menu_xpos = 0;
922
923
  else if ((menu_xpos + menu_width) > screen_width)
    menu_xpos -= ((menu_xpos + menu_width) - screen_width);
Elliot Lee's avatar
Elliot Lee committed
924
925
926

  *x = menu_xpos;
  *y = menu_ypos;
927
  *push_in = TRUE;
Elliot Lee's avatar
Elliot Lee committed
928
}
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961


static void
gtk_option_menu_show_all (GtkWidget *widget)
{
  GtkContainer *container;
  GtkOptionMenu *option_menu;
  
  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 (GTK_IS_OPTION_MENU (widget));
  container = GTK_CONTAINER (widget);

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

962
963
964
965
966
967
968
969
static gboolean
gtk_option_menu_mnemonic_activate (GtkWidget *widget,
				   gboolean   group_cycling)
{
  gtk_widget_grab_focus (widget);
  return TRUE;
}

970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
static gint
gtk_option_menu_scroll_event (GtkWidget          *widget,
			      GdkEventScroll     *event)
{
  GtkOptionMenu *option_menu = GTK_OPTION_MENU (widget);
  gint index;
  gint n_children;
  gint index_dir;
  GList *l;
  GtkMenuItem *item;
    
  index = gtk_option_menu_get_history (option_menu);

  if (index != -1)
    {
      n_children = g_list_length (GTK_MENU_SHELL (option_menu->menu)->children);
      
      if (event->direction == GDK_SCROLL_UP)
	index_dir = -1;
      else
	index_dir = 1;


      while (TRUE)
	{
	  index += index_dir;

	  if (index < 0)
	    break;
	  if (index >= n_children)
	    break;

	  l = g_list_nth (GTK_MENU_SHELL (option_menu->menu)->children, index);
	  item = GTK_MENU_ITEM (l->data);
	  if (GTK_WIDGET_VISIBLE (item) && GTK_WIDGET_IS_SENSITIVE (item))
	    {
	      gtk_option_menu_set_history (option_menu, index);
	      break;
	    }
	      
	}
    }

  return TRUE;
}