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 #include <gcontainer/gcontainer.h>
42 #define PARENT_CLASS ((AdgEntityClass *) adg_container_parent_class)
48 PROP_MODEL_TRANSFORMATION
,
49 PROP_PAPER_TRANSFORMATION
59 static void get_property (GObject
*object
,
63 static void set_property (GObject
*object
,
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
,
77 static void real_add (AdgContainer
*container
,
80 static gboolean
remove (AdgContainer
*container
,
82 static void real_remove (AdgContainer
*container
,
85 static void invalidate (AdgEntity
*entity
);
86 static void render (AdgEntity
*entity
,
89 static guint signals
[LAST_SIGNAL
] = { 0 };
92 G_DEFINE_TYPE(AdgContainer
, adg_container
, ADG_TYPE_ENTITY
)
96 adg_container_class_init(AdgContainerClass
*klass
)
98 GObjectClass
*gobject_class
;
99 AdgEntityClass
*entity_class
;
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
;
122 klass
->remove
= remove
;
124 param
= g_param_spec_boxed("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
);
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
);
174 adg_container_init(AdgContainer
*container
)
176 AdgContainerPrivate
*priv
= G_TYPE_INSTANCE_GET_PRIVATE(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
;
190 get_property(GObject
*object
, guint prop_id
, GValue
*value
, GParamSpec
*pspec
)
192 AdgContainer
*container
= (AdgContainer
*) object
;
195 case PROP_MODEL_TRANSFORMATION
:
196 g_value_set_boxed(value
, &container
->priv
->model_transformation
);
198 case PROP_PAPER_TRANSFORMATION
:
199 g_value_set_boxed(value
, &container
->priv
->paper_transformation
);
202 G_OBJECT_WARN_INVALID_PROPERTY_ID(object
, prop_id
, pspec
);
208 set_property(GObject
*object
,
209 guint prop_id
, const GValue
*value
, GParamSpec
*pspec
)
211 AdgContainer
*container
= (AdgContainer
*) object
;
215 adg_container_add(container
, g_value_get_object(value
));
217 case PROP_MODEL_TRANSFORMATION
:
218 adg_matrix_set(&container
->priv
->model_transformation
,
219 g_value_get_boxed(value
));
221 case PROP_PAPER_TRANSFORMATION
:
222 adg_matrix_set(&container
->priv
->paper_transformation
,
223 g_value_get_boxed(value
));
226 G_OBJECT_WARN_INVALID_PROPERTY_ID(object
, prop_id
, pspec
);
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
);
242 get_children(AdgContainer
*container
)
244 return g_slist_copy(container
->priv
->children
);
248 add(AdgContainer
*container
, AdgEntity
*entity
)
250 container
->priv
->children
= g_slist_append(container
->priv
->children
,
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 "
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
)));
274 if (ADG_CONTAINER_GET_CLASS(container
)->add(container
, entity
))
275 adg_entity_set_parent(entity
, container
);
277 g_signal_stop_emission(container
, signals
[ADD
], 0);
281 remove(AdgContainer
*container
, AdgEntity
*entity
)
283 GSList
*node
= g_slist_find(container
->priv
->children
, entity
);
288 container
->priv
->children
= g_slist_delete_link(container
->priv
->children
,
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
);
301 g_signal_stop_emission(container
, signals
[REMOVE
], 0);
306 model_matrix_changed(AdgEntity
*entity
, AdgMatrix
*parent_matrix
)
308 AdgContainer
*container
= (AdgContainer
*) entity
;
310 PARENT_CLASS
->model_matrix_changed(entity
, parent_matrix
);
313 cairo_matrix_multiply(&container
->priv
->model_matrix
,
315 &container
->priv
->model_transformation
);
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
);
325 paper_matrix_changed(AdgEntity
*entity
, AdgMatrix
*parent_matrix
)
327 AdgContainer
*container
= (AdgContainer
*) entity
;
329 PARENT_CLASS
->paper_matrix_changed(entity
, parent_matrix
);
332 cairo_matrix_multiply(&container
->priv
->paper_matrix
,
334 &container
->priv
->paper_transformation
);
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
;
361 invalidate(AdgEntity
*entity
)
363 adg_container_propagate_by_name((AdgContainer
*) entity
, "invalidate");
364 PARENT_CLASS
->invalidate(entity
);
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
);
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.
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.
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
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:
451 * void callback(AdgEntity *entity, gpointer user_data);
455 adg_container_foreach(AdgContainer
*container
,
456 GCallback callback
, gpointer user_data
)
460 g_return_if_fail(ADG_IS_CONTAINER(container
));
461 g_return_if_fail(callback
!= NULL
);
463 children
= adg_container_get_children (container
);
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.
486 adg_container_propagate(AdgContainer
*container
,
487 guint signal_id
, GQuark detail
, ...)
491 va_start(var_args
, detail
);
492 adg_container_propagate_valist(container
, signal_id
, detail
, 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.
508 adg_container_propagate_by_name(AdgContainer
*container
,
509 const gchar
*detailed_signal
, ...)
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
);
522 va_start(var_args
, detailed_signal
);
523 adg_container_propagate_valist(container
, signal_id
, detail
, 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.
540 adg_container_propagate_valist(AdgContainer
*container
,
541 guint signal_id
, GQuark detail
, va_list var_args
)
546 g_return_if_fail(ADG_IS_CONTAINER(container
));
548 children
= adg_container_get_children(container
);
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
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.
584 adg_container_set_model_transformation(AdgContainer
*container
,
585 AdgMatrix
*transformation
)
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
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.
626 adg_container_set_paper_transformation(AdgContainer
*container
,
627 AdgMatrix
*transformation
)
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
);