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