]> Pileus Git - ~andy/gtk/blob - gtk/gtkcombo.c
Privately export _gtk_scrolled_window_get_scrollbar_spacing().
[~andy/gtk] / gtk / gtkcombo.c
1 /* gtkcombo - combo widget for gtk+
2  * Copyright 1997 Paolo Molaro
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * 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  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19
20 /*
21  * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
22  * file for a list of people on the GTK+ Team.  See the ChangeLog
23  * files for a list of changes.  These files are distributed with
24  * GTK+ at ftp://ftp.gtk.org/pub/gtk/. 
25  */
26
27 /* Do NOT, I repeat, NOT, copy any of the code in this file.
28  * The code here relies on all sorts of internal details of GTK+
29  */
30
31 #include <string.h>
32
33 #include "gtkarrow.h"
34 #include "gtklabel.h"
35 #include "gtklist.h"
36 #include "gtkentry.h"
37 #include "gtkeventbox.h"
38 #include "gtkbutton.h"
39 #include "gtklistitem.h"
40 #include "gtkscrolledwindow.h"
41 #include "gtkmain.h"
42 #include "gtksignal.h"
43 #include "gtkwindow.h"
44 #include "gdk/gdkkeysyms.h"
45 #include "gtkcombo.h"
46 #include "gtkframe.h"
47 #include "gtkintl.h"
48
49 const gchar *gtk_combo_string_key = "gtk-combo-string-value";
50
51 #define COMBO_LIST_MAX_HEIGHT   (400)
52 #define EMPTY_LIST_HEIGHT       (15)
53
54 enum {
55   PROP_0,
56   PROP_ENABLE_ARROW_KEYS,
57   PROP_ENABLE_ARROWS_ALWAYS,
58   PROP_CASE_SENSITIVE,
59   PROP_ALLOW_EMPTY,
60   PROP_VALUE_IN_LIST
61 };
62
63 static void         gtk_combo_class_init         (GtkComboClass    *klass);
64 static void         gtk_combo_init               (GtkCombo         *combo);
65 static void         gtk_combo_realize            (GtkWidget        *widget);
66 static void         gtk_combo_unrealize          (GtkWidget        *widget);
67 static void         gtk_combo_destroy            (GtkObject        *combo);
68 static GtkListItem *gtk_combo_find               (GtkCombo         *combo);
69 static gchar *      gtk_combo_func               (GtkListItem      *li);
70 static gint         gtk_combo_focus_idle         (GtkCombo         *combo);
71 static gint         gtk_combo_entry_focus_out    (GtkEntry         *entry,
72                                                   GdkEventFocus    *event,
73                                                   GtkCombo         *combo);
74 static void         gtk_combo_get_pos            (GtkCombo         *combo,
75                                                   gint             *x,
76                                                   gint             *y,
77                                                   gint             *height,
78                                                   gint             *width);
79 static void         gtk_combo_popup_list         (GtkCombo         *combo);
80 static void         gtk_combo_activate           (GtkWidget        *widget,
81                                                   GtkCombo         *combo);
82 static gboolean     gtk_combo_popup_button_press (GtkWidget        *button,
83                                                   GdkEventButton   *event,
84                                                   GtkCombo         *combo);
85 static gboolean     gtk_combo_popup_button_leave (GtkWidget        *button,
86                                                   GdkEventCrossing *event,
87                                                   GtkCombo         *combo);
88 static void         gtk_combo_update_entry       (GtkList          *list,
89                                                   GtkCombo         *combo);
90 static void         gtk_combo_update_list        (GtkEntry         *entry,
91                                                   GtkCombo         *combo);
92 static gint         gtk_combo_button_press       (GtkWidget        *widget,
93                                                   GdkEvent         *event,
94                                                   GtkCombo         *combo);
95 static void         gtk_combo_button_event_after (GtkWidget        *widget,
96                                                   GdkEvent         *event,
97                                                   GtkCombo         *combo);
98 static gint         gtk_combo_list_enter         (GtkWidget        *widget,
99                                                   GdkEventCrossing *event,
100                                                   GtkCombo         *combo);
101 static gint         gtk_combo_list_key_press     (GtkWidget        *widget,
102                                                   GdkEventKey      *event,
103                                                   GtkCombo         *combo);
104 static gint         gtk_combo_entry_key_press    (GtkEntry         *widget,
105                                                   GdkEventKey      *event,
106                                                   GtkCombo         *combo);
107 static gint         gtk_combo_window_key_press   (GtkWidget        *window,
108                                                   GdkEventKey      *event,
109                                                   GtkCombo         *combo);
110 static void         gtk_combo_item_destroy       (GtkObject        *object);
111 static void         gtk_combo_size_allocate      (GtkWidget        *widget,
112                                                   GtkAllocation   *allocation);
113 static void         gtk_combo_set_property       (GObject         *object,
114                                                   guint            prop_id,
115                                                   const GValue    *value,
116                                                   GParamSpec      *pspec);
117 static void         gtk_combo_get_property       (GObject         *object,
118                                                   guint            prop_id,
119                                                   GValue          *value,
120                                                   GParamSpec      *pspec);
121 static GtkHBoxClass *parent_class = NULL;
122
123 static void
124 gtk_combo_class_init (GtkComboClass * klass)
125 {
126   GObjectClass *gobject_class;
127   GtkObjectClass *oclass;
128   GtkWidgetClass *widget_class;
129
130   gobject_class = (GObjectClass *) klass;
131   parent_class = gtk_type_class (GTK_TYPE_HBOX);
132   oclass = (GtkObjectClass *) klass;
133   widget_class = (GtkWidgetClass *) klass;
134
135   gobject_class->set_property = gtk_combo_set_property; 
136   gobject_class->get_property = gtk_combo_get_property; 
137
138   g_object_class_install_property (gobject_class,
139                                    PROP_ENABLE_ARROW_KEYS,
140                                    g_param_spec_boolean ("enable_arrow_keys",
141                                                          _("Enable arrow keys"),
142                                                          _("Whether the arrow keys move through the list of items"),
143                                                          TRUE,
144                                                          G_PARAM_READABLE | G_PARAM_WRITABLE));
145   g_object_class_install_property (gobject_class,
146                                    PROP_ENABLE_ARROWS_ALWAYS,
147                                    g_param_spec_boolean ("enable_arrows_always",
148                                                          _("Always enable arrows"),
149                                                          _("Whether the arrow keys work, even if the entry contents are not in the list"),
150                                                          TRUE,
151                                                          G_PARAM_READABLE | G_PARAM_WRITABLE));
152   g_object_class_install_property (gobject_class,
153                                    PROP_CASE_SENSITIVE,
154                                    g_param_spec_boolean ("case_sensitive",
155                                                          _("Case sensitive"),
156                                                          _("Whether list item matching is case sensitive"),
157                                                          FALSE,
158                                                          G_PARAM_READABLE | G_PARAM_WRITABLE));
159
160   g_object_class_install_property (gobject_class,
161                                    PROP_ALLOW_EMPTY,
162                                    g_param_spec_boolean ("allow_empty",
163                                                          _("Allow empty"),
164                                                          _("Whether an empty value may be entered in this field"),
165                                                          TRUE,
166                                                          G_PARAM_READABLE | G_PARAM_WRITABLE));
167
168   g_object_class_install_property (gobject_class,
169                                    PROP_VALUE_IN_LIST,
170                                    g_param_spec_boolean ("value_in_list",
171                                                          _("Value in list"),
172                                                          _("Whether entered values must already be present in the list"),
173                                                          FALSE,
174                                                          G_PARAM_READABLE | G_PARAM_WRITABLE));
175   
176    
177   oclass->destroy = gtk_combo_destroy;
178   
179   widget_class->size_allocate = gtk_combo_size_allocate;
180   widget_class->realize = gtk_combo_realize;
181   widget_class->unrealize = gtk_combo_unrealize;
182 }
183
184 static void
185 gtk_combo_destroy (GtkObject *object)
186 {
187   GtkCombo *combo = GTK_COMBO (object);
188
189   if (combo->popwin)
190     {
191       gtk_widget_destroy (combo->popwin);
192       gtk_widget_unref (combo->popwin);
193       combo->popwin = NULL;
194     }
195
196   GTK_OBJECT_CLASS (parent_class)->destroy (object);
197 }
198
199 static int
200 gtk_combo_entry_key_press (GtkEntry * entry, GdkEventKey * event, GtkCombo * combo)
201 {
202   GList *li;
203
204   /* completion */
205   if ((event->keyval == GDK_Tab ||
206        event->keyval == GDK_KP_Tab) &&
207       (event->state & GDK_MOD1_MASK)) 
208     {
209       GtkEditable *editable = GTK_EDITABLE (entry);
210     GCompletion * cmpl;
211     gchar* prefix;
212     gchar* nprefix = NULL;
213     gint pos;
214
215     if ( !GTK_LIST (combo->list)->children )
216       return FALSE;
217     
218     gtk_signal_emit_stop_by_name (GTK_OBJECT (entry), "key_press_event");
219
220     cmpl = g_completion_new ((GCompletionFunc)gtk_combo_func);
221     g_completion_add_items (cmpl, GTK_LIST (combo->list)->children);
222
223     pos = gtk_editable_get_position (editable);
224     prefix = gtk_editable_get_chars (editable, 0, pos);
225
226     g_completion_complete (cmpl, prefix, &nprefix);
227
228     if (nprefix && strlen (nprefix) > strlen (prefix)) 
229       {
230         gtk_editable_insert_text (editable, nprefix + pos, 
231                                   strlen (nprefix) - strlen (prefix), &pos);
232         gtk_editable_set_position (editable, pos);
233     }
234
235     if (nprefix)
236       g_free (nprefix);
237     g_free (prefix);
238     g_completion_free (cmpl);
239
240     return TRUE;
241   }
242
243   if (!combo->use_arrows || !GTK_LIST (combo->list)->children)
244     return FALSE;
245
246   li = g_list_find (GTK_LIST (combo->list)->children, gtk_combo_find (combo));
247
248   if ((event->keyval == GDK_Up)
249       || (event->keyval == GDK_KP_Up)
250       || ((event->state & GDK_MOD1_MASK) && ((event->keyval == 'p') || (event->keyval == 'P'))))
251     {
252       if (li)
253         li = li->prev;
254       if (!li && combo->use_arrows_always)
255         {
256           li = g_list_last (GTK_LIST (combo->list)->children);
257         }
258       if (li)
259         {
260           gtk_list_select_child (GTK_LIST (combo->list), GTK_WIDGET (li->data));
261           gtk_signal_emit_stop_by_name (GTK_OBJECT (entry), "key_press_event");
262           return TRUE;
263         }
264     }
265   else if ((event->keyval == GDK_Down)
266            || (event->keyval == GDK_KP_Down)
267            || ((event->state & GDK_MOD1_MASK) && ((event->keyval == 'n') || (event->keyval == 'N'))))
268     {
269       if (li)
270         li = li->next;
271       if (!li && combo->use_arrows_always)
272         {
273           li = GTK_LIST (combo->list)->children;
274         }
275       if (li)
276         {
277           gtk_list_select_child (GTK_LIST (combo->list), GTK_WIDGET (li->data));
278           gtk_signal_emit_stop_by_name (GTK_OBJECT (entry), "key_press_event");
279           return TRUE;
280         }
281     }
282   return FALSE;
283 }
284
285 static int
286 gtk_combo_window_key_press (GtkWidget   *window,
287                             GdkEventKey *event,
288                             GtkCombo    *combo)
289 {
290   if (event->keyval == GDK_Return || event->keyval == GDK_KP_Enter)
291     {
292       if (GTK_WIDGET_VISIBLE (combo->popwin))
293         {
294           gtk_widget_hide (combo->popwin);
295           
296           if (GTK_WIDGET_HAS_GRAB (combo->popwin))
297             {
298               gtk_grab_remove (combo->popwin);
299               gdk_display_pointer_ungrab (gtk_widget_get_display (window),
300                                           event->time);
301             }
302         }
303
304       gtk_signal_emit_stop_by_name (GTK_OBJECT (window), "key_press_event");
305
306       return TRUE;
307     }
308
309   return FALSE;
310 }
311
312 static GtkListItem *
313 gtk_combo_find (GtkCombo * combo)
314 {
315   const gchar *text;
316   GtkListItem *found = NULL;
317   gchar *ltext;
318   gchar *compare_text;
319   GList *clist;
320
321   text = gtk_entry_get_text (GTK_ENTRY (combo->entry));
322   if (combo->case_sensitive)
323     compare_text = (gchar *)text;
324   else
325     compare_text = g_utf8_casefold (text, -1);
326   
327   for (clist = GTK_LIST (combo->list)->children;
328        !found && clist;
329        clist = clist->next)
330     {
331       ltext = gtk_combo_func (GTK_LIST_ITEM (clist->data));
332       if (!ltext)
333         continue;
334
335       if (!combo->case_sensitive)
336         ltext = g_utf8_casefold (ltext, -1);
337
338       if (strcmp (ltext, compare_text) == 0)
339         found = clist->data;
340
341       if (!combo->case_sensitive)
342         g_free (ltext);
343     }
344
345   if (!combo->case_sensitive)
346     g_free (compare_text);
347
348   return found;
349 }
350
351 static gchar *
352 gtk_combo_func (GtkListItem * li)
353 {
354   GtkWidget *label;
355   gchar *ltext = NULL;
356
357   ltext = (gchar *) gtk_object_get_data (GTK_OBJECT (li), gtk_combo_string_key);
358   if (!ltext)
359     {
360       label = GTK_BIN (li)->child;
361       if (!label || !GTK_IS_LABEL (label))
362         return NULL;
363       gtk_label_get (GTK_LABEL (label), &ltext);
364     }
365   return ltext;
366 }
367
368 static gint
369 gtk_combo_focus_idle (GtkCombo * combo)
370 {
371   if (combo)
372     {
373       GDK_THREADS_ENTER ();
374       gtk_widget_grab_focus (combo->entry);
375       GDK_THREADS_LEAVE ();
376     }
377   return FALSE;
378 }
379
380 static gint
381 gtk_combo_entry_focus_out (GtkEntry * entry, GdkEventFocus * event, GtkCombo * combo)
382 {
383
384   if (combo->value_in_list && !gtk_combo_find (combo))
385     {
386       GSource *focus_idle;
387       
388       /* gdk_beep(); *//* this can be annoying */
389       if (combo->ok_if_empty && !strcmp (gtk_entry_get_text (entry), ""))
390         return FALSE;
391 #ifdef TEST
392       printf ("INVALID ENTRY: `%s'\n", gtk_entry_get_text (entry));
393 #endif
394       gtk_grab_add (GTK_WIDGET (combo));
395       /* this is needed because if we call gtk_widget_grab_focus() 
396          it isn't guaranteed it's the *last* call before the main-loop,
397          so the focus can be lost anyway...
398          the signal_emit_stop doesn't seem to work either...
399        */
400       focus_idle = g_idle_source_new ();
401       g_source_set_closure (focus_idle,
402                             g_cclosure_new_object (G_CALLBACK (gtk_combo_focus_idle),
403                                                    G_OBJECT (combo)));
404       g_source_attach (focus_idle, NULL);
405       
406       /*gtk_signal_emit_stop_by_name (GTK_OBJECT (entry), "focus_out_event"); */
407       return TRUE;
408     }
409   return FALSE;
410 }
411
412 static void
413 gtk_combo_get_pos (GtkCombo * combo, gint * x, gint * y, gint * height, gint * width)
414 {
415   GtkBin *popwin;
416   GtkWidget *widget;
417   GtkScrolledWindow *popup;
418   
419   gint real_height;
420   GtkRequisition list_requisition;
421   gboolean show_hscroll = FALSE;
422   gboolean show_vscroll = FALSE;
423   gint avail_height;
424   gint min_height;
425   gint alloc_width;
426   gint work_height;
427   gint old_height;
428   gint old_width;
429   gint scrollbar_spacing;
430   
431   widget = GTK_WIDGET (combo);
432   popup  = GTK_SCROLLED_WINDOW (combo->popup);
433   popwin = GTK_BIN (combo->popwin);
434
435   scrollbar_spacing = _gtk_scrolled_window_get_scrollbar_spacing (popup);
436
437   gdk_window_get_origin (combo->entry->window, x, y);
438   real_height = MIN (combo->entry->requisition.height, 
439                      combo->entry->allocation.height);
440   *y += real_height;
441   avail_height = gdk_screen_get_height (gtk_widget_get_screen (widget)) - *y;
442   
443   gtk_widget_size_request (combo->list, &list_requisition);
444   min_height = MIN (list_requisition.height, 
445                     popup->vscrollbar->requisition.height);
446   if (!GTK_LIST (combo->list)->children)
447     list_requisition.height += EMPTY_LIST_HEIGHT;
448   
449   alloc_width = (widget->allocation.width -
450                  2 * popwin->child->style->xthickness -
451                  2 * GTK_CONTAINER (popwin->child)->border_width -
452                  2 * GTK_CONTAINER (combo->popup)->border_width -
453                  2 * GTK_CONTAINER (GTK_BIN (popup)->child)->border_width - 
454                  2 * GTK_BIN (popup)->child->style->xthickness);
455   
456   work_height = (2 * popwin->child->style->ythickness +
457                  2 * GTK_CONTAINER (popwin->child)->border_width +
458                  2 * GTK_CONTAINER (combo->popup)->border_width +
459                  2 * GTK_CONTAINER (GTK_BIN (popup)->child)->border_width +
460                  2 * GTK_BIN (popup)->child->style->ythickness);
461   
462   do 
463     {
464       old_width = alloc_width;
465       old_height = work_height;
466       
467       if (!show_hscroll &&
468           alloc_width < list_requisition.width)
469         {
470           GtkRequisition requisition;
471           
472           gtk_widget_size_request (popup->hscrollbar, &requisition);
473           work_height += (requisition.height + scrollbar_spacing);
474           
475           show_hscroll = TRUE;
476         }
477       if (!show_vscroll && 
478           work_height + list_requisition.height > avail_height)
479         {
480           GtkRequisition requisition;
481           
482           if (work_height + min_height > avail_height && 
483               *y - real_height > avail_height)
484             {
485               *y -= (work_height + list_requisition.height + real_height);
486               break;
487             }
488           gtk_widget_size_request (popup->hscrollbar, &requisition);
489           alloc_width -= (requisition.width + scrollbar_spacing);
490           show_vscroll = TRUE;
491         }
492     } while (old_width != alloc_width || old_height != work_height);
493   
494   *width = widget->allocation.width;
495   if (show_vscroll)
496     *height = avail_height;
497   else
498     *height = work_height + list_requisition.height;
499   
500   if (*x < 0)
501     *x = 0;
502 }
503
504 static void
505 gtk_combo_popup_list (GtkCombo * combo)
506 {
507   gint height, width, x, y;
508   gint old_width, old_height;
509
510   old_width = combo->popwin->allocation.width;
511   old_height  = combo->popwin->allocation.height;
512
513   gtk_combo_get_pos (combo, &x, &y, &height, &width);
514
515   /* workaround for gtk_scrolled_window_size_allocate bug */
516   if (old_width != width || old_height != height)
517     {
518       gtk_widget_hide (GTK_SCROLLED_WINDOW (combo->popup)->hscrollbar);
519       gtk_widget_hide (GTK_SCROLLED_WINDOW (combo->popup)->vscrollbar);
520     }
521
522   gtk_window_move (GTK_WINDOW (combo->popwin), x, y);
523   gtk_widget_set_usize (combo->popwin, width, height);
524   gtk_widget_show (combo->popwin);
525
526   gtk_widget_grab_focus (combo->popwin);
527 }
528
529 static void
530 gtk_combo_popdown_list (GtkCombo *combo)
531 {
532   combo->current_button = 0;
533       
534   if (GTK_BUTTON (combo->button)->in_button)
535     {
536       GTK_BUTTON (combo->button)->in_button = FALSE;
537       gtk_button_released (GTK_BUTTON (combo->button));
538     }
539
540   if (GTK_WIDGET_HAS_GRAB (combo->popwin))
541     {
542       gtk_grab_remove (combo->popwin);
543       gdk_display_pointer_ungrab (gtk_widget_get_display (GTK_WIDGET (combo)),
544                                   GDK_CURRENT_TIME);
545     }
546   
547   gtk_widget_hide (combo->popwin);
548 }
549
550 static void        
551 gtk_combo_activate (GtkWidget        *widget,
552                     GtkCombo         *combo)
553 {
554   gtk_combo_popup_list (combo);
555
556   if (!GTK_WIDGET_HAS_FOCUS (combo->entry))
557     gtk_widget_grab_focus (combo->entry);
558
559   gtk_grab_add (combo->popwin);
560   gdk_pointer_grab (combo->popwin->window, TRUE,
561                     GDK_BUTTON_PRESS_MASK | 
562                     GDK_BUTTON_RELEASE_MASK |
563                     GDK_POINTER_MOTION_MASK, 
564                     NULL, NULL, GDK_CURRENT_TIME);
565 }
566
567 static gboolean
568 gtk_combo_popup_button_press (GtkWidget        *button,
569                               GdkEventButton   *event,
570                               GtkCombo         *combo)
571 {
572   if (!GTK_WIDGET_HAS_FOCUS (combo->entry))
573     gtk_widget_grab_focus (combo->entry);
574
575   if (event->button != 1)
576     return FALSE;
577
578   combo->current_button = event->button;
579
580   gtk_combo_popup_list (combo);
581   gtk_button_pressed (GTK_BUTTON (button));
582
583   gtk_grab_add (combo->popwin);
584   gdk_pointer_grab (combo->popwin->window, TRUE,
585                     GDK_BUTTON_PRESS_MASK | 
586                     GDK_BUTTON_RELEASE_MASK |
587                     GDK_POINTER_MOTION_MASK, 
588                     NULL, NULL, GDK_CURRENT_TIME);
589
590   GTK_LIST (combo->list)->drag_selection = TRUE;
591   gtk_grab_add (combo->list);
592
593   return TRUE;
594 }
595
596 static gboolean
597 gtk_combo_popup_button_leave (GtkWidget        *button,
598                               GdkEventCrossing *event,
599                               GtkCombo         *combo)
600 {
601   /* The idea here is that we want to keep the button down if the
602    * popup is popped up.
603    */
604   return combo->current_button != 0;
605 }
606
607 static void
608 gtk_combo_update_entry (GtkList * list, GtkCombo * combo)
609 {
610   char *text;
611
612   gtk_grab_remove (GTK_WIDGET (combo));
613   gtk_signal_handler_block (GTK_OBJECT (list), combo->list_change_id);
614   if (list->selection)
615     {
616       text = gtk_combo_func (GTK_LIST_ITEM (list->selection->data));
617       if (!text)
618         text = "";
619       gtk_entry_set_text (GTK_ENTRY (combo->entry), text);
620     }
621   gtk_signal_handler_unblock (GTK_OBJECT (list), combo->list_change_id);
622 }
623
624 static void
625 gtk_combo_update_list (GtkEntry * entry, GtkCombo * combo)
626 {
627   GtkList *list = GTK_LIST (combo->list);
628   GList *slist = list->selection;
629   GtkListItem *li;
630
631   gtk_grab_remove (GTK_WIDGET (combo));
632
633   gtk_signal_handler_block (GTK_OBJECT (entry), combo->entry_change_id);
634   if (slist && slist->data)
635     gtk_list_unselect_child (list, GTK_WIDGET (slist->data));
636   li = gtk_combo_find (combo);
637   if (li)
638     gtk_list_select_child (list, GTK_WIDGET (li));
639   gtk_signal_handler_unblock (GTK_OBJECT (entry), combo->entry_change_id);
640 }
641
642 static gint
643 gtk_combo_button_press (GtkWidget * widget, GdkEvent * event, GtkCombo * combo)
644 {
645   GtkWidget *child;
646
647   child = gtk_get_event_widget (event);
648
649   /* We don't ask for button press events on the grab widget, so
650    *  if an event is reported directly to the grab widget, it must
651    *  be on a window outside the application (and thus we remove
652    *  the popup window). Otherwise, we check if the widget is a child
653    *  of the grab widget, and only remove the popup window if it
654    *  is not.
655    */
656   if (child != widget)
657     {
658       while (child)
659         {
660           if (child == widget)
661             return FALSE;
662           child = child->parent;
663         }
664     }
665
666   gtk_combo_popdown_list (combo);
667
668   return TRUE;
669 }
670
671 static void
672 gtk_combo_button_event_after (GtkWidget *widget,
673                               GdkEvent  *event,
674                               GtkCombo  *combo)
675 {
676   GtkWidget *child;
677
678   if (event->type != GDK_BUTTON_RELEASE)
679     return;
680   
681   if ((combo->current_button != 0) && (event->button.button == 1))
682     {
683       /* This was the initial button press */
684
685       combo->current_button = 0;
686
687       /* Check to see if we released inside the button */
688       child = gtk_get_event_widget ((GdkEvent*) event);
689
690       while (child && child != (combo->button))
691         child = child->parent;
692
693       if (child == combo->button)
694         {
695           gtk_grab_add (combo->popwin);
696           gdk_pointer_grab (combo->popwin->window, TRUE,
697                             GDK_BUTTON_PRESS_MASK | 
698                             GDK_BUTTON_RELEASE_MASK |
699                             GDK_POINTER_MOTION_MASK, 
700                             NULL, NULL, GDK_CURRENT_TIME);
701           return;
702         }
703     }
704
705   gtk_combo_popdown_list (combo);
706 }
707
708 static gint         
709 gtk_combo_list_enter (GtkWidget        *widget,
710                       GdkEventCrossing *event,
711                       GtkCombo         *combo)
712 {
713   GtkWidget *event_widget;
714
715   event_widget = gtk_get_event_widget ((GdkEvent*) event);
716   
717   if ((event_widget == combo->list) &&
718       (combo->current_button != 0) && 
719       (!GTK_WIDGET_HAS_GRAB (combo->list)))
720     {
721       GdkEvent *tmp_event = gdk_event_new (GDK_BUTTON_PRESS);
722       gint x, y;
723       GdkModifierType mask;
724
725       gtk_grab_remove (combo->popwin);
726
727       /* Transfer the grab over to the list by synthesizing
728        * a button press event
729        */
730       gdk_window_get_pointer (combo->list->window, &x, &y, &mask);
731
732       tmp_event->button.window = g_object_ref (combo->list->window);
733       tmp_event->button.send_event = TRUE;
734       tmp_event->button.time = GDK_CURRENT_TIME; /* bad */
735       tmp_event->button.x = x;
736       tmp_event->button.y = y;
737       /* We leave all the XInput fields unfilled here, in the expectation
738        * that GtkList doesn't care.
739        */
740       tmp_event->button.button = combo->current_button;
741       tmp_event->button.state = mask;
742
743       gtk_widget_event (combo->list, tmp_event);
744       gdk_event_free (tmp_event);
745     }
746
747   return FALSE;
748 }
749
750 static int
751 gtk_combo_list_key_press (GtkWidget * widget, GdkEventKey * event, GtkCombo * combo)
752 {
753   if (event->keyval == GDK_Escape)
754     {
755       if (GTK_WIDGET_HAS_GRAB (combo->list))
756         gtk_list_end_drag_selection (GTK_LIST (combo->list));
757
758       gtk_combo_popdown_list (combo);
759       
760       return TRUE;
761     }
762   return FALSE;
763 }
764
765 static void
766 combo_event_box_realize (GtkWidget *widget)
767 {
768   GdkCursor *cursor = gdk_cursor_new_for_display (gtk_widget_get_display (widget),
769                                                   GDK_TOP_LEFT_ARROW);
770   gdk_window_set_cursor (widget->window, cursor);
771   gdk_cursor_destroy (cursor);
772 }
773
774 static void
775 gtk_combo_init (GtkCombo * combo)
776 {
777   GtkWidget *arrow;
778   GtkWidget *frame;
779   GtkWidget *event_box;
780
781   combo->case_sensitive = FALSE;
782   combo->value_in_list = FALSE;
783   combo->ok_if_empty = TRUE;
784   combo->use_arrows = TRUE;
785   combo->use_arrows_always = FALSE;
786   combo->entry = gtk_entry_new ();
787   combo->button = gtk_button_new ();
788   combo->current_button = 0;
789   arrow = gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_OUT);
790   gtk_widget_show (arrow);
791   gtk_container_add (GTK_CONTAINER (combo->button), arrow);
792   gtk_box_pack_start (GTK_BOX (combo), combo->entry, TRUE, TRUE, 0);
793   gtk_box_pack_end (GTK_BOX (combo), combo->button, FALSE, FALSE, 0);
794   GTK_WIDGET_UNSET_FLAGS (combo->button, GTK_CAN_FOCUS);
795   gtk_widget_show (combo->entry);
796   gtk_widget_show (combo->button);
797   combo->entry_change_id = gtk_signal_connect (GTK_OBJECT (combo->entry), "changed",
798                               (GtkSignalFunc) gtk_combo_update_list, combo);
799   gtk_signal_connect (GTK_OBJECT (combo->entry), "key_press_event",
800                       (GtkSignalFunc) gtk_combo_entry_key_press, combo);
801   gtk_signal_connect_after (GTK_OBJECT (combo->entry), "focus_out_event",
802                             (GtkSignalFunc) gtk_combo_entry_focus_out, combo);
803   combo->activate_id = gtk_signal_connect (GTK_OBJECT (combo->entry), "activate",
804                       (GtkSignalFunc) gtk_combo_activate, combo);
805   gtk_signal_connect (GTK_OBJECT (combo->button), "button_press_event",
806                       (GtkSignalFunc) gtk_combo_popup_button_press, combo);
807   gtk_signal_connect (GTK_OBJECT (combo->button), "leave_notify_event",
808                       (GtkSignalFunc) gtk_combo_popup_button_leave, combo);
809
810   combo->popwin = gtk_window_new (GTK_WINDOW_POPUP);
811   gtk_widget_ref (combo->popwin);
812   gtk_window_set_resizable (GTK_WINDOW (combo->popwin), FALSE);
813
814   gtk_signal_connect (GTK_OBJECT (combo->popwin), "key_press_event",
815                       GTK_SIGNAL_FUNC (gtk_combo_window_key_press), combo);
816   
817   gtk_widget_set_events (combo->popwin, GDK_KEY_PRESS_MASK);
818
819   event_box = gtk_event_box_new ();
820   gtk_container_add (GTK_CONTAINER (combo->popwin), event_box);
821   g_signal_connect (event_box, "realize",
822                     G_CALLBACK (combo_event_box_realize), NULL);
823   gtk_widget_show (event_box);
824
825
826   frame = gtk_frame_new (NULL);
827   gtk_container_add (GTK_CONTAINER (event_box), frame);
828   gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT);
829   gtk_widget_show (frame);
830
831   combo->popup = gtk_scrolled_window_new (NULL, NULL);
832   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (combo->popup),
833                                   GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
834   GTK_WIDGET_UNSET_FLAGS (GTK_SCROLLED_WINDOW (combo->popup)->hscrollbar, GTK_CAN_FOCUS);
835   GTK_WIDGET_UNSET_FLAGS (GTK_SCROLLED_WINDOW (combo->popup)->vscrollbar, GTK_CAN_FOCUS);
836   gtk_container_add (GTK_CONTAINER (frame), combo->popup);
837   gtk_widget_show (combo->popup);
838
839   combo->list = gtk_list_new ();
840   /* We'll use enter notify events to figure out when to transfer
841    * the grab to the list
842    */
843   gtk_widget_set_events (combo->list, GDK_ENTER_NOTIFY_MASK);
844
845   gtk_list_set_selection_mode (GTK_LIST(combo->list), GTK_SELECTION_BROWSE);
846   gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (combo->popup), combo->list);
847   gtk_container_set_focus_vadjustment (GTK_CONTAINER (combo->list),
848                                        gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (combo->popup)));
849   gtk_container_set_focus_hadjustment (GTK_CONTAINER (combo->list),
850                                        gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (combo->popup)));
851   gtk_widget_show (combo->list);
852
853   combo->list_change_id = gtk_signal_connect (GTK_OBJECT (combo->list), "selection_changed",
854                              (GtkSignalFunc) gtk_combo_update_entry, combo);
855   gtk_signal_connect (GTK_OBJECT (combo->popwin), "key_press_event",
856                       (GtkSignalFunc) gtk_combo_list_key_press, combo);
857   gtk_signal_connect (GTK_OBJECT (combo->popwin), "button_press_event",
858                       GTK_SIGNAL_FUNC (gtk_combo_button_press), combo);
859
860   gtk_signal_connect (GTK_OBJECT (combo->list), "event_after",
861                       (GtkSignalFunc) gtk_combo_button_event_after, combo);
862   /* We connect here on the button, because we'll have a grab on it
863    * when the event occurs. But we are actually interested in enters
864    * for the combo->list.
865    */
866   gtk_signal_connect (GTK_OBJECT (combo->button), "enter_notify_event",
867                       GTK_SIGNAL_FUNC (gtk_combo_list_enter), combo);
868 }
869
870 static void
871 gtk_combo_realize (GtkWidget *widget)
872 {
873   GtkCombo *combo = GTK_COMBO (widget);
874
875   gtk_window_set_screen (GTK_WINDOW (combo->popwin), 
876                          gtk_widget_get_screen (widget));
877   
878   GTK_WIDGET_CLASS( parent_class )->realize (widget);  
879 }
880
881 static void        
882 gtk_combo_unrealize (GtkWidget *widget)
883 {
884   GtkCombo *combo = GTK_COMBO (widget);
885
886   gtk_combo_popdown_list (combo);
887   gtk_widget_unrealize (combo->popwin);
888   
889   GTK_WIDGET_CLASS (parent_class)->unrealize (widget);
890 }
891
892 GtkType
893 gtk_combo_get_type (void)
894 {
895   static GtkType combo_type = 0;
896
897   if (!combo_type)
898     {
899       static const GtkTypeInfo combo_info =
900       {
901         "GtkCombo",
902         sizeof (GtkCombo),
903         sizeof (GtkComboClass),
904         (GtkClassInitFunc) gtk_combo_class_init,
905         (GtkObjectInitFunc) gtk_combo_init,
906         /* reserved_1 */ NULL,
907         /* reserved_2 */ NULL,
908         (GtkClassInitFunc) NULL,
909       };
910       combo_type = gtk_type_unique (GTK_TYPE_HBOX, &combo_info);
911     }
912   return combo_type;
913 }
914
915 GtkWidget*
916 gtk_combo_new (void)
917 {
918   return GTK_WIDGET (gtk_type_new (GTK_TYPE_COMBO));
919 }
920
921 void
922 gtk_combo_set_value_in_list (GtkCombo * combo, gboolean val, gboolean ok_if_empty)
923 {
924   g_return_if_fail (GTK_IS_COMBO (combo));
925   val = val != FALSE;
926   ok_if_empty = ok_if_empty != FALSE;
927
928   g_object_freeze_notify (G_OBJECT (combo));
929   if (combo->value_in_list != val)
930     {
931        combo->value_in_list = val;
932   g_object_notify (G_OBJECT (combo), "value_in_list");
933     }
934   if (combo->ok_if_empty != ok_if_empty)
935     {
936        combo->ok_if_empty = ok_if_empty;
937   g_object_notify (G_OBJECT (combo), "allow_empty");
938     }
939   g_object_thaw_notify (G_OBJECT (combo));
940 }
941
942 void
943 gtk_combo_set_case_sensitive (GtkCombo * combo, gboolean val)
944 {
945   g_return_if_fail (GTK_IS_COMBO (combo));
946   val = val != FALSE;
947
948   if (combo->case_sensitive != val) 
949     {
950   combo->case_sensitive = val;
951   g_object_notify (G_OBJECT (combo), "case_sensitive");
952     }
953 }
954
955 void
956 gtk_combo_set_use_arrows (GtkCombo * combo, gboolean val)
957 {
958   g_return_if_fail (GTK_IS_COMBO (combo));
959   val = val != FALSE;
960
961   if (combo->use_arrows != val) 
962     {
963   combo->use_arrows = val;
964   g_object_notify (G_OBJECT (combo), "enable_arrow_keys");
965     }
966 }
967
968 void
969 gtk_combo_set_use_arrows_always (GtkCombo * combo, gboolean val)
970 {
971   g_return_if_fail (GTK_IS_COMBO (combo));
972   val = val != FALSE;
973
974   if (combo->use_arrows_always != val) 
975     {
976        g_object_freeze_notify (G_OBJECT (combo));
977   combo->use_arrows_always = val;
978        g_object_notify (G_OBJECT (combo), "enable_arrows_always");
979
980        if (combo->use_arrows != TRUE) 
981          {
982   combo->use_arrows = TRUE;
983   g_object_notify (G_OBJECT (combo), "enable_arrow_keys");
984          }
985   g_object_thaw_notify (G_OBJECT (combo));
986     }
987 }
988
989 void
990 gtk_combo_set_popdown_strings (GtkCombo * combo, GList * strings)
991 {
992   GList *list;
993   GtkWidget *li;
994
995   g_return_if_fail (GTK_IS_COMBO (combo));
996   g_return_if_fail (strings != NULL);
997
998   gtk_combo_popdown_list (combo);
999
1000   gtk_list_clear_items (GTK_LIST (combo->list), 0, -1);
1001   list = strings;
1002   while (list)
1003     {
1004       li = gtk_list_item_new_with_label ((gchar *) list->data);
1005       gtk_widget_show (li);
1006       gtk_container_add (GTK_CONTAINER (combo->list), li);
1007       list = list->next;
1008     }
1009 }
1010
1011 static void
1012 gtk_combo_item_destroy (GtkObject * object)
1013 {
1014   gchar *key;
1015
1016   key = gtk_object_get_data (object, gtk_combo_string_key);
1017   if (key)
1018     {
1019       gtk_object_remove_data (object, gtk_combo_string_key);
1020       g_free (key);
1021     }
1022 }
1023
1024 void
1025 gtk_combo_set_item_string (GtkCombo * combo, GtkItem * item, const gchar * item_value)
1026 {
1027   gchar *val;
1028   gint connected = 0;
1029
1030   g_return_if_fail (GTK_IS_COMBO (combo));
1031   g_return_if_fail (item != NULL);
1032
1033   val = gtk_object_get_data (GTK_OBJECT (item), gtk_combo_string_key);
1034   if (val) 
1035     {
1036       g_free (val);
1037       connected = 1;
1038     }
1039   if (item_value)
1040     {
1041       val = g_strdup (item_value);
1042       gtk_object_set_data (GTK_OBJECT (item), gtk_combo_string_key, val);
1043       if (!connected)
1044         gtk_signal_connect (GTK_OBJECT (item), "destroy",
1045                           (GtkSignalFunc) gtk_combo_item_destroy, val);
1046     }
1047   else 
1048     {
1049       gtk_object_set_data (GTK_OBJECT (item), gtk_combo_string_key, NULL);
1050       if (connected)
1051         gtk_signal_disconnect_by_data(GTK_OBJECT (item), val);
1052     }
1053 }
1054
1055 static void
1056 gtk_combo_size_allocate (GtkWidget     *widget,
1057                          GtkAllocation *allocation)
1058 {
1059   GtkCombo *combo;
1060
1061   g_return_if_fail (GTK_IS_COMBO (widget));
1062   g_return_if_fail (allocation != NULL);
1063
1064   GTK_WIDGET_CLASS (parent_class)->size_allocate (widget, allocation);
1065   
1066   combo = GTK_COMBO (widget);
1067
1068   if (combo->entry->allocation.height > combo->entry->requisition.height)
1069     {
1070       GtkAllocation button_allocation;
1071
1072       button_allocation = combo->button->allocation;
1073       button_allocation.height = combo->entry->requisition.height;
1074       button_allocation.y = combo->entry->allocation.y + 
1075         (combo->entry->allocation.height - combo->entry->requisition.height) 
1076         / 2;
1077       gtk_widget_size_allocate (combo->button, &button_allocation);
1078     }
1079 }
1080
1081 void
1082 gtk_combo_disable_activate (GtkCombo* combo)
1083 {
1084   g_return_if_fail (GTK_IS_COMBO (combo));
1085
1086   if ( combo->activate_id ) {
1087     gtk_signal_disconnect (GTK_OBJECT(combo->entry), combo->activate_id);
1088     combo->activate_id = 0;
1089   }
1090 }
1091
1092 static void
1093 gtk_combo_set_property (GObject         *object,
1094                         guint            prop_id,
1095                         const GValue    *value,
1096                         GParamSpec      *pspec)
1097 {
1098   GtkCombo *combo = GTK_COMBO (object);
1099   
1100   switch (prop_id)
1101     {
1102     case PROP_ENABLE_ARROW_KEYS:
1103       gtk_combo_set_use_arrows (combo, g_value_get_boolean (value));
1104       break;
1105     case PROP_ENABLE_ARROWS_ALWAYS:
1106       gtk_combo_set_use_arrows_always (combo, g_value_get_boolean (value));
1107       break;
1108     case PROP_CASE_SENSITIVE:
1109       gtk_combo_set_case_sensitive (combo, g_value_get_boolean (value));
1110       break;
1111     case PROP_ALLOW_EMPTY:
1112       combo->ok_if_empty = g_value_get_boolean (value);
1113       break;
1114     case PROP_VALUE_IN_LIST:
1115       combo->value_in_list = g_value_get_boolean (value);
1116       break;
1117     default:
1118       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1119       break;
1120     }
1121   
1122 }
1123
1124 static void
1125 gtk_combo_get_property (GObject         *object,
1126                         guint            prop_id,
1127                         GValue          *value,
1128                         GParamSpec      *pspec)
1129 {
1130   GtkCombo *combo = GTK_COMBO (object);
1131   
1132   switch (prop_id)
1133     {
1134     case PROP_ENABLE_ARROW_KEYS:
1135       g_value_set_boolean (value, combo->use_arrows);
1136       break;
1137     case PROP_ENABLE_ARROWS_ALWAYS:
1138       g_value_set_boolean (value, combo->use_arrows_always);
1139       break;
1140     case PROP_CASE_SENSITIVE:
1141       g_value_set_boolean (value, combo->case_sensitive);
1142       break;
1143     case PROP_ALLOW_EMPTY:
1144       g_value_set_boolean (value, combo->ok_if_empty);
1145       break;
1146     case PROP_VALUE_IN_LIST:
1147       g_value_set_boolean (value, combo->value_in_list);
1148       break;
1149     default:
1150       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1151       break;
1152     }
1153    
1154 }