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.
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"
40 #define PARENT_CLASS ((AdgEntityClass *) adg_container_parent_class)
46 PROP_MODEL_TRANSFORMATION
,
47 PROP_PAPER_TRANSFORMATION
57 static void get_property (GObject
*object
,
61 static void set_property (GObject
*object
,
65 static void dispose (GObject
*object
);
66 static const AdgMatrix
*get_model_matrix (AdgEntity
*entity
);
67 static const AdgMatrix
*get_paper_matrix (AdgEntity
*entity
);
68 static void model_matrix_changed (AdgEntity
*entity
,
69 AdgMatrix
*parent_matrix
);
70 static void paper_matrix_changed (AdgEntity
*entity
,
71 AdgMatrix
*parent_matrix
);
72 static GSList
* get_children (AdgContainer
*container
);
73 static gboolean
add (AdgContainer
*container
,
75 static void real_add (AdgContainer
*container
,
78 static gboolean
remove (AdgContainer
*container
,
80 static void real_remove (AdgContainer
*container
,
83 static void invalidate (AdgEntity
*entity
);
84 static void render (AdgEntity
*entity
,
87 static guint signals
[LAST_SIGNAL
] = { 0 };
90 G_DEFINE_TYPE(AdgContainer
, adg_container
, ADG_TYPE_ENTITY
)
94 adg_container_class_init(AdgContainerClass
*klass
)
96 GObjectClass
*gobject_class
;
97 AdgEntityClass
*entity_class
;
100 GType param_types
[1];
102 gobject_class
= (GObjectClass
*) klass
;
103 entity_class
= (AdgEntityClass
*) klass
;
105 g_type_class_add_private(klass
, sizeof(AdgContainerPrivate
));
107 gobject_class
->get_property
= get_property
;
108 gobject_class
->set_property
= set_property
;
109 gobject_class
->dispose
= dispose
;
111 entity_class
->model_matrix_changed
= model_matrix_changed
;
112 entity_class
->paper_matrix_changed
= paper_matrix_changed
;
113 entity_class
->get_model_matrix
= get_model_matrix
;
114 entity_class
->get_paper_matrix
= get_paper_matrix
;
115 entity_class
->invalidate
= invalidate
;
116 entity_class
->render
= render
;
118 klass
->get_children
= get_children
;
120 klass
->remove
= remove
;
122 param
= g_param_spec_boxed("child",
124 P_("Can be used to add a new child to the container"),
125 ADG_TYPE_ENTITY
, G_PARAM_WRITABLE
);
126 g_object_class_install_property(gobject_class
, PROP_CHILD
, param
);
128 param
= g_param_spec_boxed("model-transformation",
129 P_("The model transformation"),
130 P_("The model transformation to be applied to this container and its children entities"),
131 ADG_TYPE_MATRIX
, G_PARAM_READWRITE
);
132 g_object_class_install_property(gobject_class
,
133 PROP_MODEL_TRANSFORMATION
, param
);
135 param
= g_param_spec_boxed("paper-transformation",
136 P_("The paper transformation"),
137 P_("The paper transformation to be applied to this container and its children entities"),
138 ADG_TYPE_MATRIX
, G_PARAM_READWRITE
);
139 g_object_class_install_property(gobject_class
,
140 PROP_PAPER_TRANSFORMATION
, param
);
144 * @container: an #AdgContainer
145 * @entity: the #AdgEntity to add
147 * Adds @entity to @container.
149 closure
= g_cclosure_new(G_CALLBACK(real_add
), (gpointer
)0xdeadbeaf, NULL
);
150 param_types
[0] = G_TYPE_OBJECT
;
151 signals
[ADD
] = g_signal_newv("add", ADG_TYPE_CONTAINER
,
152 G_SIGNAL_RUN_FIRST
, closure
, NULL
, NULL
,
153 g_cclosure_marshal_VOID__OBJECT
,
154 G_TYPE_NONE
, 1, param_types
);
157 * AdgContainer::remove:
158 * @container: an #AdgContainer
159 * @entity: the #AdgEntity to remove
161 * Removes @entity from @container.
163 closure
= g_cclosure_new(G_CALLBACK(real_remove
), (gpointer
)0xdeadbeaf, NULL
);
164 param_types
[0] = G_TYPE_OBJECT
;
165 signals
[REMOVE
] = g_signal_newv("remove", ADG_TYPE_CONTAINER
,
166 G_SIGNAL_RUN_FIRST
, closure
, NULL
, NULL
,
167 g_cclosure_marshal_VOID__OBJECT
,
168 G_TYPE_NONE
, 1, param_types
);
172 adg_container_init(AdgContainer
*container
)
174 AdgContainerPrivate
*priv
= G_TYPE_INSTANCE_GET_PRIVATE(container
,
176 AdgContainerPrivate
);
178 priv
->children
= NULL
;
179 cairo_matrix_init_identity(&priv
->model_transformation
);
180 cairo_matrix_init_identity(&priv
->paper_transformation
);
181 cairo_matrix_init_identity(&priv
->model_matrix
);
182 cairo_matrix_init_identity(&priv
->paper_matrix
);
184 container
->priv
= priv
;
188 get_property(GObject
*object
, guint prop_id
, GValue
*value
, GParamSpec
*pspec
)
190 AdgContainer
*container
= (AdgContainer
*) object
;
193 case PROP_MODEL_TRANSFORMATION
:
194 g_value_set_boxed(value
, &container
->priv
->model_transformation
);
196 case PROP_PAPER_TRANSFORMATION
:
197 g_value_set_boxed(value
, &container
->priv
->paper_transformation
);
200 G_OBJECT_WARN_INVALID_PROPERTY_ID(object
, prop_id
, pspec
);
206 set_property(GObject
*object
,
207 guint prop_id
, const GValue
*value
, GParamSpec
*pspec
)
209 AdgContainer
*container
= (AdgContainer
*) object
;
213 adg_container_add(container
, g_value_get_object(value
));
215 case PROP_MODEL_TRANSFORMATION
:
216 adg_matrix_set(&container
->priv
->model_transformation
,
217 g_value_get_boxed(value
));
219 case PROP_PAPER_TRANSFORMATION
:
220 adg_matrix_set(&container
->priv
->paper_transformation
,
221 g_value_get_boxed(value
));
224 G_OBJECT_WARN_INVALID_PROPERTY_ID(object
, prop_id
, pspec
);
229 dispose(GObject
*object
)
231 AdgContainer
*container
= (AdgContainer
*) object
;
233 adg_container_foreach(container
, G_CALLBACK(adg_entity_unparent
), NULL
);
235 ((GObjectClass
*) PARENT_CLASS
)->dispose(object
);
240 get_children(AdgContainer
*container
)
242 return g_slist_copy(container
->priv
->children
);
246 add(AdgContainer
*container
, AdgEntity
*entity
)
248 container
->priv
->children
= g_slist_append(container
->priv
->children
,
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 "
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
)));
272 if (ADG_CONTAINER_GET_CLASS(container
)->add(container
, entity
))
273 adg_entity_set_parent(entity
, container
);
275 g_signal_stop_emission(container
, signals
[ADD
], 0);
279 remove(AdgContainer
*container
, AdgEntity
*entity
)
281 GSList
*node
= g_slist_find(container
->priv
->children
, entity
);
286 container
->priv
->children
= g_slist_delete_link(container
->priv
->children
,
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
);
299 g_signal_stop_emission(container
, signals
[REMOVE
], 0);
304 model_matrix_changed(AdgEntity
*entity
, AdgMatrix
*parent_matrix
)
306 AdgContainer
*container
= (AdgContainer
*) entity
;
308 PARENT_CLASS
->model_matrix_changed(entity
, parent_matrix
);
311 cairo_matrix_multiply(&container
->priv
->model_matrix
,
313 &container
->priv
->model_transformation
);
315 adg_matrix_set(&container
->priv
->model_matrix
,
316 &container
->priv
->model_transformation
);
318 adg_container_propagate_by_name(container
, "model-matrix-changed",
319 &container
->priv
->model_matrix
);
323 paper_matrix_changed(AdgEntity
*entity
, AdgMatrix
*parent_matrix
)
325 AdgContainer
*container
= (AdgContainer
*) entity
;
327 PARENT_CLASS
->paper_matrix_changed(entity
, parent_matrix
);
330 cairo_matrix_multiply(&container
->priv
->paper_matrix
,
332 &container
->priv
->paper_transformation
);
334 adg_matrix_set(&container
->priv
->paper_matrix
,
335 &container
->priv
->paper_transformation
);
337 adg_container_propagate_by_name(container
, "paper-matrix-changed",
338 &container
->priv
->paper_matrix
);
341 static const AdgMatrix
*
342 get_model_matrix(AdgEntity
*entity
)
344 AdgContainer
*container
= (AdgContainer
*) entity
;
346 return &container
->priv
->model_matrix
;
349 static const AdgMatrix
*
350 get_paper_matrix(AdgEntity
*entity
)
352 AdgContainer
*container
= (AdgContainer
*) entity
;
354 return &container
->priv
->paper_matrix
;
359 invalidate(AdgEntity
*entity
)
361 adg_container_propagate_by_name((AdgContainer
*) entity
, "invalidate");
362 PARENT_CLASS
->invalidate(entity
);
366 render(AdgEntity
*entity
, cairo_t
*cr
)
368 cairo_set_matrix(cr
, adg_entity_get_model_matrix(entity
));
369 adg_container_propagate_by_name((AdgContainer
*) entity
, "render", cr
);
370 PARENT_CLASS
->render(entity
, cr
);
376 * @container: an #AdgContainer
377 * @entity: an #AdgEntity
379 * Emits a #AdgContainer::add signal on @container passing
380 * @entity as argument.
382 * @entity may be added to only one container at a time; you can't
383 * place the same entity inside two different containers.
386 adg_container_add(AdgContainer
*container
, AdgEntity
*entity
)
388 g_return_if_fail(ADG_IS_CONTAINER(container
));
389 g_return_if_fail(ADG_IS_ENTITY(entity
));
391 g_signal_emit(container
, signals
[ADD
], 0, entity
);
395 * adg_container_remove:
396 * @container: an #AdgContainer
397 * @entity: an #AdgEntity
399 * Emits a #AdgContainer::remove signal on @container passing
400 * @entity as argument. @entity must be inside @container.
402 * Note that @container will own a reference to @entity
403 * and that this may be the last reference held; so removing an
404 * entity from its container can destroy it.
406 * If you want to use @entity again, you need to add a reference
407 * to it, using g_object_ref(), before removing it from @container.
409 * If you don't want to use @entity again, it's usually more
410 * efficient to simply destroy it directly using g_object_unref()
411 * since this will remove it from the container.
414 adg_container_remove(AdgContainer
*container
, AdgEntity
*entity
)
416 g_return_if_fail(ADG_IS_CONTAINER(container
));
417 g_return_if_fail(ADG_IS_ENTITY(entity
));
419 g_signal_emit(container
, signals
[REMOVE
], 0, entity
);
423 * adg_container_get_children:
424 * @container: an #AdgContainer
426 * Gets the children list of @container.
427 * This list must be manually freed when no longer user.
429 * Returns: a newly allocated #GSList or %NULL on error
432 adg_container_get_children(AdgContainer
*container
)
434 g_return_val_if_fail(ADG_IS_CONTAINER(container
), NULL
);
436 return ADG_CONTAINER_GET_CLASS(container
)->get_children(container
);
440 * adg_container_foreach:
441 * @container: an #AdgContainer
442 * @callback: a callback
443 * @user_data: callback user data
445 * Invokes @callback on each child of @container.
446 * The callback should be declared as:
449 * void callback(AdgEntity *entity, gpointer user_data);
453 adg_container_foreach(AdgContainer
*container
,
454 GCallback callback
, gpointer user_data
)
458 g_return_if_fail(ADG_IS_CONTAINER(container
));
459 g_return_if_fail(callback
!= NULL
);
461 children
= adg_container_get_children (container
);
465 ((void (*) (gpointer
, gpointer
)) callback
) (children
->data
, user_data
);
467 children
= g_slist_delete_link(children
, children
);
472 * adg_container_propagate:
473 * @container: an #AdgContainer
474 * @signal_id: the signal id
475 * @detail: the detail
476 * @...: parameters to be passed to the signal, followed by a location for
477 * the return value. If the return type of the signal is G_TYPE_NONE,
478 * the return value location can be omitted.
480 * Emits the specified signal to all the children of @container
481 * using g_signal_emit_valist() calls.
484 adg_container_propagate(AdgContainer
*container
,
485 guint signal_id
, GQuark detail
, ...)
489 va_start(var_args
, detail
);
490 adg_container_propagate_valist(container
, signal_id
, detail
, var_args
);
495 * adg_container_propagate_by_name:
496 * @container: an #AdgContainer
497 * @detailed_signal: a string of the form "signal-name::detail".
498 * @...: a list of parameters to be passed to the signal, followed by
499 * a location for the return value. If the return type of the signal
500 * is G_TYPE_NONE, the return value location can be omitted.
502 * Emits the specified signal to all the children of @container
503 * using g_signal_emit_valist() calls.
506 adg_container_propagate_by_name(AdgContainer
*container
,
507 const gchar
*detailed_signal
, ...)
513 if (!g_signal_parse_name(detailed_signal
, G_TYPE_FROM_INSTANCE(container
),
514 &signal_id
, &detail
, FALSE
)) {
515 g_warning("%s: signal `%s' is invalid for instance `%p'",
516 G_STRLOC
, detailed_signal
, container
);
520 va_start(var_args
, detailed_signal
);
521 adg_container_propagate_valist(container
, signal_id
, detail
, var_args
);
526 * adg_container_propagate_valist:
527 * @container: an #AdgContainer
528 * @signal_id: the signal id
529 * @detail: the detail
530 * @var_args: a list of parameters to be passed to the signal, followed by a
531 * 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.
538 adg_container_propagate_valist(AdgContainer
*container
,
539 guint signal_id
, GQuark detail
, va_list var_args
)
544 g_return_if_fail(ADG_IS_CONTAINER(container
));
546 children
= adg_container_get_children(container
);
549 if (children
->data
) {
550 G_VA_COPY(var_copy
, var_args
);
551 g_signal_emit_valist(children
->data
, signal_id
, detail
, var_copy
);
554 children
= g_slist_delete_link(children
, children
);
559 * adg_container_get_model_transformation:
560 * @container: an #AdgContainer
562 * Returns the transformation to be combined with the transformations of the
563 * parent hierarchy to get the final matrix to be applied in the model space.
565 * Return value: the model transformation
568 adg_container_get_model_transformation(AdgContainer
*container
)
570 g_return_val_if_fail(ADG_IS_CONTAINER(container
), NULL
);
571 return &container
->priv
->model_transformation
;
575 * adg_container_set_model_transformation:
576 * @container: an #AdgContainer
577 * @transformation: the new model transformation
579 * Sets the transformation to be applied in model space.
582 adg_container_set_model_transformation(AdgContainer
*container
,
583 AdgMatrix
*transformation
)
587 const AdgMatrix
*parent_matrix
;
589 g_return_if_fail(ADG_IS_CONTAINER(container
));
590 g_return_if_fail(transformation
!= NULL
);
592 entity
= (AdgEntity
*) container
;
593 parent
= (AdgEntity
*) adg_entity_get_parent(entity
);
594 parent_matrix
= parent
? adg_entity_get_model_matrix(parent
) : NULL
;
596 adg_matrix_set(&container
->priv
->model_transformation
, transformation
);
597 adg_entity_model_matrix_changed(entity
, parent_matrix
);
601 * adg_container_get_paper_transformation:
602 * @container: an #AdgContainer
604 * Returns the transformation to be combined with the transformations of the
605 * parent hierarchy to get the final matrix to be applied in the paper space.
607 * Return value: the paper transformation
610 adg_container_get_paper_transformation(AdgContainer
*container
)
612 g_return_val_if_fail(ADG_IS_CONTAINER(container
), NULL
);
613 return &container
->priv
->paper_transformation
;
617 * adg_container_set_paper_transformation:
618 * @container: an #AdgContainer
619 * @transformation: the new paper transformation
621 * Sets the transformation to be applied in paper space.
624 adg_container_set_paper_transformation(AdgContainer
*container
,
625 AdgMatrix
*transformation
)
629 const AdgMatrix
*parent_matrix
;
631 g_return_if_fail(ADG_IS_CONTAINER(container
));
632 g_return_if_fail(transformation
!= NULL
);
634 entity
= (AdgEntity
*) container
;
635 parent
= (AdgEntity
*) adg_entity_get_parent(entity
);
636 parent_matrix
= parent
? adg_entity_get_paper_matrix(parent
) : NULL
;
638 adg_matrix_set(&container
->priv
->paper_transformation
, transformation
);
639 adg_entity_paper_matrix_changed(entity
, parent_matrix
);