gtkcombo.c 35.7 KB
Newer Older
1 2 3 4
/* gtkcombo - combo widget for gtk+
 * Copyright 1997 Paolo Molaro
 *
 * This library is free software; you can redistribute it and/or
5
 * modify it under the terms of the GNU Lesser General Public
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.
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.
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 28 29 30
/* Do NOT, I repeat, NOT, copy any of the code in this file.
 * The code here relies on all sorts of internal details of GTK+
 */

31 32
#undef GTK_DISABLE_DEPRECATED

33 34 35 36 37 38
#include <string.h>

#include "gtkarrow.h"
#include "gtklabel.h"
#include "gtklist.h"
#include "gtkentry.h"
39
#include "gtkeventbox.h"
40 41 42 43 44 45 46
#include "gtkbutton.h"
#include "gtklistitem.h"
#include "gtkscrolledwindow.h"
#include "gtkmain.h"
#include "gtkwindow.h"
#include "gdk/gdkkeysyms.h"
#include "gtkcombo.h"
47
#include "gtkframe.h"
Havoc Pennington's avatar
Havoc Pennington committed
48
#include "gtkintl.h"
49

50
const gchar *gtk_combo_string_key = "gtk-combo-string-value";
51

52 53
#define COMBO_LIST_MAX_HEIGHT	(400)
#define	EMPTY_LIST_HEIGHT	(15)
54

Havoc Pennington's avatar
Havoc Pennington committed
55 56 57 58
enum {
  PROP_0,
  PROP_ENABLE_ARROW_KEYS,
  PROP_ENABLE_ARROWS_ALWAYS,
59 60 61
  PROP_CASE_SENSITIVE,
  PROP_ALLOW_EMPTY,
  PROP_VALUE_IN_LIST
Havoc Pennington's avatar
Havoc Pennington committed
62 63
};

64 65
static void         gtk_combo_class_init         (GtkComboClass    *klass);
static void         gtk_combo_init               (GtkCombo         *combo);
66 67
static void         gtk_combo_realize		 (GtkWidget	   *widget);
static void         gtk_combo_unrealize		 (GtkWidget	   *widget);
68 69 70 71 72 73 74 75 76 77 78 79 80
static void         gtk_combo_destroy            (GtkObject        *combo);
static GtkListItem *gtk_combo_find               (GtkCombo         *combo);
static gchar *      gtk_combo_func               (GtkListItem      *li);
static gint         gtk_combo_focus_idle         (GtkCombo         *combo);
static gint         gtk_combo_entry_focus_out    (GtkEntry         *entry,
						  GdkEventFocus    *event,
						  GtkCombo         *combo);
static void         gtk_combo_get_pos            (GtkCombo         *combo,
						  gint             *x,
						  gint             *y,
						  gint             *height,
						  gint             *width);
static void         gtk_combo_popup_list         (GtkCombo         *combo);
81 82
static void         gtk_combo_popdown_list       (GtkCombo         *combo);

83 84
static void         gtk_combo_activate           (GtkWidget        *widget,
						  GtkCombo         *combo);
85
static gboolean     gtk_combo_popup_button_press (GtkWidget        *button,
86 87
						  GdkEventButton   *event,
						  GtkCombo         *combo);
88
static gboolean     gtk_combo_popup_button_leave (GtkWidget        *button,
89 90
						  GdkEventCrossing *event,
						  GtkCombo         *combo);
91
static void         gtk_combo_update_entry       (GtkCombo         *combo);
92 93 94 95 96
static void         gtk_combo_update_list        (GtkEntry         *entry,
						  GtkCombo         *combo);
static gint         gtk_combo_button_press       (GtkWidget        *widget,
						  GdkEvent         *event,
						  GtkCombo         *combo);
97
static void         gtk_combo_button_event_after (GtkWidget        *widget,
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
						  GdkEvent         *event,
						  GtkCombo         *combo);
static gint         gtk_combo_list_enter         (GtkWidget        *widget,
						  GdkEventCrossing *event,
						  GtkCombo         *combo);
static gint         gtk_combo_list_key_press     (GtkWidget        *widget,
						  GdkEventKey      *event,
						  GtkCombo         *combo);
static gint         gtk_combo_entry_key_press    (GtkEntry         *widget,
						  GdkEventKey      *event,
						  GtkCombo         *combo);
static gint         gtk_combo_window_key_press   (GtkWidget        *window,
						  GdkEventKey      *event,
						  GtkCombo         *combo);
static void         gtk_combo_size_allocate      (GtkWidget        *widget,
Havoc Pennington's avatar
Havoc Pennington committed
113 114 115 116
						  GtkAllocation   *allocation);
static void         gtk_combo_set_property       (GObject         *object,
						  guint            prop_id,
						  const GValue    *value,
Tim Janik's avatar
Tim Janik committed
117
						  GParamSpec      *pspec);
Havoc Pennington's avatar
Havoc Pennington committed
118 119 120
static void         gtk_combo_get_property       (GObject         *object,
						  guint            prop_id,
						  GValue          *value,
Tim Janik's avatar
Tim Janik committed
121
						  GParamSpec      *pspec);
122 123
static GtkHBoxClass *parent_class = NULL;

124
static void
125 126
gtk_combo_class_init (GtkComboClass * klass)
{
Havoc Pennington's avatar
Havoc Pennington committed
127
  GObjectClass *gobject_class;
128
  GtkObjectClass *oclass;
129
  GtkWidgetClass *widget_class;
130

Havoc Pennington's avatar
Havoc Pennington committed
131
  gobject_class = (GObjectClass *) klass;
132
  oclass = (GtkObjectClass *) klass;
133
  widget_class = (GtkWidgetClass *) klass;
134

Manish Singh's avatar
Manish Singh committed
135 136
  parent_class = g_type_class_peek_parent (klass);

Havoc Pennington's avatar
Havoc Pennington committed
137 138 139 140 141 142 143 144 145 146 147 148 149 150
  gobject_class->set_property = gtk_combo_set_property; 
  gobject_class->get_property = gtk_combo_get_property; 

  g_object_class_install_property (gobject_class,
                                   PROP_ENABLE_ARROW_KEYS,
                                   g_param_spec_boolean ("enable_arrow_keys",
                                                         _("Enable arrow keys"),
                                                         _("Whether the arrow keys move through the list of items"),
                                                         TRUE,
                                                         G_PARAM_READABLE | G_PARAM_WRITABLE));
  g_object_class_install_property (gobject_class,
                                   PROP_ENABLE_ARROWS_ALWAYS,
                                   g_param_spec_boolean ("enable_arrows_always",
                                                         _("Always enable arrows"),
151
                                                         _("Obsolete property, ignored"),
Havoc Pennington's avatar
Havoc Pennington committed
152 153 154 155 156 157 158 159 160
                                                         TRUE,
                                                         G_PARAM_READABLE | G_PARAM_WRITABLE));
  g_object_class_install_property (gobject_class,
                                   PROP_CASE_SENSITIVE,
                                   g_param_spec_boolean ("case_sensitive",
                                                         _("Case sensitive"),
                                                         _("Whether list item matching is case sensitive"),
                                                         FALSE,
                                                         G_PARAM_READABLE | G_PARAM_WRITABLE));
161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176

  g_object_class_install_property (gobject_class,
                                   PROP_ALLOW_EMPTY,
                                   g_param_spec_boolean ("allow_empty",
                                                         _("Allow empty"),
							 _("Whether an empty value may be entered in this field"),
                                                         TRUE,
                                                         G_PARAM_READABLE | G_PARAM_WRITABLE));

  g_object_class_install_property (gobject_class,
                                   PROP_VALUE_IN_LIST,
                                   g_param_spec_boolean ("value_in_list",
                                                         _("Value in list"),
                                                         _("Whether entered values must already be present in the list"),
                                                         FALSE,
                                                         G_PARAM_READABLE | G_PARAM_WRITABLE));
Havoc Pennington's avatar
Havoc Pennington committed
177 178
  
   
179
  oclass->destroy = gtk_combo_destroy;
180 181
  
  widget_class->size_allocate = gtk_combo_size_allocate;
182 183
  widget_class->realize = gtk_combo_realize;
  widget_class->unrealize = gtk_combo_unrealize;
184 185 186
}

static void
187
gtk_combo_destroy (GtkObject *object)
188
{
189
  GtkCombo *combo = GTK_COMBO (object);
190

191 192 193
  if (combo->popwin)
    {
      gtk_widget_destroy (combo->popwin);
Manish Singh's avatar
Manish Singh committed
194
      g_object_unref (combo->popwin);
195 196 197 198
      combo->popwin = NULL;
    }

  GTK_OBJECT_CLASS (parent_class)->destroy (object);
199 200 201 202 203 204
}

static int
gtk_combo_entry_key_press (GtkEntry * entry, GdkEventKey * event, GtkCombo * combo)
{
  GList *li;
205
  guint state = event->state & gtk_accelerator_get_default_mod_mask ();
206

207
  /* completion */
208 209
  if ((event->keyval == GDK_Tab ||  event->keyval == GDK_KP_Tab) &&
      state == GDK_MOD1_MASK)
210
    {
Owen Taylor's avatar
Owen Taylor committed
211
      GtkEditable *editable = GTK_EDITABLE (entry);
Manish Singh's avatar
Manish Singh committed
212 213 214 215
      GCompletion * cmpl;
      gchar* prefix;
      gchar* nprefix = NULL;
      gint pos;
216

Manish Singh's avatar
Manish Singh committed
217 218
      if ( !GTK_LIST (combo->list)->children )
	return FALSE;
219
    
Manish Singh's avatar
Manish Singh committed
220 221
      cmpl = g_completion_new ((GCompletionFunc)gtk_combo_func);
      g_completion_add_items (cmpl, GTK_LIST (combo->list)->children);
222

Manish Singh's avatar
Manish Singh committed
223 224
      pos = gtk_editable_get_position (editable);
      prefix = gtk_editable_get_chars (editable, 0, pos);
225

Manish Singh's avatar
Manish Singh committed
226
      g_completion_complete (cmpl, prefix, &nprefix);
227

Manish Singh's avatar
Manish Singh committed
228 229 230 231 232 233
      if (nprefix && strlen (nprefix) > strlen (prefix)) 
	{
	  gtk_editable_insert_text (editable, nprefix + pos, 
				    strlen (nprefix) - strlen (prefix), &pos);
	  gtk_editable_set_position (editable, pos);
	}
234

Manish Singh's avatar
Manish Singh committed
235 236 237 238
      if (nprefix)
	g_free (nprefix);
      g_free (prefix);
      g_completion_free (cmpl);
239

Manish Singh's avatar
Manish Singh committed
240 241
      return TRUE;
    }
242

243 244 245 246 247 248 249
  if ((event->keyval == GDK_Down || event->keyval == GDK_KP_Down) &&
      state == GDK_MOD1_MASK)
    {
      gtk_combo_activate (NULL, combo);
      return TRUE;
    }

250 251
  if (!combo->use_arrows || !GTK_LIST (combo->list)->children)
    return FALSE;
252

253
  gtk_combo_update_list (GTK_ENTRY (combo->entry), combo);
254 255
  li = g_list_find (GTK_LIST (combo->list)->children, gtk_combo_find (combo));

256 257
  if (((event->keyval == GDK_Up || event->keyval == GDK_KP_Up) && state == 0) ||
      ((event->keyval == 'p' || event->keyval == 'P') && state == GDK_MOD1_MASK))
258
    {
259 260 261
      if (!li)
	li = g_list_last (GTK_LIST (combo->list)->children);
      else
262
	li = li->prev;
263

264 265 266
      if (li)
	{
	  gtk_list_select_child (GTK_LIST (combo->list), GTK_WIDGET (li->data));
267
	  gtk_combo_update_entry (combo);
268
	}
269 270
      
      return TRUE;
271
    }
272 273
  if (((event->keyval == GDK_Down || event->keyval == GDK_KP_Down) && state == 0) ||
      ((event->keyval == 'n' || event->keyval == 'N') && state == GDK_MOD1_MASK))
274
    {
275 276 277
      if (!li)
	li = GTK_LIST (combo->list)->children;
      else if (li)
278 279 280 281
	li = li->next;
      if (li)
	{
	  gtk_list_select_child (GTK_LIST (combo->list), GTK_WIDGET (li->data));
282
	  gtk_combo_update_entry (combo);
283
	}
284 285
      
      return TRUE;
286 287 288 289
    }
  return FALSE;
}

290 291 292 293 294
static int
gtk_combo_window_key_press (GtkWidget   *window,
			    GdkEventKey *event,
			    GtkCombo    *combo)
{
295 296 297 298
  guint state = event->state & gtk_accelerator_get_default_mod_mask ();

  if ((event->keyval == GDK_Return || event->keyval == GDK_KP_Enter) &&
      state == 0)
299
    {
300 301
      gtk_combo_popdown_list (combo);
      gtk_combo_update_entry (combo);
302

303 304 305 306 307 308
      return TRUE;
    }
  else if ((event->keyval == GDK_Up || event->keyval == GDK_KP_Up) &&
	   state == GDK_MOD1_MASK)
    {
      gtk_combo_popdown_list (combo);
309 310 311

      return TRUE;
    }
312 313 314 315 316
  else if ((event->keyval == GDK_space || event->keyval == GDK_KP_Space) &&
	   state == 0)
    {
      gtk_combo_update_entry (combo);
    }
317 318 319 320

  return FALSE;
}

321 322 323
static GtkListItem *
gtk_combo_find (GtkCombo * combo)
{
324
  const gchar *text;
325
  GtkListItem *found = NULL;
326
  gchar *ltext;
327
  gchar *compare_text;
328 329
  GList *clist;

330
  text = gtk_entry_get_text (GTK_ENTRY (combo->entry));
331
  if (combo->case_sensitive)
332
    compare_text = (gchar *)text;
333
  else
334 335
    compare_text = g_utf8_casefold (text, -1);
  
336 337 338
  for (clist = GTK_LIST (combo->list)->children;
       !found && clist;
       clist = clist->next)
339 340 341 342
    {
      ltext = gtk_combo_func (GTK_LIST_ITEM (clist->data));
      if (!ltext)
	continue;
343 344 345 346 347 348 349 350 351

      if (!combo->case_sensitive)
	ltext = g_utf8_casefold (ltext, -1);

      if (strcmp (ltext, compare_text) == 0)
	found = clist->data;

      if (!combo->case_sensitive)
	g_free (ltext);
352 353
    }

354 355 356 357
  if (!combo->case_sensitive)
    g_free (compare_text);

  return found;
358 359 360 361 362 363 364 365
}

static gchar *
gtk_combo_func (GtkListItem * li)
{
  GtkWidget *label;
  gchar *ltext = NULL;

Manish Singh's avatar
Manish Singh committed
366
  ltext = g_object_get_data (G_OBJECT (li), gtk_combo_string_key);
367 368 369 370 371
  if (!ltext)
    {
      label = GTK_BIN (li)->child;
      if (!label || !GTK_IS_LABEL (label))
	return NULL;
Manish Singh's avatar
Manish Singh committed
372
      ltext = (gchar *) gtk_label_get_text (GTK_LABEL (label));
373 374 375 376 377 378 379 380
    }
  return ltext;
}

static gint
gtk_combo_focus_idle (GtkCombo * combo)
{
  if (combo)
381
    {
382
      GDK_THREADS_ENTER ();
383
      gtk_widget_grab_focus (combo->entry);
384
      GDK_THREADS_LEAVE ();
385
    }
386 387 388 389 390 391 392 393 394
  return FALSE;
}

static gint
gtk_combo_entry_focus_out (GtkEntry * entry, GdkEventFocus * event, GtkCombo * combo)
{

  if (combo->value_in_list && !gtk_combo_find (combo))
    {
395 396
      GSource *focus_idle;
      
397 398 399 400 401 402 403 404 405 406
      /* gdk_beep(); *//* this can be annoying */
      if (combo->ok_if_empty && !strcmp (gtk_entry_get_text (entry), ""))
	return FALSE;
#ifdef TEST
      printf ("INVALID ENTRY: `%s'\n", gtk_entry_get_text (entry));
#endif
      gtk_grab_add (GTK_WIDGET (combo));
      /* this is needed because if we call gtk_widget_grab_focus() 
         it isn't guaranteed it's the *last* call before the main-loop,
         so the focus can be lost anyway...
Manish Singh's avatar
Manish Singh committed
407
         the signal_stop_emission doesn't seem to work either...
408
       */
409 410 411 412 413 414
      focus_idle = g_idle_source_new ();
      g_source_set_closure (focus_idle,
			    g_cclosure_new_object (G_CALLBACK (gtk_combo_focus_idle),
						   G_OBJECT (combo)));
      g_source_attach (focus_idle, NULL);
      
Manish Singh's avatar
Manish Singh committed
415
      /*g_signal_stop_emission_by_name (entry, "focus_out_event"); */
416 417 418 419 420 421 422 423
      return TRUE;
    }
  return FALSE;
}

static void
gtk_combo_get_pos (GtkCombo * combo, gint * x, gint * y, gint * height, gint * width)
{
424 425 426
  GtkBin *popwin;
  GtkWidget *widget;
  GtkScrolledWindow *popup;
427
  
428 429 430 431 432 433 434 435 436 437
  gint real_height;
  GtkRequisition list_requisition;
  gboolean show_hscroll = FALSE;
  gboolean show_vscroll = FALSE;
  gint avail_height;
  gint min_height;
  gint alloc_width;
  gint work_height;
  gint old_height;
  gint old_width;
438
  gint scrollbar_spacing;
439
  
440
  widget = GTK_WIDGET (combo);
441 442
  popup  = GTK_SCROLLED_WINDOW (combo->popup);
  popwin = GTK_BIN (combo->popwin);
443 444 445

  scrollbar_spacing = _gtk_scrolled_window_get_scrollbar_spacing (popup);

446 447 448 449
  gdk_window_get_origin (combo->entry->window, x, y);
  real_height = MIN (combo->entry->requisition.height, 
		     combo->entry->allocation.height);
  *y += real_height;
450
  avail_height = gdk_screen_get_height (gtk_widget_get_screen (widget)) - *y;
451
  
452 453 454
  gtk_widget_size_request (combo->list, &list_requisition);
  min_height = MIN (list_requisition.height, 
		    popup->vscrollbar->requisition.height);
455 456 457 458
  if (!GTK_LIST (combo->list)->children)
    list_requisition.height += EMPTY_LIST_HEIGHT;
  
  alloc_width = (widget->allocation.width -
459
		 2 * popwin->child->style->xthickness -
460 461
		 2 * GTK_CONTAINER (popwin->child)->border_width -
		 2 * GTK_CONTAINER (combo->popup)->border_width -
462
		 2 * GTK_CONTAINER (GTK_BIN (popup)->child)->border_width - 
463
		 2 * GTK_BIN (popup)->child->style->xthickness);
464
  
465
  work_height = (2 * popwin->child->style->ythickness +
466 467
		 2 * GTK_CONTAINER (popwin->child)->border_width +
		 2 * GTK_CONTAINER (combo->popup)->border_width +
468
		 2 * GTK_CONTAINER (GTK_BIN (popup)->child)->border_width +
469
		 2 * GTK_BIN (popup)->child->style->ythickness);
470
  
471 472 473 474
  do 
    {
      old_width = alloc_width;
      old_height = work_height;
475
      
476 477 478
      if (!show_hscroll &&
	  alloc_width < list_requisition.width)
	{
479 480 481 482 483
	  GtkRequisition requisition;
	  
	  gtk_widget_size_request (popup->hscrollbar, &requisition);
	  work_height += (requisition.height + scrollbar_spacing);
	  
484 485 486 487 488
	  show_hscroll = TRUE;
	}
      if (!show_vscroll && 
	  work_height + list_requisition.height > avail_height)
	{
489 490
	  GtkRequisition requisition;
	  
491 492 493
	  if (work_height + min_height > avail_height && 
	      *y - real_height > avail_height)
	    {
494
	      *y -= (work_height + list_requisition.height + real_height);
495 496
	      break;
	    }
497 498
	  gtk_widget_size_request (popup->hscrollbar, &requisition);
	  alloc_width -= (requisition.width + scrollbar_spacing);
499 500 501
	  show_vscroll = TRUE;
	}
    } while (old_width != alloc_width || old_height != work_height);
502
  
503 504
  *width = widget->allocation.width;
  if (show_vscroll)
505
    *height = avail_height;
506 507 508 509 510
  else
    *height = work_height + list_requisition.height;
  
  if (*x < 0)
    *x = 0;
511 512 513
}

static void
514
gtk_combo_popup_list (GtkCombo * combo)
515
{
516
  GtkList *list;
517
  gint height, width, x, y;
518
  gint old_width, old_height;
519

520 521 522
  old_width = combo->popwin->allocation.width;
  old_height  = combo->popwin->allocation.height;

523 524
  gtk_combo_get_pos (combo, &x, &y, &height, &width);

525 526 527 528 529 530 531
  /* workaround for gtk_scrolled_window_size_allocate bug */
  if (old_width != width || old_height != height)
    {
      gtk_widget_hide (GTK_SCROLLED_WINDOW (combo->popup)->hscrollbar);
      gtk_widget_hide (GTK_SCROLLED_WINDOW (combo->popup)->vscrollbar);
    }

532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547
  gtk_combo_update_list (GTK_ENTRY (combo->entry), combo);

  /* We need to make sure some child of combo->popwin
   * is focused to disable GtkWindow's automatic
   * "focus-the-first-item" code. If there is no selected
   * child, we focus the list itself with some hackery.
   */
  list = GTK_LIST (combo->list);
  
  if (list->selection)
    {
      gtk_widget_grab_focus (list->selection->data);
    }
  else
    {
      GTK_WIDGET_SET_FLAGS (list, GTK_CAN_FOCUS);
548
      gtk_widget_grab_focus (combo->list);
549
      GTK_LIST (combo->list)->last_focus_child = NULL;
550 551 552
      GTK_WIDGET_UNSET_FLAGS (list, GTK_CAN_FOCUS);
    }
  
553
  gtk_window_move (GTK_WINDOW (combo->popwin), x, y);
Manish Singh's avatar
Manish Singh committed
554
  gtk_widget_set_size_request (combo->popwin, width, height);
555
  gtk_widget_show (combo->popwin);
556

557
  gtk_widget_grab_focus (combo->popwin);
558 559
}

560 561 562 563 564 565 566 567 568 569 570 571 572 573
static void
gtk_combo_popdown_list (GtkCombo *combo)
{
  combo->current_button = 0;
      
  if (GTK_BUTTON (combo->button)->in_button)
    {
      GTK_BUTTON (combo->button)->in_button = FALSE;
      gtk_button_released (GTK_BUTTON (combo->button));
    }

  if (GTK_WIDGET_HAS_GRAB (combo->popwin))
    {
      gtk_grab_remove (combo->popwin);
574
      gdk_display_pointer_ungrab (gtk_widget_get_display (GTK_WIDGET (combo)),
575 576 577
				  gtk_get_current_event_time ());
      gdk_display_keyboard_ungrab (gtk_widget_get_display (GTK_WIDGET (combo)),
				   gtk_get_current_event_time ());
578 579 580 581 582
    }
  
  gtk_widget_hide (combo->popwin);
}

583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605
static gboolean
popup_grab_on_window (GdkWindow *window,
		      guint32    activate_time)
{
  if ((gdk_pointer_grab (window, TRUE,
			 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
			 GDK_POINTER_MOTION_MASK,
			 NULL, NULL, activate_time) == 0))
    {
      if (gdk_keyboard_grab (window, TRUE,
			     activate_time) == 0)
	return TRUE;
      else
	{
	  gdk_display_pointer_ungrab (gdk_drawable_get_display (window),
				      activate_time);
	  return FALSE;
	}
    }

  return FALSE;
}

606 607 608 609
static void        
gtk_combo_activate (GtkWidget        *widget,
		    GtkCombo         *combo)
{
610 611 612
  if (!combo->button->window ||
      !popup_grab_on_window (combo->button->window,
			     gtk_get_current_event_time ()))
Owen Taylor's avatar
Owen Taylor committed
613
    return;
614

615
  gtk_combo_popup_list (combo);
616
  
617 618 619 620
  /* This must succeed since we already have the grab */
  popup_grab_on_window (combo->popwin->window,
			gtk_get_current_event_time ());

621 622
  if (!GTK_WIDGET_HAS_FOCUS (combo->entry))
    gtk_widget_grab_focus (combo->entry);
623

624 625 626
  gtk_grab_add (combo->popwin);
}

627
static gboolean
628 629 630
gtk_combo_popup_button_press (GtkWidget        *button,
			      GdkEventButton   *event,
			      GtkCombo         *combo)
631
{
632 633
  if (!GTK_WIDGET_HAS_FOCUS (combo->entry))
    gtk_widget_grab_focus (combo->entry);
634 635

  if (event->button != 1)
Havoc Pennington's avatar
Havoc Pennington committed
636
    return FALSE;
637

638 639 640 641
  if (!popup_grab_on_window (combo->button->window,
			     gtk_get_current_event_time ()))
    return FALSE;

642
  combo->current_button = event->button;
643 644

  gtk_combo_popup_list (combo);
645 646 647 648 649

  /* This must succeed since we already have the grab */
  popup_grab_on_window (combo->popwin->window,
			gtk_get_current_event_time ());

650 651 652 653
  gtk_button_pressed (GTK_BUTTON (button));

  gtk_grab_add (combo->popwin);

654
  return TRUE;
655
}
656

657
static gboolean
658 659 660 661
gtk_combo_popup_button_leave (GtkWidget        *button,
			      GdkEventCrossing *event,
			      GtkCombo         *combo)
{
662 663 664
  /* The idea here is that we want to keep the button down if the
   * popup is popped up.
   */
665
  return combo->current_button != 0;
666 667
}

668
static void
669
gtk_combo_update_entry (GtkCombo * combo)
670
{
671
  GtkList *list = GTK_LIST (combo->list);
672 673
  char *text;

674
  g_signal_handler_block (list, combo->list_change_id);
675 676 677 678 679 680 681
  if (list->selection)
    {
      text = gtk_combo_func (GTK_LIST_ITEM (list->selection->data));
      if (!text)
	text = "";
      gtk_entry_set_text (GTK_ENTRY (combo->entry), text);
    }
682 683 684 685 686 687 688 689 690
  g_signal_handler_unblock (list, combo->list_change_id);
}

static void
gtk_combo_selection_changed (GtkList  *list,
			     GtkCombo *combo)
{
  if (!GTK_WIDGET_VISIBLE (combo->popwin))
    gtk_combo_update_entry (combo);
691 692 693 694 695 696 697 698 699 700 701
}

static void
gtk_combo_update_list (GtkEntry * entry, GtkCombo * combo)
{
  GtkList *list = GTK_LIST (combo->list);
  GList *slist = list->selection;
  GtkListItem *li;

  gtk_grab_remove (GTK_WIDGET (combo));

Manish Singh's avatar
Manish Singh committed
702
  g_signal_handler_block (entry, combo->entry_change_id);
703 704 705 706 707
  if (slist && slist->data)
    gtk_list_unselect_child (list, GTK_WIDGET (slist->data));
  li = gtk_combo_find (combo);
  if (li)
    gtk_list_select_child (list, GTK_WIDGET (li));
Manish Singh's avatar
Manish Singh committed
708
  g_signal_handler_unblock (entry, combo->entry_change_id);
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
}

static gint
gtk_combo_button_press (GtkWidget * widget, GdkEvent * event, GtkCombo * combo)
{
  GtkWidget *child;

  child = gtk_get_event_widget (event);

  /* We don't ask for button press events on the grab widget, so
   *  if an event is reported directly to the grab widget, it must
   *  be on a window outside the application (and thus we remove
   *  the popup window). Otherwise, we check if the widget is a child
   *  of the grab widget, and only remove the popup window if it
   *  is not.
   */
  if (child != widget)
    {
      while (child)
	{
	  if (child == widget)
	    return FALSE;
	  child = child->parent;
	}
    }

735
  gtk_combo_popdown_list (combo);
736 737 738 739

  return TRUE;
}

740 741 742 743 744 745 746
static gboolean
is_within (GtkWidget *widget,
	   GtkWidget *ancestor)
{
  return widget == ancestor || gtk_widget_is_ancestor (widget, ancestor);
}

747 748 749 750
static void
gtk_combo_button_event_after (GtkWidget *widget,
			      GdkEvent  *event,
			      GtkCombo  *combo)
751 752
{
  GtkWidget *child;
753

754 755
  if (event->type != GDK_BUTTON_RELEASE)
    return;
756
  
757 758
  child = gtk_get_event_widget ((GdkEvent*) event);

759 760
  if ((combo->current_button != 0) && (event->button.button == 1))
    {
761 762
      /* This was the initial button press */

763 764 765
      combo->current_button = 0;

      /* Check to see if we released inside the button */
766
      if (child && is_within (child, combo->button))
767 768 769 770 771 772 773
	{
	  gtk_grab_add (combo->popwin);
	  gdk_pointer_grab (combo->popwin->window, TRUE,
			    GDK_BUTTON_PRESS_MASK | 
			    GDK_BUTTON_RELEASE_MASK |
			    GDK_POINTER_MOTION_MASK, 
			    NULL, NULL, GDK_CURRENT_TIME);
774
	  return;
775 776
	}
    }
777

778 779 780
  if (is_within (child, combo->list))
    gtk_combo_update_entry (combo);
    
781
  gtk_combo_popdown_list (combo);
782

783 784
}

Owen Taylor's avatar
Owen Taylor committed
785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807
static void
find_child_foreach (GtkWidget *widget,
		    gpointer   data)
{
  GdkEventButton *event = data;

  if (!event->window)
    {
      if (event->x >= widget->allocation.x &&
	  event->x < widget->allocation.x + widget->allocation.width &&
	  event->y >= widget->allocation.y &&
	  event->y < widget->allocation.y + widget->allocation.height)
	event->window = g_object_ref (widget->window);
    }
}

static void
find_child_window (GtkContainer   *container,
		   GdkEventButton *event)
{
  gtk_container_foreach (container, find_child_foreach, event);
}

808 809 810 811 812 813 814 815
static gint         
gtk_combo_list_enter (GtkWidget        *widget,
		      GdkEventCrossing *event,
		      GtkCombo         *combo)
{
  GtkWidget *event_widget;

  event_widget = gtk_get_event_widget ((GdkEvent*) event);
Owen Taylor's avatar
Owen Taylor committed
816

817 818 819 820
  if ((event_widget == combo->list) &&
      (combo->current_button != 0) && 
      (!GTK_WIDGET_HAS_GRAB (combo->list)))
    {
821
      GdkEvent *tmp_event = gdk_event_new (GDK_BUTTON_PRESS);
822 823 824 825 826 827 828 829 830 831
      gint x, y;
      GdkModifierType mask;

      gtk_grab_remove (combo->popwin);

      /* Transfer the grab over to the list by synthesizing
       * a button press event
       */
      gdk_window_get_pointer (combo->list->window, &x, &y, &mask);

832 833 834 835
      tmp_event->button.send_event = TRUE;
      tmp_event->button.time = GDK_CURRENT_TIME; /* bad */
      tmp_event->button.x = x;
      tmp_event->button.y = y;
836 837 838
      /* We leave all the XInput fields unfilled here, in the expectation
       * that GtkList doesn't care.
       */
839 840
      tmp_event->button.button = combo->current_button;
      tmp_event->button.state = mask;
841

Owen Taylor's avatar
Owen Taylor committed
842 843 844 845 846 847 848 849 850 851 852 853 854
      find_child_window (GTK_CONTAINER (combo->list), &tmp_event->button);
      if (!tmp_event->button.window)
	{
	  GtkWidget *child;
	  
	  if (GTK_LIST (combo->list)->children)
	    child = GTK_LIST (combo->list)->children->data;
	  else
	    child = combo->list;

	  tmp_event->button.window = g_object_ref (child->window);
	}

855 856
      gtk_widget_event (combo->list, tmp_event);
      gdk_event_free (tmp_event);
857 858 859 860 861
    }

  return FALSE;
}

862 863 864
static int
gtk_combo_list_key_press (GtkWidget * widget, GdkEventKey * event, GtkCombo * combo)
{
865 866 867
  guint state = event->state & gtk_accelerator_get_default_mod_mask ();

  if (event->keyval == GDK_Escape && state == 0)
868
    {
869
      if (GTK_WIDGET_HAS_GRAB (combo->list))
870
	gtk_list_end_drag_selection (GTK_LIST (combo->list));
871 872 873

      gtk_combo_popdown_list (combo);
      
874 875 876 877 878
      return TRUE;
    }
  return FALSE;
}

879 880 881
static void
combo_event_box_realize (GtkWidget *widget)
{
882 883
  GdkCursor *cursor = gdk_cursor_new_for_display (gtk_widget_get_display (widget),
						  GDK_TOP_LEFT_ARROW);
884
  gdk_window_set_cursor (widget->window, cursor);
Manish Singh's avatar
Manish Singh committed
885
  gdk_cursor_unref (cursor);
886 887
}

888
static void
889 890 891
gtk_combo_init (GtkCombo * combo)
{
  GtkWidget *arrow;
892
  GtkWidget *frame;
893
  GtkWidget *event_box;
894

895 896 897 898 899
  combo->case_sensitive = FALSE;
  combo->value_in_list = FALSE;
  combo->ok_if_empty = TRUE;
  combo->use_arrows = TRUE;
  combo->use_arrows_always = FALSE;
900 901
  combo->entry = gtk_entry_new ();
  combo->button = gtk_button_new ();
902
  combo->current_button = 0;
903 904 905 906 907
  arrow = gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_OUT);
  gtk_widget_show (arrow);
  gtk_container_add (GTK_CONTAINER (combo->button), arrow);
  gtk_box_pack_start (GTK_BOX (combo), combo->entry, TRUE, TRUE, 0);
  gtk_box_pack_end (GTK_BOX (combo), combo->button, FALSE, FALSE, 0);
908
  GTK_WIDGET_UNSET_FLAGS (combo->button, GTK_CAN_FOCUS);
909 910
  gtk_widget_show (combo->entry);
  gtk_widget_show (combo->button);
Manish Singh's avatar
Manish Singh committed
911 912 913
  combo->entry_change_id = g_signal_connect (combo->entry, "changed",
					     G_CALLBACK (gtk_combo_update_list),
					     combo);
914 915
  g_signal_connect_after (combo->entry, "key_press_event",
			  G_CALLBACK (gtk_combo_entry_key_press), combo);
Manish Singh's avatar
Manish Singh committed
916 917 918 919 920 921 922 923 924
  g_signal_connect_after (combo->entry, "focus_out_event",
			  G_CALLBACK (gtk_combo_entry_focus_out), combo);
  combo->activate_id = g_signal_connect (combo->entry, "activate",
					 G_CALLBACK (gtk_combo_activate),
					 combo);
  g_signal_connect (combo->button, "button_press_event",
		    G_CALLBACK (gtk_combo_popup_button_press), combo);
  g_signal_connect (combo->button, "leave_notify_event",
		    G_CALLBACK (gtk_combo_popup_button_leave), combo);
925 926

  combo->popwin = gtk_window_new (GTK_WINDOW_POPUP);
Manish Singh's avatar
Manish Singh committed
927
  g_object_ref (combo->popwin);
928
  gtk_window_set_resizable (GTK_WINDOW (combo->popwin), FALSE);
929

Manish Singh's avatar
Manish Singh committed
930 931
  g_signal_connect (combo->popwin, "key_press_event",
		    G_CALLBACK (gtk_combo_window_key_press), combo);
932 933 934 935 936
  
  gtk_widget_set_events (combo->popwin, GDK_KEY_PRESS_MASK);

  event_box = gtk_event_box_new ();
  gtk_container_add (GTK_CONTAINER (combo->popwin), event_box);
937 938
  g_signal_connect (event_box, "realize",
		    G_CALLBACK (combo_event_box_realize), NULL);
939 940
  gtk_widget_show (event_box);

941 942

  frame = gtk_frame_new (NULL);
943
  gtk_container_add (GTK_CONTAINER (event_box), frame);
944 945 946
  gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT);
  gtk_widget_show (frame);

947 948
  combo->popup = gtk_scrolled_window_new (NULL, NULL);
  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (combo->popup),
949 950 951 952 953
				  GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
  GTK_WIDGET_UNSET_FLAGS (GTK_SCROLLED_WINDOW (combo->popup)->hscrollbar, GTK_CAN_FOCUS);
  GTK_WIDGET_UNSET_FLAGS (GTK_SCROLLED_WINDOW (combo->popup)->vscrollbar, GTK_CAN_FOCUS);
  gtk_container_add (GTK_CONTAINER (frame), combo->popup);
  gtk_widget_show (combo->popup);
954

955
  combo->list = gtk_list_new ();
956 957 958 959 960
  /* We'll use enter notify events to figure out when to transfer
   * the grab to the list
   */
  gtk_widget_set_events (combo->list, GDK_ENTER_NOTIFY_MASK);

961
  gtk_list_set_selection_mode (GTK_LIST(combo->list), GTK_SELECTION_BROWSE);
962
  gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (combo->popup), combo->list);
963 964
  gtk_container_set_focus_vadjustment (GTK_CONTAINER (combo->list),
				       gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (combo->popup)));
965 966
  gtk_container_set_focus_hadjustment (GTK_CONTAINER (combo->list),
				       gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (combo->popup)));
967
  gtk_widget_show (combo->list);
968

969 970 971
  combo->list_change_id = g_signal_connect (combo->list, "selection_changed",
					    G_CALLBACK (gtk_combo_selection_changed), combo);
  
Manish Singh's avatar
Manish Singh committed
972 973 974 975 976
  g_signal_connect (combo->popwin, "key_press_event",
		    G_CALLBACK (gtk_combo_list_key_press), combo);
  g_signal_connect (combo->popwin, "button_press_event",
		    G_CALLBACK (gtk_combo_button_press), combo);

Owen Taylor's avatar
Owen Taylor committed
977 978
  g_signal_connect (combo->popwin, "event_after",
		    G_CALLBACK (gtk_combo_button_event_after), combo);
Manish Singh's avatar
Manish Singh committed
979 980
  g_signal_connect (combo->list, "event_after",
		    G_CALLBACK (gtk_combo_button_event_after), combo);
981

Owen Taylor's avatar
Owen Taylor committed
982
  g_signal_connect (combo->list, "enter_notify_event",
Manish Singh's avatar
Manish Singh committed
983
		    G_CALLBACK (gtk_combo_list_enter), combo);
984 985
}

986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007
static void
gtk_combo_realize (GtkWidget *widget)
{
  GtkCombo *combo = GTK_COMBO (widget);

  gtk_window_set_screen (GTK_WINDOW (combo->popwin), 
			 gtk_widget_get_screen (widget));
  
  GTK_WIDGET_CLASS( parent_class )->realize (widget);  
}

static void        
gtk_combo_unrealize (GtkWidget *widget)
{
  GtkCombo *combo = GTK_COMBO (widget);

  gtk_combo_popdown_list (combo);
  gtk_widget_unrealize (combo->popwin);
  
  GTK_WIDGET_CLASS (parent_class)->unrealize (widget);
}

Manish Singh's avatar
Manish Singh committed
1008
GType
1009
gtk_combo_get_type (void)
1010
{
Manish Singh's avatar
Manish Singh committed
1011
  static GType combo_type = 0;
1012 1013 1014

  if (!combo_type)
    {
Manish Singh's avatar
Manish Singh committed
1015
      static const GTypeInfo combo_info =
1016 1017
      {
	sizeof (GtkComboClass),
Manish Singh's avatar
Manish Singh committed
1018 1019 1020 1021 1022 1023 1024 1025
	NULL,		/* base_init */
	NULL,		/* base_finalize */
	(GClassInitFunc) gtk_combo_class_init,
	NULL,		/* class_finalize */
	NULL,		/* class_data */
	sizeof (GtkCombo),
	0,		/* n_preallocs */
	(GInstanceInitFunc) gtk_combo_init,
1026
      };
Manish Singh's avatar
Manish Singh committed
1027 1028 1029

      combo_type = g_type_register_static (GTK_TYPE_HBOX, "GtkCombo",
					   &combo_info, 0);
1030
    }
Manish Singh's avatar
Manish Singh committed
1031

1032 1033 1034
  return combo_type;
}

Tim Janik's avatar
Tim Janik committed
1035
GtkWidget*
1036
gtk_combo_new (void)
1037
{
Manish Singh's avatar
Manish Singh committed
1038
  return g_object_new (GTK_TYPE_COMBO, NULL);
1039 1040 1041
}

void
1042
gtk_combo_set_value_in_list (GtkCombo * combo, gboolean val, gboolean ok_if_empty)
1043 1044
{
  g_return_if_fail (GTK_IS_COMBO (combo));
1045 1046
  val = val != FALSE;
  ok_if_empty = ok_if_empty != FALSE;
1047

1048
  g_object_freeze_notify (G_OBJECT (combo));
1049 1050 1051
  if (combo->value_in_list != val)
    {
       combo->value_in_list = val;
1052
  g_object_notify (G_OBJECT (combo), "value_in_list");
1053 1054 1055 1056
    }
  if (combo->ok_if_empty != ok_if_empty)
    {
       combo->ok_if_empty = ok_if_empty;
1057
  g_object_notify (G_OBJECT (combo), "allow_empty");
1058
    }
1059
  g_object_thaw_notify (G_OBJECT (combo));
1060 1061 1062
}

void
1063
gtk_combo_set_case_sensitive (GtkCombo * combo, gboolean val)
1064 1065
{
  g_return_if_fail (GTK_IS_COMBO (combo));
1066
  val = val != FALSE;
1067

1068 1069
  if (combo->case_sensitive != val) 
    {
1070
  combo->case_sensitive = val;
Havoc Pennington's avatar
Havoc Pennington committed
1071
  g_object_notify (G_OBJECT (combo), "case_sensitive");
1072
    }
1073 1074 1075
}

void
1076
gtk_combo_set_use_arrows (GtkCombo * combo, gboolean val)
1077 1078
{
  g_return_if_fail (GTK_IS_COMBO (combo));
1079
  val = val != FALSE;
1080

1081 1082
  if (combo->use_arrows != val) 
    {
1083
  combo->use_arrows = val;
1084
  g_object_notify (G_OBJECT (combo), "enable_arrow_keys");
1085
    }
1086 1087 1088
}

void
1089
gtk_combo_set_use_arrows_always (GtkCombo * combo, gboolean val)
1090 1091
{
  g_return_if_fail (GTK_IS_COMBO (combo));
1092
  val = val != FALSE;
1093

1094 1095 1096
  if (combo->use_arrows_always != val) 
    {
       g_object_freeze_notify (G_OBJECT (combo));
1097
  combo->use_arrows_always = val;
1098 1099 1100 1101
       g_object_notify (G_OBJECT (combo), "enable_arrows_always");

       if (combo->use_arrows != TRUE) 
         {
1102
  combo->use_arrows = TRUE;
1103
  g_object_notify (G_OBJECT (combo), "enable_arrow_keys");
1104
         }
Hans Breuer's avatar
Hans Breuer committed
1105
  g_object_thaw_notify (G_OBJECT (combo));
1106
    }
1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117
}

void
gtk_combo_set_popdown_strings (GtkCombo * combo, GList * strings)
{
  GList *list;
  GtkWidget *li;

  g_return_if_fail (GTK_IS_COMBO (combo));
  g_return_if_fail (strings != NULL);

1118 1119
  gtk_combo_popdown_list (combo);

1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136
  gtk_list_clear_items (GTK_LIST (combo->list), 0, -1);
  list = strings;
  while (list)
    {
      li = gtk_list_item_new_with_label ((gchar *) list->data);
      gtk_widget_show (li);
      gtk_container_add (GTK_CONTAINER (combo->list), li);
      list = list->next;
    }
}

void
gtk_combo_set_item_string (GtkCombo * combo, GtkItem * item, const gchar * item_value)
{
  g_return_if_fail (GTK_IS_COMBO (combo));
  g_return_if_fail (item != NULL);

Manish Singh's avatar
Manish Singh committed
1137 1138
  g_object_set_data_full (G_OBJECT (item), gtk_combo_string_key,
			  g_strdup (item_value), g_free);
1139
}
1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165

static void
gtk_combo_size_allocate (GtkWidget     *widget,
			 GtkAllocation *allocation)
{
  GtkCombo *combo;

  g_return_if_fail (GTK_IS_COMBO (widget));
  g_return_if_fail (allocation != NULL);

  GTK_WIDGET_CLASS (parent_class)->size_allocate (widget, allocation);
  
  combo = GTK_COMBO (widget);

  if (combo->entry->allocation.height > combo->entry->requisition.height)
    {
      GtkAllocation button_allocation;

      button_allocation = combo->button->allocation;
      button_allocation.height = combo->entry->requisition.height;
      button_allocation.y = combo->entry->allocation.y + 
	(combo->entry->allocation.height - combo->entry->requisition.height) 
	/ 2;
      gtk_widget_size_allocate (combo->button, &button_allocation);
    }
}
1166 1167 1168 1169 1170 1171 1172

void
gtk_combo_disable_activate (GtkCombo* combo)
{
  g_return_if_fail (GTK_IS_COMBO (combo));

  if ( combo->activate_id ) {
Manish Singh's avatar
Manish Singh committed
1173
    g_signal_handler_disconnect (combo->entry, combo->activate_id);
1174 1175 1176
    combo->activate_id = 0;
  }
}
Havoc Pennington's avatar
Havoc Pennington committed
1177

Tim Janik's avatar
Tim Janik committed
1178 1179 1180 1181 1182
static void
gtk_combo_set_property (GObject         *object,
			guint            prop_id,
			const GValue    *value,
			GParamSpec      *pspec)
Havoc Pennington's avatar
Havoc Pennington committed
1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196
{
  GtkCombo *combo = GTK_COMBO (object);
  
  switch (prop_id)
    {
    case PROP_ENABLE_ARROW_KEYS:
      gtk_combo_set_use_arrows (combo, g_value_get_boolean (value));
      break;
    case PROP_ENABLE_ARROWS_ALWAYS:
      gtk_combo_set_use_arrows_always (combo, g_value_get_boolean (value));
      break;
    case PROP_CASE_SENSITIVE:
      gtk_combo_set_case_sensitive (combo, g_value_get_boolean (value));
      break;
1197 1198 1199 1200 1201 1202
    case PROP_ALLOW_EMPTY:
      combo->ok_if_empty = g_value_get_boolean (value);
      break;
    case PROP_VALUE_IN_LIST:
      combo->value_in_list = g_value_get_boolean (value);
      break;
Havoc Pennington's avatar
Havoc Pennington committed
1203 1204 1205 1206 1207 1208 1209
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
  
}

Tim Janik's avatar
Tim Janik committed
1210 1211 1212 1213 1214
static void
gtk_combo_get_property (GObject         *object,
			guint            prop_id,
			GValue          *value,
			GParamSpec      *pspec)
Havoc Pennington's avatar
Havoc Pennington committed