[AdgMarker] Using signal callback instead of weak references
[adg.git] / src / adg / adg-marker.c
blob7a7e0e2ffa9fab273ea2abe3d5743e3eadbf0679
1 /* ADG - Automatic Drawing Generation
2 * Copyright (C) 2007,2008,2009,2010 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-marker
23 * @short_description: Base entity for markers
25 * A marker is an entity to be applied at the start or end of a segment.
26 * Typical examples include arrows, ticks, dots and so on.
28 * The #AdgMarker:trail and #AdgMarker:n-segment properties specify the
29 * segment where the marker should be applied. Similarly to the
30 * #AdgStroke type, if the associated trail is destroyed the above
31 * properties are unset.
33 * The local map is used internally to align the marker to the trail end,
34 * so adg_entity_set_local_map() and friends is reserved. Therefore, if
35 * the trail is modified and the marker had no way to know it, you should
36 * call adg_entity_local_changed() to update the marker position.
38 * Use adg_marker_set_pos() to select the position where the marker
39 * should be put: %0 means the start point of the segment while %1
40 * means the end point.
42 * The #AdgMarker:model property and APIs are intended only for marker
43 * implementation purposes.
44 **/
46 /**
47 * AdgMarker:
49 * All fields are privates and should not be used directly.
50 * Use its public methods instead.
51 **/
54 #include "adg-internal.h"
55 #include "adg-marker.h"
56 #include "adg-marker-private.h"
57 #include <string.h>
59 #define PARENT_OBJECT_CLASS ((GObjectClass *) adg_marker_parent_class)
60 #define PARENT_ENTITY_CLASS ((AdgEntityClass *) adg_marker_parent_class)
63 enum {
64 PROP_0,
65 PROP_TRAIL,
66 PROP_N_SEGMENT,
67 PROP_POS,
68 PROP_SIZE,
69 PROP_MODEL
73 static void dispose (GObject *object);
74 static void get_property (GObject *object,
75 guint prop_id,
76 GValue *value,
77 GParamSpec *pspec);
78 static void set_property (GObject *object,
79 guint prop_id,
80 const GValue *value,
81 GParamSpec *pspec);
82 static void local_changed (AdgEntity *entity);
83 static void invalidate (AdgEntity *entity);
84 static gboolean set_trail (AdgMarker *marker,
85 AdgTrail *trail);
86 static void _adg_unset_trail (AdgMarker *marker);
87 static gboolean set_n_segment (AdgMarker *marker,
88 guint n_segment);
89 static gboolean set_segment (AdgMarker *marker,
90 AdgTrail *trail,
91 guint n_segment);
92 static gboolean set_pos (AdgMarker *marker,
93 gdouble pos);
94 static gboolean set_size (AdgMarker *marker,
95 gdouble size);
96 static gboolean set_model (AdgMarker *marker,
97 AdgModel *model);
98 static AdgModel * create_model (AdgMarker *marker);
101 G_DEFINE_ABSTRACT_TYPE(AdgMarker, adg_marker, ADG_TYPE_ENTITY);
104 static void
105 adg_marker_class_init(AdgMarkerClass *klass)
107 GObjectClass *gobject_class;
108 AdgEntityClass *entity_class;
109 GParamSpec *param;
111 gobject_class = (GObjectClass *) klass;
112 entity_class = (AdgEntityClass *) klass;
114 g_type_class_add_private(klass, sizeof(AdgMarkerPrivate));
116 gobject_class->dispose = dispose;
117 gobject_class->set_property = set_property;
118 gobject_class->get_property = get_property;
120 entity_class->local_changed = local_changed;
121 entity_class->invalidate = invalidate;
123 klass->create_model = create_model;
125 param = g_param_spec_object("trail",
126 P_("Trail"),
127 P_("The subject AdgTrail for this marker"),
128 ADG_TYPE_TRAIL,
129 G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
130 g_object_class_install_property(gobject_class, PROP_TRAIL, param);
132 param = g_param_spec_uint("n-segment",
133 P_("Segment Index"),
134 P_("The segment on trail where this marker should be applied (where 0 means undefined segment, 1 the first segment and so on)"),
135 0, G_MAXUINT, 0,
136 G_PARAM_READWRITE);
137 g_object_class_install_property(gobject_class, PROP_N_SEGMENT, param);
139 param = g_param_spec_double("pos",
140 P_("Position"),
141 P_("The position ratio inside the segment where to put the marker (0 means the start point while 1 means the end point)"),
142 0, 1, 0,
143 G_PARAM_READWRITE|G_PARAM_CONSTRUCT);
144 g_object_class_install_property(gobject_class, PROP_POS, param);
146 param = g_param_spec_double("size",
147 P_("Marker Size"),
148 P_("The size (in global space) of the marker"),
149 0, G_MAXDOUBLE, 10,
150 G_PARAM_READWRITE);
151 g_object_class_install_property(gobject_class, PROP_SIZE, param);
153 param = g_param_spec_object("model",
154 P_("Model"),
155 P_("A general purpose model usable by the marker implementations"),
156 ADG_TYPE_MODEL,
157 G_PARAM_READWRITE);
158 g_object_class_install_property(gobject_class, PROP_MODEL, param);
161 static void
162 adg_marker_init(AdgMarker *marker)
164 AdgMarkerPrivate *data = G_TYPE_INSTANCE_GET_PRIVATE(marker,
165 ADG_TYPE_MARKER,
166 AdgMarkerPrivate);
167 data->trail = NULL;
168 data->n_segment = 0;
169 data->backup_segment = NULL;
170 memset(&data->segment, 0, sizeof(data->segment));
171 data->pos = 0;
172 data->size = 10;
173 data->model = NULL;
175 marker->data = data;
178 static void
179 dispose(GObject *object)
181 AdgMarker *marker = (AdgMarker *) object;
183 adg_marker_set_model(marker, NULL);
184 adg_marker_set_segment(marker, NULL, 0);
186 if (PARENT_OBJECT_CLASS->dispose)
187 PARENT_OBJECT_CLASS->dispose(object);
191 static void
192 get_property(GObject *object,
193 guint prop_id, GValue *value, GParamSpec *pspec)
195 AdgMarkerPrivate *data = ((AdgMarker *) object)->data;
197 switch (prop_id) {
198 case PROP_TRAIL:
199 g_value_set_object(value, data->trail);
200 break;
201 case PROP_N_SEGMENT:
202 g_value_set_uint(value, data->n_segment);
203 break;
204 case PROP_POS:
205 g_value_set_double(value, data->pos);
206 break;
207 case PROP_SIZE:
208 g_value_set_double(value, data->size);
209 break;
210 case PROP_MODEL:
211 g_value_set_object(value, data->model);
212 break;
213 default:
214 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
215 break;
219 static void
220 set_property(GObject *object,
221 guint prop_id, const GValue *value, GParamSpec *pspec)
223 AdgMarker *marker = (AdgMarker *) object;
225 switch (prop_id) {
226 case PROP_TRAIL:
227 set_trail(marker, g_value_get_object(value));
228 break;
229 case PROP_N_SEGMENT:
230 set_n_segment(marker, g_value_get_uint(value));
231 break;
232 case PROP_POS:
233 set_pos(marker, g_value_get_double(value));
234 break;
235 case PROP_SIZE:
236 set_size(marker, g_value_get_double(value));
237 break;
238 case PROP_MODEL:
239 set_model(marker, g_value_get_object(value));
240 break;
241 default:
242 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
243 break;
249 * adg_marker_set_trail:
250 * @marker: an #AdgMarker
251 * @trail: the new trail to use
253 * Sets the #AdgMarker:trail property to @trail. It is allowed to pass
254 * %NULL to unset the current trail.
256 * This method could fail unexpectedly if the segment index specified
257 * by the #AdgMarker:n-segment property is not present inside the new
258 * segment: if you want to set a new segment it is more convenient to
259 * change both properties (#AdgMarker:trail and #AdgMarker:n-segment)
260 * at once with adg_marker_set_segment().
262 void
263 adg_marker_set_trail(AdgMarker *marker, AdgTrail *trail)
265 g_return_if_fail(ADG_IS_MARKER(marker));
267 if (set_trail(marker, trail))
268 g_object_notify((GObject *) marker, "trail");
272 * adg_marker_get_trail:
273 * @marker: an #AdgMarker
275 * Gets the trail where this marker should be applied.
277 * Returns: the trail owned by @marker or %NULL on errors
279 AdgTrail *
280 adg_marker_get_trail(AdgMarker *marker)
282 AdgMarkerPrivate *data;
284 g_return_val_if_fail(ADG_IS_MARKER(marker), NULL);
286 data = marker->data;
288 return data->trail;
292 * adg_marker_set_n_segment:
293 * @marker: an #AdgMarker
294 * @n_segment: the new segment index
296 * Sets the #AdgMarker:n-segment property to @n_segment. The trail
297 * is unchanged. If you want to set both properties at once (as
298 * usually requested to refer to a specific segment),
299 * adg_marker_set_segment() should be more convenient.
301 void
302 adg_marker_set_n_segment(AdgMarker *marker, guint n_segment)
304 AdgMarkerPrivate *data;
306 g_return_if_fail(ADG_IS_MARKER(marker));
308 data = marker->data;
310 if (set_n_segment(marker, n_segment))
311 g_object_notify((GObject *) marker, "n-segment");
315 * adg_marker_get_n_segment:
316 * @marker: an #AdgMarker
318 * Returns the segment of the associated trail where this marker
319 * will be applied, where %1 is the first segment.
321 * Returns: an index greather than %0 on success or %0 on errors
323 guint
324 adg_marker_get_n_segment(AdgMarker *marker)
326 AdgMarkerPrivate *data;
328 g_return_val_if_fail(ADG_IS_MARKER(marker), 0);
330 data = marker->data;
332 return data->n_segment;
336 * adg_marker_set_segment:
337 * @marker: an #AdgMarker
338 * @trail: the #AdgTrail
339 * @n_segment: a segment index
341 * Sets a new segment where the marker should be applied at once.
342 * A dependency between @trail and @marker is added, so when @trail
343 * changes @marker is invalidated.
345 * A callback is added to #AdgTrail::remove-dependency so manually
346 * removing the dependency (such as when @trail is destroyed) will
347 * unlink @marker from it.
349 void
350 adg_marker_set_segment(AdgMarker *marker, AdgTrail *trail, guint n_segment)
352 g_return_if_fail(ADG_IS_MARKER(marker));
354 if (set_segment(marker, trail, n_segment)) {
355 GObject *object = (GObject *) marker;
356 g_object_notify(object, "trail");
357 g_object_notify(object, "n-segment");
362 * adg_marker_get_segment:
363 * @marker: an #AdgMarker
365 * <note><para>
366 * This function is only useful in marker implementations.
367 * </para></note>
369 * Gets the segment where the marker will be applied. This segment
370 * is eventually a modified version of the backup segment, after
371 * having applied the marker.
373 * Returns: the segment or %NULL on errors
375 const AdgSegment *
376 adg_marker_get_segment(AdgMarker *marker)
378 AdgMarkerPrivate *data;
380 g_return_val_if_fail(ADG_IS_MARKER(marker), NULL);
382 data = marker->data;
384 return &data->segment;
388 * adg_marker_backup_segment:
389 * @marker: an #AdgMarker
391 * <note><para>
392 * This function is only useful in marker implementations.
393 * </para></note>
395 * Duplicates the current subject segment for backup purpose: this
396 * segment can be accessed by adg_marker_get_backup_segment().
397 * Obviously, a current segment should exist (either the
398 * #AdgMarker:trail and #AdgMarker:n-segment properties must be
399 * properly defined) or this method will fail without further
400 * processing.
402 * When the subject segment is changed (either by changing
403 * #AdgMarker:trail or #AdgMarker:n-segment) the original segment
404 * is automatically restored.
406 void
407 adg_marker_backup_segment(AdgMarker *marker)
409 AdgMarkerPrivate *data;
411 g_return_if_fail(ADG_IS_MARKER(marker));
413 data = marker->data;
415 if (data->n_segment > 0) {
416 g_return_if_fail(data->trail != NULL);
418 g_free(data->backup_segment);
420 /* Backup the segment, if a segment to backup exists */
421 if (adg_trail_put_segment(data->trail, data->n_segment,
422 &data->segment))
423 data->backup_segment = adg_segment_deep_dup(&data->segment);
428 * adg_marker_get_backup_segment:
429 * @marker: an #AdgMarker
431 * <note><para>
432 * This function is only useful in marker implementations.
433 * </para></note>
435 * Gets the original segment where the marker has been applied.
436 * Applying a marker could modify the underlying trail, usually
437 * by trimming the original segment of a #AdgMarker:size dependent
438 * length from the ends. The marker instance holds a copy of the
439 * original segment, generated by adg_marker_backup_segment(),
440 * to be used in recomputation, for example when the marker
441 * changes its size.
443 * When the subject segment is changed (either by changing
444 * #AdgMarker:trail or #AdgMarker:n-segment) the original segment
445 * is automatically restored.
447 * Returns: the original segment or %NULL on errors
449 const AdgSegment *
450 adg_marker_get_backup_segment(AdgMarker *marker)
452 AdgMarkerPrivate *data;
454 g_return_val_if_fail(ADG_IS_MARKER(marker), NULL);
456 data = marker->data;
458 return data->backup_segment;
462 * adg_marker_set_pos:
463 * @marker: an #AdgMarker
464 * @pos: the new pos
466 * Sets a new position on @marker. Check out adg_marker_get_pos() for
467 * details on what @pos represents.
469 void
470 adg_marker_set_pos(AdgMarker *marker, gdouble pos)
472 g_return_if_fail(ADG_IS_MARKER(marker));
474 if (set_pos(marker, pos))
475 g_object_notify((GObject *) marker, "pos");
479 * adg_marker_get_pos:
480 * @marker: an #AdgMarker
482 * Gets the current position of @marker. The returned value is a ratio
483 * position referred to the segment associated to @marker: %0 means the
484 * start point and %1 means the end point of the segment.
486 * Returns: the marker position
488 gdouble
489 adg_marker_get_pos(AdgMarker *marker)
491 AdgMarkerPrivate *data;
493 g_return_val_if_fail(ADG_IS_MARKER(marker), 0);
495 data = marker->data;
497 return data->pos;
501 * adg_marker_set_size:
502 * @marker: an #AdgMarker
503 * @size: the new size
505 * Sets a new size on @marker. The @size is an implementation-dependent
506 * property: it has meaning only when used by an #AdgMarker derived type.
508 void
509 adg_marker_set_size(AdgMarker *marker, gdouble size)
511 g_return_if_fail(ADG_IS_MARKER(marker));
513 if (set_size(marker, size))
514 g_object_notify((GObject *) marker, "size");
518 * adg_marker_get_size:
519 * @marker: an #AdgMarker
521 * Gets the current size of @marker.
523 * Returns: the marker size, in global space
525 gdouble
526 adg_marker_get_size(AdgMarker *marker)
528 AdgMarkerPrivate *data;
530 g_return_val_if_fail(ADG_IS_MARKER(marker), 0);
532 data = marker->data;
534 return data->size;
538 * adg_marker_set_model:
539 * @marker: an #AdgMarker
540 * @model: a new #AdgModel
542 * <note><para>
543 * This function is only useful in marker implementations.
544 * </para></note>
546 * Sets a new model for @marker. The reference to the old model (if an
547 * old model was present) is dropped while a new reference is added to
548 * @model.
550 void
551 adg_marker_set_model(AdgMarker *marker, AdgModel *model)
553 g_return_if_fail(ADG_IS_MARKER(marker));
555 if (set_model(marker, model))
556 g_object_notify((GObject *) marker, "model");
560 * adg_marker_get_model:
561 * @marker: an #AdgMarker
563 * <note><para>
564 * This function is only useful in marker implementations.
565 * </para></note>
567 * Gets the current model of @marker. This is an accessor method:
568 * if you need to get the model for rendering, use adg_marker_model()
569 * instead.
571 * Returns: the cached model owned by @marker or %NULL on errors
573 AdgModel *
574 adg_marker_get_model(AdgMarker *marker)
576 AdgMarkerPrivate *data;
578 g_return_val_if_fail(ADG_IS_MARKER(marker), NULL);
580 data = marker->data;
582 return data->model;
586 * adg_marker_model:
587 * @marker: an #AdgMarker
589 * <note><para>
590 * This function is only useful in marker implementations.
591 * </para></note>
593 * Gets the model of @marker. If the model is not found, it is
594 * automatically created by calling the create_model() virtual method.
596 * Returns: the current model owned by @marker or %NULL on errors
598 AdgModel *
599 adg_marker_model(AdgMarker *marker)
601 AdgMarkerPrivate *data;
603 g_return_val_if_fail(ADG_IS_MARKER(marker), NULL);
605 data = marker->data;
607 if (data->model == NULL) {
608 /* Model not found: regenerate it */
609 AdgMarkerClass *marker_class = ADG_MARKER_GET_CLASS(marker);
611 if (marker_class->create_model)
612 adg_marker_set_model(marker, marker_class->create_model(marker));
615 return data->model;
619 static void
620 local_changed(AdgEntity *entity)
622 AdgMarkerPrivate *data;
623 CpmlPair pair;
624 CpmlVector vector;
625 AdgMatrix map;
627 data = ((AdgMarker *) entity)->data;
628 if (data->trail == NULL)
629 return;
631 cpml_segment_put_pair_at(&data->segment, data->pos, &pair);
632 cpml_segment_put_vector_at(&data->segment, data->pos, &vector);
633 cpml_vector_set_length(&vector, data->size);
635 if (data->pos > 0.5) {
636 vector.x = -vector.x;
637 vector.y = -vector.y;
640 cairo_matrix_init(&map, vector.x, vector.y,
641 -vector.y, vector.x, pair.x, pair.y);
643 adg_entity_set_local_map(entity, &map);
645 if (PARENT_ENTITY_CLASS->local_changed)
646 PARENT_ENTITY_CLASS->local_changed(entity);
649 static void
650 invalidate(AdgEntity *entity)
652 adg_marker_set_model((AdgMarker *) entity, NULL);
656 static gboolean
657 set_trail(AdgMarker *marker, AdgTrail *trail)
659 AdgMarkerPrivate *data = marker->data;
660 return set_segment(marker, trail, data->n_segment);
663 static void
664 _adg_unset_trail(AdgMarker *marker)
666 AdgMarkerPrivate *data = marker->data;
668 if (data->trail && data->backup_segment) {
669 /* Restore the original segment in the old trail */
670 adg_segment_deep_copy(&data->segment, data->backup_segment);
671 g_free(data->backup_segment);
672 data->backup_segment = NULL;
675 data->trail = NULL;
676 data->n_segment = 0;
679 static gboolean
680 set_n_segment(AdgMarker *marker, guint n_segment)
682 AdgMarkerPrivate *data = marker->data;
683 return set_segment(marker, data->trail, n_segment);
686 static gboolean
687 set_segment(AdgMarker *marker, AdgTrail *trail, guint n_segment)
689 AdgMarkerPrivate *data;
690 AdgSegment segment = { 0 };
692 g_return_val_if_fail(trail == NULL || ADG_IS_TRAIL(trail), FALSE);
694 /* Segment validation, but only if n_segment is specified */
695 if (trail && n_segment > 0 && !adg_trail_put_segment(trail, n_segment, &segment))
696 return FALSE;
698 data = marker->data;
700 /* Do not try to cache results! Although @trail and @n_segment
701 * could be the same, the internal CpmlSegment could change.
702 * This is the case when AdgLDim arranges the layout and changes
703 * the path data (to force outside arrows for example) reusing
704 * the same CpmlPath. In other words, do not do this:
706 * if (trail == data->trail && n_segment == data->n_segment)
707 * return FALSE;
709 * Incidentally, on a 64 bit platform this issue has been never
710 * exposed. Avoiding the cache will solve the issue 33:
712 * http://dev.entidi.com/p/adg/issues/33/
715 if (trail != data->trail) {
716 AdgEntity *entity = (AdgEntity *) marker;
718 if (data->trail)
719 adg_model_remove_dependency((AdgModel *) data->trail, entity);
721 data->trail = trail;
723 if (trail) {
724 adg_model_add_dependency((AdgModel *) trail, entity);
725 g_signal_connect_swapped(trail, "remove-dependency",
726 G_CALLBACK(_adg_unset_trail), marker);
730 cpml_segment_copy(&data->segment, &segment);
731 data->n_segment = n_segment;
733 return TRUE;
736 static gboolean
737 set_pos(AdgMarker *marker, gdouble pos)
739 AdgMarkerPrivate *data;
741 /* A better approach would be to use the GParamSpec of this property */
742 g_return_val_if_fail(pos >= 0 && pos <= 1, FALSE);
744 data = marker->data;
746 if (pos == data->pos)
747 return FALSE;
749 data->pos = pos;
751 return TRUE;
754 static gboolean
755 set_size(AdgMarker *marker, gdouble size)
757 AdgMarkerPrivate *data = marker->data;
759 /* A better approach would be to use the GParamSpec of this property */
760 g_return_val_if_fail(size >= 0, FALSE);
762 if (size == data->size)
763 return FALSE;
765 data->size = size;
767 return TRUE;
770 static gboolean
771 set_model(AdgMarker *marker, AdgModel *model)
773 AdgMarkerPrivate *data = marker->data;
775 g_return_val_if_fail(model == NULL || ADG_IS_MODEL(model), FALSE);
777 if (model == data->model)
778 return FALSE;
780 if (model)
781 g_object_ref((GObject *) model);
783 if (data->model != NULL)
784 g_object_unref((GObject *) data->model);
786 data->model = model;
788 if (model)
789 adg_entity_local_changed((AdgEntity *) marker);
791 return TRUE;
794 static AdgModel *
795 create_model(AdgMarker *marker)
797 g_warning("%s: `create_model' method not implemented for type `%s'",
798 G_STRLOC, g_type_name(G_OBJECT_TYPE(marker)));
799 return NULL;