1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 *
3 * gdl-dock-master.c - Object which manages a dock ring
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"
30 #include "gdl-tools.h"
31 #include "gdl-dock-master.h"
32 #include "gdl-dock.h"
33 #include "gdl-dock-item.h"
34 #include "libgdlmarshal.h"
35 #include "libgdltypebuiltins.h"
37 /* ----- Private prototypes ----- */
39 static void gdl_dock_master_class_init (GdlDockMasterClass *klass);
40 static void gdl_dock_master_instance_init (GdlDockMaster *master);
42 static void gdl_dock_master_dispose (GObject *g_object);
43 static void gdl_dock_master_set_property (GObject *object,
44 guint prop_id,
45 const GValue *value,
46 GParamSpec *pspec);
47 static void gdl_dock_master_get_property (GObject *object,
48 guint prop_id,
49 GValue *value,
50 GParamSpec *pspec);
52 static void _gdl_dock_master_remove (GdlDockObject *object,
53 GdlDockMaster *master);
55 static void gdl_dock_master_drag_begin (GdlDockItem *item,
56 gpointer data);
57 static void gdl_dock_master_drag_end (GdlDockItem *item,
58 gboolean cancelled,
59 gpointer data);
60 static void gdl_dock_master_drag_motion (GdlDockItem *item,
61 gint x,
62 gint y,
63 gpointer data);
65 static void _gdl_dock_master_foreach (gpointer key,
66 gpointer value,
67 gpointer user_data);
69 static void gdl_dock_master_xor_rect (GdlDockMaster *master);
71 static void gdl_dock_master_layout_changed (GdlDockMaster *master);
73 static void gdl_dock_master_set_switcher_style (GdlDockMaster *master,
74 GdlSwitcherStyle switcher_style);
76 /* ----- Private data types and variables ----- */
78 enum {
79 PROP_0,
80 PROP_DEFAULT_TITLE,
81 PROP_LOCKED,
82 PROP_SWITCHER_STYLE,
83 PROP_EXPANSION_DIRECTION
84 };
86 enum {
87 LAYOUT_CHANGED,
88 LAST_SIGNAL
89 };
91 struct _GdlDockMasterPrivate {
92 gint number; /* for naming nameless manual objects */
93 gchar *default_title;
95 GdkGC *root_xor_gc;
96 gboolean rect_drawn;
97 GdlDock *rect_owner;
99 GdlDockRequest *drag_request;
101 /* source id for the idle handler to emit a layout_changed signal */
102 guint idle_layout_changed_id;
104 /* hashes to quickly calculate the overall locked status: i.e.
105 * if size(unlocked_items) == 0 then locked = 1
106 * else if size(locked_items) == 0 then locked = 0
107 * else locked = -1
108 */
109 GHashTable *locked_items;
110 GHashTable *unlocked_items;
112 GdlSwitcherStyle switcher_style;
114 GdlDockExpansionDirection expansion_direction;
115 };
117 #define COMPUTE_LOCKED(master) \
118 (g_hash_table_size ((master)->_priv->unlocked_items) == 0 ? 1 : \
119 (g_hash_table_size ((master)->_priv->locked_items) == 0 ? 0 : -1))
121 static guint master_signals [LAST_SIGNAL] = { 0 };
124 /* ----- Private interface ----- */
126 GDL_CLASS_BOILERPLATE (GdlDockMaster, gdl_dock_master, GObject, G_TYPE_OBJECT);
128 static void
129 gdl_dock_master_class_init (GdlDockMasterClass *klass)
130 {
131 GObjectClass *g_object_class;
133 g_object_class = G_OBJECT_CLASS (klass);
135 g_object_class->dispose = gdl_dock_master_dispose;
136 g_object_class->set_property = gdl_dock_master_set_property;
137 g_object_class->get_property = gdl_dock_master_get_property;
139 g_object_class_install_property (
140 g_object_class, PROP_DEFAULT_TITLE,
141 g_param_spec_string ("default-title", _("Default title"),
142 _("Default title for newly created floating docks"),
143 NULL,
144 G_PARAM_READWRITE));
146 g_object_class_install_property (
147 g_object_class, PROP_LOCKED,
148 g_param_spec_int ("locked", _("Locked"),
149 _("If is set to 1, all the dock items bound to the master "
150 "are locked; if it's 0, all are unlocked; -1 indicates "
151 "inconsistency among the items"),
152 -1, 1, 0,
153 G_PARAM_READWRITE));
155 g_object_class_install_property (
156 g_object_class, PROP_SWITCHER_STYLE,
157 g_param_spec_enum ("switcher-style", _("Switcher Style"),
158 _("Switcher buttons style"),
159 GDL_TYPE_SWITCHER_STYLE,
160 GDL_SWITCHER_STYLE_BOTH,
161 G_PARAM_READWRITE));
163 g_object_class_install_property (
164 g_object_class, PROP_EXPANSION_DIRECTION,
165 g_param_spec_enum ("expand-direction", _("Expand direction"),
166 _("Allow the master's dock items to expand their container "
167 "dock objects in the given direction"),
168 GDL_TYPE_EXPANSION_DIRECTION,
169 GDL_DOCK_EXPANSION_DIRECTION_NONE,
170 G_PARAM_READWRITE));
172 master_signals [LAYOUT_CHANGED] =
173 g_signal_new ("layout-changed",
174 G_TYPE_FROM_CLASS (klass),
175 G_SIGNAL_RUN_LAST,
176 G_STRUCT_OFFSET (GdlDockMasterClass, layout_changed),
177 NULL, /* accumulator */
178 NULL, /* accu_data */
179 gdl_marshal_VOID__VOID,
180 G_TYPE_NONE, /* return type */
181 0);
183 klass->layout_changed = gdl_dock_master_layout_changed;
184 }
186 static void
187 gdl_dock_master_instance_init (GdlDockMaster *master)
188 {
189 master->dock_objects = g_hash_table_new_full (g_str_hash, g_str_equal,
190 g_free, NULL);
191 master->toplevel_docks = NULL;
192 master->controller = NULL;
193 master->dock_number = 1;
195 master->_priv = g_new0 (GdlDockMasterPrivate, 1);
196 master->_priv->number = 1;
197 master->_priv->switcher_style = GDL_SWITCHER_STYLE_BOTH;
198 master->_priv->expansion_direction = GDL_DOCK_EXPANSION_DIRECTION_NONE;
199 master->_priv->locked_items = g_hash_table_new (g_direct_hash, g_direct_equal);
200 master->_priv->unlocked_items = g_hash_table_new (g_direct_hash, g_direct_equal);
201 }
203 static void
204 _gdl_dock_master_remove (GdlDockObject *object,
205 GdlDockMaster *master)
206 {
207 g_return_if_fail (master != NULL && object != NULL);
209 if (GDL_IS_DOCK (object)) {
210 GList *found_link;
212 found_link = g_list_find (master->toplevel_docks, object);
213 if (found_link)
214 master->toplevel_docks = g_list_delete_link (master->toplevel_docks,
215 found_link);
216 if (object == master->controller) {
217 GList *last;
218 GdlDockObject *new_controller = NULL;
220 /* now find some other non-automatic toplevel to use as a
221 new controller. start from the last dock, since it's
222 probably a non-floating and manual */
223 last = g_list_last (master->toplevel_docks);
224 while (last) {
225 if (!GDL_DOCK_OBJECT_AUTOMATIC (last->data)) {
226 new_controller = GDL_DOCK_OBJECT (last->data);
227 break;
228 }
229 last = last->prev;
230 };
232 if (new_controller) {
233 /* the new controller gets the ref (implicitly of course) */
234 master->controller = new_controller;
235 } else {
236 master->controller = NULL;
237 /* no controller, no master */
238 g_object_unref (master);
239 }
240 }
241 }
242 /* disconnect dock object signals */
243 g_signal_handlers_disconnect_matched (object, G_SIGNAL_MATCH_DATA,
244 0, 0, NULL, NULL, master);
246 /* unref the object from the hash if it's there */
247 if (object->name) {
248 GdlDockObject *found_object;
249 found_object = g_hash_table_lookup (master->dock_objects, object->name);
250 if (found_object == object) {
251 g_hash_table_remove (master->dock_objects, object->name);
252 g_object_unref (object);
253 }
254 }
255 }
257 static void
258 ht_foreach_build_slist (gpointer key,
259 gpointer value,
260 GSList **slist)
261 {
262 *slist = g_slist_prepend (*slist, value);
263 }
265 static void
266 gdl_dock_master_dispose (GObject *g_object)
267 {
268 GdlDockMaster *master;
270 g_return_if_fail (GDL_IS_DOCK_MASTER (g_object));
272 master = GDL_DOCK_MASTER (g_object);
274 if (master->toplevel_docks) {
275 g_list_foreach (master->toplevel_docks,
276 (GFunc) gdl_dock_object_unbind, NULL);
277 g_list_free (master->toplevel_docks);
278 master->toplevel_docks = NULL;
279 }
281 if (master->dock_objects) {
282 GSList *alive_docks = NULL;
283 g_hash_table_foreach (master->dock_objects,
284 (GHFunc) ht_foreach_build_slist, &alive_docks);
285 while (alive_docks) {
286 gdl_dock_object_unbind (GDL_DOCK_OBJECT (alive_docks->data));
287 alive_docks = g_slist_delete_link (alive_docks, alive_docks);
288 }
290 g_hash_table_destroy (master->dock_objects);
291 master->dock_objects = NULL;
292 }
294 if (master->_priv) {
295 if (master->_priv->idle_layout_changed_id)
296 g_source_remove (master->_priv->idle_layout_changed_id);
298 if (master->_priv->root_xor_gc) {
299 g_object_unref (master->_priv->root_xor_gc);
300 master->_priv->root_xor_gc = NULL;
301 }
302 if (master->_priv->drag_request) {
303 if (G_IS_VALUE (&master->_priv->drag_request->extra))
304 g_value_unset (&master->_priv->drag_request->extra);
305 g_free (master->_priv->drag_request);
306 master->_priv->drag_request = NULL;
307 }
308 g_free (master->_priv->default_title);
309 master->_priv->default_title = NULL;
311 g_hash_table_destroy (master->_priv->locked_items);
312 master->_priv->locked_items = NULL;
313 g_hash_table_destroy (master->_priv->unlocked_items);
314 master->_priv->unlocked_items = NULL;
316 g_free (master->_priv);
317 master->_priv = NULL;
318 }
320 GDL_CALL_PARENT (G_OBJECT_CLASS, dispose, (g_object));
321 }
323 static void
324 foreach_lock_unlock (GdlDockItem *item,
325 gboolean locked)
326 {
327 if (!GDL_IS_DOCK_ITEM (item))
328 return;
330 g_object_set (item, "locked", locked, NULL);
331 if (gdl_dock_object_is_compound (GDL_DOCK_OBJECT (item)))
332 gtk_container_foreach (GTK_CONTAINER (item),
333 (GtkCallback) foreach_lock_unlock,
334 GINT_TO_POINTER (locked));
335 }
337 static void
338 gdl_dock_master_lock_unlock (GdlDockMaster *master,
339 gboolean locked)
340 {
341 GList *l;
343 for (l = master->toplevel_docks; l; l = l->next) {
344 GdlDock *dock = GDL_DOCK (l->data);
345 if (dock->root)
346 foreach_lock_unlock (GDL_DOCK_ITEM (dock->root), locked);
347 }
349 /* just to be sure hidden items are set too */
350 gdl_dock_master_foreach (master,
351 (GFunc) foreach_lock_unlock,
352 GINT_TO_POINTER (locked));
353 }
355 static void
356 gdl_dock_master_set_property (GObject *object,
357 guint prop_id,
358 const GValue *value,
359 GParamSpec *pspec)
360 {
361 GdlDockMaster *master = GDL_DOCK_MASTER (object);
363 switch (prop_id) {
364 case PROP_DEFAULT_TITLE:
365 g_free (master->_priv->default_title);
366 master->_priv->default_title = g_value_dup_string (value);
367 break;
368 case PROP_LOCKED:
369 if (g_value_get_int (value) >= 0)
370 gdl_dock_master_lock_unlock (master, (g_value_get_int (value) > 0));
371 break;
372 case PROP_SWITCHER_STYLE:
373 gdl_dock_master_set_switcher_style (master, g_value_get_enum (value));
374 break;
375 case PROP_EXPANSION_DIRECTION:
376 master->_priv->expansion_direction = g_value_get_enum (value);
377 break;
378 default:
379 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
380 break;
381 }
382 }
384 static void
385 gdl_dock_master_get_property (GObject *object,
386 guint prop_id,
387 GValue *value,
388 GParamSpec *pspec)
389 {
390 GdlDockMaster *master = GDL_DOCK_MASTER (object);
392 switch (prop_id) {
393 case PROP_DEFAULT_TITLE:
394 g_value_set_string (value, master->_priv->default_title);
395 break;
396 case PROP_LOCKED:
397 g_value_set_int (value, COMPUTE_LOCKED (master));
398 break;
399 case PROP_SWITCHER_STYLE:
400 g_value_set_enum (value, master->_priv->switcher_style);
401 break;
402 case PROP_EXPANSION_DIRECTION:
403 g_value_set_enum (value, master->_priv->expansion_direction);
404 break;
405 default:
406 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
407 break;
408 }
409 }
411 static void
412 gdl_dock_master_drag_begin (GdlDockItem *item,
413 gpointer data)
414 {
415 GdlDockMaster *master;
416 GdlDockRequest *request;
418 g_return_if_fail (data != NULL);
419 g_return_if_fail (item != NULL);
421 master = GDL_DOCK_MASTER (data);
423 if (!master->_priv->drag_request)
424 master->_priv->drag_request = g_new0 (GdlDockRequest, 1);
426 request = master->_priv->drag_request;
428 /* Set the target to itself so it won't go floating with just a click. */
429 request->applicant = GDL_DOCK_OBJECT (item);
430 request->target = GDL_DOCK_OBJECT (item);
431 request->position = GDL_DOCK_FLOATING;
432 if (G_IS_VALUE (&request->extra))
433 g_value_unset (&request->extra);
435 master->_priv->rect_drawn = FALSE;
436 master->_priv->rect_owner = NULL;
437 }
439 static void
440 gdl_dock_master_drag_end (GdlDockItem *item,
441 gboolean cancelled,
442 gpointer data)
443 {
444 GdlDockMaster *master;
445 GdlDockRequest *request;
447 g_return_if_fail (data != NULL);
448 g_return_if_fail (item != NULL);
450 master = GDL_DOCK_MASTER (data);
451 request = master->_priv->drag_request;
453 g_return_if_fail (GDL_DOCK_OBJECT (item) == request->applicant);
455 /* Erase previously drawn rectangle */
456 if (master->_priv->rect_drawn)
457 gdl_dock_master_xor_rect (master);
459 /* cancel conditions */
460 if (cancelled || request->applicant == request->target)
461 return;
463 /* dock object to the requested position */
464 gdl_dock_object_dock (request->target,
465 request->applicant,
466 request->position,
467 &request->extra);
469 g_signal_emit (master, master_signals [LAYOUT_CHANGED], 0);
470 }
472 static void
473 gdl_dock_master_drag_motion (GdlDockItem *item,
474 gint root_x,
475 gint root_y,
476 gpointer data)
477 {
478 GdlDockMaster *master;
479 GdlDockRequest my_request, *request;
480 GdkWindow *window;
481 gint win_x, win_y;
482 gint x, y;
483 GdlDock *dock = NULL;
484 gboolean may_dock = FALSE;
486 g_return_if_fail (item != NULL && data != NULL);
488 master = GDL_DOCK_MASTER (data);
489 request = master->_priv->drag_request;
491 g_return_if_fail (GDL_DOCK_OBJECT (item) == request->applicant);
493 my_request = *request;
495 /* first look under the pointer */
496 window = gdk_window_at_pointer (&win_x, &win_y);
497 if (window) {
498 GtkWidget *widget;
499 /* ok, now get the widget who owns that window and see if we can
500 get to a GdlDock by walking up the hierarchy */
501 gdk_window_get_user_data (window, (gpointer) &widget);
502 if (GTK_IS_WIDGET (widget)) {
503 while (widget && (!GDL_IS_DOCK (widget) ||
504 GDL_DOCK_OBJECT_GET_MASTER (widget) != master))
505 widget = widget->parent;
506 if (widget) {
507 gint win_w, win_h;
509 /* verify that the pointer is still in that dock
510 (the user could have moved it) */
511 gdk_window_get_geometry (widget->window,
512 NULL, NULL, &win_w, &win_h, NULL);
513 gdk_window_get_origin (widget->window, &win_x, &win_y);
514 if (root_x >= win_x && root_x < win_x + win_w &&
515 root_y >= win_y && root_y < win_y + win_h)
516 dock = GDL_DOCK (widget);
517 }
518 }
519 }
521 if (dock) {
522 /* translate root coordinates into dock object coordinates
523 (i.e. widget coordinates) */
524 gdk_window_get_origin (GTK_WIDGET (dock)->window, &win_x, &win_y);
525 x = root_x - win_x;
526 y = root_y - win_y;
527 may_dock = gdl_dock_object_dock_request (GDL_DOCK_OBJECT (dock),
528 x, y, &my_request);
529 }
530 else {
531 GList *l;
533 /* try to dock the item in all the docks in the ring in turn */
534 for (l = master->toplevel_docks; l; l = l->next) {
535 dock = GDL_DOCK (l->data);
536 /* translate root coordinates into dock object coordinates
537 (i.e. widget coordinates) */
538 gdk_window_get_origin (GTK_WIDGET (dock)->window, &win_x, &win_y);
539 x = root_x - win_x;
540 y = root_y - win_y;
541 may_dock = gdl_dock_object_dock_request (GDL_DOCK_OBJECT (dock),
542 x, y, &my_request);
543 if (may_dock)
544 break;
545 }
546 }
549 if (!may_dock) {
550 GtkRequisition req;
551 /* Special case for GdlDockItems : they must respect the flags */
552 if(GDL_IS_DOCK_ITEM(item)
553 && GDL_DOCK_ITEM(item)->behavior & GDL_DOCK_ITEM_BEH_NEVER_FLOATING)
554 return;
556 dock = NULL;
557 my_request.target = GDL_DOCK_OBJECT (
558 gdl_dock_object_get_toplevel (request->applicant));
559 my_request.position = GDL_DOCK_FLOATING;
561 gdl_dock_item_preferred_size (GDL_DOCK_ITEM (request->applicant), &req);
562 my_request.rect.width = req.width;
563 my_request.rect.height = req.height;
565 my_request.rect.x = root_x - GDL_DOCK_ITEM (request->applicant)->dragoff_x;
566 my_request.rect.y = root_y - GDL_DOCK_ITEM (request->applicant)->dragoff_y;
568 /* setup extra docking information */
569 if (G_IS_VALUE (&my_request.extra))
570 g_value_unset (&my_request.extra);
572 g_value_init (&my_request.extra, GDK_TYPE_RECTANGLE);
573 g_value_set_boxed (&my_request.extra, &my_request.rect);
574 }
575 /* if we want to enforce GDL_DOCK_ITEM_BEH_NEVER_FLOATING */
576 /* the item must remain attached to the controller, otherwise */
577 /* it could be inserted in another floating dock */
578 /* so check for the flag at this moment */
579 else if(GDL_IS_DOCK_ITEM(item)
580 && GDL_DOCK_ITEM(item)->behavior & GDL_DOCK_ITEM_BEH_NEVER_FLOATING
581 && dock != GDL_DOCK(master->controller))
582 return;
584 if (!(my_request.rect.x == request->rect.x &&
585 my_request.rect.y == request->rect.y &&
586 my_request.rect.width == request->rect.width &&
587 my_request.rect.height == request->rect.height &&
588 dock == master->_priv->rect_owner)) {
590 /* erase the previous rectangle */
591 if (master->_priv->rect_drawn)
592 gdl_dock_master_xor_rect (master);
593 }
595 /* set the new values */
596 *request = my_request;
597 master->_priv->rect_owner = dock;
599 /* draw the previous rectangle */
600 if (~master->_priv->rect_drawn)
601 gdl_dock_master_xor_rect (master);
602 }
604 static void
605 _gdl_dock_master_foreach (gpointer key,
606 gpointer value,
607 gpointer user_data)
608 {
609 (void)key;
610 struct {
611 GFunc function;
612 gpointer user_data;
613 } *data = user_data;
615 (* data->function) (GTK_WIDGET (value), data->user_data);
616 }
618 static void
619 gdl_dock_master_xor_rect (GdlDockMaster *master)
620 {
621 gint8 dash_list [2];
622 GdkWindow *window;
623 GdkRectangle *rect;
625 if (!master->_priv || !master->_priv->drag_request)
626 return;
628 master->_priv->rect_drawn = ~master->_priv->rect_drawn;
630 if (master->_priv->rect_owner) {
631 gdl_dock_xor_rect (master->_priv->rect_owner,
632 &master->_priv->drag_request->rect);
633 return;
634 }
636 rect = &master->_priv->drag_request->rect;
637 window = gdk_get_default_root_window ();
639 if (!master->_priv->root_xor_gc) {
640 GdkGCValues values;
642 values.function = GDK_INVERT;
643 values.subwindow_mode = GDK_INCLUDE_INFERIORS;
644 master->_priv->root_xor_gc = gdk_gc_new_with_values (
645 window, &values, GDK_GC_FUNCTION | GDK_GC_SUBWINDOW);
646 };
648 gdk_gc_set_line_attributes (master->_priv->root_xor_gc, 1,
649 GDK_LINE_ON_OFF_DASH,
650 GDK_CAP_NOT_LAST,
651 GDK_JOIN_BEVEL);
653 dash_list[0] = 1;
654 dash_list[1] = 1;
655 gdk_gc_set_dashes (master->_priv->root_xor_gc, 1, dash_list, 2);
657 gdk_draw_rectangle (window, master->_priv->root_xor_gc, 0,
658 rect->x, rect->y,
659 rect->width, rect->height);
661 gdk_gc_set_dashes (master->_priv->root_xor_gc, 0, dash_list, 2);
663 gdk_draw_rectangle (window, master->_priv->root_xor_gc, 0,
664 rect->x + 1, rect->y + 1,
665 rect->width - 2, rect->height - 2);
666 }
668 static void
669 gdl_dock_master_layout_changed (GdlDockMaster *master)
670 {
671 g_return_if_fail (GDL_IS_DOCK_MASTER (master));
673 /* emit "layout-changed" on the controller to notify the user who
674 * normally shouldn't have access to us */
675 if (master->controller)
676 g_signal_emit_by_name (master->controller, "layout-changed");
678 /* remove the idle handler if there is one */
679 if (master->_priv->idle_layout_changed_id) {
680 g_source_remove (master->_priv->idle_layout_changed_id);
681 master->_priv->idle_layout_changed_id = 0;
682 }
683 }
685 static gboolean
686 idle_emit_layout_changed (gpointer user_data)
687 {
688 GdlDockMaster *master = user_data;
690 g_return_val_if_fail (master && GDL_IS_DOCK_MASTER (master), FALSE);
692 master->_priv->idle_layout_changed_id = 0;
693 g_signal_emit (master, master_signals [LAYOUT_CHANGED], 0);
695 return FALSE;
696 }
698 static void
699 item_dock_cb (GdlDockObject *object,
700 GdlDockObject *requestor,
701 GdlDockPlacement position,
702 GValue *other_data,
703 gpointer user_data)
704 {
705 GdlDockMaster *master = user_data;
707 g_return_if_fail (requestor && GDL_IS_DOCK_OBJECT (requestor));
708 g_return_if_fail (master && GDL_IS_DOCK_MASTER (master));
710 /* here we are in fact interested in the requestor, since it's
711 * assumed that object will not change its visibility... for the
712 * requestor, however, could mean that it's being shown */
713 if (!GDL_DOCK_OBJECT_IN_REFLOW (requestor) &&
714 !GDL_DOCK_OBJECT_AUTOMATIC (requestor)) {
715 if (!master->_priv->idle_layout_changed_id)
716 master->_priv->idle_layout_changed_id =
717 g_idle_add (idle_emit_layout_changed, master);
718 }
719 }
721 static void
722 item_detach_cb (GdlDockObject *object,
723 gboolean recursive,
724 gpointer user_data)
725 {
726 GdlDockMaster *master = user_data;
728 g_return_if_fail (object && GDL_IS_DOCK_OBJECT (object));
729 g_return_if_fail (master && GDL_IS_DOCK_MASTER (master));
731 if (!GDL_DOCK_OBJECT_IN_REFLOW (object) &&
732 !GDL_DOCK_OBJECT_AUTOMATIC (object)) {
733 if (!master->_priv->idle_layout_changed_id)
734 master->_priv->idle_layout_changed_id =
735 g_idle_add (idle_emit_layout_changed, master);
736 }
737 }
739 static void
740 item_notify_cb (GdlDockObject *object,
741 GParamSpec *pspec,
742 gpointer user_data)
743 {
744 GdlDockMaster *master = user_data;
745 gint locked = COMPUTE_LOCKED (master);
746 gboolean item_locked;
748 g_object_get (object, "locked", &item_locked, NULL);
750 if (item_locked) {
751 g_hash_table_remove (master->_priv->unlocked_items, object);
752 g_hash_table_insert (master->_priv->locked_items, object, NULL);
753 } else {
754 g_hash_table_remove (master->_priv->locked_items, object);
755 g_hash_table_insert (master->_priv->unlocked_items, object, NULL);
756 }
758 if (COMPUTE_LOCKED (master) != locked)
759 g_object_notify (G_OBJECT (master), "locked");
760 }
762 /* ----- Public interface ----- */
764 void
765 gdl_dock_master_add (GdlDockMaster *master,
766 GdlDockObject *object)
767 {
768 g_return_if_fail (master != NULL && object != NULL);
770 if (!GDL_DOCK_OBJECT_AUTOMATIC (object)) {
771 GdlDockObject *found_object;
773 /* create a name for the object if it doesn't have one */
774 if (!object->name)
775 /* directly set the name, since it's a construction only
776 property */
777 object->name = g_strdup_printf ("__dock_%u", master->_priv->number++);
779 /* add the object to our hash list */
780 if ((found_object = g_hash_table_lookup (master->dock_objects, object->name))) {
781 g_warning (_("master %p: unable to add object %p[%s] to the hash. "
782 "There already is an item with that name (%p)."),
783 master, object, object->name, found_object);
784 }
785 else {
786 g_object_ref (object);
787 gtk_object_sink (GTK_OBJECT (object));
788 g_hash_table_insert (master->dock_objects, g_strdup (object->name), object);
789 }
790 }
792 if (GDL_IS_DOCK (object)) {
793 gboolean floating;
795 /* if this is the first toplevel we are adding, name it controller */
796 if (!master->toplevel_docks)
797 /* the dock should already have the ref */
798 master->controller = object;
800 /* add dock to the toplevel list */
801 g_object_get (object, "floating", &floating, NULL);
802 if (floating)
803 master->toplevel_docks = g_list_prepend (master->toplevel_docks, object);
804 else
805 master->toplevel_docks = g_list_append (master->toplevel_docks, object);
807 /* we are interested in the dock request this toplevel
808 * receives to update the layout */
809 g_signal_connect (object, "dock",
810 G_CALLBACK (item_dock_cb), master);
812 }
813 else if (GDL_IS_DOCK_ITEM (object)) {
814 /* we need to connect the item's signals */
815 g_signal_connect (object, "dock_drag_begin",
816 G_CALLBACK (gdl_dock_master_drag_begin), master);
817 g_signal_connect (object, "dock_drag_motion",
818 G_CALLBACK (gdl_dock_master_drag_motion), master);
819 g_signal_connect (object, "dock_drag_end",
820 G_CALLBACK (gdl_dock_master_drag_end), master);
821 g_signal_connect (object, "dock",
822 G_CALLBACK (item_dock_cb), master);
823 g_signal_connect (object, "detach",
824 G_CALLBACK (item_detach_cb), master);
826 /* register to "locked" notification if the item has a grip,
827 * and add the item to the corresponding hash */
828 if (GDL_DOCK_ITEM_HAS_GRIP (GDL_DOCK_ITEM (object))) {
829 g_signal_connect (object, "notify::locked",
830 G_CALLBACK (item_notify_cb), master);
831 item_notify_cb (object, NULL, master);
832 }
834 /* If the item is notebook, set the switcher style */
835 if (GDL_IS_DOCK_NOTEBOOK (object) &&
836 GDL_IS_SWITCHER (GDL_DOCK_ITEM (object)->child))
837 {
838 g_object_set (GDL_DOCK_ITEM (object)->child, "switcher-style",
839 master->_priv->switcher_style, NULL);
840 }
842 /* post a layout_changed emission if the item is not automatic
843 * (since it should be added to the items model) */
844 if (!GDL_DOCK_OBJECT_AUTOMATIC (object)) {
845 if (!master->_priv->idle_layout_changed_id)
846 master->_priv->idle_layout_changed_id =
847 g_idle_add (idle_emit_layout_changed, master);
848 }
849 }
850 }
852 void
853 gdl_dock_master_remove (GdlDockMaster *master,
854 GdlDockObject *object)
855 {
856 g_return_if_fail (master != NULL && object != NULL);
858 /* remove from locked/unlocked hashes and property change if
859 * that's the case */
860 if (GDL_IS_DOCK_ITEM (object) && GDL_DOCK_ITEM_HAS_GRIP (GDL_DOCK_ITEM (object))) {
861 gint locked = COMPUTE_LOCKED (master);
862 if (g_hash_table_remove (master->_priv->locked_items, object) ||
863 g_hash_table_remove (master->_priv->unlocked_items, object)) {
864 if (COMPUTE_LOCKED (master) != locked)
865 g_object_notify (G_OBJECT (master), "locked");
866 }
867 }
869 /* ref the master, since removing the controller could cause master disposal */
870 g_object_ref (master);
872 /* all the interesting stuff happens in _gdl_dock_master_remove */
873 _gdl_dock_master_remove (object, master);
875 /* post a layout_changed emission if the item is not automatic
876 * (since it should be removed from the items model) */
877 if (!GDL_DOCK_OBJECT_AUTOMATIC (object)) {
878 if (!master->_priv->idle_layout_changed_id)
879 master->_priv->idle_layout_changed_id =
880 g_idle_add (idle_emit_layout_changed, master);
881 }
883 /* balance ref count */
884 g_object_unref (master);
885 }
887 void
888 gdl_dock_master_foreach (GdlDockMaster *master,
889 GFunc function,
890 gpointer user_data)
891 {
892 struct {
893 GFunc function;
894 gpointer user_data;
895 } data;
897 g_return_if_fail (master != NULL && function != NULL);
899 data.function = function;
900 data.user_data = user_data;
901 g_hash_table_foreach (master->dock_objects, _gdl_dock_master_foreach, &data);
902 }
904 void
905 gdl_dock_master_foreach_toplevel (GdlDockMaster *master,
906 gboolean include_controller,
907 GFunc function,
908 gpointer user_data)
909 {
910 GList *l;
912 g_return_if_fail (master != NULL && function != NULL);
914 for (l = master->toplevel_docks; l; ) {
915 GdlDockObject *object = GDL_DOCK_OBJECT (l->data);
916 l = l->next;
917 if (object != master->controller || include_controller)
918 (* function) (GTK_WIDGET (object), user_data);
919 }
920 }
922 GdlDockObject *
923 gdl_dock_master_get_object (GdlDockMaster *master,
924 const gchar *nick_name)
925 {
926 gpointer *found;
928 g_return_val_if_fail (master != NULL, NULL);
930 if (!nick_name)
931 return NULL;
933 found = g_hash_table_lookup (master->dock_objects, nick_name);
935 return found ? GDL_DOCK_OBJECT (found) : NULL;
936 }
938 GdlDockObject *
939 gdl_dock_master_get_controller (GdlDockMaster *master)
940 {
941 g_return_val_if_fail (master != NULL, NULL);
943 return master->controller;
944 }
946 void
947 gdl_dock_master_set_controller (GdlDockMaster *master,
948 GdlDockObject *new_controller)
949 {
950 g_return_if_fail (master != NULL);
952 if (new_controller) {
953 if (GDL_DOCK_OBJECT_AUTOMATIC (new_controller))
954 g_warning (_("The new dock controller %p is automatic. Only manual "
955 "dock objects should be named controller."), new_controller);
957 /* check that the controller is in the toplevel list */
958 if (!g_list_find (master->toplevel_docks, new_controller))
959 gdl_dock_master_add (master, new_controller);
960 master->controller = new_controller;
962 } else {
963 master->controller = NULL;
964 /* no controller, no master */
965 g_object_unref (master);
966 }
967 }
969 static void
970 set_switcher_style_foreach (GtkWidget *obj, gpointer user_data)
971 {
972 GdlSwitcherStyle style = GPOINTER_TO_INT (user_data);
974 if (!GDL_IS_DOCK_ITEM (obj))
975 return;
977 if (GDL_IS_DOCK_NOTEBOOK (obj)) {
979 GtkWidget *child = GDL_DOCK_ITEM (obj)->child;
980 if (GDL_IS_SWITCHER (child)) {
982 g_object_set (child, "switcher-style", style, NULL);
983 }
984 } else if (gdl_dock_object_is_compound (GDL_DOCK_OBJECT (obj))) {
986 gtk_container_foreach (GTK_CONTAINER (obj),
987 set_switcher_style_foreach,
988 user_data);
989 }
990 }
992 static void
993 gdl_dock_master_set_switcher_style (GdlDockMaster *master,
994 GdlSwitcherStyle switcher_style)
995 {
996 GList *l;
997 g_return_if_fail (GDL_IS_DOCK_MASTER (master));
999 master->_priv->switcher_style = switcher_style;
1000 for (l = master->toplevel_docks; l; l = l->next) {
1001 GdlDock *dock = GDL_DOCK (l->data);
1002 if (dock->root)
1003 set_switcher_style_foreach (GTK_WIDGET (dock->root),
1004 GINT_TO_POINTER (switcher_style));
1005 }
1007 /* just to be sure hidden items are set too */
1008 gdl_dock_master_foreach (master, (GFunc) set_switcher_style_foreach,
1009 GINT_TO_POINTER (switcher_style));
1010 }