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.
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
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.
50 * All fields are private and should not be used directly.
51 * Use its public methods instead.
58 * @named_pair: virtual method that returns the #CpmlPair bound to a
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().
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().
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().
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(AdgModel
, adg_model
, G_TYPE_OBJECT
)
141 static void _adg_dispose (GObject
*object
);
142 static void _adg_set_property (GObject
*object
,
146 static void _adg_add_dependency (AdgModel
*model
,
148 static void _adg_remove_dependency (AdgModel
*model
,
150 static const CpmlPair
* _adg_named_pair (AdgModel
*model
,
152 static void _adg_reset (AdgModel
*model
);
153 static void _adg_set_named_pair (AdgModel
*model
,
155 const CpmlPair
*pair
);
156 static void _adg_changed (AdgModel
*model
);
157 static void _adg_named_pair_wrapper (gpointer key
,
160 static void _adg_invalidate_wrapper (AdgModel
*model
,
163 static guint _adg_signals
[LAST_SIGNAL
] = { 0 };
167 adg_model_class_init(AdgModelClass
*klass
)
169 GObjectClass
*gobject_class
;
172 gobject_class
= (GObjectClass
*) klass
;
174 g_type_class_add_private(klass
, sizeof(AdgModelPrivate
));
176 gobject_class
->dispose
= _adg_dispose
;
177 gobject_class
->set_property
= _adg_set_property
;
179 klass
->add_dependency
= _adg_add_dependency
;
180 klass
->remove_dependency
= _adg_remove_dependency
;
181 klass
->named_pair
= _adg_named_pair
;
182 klass
->set_named_pair
= _adg_set_named_pair
;
184 klass
->reset
= _adg_reset
;
185 klass
->changed
= _adg_changed
;
187 param
= g_param_spec_object("dependency",
189 P_("Can be used to add a new dependency from this model (this entity will be invalidated on model changed)"),
192 g_object_class_install_property(gobject_class
, PROP_DEPENDENCY
, param
);
195 * AdgModel::add-dependency:
196 * @model: an #AdgModel
197 * @entity: an #AdgEntity that depends on @model
199 * Adds @entity to @model. After that @entity will depend on @model,
200 * that is #AdgModel::changed on @model will invalidate @entity.
204 _adg_signals
[ADD_DEPENDENCY
] =
205 g_signal_new("add-dependency",
206 G_OBJECT_CLASS_TYPE(gobject_class
),
208 G_STRUCT_OFFSET(AdgModelClass
, add_dependency
),
210 g_cclosure_marshal_VOID__OBJECT
,
211 G_TYPE_NONE
, 1, ADG_TYPE_ENTITY
);
214 * AdgModel::remove-dependency:
215 * @model: an #AdgModel
216 * @entity: the #AdgEntity that does not depend on @model anymore
218 * Removes the @entity from @model, that is @entity will not depend
223 _adg_signals
[REMOVE_DEPENDENCY
] =
224 g_signal_new("remove-dependency",
225 G_OBJECT_CLASS_TYPE(gobject_class
),
227 G_STRUCT_OFFSET(AdgModelClass
, remove_dependency
),
229 g_cclosure_marshal_VOID__OBJECT
,
230 G_TYPE_NONE
, 1, ADG_TYPE_ENTITY
);
233 * AdgModel::set-named-pair:
234 * @model: an #AdgModel
235 * @name: an arbitrary name
236 * @pair: an #CpmlPair
238 * Adds, updates or deletes a named pair, accordling to the given
241 * If @pair is <constant>NULL</constant>, the @name named pair is
242 * searched and deleted. If it is not found, a warning is raised.
244 * Otherwise, the @name named pair is searched: if it is found,
245 * its data are updated with @pair. If it is not found, a new
246 * named pair is created using @name and @pair.
250 _adg_signals
[SET_NAMED_PAIR
] =
251 g_signal_new("set-named-pair",
252 G_OBJECT_CLASS_TYPE(gobject_class
),
254 G_STRUCT_OFFSET(AdgModelClass
, set_named_pair
),
256 adg_marshal_VOID__STRING_POINTER
,
258 G_TYPE_STRING
, G_TYPE_POINTER
);
262 * @model: an #AdgModel
265 * This signal is only useful in model implementations.
268 * Removes any information from @model cached by the implementation
269 * code. Useful to force a recomputation of the cache when something
270 * in the model has changed.
274 _adg_signals
[CLEAR
] =
275 g_signal_new("clear", ADG_TYPE_MODEL
,
276 G_SIGNAL_RUN_LAST
|G_SIGNAL_NO_RECURSE
,
277 G_STRUCT_OFFSET(AdgModelClass
, clear
),
279 g_cclosure_marshal_VOID__VOID
,
284 * @model: an #AdgModel
286 * Resets the state of @model by destroying any named pair
287 * associated to it. This step also involves the emission of the
288 * #AdgModel::clear signal.
290 * This signal is intended to be used while redefining the model.
291 * A typical usage would be in these terms:
293 * <informalexample><programlisting language="C">
294 * adg_model_reset(model);
295 * // Definition of model. This also requires the redefinition of
296 * // the named pairs because the old ones have been destroyed.
298 * adg_model_changed(model);
299 * </programlisting></informalexample>
303 _adg_signals
[RESET
] =
304 g_signal_new("reset", ADG_TYPE_MODEL
,
305 G_SIGNAL_RUN_LAST
|G_SIGNAL_NO_RECURSE
,
306 G_STRUCT_OFFSET(AdgModelClass
, reset
),
308 g_cclosure_marshal_VOID__VOID
,
313 * @model: an #AdgModel
315 * Notificates that the model has changed. By default, all the
316 * dependent entities are invalidated.
320 _adg_signals
[CHANGED
] =
321 g_signal_new("changed", ADG_TYPE_MODEL
,
322 G_SIGNAL_RUN_LAST
|G_SIGNAL_NO_RECURSE
,
323 G_STRUCT_OFFSET(AdgModelClass
, changed
),
325 g_cclosure_marshal_VOID__VOID
,
330 adg_model_init(AdgModel
*model
)
332 AdgModelPrivate
*data
= G_TYPE_INSTANCE_GET_PRIVATE(model
, ADG_TYPE_MODEL
,
335 data
->dependencies
= NULL
;
341 _adg_dispose(GObject
*object
)
343 static gboolean is_disposed
= FALSE
;
345 if (G_UNLIKELY(!is_disposed
)) {
347 AdgModelPrivate
*data
;
350 model
= (AdgModel
*) object
;
353 /* Remove all the dependencies: this will emit a
354 * "remove-dependency" signal for every dependency, dropping
355 * all references from entities to this model */
356 while (data
->dependencies
!= NULL
) {
357 entity
= (AdgEntity
*) data
->dependencies
->data
;
358 adg_model_remove_dependency(model
, entity
);
361 g_signal_emit(model
, _adg_signals
[RESET
], 0);
364 if (_ADG_OLD_OBJECT_CLASS
->dispose
)
365 _ADG_OLD_OBJECT_CLASS
->dispose(object
);
369 _adg_set_property(GObject
*object
, guint prop_id
,
370 const GValue
*value
, GParamSpec
*pspec
)
373 case PROP_DEPENDENCY
:
374 g_signal_emit(object
, _adg_signals
[ADD_DEPENDENCY
], 0,
375 g_value_get_object(value
));
378 G_OBJECT_WARN_INVALID_PROPERTY_ID(object
, prop_id
, pspec
);
384 * adg_model_add_dependency:
385 * @model: an #AdgModel
386 * @entity: an #AdgEntity
389 * This function is only useful in entity implementations.
392 * Emits a #AdgModel::add-dependency signal on @model passing @entity
393 * as argument. This will add a reference to @entity owned by @model.
398 adg_model_add_dependency(AdgModel
*model
, AdgEntity
*entity
)
400 g_return_if_fail(ADG_IS_MODEL(model
));
401 g_return_if_fail(ADG_IS_ENTITY(entity
));
402 g_object_set(model
, "dependency", entity
, NULL
);
406 * adg_model_remove_dependency:
407 * @model: an #AdgModel
408 * @entity: an #AdgEntity
411 * This function is only useful in entity implementations.
414 * Emits a #AdgModel::remove-dependency signal on @model passing
415 * @entity as argument. @entity must be inside @model.
417 * Note that @model will own a reference to @entity and it
418 * may be the last reference held: this means removing an entity
419 * from the model can destroy it.
424 adg_model_remove_dependency(AdgModel
*model
, AdgEntity
*entity
)
426 g_return_if_fail(ADG_IS_MODEL(model
));
427 g_return_if_fail(ADG_IS_ENTITY(entity
));
429 g_signal_emit(model
, _adg_signals
[REMOVE_DEPENDENCY
], 0, entity
);
433 * adg_model_get_dependencies:
434 * @model: an #AdgModel
436 * Gets the list of entities dependending on @model. This list
437 * is owned by @model and must not be modified or freed.
439 * Returns: (transfer none) (element-type Adg.Entity): a #GSList of dependencies or <constant>NULL</constant> on error.
444 adg_model_get_dependencies(AdgModel
*model
)
446 AdgModelPrivate
*data
;
448 g_return_val_if_fail(ADG_IS_MODEL(model
), NULL
);
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.
466 adg_model_foreach_dependency(AdgModel
*model
, AdgDependencyFunc callback
,
469 AdgModelPrivate
*data
;
473 g_return_if_fail(ADG_IS_MODEL(model
));
474 g_return_if_fail(callback
!= NULL
);
477 dependency
= data
->dependencies
;
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
496 * This function is only useful in model definitions, such as
497 * inside an #AdgTrailCallback function or while constructing
498 * an #AdgPath instance.
501 * Emits a #AdgModel::set-named-pair signal on @model passing
502 * @name and @pair as arguments.
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
524 * This function is only useful in model definitions, such as
525 * inside an #AdgTrailCallback function or while constructing
526 * an #AdgPath instance.
529 * Convenient wrapper on adg_model_set_named_pair() that accepts
530 * explicit coordinates.
535 adg_model_set_named_pair_explicit(AdgModel
*model
, const gchar
*name
,
536 gdouble x
, gdouble 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 <constant>NULL</constant> if not found.
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
)
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.
587 adg_model_foreach_named_pair(AdgModel
*model
, AdgNamedPairFunc callback
,
590 AdgModelPrivate
*data
;
591 AdgWrapperHelper helper
;
593 g_return_if_fail(ADG_IS_MODEL(model
));
594 g_return_if_fail(callback
!= NULL
);
598 if (data
->named_pairs
== NULL
)
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
);
610 * @model: an #AdgModel
613 * This function is only useful new model implementations.
616 * Emits the #AdgModel::clear signal on @model.
621 adg_model_clear(AdgModel
*model
)
623 g_return_if_fail(ADG_IS_MODEL(model
));
625 g_signal_emit(model
, _adg_signals
[CLEAR
], 0);
630 * @model: an #AdgModel
632 * Emits the #AdgModel::reset signal on @model.
637 adg_model_reset(AdgModel
*model
)
639 g_return_if_fail(ADG_IS_MODEL(model
));
641 g_signal_emit(model
, _adg_signals
[RESET
], 0);
646 * @model: an #AdgModel
649 * This function is only useful in entity implementations.
652 * Emits the #AdgModel::changed signal on @model.
657 adg_model_changed(AdgModel
*model
)
659 g_return_if_fail(ADG_IS_MODEL(model
));
661 g_signal_emit(model
, _adg_signals
[CHANGED
], 0);
666 _adg_add_dependency(AdgModel
*model
, AdgEntity
*entity
)
668 AdgModelPrivate
*data
;
670 /* Do not add NULL values */
676 /* The prepend operation is more efficient */
677 data
->dependencies
= g_slist_prepend(data
->dependencies
, entity
);
679 g_object_ref(entity
);
683 _adg_remove_dependency(AdgModel
*model
, AdgEntity
*entity
)
685 AdgModelPrivate
*data
;
689 node
= g_slist_find(data
->dependencies
, entity
);
692 g_warning(_("%s: attempting to remove the nonexistent dependency "
693 "on the entity with type %s from a model of type %s"),
695 g_type_name(G_OBJECT_TYPE(entity
)),
696 g_type_name(G_OBJECT_TYPE(model
)));
700 data
->dependencies
= g_slist_delete_link(data
->dependencies
, node
);
701 g_object_unref(entity
);
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
;
718 _adg_set_named_pair(AdgModel
*model
, const gchar
*name
, const CpmlPair
*pair
)
720 AdgModelPrivate
*data
;
726 hash
= &data
->named_pairs
;
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"),
737 /* Insert or update mode */
738 key
= g_strdup(name
);
739 value
= cpml_pair_dup(pair
);
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
)
755 return g_hash_table_lookup(data
->named_pairs
, name
);
759 _adg_changed(AdgModel
*model
)
761 /* Invalidate all the entities dependent on this model */
762 adg_model_foreach_dependency(model
, _adg_invalidate_wrapper
, NULL
);
766 _adg_named_pair_wrapper(gpointer key
, gpointer value
, gpointer user_data
)
770 AdgWrapperHelper
*helper
;
776 helper
->callback(helper
->model
, name
, pair
, helper
->user_data
);
780 _adg_invalidate_wrapper(AdgModel
*model
, AdgEntity
*entity
, gpointer user_data
)
782 adg_entity_invalidate(entity
);