AdgEntity: added "destroy" signal
[adg.git] / src / adg / adg-container.c
blobd9d36101e215de566f9cf70fd900ac1f59c8da35
1 /* ADG - Automatic Drawing Generation
2 * Copyright (C) 2007,2008,2009,2010,2011 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.
35 * Since: 1.0
36 **/
38 /**
39 * AdgContainer:
41 * All fields are private and should not be used directly.
42 * Use its public methods instead.
44 * Since: 1.0
45 **/
48 #include "adg-internal.h"
50 #include "adg-container.h"
51 #include "adg-container-private.h"
54 #define _ADG_PARENT_OBJECT_CLASS ((GObjectClass *) adg_container_parent_class)
55 #define _ADG_PARENT_ENTITY_CLASS ((AdgEntityClass *) adg_container_parent_class)
58 G_DEFINE_TYPE(AdgContainer, adg_container, ADG_TYPE_ENTITY)
60 enum {
61 PROP_0,
62 PROP_CHILD
65 enum {
66 ADD,
67 REMOVE,
68 LAST_SIGNAL
72 static void _adg_dispose (GObject *object);
73 static void _adg_set_property (GObject *object,
74 guint prop_id,
75 const GValue *value,
76 GParamSpec *pspec);
77 static void _adg_destroy (AdgEntity *entity);
78 static void _adg_global_changed (AdgEntity *entity);
79 static void _adg_local_changed (AdgEntity *entity);
80 static void _adg_invalidate (AdgEntity *entity);
81 static void _adg_arrange (AdgEntity *entity);
82 static void _adg_add_extents (AdgEntity *entity,
83 CpmlExtents *extents);
84 static void _adg_render (AdgEntity *entity,
85 cairo_t *cr);
86 static GSList * _adg_children (AdgContainer *container);
87 static void _adg_add (AdgContainer *container,
88 AdgEntity *entity);
89 static void _adg_remove (AdgContainer *container,
90 AdgEntity *entity);
91 static void _adg_remove_from_list (gpointer container,
92 GObject *entity);
94 static guint _adg_signals[LAST_SIGNAL] = { 0 };
97 static void
98 adg_container_class_init(AdgContainerClass *klass)
100 GObjectClass *gobject_class;
101 AdgEntityClass *entity_class;
102 GParamSpec *param;
104 gobject_class = (GObjectClass *) klass;
105 entity_class = (AdgEntityClass *) klass;
107 g_type_class_add_private(klass, sizeof(AdgContainerPrivate));
109 gobject_class->dispose = _adg_dispose;
110 gobject_class->set_property = _adg_set_property;
112 entity_class->destroy = _adg_destroy;
113 entity_class->global_changed = _adg_global_changed;
114 entity_class->local_changed = _adg_local_changed;
115 entity_class->invalidate = _adg_invalidate;
116 entity_class->arrange = _adg_arrange;
117 entity_class->render = _adg_render;
119 klass->children = _adg_children;
120 klass->add = _adg_add;
121 klass->remove = _adg_remove;
123 param = g_param_spec_object("child",
124 P_("Child"),
125 P_("Can be used to add a new child to the container"),
126 ADG_TYPE_ENTITY,
127 G_PARAM_WRITABLE);
128 g_object_class_install_property(gobject_class, PROP_CHILD, param);
131 * AdgContainer::add:
132 * @container: an #AdgContainer
133 * @entity: the #AdgEntity to add
135 * Adds @entity to @container. @entity must not be inside another
136 * container or the operation will fail.
138 * Since: 1.0
140 _adg_signals[ADD] = g_signal_new("add",
141 G_OBJECT_CLASS_TYPE(gobject_class),
142 G_SIGNAL_RUN_FIRST,
143 G_STRUCT_OFFSET(AdgContainerClass, add),
144 NULL, NULL,
145 adg_marshal_VOID__OBJECT,
146 G_TYPE_NONE, 1, ADG_TYPE_ENTITY);
149 * AdgContainer::remove:
150 * @container: an #AdgContainer
151 * @entity: the #AdgEntity to remove
153 * Removes @entity from @container.
155 * Since: 1.0
157 _adg_signals[REMOVE] = g_signal_new("remove",
158 G_OBJECT_CLASS_TYPE(gobject_class),
159 G_SIGNAL_RUN_FIRST,
160 G_STRUCT_OFFSET(AdgContainerClass, remove),
161 NULL, NULL,
162 adg_marshal_VOID__OBJECT,
163 G_TYPE_NONE, 1, ADG_TYPE_ENTITY);
166 static void
167 adg_container_init(AdgContainer *container)
169 AdgContainerPrivate *data = G_TYPE_INSTANCE_GET_PRIVATE(container,
170 ADG_TYPE_CONTAINER,
171 AdgContainerPrivate);
173 data->children = NULL;
175 container->data = data;
178 static void
179 _adg_dispose(GObject *object)
181 AdgContainer *container;
182 AdgContainerPrivate *data;
184 container = (AdgContainer *) object;
185 data = container->data;
187 /* Remove all the children from the container: these will emit
188 * a "remove" signal for every child and will drop all the
189 * references from the children to this container (and, obviously,
190 * from the container to the children). */
191 while (data->children != NULL)
192 adg_container_remove(container, (AdgEntity *) data->children->data);
194 if (_ADG_PARENT_OBJECT_CLASS->dispose)
195 _ADG_PARENT_OBJECT_CLASS->dispose(object);
198 static void
199 _adg_set_property(GObject *object,
200 guint prop_id, const GValue *value, GParamSpec *pspec)
202 AdgContainer *container = (AdgContainer *) object;
204 switch (prop_id) {
205 case PROP_CHILD:
206 adg_container_add(container, g_value_get_object(value));
207 break;
208 default:
209 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
215 * adg_container_new:
217 * Creates a new container entity.
219 * Returns: the newly created container entity
221 * Since: 1.0
223 AdgContainer *
224 adg_container_new(void)
226 return g_object_new(ADG_TYPE_CONTAINER, NULL);
231 * adg_container_add:
232 * @container: an #AdgContainer
233 * @entity: an #AdgEntity
235 * Emits a #AdgContainer::add signal on @container passing @entity
236 * as argument. @entity must be added to only one container at a time,
237 * you can't place the same entity inside two different containers.
239 * Once @entity has been added, the floating reference will be removed
240 * and @container will own a reference to @entity. This means the only
241 * proper way to destroy @entity is to call adg_container_remove().
243 * Since: 1.0
245 void
246 adg_container_add(AdgContainer *container, AdgEntity *entity)
248 g_return_if_fail(ADG_IS_CONTAINER(container));
249 g_return_if_fail(ADG_IS_ENTITY(entity));
251 g_signal_emit(container, _adg_signals[ADD], 0, entity);
255 * adg_container_remove:
256 * @container: an #AdgContainer
257 * @entity: an #AdgEntity
259 * Emits a #AdgContainer::remove signal on @container passing
260 * @entity as argument. @entity must be inside @container.
262 * Note that @container will own a reference to @entity and it
263 * may be the last reference held: this means removing an entity
264 * from its container can destroy it.
266 * If you want to use @entity again, you need to add a reference
267 * to it, using g_object_ref(), before removing it from @container.
268 * The following typical example shows you how to properly move
269 * <varname>entity</varname> from <varname>container1</varname>
270 * to <varname>container2</varname>:
272 * |[
273 * g_object_ref(entity);
274 * adg_container_remove(container1, entity);
275 * adg_container_add(container2, entity)
276 * g_object_unref(entity);
277 * ]|
279 * Since: 1.0
281 void
282 adg_container_remove(AdgContainer *container, AdgEntity *entity)
284 g_return_if_fail(ADG_IS_CONTAINER(container));
285 g_return_if_fail(ADG_IS_ENTITY(entity));
287 g_signal_emit(container, _adg_signals[REMOVE], 0, entity);
291 * adg_container_children:
292 * @container: an #AdgContainer
294 * Gets the children list of @container. This list must be manually
295 * freed with g_slist_free() when no longer user.
297 * The returned list is ordered from the most recently added child
298 * to the oldest one.
300 * Returns: (element-type AdgEntity) (transfer container): a newly allocated #GSList of #AdgEntity or %NULL empty list or on errors
302 * Since: 1.0
304 GSList *
305 adg_container_children(AdgContainer *container)
307 AdgContainerClass *klass;
309 g_return_val_if_fail(ADG_IS_CONTAINER(container), NULL);
311 klass = ADG_CONTAINER_GET_CLASS(container);
313 if (klass->children == NULL)
314 return NULL;
316 return klass->children(container);
320 * adg_container_foreach:
321 * @container: an #AdgContainer
322 * @callback: (scope call): a callback
323 * @user_data: callback user data
325 * Invokes @callback on each child of @container.
326 * The callback should be declared as:
328 * |[
329 * void callback(AdgEntity *entity, gpointer user_data);
330 * ]|
332 * Since: 1.0
334 void
335 adg_container_foreach(AdgContainer *container,
336 GCallback callback, gpointer user_data)
338 GSList *children;
340 g_return_if_fail(ADG_IS_CONTAINER(container));
341 g_return_if_fail(callback != NULL);
343 children = adg_container_children(container);
345 while (children != NULL) {
346 if (children->data != NULL)
347 ((void (*) (gpointer, gpointer)) callback) (children->data, user_data);
349 children = g_slist_delete_link(children, children);
354 * adg_container_propagate:
355 * @container: an #AdgContainer
356 * @signal_id: the signal id
357 * @detail: the detail
358 * @...: parameters to be passed to the signal, followed by a pointer
359 * to the allocated memory where to store the return type: if
360 * the signal is %G_TYPE_NONE (void return type), this trailing
361 * pointer should be omitted
363 * Emits the specified signal to all the children of @container
364 * using g_signal_emit_valist() calls.
366 * Since: 1.0
368 void
369 adg_container_propagate(AdgContainer *container,
370 guint signal_id, GQuark detail, ...)
372 va_list var_args;
374 va_start(var_args, detail);
375 adg_container_propagate_valist(container, signal_id, detail, var_args);
376 va_end(var_args);
380 * adg_container_propagate_by_name:
381 * @container: an #AdgContainer
382 * @detailed_signal: a string of the form "signal-name::detail".
383 * @...: parameters to be passed to the signal, followed by a pointer
384 * to the allocated memory where to store the return type: if
385 * the signal is %G_TYPE_NONE (void return type), this trailing
386 * pointer should be omitted
388 * Emits the specified signal to all the children of @container
389 * using g_signal_emit_valist() calls.
391 * Since: 1.0
393 void
394 adg_container_propagate_by_name(AdgContainer *container,
395 const gchar *detailed_signal, ...)
397 guint signal_id;
398 GQuark detail = 0;
399 va_list var_args;
401 if (!g_signal_parse_name(detailed_signal, G_TYPE_FROM_INSTANCE(container),
402 &signal_id, &detail, FALSE)) {
403 g_warning(_("%s: signal `%s' is invalid for instance `%p'"),
404 G_STRLOC, detailed_signal, container);
405 return;
408 va_start(var_args, detailed_signal);
409 adg_container_propagate_valist(container, signal_id, detail, var_args);
410 va_end(var_args);
414 * adg_container_propagate_valist:
415 * @container: an #AdgContainer
416 * @signal_id: the signal id
417 * @detail: the detail
418 * @var_args: parameters to be passed to the signal, followed by a
419 * pointer to the allocated memory where to store the
420 * return type: if the signal is %G_TYPE_NONE (void return
421 * type), this trailing pointer should be omitted
423 * Emits the specified signal to all the children of @container
424 * using g_signal_emit_valist() calls.
426 * Since: 1.0
428 void
429 adg_container_propagate_valist(AdgContainer *container,
430 guint signal_id, GQuark detail, va_list var_args)
432 GSList *children;
433 va_list var_copy;
435 g_return_if_fail(ADG_IS_CONTAINER(container));
437 children = adg_container_children(container);
439 while (children != NULL) {
440 if (children->data != NULL) {
441 G_VA_COPY(var_copy, var_args);
442 g_signal_emit_valist(children->data, signal_id, detail, var_copy);
445 children = g_slist_delete_link(children, children);
450 static void
451 _adg_destroy(AdgEntity *entity)
453 adg_container_propagate_by_name((AdgContainer *) entity, "destroy");
455 if (_ADG_PARENT_ENTITY_CLASS->destroy)
456 _ADG_PARENT_ENTITY_CLASS->destroy(entity);
459 static void
460 _adg_global_changed(AdgEntity *entity)
462 if (_ADG_PARENT_ENTITY_CLASS->global_changed)
463 _ADG_PARENT_ENTITY_CLASS->global_changed(entity);
465 adg_container_propagate_by_name((AdgContainer *) entity, "global-changed");
468 static void
469 _adg_local_changed(AdgEntity *entity)
471 if (_ADG_PARENT_ENTITY_CLASS->local_changed)
472 _ADG_PARENT_ENTITY_CLASS->local_changed(entity);
474 adg_container_propagate_by_name((AdgContainer *) entity, "local-changed");
477 static void
478 _adg_invalidate(AdgEntity *entity)
480 adg_container_propagate_by_name((AdgContainer *) entity, "invalidate");
483 static void
484 _adg_arrange(AdgEntity *entity)
486 AdgContainer *container = (AdgContainer *) entity;
487 CpmlExtents extents = { 0 };
489 adg_container_propagate_by_name(container, "arrange", NULL);
490 adg_container_foreach(container, G_CALLBACK(_adg_add_extents), &extents);
491 adg_entity_set_extents(entity, &extents);
494 static void
495 _adg_add_extents(AdgEntity *entity, CpmlExtents *extents)
497 cpml_extents_add(extents, adg_entity_get_extents(entity));
500 static void
501 _adg_render(AdgEntity *entity, cairo_t *cr)
503 adg_container_propagate_by_name((AdgContainer *) entity, "render", cr);
507 static GSList *
508 _adg_children(AdgContainer *container)
510 AdgContainerPrivate *data = container->data;
512 /* The NULL case is yet managed by GLib */
513 return g_slist_copy(data->children);
516 static void
517 _adg_add(AdgContainer *container, AdgEntity *entity)
519 const AdgEntity *old_parent;
520 AdgContainerPrivate *data;
522 old_parent = adg_entity_get_parent(entity);
523 if (old_parent != NULL) {
524 g_warning(_("Attempting to add an entity with type %s to a container "
525 "of type %s, but the entity is already inside a container "
526 "of type %s"),
527 g_type_name(G_OBJECT_TYPE(entity)),
528 g_type_name(G_OBJECT_TYPE(container)),
529 g_type_name(G_OBJECT_TYPE(old_parent)));
530 return;
533 data = container->data;
534 data->children = g_slist_prepend(data->children, entity);
536 g_object_ref_sink(entity);
537 adg_entity_set_parent(entity, (AdgEntity *) container);
538 g_object_weak_ref((GObject *) entity, _adg_remove_from_list, container);
541 static void
542 _adg_remove_from_list(gpointer container, GObject *entity)
544 AdgContainerPrivate *data = ((AdgContainer *) container)->data;
545 data->children = g_slist_remove(data->children, entity);
548 static void
549 _adg_remove(AdgContainer *container, AdgEntity *entity)
551 AdgContainerPrivate *data;
552 GSList *node;
554 data = container->data;
555 node = g_slist_find(data->children, entity);
557 if (node == NULL) {
558 g_warning(_("Attempting to remove an entity with type %s from a "
559 "container of type %s, but the entity is not present"),
560 g_type_name(G_OBJECT_TYPE(entity)),
561 g_type_name(G_OBJECT_TYPE(container)));
562 return;
565 g_object_weak_unref((GObject *) entity, _adg_remove_from_list, container);
566 data->children = g_slist_delete_link(data->children, node);
567 adg_entity_set_parent(entity, NULL);
568 g_object_unref(entity);