[docs] Improved the ADG docs
[adg.git] / adg / adg-container.c
blobeb328e77841a21efa1b66ba41f04d722fe19a4d4
1 /* ADG - Automatic Drawing Generation
2 * Copyright (C) 2007,2008,2009 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 * Each AdgContainer has its paper matrix (#AdgContainer:paper_matrix) to be
27 * used on paper-dependent references (such as font and arrow sizes, line
28 * thickness etc...) and a model matrix usually applied to the model view. See
29 * http://adg.entidi.com/tutorial/view/3 for details on this topic.
31 * This means an AdgContainer can be thought as a group of entities with the
32 * same geometrical identity (same scale, reference point ecc...).
35 /**
36 * AdgContainer:
38 * All fields are private and should not be used directly.
39 * Use its public methods instead.
40 **/
42 #include "adg-container.h"
43 #include "adg-container-private.h"
44 #include "adg-intl.h"
47 enum {
48 PROP_0,
49 PROP_CHILD,
50 PROP_MODEL_TRANSFORMATION,
51 PROP_PAPER_TRANSFORMATION
54 enum {
55 ADD,
56 REMOVE,
57 LAST_SIGNAL
61 static void get_property (GObject *object,
62 guint prop_id,
63 GValue *value,
64 GParamSpec *pspec);
65 static void set_property (GObject *object,
66 guint prop_id,
67 const GValue *value,
68 GParamSpec *pspec);
69 static void dispose (GObject *object);
70 static const AdgMatrix *get_model_matrix (AdgEntity *entity);
71 static const AdgMatrix *get_paper_matrix (AdgEntity *entity);
72 static void model_matrix_changed (AdgEntity *entity,
73 AdgMatrix *parent_matrix);
74 static void paper_matrix_changed (AdgEntity *entity,
75 AdgMatrix *parent_matrix);
76 static GSList * get_children (AdgContainer *container);
77 static gboolean add (AdgContainer *container,
78 AdgEntity *entity);
79 static void real_add (AdgContainer *container,
80 AdgEntity *entity,
81 gpointer user_data);
82 static gboolean remove (AdgContainer *container,
83 AdgEntity *entity);
84 static void real_remove (AdgContainer *container,
85 AdgEntity *entity,
86 gpointer user_data);
87 static void invalidate (AdgEntity *entity);
88 static void render (AdgEntity *entity,
89 cairo_t *cr);
91 static guint signals[LAST_SIGNAL] = { 0 };
94 G_DEFINE_TYPE(AdgContainer, adg_container, ADG_TYPE_ENTITY)
97 static void
98 adg_container_class_init(AdgContainerClass *klass)
100 GObjectClass *gobject_class;
101 AdgEntityClass *entity_class;
102 GParamSpec *param;
103 GClosure *closure;
104 GType param_types[1];
106 gobject_class = (GObjectClass *) klass;
107 entity_class = (AdgEntityClass *) klass;
109 g_type_class_add_private(klass, sizeof(AdgContainerPrivate));
111 gobject_class->get_property = get_property;
112 gobject_class->set_property = set_property;
113 gobject_class->dispose = dispose;
115 entity_class->model_matrix_changed = model_matrix_changed;
116 entity_class->paper_matrix_changed = paper_matrix_changed;
117 entity_class->get_model_matrix = get_model_matrix;
118 entity_class->get_paper_matrix = get_paper_matrix;
119 entity_class->invalidate = invalidate;
120 entity_class->render = render;
122 klass->get_children = get_children;
123 klass->add = add;
124 klass->remove = remove;
126 param = g_param_spec_boxed("child",
127 P_("Child"),
128 P_("Can be used to add a new child to the container"),
129 ADG_TYPE_ENTITY, G_PARAM_WRITABLE);
130 g_object_class_install_property(gobject_class, PROP_CHILD, param);
132 param = g_param_spec_boxed("model-transformation",
133 P_("The model transformation"),
134 P_("The model transformation to be applied to this container and its children entities"),
135 ADG_TYPE_MATRIX, G_PARAM_READWRITE);
136 g_object_class_install_property(gobject_class,
137 PROP_MODEL_TRANSFORMATION, param);
139 param = g_param_spec_boxed("paper-transformation",
140 P_("The paper transformation"),
141 P_("The paper transformation to be applied to this container and its children entities"),
142 ADG_TYPE_MATRIX, G_PARAM_READWRITE);
143 g_object_class_install_property(gobject_class,
144 PROP_PAPER_TRANSFORMATION, param);
147 * AdgContainer::add:
148 * @container: an #AdgContainer
149 * @entity: the #AdgEntity to add
151 * Adds @entity to @container.
153 closure = g_cclosure_new(G_CALLBACK(real_add), (gpointer)0xdeadbeaf, NULL);
154 param_types[0] = G_TYPE_OBJECT;
155 signals[ADD] = g_signal_newv("add", ADG_TYPE_CONTAINER,
156 G_SIGNAL_RUN_FIRST, closure, NULL, NULL,
157 g_cclosure_marshal_VOID__OBJECT,
158 G_TYPE_NONE, 1, param_types);
161 * AdgContainer::remove:
162 * @container: an #AdgContainer
163 * @entity: the #AdgEntity to remove
165 * Removes @entity from @container.
167 closure = g_cclosure_new(G_CALLBACK(real_remove), (gpointer)0xdeadbeaf, NULL);
168 param_types[0] = G_TYPE_OBJECT;
169 signals[REMOVE] = g_signal_newv("remove", ADG_TYPE_CONTAINER,
170 G_SIGNAL_RUN_FIRST, closure, NULL, NULL,
171 g_cclosure_marshal_VOID__OBJECT,
172 G_TYPE_NONE, 1, param_types);
175 static void
176 adg_container_init(AdgContainer *container)
178 AdgContainerPrivate *data = G_TYPE_INSTANCE_GET_PRIVATE(container,
179 ADG_TYPE_CONTAINER,
180 AdgContainerPrivate);
182 data->children = NULL;
183 cairo_matrix_init_identity(&data->model_transformation);
184 cairo_matrix_init_identity(&data->paper_transformation);
185 cairo_matrix_init_identity(&data->model_matrix);
186 cairo_matrix_init_identity(&data->paper_matrix);
188 container->data = data;
191 static void
192 get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
194 AdgContainerPrivate *data = ((AdgContainer *) object)->data;
196 switch (prop_id) {
197 case PROP_MODEL_TRANSFORMATION:
198 g_value_set_boxed(value, &data->model_transformation);
199 break;
200 case PROP_PAPER_TRANSFORMATION:
201 g_value_set_boxed(value, &data->paper_transformation);
202 break;
203 default:
204 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
205 break;
209 static void
210 set_property(GObject *object,
211 guint prop_id, const GValue *value, GParamSpec *pspec)
213 AdgContainer *container;
214 AdgContainerPrivate *data;
216 container = (AdgContainer *) object;
217 data = container->data;
219 switch (prop_id) {
220 case PROP_CHILD:
221 adg_container_add(container, g_value_get_object(value));
222 break;
223 case PROP_MODEL_TRANSFORMATION:
224 adg_matrix_copy(&data->model_transformation, g_value_get_boxed(value));
225 break;
226 case PROP_PAPER_TRANSFORMATION:
227 adg_matrix_copy(&data->paper_transformation, g_value_get_boxed(value));
228 break;
229 default:
230 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
234 static void
235 dispose(GObject *object)
237 GObjectClass *object_class = (GObjectClass *) adg_container_parent_class;
239 adg_container_foreach((AdgContainer *) object,
240 G_CALLBACK(adg_entity_unparent), NULL);
242 if (object_class->dispose != NULL)
243 object_class->dispose(object);
247 static GSList *
248 get_children(AdgContainer *container)
250 AdgContainerPrivate *data = container->data;
252 return g_slist_copy(data->children);
255 static gboolean
256 add(AdgContainer *container, AdgEntity *entity)
258 AdgContainerPrivate *data = container->data;
260 data->children = g_slist_append(data->children, entity);
262 return TRUE;
265 static void
266 real_add(AdgContainer *container, AdgEntity *entity, gpointer user_data)
268 AdgContainer *old_parent;
270 g_assert(user_data == (gpointer) 0xdeadbeaf);
272 old_parent = adg_entity_get_parent(entity);
274 if (old_parent != NULL) {
275 g_warning("Attempting to add an object with type %s to a container "
276 "of type %s, but the object is already inside a container "
277 "of type %s.",
278 g_type_name(G_OBJECT_TYPE(entity)),
279 g_type_name(G_OBJECT_TYPE(container)),
280 g_type_name(G_OBJECT_TYPE(old_parent)));
281 return;
284 if (ADG_CONTAINER_GET_CLASS(container)->add(container, entity))
285 adg_entity_set_parent(entity, container);
286 else
287 g_signal_stop_emission(container, signals[ADD], 0);
290 static gboolean
291 remove(AdgContainer *container, AdgEntity *entity)
293 AdgContainerPrivate *data;
294 GSList *node;
296 data = container->data;
297 node = g_slist_find(data->children, entity);
299 if (!node)
300 return FALSE;
302 data->children = g_slist_delete_link(data->children, node);
304 return TRUE;
307 static void
308 real_remove(AdgContainer *container, AdgEntity *entity, gpointer user_data)
310 g_assert(user_data == (gpointer) 0xdeadbeaf);
312 if (ADG_CONTAINER_GET_CLASS(container)->remove(container, entity))
313 adg_entity_unparent(entity);
314 else
315 g_signal_stop_emission(container, signals[REMOVE], 0);
319 static void
320 model_matrix_changed(AdgEntity *entity, AdgMatrix *parent_matrix)
322 AdgContainer *container;
323 AdgContainerPrivate *data;
324 AdgEntityClass *entity_class;
326 container = (AdgContainer *) entity;
327 data = container->data;
328 entity_class = (AdgEntityClass *) adg_container_parent_class;
330 if (entity_class->model_matrix_changed != NULL)
331 entity_class->model_matrix_changed(entity, parent_matrix);
333 if (parent_matrix)
334 cairo_matrix_multiply(&data->model_matrix,
335 &data->model_transformation,
336 parent_matrix);
337 else
338 adg_matrix_copy(&data->model_matrix, &data->model_transformation);
340 adg_container_propagate_by_name(container, "model-matrix-changed",
341 &data->model_matrix);
344 static void
345 paper_matrix_changed(AdgEntity *entity, AdgMatrix *parent_matrix)
347 AdgContainer *container;
348 AdgContainerPrivate *data;
349 AdgEntityClass *entity_class;
351 container = (AdgContainer *) entity;
352 data = container->data;
353 entity_class = (AdgEntityClass *) adg_container_parent_class;
355 if (entity_class->paper_matrix_changed != NULL)
356 entity_class->paper_matrix_changed(entity, parent_matrix);
358 if (parent_matrix)
359 cairo_matrix_multiply(&data->paper_matrix,
360 &data->paper_transformation,
361 parent_matrix);
362 else
363 adg_matrix_copy(&data->paper_matrix, &data->paper_transformation);
365 adg_container_propagate_by_name(container, "paper-matrix-changed",
366 &data->paper_matrix);
369 static const AdgMatrix *
370 get_model_matrix(AdgEntity *entity)
372 AdgContainerPrivate *data = ((AdgContainer *) entity)->data;
374 return &data->model_matrix;
377 static const AdgMatrix *
378 get_paper_matrix(AdgEntity *entity)
380 AdgContainerPrivate *data = ((AdgContainer *) entity)->data;
382 return &data->paper_matrix;
386 static void
387 invalidate(AdgEntity *entity)
389 AdgEntityClass *entity_class = (AdgEntityClass *) adg_container_parent_class;
391 adg_container_propagate_by_name((AdgContainer *) entity, "invalidate");
393 if (entity_class->invalidate)
394 entity_class->invalidate(entity);
397 static void
398 render(AdgEntity *entity, cairo_t *cr)
400 AdgEntityClass *entity_class = (AdgEntityClass *) adg_container_parent_class;
402 cairo_set_matrix(cr, adg_entity_get_model_matrix(entity));
403 adg_container_propagate_by_name((AdgContainer *) entity, "render", cr);
405 if (entity_class->render)
406 entity_class->render(entity, cr);
411 * adg_container_new:
413 * Creates a new container entity.
415 * Return value: the newly created entity
417 AdgEntity *
418 adg_container_new(void)
420 return (AdgEntity *) g_object_new(ADG_TYPE_CONTAINER, NULL);
425 * adg_container_add:
426 * @container: an #AdgContainer
427 * @entity: an #AdgEntity
429 * Emits a #AdgContainer::add signal on @container passing
430 * @entity as argument.
432 * @entity may be added to only one container at a time; you can't
433 * place the same entity inside two different containers.
435 void
436 adg_container_add(AdgContainer *container, AdgEntity *entity)
438 g_return_if_fail(ADG_IS_CONTAINER(container));
439 g_return_if_fail(ADG_IS_ENTITY(entity));
441 g_signal_emit(container, signals[ADD], 0, entity);
445 * adg_container_remove:
446 * @container: an #AdgContainer
447 * @entity: an #AdgEntity
449 * Emits a #AdgContainer::remove signal on @container passing
450 * @entity as argument. @entity must be inside @container.
452 * Note that @container will own a reference to @entity
453 * and that this may be the last reference held; so removing an
454 * entity from its container can destroy it.
456 * If you want to use @entity again, you need to add a reference
457 * to it, using g_object_ref(), before removing it from @container.
459 * If you don't want to use @entity again, it's usually more
460 * efficient to simply destroy it directly using g_object_unref()
461 * since this will remove it from the container.
463 void
464 adg_container_remove(AdgContainer *container, AdgEntity *entity)
466 g_return_if_fail(ADG_IS_CONTAINER(container));
467 g_return_if_fail(ADG_IS_ENTITY(entity));
469 g_signal_emit(container, signals[REMOVE], 0, entity);
473 * adg_container_get_children:
474 * @container: an #AdgContainer
476 * Gets the children list of @container.
477 * This list must be manually freed when no longer user.
479 * Returns: a newly allocated #GSList or %NULL on error
481 GSList *
482 adg_container_get_children(AdgContainer *container)
484 g_return_val_if_fail(ADG_IS_CONTAINER(container), NULL);
486 return ADG_CONTAINER_GET_CLASS(container)->get_children(container);
490 * adg_container_foreach:
491 * @container: an #AdgContainer
492 * @callback: a callback
493 * @user_data: callback user data
495 * Invokes @callback on each child of @container.
496 * The callback should be declared as:
498 * <code>
499 * void callback(AdgEntity *entity, gpointer user_data);
500 * </code>
502 void
503 adg_container_foreach(AdgContainer *container,
504 GCallback callback, gpointer user_data)
506 GSList *children;
508 g_return_if_fail(ADG_IS_CONTAINER(container));
509 g_return_if_fail(callback != NULL);
511 children = adg_container_get_children (container);
513 while (children) {
514 if (children->data)
515 ((void (*) (gpointer, gpointer)) callback) (children->data, user_data);
517 children = g_slist_delete_link(children, children);
522 * adg_container_propagate:
523 * @container: an #AdgContainer
524 * @signal_id: the signal id
525 * @detail: the detail
526 * @...: parameters to be passed to the signal, followed by a location for
527 * the return value. If the return type of the signal is G_TYPE_NONE,
528 * the return value location can be omitted.
530 * Emits the specified signal to all the children of @container
531 * using g_signal_emit_valist() calls.
533 void
534 adg_container_propagate(AdgContainer *container,
535 guint signal_id, GQuark detail, ...)
537 va_list var_args;
539 va_start(var_args, detail);
540 adg_container_propagate_valist(container, signal_id, detail, var_args);
541 va_end(var_args);
545 * adg_container_propagate_by_name:
546 * @container: an #AdgContainer
547 * @detailed_signal: a string of the form "signal-name::detail".
548 * @...: a list of parameters to be passed to the signal, followed by
549 * a location for the return value. If the return type of the signal
550 * is G_TYPE_NONE, the return value location can be omitted.
552 * Emits the specified signal to all the children of @container
553 * using g_signal_emit_valist() calls.
555 void
556 adg_container_propagate_by_name(AdgContainer *container,
557 const gchar *detailed_signal, ...)
559 guint signal_id;
560 GQuark detail = 0;
561 va_list var_args;
563 if (!g_signal_parse_name(detailed_signal, G_TYPE_FROM_INSTANCE(container),
564 &signal_id, &detail, FALSE)) {
565 g_warning("%s: signal `%s' is invalid for instance `%p'",
566 G_STRLOC, detailed_signal, container);
567 return;
570 va_start(var_args, detailed_signal);
571 adg_container_propagate_valist(container, signal_id, detail, var_args);
572 va_end(var_args);
576 * adg_container_propagate_valist:
577 * @container: an #AdgContainer
578 * @signal_id: the signal id
579 * @detail: the detail
580 * @var_args: a list of parameters to be passed to the signal, followed by a
581 * location for the return value. If the return type of the signal
582 * is G_TYPE_NONE, the return value location can be omitted.
584 * Emits the specified signal to all the children of @container
585 * using g_signal_emit_valist() calls.
587 void
588 adg_container_propagate_valist(AdgContainer *container,
589 guint signal_id, GQuark detail, va_list var_args)
591 GSList *children;
592 va_list var_copy;
594 g_return_if_fail(ADG_IS_CONTAINER(container));
596 children = adg_container_get_children(container);
598 while (children) {
599 if (children->data) {
600 G_VA_COPY(var_copy, var_args);
601 g_signal_emit_valist(children->data, signal_id, detail, var_copy);
604 children = g_slist_delete_link(children, children);
609 * adg_container_get_model_transformation:
610 * @container: an #AdgContainer
612 * Returns the transformation to be combined with the transformations of the
613 * parent hierarchy to get the final matrix to be applied in the model space.
615 * Return value: the model transformation
617 const AdgMatrix *
618 adg_container_get_model_transformation(AdgContainer *container)
620 AdgContainerPrivate *data;
622 g_return_val_if_fail(ADG_IS_CONTAINER(container), NULL);
624 data = container->data;
626 return &data->model_transformation;
630 * adg_container_set_model_transformation:
631 * @container: an #AdgContainer
632 * @transformation: the new model transformation
634 * Sets the transformation to be applied in model space.
636 void
637 adg_container_set_model_transformation(AdgContainer *container,
638 const AdgMatrix *transformation)
640 AdgEntity *entity;
641 AdgContainerPrivate *data;
642 AdgEntity *parent;
643 const AdgMatrix *parent_matrix;
645 g_return_if_fail(ADG_IS_CONTAINER(container));
646 g_return_if_fail(transformation != NULL);
648 entity = (AdgEntity *) container;
649 data = container->data;
650 parent = (AdgEntity *) adg_entity_get_parent(entity);
651 parent_matrix = parent ? adg_entity_get_model_matrix(parent) : NULL;
653 adg_matrix_copy(&data->model_transformation, transformation);
654 adg_entity_model_matrix_changed(entity, parent_matrix);
656 /* Temporary workaround: this function will be removed soon */
657 adg_entity_set_local_map(entity, transformation);
661 * adg_container_get_paper_transformation:
662 * @container: an #AdgContainer
664 * Returns the transformation to be combined with the transformations of the
665 * parent hierarchy to get the final matrix to be applied in the paper space.
667 * Return value: the paper transformation
669 const AdgMatrix *
670 adg_container_get_paper_transformation(AdgContainer *container)
672 AdgContainerPrivate *data;
674 g_return_val_if_fail(ADG_IS_CONTAINER(container), NULL);
676 data = container->data;
678 return &data->paper_transformation;
682 * adg_container_set_paper_transformation:
683 * @container: an #AdgContainer
684 * @transformation: the new paper transformation
686 * Sets the transformation to be applied in paper space.
688 void
689 adg_container_set_paper_transformation(AdgContainer *container,
690 const AdgMatrix *transformation)
692 AdgEntity *entity;
693 AdgContainerPrivate *data;
694 AdgEntity *parent;
695 const AdgMatrix *parent_matrix;
697 g_return_if_fail(ADG_IS_CONTAINER(container));
698 g_return_if_fail(transformation != NULL);
700 entity = (AdgEntity *) container;
701 data = container->data;
702 parent = (AdgEntity *) adg_entity_get_parent(entity);
703 parent_matrix = parent ? adg_entity_get_paper_matrix(parent) : NULL;
705 adg_matrix_copy(&data->paper_transformation, transformation);
706 adg_entity_paper_matrix_changed(entity, parent_matrix);
708 /* Temporary workaround: this function will be removed soon */
709 adg_entity_set_global_map(entity, transformation);