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 * @short_description: Base class for entity that can contain other entities
25 * The #AdgContainer is an entity that can contains more sub-entities.
26 * Each AdgContainer has its paper matrix (#AdgContainer:paper_matrix) to be
27 * used on paper-dependent references (such as font and arrow sizes, line
28 * thickness etc...) and a model matrix usually applied to the model view. See
29 * http://adg.entidi.com/tutorial/view/3 for details on this topic.
31 * This means an AdgContainer can be thought as a group of entities with the
32 * same geometrical identity (same scale, reference point ecc...).
38 * All fields are private and should not be used directly.
39 * Use its public methods instead.
42 #include "adg-container.h"
43 #include "adg-container-private.h"
50 PROP_MODEL_TRANSFORMATION
,
51 PROP_PAPER_TRANSFORMATION
61 static void get_property (GObject
*object
,
65 static void set_property (GObject
*object
,
69 static void dispose (GObject
*object
);
70 static const AdgMatrix
*get_model_matrix (AdgEntity
*entity
);
71 static const AdgMatrix
*get_paper_matrix (AdgEntity
*entity
);
72 static void model_matrix_changed (AdgEntity
*entity
,
73 AdgMatrix
*parent_matrix
);
74 static void paper_matrix_changed (AdgEntity
*entity
,
75 AdgMatrix
*parent_matrix
);
76 static GSList
* get_children (AdgContainer
*container
);
77 static gboolean
add (AdgContainer
*container
,
79 static void real_add (AdgContainer
*container
,
82 static gboolean
remove (AdgContainer
*container
,
84 static void real_remove (AdgContainer
*container
,
87 static void invalidate (AdgEntity
*entity
);
88 static void render (AdgEntity
*entity
,
91 static guint signals
[LAST_SIGNAL
] = { 0 };
94 G_DEFINE_TYPE(AdgContainer
, adg_container
, ADG_TYPE_ENTITY
)
98 adg_container_class_init(AdgContainerClass
*klass
)
100 GObjectClass
*gobject_class
;
101 AdgEntityClass
*entity_class
;
104 GType param_types
[1];
106 gobject_class
= (GObjectClass
*) klass
;
107 entity_class
= (AdgEntityClass
*) klass
;
109 g_type_class_add_private(klass
, sizeof(AdgContainerPrivate
));
111 gobject_class
->get_property
= get_property
;
112 gobject_class
->set_property
= set_property
;
113 gobject_class
->dispose
= dispose
;
115 entity_class
->model_matrix_changed
= model_matrix_changed
;
116 entity_class
->paper_matrix_changed
= paper_matrix_changed
;
117 entity_class
->get_model_matrix
= get_model_matrix
;
118 entity_class
->get_paper_matrix
= get_paper_matrix
;
119 entity_class
->invalidate
= invalidate
;
120 entity_class
->render
= render
;
122 klass
->get_children
= get_children
;
124 klass
->remove
= remove
;
126 param
= g_param_spec_boxed("child",
128 P_("Can be used to add a new child to the container"),
129 ADG_TYPE_ENTITY
, G_PARAM_WRITABLE
);
130 g_object_class_install_property(gobject_class
, PROP_CHILD
, param
);
132 param
= g_param_spec_boxed("model-transformation",
133 P_("The model transformation"),
134 P_("The model transformation to be applied to this container and its children entities"),
135 ADG_TYPE_MATRIX
, G_PARAM_READWRITE
);
136 g_object_class_install_property(gobject_class
,
137 PROP_MODEL_TRANSFORMATION
, param
);
139 param
= g_param_spec_boxed("paper-transformation",
140 P_("The paper transformation"),
141 P_("The paper transformation to be applied to this container and its children entities"),
142 ADG_TYPE_MATRIX
, G_PARAM_READWRITE
);
143 g_object_class_install_property(gobject_class
,
144 PROP_PAPER_TRANSFORMATION
, param
);
148 * @container: an #AdgContainer
149 * @entity: the #AdgEntity to add
151 * Adds @entity to @container.
153 closure
= g_cclosure_new(G_CALLBACK(real_add
), (gpointer
)0xdeadbeaf, NULL
);
154 param_types
[0] = G_TYPE_OBJECT
;
155 signals
[ADD
] = g_signal_newv("add", ADG_TYPE_CONTAINER
,
156 G_SIGNAL_RUN_FIRST
, closure
, NULL
, NULL
,
157 g_cclosure_marshal_VOID__OBJECT
,
158 G_TYPE_NONE
, 1, param_types
);
161 * AdgContainer::remove:
162 * @container: an #AdgContainer
163 * @entity: the #AdgEntity to remove
165 * Removes @entity from @container.
167 closure
= g_cclosure_new(G_CALLBACK(real_remove
), (gpointer
)0xdeadbeaf, NULL
);
168 param_types
[0] = G_TYPE_OBJECT
;
169 signals
[REMOVE
] = g_signal_newv("remove", ADG_TYPE_CONTAINER
,
170 G_SIGNAL_RUN_FIRST
, closure
, NULL
, NULL
,
171 g_cclosure_marshal_VOID__OBJECT
,
172 G_TYPE_NONE
, 1, param_types
);
176 adg_container_init(AdgContainer
*container
)
178 AdgContainerPrivate
*data
= G_TYPE_INSTANCE_GET_PRIVATE(container
,
180 AdgContainerPrivate
);
182 data
->children
= NULL
;
183 cairo_matrix_init_identity(&data
->model_transformation
);
184 cairo_matrix_init_identity(&data
->paper_transformation
);
185 cairo_matrix_init_identity(&data
->model_matrix
);
186 cairo_matrix_init_identity(&data
->paper_matrix
);
188 container
->data
= data
;
192 get_property(GObject
*object
, guint prop_id
, GValue
*value
, GParamSpec
*pspec
)
194 AdgContainerPrivate
*data
= ((AdgContainer
*) object
)->data
;
197 case PROP_MODEL_TRANSFORMATION
:
198 g_value_set_boxed(value
, &data
->model_transformation
);
200 case PROP_PAPER_TRANSFORMATION
:
201 g_value_set_boxed(value
, &data
->paper_transformation
);
204 G_OBJECT_WARN_INVALID_PROPERTY_ID(object
, prop_id
, pspec
);
210 set_property(GObject
*object
,
211 guint prop_id
, const GValue
*value
, GParamSpec
*pspec
)
213 AdgContainer
*container
;
214 AdgContainerPrivate
*data
;
216 container
= (AdgContainer
*) object
;
217 data
= container
->data
;
221 adg_container_add(container
, g_value_get_object(value
));
223 case PROP_MODEL_TRANSFORMATION
:
224 adg_matrix_copy(&data
->model_transformation
, g_value_get_boxed(value
));
226 case PROP_PAPER_TRANSFORMATION
:
227 adg_matrix_copy(&data
->paper_transformation
, g_value_get_boxed(value
));
230 G_OBJECT_WARN_INVALID_PROPERTY_ID(object
, prop_id
, pspec
);
235 dispose(GObject
*object
)
237 GObjectClass
*object_class
= (GObjectClass
*) adg_container_parent_class
;
239 adg_container_foreach((AdgContainer
*) object
,
240 G_CALLBACK(adg_entity_unparent
), NULL
);
242 if (object_class
->dispose
!= NULL
)
243 object_class
->dispose(object
);
248 get_children(AdgContainer
*container
)
250 AdgContainerPrivate
*data
= container
->data
;
252 return g_slist_copy(data
->children
);
256 add(AdgContainer
*container
, AdgEntity
*entity
)
258 AdgContainerPrivate
*data
= container
->data
;
260 data
->children
= g_slist_append(data
->children
, entity
);
266 real_add(AdgContainer
*container
, AdgEntity
*entity
, gpointer user_data
)
268 AdgContainer
*old_parent
;
270 g_assert(user_data
== (gpointer
) 0xdeadbeaf);
272 old_parent
= adg_entity_get_parent(entity
);
274 if (old_parent
!= NULL
) {
275 g_warning("Attempting to add an object with type %s to a container "
276 "of type %s, but the object is already inside a container "
278 g_type_name(G_OBJECT_TYPE(entity
)),
279 g_type_name(G_OBJECT_TYPE(container
)),
280 g_type_name(G_OBJECT_TYPE(old_parent
)));
284 if (ADG_CONTAINER_GET_CLASS(container
)->add(container
, entity
))
285 adg_entity_set_parent(entity
, container
);
287 g_signal_stop_emission(container
, signals
[ADD
], 0);
291 remove(AdgContainer
*container
, AdgEntity
*entity
)
293 AdgContainerPrivate
*data
;
296 data
= container
->data
;
297 node
= g_slist_find(data
->children
, entity
);
302 data
->children
= g_slist_delete_link(data
->children
, node
);
308 real_remove(AdgContainer
*container
, AdgEntity
*entity
, gpointer user_data
)
310 g_assert(user_data
== (gpointer
) 0xdeadbeaf);
312 if (ADG_CONTAINER_GET_CLASS(container
)->remove(container
, entity
))
313 adg_entity_unparent(entity
);
315 g_signal_stop_emission(container
, signals
[REMOVE
], 0);
320 model_matrix_changed(AdgEntity
*entity
, AdgMatrix
*parent_matrix
)
322 AdgContainer
*container
;
323 AdgContainerPrivate
*data
;
324 AdgEntityClass
*entity_class
;
326 container
= (AdgContainer
*) entity
;
327 data
= container
->data
;
328 entity_class
= (AdgEntityClass
*) adg_container_parent_class
;
330 if (entity_class
->model_matrix_changed
!= NULL
)
331 entity_class
->model_matrix_changed(entity
, parent_matrix
);
334 cairo_matrix_multiply(&data
->model_matrix
,
335 &data
->model_transformation
,
338 adg_matrix_copy(&data
->model_matrix
, &data
->model_transformation
);
340 adg_container_propagate_by_name(container
, "model-matrix-changed",
341 &data
->model_matrix
);
345 paper_matrix_changed(AdgEntity
*entity
, AdgMatrix
*parent_matrix
)
347 AdgContainer
*container
;
348 AdgContainerPrivate
*data
;
349 AdgEntityClass
*entity_class
;
351 container
= (AdgContainer
*) entity
;
352 data
= container
->data
;
353 entity_class
= (AdgEntityClass
*) adg_container_parent_class
;
355 if (entity_class
->paper_matrix_changed
!= NULL
)
356 entity_class
->paper_matrix_changed(entity
, parent_matrix
);
359 cairo_matrix_multiply(&data
->paper_matrix
,
360 &data
->paper_transformation
,
363 adg_matrix_copy(&data
->paper_matrix
, &data
->paper_transformation
);
365 adg_container_propagate_by_name(container
, "paper-matrix-changed",
366 &data
->paper_matrix
);
369 static const AdgMatrix
*
370 get_model_matrix(AdgEntity
*entity
)
372 AdgContainerPrivate
*data
= ((AdgContainer
*) entity
)->data
;
374 return &data
->model_matrix
;
377 static const AdgMatrix
*
378 get_paper_matrix(AdgEntity
*entity
)
380 AdgContainerPrivate
*data
= ((AdgContainer
*) entity
)->data
;
382 return &data
->paper_matrix
;
387 invalidate(AdgEntity
*entity
)
389 AdgEntityClass
*entity_class
= (AdgEntityClass
*) adg_container_parent_class
;
391 adg_container_propagate_by_name((AdgContainer
*) entity
, "invalidate");
393 if (entity_class
->invalidate
)
394 entity_class
->invalidate(entity
);
398 render(AdgEntity
*entity
, cairo_t
*cr
)
400 AdgEntityClass
*entity_class
= (AdgEntityClass
*) adg_container_parent_class
;
402 cairo_set_matrix(cr
, adg_entity_get_model_matrix(entity
));
403 adg_container_propagate_by_name((AdgContainer
*) entity
, "render", cr
);
405 if (entity_class
->render
)
406 entity_class
->render(entity
, cr
);
413 * Creates a new container entity.
415 * Return value: the newly created entity
418 adg_container_new(void)
420 return (AdgEntity
*) g_object_new(ADG_TYPE_CONTAINER
, NULL
);
426 * @container: an #AdgContainer
427 * @entity: an #AdgEntity
429 * Emits a #AdgContainer::add signal on @container passing
430 * @entity as argument.
432 * @entity may be added to only one container at a time; you can't
433 * place the same entity inside two different containers.
436 adg_container_add(AdgContainer
*container
, AdgEntity
*entity
)
438 g_return_if_fail(ADG_IS_CONTAINER(container
));
439 g_return_if_fail(ADG_IS_ENTITY(entity
));
441 g_signal_emit(container
, signals
[ADD
], 0, entity
);
445 * adg_container_remove:
446 * @container: an #AdgContainer
447 * @entity: an #AdgEntity
449 * Emits a #AdgContainer::remove signal on @container passing
450 * @entity as argument. @entity must be inside @container.
452 * Note that @container will own a reference to @entity
453 * and that this may be the last reference held; so removing an
454 * entity from its container can destroy it.
456 * If you want to use @entity again, you need to add a reference
457 * to it, using g_object_ref(), before removing it from @container.
459 * If you don't want to use @entity again, it's usually more
460 * efficient to simply destroy it directly using g_object_unref()
461 * since this will remove it from the container.
464 adg_container_remove(AdgContainer
*container
, AdgEntity
*entity
)
466 g_return_if_fail(ADG_IS_CONTAINER(container
));
467 g_return_if_fail(ADG_IS_ENTITY(entity
));
469 g_signal_emit(container
, signals
[REMOVE
], 0, entity
);
473 * adg_container_get_children:
474 * @container: an #AdgContainer
476 * Gets the children list of @container.
477 * This list must be manually freed when no longer user.
479 * Returns: a newly allocated #GSList or %NULL on error
482 adg_container_get_children(AdgContainer
*container
)
484 g_return_val_if_fail(ADG_IS_CONTAINER(container
), NULL
);
486 return ADG_CONTAINER_GET_CLASS(container
)->get_children(container
);
490 * adg_container_foreach:
491 * @container: an #AdgContainer
492 * @callback: a callback
493 * @user_data: callback user data
495 * Invokes @callback on each child of @container.
496 * The callback should be declared as:
499 * void callback(AdgEntity *entity, gpointer user_data);
503 adg_container_foreach(AdgContainer
*container
,
504 GCallback callback
, gpointer user_data
)
508 g_return_if_fail(ADG_IS_CONTAINER(container
));
509 g_return_if_fail(callback
!= NULL
);
511 children
= adg_container_get_children (container
);
515 ((void (*) (gpointer
, gpointer
)) callback
) (children
->data
, user_data
);
517 children
= g_slist_delete_link(children
, children
);
522 * adg_container_propagate:
523 * @container: an #AdgContainer
524 * @signal_id: the signal id
525 * @detail: the detail
526 * @...: parameters to be passed to the signal, followed by a location for
527 * the return value. If the return type of the signal is G_TYPE_NONE,
528 * the return value location can be omitted.
530 * Emits the specified signal to all the children of @container
531 * using g_signal_emit_valist() calls.
534 adg_container_propagate(AdgContainer
*container
,
535 guint signal_id
, GQuark detail
, ...)
539 va_start(var_args
, detail
);
540 adg_container_propagate_valist(container
, signal_id
, detail
, var_args
);
545 * adg_container_propagate_by_name:
546 * @container: an #AdgContainer
547 * @detailed_signal: a string of the form "signal-name::detail".
548 * @...: a list of parameters to be passed to the signal, followed by
549 * a location for the return value. If the return type of the signal
550 * is G_TYPE_NONE, the return value location can be omitted.
552 * Emits the specified signal to all the children of @container
553 * using g_signal_emit_valist() calls.
556 adg_container_propagate_by_name(AdgContainer
*container
,
557 const gchar
*detailed_signal
, ...)
563 if (!g_signal_parse_name(detailed_signal
, G_TYPE_FROM_INSTANCE(container
),
564 &signal_id
, &detail
, FALSE
)) {
565 g_warning("%s: signal `%s' is invalid for instance `%p'",
566 G_STRLOC
, detailed_signal
, container
);
570 va_start(var_args
, detailed_signal
);
571 adg_container_propagate_valist(container
, signal_id
, detail
, var_args
);
576 * adg_container_propagate_valist:
577 * @container: an #AdgContainer
578 * @signal_id: the signal id
579 * @detail: the detail
580 * @var_args: a list of parameters to be passed to the signal, followed by a
581 * location for the return value. If the return type of the signal
582 * is G_TYPE_NONE, the return value location can be omitted.
584 * Emits the specified signal to all the children of @container
585 * using g_signal_emit_valist() calls.
588 adg_container_propagate_valist(AdgContainer
*container
,
589 guint signal_id
, GQuark detail
, va_list var_args
)
594 g_return_if_fail(ADG_IS_CONTAINER(container
));
596 children
= adg_container_get_children(container
);
599 if (children
->data
) {
600 G_VA_COPY(var_copy
, var_args
);
601 g_signal_emit_valist(children
->data
, signal_id
, detail
, var_copy
);
604 children
= g_slist_delete_link(children
, children
);
609 * adg_container_get_model_transformation:
610 * @container: an #AdgContainer
612 * Returns the transformation to be combined with the transformations of the
613 * parent hierarchy to get the final matrix to be applied in the model space.
615 * Return value: the model transformation
618 adg_container_get_model_transformation(AdgContainer
*container
)
620 AdgContainerPrivate
*data
;
622 g_return_val_if_fail(ADG_IS_CONTAINER(container
), NULL
);
624 data
= container
->data
;
626 return &data
->model_transformation
;
630 * adg_container_set_model_transformation:
631 * @container: an #AdgContainer
632 * @transformation: the new model transformation
634 * Sets the transformation to be applied in model space.
637 adg_container_set_model_transformation(AdgContainer
*container
,
638 const AdgMatrix
*transformation
)
641 AdgContainerPrivate
*data
;
643 const AdgMatrix
*parent_matrix
;
645 g_return_if_fail(ADG_IS_CONTAINER(container
));
646 g_return_if_fail(transformation
!= NULL
);
648 entity
= (AdgEntity
*) container
;
649 data
= container
->data
;
650 parent
= (AdgEntity
*) adg_entity_get_parent(entity
);
651 parent_matrix
= parent
? adg_entity_get_model_matrix(parent
) : NULL
;
653 adg_matrix_copy(&data
->model_transformation
, transformation
);
654 adg_entity_model_matrix_changed(entity
, parent_matrix
);
656 /* Temporary workaround: this function will be removed soon */
657 adg_entity_set_local_map(entity
, transformation
);
661 * adg_container_get_paper_transformation:
662 * @container: an #AdgContainer
664 * Returns the transformation to be combined with the transformations of the
665 * parent hierarchy to get the final matrix to be applied in the paper space.
667 * Return value: the paper transformation
670 adg_container_get_paper_transformation(AdgContainer
*container
)
672 AdgContainerPrivate
*data
;
674 g_return_val_if_fail(ADG_IS_CONTAINER(container
), NULL
);
676 data
= container
->data
;
678 return &data
->paper_transformation
;
682 * adg_container_set_paper_transformation:
683 * @container: an #AdgContainer
684 * @transformation: the new paper transformation
686 * Sets the transformation to be applied in paper space.
689 adg_container_set_paper_transformation(AdgContainer
*container
,
690 const AdgMatrix
*transformation
)
693 AdgContainerPrivate
*data
;
695 const AdgMatrix
*parent_matrix
;
697 g_return_if_fail(ADG_IS_CONTAINER(container
));
698 g_return_if_fail(transformation
!= NULL
);
700 entity
= (AdgEntity
*) container
;
701 data
= container
->data
;
702 parent
= (AdgEntity
*) adg_entity_get_parent(entity
);
703 parent_matrix
= parent
? adg_entity_get_paper_matrix(parent
) : NULL
;
705 adg_matrix_copy(&data
->paper_transformation
, transformation
);
706 adg_entity_paper_matrix_changed(entity
, parent_matrix
);
708 /* Temporary workaround: this function will be removed soon */
709 adg_entity_set_global_map(entity
, transformation
);