[adg-container.* adg-entity.*] Merged gcontainer in AdgContainer & AdgEntity
[adg.git] / adg / adg-container.c
blob718ef5d8e9754fd4609577c501660cd36cc77191
1 /* ADG - Automatic Drawing Generation
2 * Copyright (C) 2007-2008, 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:container
23 * @title: AdgContainer
24 * @short_description: Base class for entity that can contain other entities
26 * The #AdgContainer is an entity that can contains more sub-entities.
27 * Each AdgContainer has its paper matrix (#AdgContainer:paper_matrix) to be
28 * used on paper-dependent references (such as font and arrow sizes, line
29 * thickness etc...) and a model matrix usually applied to the model view. See
30 * http://www.entidi.it/adg/tutorial/view/3 for details on this topic.
32 * This means an AdgContainer can be thought as a group of entities with the
33 * same geometrical identity (same scale, reference point ecc...).
36 #include "adg-container.h"
37 #include "adg-container-private.h"
38 #include "adg-intl.h"
40 #include <gcontainer/gcontainer.h>
42 #define PARENT_CLASS ((AdgEntityClass *) adg_container_parent_class)
45 enum {
46 PROP_0,
47 PROP_CHILD,
48 PROP_MODEL_TRANSFORMATION,
49 PROP_PAPER_TRANSFORMATION
52 enum {
53 ADD,
54 REMOVE,
55 LAST_SIGNAL
59 static void get_property (GObject *object,
60 guint prop_id,
61 GValue *value,
62 GParamSpec *pspec);
63 static void set_property (GObject *object,
64 guint prop_id,
65 const GValue *value,
66 GParamSpec *pspec);
67 static void dispose (GObject *object);
68 static const AdgMatrix *get_model_matrix (AdgEntity *entity);
69 static const AdgMatrix *get_paper_matrix (AdgEntity *entity);
70 static void model_matrix_changed (AdgEntity *entity,
71 AdgMatrix *parent_matrix);
72 static void paper_matrix_changed (AdgEntity *entity,
73 AdgMatrix *parent_matrix);
74 static GSList * get_children (AdgContainer *container);
75 static gboolean add (AdgContainer *container,
76 AdgEntity *entity);
77 static void real_add (AdgContainer *container,
78 AdgEntity *entity,
79 gpointer user_data);
80 static gboolean remove (AdgContainer *container,
81 AdgEntity *entity);
82 static void real_remove (AdgContainer *container,
83 AdgEntity *entity,
84 gpointer user_data);
85 static void invalidate (AdgEntity *entity);
86 static void render (AdgEntity *entity,
87 cairo_t *cr);
89 static guint signals[LAST_SIGNAL] = { 0 };
92 G_DEFINE_TYPE(AdgContainer, adg_container, ADG_TYPE_ENTITY)
95 static void
96 adg_container_class_init(AdgContainerClass *klass)
98 GObjectClass *gobject_class;
99 AdgEntityClass *entity_class;
100 GParamSpec *param;
101 GClosure *closure;
102 GType param_types[1];
104 gobject_class = (GObjectClass *) klass;
105 entity_class = (AdgEntityClass *) klass;
107 g_type_class_add_private(klass, sizeof(AdgContainerPrivate));
109 gobject_class->get_property = get_property;
110 gobject_class->set_property = set_property;
111 gobject_class->dispose = dispose;
113 entity_class->model_matrix_changed = model_matrix_changed;
114 entity_class->paper_matrix_changed = paper_matrix_changed;
115 entity_class->get_model_matrix = get_model_matrix;
116 entity_class->get_paper_matrix = get_paper_matrix;
117 entity_class->invalidate = invalidate;
118 entity_class->render = render;
120 klass->get_children = get_children;
121 klass->add = add;
122 klass->remove = remove;
124 param = g_param_spec_boxed("child",
125 P_("Child"),
126 P_("Can be used to add a new child to the container"),
127 ADG_TYPE_ENTITY, G_PARAM_WRITABLE);
128 g_object_class_install_property(gobject_class, PROP_CHILD, param);
130 param = g_param_spec_boxed("model-transformation",
131 P_("The model transformation"),
132 P_("The model transformation to be applied to this container and its children entities"),
133 ADG_TYPE_MATRIX, G_PARAM_READWRITE);
134 g_object_class_install_property(gobject_class,
135 PROP_MODEL_TRANSFORMATION, param);
137 param = g_param_spec_boxed("paper-transformation",
138 P_("The paper transformation"),
139 P_("The paper transformation to be applied to this container and its children entities"),
140 ADG_TYPE_MATRIX, G_PARAM_READWRITE);
141 g_object_class_install_property(gobject_class,
142 PROP_PAPER_TRANSFORMATION, param);
145 * AdgContainer::add:
146 * @container: an #AdgContainer
147 * @entity: the #AdgEntity to add
149 * Adds @entity to @container.
151 closure = g_cclosure_new(G_CALLBACK(real_add), (gpointer)0xdeadbeaf, NULL);
152 param_types[0] = G_TYPE_OBJECT;
153 signals[ADD] = g_signal_newv("add", ADG_TYPE_CONTAINER,
154 G_SIGNAL_RUN_FIRST, closure, NULL, NULL,
155 g_cclosure_marshal_VOID__OBJECT,
156 G_TYPE_NONE, 1, param_types);
159 * AdgContainer::remove:
160 * @container: an #AdgContainer
161 * @entity: the #AdgEntity to remove
163 * Removes @entity from @container.
165 closure = g_cclosure_new(G_CALLBACK(real_remove), (gpointer)0xdeadbeaf, NULL);
166 param_types[0] = G_TYPE_OBJECT;
167 signals[REMOVE] = g_signal_newv("remove", ADG_TYPE_CONTAINER,
168 G_SIGNAL_RUN_FIRST, closure, NULL, NULL,
169 g_cclosure_marshal_VOID__OBJECT,
170 G_TYPE_NONE, 1, param_types);
173 static void
174 adg_container_init(AdgContainer *container)
176 AdgContainerPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE(container,
177 ADG_TYPE_CONTAINER,
178 AdgContainerPrivate);
180 priv->children = NULL;
181 cairo_matrix_init_identity(&priv->model_transformation);
182 cairo_matrix_init_identity(&priv->paper_transformation);
183 cairo_matrix_init_identity(&priv->model_matrix);
184 cairo_matrix_init_identity(&priv->paper_matrix);
186 container->priv = priv;
189 static void
190 get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
192 AdgContainer *container = (AdgContainer *) object;
194 switch (prop_id) {
195 case PROP_MODEL_TRANSFORMATION:
196 g_value_set_boxed(value, &container->priv->model_transformation);
197 break;
198 case PROP_PAPER_TRANSFORMATION:
199 g_value_set_boxed(value, &container->priv->paper_transformation);
200 break;
201 default:
202 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
203 break;
207 static void
208 set_property(GObject *object,
209 guint prop_id, const GValue *value, GParamSpec *pspec)
211 AdgContainer *container = (AdgContainer *) object;
213 switch (prop_id) {
214 case PROP_CHILD:
215 adg_container_add(container, g_value_get_object(value));
216 break;
217 case PROP_MODEL_TRANSFORMATION:
218 adg_matrix_set(&container->priv->model_transformation,
219 g_value_get_boxed(value));
220 break;
221 case PROP_PAPER_TRANSFORMATION:
222 adg_matrix_set(&container->priv->paper_transformation,
223 g_value_get_boxed(value));
224 break;
225 default:
226 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
230 static void
231 dispose(GObject *object)
233 AdgContainer *container = (AdgContainer *) object;
235 adg_container_foreach(container, G_CALLBACK(adg_entity_unparent), NULL);
237 ((GObjectClass *) PARENT_CLASS)->dispose(object);
241 static GSList *
242 get_children(AdgContainer *container)
244 return g_slist_copy(container->priv->children);
247 static gboolean
248 add(AdgContainer *container, AdgEntity *entity)
250 container->priv->children = g_slist_append(container->priv->children,
251 entity);
252 return TRUE;
255 static void
256 real_add(AdgContainer *container, AdgEntity *entity, gpointer user_data)
258 AdgContainer *old_parent;
260 g_assert(user_data == (gpointer) 0xdeadbeaf);
262 old_parent = adg_entity_get_parent(entity);
264 if (old_parent != NULL) {
265 g_warning("Attempting to add an object with type %s to a container "
266 "of type %s, but the object is already inside a container "
267 "of type %s.",
268 g_type_name(G_OBJECT_TYPE(entity)),
269 g_type_name(G_OBJECT_TYPE(container)),
270 g_type_name(G_OBJECT_TYPE(old_parent)));
271 return;
274 if (ADG_CONTAINER_GET_CLASS(container)->add(container, entity))
275 adg_entity_set_parent(entity, container);
276 else
277 g_signal_stop_emission(container, signals[ADD], 0);
280 static gboolean
281 remove(AdgContainer *container, AdgEntity *entity)
283 GSList *node = g_slist_find(container->priv->children, entity);
285 if (!node)
286 return FALSE;
288 container->priv->children = g_slist_delete_link(container->priv->children,
289 node);
290 return TRUE;
293 static void
294 real_remove(AdgContainer *container, AdgEntity *entity, gpointer user_data)
296 g_assert(user_data == (gpointer) 0xdeadbeaf);
298 if (ADG_CONTAINER_GET_CLASS(container)->remove(container, entity))
299 adg_entity_unparent(entity);
300 else
301 g_signal_stop_emission(container, signals[REMOVE], 0);
305 static void
306 model_matrix_changed(AdgEntity *entity, AdgMatrix *parent_matrix)
308 AdgContainer *container = (AdgContainer *) entity;
310 PARENT_CLASS->model_matrix_changed(entity, parent_matrix);
312 if (parent_matrix)
313 cairo_matrix_multiply(&container->priv->model_matrix,
314 parent_matrix,
315 &container->priv->model_transformation);
316 else
317 adg_matrix_set(&container->priv->model_matrix,
318 &container->priv->model_transformation);
320 adg_container_propagate_by_name(container, "model-matrix-changed",
321 &container->priv->model_matrix);
324 static void
325 paper_matrix_changed(AdgEntity *entity, AdgMatrix *parent_matrix)
327 AdgContainer *container = (AdgContainer *) entity;
329 PARENT_CLASS->paper_matrix_changed(entity, parent_matrix);
331 if (parent_matrix)
332 cairo_matrix_multiply(&container->priv->paper_matrix,
333 parent_matrix,
334 &container->priv->paper_transformation);
335 else
336 adg_matrix_set(&container->priv->paper_matrix,
337 &container->priv->paper_transformation);
339 adg_container_propagate_by_name(container, "paper-matrix-changed",
340 &container->priv->paper_matrix);
343 static const AdgMatrix *
344 get_model_matrix(AdgEntity *entity)
346 AdgContainer *container = (AdgContainer *) entity;
348 return &container->priv->model_matrix;
351 static const AdgMatrix *
352 get_paper_matrix(AdgEntity *entity)
354 AdgContainer *container = (AdgContainer *) entity;
356 return &container->priv->paper_matrix;
360 static void
361 invalidate(AdgEntity *entity)
363 adg_container_propagate_by_name((AdgContainer *) entity, "invalidate");
364 PARENT_CLASS->invalidate(entity);
367 static void
368 render(AdgEntity *entity, cairo_t *cr)
370 cairo_set_matrix(cr, adg_entity_get_model_matrix(entity));
371 adg_container_propagate_by_name((AdgContainer *) entity, "render", cr);
372 PARENT_CLASS->render(entity, cr);
377 * adg_container_add:
378 * @container: an #AdgContainer
379 * @entity: an #AdgEntity
381 * Emits a #AdgContainer::add signal on @container passing
382 * @entity as argument.
384 * @entity may be added to only one container at a time; you can't
385 * place the same entity inside two different containers.
387 void
388 adg_container_add(AdgContainer *container, AdgEntity *entity)
390 g_return_if_fail(ADG_IS_CONTAINER(container));
391 g_return_if_fail(ADG_IS_ENTITY(entity));
393 g_signal_emit(container, signals[ADD], 0, entity);
397 * adg_container_remove:
398 * @container: an #AdgContainer
399 * @entity: an #AdgEntity
401 * Emits a #AdgContainer::remove signal on @container passing
402 * @entity as argument. @entity must be inside @container.
404 * Note that @container will own a reference to @entity
405 * and that this may be the last reference held; so removing an
406 * entity from its container can destroy it.
408 * If you want to use @entity again, you need to add a reference
409 * to it, using g_object_ref(), before removing it from @container.
411 * If you don't want to use @entity again, it's usually more
412 * efficient to simply destroy it directly using g_object_unref()
413 * since this will remove it from the container.
415 void
416 adg_container_remove(AdgContainer *container, AdgEntity *entity)
418 g_return_if_fail(ADG_IS_CONTAINER(container));
419 g_return_if_fail(ADG_IS_ENTITY(entity));
421 g_signal_emit(container, signals[REMOVE], 0, entity);
425 * adg_container_get_children:
426 * @container: an #AdgContainer
428 * Gets the children list of @container.
429 * This list must be manually freed when no longer user.
431 * Returns: a newly allocated #GSList or %NULL on error
433 GSList *
434 adg_container_get_children(AdgContainer *container)
436 g_return_val_if_fail(ADG_IS_CONTAINER(container), NULL);
438 return ADG_CONTAINER_GET_CLASS(container)->get_children(container);
442 * adg_container_foreach:
443 * @container: an #AdgContainer
444 * @callback: a callback
445 * @user_data: callback user data
447 * Invokes @callback on each child of @container.
448 * The callback should be declared as:
450 * <code>
451 * void callback(AdgEntity *entity, gpointer user_data);
452 * </code>
454 void
455 adg_container_foreach(AdgContainer *container,
456 GCallback callback, gpointer user_data)
458 GSList *children;
460 g_return_if_fail(ADG_IS_CONTAINER(container));
461 g_return_if_fail(callback != NULL);
463 children = adg_container_get_children (container);
465 while (children) {
466 if (children->data)
467 ((void (*) (gpointer, gpointer)) callback) (children->data, user_data);
469 children = g_slist_delete_link(children, children);
474 * adg_container_propagate:
475 * @container: an #AdgContainer
476 * @signal_id: the signal id
477 * @detail: the detail
478 * @...: parameters to be passed to the signal, followed by a location for
479 * the return value. If the return type of the signal is G_TYPE_NONE,
480 * the return value location can be omitted.
482 * Emits the specified signal to all the children of @container
483 * using g_signal_emit_valist() calls.
485 void
486 adg_container_propagate(AdgContainer *container,
487 guint signal_id, GQuark detail, ...)
489 va_list var_args;
491 va_start(var_args, detail);
492 adg_container_propagate_valist(container, signal_id, detail, var_args);
493 va_end(var_args);
497 * adg_container_propagate_by_name:
498 * @container: an #AdgContainer
499 * @detailed_signal: a string of the form "signal-name::detail".
500 * @...: a list of parameters to be passed to the signal, followed by
501 * a location for the return value. If the return type of the signal
502 * is G_TYPE_NONE, the return value location can be omitted.
504 * Emits the specified signal to all the children of @container
505 * using g_signal_emit_valist() calls.
507 void
508 adg_container_propagate_by_name(AdgContainer *container,
509 const gchar *detailed_signal, ...)
511 guint signal_id;
512 GQuark detail = 0;
513 va_list var_args;
515 if (!g_signal_parse_name(detailed_signal, G_TYPE_FROM_INSTANCE(container),
516 &signal_id, &detail, FALSE)) {
517 g_warning("%s: signal `%s' is invalid for instance `%p'",
518 G_STRLOC, detailed_signal, container);
519 return;
522 va_start(var_args, detailed_signal);
523 adg_container_propagate_valist(container, signal_id, detail, var_args);
524 va_end(var_args);
528 * adg_container_propagate_valist:
529 * @container: an #AdgContainer
530 * @signal_id: the signal id
531 * @detail: the detail
532 * @var_args: a list of parameters to be passed to the signal, followed by a
533 * location for the return value. If the return type of the signal
534 * is G_TYPE_NONE, the return value location can be omitted.
536 * Emits the specified signal to all the children of @container
537 * using g_signal_emit_valist() calls.
539 void
540 adg_container_propagate_valist(AdgContainer *container,
541 guint signal_id, GQuark detail, va_list var_args)
543 GSList *children;
544 va_list var_copy;
546 g_return_if_fail(ADG_IS_CONTAINER(container));
548 children = adg_container_get_children(container);
550 while (children) {
551 if (children->data) {
552 G_VA_COPY(var_copy, var_args);
553 g_signal_emit_valist(children->data, signal_id, detail, var_copy);
556 children = g_slist_delete_link(children, children);
561 * adg_container_get_model_transformation:
562 * @container: an #AdgContainer
564 * Returns the transformation to be combined with the transformations of the
565 * parent hierarchy to get the final matrix to be applied in the model space.
567 * Return value: the model transformation
569 const AdgMatrix *
570 adg_container_get_model_transformation(AdgContainer *container)
572 g_return_val_if_fail(ADG_IS_CONTAINER(container), NULL);
573 return &container->priv->model_transformation;
577 * adg_container_set_model_transformation:
578 * @container: an #AdgContainer
579 * @transformation: the new model transformation
581 * Sets the transformation to be applied in model space.
583 void
584 adg_container_set_model_transformation(AdgContainer *container,
585 AdgMatrix *transformation)
587 AdgEntity *entity;
588 AdgEntity *parent;
589 const AdgMatrix *parent_matrix;
591 g_return_if_fail(ADG_IS_CONTAINER(container));
592 g_return_if_fail(transformation != NULL);
594 entity = (AdgEntity *) container;
595 parent = (AdgEntity *) adg_entity_get_parent(entity);
596 parent_matrix = parent ? adg_entity_get_model_matrix(parent) : NULL;
598 adg_matrix_set(&container->priv->model_transformation, transformation);
599 adg_entity_model_matrix_changed(entity, parent_matrix);
603 * adg_container_get_paper_transformation:
604 * @container: an #AdgContainer
606 * Returns the transformation to be combined with the transformations of the
607 * parent hierarchy to get the final matrix to be applied in the paper space.
609 * Return value: the paper transformation
611 const AdgMatrix *
612 adg_container_get_paper_transformation(AdgContainer *container)
614 g_return_val_if_fail(ADG_IS_CONTAINER(container), NULL);
615 return &container->priv->paper_transformation;
619 * adg_container_set_paper_transformation:
620 * @container: an #AdgContainer
621 * @transformation: the new paper transformation
623 * Sets the transformation to be applied in paper space.
625 void
626 adg_container_set_paper_transformation(AdgContainer *container,
627 AdgMatrix *transformation)
629 AdgEntity *entity;
630 AdgEntity *parent;
631 const AdgMatrix *parent_matrix;
633 g_return_if_fail(ADG_IS_CONTAINER(container));
634 g_return_if_fail(transformation != NULL);
636 entity = (AdgEntity *) container;
637 parent = (AdgEntity *) adg_entity_get_parent(entity);
638 parent_matrix = parent ? adg_entity_get_paper_matrix(parent) : NULL;
640 adg_matrix_set(&container->priv->paper_transformation, transformation);
641 adg_entity_paper_matrix_changed(entity, parent_matrix);