build: depends on cairo-gobject if introspection is enabled
[adg.git] / src / adg / adg-model.c
blob4dab39823d84a0a40e04831be95cd6920f33eaac
1 /* ADG - Automatic Drawing Generation
2 * Copyright (C) 2007,2008,2009,2010,2011,2012,2013 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-model
23 * @short_description: The base class of the ADG model infrastructure
25 * A model is a conceptual representation of something. From an ADG
26 * user point of view, it is a collection of data and rules that
27 * defines how an object should be represented on a drawing.
29 * Because #AdgModel instances are only a conceptual idea, they are
30 * not renderable (that is, #AdgModel is not derived from #AdgEntity).
31 * Instead, it must be passed as subject to entities such as #AdgStroke
32 * or #AdgHatch.
34 * The relationships between model and view are handled by dependencies:
35 * whenever an #AdgModel changes (that is the #AdgModel:changed signal is
36 * emitted), every dependency of the model (#AdgEntity instances) is
37 * invalidated with adg_entity_invalidate().
39 * To help the interaction between model and view another concept is
40 * introduced: named pairs. This provides a way to abstract real values (the
41 * coordinates stored in #CpmlPair) by accessing them using a string. To easily
42 * the access of named pairs from the view, use #AdgPoint instead of #CpmlPair.
44 * Since: 1.0
45 **/
47 /**
48 * AdgModel:
50 * All fields are private and should not be used directly.
51 * Use its public methods instead.
53 * Since: 1.0
54 **/
56 /**
57 * AdgModelClass:
58 * @named_pair: virtual method that returns the #CpmlPair bound to a
59 * given name.
60 * @set_named_pair: signal for defining or undefining a new named pair.
61 * @clear: signal for removing the internal cache data, if any.
62 * @reset: signal used to redefine a model from scratch.
63 * @add_dependency: signal for adding a new dependency.
64 * @remove_dependency: signal used to remove an old dependency.
65 * @changed: signal for emitting an #AdgModel::changed signal.
67 * The default @named_pair implementation looks up the #CpmlPair in an internal
68 * #GHashTable that uses the pair name as key and the #CpmlPair struct as value.
70 * The default @set_named_pair implementation can be used for either adding
71 * (if the #CpmlPair is not %NULL) or removing (if #CpmlPair is %NULL) an item
72 * from the named pairs hash table.
74 * The default handler for @clear signals does not do anything.
76 * The default @reset involves the clearing of the internal cache data
77 * (done by emitting the #AdgModel::clear signal) and the destruction of the
78 * internal named pair hash table.
80 * The default @add_dependency and @remove_dependency implementations add and
81 * remove items from an internal #GSList of #AdgEntity.
83 * The default handler of the @changed signal calls adg_entity_invalidate()
84 * on every dependency by using adg_model_foreach_dependency().
86 * Since: 1.0
87 **/
89 /**
90 * AdgDependencyFunc:
91 * @model: the #AdgModel
92 * @entity: the #AdgEntity dependent on @model
93 * @user_data: a general purpose pointer
95 * Callback used by adg_model_foreach_dependency().
97 * Since: 1.0
98 **/
101 * AdgNamedPairFunc:
102 * @model: the #AdgModel
103 * @name: the name of the named pair
104 * @pair: an #CpmlPair
105 * @user_data: a general purpose pointer
107 * Callback used by adg_model_foreach_named_pair().
109 * Since: 1.0
113 #include "adg-internal.h"
115 #include "adg-model.h"
116 #include "adg-model-private.h"
119 #define _ADG_OLD_OBJECT_CLASS ((GObjectClass *) adg_model_parent_class)
122 G_DEFINE_ABSTRACT_TYPE(AdgModel, adg_model, G_TYPE_OBJECT)
124 enum {
125 PROP_0,
126 PROP_DEPENDENCY
129 enum {
130 ADD_DEPENDENCY,
131 REMOVE_DEPENDENCY,
132 SET_NAMED_PAIR,
133 CLEAR,
134 RESET,
135 CHANGED,
136 LAST_SIGNAL
140 static void _adg_dispose (GObject *object);
141 static void _adg_set_property (GObject *object,
142 guint prop_id,
143 const GValue *value,
144 GParamSpec *pspec);
145 static void _adg_add_dependency (AdgModel *model,
146 AdgEntity *entity);
147 static void _adg_remove_dependency (AdgModel *model,
148 AdgEntity *entity);
149 static const CpmlPair * _adg_named_pair (AdgModel *model,
150 const gchar *name);
151 static void _adg_reset (AdgModel *model);
152 static void _adg_set_named_pair (AdgModel *model,
153 const gchar *name,
154 const CpmlPair *pair);
155 static void _adg_changed (AdgModel *model);
156 static void _adg_named_pair_wrapper (gpointer key,
157 gpointer value,
158 gpointer user_data);
159 static void _adg_invalidate_wrapper (AdgModel *model,
160 AdgEntity *entity,
161 gpointer user_data);
162 static guint _adg_signals[LAST_SIGNAL] = { 0 };
165 static void
166 adg_model_class_init(AdgModelClass *klass)
168 GObjectClass *gobject_class;
169 GParamSpec *param;
171 gobject_class = (GObjectClass *) klass;
173 g_type_class_add_private(klass, sizeof(AdgModelPrivate));
175 gobject_class->dispose = _adg_dispose;
176 gobject_class->set_property = _adg_set_property;
178 klass->add_dependency = _adg_add_dependency;
179 klass->remove_dependency = _adg_remove_dependency;
180 klass->named_pair = _adg_named_pair;
181 klass->set_named_pair = _adg_set_named_pair;
182 klass->clear = NULL;
183 klass->reset = _adg_reset;
184 klass->changed = _adg_changed;
186 param = g_param_spec_object("dependency",
187 P_("Dependency"),
188 P_("Can be used to add a new dependency from this model (this entity will be invalidated on model changed)"),
189 ADG_TYPE_ENTITY,
190 G_PARAM_WRITABLE);
191 g_object_class_install_property(gobject_class, PROP_DEPENDENCY, param);
194 * AdgModel::add-dependency:
195 * @model: an #AdgModel
196 * @entity: an #AdgEntity that depends on @model
198 * Adds @entity to @model. After that @entity will depend on @model,
199 * that is #AdgModel::changed on @model will invalidate @entity.
201 * Since: 1.0
203 _adg_signals[ADD_DEPENDENCY] =
204 g_signal_new("add-dependency",
205 G_OBJECT_CLASS_TYPE(gobject_class),
206 G_SIGNAL_RUN_FIRST,
207 G_STRUCT_OFFSET(AdgModelClass, add_dependency),
208 NULL, NULL,
209 adg_marshal_VOID__OBJECT,
210 G_TYPE_NONE, 1, ADG_TYPE_ENTITY);
213 * AdgModel::remove-dependency:
214 * @model: an #AdgModel
215 * @entity: the #AdgEntity that does not depend on @model anymore
217 * Removes the @entity from @model, that is @entity will not depend
218 * on @model anymore.
220 * Since: 1.0
222 _adg_signals[REMOVE_DEPENDENCY] =
223 g_signal_new("remove-dependency",
224 G_OBJECT_CLASS_TYPE(gobject_class),
225 G_SIGNAL_RUN_FIRST,
226 G_STRUCT_OFFSET(AdgModelClass, remove_dependency),
227 NULL, NULL,
228 adg_marshal_VOID__OBJECT,
229 G_TYPE_NONE, 1, ADG_TYPE_ENTITY);
232 * AdgModel::set-named-pair:
233 * @model: an #AdgModel
234 * @name: an arbitrary name
235 * @pair: an #CpmlPair
237 * Adds, updates or deletes a named pair, accordling to the given
238 * parameters.
240 * If @pair is %NULL, the @name named pair is searched and deleted.
241 * If it is not found, a warning is raised.
243 * Otherwise, the @name named pair is searched: if it is found,
244 * its data are updated with @pair. If it is not found, a new
245 * named pair is created using @name and @pair.
247 * Since: 1.0
249 _adg_signals[SET_NAMED_PAIR] =
250 g_signal_new("set-named-pair",
251 G_OBJECT_CLASS_TYPE(gobject_class),
252 G_SIGNAL_RUN_FIRST,
253 G_STRUCT_OFFSET(AdgModelClass, set_named_pair),
254 NULL, NULL,
255 adg_marshal_VOID__STRING_POINTER,
256 G_TYPE_NONE, 2,
257 G_TYPE_STRING, G_TYPE_POINTER);
260 * AdgModel::clear:
261 * @model: an #AdgModel
263 * <note><para>
264 * This signal is only useful in model implementations.
265 * </para></note>
267 * Removes any information from @model cached by the implementation
268 * code. Useful to force a recomputation of the cache when something
269 * in the model has changed.
271 * Since: 1.0
273 _adg_signals[CLEAR] =
274 g_signal_new("clear", ADG_TYPE_MODEL,
275 G_SIGNAL_RUN_LAST|G_SIGNAL_NO_RECURSE,
276 G_STRUCT_OFFSET(AdgModelClass, clear),
277 NULL, NULL,
278 adg_marshal_VOID__VOID,
279 G_TYPE_NONE, 0);
282 * AdgModel::reset:
283 * @model: an #AdgModel
285 * Resets the state of @model by destroying any named pair
286 * associated to it. This step also involves the emission of the
287 * #AdgModel::clear signal.
289 * This signal is intended to be used while redefining the model.
290 * A typical usage would be in these terms:
292 * |[
293 * adg_model_reset(model);
294 * // Definition of model. This also requires the redefinition of
295 * // the named pairs because the old ones have been destroyed.
296 * ...
297 * adg_model_changed(model);
298 * ]|
300 * Since: 1.0
302 _adg_signals[RESET] =
303 g_signal_new("reset", ADG_TYPE_MODEL,
304 G_SIGNAL_RUN_LAST|G_SIGNAL_NO_RECURSE,
305 G_STRUCT_OFFSET(AdgModelClass, reset),
306 NULL, NULL,
307 adg_marshal_VOID__VOID,
308 G_TYPE_NONE, 0);
311 * AdgModel::changed:
312 * @model: an #AdgModel
314 * Notificates that the model has changed. By default, all the
315 * dependent entities are invalidated.
317 * Since: 1.0
319 _adg_signals[CHANGED] =
320 g_signal_new("changed", ADG_TYPE_MODEL,
321 G_SIGNAL_RUN_LAST|G_SIGNAL_NO_RECURSE,
322 G_STRUCT_OFFSET(AdgModelClass, changed),
323 NULL, NULL,
324 adg_marshal_VOID__VOID,
325 G_TYPE_NONE, 0);
328 static void
329 adg_model_init(AdgModel *model)
331 AdgModelPrivate *data = G_TYPE_INSTANCE_GET_PRIVATE(model, ADG_TYPE_MODEL,
332 AdgModelPrivate);
334 data->dependencies = NULL;
336 model->data = data;
339 static void
340 _adg_dispose(GObject *object)
342 static gboolean is_disposed = FALSE;
344 if (G_UNLIKELY(!is_disposed)) {
345 AdgModel *model;
346 AdgModelPrivate *data;
347 AdgEntity *entity;
349 model = (AdgModel *) object;
350 data = model->data;
352 /* Remove all the dependencies: this will emit a
353 * "remove-dependency" signal for every dependency, dropping
354 * all references from entities to this model */
355 while (data->dependencies != NULL) {
356 entity = (AdgEntity *) data->dependencies->data;
357 adg_model_remove_dependency(model, entity);
360 g_signal_emit(model, _adg_signals[RESET], 0);
363 if (_ADG_OLD_OBJECT_CLASS->dispose)
364 _ADG_OLD_OBJECT_CLASS->dispose(object);
367 static void
368 _adg_set_property(GObject *object, guint prop_id,
369 const GValue *value, GParamSpec *pspec)
371 switch (prop_id) {
372 case PROP_DEPENDENCY:
373 g_signal_emit(object, _adg_signals[ADD_DEPENDENCY], 0,
374 g_value_get_object(value));
375 break;
376 default:
377 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
383 * adg_model_add_dependency:
384 * @model: an #AdgModel
385 * @entity: an #AdgEntity
387 * <note><para>
388 * This function is only useful in entity implementations.
389 * </para></note>
391 * Emits a #AdgModel::add-dependency signal on @model passing @entity
392 * as argument. This will add a reference to @entity owned by @model.
394 * Since: 1.0
396 void
397 adg_model_add_dependency(AdgModel *model, AdgEntity *entity)
399 g_return_if_fail(ADG_IS_MODEL(model));
400 g_return_if_fail(ADG_IS_ENTITY(entity));
401 g_object_set(model, "dependency", entity, NULL);
405 * adg_model_remove_dependency:
406 * @model: an #AdgModel
407 * @entity: an #AdgEntity
409 * <note><para>
410 * This function is only useful in entity implementations.
411 * </para></note>
413 * Emits a #AdgModel::remove-dependency signal on @model passing
414 * @entity as argument. @entity must be inside @model.
416 * Note that @model will own a reference to @entity and it
417 * may be the last reference held: this means removing an entity
418 * from the model can destroy it.
420 * Since: 1.0
422 void
423 adg_model_remove_dependency(AdgModel *model, AdgEntity *entity)
425 g_return_if_fail(ADG_IS_MODEL(model));
426 g_return_if_fail(ADG_IS_ENTITY(entity));
428 g_signal_emit(model, _adg_signals[REMOVE_DEPENDENCY], 0, entity);
432 * adg_model_get_dependencies:
433 * @model: an #AdgModel
435 * Gets the list of entities dependending on @model. This list
436 * is owned by @model and must not be modified or freed.
438 * Returns: (transfer none) (element-type Adg.Entity): a #GSList of
439 * dependencies or %NULL on error.
441 * Since: 1.0
443 const GSList *
444 adg_model_get_dependencies(AdgModel *model)
446 AdgModelPrivate *data;
448 g_return_val_if_fail(ADG_IS_MODEL(model), NULL);
450 data = model->data;
452 return data->dependencies;
456 * adg_model_foreach_dependency:
457 * @model: an #AdgModel
458 * @callback: (scope call): the entity callback
459 * @user_data: general purpose user data passed "as is" to @callback
461 * Invokes @callback on each entity linked to @model.
463 * Since: 1.0
465 void
466 adg_model_foreach_dependency(AdgModel *model, AdgDependencyFunc callback,
467 gpointer user_data)
469 AdgModelPrivate *data;
470 GSList *dependency;
471 AdgEntity *entity;
473 g_return_if_fail(ADG_IS_MODEL(model));
474 g_return_if_fail(callback != NULL);
476 data = model->data;
477 dependency = data->dependencies;
479 while (dependency) {
480 entity = dependency->data;
482 if (entity != NULL && ADG_IS_ENTITY(entity))
483 callback(model, entity, user_data);
485 dependency = dependency->next;
490 * adg_model_set_named_pair:
491 * @model: an #AdgModel
492 * @name: the name to associate to the pair
493 * @pair: the #CpmlPair
495 * <note><para>
496 * This function is only useful in model definitions, such as
497 * inside an #AdgTrailCallback function or while constructing
498 * an #AdgPath instance.
499 * </para></note>
501 * Emits a #AdgModel::set-named-pair signal on @model passing
502 * @name and @pair as arguments.
504 * Since: 1.0
506 void
507 adg_model_set_named_pair(AdgModel *model, const gchar *name,
508 const CpmlPair *pair)
510 g_return_if_fail(ADG_IS_MODEL(model));
511 g_return_if_fail(name != NULL);
513 g_signal_emit(model, _adg_signals[SET_NAMED_PAIR], 0, name, pair);
517 * adg_model_set_named_pair_explicit:
518 * @model: an #AdgModel
519 * @name: the name to associate to the pair
520 * @x: the x coordinate of the point
521 * @y: the y coordinate of the point
523 * <note><para>
524 * This function is only useful in model definitions, such as
525 * inside an #AdgTrailCallback function or while constructing
526 * an #AdgPath instance.
527 * </para></note>
529 * Convenient wrapper on adg_model_set_named_pair() that accepts
530 * explicit coordinates.
532 * Since: 1.0
534 void
535 adg_model_set_named_pair_explicit(AdgModel *model, const gchar *name,
536 gdouble x, gdouble y)
538 CpmlPair pair;
540 pair.x = x;
541 pair.y = y;
543 adg_model_set_named_pair(model, name, &pair);
547 * adg_model_get_named_pair:
548 * @model: an #AdgModel
549 * @name: the name of the pair to get
551 * Gets the @name named pair associated to @model. The returned
552 * pair is owned by @model and must not be modified or freed.
554 * Returns: the requested #CpmlPair or %NULL if not found
556 * Since: 1.0
558 const CpmlPair *
559 adg_model_get_named_pair(AdgModel *model, const gchar *name)
561 AdgModelClass *klass;
563 g_return_val_if_fail(ADG_IS_MODEL(model), NULL);
564 g_return_val_if_fail(name != NULL, NULL);
566 klass = ADG_MODEL_GET_CLASS(model);
568 if (klass->named_pair == NULL)
569 return NULL;
571 return klass->named_pair(model, name);
575 * adg_model_foreach_named_pair:
576 * @model: an #AdgModel
577 * @callback: (scope call): the named pair callback
578 * @user_data: general purpose user data passed "as is" to @callback
580 * Invokes @callback for each named pair set on @model. This can
581 * be used, for example, to retrieve all the named pairs of a @model
582 * or to duplicate a transformed version of every named pair.
584 * Since: 1.0
586 void
587 adg_model_foreach_named_pair(AdgModel *model, AdgNamedPairFunc callback,
588 gpointer user_data)
590 AdgModelPrivate *data;
591 AdgWrapperHelper helper;
593 g_return_if_fail(ADG_IS_MODEL(model));
594 g_return_if_fail(callback != NULL);
596 data = model->data;
598 if (data->named_pairs == NULL)
599 return;
601 helper.callback = callback;
602 helper.model = model;
603 helper.user_data = user_data;
605 g_hash_table_foreach(data->named_pairs, _adg_named_pair_wrapper, &helper);
609 * adg_model_clear:
610 * @model: an #AdgModel
612 * <note><para>
613 * This function is only useful new model implementations.
614 * </para></note>
616 * Emits the #AdgModel::clear signal on @model.
618 * Since: 1.0
620 void
621 adg_model_clear(AdgModel *model)
623 g_return_if_fail(ADG_IS_MODEL(model));
625 g_signal_emit(model, _adg_signals[CLEAR], 0);
629 * adg_model_reset:
630 * @model: an #AdgModel
632 * Emits the #AdgModel::reset signal on @model.
634 * Since: 1.0
636 void
637 adg_model_reset(AdgModel *model)
639 g_return_if_fail(ADG_IS_MODEL(model));
641 g_signal_emit(model, _adg_signals[RESET], 0);
645 * adg_model_changed:
646 * @model: an #AdgModel
648 * <note><para>
649 * This function is only useful in entity implementations.
650 * </para></note>
652 * Emits the #AdgModel::changed signal on @model.
654 * Since: 1.0
656 void
657 adg_model_changed(AdgModel *model)
659 g_return_if_fail(ADG_IS_MODEL(model));
661 g_signal_emit(model, _adg_signals[CHANGED], 0);
665 static void
666 _adg_add_dependency(AdgModel *model, AdgEntity *entity)
668 AdgModelPrivate *data;
670 /* Do not add NULL values */
671 if (entity == NULL)
672 return;
674 data = model->data;
676 /* The prepend operation is more efficient */
677 data->dependencies = g_slist_prepend(data->dependencies, entity);
679 g_object_ref(entity);
682 static void
683 _adg_remove_dependency(AdgModel *model, AdgEntity *entity)
685 AdgModelPrivate *data;
686 GSList *node;
688 data = model->data;
689 node = g_slist_find(data->dependencies, entity);
691 if (node == NULL) {
692 g_warning(_("%s: attempting to remove the nonexistent dependency "
693 "on the entity with type %s from a model of type %s"),
694 G_STRLOC,
695 g_type_name(G_OBJECT_TYPE(entity)),
696 g_type_name(G_OBJECT_TYPE(model)));
697 return;
700 data->dependencies = g_slist_delete_link(data->dependencies, node);
701 g_object_unref(entity);
704 static void
705 _adg_reset(AdgModel *model)
707 AdgModelPrivate *data = model->data;
709 adg_model_clear(model);
711 if (data->named_pairs) {
712 g_hash_table_destroy(data->named_pairs);
713 data->named_pairs = NULL;
717 static void
718 _adg_set_named_pair(AdgModel *model, const gchar *name, const CpmlPair *pair)
720 AdgModelPrivate *data;
721 GHashTable **hash;
722 gchar *key;
723 CpmlPair *value;
725 data = model->data;
726 hash = &data->named_pairs;
728 if (pair == NULL) {
729 /* Delete mode: raise a warning if @name is not found */
730 if (*hash == NULL || !g_hash_table_remove(*hash, name))
731 g_warning(_("%s: attempting to remove nonexistent `%s' named pair"),
732 G_STRLOC, name);
734 return;
737 /* Insert or update mode */
738 key = g_strdup(name);
739 value = cpml_pair_dup(pair);
741 if (*hash == NULL)
742 *hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
744 g_hash_table_insert(*hash, key, value);
747 static const CpmlPair *
748 _adg_named_pair(AdgModel *model, const gchar *name)
750 AdgModelPrivate *data = model->data;
752 if (data->named_pairs == NULL)
753 return NULL;
755 return g_hash_table_lookup(data->named_pairs, name);
758 static void
759 _adg_changed(AdgModel *model)
761 /* Invalidate all the entities dependent on this model */
762 adg_model_foreach_dependency(model, _adg_invalidate_wrapper, NULL);
765 static void
766 _adg_named_pair_wrapper(gpointer key, gpointer value, gpointer user_data)
768 const gchar *name;
769 CpmlPair *pair;
770 AdgWrapperHelper *helper;
772 name = key;
773 pair = value;
774 helper = user_data;
776 helper->callback(helper->model, name, pair, helper->user_data);
779 static void
780 _adg_invalidate_wrapper(AdgModel *model, AdgEntity *entity, gpointer user_data)
782 adg_entity_invalidate(entity);