]> Pileus Git - ~andy/gtk/blob - gtk/gtkcombo.c
Move main thread lock back to GDK - we need it there for locking when
[~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 Library 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  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library 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 #include <string.h>
21
22 #include "gtkarrow.h"
23 #include "gtklabel.h"
24 #include "gtklist.h"
25 #include "gtkentry.h"
26 #include "gtkeventbox.h"
27 #include "gtkbutton.h"
28 #include "gtklistitem.h"
29 #include "gtkscrolledwindow.h"
30 #include "gtkmain.h"
31 #include "gtksignal.h"
32 #include "gtkwindow.h"
33 #include "gdk/gdkkeysyms.h"
34 #include "gtkcombo.h"
35 #include "gtkframe.h"
36
37 const gchar *gtk_combo_string_key = "gtk-combo-string-value";
38
39 #define COMBO_LIST_MAX_HEIGHT   (400)
40 #define EMPTY_LIST_HEIGHT       (15)
41
42 static void         gtk_combo_class_init      (GtkComboClass *klass);
43 static void         gtk_combo_init            (GtkCombo      *combo);
44 static void         gtk_combo_destroy         (GtkObject     *combo);
45 static GtkListItem *gtk_combo_find            (GtkCombo      *combo);
46 static gchar *      gtk_combo_func            (GtkListItem  *li);
47 static gint         gtk_combo_focus_idle      (GtkCombo      *combo);
48 static gint         gtk_combo_entry_focus_out (GtkEntry      *entry, 
49                                                GdkEventFocus *event, 
50                                                GtkCombo      *combo);
51 static void         gtk_combo_get_pos         (GtkCombo      *combo, 
52                                                gint          *x, 
53                                                gint          *y, 
54                                                gint          *height, 
55                                                gint          *width);
56 static void         gtk_combo_popup_list      (GtkCombo      *combo);
57 static void         gtk_combo_activate        (GtkWidget        *widget,
58                                                GtkCombo         *combo);
59 static void         gtk_combo_popup_button_press (GtkWidget        *button,
60                                                   GdkEventButton   *event,
61                                                   GtkCombo         *combo);
62 static void         gtk_combo_popup_button_leave (GtkWidget        *button,
63                                                   GdkEventCrossing *event,
64                                                   GtkCombo         *combo);
65 static void         gtk_combo_update_entry    (GtkList       *list, 
66                                                GtkCombo      *combo);
67 static void         gtk_combo_update_list     (GtkEntry      *entry, 
68                                                GtkCombo      *combo);
69 static gint         gtk_combo_button_press    (GtkWidget     *widget,
70                                                GdkEvent      *event,
71                                                GtkCombo      *combo);
72 static gint         gtk_combo_button_release  (GtkWidget     *widget,
73                                                GdkEvent      *event,
74                                                GtkCombo      *combo);
75 static gint         gtk_combo_list_enter      (GtkWidget        *widget,
76                                                GdkEventCrossing *event,
77                                                GtkCombo         *combo);
78 static gint         gtk_combo_list_key_press  (GtkWidget     *widget, 
79                                                GdkEventKey   *event, 
80                                                GtkCombo      *combo);
81 static gint         gtk_combo_entry_key_press (GtkEntry      *widget, 
82                                                GdkEventKey   *event, 
83                                                GtkCombo      *combo);
84 static void         gtk_combo_item_destroy    (GtkObject     *object);
85 static void         gtk_combo_size_allocate   (GtkWidget     *widget,
86                                                GtkAllocation *allocation);
87
88 static GtkHBoxClass *parent_class = NULL;
89
90 static void
91 gtk_combo_class_init (GtkComboClass * klass)
92 {
93   GtkObjectClass *oclass;
94   GtkWidgetClass *widget_class;
95
96   parent_class = gtk_type_class (gtk_hbox_get_type ());
97   oclass = (GtkObjectClass *) klass;
98   widget_class = (GtkWidgetClass *) klass;
99
100   oclass->destroy = gtk_combo_destroy;
101   
102   widget_class->size_allocate = gtk_combo_size_allocate;
103 }
104
105 static void
106 gtk_combo_destroy (GtkObject * combo)
107 {
108   gtk_widget_destroy (GTK_COMBO (combo)->popwin);
109   gtk_widget_unref (GTK_COMBO (combo)->popwin);
110
111   if (GTK_OBJECT_CLASS (parent_class)->destroy)
112     (*GTK_OBJECT_CLASS (parent_class)->destroy) (combo);
113 }
114
115 static int
116 gtk_combo_entry_key_press (GtkEntry * entry, GdkEventKey * event, GtkCombo * combo)
117 {
118   GList *li;
119
120   /* completion */
121   if ((event->keyval == GDK_Tab) && (event->state & GDK_MOD1_MASK)) 
122     {
123     GCompletion * cmpl;
124     gchar* prefix;
125     gchar* nprefix = NULL;
126     gint pos;
127
128     if ( !GTK_LIST(combo->list)->children )
129       return FALSE;
130     
131     gtk_signal_emit_stop_by_name (GTK_OBJECT (entry), "key_press_event");
132     cmpl = g_completion_new((GCompletionFunc)gtk_combo_func);
133     g_completion_add_items(cmpl, GTK_LIST(combo->list)->children);
134     pos = GTK_EDITABLE(entry)->current_pos;
135     prefix = gtk_editable_get_chars(GTK_EDITABLE(entry), 0, pos);
136     g_completion_complete(cmpl, prefix, &nprefix);
137     if (nprefix && strlen(nprefix) > strlen(prefix)) 
138       {
139         gtk_editable_insert_text(GTK_EDITABLE(entry), nprefix+pos, 
140                                  strlen(nprefix)-strlen(prefix), &pos);
141         GTK_EDITABLE(entry)->current_pos = pos;
142     }
143     g_free(prefix);
144     g_completion_free(cmpl);
145     return TRUE;
146   }
147   if (!combo->use_arrows || !GTK_LIST (combo->list)->children)
148     return FALSE;
149   li = g_list_find (GTK_LIST (combo->list)->children, gtk_combo_find (combo));
150
151   if ((event->keyval == GDK_Up)
152       || (event->keyval == GDK_KP_Up)
153       || ((event->state & GDK_MOD1_MASK) && ((event->keyval == 'p') || (event->keyval == 'P'))))
154     {
155       if (li)
156         li = li->prev;
157       if (!li && combo->use_arrows_always)
158         {
159           li = g_list_last (GTK_LIST (combo->list)->children);
160         }
161       if (li)
162         {
163           gtk_list_select_child (GTK_LIST (combo->list), GTK_WIDGET (li->data));
164           gtk_signal_emit_stop_by_name (GTK_OBJECT (entry), "key_press_event");
165           return TRUE;
166         }
167     }
168   else if ((event->keyval == GDK_Down)
169            || (event->keyval == GDK_KP_Down)
170            || ((event->state & GDK_MOD1_MASK) && ((event->keyval == 'n') || (event->keyval == 'N'))))
171     {
172       if (li)
173         li = li->next;
174       if (!li && combo->use_arrows_always)
175         {
176           li = GTK_LIST (combo->list)->children;
177         }
178       if (li)
179         {
180           gtk_list_select_child (GTK_LIST (combo->list), GTK_WIDGET (li->data));
181           gtk_signal_emit_stop_by_name (GTK_OBJECT (entry), "key_press_event");
182           return TRUE;
183         }
184     }
185   return FALSE;
186 }
187
188 static GtkListItem *
189 gtk_combo_find (GtkCombo * combo)
190 {
191   gchar *text;
192   gchar *ltext;
193   GList *clist;
194   int (*string_compare) (const char *, const char *);
195
196   if (combo->case_sensitive)
197     string_compare = strcmp;
198   else
199     string_compare = g_strcasecmp;
200
201   text = gtk_entry_get_text (GTK_ENTRY (combo->entry));
202   clist = GTK_LIST (combo->list)->children;
203
204   while (clist && clist->data)
205     {
206       ltext = gtk_combo_func (GTK_LIST_ITEM (clist->data));
207       if (!ltext)
208         continue;
209       if (!(*string_compare) (ltext, text))
210         return (GtkListItem *) clist->data;
211       clist = clist->next;
212     }
213
214   return NULL;
215 }
216
217 static gchar *
218 gtk_combo_func (GtkListItem * li)
219 {
220   GtkWidget *label;
221   gchar *ltext = NULL;
222
223   ltext = (gchar *) gtk_object_get_data (GTK_OBJECT (li), gtk_combo_string_key);
224   if (!ltext)
225     {
226       label = GTK_BIN (li)->child;
227       if (!label || !GTK_IS_LABEL (label))
228         return NULL;
229       gtk_label_get (GTK_LABEL (label), &ltext);
230     }
231   return ltext;
232 }
233
234 static gint
235 gtk_combo_focus_idle (GtkCombo * combo)
236 {
237   if (combo)
238     {
239       GDK_THREADS_ENTER ();
240       gtk_widget_grab_focus (combo->entry);
241       GDK_THREADS_LEAVE ();
242     }
243   return FALSE;
244 }
245
246 static gint
247 gtk_combo_entry_focus_out (GtkEntry * entry, GdkEventFocus * event, GtkCombo * combo)
248 {
249
250   if (combo->value_in_list && !gtk_combo_find (combo))
251     {
252       /* gdk_beep(); *//* this can be annoying */
253       if (combo->ok_if_empty && !strcmp (gtk_entry_get_text (entry), ""))
254         return FALSE;
255 #ifdef TEST
256       printf ("INVALID ENTRY: `%s'\n", gtk_entry_get_text (entry));
257 #endif
258       gtk_grab_add (GTK_WIDGET (combo));
259       /* this is needed because if we call gtk_widget_grab_focus() 
260          it isn't guaranteed it's the *last* call before the main-loop,
261          so the focus can be lost anyway...
262          the signal_emit_stop doesn't seem to work either...
263        */
264       gtk_idle_add ((GtkFunction) gtk_combo_focus_idle, combo);
265       /*gtk_signal_emit_stop_by_name (GTK_OBJECT (entry), "focus_out_event"); */
266       return TRUE;
267     }
268   return FALSE;
269 }
270
271 static void
272 gtk_combo_get_pos (GtkCombo * combo, gint * x, gint * y, gint * height, gint * width)
273 {
274   GtkBin *popwin;
275   GtkWidget *widget;
276   GtkScrolledWindow *popup;
277   
278   gint real_height;
279   GtkRequisition list_requisition;
280   gboolean show_hscroll = FALSE;
281   gboolean show_vscroll = FALSE;
282   gint avail_height;
283   gint min_height;
284   gint alloc_width;
285   gint work_height;
286   gint old_height;
287   gint old_width;
288   
289   widget = GTK_WIDGET(combo);
290   popup  = GTK_SCROLLED_WINDOW (combo->popup);
291   popwin = GTK_BIN (combo->popwin);
292   
293   gdk_window_get_origin (combo->entry->window, x, y);
294   real_height = MIN (combo->entry->requisition.height, 
295                      combo->entry->allocation.height);
296   *y += real_height;
297   avail_height = gdk_screen_height () - *y;
298   
299   gtk_widget_size_request (combo->list, &list_requisition);
300   min_height = MIN (list_requisition.height, 
301                     popup->vscrollbar->requisition.height);
302   if (!GTK_LIST (combo->list)->children)
303     list_requisition.height += EMPTY_LIST_HEIGHT;
304   
305   alloc_width = (widget->allocation.width -
306                  2 * popwin->child->style->klass->xthickness -
307                  2 * GTK_CONTAINER (popwin->child)->border_width -
308                  2 * GTK_CONTAINER (combo->popup)->border_width -
309                  2 * GTK_CONTAINER (GTK_BIN (popup)->child)->border_width - 
310                  2 * GTK_BIN (popup)->child->style->klass->xthickness);
311   
312   work_height = (2 * popwin->child->style->klass->ythickness +
313                  2 * GTK_CONTAINER (popwin->child)->border_width +
314                  2 * GTK_CONTAINER (combo->popup)->border_width +
315                  2 * GTK_CONTAINER (GTK_BIN (popup)->child)->border_width +
316                  2 * GTK_BIN (popup)->child->style->klass->xthickness);
317   
318   do 
319     {
320       old_width = alloc_width;
321       old_height = work_height;
322       
323       if (!show_hscroll &&
324           alloc_width < list_requisition.width)
325         {
326           work_height += popup->hscrollbar->requisition.height +
327             GTK_SCROLLED_WINDOW_CLASS 
328             (GTK_OBJECT (combo->popup)->klass)->scrollbar_spacing;
329           show_hscroll = TRUE;
330         }
331       if (!show_vscroll && 
332           work_height + list_requisition.height > avail_height)
333         {
334           if (work_height + min_height > avail_height && 
335               *y - real_height > avail_height)
336             {
337               *y -= (work_height + list_requisition.height + real_height);
338               break;
339             }
340           alloc_width -= 
341             popup->vscrollbar->requisition.width +
342             GTK_SCROLLED_WINDOW_CLASS 
343             (GTK_OBJECT (combo->popup)->klass)->scrollbar_spacing;
344           show_vscroll = TRUE;
345         }
346     } while (old_width != alloc_width || old_height != work_height);
347   
348   *width = widget->allocation.width;
349   if (show_vscroll)
350     *height = avail_height;
351   else
352     *height = work_height + list_requisition.height;
353   
354   if (*x < 0)
355     *x = 0;
356 }
357
358 static void
359 gtk_combo_popup_list (GtkCombo * combo)
360 {
361   gint height, width, x, y;
362   gint old_width, old_height;
363
364   old_width = combo->popwin->allocation.width;
365   old_height  = combo->popwin->allocation.height;
366
367   gtk_combo_get_pos (combo, &x, &y, &height, &width);
368
369   /* workaround for gtk_scrolled_window_size_allocate bug */
370   if (old_width != width || old_height != height)
371     {
372       gtk_widget_hide (GTK_SCROLLED_WINDOW (combo->popup)->hscrollbar);
373       gtk_widget_hide (GTK_SCROLLED_WINDOW (combo->popup)->vscrollbar);
374     }
375
376   gtk_widget_set_uposition (combo->popwin, x, y);
377   gtk_widget_set_usize (combo->popwin, width, height);
378   gtk_widget_realize (combo->popwin);
379   gdk_window_resize (combo->popwin->window, width, height);
380   gtk_widget_show (combo->popwin);
381
382   gtk_widget_grab_focus (combo->popwin);
383 }
384
385 static void        
386 gtk_combo_activate (GtkWidget        *widget,
387                     GtkCombo         *combo)
388 {
389   gtk_combo_popup_list (combo);
390
391   if (!GTK_WIDGET_HAS_FOCUS (combo->entry))
392     gtk_widget_grab_focus (combo->entry);
393
394   gtk_grab_add (combo->popwin);
395   gdk_pointer_grab (combo->popwin->window, TRUE,
396                     GDK_BUTTON_PRESS_MASK | 
397                     GDK_BUTTON_RELEASE_MASK |
398                     GDK_POINTER_MOTION_MASK, 
399                     NULL, NULL, GDK_CURRENT_TIME);
400 }
401
402 static void        
403 gtk_combo_popup_button_press (GtkWidget        *button,
404                               GdkEventButton   *event,
405                               GtkCombo         *combo)
406 {
407   if (!GTK_WIDGET_HAS_FOCUS (combo->entry))
408     gtk_widget_grab_focus (combo->entry);
409   if (!combo->current_button && (event->button == 1))
410     gtk_combo_popup_list (combo);
411
412   combo->current_button = event->button;
413   
414   GTK_LIST (combo->list)->drag_selection = TRUE;
415   gdk_pointer_grab (combo->list->window, TRUE,
416                     GDK_POINTER_MOTION_HINT_MASK |
417                     GDK_BUTTON1_MOTION_MASK |
418                     GDK_BUTTON_RELEASE_MASK,
419                     NULL, NULL, event->time);
420   gtk_grab_add (combo->list);
421 }
422
423 static void         
424 gtk_combo_popup_button_leave (GtkWidget        *button,
425                               GdkEventCrossing *event,
426                               GtkCombo         *combo)
427 {
428   if (combo->current_button)
429     gtk_signal_emit_stop_by_name (GTK_OBJECT (button), "leave_notify_event");
430 }
431
432
433 static void
434 gtk_combo_update_entry (GtkList * list, GtkCombo * combo)
435 {
436   char *text;
437
438   gtk_grab_remove (GTK_WIDGET (combo));
439   gtk_signal_handler_block (GTK_OBJECT (list), combo->list_change_id);
440   if (list->selection)
441     {
442       text = gtk_combo_func (GTK_LIST_ITEM (list->selection->data));
443       if (!text)
444         text = "";
445       gtk_entry_set_text (GTK_ENTRY (combo->entry), text);
446     }
447   gtk_signal_handler_unblock (GTK_OBJECT (list), combo->list_change_id);
448 }
449
450 static void
451 gtk_combo_update_list (GtkEntry * entry, GtkCombo * combo)
452 {
453   GtkList *list = GTK_LIST (combo->list);
454   GList *slist = list->selection;
455   GtkListItem *li;
456
457   gtk_grab_remove (GTK_WIDGET (combo));
458
459   gtk_signal_handler_block (GTK_OBJECT (entry), combo->entry_change_id);
460   if (slist && slist->data)
461     gtk_list_unselect_child (list, GTK_WIDGET (slist->data));
462   li = gtk_combo_find (combo);
463   if (li)
464     gtk_list_select_child (list, GTK_WIDGET (li));
465   gtk_signal_handler_unblock (GTK_OBJECT (entry), combo->entry_change_id);
466 }
467
468 static gint
469 gtk_combo_button_press (GtkWidget * widget, GdkEvent * event, GtkCombo * combo)
470 {
471   GtkWidget *child;
472
473   child = gtk_get_event_widget (event);
474
475   /* We don't ask for button press events on the grab widget, so
476    *  if an event is reported directly to the grab widget, it must
477    *  be on a window outside the application (and thus we remove
478    *  the popup window). Otherwise, we check if the widget is a child
479    *  of the grab widget, and only remove the popup window if it
480    *  is not.
481    */
482   if (child != widget)
483     {
484       while (child)
485         {
486           if (child == widget)
487             return FALSE;
488           child = child->parent;
489         }
490     }
491
492   gtk_widget_hide (combo->popwin);
493   gtk_grab_remove (combo->popwin);
494   gdk_pointer_ungrab (event->button.time);
495
496   return TRUE;
497 }
498
499 static gint
500 gtk_combo_button_release (GtkWidget * widget, GdkEvent * event, GtkCombo * combo)
501 {
502   GtkWidget *child;
503
504   if ((combo->current_button != 0) && (event->button.button == 1))
505     {
506       /* This was the initial button press */
507
508       GdkEventCrossing tmp_event;
509
510       combo->current_button = 0;
511
512       if (widget != combo->button)
513         gtk_widget_event (combo->button, event);
514
515       /* Un-pre-hightlight */
516       
517       tmp_event.type = GDK_LEAVE_NOTIFY;
518       tmp_event.window = combo->button->window;
519       tmp_event.send_event = TRUE;
520       tmp_event.subwindow = NULL;
521       tmp_event.detail = GDK_NOTIFY_ANCESTOR;
522       
523       gtk_widget_event (combo->button, (GdkEvent *)&tmp_event);
524
525       /* Check to see if we released inside the button */
526       child = gtk_get_event_widget ((GdkEvent*) event);
527
528       while (child && child != (combo->button))
529         child = child->parent;
530
531       if (child == combo->button)
532         {
533           gtk_grab_add (combo->popwin);
534           gdk_pointer_grab (combo->popwin->window, TRUE,
535                             GDK_BUTTON_PRESS_MASK | 
536                             GDK_BUTTON_RELEASE_MASK |
537                             GDK_POINTER_MOTION_MASK, 
538                             NULL, NULL, GDK_CURRENT_TIME);
539           return FALSE;
540         }
541     }
542   else
543     {
544       /* The user has clicked inside the popwin and released */
545
546       if (GTK_WIDGET_HAS_GRAB (combo->popwin))
547         {
548           gtk_grab_remove (combo->popwin);
549           gdk_pointer_ungrab (event->button.time);
550         }
551     }
552   
553   gtk_widget_hide (combo->popwin);
554
555   return TRUE;
556 }
557
558 static gint         
559 gtk_combo_list_enter (GtkWidget        *widget,
560                       GdkEventCrossing *event,
561                       GtkCombo         *combo)
562 {
563   GtkWidget *event_widget;
564
565   event_widget = gtk_get_event_widget ((GdkEvent*) event);
566   
567   if ((event_widget == combo->list) &&
568       (combo->current_button != 0) && 
569       (!GTK_WIDGET_HAS_GRAB (combo->list)))
570     {
571       GdkEvent tmp_event;
572       gint x, y;
573       GdkModifierType mask;
574
575       gtk_grab_remove (combo->popwin);
576
577       /* Transfer the grab over to the list by synthesizing
578        * a button press event
579        */
580       gdk_window_get_pointer (combo->list->window, &x, &y, &mask);
581
582       tmp_event.button.type = GDK_BUTTON_PRESS;
583       tmp_event.button.window = combo->list->window;
584       tmp_event.button.send_event = TRUE;
585       tmp_event.button.time = GDK_CURRENT_TIME; /* bad */
586       tmp_event.button.x = x;
587       tmp_event.button.y = y;
588       /* We leave all the XInput fields unfilled here, in the expectation
589        * that GtkList doesn't care.
590        */
591       tmp_event.button.button = combo->current_button;
592       tmp_event.button.state = mask;
593
594       gtk_widget_event (combo->list, &tmp_event);
595     }
596
597   return FALSE;
598 }
599
600 static int
601 gtk_combo_list_key_press (GtkWidget * widget, GdkEventKey * event, GtkCombo * combo)
602 {
603   if (event->keyval == GDK_Escape)
604     {
605       if (GTK_WIDGET_HAS_GRAB (combo->popwin))
606         {
607           gtk_grab_remove (combo->popwin);
608           gdk_pointer_ungrab (GDK_CURRENT_TIME);
609         }
610       else if (GTK_WIDGET_HAS_GRAB (combo->list))
611         gtk_list_end_drag_selection (GTK_LIST (combo->list));
612       gtk_widget_hide (combo->popwin);
613       if (GTK_WIDGET_HAS_GRAB (combo->button))
614         {
615           combo->current_button = 0;
616           GTK_BUTTON (combo->button)->in_button = FALSE;
617           gtk_button_released (GTK_BUTTON (combo->button));
618           gtk_grab_remove (combo->button);
619         }
620       return TRUE;
621     }
622   return FALSE;
623 }
624
625 static void
626 gtk_combo_init (GtkCombo * combo)
627 {
628   GtkWidget *arrow;
629   GtkWidget *frame;
630   GtkWidget *event_box;
631   GdkCursor *cursor;
632
633   combo->case_sensitive = 0;
634   combo->value_in_list = 0;
635   combo->ok_if_empty = 1;
636   combo->use_arrows = 1;
637   combo->use_arrows_always = 0;
638   combo->entry = gtk_entry_new ();
639   combo->button = gtk_button_new ();
640   combo->current_button = 0;
641   arrow = gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_OUT);
642   gtk_widget_show (arrow);
643   gtk_container_add (GTK_CONTAINER (combo->button), arrow);
644   gtk_box_pack_start (GTK_BOX (combo), combo->entry, TRUE, TRUE, 0);
645   gtk_box_pack_end (GTK_BOX (combo), combo->button, FALSE, FALSE, 0);
646   GTK_WIDGET_UNSET_FLAGS (combo->button, GTK_CAN_FOCUS);
647   gtk_widget_show (combo->entry);
648   gtk_widget_show (combo->button);
649   combo->entry_change_id = gtk_signal_connect (GTK_OBJECT (combo->entry), "changed",
650                               (GtkSignalFunc) gtk_combo_update_list, combo);
651   gtk_signal_connect (GTK_OBJECT (combo->entry), "key_press_event",
652                       (GtkSignalFunc) gtk_combo_entry_key_press, combo);
653   gtk_signal_connect_after (GTK_OBJECT (combo->entry), "focus_out_event",
654                             (GtkSignalFunc) gtk_combo_entry_focus_out, combo);
655   combo->activate_id = gtk_signal_connect (GTK_OBJECT (combo->entry), "activate",
656                       (GtkSignalFunc) gtk_combo_activate, combo);
657   gtk_signal_connect_after (GTK_OBJECT (combo->button), "button_press_event",
658                             (GtkSignalFunc) gtk_combo_popup_button_press, combo);
659   /*gtk_signal_connect_after (GTK_OBJECT (combo->button), "button_release_event",
660     (GtkSignalFunc) gtk_combo_button_release, combo);*/
661   gtk_signal_connect (GTK_OBJECT (combo->button), "leave_notify_event",
662                       (GtkSignalFunc) gtk_combo_popup_button_leave, combo);
663   /*gtk_signal_connect(GTK_OBJECT(combo->button), "clicked",
664      (GtkSignalFunc)prelight_bug, combo); */
665
666   combo->popwin = gtk_window_new (GTK_WINDOW_POPUP);
667   gtk_widget_ref (combo->popwin);
668   gtk_window_set_policy (GTK_WINDOW (combo->popwin), 1, 1, 0);
669   
670   gtk_widget_set_events (combo->popwin, GDK_KEY_PRESS_MASK);
671
672   event_box = gtk_event_box_new ();
673   gtk_container_add (GTK_CONTAINER (combo->popwin), event_box);
674   gtk_widget_show (event_box);
675
676   gtk_widget_realize (event_box);
677   cursor = gdk_cursor_new (GDK_TOP_LEFT_ARROW);
678   gdk_window_set_cursor (event_box->window, cursor);
679   gdk_cursor_destroy (cursor);
680
681   frame = gtk_frame_new (NULL);
682   gtk_container_add (GTK_CONTAINER (event_box), frame);
683   gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT);
684   gtk_widget_show (frame);
685
686   combo->popup = gtk_scrolled_window_new (NULL, NULL);
687   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (combo->popup),
688                                   GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
689   GTK_WIDGET_UNSET_FLAGS (GTK_SCROLLED_WINDOW (combo->popup)->hscrollbar, GTK_CAN_FOCUS);
690   GTK_WIDGET_UNSET_FLAGS (GTK_SCROLLED_WINDOW (combo->popup)->vscrollbar, GTK_CAN_FOCUS);
691   gtk_container_add (GTK_CONTAINER (frame), combo->popup);
692   gtk_widget_show (combo->popup);
693
694   combo->list = gtk_list_new ();
695   /* We'll use enter notify events to figure out when to transfer
696    * the grab to the list
697    */
698   gtk_widget_set_events (combo->list, GDK_ENTER_NOTIFY_MASK);
699
700   gtk_list_set_selection_mode(GTK_LIST(combo->list), GTK_SELECTION_BROWSE);
701   gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (combo->popup), combo->list);
702   gtk_container_set_focus_vadjustment (GTK_CONTAINER (combo->list),
703                                        gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (combo->popup)));
704   gtk_container_set_focus_hadjustment (GTK_CONTAINER (combo->list),
705                                        gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (combo->popup)));
706   gtk_widget_show (combo->list);
707
708   combo->list_change_id = gtk_signal_connect (GTK_OBJECT (combo->list), "selection_changed",
709                              (GtkSignalFunc) gtk_combo_update_entry, combo);
710   gtk_signal_connect (GTK_OBJECT (combo->popwin), "key_press_event",
711                       (GtkSignalFunc) gtk_combo_list_key_press, combo);
712   gtk_signal_connect (GTK_OBJECT (combo->popwin), "button_press_event",
713                       GTK_SIGNAL_FUNC (gtk_combo_button_press), combo);
714
715   gtk_signal_connect_after (GTK_OBJECT (combo->list), "button_release_event",
716                             GTK_SIGNAL_FUNC (gtk_combo_button_release), combo);
717   /* We connect here on the button, because we'll have a grab on it
718    * when the event occurs. But we are actually interested in enters
719    * for the combo->list.
720    */
721   gtk_signal_connect (GTK_OBJECT (combo->button), "enter_notify_event",
722                       GTK_SIGNAL_FUNC (gtk_combo_list_enter), combo);
723 }
724
725 guint
726 gtk_combo_get_type (void)
727 {
728   static guint combo_type = 0;
729
730   if (!combo_type)
731     {
732       static const GtkTypeInfo combo_info =
733       {
734         "GtkCombo",
735         sizeof (GtkCombo),
736         sizeof (GtkComboClass),
737         (GtkClassInitFunc) gtk_combo_class_init,
738         (GtkObjectInitFunc) gtk_combo_init,
739         /* reserved_1 */ NULL,
740         /* reserved_2 */ NULL,
741         (GtkClassInitFunc) NULL,
742       };
743       combo_type = gtk_type_unique (gtk_hbox_get_type (), &combo_info);
744     }
745   return combo_type;
746 }
747
748 GtkWidget *
749 gtk_combo_new (void)
750 {
751   return GTK_WIDGET (gtk_type_new (gtk_combo_get_type ()));
752 }
753
754 void
755 gtk_combo_set_value_in_list (GtkCombo * combo, gint val, gint ok_if_empty)
756 {
757   g_return_if_fail (combo != NULL);
758   g_return_if_fail (GTK_IS_COMBO (combo));
759
760   combo->value_in_list = val;
761   combo->ok_if_empty = ok_if_empty;
762 }
763
764 void
765 gtk_combo_set_case_sensitive (GtkCombo * combo, gint val)
766 {
767   g_return_if_fail (combo != NULL);
768   g_return_if_fail (GTK_IS_COMBO (combo));
769
770   combo->case_sensitive = val;
771 }
772
773 void
774 gtk_combo_set_use_arrows (GtkCombo * combo, gint val)
775 {
776   g_return_if_fail (combo != NULL);
777   g_return_if_fail (GTK_IS_COMBO (combo));
778
779   combo->use_arrows = val;
780 }
781
782 void
783 gtk_combo_set_use_arrows_always (GtkCombo * combo, gint val)
784 {
785   g_return_if_fail (combo != NULL);
786   g_return_if_fail (GTK_IS_COMBO (combo));
787
788   combo->use_arrows_always = val;
789   combo->use_arrows = 1;
790 }
791
792 void
793 gtk_combo_set_popdown_strings (GtkCombo * combo, GList * strings)
794 {
795   GList *list;
796   GtkWidget *li;
797
798   g_return_if_fail (combo != NULL);
799   g_return_if_fail (GTK_IS_COMBO (combo));
800   g_return_if_fail (strings != NULL);
801
802   gtk_list_clear_items (GTK_LIST (combo->list), 0, -1);
803   list = strings;
804   while (list)
805     {
806       li = gtk_list_item_new_with_label ((gchar *) list->data);
807       gtk_widget_show (li);
808       gtk_container_add (GTK_CONTAINER (combo->list), li);
809       list = list->next;
810     }
811 }
812
813 static void
814 gtk_combo_item_destroy (GtkObject * object)
815 {
816   gchar *key;
817
818   key = gtk_object_get_data (object, gtk_combo_string_key);
819   if (key)
820     g_free (key);
821 }
822
823 void
824 gtk_combo_set_item_string (GtkCombo * combo, GtkItem * item, const gchar * item_value)
825 {
826   gchar *val;
827   gint connected = 0;
828
829   g_return_if_fail (combo != NULL);
830   g_return_if_fail (GTK_IS_COMBO (combo));
831   g_return_if_fail (item != NULL);
832
833   val = gtk_object_get_data (GTK_OBJECT (item), gtk_combo_string_key);
834   if (val) 
835     {
836       g_free (val);
837       connected = 1;
838     }
839   if (item_value)
840     {
841       val = g_strdup(item_value);
842       gtk_object_set_data (GTK_OBJECT (item), gtk_combo_string_key, val);
843       if (!connected)
844         gtk_signal_connect (GTK_OBJECT (item), "destroy",
845                           (GtkSignalFunc) gtk_combo_item_destroy, val);
846     }
847   else 
848     {
849       gtk_object_set_data (GTK_OBJECT (item), gtk_combo_string_key, NULL);
850       if (connected)
851         gtk_signal_disconnect_by_data(GTK_OBJECT (item), val);
852     }
853 }
854
855 static void
856 gtk_combo_size_allocate (GtkWidget     *widget,
857                          GtkAllocation *allocation)
858 {
859   GtkCombo *combo;
860
861   g_return_if_fail (widget != NULL);
862   g_return_if_fail (GTK_IS_COMBO (widget));
863   g_return_if_fail (allocation != NULL);
864
865   GTK_WIDGET_CLASS (parent_class)->size_allocate (widget, allocation);
866   
867   combo = GTK_COMBO (widget);
868
869   if (combo->entry->allocation.height > combo->entry->requisition.height)
870     {
871       GtkAllocation button_allocation;
872
873       button_allocation = combo->button->allocation;
874       button_allocation.height = combo->entry->requisition.height;
875       button_allocation.y = combo->entry->allocation.y + 
876         (combo->entry->allocation.height - combo->entry->requisition.height) 
877         / 2;
878       gtk_widget_size_allocate (combo->button, &button_allocation);
879     }
880 }
881
882 void
883 gtk_combo_disable_activate (GtkCombo* combo)
884 {
885   g_return_if_fail (combo != NULL);
886   g_return_if_fail (GTK_IS_COMBO (combo));
887
888   if ( combo->activate_id ) {
889     gtk_signal_disconnect(GTK_OBJECT(combo->entry), combo->activate_id);
890     combo->activate_id = 0;
891   }
892 }