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"
36 #ifdef WIN32
37 #include "gdl-win32.h"
38 #endif
40 /* ----- Private prototypes ----- */
42 static void gdl_dock_master_class_init (GdlDockMasterClass *klass);
43 static void gdl_dock_master_instance_init (GdlDockMaster *master);
45 static void gdl_dock_master_dispose (GObject *g_object);
46 static void gdl_dock_master_set_property (GObject *object,
47 guint prop_id,
48 const GValue *value,
49 GParamSpec *pspec);
50 static void gdl_dock_master_get_property (GObject *object,
51 guint prop_id,
52 GValue *value,
53 GParamSpec *pspec);
55 static void _gdl_dock_master_remove (GdlDockObject *object,
56 GdlDockMaster *master);
58 static void gdl_dock_master_drag_begin (GdlDockItem *item,
59 gpointer data);
60 static void gdl_dock_master_drag_end (GdlDockItem *item,
61 gboolean cancelled,
62 gpointer data);
63 static void gdl_dock_master_drag_motion (GdlDockItem *item,
64 gint x,
65 gint y,
66 gpointer data);
68 static void _gdl_dock_master_foreach (gpointer key,
69 gpointer value,
70 gpointer user_data);
72 static void gdl_dock_master_xor_rect (GdlDockMaster *master);
74 static void gdl_dock_master_layout_changed (GdlDockMaster *master);
76 static void gdl_dock_master_set_switcher_style (GdlDockMaster *master,
77 GdlSwitcherStyle switcher_style);
79 /* ----- Private data types and variables ----- */
81 enum {
82 PROP_0,
83 PROP_DEFAULT_TITLE,
84 PROP_LOCKED,
85 PROP_SWITCHER_STYLE,
86 PROP_EXPANSION_DIRECTION
87 };
89 enum {
90 LAYOUT_CHANGED,
91 LAST_SIGNAL
92 };
94 struct _GdlDockMasterPrivate {
95 gint number; /* for naming nameless manual objects */
96 gchar *default_title;
98 GdkGC *root_xor_gc;
99 gboolean rect_drawn;
100 GdlDock *rect_owner;
102 GdlDockRequest *drag_request;
104 /* source id for the idle handler to emit a layout_changed signal */
105 guint idle_layout_changed_id;
107 /* hashes to quickly calculate the overall locked status: i.e.
108 * if size(unlocked_items) == 0 then locked = 1
109 * else if size(locked_items) == 0 then locked = 0
110 * else locked = -1
111 */
112 GHashTable *locked_items;
113 GHashTable *unlocked_items;
115 GdlSwitcherStyle switcher_style;
117 GdlDockExpansionDirection expansion_direction;
118 };
120 #define COMPUTE_LOCKED(master) \
121 (g_hash_table_size ((master)->_priv->unlocked_items) == 0 ? 1 : \
122 (g_hash_table_size ((master)->_priv->locked_items) == 0 ? 0 : -1))
124 static guint master_signals [LAST_SIGNAL] = { 0 };
127 /* ----- Private interface ----- */
129 GDL_CLASS_BOILERPLATE (GdlDockMaster, gdl_dock_master, GObject, G_TYPE_OBJECT);
131 static void
132 gdl_dock_master_class_init (GdlDockMasterClass *klass)
133 {
134 GObjectClass *g_object_class;
136 g_object_class = G_OBJECT_CLASS (klass);
138 g_object_class->dispose = gdl_dock_master_dispose;
139 g_object_class->set_property = gdl_dock_master_set_property;
140 g_object_class->get_property = gdl_dock_master_get_property;
142 g_object_class_install_property (
143 g_object_class, PROP_DEFAULT_TITLE,
144 g_param_spec_string ("default-title", _("Default title"),
145 _("Default title for newly created floating docks"),
146 NULL,
147 G_PARAM_READWRITE));
149 g_object_class_install_property (
150 g_object_class, PROP_LOCKED,
151 g_param_spec_int ("locked", _("Locked"),
152 _("If is set to 1, all the dock items bound to the master "
153 "are locked; if it's 0, all are unlocked; -1 indicates "
154 "inconsistency among the items"),
155 -1, 1, 0,
156 G_PARAM_READWRITE));
158 g_object_class_install_property (
159 g_object_class, PROP_SWITCHER_STYLE,
160 g_param_spec_enum ("switcher-style", _("Switcher Style"),
161 _("Switcher buttons style"),
162 GDL_TYPE_SWITCHER_STYLE,
163 GDL_SWITCHER_STYLE_BOTH,
164 G_PARAM_READWRITE));
166 g_object_class_install_property (
167 g_object_class, PROP_EXPANSION_DIRECTION,
168 g_param_spec_enum ("expand-direction", _("Expand direction"),
169 _("Allow the master's dock items to expand their container "
170 "dock objects in the given direction"),
171 GDL_TYPE_EXPANSION_DIRECTION,
172 GDL_DOCK_EXPANSION_DIRECTION_NONE,
173 G_PARAM_READWRITE));
175 master_signals [LAYOUT_CHANGED] =
176 g_signal_new ("layout-changed",
177 G_TYPE_FROM_CLASS (klass),
178 G_SIGNAL_RUN_LAST,
179 G_STRUCT_OFFSET (GdlDockMasterClass, layout_changed),
180 NULL, /* accumulator */
181 NULL, /* accu_data */
182 gdl_marshal_VOID__VOID,
183 G_TYPE_NONE, /* return type */
184 0);
186 klass->layout_changed = gdl_dock_master_layout_changed;
187 }
189 static void
190 gdl_dock_master_instance_init (GdlDockMaster *master)
191 {
192 master->dock_objects = g_hash_table_new_full (g_str_hash, g_str_equal,
193 g_free, NULL);
194 master->toplevel_docks = NULL;
195 master->controller = NULL;
196 master->dock_number = 1;
198 master->_priv = g_new0 (GdlDockMasterPrivate, 1);
199 master->_priv->number = 1;
200 master->_priv->switcher_style = GDL_SWITCHER_STYLE_BOTH;
201 master->_priv->expansion_direction = GDL_DOCK_EXPANSION_DIRECTION_NONE;
202 master->_priv->locked_items = g_hash_table_new (g_direct_hash, g_direct_equal);
203 master->_priv->unlocked_items = g_hash_table_new (g_direct_hash, g_direct_equal);
204 }
206 static void
207 _gdl_dock_master_remove (GdlDockObject *object,
208 GdlDockMaster *master)
209 {
210 g_return_if_fail (master != NULL && object != NULL);
212 if (GDL_IS_DOCK (object)) {
213 GList *found_link;
215 found_link = g_list_find (master->toplevel_docks, object);
216 if (found_link)
217 master->toplevel_docks = g_list_delete_link (master->toplevel_docks,
218 found_link);
219 if (object == master->controller) {
220 GList *last;
221 GdlDockObject *new_controller = NULL;
223 /* now find some other non-automatic toplevel to use as a
224 new controller. start from the last dock, since it's
225 probably a non-floating and manual */
226 last = g_list_last (master->toplevel_docks);
227 while (last) {
228 if (!GDL_DOCK_OBJECT_AUTOMATIC (last->data)) {
229 new_controller = GDL_DOCK_OBJECT (last->data);
230 break;
231 }
232 last = last->prev;
233 };
235 if (new_controller) {
236 /* the new controller gets the ref (implicitly of course) */
237 master->controller = new_controller;
238 } else {
239 master->controller = NULL;
240 /* no controller, no master */
241 g_object_unref (master);
242 }
243 }
244 }
245 /* disconnect dock object signals */
246 g_signal_handlers_disconnect_matched (object, G_SIGNAL_MATCH_DATA,
247 0, 0, NULL, NULL, master);
249 /* unref the object from the hash if it's there */
250 if (object->name) {
251 GdlDockObject *found_object;
252 found_object = g_hash_table_lookup (master->dock_objects, object->name);
253 if (found_object == object) {
254 g_hash_table_remove (master->dock_objects, object->name);
255 g_object_unref (object);
256 }
257 }
258 }
260 static void
261 ht_foreach_build_slist (gpointer key,
262 gpointer value,
263 GSList **slist)
264 {
265 *slist = g_slist_prepend (*slist, value);
266 }
268 static void
269 gdl_dock_master_dispose (GObject *g_object)
270 {
271 GdlDockMaster *master;
273 g_return_if_fail (GDL_IS_DOCK_MASTER (g_object));
275 master = GDL_DOCK_MASTER (g_object);
277 if (master->toplevel_docks) {
278 g_list_foreach (master->toplevel_docks,
279 (GFunc) gdl_dock_object_unbind, NULL);
280 g_list_free (master->toplevel_docks);
281 master->toplevel_docks = NULL;
282 }
284 if (master->dock_objects) {
285 GSList *alive_docks = NULL;
286 g_hash_table_foreach (master->dock_objects,
287 (GHFunc) ht_foreach_build_slist, &alive_docks);
288 while (alive_docks) {
289 gdl_dock_object_unbind (GDL_DOCK_OBJECT (alive_docks->data));
290 alive_docks = g_slist_delete_link (alive_docks, alive_docks);
291 }
293 g_hash_table_destroy (master->dock_objects);
294 master->dock_objects = NULL;
295 }
297 if (master->_priv) {
298 if (master->_priv->idle_layout_changed_id)
299 g_source_remove (master->_priv->idle_layout_changed_id);
301 if (master->_priv->root_xor_gc) {
302 g_object_unref (master->_priv->root_xor_gc);
303 master->_priv->root_xor_gc = NULL;
304 }
305 if (master->_priv->drag_request) {
306 if (G_IS_VALUE (&master->_priv->drag_request->extra))
307 g_value_unset (&master->_priv->drag_request->extra);
308 g_free (master->_priv->drag_request);
309 master->_priv->drag_request = NULL;
310 }
311 g_free (master->_priv->default_title);
312 master->_priv->default_title = NULL;
314 g_hash_table_destroy (master->_priv->locked_items);
315 master->_priv->locked_items = NULL;
316 g_hash_table_destroy (master->_priv->unlocked_items);
317 master->_priv->unlocked_items = NULL;
319 g_free (master->_priv);
320 master->_priv = NULL;
321 }
323 GDL_CALL_PARENT (G_OBJECT_CLASS, dispose, (g_object));
324 }
326 static void
327 foreach_lock_unlock (GdlDockItem *item,
328 gboolean locked)
329 {
330 if (!GDL_IS_DOCK_ITEM (item))
331 return;
333 g_object_set (item, "locked", locked, NULL);
334 if (gdl_dock_object_is_compound (GDL_DOCK_OBJECT (item)))
335 gtk_container_foreach (GTK_CONTAINER (item),
336 (GtkCallback) foreach_lock_unlock,
337 GINT_TO_POINTER (locked));
338 }
340 static void
341 gdl_dock_master_lock_unlock (GdlDockMaster *master,
342 gboolean locked)
343 {
344 GList *l;
346 for (l = master->toplevel_docks; l; l = l->next) {
347 GdlDock *dock = GDL_DOCK (l->data);
348 if (dock->root)
349 foreach_lock_unlock (GDL_DOCK_ITEM (dock->root), locked);
350 }
352 /* just to be sure hidden items are set too */
353 gdl_dock_master_foreach (master,
354 (GFunc) foreach_lock_unlock,
355 GINT_TO_POINTER (locked));
356 }
358 static void
359 gdl_dock_master_set_property (GObject *object,
360 guint prop_id,
361 const GValue *value,
362 GParamSpec *pspec)
363 {
364 GdlDockMaster *master = GDL_DOCK_MASTER (object);
366 switch (prop_id) {
367 case PROP_DEFAULT_TITLE:
368 g_free (master->_priv->default_title);
369 master->_priv->default_title = g_value_dup_string (value);
370 break;
371 case PROP_LOCKED:
372 if (g_value_get_int (value) >= 0)
373 gdl_dock_master_lock_unlock (master, (g_value_get_int (value) > 0));
374 break;
375 case PROP_SWITCHER_STYLE:
376 gdl_dock_master_set_switcher_style (master, g_value_get_enum (value));
377 break;
378 case PROP_EXPANSION_DIRECTION:
379 master->_priv->expansion_direction = g_value_get_enum (value);
380 break;
381 default:
382 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
383 break;
384 }
385 }
387 static void
388 gdl_dock_master_get_property (GObject *object,
389 guint prop_id,
390 GValue *value,
391 GParamSpec *pspec)
392 {
393 GdlDockMaster *master = GDL_DOCK_MASTER (object);
395 switch (prop_id) {
396 case PROP_DEFAULT_TITLE:
397 g_value_set_string (value, master->_priv->default_title);
398 break;
399 case PROP_LOCKED:
400 g_value_set_int (value, COMPUTE_LOCKED (master));
401 break;
402 case PROP_SWITCHER_STYLE:
403 g_value_set_enum (value, master->_priv->switcher_style);
404 break;
405 case PROP_EXPANSION_DIRECTION:
406 g_value_set_enum (value, master->_priv->expansion_direction);
407 break;
408 default:
409 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
410 break;
411 }
412 }
414 static void
415 gdl_dock_master_drag_begin (GdlDockItem *item,
416 gpointer data)
417 {
418 GdlDockMaster *master;
419 GdlDockRequest *request;
421 g_return_if_fail (data != NULL);
422 g_return_if_fail (item != NULL);
424 master = GDL_DOCK_MASTER (data);
426 if (!master->_priv->drag_request)
427 master->_priv->drag_request = g_new0 (GdlDockRequest, 1);
429 request = master->_priv->drag_request;
431 /* Set the target to itself so it won't go floating with just a click. */
432 request->applicant = GDL_DOCK_OBJECT (item);
433 request->target = GDL_DOCK_OBJECT (item);
434 request->position = GDL_DOCK_FLOATING;
435 if (G_IS_VALUE (&request->extra))
436 g_value_unset (&request->extra);
438 master->_priv->rect_drawn = FALSE;
439 master->_priv->rect_owner = NULL;
440 }
442 static void
443 gdl_dock_master_drag_end (GdlDockItem *item,
444 gboolean cancelled,
445 gpointer data)
446 {
447 GdlDockMaster *master;
448 GdlDockRequest *request;
450 g_return_if_fail (data != NULL);
451 g_return_if_fail (item != NULL);
453 master = GDL_DOCK_MASTER (data);
454 request = master->_priv->drag_request;
456 g_return_if_fail (GDL_DOCK_OBJECT (item) == request->applicant);
458 /* Erase previously drawn rectangle */
459 if (master->_priv->rect_drawn)
460 gdl_dock_master_xor_rect (master);
462 /* cancel conditions */
463 if (cancelled || request->applicant == request->target)
464 return;
466 /* dock object to the requested position */
467 gdl_dock_object_dock (request->target,
468 request->applicant,
469 request->position,
470 &request->extra);
472 g_signal_emit (master, master_signals [LAYOUT_CHANGED], 0);
473 }
475 static void
476 gdl_dock_master_drag_motion (GdlDockItem *item,
477 gint root_x,
478 gint root_y,
479 gpointer data)
480 {
481 GdlDockMaster *master;
482 GdlDockRequest my_request, *request;
483 GdkWindow *window;
484 gint win_x, win_y;
485 gint x, y;
486 GdlDock *dock = NULL;
487 gboolean may_dock = FALSE;
489 g_return_if_fail (item != NULL && data != NULL);
491 master = GDL_DOCK_MASTER (data);
492 request = master->_priv->drag_request;
494 g_return_if_fail (GDL_DOCK_OBJECT (item) == request->applicant);
496 my_request = *request;
498 /* first look under the pointer */
499 window = gdk_window_at_pointer (&win_x, &win_y);
500 if (window) {
501 GtkWidget *widget;
502 /* ok, now get the widget who owns that window and see if we can
503 get to a GdlDock by walking up the hierarchy */
504 gdk_window_get_user_data (window, (gpointer) &widget);
505 if (GTK_IS_WIDGET (widget)) {
506 while (widget && (!GDL_IS_DOCK (widget) ||
507 GDL_DOCK_OBJECT_GET_MASTER (widget) != master))
508 widget = widget->parent;
509 if (widget) {
510 gint win_w, win_h;
512 /* verify that the pointer is still in that dock
513 (the user could have moved it) */
514 gdk_window_get_geometry (widget->window,
515 NULL, NULL, &win_w, &win_h, NULL);
516 gdk_window_get_origin (widget->window, &win_x, &win_y);
517 if (root_x >= win_x && root_x < win_x + win_w &&
518 root_y >= win_y && root_y < win_y + win_h)
519 dock = GDL_DOCK (widget);
520 }
521 }
522 }
524 if (dock) {
525 /* translate root coordinates into dock object coordinates
526 (i.e. widget coordinates) */
527 gdk_window_get_origin (GTK_WIDGET (dock)->window, &win_x, &win_y);
528 x = root_x - win_x;
529 y = root_y - win_y;
530 may_dock = gdl_dock_object_dock_request (GDL_DOCK_OBJECT (dock),
531 x, y, &my_request);
532 }
533 else {
534 GList *l;
536 /* try to dock the item in all the docks in the ring in turn */
537 for (l = master->toplevel_docks; l; l = l->next) {
538 dock = GDL_DOCK (l->data);
539 /* translate root coordinates into dock object coordinates
540 (i.e. widget coordinates) */
541 gdk_window_get_origin (GTK_WIDGET (dock)->window, &win_x, &win_y);
542 x = root_x - win_x;
543 y = root_y - win_y;
544 may_dock = gdl_dock_object_dock_request (GDL_DOCK_OBJECT (dock),
545 x, y, &my_request);
546 if (may_dock)
547 break;
548 }
549 }
552 if (!may_dock) {
553 GtkRequisition req;
554 /* Special case for GdlDockItems : they must respect the flags */
555 if(GDL_IS_DOCK_ITEM(item)
556 && GDL_DOCK_ITEM(item)->behavior & GDL_DOCK_ITEM_BEH_NEVER_FLOATING)
557 return;
559 dock = NULL;
560 my_request.target = GDL_DOCK_OBJECT (
561 gdl_dock_object_get_toplevel (request->applicant));
562 my_request.position = GDL_DOCK_FLOATING;
564 gdl_dock_item_preferred_size (GDL_DOCK_ITEM (request->applicant), &req);
565 my_request.rect.width = req.width;
566 my_request.rect.height = req.height;
568 my_request.rect.x = root_x - GDL_DOCK_ITEM (request->applicant)->dragoff_x;
569 my_request.rect.y = root_y - GDL_DOCK_ITEM (request->applicant)->dragoff_y;
571 /* setup extra docking information */
572 if (G_IS_VALUE (&my_request.extra))
573 g_value_unset (&my_request.extra);
575 g_value_init (&my_request.extra, GDK_TYPE_RECTANGLE);
576 g_value_set_boxed (&my_request.extra, &my_request.rect);
577 }
578 /* if we want to enforce GDL_DOCK_ITEM_BEH_NEVER_FLOATING */
579 /* the item must remain attached to the controller, otherwise */
580 /* it could be inserted in another floating dock */
581 /* so check for the flag at this moment */
582 else if(GDL_IS_DOCK_ITEM(item)
583 && GDL_DOCK_ITEM(item)->behavior & GDL_DOCK_ITEM_BEH_NEVER_FLOATING
584 && dock != GDL_DOCK(master->controller))
585 return;
587 if (!(my_request.rect.x == request->rect.x &&
588 my_request.rect.y == request->rect.y &&
589 my_request.rect.width == request->rect.width &&
590 my_request.rect.height == request->rect.height &&
591 dock == master->_priv->rect_owner)) {
593 /* erase the previous rectangle */
594 if (master->_priv->rect_drawn)
595 gdl_dock_master_xor_rect (master);
596 }
598 /* set the new values */
599 *request = my_request;
600 master->_priv->rect_owner = dock;
602 /* draw the previous rectangle */
603 if (~master->_priv->rect_drawn)
604 gdl_dock_master_xor_rect (master);
605 }
607 static void
608 _gdl_dock_master_foreach (gpointer key,
609 gpointer value,
610 gpointer user_data)
611 {
612 (void)key;
613 struct {
614 GFunc function;
615 gpointer user_data;
616 } *data = user_data;
618 (* data->function) (GTK_WIDGET (value), data->user_data);
619 }
621 static void
622 gdl_dock_master_xor_rect (GdlDockMaster *master)
623 {
624 gint8 dash_list [2];
625 GdkWindow *window;
626 GdkRectangle *rect;
628 if (!master->_priv || !master->_priv->drag_request)
629 return;
631 master->_priv->rect_drawn = ~master->_priv->rect_drawn;
633 if (master->_priv->rect_owner) {
634 gdl_dock_xor_rect (master->_priv->rect_owner,
635 &master->_priv->drag_request->rect);
636 return;
637 }
639 rect = &master->_priv->drag_request->rect;
640 window = gdk_get_default_root_window ();
642 if (!master->_priv->root_xor_gc) {
643 GdkGCValues values;
645 values.function = GDK_INVERT;
646 values.subwindow_mode = GDK_INCLUDE_INFERIORS;
647 master->_priv->root_xor_gc = gdk_gc_new_with_values (
648 window, &values, GDK_GC_FUNCTION | GDK_GC_SUBWINDOW);
649 };
651 #ifdef WIN32
652 GdkLineStyle lineStyle = GDK_LINE_ON_OFF_DASH;
653 if (is_os_vista())
654 {
655 // On Vista the dash-line is increadibly slow to draw, it takes several minutes to draw the tracking lines
656 // With GDK_LINE_SOLID it is parts of a second
657 // No performance issue on WinXP
658 lineStyle = GDK_LINE_SOLID;
659 }
660 #else
661 GdkLineStyle lineStyle = GDK_LINE_ON_OFF_DASH;
662 #endif
663 gdk_gc_set_line_attributes (master->_priv->root_xor_gc, 1,
664 lineStyle,
665 GDK_CAP_NOT_LAST,
666 GDK_JOIN_BEVEL);
668 dash_list[0] = 1;
669 dash_list[1] = 1;
670 gdk_gc_set_dashes (master->_priv->root_xor_gc, 1, dash_list, 2);
672 gdk_draw_rectangle (window, master->_priv->root_xor_gc, 0,
673 rect->x, rect->y,
674 rect->width, rect->height);
676 gdk_gc_set_dashes (master->_priv->root_xor_gc, 0, dash_list, 2);
678 gdk_draw_rectangle (window, master->_priv->root_xor_gc, 0,
679 rect->x + 1, rect->y + 1,
680 rect->width - 2, rect->height - 2);
681 }
683 static void
684 gdl_dock_master_layout_changed (GdlDockMaster *master)
685 {
686 g_return_if_fail (GDL_IS_DOCK_MASTER (master));
688 /* emit "layout-changed" on the controller to notify the user who
689 * normally shouldn't have access to us */
690 if (master->controller)
691 g_signal_emit_by_name (master->controller, "layout-changed");
693 /* remove the idle handler if there is one */
694 if (master->_priv->idle_layout_changed_id) {
695 g_source_remove (master->_priv->idle_layout_changed_id);
696 master->_priv->idle_layout_changed_id = 0;
697 }
698 }
700 static gboolean
701 idle_emit_layout_changed (gpointer user_data)
702 {
703 GdlDockMaster *master = user_data;
705 g_return_val_if_fail (master && GDL_IS_DOCK_MASTER (master), FALSE);
707 master->_priv->idle_layout_changed_id = 0;
708 g_signal_emit (master, master_signals [LAYOUT_CHANGED], 0);
710 return FALSE;
711 }
713 static void
714 item_dock_cb (GdlDockObject *object,
715 GdlDockObject *requestor,
716 GdlDockPlacement position,
717 GValue *other_data,
718 gpointer user_data)
719 {
720 GdlDockMaster *master = user_data;
722 g_return_if_fail (requestor && GDL_IS_DOCK_OBJECT (requestor));
723 g_return_if_fail (master && GDL_IS_DOCK_MASTER (master));
725 /* here we are in fact interested in the requestor, since it's
726 * assumed that object will not change its visibility... for the
727 * requestor, however, could mean that it's being shown */
728 if (!GDL_DOCK_OBJECT_IN_REFLOW (requestor) &&
729 !GDL_DOCK_OBJECT_AUTOMATIC (requestor)) {
730 if (!master->_priv->idle_layout_changed_id)
731 master->_priv->idle_layout_changed_id =
732 g_idle_add (idle_emit_layout_changed, master);
733 }
734 }
736 static void
737 item_detach_cb (GdlDockObject *object,
738 gboolean recursive,
739 gpointer user_data)
740 {
741 GdlDockMaster *master = user_data;
743 g_return_if_fail (object && GDL_IS_DOCK_OBJECT (object));
744 g_return_if_fail (master && GDL_IS_DOCK_MASTER (master));
746 if (!GDL_DOCK_OBJECT_IN_REFLOW (object) &&
747 !GDL_DOCK_OBJECT_AUTOMATIC (object)) {
748 if (!master->_priv->idle_layout_changed_id)
749 master->_priv->idle_layout_changed_id =
750 g_idle_add (idle_emit_layout_changed, master);
751 }
752 }
754 static void
755 item_notify_cb (GdlDockObject *object,
756 GParamSpec *pspec,
757 gpointer user_data)
758 {
759 GdlDockMaster *master = user_data;
760 gint locked = COMPUTE_LOCKED (master);
761 gboolean item_locked;
763 g_object_get (object, "locked", &item_locked, NULL);
765 if (item_locked) {
766 g_hash_table_remove (master->_priv->unlocked_items, object);
767 g_hash_table_insert (master->_priv->locked_items, object, NULL);
768 } else {
769 g_hash_table_remove (master->_priv->locked_items, object);
770 g_hash_table_insert (master->_priv->unlocked_items, object, NULL);
771 }
773 if (COMPUTE_LOCKED (master) != locked)
774 g_object_notify (G_OBJECT (master), "locked");
775 }
777 /* ----- Public interface ----- */
779 void
780 gdl_dock_master_add (GdlDockMaster *master,
781 GdlDockObject *object)
782 {
783 g_return_if_fail (master != NULL && object != NULL);
785 if (!GDL_DOCK_OBJECT_AUTOMATIC (object)) {
786 GdlDockObject *found_object;
788 /* create a name for the object if it doesn't have one */
789 if (!object->name)
790 /* directly set the name, since it's a construction only
791 property */
792 object->name = g_strdup_printf ("__dock_%u", master->_priv->number++);
794 /* add the object to our hash list */
795 if ((found_object = g_hash_table_lookup (master->dock_objects, object->name))) {
796 g_warning (_("master %p: unable to add object %p[%s] to the hash. "
797 "There already is an item with that name (%p)."),
798 master, object, object->name, found_object);
799 }
800 else {
801 g_object_ref (object);
802 gtk_object_sink (GTK_OBJECT (object));
803 g_hash_table_insert (master->dock_objects, g_strdup (object->name), object);
804 }
805 }
807 if (GDL_IS_DOCK (object)) {
808 gboolean floating;
810 /* if this is the first toplevel we are adding, name it controller */
811 if (!master->toplevel_docks)
812 /* the dock should already have the ref */
813 master->controller = object;
815 /* add dock to the toplevel list */
816 g_object_get (object, "floating", &floating, NULL);
817 if (floating)
818 master->toplevel_docks = g_list_prepend (master->toplevel_docks, object);
819 else
820 master->toplevel_docks = g_list_append (master->toplevel_docks, object);
822 /* we are interested in the dock request this toplevel
823 * receives to update the layout */
824 g_signal_connect (object, "dock",
825 G_CALLBACK (item_dock_cb), master);
827 }
828 else if (GDL_IS_DOCK_ITEM (object)) {
829 /* we need to connect the item's signals */
830 g_signal_connect (object, "dock_drag_begin",
831 G_CALLBACK (gdl_dock_master_drag_begin), master);
832 g_signal_connect (object, "dock_drag_motion",
833 G_CALLBACK (gdl_dock_master_drag_motion), master);
834 g_signal_connect (object, "dock_drag_end",
835 G_CALLBACK (gdl_dock_master_drag_end), master);
836 g_signal_connect (object, "dock",
837 G_CALLBACK (item_dock_cb), master);
838 g_signal_connect (object, "detach",
839 G_CALLBACK (item_detach_cb), master);
841 /* register to "locked" notification if the item has a grip,
842 * and add the item to the corresponding hash */
843 if (GDL_DOCK_ITEM_HAS_GRIP (GDL_DOCK_ITEM (object))) {
844 g_signal_connect (object, "notify::locked",
845 G_CALLBACK (item_notify_cb), master);
846 item_notify_cb (object, NULL, master);
847 }
849 /* If the item is notebook, set the switcher style */
850 if (GDL_IS_DOCK_NOTEBOOK (object) &&
851 GDL_IS_SWITCHER (GDL_DOCK_ITEM (object)->child))
852 {
853 g_object_set (GDL_DOCK_ITEM (object)->child, "switcher-style",
854 master->_priv->switcher_style, NULL);
855 }
857 /* post a layout_changed emission if the item is not automatic
858 * (since it should be added to the items model) */
859 if (!GDL_DOCK_OBJECT_AUTOMATIC (object)) {
860 if (!master->_priv->idle_layout_changed_id)
861 master->_priv->idle_layout_changed_id =
862 g_idle_add (idle_emit_layout_changed, master);
863 }
864 }
865 }
867 void
868 gdl_dock_master_remove (GdlDockMaster *master,
869 GdlDockObject *object)
870 {
871 g_return_if_fail (master != NULL && object != NULL);
873 /* remove from locked/unlocked hashes and property change if
874 * that's the case */
875 if (GDL_IS_DOCK_ITEM (object) && GDL_DOCK_ITEM_HAS_GRIP (GDL_DOCK_ITEM (object))) {
876 gint locked = COMPUTE_LOCKED (master);
877 if (g_hash_table_remove (master->_priv->locked_items, object) ||
878 g_hash_table_remove (master->_priv->unlocked_items, object)) {
879 if (COMPUTE_LOCKED (master) != locked)
880 g_object_notify (G_OBJECT (master), "locked");
881 }
882 }
884 /* ref the master, since removing the controller could cause master disposal */
885 g_object_ref (master);
887 /* all the interesting stuff happens in _gdl_dock_master_remove */
888 _gdl_dock_master_remove (object, master);
890 /* post a layout_changed emission if the item is not automatic
891 * (since it should be removed from the items model) */
892 if (!GDL_DOCK_OBJECT_AUTOMATIC (object)) {
893 if (!master->_priv->idle_layout_changed_id)
894 master->_priv->idle_layout_changed_id =
895 g_idle_add (idle_emit_layout_changed, master);
896 }
898 /* balance ref count */
899 g_object_unref (master);
900 }
902 void
903 gdl_dock_master_foreach (GdlDockMaster *master,
904 GFunc function,
905 gpointer user_data)
906 {
907 struct {
908 GFunc function;
909 gpointer user_data;
910 } data;
912 g_return_if_fail (master != NULL && function != NULL);
914 data.function = function;
915 data.user_data = user_data;
916 g_hash_table_foreach (master->dock_objects, _gdl_dock_master_foreach, &data);
917 }
919 void
920 gdl_dock_master_foreach_toplevel (GdlDockMaster *master,
921 gboolean include_controller,
922 GFunc function,
923 gpointer user_data)
924 {
925 GList *l;
927 g_return_if_fail (master != NULL && function != NULL);
929 for (l = master->toplevel_docks; l; ) {
930 GdlDockObject *object = GDL_DOCK_OBJECT (l->data);
931 l = l->next;
932 if (object != master->controller || include_controller)
933 (* function) (GTK_WIDGET (object), user_data);
934 }
935 }
937 GdlDockObject *
938 gdl_dock_master_get_object (GdlDockMaster *master,
939 const gchar *nick_name)
940 {
941 gpointer *found;
943 g_return_val_if_fail (master != NULL, NULL);
945 if (!nick_name)
946 return NULL;
948 found = g_hash_table_lookup (master->dock_objects, nick_name);
950 return found ? GDL_DOCK_OBJECT (found) : NULL;
951 }
953 GdlDockObject *
954 gdl_dock_master_get_controller (GdlDockMaster *master)
955 {
956 g_return_val_if_fail (master != NULL, NULL);
958 return master->controller;
959 }
961 void
962 gdl_dock_master_set_controller (GdlDockMaster *master,
963 GdlDockObject *new_controller)
964 {
965 g_return_if_fail (master != NULL);
967 if (new_controller) {
968 if (GDL_DOCK_OBJECT_AUTOMATIC (new_controller))
969 g_warning (_("The new dock controller %p is automatic. Only manual "
970 "dock objects should be named controller."), new_controller);
972 /* check that the controller is in the toplevel list */
973 if (!g_list_find (master->toplevel_docks, new_controller))
974 gdl_dock_master_add (master, new_controller);
975 master->controller = new_controller;
977 } else {
978 master->controller = NULL;
979 /* no controller, no master */
980 g_object_unref (master);
981 }
982 }
984 static void
985 set_switcher_style_foreach (GtkWidget *obj, gpointer user_data)
986 {
987 GdlSwitcherStyle style = GPOINTER_TO_INT (user_data);
989 if (!GDL_IS_DOCK_ITEM (obj))
990 return;
992 if (GDL_IS_DOCK_NOTEBOOK (obj)) {
994 GtkWidget *child = GDL_DOCK_ITEM (obj)->child;
995 if (GDL_IS_SWITCHER (child)) {
997 g_object_set (child, "switcher-style", style, NULL);
998 }
999 } else if (gdl_dock_object_is_compound (GDL_DOCK_OBJECT (obj))) {
1001 gtk_container_foreach (GTK_CONTAINER (obj),
1002 set_switcher_style_foreach,
1003 user_data);
1004 }
1005 }
1007 static void
1008 gdl_dock_master_set_switcher_style (GdlDockMaster *master,
1009 GdlSwitcherStyle switcher_style)
1010 {
1011 GList *l;
1012 g_return_if_fail (GDL_IS_DOCK_MASTER (master));
1014 master->_priv->switcher_style = switcher_style;
1015 for (l = master->toplevel_docks; l; l = l->next) {
1016 GdlDock *dock = GDL_DOCK (l->data);
1017 if (dock->root)
1018 set_switcher_style_foreach (GTK_WIDGET (dock->root),
1019 GINT_TO_POINTER (switcher_style));
1020 }
1022 /* just to be sure hidden items are set too */
1023 gdl_dock_master_foreach (master, (GFunc) set_switcher_style_foreach,
1024 GINT_TO_POINTER (switcher_style));
1025 }