[AdgContainer] Changed order of matrix multiplication
[adg.git] / adg / adg-container.c
blob4c007c4c69f9b662b5447405eb31bbc37254237f
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: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://adg.entidi.com/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"
41 enum {
42 PROP_0,
43 PROP_CHILD,
44 PROP_MODEL_TRANSFORMATION,
45 PROP_PAPER_TRANSFORMATION
48 enum {
49 ADD,
50 REMOVE,
51 LAST_SIGNAL
55 static void get_property (GObject *object,
56 guint prop_id,
57 GValue *value,
58 GParamSpec *pspec);
59 static void set_property (GObject *object,
60 guint prop_id,
61 const GValue *value,
62 GParamSpec *pspec);
63 static void dispose (GObject *object);
64 static const AdgMatrix *get_model_matrix (AdgEntity *entity);
65 static const AdgMatrix *get_paper_matrix (AdgEntity *entity);
66 static void model_matrix_changed (AdgEntity *entity,
67 AdgMatrix *parent_matrix);
68 static void paper_matrix_changed (AdgEntity *entity,
69 AdgMatrix *parent_matrix);
70 static GSList * get_children (AdgContainer *container);
71 static gboolean add (AdgContainer *container,
72 AdgEntity *entity);
73 static void real_add (AdgContainer *container,
74 AdgEntity *entity,
75 gpointer user_data);
76 static gboolean remove (AdgContainer *container,
77 AdgEntity *entity);
78 static void real_remove (AdgContainer *container,
79 AdgEntity *entity,
80 gpointer user_data);
81 static void invalidate (AdgEntity *entity);
82 static void render (AdgEntity *entity,
83 cairo_t *cr);
85 static guint signals[LAST_SIGNAL] = { 0 };
88 G_DEFINE_TYPE(AdgContainer, adg_container, ADG_TYPE_ENTITY)
91 static void
92 adg_container_class_init(AdgContainerClass *klass)
94 GObjectClass *gobject_class;
95 AdgEntityClass *entity_class;
96 GParamSpec *param;
97 GClosure *closure;
98 GType param_types[1];
100 gobject_class = (GObjectClass *) klass;
101 entity_class = (AdgEntityClass *) klass;
103 g_type_class_add_private(klass, sizeof(AdgContainerPrivate));
105 gobject_class->get_property = get_property;
106 gobject_class->set_property = set_property;
107 gobject_class->dispose = dispose;
109 entity_class->model_matrix_changed = model_matrix_changed;
110 entity_class->paper_matrix_changed = paper_matrix_changed;
111 entity_class->get_model_matrix = get_model_matrix;
112 entity_class->get_paper_matrix = get_paper_matrix;
113 entity_class->invalidate = invalidate;
114 entity_class->render = render;
116 klass->get_children = get_children;
117 klass->add = add;
118 klass->remove = remove;
120 param = g_param_spec_boxed("child",
121 P_("Child"),
122 P_("Can be used to add a new child to the container"),
123 ADG_TYPE_ENTITY, G_PARAM_WRITABLE);
124 g_object_class_install_property(gobject_class, PROP_CHILD, param);
126 param = g_param_spec_boxed("model-transformation",
127 P_("The model transformation"),
128 P_("The model transformation to be applied to this container and its children entities"),
129 ADG_TYPE_MATRIX, G_PARAM_READWRITE);
130 g_object_class_install_property(gobject_class,
131 PROP_MODEL_TRANSFORMATION, param);
133 param = g_param_spec_boxed("paper-transformation",
134 P_("The paper transformation"),
135 P_("The paper transformation to be applied to this container and its children entities"),
136 ADG_TYPE_MATRIX, G_PARAM_READWRITE);
137 g_object_class_install_property(gobject_class,
138 PROP_PAPER_TRANSFORMATION, param);
141 * AdgContainer::add:
142 * @container: an #AdgContainer
143 * @entity: the #AdgEntity to add
145 * Adds @entity to @container.
147 closure = g_cclosure_new(G_CALLBACK(real_add), (gpointer)0xdeadbeaf, NULL);
148 param_types[0] = G_TYPE_OBJECT;
149 signals[ADD] = g_signal_newv("add", ADG_TYPE_CONTAINER,
150 G_SIGNAL_RUN_FIRST, closure, NULL, NULL,
151 g_cclosure_marshal_VOID__OBJECT,
152 G_TYPE_NONE, 1, param_types);
155 * AdgContainer::remove:
156 * @container: an #AdgContainer
157 * @entity: the #AdgEntity to remove
159 * Removes @entity from @container.
161 closure = g_cclosure_new(G_CALLBACK(real_remove), (gpointer)0xdeadbeaf, NULL);
162 param_types[0] = G_TYPE_OBJECT;
163 signals[REMOVE] = g_signal_newv("remove", ADG_TYPE_CONTAINER,
164 G_SIGNAL_RUN_FIRST, closure, NULL, NULL,
165 g_cclosure_marshal_VOID__OBJECT,
166 G_TYPE_NONE, 1, param_types);
169 static void
170 adg_container_init(AdgContainer *container)
172 AdgContainerPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE(container,
173 ADG_TYPE_CONTAINER,
174 AdgContainerPrivate);
176 priv->children = NULL;
177 cairo_matrix_init_identity(&priv->model_transformation);
178 cairo_matrix_init_identity(&priv->paper_transformation);
179 cairo_matrix_init_identity(&priv->model_matrix);
180 cairo_matrix_init_identity(&priv->paper_matrix);
182 container->priv = priv;
185 static void
186 get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
188 AdgContainer *container = (AdgContainer *) object;
190 switch (prop_id) {
191 case PROP_MODEL_TRANSFORMATION:
192 g_value_set_boxed(value, &container->priv->model_transformation);
193 break;
194 case PROP_PAPER_TRANSFORMATION:
195 g_value_set_boxed(value, &container->priv->paper_transformation);
196 break;
197 default:
198 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
199 break;
203 static void
204 set_property(GObject *object,
205 guint prop_id, const GValue *value, GParamSpec *pspec)
207 AdgContainer *container = (AdgContainer *) object;
209 switch (prop_id) {
210 case PROP_CHILD:
211 adg_container_add(container, g_value_get_object(value));
212 break;
213 case PROP_MODEL_TRANSFORMATION:
214 adg_matrix_copy(&container->priv->model_transformation,
215 g_value_get_boxed(value));
216 break;
217 case PROP_PAPER_TRANSFORMATION:
218 adg_matrix_copy(&container->priv->paper_transformation,
219 g_value_get_boxed(value));
220 break;
221 default:
222 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
226 static void
227 dispose(GObject *object)
229 GObjectClass *object_class = (GObjectClass *) adg_container_parent_class;
231 adg_container_foreach((AdgContainer *) object,
232 G_CALLBACK(adg_entity_unparent), NULL);
234 if (object_class->dispose != NULL)
235 object_class->dispose(object);
239 static GSList *
240 get_children(AdgContainer *container)
242 return g_slist_copy(container->priv->children);
245 static gboolean
246 add(AdgContainer *container, AdgEntity *entity)
248 container->priv->children = g_slist_append(container->priv->children,
249 entity);
250 return TRUE;
253 static void
254 real_add(AdgContainer *container, AdgEntity *entity, gpointer user_data)
256 AdgContainer *old_parent;
258 g_assert(user_data == (gpointer) 0xdeadbeaf);
260 old_parent = adg_entity_get_parent(entity);
262 if (old_parent != NULL) {
263 g_warning("Attempting to add an object with type %s to a container "
264 "of type %s, but the object is already inside a container "
265 "of type %s.",
266 g_type_name(G_OBJECT_TYPE(entity)),
267 g_type_name(G_OBJECT_TYPE(container)),
268 g_type_name(G_OBJECT_TYPE(old_parent)));
269 return;
272 if (ADG_CONTAINER_GET_CLASS(container)->add(container, entity))
273 adg_entity_set_parent(entity, container);
274 else
275 g_signal_stop_emission(container, signals[ADD], 0);
278 static gboolean
279 remove(AdgContainer *container, AdgEntity *entity)
281 GSList *node = g_slist_find(container->priv->children, entity);
283 if (!node)
284 return FALSE;
286 container->priv->children = g_slist_delete_link(container->priv->children,
287 node);
288 return TRUE;
291 static void
292 real_remove(AdgContainer *container, AdgEntity *entity, gpointer user_data)
294 g_assert(user_data == (gpointer) 0xdeadbeaf);
296 if (ADG_CONTAINER_GET_CLASS(container)->remove(container, entity))
297 adg_entity_unparent(entity);
298 else
299 g_signal_stop_emission(container, signals[REMOVE], 0);
303 static void
304 model_matrix_changed(AdgEntity *entity, AdgMatrix *parent_matrix)
306 AdgContainer *container;
307 AdgEntityClass *entity_class;
309 container = (AdgContainer *) entity;
310 entity_class = (AdgEntityClass *) adg_container_parent_class;
312 if (entity_class->model_matrix_changed != NULL)
313 entity_class->model_matrix_changed(entity, parent_matrix);
315 if (parent_matrix)
316 cairo_matrix_multiply(&container->priv->model_matrix,
317 &container->priv->model_transformation,
318 parent_matrix);
319 else
320 adg_matrix_copy(&container->priv->model_matrix,
321 &container->priv->model_transformation);
323 adg_container_propagate_by_name(container, "model-matrix-changed",
324 &container->priv->model_matrix);
327 static void
328 paper_matrix_changed(AdgEntity *entity, AdgMatrix *parent_matrix)
330 AdgContainer *container;
331 AdgEntityClass *entity_class;
333 container = (AdgContainer *) entity;
334 entity_class = (AdgEntityClass *) adg_container_parent_class;
336 if (entity_class->paper_matrix_changed != NULL)
337 entity_class->paper_matrix_changed(entity, parent_matrix);
339 if (parent_matrix)
340 cairo_matrix_multiply(&container->priv->paper_matrix,
341 &container->priv->paper_transformation,
342 parent_matrix);
343 else
344 adg_matrix_copy(&container->priv->paper_matrix,
345 &container->priv->paper_transformation);
347 adg_container_propagate_by_name(container, "paper-matrix-changed",
348 &container->priv->paper_matrix);
351 static const AdgMatrix *
352 get_model_matrix(AdgEntity *entity)
354 AdgContainer *container = (AdgContainer *) entity;
356 return &container->priv->model_matrix;
359 static const AdgMatrix *
360 get_paper_matrix(AdgEntity *entity)
362 AdgContainer *container = (AdgContainer *) entity;
364 return &container->priv->paper_matrix;
368 static void
369 invalidate(AdgEntity *entity)
371 AdgEntityClass *entity_class = (AdgEntityClass *) adg_container_parent_class;
373 adg_container_propagate_by_name((AdgContainer *) entity, "invalidate");
375 if (entity_class->invalidate)
376 entity_class->invalidate(entity);
379 static void
380 render(AdgEntity *entity, cairo_t *cr)
382 AdgEntityClass *entity_class = (AdgEntityClass *) adg_container_parent_class;
384 cairo_set_matrix(cr, adg_entity_get_model_matrix(entity));
385 adg_container_propagate_by_name((AdgContainer *) entity, "render", cr);
387 if (entity_class->render)
388 entity_class->render(entity, cr);
393 * adg_container_new:
395 * Creates a new container entity.
397 * Return value: the newly created entity
399 AdgEntity *
400 adg_container_new(void)
402 return (AdgEntity *) g_object_new(ADG_TYPE_CONTAINER, NULL);
407 * adg_container_add:
408 * @container: an #AdgContainer
409 * @entity: an #AdgEntity
411 * Emits a #AdgContainer::add signal on @container passing
412 * @entity as argument.
414 * @entity may be added to only one container at a time; you can't
415 * place the same entity inside two different containers.
417 void
418 adg_container_add(AdgContainer *container, AdgEntity *entity)
420 g_return_if_fail(ADG_IS_CONTAINER(container));
421 g_return_if_fail(ADG_IS_ENTITY(entity));
423 g_signal_emit(container, signals[ADD], 0, entity);
427 * adg_container_remove:
428 * @container: an #AdgContainer
429 * @entity: an #AdgEntity
431 * Emits a #AdgContainer::remove signal on @container passing
432 * @entity as argument. @entity must be inside @container.
434 * Note that @container will own a reference to @entity
435 * and that this may be the last reference held; so removing an
436 * entity from its container can destroy it.
438 * If you want to use @entity again, you need to add a reference
439 * to it, using g_object_ref(), before removing it from @container.
441 * If you don't want to use @entity again, it's usually more
442 * efficient to simply destroy it directly using g_object_unref()
443 * since this will remove it from the container.
445 void
446 adg_container_remove(AdgContainer *container, AdgEntity *entity)
448 g_return_if_fail(ADG_IS_CONTAINER(container));
449 g_return_if_fail(ADG_IS_ENTITY(entity));
451 g_signal_emit(container, signals[REMOVE], 0, entity);
455 * adg_container_get_children:
456 * @container: an #AdgContainer
458 * Gets the children list of @container.
459 * This list must be manually freed when no longer user.
461 * Returns: a newly allocated #GSList or %NULL on error
463 GSList *
464 adg_container_get_children(AdgContainer *container)
466 g_return_val_if_fail(ADG_IS_CONTAINER(container), NULL);
468 return ADG_CONTAINER_GET_CLASS(container)->get_children(container);
472 * adg_container_foreach:
473 * @container: an #AdgContainer
474 * @callback: a callback
475 * @user_data: callback user data
477 * Invokes @callback on each child of @container.
478 * The callback should be declared as:
480 * <code>
481 * void callback(AdgEntity *entity, gpointer user_data);
482 * </code>
484 void
485 adg_container_foreach(AdgContainer *container,
486 GCallback callback, gpointer user_data)
488 GSList *children;
490 g_return_if_fail(ADG_IS_CONTAINER(container));
491 g_return_if_fail(callback != NULL);
493 children = adg_container_get_children (container);
495 while (children) {
496 if (children->data)
497 ((void (*) (gpointer, gpointer)) callback) (children->data, user_data);
499 children = g_slist_delete_link(children, children);
504 * adg_container_propagate:
505 * @container: an #AdgContainer
506 * @signal_id: the signal id
507 * @detail: the detail
508 * @...: parameters to be passed to the signal, followed by a location for
509 * the return value. If the return type of the signal is G_TYPE_NONE,
510 * the return value location can be omitted.
512 * Emits the specified signal to all the children of @container
513 * using g_signal_emit_valist() calls.
515 void
516 adg_container_propagate(AdgContainer *container,
517 guint signal_id, GQuark detail, ...)
519 va_list var_args;
521 va_start(var_args, detail);
522 adg_container_propagate_valist(container, signal_id, detail, var_args);
523 va_end(var_args);
527 * adg_container_propagate_by_name:
528 * @container: an #AdgContainer
529 * @detailed_signal: a string of the form "signal-name::detail".
530 * @...: a list of parameters to be passed to the signal, followed by
531 * a location for the return value. If the return type of the signal
532 * is G_TYPE_NONE, the return value location can be omitted.
534 * Emits the specified signal to all the children of @container
535 * using g_signal_emit_valist() calls.
537 void
538 adg_container_propagate_by_name(AdgContainer *container,
539 const gchar *detailed_signal, ...)
541 guint signal_id;
542 GQuark detail = 0;
543 va_list var_args;
545 if (!g_signal_parse_name(detailed_signal, G_TYPE_FROM_INSTANCE(container),
546 &signal_id, &detail, FALSE)) {
547 g_warning("%s: signal `%s' is invalid for instance `%p'",
548 G_STRLOC, detailed_signal, container);
549 return;
552 va_start(var_args, detailed_signal);
553 adg_container_propagate_valist(container, signal_id, detail, var_args);
554 va_end(var_args);
558 * adg_container_propagate_valist:
559 * @container: an #AdgContainer
560 * @signal_id: the signal id
561 * @detail: the detail
562 * @var_args: a list of parameters to be passed to the signal, followed by a
563 * location for the return value. If the return type of the signal
564 * is G_TYPE_NONE, the return value location can be omitted.
566 * Emits the specified signal to all the children of @container
567 * using g_signal_emit_valist() calls.
569 void
570 adg_container_propagate_valist(AdgContainer *container,
571 guint signal_id, GQuark detail, va_list var_args)
573 GSList *children;
574 va_list var_copy;
576 g_return_if_fail(ADG_IS_CONTAINER(container));
578 children = adg_container_get_children(container);
580 while (children) {
581 if (children->data) {
582 G_VA_COPY(var_copy, var_args);
583 g_signal_emit_valist(children->data, signal_id, detail, var_copy);
586 children = g_slist_delete_link(children, children);
591 * adg_container_get_model_transformation:
592 * @container: an #AdgContainer
594 * Returns the transformation to be combined with the transformations of the
595 * parent hierarchy to get the final matrix to be applied in the model space.
597 * Return value: the model transformation
599 const AdgMatrix *
600 adg_container_get_model_transformation(AdgContainer *container)
602 g_return_val_if_fail(ADG_IS_CONTAINER(container), NULL);
603 return &container->priv->model_transformation;
607 * adg_container_set_model_transformation:
608 * @container: an #AdgContainer
609 * @transformation: the new model transformation
611 * Sets the transformation to be applied in model space.
613 void
614 adg_container_set_model_transformation(AdgContainer *container,
615 const AdgMatrix *transformation)
617 AdgEntity *entity;
618 AdgEntity *parent;
619 const AdgMatrix *parent_matrix;
621 g_return_if_fail(ADG_IS_CONTAINER(container));
622 g_return_if_fail(transformation != NULL);
624 entity = (AdgEntity *) container;
625 parent = (AdgEntity *) adg_entity_get_parent(entity);
626 parent_matrix = parent ? adg_entity_get_model_matrix(parent) : NULL;
628 adg_matrix_copy(&container->priv->model_transformation, transformation);
629 adg_entity_model_matrix_changed(entity, parent_matrix);
633 * adg_container_get_paper_transformation:
634 * @container: an #AdgContainer
636 * Returns the transformation to be combined with the transformations of the
637 * parent hierarchy to get the final matrix to be applied in the paper space.
639 * Return value: the paper transformation
641 const AdgMatrix *
642 adg_container_get_paper_transformation(AdgContainer *container)
644 g_return_val_if_fail(ADG_IS_CONTAINER(container), NULL);
645 return &container->priv->paper_transformation;
649 * adg_container_set_paper_transformation:
650 * @container: an #AdgContainer
651 * @transformation: the new paper transformation
653 * Sets the transformation to be applied in paper space.
655 void
656 adg_container_set_paper_transformation(AdgContainer *container,
657 const AdgMatrix *transformation)
659 AdgEntity *entity;
660 AdgEntity *parent;
661 const AdgMatrix *parent_matrix;
663 g_return_if_fail(ADG_IS_CONTAINER(container));
664 g_return_if_fail(transformation != NULL);
666 entity = (AdgEntity *) container;
667 parent = (AdgEntity *) adg_entity_get_parent(entity);
668 parent_matrix = parent ? adg_entity_get_paper_matrix(parent) : NULL;
670 adg_matrix_copy(&container->priv->paper_transformation, transformation);
671 adg_entity_paper_matrix_changed(entity, parent_matrix);