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