adg: provide example on how to customize a style
[adg.git] / src / adg / adg-entity.c
blob3028ec56f0cf9d5577ca3f40d326cc01ffdc2508
1 /* ADG - Automatic Drawing Generation
2 * Copyright (C) 2007-2017 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 create a drawing you usually create entities by calling their
28 * specific constructors and add them to a single #AdgCanvas
29 * instance. When cleaning up, you therefore need to destroy only
30 * that canvas instance with adg_entity_destroy(): this in turn
31 * will destroy every contained entity. This is pretty similar to
32 * how the GTK+ world works: you add #GtkWidget instances to a
33 * single #GtkWindow and destroy only that window when finished.
35 * To provide a proper #AdgEntity derived type, you must at least
36 * implement its arrange() and render() virtual methods. Also, if
37 * you are using some sort of caching, ensure to clear it in the
38 * invalidate() method.
40 * Since: 1.0
41 **/
43 /**
44 * AdgEntity:
46 * All fields are private and should not be used directly.
47 * Use its public methods instead.
49 * Since: 1.0
50 **/
52 /**
53 * AdgEntityClass:
54 * @destroy: when a destroy request has been explicitely requested
55 * @parent_set: called whenever the parent of an entity has changed
56 * @global_changed: the global matrix has been invalidated
57 * @local_changed: the local matrix has been invalidated
58 * @invalidate: invalidating callback, used to clear the internal cache
59 * @arrange: prepare the layout and fill the extents struct
60 * @render: rendering callback, it must be implemented by every entity
62 * Any entity (if not abstract) must implement at least the @render method.
63 * The other signal handlers can be overriden to provide custom behaviors
64 * and usually must chain up the original handler.
66 * Since: 1.0
67 **/
69 /**
70 * AdgEntityCallback:
71 * @entity: an #AdgEntity
72 * @user_data: a general purpose pointer
74 * Callback used when inspecting or browsing entities. For example,
75 * it is passed to adg_model_foreach_dependency() to perform an
76 * operation on all the entities depending on an #AdgModel.
78 * Since: 1.0
79 **/
82 #include "adg-internal.h"
83 #if GTK3_ENABLED || GTK2_ENABLED
84 #include <gtk/gtk.h>
85 #endif
87 #include "adg-container.h"
88 #include "adg-table.h"
89 #include "adg-title-block.h"
90 #include <adg-canvas.h>
91 #include "adg-dress.h"
92 #include "adg-style.h"
93 #include "adg-model.h"
94 #include "adg-point.h"
95 #include "adg-cairo-fallback.h"
97 #include "adg-entity-private.h"
100 #define _ADG_OLD_OBJECT_CLASS ((GObjectClass *) adg_entity_parent_class)
103 G_DEFINE_ABSTRACT_TYPE(AdgEntity, adg_entity, G_TYPE_INITIALLY_UNOWNED)
105 enum {
106 PROP_0,
107 PROP_FLOATING,
108 PROP_PARENT,
109 PROP_GLOBAL_MAP,
110 PROP_LOCAL_MAP,
111 PROP_LOCAL_MIX
114 enum {
115 DESTROY,
116 PARENT_SET,
117 GLOBAL_CHANGED,
118 LOCAL_CHANGED,
119 INVALIDATE,
120 ARRANGE,
121 RENDER,
122 LAST_SIGNAL
126 static void _adg_dispose (GObject *object);
127 static void _adg_get_property (GObject *object,
128 guint prop_id,
129 GValue *value,
130 GParamSpec *pspec);
131 static void _adg_set_property (GObject *object,
132 guint prop_id,
133 const GValue *value,
134 GParamSpec *pspec);
135 static void _adg_destroy (AdgEntity *entity);
136 static void _adg_set_parent (AdgEntity *entity,
137 AdgEntity *parent);
138 static void _adg_global_changed (AdgEntity *entity);
139 static void _adg_local_changed (AdgEntity *entity);
140 static void _adg_real_invalidate (AdgEntity *entity);
141 static void _adg_real_arrange (AdgEntity *entity);
142 static void _adg_real_render (AdgEntity *entity,
143 cairo_t *cr);
144 static guint _adg_signals[LAST_SIGNAL] = { 0 };
145 static gboolean _adg_show_extents = FALSE;
148 static void
149 adg_entity_class_init(AdgEntityClass *klass)
151 GObjectClass *gobject_class;
152 GParamSpec *param;
153 GClosure *closure;
154 GType param_types[1];
156 gobject_class = (GObjectClass *) klass;
158 g_type_class_add_private(klass, sizeof(AdgEntityPrivate));
160 gobject_class->dispose = _adg_dispose;
161 gobject_class->get_property = _adg_get_property;
162 gobject_class->set_property = _adg_set_property;
164 klass->destroy = _adg_destroy;
165 klass->parent_set = NULL;
166 klass->global_changed = _adg_global_changed;
167 klass->local_changed = _adg_local_changed;
168 klass->invalidate = NULL;
169 klass->arrange= NULL;
170 klass->render = NULL;
172 param = g_param_spec_boolean("floating",
173 P_("Floating Entity"),
174 P_("Flag that includes (FALSE) or excludes (TRUE) this entity from the computation of the parent entity extents"),
175 FALSE, G_PARAM_READWRITE);
176 g_object_class_install_property(gobject_class, PROP_FLOATING, param);
178 param = g_param_spec_object("parent",
179 P_("Parent Entity"),
180 P_("The parent entity of this entity or NULL if this is a top-level entity"),
181 ADG_TYPE_ENTITY,
182 G_PARAM_READWRITE);
183 g_object_class_install_property(gobject_class, PROP_PARENT, param);
185 param = g_param_spec_boxed("global-map",
186 P_("Global Map"),
187 P_("The transformation to be combined with the parent ones to get the global matrix"),
188 CAIRO_GOBJECT_TYPE_MATRIX,
189 G_PARAM_READWRITE);
190 g_object_class_install_property(gobject_class, PROP_GLOBAL_MAP, param);
192 param = g_param_spec_boxed("local-map",
193 P_("Local Map"),
194 P_("The local transformation that could be used to compute the local matrix in the way specified by the #AdgEntity:local-mix property"),
195 CAIRO_GOBJECT_TYPE_MATRIX,
196 G_PARAM_READWRITE);
197 g_object_class_install_property(gobject_class, PROP_LOCAL_MAP, param);
199 param = g_param_spec_enum("local-mix",
200 P_("Local Mix Method"),
201 P_("Define how the local maps of the entity and its ancestors should be combined to get the local matrix"),
202 ADG_TYPE_MIX, ADG_MIX_ANCESTORS,
203 G_PARAM_READWRITE);
204 g_object_class_install_property(gobject_class, PROP_LOCAL_MIX, param);
207 * AdgEntity::destroy:
208 * @entity: an #AdgEntity
210 * Emitted to explicitely destroy @entity. It unreferences
211 * @entity so that will be destroyed, unless the caller owns
212 * an additional references added with g_object_ref().
214 * In the usual case, this is equivalent of calling
215 * g_object_unref() on @entity but, for composite entities or
216 * containers, the destroy signal is propagated to the children.
218 * Since: 1.0
220 _adg_signals[DESTROY] =
221 g_signal_new("destroy",
222 G_OBJECT_CLASS_TYPE(gobject_class),
223 G_SIGNAL_RUN_FIRST,
224 G_STRUCT_OFFSET(AdgEntityClass, destroy),
225 NULL, NULL,
226 g_cclosure_marshal_VOID__VOID,
227 G_TYPE_NONE, 0);
230 * AdgEntity::parent-set:
231 * @entity: an #AdgEntity
232 * @old_parent: the old parent
234 * Emitted after the parent entity has changed. The new parent
235 * can be inspected using adg_entity_get_parent().
237 * It is allowed for both old and new parent to
238 * be <constant>NULL</constant>.
240 * Since: 1.0
242 _adg_signals[PARENT_SET] =
243 g_signal_new("parent-set",
244 G_OBJECT_CLASS_TYPE(gobject_class),
245 G_SIGNAL_RUN_FIRST,
246 G_STRUCT_OFFSET(AdgEntityClass, parent_set),
247 NULL, NULL,
248 g_cclosure_marshal_VOID__OBJECT,
249 G_TYPE_NONE, 1, ADG_TYPE_ENTITY);
252 * AdgEntity::global-changed:
253 * @entity: an #AdgEntity
255 * Emitted when the global map of @entity or any of its parent
256 * has changed. The default handler will compute the new global
257 * matrix, updating the internal cache.
259 * This signal has lazy emission, i.e. it is emitted only when
260 * the global matrix is requested, typically in the arrange phase.
262 * Since: 1.0
264 _adg_signals[GLOBAL_CHANGED] =
265 g_signal_new("global-changed",
266 G_OBJECT_CLASS_TYPE(gobject_class),
267 G_SIGNAL_RUN_FIRST,
268 G_STRUCT_OFFSET(AdgEntityClass, global_changed),
269 NULL, NULL,
270 g_cclosure_marshal_VOID__VOID,
271 G_TYPE_NONE, 0);
274 * AdgEntity::local-changed:
275 * @entity: an #AdgEntity
277 * Emitted when the local map of @entity or any of its parent
278 * has changed. The default handler will compute the new local
279 * matrix, updating the internal cache.
281 * This signal has lazy emission, i.e. it is emitted only when
282 * the local matrix is requested, typically in the arrange phase.
284 * Since: 1.0
286 _adg_signals[LOCAL_CHANGED] =
287 g_signal_new("local-changed",
288 G_OBJECT_CLASS_TYPE(gobject_class),
289 G_SIGNAL_RUN_FIRST,
290 G_STRUCT_OFFSET(AdgEntityClass, local_changed),
291 NULL, NULL,
292 g_cclosure_marshal_VOID__VOID,
293 G_TYPE_NONE, 0);
296 * AdgEntity::invalidate:
297 * @entity: an #AdgEntity
299 * Invalidates the whole @entity, that is resets all the cache
300 * (if present) built during the #AdgEntity::arrange signal.
301 * The resulting state is a clean entity, similar to what you
302 * have just before the first rendering.
304 * Since: 1.0
306 closure = g_cclosure_new(G_CALLBACK(_adg_real_invalidate), NULL, NULL);
307 _adg_signals[INVALIDATE] =
308 g_signal_newv("invalidate", ADG_TYPE_ENTITY,
309 G_SIGNAL_RUN_LAST, closure, NULL, NULL,
310 g_cclosure_marshal_VOID__VOID,
311 G_TYPE_NONE, 0, param_types);
314 * AdgEntity::arrange:
315 * @entity: an #AdgEntity
317 * Arranges the layout of @entity, updating the cache if necessary,
318 * and computes the extents of @entity.
320 * Since: 1.0
322 closure = g_cclosure_new(G_CALLBACK(_adg_real_arrange), NULL, NULL);
323 _adg_signals[ARRANGE] =
324 g_signal_newv("arrange", ADG_TYPE_ENTITY,
325 G_SIGNAL_RUN_LAST, closure, NULL, NULL,
326 g_cclosure_marshal_VOID__VOID,
327 G_TYPE_NONE, 0, param_types);
330 * AdgEntity::render:
331 * @entity: an #AdgEntity
332 * @cr: a #cairo_t drawing context
334 * Causes the rendering of @entity on @cr. A render signal will
335 * automatically emit #AdgEntity::arrange just before the real
336 * rendering on the cairo context.
338 * Since: 1.0
340 closure = g_cclosure_new(G_CALLBACK(_adg_real_render), NULL, NULL);
341 param_types[0] = G_TYPE_POINTER;
342 _adg_signals[RENDER] =
343 g_signal_newv("render", ADG_TYPE_ENTITY,
344 G_SIGNAL_RUN_LAST, closure, NULL, NULL,
345 g_cclosure_marshal_VOID__POINTER,
346 G_TYPE_NONE, 1, param_types);
349 static void
350 adg_entity_init(AdgEntity *entity)
352 AdgEntityPrivate *data = G_TYPE_INSTANCE_GET_PRIVATE(entity,
353 ADG_TYPE_ENTITY,
354 AdgEntityPrivate);
355 data->floating = FALSE;
356 data->parent = NULL;
357 cairo_matrix_init_identity(&data->global_map);
358 cairo_matrix_init_identity(&data->local_map);
359 data->local_mix = ADG_MIX_ANCESTORS;
360 data->hash_styles = NULL;
361 data->global.is_defined = FALSE;
362 adg_matrix_copy(&data->global.matrix, adg_matrix_null());
363 data->local.is_defined = FALSE;
364 adg_matrix_copy(&data->local.matrix, adg_matrix_null());
365 data->extents.is_defined = FALSE;
367 entity->data = data;
370 static void
371 _adg_dispose(GObject *object)
373 AdgEntity *entity;
374 AdgEntityPrivate *data;
376 entity = (AdgEntity *) object;
377 data = entity->data;
379 /* This call will emit a "notify" signal for parent.
380 * Consequentially, the references to the old parent is dropped. */
381 adg_entity_set_parent(entity, NULL);
383 if (data->hash_styles != NULL) {
384 g_hash_table_destroy(data->hash_styles);
385 data->hash_styles = NULL;
388 if (_ADG_OLD_OBJECT_CLASS->dispose)
389 _ADG_OLD_OBJECT_CLASS->dispose(object);
392 static void
393 _adg_get_property(GObject *object, guint prop_id,
394 GValue *value, GParamSpec *pspec)
396 AdgEntity *entity;
397 AdgEntityPrivate *data;
399 entity = (AdgEntity *) object;
400 data = entity->data;
402 switch (prop_id) {
403 case PROP_FLOATING:
404 g_value_set_boolean(value, data->floating);
405 break;
406 case PROP_PARENT:
407 g_value_set_object(value, data->parent);
408 break;
409 case PROP_GLOBAL_MAP:
410 g_value_set_boxed(value, &data->global_map);
411 break;
412 case PROP_LOCAL_MAP:
413 g_value_set_boxed(value, &data->local_map);
414 break;
415 case PROP_LOCAL_MIX:
416 g_value_set_enum(value, data->local_mix);
417 break;
418 default:
419 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
420 break;
424 static void
425 _adg_set_property(GObject *object, guint prop_id,
426 const GValue *value, GParamSpec *pspec)
428 AdgEntityPrivate *data = ((AdgEntity *) object)->data;
430 switch (prop_id) {
431 case PROP_FLOATING:
432 data->floating = g_value_get_boolean(value);
433 break;
434 case PROP_PARENT:
435 _adg_set_parent((AdgEntity *) object,
436 (AdgEntity *) g_value_get_object(value));
437 break;
438 case PROP_GLOBAL_MAP:
439 adg_matrix_copy(&data->global_map, g_value_get_boxed(value));
440 data->global.is_defined = FALSE;
441 break;
442 case PROP_LOCAL_MAP:
443 adg_matrix_copy(&data->local_map, g_value_get_boxed(value));
444 data->local.is_defined = FALSE;
445 break;
446 case PROP_LOCAL_MIX:
447 data->local_mix = g_value_get_enum(value);
448 data->local.is_defined = FALSE;
449 break;
450 default:
451 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
452 break;
458 * adg_switch_extents:
459 * @state: new extents state
461 * Strokes (if @state is <constant>TRUE</constant>) a rectangle
462 * around every entity to show their extents. Useful for
463 * debugging purposes.
465 * Since: 1.0
467 void
468 adg_switch_extents(gboolean state)
470 _adg_show_extents = state;
474 * adg_entity_destroy:
475 * @entity: an #AdgEntity
477 * Emits the #AdgEntity::destroy signal on @entity and on all of
478 * its children, if any.
480 * Since: 1.0
482 void
483 adg_entity_destroy(AdgEntity *entity)
485 g_return_if_fail(ADG_IS_ENTITY(entity));
487 g_signal_emit(entity, _adg_signals[DESTROY], 0);
491 * adg_entity_switch_floating:
492 * @entity: an #AdgEntity
493 * @new_state: the new floating state
495 * Sets or resets the floating state of @entity.
497 * By default all entities are not floating. When an entity is "floating", its
498 * extents does not concur on increasing the extents of its own container. In
499 * other words, during the arrange phase #AdgContainer only considers the
500 * non-floating children to compute its extents. In particular, this affects
501 * how adg_canvas_autoscale() works: all floating entities are not taken into
502 * consideration.
504 * A typical example is the title block or any other annotation not dependent
505 * from the model for positioning.
507 * Since: 1.0
509 void
510 adg_entity_switch_floating(AdgEntity *entity, gboolean new_state)
512 AdgEntityPrivate *data;
514 g_return_if_fail(ADG_IS_ENTITY(entity));
515 g_return_if_fail(adg_is_boolean_value(new_state));
517 data = entity->data;
519 data->floating = new_state;
523 * adg_entity_has_floating:
524 * @entity: an #AdgEntity
526 * Checks if @entity has the floating state enabled.
528 * See adg_entity_switch_floating() for a description of what the floating
529 * state is.
531 * Returns: the current state of the floating flag.
533 * Since: 1.0
535 gboolean
536 adg_entity_has_floating(AdgEntity *entity)
538 AdgEntityPrivate *data;
540 g_return_val_if_fail(ADG_IS_ENTITY(entity), FALSE);
542 data = entity->data;
544 return data->floating;
548 * adg_entity_get_canvas:
549 * @entity: an #AdgEntity
551 * Walks on the @entity hierarchy and gets the first parent of @entity,
552 * that is the first #AdgCanvas instance. The returned object is
553 * owned by @entity and should not be freed or modified.
555 * Returns: (transfer none): the requested canvas or <constant>NULL</constant> on errors or if there is no #AdgCanvas in the @entity hierarchy.
557 * Since: 1.0
559 AdgCanvas *
560 adg_entity_get_canvas(AdgEntity *entity)
562 g_return_val_if_fail(ADG_IS_ENTITY(entity), NULL);
564 while (entity) {
565 if (ADG_IS_CANVAS(entity))
566 return (AdgCanvas *) entity;
568 entity = adg_entity_get_parent(entity);
571 return NULL;
575 * adg_entity_set_parent:
576 * @entity: an #AdgEntity
577 * @parent: the parent entity
579 * <note><para>
580 * This function is only useful in entity implementations.
581 * </para></note>
583 * Sets a new parent on @entity. Changing the @parent of an entity
584 * emits the #AdgEntity::parent-set signal on it.
586 * There is no reference management at this level: they should be
587 * handled at a higher level, e.g. by #AdgContainer.
589 * Since: 1.0
591 void
592 adg_entity_set_parent(AdgEntity *entity, AdgEntity *parent)
594 g_return_if_fail(ADG_IS_ENTITY(entity));
595 g_object_set(entity, "parent", parent, NULL);
599 * adg_entity_get_parent:
600 * @entity: an #AdgEntity
602 * Gets the parent of @entity. The returned object is owned
603 * by @entity and should not be freed or modified.
605 * Returns: (transfer none): the parent entity or <constant>NULL</constant> on errors or if @entity is a toplevel.
607 * Since: 1.0
609 AdgEntity *
610 adg_entity_get_parent(AdgEntity *entity)
612 AdgEntityPrivate *data;
614 g_return_val_if_fail(ADG_IS_ENTITY(entity), NULL);
616 data = entity->data;
618 return data->parent;
622 * adg_entity_set_global_map:
623 * @entity: an #AdgEntity object
624 * @map: the new map
626 * Sets the new global transformation of @entity to @map:
627 * the old map is discarded. If @map is <constant>NULL</constant>,
628 * the global map is left unchanged.
630 * Since: 1.0
632 void
633 adg_entity_set_global_map(AdgEntity *entity, const cairo_matrix_t *map)
635 g_return_if_fail(ADG_IS_ENTITY(entity));
636 g_object_set(entity, "global-map", map, NULL);
640 * adg_entity_transform_global_map:
641 * @entity: an #AdgEntity object
642 * @transformation: the transformation to apply
643 * @mode: how @transformation should be applied
645 * Convenient function to change the global map of @entity by
646 * applying @tranformation using the @mode operator. This is
647 * logically equivalent to the following:
649 * <informalexample><programlisting language="C">
650 * cairo_matrix_t map;
651 * adg_matrix_copy(&map, adg_entity_get_global_map(entity));
652 * adg_matrix_transform(&map, transformation, mode);
653 * adg_entity_set_global_map(entity, &map);
654 * </programlisting></informalexample>
656 * Since: 1.0
658 void
659 adg_entity_transform_global_map(AdgEntity *entity,
660 const cairo_matrix_t *transformation,
661 AdgTransformMode mode)
663 AdgEntityPrivate *data;
664 cairo_matrix_t map;
666 g_return_if_fail(ADG_IS_ENTITY(entity));
667 g_return_if_fail(transformation != NULL);
669 data = entity->data;
671 adg_matrix_copy(&map, &data->global_map);
672 adg_matrix_transform(&map, transformation, mode);
674 g_object_set(entity, "global-map", &map, NULL);
678 * adg_entity_get_global_map:
679 * @entity: an #AdgEntity object
681 * Gets the transformation to be used to compute the global matrix
682 * of @entity.
684 * Returns: the requested map or <constant>NULL</constant> on errors.
686 * Since: 1.0
688 const cairo_matrix_t *
689 adg_entity_get_global_map(AdgEntity *entity)
691 AdgEntityPrivate *data;
693 g_return_val_if_fail(ADG_IS_ENTITY(entity), NULL);
695 data = entity->data;
697 return &data->global_map;
701 * adg_entity_get_global_matrix:
702 * @entity: an #AdgEntity object
704 * Gets the current global matrix of @entity. The returned value
705 * is owned by @entity and should not be changed or freed.
707 * The global matrix is computed in the arrange() phase by
708 * combining all the global maps of the @entity hierarchy using
709 * the %ADG_MIX_ANCESTORS method.
711 * Returns: the global matrix or <constant>NULL</constant> on errors.
713 * Since: 1.0
715 const cairo_matrix_t *
716 adg_entity_get_global_matrix(AdgEntity *entity)
718 AdgEntityPrivate *data;
720 g_return_val_if_fail(ADG_IS_ENTITY(entity), NULL);
722 data = entity->data;
724 return &data->global.matrix;
728 * adg_entity_set_local_map:
729 * @entity: an #AdgEntity object
730 * @map: the new map
732 * Sets the new local transformation of @entity to @map:
733 * the old map is discarded. If @map is <constant>NULL</constant>,
734 * the local map is left unchanged.
736 * Since: 1.0
738 void
739 adg_entity_set_local_map(AdgEntity *entity, const cairo_matrix_t *map)
741 g_return_if_fail(ADG_IS_ENTITY(entity));
742 g_object_set(entity, "local-map", map, NULL);
746 * adg_entity_transform_local_map:
747 * @entity: an #AdgEntity object
748 * @transformation: the transformation to apply
749 * @mode: how @transformation should be applied
751 * Convenient function to change the local map of @entity by
752 * applying @tranformation using the @mode operator. This is
753 * logically equivalent to the following:
755 * <informalexample><programlisting language="C">
756 * cairo_matrix_t map;
757 * adg_matrix_copy(&map, adg_entity_get_local_map(entity));
758 * adg_matrix_transform(&map, transformation, mode);
759 * adg_entity_set_local_map(entity, &map);
760 * </programlisting></informalexample>
762 * Since: 1.0
764 void
765 adg_entity_transform_local_map(AdgEntity *entity,
766 const cairo_matrix_t *transformation,
767 AdgTransformMode mode)
769 AdgEntityPrivate *data;
770 cairo_matrix_t map;
772 g_return_if_fail(ADG_IS_ENTITY(entity));
773 g_return_if_fail(transformation != NULL);
775 data = entity->data;
777 adg_matrix_copy(&map, &data->local_map);
778 adg_matrix_transform(&map, transformation, mode);
779 g_object_set(entity, "local-map", &map, NULL);
783 * adg_entity_get_local_map:
784 * @entity: an #AdgEntity object
786 * Gets the transformation to be used to compute the local matrix
787 * of @entity and store it in @map.
789 * Returns: the requested map or <constant>NULL</constant> on errors.
791 * Since: 1.0
793 const cairo_matrix_t *
794 adg_entity_get_local_map(AdgEntity *entity)
796 AdgEntityPrivate *data;
798 g_return_val_if_fail(ADG_IS_ENTITY(entity), NULL);
800 data = entity->data;
802 return &data->local_map;
806 * adg_entity_get_local_matrix:
807 * @entity: an #AdgEntity object
809 * Gets the current local matrix of @entity. The returned value
810 * is owned by @entity and should not be changed or freed.
812 * The local matrix is computed in the arrange() phase by
813 * combining all the local maps of the @entity hierarchy using
814 * the method specified by the #AdgEntity:local-mix property.
816 * Returns: the local matrix or <constant>NULL</constant> on errors.
818 * Since: 1.0
820 const cairo_matrix_t *
821 adg_entity_get_local_matrix(AdgEntity *entity)
823 AdgEntityPrivate *data;
825 g_return_val_if_fail(ADG_IS_ENTITY(entity), NULL);
827 data = entity->data;
829 return &data->local.matrix;
833 * adg_entity_set_local_mix:
834 * @entity: an #AdgEntity object
835 * @local_mix: new mix method
837 * Sets a new local mix method on @entity. The
838 * #AdgEntity:local-mix property defines how the local
839 * matrix must be computed: check out the #AdgMix
840 * documentation to know what are the availables methods
841 * and how they affect the local matrix computation.
843 * Setting a different local mix method emits an
844 * #AdgEntity::local-changed signal on @entity.
846 * Since: 1.0
848 void
849 adg_entity_set_local_mix(AdgEntity *entity, AdgMix local_mix)
851 g_return_if_fail(ADG_IS_ENTITY(entity));
852 g_object_set(entity, "local-mix", local_mix, NULL);
856 * adg_entity_get_local_mix:
857 * @entity: an #AdgEntity object
859 * Gets the local mix method of @entity. Check out the
860 * adg_entity_set_local_mix() documentation to know what the
861 * local mix method is used for.
863 * Returns: the local mix method of @entity or %ADG_MIX_UNDEFINED on errors
865 * Since: 1.0
867 AdgMix
868 adg_entity_get_local_mix(AdgEntity *entity)
870 AdgEntityPrivate *data;
872 g_return_val_if_fail(ADG_IS_ENTITY(entity), ADG_MIX_UNDEFINED);
874 data = entity->data;
876 return data->local_mix;
880 * adg_entity_set_extents:
881 * @entity: an #AdgEntity
882 * @extents: the new extents
884 * <note><para>
885 * This function is only useful in entity implementations.
886 * </para></note>
888 * Sets a new bounding box for @entity. @extents can
889 * be <constant>NULL</constant>, in which case the extents are unset.
891 * Since: 1.0
893 void
894 adg_entity_set_extents(AdgEntity *entity, const CpmlExtents *extents)
896 AdgEntityPrivate *data;
898 g_return_if_fail(ADG_IS_ENTITY(entity));
900 data = entity->data;
902 if (extents == NULL)
903 data->extents.is_defined = FALSE;
904 else
905 cpml_extents_copy(&data->extents, extents);
909 * adg_entity_get_extents:
910 * @entity: an #AdgEntity
912 * Gets the bounding box of @entity. The returned struct is
913 * owned by @entity and should not modified or freed.
915 * This struct specifies the surface portion (in global space
916 * of @entity) occupied by the entity without taking into
917 * account rendering properties such as line thickness or caps.
919 * The #AdgEntity::arrange signal should be emitted before
920 * this call (either explicitely trought adg_entity_arrange()
921 * or implicitely with adg_entity_render()) in order to get
922 * an up to date boundary box.
924 * Returns: the bounding box of @entity or <constant>NULL</constant> on errors.
926 * Since: 1.0
928 const CpmlExtents *
929 adg_entity_get_extents(AdgEntity *entity)
931 AdgEntityPrivate *data;
933 g_return_val_if_fail(ADG_IS_ENTITY(entity), NULL);
935 data = entity->data;
937 return &data->extents;
941 * adg_entity_set_style:
942 * @entity: an #AdgEntity
943 * @dress: a dress style
944 * @style: the new style to use
946 * Overrides the style of @dress for @entity and its children.
947 * If @style is <constant>NULL</constant>, any previous
948 * override is removed.
950 * The new style must still be compatible with @dress: check out
951 * the adg_dress_style_is_compatible() documentation to know
952 * what a compatible style means.
954 * Since: 1.0
956 void
957 adg_entity_set_style(AdgEntity *entity, AdgDress dress, AdgStyle *style)
959 AdgEntityPrivate *data;
960 gpointer p_dress;
961 AdgStyle *old_style;
963 g_return_if_fail(ADG_IS_ENTITY(entity));
965 data = entity->data;
967 if (data->hash_styles == NULL && style == NULL)
968 return;
970 if (data->hash_styles == NULL)
971 data->hash_styles = g_hash_table_new_full(NULL, NULL,
972 NULL, g_object_unref);
974 p_dress = GINT_TO_POINTER(dress);
975 old_style = g_hash_table_lookup(data->hash_styles, p_dress);
977 if (style == old_style)
978 return;
980 if (style == NULL) {
981 g_hash_table_remove(data->hash_styles, p_dress);
982 return;
985 if (!adg_dress_style_is_compatible(dress, style)) {
986 GType ancestor_type = adg_dress_get_ancestor_type(dress);
988 g_warning(_("%s: '%s' is not compatible with '%s' for '%s' dress (%d)"),
989 G_STRLOC, g_type_name(G_TYPE_FROM_INSTANCE(style)),
990 g_type_name(ancestor_type), adg_dress_get_name(dress), dress);
992 return;
995 g_object_ref(style);
996 g_hash_table_replace(data->hash_styles, p_dress, style);
1000 * adg_entity_get_style:
1001 * @entity: an #AdgEntity
1002 * @dress: the dress of the style to get
1004 * Gets the overriden @dress style from @entity. This is a kind
1005 * of accessor function: for rendering purpose use adg_entity_style()
1006 * instead. The returned object is owned by @entity and should not be
1007 * freed or modified.
1009 * Returns: (transfer none): the requested style or <constant>NULL</constant> if the @dress style is not overriden.
1011 * Since: 1.0
1013 AdgStyle *
1014 adg_entity_get_style(AdgEntity *entity, AdgDress dress)
1016 AdgEntityPrivate *data;
1018 g_return_val_if_fail(ADG_IS_ENTITY(entity), NULL);
1020 data = entity->data;
1022 if (data->hash_styles == NULL)
1023 return NULL;
1025 return g_hash_table_lookup(data->hash_styles, GINT_TO_POINTER(dress));
1029 * adg_entity_style:
1030 * @entity: an #AdgEntity
1031 * @dress: the dress of the style to get
1033 * Gets the style to be used for @entity. @dress specifies which
1034 * "family" of style to get.
1036 * The following sequence of checks is performed to get the proper
1037 * style, stopping at the first succesfull result:
1039 * <orderedlist>
1040 * <listitem>check if the style is directly overriden by this entity,
1041 * as returned by adg_entity_get_style();</listitem>
1042 * <listitem>check if @entity has a parent, in which case returns the
1043 * adg_entity_style() of the parent;</listitem>
1044 * <listitem>returns the main style with adg_dress_get_fallback().</listitem>
1045 * </orderedlist>
1047 * The returned object is owned by @entity and should not be
1048 * freed or modified.
1050 * Returns: (transfer none): the requested style or <constant>NULL</constant> for transparent dresses or errors.
1052 * Since: 1.0
1054 AdgStyle *
1055 adg_entity_style(AdgEntity *entity, AdgDress dress)
1057 AdgStyle *style;
1059 g_return_val_if_fail(ADG_IS_ENTITY(entity), NULL);
1061 style = adg_entity_get_style(entity, dress);
1063 if (style == NULL) {
1064 AdgEntityPrivate *data = entity->data;
1066 if (data->parent != NULL)
1067 style = adg_entity_style(data->parent, dress);
1068 else
1069 style = adg_dress_get_fallback(dress);
1072 return style;
1076 * adg_entity_apply_dress:
1077 * @entity: an #AdgEntity
1078 * @dress: the dress style to apply
1079 * @cr: a #cairo_t drawing context
1081 * Convenient function to apply a @dress style (as returned by
1082 * adg_entity_style()) to the @cr cairo context.
1084 * Since: 1.0
1086 void
1087 adg_entity_apply_dress(AdgEntity *entity, AdgDress dress, cairo_t *cr)
1089 AdgStyle *style;
1091 g_return_if_fail(ADG_IS_ENTITY(entity));
1092 g_return_if_fail(cr != NULL);
1094 style = adg_entity_style(entity, dress);
1096 if (style != NULL)
1097 adg_style_apply(style, entity, cr);
1101 * adg_entity_global_changed:
1102 * @entity: an #AdgEntity
1104 * Emits the #AdgEntity::global-changed signal on @entity and on all of
1105 * its children, if any.
1107 * Since: 1.0
1109 void
1110 adg_entity_global_changed(AdgEntity *entity)
1112 g_return_if_fail(ADG_IS_ENTITY(entity));
1114 g_signal_emit(entity, _adg_signals[GLOBAL_CHANGED], 0);
1118 * adg_entity_local_changed:
1119 * @entity: an #AdgEntity
1121 * Emits the #AdgEntity::local-changed signal on @entity and on all of
1122 * its children, if any.
1124 * Since: 1.0
1126 void
1127 adg_entity_local_changed(AdgEntity *entity)
1129 g_return_if_fail(ADG_IS_ENTITY(entity));
1131 g_signal_emit(entity, _adg_signals[LOCAL_CHANGED], 0);
1135 * adg_entity_invalidate:
1136 * @entity: an #AdgEntity
1138 * Emits the #AdgEntity::invalidate signal on @entity and on all of
1139 * its children, if any, clearing the eventual cache stored by the
1140 * #AdgEntity::arrange signal and setting the entity state similary
1141 * to the just initialized entity.
1143 * Since: 1.0
1145 void
1146 adg_entity_invalidate(AdgEntity *entity)
1148 g_return_if_fail(ADG_IS_ENTITY(entity));
1150 g_signal_emit(entity, _adg_signals[INVALIDATE], 0);
1154 * adg_entity_arrange:
1155 * @entity: an #AdgEntity
1157 * Emits the #AdgEntity::arrange signal on @entity and all its children,
1158 * if any. The arrange call is implicitely called by the
1159 * #AdgEntity::render signal but not by adg_entity_get_extents().
1161 * Since: 1.0
1163 void
1164 adg_entity_arrange(AdgEntity *entity)
1166 g_return_if_fail(ADG_IS_ENTITY(entity));
1168 g_signal_emit(entity, _adg_signals[ARRANGE], 0);
1172 * adg_entity_render:
1173 * @entity: an #AdgEntity
1174 * @cr: a #cairo_t drawing context
1176 * Emits the #AdgEntity::render signal on @entity and on all of its
1177 * children, if any, causing the rendering to the @cr cairo context.
1179 * Since: 1.0
1181 void
1182 adg_entity_render(AdgEntity *entity, cairo_t *cr)
1184 g_return_if_fail(ADG_IS_ENTITY(entity));
1186 g_signal_emit(entity, _adg_signals[RENDER], 0, cr);
1190 * adg_entity_point:
1191 * @entity: an #AdgEntity
1192 * @point: the #AdgPoint to define
1193 * @new_point: the new #AdgPoint value
1195 * <note><para>
1196 * This function is only useful in entity implementations.
1197 * </para></note>
1199 * A convenient method to set an #AdgPoint owned by @entity.
1200 * @old_point is the old value while @new_point is the new value.
1201 * It can be used for changing a private #AdgPoint struct, such as:
1203 * <informalexample><programlisting language="C">
1204 * data->point = adg_entity_point(entity, data->point, new_point);
1205 * </programlisting></informalexample>
1207 * This function takes care of the dependencies between @entity and
1208 * the eventual models bound to the old and new points.
1210 * @old_point can be <constant>NULL</constant>, in which case a
1211 * clone of @new_point will be returned. Also @new_point can
1212 * be <constant>NULL</constant>, in which case @old_point
1213 * is destroyed and <constant>NULL</constant> will be returned.
1215 * Returns: (transfer full): the new properly defined point
1217 * Since: 1.0
1219 AdgPoint *
1220 adg_entity_point(AdgEntity *entity, AdgPoint *old_point, const AdgPoint *new_point)
1222 AdgPoint *point;
1224 g_return_val_if_fail(ADG_IS_ENTITY(entity), NULL);
1226 point = NULL;
1228 if (! adg_point_equal(old_point, new_point)) {
1229 AdgModel *old_model, *new_model;
1231 old_model = old_point != NULL ? adg_point_get_model(old_point) : NULL;
1232 new_model = new_point != NULL ? adg_point_get_model(new_point) : NULL;
1234 if (new_model != old_model) {
1235 /* Handle model-entity dependencies */
1236 if (new_model != NULL)
1237 adg_model_add_dependency(new_model, entity);
1238 if (old_model != NULL)
1239 adg_model_remove_dependency(old_model, entity);
1242 if (new_point != NULL)
1243 point = adg_point_dup(new_point);
1244 if (old_point != NULL)
1245 adg_point_destroy(old_point);
1248 return point;
1252 static void
1253 _adg_destroy(AdgEntity *entity)
1255 GObject *object = (GObject *) entity;
1257 g_object_run_dispose(object);
1258 g_object_unref(object);
1261 static void
1262 _adg_set_parent(AdgEntity *entity, AdgEntity *parent)
1264 AdgEntityPrivate *data;
1265 AdgEntity *old_parent;
1267 data = entity->data;
1268 old_parent = data->parent;
1270 data->parent = parent;
1271 data->global.is_defined = FALSE;
1272 data->local.is_defined = FALSE;
1274 g_signal_emit(entity, _adg_signals[PARENT_SET], 0, old_parent);
1277 static void
1278 _adg_global_changed(AdgEntity *entity)
1280 AdgEntityPrivate *data;
1281 const cairo_matrix_t *map;
1282 cairo_matrix_t *matrix;
1284 data = entity->data;
1285 map = &data->global_map;
1286 matrix = &data->global.matrix;
1288 if (data->parent) {
1289 adg_matrix_copy(matrix, adg_entity_get_global_matrix(data->parent));
1290 adg_matrix_transform(matrix, map, ADG_TRANSFORM_BEFORE);
1291 } else {
1292 adg_matrix_copy(matrix, map);
1296 static void
1297 _adg_local_changed(AdgEntity *entity)
1299 AdgEntityPrivate *data;
1300 const cairo_matrix_t *map;
1301 cairo_matrix_t *matrix;
1303 data = entity->data;
1304 map = &data->local_map;
1305 matrix = &data->local.matrix;
1307 switch (data->local_mix) {
1308 case ADG_MIX_DISABLED:
1309 adg_matrix_copy(matrix, adg_matrix_identity());
1310 break;
1311 case ADG_MIX_NONE:
1312 adg_matrix_copy(matrix, map);
1313 break;
1314 case ADG_MIX_ANCESTORS:
1315 if (data->parent) {
1316 adg_matrix_copy(matrix, adg_entity_get_local_matrix(data->parent));
1317 adg_matrix_transform(matrix, map, ADG_TRANSFORM_BEFORE);
1318 } else {
1319 adg_matrix_copy(matrix, map);
1321 break;
1322 case ADG_MIX_ANCESTORS_NORMALIZED:
1323 if (data->parent) {
1324 adg_matrix_copy(matrix, adg_entity_get_local_matrix(data->parent));
1325 adg_matrix_transform(matrix, map, ADG_TRANSFORM_BEFORE);
1326 } else {
1327 adg_matrix_copy(matrix, map);
1329 adg_matrix_normalize(matrix);
1330 break;
1331 case ADG_MIX_PARENT:
1332 if (data->parent) {
1333 adg_matrix_copy(matrix, adg_entity_get_local_map(data->parent));
1334 adg_matrix_transform(matrix, map, ADG_TRANSFORM_BEFORE);
1335 } else {
1336 adg_matrix_copy(matrix, map);
1338 break;
1339 case ADG_MIX_PARENT_NORMALIZED:
1340 if (data->parent) {
1341 adg_matrix_copy(matrix, adg_entity_get_local_map(data->parent));
1342 adg_matrix_transform(matrix, map, ADG_TRANSFORM_BEFORE);
1343 } else {
1344 adg_matrix_copy(matrix, map);
1346 adg_matrix_normalize(matrix);
1347 break;
1348 case ADG_MIX_UNDEFINED:
1349 g_warning(_("%s: requested to mix the maps using an undefined mix method"),
1350 G_STRLOC);
1351 break;
1352 default:
1353 g_return_if_reached();
1354 break;
1358 static void
1359 _adg_real_invalidate(AdgEntity *entity)
1361 AdgEntityClass *klass = ADG_ENTITY_GET_CLASS(entity);
1362 AdgEntityPrivate *data = entity->data;
1364 /* Do not raise any warning if invalidate() is not defined,
1365 * assuming entity does not have additional cache to be cleared */
1366 if (klass->invalidate)
1367 klass->invalidate(entity);
1369 data->extents.is_defined = FALSE;
1372 static void
1373 _adg_real_arrange(AdgEntity *entity)
1375 AdgEntityClass *klass;
1376 AdgEntityPrivate *data;
1378 klass = ADG_ENTITY_GET_CLASS(entity);
1379 data = entity->data;
1381 /* Update the global matrix, if required */
1382 if (!data->global.is_defined) {
1383 data->global.is_defined = TRUE;
1384 g_signal_emit(entity, _adg_signals[GLOBAL_CHANGED], 0);
1387 /* Update the local matrix, if required */
1388 if (!data->local.is_defined) {
1389 data->local.is_defined = TRUE;
1390 g_signal_emit(entity, _adg_signals[LOCAL_CHANGED], 0);
1393 /* The arrange() method must be defined */
1394 if (klass->arrange == NULL) {
1395 g_warning(_("%s: 'arrange' method not implemented for type '%s'"),
1396 G_STRLOC, g_type_name(G_OBJECT_TYPE(entity)));
1397 data->extents.is_defined = FALSE;
1398 return;
1401 klass->arrange(entity);
1404 static void
1405 _adg_real_render(AdgEntity *entity, cairo_t *cr)
1407 AdgEntityClass *klass = ADG_ENTITY_GET_CLASS(entity);
1409 /* The render method must be defined */
1410 if (klass->render == NULL) {
1411 g_warning(_("%s: 'render' method not implemented for type '%s'"),
1412 G_STRLOC, g_type_name(G_OBJECT_TYPE(entity)));
1413 return;
1416 /* Before the rendering, the entity should be arranged */
1417 g_signal_emit(entity, _adg_signals[ARRANGE], 0);
1419 cairo_save(cr);
1420 klass->render(entity, cr);
1421 cairo_restore(cr);
1423 if (_adg_show_extents) {
1424 AdgEntityPrivate *data = entity->data;
1425 CpmlExtents *extents = &data->extents;
1427 if (extents->is_defined) {
1428 cairo_save(cr);
1429 cairo_set_source_rgba(cr, 0.15, 0.15, 0.15, 0.15);
1430 cairo_rectangle(cr, extents->org.x, extents->org.y,
1431 extents->size.x, extents->size.y);
1432 cairo_fill(cr);
1433 cairo_restore(cr);