]> Pileus Git - ~andy/gtk/blob - gtk/gtkcombo.c
Add pointer warping to GDK. (#160437)
[~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 /* Do NOT, I repeat, NOT, copy any of the code in this file.
28  * The code here relies on all sorts of internal details of GTK+
29  */
30
31 #undef GTK_DISABLE_DEPRECATED
32
33 #include <config.h>
34 #include <string.h>
35
36 #include "gtkalias.h"
37 #include "gtkarrow.h"
38 #include "gtklabel.h"
39 #include "gtklist.h"
40 #include "gtkentry.h"
41 #include "gtkeventbox.h"
42 #include "gtkbutton.h"
43 #include "gtklistitem.h"
44 #include "gtkscrolledwindow.h"
45 #include "gtkmain.h"
46 #include "gtkwindow.h"
47 #include "gdk/gdkkeysyms.h"
48 #include "gtkcombo.h"
49 #include "gtkframe.h"
50 #include "gtkintl.h"
51
52 const gchar *gtk_combo_string_key = "gtk-combo-string-value";
53
54 #define COMBO_LIST_MAX_HEIGHT   (400)
55 #define EMPTY_LIST_HEIGHT       (15)
56
57 enum {
58   PROP_0,
59   PROP_ENABLE_ARROW_KEYS,
60   PROP_ENABLE_ARROWS_ALWAYS,
61   PROP_CASE_SENSITIVE,
62   PROP_ALLOW_EMPTY,
63   PROP_VALUE_IN_LIST
64 };
65
66 static void         gtk_combo_class_init         (GtkComboClass    *klass);
67 static void         gtk_combo_init               (GtkCombo         *combo);
68 static void         gtk_combo_realize            (GtkWidget        *widget);
69 static void         gtk_combo_unrealize          (GtkWidget        *widget);
70 static void         gtk_combo_destroy            (GtkObject        *combo);
71 static GtkListItem *gtk_combo_find               (GtkCombo         *combo);
72 static gchar *      gtk_combo_func               (GtkListItem      *li);
73 static gint         gtk_combo_focus_idle         (GtkCombo         *combo);
74 static gint         gtk_combo_entry_focus_out    (GtkEntry         *entry,
75                                                   GdkEventFocus    *event,
76                                                   GtkCombo         *combo);
77 static void         gtk_combo_get_pos            (GtkCombo         *combo,
78                                                   gint             *x,
79                                                   gint             *y,
80                                                   gint             *height,
81                                                   gint             *width);
82 static void         gtk_combo_popup_list         (GtkCombo         *combo);
83 static void         gtk_combo_popdown_list       (GtkCombo         *combo);
84
85 static void         gtk_combo_activate           (GtkWidget        *widget,
86                                                   GtkCombo         *combo);
87 static gboolean     gtk_combo_popup_button_press (GtkWidget        *button,
88                                                   GdkEventButton   *event,
89                                                   GtkCombo         *combo);
90 static gboolean     gtk_combo_popup_button_leave (GtkWidget        *button,
91                                                   GdkEventCrossing *event,
92                                                   GtkCombo         *combo);
93 static void         gtk_combo_update_entry       (GtkCombo         *combo);
94 static void         gtk_combo_update_list        (GtkEntry         *entry,
95                                                   GtkCombo         *combo);
96 static gint         gtk_combo_button_press       (GtkWidget        *widget,
97                                                   GdkEvent         *event,
98                                                   GtkCombo         *combo);
99 static void         gtk_combo_button_event_after (GtkWidget        *widget,
100                                                   GdkEvent         *event,
101                                                   GtkCombo         *combo);
102 static gint         gtk_combo_list_enter         (GtkWidget        *widget,
103                                                   GdkEventCrossing *event,
104                                                   GtkCombo         *combo);
105 static gint         gtk_combo_list_key_press     (GtkWidget        *widget,
106                                                   GdkEventKey      *event,
107                                                   GtkCombo         *combo);
108 static gint         gtk_combo_entry_key_press    (GtkEntry         *widget,
109                                                   GdkEventKey      *event,
110                                                   GtkCombo         *combo);
111 static gint         gtk_combo_window_key_press   (GtkWidget        *window,
112                                                   GdkEventKey      *event,
113                                                   GtkCombo         *combo);
114 static void         gtk_combo_size_allocate      (GtkWidget        *widget,
115                                                   GtkAllocation   *allocation);
116 static void         gtk_combo_set_property       (GObject         *object,
117                                                   guint            prop_id,
118                                                   const GValue    *value,
119                                                   GParamSpec      *pspec);
120 static void         gtk_combo_get_property       (GObject         *object,
121                                                   guint            prop_id,
122                                                   GValue          *value,
123                                                   GParamSpec      *pspec);
124 static GtkHBoxClass *parent_class = NULL;
125
126 static void
127 gtk_combo_class_init (GtkComboClass * klass)
128 {
129   GObjectClass *gobject_class;
130   GtkObjectClass *oclass;
131   GtkWidgetClass *widget_class;
132
133   gobject_class = (GObjectClass *) klass;
134   oclass = (GtkObjectClass *) klass;
135   widget_class = (GtkWidgetClass *) klass;
136
137   parent_class = g_type_class_peek_parent (klass);
138
139   gobject_class->set_property = gtk_combo_set_property; 
140   gobject_class->get_property = gtk_combo_get_property; 
141
142   g_object_class_install_property (gobject_class,
143                                    PROP_ENABLE_ARROW_KEYS,
144                                    g_param_spec_boolean ("enable_arrow_keys",
145                                                          P_("Enable arrow keys"),
146                                                          P_("Whether the arrow keys move through the list of items"),
147                                                          TRUE,
148                                                          G_PARAM_READABLE | G_PARAM_WRITABLE));
149   g_object_class_install_property (gobject_class,
150                                    PROP_ENABLE_ARROWS_ALWAYS,
151                                    g_param_spec_boolean ("enable_arrows_always",
152                                                          P_("Always enable arrows"),
153                                                          P_("Obsolete property, ignored"),
154                                                          TRUE,
155                                                          G_PARAM_READABLE | G_PARAM_WRITABLE));
156   g_object_class_install_property (gobject_class,
157                                    PROP_CASE_SENSITIVE,
158                                    g_param_spec_boolean ("case_sensitive",
159                                                          P_("Case sensitive"),
160                                                          P_("Whether list item matching is case sensitive"),
161                                                          FALSE,
162                                                          G_PARAM_READABLE | G_PARAM_WRITABLE));
163
164   g_object_class_install_property (gobject_class,
165                                    PROP_ALLOW_EMPTY,
166                                    g_param_spec_boolean ("allow_empty",
167                                                          P_("Allow empty"),
168                                                          P_("Whether an empty value may be entered in this field"),
169                                                          TRUE,
170                                                          G_PARAM_READABLE | G_PARAM_WRITABLE));
171
172   g_object_class_install_property (gobject_class,
173                                    PROP_VALUE_IN_LIST,
174                                    g_param_spec_boolean ("value_in_list",
175                                                          P_("Value in list"),
176                                                          P_("Whether entered values must already be present in the list"),
177                                                          FALSE,
178                                                          G_PARAM_READABLE | G_PARAM_WRITABLE));
179   
180    
181   oclass->destroy = gtk_combo_destroy;
182   
183   widget_class->size_allocate = gtk_combo_size_allocate;
184   widget_class->realize = gtk_combo_realize;
185   widget_class->unrealize = gtk_combo_unrealize;
186 }
187
188 static void
189 gtk_combo_destroy (GtkObject *object)
190 {
191   GtkCombo *combo = GTK_COMBO (object);
192
193   if (combo->popwin)
194     {
195       gtk_widget_destroy (combo->popwin);
196       g_object_unref (combo->popwin);
197       combo->popwin = NULL;
198     }
199
200   GTK_OBJECT_CLASS (parent_class)->destroy (object);
201 }
202
203 static int
204 gtk_combo_entry_key_press (GtkEntry * entry, GdkEventKey * event, GtkCombo * combo)
205 {
206   GList *li;
207   guint state = event->state & gtk_accelerator_get_default_mod_mask ();
208
209   /* completion */
210   if ((event->keyval == GDK_Tab ||  event->keyval == GDK_KP_Tab) &&
211       state == GDK_MOD1_MASK)
212     {
213       GtkEditable *editable = GTK_EDITABLE (entry);
214       GCompletion * cmpl;
215       gchar* prefix;
216       gchar* nprefix = NULL;
217       gint pos;
218
219       if ( !GTK_LIST (combo->list)->children )
220         return FALSE;
221     
222       cmpl = g_completion_new ((GCompletionFunc)gtk_combo_func);
223       g_completion_add_items (cmpl, GTK_LIST (combo->list)->children);
224
225       pos = gtk_editable_get_position (editable);
226       prefix = gtk_editable_get_chars (editable, 0, pos);
227
228       g_completion_complete_utf8 (cmpl, prefix, &nprefix);
229
230       if (nprefix && strlen (nprefix) > strlen (prefix)) 
231         {
232           gtk_editable_insert_text (editable, g_utf8_offset_to_pointer (nprefix, pos), 
233                                     strlen (nprefix) - strlen (prefix), &pos);
234           gtk_editable_set_position (editable, pos);
235         }
236
237       if (nprefix)
238         g_free (nprefix);
239       g_free (prefix);
240       g_completion_free (cmpl);
241
242       return TRUE;
243     }
244
245   if ((event->keyval == GDK_Down || event->keyval == GDK_KP_Down) &&
246       state == GDK_MOD1_MASK)
247     {
248       gtk_combo_activate (NULL, combo);
249       return TRUE;
250     }
251
252   if (!combo->use_arrows || !GTK_LIST (combo->list)->children)
253     return FALSE;
254
255   gtk_combo_update_list (GTK_ENTRY (combo->entry), combo);
256   li = g_list_find (GTK_LIST (combo->list)->children, gtk_combo_find (combo));
257
258   if (((event->keyval == GDK_Up || event->keyval == GDK_KP_Up) && state == 0) ||
259       ((event->keyval == 'p' || event->keyval == 'P') && state == GDK_MOD1_MASK))
260     {
261       if (!li)
262         li = g_list_last (GTK_LIST (combo->list)->children);
263       else
264         li = li->prev;
265
266       if (li)
267         {
268           gtk_list_select_child (GTK_LIST (combo->list), GTK_WIDGET (li->data));
269           gtk_combo_update_entry (combo);
270         }
271       
272       return TRUE;
273     }
274   if (((event->keyval == GDK_Down || event->keyval == GDK_KP_Down) && state == 0) ||
275       ((event->keyval == 'n' || event->keyval == 'N') && state == GDK_MOD1_MASK))
276     {
277       if (!li)
278         li = GTK_LIST (combo->list)->children;
279       else if (li)
280         li = li->next;
281       if (li)
282         {
283           gtk_list_select_child (GTK_LIST (combo->list), GTK_WIDGET (li->data));
284           gtk_combo_update_entry (combo);
285         }
286       
287       return TRUE;
288     }
289   return FALSE;
290 }
291
292 static int
293 gtk_combo_window_key_press (GtkWidget   *window,
294                             GdkEventKey *event,
295                             GtkCombo    *combo)
296 {
297   guint state = event->state & gtk_accelerator_get_default_mod_mask ();
298
299   if ((event->keyval == GDK_Return || event->keyval == GDK_KP_Enter) &&
300       state == 0)
301     {
302       gtk_combo_popdown_list (combo);
303       gtk_combo_update_entry (combo);
304
305       return TRUE;
306     }
307   else if ((event->keyval == GDK_Up || event->keyval == GDK_KP_Up) &&
308            state == GDK_MOD1_MASK)
309     {
310       gtk_combo_popdown_list (combo);
311
312       return TRUE;
313     }
314   else if ((event->keyval == GDK_space || event->keyval == GDK_KP_Space) &&
315            state == 0)
316     {
317       gtk_combo_update_entry (combo);
318     }
319
320   return FALSE;
321 }
322
323 static GtkListItem *
324 gtk_combo_find (GtkCombo * combo)
325 {
326   const gchar *text;
327   GtkListItem *found = NULL;
328   gchar *ltext;
329   gchar *compare_text;
330   GList *clist;
331
332   text = gtk_entry_get_text (GTK_ENTRY (combo->entry));
333   if (combo->case_sensitive)
334     compare_text = (gchar *)text;
335   else
336     compare_text = g_utf8_casefold (text, -1);
337   
338   for (clist = GTK_LIST (combo->list)->children;
339        !found && clist;
340        clist = clist->next)
341     {
342       ltext = gtk_combo_func (GTK_LIST_ITEM (clist->data));
343       if (!ltext)
344         continue;
345
346       if (!combo->case_sensitive)
347         ltext = g_utf8_casefold (ltext, -1);
348
349       if (strcmp (ltext, compare_text) == 0)
350         found = clist->data;
351
352       if (!combo->case_sensitive)
353         g_free (ltext);
354     }
355
356   if (!combo->case_sensitive)
357     g_free (compare_text);
358
359   return found;
360 }
361
362 static gchar *
363 gtk_combo_func (GtkListItem * li)
364 {
365   GtkWidget *label;
366   gchar *ltext = NULL;
367
368   ltext = g_object_get_data (G_OBJECT (li), gtk_combo_string_key);
369   if (!ltext)
370     {
371       label = GTK_BIN (li)->child;
372       if (!label || !GTK_IS_LABEL (label))
373         return NULL;
374       ltext = (gchar *) gtk_label_get_text (GTK_LABEL (label));
375     }
376   return ltext;
377 }
378
379 static gint
380 gtk_combo_focus_idle (GtkCombo * combo)
381 {
382   if (combo)
383     {
384       GDK_THREADS_ENTER ();
385       gtk_widget_grab_focus (combo->entry);
386       GDK_THREADS_LEAVE ();
387     }
388   return FALSE;
389 }
390
391 static gint
392 gtk_combo_entry_focus_out (GtkEntry * entry, GdkEventFocus * event, GtkCombo * combo)
393 {
394
395   if (combo->value_in_list && !gtk_combo_find (combo))
396     {
397       GSource *focus_idle;
398       
399       /* gdk_beep(); *//* this can be annoying */
400       if (combo->ok_if_empty && !strcmp (gtk_entry_get_text (entry), ""))
401         return FALSE;
402 #ifdef TEST
403       printf ("INVALID ENTRY: `%s'\n", gtk_entry_get_text (entry));
404 #endif
405       gtk_grab_add (GTK_WIDGET (combo));
406       /* this is needed because if we call gtk_widget_grab_focus() 
407          it isn't guaranteed it's the *last* call before the main-loop,
408          so the focus can be lost anyway...
409          the signal_stop_emission doesn't seem to work either...
410        */
411       focus_idle = g_idle_source_new ();
412       g_source_set_closure (focus_idle,
413                             g_cclosure_new_object (G_CALLBACK (gtk_combo_focus_idle),
414                                                    G_OBJECT (combo)));
415       g_source_attach (focus_idle, NULL);
416       
417       /*g_signal_stop_emission_by_name (entry, "focus_out_event"); */
418       return TRUE;
419     }
420   return FALSE;
421 }
422
423 static void
424 gtk_combo_get_pos (GtkCombo * combo, gint * x, gint * y, gint * height, gint * width)
425 {
426   GtkBin *popwin;
427   GtkWidget *widget;
428   GtkScrolledWindow *popup;
429   
430   gint real_height;
431   GtkRequisition list_requisition;
432   gboolean show_hscroll = FALSE;
433   gboolean show_vscroll = FALSE;
434   gint avail_height;
435   gint min_height;
436   gint alloc_width;
437   gint work_height;
438   gint old_height;
439   gint old_width;
440   gint scrollbar_spacing;
441   
442   widget = GTK_WIDGET (combo);
443   popup  = GTK_SCROLLED_WINDOW (combo->popup);
444   popwin = GTK_BIN (combo->popwin);
445
446   scrollbar_spacing = _gtk_scrolled_window_get_scrollbar_spacing (popup);
447
448   gdk_window_get_origin (combo->entry->window, x, y);
449   if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL) 
450     *x -= widget->allocation.width - combo->entry->allocation.width;
451   real_height = MIN (combo->entry->requisition.height, 
452                      combo->entry->allocation.height);
453   *y += real_height;
454   avail_height = gdk_screen_get_height (gtk_widget_get_screen (widget)) - *y;
455   
456   gtk_widget_size_request (combo->list, &list_requisition);
457   min_height = MIN (list_requisition.height, 
458                     popup->vscrollbar->requisition.height);
459   if (!GTK_LIST (combo->list)->children)
460     list_requisition.height += EMPTY_LIST_HEIGHT;
461   
462   alloc_width = (widget->allocation.width -
463                  2 * popwin->child->style->xthickness -
464                  2 * GTK_CONTAINER (popwin->child)->border_width -
465                  2 * GTK_CONTAINER (combo->popup)->border_width -
466                  2 * GTK_CONTAINER (GTK_BIN (popup)->child)->border_width - 
467                  2 * GTK_BIN (popup)->child->style->xthickness);
468   
469   work_height = (2 * popwin->child->style->ythickness +
470                  2 * GTK_CONTAINER (popwin->child)->border_width +
471                  2 * GTK_CONTAINER (combo->popup)->border_width +
472                  2 * GTK_CONTAINER (GTK_BIN (popup)->child)->border_width +
473                  2 * GTK_BIN (popup)->child->style->ythickness);
474   
475   do 
476     {
477       old_width = alloc_width;
478       old_height = work_height;
479       
480       if (!show_hscroll &&
481           alloc_width < list_requisition.width)
482         {
483           GtkRequisition requisition;
484           
485           gtk_widget_size_request (popup->hscrollbar, &requisition);
486           work_height += (requisition.height + scrollbar_spacing);
487           
488           show_hscroll = TRUE;
489         }
490       if (!show_vscroll && 
491           work_height + list_requisition.height > avail_height)
492         {
493           GtkRequisition requisition;
494           
495           if (work_height + min_height > avail_height && 
496               *y - real_height > avail_height)
497             {
498               *y -= (work_height + list_requisition.height + real_height);
499               break;
500             }
501           gtk_widget_size_request (popup->hscrollbar, &requisition);
502           alloc_width -= (requisition.width + scrollbar_spacing);
503           show_vscroll = TRUE;
504         }
505     } while (old_width != alloc_width || old_height != work_height);
506   
507   *width = widget->allocation.width;
508   if (show_vscroll)
509     *height = avail_height;
510   else
511     *height = work_height + list_requisition.height;
512   
513   if (*x < 0)
514     *x = 0;
515 }
516
517 static void
518 gtk_combo_popup_list (GtkCombo *combo)
519 {
520   GtkWidget *toplevel;
521   GtkList *list;
522   gint height, width, x, y;
523   gint old_width, old_height;
524
525   old_width = combo->popwin->allocation.width;
526   old_height  = combo->popwin->allocation.height;
527
528   gtk_combo_get_pos (combo, &x, &y, &height, &width);
529
530   /* workaround for gtk_scrolled_window_size_allocate bug */
531   if (old_width != width || old_height != height)
532     {
533       gtk_widget_hide (GTK_SCROLLED_WINDOW (combo->popup)->hscrollbar);
534       gtk_widget_hide (GTK_SCROLLED_WINDOW (combo->popup)->vscrollbar);
535     }
536
537   gtk_combo_update_list (GTK_ENTRY (combo->entry), combo);
538
539   /* We need to make sure some child of combo->popwin
540    * is focused to disable GtkWindow's automatic
541    * "focus-the-first-item" code. If there is no selected
542    * child, we focus the list itself with some hackery.
543    */
544   list = GTK_LIST (combo->list);
545   
546   if (list->selection)
547     {
548       gtk_widget_grab_focus (list->selection->data);
549     }
550   else
551     {
552       GTK_WIDGET_SET_FLAGS (list, GTK_CAN_FOCUS);
553       gtk_widget_grab_focus (combo->list);
554       GTK_LIST (combo->list)->last_focus_child = NULL;
555       GTK_WIDGET_UNSET_FLAGS (list, GTK_CAN_FOCUS);
556     }
557   
558   gtk_window_move (GTK_WINDOW (combo->popwin), x, y);
559
560   toplevel = gtk_widget_get_toplevel (GTK_WIDGET (combo));
561
562   if (GTK_IS_WINDOW (toplevel))
563     {
564       gtk_window_group_add_window (_gtk_window_get_group (GTK_WINDOW (toplevel)), 
565                                    GTK_WINDOW (combo->popwin));
566     }
567
568   gtk_widget_set_size_request (combo->popwin, width, height);
569   gtk_widget_show (combo->popwin);
570
571   gtk_widget_grab_focus (combo->popwin);
572 }
573
574 static void
575 gtk_combo_popdown_list (GtkCombo *combo)
576 {
577   combo->current_button = 0;
578       
579   if (GTK_BUTTON (combo->button)->in_button)
580     {
581       GTK_BUTTON (combo->button)->in_button = FALSE;
582       gtk_button_released (GTK_BUTTON (combo->button));
583     }
584
585   if (GTK_WIDGET_HAS_GRAB (combo->popwin))
586     {
587       gtk_grab_remove (combo->popwin);
588       gdk_display_pointer_ungrab (gtk_widget_get_display (GTK_WIDGET (combo)),
589                                   gtk_get_current_event_time ());
590       gdk_display_keyboard_ungrab (gtk_widget_get_display (GTK_WIDGET (combo)),
591                                    gtk_get_current_event_time ());
592     }
593   
594   gtk_widget_hide (combo->popwin);
595
596   gtk_window_group_add_window (_gtk_window_get_group (NULL), GTK_WINDOW (combo->popwin));
597 }
598
599 static gboolean
600 popup_grab_on_window (GdkWindow *window,
601                       guint32    activate_time)
602 {
603   if ((gdk_pointer_grab (window, TRUE,
604                          GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
605                          GDK_POINTER_MOTION_MASK,
606                          NULL, NULL, activate_time) == 0))
607     {
608       if (gdk_keyboard_grab (window, TRUE,
609                              activate_time) == 0)
610         return TRUE;
611       else
612         {
613           gdk_display_pointer_ungrab (gdk_drawable_get_display (window),
614                                       activate_time);
615           return FALSE;
616         }
617     }
618
619   return FALSE;
620 }
621
622 static void        
623 gtk_combo_activate (GtkWidget        *widget,
624                     GtkCombo         *combo)
625 {
626   if (!combo->button->window ||
627       !popup_grab_on_window (combo->button->window,
628                              gtk_get_current_event_time ()))
629     return;
630
631   gtk_combo_popup_list (combo);
632   
633   /* This must succeed since we already have the grab */
634   popup_grab_on_window (combo->popwin->window,
635                         gtk_get_current_event_time ());
636
637   if (!GTK_WIDGET_HAS_FOCUS (combo->entry))
638     gtk_widget_grab_focus (combo->entry);
639
640   gtk_grab_add (combo->popwin);
641 }
642
643 static gboolean
644 gtk_combo_popup_button_press (GtkWidget        *button,
645                               GdkEventButton   *event,
646                               GtkCombo         *combo)
647 {
648   if (!GTK_WIDGET_HAS_FOCUS (combo->entry))
649     gtk_widget_grab_focus (combo->entry);
650
651   if (event->button != 1)
652     return FALSE;
653
654   if (!popup_grab_on_window (combo->button->window,
655                              gtk_get_current_event_time ()))
656     return FALSE;
657
658   combo->current_button = event->button;
659
660   gtk_combo_popup_list (combo);
661
662   /* This must succeed since we already have the grab */
663   popup_grab_on_window (combo->popwin->window,
664                         gtk_get_current_event_time ());
665
666   gtk_button_pressed (GTK_BUTTON (button));
667
668   gtk_grab_add (combo->popwin);
669
670   return TRUE;
671 }
672
673 static gboolean
674 gtk_combo_popup_button_leave (GtkWidget        *button,
675                               GdkEventCrossing *event,
676                               GtkCombo         *combo)
677 {
678   /* The idea here is that we want to keep the button down if the
679    * popup is popped up.
680    */
681   return combo->current_button != 0;
682 }
683
684 static void
685 gtk_combo_update_entry (GtkCombo * combo)
686 {
687   GtkList *list = GTK_LIST (combo->list);
688   char *text;
689
690   g_signal_handler_block (list, combo->list_change_id);
691   if (list->selection)
692     {
693       text = gtk_combo_func (GTK_LIST_ITEM (list->selection->data));
694       if (!text)
695         text = "";
696       gtk_entry_set_text (GTK_ENTRY (combo->entry), text);
697     }
698   g_signal_handler_unblock (list, combo->list_change_id);
699 }
700
701 static void
702 gtk_combo_selection_changed (GtkList  *list,
703                              GtkCombo *combo)
704 {
705   if (!GTK_WIDGET_VISIBLE (combo->popwin))
706     gtk_combo_update_entry (combo);
707 }
708
709 static void
710 gtk_combo_update_list (GtkEntry * entry, GtkCombo * combo)
711 {
712   GtkList *list = GTK_LIST (combo->list);
713   GList *slist = list->selection;
714   GtkListItem *li;
715
716   gtk_grab_remove (GTK_WIDGET (combo));
717
718   g_signal_handler_block (entry, combo->entry_change_id);
719   if (slist && slist->data)
720     gtk_list_unselect_child (list, GTK_WIDGET (slist->data));
721   li = gtk_combo_find (combo);
722   if (li)
723     gtk_list_select_child (list, GTK_WIDGET (li));
724   g_signal_handler_unblock (entry, combo->entry_change_id);
725 }
726
727 static gint
728 gtk_combo_button_press (GtkWidget * widget, GdkEvent * event, GtkCombo * combo)
729 {
730   GtkWidget *child;
731
732   child = gtk_get_event_widget (event);
733
734   /* We don't ask for button press events on the grab widget, so
735    *  if an event is reported directly to the grab widget, it must
736    *  be on a window outside the application (and thus we remove
737    *  the popup window). Otherwise, we check if the widget is a child
738    *  of the grab widget, and only remove the popup window if it
739    *  is not.
740    */
741   if (child != widget)
742     {
743       while (child)
744         {
745           if (child == widget)
746             return FALSE;
747           child = child->parent;
748         }
749     }
750
751   gtk_combo_popdown_list (combo);
752
753   return TRUE;
754 }
755
756 static gboolean
757 is_within (GtkWidget *widget,
758            GtkWidget *ancestor)
759 {
760   return widget == ancestor || gtk_widget_is_ancestor (widget, ancestor);
761 }
762
763 static void
764 gtk_combo_button_event_after (GtkWidget *widget,
765                               GdkEvent  *event,
766                               GtkCombo  *combo)
767 {
768   GtkWidget *child;
769
770   if (event->type != GDK_BUTTON_RELEASE)
771     return;
772   
773   child = gtk_get_event_widget ((GdkEvent*) event);
774
775   if ((combo->current_button != 0) && (event->button.button == 1))
776     {
777       /* This was the initial button press */
778
779       combo->current_button = 0;
780
781       /* Check to see if we released inside the button */
782       if (child && is_within (child, combo->button))
783         {
784           gtk_grab_add (combo->popwin);
785           gdk_pointer_grab (combo->popwin->window, TRUE,
786                             GDK_BUTTON_PRESS_MASK | 
787                             GDK_BUTTON_RELEASE_MASK |
788                             GDK_POINTER_MOTION_MASK, 
789                             NULL, NULL, GDK_CURRENT_TIME);
790           return;
791         }
792     }
793
794   if (is_within (child, combo->list))
795     gtk_combo_update_entry (combo);
796     
797   gtk_combo_popdown_list (combo);
798
799 }
800
801 static void
802 find_child_foreach (GtkWidget *widget,
803                     gpointer   data)
804 {
805   GdkEventButton *event = data;
806
807   if (!event->window)
808     {
809       if (event->x >= widget->allocation.x &&
810           event->x < widget->allocation.x + widget->allocation.width &&
811           event->y >= widget->allocation.y &&
812           event->y < widget->allocation.y + widget->allocation.height)
813         event->window = g_object_ref (widget->window);
814     }
815 }
816
817 static void
818 find_child_window (GtkContainer   *container,
819                    GdkEventButton *event)
820 {
821   gtk_container_foreach (container, find_child_foreach, event);
822 }
823
824 static gint         
825 gtk_combo_list_enter (GtkWidget        *widget,
826                       GdkEventCrossing *event,
827                       GtkCombo         *combo)
828 {
829   GtkWidget *event_widget;
830
831   event_widget = gtk_get_event_widget ((GdkEvent*) event);
832
833   if ((event_widget == combo->list) &&
834       (combo->current_button != 0) && 
835       (!GTK_WIDGET_HAS_GRAB (combo->list)))
836     {
837       GdkEvent *tmp_event = gdk_event_new (GDK_BUTTON_PRESS);
838       gint x, y;
839       GdkModifierType mask;
840
841       gtk_grab_remove (combo->popwin);
842
843       /* Transfer the grab over to the list by synthesizing
844        * a button press event
845        */
846       gdk_window_get_pointer (combo->list->window, &x, &y, &mask);
847
848       tmp_event->button.send_event = TRUE;
849       tmp_event->button.time = GDK_CURRENT_TIME; /* bad */
850       tmp_event->button.x = x;
851       tmp_event->button.y = y;
852       /* We leave all the XInput fields unfilled here, in the expectation
853        * that GtkList doesn't care.
854        */
855       tmp_event->button.button = combo->current_button;
856       tmp_event->button.state = mask;
857
858       find_child_window (GTK_CONTAINER (combo->list), &tmp_event->button);
859       if (!tmp_event->button.window)
860         {
861           GtkWidget *child;
862           
863           if (GTK_LIST (combo->list)->children)
864             child = GTK_LIST (combo->list)->children->data;
865           else
866             child = combo->list;
867
868           tmp_event->button.window = g_object_ref (child->window);
869         }
870
871       gtk_widget_event (combo->list, tmp_event);
872       gdk_event_free (tmp_event);
873     }
874
875   return FALSE;
876 }
877
878 static int
879 gtk_combo_list_key_press (GtkWidget * widget, GdkEventKey * event, GtkCombo * combo)
880 {
881   guint state = event->state & gtk_accelerator_get_default_mod_mask ();
882
883   if (event->keyval == GDK_Escape && state == 0)
884     {
885       if (GTK_WIDGET_HAS_GRAB (combo->list))
886         gtk_list_end_drag_selection (GTK_LIST (combo->list));
887
888       gtk_combo_popdown_list (combo);
889       
890       return TRUE;
891     }
892   return FALSE;
893 }
894
895 static void
896 combo_event_box_realize (GtkWidget *widget)
897 {
898   GdkCursor *cursor = gdk_cursor_new_for_display (gtk_widget_get_display (widget),
899                                                   GDK_TOP_LEFT_ARROW);
900   gdk_window_set_cursor (widget->window, cursor);
901   gdk_cursor_unref (cursor);
902 }
903
904 static void
905 gtk_combo_init (GtkCombo * combo)
906 {
907   GtkWidget *arrow;
908   GtkWidget *frame;
909   GtkWidget *event_box;
910
911   combo->case_sensitive = FALSE;
912   combo->value_in_list = FALSE;
913   combo->ok_if_empty = TRUE;
914   combo->use_arrows = TRUE;
915   combo->use_arrows_always = FALSE;
916   combo->entry = gtk_entry_new ();
917   combo->button = gtk_button_new ();
918   combo->current_button = 0;
919   arrow = gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_OUT);
920   gtk_widget_show (arrow);
921   gtk_container_add (GTK_CONTAINER (combo->button), arrow);
922   gtk_box_pack_start (GTK_BOX (combo), combo->entry, TRUE, TRUE, 0);
923   gtk_box_pack_end (GTK_BOX (combo), combo->button, FALSE, FALSE, 0);
924   GTK_WIDGET_UNSET_FLAGS (combo->button, GTK_CAN_FOCUS);
925   gtk_widget_show (combo->entry);
926   gtk_widget_show (combo->button);
927   combo->entry_change_id = g_signal_connect (combo->entry, "changed",
928                                              G_CALLBACK (gtk_combo_update_list),
929                                              combo);
930   g_signal_connect_after (combo->entry, "key_press_event",
931                           G_CALLBACK (gtk_combo_entry_key_press), combo);
932   g_signal_connect_after (combo->entry, "focus_out_event",
933                           G_CALLBACK (gtk_combo_entry_focus_out), combo);
934   combo->activate_id = g_signal_connect (combo->entry, "activate",
935                                          G_CALLBACK (gtk_combo_activate),
936                                          combo);
937   g_signal_connect (combo->button, "button_press_event",
938                     G_CALLBACK (gtk_combo_popup_button_press), combo);
939   g_signal_connect (combo->button, "leave_notify_event",
940                     G_CALLBACK (gtk_combo_popup_button_leave), combo);
941
942   combo->popwin = gtk_window_new (GTK_WINDOW_POPUP);
943   g_object_ref (combo->popwin);
944   gtk_window_set_resizable (GTK_WINDOW (combo->popwin), FALSE);
945
946   g_signal_connect (combo->popwin, "key_press_event",
947                     G_CALLBACK (gtk_combo_window_key_press), combo);
948   
949   gtk_widget_set_events (combo->popwin, GDK_KEY_PRESS_MASK);
950
951   event_box = gtk_event_box_new ();
952   gtk_container_add (GTK_CONTAINER (combo->popwin), event_box);
953   g_signal_connect (event_box, "realize",
954                     G_CALLBACK (combo_event_box_realize), NULL);
955   gtk_widget_show (event_box);
956
957
958   frame = gtk_frame_new (NULL);
959   gtk_container_add (GTK_CONTAINER (event_box), frame);
960   gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT);
961   gtk_widget_show (frame);
962
963   combo->popup = gtk_scrolled_window_new (NULL, NULL);
964   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (combo->popup),
965                                   GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
966   GTK_WIDGET_UNSET_FLAGS (GTK_SCROLLED_WINDOW (combo->popup)->hscrollbar, GTK_CAN_FOCUS);
967   GTK_WIDGET_UNSET_FLAGS (GTK_SCROLLED_WINDOW (combo->popup)->vscrollbar, GTK_CAN_FOCUS);
968   gtk_container_add (GTK_CONTAINER (frame), combo->popup);
969   gtk_widget_show (combo->popup);
970
971   combo->list = gtk_list_new ();
972   /* We'll use enter notify events to figure out when to transfer
973    * the grab to the list
974    */
975   gtk_widget_set_events (combo->list, GDK_ENTER_NOTIFY_MASK);
976
977   gtk_list_set_selection_mode (GTK_LIST(combo->list), GTK_SELECTION_BROWSE);
978   gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (combo->popup), combo->list);
979   gtk_container_set_focus_vadjustment (GTK_CONTAINER (combo->list),
980                                        gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (combo->popup)));
981   gtk_container_set_focus_hadjustment (GTK_CONTAINER (combo->list),
982                                        gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (combo->popup)));
983   gtk_widget_show (combo->list);
984
985   combo->list_change_id = g_signal_connect (combo->list, "selection_changed",
986                                             G_CALLBACK (gtk_combo_selection_changed), combo);
987   
988   g_signal_connect (combo->popwin, "key_press_event",
989                     G_CALLBACK (gtk_combo_list_key_press), combo);
990   g_signal_connect (combo->popwin, "button_press_event",
991                     G_CALLBACK (gtk_combo_button_press), combo);
992
993   g_signal_connect (combo->popwin, "event_after",
994                     G_CALLBACK (gtk_combo_button_event_after), combo);
995   g_signal_connect (combo->list, "event_after",
996                     G_CALLBACK (gtk_combo_button_event_after), combo);
997
998   g_signal_connect (combo->list, "enter_notify_event",
999                     G_CALLBACK (gtk_combo_list_enter), combo);
1000 }
1001
1002 static void
1003 gtk_combo_realize (GtkWidget *widget)
1004 {
1005   GtkCombo *combo = GTK_COMBO (widget);
1006
1007   gtk_window_set_screen (GTK_WINDOW (combo->popwin), 
1008                          gtk_widget_get_screen (widget));
1009   
1010   GTK_WIDGET_CLASS( parent_class )->realize (widget);  
1011 }
1012
1013 static void        
1014 gtk_combo_unrealize (GtkWidget *widget)
1015 {
1016   GtkCombo *combo = GTK_COMBO (widget);
1017
1018   gtk_combo_popdown_list (combo);
1019   gtk_widget_unrealize (combo->popwin);
1020   
1021   GTK_WIDGET_CLASS (parent_class)->unrealize (widget);
1022 }
1023
1024 GType
1025 gtk_combo_get_type (void)
1026 {
1027   static GType combo_type = 0;
1028
1029   if (!combo_type)
1030     {
1031       static const GTypeInfo combo_info =
1032       {
1033         sizeof (GtkComboClass),
1034         NULL,           /* base_init */
1035         NULL,           /* base_finalize */
1036         (GClassInitFunc) gtk_combo_class_init,
1037         NULL,           /* class_finalize */
1038         NULL,           /* class_data */
1039         sizeof (GtkCombo),
1040         0,              /* n_preallocs */
1041         (GInstanceInitFunc) gtk_combo_init,
1042       };
1043
1044       combo_type = g_type_register_static (GTK_TYPE_HBOX, "GtkCombo",
1045                                            &combo_info, 0);
1046     }
1047
1048   return combo_type;
1049 }
1050
1051 GtkWidget*
1052 gtk_combo_new (void)
1053 {
1054   return g_object_new (GTK_TYPE_COMBO, NULL);
1055 }
1056
1057 void
1058 gtk_combo_set_value_in_list (GtkCombo * combo, gboolean val, gboolean ok_if_empty)
1059 {
1060   g_return_if_fail (GTK_IS_COMBO (combo));
1061   val = val != FALSE;
1062   ok_if_empty = ok_if_empty != FALSE;
1063
1064   g_object_freeze_notify (G_OBJECT (combo));
1065   if (combo->value_in_list != val)
1066     {
1067        combo->value_in_list = val;
1068   g_object_notify (G_OBJECT (combo), "value_in_list");
1069     }
1070   if (combo->ok_if_empty != ok_if_empty)
1071     {
1072        combo->ok_if_empty = ok_if_empty;
1073   g_object_notify (G_OBJECT (combo), "allow_empty");
1074     }
1075   g_object_thaw_notify (G_OBJECT (combo));
1076 }
1077
1078 void
1079 gtk_combo_set_case_sensitive (GtkCombo * combo, gboolean val)
1080 {
1081   g_return_if_fail (GTK_IS_COMBO (combo));
1082   val = val != FALSE;
1083
1084   if (combo->case_sensitive != val) 
1085     {
1086   combo->case_sensitive = val;
1087   g_object_notify (G_OBJECT (combo), "case_sensitive");
1088     }
1089 }
1090
1091 void
1092 gtk_combo_set_use_arrows (GtkCombo * combo, gboolean val)
1093 {
1094   g_return_if_fail (GTK_IS_COMBO (combo));
1095   val = val != FALSE;
1096
1097   if (combo->use_arrows != val) 
1098     {
1099   combo->use_arrows = val;
1100   g_object_notify (G_OBJECT (combo), "enable_arrow_keys");
1101     }
1102 }
1103
1104 void
1105 gtk_combo_set_use_arrows_always (GtkCombo * combo, gboolean val)
1106 {
1107   g_return_if_fail (GTK_IS_COMBO (combo));
1108   val = val != FALSE;
1109
1110   if (combo->use_arrows_always != val) 
1111     {
1112        g_object_freeze_notify (G_OBJECT (combo));
1113   combo->use_arrows_always = val;
1114        g_object_notify (G_OBJECT (combo), "enable_arrows_always");
1115
1116        if (combo->use_arrows != TRUE) 
1117          {
1118   combo->use_arrows = TRUE;
1119   g_object_notify (G_OBJECT (combo), "enable_arrow_keys");
1120          }
1121   g_object_thaw_notify (G_OBJECT (combo));
1122     }
1123 }
1124
1125 void
1126 gtk_combo_set_popdown_strings (GtkCombo *combo, 
1127                                GList    *strings)
1128 {
1129   GList *list;
1130   GtkWidget *li;
1131
1132   g_return_if_fail (GTK_IS_COMBO (combo));
1133
1134   gtk_combo_popdown_list (combo);
1135
1136   gtk_list_clear_items (GTK_LIST (combo->list), 0, -1);
1137   list = strings;
1138   while (list)
1139     {
1140       li = gtk_list_item_new_with_label ((gchar *) list->data);
1141       gtk_widget_show (li);
1142       gtk_container_add (GTK_CONTAINER (combo->list), li);
1143       list = list->next;
1144     }
1145 }
1146
1147 void
1148 gtk_combo_set_item_string (GtkCombo * combo, GtkItem * item, const gchar * item_value)
1149 {
1150   g_return_if_fail (GTK_IS_COMBO (combo));
1151   g_return_if_fail (item != NULL);
1152
1153   g_object_set_data_full (G_OBJECT (item), gtk_combo_string_key,
1154                           g_strdup (item_value), g_free);
1155 }
1156
1157 static void
1158 gtk_combo_size_allocate (GtkWidget     *widget,
1159                          GtkAllocation *allocation)
1160 {
1161   GtkCombo *combo;
1162
1163   g_return_if_fail (GTK_IS_COMBO (widget));
1164   g_return_if_fail (allocation != NULL);
1165
1166   GTK_WIDGET_CLASS (parent_class)->size_allocate (widget, allocation);
1167   
1168   combo = GTK_COMBO (widget);
1169
1170   if (combo->entry->allocation.height > combo->entry->requisition.height)
1171     {
1172       GtkAllocation button_allocation;
1173
1174       button_allocation = combo->button->allocation;
1175       button_allocation.height = combo->entry->requisition.height;
1176       button_allocation.y = combo->entry->allocation.y + 
1177         (combo->entry->allocation.height - combo->entry->requisition.height) 
1178         / 2;
1179       gtk_widget_size_allocate (combo->button, &button_allocation);
1180     }
1181 }
1182
1183 void
1184 gtk_combo_disable_activate (GtkCombo* combo)
1185 {
1186   g_return_if_fail (GTK_IS_COMBO (combo));
1187
1188   if ( combo->activate_id ) {
1189     g_signal_handler_disconnect (combo->entry, combo->activate_id);
1190     combo->activate_id = 0;
1191   }
1192 }
1193
1194 static void
1195 gtk_combo_set_property (GObject         *object,
1196                         guint            prop_id,
1197                         const GValue    *value,
1198                         GParamSpec      *pspec)
1199 {
1200   GtkCombo *combo = GTK_COMBO (object);
1201   
1202   switch (prop_id)
1203     {
1204     case PROP_ENABLE_ARROW_KEYS:
1205       gtk_combo_set_use_arrows (combo, g_value_get_boolean (value));
1206       break;
1207     case PROP_ENABLE_ARROWS_ALWAYS:
1208       gtk_combo_set_use_arrows_always (combo, g_value_get_boolean (value));
1209       break;
1210     case PROP_CASE_SENSITIVE:
1211       gtk_combo_set_case_sensitive (combo, g_value_get_boolean (value));
1212       break;
1213     case PROP_ALLOW_EMPTY:
1214       combo->ok_if_empty = g_value_get_boolean (value);
1215       break;
1216     case PROP_VALUE_IN_LIST:
1217       combo->value_in_list = g_value_get_boolean (value);
1218       break;
1219     default:
1220       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1221       break;
1222     }
1223   
1224 }
1225
1226 static void
1227 gtk_combo_get_property (GObject         *object,
1228                         guint            prop_id,
1229                         GValue          *value,
1230                         GParamSpec      *pspec)
1231 {
1232   GtkCombo *combo = GTK_COMBO (object);
1233   
1234   switch (prop_id)
1235     {
1236     case PROP_ENABLE_ARROW_KEYS:
1237       g_value_set_boolean (value, combo->use_arrows);
1238       break;
1239     case PROP_ENABLE_ARROWS_ALWAYS:
1240       g_value_set_boolean (value, combo->use_arrows_always);
1241       break;
1242     case PROP_CASE_SENSITIVE:
1243       g_value_set_boolean (value, combo->case_sensitive);
1244       break;
1245     case PROP_ALLOW_EMPTY:
1246       g_value_set_boolean (value, combo->ok_if_empty);
1247       break;
1248     case PROP_VALUE_IN_LIST:
1249       g_value_set_boolean (value, combo->value_in_list);
1250       break;
1251     default:
1252       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1253       break;
1254     }
1255    
1256 }