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