1 /* GIMP - The GNU Image Manipulation Program
2  * Copyright (C) 1995 Spencer Kimball and Peter Mattis
3  *
4  * gimppanedbox.c
5  * Copyright (C) 2001-2005 Michael Natterer <mitch@gimp.org>
6  * Copyright (C) 2009-2011 Martin Nordholts <martinn@src.gnome.org>
7  *
8  * This program is free software: you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 3 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
20  */
21 
22 #include "config.h"
23 
24 #include <gegl.h>
25 #ifdef GDK_DISABLE_DEPRECATED
26 #undef GDK_DISABLE_DEPRECATED
27 #endif
28 #include <gtk/gtk.h>
29 
30 #include "libgimpcolor/gimpcolor.h"
31 #include "libgimpwidgets/gimpwidgets.h"
32 
33 #include "widgets-types.h"
34 
35 #include "core/gimp.h"
36 #include "core/gimpcontext.h"
37 #include "core/gimpmarshal.h"
38 
39 #include "gimpdialogfactory.h"
40 #include "gimpdnd.h"
41 #include "gimpdockable.h"
42 #include "gimpdockbook.h"
43 #include "gimpmenudock.h"
44 #include "gimppanedbox.h"
45 #include "gimptoolbox.h"
46 
47 #include "gimp-log.h"
48 
49 
50 /**
51  * Defines the size of the area that dockables can be dropped on in
52  * order to be inserted and get space on their own (rather than
53  * inserted among others and sharing space)
54  */
55 #define DROP_AREA_SIZE                  6
56 
57 #define DROP_HIGHLIGHT_MIN_SIZE         32
58 #define DROP_HIGHLIGHT_COLOR            "#215d9c"
59 #define DROP_HIGHLIGHT_OPACITY_ACTIVE   1.0
60 #define DROP_HIGHLIGHT_OPACITY_INACTIVE 0.5
61 
62 #define INSERT_INDEX_UNUSED             G_MININT
63 
64 
65 struct _GimpPanedBoxPrivate
66 {
67   /* Widgets that are separated by panes */
68   GList                  *widgets;
69 
70   /* Windows used for drag-and-drop output */
71   GdkWindow              *dnd_windows[3];
72   GdkDragContext         *dnd_context;
73   gint                    dnd_paned_position;
74   gint                    dnd_idle_id;
75 
76   /* The insert index to use on drop */
77   gint                    insert_index;
78 
79   /* Callback on drop */
80   GimpPanedBoxDroppedFunc dropped_cb;
81   gpointer                dropped_cb_data;
82 
83   /* A drag handler offered to handle drag events */
84   GimpPanedBox           *drag_handler;
85 };
86 
87 
88 static void      gimp_paned_box_dispose                 (GObject        *object);
89 
90 static void      gimp_paned_box_drag_leave              (GtkWidget      *widget,
91                                                          GdkDragContext *context,
92                                                          guint           time);
93 static gboolean  gimp_paned_box_drag_motion             (GtkWidget      *widget,
94                                                          GdkDragContext *context,
95                                                          gint            x,
96                                                          gint            y,
97                                                          guint           time);
98 static gboolean  gimp_paned_box_drag_drop               (GtkWidget      *widget,
99                                                          GdkDragContext *context,
100                                                          gint            x,
101                                                          gint            y,
102                                                          guint           time);
103 static void      gimp_paned_box_realize                 (GtkWidget      *widget);
104 static void      gimp_paned_box_unrealize               (GtkWidget      *widget);
105 static void      gimp_paned_box_set_widget_drag_handler (GtkWidget      *widget,
106                                                          GimpPanedBox   *handler);
107 static gint      gimp_paned_box_get_drop_area_size      (GimpPanedBox   *paned_box);
108 
109 static void      gimp_paned_box_drag_callback           (GdkDragContext *context,
110                                                          gboolean        begin,
111                                                          GimpPanedBox   *paned_box);
112 
113 
114 G_DEFINE_TYPE_WITH_PRIVATE (GimpPanedBox, gimp_paned_box, GTK_TYPE_BOX)
115 
116 #define parent_class gimp_paned_box_parent_class
117 
118 static const GtkTargetEntry dialog_target_table[] = { GIMP_TARGET_DIALOG };
119 
120 
121 static void
gimp_paned_box_class_init(GimpPanedBoxClass * klass)122 gimp_paned_box_class_init (GimpPanedBoxClass *klass)
123 {
124   GObjectClass   *object_class = G_OBJECT_CLASS (klass);
125   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
126 
127   object_class->dispose     = gimp_paned_box_dispose;
128 
129   widget_class->drag_leave  = gimp_paned_box_drag_leave;
130   widget_class->drag_motion = gimp_paned_box_drag_motion;
131   widget_class->drag_drop   = gimp_paned_box_drag_drop;
132   widget_class->realize     = gimp_paned_box_realize;
133   widget_class->unrealize   = gimp_paned_box_unrealize;
134 }
135 
136 static void
gimp_paned_box_init(GimpPanedBox * paned_box)137 gimp_paned_box_init (GimpPanedBox *paned_box)
138 {
139   paned_box->p = gimp_paned_box_get_instance_private (paned_box);
140 
141   /* Setup DND */
142   gtk_drag_dest_set (GTK_WIDGET (paned_box),
143                      0,
144                      dialog_target_table, G_N_ELEMENTS (dialog_target_table),
145                      GDK_ACTION_MOVE);
146 
147   gimp_dockbook_add_drag_callback (
148     (GimpDockbookDragCallback) gimp_paned_box_drag_callback,
149     paned_box);
150 }
151 
152 static void
gimp_paned_box_dispose(GObject * object)153 gimp_paned_box_dispose (GObject *object)
154 {
155   GimpPanedBox *paned_box = GIMP_PANED_BOX (object);
156 
157   if (paned_box->p->dnd_idle_id)
158     {
159       g_source_remove (paned_box->p->dnd_idle_id);
160 
161       paned_box->p->dnd_idle_id = 0;
162     }
163 
164   while (paned_box->p->widgets)
165     {
166       GtkWidget *widget = paned_box->p->widgets->data;
167 
168       g_object_ref (widget);
169       gimp_paned_box_remove_widget (paned_box, widget);
170       gtk_widget_destroy (widget);
171       g_object_unref (widget);
172     }
173 
174   gimp_dockbook_remove_drag_callback (
175     (GimpDockbookDragCallback) gimp_paned_box_drag_callback,
176     paned_box);
177 
178   G_OBJECT_CLASS (parent_class)->dispose (object);
179 }
180 
181 static void
gimp_paned_box_realize(GtkWidget * widget)182 gimp_paned_box_realize (GtkWidget *widget)
183 {
184   GTK_WIDGET_CLASS (parent_class)->realize (widget);
185 
186   /* We realize() dnd_window on demand in
187    * gimp_paned_box_show_separators()
188    */
189 }
190 
191 static void
gimp_paned_box_unrealize(GtkWidget * widget)192 gimp_paned_box_unrealize (GtkWidget *widget)
193 {
194   GimpPanedBox *paned_box = GIMP_PANED_BOX (widget);
195   gint          i;
196 
197   for (i = 0; i < G_N_ELEMENTS (paned_box->p->dnd_windows); i++)
198     {
199       GdkWindow *window = paned_box->p->dnd_windows[i];
200 
201       if (window)
202         {
203           gdk_window_set_user_data (window, NULL);
204           gdk_window_destroy (window);
205 
206           paned_box->p->dnd_windows[i] = NULL;
207         }
208     }
209 
210   GTK_WIDGET_CLASS (parent_class)->unrealize (widget);
211 }
212 
213 static void
gimp_paned_box_set_widget_drag_handler(GtkWidget * widget,GimpPanedBox * drag_handler)214 gimp_paned_box_set_widget_drag_handler (GtkWidget    *widget,
215                                         GimpPanedBox *drag_handler)
216 {
217   /* Hook us in for drag events. We could abstract this properly and
218    * put gimp_paned_box_will_handle_drag() in an interface for
219    * example, but it doesn't feel worth it at this point
220    *
221    * Note that we don't have 'else if's because a widget can be both a
222    * dock and a toolbox for example, in which case we want to set a
223    * drag handler in two ways
224    *
225    * We so need to introduce some abstractions here...
226    */
227 
228   if (GIMP_IS_DOCKBOOK (widget))
229     {
230       gimp_dockbook_set_drag_handler (GIMP_DOCKBOOK (widget),
231                                       drag_handler);
232     }
233 
234   if (GIMP_IS_DOCK (widget))
235     {
236       GimpPanedBox *dock_paned_box = NULL;
237       dock_paned_box = GIMP_PANED_BOX (gimp_dock_get_vbox (GIMP_DOCK (widget)));
238       gimp_paned_box_set_drag_handler (dock_paned_box, drag_handler);
239     }
240 
241   if (GIMP_IS_TOOLBOX (widget))
242     {
243       GimpToolbox *toolbox = GIMP_TOOLBOX (widget);
244       gimp_toolbox_set_drag_handler (toolbox, drag_handler);
245     }
246 }
247 
248 static gint
gimp_paned_box_get_drop_area_size(GimpPanedBox * paned_box)249 gimp_paned_box_get_drop_area_size (GimpPanedBox *paned_box)
250 {
251   gint drop_area_size = 0;
252 
253   if (! paned_box->p->widgets)
254     {
255       GtkAllocation  allocation;
256       GtkOrientation orientation;
257 
258       gtk_widget_get_allocation (GTK_WIDGET (paned_box), &allocation);
259       orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (paned_box));
260 
261       if (orientation == GTK_ORIENTATION_HORIZONTAL)
262         drop_area_size = allocation.width;
263       else if (orientation == GTK_ORIENTATION_VERTICAL)
264         drop_area_size = allocation.height;
265     }
266 
267   drop_area_size = MAX (drop_area_size, DROP_AREA_SIZE);
268 
269   return drop_area_size;
270 }
271 
272 static gboolean
gimp_paned_box_get_handle_drag(GimpPanedBox * paned_box,GdkDragContext * context,gint x,gint y,guint time,gint * insert_index,GeglRectangle * area)273 gimp_paned_box_get_handle_drag (GimpPanedBox   *paned_box,
274                                 GdkDragContext *context,
275                                 gint            x,
276                                 gint            y,
277                                 guint           time,
278                                 gint           *insert_index,
279                                 GeglRectangle  *area)
280 {
281   gint           index          = INSERT_INDEX_UNUSED;
282   GtkAllocation  allocation     = { 0, };
283   gint           area_x         = 0;
284   gint           area_y         = 0;
285   gint           area_w         = 0;
286   gint           area_h         = 0;
287   GtkOrientation orientation    = 0;
288   gint           drop_area_size = gimp_paned_box_get_drop_area_size (paned_box);
289 
290   if (gimp_paned_box_will_handle_drag (paned_box->p->drag_handler,
291                                        GTK_WIDGET (paned_box),
292                                        context,
293                                        x, y,
294                                        time))
295     {
296       return FALSE;
297     }
298 
299   if (gtk_drag_dest_find_target (GTK_WIDGET (paned_box), context, NULL) ==
300       GDK_NONE)
301     {
302       return FALSE;
303     }
304 
305   gtk_widget_get_allocation (GTK_WIDGET (paned_box), &allocation);
306 
307   /* See if we're at the edge of the dock If there are no dockables,
308    * the entire paned box is a drop area
309    */
310   orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (paned_box));
311   if (orientation == GTK_ORIENTATION_HORIZONTAL)
312     {
313       area_y = 0;
314       area_h = allocation.height;
315 
316       /* If there are no widgets, the drop area is as big as the paned
317        * box
318        */
319       if (! paned_box->p->widgets)
320         area_w = allocation.width;
321       else
322         area_w = drop_area_size;
323 
324       if (x < drop_area_size)
325         {
326           index = 0;
327           area_x = 0;
328         }
329       if (x > allocation.width - drop_area_size)
330         {
331           index = -1;
332           area_x = allocation.width - drop_area_size;
333         }
334     }
335   else /* if (orientation = GTK_ORIENTATION_VERTICAL) */
336     {
337       area_x = 0;
338       area_w = allocation.width;
339 
340       /* If there are no widgets, the drop area is as big as the paned
341        * box
342        */
343       if (! paned_box->p->widgets)
344         area_h = allocation.height;
345       else
346         area_h = drop_area_size;
347 
348       if (y < drop_area_size)
349         {
350           index = 0;
351           area_y = 0;
352         }
353       if (y > allocation.height - drop_area_size)
354         {
355           index = -1;
356           area_y = allocation.height - drop_area_size;
357         }
358     }
359 
360   if (area)
361     {
362       area->x      = allocation.x + area_x;
363       area->y      = allocation.y + area_y;
364       area->width  = area_w;
365       area->height = area_h;
366     }
367 
368   if (insert_index)
369     *insert_index = index;
370 
371   return index != INSERT_INDEX_UNUSED;
372 }
373 
374 static void
gimp_paned_box_position_drop_indicator(GimpPanedBox * paned_box,gint index,gint x,gint y,gint width,gint height,gdouble opacity)375 gimp_paned_box_position_drop_indicator (GimpPanedBox *paned_box,
376                                         gint          index,
377                                         gint          x,
378                                         gint          y,
379                                         gint          width,
380                                         gint          height,
381                                         gdouble       opacity)
382 {
383   GtkWidget    *widget = GTK_WIDGET (paned_box);
384   GdkWindow    *window = paned_box->p->dnd_windows[index];
385   GtkStyle     *style  = gtk_widget_get_style (widget);
386   GtkStateType  state  = gtk_widget_get_state (widget);
387   GimpRGB       bg;
388   GimpRGB       fg;
389   GdkColor      color;
390 
391   if (! gtk_widget_is_drawable (widget))
392     return;
393 
394   /* Create or move the GdkWindow in place */
395   if (! window)
396     {
397       GtkAllocation allocation;
398       GdkWindowAttr attributes;
399 
400       gtk_widget_get_allocation (widget, &allocation);
401 
402       attributes.x           = x;
403       attributes.y           = y;
404       attributes.width       = width;
405       attributes.height      = height;
406       attributes.window_type = GDK_WINDOW_CHILD;
407       attributes.wclass      = GDK_INPUT_OUTPUT;
408       attributes.event_mask  = gtk_widget_get_events (widget);
409 
410       window = gdk_window_new (gtk_widget_get_window (widget),
411                                &attributes,
412                                GDK_WA_X | GDK_WA_Y);
413       gdk_window_set_user_data (window, widget);
414 
415       paned_box->p->dnd_windows[index] = window;
416     }
417   else
418     {
419       gdk_window_move_resize (window,
420                               x, y,
421                               width, height);
422     }
423 
424   gimp_rgb_set_uchar (&bg,
425                       style->bg[state].red   >> 8,
426                       style->bg[state].green >> 8,
427                       style->bg[state].blue  >> 8);
428   bg.a = 1.0;
429 
430   gimp_rgb_parse_hex (&fg, DROP_HIGHLIGHT_COLOR, -1);
431   fg.a = opacity;
432 
433   gimp_rgb_composite (&bg, &fg, GIMP_RGB_COMPOSITE_NORMAL);
434 
435   color.red   = bg.r * 0xffff;
436   color.green = bg.g * 0xffff;
437   color.blue  = bg.b * 0xffff;
438 
439   gdk_rgb_find_color (gtk_widget_get_colormap (widget), &color);
440 
441   gdk_window_set_background (window, &color);
442 
443   gdk_window_show (window);
444 }
445 
446 static void
gimp_paned_box_hide_drop_indicator(GimpPanedBox * paned_box,gint index)447 gimp_paned_box_hide_drop_indicator (GimpPanedBox *paned_box,
448                                     gint          index)
449 {
450   if (! paned_box->p->dnd_windows[index])
451     return;
452 
453   gdk_window_hide (paned_box->p->dnd_windows[index]);
454 }
455 
456 static void
gimp_paned_box_drag_leave(GtkWidget * widget,GdkDragContext * context,guint time)457 gimp_paned_box_drag_leave (GtkWidget      *widget,
458                            GdkDragContext *context,
459                            guint           time)
460 {
461   GimpPanedBox *paned_box = GIMP_PANED_BOX (widget);
462 
463   gimp_paned_box_hide_drop_indicator (paned_box, 0);
464 }
465 
466 static gboolean
gimp_paned_box_drag_motion(GtkWidget * widget,GdkDragContext * context,gint x,gint y,guint time)467 gimp_paned_box_drag_motion (GtkWidget      *widget,
468                             GdkDragContext *context,
469                             gint            x,
470                             gint            y,
471                             guint           time)
472 {
473   GimpPanedBox  *paned_box = GIMP_PANED_BOX (widget);
474   gint           insert_index;
475   GeglRectangle  area;
476   gboolean       handle;
477 
478   handle = gimp_paned_box_get_handle_drag (paned_box, context, x, y, time,
479                                            &insert_index, &area);
480 
481   /* If we are at the edge, show a GdkWindow to communicate that a
482    * drop will create a new dock column
483    */
484   if (handle)
485     {
486       gimp_paned_box_position_drop_indicator (paned_box,
487                                               0,
488                                               area.x,
489                                               area.y,
490                                               area.width,
491                                               area.height,
492                                               DROP_HIGHLIGHT_OPACITY_ACTIVE);
493     }
494   else
495     {
496       gimp_paned_box_hide_drop_indicator (paned_box, 0);
497     }
498 
499   /* Save the insert index for drag-drop */
500   paned_box->p->insert_index = insert_index;
501 
502   gdk_drag_status (context, handle ? GDK_ACTION_MOVE : 0, time);
503 
504   /* Return TRUE so drag_leave() is called */
505   return handle;
506 }
507 
508 static gboolean
gimp_paned_box_drag_drop(GtkWidget * widget,GdkDragContext * context,gint x,gint y,guint time)509 gimp_paned_box_drag_drop (GtkWidget      *widget,
510                           GdkDragContext *context,
511                           gint            x,
512                           gint            y,
513                           guint           time)
514 {
515   GimpPanedBox *paned_box = GIMP_PANED_BOX (widget);
516   gboolean      dropped   = FALSE;
517 
518   if (gimp_paned_box_will_handle_drag (paned_box->p->drag_handler,
519                                        widget,
520                                        context,
521                                        x, y,
522                                        time))
523     {
524       return FALSE;
525     }
526 
527   if (paned_box->p->dropped_cb)
528     {
529       GtkWidget *source = gtk_drag_get_source_widget (context);
530 
531       if (source)
532         dropped = paned_box->p->dropped_cb (source,
533                                             paned_box->p->insert_index,
534                                             paned_box->p->dropped_cb_data);
535     }
536 
537   gtk_drag_finish (context, dropped, TRUE, time);
538 
539   return TRUE;
540 }
541 
542 static gboolean
gimp_paned_box_drag_callback_idle(GimpPanedBox * paned_box)543 gimp_paned_box_drag_callback_idle (GimpPanedBox *paned_box)
544 {
545   GtkAllocation  allocation;
546   GtkOrientation orientation;
547   GeglRectangle  area;
548 
549   paned_box->p->dnd_idle_id = 0;
550 
551   gtk_widget_get_allocation (GTK_WIDGET (paned_box), &allocation);
552   orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (paned_box));
553 
554   #define ADD_AREA(index, left, top)               \
555     if (gimp_paned_box_get_handle_drag (           \
556         paned_box,                                 \
557         paned_box->p->dnd_context,                 \
558         (left), (top),                             \
559         0,                                         \
560         NULL, &area))                              \
561       {                                            \
562         gimp_paned_box_position_drop_indicator (   \
563           paned_box,                               \
564           index,                                   \
565           area.x, area.y, area.width, area.height, \
566           DROP_HIGHLIGHT_OPACITY_INACTIVE);        \
567       }
568 
569   if (! paned_box->p->widgets)
570     {
571       ADD_AREA (1, allocation.width / 2, allocation.height / 2)
572     }
573   else if (orientation == GTK_ORIENTATION_HORIZONTAL)
574     {
575       ADD_AREA (1, 0,                    allocation.height / 2)
576       ADD_AREA (2, allocation.width - 1, allocation.height / 2)
577     }
578   else
579     {
580       ADD_AREA (1, allocation.width / 2, 0)
581       ADD_AREA (2, allocation.width / 2, allocation.height - 1)
582     }
583 
584   #undef ADD_AREA
585 
586   return G_SOURCE_REMOVE;
587 }
588 
589 static void
gimp_paned_box_drag_callback(GdkDragContext * context,gboolean begin,GimpPanedBox * paned_box)590 gimp_paned_box_drag_callback (GdkDragContext *context,
591                               gboolean        begin,
592                               GimpPanedBox   *paned_box)
593 {
594   GtkWidget *paned;
595   gint       position;
596 
597   if (! gtk_widget_get_sensitive (GTK_WIDGET (paned_box)))
598     return;
599 
600   paned = gtk_widget_get_ancestor (GTK_WIDGET (paned_box),
601                                    GTK_TYPE_PANED);
602 
603   /* apparently, we can be called multiple times when beginning a drag
604    * (possibly a gtk bug); make sure not to leak the idle.
605    *
606    * see issue #4895.
607    */
608   if (begin && ! paned_box->p->dnd_context)
609     {
610       paned_box->p->dnd_context = context;
611 
612       if (paned)
613         {
614           GtkAllocation allocation;
615 
616           gtk_widget_get_allocation (paned, &allocation);
617 
618           position = gtk_paned_get_position (GTK_PANED (paned));
619 
620           paned_box->p->dnd_paned_position = position;
621 
622           if (position < 0)
623             {
624               position = 0;
625             }
626           else if (gtk_widget_is_ancestor (
627                      GTK_WIDGET (paned_box),
628                      gtk_paned_get_child2 (GTK_PANED (paned))))
629             {
630               position = allocation.width - position;
631             }
632 
633           if (position < DROP_HIGHLIGHT_MIN_SIZE)
634             {
635               position = DROP_HIGHLIGHT_MIN_SIZE;
636 
637               if (gtk_widget_is_ancestor (
638                     GTK_WIDGET (paned_box),
639                     gtk_paned_get_child2 (GTK_PANED (paned))))
640                 {
641                   position = allocation.width - position;
642                 }
643 
644               gtk_paned_set_position (GTK_PANED (paned), position);
645             }
646         }
647 
648       paned_box->p->dnd_idle_id = g_idle_add (
649         (GSourceFunc) gimp_paned_box_drag_callback_idle,
650         paned_box);
651     }
652   else if (! begin && paned_box->p->dnd_context)
653     {
654       if (paned_box->p->dnd_idle_id)
655         {
656           g_source_remove (paned_box->p->dnd_idle_id);
657 
658           paned_box->p->dnd_idle_id = 0;
659         }
660 
661       paned_box->p->dnd_context = NULL;
662 
663       gimp_paned_box_hide_drop_indicator (paned_box, 1);
664       gimp_paned_box_hide_drop_indicator (paned_box, 2);
665 
666       if (paned)
667         {
668           gtk_paned_set_position (GTK_PANED (paned),
669                                   paned_box->p->dnd_paned_position);
670         }
671     }
672 }
673 
674 
675 GtkWidget *
gimp_paned_box_new(gboolean homogeneous,gint spacing,GtkOrientation orientation)676 gimp_paned_box_new (gboolean       homogeneous,
677                     gint           spacing,
678                     GtkOrientation orientation)
679 {
680   return g_object_new (GIMP_TYPE_PANED_BOX,
681                        "homogeneous",  homogeneous,
682                        "spacing",      0,
683                        "orientation",  orientation,
684                        NULL);
685 }
686 
687 void
gimp_paned_box_set_dropped_cb(GimpPanedBox * paned_box,GimpPanedBoxDroppedFunc dropped_cb,gpointer dropped_cb_data)688 gimp_paned_box_set_dropped_cb (GimpPanedBox            *paned_box,
689                                GimpPanedBoxDroppedFunc  dropped_cb,
690                                gpointer                 dropped_cb_data)
691 {
692   g_return_if_fail (GIMP_IS_PANED_BOX (paned_box));
693 
694   paned_box->p->dropped_cb      = dropped_cb;
695   paned_box->p->dropped_cb_data = dropped_cb_data;
696 }
697 
698 /**
699  * gimp_paned_box_add_widget:
700  * @paned_box: A #GimpPanedBox
701  * @widget:    The #GtkWidget to add
702  * @index:     Where to add the @widget
703  *
704  * Add a #GtkWidget to the #GimpPanedBox in a hierarchy of #GtkPaned:s
705  * so the space can be manually distributed between the widgets.
706  **/
707 void
gimp_paned_box_add_widget(GimpPanedBox * paned_box,GtkWidget * widget,gint index)708 gimp_paned_box_add_widget (GimpPanedBox *paned_box,
709                            GtkWidget    *widget,
710                            gint          index)
711 {
712   gint old_length = 0;
713 
714   g_return_if_fail (GIMP_IS_PANED_BOX (paned_box));
715   g_return_if_fail (GTK_IS_WIDGET (widget));
716 
717   GIMP_LOG (DND, "Adding GtkWidget %p to GimpPanedBox %p", widget, paned_box);
718 
719   /* Calculate length */
720   old_length = g_list_length (paned_box->p->widgets);
721 
722   /* If index is invalid append at the end */
723   if (index >= old_length || index < 0)
724     {
725       index = old_length;
726     }
727 
728   /* Insert into the list */
729   paned_box->p->widgets = g_list_insert (paned_box->p->widgets, widget, index);
730 
731   /* Hook us in for drag events. We could abstract this but it doesn't
732    * seem worth it at this point
733    */
734   gimp_paned_box_set_widget_drag_handler (widget, paned_box);
735 
736   /* Insert into the GtkPaned hierarchy */
737   if (old_length == 0)
738     {
739       gtk_box_pack_start (GTK_BOX (paned_box), widget, TRUE, TRUE, 0);
740     }
741   else
742     {
743       GtkWidget      *old_widget;
744       GtkWidget      *parent;
745       GtkWidget      *paned;
746       GtkOrientation  orientation;
747 
748       /* Figure out what widget to detach */
749       if (index == 0)
750         {
751           old_widget = g_list_nth_data (paned_box->p->widgets, index + 1);
752         }
753       else
754         {
755           old_widget = g_list_nth_data (paned_box->p->widgets, index - 1);
756         }
757 
758       parent = gtk_widget_get_parent (old_widget);
759 
760       if (old_length > 1 && index > 0)
761         {
762           GtkWidget *grandparent = gtk_widget_get_parent (parent);
763 
764           old_widget = parent;
765           parent     = grandparent;
766         }
767 
768       /* Detach the widget and build up a new hierarchy */
769       g_object_ref (old_widget);
770       gtk_container_remove (GTK_CONTAINER (parent), old_widget);
771 
772       /* GtkPaned is abstract :( */
773       orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (paned_box));
774       paned = gtk_paned_new (orientation);
775 
776       if (GTK_IS_PANED (parent))
777         {
778           gtk_paned_pack1 (GTK_PANED (parent), paned, TRUE, FALSE);
779         }
780       else
781         {
782           gtk_box_pack_start (GTK_BOX (parent), paned, TRUE, TRUE, 0);
783         }
784       gtk_widget_show (paned);
785 
786       if (index == 0)
787         {
788           gtk_paned_pack1 (GTK_PANED (paned), widget,
789                            TRUE, FALSE);
790           gtk_paned_pack2 (GTK_PANED (paned), old_widget,
791                            TRUE, FALSE);
792         }
793       else
794         {
795           gtk_paned_pack1 (GTK_PANED (paned), old_widget,
796                            TRUE, FALSE);
797           gtk_paned_pack2 (GTK_PANED (paned), widget,
798                            TRUE, FALSE);
799         }
800 
801       g_object_unref (old_widget);
802     }
803 }
804 
805 /**
806  * gimp_paned_box_remove_widget:
807  * @paned_box: A #GimpPanedBox
808  * @widget:    The #GtkWidget to remove
809  *
810  * Remove a #GtkWidget from a #GimpPanedBox added with
811  * gimp_widgets_add_paned_widget().
812  **/
813 void
gimp_paned_box_remove_widget(GimpPanedBox * paned_box,GtkWidget * widget)814 gimp_paned_box_remove_widget (GimpPanedBox *paned_box,
815                               GtkWidget    *widget)
816 {
817   gint       old_length   = 0;
818   gint       index        = 0;
819   GtkWidget *other_widget = NULL;
820   GtkWidget *parent       = NULL;
821   GtkWidget *grandparent  = NULL;
822 
823   g_return_if_fail (GIMP_IS_PANED_BOX (paned_box));
824   g_return_if_fail (GTK_IS_WIDGET (widget));
825 
826   GIMP_LOG (DND, "Removing GtkWidget %p from GimpPanedBox %p", widget, paned_box);
827 
828   /* Calculate length and index */
829   old_length = g_list_length (paned_box->p->widgets);
830   index      = g_list_index (paned_box->p->widgets, widget);
831 
832   /* Remove from list */
833   paned_box->p->widgets = g_list_remove (paned_box->p->widgets, widget);
834 
835   /* Reset the drag events hook */
836   gimp_paned_box_set_widget_drag_handler (widget, NULL);
837 
838   /* Remove from widget hierarchy */
839   if (old_length == 1)
840     {
841       /* The widget might already be parent-less if we are in
842        * destruction, .e.g when closing a dock window.
843        */
844       if (gtk_widget_get_parent (widget) != NULL)
845         gtk_container_remove (GTK_CONTAINER (paned_box), widget);
846     }
847   else
848     {
849       g_object_ref (widget);
850 
851       parent      = gtk_widget_get_parent (GTK_WIDGET (widget));
852       grandparent = gtk_widget_get_parent (parent);
853 
854       if (index == 0)
855         other_widget = gtk_paned_get_child2 (GTK_PANED (parent));
856       else
857         other_widget = gtk_paned_get_child1 (GTK_PANED (parent));
858 
859       g_object_ref (other_widget);
860 
861       gtk_container_remove (GTK_CONTAINER (parent), other_widget);
862       gtk_container_remove (GTK_CONTAINER (parent), GTK_WIDGET (widget));
863 
864       gtk_container_remove (GTK_CONTAINER (grandparent), parent);
865 
866       if (GTK_IS_PANED (grandparent))
867         gtk_paned_pack1 (GTK_PANED (grandparent), other_widget, TRUE, FALSE);
868       else
869         gtk_box_pack_start (GTK_BOX (paned_box), other_widget, TRUE, TRUE, 0);
870 
871       g_object_unref (other_widget);
872 
873       g_object_unref (widget);
874     }
875 }
876 
877 /**
878  * gimp_paned_box_will_handle_drag:
879  * @paned_box: A #GimpPanedBox
880  * @widget:    The widget that got the drag event
881  * @context:   Context from drag event
882  * @x:         x from drag event
883  * @y:         y from drag event
884  * @time:      time from drag event
885  *
886  * Returns: %TRUE if the drag event on @widget will be handled by
887  *          @paned_box.
888  **/
889 gboolean
gimp_paned_box_will_handle_drag(GimpPanedBox * paned_box,GtkWidget * widget,GdkDragContext * context,gint x,gint y,gint time)890 gimp_paned_box_will_handle_drag (GimpPanedBox   *paned_box,
891                                  GtkWidget      *widget,
892                                  GdkDragContext *context,
893                                  gint            x,
894                                  gint            y,
895                                  gint            time)
896 {
897   gint           paned_box_x    = 0;
898   gint           paned_box_y    = 0;
899   GtkAllocation  allocation     = { 0, };
900   GtkOrientation orientation    = 0;
901   gboolean       will_handle    = FALSE;
902   gint           drop_area_size = 0;
903 
904   g_return_val_if_fail (paned_box == NULL ||
905                         GIMP_IS_PANED_BOX (paned_box), FALSE);
906 
907   /* Check for NULL to allow cleaner client code */
908   if (paned_box == NULL)
909     return FALSE;
910 
911   /* Our handler might handle it */
912   if (gimp_paned_box_will_handle_drag (paned_box->p->drag_handler,
913                                        widget,
914                                        context,
915                                        x, y,
916                                        time))
917     {
918       /* Return TRUE so the client will pass on the drag event */
919       return TRUE;
920     }
921 
922   /* If we don't have a common ancenstor we will not handle it */
923   if (! gtk_widget_translate_coordinates (widget,
924                                           GTK_WIDGET (paned_box),
925                                           x, y,
926                                           &paned_box_x, &paned_box_y))
927     {
928       /* Return FALSE so the client can take care of the drag event */
929       return FALSE;
930     }
931 
932   /* We now have paned_box coordinates, see if the paned_box will
933    * handle the event
934    */
935   gtk_widget_get_allocation (GTK_WIDGET (paned_box), &allocation);
936   orientation    = gtk_orientable_get_orientation (GTK_ORIENTABLE (paned_box));
937   drop_area_size = gimp_paned_box_get_drop_area_size (paned_box);
938   if (orientation == GTK_ORIENTATION_HORIZONTAL)
939     {
940       will_handle = (paned_box_x < drop_area_size ||
941                      paned_box_x > allocation.width - drop_area_size);
942     }
943   else /*if (orientation = GTK_ORIENTATION_VERTICAL)*/
944     {
945       will_handle = (paned_box_y < drop_area_size ||
946                      paned_box_y > allocation.height - drop_area_size);
947     }
948 
949   return will_handle;
950 }
951 
952 void
gimp_paned_box_set_drag_handler(GimpPanedBox * paned_box,GimpPanedBox * drag_handler)953 gimp_paned_box_set_drag_handler (GimpPanedBox *paned_box,
954                                  GimpPanedBox *drag_handler)
955 {
956   g_return_if_fail (GIMP_IS_PANED_BOX (paned_box));
957 
958   paned_box->p->drag_handler = drag_handler;
959 }
960