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