[AdgMarker] Avoid CpmlSegment caching
[adg.git] / adg / adg-marker.c
blobd73c40946f43b8a25a6eb7f276524ab884812dcf
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_segment (AdgMarker *marker,
85 AdgTrail *trail,
86 guint n_segment);
87 static void unset_trail (AdgMarker *marker);
88 static gboolean set_n_segment (AdgMarker *marker,
89 guint n_segment);
90 static gboolean set_pos (AdgMarker *marker,
91 gdouble pos);
92 static gboolean set_size (AdgMarker *marker,
93 gdouble size);
94 static gboolean set_model (AdgMarker *marker,
95 AdgModel *model);
96 static AdgModel * create_model (AdgMarker *marker);
99 G_DEFINE_ABSTRACT_TYPE(AdgMarker, adg_marker, ADG_TYPE_ENTITY);
102 static void
103 adg_marker_class_init(AdgMarkerClass *klass)
105 GObjectClass *gobject_class;
106 AdgEntityClass *entity_class;
107 GParamSpec *param;
109 gobject_class = (GObjectClass *) klass;
110 entity_class = (AdgEntityClass *) klass;
112 g_type_class_add_private(klass, sizeof(AdgMarkerPrivate));
114 gobject_class->dispose = dispose;
115 gobject_class->set_property = set_property;
116 gobject_class->get_property = get_property;
118 entity_class->local_changed = local_changed;
119 entity_class->invalidate = invalidate;
121 klass->create_model = create_model;
123 param = g_param_spec_object("trail",
124 P_("Trail"),
125 P_("The subject AdgTrail for this marker"),
126 ADG_TYPE_TRAIL,
127 G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
128 g_object_class_install_property(gobject_class, PROP_TRAIL, param);
130 param = g_param_spec_uint("n-segment",
131 P_("Segment Index"),
132 P_("The segment on trail where this marker should be applied (where 0 means undefined segment, 1 the first segment and so on)"),
133 0, G_MAXUINT, 0,
134 G_PARAM_READWRITE);
135 g_object_class_install_property(gobject_class, PROP_N_SEGMENT, param);
137 param = g_param_spec_double("pos",
138 P_("Position"),
139 P_("The position ratio inside the segment where to put the marker (0 means the start point while 1 means the end point)"),
140 0, 1, 0,
141 G_PARAM_READWRITE);
142 g_object_class_install_property(gobject_class, PROP_POS, param);
144 param = g_param_spec_double("size",
145 P_("Marker Size"),
146 P_("The size (in global space) of the marker"),
147 0, G_MAXDOUBLE, 10,
148 G_PARAM_READWRITE);
149 g_object_class_install_property(gobject_class, PROP_SIZE, param);
151 param = g_param_spec_object("model",
152 P_("Model"),
153 P_("A general purpose model usable by the marker implementations"),
154 ADG_TYPE_MODEL,
155 G_PARAM_READWRITE);
156 g_object_class_install_property(gobject_class, PROP_MODEL, param);
159 static void
160 adg_marker_init(AdgMarker *marker)
162 AdgMarkerPrivate *data = G_TYPE_INSTANCE_GET_PRIVATE(marker,
163 ADG_TYPE_MARKER,
164 AdgMarkerPrivate);
165 data->trail = NULL;
166 data->n_segment = 0;
167 data->backup_segment = NULL;
168 memset(&data->segment, 0, sizeof(data->segment));
169 data->pos = 0;
170 data->size = 10;
171 data->model = NULL;
173 marker->data = data;
176 static void
177 dispose(GObject *object)
179 AdgMarker *marker = (AdgMarker *) object;
181 adg_marker_set_model(marker, NULL);
182 adg_marker_set_segment(marker, NULL, 0);
184 if (PARENT_OBJECT_CLASS->dispose)
185 PARENT_OBJECT_CLASS->dispose(object);
189 static void
190 get_property(GObject *object,
191 guint prop_id, GValue *value, GParamSpec *pspec)
193 AdgMarkerPrivate *data = ((AdgMarker *) object)->data;
195 switch (prop_id) {
196 case PROP_TRAIL:
197 g_value_set_object(value, data->trail);
198 break;
199 case PROP_N_SEGMENT:
200 g_value_set_uint(value, data->n_segment);
201 break;
202 case PROP_POS:
203 g_value_set_double(value, data->pos);
204 break;
205 case PROP_SIZE:
206 g_value_set_double(value, data->size);
207 break;
208 case PROP_MODEL:
209 g_value_set_object(value, data->model);
210 break;
211 default:
212 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
213 break;
217 static void
218 set_property(GObject *object,
219 guint prop_id, const GValue *value, GParamSpec *pspec)
221 AdgMarker *marker = (AdgMarker *) object;
223 switch (prop_id) {
224 case PROP_TRAIL:
225 set_segment(marker, g_value_get_object(value), 1);
226 break;
227 case PROP_N_SEGMENT:
228 set_n_segment(marker, g_value_get_uint(value));
229 break;
230 case PROP_POS:
231 set_pos(marker, g_value_get_double(value));
232 break;
233 case PROP_SIZE:
234 set_size(marker, g_value_get_double(value));
235 break;
236 case PROP_MODEL:
237 set_model(marker, g_value_get_object(value));
238 break;
239 default:
240 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
241 break;
247 * adg_marker_set_segment:
248 * @marker: an #AdgMarker
249 * @trail: the #AdgTrail
250 * @n_segment: a segment index
252 * Sets the new segment where the marker should be applied. The weak
253 * reference to the old trail (if an old trail was present) is dropped
254 * while a new weak reference is added to @trail. If @trail is destroyed,
255 * the weak reference callback will automatically unset #AdgMarker:trai
256 * and will set #AdgMarker:n-segment to %0.
258 void
259 adg_marker_set_segment(AdgMarker *marker, AdgTrail *trail, guint n_segment)
261 g_return_if_fail(ADG_IS_MARKER(marker));
263 if (set_segment(marker, trail, n_segment))
264 g_object_notify((GObject *) marker, "trail");
268 * adg_marker_get_trail:
269 * @marker: an #AdgMarker
271 * Gets the trail where this marker should be applied.
273 * Returns: the trail owned by @marker or %NULL on errors
275 AdgTrail *
276 adg_marker_get_trail(AdgMarker *marker)
278 AdgMarkerPrivate *data;
280 g_return_val_if_fail(ADG_IS_MARKER(marker), NULL);
282 data = marker->data;
284 return data->trail;
288 * adg_marker_get_n_segment:
289 * @marker: an #AdgMarker
291 * Returns the segment of the associated trail where this marker
292 * will be applied, where %1 is the first segment.
294 * Returns: an index greather than %0 on success or %0 on errors
296 guint
297 adg_marker_get_n_segment(AdgMarker *marker)
299 AdgMarkerPrivate *data;
301 g_return_val_if_fail(ADG_IS_MARKER(marker), 0);
303 data = marker->data;
305 return data->n_segment;
309 * adg_marker_get_segment:
310 * @marker: an #AdgMarker
312 * <note><para>
313 * This function is only useful in marker implementations.
314 * </para></note>
316 * Gets the segment where the marker will be applied. This segment
317 * is eventually a modified version of the backup segment, after
318 * having applied the marker.
320 * Returns: the segment or %NULL on errors
322 const AdgSegment *
323 adg_marker_get_segment(AdgMarker *marker)
325 AdgMarkerPrivate *data;
327 g_return_val_if_fail(ADG_IS_MARKER(marker), NULL);
329 data = marker->data;
331 return &data->segment;
335 * adg_marker_backup_segment:
336 * @marker: an #AdgMarker
338 * <note><para>
339 * This function is only useful in marker implementations.
340 * </para></note>
342 * Duplicates the current subject segment for backup purpose: this
343 * segment can be accessed by adg_marker_get_backup_segment().
344 * Obviously, a current segment should exist (either the
345 * #AdgMarker:trail and #AdgMarker:n-segment properties must be
346 * properly defined) or this method will fail without further
347 * processing.
349 * When the subject segment is changed (either by changing
350 * #AdgMarker:trail or #AdgMarker:n-segment) the original segment
351 * is automatically restored.
353 void
354 adg_marker_backup_segment(AdgMarker *marker)
356 AdgMarkerPrivate *data;
358 g_return_if_fail(ADG_IS_MARKER(marker));
360 data = marker->data;
362 if (data->n_segment > 0) {
363 g_return_if_fail(data->trail != NULL);
365 g_free(data->backup_segment);
367 /* Backup the segment, if a segment to backup exists */
368 if (adg_trail_put_segment(data->trail, data->n_segment,
369 &data->segment))
370 data->backup_segment = adg_segment_deep_dup(&data->segment);
375 * adg_marker_get_backup_segment:
376 * @marker: an #AdgMarker
378 * <note><para>
379 * This function is only useful in marker implementations.
380 * </para></note>
382 * Gets the original segment where the marker has been applied.
383 * Applying a marker could modify the underlying trail, usually
384 * by trimming the original segment of a #AdgMarker:size dependent
385 * length from the ends. The marker instance holds a copy of the
386 * original segment, generated by adg_marker_backup_segment(),
387 * to be used in recomputation, for example when the marker
388 * changes its size.
390 * When the subject segment is changed (either by changing
391 * #AdgMarker:trail or #AdgMarker:n-segment) the original segment
392 * is automatically restored.
394 * Returns: the original segment or %NULL on errors
396 const AdgSegment *
397 adg_marker_get_backup_segment(AdgMarker *marker)
399 AdgMarkerPrivate *data;
401 g_return_val_if_fail(ADG_IS_MARKER(marker), NULL);
403 data = marker->data;
405 return data->backup_segment;
409 * adg_marker_set_pos:
410 * @marker: an #AdgMarker
411 * @pos: the new pos
413 * Sets a new position on @marker. Check out adg_marker_get_pos() for
414 * details on what @pos represents.
416 void
417 adg_marker_set_pos(AdgMarker *marker, gdouble pos)
419 g_return_if_fail(ADG_IS_MARKER(marker));
421 if (set_pos(marker, pos))
422 g_object_notify((GObject *) marker, "pos");
426 * adg_marker_get_pos:
427 * @marker: an #AdgMarker
429 * Gets the current position of @marker. The returned value is a ratio
430 * position referred to the segment associated to @marker: %0 means the
431 * start point and %1 means the end point of the segment.
433 * Returns: the marker position
435 gdouble
436 adg_marker_get_pos(AdgMarker *marker)
438 AdgMarkerPrivate *data;
440 g_return_val_if_fail(ADG_IS_MARKER(marker), 0);
442 data = marker->data;
444 return data->pos;
448 * adg_marker_set_size:
449 * @marker: an #AdgMarker
450 * @size: the new size
452 * Sets a new size on @marker. The @size is an implementation-dependent
453 * property: it has meaning only when used by an #AdgMarker derived type.
455 void
456 adg_marker_set_size(AdgMarker *marker, gdouble size)
458 g_return_if_fail(ADG_IS_MARKER(marker));
460 if (set_size(marker, size))
461 g_object_notify((GObject *) marker, "size");
465 * adg_marker_get_size:
466 * @marker: an #AdgMarker
468 * Gets the current size of @marker.
470 * Returns: the marker size, in global space
472 gdouble
473 adg_marker_get_size(AdgMarker *marker)
475 AdgMarkerPrivate *data;
477 g_return_val_if_fail(ADG_IS_MARKER(marker), 0);
479 data = marker->data;
481 return data->size;
485 * adg_marker_set_model:
486 * @marker: an #AdgMarker
487 * @model: a new #AdgModel
489 * <note><para>
490 * This function is only useful in marker implementations.
491 * </para></note>
493 * Sets a new model for @marker. The reference to the old model (if an
494 * old model was present) is dropped while a new reference is added to
495 * @model.
497 void
498 adg_marker_set_model(AdgMarker *marker, AdgModel *model)
500 g_return_if_fail(ADG_IS_MARKER(marker));
502 if (set_model(marker, model))
503 g_object_notify((GObject *) marker, "model");
507 * adg_marker_get_model:
508 * @marker: an #AdgMarker
510 * <note><para>
511 * This function is only useful in marker implementations.
512 * </para></note>
514 * Gets the current model of @marker. This is an accessor method:
515 * if you need to get the model for rendering, use adg_marker_model()
516 * instead.
518 * Returns: the cached model owned by @marker or %NULL on errors
520 AdgModel *
521 adg_marker_get_model(AdgMarker *marker)
523 AdgMarkerPrivate *data;
525 g_return_val_if_fail(ADG_IS_MARKER(marker), NULL);
527 data = marker->data;
529 return data->model;
533 * adg_marker_model:
534 * @marker: an #AdgMarker
536 * <note><para>
537 * This function is only useful in marker implementations.
538 * </para></note>
540 * Gets the model of @marker. If the model is not found, it is
541 * automatically created by calling the create_model() virtual method.
543 * Returns: the current model owned by @marker or %NULL on errors
545 AdgModel *
546 adg_marker_model(AdgMarker *marker)
548 AdgMarkerPrivate *data;
550 g_return_val_if_fail(ADG_IS_MARKER(marker), NULL);
552 data = marker->data;
554 if (data->model == NULL) {
555 /* Model not found: regenerate it */
556 AdgMarkerClass *marker_class = ADG_MARKER_GET_CLASS(marker);
558 if (marker_class->create_model)
559 adg_marker_set_model(marker, marker_class->create_model(marker));
562 return data->model;
566 static void
567 local_changed(AdgEntity *entity)
569 AdgMarkerPrivate *data;
570 CpmlPair pair;
571 CpmlVector vector;
572 AdgMatrix map;
574 data = ((AdgMarker *) entity)->data;
575 if (data->trail == NULL)
576 return;
578 cpml_segment_put_pair_at(&data->segment, data->pos, &pair);
579 cpml_segment_put_vector_at(&data->segment, data->pos, &vector);
580 cpml_vector_set_length(&vector, data->size);
582 if (data->pos > 0.5) {
583 vector.x = -vector.x;
584 vector.y = -vector.y;
587 cairo_matrix_init(&map, vector.x, vector.y,
588 -vector.y, vector.x, pair.x, pair.y);
590 adg_entity_set_local_map(entity, &map);
592 if (PARENT_ENTITY_CLASS->local_changed)
593 PARENT_ENTITY_CLASS->local_changed(entity);
596 static void
597 invalidate(AdgEntity *entity)
599 adg_marker_set_model((AdgMarker *) entity, NULL);
603 static gboolean
604 set_segment(AdgMarker *marker, AdgTrail *trail, guint n_segment)
606 AdgMarkerPrivate *data;
607 AdgEntity *entity;
609 data = marker->data;
610 entity = (AdgEntity *) marker;
612 /* Do not try to cache results! Although @trail and @n_segment
613 * could be the same, the internal CpmlSegment could change.
614 * This is the case when AdgLDim arranges the layout and changes
615 * the path data (to force outside arrows for example) reusing
616 * the same CpmlPath. In other words, do not do this:
618 * if (trail == data->trail && n_segment == data->n_segment)
619 * return FALSE;
621 * Incidentally, on a 64 bit platform this issue has been never
622 * exposed. Avoiding the cache will solve the issue 33:
624 * http://dev.entidi.com/p/adg/issues/33/
627 if (data->trail != NULL) {
628 /* Restore the original segment in the old trail */
629 set_n_segment(marker, 0);
631 g_object_weak_unref((GObject *) data->trail,
632 (GWeakNotify) unset_trail, marker);
633 adg_model_remove_dependency((AdgModel *) data->trail, entity);
636 data->trail = trail;
637 data->n_segment = 0;
639 if (data->trail != NULL) {
640 g_object_weak_ref((GObject *) data->trail,
641 (GWeakNotify) unset_trail, marker);
642 adg_model_add_dependency((AdgModel *) data->trail, entity);
644 set_n_segment(marker, n_segment);
647 return TRUE;
650 static void
651 unset_trail(AdgMarker *marker)
653 AdgMarkerPrivate *data = marker->data;
655 if (data->trail != NULL)
656 set_segment(marker, NULL, 0);
659 static gboolean
660 set_n_segment(AdgMarker *marker, guint n_segment)
662 AdgMarkerPrivate *data = marker->data;
664 if (n_segment == data->n_segment)
665 return FALSE;
667 if (data->backup_segment != NULL) {
668 /* Restore the original segment, if any */
669 if (data->trail != NULL)
670 adg_segment_deep_copy(&data->segment, data->backup_segment);
672 g_free(data->backup_segment);
673 data->backup_segment = NULL;
676 data->n_segment = n_segment;
678 if (n_segment == 0) {
679 memset(&data->segment, 0, sizeof(data->segment));
680 return TRUE;
683 return adg_trail_put_segment(data->trail, n_segment, &data->segment);
686 static gboolean
687 set_pos(AdgMarker *marker, gdouble pos)
689 AdgMarkerPrivate *data = marker->data;
691 if (pos == data->pos)
692 return FALSE;
694 data->pos = pos;
696 return TRUE;
699 static gboolean
700 set_size(AdgMarker *marker, gdouble size)
702 AdgMarkerPrivate *data = marker->data;
704 if (size == data->size)
705 return FALSE;
707 data->size = size;
709 return TRUE;
712 static gboolean
713 set_model(AdgMarker *marker, AdgModel *model)
715 AdgMarkerPrivate *data = marker->data;
717 if (model == data->model)
718 return FALSE;
720 if (data->model != NULL)
721 g_object_unref((GObject *) data->model);
723 data->model = model;
725 if (data->model != NULL) {
726 g_object_ref((GObject *) data->model);
727 adg_entity_local_changed((AdgEntity *) marker);
730 return TRUE;
733 static AdgModel *
734 create_model(AdgMarker *marker)
736 g_warning("%s: `create_model' method not implemented for type `%s'",
737 G_STRLOC, g_type_name(G_OBJECT_TYPE(marker)));
738 return NULL;