]> Pileus Git - ~andy/gtk/blob - gtk/gtkcombo.c
Wed, 14 Oct 1998 10:17:13 +0200 Paolo Molaro <lupus@debian.org>
[~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     gtk_widget_grab_focus (combo->entry);
239   return FALSE;
240 }
241
242 static gint
243 gtk_combo_entry_focus_out (GtkEntry * entry, GdkEventFocus * event, GtkCombo * combo)
244 {
245
246   if (combo->value_in_list && !gtk_combo_find (combo))
247     {
248       /* gdk_beep(); *//* this can be annoying */
249       if (combo->ok_if_empty && !strcmp (gtk_entry_get_text (entry), ""))
250         return FALSE;
251 #ifdef TEST
252       printf ("INVALID ENTRY: `%s'\n", gtk_entry_get_text (entry));
253 #endif
254       gtk_grab_add (GTK_WIDGET (combo));
255       /* this is needed because if we call gtk_widget_grab_focus() 
256          it isn't guaranteed it's the *last* call before the main-loop,
257          so the focus can be lost anyway...
258          the signal_emit_stop doesn't seem to work either...
259        */
260       gtk_idle_add ((GtkFunction) gtk_combo_focus_idle, combo);
261       /*gtk_signal_emit_stop_by_name (GTK_OBJECT (entry), "focus_out_event"); */
262       return TRUE;
263     }
264   return FALSE;
265 }
266
267 static void
268 gtk_combo_get_pos (GtkCombo * combo, gint * x, gint * y, gint * height, gint * width)
269 {
270   GtkBin *popwin;
271   GtkWidget *widget;
272   GtkScrolledWindow *popup;
273   
274   gint real_height;
275   GtkRequisition list_requisition;
276   gboolean show_hscroll = FALSE;
277   gboolean show_vscroll = FALSE;
278   gint avail_height;
279   gint min_height;
280   gint alloc_width;
281   gint work_height;
282   gint old_height;
283   gint old_width;
284   
285   widget = GTK_WIDGET(combo);
286   popup  = GTK_SCROLLED_WINDOW (combo->popup);
287   popwin = GTK_BIN (combo->popwin);
288   
289   gdk_window_get_origin (combo->entry->window, x, y);
290   real_height = MIN (combo->entry->requisition.height, 
291                      combo->entry->allocation.height);
292   *y += real_height;
293   avail_height = gdk_screen_height () - *y;
294   
295   gtk_widget_size_request (combo->list, &list_requisition);
296   min_height = MIN (list_requisition.height, 
297                     popup->vscrollbar->requisition.height);
298   if (!GTK_LIST (combo->list)->children)
299     list_requisition.height += EMPTY_LIST_HEIGHT;
300   
301   alloc_width = (widget->allocation.width -
302                  2 * popwin->child->style->klass->xthickness -
303                  2 * GTK_CONTAINER (popwin->child)->border_width -
304                  2 * GTK_CONTAINER (combo->popup)->border_width -
305                  2 * GTK_CONTAINER (popup->viewport)->border_width - 
306                  2 * popup->viewport->style->klass->xthickness);
307   
308   work_height = (2 * popwin->child->style->klass->ythickness +
309                  2 * GTK_CONTAINER (popwin->child)->border_width +
310                  2 * GTK_CONTAINER (combo->popup)->border_width +
311                  2 * GTK_CONTAINER (popup->viewport)->border_width +
312                  2 * popup->viewport->style->klass->xthickness);
313   
314   do 
315     {
316       old_width = alloc_width;
317       old_height = work_height;
318       
319       if (!show_hscroll &&
320           alloc_width < list_requisition.width)
321         {
322           work_height += popup->hscrollbar->requisition.height +
323             GTK_SCROLLED_WINDOW_CLASS 
324             (GTK_OBJECT (combo->popup)->klass)->scrollbar_spacing;
325           show_hscroll = TRUE;
326         }
327       if (!show_vscroll && 
328           work_height + list_requisition.height > avail_height)
329         {
330           if (work_height + min_height > avail_height && 
331               *y - real_height > avail_height)
332             {
333               *y -= (work_height + list_requisition.height + real_height);
334               break;
335             }
336           alloc_width -= 
337             popup->vscrollbar->requisition.width +
338             GTK_SCROLLED_WINDOW_CLASS 
339             (GTK_OBJECT (combo->popup)->klass)->scrollbar_spacing;
340           show_vscroll = TRUE;
341         }
342     } while (old_width != alloc_width || old_height != work_height);
343   
344   *width = widget->allocation.width;
345   if (show_vscroll)
346     *height = avail_height;
347   else
348     *height = work_height + list_requisition.height;
349   
350   if (*x < 0)
351     *x = 0;
352 }
353
354 static void
355 gtk_combo_popup_list (GtkCombo * combo)
356 {
357   gint height, width, x, y;
358   gint old_width, old_height;
359
360   old_width = combo->popwin->allocation.width;
361   old_height  = combo->popwin->allocation.height;
362
363   gtk_combo_get_pos (combo, &x, &y, &height, &width);
364
365   /* workaround for gtk_scrolled_window_size_allocate bug */
366   if (old_width != width || old_height != height)
367     {
368       gtk_widget_hide (GTK_SCROLLED_WINDOW (combo->popup)->hscrollbar);
369       gtk_widget_hide (GTK_SCROLLED_WINDOW (combo->popup)->vscrollbar);
370     }
371
372   gtk_widget_set_uposition (combo->popwin, x, y);
373   gtk_widget_set_usize (combo->popwin, width, height);
374   gtk_widget_realize (combo->popwin);
375   gdk_window_resize (combo->popwin->window, width, height);
376   gtk_widget_show (combo->popwin);
377
378   gtk_widget_grab_focus (combo->popwin);
379 }
380
381 static void        
382 gtk_combo_activate (GtkWidget        *widget,
383                     GtkCombo         *combo)
384 {
385   gtk_combo_popup_list (combo);
386
387   if (!GTK_WIDGET_HAS_FOCUS (combo->entry))
388     gtk_widget_grab_focus (combo->entry);
389
390   gtk_grab_add (combo->popwin);
391   gdk_pointer_grab (combo->popwin->window, TRUE,
392                     GDK_BUTTON_PRESS_MASK | 
393                     GDK_BUTTON_RELEASE_MASK |
394                     GDK_POINTER_MOTION_MASK, 
395                     NULL, NULL, GDK_CURRENT_TIME);
396 }
397
398 static void        
399 gtk_combo_popup_button_press (GtkWidget        *button,
400                               GdkEventButton   *event,
401                               GtkCombo         *combo)
402 {
403   if (!GTK_WIDGET_HAS_FOCUS (combo->entry))
404     gtk_widget_grab_focus (combo->entry);
405   if (!combo->current_button && (event->button == 1))
406     gtk_combo_popup_list (combo);
407
408   combo->current_button = event->button;
409   
410   GTK_LIST (combo->list)->drag_selection = TRUE;
411   gdk_pointer_grab (combo->list->window, TRUE,
412                     GDK_POINTER_MOTION_HINT_MASK |
413                     GDK_BUTTON1_MOTION_MASK |
414                     GDK_BUTTON_RELEASE_MASK,
415                     NULL, NULL, event->time);
416   gtk_grab_add (combo->list);
417 }
418
419 static void         
420 gtk_combo_popup_button_leave (GtkWidget        *button,
421                               GdkEventCrossing *event,
422                               GtkCombo         *combo)
423 {
424   if (combo->current_button)
425     gtk_signal_emit_stop_by_name (GTK_OBJECT (button), "leave_notify_event");
426 }
427
428
429 static void
430 gtk_combo_update_entry (GtkList * list, GtkCombo * combo)
431 {
432   char *text;
433
434   gtk_grab_remove (GTK_WIDGET (combo));
435   gtk_signal_handler_block (GTK_OBJECT (list), combo->list_change_id);
436   if (list->selection)
437     {
438       text = gtk_combo_func (GTK_LIST_ITEM (list->selection->data));
439       if (!text)
440         text = "";
441       gtk_entry_set_text (GTK_ENTRY (combo->entry), text);
442     }
443   gtk_signal_handler_unblock (GTK_OBJECT (list), combo->list_change_id);
444 }
445
446 static void
447 gtk_combo_update_list (GtkEntry * entry, GtkCombo * combo)
448 {
449   GtkList *list = GTK_LIST (combo->list);
450   GList *slist = list->selection;
451   GtkListItem *li;
452
453   gtk_grab_remove (GTK_WIDGET (combo));
454
455   gtk_signal_handler_block (GTK_OBJECT (entry), combo->entry_change_id);
456   if (slist && slist->data)
457     gtk_list_unselect_child (list, GTK_WIDGET (slist->data));
458   li = gtk_combo_find (combo);
459   if (li)
460     gtk_list_select_child (list, GTK_WIDGET (li));
461   gtk_signal_handler_unblock (GTK_OBJECT (entry), combo->entry_change_id);
462 }
463
464 static gint
465 gtk_combo_button_press (GtkWidget * widget, GdkEvent * event, GtkCombo * combo)
466 {
467   GtkWidget *child;
468
469   child = gtk_get_event_widget (event);
470
471   /* We don't ask for button press events on the grab widget, so
472    *  if an event is reported directly to the grab widget, it must
473    *  be on a window outside the application (and thus we remove
474    *  the popup window). Otherwise, we check if the widget is a child
475    *  of the grab widget, and only remove the popup window if it
476    *  is not.
477    */
478   if (child != widget)
479     {
480       while (child)
481         {
482           if (child == widget)
483             return FALSE;
484           child = child->parent;
485         }
486     }
487
488   gtk_widget_hide (combo->popwin);
489   gtk_grab_remove (combo->popwin);
490   gdk_pointer_ungrab (event->button.time);
491
492   return TRUE;
493 }
494
495 static gint
496 gtk_combo_button_release (GtkWidget * widget, GdkEvent * event, GtkCombo * combo)
497 {
498   GtkWidget *child;
499
500   if ((combo->current_button != 0) && (event->button.button == 1))
501     {
502       /* This was the initial button press */
503
504       GdkEventCrossing tmp_event;
505
506       combo->current_button = 0;
507
508       if (widget != combo->button)
509         gtk_widget_event (combo->button, event);
510
511       /* Un-pre-hightlight */
512       
513       tmp_event.type = GDK_LEAVE_NOTIFY;
514       tmp_event.window = combo->button->window;
515       tmp_event.send_event = TRUE;
516       tmp_event.subwindow = NULL;
517       tmp_event.detail = GDK_NOTIFY_ANCESTOR;
518       
519       gtk_widget_event (combo->button, (GdkEvent *)&tmp_event);
520
521       /* Check to see if we released inside the button */
522       child = gtk_get_event_widget ((GdkEvent*) event);
523
524       while (child && child != (combo->button))
525         child = child->parent;
526
527       if (child == combo->button)
528         {
529           gtk_grab_add (combo->popwin);
530           gdk_pointer_grab (combo->popwin->window, TRUE,
531                             GDK_BUTTON_PRESS_MASK | 
532                             GDK_BUTTON_RELEASE_MASK |
533                             GDK_POINTER_MOTION_MASK, 
534                             NULL, NULL, GDK_CURRENT_TIME);
535           return FALSE;
536         }
537     }
538   else
539     {
540       /* The user has clicked inside the popwin and released */
541
542       if (GTK_WIDGET_HAS_GRAB (combo->popwin))
543         {
544           gtk_grab_remove (combo->popwin);
545           gdk_pointer_ungrab (event->button.time);
546         }
547     }
548   
549   gtk_widget_hide (combo->popwin);
550
551   return TRUE;
552 }
553
554 static gint         
555 gtk_combo_list_enter (GtkWidget        *widget,
556                       GdkEventCrossing *event,
557                       GtkCombo         *combo)
558 {
559   GtkWidget *event_widget;
560
561   event_widget = gtk_get_event_widget ((GdkEvent*) event);
562   
563   if ((event_widget == combo->list) &&
564       (combo->current_button != 0) && 
565       (!GTK_WIDGET_HAS_GRAB (combo->list)))
566     {
567       GdkEvent tmp_event;
568       gint x, y;
569       GdkModifierType mask;
570
571       gtk_grab_remove (combo->popwin);
572
573       /* Transfer the grab over to the list by synthesizing
574        * a button press event
575        */
576       gdk_window_get_pointer (combo->list->window, &x, &y, &mask);
577
578       tmp_event.button.type = GDK_BUTTON_PRESS;
579       tmp_event.button.window = combo->list->window;
580       tmp_event.button.send_event = TRUE;
581       tmp_event.button.time = GDK_CURRENT_TIME; /* bad */
582       tmp_event.button.x = x;
583       tmp_event.button.y = y;
584       /* We leave all the XInput fields unfilled here, in the expectation
585        * that GtkList doesn't care.
586        */
587       tmp_event.button.button = combo->current_button;
588       tmp_event.button.state = mask;
589
590       gtk_widget_event (combo->list, &tmp_event);
591     }
592
593   return FALSE;
594 }
595
596 static int
597 gtk_combo_list_key_press (GtkWidget * widget, GdkEventKey * event, GtkCombo * combo)
598 {
599   if (event->keyval == GDK_Escape)
600     {
601       if (GTK_WIDGET_HAS_GRAB (combo->popwin))
602         {
603           gtk_grab_remove (combo->popwin);
604           gdk_pointer_ungrab (GDK_CURRENT_TIME);
605         }
606       else if (GTK_WIDGET_HAS_GRAB (combo->list))
607         gtk_list_end_drag_selection (GTK_LIST (combo->list));
608       gtk_widget_hide (combo->popwin);
609       if (GTK_WIDGET_HAS_GRAB (combo->button))
610         {
611           combo->current_button = 0;
612           GTK_BUTTON (combo->button)->in_button = FALSE;
613           gtk_button_released (GTK_BUTTON (combo->button));
614           gtk_grab_remove (combo->button);
615         }
616       return TRUE;
617     }
618   return FALSE;
619 }
620
621 static void
622 gtk_combo_init (GtkCombo * combo)
623 {
624   GtkWidget *arrow;
625   GtkWidget *frame;
626   GtkWidget *event_box;
627   GdkCursor *cursor;
628
629   combo->case_sensitive = 0;
630   combo->value_in_list = 0;
631   combo->ok_if_empty = 1;
632   combo->use_arrows = 1;
633   combo->use_arrows_always = 0;
634   combo->entry = gtk_entry_new ();
635   combo->button = gtk_button_new ();
636   combo->current_button = 0;
637   arrow = gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_OUT);
638   gtk_widget_show (arrow);
639   gtk_container_add (GTK_CONTAINER (combo->button), arrow);
640   gtk_box_pack_start (GTK_BOX (combo), combo->entry, TRUE, TRUE, 0);
641   gtk_box_pack_end (GTK_BOX (combo), combo->button, FALSE, FALSE, 0);
642   GTK_WIDGET_UNSET_FLAGS (combo->button, GTK_CAN_FOCUS);
643   gtk_widget_show (combo->entry);
644   gtk_widget_show (combo->button);
645   combo->entry_change_id = gtk_signal_connect (GTK_OBJECT (combo->entry), "changed",
646                               (GtkSignalFunc) gtk_combo_update_list, combo);
647   gtk_signal_connect (GTK_OBJECT (combo->entry), "key_press_event",
648                       (GtkSignalFunc) gtk_combo_entry_key_press, combo);
649   gtk_signal_connect_after (GTK_OBJECT (combo->entry), "focus_out_event",
650                             (GtkSignalFunc) gtk_combo_entry_focus_out, combo);
651   combo->activate_id = gtk_signal_connect (GTK_OBJECT (combo->entry), "activate",
652                       (GtkSignalFunc) gtk_combo_activate, combo);
653   gtk_signal_connect_after (GTK_OBJECT (combo->button), "button_press_event",
654                             (GtkSignalFunc) gtk_combo_popup_button_press, combo);
655   /*gtk_signal_connect_after (GTK_OBJECT (combo->button), "button_release_event",
656     (GtkSignalFunc) gtk_combo_button_release, combo);*/
657   gtk_signal_connect (GTK_OBJECT (combo->button), "leave_notify_event",
658                       (GtkSignalFunc) gtk_combo_popup_button_leave, combo);
659   /*gtk_signal_connect(GTK_OBJECT(combo->button), "clicked",
660      (GtkSignalFunc)prelight_bug, combo); */
661
662   combo->popwin = gtk_window_new (GTK_WINDOW_POPUP);
663   gtk_widget_ref (combo->popwin);
664   gtk_window_set_policy (GTK_WINDOW (combo->popwin), 1, 1, 0);
665   
666   gtk_widget_set_events (combo->popwin, GDK_KEY_PRESS_MASK);
667
668   event_box = gtk_event_box_new ();
669   gtk_container_add (GTK_CONTAINER (combo->popwin), event_box);
670   gtk_widget_show (event_box);
671
672   gtk_widget_realize (event_box);
673   cursor = gdk_cursor_new (GDK_TOP_LEFT_ARROW);
674   gdk_window_set_cursor (event_box->window, cursor);
675   gdk_cursor_destroy (cursor);
676
677   frame = gtk_frame_new (NULL);
678   gtk_container_add (GTK_CONTAINER (event_box), frame);
679   gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT);
680   gtk_widget_show (frame);
681
682   combo->popup = gtk_scrolled_window_new (NULL, NULL);
683   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (combo->popup),
684                                   GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
685   GTK_WIDGET_UNSET_FLAGS (GTK_SCROLLED_WINDOW (combo->popup)->hscrollbar, GTK_CAN_FOCUS);
686   GTK_WIDGET_UNSET_FLAGS (GTK_SCROLLED_WINDOW (combo->popup)->vscrollbar, GTK_CAN_FOCUS);
687   gtk_container_add (GTK_CONTAINER (frame), combo->popup);
688   gtk_widget_show (combo->popup);
689
690   combo->list = gtk_list_new ();
691   /* We'll use enter notify events to figure out when to transfer
692    * the grab to the list
693    */
694   gtk_widget_set_events (combo->list, GDK_ENTER_NOTIFY_MASK);
695
696   gtk_list_set_selection_mode(GTK_LIST(combo->list), GTK_SELECTION_BROWSE);
697   gtk_container_add (GTK_CONTAINER (combo->popup), combo->list);
698   gtk_container_set_focus_vadjustment (GTK_CONTAINER (combo->list),
699                                        gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (combo->popup)));
700   gtk_container_set_focus_hadjustment (GTK_CONTAINER (combo->list),
701                                        gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (combo->popup)));
702   gtk_widget_show (combo->list);
703
704   combo->list_change_id = gtk_signal_connect (GTK_OBJECT (combo->list), "selection_changed",
705                              (GtkSignalFunc) gtk_combo_update_entry, combo);
706   gtk_signal_connect (GTK_OBJECT (combo->popwin), "key_press_event",
707                       (GtkSignalFunc) gtk_combo_list_key_press, combo);
708   gtk_signal_connect (GTK_OBJECT (combo->popwin), "button_press_event",
709                       GTK_SIGNAL_FUNC (gtk_combo_button_press), combo);
710
711   gtk_signal_connect_after (GTK_OBJECT (combo->list), "button_release_event",
712                             GTK_SIGNAL_FUNC (gtk_combo_button_release), combo);
713   /* We connect here on the button, because we'll have a grab on it
714    * when the event occurs. But we are actually interested in enters
715    * for the combo->list.
716    */
717   gtk_signal_connect (GTK_OBJECT (combo->button), "enter_notify_event",
718                       GTK_SIGNAL_FUNC (gtk_combo_list_enter), combo);
719 }
720
721 guint
722 gtk_combo_get_type (void)
723 {
724   static guint combo_type = 0;
725
726   if (!combo_type)
727     {
728       GtkTypeInfo combo_info =
729       {
730         "GtkCombo",
731         sizeof (GtkCombo),
732         sizeof (GtkComboClass),
733         (GtkClassInitFunc) gtk_combo_class_init,
734         (GtkObjectInitFunc) gtk_combo_init,
735         /* reserved_1 */ NULL,
736         /* reserved_2 */ NULL,
737         (GtkClassInitFunc) NULL,
738       };
739       combo_type = gtk_type_unique (gtk_hbox_get_type (), &combo_info);
740     }
741   return combo_type;
742 }
743
744 GtkWidget *
745 gtk_combo_new (void)
746 {
747   return GTK_WIDGET (gtk_type_new (gtk_combo_get_type ()));
748 }
749
750 void
751 gtk_combo_set_value_in_list (GtkCombo * combo, gint val, gint ok_if_empty)
752 {
753   g_return_if_fail (combo != NULL);
754   g_return_if_fail (GTK_IS_COMBO (combo));
755
756   combo->value_in_list = val;
757   combo->ok_if_empty = ok_if_empty;
758 }
759
760 void
761 gtk_combo_set_case_sensitive (GtkCombo * combo, gint val)
762 {
763   g_return_if_fail (combo != NULL);
764   g_return_if_fail (GTK_IS_COMBO (combo));
765
766   combo->case_sensitive = val;
767 }
768
769 void
770 gtk_combo_set_use_arrows (GtkCombo * combo, gint val)
771 {
772   g_return_if_fail (combo != NULL);
773   g_return_if_fail (GTK_IS_COMBO (combo));
774
775   combo->use_arrows = val;
776 }
777
778 void
779 gtk_combo_set_use_arrows_always (GtkCombo * combo, gint val)
780 {
781   g_return_if_fail (combo != NULL);
782   g_return_if_fail (GTK_IS_COMBO (combo));
783
784   combo->use_arrows_always = val;
785   combo->use_arrows = 1;
786 }
787
788 void
789 gtk_combo_set_popdown_strings (GtkCombo * combo, GList * strings)
790 {
791   GList *list;
792   GtkWidget *li;
793
794   g_return_if_fail (combo != NULL);
795   g_return_if_fail (GTK_IS_COMBO (combo));
796   g_return_if_fail (strings != NULL);
797
798   gtk_list_clear_items (GTK_LIST (combo->list), 0, -1);
799   list = strings;
800   while (list)
801     {
802       li = gtk_list_item_new_with_label ((gchar *) list->data);
803       gtk_widget_show (li);
804       gtk_container_add (GTK_CONTAINER (combo->list), li);
805       list = list->next;
806     }
807 }
808
809 static void
810 gtk_combo_item_destroy (GtkObject * object)
811 {
812   gchar *key;
813
814   key = gtk_object_get_data (object, gtk_combo_string_key);
815   if (key)
816     g_free (key);
817 }
818
819 void
820 gtk_combo_set_item_string (GtkCombo * combo, GtkItem * item, const gchar * item_value)
821 {
822   gchar *val;
823   gint connected = 0;
824
825   g_return_if_fail (combo != NULL);
826   g_return_if_fail (GTK_IS_COMBO (combo));
827   g_return_if_fail (item != NULL);
828
829   val = gtk_object_get_data (GTK_OBJECT (item), gtk_combo_string_key);
830   if (val) 
831     {
832       g_free (val);
833       connected = 1;
834     }
835   if (item_value)
836     {
837       val = g_strdup(item_value);
838       gtk_object_set_data (GTK_OBJECT (item), gtk_combo_string_key, val);
839       if (!connected)
840         gtk_signal_connect (GTK_OBJECT (item), "destroy",
841                           (GtkSignalFunc) gtk_combo_item_destroy, val);
842     }
843   else 
844     {
845       gtk_object_set_data (GTK_OBJECT (item), gtk_combo_string_key, NULL);
846       if (connected)
847         gtk_signal_disconnect_by_data(GTK_OBJECT (item), val);
848     }
849 }
850
851 static void
852 gtk_combo_size_allocate (GtkWidget     *widget,
853                          GtkAllocation *allocation)
854 {
855   GtkCombo *combo;
856
857   g_return_if_fail (widget != NULL);
858   g_return_if_fail (GTK_IS_COMBO (widget));
859   g_return_if_fail (allocation != NULL);
860
861   GTK_WIDGET_CLASS (parent_class)->size_allocate (widget, allocation);
862   
863   combo = GTK_COMBO (widget);
864
865   if (combo->entry->allocation.height > combo->entry->requisition.height)
866     {
867       GtkAllocation button_allocation;
868
869       button_allocation = combo->button->allocation;
870       button_allocation.height = combo->entry->requisition.height;
871       button_allocation.y = combo->entry->allocation.y + 
872         (combo->entry->allocation.height - combo->entry->requisition.height) 
873         / 2;
874       gtk_widget_size_allocate (combo->button, &button_allocation);
875     }
876 }
877
878 void
879 gtk_combo_disable_activate (GtkCombo* combo)
880 {
881   g_return_if_fail (combo != NULL);
882   g_return_if_fail (GTK_IS_COMBO (combo));
883
884   if ( combo->activate_id ) {
885     gtk_signal_disconnect(GTK_OBJECT(combo->entry), combo->activate_id);
886     combo->activate_id = 0;
887   }
888 }