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