[AdgContainer] Allow to g_object_unref() the children
[adg.git] / src / adg / adg-container.c
blobe8a8c469ae49a003d6c43913a7d87cdf479d35dd
1 /* ADG - Automatic Drawing Generation
2 * Copyright (C) 2007,2008,2009,2010 Nicola Fontana <ntd at entidi.it>
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
21 /**
22 * SECTION:adg-container
23 * @short_description: Base class for entity that can contain other entities
25 * The #AdgContainer is an entity that can contains more sub-entities.
26 * Moreover, it can apply a common transformation to local and/or global
27 * maps: see http://adg.entidi.com/tutorial/view/3 for further details.
29 * Adding an entity to a container will make a circular dependency
30 * between the two objects. The container will also add a weak reference
31 * to the child entity to intercept when the entity is manually
32 * destroyed (usually by calling g_object_unref()) and remove the child
33 * reference from the internal children list.
34 **/
36 /**
37 * AdgContainer:
39 * All fields are private and should not be used directly.
40 * Use its public methods instead.
41 **/
43 #include "adg-internal.h"
44 #include "adg-container.h"
45 #include "adg-container-private.h"
46 #include "adg-marshal.h"
48 #define _ADG_PARENT_OBJECT_CLASS ((GObjectClass *) adg_container_parent_class)
49 #define _ADG_PARENT_ENTITY_CLASS ((AdgEntityClass *) adg_container_parent_class)
52 G_DEFINE_TYPE(AdgContainer, adg_container, ADG_TYPE_ENTITY);
54 enum {
55 PROP_0,
56 PROP_CHILD
59 enum {
60 ADD,
61 REMOVE,
62 LAST_SIGNAL
66 static void _adg_dispose (GObject *object);
67 static void _adg_set_property (GObject *object,
68 guint prop_id,
69 const GValue *value,
70 GParamSpec *pspec);
71 static void _adg_global_changed (AdgEntity *entity);
72 static void _adg_local_changed (AdgEntity *entity);
73 static void _adg_invalidate (AdgEntity *entity);
74 static void _adg_arrange (AdgEntity *entity);
75 static void _adg_add_extents (AdgEntity *entity,
76 CpmlExtents *extents);
77 static void _adg_render (AdgEntity *entity,
78 cairo_t *cr);
79 static GSList * _adg_children (AdgContainer *container);
80 static void _adg_add (AdgContainer *container,
81 AdgEntity *entity);
82 static void _adg_remove (AdgContainer *container,
83 AdgEntity *entity);
84 static void _adg_remove_from_list (gpointer container,
85 GObject *entity);
87 static guint _adg_signals[LAST_SIGNAL] = { 0 };
90 static void
91 adg_container_class_init(AdgContainerClass *klass)
93 GObjectClass *gobject_class;
94 AdgEntityClass *entity_class;
95 GParamSpec *param;
97 gobject_class = (GObjectClass *) klass;
98 entity_class = (AdgEntityClass *) klass;
100 g_type_class_add_private(klass, sizeof(AdgContainerPrivate));
102 gobject_class->dispose = _adg_dispose;
103 gobject_class->set_property = _adg_set_property;
105 entity_class->global_changed = _adg_global_changed;
106 entity_class->local_changed = _adg_local_changed;
107 entity_class->invalidate = _adg_invalidate;
108 entity_class->arrange = _adg_arrange;
109 entity_class->render = _adg_render;
111 klass->children = _adg_children;
112 klass->add = _adg_add;
113 klass->remove = _adg_remove;
115 param = g_param_spec_object("child",
116 P_("Child"),
117 P_("Can be used to add a new child to the container"),
118 ADG_TYPE_ENTITY,
119 G_PARAM_WRITABLE);
120 g_object_class_install_property(gobject_class, PROP_CHILD, param);
123 * AdgContainer::add:
124 * @container: an #AdgContainer
125 * @entity: the #AdgEntity to add
127 * Adds @entity to @container. @entity must not be inside another
128 * container or the operation will fail.
130 _adg_signals[ADD] = g_signal_new("add",
131 G_OBJECT_CLASS_TYPE(gobject_class),
132 G_SIGNAL_RUN_FIRST,
133 G_STRUCT_OFFSET(AdgContainerClass, add),
134 NULL, NULL,
135 adg_marshal_VOID__OBJECT,
136 G_TYPE_NONE, 1, ADG_TYPE_ENTITY);
139 * AdgContainer::remove:
140 * @container: an #AdgContainer
141 * @entity: the #AdgEntity to remove
143 * Removes @entity from @container.
145 _adg_signals[REMOVE] = g_signal_new("remove",
146 G_OBJECT_CLASS_TYPE(gobject_class),
147 G_SIGNAL_RUN_FIRST,
148 G_STRUCT_OFFSET(AdgContainerClass, remove),
149 NULL, NULL,
150 adg_marshal_VOID__OBJECT,
151 G_TYPE_NONE, 1, ADG_TYPE_ENTITY);
154 static void
155 adg_container_init(AdgContainer *container)
157 AdgContainerPrivate *data = G_TYPE_INSTANCE_GET_PRIVATE(container,
158 ADG_TYPE_CONTAINER,
159 AdgContainerPrivate);
161 data->children = NULL;
163 container->data = data;
166 static void
167 _adg_dispose(GObject *object)
169 AdgContainer *container;
170 AdgContainerPrivate *data;
172 container = (AdgContainer *) object;
173 data = container->data;
175 /* Remove all the children from the container: these will emit
176 * a "remove" signal for every child and will drop all the
177 * references from the children to this container (and, obviously,
178 * from the container to the children). */
179 while (data->children != NULL)
180 adg_container_remove(container, (AdgEntity *) data->children->data);
182 if (_ADG_PARENT_OBJECT_CLASS->dispose)
183 _ADG_PARENT_OBJECT_CLASS->dispose(object);
186 static void
187 _adg_set_property(GObject *object,
188 guint prop_id, const GValue *value, GParamSpec *pspec)
190 AdgContainer *container = (AdgContainer *) object;
192 switch (prop_id) {
193 case PROP_CHILD:
194 adg_container_add(container, g_value_get_object(value));
195 break;
196 default:
197 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
203 * adg_container_new:
205 * Creates a new container entity.
207 * Returns: the newly created container entity
209 AdgContainer *
210 adg_container_new(void)
212 return g_object_new(ADG_TYPE_CONTAINER, NULL);
217 * adg_container_add:
218 * @container: an #AdgContainer
219 * @entity: an #AdgEntity
221 * Emits a #AdgContainer::add signal on @container passing @entity
222 * as argument. @entity must be added to only one container at a time,
223 * you can't place the same entity inside two different containers.
225 * Once @entity has been added, the floating reference will be removed
226 * and @container will own a reference to @entity. This means the only
227 * proper way to destroy @entity is to call adg_container_remove().
229 void
230 adg_container_add(AdgContainer *container, AdgEntity *entity)
232 g_return_if_fail(ADG_IS_CONTAINER(container));
233 g_return_if_fail(ADG_IS_ENTITY(entity));
235 g_signal_emit(container, _adg_signals[ADD], 0, entity);
239 * adg_container_remove:
240 * @container: an #AdgContainer
241 * @entity: an #AdgEntity
243 * Emits a #AdgContainer::remove signal on @container passing
244 * @entity as argument. @entity must be inside @container.
246 * Note that @container will own a reference to @entity and it
247 * may be the last reference held: this means removing an entity
248 * from its container can destroy it.
250 * If you want to use @entity again, you need to add a reference
251 * to it, using g_object_ref(), before removing it from @container.
252 * The following typical example shows you how to properly move
253 * <varname>entity</varname> from <varname>container1</varname>
254 * to <varname>container2</varname>:
256 * |[
257 * g_object_ref(entity);
258 * adg_container_remove(container1, entity);
259 * adg_container_add(container2, entity)
260 * g_object_unref(entity);
261 * ]|
263 void
264 adg_container_remove(AdgContainer *container, AdgEntity *entity)
266 g_return_if_fail(ADG_IS_CONTAINER(container));
267 g_return_if_fail(ADG_IS_ENTITY(entity));
269 g_signal_emit(container, _adg_signals[REMOVE], 0, entity);
273 * adg_container_children:
274 * @container: an #AdgContainer
276 * Gets the children list of @container. This list must be manually
277 * freed with g_slist_free() when no longer user.
279 * The returned list is ordered from the most recently added child
280 * to the oldest one.
282 * Returns: a newly allocated #GSList or %NULL empty list or on errors
284 GSList *
285 adg_container_children(AdgContainer *container)
287 AdgContainerClass *klass;
289 g_return_val_if_fail(ADG_IS_CONTAINER(container), NULL);
291 klass = ADG_CONTAINER_GET_CLASS(container);
293 if (klass->children == NULL)
294 return NULL;
296 return klass->children(container);
300 * adg_container_foreach:
301 * @container: an #AdgContainer
302 * @callback: a callback
303 * @user_data: callback user data
305 * Invokes @callback on each child of @container.
306 * The callback should be declared as:
308 * |[
309 * void callback(AdgEntity *entity, gpointer user_data);
310 * ]|
312 void
313 adg_container_foreach(AdgContainer *container,
314 GCallback callback, gpointer user_data)
316 GSList *children;
318 g_return_if_fail(ADG_IS_CONTAINER(container));
319 g_return_if_fail(callback != NULL);
321 children = adg_container_children(container);
323 while (children != NULL) {
324 if (children->data != NULL)
325 ((void (*) (gpointer, gpointer)) callback) (children->data, user_data);
327 children = g_slist_delete_link(children, children);
332 * adg_container_propagate:
333 * @container: an #AdgContainer
334 * @signal_id: the signal id
335 * @detail: the detail
336 * @...: parameters to be passed to the signal, followed by a pointer
337 * to the allocated memory where to store the return type: if
338 * the signal is %G_TYPE_NONE (void return type), this trailing
339 * pointer should be omitted
341 * Emits the specified signal to all the children of @container
342 * using g_signal_emit_valist() calls.
344 void
345 adg_container_propagate(AdgContainer *container,
346 guint signal_id, GQuark detail, ...)
348 va_list var_args;
350 va_start(var_args, detail);
351 adg_container_propagate_valist(container, signal_id, detail, var_args);
352 va_end(var_args);
356 * adg_container_propagate_by_name:
357 * @container: an #AdgContainer
358 * @detailed_signal: a string of the form "signal-name::detail".
359 * @...: parameters to be passed to the signal, followed by a pointer
360 * to the allocated memory where to store the return type: if
361 * the signal is %G_TYPE_NONE (void return type), this trailing
362 * pointer should be omitted
364 * Emits the specified signal to all the children of @container
365 * using g_signal_emit_valist() calls.
367 void
368 adg_container_propagate_by_name(AdgContainer *container,
369 const gchar *detailed_signal, ...)
371 guint signal_id;
372 GQuark detail = 0;
373 va_list var_args;
375 if (!g_signal_parse_name(detailed_signal, G_TYPE_FROM_INSTANCE(container),
376 &signal_id, &detail, FALSE)) {
377 g_warning(_("%s: signal `%s' is invalid for instance `%p'"),
378 G_STRLOC, detailed_signal, container);
379 return;
382 va_start(var_args, detailed_signal);
383 adg_container_propagate_valist(container, signal_id, detail, var_args);
384 va_end(var_args);
388 * adg_container_propagate_valist:
389 * @container: an #AdgContainer
390 * @signal_id: the signal id
391 * @detail: the detail
392 * @var_args: parameters to be passed to the signal, followed by a
393 * pointer to the allocated memory where to store the
394 * return type: if the signal is %G_TYPE_NONE (void return
395 * type), this trailing pointer should be omitted
397 * Emits the specified signal to all the children of @container
398 * using g_signal_emit_valist() calls.
400 void
401 adg_container_propagate_valist(AdgContainer *container,
402 guint signal_id, GQuark detail, va_list var_args)
404 GSList *children;
405 va_list var_copy;
407 g_return_if_fail(ADG_IS_CONTAINER(container));
409 children = adg_container_children(container);
411 while (children != NULL) {
412 if (children->data != NULL) {
413 G_VA_COPY(var_copy, var_args);
414 g_signal_emit_valist(children->data, signal_id, detail, var_copy);
417 children = g_slist_delete_link(children, children);
422 static void
423 _adg_global_changed(AdgEntity *entity)
425 if (_ADG_PARENT_ENTITY_CLASS->global_changed)
426 _ADG_PARENT_ENTITY_CLASS->global_changed(entity);
428 adg_container_propagate_by_name((AdgContainer *) entity, "global-changed");
431 static void
432 _adg_local_changed(AdgEntity *entity)
434 if (_ADG_PARENT_ENTITY_CLASS->local_changed)
435 _ADG_PARENT_ENTITY_CLASS->local_changed(entity);
437 adg_container_propagate_by_name((AdgContainer *) entity, "local-changed");
440 static void
441 _adg_invalidate(AdgEntity *entity)
443 adg_container_propagate_by_name((AdgContainer *) entity, "invalidate");
446 static void
447 _adg_arrange(AdgEntity *entity)
449 AdgContainer *container = (AdgContainer *) entity;
450 CpmlExtents extents = { 0 };
452 adg_container_propagate_by_name(container, "arrange", NULL);
453 adg_container_foreach(container, G_CALLBACK(_adg_add_extents), &extents);
454 adg_entity_set_extents(entity, &extents);
457 static void
458 _adg_add_extents(AdgEntity *entity, CpmlExtents *extents)
460 cpml_extents_add(extents, adg_entity_get_extents(entity));
463 static void
464 _adg_render(AdgEntity *entity, cairo_t *cr)
466 adg_container_propagate_by_name((AdgContainer *) entity, "render", cr);
470 static GSList *
471 _adg_children(AdgContainer *container)
473 AdgContainerPrivate *data = container->data;
475 /* The NULL case is yet managed by GLib */
476 return g_slist_copy(data->children);
479 static void
480 _adg_add(AdgContainer *container, AdgEntity *entity)
482 const AdgEntity *old_parent;
483 AdgContainerPrivate *data;
485 old_parent = adg_entity_get_parent(entity);
486 if (old_parent != NULL) {
487 g_warning(_("Attempting to add an entity with type %s to a container "
488 "of type %s, but the entity is already inside a container "
489 "of type %s."),
490 g_type_name(G_OBJECT_TYPE(entity)),
491 g_type_name(G_OBJECT_TYPE(container)),
492 g_type_name(G_OBJECT_TYPE(old_parent)));
493 return;
496 data = container->data;
497 data->children = g_slist_prepend(data->children, entity);
499 g_object_ref_sink(entity);
500 adg_entity_set_parent(entity, (AdgEntity *) container);
501 g_object_weak_ref((GObject *) entity, _adg_remove_from_list, container);
504 static void
505 _adg_remove_from_list(gpointer container, GObject *entity)
507 AdgContainerPrivate *data = ((AdgContainer *) container)->data;
508 data->children = g_slist_remove(data->children, entity);
511 static void
512 _adg_remove(AdgContainer *container, AdgEntity *entity)
514 AdgContainerPrivate *data;
515 GSList *node;
517 data = container->data;
518 node = g_slist_find(data->children, entity);
520 if (node == NULL) {
521 g_warning(_("Attempting to remove an entity with type %s from a "
522 "container of type %s, but the entity is not present"),
523 g_type_name(G_OBJECT_TYPE(entity)),
524 g_type_name(G_OBJECT_TYPE(container)));
525 return;
528 g_object_weak_unref((GObject *) entity, _adg_remove_from_list, container);
529 data->children = g_slist_delete_link(data->children, node);
530 adg_entity_set_parent(entity, NULL);
531 g_object_unref(entity);