doc: update copyright line for 2021
[adg.git] / src / adg / adg-marker.c
blob0bde205f69e895e51272fb687c3dbe1e3b642418
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-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.
45 * Since: 1.0
46 **/
48 /**
49 * AdgMarker:
51 * All fields are privates and should not be used directly.
52 * Use its public methods instead.
54 * Since: 1.0
55 **/
57 /**
58 * AdgMarkerClass:
59 * @create_model: abstract virtual method that creates a model template for
60 * all the markers used by this class.
62 * The @create_model method must be implemented by any #AdgMarker derived
63 * classes. The derived classes are expected to apply a single model
64 * (the one returned by this method) to every path endings by using
65 * different transformations.
67 * Since: 1.0
68 **/
71 #include "adg-internal.h"
72 #include "adg-model.h"
73 #include "adg-trail.h"
74 #include <string.h>
76 #include "adg-marker.h"
77 #include "adg-marker-private.h"
80 #define _ADG_OLD_OBJECT_CLASS ((GObjectClass *) adg_marker_parent_class)
81 #define _ADG_OLD_ENTITY_CLASS ((AdgEntityClass *) adg_marker_parent_class)
84 G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE(AdgMarker, adg_marker, ADG_TYPE_ENTITY)
86 enum {
87 PROP_0,
88 PROP_TRAIL,
89 PROP_N_SEGMENT,
90 PROP_POS,
91 PROP_SIZE,
92 PROP_MODEL
96 static void _adg_dispose (GObject *object);
97 static void _adg_get_property (GObject *object,
98 guint prop_id,
99 GValue *value,
100 GParamSpec *pspec);
101 static void _adg_set_property (GObject *object,
102 guint prop_id,
103 const GValue *value,
104 GParamSpec *pspec);
105 static void _adg_local_changed (AdgEntity *entity);
106 static void _adg_invalidate (AdgEntity *entity);
107 static void _adg_clear_trail (AdgMarker *marker);
108 static gboolean _adg_set_segment (AdgMarker *marker,
109 AdgTrail *trail,
110 guint n_segment);
111 static AdgModel * _adg_create_model (AdgMarker *marker);
114 static void
115 adg_marker_class_init(AdgMarkerClass *klass)
117 GObjectClass *gobject_class;
118 AdgEntityClass *entity_class;
119 GParamSpec *param;
121 gobject_class = (GObjectClass *) klass;
122 entity_class = (AdgEntityClass *) klass;
124 gobject_class->dispose = _adg_dispose;
125 gobject_class->set_property = _adg_set_property;
126 gobject_class->get_property = _adg_get_property;
128 entity_class->local_changed = _adg_local_changed;
129 entity_class->invalidate = _adg_invalidate;
131 klass->create_model = _adg_create_model;
133 param = g_param_spec_object("trail",
134 P_("Trail"),
135 P_("The subject AdgTrail for this marker"),
136 ADG_TYPE_TRAIL,
137 G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
138 g_object_class_install_property(gobject_class, PROP_TRAIL, param);
140 param = g_param_spec_uint("n-segment",
141 P_("Segment Index"),
142 P_("The segment on trail where this marker should be applied (where 0 means undefined segment, 1 the first segment and so on)"),
143 0, G_MAXUINT, 0,
144 G_PARAM_READWRITE);
145 g_object_class_install_property(gobject_class, PROP_N_SEGMENT, param);
147 param = g_param_spec_double("pos",
148 P_("Position"),
149 P_("The position ratio inside the segment where to put the marker (0 means the start point while 1 means the end point)"),
150 0, 1, 0,
151 G_PARAM_READWRITE|G_PARAM_CONSTRUCT);
152 g_object_class_install_property(gobject_class, PROP_POS, param);
154 param = g_param_spec_double("size",
155 P_("Marker Size"),
156 P_("The size (in global space) of the marker"),
157 0, G_MAXDOUBLE, 10,
158 G_PARAM_READWRITE);
159 g_object_class_install_property(gobject_class, PROP_SIZE, param);
161 param = g_param_spec_object("model",
162 P_("Model"),
163 P_("A general purpose model usable by the marker implementations"),
164 ADG_TYPE_MODEL,
165 G_PARAM_READWRITE);
166 g_object_class_install_property(gobject_class, PROP_MODEL, param);
169 static void
170 adg_marker_init(AdgMarker *marker)
172 AdgMarkerPrivate *data = adg_marker_get_instance_private(marker);
173 data->trail = NULL;
174 data->n_segment = 0;
175 data->backup_segment = NULL;
176 memset(&data->segment, 0, sizeof(data->segment));
177 data->pos = 0;
178 data->size = 10;
179 data->model = NULL;
182 static void
183 _adg_dispose(GObject *object)
185 AdgMarker *marker = (AdgMarker *) object;
187 adg_marker_set_model(marker, NULL);
188 adg_marker_set_segment(marker, NULL, 0);
190 if (_ADG_OLD_OBJECT_CLASS->dispose)
191 _ADG_OLD_OBJECT_CLASS->dispose(object);
195 static void
196 _adg_get_property(GObject *object, guint prop_id,
197 GValue *value, GParamSpec *pspec)
199 AdgMarkerPrivate *data = adg_marker_get_instance_private((AdgMarker *) object);
201 switch (prop_id) {
202 case PROP_TRAIL:
203 g_value_set_object(value, data->trail);
204 break;
205 case PROP_N_SEGMENT:
206 g_value_set_uint(value, data->n_segment);
207 break;
208 case PROP_POS:
209 g_value_set_double(value, data->pos);
210 break;
211 case PROP_SIZE:
212 g_value_set_double(value, data->size);
213 break;
214 case PROP_MODEL:
215 g_value_set_object(value, data->model);
216 break;
217 default:
218 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
219 break;
223 static void
224 _adg_set_property(GObject *object, guint prop_id,
225 const GValue *value, GParamSpec *pspec)
227 AdgMarker *marker = (AdgMarker *) object;
228 AdgMarkerPrivate *data = adg_marker_get_instance_private(marker);
229 AdgModel *old_model;
231 switch (prop_id) {
232 case PROP_TRAIL:
233 _adg_set_segment(marker, g_value_get_object(value), data->n_segment);
234 break;
235 case PROP_N_SEGMENT:
236 _adg_set_segment(marker, data->trail, g_value_get_uint(value));
237 break;
238 case PROP_POS:
239 data->pos = g_value_get_double(value);
240 break;
241 case PROP_SIZE:
242 data->size = g_value_get_double(value);
243 break;
244 case PROP_MODEL:
245 old_model = data->model;
246 data->model = g_value_get_object(value);
248 if (data->model) {
249 g_object_ref((GObject *) data->model);
250 adg_entity_local_changed((AdgEntity *) object);
252 if (old_model)
253 g_object_unref(old_model);
254 break;
255 default:
256 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
257 break;
263 * adg_marker_set_trail:
264 * @marker: an #AdgMarker
265 * @trail: the new trail to use
267 * Sets the #AdgMarker:trail property to @trail. It is allowed to
268 * pass <constant>NULL</constant> to clear the current trail.
270 * This method could fail unexpectedly if the segment index specified
271 * by the #AdgMarker:n-segment property is not present inside the new
272 * segment: if you want to set a new segment it is more convenient to
273 * change both properties (#AdgMarker:trail and #AdgMarker:n-segment)
274 * at once with adg_marker_set_segment().
276 * Since: 1.0
278 void
279 adg_marker_set_trail(AdgMarker *marker, AdgTrail *trail)
281 g_return_if_fail(ADG_IS_MARKER(marker));
282 g_object_set(marker, "trail", trail, NULL);
286 * adg_marker_get_trail:
287 * @marker: an #AdgMarker
289 * Gets the trail where this marker should be applied.
290 * The returned object is owned by @marker and should not be
291 * freed or modified.
293 * Returns: (transfer none): the requested trail or <constant>NULL</constant> on errors.
295 * Since: 1.0
297 AdgTrail *
298 adg_marker_get_trail(AdgMarker *marker)
300 AdgMarkerPrivate *data;
302 g_return_val_if_fail(ADG_IS_MARKER(marker), NULL);
304 data = adg_marker_get_instance_private(marker);
305 return data->trail;
309 * adg_marker_set_n_segment:
310 * @marker: an #AdgMarker
311 * @n_segment: the new segment index
313 * Sets the #AdgMarker:n-segment property to @n_segment. The trail
314 * is unchanged. If you want to set both properties at once (as
315 * usually requested to refer to a specific segment),
316 * adg_marker_set_segment() should be more convenient.
318 * Since: 1.0
320 void
321 adg_marker_set_n_segment(AdgMarker *marker, guint n_segment)
323 g_return_if_fail(ADG_IS_MARKER(marker));
324 g_object_set(marker, "n-segment", n_segment, NULL);
328 * adg_marker_get_n_segment:
329 * @marker: an #AdgMarker
331 * Returns the segment of the associated trail where this marker
332 * will be applied, where 1 is the first segment.
334 * Returns: an index greather than 0 on success or 0 on errors.
336 * Since: 1.0
338 guint
339 adg_marker_get_n_segment(AdgMarker *marker)
341 AdgMarkerPrivate *data;
343 g_return_val_if_fail(ADG_IS_MARKER(marker), 0);
345 data = adg_marker_get_instance_private(marker);
346 return data->n_segment;
350 * adg_marker_set_segment:
351 * @marker: an #AdgMarker
352 * @trail: the #AdgTrail
353 * @n_segment: a segment index
355 * Sets a new segment where the marker should be applied at once.
356 * A dependency between @trail and @marker is added, so when @trail
357 * changes @marker is invalidated.
359 * A callback is added to #AdgModel::remove-dependency so manually
360 * removing the dependency (such as when @trail is destroyed) will
361 * unlink @marker from it.
363 * Since: 1.0
365 void
366 adg_marker_set_segment(AdgMarker *marker, AdgTrail *trail, guint n_segment)
368 g_return_if_fail(ADG_IS_MARKER(marker));
369 /* To avoid referring to an inexistent trail/n-segment couple, the
370 * "n-segment" property is reset before to avoid segment validation */
371 g_object_set(marker, "n-segment", 0,
372 "trail", trail, "n-segment", n_segment, NULL);
376 * adg_marker_get_segment:
377 * @marker: an #AdgMarker
379 * <note><para>
380 * This function is only useful in marker implementations.
381 * </para></note>
383 * Gets the segment where the marker will be applied. This segment
384 * is eventually a modified version of the backup segment, after
385 * having applied the marker.
387 * Returns: the segment or <constant>NULL</constant> on errors.
389 * Since: 1.0
391 const CpmlSegment *
392 adg_marker_get_segment(AdgMarker *marker)
394 AdgMarkerPrivate *data;
396 g_return_val_if_fail(ADG_IS_MARKER(marker), NULL);
398 data = adg_marker_get_instance_private(marker);
399 return &data->segment;
403 * adg_marker_backup_segment:
404 * @marker: an #AdgMarker
406 * <note><para>
407 * This function is only useful in marker implementations.
408 * </para></note>
410 * Duplicates the current subject segment for backup purpose: this
411 * segment can be accessed by adg_marker_get_backup_segment().
413 * A current segment should exist (i.e. both #AdgMarker:trail and
414 * #AdgMarker:n-segment properties must be properly set) or this
415 * method will fail without further processing.
417 * When the subject segment is changed (either by changing
418 * #AdgMarker:trail or #AdgMarker:n-segment) the original segment
419 * is automatically restored.
421 * Since: 1.0
423 void
424 adg_marker_backup_segment(AdgMarker *marker)
426 AdgMarkerPrivate *data;
428 g_return_if_fail(ADG_IS_MARKER(marker));
430 data = adg_marker_get_instance_private(marker);
432 if (data->n_segment > 0) {
433 g_return_if_fail(data->trail != NULL);
435 g_free(data->backup_segment);
437 /* Backup the segment, if a segment to backup exists */
438 if (adg_trail_put_segment(data->trail, data->n_segment,
439 &data->segment))
440 data->backup_segment = cpml_segment_deep_dup(&data->segment);
441 else
442 data->backup_segment = NULL;
447 * adg_marker_get_backup_segment:
448 * @marker: an #AdgMarker
450 * <note><para>
451 * This function is only useful in marker implementations.
452 * </para></note>
454 * Gets the original segment where the marker has been applied.
455 * Applying a marker could modify the underlying trail, usually
456 * by trimming the original segment of a #AdgMarker:size dependent
457 * length from the ends. The marker instance holds a copy of the
458 * original segment, generated by adg_marker_backup_segment(),
459 * to be used in recomputation, for example when the marker
460 * changes its size.
462 * When the subject segment is changed (either by changing
463 * #AdgMarker:trail or #AdgMarker:n-segment) the original segment
464 * is automatically restored.
466 * Returns: the original segment or <constant>NULL</constant> on errors.
468 * Since: 1.0
470 const CpmlSegment *
471 adg_marker_get_backup_segment(AdgMarker *marker)
473 AdgMarkerPrivate *data;
475 g_return_val_if_fail(ADG_IS_MARKER(marker), NULL);
477 data = adg_marker_get_instance_private(marker);
478 return data->backup_segment;
482 * adg_marker_set_pos:
483 * @marker: an #AdgMarker
484 * @pos: the new pos
486 * Sets a new position on @marker. Check out adg_marker_get_pos() for
487 * details on what @pos represents.
489 * Since: 1.0
491 void
492 adg_marker_set_pos(AdgMarker *marker, gdouble pos)
494 g_return_if_fail(ADG_IS_MARKER(marker));
495 g_object_set(marker, "pos", pos, NULL);
499 * adg_marker_get_pos:
500 * @marker: an #AdgMarker
502 * Gets the current position of @marker. The returned value is a ratio
503 * position referred to the segment associated to @marker: 0 means the
504 * start point and 1 means the end point of the segment.
506 * Returns: the marker position.
508 * Since: 1.0
510 gdouble
511 adg_marker_get_pos(AdgMarker *marker)
513 AdgMarkerPrivate *data;
515 g_return_val_if_fail(ADG_IS_MARKER(marker), 0);
517 data = adg_marker_get_instance_private(marker);
518 return data->pos;
522 * adg_marker_set_size:
523 * @marker: an #AdgMarker
524 * @size: the new size
526 * Sets a new size on @marker. The @size is an implementation-dependent
527 * property: it has meaning only when used by an #AdgMarker derived type.
529 * Since: 1.0
531 void
532 adg_marker_set_size(AdgMarker *marker, gdouble size)
534 g_return_if_fail(ADG_IS_MARKER(marker));
535 g_object_set(marker, "size", size, NULL);
539 * adg_marker_get_size:
540 * @marker: an #AdgMarker
542 * Gets the current size of @marker.
544 * Returns: the marker size, in global space
546 * Since: 1.0
548 gdouble
549 adg_marker_get_size(AdgMarker *marker)
551 AdgMarkerPrivate *data;
553 g_return_val_if_fail(ADG_IS_MARKER(marker), 0);
555 data = adg_marker_get_instance_private(marker);
556 return data->size;
560 * adg_marker_set_model:
561 * @marker: an #AdgMarker
562 * @model: a new #AdgModel
564 * <note><para>
565 * This function is only useful in marker implementations.
566 * </para></note>
568 * Sets a new model for @marker. The reference to the old model (if an
569 * old model was present) is dropped while a new reference is added to
570 * @model.
572 * Since: 1.0
574 void
575 adg_marker_set_model(AdgMarker *marker, AdgModel *model)
577 g_return_if_fail(ADG_IS_MARKER(marker));
578 g_object_set(marker, "model", model, NULL);
582 * adg_marker_get_model:
583 * @marker: an #AdgMarker
585 * <note><para>
586 * This function is only useful in marker implementations.
587 * </para></note>
589 * Gets the current model of @marker. This is an accessor method:
590 * if you need to get the model for rendering, use adg_marker_model()
591 * instead. The returned object is owned by @marker and should not be
592 * freed or modified.
594 * Returns: (transfer none): the cached model or <constant>NULL</constant> on errors.
596 * Since: 1.0
598 AdgModel *
599 adg_marker_get_model(AdgMarker *marker)
601 AdgMarkerPrivate *data;
603 g_return_val_if_fail(ADG_IS_MARKER(marker), NULL);
605 data = adg_marker_get_instance_private(marker);
606 return data->model;
610 * adg_marker_model:
611 * @marker: an #AdgMarker
613 * <note><para>
614 * This function is only useful in marker implementations.
615 * </para></note>
617 * Gets the model of @marker. If the model is not found, it is
618 * automatically created by calling the <function>create_model</function>
619 * virtual method. The returned object is owned by @marker and
620 * should not be freed or modified.
622 * Returns: (transfer none): the current model or <constant>NULL</constant> on errors.
624 * Since: 1.0
626 AdgModel *
627 adg_marker_model(AdgMarker *marker)
629 AdgMarkerPrivate *data;
631 g_return_val_if_fail(ADG_IS_MARKER(marker), NULL);
633 data = adg_marker_get_instance_private(marker);
635 if (data->model == NULL) {
636 /* Model not found: regenerate it */
637 AdgMarkerClass *marker_class = ADG_MARKER_GET_CLASS(marker);
639 if (marker_class->create_model)
640 adg_marker_set_model(marker, marker_class->create_model(marker));
643 return data->model;
647 static void
648 _adg_local_changed(AdgEntity *entity)
650 AdgMarkerPrivate *data = adg_marker_get_instance_private((AdgMarker *) entity);
652 /* On invalid segments, segment.data is not set: do not crash */
653 if (data->segment.data != NULL) {
654 CpmlPair pair;
655 CpmlVector vector;
656 cairo_matrix_t map;
658 cpml_segment_put_pair_at(&data->segment, data->pos, &pair);
659 cpml_segment_put_vector_at(&data->segment, data->pos, &vector);
660 cpml_vector_set_length(&vector, data->size);
662 if (data->pos > 0.5) {
663 vector.x = -vector.x;
664 vector.y = -vector.y;
667 cairo_matrix_init(&map, vector.x, vector.y,
668 -vector.y, vector.x, pair.x, pair.y);
670 adg_entity_set_local_map(entity, &map);
674 if (_ADG_OLD_ENTITY_CLASS->local_changed)
675 _ADG_OLD_ENTITY_CLASS->local_changed(entity);
678 static void
679 _adg_invalidate(AdgEntity *entity)
681 adg_marker_set_model((AdgMarker *) entity, NULL);
685 static void
686 _adg_clear_trail(AdgMarker *marker)
688 AdgMarkerPrivate *data = adg_marker_get_instance_private(marker);
690 if (data->trail && data->backup_segment) {
691 /* Restore the original segment in the old trail */
692 cpml_segment_copy_data(&data->segment, data->backup_segment);
693 g_free(data->backup_segment);
694 data->backup_segment = NULL;
697 data->trail = NULL;
698 data->n_segment = 0;
701 static gboolean
702 _adg_set_segment(AdgMarker *marker, AdgTrail *trail, guint n_segment)
704 AdgMarkerPrivate *data;
705 CpmlSegment segment = { 0 };
707 g_return_val_if_fail(trail == NULL || ADG_IS_TRAIL(trail), FALSE);
709 /* Segment validation, but only if trail and n_segment are specified */
710 if (trail && n_segment > 0 && !adg_trail_put_segment(trail, n_segment, &segment))
711 return FALSE;
713 data = adg_marker_get_instance_private(marker);
715 /* Do not try to cache results! Although @trail and @n_segment
716 * could be the same, the internal CpmlSegment could change.
717 * This is the case when AdgLDim arranges the layout and changes
718 * the path data (to force outside arrows for example) reusing
719 * the same CpmlPath. In other words, do not do this:
721 * if (trail == data->trail && n_segment == data->n_segment)
722 * return FALSE;
724 * Incidentally, on a 64 bit platform this issue has been never
725 * exposed. Avoiding the cache will solve the issue 33:
727 * http://dev.entidi.com/p/adg/issues/33/
730 if (trail != data->trail) {
731 AdgEntity *entity = (AdgEntity *) marker;
733 if (data->trail)
734 adg_model_remove_dependency((AdgModel *) data->trail, entity);
736 data->trail = trail;
738 if (trail) {
739 adg_model_add_dependency((AdgModel *) trail, entity);
740 g_signal_connect_swapped(trail, "remove-dependency",
741 G_CALLBACK(_adg_clear_trail), marker);
745 cpml_segment_copy(&data->segment, &segment);
746 data->n_segment = n_segment;
748 return TRUE;
751 static AdgModel *
752 _adg_create_model(AdgMarker *marker)
754 g_warning(_("%s: 'create_model' method not implemented for type '%s'"),
755 G_STRLOC, g_type_name(G_OBJECT_TYPE(marker)));
756 return NULL;