]> git.sur5r.net Git - glabels/blob - src/mygal/mygal-combo-box.c
Imported Upstream version 2.2.8
[glabels] / src / mygal / mygal-combo-box.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * mygal-combo-box.c - a customizable combobox
4  * Copyright 2000, 2001, Ximian, Inc.
5  *
6  * Authors:
7  *   Miguel de Icaza (miguel@gnu.org)
8  *   Adrian E Feiguin (feiguin@ifir.edu.ar)
9  *   Paolo Molnaro (lupus@debian.org).
10  *   Jon K Hellan (hellan@acm.org)
11  *
12  * Modified for gLabels by:
13  *   Jim Evins <evins@snaught.com>
14  *
15  * This library is free software; you can redistribute it and/or
16  * modify it under the terms of the GNU General Public
17  * License, version 2, as published by the Free Software Foundation.
18  *
19  * This library is distributed in the hope that it will be useful, but
20  * WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
22  * General Public License for more details.
23  *
24  * You should have received a copy of the GNU General Public
25  * License along with this library; if not, write to the Free Software
26  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
27  * 02111-1307, USA.
28  */
29
30 #include <config.h>
31 #include <gtk/gtkhbox.h>
32 #include <gtk/gtktogglebutton.h>
33 #include <gtk/gtkarrow.h>
34 #include <gtk/gtkeventbox.h>
35 #include <gtk/gtkmain.h>
36 #include <gtk/gtksignal.h>
37 #include <gtk/gtkwindow.h>
38 #include <gtk/gtkframe.h>
39 #include <gtk/gtkvbox.h>
40 #include <gtk/gtktearoffmenuitem.h>
41 #include <gdk/gdkkeysyms.h>
42 #include "mygal-combo-box.h"
43 #include "e-util.h"
44
45 #define PARENT_TYPE GTK_TYPE_HBOX
46 static GObjectClass *mygal_combo_box_parent_class;
47
48 static int mygal_combo_toggle_pressed (GtkToggleButton *tbutton,
49                                      MygalComboBox *combo_box);
50 static void mygal_combo_popup_tear_off (MygalComboBox *combo,
51                                       gboolean set_position);
52 static void mygal_combo_set_tearoff_state (MygalComboBox *combo,
53                                          gboolean torn_off);
54 static void mygal_combo_popup_reparent (GtkWidget *popup, GtkWidget *new_parent, 
55                                       gboolean unrealize);
56 static gboolean cb_popup_delete (GtkWidget *w, GdkEventAny *event,
57                              MygalComboBox *combo);
58 static void mygal_combo_tearoff_bg_copy (MygalComboBox *combo);
59
60 enum {
61         POP_DOWN_WIDGET,
62         POP_DOWN_DONE,
63         PRE_POP_DOWN,
64         POST_POP_HIDE,
65         LAST_SIGNAL
66 };
67
68 static guint mygal_combo_box_signals [LAST_SIGNAL] = { 0, };
69
70 struct _MygalComboBoxPrivate {
71         GtkWidget *pop_down_widget;
72         GtkWidget *display_widget;
73
74         /*
75          * Internal widgets used to implement the ComboBox
76          */
77         GtkWidget *frame;
78         GtkWidget *arrow_button;
79
80         GtkWidget *toplevel;    /* Popup's toplevel when not torn off */
81         GtkWidget *tearoff_window; /* Popup's toplevel when torn off */
82         guint torn_off;
83         
84         GtkWidget *tearable;    /* The tearoff "button" */
85         GtkWidget *popup;       /* Popup */
86
87         /*
88          * Closure for invoking the callbacks above
89          */
90         void *closure;
91 };
92
93 static void
94 mygal_combo_box_finalize (GObject *object)
95 {
96         MygalComboBox *combo_box = MYGAL_COMBO_BOX (object);
97
98         g_free (combo_box->priv);
99
100         mygal_combo_box_parent_class->finalize (object);
101 }
102
103 static void
104 mygal_combo_box_destroy (GtkObject *object)
105 {
106         GtkObjectClass *klass = (GtkObjectClass *)mygal_combo_box_parent_class;
107         MygalComboBox *combo_box = MYGAL_COMBO_BOX (object);
108
109         if (combo_box->priv->toplevel) {
110                 gtk_object_destroy (GTK_OBJECT (combo_box->priv->toplevel));
111                 combo_box->priv->toplevel = NULL;
112         }
113
114         if (combo_box->priv->tearoff_window) {
115                 gtk_object_destroy (GTK_OBJECT (combo_box->priv->tearoff_window));
116                 combo_box->priv->tearoff_window = NULL;
117         }
118
119         if (klass->destroy)
120                 klass->destroy (object);
121 }
122
123 static gboolean
124 mygal_combo_box_mnemonic_activate (GtkWidget *w, gboolean group_cycling)
125 {
126         MygalComboBox *combo_box = MYGAL_COMBO_BOX (w);
127         gtk_toggle_button_set_active (
128                 GTK_TOGGLE_BUTTON (combo_box->priv->arrow_button), TRUE);
129         return TRUE;
130 }
131
132 static void
133 mygal_combo_box_class_init (GObjectClass *object_class)
134 {
135         GtkWidgetClass *widget_class = (GtkWidgetClass *)object_class;
136         mygal_combo_box_parent_class = g_type_class_peek_parent (object_class);
137
138         object_class->finalize = mygal_combo_box_finalize;
139         widget_class->mnemonic_activate = mygal_combo_box_mnemonic_activate;
140         ((GtkObjectClass *)object_class)->destroy = mygal_combo_box_destroy;
141
142         mygal_combo_box_signals [POP_DOWN_WIDGET] = g_signal_new (
143                 "pop_down_widget",
144                 G_OBJECT_CLASS_TYPE (object_class),
145                 G_SIGNAL_RUN_LAST,
146                 G_STRUCT_OFFSET (MygalComboBoxClass, pop_down_widget),
147                 NULL, NULL,
148                 e_marshal_POINTER__NONE,
149                 G_TYPE_POINTER, 0, G_TYPE_NONE);
150
151         mygal_combo_box_signals [POP_DOWN_DONE] = g_signal_new (
152                 "pop_down_done",
153                 G_OBJECT_CLASS_TYPE (object_class),
154                 G_SIGNAL_RUN_LAST,
155                 G_STRUCT_OFFSET (MygalComboBoxClass, pop_down_done),
156                 NULL, NULL,
157                 e_marshal_BOOLEAN__OBJECT,
158                 G_TYPE_BOOLEAN, 1, G_TYPE_OBJECT);
159
160         mygal_combo_box_signals [PRE_POP_DOWN] = g_signal_new (
161                 "pre_pop_down",
162                 G_OBJECT_CLASS_TYPE (object_class),
163                 G_SIGNAL_RUN_LAST,
164                 G_STRUCT_OFFSET (MygalComboBoxClass, pre_pop_down),
165                 NULL, NULL,
166                 e_marshal_NONE__NONE,
167                 G_TYPE_NONE, 0);
168
169         mygal_combo_box_signals [POST_POP_HIDE] = g_signal_new (
170                 "post_pop_hide",
171                 G_OBJECT_CLASS_TYPE (object_class),
172                 G_SIGNAL_RUN_LAST,
173                 G_STRUCT_OFFSET (MygalComboBoxClass, post_pop_hide),
174                 NULL, NULL,
175                 e_marshal_NONE__NONE,
176                 G_TYPE_NONE, 0);
177 }
178
179 static void
180 deactivate_arrow (MygalComboBox *combo_box)
181 {
182         GtkToggleButton *arrow;
183
184         arrow = GTK_TOGGLE_BUTTON (combo_box->priv->arrow_button);
185         g_signal_handlers_block_matched (arrow,
186                                          G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA,
187                                          0, 0, NULL,
188                                          mygal_combo_toggle_pressed, combo_box);
189
190         gtk_toggle_button_set_active (arrow, FALSE);
191         
192         g_signal_handlers_unblock_matched (arrow,
193                                            G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA,
194                                            0, 0, NULL,
195                                            mygal_combo_toggle_pressed, combo_box);
196 }
197
198 /**
199  * mygal_combo_box_popup_hide_unconditional
200  * @combo_box:  Combo box
201  *
202  * Hide popup, whether or not it is torn off.
203  */
204 static void
205 mygal_combo_box_popup_hide_unconditional (MygalComboBox *combo_box)
206 {
207         gboolean popup_info_destroyed = FALSE;
208
209         g_return_if_fail (combo_box != NULL);
210         g_return_if_fail (MYGAL_IS_COMBO_BOX (combo_box));
211
212         gtk_widget_hide (combo_box->priv->toplevel);
213         gtk_widget_hide (combo_box->priv->popup);
214         if (combo_box->priv->torn_off) {
215                 GTK_TEAROFF_MENU_ITEM (combo_box->priv->tearable)->torn_off
216                         = FALSE;
217                 mygal_combo_set_tearoff_state (combo_box, FALSE);
218         }
219         
220         gtk_grab_remove (combo_box->priv->toplevel);
221         gdk_pointer_ungrab (GDK_CURRENT_TIME);
222                 
223         g_object_ref (combo_box->priv->pop_down_widget);
224         g_signal_emit (combo_box,
225                        mygal_combo_box_signals [POP_DOWN_DONE], 0,
226                        combo_box->priv->pop_down_widget, &popup_info_destroyed);
227
228         if (popup_info_destroyed){
229                 gtk_container_remove (
230                         GTK_CONTAINER (combo_box->priv->frame),
231                         combo_box->priv->pop_down_widget);
232                 combo_box->priv->pop_down_widget = NULL;
233         }
234         g_object_unref (combo_box->priv->pop_down_widget);
235         deactivate_arrow (combo_box);
236
237         g_signal_emit (combo_box, mygal_combo_box_signals [POST_POP_HIDE], 0);
238 }
239
240 /**
241  * mygal_combo_box_popup_hide:
242  * @combo_box:  Combo box
243  *
244  * Hide popup, but not when it is torn off.
245  * This is the external interface - for subclasses and apps which expect a
246  * regular combo which doesn't do tearoffs.
247  */
248 void
249 mygal_combo_box_popup_hide (MygalComboBox *combo_box)
250 {
251         if (!combo_box->priv->torn_off)
252                 mygal_combo_box_popup_hide_unconditional (combo_box);
253         else if (GTK_WIDGET_VISIBLE (combo_box->priv->toplevel)) {
254                 /* Both popup and tearoff window present. Get rid of just
255                    the popup shell. */
256                 mygal_combo_popup_tear_off (combo_box, FALSE);
257                 deactivate_arrow (combo_box);
258         }                
259 }
260
261 /*
262  * Find best location for displaying
263  */
264 void
265 mygal_combo_box_get_pos (MygalComboBox *combo_box, int *x, int *y)
266 {
267         GtkWidget *wcombo = GTK_WIDGET (combo_box);
268         int ph, pw;
269
270         gdk_window_get_origin (wcombo->window, x, y);
271         *y += wcombo->allocation.height + wcombo->allocation.y;
272         *x += wcombo->allocation.x;
273
274         ph = combo_box->priv->popup->allocation.height;
275         pw = combo_box->priv->popup->allocation.width;
276
277         if ((*y + ph) > gdk_screen_height ())
278                 *y = gdk_screen_height () - ph;
279
280         if ((*x + pw) > gdk_screen_width ())
281                 *x = gdk_screen_width () - pw;
282 }
283
284 static void
285 mygal_combo_box_popup_display (MygalComboBox *combo_box)
286 {
287         int x, y;
288
289         g_return_if_fail (combo_box != NULL);
290         g_return_if_fail (MYGAL_IS_COMBO_BOX (combo_box));
291         
292         /*
293          * If we have no widget to display on the popdown,
294          * create it
295          */
296         if (!combo_box->priv->pop_down_widget){
297                 GtkWidget *pw = NULL;
298
299                 g_signal_emit (combo_box,
300                                mygal_combo_box_signals [POP_DOWN_WIDGET], 0, &pw);
301                 g_assert (pw != NULL);
302                 combo_box->priv->pop_down_widget = pw;
303                 gtk_container_add (GTK_CONTAINER (combo_box->priv->frame), pw);
304         }
305
306         g_signal_emit (combo_box, mygal_combo_box_signals [PRE_POP_DOWN], 0);
307         
308         if (combo_box->priv->torn_off) {
309                 /* To give the illusion that tearoff still displays the
310                  * popup, we copy the image in the popup window to the
311                  * background. Thus, it won't be blank after reparenting */
312                 mygal_combo_tearoff_bg_copy (combo_box);
313
314                 /* We force an unrealize here so that we don't trigger
315                  * redrawing/ clearing code - we just want to reveal our
316                  * backing pixmap.
317                  */
318                 mygal_combo_popup_reparent (combo_box->priv->popup,
319                                           combo_box->priv->toplevel, TRUE);
320         }
321
322         mygal_combo_box_get_pos (combo_box, &x, &y);
323         
324         gtk_widget_set_uposition (combo_box->priv->toplevel, x, y);
325         gtk_widget_realize (combo_box->priv->popup);
326         gtk_widget_show (combo_box->priv->popup);
327         gtk_widget_realize (combo_box->priv->toplevel);
328         gtk_widget_show (combo_box->priv->toplevel);
329         
330         gtk_grab_add (combo_box->priv->toplevel);
331         gdk_pointer_grab (combo_box->priv->toplevel->window, TRUE,
332                           GDK_BUTTON_PRESS_MASK |
333                           GDK_BUTTON_RELEASE_MASK |
334                           GDK_POINTER_MOTION_MASK,
335                           NULL, NULL, GDK_CURRENT_TIME);
336 }
337
338 static int
339 mygal_combo_toggle_pressed (GtkToggleButton *tbutton, MygalComboBox *combo_box)
340 {
341         if (tbutton->active)
342                 mygal_combo_box_popup_display (combo_box);
343         else
344                 mygal_combo_box_popup_hide_unconditional (combo_box);
345
346         return TRUE;
347 }
348
349 static  gint
350 mygal_combo_box_button_press (GtkWidget *widget, GdkEventButton *event, MygalComboBox *combo_box)
351 {
352         GtkWidget *child;
353
354         child = gtk_get_event_widget ((GdkEvent *) event);
355         if (child != widget){
356                 while (child){
357                         if (child == widget)
358                                 return FALSE;
359                         child = child->parent;
360                 }
361         }
362
363         mygal_combo_box_popup_hide (combo_box);
364         return TRUE;
365 }
366
367 /**
368  * mygal_combo_box_key_press
369  * @widget:     Widget
370  * @event:      Event
371  * @combo_box:  Combo box
372  *
373  * Key press handler which dismisses popup on escape.
374  * Popup is dismissed whether or not popup is torn off.
375  */
376 static  gint
377 mygal_combo_box_key_press (GtkWidget *widget, GdkEventKey *event,
378                          MygalComboBox *combo_box)
379 {
380         if (event->keyval == GDK_Escape) {
381                 mygal_combo_box_popup_hide_unconditional (combo_box);
382                 return TRUE;
383         } else
384                 return FALSE;
385 }
386
387 static void
388 cb_state_change (GtkWidget *widget, GtkStateType old_state, MygalComboBox *combo_box)
389 {
390         GtkStateType const new_state = GTK_WIDGET_STATE(widget);
391         gtk_widget_set_state (combo_box->priv->display_widget, new_state);
392 }
393
394 static void
395 mygal_combo_box_init (MygalComboBox *combo_box)
396 {
397         GtkWidget *arrow;
398         GdkCursor *cursor;
399
400         combo_box->priv = g_new0 (MygalComboBoxPrivate, 1);
401
402         /*
403          * Create the arrow
404          */
405         combo_box->priv->arrow_button = gtk_toggle_button_new ();
406         gtk_button_set_relief (GTK_BUTTON (combo_box->priv->arrow_button), GTK_RELIEF_NONE);
407         GTK_WIDGET_UNSET_FLAGS (combo_box->priv->arrow_button, GTK_CAN_FOCUS);
408
409         arrow = gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_IN);
410         gtk_container_add (GTK_CONTAINER (combo_box->priv->arrow_button), arrow);
411         gtk_box_pack_end (GTK_BOX (combo_box), combo_box->priv->arrow_button, FALSE, FALSE, 0);
412         g_signal_connect (combo_box->priv->arrow_button, "toggled",
413                           G_CALLBACK (mygal_combo_toggle_pressed), combo_box);
414         gtk_widget_show_all (combo_box->priv->arrow_button);
415
416         /*
417          * prelight the display widget when mousing over the arrow.
418          */
419         g_signal_connect (combo_box->priv->arrow_button, "state-changed",
420                           G_CALLBACK (cb_state_change), combo_box);
421
422         /*
423          * The pop-down container
424          */
425
426         combo_box->priv->toplevel = gtk_window_new (GTK_WINDOW_POPUP);
427         gtk_widget_ref (combo_box->priv->toplevel);
428         gtk_object_sink (GTK_OBJECT (combo_box->priv->toplevel));
429         gtk_window_set_policy (GTK_WINDOW (combo_box->priv->toplevel),
430                                FALSE, TRUE, FALSE);
431
432         combo_box->priv->popup = gtk_event_box_new ();
433         gtk_container_add (GTK_CONTAINER (combo_box->priv->toplevel),
434                            combo_box->priv->popup);
435         gtk_widget_show (combo_box->priv->popup);
436
437         gtk_widget_realize (combo_box->priv->popup);
438         cursor = gdk_cursor_new (GDK_TOP_LEFT_ARROW);
439         gdk_window_set_cursor (combo_box->priv->popup->window, cursor);
440         gdk_cursor_unref (cursor);
441
442         combo_box->priv->torn_off = FALSE;
443         combo_box->priv->tearoff_window = NULL;
444         
445         combo_box->priv->frame = gtk_frame_new (NULL);
446         gtk_container_add (GTK_CONTAINER (combo_box->priv->popup),
447                            combo_box->priv->frame);
448         gtk_frame_set_shadow_type (GTK_FRAME (combo_box->priv->frame), GTK_SHADOW_OUT);
449
450         g_signal_connect (combo_box->priv->toplevel, "button_press_event",
451                           G_CALLBACK (mygal_combo_box_button_press), combo_box);
452         g_signal_connect (combo_box->priv->toplevel, "key_press_event",
453                           G_CALLBACK (mygal_combo_box_key_press), combo_box);
454 }
455
456 E_MAKE_TYPE (mygal_combo_box,
457              "MygalComboBox",
458              MygalComboBox,
459              mygal_combo_box_class_init,
460              mygal_combo_box_init,
461              PARENT_TYPE)
462
463 /**
464  * mygal_combo_box_set_display:
465  * @combo_box: the Combo Box to modify
466  * @display_widget: The widget to be displayed
467
468  * Sets the displayed widget for the @combo_box to be @display_widget
469  */
470 void
471 mygal_combo_box_set_display (MygalComboBox *combo_box, GtkWidget *display_widget)
472 {
473         g_return_if_fail (combo_box != NULL);
474         g_return_if_fail (MYGAL_IS_COMBO_BOX (combo_box));
475         g_return_if_fail (display_widget != NULL);
476         g_return_if_fail (GTK_IS_WIDGET (display_widget));
477
478         if (combo_box->priv->display_widget &&
479             combo_box->priv->display_widget != display_widget)
480                 gtk_container_remove (GTK_CONTAINER (combo_box),
481                                       combo_box->priv->display_widget);
482
483         combo_box->priv->display_widget = display_widget;
484
485         gtk_box_pack_start (GTK_BOX (combo_box), display_widget, TRUE, TRUE, 0);
486 }
487
488 static gboolean
489 cb_tearable_enter_leave (GtkWidget *w, GdkEventCrossing *event, gpointer data)
490 {
491         gboolean const flag = GPOINTER_TO_INT(data);
492         gtk_widget_set_state (w, flag ? GTK_STATE_PRELIGHT : GTK_STATE_NORMAL);
493         return FALSE;
494 }
495
496 /**
497  * mygal_combo_popup_tear_off
498  * @combo:         Combo box
499  * @set_position:  Set to position of popup shell if true
500  *
501  * Tear off the popup
502  *
503  * FIXME:
504  * Gtk popup menus are toplevel windows, not dialogs. I think this is wrong,
505  * and make the popups dialogs. But may be there should be a way to make
506  * them toplevel. We can do this after creating:
507  * GTK_WINDOW (tearoff)->type = GTK_WINDOW_TOPLEVEL;
508  */
509 static void
510 mygal_combo_popup_tear_off (MygalComboBox *combo, gboolean set_position)
511 {
512         int x, y;
513         
514         if (!combo->priv->tearoff_window) {
515                 GtkWidget *tearoff;
516                 gchar *title;
517                 
518                 /* FIXME: made this a toplevel, not a dialog ! */
519                 tearoff = gtk_window_new (GTK_WINDOW_TOPLEVEL);
520                 gtk_widget_ref (tearoff);
521                 gtk_object_sink (GTK_OBJECT (tearoff));
522                 combo->priv->tearoff_window = tearoff;
523                 gtk_widget_set_app_paintable (tearoff, TRUE);
524                 g_signal_connect (tearoff, "key_press_event",
525                                   G_CALLBACK (mygal_combo_box_key_press),
526                                   combo);
527                 gtk_widget_realize (tearoff);
528                 title = g_object_get_data (G_OBJECT (combo),
529                                            "gtk-combo-title");
530                 if (title)
531                         gdk_window_set_title (tearoff->window, title);
532                 gtk_window_set_policy (GTK_WINDOW (tearoff),
533                                        FALSE, TRUE, FALSE);
534                 gtk_window_set_transient_for 
535                         (GTK_WINDOW (tearoff),
536                          GTK_WINDOW (gtk_widget_get_toplevel
537                                      GTK_WIDGET (combo)));
538         }
539
540         if (GTK_WIDGET_VISIBLE (combo->priv->popup)) {
541                 gtk_widget_hide (combo->priv->toplevel);
542                 
543                 gtk_grab_remove (combo->priv->toplevel);
544                 gdk_pointer_ungrab (GDK_CURRENT_TIME);
545         }
546
547         mygal_combo_popup_reparent (combo->priv->popup,
548                                   combo->priv->tearoff_window, FALSE);
549
550         /* It may have got confused about size */
551         gtk_widget_queue_resize (GTK_WIDGET (combo->priv->popup));
552
553         if (set_position) {
554                 mygal_combo_box_get_pos (combo, &x, &y);
555                 gtk_widget_set_uposition (combo->priv->tearoff_window, x, y);
556         }
557         gtk_widget_show (GTK_WIDGET (combo->priv->popup));
558         gtk_widget_show (combo->priv->tearoff_window);
559                 
560 }
561
562 /**
563  * mygal_combo_set_tearoff_state
564  * @combo_box:  Combo box
565  * @torn_off:   TRUE: Tear off. FALSE: Pop down and reattach
566  *
567  * Set the tearoff state of the popup
568  *
569  * Compare with gtk_menu_set_tearoff_state in gtk/gtkmenu.c
570  */
571 static void       
572 mygal_combo_set_tearoff_state (MygalComboBox *combo,
573                              gboolean  torn_off)
574 {
575         g_return_if_fail (combo != NULL);
576         g_return_if_fail (MYGAL_IS_COMBO_BOX (combo));
577         
578         if (combo->priv->torn_off != torn_off) {
579                 combo->priv->torn_off = torn_off;
580                 
581                 if (combo->priv->torn_off) {
582                         mygal_combo_popup_tear_off (combo, TRUE);
583                         deactivate_arrow (combo);
584                 } else {
585                         gtk_widget_hide (combo->priv->tearoff_window);
586                         mygal_combo_popup_reparent (combo->priv->popup,
587                                                   combo->priv->toplevel,
588                                                   FALSE);
589                 }
590         }
591 }
592
593 /**
594  * mygal_combo_tearoff_bg_copy
595  * @combo_box:  Combo box
596  *
597  * Copy popup window image to the tearoff window.
598  */
599 static void
600 mygal_combo_tearoff_bg_copy (MygalComboBox *combo)
601 {
602         GdkPixmap *pixmap;
603         GdkGC *gc;
604         GdkGCValues gc_values;
605
606         GtkWidget *widget = combo->priv->popup;
607
608         if (combo->priv->torn_off) {
609                 gc_values.subwindow_mode = GDK_INCLUDE_INFERIORS;
610                 gc = gdk_gc_new_with_values (widget->window,
611                                              &gc_values, GDK_GC_SUBWINDOW);
612       
613                 pixmap = gdk_pixmap_new (widget->window,
614                                          widget->allocation.width,
615                                          widget->allocation.height,
616                                          -1);
617
618                 gdk_draw_drawable (pixmap, gc,
619                                  widget->window,
620                                  0, 0, 0, 0, -1, -1);
621                 gdk_gc_unref (gc);
622       
623                 gtk_widget_set_usize (combo->priv->tearoff_window,
624                                       widget->allocation.width,
625                                       widget->allocation.height);
626       
627                 gdk_window_set_back_pixmap
628                         (combo->priv->tearoff_window->window, pixmap, FALSE);
629                 gdk_drawable_unref (pixmap);
630         }
631 }
632
633 /**
634  * mygal_combo_popup_reparent
635  * @popup:       Popup
636  * @new_parent:  New parent
637  * @unrealize:   Unrealize popup if TRUE.
638  *
639  * Reparent the popup, taking care of the refcounting
640  *
641  * Compare with gtk_menu_reparent in gtk/gtkmenu.c
642  */
643 static void 
644 mygal_combo_popup_reparent (GtkWidget *popup, 
645                           GtkWidget *new_parent, 
646                           gboolean unrealize)
647 {
648         GtkObject *object = GTK_OBJECT (popup);
649         gboolean was_floating = GTK_OBJECT_FLOATING (object);
650
651         g_object_ref (object);
652         gtk_object_sink (object);
653
654         if (unrealize) {
655                 g_object_ref (object);
656                 gtk_container_remove (GTK_CONTAINER (popup->parent), popup);
657                 gtk_container_add (GTK_CONTAINER (new_parent), popup);
658                 g_object_unref (object);
659         }
660         else
661                 gtk_widget_reparent (GTK_WIDGET (popup), new_parent);
662         gtk_widget_set_usize (new_parent, -1, -1);
663   
664         if (was_floating)
665                 GTK_OBJECT_SET_FLAGS (object, GTK_FLOATING);
666         else
667                 g_object_unref (object);
668 }
669
670 /**
671  * cb_tearable_button_release
672  * @w:      Widget
673  * @event:  Event
674  * @combo:  Combo box
675  *
676  * Toggle tearoff state.
677  */
678 static gboolean
679 cb_tearable_button_release (GtkWidget *w, GdkEventButton *event,
680                             MygalComboBox *combo)
681 {
682         GtkTearoffMenuItem *tearable;
683         
684         g_return_val_if_fail (w != NULL, FALSE);
685         g_return_val_if_fail (GTK_IS_TEAROFF_MENU_ITEM (w), FALSE);
686
687         tearable = GTK_TEAROFF_MENU_ITEM (w);
688         tearable->torn_off = !tearable->torn_off;
689
690         if (!combo->priv->torn_off) {
691                 gboolean need_connect;
692                         
693                 need_connect = (!combo->priv->tearoff_window);
694                 mygal_combo_set_tearoff_state (combo, TRUE);
695                 if (need_connect)
696                         g_signal_connect (combo->priv->tearoff_window,  
697                                           "delete_event",
698                                           G_CALLBACK (cb_popup_delete),
699                                           combo);
700         } else
701                 mygal_combo_box_popup_hide_unconditional (combo);
702         
703         return TRUE;
704 }
705
706 static gboolean
707 cb_popup_delete (GtkWidget *w, GdkEventAny *event, MygalComboBox *combo)
708 {
709         mygal_combo_box_popup_hide_unconditional (combo);
710         return TRUE;
711 }
712
713 void
714 mygal_combo_box_construct (MygalComboBox *combo_box, GtkWidget *display_widget, GtkWidget *pop_down_widget)
715 {
716         GtkWidget *tearable;
717         GtkWidget *vbox;
718
719         g_return_if_fail (combo_box != NULL);
720         g_return_if_fail (MYGAL_IS_COMBO_BOX (combo_box));
721         g_return_if_fail (display_widget  != NULL);
722         g_return_if_fail (GTK_IS_WIDGET (display_widget));
723
724         GTK_BOX (combo_box)->spacing = 0;
725         GTK_BOX (combo_box)->homogeneous = FALSE;
726
727         combo_box->priv->pop_down_widget = pop_down_widget;
728         combo_box->priv->display_widget = NULL;
729
730         vbox = gtk_vbox_new (FALSE, 5);
731         tearable = gtk_tearoff_menu_item_new ();
732         g_signal_connect (tearable, "enter-notify-event",
733                           G_CALLBACK (cb_tearable_enter_leave),
734                           GINT_TO_POINTER (TRUE));
735         g_signal_connect (tearable, "leave-notify-event",
736                           G_CALLBACK (cb_tearable_enter_leave),
737                           GINT_TO_POINTER (FALSE));
738         g_signal_connect (tearable, "button-release-event",
739                           G_CALLBACK (cb_tearable_button_release),
740                           (gpointer) combo_box);
741         gtk_box_pack_start (GTK_BOX (vbox), tearable, FALSE, FALSE, 0);
742         gtk_box_pack_start (GTK_BOX (vbox), pop_down_widget, TRUE, TRUE, 0);
743         combo_box->priv->tearable = tearable;
744
745         /*
746          * Finish setup
747          */
748         mygal_combo_box_set_display (combo_box, display_widget);
749
750         gtk_container_add (GTK_CONTAINER (combo_box->priv->frame), vbox);
751         gtk_widget_show_all (combo_box->priv->frame);
752 }
753
754 GtkWidget *
755 mygal_combo_box_new (GtkWidget *display_widget, GtkWidget *optional_popdown)
756 {
757         MygalComboBox *combo_box;
758
759         g_return_val_if_fail (display_widget  != NULL, NULL);
760         g_return_val_if_fail (GTK_IS_WIDGET (display_widget), NULL);
761
762         combo_box = g_object_new (MYGAL_COMBO_BOX_TYPE, NULL);
763         mygal_combo_box_construct (combo_box, display_widget, optional_popdown);
764         return GTK_WIDGET (combo_box);
765 }
766
767 void
768 mygal_combo_box_set_arrow_relief (MygalComboBox *cc, GtkReliefStyle relief)
769 {
770         g_return_if_fail (cc != NULL);
771         g_return_if_fail (MYGAL_IS_COMBO_BOX (cc));
772
773         gtk_button_set_relief (GTK_BUTTON (cc->priv->arrow_button), relief);
774 }
775
776 /**
777  * mygal_combo_box_set_title
778  * @combo: Combo box
779  * @title: Title
780  *
781  * Set a title to display over the tearoff window.
782  *
783  * FIXME:
784  *
785  * This should really change the title even when the popup is already torn off.
786  * I guess the tearoff window could attach a listener to title change or
787  * something. But I don't think we need the functionality, so I didn't bother
788  * to investigate.
789  */
790 void       
791 mygal_combo_box_set_title (MygalComboBox *combo,
792                          const gchar *title)
793 {
794         g_return_if_fail (combo != NULL);
795         g_return_if_fail (MYGAL_IS_COMBO_BOX (combo));
796         
797         g_object_set_data_full (G_OBJECT (combo), "gtk-combo-title",
798                                 g_strdup (title), (GDestroyNotify) g_free);
799 }
800
801 /**
802  * mygal_combo_box_set_arrow_sensitive
803  * @combo:  Combo box
804  * @sensitive:  Sensitivity value
805  *
806  * Toggle the sensitivity of the arrow button
807  */
808
809 void
810 mygal_combo_box_set_arrow_sensitive (MygalComboBox *combo,
811                                    gboolean sensitive)
812 {
813         g_return_if_fail (combo != NULL);
814
815         gtk_widget_set_sensitive (combo->priv->arrow_button, sensitive);
816 }
817
818 /**
819  * mygal_combo_box_set_tearable:
820  * @combo: Combo box
821  * @tearable: whether to allow the @combo to be tearable
822  *
823  * controls whether the combo box's pop up widget can be torn off.
824  */
825 void
826 mygal_combo_box_set_tearable (MygalComboBox *combo, gboolean tearable)
827 {
828         g_return_if_fail (combo != NULL);
829         g_return_if_fail (MYGAL_IS_COMBO_BOX (combo));
830
831         if (tearable){
832                 gtk_widget_show (combo->priv->tearable);
833         } else {
834                 mygal_combo_set_tearoff_state (combo, FALSE);
835                 gtk_widget_hide (combo->priv->tearable);
836         }
837 }