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