tests: additional checks on container types
[adg.git] / src / adg / adg-entity.c
blob45493736150e4aabc468795101b096a4a9b21b17
1 /* ADG - Automatic Drawing Generation
2 * Copyright (C) 2007-2015 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-entity
23 * @short_description: The base class for renderable objects
25 * This abstract class provides the base for all renderable objects.
27 * To provide a proper #AdgEntity derived type, you must at least
28 * implement its arrange() and render() virtual methods. Also, if
29 * you are using some sort of caching, ensure to clear it in the
30 * invalidate() method.
32 * Since: 1.0
33 **/
35 /**
36 * AdgEntity:
38 * All fields are private and should not be used directly.
39 * Use its public methods instead.
41 * Since: 1.0
42 **/
44 /**
45 * AdgEntityClass:
46 * @destroy: when a destroy request has been explicitely requested
47 * @parent_set: called whenever the parent of an entity has changed
48 * @global_changed: the global matrix has been invalidated
49 * @local_changed: the local matrix has been invalidated
50 * @invalidate: invalidating callback, used to clear the internal cache
51 * @arrange: prepare the layout and fill the extents struct
52 * @render: rendering callback, it must be implemented by every entity
54 * Any entity (if not abstract) must implement at least the @render method.
55 * The other signal handlers can be overriden to provide custom behaviors
56 * and usually must chain up the original handler.
58 * Since: 1.0
59 **/
61 /**
62 * AdgEntityCallback:
63 * @entity: an #AdgEntity
64 * @user_data: a general purpose pointer
66 * Callback used when inspecting or browsing entities. For example,
67 * it is passed to adg_model_foreach_dependency() to perform an
68 * operation on all the entities depending on an #AdgModel.
70 * Since: 1.0
71 **/
74 #include "adg-internal.h"
75 #if GTK3_ENABLED || GTK2_ENABLED
76 #include <gtk/gtk.h>
77 #endif
79 #include "adg-container.h"
80 #include "adg-table.h"
81 #include "adg-title-block.h"
82 #include <adg-canvas.h>
83 #include "adg-dress.h"
84 #include "adg-style.h"
85 #include "adg-model.h"
86 #include "adg-point.h"
87 #include "adg-cairo-fallback.h"
89 #include "adg-entity-private.h"
92 #define _ADG_OLD_OBJECT_CLASS ((GObjectClass *) adg_entity_parent_class)
95 G_DEFINE_ABSTRACT_TYPE(AdgEntity, adg_entity, G_TYPE_INITIALLY_UNOWNED)
97 enum {
98 PROP_0,
99 PROP_PARENT,
100 PROP_GLOBAL_MAP,
101 PROP_LOCAL_MAP,
102 PROP_LOCAL_MIX
105 enum {
106 DESTROY,
107 PARENT_SET,
108 GLOBAL_CHANGED,
109 LOCAL_CHANGED,
110 INVALIDATE,
111 ARRANGE,
112 RENDER,
113 LAST_SIGNAL
117 static void _adg_dispose (GObject *object);
118 static void _adg_get_property (GObject *object,
119 guint prop_id,
120 GValue *value,
121 GParamSpec *pspec);
122 static void _adg_set_property (GObject *object,
123 guint prop_id,
124 const GValue *value,
125 GParamSpec *pspec);
126 static void _adg_set_parent (AdgEntity *entity,
127 AdgEntity *parent);
128 static void _adg_global_changed (AdgEntity *entity);
129 static void _adg_local_changed (AdgEntity *entity);
130 static void _adg_real_invalidate (AdgEntity *entity);
131 static void _adg_real_arrange (AdgEntity *entity);
132 static void _adg_real_render (AdgEntity *entity,
133 cairo_t *cr);
134 static guint _adg_signals[LAST_SIGNAL] = { 0 };
135 static gboolean _adg_show_extents = FALSE;
138 static void
139 adg_entity_class_init(AdgEntityClass *klass)
141 GObjectClass *gobject_class;
142 GParamSpec *param;
143 GClosure *closure;
144 GType param_types[1];
146 gobject_class = (GObjectClass *) klass;
148 g_type_class_add_private(klass, sizeof(AdgEntityPrivate));
150 gobject_class->dispose = _adg_dispose;
151 gobject_class->get_property = _adg_get_property;
152 gobject_class->set_property = _adg_set_property;
154 klass->destroy = (void (*)(AdgEntity *)) g_object_unref;
155 klass->parent_set = NULL;
156 klass->global_changed = _adg_global_changed;
157 klass->local_changed = _adg_local_changed;
158 klass->invalidate = NULL;
159 klass->arrange= NULL;
160 klass->render = NULL;
162 param = g_param_spec_object("parent",
163 P_("Parent Entity"),
164 P_("The parent entity of this entity or NULL if this is a top-level entity"),
165 ADG_TYPE_ENTITY,
166 G_PARAM_READWRITE);
167 g_object_class_install_property(gobject_class, PROP_PARENT, param);
169 param = g_param_spec_boxed("global-map",
170 P_("Global Map"),
171 P_("The transformation to be combined with the parent ones to get the global matrix"),
172 CAIRO_GOBJECT_TYPE_MATRIX,
173 G_PARAM_READWRITE);
174 g_object_class_install_property(gobject_class, PROP_GLOBAL_MAP, param);
176 param = g_param_spec_boxed("local-map",
177 P_("Local Map"),
178 P_("The local transformation that could be used to compute the local matrix in the way specified by the #AdgEntity:local-mix property"),
179 CAIRO_GOBJECT_TYPE_MATRIX,
180 G_PARAM_READWRITE);
181 g_object_class_install_property(gobject_class, PROP_LOCAL_MAP, param);
183 param = g_param_spec_enum("local-mix",
184 P_("Local Mix Method"),
185 P_("Define how the local maps of the entity and its ancestors should be combined to get the local matrix"),
186 ADG_TYPE_MIX, ADG_MIX_ANCESTORS,
187 G_PARAM_READWRITE);
188 g_object_class_install_property(gobject_class, PROP_LOCAL_MIX, param);
191 * AdgEntity::destroy:
192 * @entity: an #AdgEntity
194 * Emitted to explicitely destroy @entity. It unreferences
195 * @entity so that will be destroyed, unless the caller owns
196 * an additional references added with g_object_ref().
198 * In the usual case, this is equivalent of calling
199 * g_object_unref() on @entity but, for composite entities or
200 * containers, the destroy signal is propagated to the children.
202 * Since: 1.0
204 _adg_signals[DESTROY] =
205 g_signal_new("destroy",
206 G_OBJECT_CLASS_TYPE(gobject_class),
207 G_SIGNAL_RUN_FIRST,
208 G_STRUCT_OFFSET(AdgEntityClass, destroy),
209 NULL, NULL,
210 adg_marshal_VOID__VOID,
211 G_TYPE_NONE, 0);
214 * AdgEntity::parent-set:
215 * @entity: an #AdgEntity
216 * @old_parent: the old parent
218 * Emitted after the parent entity has changed. The new parent
219 * can be inspected using adg_entity_get_parent().
221 * It is allowed for both old and new parent to
222 * be <constant>NULL</constant>.
224 * Since: 1.0
226 _adg_signals[PARENT_SET] =
227 g_signal_new("parent-set",
228 G_OBJECT_CLASS_TYPE(gobject_class),
229 G_SIGNAL_RUN_FIRST,
230 G_STRUCT_OFFSET(AdgEntityClass, parent_set),
231 NULL, NULL,
232 adg_marshal_VOID__OBJECT,
233 G_TYPE_NONE, 1, ADG_TYPE_ENTITY);
236 * AdgEntity::global-changed:
237 * @entity: an #AdgEntity
239 * Emitted when the global map of @entity or any of its parent
240 * has changed. The default handler will compute the new global
241 * matrix, updating the internal cache.
243 * Since: 1.0
245 _adg_signals[GLOBAL_CHANGED] =
246 g_signal_new("global-changed",
247 G_OBJECT_CLASS_TYPE(gobject_class),
248 G_SIGNAL_RUN_FIRST,
249 G_STRUCT_OFFSET(AdgEntityClass, global_changed),
250 NULL, NULL,
251 adg_marshal_VOID__VOID,
252 G_TYPE_NONE, 0);
255 * AdgEntity::local-changed:
256 * @entity: an #AdgEntity
258 * Emitted when the local map of @entity or any of its parent
259 * has changed. The default handler will compute the new local
260 * matrix, updating the internal cache.
262 * Since: 1.0
264 _adg_signals[LOCAL_CHANGED] =
265 g_signal_new("local-changed",
266 G_OBJECT_CLASS_TYPE(gobject_class),
267 G_SIGNAL_RUN_FIRST,
268 G_STRUCT_OFFSET(AdgEntityClass, local_changed),
269 NULL, NULL,
270 adg_marshal_VOID__VOID,
271 G_TYPE_NONE, 0);
274 * AdgEntity::invalidate:
275 * @entity: an #AdgEntity
277 * Invalidates the whole @entity, that is resets all the cache
278 * (if present) built during the #AdgEntity::arrange signal.
279 * The resulting state is a clean entity, similar to what you
280 * have just before the first rendering.
282 * Since: 1.0
284 closure = g_cclosure_new(G_CALLBACK(_adg_real_invalidate), NULL, NULL);
285 _adg_signals[INVALIDATE] =
286 g_signal_newv("invalidate", ADG_TYPE_ENTITY,
287 G_SIGNAL_RUN_LAST, closure, NULL, NULL,
288 adg_marshal_VOID__VOID,
289 G_TYPE_NONE, 0, param_types);
292 * AdgEntity::arrange:
293 * @entity: an #AdgEntity
295 * Arranges the layout of @entity, updating the cache if necessary,
296 * and computes the extents of @entity.
298 * Since: 1.0
300 closure = g_cclosure_new(G_CALLBACK(_adg_real_arrange), NULL, NULL);
301 _adg_signals[ARRANGE] =
302 g_signal_newv("arrange", ADG_TYPE_ENTITY,
303 G_SIGNAL_RUN_LAST, closure, NULL, NULL,
304 adg_marshal_VOID__VOID,
305 G_TYPE_NONE, 0, param_types);
308 * AdgEntity::render:
309 * @entity: an #AdgEntity
310 * @cr: a #cairo_t drawing context
312 * Causes the rendering of @entity on @cr. A render signal will
313 * automatically emit #AdgEntity::arrange just before the real
314 * rendering on the cairo context.
316 * Since: 1.0
318 closure = g_cclosure_new(G_CALLBACK(_adg_real_render), NULL, NULL);
319 param_types[0] = G_TYPE_POINTER;
320 _adg_signals[RENDER] =
321 g_signal_newv("render", ADG_TYPE_ENTITY,
322 G_SIGNAL_RUN_LAST, closure, NULL, NULL,
323 adg_marshal_VOID__POINTER,
324 G_TYPE_NONE, 1, param_types);
327 static void
328 adg_entity_init(AdgEntity *entity)
330 AdgEntityPrivate *data = G_TYPE_INSTANCE_GET_PRIVATE(entity,
331 ADG_TYPE_ENTITY,
332 AdgEntityPrivate);
333 data->parent = NULL;
334 cairo_matrix_init_identity(&data->global_map);
335 cairo_matrix_init_identity(&data->local_map);
336 data->local_mix = ADG_MIX_ANCESTORS;
337 data->hash_styles = NULL;
338 data->global.is_defined = FALSE;
339 adg_matrix_copy(&data->global.matrix, adg_matrix_null());
340 data->local.is_defined = FALSE;
341 adg_matrix_copy(&data->local.matrix, adg_matrix_null());
342 data->extents.is_defined = FALSE;
344 entity->data = data;
347 static void
348 _adg_dispose(GObject *object)
350 AdgEntity *entity;
351 AdgEntityPrivate *data;
353 entity = (AdgEntity *) object;
354 data = entity->data;
356 /* This call will emit a "notify" signal for parent.
357 * Consequentially, the references to the old parent is dropped. */
358 adg_entity_set_parent(entity, NULL);
360 if (data->hash_styles != NULL) {
361 g_hash_table_destroy(data->hash_styles);
362 data->hash_styles = NULL;
365 if (_ADG_OLD_OBJECT_CLASS->dispose)
366 _ADG_OLD_OBJECT_CLASS->dispose(object);
369 static void
370 _adg_get_property(GObject *object, guint prop_id,
371 GValue *value, GParamSpec *pspec)
373 AdgEntity *entity;
374 AdgEntityPrivate *data;
376 entity = (AdgEntity *) object;
377 data = entity->data;
379 switch (prop_id) {
380 case PROP_PARENT:
381 g_value_set_object(value, data->parent);
382 break;
383 case PROP_GLOBAL_MAP:
384 g_value_set_boxed(value, &data->global_map);
385 break;
386 case PROP_LOCAL_MAP:
387 g_value_set_boxed(value, &data->local_map);
388 break;
389 case PROP_LOCAL_MIX:
390 g_value_set_enum(value, data->local_mix);
391 break;
392 default:
393 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
394 break;
398 static void
399 _adg_set_property(GObject *object, guint prop_id,
400 const GValue *value, GParamSpec *pspec)
402 AdgEntityPrivate *data = ((AdgEntity *) object)->data;
404 switch (prop_id) {
405 case PROP_PARENT:
406 _adg_set_parent((AdgEntity *) object,
407 (AdgEntity *) g_value_get_object(value));
408 break;
409 case PROP_GLOBAL_MAP:
410 adg_matrix_copy(&data->global_map, g_value_get_boxed(value));
411 data->global.is_defined = FALSE;
412 break;
413 case PROP_LOCAL_MAP:
414 adg_matrix_copy(&data->local_map, g_value_get_boxed(value));
415 data->local.is_defined = FALSE;
416 break;
417 case PROP_LOCAL_MIX:
418 data->local_mix = g_value_get_enum(value);
419 g_signal_emit(object, _adg_signals[LOCAL_CHANGED], 0);
420 break;
421 default:
422 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
423 break;
429 * adg_switch_extents:
430 * @state: new extents state
432 * Strokes (if @state is <constant>TRUE</constant>) a rectangle
433 * around every entity to show their extents. Useful for
434 * debugging purposes.
436 * Since: 1.0
438 void
439 adg_switch_extents(gboolean state)
441 _adg_show_extents = state;
445 * adg_entity_destroy:
446 * @entity: an #AdgEntity
448 * Emits the #AdgEntity::destroy signal on @entity and on all of
449 * its children, if any.
451 * Since: 1.0
453 void
454 adg_entity_destroy(AdgEntity *entity)
456 g_return_if_fail(ADG_IS_ENTITY(entity));
458 g_signal_emit(entity, _adg_signals[DESTROY], 0);
462 * adg_entity_get_canvas:
463 * @entity: an #AdgEntity
465 * Walks on the @entity hierarchy and gets the first parent of @entity,
466 * that is the first #AdgCanvas instance. The returned object is
467 * owned by @entity and should not be freed or modified.
469 * Returns: (transfer none): the requested canvas or <constant>NULL</constant> on errors or if there is no #AdgCanvas in the @entity hierarchy.
471 * Since: 1.0
473 AdgCanvas *
474 adg_entity_get_canvas(AdgEntity *entity)
476 g_return_val_if_fail(ADG_IS_ENTITY(entity), NULL);
478 while (entity) {
479 if (ADG_IS_CANVAS(entity))
480 return (AdgCanvas *) entity;
482 entity = adg_entity_get_parent(entity);
485 return NULL;
489 * adg_entity_set_parent:
490 * @entity: an #AdgEntity
491 * @parent: the parent entity
493 * <note><para>
494 * This function is only useful in entity implementations.
495 * </para></note>
497 * Sets a new parent on @entity. Changing the @parent of an entity
498 * emits the #AdgEntity::parent-set signal on it.
500 * There is no reference management at this level: they should be
501 * handled at a higher level, e.g. by #AdgContainer.
503 * Since: 1.0
505 void
506 adg_entity_set_parent(AdgEntity *entity, AdgEntity *parent)
508 g_return_if_fail(ADG_IS_ENTITY(entity));
509 g_object_set(entity, "parent", parent, NULL);
513 * adg_entity_get_parent:
514 * @entity: an #AdgEntity
516 * Gets the parent of @entity. The returned object is owned
517 * by @entity and should not be freed or modified.
519 * Returns: (transfer none): the parent entity or <constant>NULL</constant> on errors or if @entity is a toplevel.
521 * Since: 1.0
523 AdgEntity *
524 adg_entity_get_parent(AdgEntity *entity)
526 AdgEntityPrivate *data;
528 g_return_val_if_fail(ADG_IS_ENTITY(entity), NULL);
530 data = entity->data;
532 return data->parent;
536 * adg_entity_set_global_map:
537 * @entity: an #AdgEntity object
538 * @map: the new map
540 * Sets the new global transformation of @entity to @map:
541 * the old map is discarded. If @map is <constant>NULL</constant>,
542 * the global map is left unchanged.
544 * Since: 1.0
546 void
547 adg_entity_set_global_map(AdgEntity *entity, const cairo_matrix_t *map)
549 g_return_if_fail(ADG_IS_ENTITY(entity));
550 g_object_set(entity, "global-map", map, NULL);
554 * adg_entity_transform_global_map:
555 * @entity: an #AdgEntity object
556 * @transformation: the transformation to apply
557 * @mode: how @transformation should be applied
559 * Convenient function to change the global map of @entity by
560 * applying @tranformation using the @mode operator. This is
561 * logically equivalent to the following:
563 * <informalexample><programlisting language="C">
564 * cairo_matrix_t map;
565 * adg_matrix_copy(&map, adg_entity_get_global_map(entity));
566 * adg_matrix_transform(&map, transformation, mode);
567 * adg_entity_set_global_map(entity, &map);
568 * </programlisting></informalexample>
570 * Since: 1.0
572 void
573 adg_entity_transform_global_map(AdgEntity *entity,
574 const cairo_matrix_t *transformation,
575 AdgTransformMode mode)
577 AdgEntityPrivate *data;
578 cairo_matrix_t map;
580 g_return_if_fail(ADG_IS_ENTITY(entity));
581 g_return_if_fail(transformation != NULL);
583 data = entity->data;
585 adg_matrix_copy(&map, &data->global_map);
586 adg_matrix_transform(&map, transformation, mode);
588 g_object_set(entity, "global-map", &map, NULL);
592 * adg_entity_get_global_map:
593 * @entity: an #AdgEntity object
595 * Gets the transformation to be used to compute the global matrix
596 * of @entity.
598 * Returns: the requested map or <constant>NULL</constant> on errors.
600 * Since: 1.0
602 const cairo_matrix_t *
603 adg_entity_get_global_map(AdgEntity *entity)
605 AdgEntityPrivate *data;
607 g_return_val_if_fail(ADG_IS_ENTITY(entity), NULL);
609 data = entity->data;
611 return &data->global_map;
615 * adg_entity_get_global_matrix:
616 * @entity: an #AdgEntity object
618 * Gets the current global matrix of @entity. The returned value
619 * is owned by @entity and should not be changed or freed.
621 * The global matrix is computed in the arrange() phase by
622 * combining all the global maps of the @entity hierarchy using
623 * the %ADG_MIX_ANCESTORS method.
625 * Returns: the global matrix or <constant>NULL</constant> on errors.
627 * Since: 1.0
629 const cairo_matrix_t *
630 adg_entity_get_global_matrix(AdgEntity *entity)
632 AdgEntityPrivate *data;
634 g_return_val_if_fail(ADG_IS_ENTITY(entity), NULL);
636 data = entity->data;
638 return &data->global.matrix;
642 * adg_entity_set_local_map:
643 * @entity: an #AdgEntity object
644 * @map: the new map
646 * Sets the new local transformation of @entity to @map:
647 * the old map is discarded. If @map is <constant>NULL</constant>,
648 * the local map is left unchanged.
650 * Since: 1.0
652 void
653 adg_entity_set_local_map(AdgEntity *entity, const cairo_matrix_t *map)
655 g_return_if_fail(ADG_IS_ENTITY(entity));
656 g_object_set(entity, "local-map", map, NULL);
660 * adg_entity_transform_local_map:
661 * @entity: an #AdgEntity object
662 * @transformation: the transformation to apply
663 * @mode: how @transformation should be applied
665 * Convenient function to change the local map of @entity by
666 * applying @tranformation using the @mode operator. This is
667 * logically equivalent to the following:
669 * <informalexample><programlisting language="C">
670 * cairo_matrix_t map;
671 * adg_matrix_copy(&map, adg_entity_get_local_map(entity));
672 * adg_matrix_transform(&map, transformation, mode);
673 * adg_entity_set_local_map(entity, &map);
674 * </programlisting></informalexample>
676 * Since: 1.0
678 void
679 adg_entity_transform_local_map(AdgEntity *entity,
680 const cairo_matrix_t *transformation,
681 AdgTransformMode mode)
683 AdgEntityPrivate *data;
684 cairo_matrix_t map;
686 g_return_if_fail(ADG_IS_ENTITY(entity));
687 g_return_if_fail(transformation != NULL);
689 data = entity->data;
691 adg_matrix_copy(&map, &data->local_map);
692 adg_matrix_transform(&map, transformation, mode);
693 g_object_set(entity, "local-map", &map, NULL);
697 * adg_entity_get_local_map:
698 * @entity: an #AdgEntity object
700 * Gets the transformation to be used to compute the local matrix
701 * of @entity and store it in @map.
703 * Returns: the requested map or <constant>NULL</constant> on errors.
705 * Since: 1.0
707 const cairo_matrix_t *
708 adg_entity_get_local_map(AdgEntity *entity)
710 AdgEntityPrivate *data;
712 g_return_val_if_fail(ADG_IS_ENTITY(entity), NULL);
714 data = entity->data;
716 return &data->local_map;
720 * adg_entity_get_local_matrix:
721 * @entity: an #AdgEntity object
723 * Gets the current local matrix of @entity. The returned value
724 * is owned by @entity and should not be changed or freed.
726 * The local matrix is computed in the arrange() phase by
727 * combining all the local maps of the @entity hierarchy using
728 * the method specified by the #AdgEntity:local-mix property.
730 * Returns: the local matrix or <constant>NULL</constant> on errors.
732 * Since: 1.0
734 const cairo_matrix_t *
735 adg_entity_get_local_matrix(AdgEntity *entity)
737 AdgEntityPrivate *data;
739 g_return_val_if_fail(ADG_IS_ENTITY(entity), NULL);
741 data = entity->data;
743 return &data->local.matrix;
747 * adg_entity_set_local_mix:
748 * @entity: an #AdgEntity object
749 * @local_mix: new mix method
751 * Sets a new local mix method on @entity. The
752 * #AdgEntity:local-mix property defines how the local
753 * matrix must be computed: check out the #AdgMix
754 * documentation to know what are the availables methods
755 * and how they affect the local matrix computation.
757 * Setting a different local mix method emits an
758 * #AdgEntity::local-changed signal on @entity.
760 * Since: 1.0
762 void
763 adg_entity_set_local_mix(AdgEntity *entity, AdgMix local_mix)
765 g_return_if_fail(ADG_IS_ENTITY(entity));
766 g_object_set(entity, "local-mix", local_mix, NULL);
770 * adg_entity_get_local_mix:
771 * @entity: an #AdgEntity object
773 * Gets the local mix method of @entity. Check out the
774 * adg_entity_set_local_mix() documentation to know what the
775 * local mix method is used for.
777 * Returns: the local mix method of @entity or %ADG_MIX_UNDEFINED on errors
779 * Since: 1.0
781 AdgMix
782 adg_entity_get_local_mix(AdgEntity *entity)
784 AdgEntityPrivate *data;
786 g_return_val_if_fail(ADG_IS_ENTITY(entity), ADG_MIX_UNDEFINED);
788 data = entity->data;
790 return data->local_mix;
794 * adg_entity_set_extents:
795 * @entity: an #AdgEntity
796 * @extents: the new extents
798 * <note><para>
799 * This function is only useful in entity implementations.
800 * </para></note>
802 * Sets a new bounding box for @entity. @extents can
803 * be <constant>NULL</constant>, in which case the extents are unset.
805 * Since: 1.0
807 void
808 adg_entity_set_extents(AdgEntity *entity, const CpmlExtents *extents)
810 AdgEntityPrivate *data;
812 g_return_if_fail(ADG_IS_ENTITY(entity));
814 data = entity->data;
816 if (extents == NULL)
817 data->extents.is_defined = FALSE;
818 else
819 cpml_extents_copy(&data->extents, extents);
823 * adg_entity_get_extents:
824 * @entity: an #AdgEntity
826 * Gets the bounding box of @entity. The returned struct is
827 * owned by @entity and should not modified or freed.
829 * This struct specifies the surface portion (in global space
830 * of @entity) occupied by the entity without taking into
831 * account rendering properties such as line thickness or caps.
833 * The #AdgEntity::arrange signal should be emitted before
834 * this call (either explicitely trought adg_entity_arrange()
835 * or implicitely with adg_entity_render()) in order to get
836 * an up to date boundary box.
838 * Returns: the bounding box of @entity or <constant>NULL</constant> on errors.
840 * Since: 1.0
842 const CpmlExtents *
843 adg_entity_get_extents(AdgEntity *entity)
845 AdgEntityPrivate *data;
847 g_return_val_if_fail(ADG_IS_ENTITY(entity), NULL);
849 data = entity->data;
851 return &data->extents;
855 * adg_entity_set_style:
856 * @entity: an #AdgEntity
857 * @dress: a dress style
858 * @style: the new style to use
860 * Overrides the style of @dress for @entity and its children.
861 * If @style is <constant>NULL</constant>, any previous
862 * override is removed.
864 * The new style must still be compatible with @dress: check out
865 * the adg_dress_style_is_compatible() documentation to know
866 * what a compatible style means.
868 * Since: 1.0
870 void
871 adg_entity_set_style(AdgEntity *entity, AdgDress dress, AdgStyle *style)
873 AdgEntityPrivate *data;
874 gpointer p_dress;
875 AdgStyle *old_style;
877 g_return_if_fail(ADG_IS_ENTITY(entity));
879 data = entity->data;
881 if (data->hash_styles == NULL && style == NULL)
882 return;
884 if (data->hash_styles == NULL)
885 data->hash_styles = g_hash_table_new_full(NULL, NULL,
886 NULL, g_object_unref);
888 p_dress = GINT_TO_POINTER(dress);
889 old_style = g_hash_table_lookup(data->hash_styles, p_dress);
891 if (style == old_style)
892 return;
894 if (style == NULL) {
895 g_hash_table_remove(data->hash_styles, p_dress);
896 return;
899 if (!adg_dress_style_is_compatible(dress, style)) {
900 GType ancestor_type = adg_dress_get_ancestor_type(dress);
902 g_warning(_("%s: '%s' is not compatible with '%s' for '%s' dress (%d)"),
903 G_STRLOC, g_type_name(G_TYPE_FROM_INSTANCE(style)),
904 g_type_name(ancestor_type), adg_dress_get_name(dress), dress);
906 return;
909 g_object_ref(style);
910 g_hash_table_replace(data->hash_styles, p_dress, style);
914 * adg_entity_get_style:
915 * @entity: an #AdgEntity
916 * @dress: the dress of the style to get
918 * Gets the overriden @dress style from @entity. This is a kind
919 * of accessor function: for rendering purpose use adg_entity_style()
920 * instead. The returned object is owned by @entity and should not be
921 * freed or modified.
923 * Returns: (transfer none): the requested style or <constant>NULL</constant> if the @dress style is not overriden.
925 * Since: 1.0
927 AdgStyle *
928 adg_entity_get_style(AdgEntity *entity, AdgDress dress)
930 AdgEntityPrivate *data;
932 g_return_val_if_fail(ADG_IS_ENTITY(entity), NULL);
934 data = entity->data;
936 if (data->hash_styles == NULL)
937 return NULL;
939 return g_hash_table_lookup(data->hash_styles, GINT_TO_POINTER(dress));
943 * adg_entity_style:
944 * @entity: an #AdgEntity
945 * @dress: the dress of the style to get
947 * Gets the style to be used for @entity. @dress specifies which
948 * "family" of style to get.
950 * The following sequence of checks is performed to get the proper
951 * style, stopping at the first succesfull result:
953 * <orderedlist>
954 * <listitem>check if the style is directly overriden by this entity,
955 * as returned by adg_entity_get_style();</listitem>
956 * <listitem>check if @entity has a parent, in which case returns the
957 * adg_entity_style() of the parent;</listitem>
958 * <listitem>returns the main style with adg_dress_get_fallback().</listitem>
959 * </orderedlist>
961 * The returned object is owned by @entity and should not be
962 * freed or modified.
964 * Returns: (transfer none): the requested style or <constant>NULL</constant> for transparent dresses or errors.
966 * Since: 1.0
968 AdgStyle *
969 adg_entity_style(AdgEntity *entity, AdgDress dress)
971 AdgStyle *style;
973 g_return_val_if_fail(ADG_IS_ENTITY(entity), NULL);
975 style = adg_entity_get_style(entity, dress);
977 if (style == NULL) {
978 AdgEntityPrivate *data = entity->data;
980 if (data->parent != NULL)
981 style = adg_entity_style(data->parent, dress);
982 else
983 style = adg_dress_get_fallback(dress);
986 return style;
990 * adg_entity_apply_dress:
991 * @entity: an #AdgEntity
992 * @dress: the dress style to apply
993 * @cr: a #cairo_t drawing context
995 * Convenient function to apply a @dress style (as returned by
996 * adg_entity_style()) to the @cr cairo context.
998 * Since: 1.0
1000 void
1001 adg_entity_apply_dress(AdgEntity *entity, AdgDress dress, cairo_t *cr)
1003 AdgStyle *style;
1005 g_return_if_fail(ADG_IS_ENTITY(entity));
1006 g_return_if_fail(cr != NULL);
1008 style = adg_entity_style(entity, dress);
1010 if (style != NULL)
1011 adg_style_apply(style, entity, cr);
1015 * adg_entity_global_changed:
1016 * @entity: an #AdgEntity
1018 * Emits the #AdgEntity::global-changed signal on @entity and on all of
1019 * its children, if any.
1021 * Since: 1.0
1023 void
1024 adg_entity_global_changed(AdgEntity *entity)
1026 g_return_if_fail(ADG_IS_ENTITY(entity));
1028 g_signal_emit(entity, _adg_signals[GLOBAL_CHANGED], 0);
1032 * adg_entity_local_changed:
1033 * @entity: an #AdgEntity
1035 * Emits the #AdgEntity::local-changed signal on @entity and on all of
1036 * its children, if any.
1038 * Since: 1.0
1040 void
1041 adg_entity_local_changed(AdgEntity *entity)
1043 g_return_if_fail(ADG_IS_ENTITY(entity));
1045 g_signal_emit(entity, _adg_signals[LOCAL_CHANGED], 0);
1049 * adg_entity_invalidate:
1050 * @entity: an #AdgEntity
1052 * Emits the #AdgEntity::invalidate signal on @entity and on all of
1053 * its children, if any, clearing the eventual cache stored by the
1054 * #AdgEntity::arrange signal and setting the entity state similary
1055 * to the just initialized entity.
1057 * Since: 1.0
1059 void
1060 adg_entity_invalidate(AdgEntity *entity)
1062 g_return_if_fail(ADG_IS_ENTITY(entity));
1064 g_signal_emit(entity, _adg_signals[INVALIDATE], 0);
1068 * adg_entity_arrange:
1069 * @entity: an #AdgEntity
1071 * Emits the #AdgEntity::arrange signal on @entity and all its children,
1072 * if any. The arrange call is implicitely called by the
1073 * #AdgEntity::render signal but not by adg_entity_get_extents().
1075 * Since: 1.0
1077 void
1078 adg_entity_arrange(AdgEntity *entity)
1080 g_return_if_fail(ADG_IS_ENTITY(entity));
1082 g_signal_emit(entity, _adg_signals[ARRANGE], 0);
1086 * adg_entity_render:
1087 * @entity: an #AdgEntity
1088 * @cr: a #cairo_t drawing context
1090 * Emits the #AdgEntity::render signal on @entity and on all of its
1091 * children, if any, causing the rendering to the @cr cairo context.
1093 * Since: 1.0
1095 void
1096 adg_entity_render(AdgEntity *entity, cairo_t *cr)
1098 g_return_if_fail(ADG_IS_ENTITY(entity));
1100 g_signal_emit(entity, _adg_signals[RENDER], 0, cr);
1104 * adg_entity_point:
1105 * @entity: an #AdgEntity
1106 * @point: the #AdgPoint to define
1107 * @new_point: the new #AdgPoint value
1109 * <note><para>
1110 * This function is only useful in entity implementations.
1111 * </para></note>
1113 * A convenient method to set an #AdgPoint owned by @entity.
1114 * @old_point is the old value while @new_point is the new value.
1115 * It can be used for changing a private #AdgPoint struct, such as:
1117 * <informalexample><programlisting language="C">
1118 * data->point = adg_entity_point(entity, data->point, new_point);
1119 * </programlisting></informalexample>
1121 * This function takes care of the dependencies between @entity and
1122 * the eventual models bound to the old and new points.
1124 * @old_point can be <constant>NULL</constant>, in which case a
1125 * clone of @new_point will be returned. Also @new_point can
1126 * be <constant>NULL</constant>, in which case @old_point
1127 * is destroyed and <constant>NULL</constant> will be returned.
1129 * Returns: (transfer full): the new properly defined point
1131 * Since: 1.0
1133 AdgPoint *
1134 adg_entity_point(AdgEntity *entity, AdgPoint *old_point, const AdgPoint *new_point)
1136 AdgPoint *point;
1138 g_return_val_if_fail(ADG_IS_ENTITY(entity), NULL);
1140 point = NULL;
1142 if (! adg_point_equal(old_point, new_point)) {
1143 AdgModel *old_model, *new_model;
1145 old_model = old_point != NULL ? adg_point_get_model(old_point) : NULL;
1146 new_model = new_point != NULL ? adg_point_get_model(new_point) : NULL;
1148 if (new_model != old_model) {
1149 /* Handle model-entity dependencies */
1150 if (new_model != NULL)
1151 adg_model_add_dependency(new_model, entity);
1152 if (old_model != NULL)
1153 adg_model_remove_dependency(old_model, entity);
1156 if (new_point != NULL)
1157 point = adg_point_dup(new_point);
1158 if (old_point != NULL)
1159 adg_point_destroy(old_point);
1162 return point;
1166 static void
1167 _adg_set_parent(AdgEntity *entity, AdgEntity *parent)
1169 AdgEntityPrivate *data;
1170 AdgEntity *old_parent;
1172 data = entity->data;
1173 old_parent = data->parent;
1175 data->parent = parent;
1176 data->global.is_defined = FALSE;
1177 data->local.is_defined = FALSE;
1179 g_signal_emit(entity, _adg_signals[PARENT_SET], 0, old_parent);
1182 static void
1183 _adg_global_changed(AdgEntity *entity)
1185 AdgEntityPrivate *data;
1186 const cairo_matrix_t *map;
1187 cairo_matrix_t *matrix;
1189 data = entity->data;
1190 map = &data->global_map;
1191 matrix = &data->global.matrix;
1193 if (data->parent) {
1194 adg_matrix_copy(matrix, adg_entity_get_global_matrix(data->parent));
1195 adg_matrix_transform(matrix, map, ADG_TRANSFORM_BEFORE);
1196 } else {
1197 adg_matrix_copy(matrix, map);
1201 static void
1202 _adg_local_changed(AdgEntity *entity)
1204 AdgEntityPrivate *data;
1205 const cairo_matrix_t *map;
1206 cairo_matrix_t *matrix;
1208 data = entity->data;
1209 map = &data->local_map;
1210 matrix = &data->local.matrix;
1212 switch (data->local_mix) {
1213 case ADG_MIX_DISABLED:
1214 adg_matrix_copy(matrix, adg_matrix_identity());
1215 break;
1216 case ADG_MIX_NONE:
1217 adg_matrix_copy(matrix, map);
1218 break;
1219 case ADG_MIX_ANCESTORS:
1220 if (data->parent) {
1221 adg_matrix_copy(matrix, adg_entity_get_local_matrix(data->parent));
1222 adg_matrix_transform(matrix, map, ADG_TRANSFORM_BEFORE);
1223 } else {
1224 adg_matrix_copy(matrix, map);
1226 break;
1227 case ADG_MIX_ANCESTORS_NORMALIZED:
1228 if (data->parent) {
1229 adg_matrix_copy(matrix, adg_entity_get_local_matrix(data->parent));
1230 adg_matrix_transform(matrix, map, ADG_TRANSFORM_BEFORE);
1231 } else {
1232 adg_matrix_copy(matrix, map);
1234 adg_matrix_normalize(matrix);
1235 break;
1236 case ADG_MIX_PARENT:
1237 if (data->parent) {
1238 adg_matrix_copy(matrix, adg_entity_get_local_map(data->parent));
1239 adg_matrix_transform(matrix, map, ADG_TRANSFORM_BEFORE);
1240 } else {
1241 adg_matrix_copy(matrix, map);
1243 break;
1244 case ADG_MIX_PARENT_NORMALIZED:
1245 if (data->parent) {
1246 adg_matrix_copy(matrix, adg_entity_get_local_map(data->parent));
1247 adg_matrix_transform(matrix, map, ADG_TRANSFORM_BEFORE);
1248 } else {
1249 adg_matrix_copy(matrix, map);
1251 adg_matrix_normalize(matrix);
1252 break;
1253 case ADG_MIX_UNDEFINED:
1254 g_warning(_("%s: requested to mix the maps using an undefined mix method"),
1255 G_STRLOC);
1256 break;
1257 default:
1258 g_return_if_reached();
1259 break;
1263 static void
1264 _adg_real_invalidate(AdgEntity *entity)
1266 AdgEntityClass *klass = ADG_ENTITY_GET_CLASS(entity);
1267 AdgEntityPrivate *data = entity->data;
1269 /* Do not raise any warning if invalidate() is not defined,
1270 * assuming entity does not have additional cache to be cleared */
1271 if (klass->invalidate)
1272 klass->invalidate(entity);
1274 data->extents.is_defined = FALSE;
1277 static void
1278 _adg_real_arrange(AdgEntity *entity)
1280 AdgEntityClass *klass;
1281 AdgEntityPrivate *data;
1283 klass = ADG_ENTITY_GET_CLASS(entity);
1284 data = entity->data;
1286 /* Update the global matrix, if required */
1287 if (!data->global.is_defined) {
1288 data->global.is_defined = TRUE;
1289 g_signal_emit(entity, _adg_signals[GLOBAL_CHANGED], 0);
1292 /* Update the local matrix, if required */
1293 if (!data->local.is_defined) {
1294 data->local.is_defined = TRUE;
1295 g_signal_emit(entity, _adg_signals[LOCAL_CHANGED], 0);
1298 /* The arrange() method must be defined */
1299 if (klass->arrange == NULL) {
1300 g_warning(_("%s: 'arrange' method not implemented for type '%s'"),
1301 G_STRLOC, g_type_name(G_OBJECT_TYPE(entity)));
1302 data->extents.is_defined = FALSE;
1303 return;
1306 klass->arrange(entity);
1309 static void
1310 _adg_real_render(AdgEntity *entity, cairo_t *cr)
1312 AdgEntityClass *klass = ADG_ENTITY_GET_CLASS(entity);
1314 /* The render method must be defined */
1315 if (klass->render == NULL) {
1316 g_warning(_("%s: 'render' method not implemented for type '%s'"),
1317 G_STRLOC, g_type_name(G_OBJECT_TYPE(entity)));
1318 return;
1321 /* Before the rendering, the entity should be arranged */
1322 g_signal_emit(entity, _adg_signals[ARRANGE], 0);
1324 cairo_save(cr);
1325 klass->render(entity, cr);
1326 cairo_restore(cr);
1328 if (_adg_show_extents) {
1329 AdgEntityPrivate *data = entity->data;
1330 CpmlExtents *extents = &data->extents;
1332 if (extents->is_defined) {
1333 cairo_save(cr);
1334 cairo_set_source_rgba(cr, 0.15, 0.15, 0.15, 0.15);
1335 cairo_rectangle(cr, extents->org.x, extents->org.y,
1336 extents->size.x, extents->size.y);
1337 cairo_fill(cr);
1338 cairo_restore(cr);