[AdgPoint] Removed from ADG
[adg.git] / adg / adg-container.c
blob7990b7c1cc5e86fbececeb4b815e15807354cd2e
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 * @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 /**
37 * AdgContainer:
39 * All fields are private and should not be used directly.
40 * Use its public methods instead.
41 **/
43 #include "adg-container.h"
44 #include "adg-container-private.h"
45 #include "adg-intl.h"
48 enum {
49 PROP_0,
50 PROP_CHILD,
51 PROP_MODEL_TRANSFORMATION,
52 PROP_PAPER_TRANSFORMATION
55 enum {
56 ADD,
57 REMOVE,
58 LAST_SIGNAL
62 static void get_property (GObject *object,
63 guint prop_id,
64 GValue *value,
65 GParamSpec *pspec);
66 static void set_property (GObject *object,
67 guint prop_id,
68 const GValue *value,
69 GParamSpec *pspec);
70 static void dispose (GObject *object);
71 static const AdgMatrix *get_model_matrix (AdgEntity *entity);
72 static const AdgMatrix *get_paper_matrix (AdgEntity *entity);
73 static void model_matrix_changed (AdgEntity *entity,
74 AdgMatrix *parent_matrix);
75 static void paper_matrix_changed (AdgEntity *entity,
76 AdgMatrix *parent_matrix);
77 static GSList * get_children (AdgContainer *container);
78 static gboolean add (AdgContainer *container,
79 AdgEntity *entity);
80 static void real_add (AdgContainer *container,
81 AdgEntity *entity,
82 gpointer user_data);
83 static gboolean remove (AdgContainer *container,
84 AdgEntity *entity);
85 static void real_remove (AdgContainer *container,
86 AdgEntity *entity,
87 gpointer user_data);
88 static void invalidate (AdgEntity *entity);
89 static void render (AdgEntity *entity,
90 cairo_t *cr);
92 static guint signals[LAST_SIGNAL] = { 0 };
95 G_DEFINE_TYPE(AdgContainer, adg_container, ADG_TYPE_ENTITY)
98 static void
99 adg_container_class_init(AdgContainerClass *klass)
101 GObjectClass *gobject_class;
102 AdgEntityClass *entity_class;
103 GParamSpec *param;
104 GClosure *closure;
105 GType param_types[1];
107 gobject_class = (GObjectClass *) klass;
108 entity_class = (AdgEntityClass *) klass;
110 g_type_class_add_private(klass, sizeof(AdgContainerPrivate));
112 gobject_class->get_property = get_property;
113 gobject_class->set_property = set_property;
114 gobject_class->dispose = dispose;
116 entity_class->model_matrix_changed = model_matrix_changed;
117 entity_class->paper_matrix_changed = paper_matrix_changed;
118 entity_class->get_model_matrix = get_model_matrix;
119 entity_class->get_paper_matrix = get_paper_matrix;
120 entity_class->invalidate = invalidate;
121 entity_class->render = render;
123 klass->get_children = get_children;
124 klass->add = add;
125 klass->remove = remove;
127 param = g_param_spec_boxed("child",
128 P_("Child"),
129 P_("Can be used to add a new child to the container"),
130 ADG_TYPE_ENTITY, G_PARAM_WRITABLE);
131 g_object_class_install_property(gobject_class, PROP_CHILD, param);
133 param = g_param_spec_boxed("model-transformation",
134 P_("The model transformation"),
135 P_("The model 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_MODEL_TRANSFORMATION, param);
140 param = g_param_spec_boxed("paper-transformation",
141 P_("The paper transformation"),
142 P_("The paper transformation to be applied to this container and its children entities"),
143 ADG_TYPE_MATRIX, G_PARAM_READWRITE);
144 g_object_class_install_property(gobject_class,
145 PROP_PAPER_TRANSFORMATION, param);
148 * AdgContainer::add:
149 * @container: an #AdgContainer
150 * @entity: the #AdgEntity to add
152 * Adds @entity to @container.
154 closure = g_cclosure_new(G_CALLBACK(real_add), (gpointer)0xdeadbeaf, NULL);
155 param_types[0] = G_TYPE_OBJECT;
156 signals[ADD] = g_signal_newv("add", ADG_TYPE_CONTAINER,
157 G_SIGNAL_RUN_FIRST, closure, NULL, NULL,
158 g_cclosure_marshal_VOID__OBJECT,
159 G_TYPE_NONE, 1, param_types);
162 * AdgContainer::remove:
163 * @container: an #AdgContainer
164 * @entity: the #AdgEntity to remove
166 * Removes @entity from @container.
168 closure = g_cclosure_new(G_CALLBACK(real_remove), (gpointer)0xdeadbeaf, NULL);
169 param_types[0] = G_TYPE_OBJECT;
170 signals[REMOVE] = g_signal_newv("remove", ADG_TYPE_CONTAINER,
171 G_SIGNAL_RUN_FIRST, closure, NULL, NULL,
172 g_cclosure_marshal_VOID__OBJECT,
173 G_TYPE_NONE, 1, param_types);
176 static void
177 adg_container_init(AdgContainer *container)
179 AdgContainerPrivate *data = G_TYPE_INSTANCE_GET_PRIVATE(container,
180 ADG_TYPE_CONTAINER,
181 AdgContainerPrivate);
183 data->children = NULL;
184 cairo_matrix_init_identity(&data->model_transformation);
185 cairo_matrix_init_identity(&data->paper_transformation);
186 cairo_matrix_init_identity(&data->model_matrix);
187 cairo_matrix_init_identity(&data->paper_matrix);
189 container->data = data;
192 static void
193 get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
195 AdgContainerPrivate *data = ((AdgContainer *) object)->data;
197 switch (prop_id) {
198 case PROP_MODEL_TRANSFORMATION:
199 g_value_set_boxed(value, &data->model_transformation);
200 break;
201 case PROP_PAPER_TRANSFORMATION:
202 g_value_set_boxed(value, &data->paper_transformation);
203 break;
204 default:
205 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
206 break;
210 static void
211 set_property(GObject *object,
212 guint prop_id, const GValue *value, GParamSpec *pspec)
214 AdgContainer *container;
215 AdgContainerPrivate *data;
217 container = (AdgContainer *) object;
218 data = container->data;
220 switch (prop_id) {
221 case PROP_CHILD:
222 adg_container_add(container, g_value_get_object(value));
223 break;
224 case PROP_MODEL_TRANSFORMATION:
225 adg_matrix_copy(&data->model_transformation, g_value_get_boxed(value));
226 break;
227 case PROP_PAPER_TRANSFORMATION:
228 adg_matrix_copy(&data->paper_transformation, g_value_get_boxed(value));
229 break;
230 default:
231 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
235 static void
236 dispose(GObject *object)
238 GObjectClass *object_class = (GObjectClass *) adg_container_parent_class;
240 adg_container_foreach((AdgContainer *) object,
241 G_CALLBACK(adg_entity_unparent), NULL);
243 if (object_class->dispose != NULL)
244 object_class->dispose(object);
248 static GSList *
249 get_children(AdgContainer *container)
251 AdgContainerPrivate *data = container->data;
253 return g_slist_copy(data->children);
256 static gboolean
257 add(AdgContainer *container, AdgEntity *entity)
259 AdgContainerPrivate *data = container->data;
261 data->children = g_slist_append(data->children, entity);
263 return TRUE;
266 static void
267 real_add(AdgContainer *container, AdgEntity *entity, gpointer user_data)
269 AdgContainer *old_parent;
271 g_assert(user_data == (gpointer) 0xdeadbeaf);
273 old_parent = adg_entity_get_parent(entity);
275 if (old_parent != NULL) {
276 g_warning("Attempting to add an object with type %s to a container "
277 "of type %s, but the object is already inside a container "
278 "of type %s.",
279 g_type_name(G_OBJECT_TYPE(entity)),
280 g_type_name(G_OBJECT_TYPE(container)),
281 g_type_name(G_OBJECT_TYPE(old_parent)));
282 return;
285 if (ADG_CONTAINER_GET_CLASS(container)->add(container, entity))
286 adg_entity_set_parent(entity, container);
287 else
288 g_signal_stop_emission(container, signals[ADD], 0);
291 static gboolean
292 remove(AdgContainer *container, AdgEntity *entity)
294 AdgContainerPrivate *data;
295 GSList *node;
297 data = container->data;
298 node = g_slist_find(data->children, entity);
300 if (!node)
301 return FALSE;
303 data->children = g_slist_delete_link(data->children, node);
305 return TRUE;
308 static void
309 real_remove(AdgContainer *container, AdgEntity *entity, gpointer user_data)
311 g_assert(user_data == (gpointer) 0xdeadbeaf);
313 if (ADG_CONTAINER_GET_CLASS(container)->remove(container, entity))
314 adg_entity_unparent(entity);
315 else
316 g_signal_stop_emission(container, signals[REMOVE], 0);
320 static void
321 model_matrix_changed(AdgEntity *entity, AdgMatrix *parent_matrix)
323 AdgContainer *container;
324 AdgContainerPrivate *data;
325 AdgEntityClass *entity_class;
327 container = (AdgContainer *) entity;
328 data = container->data;
329 entity_class = (AdgEntityClass *) adg_container_parent_class;
331 if (entity_class->model_matrix_changed != NULL)
332 entity_class->model_matrix_changed(entity, parent_matrix);
334 if (parent_matrix)
335 cairo_matrix_multiply(&data->model_matrix,
336 &data->model_transformation,
337 parent_matrix);
338 else
339 adg_matrix_copy(&data->model_matrix, &data->model_transformation);
341 adg_container_propagate_by_name(container, "model-matrix-changed",
342 &data->model_matrix);
345 static void
346 paper_matrix_changed(AdgEntity *entity, AdgMatrix *parent_matrix)
348 AdgContainer *container;
349 AdgContainerPrivate *data;
350 AdgEntityClass *entity_class;
352 container = (AdgContainer *) entity;
353 data = container->data;
354 entity_class = (AdgEntityClass *) adg_container_parent_class;
356 if (entity_class->paper_matrix_changed != NULL)
357 entity_class->paper_matrix_changed(entity, parent_matrix);
359 if (parent_matrix)
360 cairo_matrix_multiply(&data->paper_matrix,
361 &data->paper_transformation,
362 parent_matrix);
363 else
364 adg_matrix_copy(&data->paper_matrix, &data->paper_transformation);
366 adg_container_propagate_by_name(container, "paper-matrix-changed",
367 &data->paper_matrix);
370 static const AdgMatrix *
371 get_model_matrix(AdgEntity *entity)
373 AdgContainerPrivate *data = ((AdgContainer *) entity)->data;
375 return &data->model_matrix;
378 static const AdgMatrix *
379 get_paper_matrix(AdgEntity *entity)
381 AdgContainerPrivate *data = ((AdgContainer *) entity)->data;
383 return &data->paper_matrix;
387 static void
388 invalidate(AdgEntity *entity)
390 AdgEntityClass *entity_class = (AdgEntityClass *) adg_container_parent_class;
392 adg_container_propagate_by_name((AdgContainer *) entity, "invalidate");
394 if (entity_class->invalidate)
395 entity_class->invalidate(entity);
398 static void
399 render(AdgEntity *entity, cairo_t *cr)
401 AdgEntityClass *entity_class = (AdgEntityClass *) adg_container_parent_class;
403 cairo_set_matrix(cr, adg_entity_get_model_matrix(entity));
404 adg_container_propagate_by_name((AdgContainer *) entity, "render", cr);
406 if (entity_class->render)
407 entity_class->render(entity, cr);
412 * adg_container_new:
414 * Creates a new container entity.
416 * Return value: the newly created entity
418 AdgEntity *
419 adg_container_new(void)
421 return (AdgEntity *) g_object_new(ADG_TYPE_CONTAINER, NULL);
426 * adg_container_add:
427 * @container: an #AdgContainer
428 * @entity: an #AdgEntity
430 * Emits a #AdgContainer::add signal on @container passing
431 * @entity as argument.
433 * @entity may be added to only one container at a time; you can't
434 * place the same entity inside two different containers.
436 void
437 adg_container_add(AdgContainer *container, AdgEntity *entity)
439 g_return_if_fail(ADG_IS_CONTAINER(container));
440 g_return_if_fail(ADG_IS_ENTITY(entity));
442 g_signal_emit(container, signals[ADD], 0, entity);
446 * adg_container_remove:
447 * @container: an #AdgContainer
448 * @entity: an #AdgEntity
450 * Emits a #AdgContainer::remove signal on @container passing
451 * @entity as argument. @entity must be inside @container.
453 * Note that @container will own a reference to @entity
454 * and that this may be the last reference held; so removing an
455 * entity from its container can destroy it.
457 * If you want to use @entity again, you need to add a reference
458 * to it, using g_object_ref(), before removing it from @container.
460 * If you don't want to use @entity again, it's usually more
461 * efficient to simply destroy it directly using g_object_unref()
462 * since this will remove it from the container.
464 void
465 adg_container_remove(AdgContainer *container, AdgEntity *entity)
467 g_return_if_fail(ADG_IS_CONTAINER(container));
468 g_return_if_fail(ADG_IS_ENTITY(entity));
470 g_signal_emit(container, signals[REMOVE], 0, entity);
474 * adg_container_get_children:
475 * @container: an #AdgContainer
477 * Gets the children list of @container.
478 * This list must be manually freed when no longer user.
480 * Returns: a newly allocated #GSList or %NULL on error
482 GSList *
483 adg_container_get_children(AdgContainer *container)
485 g_return_val_if_fail(ADG_IS_CONTAINER(container), NULL);
487 return ADG_CONTAINER_GET_CLASS(container)->get_children(container);
491 * adg_container_foreach:
492 * @container: an #AdgContainer
493 * @callback: a callback
494 * @user_data: callback user data
496 * Invokes @callback on each child of @container.
497 * The callback should be declared as:
499 * <code>
500 * void callback(AdgEntity *entity, gpointer user_data);
501 * </code>
503 void
504 adg_container_foreach(AdgContainer *container,
505 GCallback callback, gpointer user_data)
507 GSList *children;
509 g_return_if_fail(ADG_IS_CONTAINER(container));
510 g_return_if_fail(callback != NULL);
512 children = adg_container_get_children (container);
514 while (children) {
515 if (children->data)
516 ((void (*) (gpointer, gpointer)) callback) (children->data, user_data);
518 children = g_slist_delete_link(children, children);
523 * adg_container_propagate:
524 * @container: an #AdgContainer
525 * @signal_id: the signal id
526 * @detail: the detail
527 * @...: parameters to be passed to the signal, followed by a location for
528 * the return value. If the return type of the signal is G_TYPE_NONE,
529 * the return value location can be omitted.
531 * Emits the specified signal to all the children of @container
532 * using g_signal_emit_valist() calls.
534 void
535 adg_container_propagate(AdgContainer *container,
536 guint signal_id, GQuark detail, ...)
538 va_list var_args;
540 va_start(var_args, detail);
541 adg_container_propagate_valist(container, signal_id, detail, var_args);
542 va_end(var_args);
546 * adg_container_propagate_by_name:
547 * @container: an #AdgContainer
548 * @detailed_signal: a string of the form "signal-name::detail".
549 * @...: a list of parameters to be passed to the signal, followed by
550 * a location for the return value. If the return type of the signal
551 * is G_TYPE_NONE, the return value location can be omitted.
553 * Emits the specified signal to all the children of @container
554 * using g_signal_emit_valist() calls.
556 void
557 adg_container_propagate_by_name(AdgContainer *container,
558 const gchar *detailed_signal, ...)
560 guint signal_id;
561 GQuark detail = 0;
562 va_list var_args;
564 if (!g_signal_parse_name(detailed_signal, G_TYPE_FROM_INSTANCE(container),
565 &signal_id, &detail, FALSE)) {
566 g_warning("%s: signal `%s' is invalid for instance `%p'",
567 G_STRLOC, detailed_signal, container);
568 return;
571 va_start(var_args, detailed_signal);
572 adg_container_propagate_valist(container, signal_id, detail, var_args);
573 va_end(var_args);
577 * adg_container_propagate_valist:
578 * @container: an #AdgContainer
579 * @signal_id: the signal id
580 * @detail: the detail
581 * @var_args: a list of parameters to be passed to the signal, followed by a
582 * location for the return value. If the return type of the signal
583 * is G_TYPE_NONE, the return value location can be omitted.
585 * Emits the specified signal to all the children of @container
586 * using g_signal_emit_valist() calls.
588 void
589 adg_container_propagate_valist(AdgContainer *container,
590 guint signal_id, GQuark detail, va_list var_args)
592 GSList *children;
593 va_list var_copy;
595 g_return_if_fail(ADG_IS_CONTAINER(container));
597 children = adg_container_get_children(container);
599 while (children) {
600 if (children->data) {
601 G_VA_COPY(var_copy, var_args);
602 g_signal_emit_valist(children->data, signal_id, detail, var_copy);
605 children = g_slist_delete_link(children, children);
610 * adg_container_get_model_transformation:
611 * @container: an #AdgContainer
613 * Returns the transformation to be combined with the transformations of the
614 * parent hierarchy to get the final matrix to be applied in the model space.
616 * Return value: the model transformation
618 const AdgMatrix *
619 adg_container_get_model_transformation(AdgContainer *container)
621 AdgContainerPrivate *data;
623 g_return_val_if_fail(ADG_IS_CONTAINER(container), NULL);
625 data = container->data;
627 return &data->model_transformation;
631 * adg_container_set_model_transformation:
632 * @container: an #AdgContainer
633 * @transformation: the new model transformation
635 * Sets the transformation to be applied in model space.
637 void
638 adg_container_set_model_transformation(AdgContainer *container,
639 const AdgMatrix *transformation)
641 AdgEntity *entity;
642 AdgContainerPrivate *data;
643 AdgEntity *parent;
644 const AdgMatrix *parent_matrix;
646 g_return_if_fail(ADG_IS_CONTAINER(container));
647 g_return_if_fail(transformation != NULL);
649 entity = (AdgEntity *) container;
650 data = container->data;
651 parent = (AdgEntity *) adg_entity_get_parent(entity);
652 parent_matrix = parent ? adg_entity_get_model_matrix(parent) : NULL;
654 adg_matrix_copy(&data->model_transformation, transformation);
655 adg_entity_model_matrix_changed(entity, parent_matrix);
657 /* Temporary workaround: this function will be removed soon */
658 adg_entity_set_local_map(entity, transformation);
662 * adg_container_get_paper_transformation:
663 * @container: an #AdgContainer
665 * Returns the transformation to be combined with the transformations of the
666 * parent hierarchy to get the final matrix to be applied in the paper space.
668 * Return value: the paper transformation
670 const AdgMatrix *
671 adg_container_get_paper_transformation(AdgContainer *container)
673 AdgContainerPrivate *data;
675 g_return_val_if_fail(ADG_IS_CONTAINER(container), NULL);
677 data = container->data;
679 return &data->paper_transformation;
683 * adg_container_set_paper_transformation:
684 * @container: an #AdgContainer
685 * @transformation: the new paper transformation
687 * Sets the transformation to be applied in paper space.
689 void
690 adg_container_set_paper_transformation(AdgContainer *container,
691 const AdgMatrix *transformation)
693 AdgEntity *entity;
694 AdgContainerPrivate *data;
695 AdgEntity *parent;
696 const AdgMatrix *parent_matrix;
698 g_return_if_fail(ADG_IS_CONTAINER(container));
699 g_return_if_fail(transformation != NULL);
701 entity = (AdgEntity *) container;
702 data = container->data;
703 parent = (AdgEntity *) adg_entity_get_parent(entity);
704 parent_matrix = parent ? adg_entity_get_paper_matrix(parent) : NULL;
706 adg_matrix_copy(&data->paper_transformation, transformation);
707 adg_entity_paper_matrix_changed(entity, parent_matrix);
709 /* Temporary workaround: this function will be removed soon */
710 adg_entity_set_global_map(entity, transformation);