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