adg: use G_PRIVATE_ADD and friends
[adg.git] / src / adg / adg-container.c
blob1068bcd8a0a8e74ec81d15d6efda511285bb19e7
1 /* ADG - Automatic Drawing Generation
2 * Copyright (C) 2007-2019 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/home/details/ for further details.
29 * Adding an entity to a container will create a strong reference to the
30 * container in the entity and a weak reference to the entity on the
31 * container. This way the container will be able to destroy its children
32 * when destroyed and it will be able to update its children when an entity
33 * is destroyed.
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 **/
47 /**
48 * AdgContainerClass:
49 * @children: virtual method that gets the list of entities (that is
50 * #AdgEntity and derived instances) owned by the container.
51 * @add: signal that adds a new entity to the container.
52 * @remove: signal that removes a specific entity from the container.
54 * #AdgContainer effectively stores a #GSList of children into its
55 * private data and keeps a reference to every child it owns.
57 * Since: 1.0
58 **/
61 #include "adg-internal.h"
63 #include "adg-container.h"
64 #include "adg-container-private.h"
67 #define _ADG_PARENT_OBJECT_CLASS ((GObjectClass *) adg_container_parent_class)
68 #define _ADG_PARENT_ENTITY_CLASS ((AdgEntityClass *) adg_container_parent_class)
71 G_DEFINE_TYPE_WITH_PRIVATE(AdgContainer, adg_container, ADG_TYPE_ENTITY)
73 enum {
74 PROP_0,
75 PROP_CHILD
78 enum {
79 ADD,
80 REMOVE,
81 LAST_SIGNAL
85 static void _adg_dispose (GObject *object);
86 static void _adg_set_property (GObject *object,
87 guint prop_id,
88 const GValue *value,
89 GParamSpec *pspec);
90 static void _adg_destroy (AdgEntity *entity);
91 static void _adg_global_changed (AdgEntity *entity);
92 static void _adg_local_changed (AdgEntity *entity);
93 static void _adg_invalidate (AdgEntity *entity);
94 static void _adg_arrange (AdgEntity *entity);
95 static void _adg_add_extents (AdgEntity *entity,
96 CpmlExtents *extents);
97 static void _adg_render (AdgEntity *entity,
98 cairo_t *cr);
99 static GSList * _adg_children (AdgContainer *container);
100 static void _adg_add (AdgContainer *container,
101 AdgEntity *entity);
102 static void _adg_remove (AdgContainer *container,
103 AdgEntity *entity);
104 static void _adg_remove_from_list (gpointer container,
105 GObject *entity);
107 static guint _adg_signals[LAST_SIGNAL] = { 0 };
110 static void
111 adg_container_class_init(AdgContainerClass *klass)
113 GObjectClass *gobject_class;
114 AdgEntityClass *entity_class;
115 GParamSpec *param;
117 gobject_class = (GObjectClass *) klass;
118 entity_class = (AdgEntityClass *) klass;
120 gobject_class->dispose = _adg_dispose;
121 gobject_class->set_property = _adg_set_property;
123 entity_class->destroy = _adg_destroy;
124 entity_class->global_changed = _adg_global_changed;
125 entity_class->local_changed = _adg_local_changed;
126 entity_class->invalidate = _adg_invalidate;
127 entity_class->arrange = _adg_arrange;
128 entity_class->render = _adg_render;
130 klass->children = _adg_children;
131 klass->add = _adg_add;
132 klass->remove = _adg_remove;
134 param = g_param_spec_object("child",
135 P_("Child"),
136 P_("Can be used to add a new child to the container"),
137 ADG_TYPE_ENTITY,
138 G_PARAM_WRITABLE);
139 g_object_class_install_property(gobject_class, PROP_CHILD, param);
142 * AdgContainer::add:
143 * @container: an #AdgContainer
144 * @entity: the #AdgEntity to add
146 * Adds @entity to @container. @entity must not be inside another
147 * container or the operation will fail.
149 * Since: 1.0
151 _adg_signals[ADD] = g_signal_new("add",
152 G_OBJECT_CLASS_TYPE(gobject_class),
153 G_SIGNAL_RUN_FIRST,
154 G_STRUCT_OFFSET(AdgContainerClass, add),
155 NULL, NULL,
156 g_cclosure_marshal_VOID__OBJECT,
157 G_TYPE_NONE, 1, ADG_TYPE_ENTITY);
160 * AdgContainer::remove:
161 * @container: an #AdgContainer
162 * @entity: the #AdgEntity to remove
164 * Removes @entity from @container.
166 * Since: 1.0
168 _adg_signals[REMOVE] = g_signal_new("remove",
169 G_OBJECT_CLASS_TYPE(gobject_class),
170 G_SIGNAL_RUN_FIRST,
171 G_STRUCT_OFFSET(AdgContainerClass, remove),
172 NULL, NULL,
173 g_cclosure_marshal_VOID__OBJECT,
174 G_TYPE_NONE, 1, ADG_TYPE_ENTITY);
177 static void
178 adg_container_init(AdgContainer *container)
180 AdgContainerPrivate *data = adg_container_get_instance_private(container);
181 data->children = NULL;
184 static void
185 _adg_dispose(GObject *object)
187 AdgContainer *container = (AdgContainer *) object;
188 AdgContainerPrivate *data = adg_container_get_instance_private(container);
190 /* Remove all the children from the container: these will emit
191 * a "remove" signal for every child and will drop all the
192 * references from the children to this container (and, obviously,
193 * from the container to the children). */
194 while (data->children != NULL)
195 adg_container_remove(container, (AdgEntity *) data->children->data);
197 if (_ADG_PARENT_OBJECT_CLASS->dispose)
198 _ADG_PARENT_OBJECT_CLASS->dispose(object);
201 static void
202 _adg_set_property(GObject *object,
203 guint prop_id, const GValue *value, GParamSpec *pspec)
205 AdgContainer *container = (AdgContainer *) object;
207 switch (prop_id) {
208 case PROP_CHILD:
209 adg_container_add(container, g_value_get_object(value));
210 break;
211 default:
212 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
218 * adg_container_new:
220 * Creates a new container entity.
222 * Returns: the newly created container entity
224 * Since: 1.0
226 AdgContainer *
227 adg_container_new(void)
229 return g_object_new(ADG_TYPE_CONTAINER, NULL);
234 * adg_container_add:
235 * @container: an #AdgContainer
236 * @entity: an #AdgEntity
238 * Emits a #AdgContainer::add signal on @container passing @entity
239 * as argument. @entity must be added to only one container at a time,
240 * you can't place the same entity inside two different containers.
242 * Once @entity has been added, the floating reference will be removed
243 * and @container will own a reference to @entity. This means the only
244 * proper way to destroy @entity is to call adg_container_remove().
246 * Since: 1.0
248 void
249 adg_container_add(AdgContainer *container, AdgEntity *entity)
251 g_return_if_fail(ADG_IS_CONTAINER(container));
252 g_return_if_fail(ADG_IS_ENTITY(entity));
254 g_signal_emit(container, _adg_signals[ADD], 0, entity);
258 * adg_container_remove:
259 * @container: an #AdgContainer
260 * @entity: an #AdgEntity
262 * Emits a #AdgContainer::remove signal on @container passing
263 * @entity as argument. @entity must be inside @container.
265 * Note that @container will own a reference to @entity and it
266 * may be the last reference held: this means removing an entity
267 * from its container can destroy it.
269 * If you want to use @entity again, you need to add a reference
270 * to it, using g_object_ref(), before removing it from @container.
271 * The following typical example shows you how to properly move
272 * <varname>entity</varname> from <varname>container1</varname>
273 * to <varname>container2</varname>:
275 * <informalexample><programlisting language="C">
276 * g_object_ref(entity);
277 * adg_container_remove(container1, entity);
278 * adg_container_add(container2, entity)
279 * g_object_unref(entity);
280 * </programlisting></informalexample>
282 * Since: 1.0
284 void
285 adg_container_remove(AdgContainer *container, AdgEntity *entity)
287 g_return_if_fail(ADG_IS_CONTAINER(container));
288 g_return_if_fail(ADG_IS_ENTITY(entity));
290 g_signal_emit(container, _adg_signals[REMOVE], 0, entity);
294 * adg_container_children:
295 * @container: an #AdgContainer
297 * Gets the children list of @container. This list must be manually
298 * freed with g_slist_free() when no longer user.
300 * The returned list is ordered from the most recently added child
301 * to the oldest one.
303 * Returns: (element-type AdgEntity) (transfer container): a newly allocated #GSList of #AdgEntity or <constant>NULL</constant> on no children or errors
305 * Since: 1.0
307 GSList *
308 adg_container_children(AdgContainer *container)
310 AdgContainerClass *klass;
312 g_return_val_if_fail(ADG_IS_CONTAINER(container), NULL);
314 klass = ADG_CONTAINER_GET_CLASS(container);
316 if (klass->children == NULL)
317 return NULL;
319 return klass->children(container);
323 * adg_container_foreach:
324 * @container: an #AdgContainer
325 * @callback: (scope call): a callback
326 * @user_data: callback user data
328 * Invokes @callback on each child of @container.
329 * The callback should be declared as:
331 * <informalexample><programlisting language="C">
332 * void callback(AdgEntity *entity, gpointer user_data);
333 * </programlisting></informalexample>
335 * Since: 1.0
337 void
338 adg_container_foreach(AdgContainer *container,
339 GCallback callback, gpointer user_data)
341 GSList *children;
343 g_return_if_fail(ADG_IS_CONTAINER(container));
344 g_return_if_fail(callback != NULL);
346 children = adg_container_children(container);
348 while (children != NULL) {
349 if (children->data != NULL)
350 ((void (*) (gpointer, gpointer)) callback) (children->data, user_data);
352 children = g_slist_delete_link(children, children);
357 * adg_container_propagate:
358 * @container: an #AdgContainer
359 * @signal_id: the signal id
360 * @detail: the detail
361 * @...: parameters to be passed to the signal, followed by a pointer
362 * to the allocated memory where to store the return type: if
363 * the signal is %G_TYPE_NONE (void return type), this trailing
364 * pointer should be omitted
366 * Emits the specified signal to all the children of @container
367 * using g_signal_emit_valist() calls.
369 * Since: 1.0
371 void
372 adg_container_propagate(AdgContainer *container,
373 guint signal_id, GQuark detail, ...)
375 va_list var_args;
377 va_start(var_args, detail);
378 adg_container_propagate_valist(container, signal_id, detail, var_args);
379 va_end(var_args);
383 * adg_container_propagate_by_name:
384 * @container: an #AdgContainer
385 * @detailed_signal: a string of the form "signal-name::detail".
386 * @...: parameters to be passed to the signal, followed by a pointer
387 * to the allocated memory where to store the return type: if
388 * the signal is %G_TYPE_NONE (void return type), this trailing
389 * pointer should be omitted
391 * Emits the specified signal to all the children of @container
392 * using g_signal_emit_valist() calls.
394 * Since: 1.0
396 void
397 adg_container_propagate_by_name(AdgContainer *container,
398 const gchar *detailed_signal, ...)
400 guint signal_id;
401 GQuark detail = 0;
402 va_list var_args;
404 if (!g_signal_parse_name(detailed_signal, G_TYPE_FROM_INSTANCE(container),
405 &signal_id, &detail, FALSE)) {
406 g_warning(_("%s: signal '%s' is invalid for instance %p"),
407 G_STRLOC, detailed_signal, container);
408 return;
411 va_start(var_args, detailed_signal);
412 adg_container_propagate_valist(container, signal_id, detail, var_args);
413 va_end(var_args);
417 * adg_container_propagate_valist:
418 * @container: an #AdgContainer
419 * @signal_id: the signal id
420 * @detail: the detail
421 * @var_args: parameters to be passed to the signal, followed by a
422 * pointer to the allocated memory where to store the
423 * return type: if the signal is %G_TYPE_NONE (void return
424 * type), this trailing pointer should be omitted
426 * Emits the specified signal to all the children of @container
427 * using g_signal_emit_valist() calls.
429 * Since: 1.0
431 void
432 adg_container_propagate_valist(AdgContainer *container,
433 guint signal_id, GQuark detail, va_list var_args)
435 GSList *children;
436 va_list var_copy;
438 g_return_if_fail(ADG_IS_CONTAINER(container));
440 children = adg_container_children(container);
442 while (children != NULL) {
443 if (children->data != NULL) {
444 G_VA_COPY(var_copy, var_args);
445 g_signal_emit_valist(children->data, signal_id, detail, var_copy);
448 children = g_slist_delete_link(children, children);
453 static void
454 _adg_destroy(AdgEntity *entity)
456 adg_container_propagate_by_name((AdgContainer *) entity, "destroy");
458 if (_ADG_PARENT_ENTITY_CLASS->destroy)
459 _ADG_PARENT_ENTITY_CLASS->destroy(entity);
462 static void
463 _adg_global_changed(AdgEntity *entity)
465 if (_ADG_PARENT_ENTITY_CLASS->global_changed)
466 _ADG_PARENT_ENTITY_CLASS->global_changed(entity);
468 adg_container_propagate_by_name((AdgContainer *) entity, "global-changed");
471 static void
472 _adg_local_changed(AdgEntity *entity)
474 if (_ADG_PARENT_ENTITY_CLASS->local_changed)
475 _ADG_PARENT_ENTITY_CLASS->local_changed(entity);
477 adg_container_propagate_by_name((AdgContainer *) entity, "local-changed");
480 static void
481 _adg_invalidate(AdgEntity *entity)
483 adg_container_propagate_by_name((AdgContainer *) entity, "invalidate");
486 static void
487 _adg_arrange(AdgEntity *entity)
489 AdgContainer *container = (AdgContainer *) entity;
490 CpmlExtents extents = { 0 };
492 adg_container_propagate_by_name(container, "arrange", NULL);
493 adg_container_foreach(container, G_CALLBACK(_adg_add_extents), &extents);
494 adg_entity_set_extents(entity, &extents);
497 static void
498 _adg_add_extents(AdgEntity *entity, CpmlExtents *extents)
500 if (! adg_entity_has_floating(entity)) {
501 cpml_extents_add(extents, adg_entity_get_extents(entity));
505 static void
506 _adg_render(AdgEntity *entity, cairo_t *cr)
508 adg_container_propagate_by_name((AdgContainer *) entity, "render", cr);
512 static GSList *
513 _adg_children(AdgContainer *container)
515 AdgContainerPrivate *data = adg_container_get_instance_private(container);
517 /* The NULL case is already managed by GLib */
518 return g_slist_copy(data->children);
521 static void
522 _adg_add(AdgContainer *container, AdgEntity *entity)
524 const AdgEntity *old_parent;
525 AdgContainerPrivate *data;
527 old_parent = adg_entity_get_parent(entity);
528 if (old_parent != NULL) {
529 g_warning(_("Attempting to add an entity with type %s to a container "
530 "of type %s, but the entity is already inside a container "
531 "of type %s"),
532 g_type_name(G_OBJECT_TYPE(entity)),
533 g_type_name(G_OBJECT_TYPE(container)),
534 g_type_name(G_OBJECT_TYPE(old_parent)));
535 return;
538 data = adg_container_get_instance_private(container);
539 data->children = g_slist_prepend(data->children, entity);
541 g_object_ref_sink(entity);
542 adg_entity_set_parent(entity, (AdgEntity *) container);
543 g_object_weak_ref((GObject *) entity, _adg_remove_from_list, container);
546 static void
547 _adg_remove_from_list(gpointer container, GObject *entity)
549 AdgContainerPrivate *data = adg_container_get_instance_private((AdgContainer *) container);
550 data->children = g_slist_remove(data->children, entity);
553 static void
554 _adg_remove(AdgContainer *container, AdgEntity *entity)
556 AdgContainerPrivate *data = adg_container_get_instance_private(container);
557 GSList *node = g_slist_find(data->children, entity);
559 if (node == NULL) {
560 g_warning(_("Attempting to remove an entity with type %s from a "
561 "container of type %s, but the entity is not present"),
562 g_type_name(G_OBJECT_TYPE(entity)),
563 g_type_name(G_OBJECT_TYPE(container)));
564 return;
567 g_object_weak_unref((GObject *) entity, _adg_remove_from_list, container);
568 data->children = g_slist_delete_link(data->children, node);
569 adg_entity_set_parent(entity, NULL);
570 g_object_unref(entity);