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.
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...).
39 * All fields are private and should not be used directly.
40 * Use its public methods instead.
43 #include "adg-container.h"
44 #include "adg-container-private.h"
51 PROP_MODEL_TRANSFORMATION
,
52 PROP_PAPER_TRANSFORMATION
62 static void get_property (GObject
*object
,
66 static void set_property (GObject
*object
,
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
,
80 static void real_add (AdgContainer
*container
,
83 static gboolean
remove (AdgContainer
*container
,
85 static void real_remove (AdgContainer
*container
,
88 static void invalidate (AdgEntity
*entity
);
89 static void render (AdgEntity
*entity
,
92 static guint signals
[LAST_SIGNAL
] = { 0 };
95 G_DEFINE_TYPE(AdgContainer
, adg_container
, ADG_TYPE_ENTITY
)
99 adg_container_class_init(AdgContainerClass
*klass
)
101 GObjectClass
*gobject_class
;
102 AdgEntityClass
*entity_class
;
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
;
125 klass
->remove
= remove
;
127 param
= g_param_spec_boxed("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
);
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
);
177 adg_container_init(AdgContainer
*container
)
179 AdgContainerPrivate
*data
= G_TYPE_INSTANCE_GET_PRIVATE(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
;
193 get_property(GObject
*object
, guint prop_id
, GValue
*value
, GParamSpec
*pspec
)
195 AdgContainerPrivate
*data
= ((AdgContainer
*) object
)->data
;
198 case PROP_MODEL_TRANSFORMATION
:
199 g_value_set_boxed(value
, &data
->model_transformation
);
201 case PROP_PAPER_TRANSFORMATION
:
202 g_value_set_boxed(value
, &data
->paper_transformation
);
205 G_OBJECT_WARN_INVALID_PROPERTY_ID(object
, prop_id
, pspec
);
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
;
222 adg_container_add(container
, g_value_get_object(value
));
224 case PROP_MODEL_TRANSFORMATION
:
225 adg_matrix_copy(&data
->model_transformation
, g_value_get_boxed(value
));
227 case PROP_PAPER_TRANSFORMATION
:
228 adg_matrix_copy(&data
->paper_transformation
, g_value_get_boxed(value
));
231 G_OBJECT_WARN_INVALID_PROPERTY_ID(object
, prop_id
, pspec
);
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
);
249 get_children(AdgContainer
*container
)
251 AdgContainerPrivate
*data
= container
->data
;
253 return g_slist_copy(data
->children
);
257 add(AdgContainer
*container
, AdgEntity
*entity
)
259 AdgContainerPrivate
*data
= container
->data
;
261 data
->children
= g_slist_append(data
->children
, entity
);
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 "
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
)));
285 if (ADG_CONTAINER_GET_CLASS(container
)->add(container
, entity
))
286 adg_entity_set_parent(entity
, container
);
288 g_signal_stop_emission(container
, signals
[ADD
], 0);
292 remove(AdgContainer
*container
, AdgEntity
*entity
)
294 AdgContainerPrivate
*data
;
297 data
= container
->data
;
298 node
= g_slist_find(data
->children
, entity
);
303 data
->children
= g_slist_delete_link(data
->children
, node
);
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
);
316 g_signal_stop_emission(container
, signals
[REMOVE
], 0);
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
);
335 cairo_matrix_multiply(&data
->model_matrix
,
336 &data
->model_transformation
,
339 adg_matrix_copy(&data
->model_matrix
, &data
->model_transformation
);
341 adg_container_propagate_by_name(container
, "model-matrix-changed",
342 &data
->model_matrix
);
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
);
360 cairo_matrix_multiply(&data
->paper_matrix
,
361 &data
->paper_transformation
,
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
;
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
);
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
);
414 * Creates a new container entity.
416 * Return value: the newly created entity
419 adg_container_new(void)
421 return (AdgEntity
*) g_object_new(ADG_TYPE_CONTAINER
, NULL
);
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.
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.
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
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:
500 * void callback(AdgEntity *entity, gpointer user_data);
504 adg_container_foreach(AdgContainer
*container
,
505 GCallback callback
, gpointer user_data
)
509 g_return_if_fail(ADG_IS_CONTAINER(container
));
510 g_return_if_fail(callback
!= NULL
);
512 children
= adg_container_get_children (container
);
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.
535 adg_container_propagate(AdgContainer
*container
,
536 guint signal_id
, GQuark detail
, ...)
540 va_start(var_args
, detail
);
541 adg_container_propagate_valist(container
, signal_id
, detail
, 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.
557 adg_container_propagate_by_name(AdgContainer
*container
,
558 const gchar
*detailed_signal
, ...)
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
);
571 va_start(var_args
, detailed_signal
);
572 adg_container_propagate_valist(container
, signal_id
, detail
, 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.
589 adg_container_propagate_valist(AdgContainer
*container
,
590 guint signal_id
, GQuark detail
, va_list var_args
)
595 g_return_if_fail(ADG_IS_CONTAINER(container
));
597 children
= adg_container_get_children(container
);
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
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.
638 adg_container_set_model_transformation(AdgContainer
*container
,
639 const AdgMatrix
*transformation
)
642 AdgContainerPrivate
*data
;
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
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.
690 adg_container_set_paper_transformation(AdgContainer
*container
,
691 const AdgMatrix
*transformation
)
694 AdgContainerPrivate
*data
;
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
);