doc: update copyright line for 2021
[adg.git] / src / adg / adg-model.c
blob2b6d387c22bed3f9b588961ba7b71ed1c27ff0e6
1 /* ADG - Automatic Drawing Generation
2 * Copyright (C) 2007-2021 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.
68 * The default @named_pair implementation looks up the #CpmlPair in an internal
69 * #GHashTable that uses the pair name as key and the #CpmlPair struct as value.
71 * The default @set_named_pair implementation can be used for either adding
72 * (if the #CpmlPair is not <constant>NULL</constant>) or removing (if #CpmlPair
73 * is <constant>NULL</constant>) an item from the named pairs hash table.
75 * The default handler for @clear signals does not do anything.
77 * The default @reset involves the clearing of the internal cache data
78 * (done by emitting the #AdgModel::clear signal) and the destruction of the
79 * internal named pair hash table.
81 * The default @add_dependency and @remove_dependency implementations add and
82 * remove items from an internal #GSList of #AdgEntity.
84 * The default handler of the @changed signal calls adg_entity_invalidate()
85 * on every dependency by using adg_model_foreach_dependency().
87 * Since: 1.0
88 **/
90 /**
91 * AdgDependencyFunc:
92 * @model: the #AdgModel
93 * @entity: the #AdgEntity dependent on @model
94 * @user_data: a general purpose pointer
96 * Callback used by adg_model_foreach_dependency().
98 * Since: 1.0
99 **/
102 * AdgNamedPairFunc:
103 * @model: the #AdgModel
104 * @name: the name of the named pair
105 * @pair: an #CpmlPair
106 * @user_data: a general purpose pointer
108 * Callback used by adg_model_foreach_named_pair().
110 * Since: 1.0
114 #include "adg-internal.h"
116 #include "adg-model.h"
117 #include "adg-model-private.h"
120 #define _ADG_OLD_OBJECT_CLASS ((GObjectClass *) adg_model_parent_class)
123 G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE(AdgModel, adg_model, G_TYPE_OBJECT)
125 enum {
126 PROP_0,
127 PROP_DEPENDENCY
130 enum {
131 ADD_DEPENDENCY,
132 REMOVE_DEPENDENCY,
133 SET_NAMED_PAIR,
134 CLEAR,
135 RESET,
136 CHANGED,
137 LAST_SIGNAL
141 static void _adg_dispose (GObject *object);
142 static void _adg_set_property (GObject *object,
143 guint prop_id,
144 const GValue *value,
145 GParamSpec *pspec);
146 static void _adg_add_dependency (AdgModel *model,
147 AdgEntity *entity);
148 static void _adg_remove_dependency (AdgModel *model,
149 AdgEntity *entity);
150 static const CpmlPair * _adg_named_pair (AdgModel *model,
151 const gchar *name);
152 static void _adg_reset (AdgModel *model);
153 static void _adg_set_named_pair (AdgModel *model,
154 const gchar *name,
155 const CpmlPair *pair);
156 static void _adg_changed (AdgModel *model);
157 static void _adg_named_pair_wrapper (gpointer key,
158 gpointer value,
159 gpointer user_data);
160 static void _adg_invalidate_wrapper (AdgModel *model,
161 AdgEntity *entity,
162 gpointer user_data);
163 static guint _adg_signals[LAST_SIGNAL] = { 0 };
166 static void
167 adg_model_class_init(AdgModelClass *klass)
169 GObjectClass *gobject_class;
170 GParamSpec *param;
172 gobject_class = (GObjectClass *) klass;
174 gobject_class->dispose = _adg_dispose;
175 gobject_class->set_property = _adg_set_property;
177 klass->add_dependency = _adg_add_dependency;
178 klass->remove_dependency = _adg_remove_dependency;
179 klass->named_pair = _adg_named_pair;
180 klass->set_named_pair = _adg_set_named_pair;
181 klass->clear = NULL;
182 klass->reset = _adg_reset;
183 klass->changed = _adg_changed;
185 param = g_param_spec_object("dependency",
186 P_("Dependency"),
187 P_("Can be used to add a new dependency from this model (this entity will be invalidated on model changed)"),
188 ADG_TYPE_ENTITY,
189 G_PARAM_WRITABLE);
190 g_object_class_install_property(gobject_class, PROP_DEPENDENCY, param);
193 * AdgModel::add-dependency:
194 * @model: an #AdgModel
195 * @entity: an #AdgEntity that depends on @model
197 * Adds @entity to @model. After that @entity will depend on @model,
198 * that is #AdgModel::changed on @model will invalidate @entity.
200 * Since: 1.0
202 _adg_signals[ADD_DEPENDENCY] =
203 g_signal_new("add-dependency",
204 G_OBJECT_CLASS_TYPE(gobject_class),
205 G_SIGNAL_RUN_FIRST,
206 G_STRUCT_OFFSET(AdgModelClass, add_dependency),
207 NULL, NULL,
208 g_cclosure_marshal_VOID__OBJECT,
209 G_TYPE_NONE, 1, ADG_TYPE_ENTITY);
212 * AdgModel::remove-dependency:
213 * @model: an #AdgModel
214 * @entity: the #AdgEntity that does not depend on @model anymore
216 * Removes the @entity from @model, that is @entity will not depend
217 * on @model anymore.
219 * Since: 1.0
221 _adg_signals[REMOVE_DEPENDENCY] =
222 g_signal_new("remove-dependency",
223 G_OBJECT_CLASS_TYPE(gobject_class),
224 G_SIGNAL_RUN_FIRST,
225 G_STRUCT_OFFSET(AdgModelClass, remove_dependency),
226 NULL, NULL,
227 g_cclosure_marshal_VOID__OBJECT,
228 G_TYPE_NONE, 1, ADG_TYPE_ENTITY);
231 * AdgModel::set-named-pair:
232 * @model: an #AdgModel
233 * @name: an arbitrary name
234 * @pair: an #CpmlPair
236 * Adds, updates or deletes a named pair, accordling to the given
237 * parameters.
239 * If @pair is <constant>NULL</constant>, the @name named pair is
240 * searched and deleted. If it is not found, a warning is raised.
242 * Otherwise, the @name named pair is searched: if it is found,
243 * its data are updated with @pair. If it is not found, a new
244 * named pair is created using @name and @pair.
246 * Since: 1.0
248 _adg_signals[SET_NAMED_PAIR] =
249 g_signal_new("set-named-pair",
250 G_OBJECT_CLASS_TYPE(gobject_class),
251 G_SIGNAL_RUN_FIRST,
252 G_STRUCT_OFFSET(AdgModelClass, set_named_pair),
253 NULL, NULL,
254 adg_marshal_VOID__STRING_POINTER,
255 G_TYPE_NONE, 2,
256 G_TYPE_STRING, G_TYPE_POINTER);
259 * AdgModel::clear:
260 * @model: an #AdgModel
262 * <note><para>
263 * This signal is only useful in model implementations.
264 * </para></note>
266 * Removes any information from @model cached by the implementation
267 * code. Useful to force a recomputation of the cache when something
268 * in the model has changed.
270 * Since: 1.0
272 _adg_signals[CLEAR] =
273 g_signal_new("clear", ADG_TYPE_MODEL,
274 G_SIGNAL_RUN_LAST|G_SIGNAL_NO_RECURSE,
275 G_STRUCT_OFFSET(AdgModelClass, clear),
276 NULL, NULL,
277 g_cclosure_marshal_VOID__VOID,
278 G_TYPE_NONE, 0);
281 * AdgModel::reset:
282 * @model: an #AdgModel
284 * Resets the state of @model by destroying any named pair
285 * associated to it. This step also involves the emission of the
286 * #AdgModel::clear signal.
288 * This signal is intended to be used while redefining the model.
289 * A typical usage would be in these terms:
291 * <informalexample><programlisting language="C">
292 * adg_model_reset(model);
293 * // Definition of model. This also requires the redefinition of
294 * // the named pairs because the old ones have been destroyed.
295 * ...
296 * adg_model_changed(model);
297 * </programlisting></informalexample>
299 * Since: 1.0
301 _adg_signals[RESET] =
302 g_signal_new("reset", ADG_TYPE_MODEL,
303 G_SIGNAL_RUN_LAST|G_SIGNAL_NO_RECURSE,
304 G_STRUCT_OFFSET(AdgModelClass, reset),
305 NULL, NULL,
306 g_cclosure_marshal_VOID__VOID,
307 G_TYPE_NONE, 0);
310 * AdgModel::changed:
311 * @model: an #AdgModel
313 * Notificates that the model has changed. By default, all the
314 * dependent entities are invalidated.
316 * Since: 1.0
318 _adg_signals[CHANGED] =
319 g_signal_new("changed", ADG_TYPE_MODEL,
320 G_SIGNAL_RUN_LAST|G_SIGNAL_NO_RECURSE,
321 G_STRUCT_OFFSET(AdgModelClass, changed),
322 NULL, NULL,
323 g_cclosure_marshal_VOID__VOID,
324 G_TYPE_NONE, 0);
327 static void
328 adg_model_init(AdgModel *model)
330 AdgModelPrivate *data = adg_model_get_instance_private(model);
331 data->dependencies = NULL;
334 static void
335 _adg_dispose(GObject *object)
337 static gboolean is_disposed = FALSE;
339 if (G_UNLIKELY(!is_disposed)) {
340 AdgModel *model = (AdgModel *) object;
341 AdgModelPrivate *data = adg_model_get_instance_private(model);
342 AdgEntity *entity;
344 /* Remove all the dependencies: this will emit a
345 * "remove-dependency" signal for every dependency, dropping
346 * all references from entities to this model */
347 while (data->dependencies != NULL) {
348 entity = (AdgEntity *) data->dependencies->data;
349 adg_model_remove_dependency(model, entity);
352 g_signal_emit(model, _adg_signals[RESET], 0);
355 if (_ADG_OLD_OBJECT_CLASS->dispose)
356 _ADG_OLD_OBJECT_CLASS->dispose(object);
359 static void
360 _adg_set_property(GObject *object, guint prop_id,
361 const GValue *value, GParamSpec *pspec)
363 switch (prop_id) {
364 case PROP_DEPENDENCY:
365 g_signal_emit(object, _adg_signals[ADD_DEPENDENCY], 0,
366 g_value_get_object(value));
367 break;
368 default:
369 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
375 * adg_model_add_dependency:
376 * @model: an #AdgModel
377 * @entity: an #AdgEntity
379 * <note><para>
380 * This function is only useful in entity implementations.
381 * </para></note>
383 * Emits a #AdgModel::add-dependency signal on @model passing @entity
384 * as argument. This will add a reference to @entity owned by @model.
386 * Since: 1.0
388 void
389 adg_model_add_dependency(AdgModel *model, AdgEntity *entity)
391 g_return_if_fail(ADG_IS_MODEL(model));
392 g_return_if_fail(ADG_IS_ENTITY(entity));
393 g_object_set(model, "dependency", entity, NULL);
397 * adg_model_remove_dependency:
398 * @model: an #AdgModel
399 * @entity: an #AdgEntity
401 * <note><para>
402 * This function is only useful in entity implementations.
403 * </para></note>
405 * Emits a #AdgModel::remove-dependency signal on @model passing
406 * @entity as argument. @entity must be inside @model.
408 * Note that @model will own a reference to @entity and it
409 * may be the last reference held: this means removing an entity
410 * from the model can destroy it.
412 * Since: 1.0
414 void
415 adg_model_remove_dependency(AdgModel *model, AdgEntity *entity)
417 g_return_if_fail(ADG_IS_MODEL(model));
418 g_return_if_fail(ADG_IS_ENTITY(entity));
420 g_signal_emit(model, _adg_signals[REMOVE_DEPENDENCY], 0, entity);
424 * adg_model_get_dependencies:
425 * @model: an #AdgModel
427 * Gets the list of entities dependending on @model. This list
428 * is owned by @model and must not be modified or freed.
430 * Returns: (transfer none) (element-type Adg.Entity): a #GSList of dependencies or <constant>NULL</constant> on error.
432 * Since: 1.0
434 const GSList *
435 adg_model_get_dependencies(AdgModel *model)
437 AdgModelPrivate *data;
439 g_return_val_if_fail(ADG_IS_MODEL(model), NULL);
441 data = adg_model_get_instance_private(model);
442 return data->dependencies;
446 * adg_model_foreach_dependency:
447 * @model: an #AdgModel
448 * @callback: (scope call): the entity callback
449 * @user_data: general purpose user data passed "as is" to @callback
451 * Invokes @callback on each entity linked to @model.
453 * Since: 1.0
455 void
456 adg_model_foreach_dependency(AdgModel *model, AdgDependencyFunc callback,
457 gpointer user_data)
459 AdgModelPrivate *data;
460 GSList *dependency;
461 AdgEntity *entity;
463 g_return_if_fail(ADG_IS_MODEL(model));
464 g_return_if_fail(callback != NULL);
466 data = adg_model_get_instance_private(model);
467 dependency = data->dependencies;
469 while (dependency) {
470 entity = dependency->data;
472 if (entity != NULL && ADG_IS_ENTITY(entity))
473 callback(model, entity, user_data);
475 dependency = dependency->next;
480 * adg_model_set_named_pair:
481 * @model: an #AdgModel
482 * @name: the name to associate to the pair
483 * @pair: the #CpmlPair
485 * <note><para>
486 * This function is only useful in model definitions, such as
487 * inside an #AdgTrailCallback function or while constructing
488 * an #AdgPath instance.
489 * </para></note>
491 * Emits a #AdgModel::set-named-pair signal on @model passing
492 * @name and @pair as arguments.
494 * Since: 1.0
496 void
497 adg_model_set_named_pair(AdgModel *model, const gchar *name,
498 const CpmlPair *pair)
500 g_return_if_fail(ADG_IS_MODEL(model));
501 g_return_if_fail(name != NULL);
503 g_signal_emit(model, _adg_signals[SET_NAMED_PAIR], 0, name, pair);
507 * adg_model_set_named_pair_explicit:
508 * @model: an #AdgModel
509 * @name: the name to associate to the pair
510 * @x: the x coordinate of the point
511 * @y: the y coordinate of the point
513 * <note><para>
514 * This function is only useful in model definitions, such as
515 * inside an #AdgTrailCallback function or while constructing
516 * an #AdgPath instance.
517 * </para></note>
519 * Convenient wrapper on adg_model_set_named_pair() that accepts
520 * explicit coordinates.
522 * Since: 1.0
524 void
525 adg_model_set_named_pair_explicit(AdgModel *model, const gchar *name,
526 gdouble x, gdouble y)
528 CpmlPair pair;
530 pair.x = x;
531 pair.y = y;
533 adg_model_set_named_pair(model, name, &pair);
537 * adg_model_get_named_pair:
538 * @model: an #AdgModel
539 * @name: the name of the pair to get
541 * Gets the @name named pair associated to @model. The returned
542 * pair is owned by @model and must not be modified or freed.
544 * Returns: the requested #CpmlPair or <constant>NULL</constant> if not found.
546 * Since: 1.0
548 const CpmlPair *
549 adg_model_get_named_pair(AdgModel *model, const gchar *name)
551 AdgModelClass *klass;
553 g_return_val_if_fail(ADG_IS_MODEL(model), NULL);
554 g_return_val_if_fail(name != NULL, NULL);
556 klass = ADG_MODEL_GET_CLASS(model);
558 if (klass->named_pair == NULL)
559 return NULL;
561 return klass->named_pair(model, name);
565 * adg_model_foreach_named_pair:
566 * @model: an #AdgModel
567 * @callback: (scope call): the named pair callback
568 * @user_data: general purpose user data passed "as is" to @callback
570 * Invokes @callback for each named pair set on @model. This can
571 * be used, for example, to retrieve all the named pairs of a @model
572 * or to duplicate a transformed version of every named pair.
574 * Since: 1.0
576 void
577 adg_model_foreach_named_pair(AdgModel *model, AdgNamedPairFunc callback,
578 gpointer user_data)
580 AdgModelPrivate *data;
581 AdgWrapperHelper helper;
583 g_return_if_fail(ADG_IS_MODEL(model));
584 g_return_if_fail(callback != NULL);
586 data = adg_model_get_instance_private(model);
588 if (data->named_pairs == NULL)
589 return;
591 helper.callback = callback;
592 helper.model = model;
593 helper.user_data = user_data;
595 g_hash_table_foreach(data->named_pairs, _adg_named_pair_wrapper, &helper);
599 * adg_model_clear:
600 * @model: an #AdgModel
602 * <note><para>
603 * This function is only useful new model implementations.
604 * </para></note>
606 * Emits the #AdgModel::clear signal on @model.
608 * Since: 1.0
610 void
611 adg_model_clear(AdgModel *model)
613 g_return_if_fail(ADG_IS_MODEL(model));
615 g_signal_emit(model, _adg_signals[CLEAR], 0);
619 * adg_model_reset:
620 * @model: an #AdgModel
622 * Emits the #AdgModel::reset signal on @model.
624 * Since: 1.0
626 void
627 adg_model_reset(AdgModel *model)
629 g_return_if_fail(ADG_IS_MODEL(model));
631 g_signal_emit(model, _adg_signals[RESET], 0);
635 * adg_model_changed:
636 * @model: an #AdgModel
638 * <note><para>
639 * This function is only useful in entity implementations.
640 * </para></note>
642 * Emits the #AdgModel::changed signal on @model.
644 * Since: 1.0
646 void
647 adg_model_changed(AdgModel *model)
649 g_return_if_fail(ADG_IS_MODEL(model));
651 g_signal_emit(model, _adg_signals[CHANGED], 0);
655 static void
656 _adg_add_dependency(AdgModel *model, AdgEntity *entity)
658 AdgModelPrivate *data;
660 /* Do not add NULL values */
661 if (entity == NULL)
662 return;
664 data = adg_model_get_instance_private(model);
666 /* The prepend operation is more efficient */
667 data->dependencies = g_slist_prepend(data->dependencies, entity);
669 g_object_ref(entity);
672 static void
673 _adg_remove_dependency(AdgModel *model, AdgEntity *entity)
675 AdgModelPrivate *data = adg_model_get_instance_private(model);
676 GSList *node = g_slist_find(data->dependencies, entity);
678 if (node == NULL) {
679 g_warning(_("%s: attempting to remove the nonexistent dependency "
680 "on the entity with type %s from a model of type %s"),
681 G_STRLOC,
682 g_type_name(G_OBJECT_TYPE(entity)),
683 g_type_name(G_OBJECT_TYPE(model)));
684 return;
687 data->dependencies = g_slist_delete_link(data->dependencies, node);
688 g_object_unref(entity);
691 static void
692 _adg_reset(AdgModel *model)
694 AdgModelPrivate *data = adg_model_get_instance_private(model);
696 adg_model_clear(model);
698 if (data->named_pairs) {
699 g_hash_table_destroy(data->named_pairs);
700 data->named_pairs = NULL;
704 static void
705 _adg_set_named_pair(AdgModel *model, const gchar *name, const CpmlPair *pair)
707 AdgModelPrivate *data = adg_model_get_instance_private(model);
708 GHashTable **hash = &data->named_pairs;
709 gchar *key;
710 CpmlPair *value;
712 if (pair == NULL) {
713 /* Delete mode: raise a warning if @name is not found */
714 if (*hash == NULL || !g_hash_table_remove(*hash, name))
715 g_warning(_("%s: attempting to remove nonexistent '%s' named pair"),
716 G_STRLOC, name);
718 return;
721 /* Insert or update mode */
722 key = g_strdup(name);
723 value = cpml_pair_dup(pair);
725 if (*hash == NULL)
726 *hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
728 g_hash_table_insert(*hash, key, value);
731 static const CpmlPair *
732 _adg_named_pair(AdgModel *model, const gchar *name)
734 AdgModelPrivate *data = adg_model_get_instance_private(model);
736 if (data->named_pairs == NULL)
737 return NULL;
739 return g_hash_table_lookup(data->named_pairs, name);
742 static void
743 _adg_changed(AdgModel *model)
745 /* Invalidate all the entities dependent on this model */
746 adg_model_foreach_dependency(model, _adg_invalidate_wrapper, NULL);
749 static void
750 _adg_named_pair_wrapper(gpointer key, gpointer value, gpointer user_data)
752 const gchar *name;
753 CpmlPair *pair;
754 AdgWrapperHelper *helper;
756 name = key;
757 pair = value;
758 helper = user_data;
760 helper->callback(helper->model, name, pair, helper->user_data);
763 static void
764 _adg_invalidate_wrapper(AdgModel *model, AdgEntity *entity, gpointer user_data)
766 adg_entity_invalidate(entity);