1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 *
3 * gdl-dock-paned.h
4 *
5 * This file is part of the GNOME Devtools Libraries.
6 *
7 * Copyright (C) 2002 Gustavo Giráldez <gustavo.giraldez@gmx.net>
8 *
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public
11 * License as published by the Free Software Foundation; either
12 * version 2.1 of the License, or (at your option) any later version.
13 *
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
18 *
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with this library; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
22 */
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
28 #include "gdl-i18n.h"
29 #include <string.h>
30 #include <gtk/gtkhpaned.h>
31 #include <gtk/gtkvpaned.h>
33 #include "gdl-tools.h"
34 #include "gdl-dock-paned.h"
37 /* Private prototypes */
39 static void gdl_dock_paned_class_init (GdlDockPanedClass *klass);
40 static void gdl_dock_paned_instance_init (GdlDockPaned *paned);
41 static GObject *gdl_dock_paned_constructor (GType type,
42 guint n_construct_properties,
43 GObjectConstructParam *construct_param);
44 static void gdl_dock_paned_set_property (GObject *object,
45 guint prop_id,
46 const GValue *value,
47 GParamSpec *pspec);
48 static void gdl_dock_paned_get_property (GObject *object,
49 guint prop_id,
50 GValue *value,
51 GParamSpec *pspec);
53 static void gdl_dock_paned_destroy (GtkObject *object);
55 static void gdl_dock_paned_add (GtkContainer *container,
56 GtkWidget *widget);
57 static void gdl_dock_paned_forall (GtkContainer *container,
58 gboolean include_internals,
59 GtkCallback callback,
60 gpointer callback_data);
61 static GType gdl_dock_paned_child_type (GtkContainer *container);
63 static gboolean gdl_dock_paned_dock_request (GdlDockObject *object,
64 gint x,
65 gint y,
66 GdlDockRequest *request);
67 static void gdl_dock_paned_dock (GdlDockObject *object,
68 GdlDockObject *requestor,
69 GdlDockPlacement position,
70 GValue *other_data);
72 static void gdl_dock_paned_set_orientation (GdlDockItem *item,
73 GtkOrientation orientation);
75 static gboolean gdl_dock_paned_child_placement (GdlDockObject *object,
76 GdlDockObject *child,
77 GdlDockPlacement *placement);
80 /* ----- Class variables and definitions ----- */
82 #define SPLIT_RATIO 0.3
84 enum {
85 PROP_0,
86 PROP_POSITION
87 };
90 /* ----- Private functions ----- */
92 GDL_CLASS_BOILERPLATE (GdlDockPaned, gdl_dock_paned, GdlDockItem, GDL_TYPE_DOCK_ITEM);
94 static void
95 gdl_dock_paned_class_init (GdlDockPanedClass *klass)
96 {
97 GObjectClass *g_object_class;
98 GtkObjectClass *gtk_object_class;
99 GtkWidgetClass *widget_class;
100 GtkContainerClass *container_class;
101 GdlDockObjectClass *object_class;
102 GdlDockItemClass *item_class;
104 g_object_class = G_OBJECT_CLASS (klass);
105 gtk_object_class = GTK_OBJECT_CLASS (klass);
106 widget_class = GTK_WIDGET_CLASS (klass);
107 container_class = GTK_CONTAINER_CLASS (klass);
108 object_class = GDL_DOCK_OBJECT_CLASS (klass);
109 item_class = GDL_DOCK_ITEM_CLASS (klass);
111 g_object_class->set_property = gdl_dock_paned_set_property;
112 g_object_class->get_property = gdl_dock_paned_get_property;
113 g_object_class->constructor = gdl_dock_paned_constructor;
115 gtk_object_class->destroy = gdl_dock_paned_destroy;
117 container_class->add = gdl_dock_paned_add;
118 container_class->forall = gdl_dock_paned_forall;
119 container_class->child_type = gdl_dock_paned_child_type;
121 object_class->is_compound = TRUE;
123 object_class->dock_request = gdl_dock_paned_dock_request;
124 object_class->dock = gdl_dock_paned_dock;
125 object_class->child_placement = gdl_dock_paned_child_placement;
127 item_class->has_grip = FALSE;
128 item_class->set_orientation = gdl_dock_paned_set_orientation;
130 g_object_class_install_property (
131 g_object_class, PROP_POSITION,
132 g_param_spec_uint ("position", _("Position"),
133 _("Position of the divider in pixels"),
134 0, G_MAXINT, 0,
135 G_PARAM_READWRITE |
136 GDL_DOCK_PARAM_EXPORT | GDL_DOCK_PARAM_AFTER));
137 }
139 static void
140 gdl_dock_paned_instance_init (GdlDockPaned *paned)
141 {
142 paned->position_changed = FALSE;
143 }
145 static void
146 gdl_dock_paned_notify_cb (GObject *g_object,
147 GParamSpec *pspec,
148 gpointer user_data)
149 {
150 GdlDockPaned *paned;
152 g_return_if_fail (user_data != NULL && GDL_IS_DOCK_PANED (user_data));
154 /* chain the notification to the GdlDockPaned */
155 g_object_notify (G_OBJECT (user_data), pspec->name);
157 paned = GDL_DOCK_PANED (user_data);
159 if (GDL_DOCK_ITEM_USER_ACTION (user_data) && !strcmp (pspec->name, "position"))
160 paned->position_changed = TRUE;
161 }
163 static gboolean
164 gdl_dock_paned_button_cb (GtkWidget *widget,
165 GdkEventButton *event,
166 gpointer user_data)
167 {
168 GdlDockPaned *paned;
170 g_return_val_if_fail (user_data != NULL && GDL_IS_DOCK_PANED (user_data), FALSE);
172 paned = GDL_DOCK_PANED (user_data);
173 if (event->button == 1) {
174 if (event->type == GDK_BUTTON_PRESS)
175 GDL_DOCK_ITEM_SET_FLAGS (user_data, GDL_DOCK_USER_ACTION);
176 else {
177 GDL_DOCK_ITEM_UNSET_FLAGS (user_data, GDL_DOCK_USER_ACTION);
178 if (paned->position_changed) {
179 /* emit pending layout changed signal to track separator position */
180 if (GDL_DOCK_OBJECT (paned)->master)
181 g_signal_emit_by_name (GDL_DOCK_OBJECT (paned)->master, "layout-changed");
182 paned->position_changed = FALSE;
183 }
184 }
185 }
187 return FALSE;
188 }
190 static void
191 gdl_dock_paned_create_child (GdlDockPaned *paned,
192 GtkOrientation orientation)
193 {
194 GdlDockItem *item;
196 item = GDL_DOCK_ITEM (paned);
198 if (item->child)
199 gtk_widget_unparent (GTK_WIDGET (item->child));
201 /* create the container paned */
202 if (orientation == GTK_ORIENTATION_HORIZONTAL)
203 item->child = gtk_hpaned_new ();
204 else
205 item->child = gtk_vpaned_new ();
207 /* get notification for propagation */
208 g_signal_connect (item->child, "notify::position",
209 (GCallback) gdl_dock_paned_notify_cb, (gpointer) item);
210 g_signal_connect (item->child, "button-press-event",
211 (GCallback) gdl_dock_paned_button_cb, (gpointer) item);
212 g_signal_connect (item->child, "button-release-event",
213 (GCallback) gdl_dock_paned_button_cb, (gpointer) item);
215 gtk_widget_set_parent (item->child, GTK_WIDGET (item));
216 gtk_widget_show (item->child);
217 }
219 static GObject *
220 gdl_dock_paned_constructor (GType type,
221 guint n_construct_properties,
222 GObjectConstructParam *construct_param)
223 {
224 GObject *g_object;
226 g_object = GDL_CALL_PARENT_WITH_DEFAULT (G_OBJECT_CLASS,
227 constructor,
228 (type,
229 n_construct_properties,
230 construct_param),
231 NULL);
232 if (g_object) {
233 GdlDockItem *item = GDL_DOCK_ITEM (g_object);
235 if (!item->child)
236 gdl_dock_paned_create_child (GDL_DOCK_PANED (g_object),
237 item->orientation);
238 /* otherwise, the orientation was set as a construction
239 parameter and the child is already created */
240 }
242 return g_object;
243 }
245 static void
246 gdl_dock_paned_set_property (GObject *object,
247 guint prop_id,
248 const GValue *value,
249 GParamSpec *pspec)
250 {
251 GdlDockItem *item = GDL_DOCK_ITEM (object);
253 switch (prop_id) {
254 case PROP_POSITION:
255 if (item->child && GTK_IS_PANED (item->child))
256 gtk_paned_set_position (GTK_PANED (item->child),
257 g_value_get_uint (value));
258 break;
259 default:
260 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
261 break;
262 }
263 }
265 static void
266 gdl_dock_paned_get_property (GObject *object,
267 guint prop_id,
268 GValue *value,
269 GParamSpec *pspec)
270 {
271 GdlDockItem *item = GDL_DOCK_ITEM (object);
273 switch (prop_id) {
274 case PROP_POSITION:
275 if (item->child && GTK_IS_PANED (item->child))
276 g_value_set_uint (value,
277 gtk_paned_get_position (GTK_PANED (item->child)));
278 else
279 g_value_set_uint (value, 0);
280 break;
281 default:
282 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
283 break;
284 }
285 }
287 static void
288 gdl_dock_paned_destroy (GtkObject *object)
289 {
290 GdlDockItem *item = GDL_DOCK_ITEM (object);
292 /* we need to call the virtual first, since in GdlDockDestroy our
293 children dock objects are detached */
294 GDL_CALL_PARENT (GTK_OBJECT_CLASS, destroy, (object));
296 /* after that we can remove the GtkNotebook */
297 if (item->child) {
298 gtk_widget_unparent (item->child);
299 item->child = NULL;
300 };
301 }
303 static void
304 gdl_dock_paned_add (GtkContainer *container,
305 GtkWidget *widget)
306 {
307 GdlDockItem *item;
308 GtkPaned *paned;
309 GdlDockPlacement pos = GDL_DOCK_NONE;
311 g_return_if_fail (container != NULL && widget != NULL);
312 g_return_if_fail (GDL_IS_DOCK_PANED (container));
313 g_return_if_fail (GDL_IS_DOCK_ITEM (widget));
315 item = GDL_DOCK_ITEM (container);
316 g_return_if_fail (item->child != NULL);
317 paned = GTK_PANED (item->child);
318 g_return_if_fail (!paned->child1 || !paned->child2);
320 if (!paned->child1)
321 pos = item->orientation == GTK_ORIENTATION_HORIZONTAL ?
322 GDL_DOCK_LEFT : GDL_DOCK_TOP;
323 else if (!paned->child2)
324 pos = item->orientation == GTK_ORIENTATION_HORIZONTAL ?
325 GDL_DOCK_RIGHT : GDL_DOCK_BOTTOM;
327 if (pos != GDL_DOCK_NONE)
328 gdl_dock_object_dock (GDL_DOCK_OBJECT (container),
329 GDL_DOCK_OBJECT (widget),
330 pos, NULL);
331 }
333 static void
334 gdl_dock_paned_forall (GtkContainer *container,
335 gboolean include_internals,
336 GtkCallback callback,
337 gpointer callback_data)
338 {
339 GdlDockItem *item;
341 g_return_if_fail (container != NULL);
342 g_return_if_fail (GDL_IS_DOCK_PANED (container));
343 g_return_if_fail (callback != NULL);
345 if (include_internals) {
346 /* use GdlDockItem's forall */
347 GDL_CALL_PARENT (GTK_CONTAINER_CLASS, forall,
348 (container, include_internals, callback, callback_data));
349 }
350 else {
351 item = GDL_DOCK_ITEM (container);
352 if (item->child)
353 gtk_container_foreach (GTK_CONTAINER (item->child), callback, callback_data);
354 }
355 }
357 static GType
358 gdl_dock_paned_child_type (GtkContainer *container)
359 {
360 GdlDockItem *item = GDL_DOCK_ITEM (container);
362 if (gtk_container_child_type (GTK_CONTAINER (item->child)) == G_TYPE_NONE)
363 return G_TYPE_NONE;
364 else
365 return GDL_TYPE_DOCK_ITEM;
366 }
368 static void
369 gdl_dock_paned_request_foreach (GdlDockObject *object,
370 gpointer user_data)
371 {
372 struct {
373 gint x, y;
374 GdlDockRequest *request;
375 gboolean may_dock;
376 } *data = user_data;
378 GdlDockRequest my_request;
379 gboolean may_dock;
381 my_request = *data->request;
382 may_dock = gdl_dock_object_dock_request (object, data->x, data->y, &my_request);
383 if (may_dock) {
384 data->may_dock = TRUE;
385 *data->request = my_request;
386 }
387 }
389 static gboolean
390 gdl_dock_paned_dock_request (GdlDockObject *object,
391 gint x,
392 gint y,
393 GdlDockRequest *request)
394 {
395 GdlDockItem *item;
396 guint bw;
397 gint rel_x, rel_y;
398 GtkAllocation *alloc;
399 gboolean may_dock = FALSE;
400 GdlDockRequest my_request;
402 g_return_val_if_fail (GDL_IS_DOCK_ITEM (object), FALSE);
404 /* we get (x,y) in our allocation coordinates system */
406 item = GDL_DOCK_ITEM (object);
408 /* Get item's allocation. */
409 alloc = &(GTK_WIDGET (object)->allocation);
410 bw = GTK_CONTAINER (object)->border_width;
412 /* Get coordinates relative to our window. */
413 rel_x = x - alloc->x;
414 rel_y = y - alloc->y;
416 if (request)
417 my_request = *request;
419 /* Check if coordinates are inside the widget. */
420 if (rel_x > 0 && rel_x < alloc->width &&
421 rel_y > 0 && rel_y < alloc->height) {
422 GtkRequisition my, other;
423 gint divider = -1;
425 gdl_dock_item_preferred_size (GDL_DOCK_ITEM (my_request.applicant), &other);
426 gdl_dock_item_preferred_size (GDL_DOCK_ITEM (object), &my);
428 /* It's inside our area. */
429 may_dock = TRUE;
431 /* Set docking indicator rectangle to the widget size. */
432 my_request.rect.x = bw;
433 my_request.rect.y = bw;
434 my_request.rect.width = alloc->width - 2*bw;
435 my_request.rect.height = alloc->height - 2*bw;
437 my_request.target = object;
439 /* See if it's in the border_width band. */
440 if (rel_x < bw) {
441 my_request.position = GDL_DOCK_LEFT;
442 my_request.rect.width *= SPLIT_RATIO;
443 divider = other.width;
444 } else if (rel_x > alloc->width - bw) {
445 my_request.position = GDL_DOCK_RIGHT;
446 my_request.rect.x += my_request.rect.width * (1 - SPLIT_RATIO);
447 my_request.rect.width *= SPLIT_RATIO;
448 divider = MAX (0, my.width - other.width);
449 } else if (rel_y < bw) {
450 my_request.position = GDL_DOCK_TOP;
451 my_request.rect.height *= SPLIT_RATIO;
452 divider = other.height;
453 } else if (rel_y > alloc->height - bw) {
454 my_request.position = GDL_DOCK_BOTTOM;
455 my_request.rect.y += my_request.rect.height * (1 - SPLIT_RATIO);
456 my_request.rect.height *= SPLIT_RATIO;
457 divider = MAX (0, my.height - other.height);
459 } else { /* Otherwise try our children. */
460 struct {
461 gint x, y;
462 GdlDockRequest *request;
463 gboolean may_dock;
464 } data;
466 /* give them coordinates in their allocation system... the
467 GtkPaned has no window, so our children allocation
468 coordinates are our window coordinates */
469 data.x = rel_x;
470 data.y = rel_y;
471 data.request = &my_request;
472 data.may_dock = FALSE;
474 gtk_container_foreach (GTK_CONTAINER (object),
475 (GtkCallback) gdl_dock_paned_request_foreach,
476 &data);
478 may_dock = data.may_dock;
479 if (!may_dock) {
480 /* the pointer is on the handle, so snap to top/bottom
481 or left/right */
482 may_dock = TRUE;
483 if (item->orientation == GTK_ORIENTATION_HORIZONTAL) {
484 if (rel_y < alloc->height / 2) {
485 my_request.position = GDL_DOCK_TOP;
486 my_request.rect.height *= SPLIT_RATIO;
487 divider = other.height;
488 } else {
489 my_request.position = GDL_DOCK_BOTTOM;
490 my_request.rect.y += my_request.rect.height * (1 - SPLIT_RATIO);
491 my_request.rect.height *= SPLIT_RATIO;
492 divider = MAX (0, my.height - other.height);
493 }
494 } else {
495 if (rel_x < alloc->width / 2) {
496 my_request.position = GDL_DOCK_LEFT;
497 my_request.rect.width *= SPLIT_RATIO;
498 divider = other.width;
499 } else {
500 my_request.position = GDL_DOCK_RIGHT;
501 my_request.rect.x += my_request.rect.width * (1 - SPLIT_RATIO);
502 my_request.rect.width *= SPLIT_RATIO;
503 divider = MAX (0, my.width - other.width);
504 }
505 }
506 }
507 }
509 if (divider >= 0 && my_request.position != GDL_DOCK_CENTER) {
510 if (G_IS_VALUE (&my_request.extra))
511 g_value_unset (&my_request.extra);
512 g_value_init (&my_request.extra, G_TYPE_UINT);
513 g_value_set_uint (&my_request.extra, (guint) divider);
514 }
516 if (may_dock) {
517 /* adjust returned coordinates so they are relative to
518 our allocation */
519 my_request.rect.x += alloc->x;
520 my_request.rect.y += alloc->y;
521 }
522 }
524 if (may_dock && request)
525 *request = my_request;
527 return may_dock;
528 }
530 static void
531 gdl_dock_paned_dock (GdlDockObject *object,
532 GdlDockObject *requestor,
533 GdlDockPlacement position,
534 GValue *other_data)
535 {
536 GtkPaned *paned;
537 gboolean done = FALSE;
538 gboolean hresize = FALSE;
539 gboolean wresize = FALSE;
540 gint temp = 0;
542 g_return_if_fail (GDL_IS_DOCK_PANED (object));
543 g_return_if_fail (GDL_DOCK_ITEM (object)->child != NULL);
545 paned = GTK_PANED (GDL_DOCK_ITEM (object)->child);
547 if (GDL_IS_DOCK_ITEM (requestor)) {
548 g_object_get (G_OBJECT (requestor), "preferred_height", &temp, NULL);
549 if (temp == -2)
550 hresize = TRUE;
551 temp = 0;
552 g_object_get (G_OBJECT (requestor), "preferred_width", &temp, NULL);
553 if (temp == -2)
554 wresize = TRUE;
555 }
557 /* see if we can dock the item in our paned */
558 switch (GDL_DOCK_ITEM (object)->orientation) {
559 case GTK_ORIENTATION_HORIZONTAL:
560 if (!paned->child1 && position == GDL_DOCK_LEFT) {
561 gtk_paned_pack1 (paned, GTK_WIDGET (requestor), FALSE, FALSE);
562 done = TRUE;
563 } else if (!paned->child2 && position == GDL_DOCK_RIGHT) {
564 gtk_paned_pack2 (paned, GTK_WIDGET (requestor), TRUE, FALSE);
565 done = TRUE;
566 }
567 break;
568 case GTK_ORIENTATION_VERTICAL:
569 if (!paned->child1 && position == GDL_DOCK_TOP) {
570 gtk_paned_pack1 (paned, GTK_WIDGET (requestor), hresize, FALSE);
571 done = TRUE;
572 } else if (!paned->child2 && position == GDL_DOCK_BOTTOM) {
573 gtk_paned_pack2 (paned, GTK_WIDGET (requestor), hresize, FALSE);
574 done = TRUE;
575 }
576 break;
577 default:
578 break;
579 }
581 if (!done) {
582 /* this will create another paned and reparent us there */
583 GDL_CALL_PARENT (GDL_DOCK_OBJECT_CLASS, dock, (object, requestor, position,
584 other_data));
585 }
586 else {
587 gdl_dock_item_show_grip (GDL_DOCK_ITEM (requestor));
588 GDL_DOCK_OBJECT_SET_FLAGS (requestor, GDL_DOCK_ATTACHED);
589 }
590 }
592 static void
593 gdl_dock_paned_set_orientation (GdlDockItem *item,
594 GtkOrientation orientation)
595 {
596 GtkPaned *old_paned = NULL, *new_paned;
597 GtkWidget *child1, *child2;
599 g_return_if_fail (GDL_IS_DOCK_PANED (item));
601 if (item->child) {
602 old_paned = GTK_PANED (item->child);
603 g_object_ref (old_paned);
604 gtk_widget_unparent (GTK_WIDGET (old_paned));
605 item->child = NULL;
606 }
608 gdl_dock_paned_create_child (GDL_DOCK_PANED (item), orientation);
610 if (old_paned) {
611 new_paned = GTK_PANED (item->child);
612 child1 = old_paned->child1;
613 child2 = old_paned->child2;
615 if (child1) {
616 g_object_ref (child1);
617 gtk_container_remove (GTK_CONTAINER (old_paned), child1);
618 gtk_paned_pack1 (new_paned, child1, TRUE, FALSE);
619 g_object_unref (child1);
620 }
621 if (child2) {
622 g_object_ref (child2);
623 gtk_container_remove (GTK_CONTAINER (old_paned), child2);
624 gtk_paned_pack1 (new_paned, child2, TRUE, FALSE);
625 g_object_unref (child2);
626 }
627 }
629 GDL_CALL_PARENT (GDL_DOCK_ITEM_CLASS, set_orientation, (item, orientation));
630 }
632 static gboolean
633 gdl_dock_paned_child_placement (GdlDockObject *object,
634 GdlDockObject *child,
635 GdlDockPlacement *placement)
636 {
637 GdlDockItem *item = GDL_DOCK_ITEM (object);
638 GtkPaned *paned;
639 GdlDockPlacement pos = GDL_DOCK_NONE;
641 if (item->child) {
642 paned = GTK_PANED (item->child);
643 if (GTK_WIDGET (child) == paned->child1)
644 pos = item->orientation == GTK_ORIENTATION_HORIZONTAL ?
645 GDL_DOCK_LEFT : GDL_DOCK_TOP;
646 else if (GTK_WIDGET (child) == paned->child2)
647 pos = item->orientation == GTK_ORIENTATION_HORIZONTAL ?
648 GDL_DOCK_RIGHT : GDL_DOCK_BOTTOM;
649 }
651 if (pos != GDL_DOCK_NONE) {
652 if (placement)
653 *placement = pos;
654 return TRUE;
655 }
656 else
657 return FALSE;
658 }
661 /* ----- Public interface ----- */
663 GtkWidget *
664 gdl_dock_paned_new (GtkOrientation orientation)
665 {
666 GdlDockPaned *paned;
668 paned = GDL_DOCK_PANED (g_object_new (GDL_TYPE_DOCK_PANED,
669 "orientation", orientation, NULL));
670 GDL_DOCK_OBJECT_UNSET_FLAGS (paned, GDL_DOCK_AUTOMATIC);
672 return GTK_WIDGET (paned);
673 }