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