]> Pileus Git - ~andy/gtk/blob - gtk/gtkhandlebox.c
queue a resize here; will temporarily slow down the widget a lot, until we
[~andy/gtk] / gtk / gtkhandlebox.c
1 /* GTK - The GIMP Toolkit
2  * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
3  * Copyright (C) 1998 Elliot Lee
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18  * Boston, MA 02111-1307, USA.
19  */
20
21 /*
22  * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
23  * file for a list of people on the GTK+ Team.  See the ChangeLog
24  * files for a list of changes.  These files are distributed with
25  * GTK+ at ftp://ftp.gtk.org/pub/gtk/. 
26  */
27
28 #include <stdlib.h>
29 #include "gtkhandlebox.h"
30 #include "gtkmain.h"
31 #include "gtksignal.h"
32 #include "gtkwindow.h"
33 #include "gtkintl.h"
34
35 enum {
36   PROP_0,
37   PROP_SHADOW,
38   PROP_SHADOW_TYPE,
39   PROP_HANDLE_POSITION,
40   PROP_SNAP_EDGE
41 };
42
43 #define DRAG_HANDLE_SIZE 10
44 #define CHILDLESS_SIZE  25
45 #define GHOST_HEIGHT 3
46 #define TOLERANCE 5
47
48 enum {
49   SIGNAL_CHILD_ATTACHED,
50   SIGNAL_CHILD_DETACHED,
51   SIGNAL_LAST
52 };
53
54 /* The algorithm for docking and redocking implemented here
55  * has a couple of nice properties:
56  *
57  * 1) During a single drag, docking always occurs at the
58  *    the same cursor position. This means that the users
59  *    motions are reversible, and that you won't
60  *    undock/dock oscillations.
61  *
62  * 2) Docking generally occurs at user-visible features.
63  *    The user, once they figure out to redock, will
64  *    have useful information about doing it again in
65  *    the future.
66  *
67  * Please try to preserve these properties if you
68  * change the algorithm. (And the current algorithm
69  * is far from ideal). Briefly, the current algorithm
70  * for deciding whether the handlebox is docked or not:
71  *
72  * 1) The decision is done by comparing two rectangles - the
73  *    allocation if the widget at the start of the drag,
74  *    and the boundary of hb->bin_window at the start of
75  *    of the drag offset by the distance that the cursor
76  *    has moved.
77  *
78  * 2) These rectangles must have one edge, the "snap_edge"
79  *    of the handlebox, aligned within TOLERANCE.
80  * 
81  * 3) On the other dimension, the extents of one rectangle
82  *    must be contained in the extents of the other,
83  *    extended by tolerance. That is, either we can have:
84  *
85  * <-TOLERANCE-|--------bin_window--------------|-TOLERANCE->
86  *         <--------float_window-------------------->
87  *
88  * or we can have:
89  *
90  * <-TOLERANCE-|------float_window--------------|-TOLERANCE->
91  *          <--------bin_window-------------------->
92  */
93
94 static void gtk_handle_box_class_init     (GtkHandleBoxClass *klass);
95 static void gtk_handle_box_init           (GtkHandleBox      *handle_box);
96 static void gtk_handle_box_set_property   (GObject      *object,
97                                            guint         param_id,
98                                            const GValue *value,
99                                            GParamSpec   *pspec);
100 static void gtk_handle_box_get_property   (GObject     *object,
101                                            guint        param_id,
102                                            GValue      *value,
103                                            GParamSpec  *pspec);
104 static void gtk_handle_box_destroy        (GtkObject         *object);
105 static void gtk_handle_box_map            (GtkWidget         *widget);
106 static void gtk_handle_box_unmap          (GtkWidget         *widget);
107 static void gtk_handle_box_realize        (GtkWidget         *widget);
108 static void gtk_handle_box_unrealize      (GtkWidget         *widget);
109 static void gtk_handle_box_style_set      (GtkWidget         *widget,
110                                            GtkStyle          *previous_style);
111 static void gtk_handle_box_size_request   (GtkWidget         *widget,
112                                            GtkRequisition    *requisition);
113 static void gtk_handle_box_size_allocate  (GtkWidget         *widget,
114                                            GtkAllocation     *real_allocation);
115 static void gtk_handle_box_add            (GtkContainer      *container,
116                                            GtkWidget         *widget);
117 static void gtk_handle_box_remove         (GtkContainer      *container,
118                                            GtkWidget         *widget);
119 static void gtk_handle_box_draw_ghost     (GtkHandleBox      *hb);
120 static void gtk_handle_box_paint          (GtkWidget         *widget,
121                                            GdkEventExpose    *event,
122                                            GdkRectangle      *area);
123 static gint gtk_handle_box_expose         (GtkWidget         *widget,
124                                            GdkEventExpose    *event);
125 static gint gtk_handle_box_button_changed (GtkWidget         *widget,
126                                            GdkEventButton    *event);
127 static gint gtk_handle_box_motion         (GtkWidget         *widget,
128                                            GdkEventMotion    *event);
129 static gint gtk_handle_box_delete_event   (GtkWidget         *widget,
130                                            GdkEventAny       *event);
131 static void gtk_handle_box_reattach       (GtkHandleBox      *hb);
132
133
134 static GtkBinClass *parent_class;
135 static guint        handle_box_signals[SIGNAL_LAST] = { 0 };
136
137
138 GtkType
139 gtk_handle_box_get_type (void)
140 {
141   static GtkType handle_box_type = 0;
142
143   if (!handle_box_type)
144     {
145       static const GtkTypeInfo handle_box_info =
146       {
147         "GtkHandleBox",
148         sizeof (GtkHandleBox),
149         sizeof (GtkHandleBoxClass),
150         (GtkClassInitFunc) gtk_handle_box_class_init,
151         (GtkObjectInitFunc) gtk_handle_box_init,
152         /* reserved_1 */ NULL,
153         /* reserved_2 */ NULL,
154         (GtkClassInitFunc) NULL,
155       };
156
157       handle_box_type = gtk_type_unique (GTK_TYPE_BIN, &handle_box_info);
158     }
159
160   return handle_box_type;
161 }
162
163 static void
164 gtk_handle_box_class_init (GtkHandleBoxClass *class)
165 {
166   GObjectClass *gobject_class;
167   GtkObjectClass *object_class;
168   GtkWidgetClass *widget_class;
169   GtkContainerClass *container_class;
170
171   gobject_class = (GObjectClass *) class;
172   object_class = (GtkObjectClass *) class;
173   widget_class = (GtkWidgetClass *) class;
174   container_class = (GtkContainerClass *) class;
175
176   parent_class = gtk_type_class (GTK_TYPE_BIN);
177
178   gobject_class->set_property = gtk_handle_box_set_property;
179   gobject_class->get_property = gtk_handle_box_get_property;
180   
181   g_object_class_install_property (gobject_class,
182                                    PROP_SHADOW,
183                                    g_param_spec_enum ("shadow", NULL,
184                                                       _("Deprecated property, use shadow_type instead."),
185                                                       GTK_TYPE_SHADOW_TYPE,
186                                                       GTK_SHADOW_ETCHED_OUT,
187                                                       G_PARAM_READABLE | G_PARAM_WRITABLE));
188   g_object_class_install_property (gobject_class,
189                                    PROP_SHADOW_TYPE,
190                                    g_param_spec_enum ("shadow_type",
191                                                       _("Shadow type"),
192                                                       _("Appearance of the shadow that surrounds the container."),
193                                                       GTK_TYPE_SHADOW_TYPE,
194                                                       GTK_SHADOW_ETCHED_OUT,
195                                                       G_PARAM_READABLE | G_PARAM_WRITABLE));
196   
197   g_object_class_install_property (gobject_class,
198                                    PROP_HANDLE_POSITION,
199                                    g_param_spec_enum ("handle_position",
200                                                       _("Handle position"),
201                                                       _("Position of the handle relative to the child widget."),
202                                                       GTK_TYPE_POSITION_TYPE,
203                                                       GTK_POS_LEFT,
204                                                       G_PARAM_READABLE | G_PARAM_WRITABLE));
205   
206   g_object_class_install_property (gobject_class,
207                                    PROP_SNAP_EDGE,
208                                    g_param_spec_enum ("snap_edge",
209                                                       _("Snap edge"),
210                                                       _("Side of the handlebox that's lined up with the docking point to dock the handlebox."),
211                                                       GTK_TYPE_POSITION_TYPE,
212                                                       GTK_POS_TOP,
213                                                       G_PARAM_READABLE | G_PARAM_WRITABLE));
214   object_class->destroy = gtk_handle_box_destroy;
215
216   widget_class->map = gtk_handle_box_map;
217   widget_class->unmap = gtk_handle_box_unmap;
218   widget_class->realize = gtk_handle_box_realize;
219   widget_class->unrealize = gtk_handle_box_unrealize;
220   widget_class->style_set = gtk_handle_box_style_set;
221   widget_class->size_request = gtk_handle_box_size_request;
222   widget_class->size_allocate = gtk_handle_box_size_allocate;
223   widget_class->expose_event = gtk_handle_box_expose;
224   widget_class->button_press_event = gtk_handle_box_button_changed;
225   widget_class->button_release_event = gtk_handle_box_button_changed;
226   widget_class->motion_notify_event = gtk_handle_box_motion;
227   widget_class->delete_event = gtk_handle_box_delete_event;
228
229   container_class->add = gtk_handle_box_add;
230   container_class->remove = gtk_handle_box_remove;
231
232   class->child_attached = NULL;
233   class->child_detached = NULL;
234
235   handle_box_signals[SIGNAL_CHILD_ATTACHED] =
236     gtk_signal_new ("child_attached",
237                     GTK_RUN_FIRST,
238                     GTK_CLASS_TYPE (object_class),
239                     GTK_SIGNAL_OFFSET (GtkHandleBoxClass, child_attached),
240                     gtk_marshal_VOID__OBJECT,
241                     GTK_TYPE_NONE, 1,
242                     GTK_TYPE_WIDGET);
243   handle_box_signals[SIGNAL_CHILD_DETACHED] =
244     gtk_signal_new ("child_detached",
245                     GTK_RUN_FIRST,
246                     GTK_CLASS_TYPE (object_class),
247                     GTK_SIGNAL_OFFSET (GtkHandleBoxClass, child_detached),
248                     gtk_marshal_VOID__OBJECT,
249                     GTK_TYPE_NONE, 1,
250                     GTK_TYPE_WIDGET);
251 }
252
253 static void
254 gtk_handle_box_init (GtkHandleBox *handle_box)
255 {
256   GTK_WIDGET_UNSET_FLAGS (handle_box, GTK_NO_WINDOW);
257
258   handle_box->bin_window = NULL;
259   handle_box->float_window = NULL;
260   handle_box->shadow_type = GTK_SHADOW_OUT;
261   handle_box->handle_position = GTK_POS_LEFT;
262   handle_box->float_window_mapped = FALSE;
263   handle_box->child_detached = FALSE;
264   handle_box->in_drag = FALSE;
265   handle_box->shrink_on_detach = TRUE;
266   handle_box->snap_edge = -1;
267 }
268
269 static void 
270 gtk_handle_box_set_property (GObject         *object,
271                              guint            prop_id,
272                              const GValue    *value,
273                              GParamSpec      *pspec)
274 {
275   GtkHandleBox *handle_box = GTK_HANDLE_BOX (object);
276
277   switch (prop_id)
278     {
279     case PROP_SHADOW:
280     case PROP_SHADOW_TYPE:
281       gtk_handle_box_set_shadow_type (handle_box, g_value_get_enum (value));
282       break;
283     case PROP_HANDLE_POSITION:
284       gtk_handle_box_set_handle_position (handle_box, g_value_get_enum (value));
285       break;
286     case PROP_SNAP_EDGE:
287       gtk_handle_box_set_snap_edge (handle_box, g_value_get_enum (value));
288       break;
289     default:
290       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
291       break;
292     }
293 }
294
295 static void 
296 gtk_handle_box_get_property (GObject         *object,
297                              guint            prop_id,
298                              GValue          *value,
299                              GParamSpec      *pspec)
300 {
301   GtkHandleBox *handle_box = GTK_HANDLE_BOX (object);
302   
303   switch (prop_id)
304     {
305     case PROP_SHADOW:
306     case PROP_SHADOW_TYPE:
307       g_value_set_enum (value, handle_box->shadow_type);
308       break;
309     case PROP_HANDLE_POSITION:
310       g_value_set_enum (value, handle_box->handle_position);
311       break;
312     case PROP_SNAP_EDGE:
313       g_value_set_enum (value, handle_box->snap_edge);
314       break;
315     default:
316       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
317       break;
318     }
319 }
320  
321 GtkWidget*
322 gtk_handle_box_new (void)
323 {
324   return GTK_WIDGET (gtk_type_new (gtk_handle_box_get_type ()));
325 }
326
327 static void
328 gtk_handle_box_destroy (GtkObject *object)
329 {
330   GtkHandleBox *hb;
331
332   g_return_if_fail (GTK_IS_HANDLE_BOX (object));
333
334   hb = GTK_HANDLE_BOX (object);
335
336   if (GTK_OBJECT_CLASS (parent_class)->destroy)
337     (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
338 }
339
340 static void
341 gtk_handle_box_map (GtkWidget *widget)
342 {
343   GtkBin *bin;
344   GtkHandleBox *hb;
345
346   g_return_if_fail (GTK_IS_HANDLE_BOX (widget));
347
348   GTK_WIDGET_SET_FLAGS (widget, GTK_MAPPED);
349
350   bin = GTK_BIN (widget);
351   hb = GTK_HANDLE_BOX (widget);
352
353   if (bin->child &&
354       GTK_WIDGET_VISIBLE (bin->child) &&
355       !GTK_WIDGET_MAPPED (bin->child))
356     gtk_widget_map (bin->child);
357
358   if (hb->child_detached && !hb->float_window_mapped)
359     {
360       gdk_window_show (hb->float_window);
361       hb->float_window_mapped = TRUE;
362     }
363
364   gdk_window_show (hb->bin_window);
365   gdk_window_show (widget->window);
366 }
367
368 static void
369 gtk_handle_box_unmap (GtkWidget *widget)
370 {
371   GtkHandleBox *hb;
372
373   g_return_if_fail (GTK_IS_HANDLE_BOX (widget));
374
375   GTK_WIDGET_UNSET_FLAGS (widget, GTK_MAPPED);
376
377   hb = GTK_HANDLE_BOX (widget);
378
379   gdk_window_hide (widget->window);
380   if (hb->float_window_mapped)
381     {
382       gdk_window_hide (hb->float_window);
383       hb->float_window_mapped = FALSE;
384     }
385 }
386
387 static void
388 gtk_handle_box_realize (GtkWidget *widget)
389 {
390   GdkWindowAttr attributes;
391   gint attributes_mask;
392   GtkHandleBox *hb;
393
394   g_return_if_fail (GTK_IS_HANDLE_BOX (widget));
395
396   hb = GTK_HANDLE_BOX (widget);
397
398   GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
399
400   attributes.x = widget->allocation.x;
401   attributes.y = widget->allocation.y;
402   attributes.width = widget->allocation.width;
403   attributes.height = widget->allocation.height;
404   attributes.window_type = GDK_WINDOW_CHILD;
405   attributes.wclass = GDK_INPUT_OUTPUT;
406   attributes.visual = gtk_widget_get_visual (widget);
407   attributes.colormap = gtk_widget_get_colormap (widget);
408   attributes.event_mask = (gtk_widget_get_events (widget)
409                            | GDK_EXPOSURE_MASK);
410   attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
411   widget->window = gdk_window_new (gtk_widget_get_parent_window (widget), &attributes, attributes_mask);
412   gdk_window_set_user_data (widget->window, widget);
413
414   attributes.x = 0;
415   attributes.y = 0;
416   attributes.width = widget->allocation.width;
417   attributes.height = widget->allocation.height;
418   attributes.window_type = GDK_WINDOW_CHILD;
419   attributes.event_mask |= (gtk_widget_get_events (widget) |
420                             GDK_EXPOSURE_MASK |
421                             GDK_BUTTON1_MOTION_MASK |
422                             GDK_POINTER_MOTION_HINT_MASK |
423                             GDK_BUTTON_PRESS_MASK |
424                             GDK_BUTTON_RELEASE_MASK);
425   attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
426   hb->bin_window = gdk_window_new (widget->window, &attributes, attributes_mask);
427   gdk_window_set_user_data (hb->bin_window, widget);
428   if (GTK_BIN (hb)->child)
429     gtk_widget_set_parent_window (GTK_BIN (hb)->child, hb->bin_window);
430   
431   attributes.x = 0;
432   attributes.y = 0;
433   attributes.width = widget->requisition.width;
434   attributes.height = widget->requisition.height;
435   attributes.window_type = GDK_WINDOW_TOPLEVEL;
436   attributes.wclass = GDK_INPUT_OUTPUT;
437   attributes.visual = gtk_widget_get_visual (widget);
438   attributes.colormap = gtk_widget_get_colormap (widget);
439   attributes.event_mask = (gtk_widget_get_events (widget) |
440                            GDK_KEY_PRESS_MASK |
441                            GDK_ENTER_NOTIFY_MASK |
442                            GDK_LEAVE_NOTIFY_MASK |
443                            GDK_FOCUS_CHANGE_MASK |
444                            GDK_STRUCTURE_MASK);
445   attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
446   hb->float_window = gdk_window_new (NULL, &attributes, attributes_mask);
447   gdk_window_set_user_data (hb->float_window, widget);
448   gdk_window_set_decorations (hb->float_window, 0);
449   gdk_window_set_type_hint (hb->float_window, GDK_WINDOW_TYPE_HINT_TOOLBAR);
450   
451   widget->style = gtk_style_attach (widget->style, widget->window);
452   gtk_style_set_background (widget->style, widget->window, GTK_WIDGET_STATE (hb));
453   gtk_style_set_background (widget->style, hb->bin_window, GTK_WIDGET_STATE (hb));
454   gtk_style_set_background (widget->style, hb->float_window, GTK_WIDGET_STATE (hb));
455   gdk_window_set_back_pixmap (widget->window, NULL, TRUE);
456 }
457
458 static void
459 gtk_handle_box_unrealize (GtkWidget *widget)
460 {
461   GtkHandleBox *hb;
462
463   g_return_if_fail (GTK_IS_HANDLE_BOX (widget));
464
465   hb = GTK_HANDLE_BOX (widget);
466
467   gdk_window_set_user_data (hb->bin_window, NULL);
468   gdk_window_destroy (hb->bin_window);
469   hb->bin_window = NULL;
470   gdk_window_set_user_data (hb->float_window, NULL);
471   gdk_window_destroy (hb->float_window);
472   hb->float_window = NULL;
473
474   if (GTK_WIDGET_CLASS (parent_class)->unrealize)
475     (* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget);
476 }
477
478 static void
479 gtk_handle_box_style_set (GtkWidget *widget,
480                           GtkStyle  *previous_style)
481 {
482   GtkHandleBox *hb;
483
484   g_return_if_fail (GTK_IS_HANDLE_BOX (widget));
485
486   hb = GTK_HANDLE_BOX (widget);
487
488   if (GTK_WIDGET_REALIZED (widget) &&
489       !GTK_WIDGET_NO_WINDOW (widget))
490     {
491       gtk_style_set_background (widget->style, widget->window,
492 widget->state);
493       gtk_style_set_background (widget->style, hb->bin_window, widget->state);
494       gtk_style_set_background (widget->style, hb->float_window, widget->state);
495     }
496 }
497
498 static void
499 gtk_handle_box_size_request (GtkWidget      *widget,
500                              GtkRequisition *requisition)
501 {
502   GtkBin *bin;
503   GtkHandleBox *hb;
504   GtkRequisition child_requisition;
505
506   g_return_if_fail (GTK_IS_HANDLE_BOX (widget));
507   g_return_if_fail (requisition != NULL);
508
509   bin = GTK_BIN (widget);
510   hb = GTK_HANDLE_BOX (widget);
511
512   if (hb->handle_position == GTK_POS_LEFT ||
513       hb->handle_position == GTK_POS_RIGHT)
514     {
515       requisition->width = DRAG_HANDLE_SIZE;
516       requisition->height = 0;
517     }
518   else
519     {
520       requisition->width = 0;
521       requisition->height = DRAG_HANDLE_SIZE;
522     }
523
524   /* if our child is not visible, we still request its size, since we
525    * won't have any usefull hint for our size otherwise.
526    */
527   if (bin->child)
528     gtk_widget_size_request (bin->child, &child_requisition);
529   else
530     {
531       child_requisition.width = 0;
532       child_requisition.height = 0;
533     }      
534
535   if (hb->child_detached)
536     {
537       /* FIXME: This doesn't work currently */
538       if (!hb->shrink_on_detach)
539         {
540           if (hb->handle_position == GTK_POS_LEFT ||
541               hb->handle_position == GTK_POS_RIGHT)
542             requisition->height += child_requisition.height;
543           else
544             requisition->width += child_requisition.width;
545         }
546       else
547         {
548           if (hb->handle_position == GTK_POS_LEFT ||
549               hb->handle_position == GTK_POS_RIGHT)
550             requisition->height += widget->style->ythickness;
551           else
552             requisition->width += widget->style->xthickness;
553         }
554     }
555   else
556     {
557       requisition->width += GTK_CONTAINER (widget)->border_width * 2;
558       requisition->height += GTK_CONTAINER (widget)->border_width * 2;
559       
560       if (bin->child)
561         {
562           requisition->width += child_requisition.width;
563           requisition->height += child_requisition.height;
564         }
565       else
566         {
567           requisition->width += CHILDLESS_SIZE;
568           requisition->height += CHILDLESS_SIZE;
569         }
570     }
571 }
572
573 static void
574 gtk_handle_box_size_allocate (GtkWidget     *widget,
575                               GtkAllocation *allocation)
576 {
577   GtkBin *bin;
578   GtkHandleBox *hb;
579   GtkRequisition child_requisition;
580   
581   g_return_if_fail (GTK_IS_HANDLE_BOX (widget));
582   g_return_if_fail (allocation != NULL);
583   
584   bin = GTK_BIN (widget);
585   hb = GTK_HANDLE_BOX (widget);
586   
587   if (bin->child)
588     gtk_widget_get_child_requisition (bin->child, &child_requisition);
589   else
590     {
591       child_requisition.width = 0;
592       child_requisition.height = 0;
593     }      
594       
595   widget->allocation = *allocation;
596
597   if (GTK_WIDGET_REALIZED (hb))
598     gdk_window_move_resize (widget->window,
599                             widget->allocation.x,
600                             widget->allocation.y,
601                             widget->allocation.width,
602                             widget->allocation.height);
603
604
605   if (bin->child && GTK_WIDGET_VISIBLE (bin->child))
606     {
607       GtkWidget *child;
608       GtkAllocation child_allocation;
609       guint border_width;
610
611       child = bin->child;
612       border_width = GTK_CONTAINER (widget)->border_width;
613
614       child_allocation.x = border_width;
615       child_allocation.y = border_width;
616       if (hb->handle_position == GTK_POS_LEFT)
617         child_allocation.x += DRAG_HANDLE_SIZE;
618       else if (hb->handle_position == GTK_POS_TOP)
619         child_allocation.y += DRAG_HANDLE_SIZE;
620
621       if (hb->child_detached)
622         {
623           guint float_width;
624           guint float_height;
625           
626           child_allocation.width = child_requisition.width;
627           child_allocation.height = child_requisition.height;
628           
629           float_width = child_allocation.width + 2 * border_width;
630           float_height = child_allocation.height + 2 * border_width;
631           
632           if (hb->handle_position == GTK_POS_LEFT ||
633               hb->handle_position == GTK_POS_RIGHT)
634             float_width += DRAG_HANDLE_SIZE;
635           else
636             float_height += DRAG_HANDLE_SIZE;
637
638           if (GTK_WIDGET_REALIZED (hb))
639             {
640               gdk_window_resize (hb->float_window,
641                                  float_width,
642                                  float_height);
643               gdk_window_move_resize (hb->bin_window,
644                                       0,
645                                       0,
646                                       float_width,
647                                       float_height);
648             }
649         }
650       else
651         {
652           child_allocation.width = MAX (1, (gint)widget->allocation.width - 2 * border_width);
653           child_allocation.height = MAX (1, (gint)widget->allocation.height - 2 * border_width);
654
655           if (hb->handle_position == GTK_POS_LEFT ||
656               hb->handle_position == GTK_POS_RIGHT)
657             child_allocation.width -= DRAG_HANDLE_SIZE;
658           else
659             child_allocation.height -= DRAG_HANDLE_SIZE;
660           
661           if (GTK_WIDGET_REALIZED (hb))
662             gdk_window_move_resize (hb->bin_window,
663                                     0,
664                                     0,
665                                     widget->allocation.width,
666                                     widget->allocation.height);
667         }
668
669       gtk_widget_size_allocate (bin->child, &child_allocation);
670     }
671 }
672
673 static void
674 gtk_handle_box_draw_ghost (GtkHandleBox *hb)
675 {
676   GtkWidget *widget;
677   guint x;
678   guint y;
679   guint width;
680   guint height;
681
682   widget = GTK_WIDGET (hb);
683
684   if (hb->handle_position == GTK_POS_LEFT ||
685       hb->handle_position == GTK_POS_RIGHT)
686     {
687       x = hb->handle_position == GTK_POS_LEFT ? 0 : widget->allocation.width - DRAG_HANDLE_SIZE;
688       y = 0;
689       width = DRAG_HANDLE_SIZE;
690       height = widget->allocation.height;
691     }
692   else
693     {
694       x = 0;
695       y = hb->handle_position == GTK_POS_TOP ? 0 : widget->allocation.height - DRAG_HANDLE_SIZE;
696       width = widget->allocation.width;
697       height = DRAG_HANDLE_SIZE;
698     }
699   gtk_paint_shadow (widget->style,
700                     widget->window,
701                     GTK_WIDGET_STATE (widget),
702                     GTK_SHADOW_ETCHED_IN,
703                     NULL, widget, "handle",
704                     x,
705                     y,
706                     width,
707                     height);
708    if (hb->handle_position == GTK_POS_LEFT ||
709        hb->handle_position == GTK_POS_RIGHT)
710      gtk_paint_hline (widget->style,
711                       widget->window,
712                       GTK_WIDGET_STATE (widget),
713                       NULL, widget, "handlebox",
714                       hb->handle_position == GTK_POS_LEFT ? DRAG_HANDLE_SIZE : 0,
715                       hb->handle_position == GTK_POS_LEFT ? widget->allocation.width : widget->allocation.width - DRAG_HANDLE_SIZE,
716                       widget->allocation.height / 2);
717    else
718      gtk_paint_vline (widget->style,
719                       widget->window,
720                       GTK_WIDGET_STATE (widget),
721                       NULL, widget, "handlebox",
722                       hb->handle_position == GTK_POS_TOP ? DRAG_HANDLE_SIZE : 0,
723                       hb->handle_position == GTK_POS_TOP ? widget->allocation.height : widget->allocation.height - DRAG_HANDLE_SIZE,
724                       widget->allocation.width / 2);
725 }
726
727 static void
728 draw_textured_frame (GtkWidget *widget, GdkWindow *window, GdkRectangle *rect, GtkShadowType shadow,
729                      GdkRectangle *clip)
730 {
731    gtk_paint_handle (widget->style, window, GTK_STATE_NORMAL, shadow,
732                      clip, widget, "handlebox",
733                      rect->x, rect->y, rect->width, rect->height, 
734                      GTK_ORIENTATION_VERTICAL);
735 }
736
737 void
738 gtk_handle_box_set_shadow_type (GtkHandleBox  *handle_box,
739                                 GtkShadowType  type)
740 {
741   g_return_if_fail (GTK_IS_HANDLE_BOX (handle_box));
742
743   if ((GtkShadowType) handle_box->shadow_type != type)
744     {
745       handle_box->shadow_type = type;
746       g_object_notify (G_OBJECT (handle_box), "shadow_type");
747       gtk_widget_queue_resize (GTK_WIDGET (handle_box));
748     }
749 }
750
751 /**
752  * gtk_handle_box_get_shadow_type:
753  * @handle_box: a #GtkHandleBox
754  * 
755  * Gets the type of shadow drawn around the handle box. See
756  * gtk_handle_box_set_shadow_type().
757  *
758  * Return value: the type of shadow currently drawn around the handle box.
759  **/
760 GtkShadowType
761 gtk_handle_box_get_shadow_type (GtkHandleBox *handle_box)
762 {
763   g_return_val_if_fail (GTK_IS_HANDLE_BOX (handle_box), GTK_SHADOW_ETCHED_OUT);
764
765   return handle_box->shadow_type;
766 }
767
768 void        
769 gtk_handle_box_set_handle_position  (GtkHandleBox    *handle_box,
770                                      GtkPositionType  position)
771 {
772   if ((GtkPositionType) handle_box->handle_position != position)
773     {
774       handle_box->handle_position = position;
775       g_object_notify (G_OBJECT (handle_box), "handle_position");
776       gtk_widget_queue_resize (GTK_WIDGET (handle_box));
777     }
778 }
779
780 /**
781  * gtk_handle_box_get_handle_position:
782  * @handle_box: a #GtkHandleBox
783  *
784  * Gets the handle position of the handle box. See
785  * gtk_handle_box_set_handle_position().
786  *
787  * Return value: the current handle position.
788  **/
789 GtkPositionType
790 gtk_handle_box_get_handle_position (GtkHandleBox *handle_box)
791 {
792   g_return_val_if_fail (GTK_IS_HANDLE_BOX (handle_box), GTK_POS_LEFT);
793
794   return handle_box->handle_position;
795 }
796
797 void        
798 gtk_handle_box_set_snap_edge        (GtkHandleBox    *handle_box,
799                                      GtkPositionType  edge)
800 {
801   g_return_if_fail (GTK_IS_HANDLE_BOX (handle_box));
802
803   if (handle_box->snap_edge != edge)
804     {
805       handle_box->snap_edge = edge;
806       g_object_notify (G_OBJECT (handle_box), "snap_edge");
807     }
808 }
809
810 /**
811  * gtk_handle_box_get_snap_edge:
812  * @handle_box: a #GtkHandleBox
813  * 
814  * Gets the edge used for determining reattachment of the handle box. See
815  * gtk_handle_box_set_snap_edge().
816  *
817  * Return value: the edge used for determining reattachment, or (GtkPositionType)-1 if this
818  *               is determined (as per default) from the handle position. 
819  **/
820 GtkPositionType
821 gtk_handle_box_get_snap_edge (GtkHandleBox *handle_box)
822 {
823   g_return_val_if_fail (GTK_IS_HANDLE_BOX (handle_box), (GtkPositionType)-1);
824
825   return handle_box->snap_edge;
826 }
827
828 static void
829 gtk_handle_box_paint (GtkWidget      *widget,
830                       GdkEventExpose *event,
831                       GdkRectangle   *area)
832 {
833   GtkBin *bin;
834   GtkHandleBox *hb;
835   guint width;
836   guint height;
837   GdkRectangle rect;
838   GdkRectangle dest;
839
840   bin = GTK_BIN (widget);
841   hb = GTK_HANDLE_BOX (widget);
842
843   gdk_window_get_size (hb->bin_window, &width, &height);
844   
845   if (!event)
846     gtk_paint_box (widget->style,
847                    hb->bin_window,
848                    GTK_WIDGET_STATE (widget),
849                    hb->shadow_type,
850                    area, widget, "handlebox_bin",
851                    0, 0, -1, -1);
852   else
853    gtk_paint_box (widget->style,
854                   hb->bin_window,
855                   GTK_WIDGET_STATE (widget),
856                   hb->shadow_type,
857                   &event->area, widget, "handlebox_bin",
858                   0, 0, -1, -1);
859
860 /* We currently draw the handle _above_ the relief of the handlebox.
861  * it could also be drawn on the same level...
862
863                  hb->handle_position == GTK_POS_LEFT ? DRAG_HANDLE_SIZE : 0,
864                  hb->handle_position == GTK_POS_TOP ? DRAG_HANDLE_SIZE : 0,
865                  width,
866                  height);*/
867
868   switch (hb->handle_position)
869     {
870     case GTK_POS_LEFT:
871       rect.x = 0;
872       rect.y = 0; 
873       rect.width = DRAG_HANDLE_SIZE;
874       rect.height = height;
875       break;
876     case GTK_POS_RIGHT:
877       rect.x = width - DRAG_HANDLE_SIZE; 
878       rect.y = 0;
879       rect.width = DRAG_HANDLE_SIZE;
880       rect.height = height;
881       break;
882     case GTK_POS_TOP:
883       rect.x = 0;
884       rect.y = 0; 
885       rect.width = width;
886       rect.height = DRAG_HANDLE_SIZE;
887       break;
888     case GTK_POS_BOTTOM:
889       rect.x = 0;
890       rect.y = height - DRAG_HANDLE_SIZE;
891       rect.width = width;
892       rect.height = DRAG_HANDLE_SIZE;
893       break;
894     }
895
896   if (gdk_rectangle_intersect (event ? &event->area : area, &rect, &dest))
897     draw_textured_frame (widget, hb->bin_window, &rect,
898                          GTK_SHADOW_OUT,
899                          event ? &event->area : area);
900
901   if (bin->child && GTK_WIDGET_VISIBLE (bin->child))
902     {
903       GdkRectangle child_area;
904
905       if (!event) /* we were called from draw() */
906         {
907           if (gtk_widget_intersect (bin->child, area, &child_area))
908             gtk_widget_draw (bin->child, &child_area);
909         }
910       else /* we were called from expose() */
911         (* GTK_WIDGET_CLASS (parent_class)->expose_event) (widget, event);
912     }
913 }
914
915 static gint
916 gtk_handle_box_expose (GtkWidget      *widget,
917                        GdkEventExpose *event)
918 {
919   GtkHandleBox *hb;
920
921   g_return_val_if_fail (GTK_IS_HANDLE_BOX (widget), FALSE);
922   g_return_val_if_fail (event != NULL, FALSE);
923
924   if (GTK_WIDGET_DRAWABLE (widget))
925     {
926       hb = GTK_HANDLE_BOX (widget);
927
928       if (event->window == widget->window)
929         {
930           if (hb->child_detached)
931             gtk_handle_box_draw_ghost (hb);
932         }
933       else
934         gtk_handle_box_paint (widget, event, NULL);
935     }
936   
937   return FALSE;
938 }
939
940 static gint
941 gtk_handle_box_button_changed (GtkWidget      *widget,
942                                GdkEventButton *event)
943 {
944   GtkHandleBox *hb;
945   gboolean event_handled;
946   GdkCursor *fleur;
947   
948   g_return_val_if_fail (GTK_IS_HANDLE_BOX (widget), FALSE);
949   g_return_val_if_fail (event != NULL, FALSE);
950
951   hb = GTK_HANDLE_BOX (widget);
952
953   event_handled = FALSE;
954   if ((event->button == 1) && 
955       (event->type == GDK_BUTTON_PRESS || event->type == GDK_2BUTTON_PRESS))
956     {
957       GtkWidget *child;
958       gboolean in_handle;
959       
960       if (event->window != hb->bin_window)
961         return FALSE;
962
963       child = GTK_BIN (hb)->child;
964
965       if (child)
966         {
967           switch (hb->handle_position)
968             {
969             case GTK_POS_LEFT:
970               in_handle = event->x < DRAG_HANDLE_SIZE;
971               break;
972             case GTK_POS_TOP:
973               in_handle = event->y < DRAG_HANDLE_SIZE;
974               break;
975             case GTK_POS_RIGHT:
976               in_handle = event->x > 2 * GTK_CONTAINER (hb)->border_width + child->allocation.width;
977               break;
978             case GTK_POS_BOTTOM:
979               in_handle = event->y > 2 * GTK_CONTAINER (hb)->border_width + child->allocation.height;
980               break;
981             default:
982               in_handle = FALSE;
983               break;
984             }
985         }
986       else
987         {
988           in_handle = FALSE;
989           event_handled = TRUE;
990         }
991       
992       if (in_handle)
993         {
994           if (event->type == GDK_BUTTON_PRESS) /* Start a drag */
995             {
996               gint desk_x, desk_y;
997               gint root_x, root_y;
998               gint width, height;
999               
1000               gdk_window_get_deskrelative_origin (hb->bin_window, &desk_x, &desk_y);
1001               gdk_window_get_origin (hb->bin_window, &root_x, &root_y);
1002               gdk_window_get_size (hb->bin_window, &width, &height);
1003               
1004               hb->float_allocation.x = root_x - event->x_root;
1005               hb->float_allocation.y = root_y - event->y_root;
1006               hb->float_allocation.width = width;
1007               hb->float_allocation.height = height;
1008               
1009               hb->deskoff_x = desk_x - root_x;
1010               hb->deskoff_y = desk_y - root_y;
1011               
1012               gdk_window_get_origin (widget->window, &root_x, &root_y);
1013               gdk_window_get_size (widget->window, &width, &height);
1014               
1015               hb->attach_allocation.x = root_x;
1016               hb->attach_allocation.y = root_y;
1017               hb->attach_allocation.width = width;
1018               hb->attach_allocation.height = height;
1019
1020               hb->in_drag = TRUE;
1021               fleur = gdk_cursor_new (GDK_FLEUR);
1022               if (gdk_pointer_grab (widget->window,
1023                                     FALSE,
1024                                     (GDK_BUTTON1_MOTION_MASK |
1025                                      GDK_POINTER_MOTION_HINT_MASK |
1026                                      GDK_BUTTON_RELEASE_MASK),
1027                                     NULL,
1028                                     fleur,
1029                                     GDK_CURRENT_TIME) != 0)
1030                 {
1031                   hb->in_drag = FALSE;
1032                 }
1033               
1034               gdk_cursor_destroy (fleur);
1035               event_handled = TRUE;
1036             }
1037           else if (hb->child_detached) /* Double click */
1038             {
1039               gtk_handle_box_reattach (hb);
1040             }
1041         }
1042     }
1043   else if (event->type == GDK_BUTTON_RELEASE &&
1044            hb->in_drag)
1045     {
1046       if (event->window != widget->window)
1047         return FALSE;
1048       
1049       gdk_pointer_ungrab (GDK_CURRENT_TIME);
1050       hb->in_drag = FALSE;
1051       event_handled = TRUE;
1052     }
1053   
1054   return event_handled;
1055 }
1056
1057 static gint
1058 gtk_handle_box_motion (GtkWidget      *widget,
1059                        GdkEventMotion *event)
1060 {
1061   GtkHandleBox *hb;
1062   gint new_x, new_y;
1063   gint snap_edge;
1064   gboolean is_snapped = FALSE;
1065
1066   g_return_val_if_fail (GTK_IS_HANDLE_BOX (widget), FALSE);
1067   g_return_val_if_fail (event != NULL, FALSE);
1068
1069   hb = GTK_HANDLE_BOX (widget);
1070   if (!hb->in_drag)
1071     return FALSE;
1072
1073   if (!hb->in_drag || (event->window != widget->window))
1074     return FALSE;
1075   
1076   /* Calculate the attachment point on the float, if the float
1077    * were detached
1078    */
1079   new_x = 0;
1080   new_y = 0;
1081   gdk_window_get_pointer (NULL, &new_x, &new_y, NULL);
1082   new_x += hb->float_allocation.x;
1083   new_y += hb->float_allocation.y;
1084
1085   snap_edge = hb->snap_edge;
1086   if (snap_edge == -1)
1087     snap_edge = (hb->handle_position == GTK_POS_LEFT ||
1088                   hb->handle_position == GTK_POS_RIGHT) ?
1089       GTK_POS_TOP : GTK_POS_LEFT;
1090
1091   /* First, check if the snapped edge is aligned
1092    */
1093   switch (snap_edge)
1094     {
1095     case GTK_POS_TOP:
1096       is_snapped = abs (hb->attach_allocation.y - new_y) < TOLERANCE;
1097       break;
1098     case GTK_POS_BOTTOM:
1099       is_snapped = abs (hb->attach_allocation.y + (gint)hb->attach_allocation.height -
1100                         new_y - (gint)hb->float_allocation.height) < TOLERANCE;
1101       break;
1102     case GTK_POS_LEFT:
1103       is_snapped = abs (hb->attach_allocation.x - new_x) < TOLERANCE;
1104       break;
1105     case GTK_POS_RIGHT:
1106       is_snapped = abs (hb->attach_allocation.x + (gint)hb->attach_allocation.width -
1107                         new_x - (gint)hb->float_allocation.width) < TOLERANCE;
1108       break;
1109     }
1110
1111   /* Next, check if coordinates in the other direction are sufficiently
1112    * aligned
1113    */
1114   if (is_snapped)
1115     {
1116       gint float_pos1 = 0;      /* Initialize to suppress warnings */
1117       gint float_pos2 = 0;
1118       gint attach_pos1 = 0;
1119       gint attach_pos2 = 0;
1120       
1121       switch (snap_edge)
1122         {
1123         case GTK_POS_TOP:
1124         case GTK_POS_BOTTOM:
1125           attach_pos1 = hb->attach_allocation.x;
1126           attach_pos2 = hb->attach_allocation.x + hb->attach_allocation.width;
1127           float_pos1 = new_x;
1128           float_pos2 = new_x + hb->float_allocation.width;
1129           break;
1130         case GTK_POS_LEFT:
1131         case GTK_POS_RIGHT:
1132           attach_pos1 = hb->attach_allocation.y;
1133           attach_pos2 = hb->attach_allocation.y + hb->attach_allocation.height;
1134           float_pos1 = new_y;
1135           float_pos2 = new_y + hb->float_allocation.height;
1136           break;
1137         }
1138
1139       is_snapped = ((attach_pos1 - TOLERANCE < float_pos1) && 
1140                     (attach_pos2 + TOLERANCE > float_pos2)) ||
1141                    ((float_pos1 - TOLERANCE < attach_pos1) &&
1142                     (float_pos2 + TOLERANCE > attach_pos2));
1143     }
1144
1145   if (is_snapped)
1146     {
1147       if (hb->child_detached)
1148         {
1149           hb->child_detached = FALSE;
1150           gdk_window_hide (hb->float_window);
1151           gdk_window_reparent (hb->bin_window, widget->window, 0, 0);
1152           hb->float_window_mapped = FALSE;
1153           gtk_signal_emit (GTK_OBJECT (hb),
1154                            handle_box_signals[SIGNAL_CHILD_ATTACHED],
1155                            GTK_BIN (hb)->child);
1156           
1157           gtk_widget_queue_resize (widget);
1158         }
1159     }
1160   else
1161     {
1162       gint width, height;
1163
1164       gdk_window_get_size (hb->float_window, &width, &height);
1165       new_x += hb->deskoff_x;
1166       new_y += hb->deskoff_y;
1167
1168       switch (hb->handle_position)
1169         {
1170         case GTK_POS_LEFT:
1171           new_y += ((gint)hb->float_allocation.height - height) / 2;
1172           break;
1173         case GTK_POS_RIGHT:
1174           new_x += (gint)hb->float_allocation.width - width;
1175           new_y += ((gint)hb->float_allocation.height - height) / 2;
1176           break;
1177         case GTK_POS_TOP:
1178           new_x += ((gint)hb->float_allocation.width - width) / 2;
1179           break;
1180         case GTK_POS_BOTTOM:
1181           new_x += ((gint)hb->float_allocation.width - width) / 2;
1182           new_y += (gint)hb->float_allocation.height - height;
1183           break;
1184         }
1185
1186       if (hb->child_detached)
1187         {
1188           gdk_window_move (hb->float_window, new_x, new_y);
1189           gdk_window_raise (hb->float_window);
1190         }
1191       else
1192         {
1193           gint width;
1194           gint height;
1195           GtkRequisition child_requisition;
1196
1197           hb->child_detached = TRUE;
1198
1199           if (GTK_BIN (hb)->child)
1200             gtk_widget_get_child_requisition (GTK_BIN (hb)->child, &child_requisition);
1201           else
1202             {
1203               child_requisition.width = 0;
1204               child_requisition.height = 0;
1205             }      
1206
1207           width = child_requisition.width + 2 * GTK_CONTAINER (hb)->border_width;
1208           height = child_requisition.height + 2 * GTK_CONTAINER (hb)->border_width;
1209           gdk_window_move_resize (hb->float_window, new_x, new_y, width, height);
1210           gdk_window_reparent (hb->bin_window, hb->float_window, 0, 0);
1211           gdk_window_set_hints (hb->float_window, new_x, new_y, 0, 0, 0, 0, GDK_HINT_POS);
1212           gdk_window_show (hb->float_window);
1213           hb->float_window_mapped = TRUE;
1214 #if     0
1215           /* this extra move is neccessary if we use decorations, or our
1216            * window manager insists on decorations.
1217            */
1218           gdk_flush ();
1219           gdk_window_move (hb->float_window, new_x, new_y);
1220           gdk_flush ();
1221 #endif  /* 0 */
1222           gtk_signal_emit (GTK_OBJECT (hb),
1223                            handle_box_signals[SIGNAL_CHILD_DETACHED],
1224                            GTK_BIN (hb)->child);
1225           gtk_handle_box_draw_ghost (hb);
1226           
1227           gtk_widget_queue_resize (widget);
1228         }
1229     }
1230
1231   return TRUE;
1232 }
1233
1234 static void
1235 gtk_handle_box_add (GtkContainer *container,
1236                     GtkWidget    *widget)
1237 {
1238   g_return_if_fail (GTK_IS_HANDLE_BOX (container));
1239   g_return_if_fail (GTK_BIN (container)->child == NULL);
1240   g_return_if_fail (widget->parent == NULL);
1241
1242   gtk_widget_set_parent_window (widget, GTK_HANDLE_BOX (container)->bin_window);
1243   GTK_CONTAINER_CLASS (parent_class)->add (container, widget);
1244 }
1245
1246 static void
1247 gtk_handle_box_remove (GtkContainer *container,
1248                        GtkWidget    *widget)
1249 {
1250   g_return_if_fail (GTK_IS_HANDLE_BOX (container));
1251   g_return_if_fail (GTK_BIN (container)->child == widget);
1252
1253   GTK_CONTAINER_CLASS (parent_class)->remove (container, widget);
1254
1255   gtk_handle_box_reattach (GTK_HANDLE_BOX (container));
1256 }
1257
1258 static gint
1259 gtk_handle_box_delete_event (GtkWidget *widget,
1260                              GdkEventAny  *event)
1261 {
1262   GtkHandleBox *hb;
1263
1264   g_return_val_if_fail (GTK_IS_HANDLE_BOX (widget), FALSE);
1265   g_return_val_if_fail (event != NULL, FALSE);
1266
1267   hb = GTK_HANDLE_BOX (widget);
1268
1269   if (event->window == hb->float_window)
1270     {
1271       gtk_handle_box_reattach (hb);
1272       
1273       return TRUE;
1274     }
1275
1276   return FALSE;
1277 }
1278
1279 static void
1280 gtk_handle_box_reattach (GtkHandleBox *hb)
1281 {
1282   if (hb->child_detached)
1283     {
1284       hb->child_detached = FALSE;
1285       if (GTK_WIDGET_REALIZED (hb))
1286         {
1287           gdk_window_hide (hb->float_window);
1288           gdk_window_reparent (hb->bin_window, GTK_WIDGET (hb)->window, 0, 0);
1289
1290           if (GTK_BIN (hb)->child)
1291             gtk_signal_emit (GTK_OBJECT (hb),
1292                              handle_box_signals[SIGNAL_CHILD_ATTACHED],
1293                              GTK_BIN (hb)->child);
1294
1295         }
1296       hb->float_window_mapped = FALSE;
1297     }
1298   if (hb->in_drag)
1299     {
1300       gdk_pointer_ungrab (GDK_CURRENT_TIME);
1301       hb->in_drag = FALSE;
1302     }
1303
1304   gtk_widget_queue_resize (GTK_WIDGET (hb));
1305 }