adg: provide example on how to customize a style
[adg.git] / src / adg / adg-dim.c
blob10bcfa942708df5b3070a6fe8b6455b8bb9879d4
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.
21 /**
22 * SECTION:adg-dim
23 * @short_description: Root abstract class for all dimension entities
25 * The #AdgDim class is the base stub of all the dimension entities.
27 * Since: 1.0
28 **/
30 /**
31 * AdgDim:
33 * All fields are private and should not be used directly.
34 * Use its public methods instead.
36 * Since: 1.0
37 **/
39 /**
40 * AdgDimClass:
41 * @quote_angle: virtual method that must return the rotation angle of the
42 * quote (in radians) of the current dimension.
43 * @default_value: abstract virtual method that must return the default value
44 * (as a newly allocated string to be freed with g_free()) of
45 * the current dimension.
47 * The default implementation of @quote_angle flips the quote if it should be
48 * rotated in the bottom right half of the circle, that is:
50 * <informalexample><programlisting>
51 * if 1/3 PI <= angle <= -3/4 PI; then angle += PI.
52 * </programlisting></informalexample>
54 * The virtual method @default_value instead *must* be implemented by any
55 * derived class. The default implementation will trigger an error if called.
57 * Since: 1.0
58 **/
61 #include "adg-internal.h"
62 #include "adg-text-internal.h"
64 #include "adg-container.h"
65 #include "adg-alignment.h"
66 #include "adg-model.h"
67 #include "adg-trail.h"
68 #include "adg-point.h"
69 #include "adg-marker.h"
70 #include "adg-dim-style.h"
71 #include "adg-dress.h"
72 #include "adg-param-dress.h"
74 #include "adg-dim.h"
75 #include "adg-dim-private.h"
77 #include <string.h>
81 #define _ADG_OLD_OBJECT_CLASS ((GObjectClass *) adg_dim_parent_class)
82 #define _ADG_OLD_ENTITY_CLASS ((AdgEntityClass *) adg_dim_parent_class)
84 /* A convenience macro for ORing two AdgThreeState values */
85 #define OR_3S(a,b) ( \
86 ((a) == ADG_THREE_STATE_ON || (b) == ADG_THREE_STATE_ON) ? ADG_THREE_STATE_ON : \
87 ((a) == ADG_THREE_STATE_UNKNOWN && (b) == ADG_THREE_STATE_UNKNOWN) ? ADG_THREE_STATE_UNKNOWN : \
88 ADG_THREE_STATE_OFF )
91 G_DEFINE_ABSTRACT_TYPE(AdgDim, adg_dim, ADG_TYPE_ENTITY)
93 enum {
94 PROP_0,
95 PROP_DIM_DRESS,
96 PROP_REF1,
97 PROP_REF2,
98 PROP_POS,
99 PROP_LEVEL,
100 PROP_OUTSIDE,
101 PROP_DETACHED,
102 PROP_VALUE,
103 PROP_MIN,
104 PROP_MAX
108 static void _adg_dispose (GObject *object);
109 static void _adg_finalize (GObject *object);
110 static void _adg_get_property (GObject *object,
111 guint param_id,
112 GValue *value,
113 GParamSpec *pspec);
114 static void _adg_set_property (GObject *object,
115 guint param_id,
116 const GValue *value,
117 GParamSpec *pspec);
118 static void _adg_global_changed (AdgEntity *entity);
119 static void _adg_local_changed (AdgEntity *entity);
120 static void _adg_invalidate (AdgEntity *entity);
121 static void _adg_arrange (AdgEntity *entity);
122 static gchar * _adg_default_value (AdgDim *dim);
123 static gdouble _adg_quote_angle (gdouble angle);
124 static gboolean _adg_set_outside (AdgDim *dim,
125 AdgThreeState outside);
126 static gboolean _adg_set_detached (AdgDim *dim,
127 AdgThreeState detached);
128 static gboolean _adg_set_value (AdgDim *dim,
129 const gchar *value);
130 static gboolean _adg_set_min (AdgDim *dim,
131 const gchar *min);
132 static gboolean _adg_set_max (AdgDim *dim,
133 const gchar *max);
134 static gboolean _adg_replace (const GMatchInfo *match_info,
135 GString *result,
136 gpointer user_data);
137 static gchar * _adg_text_expand (AdgDimReplaceData *data);
140 static void
141 adg_dim_class_init(AdgDimClass *klass)
143 GObjectClass *gobject_class;
144 AdgEntityClass *entity_class;
145 GParamSpec *param;
147 gobject_class = (GObjectClass *) klass;
148 entity_class = (AdgEntityClass *) klass;
150 g_type_class_add_private(klass, sizeof(AdgDimPrivate));
152 gobject_class->dispose = _adg_dispose;
153 gobject_class->finalize = _adg_finalize;
154 gobject_class->get_property = _adg_get_property;
155 gobject_class->set_property = _adg_set_property;
157 entity_class->global_changed = _adg_global_changed;
158 entity_class->local_changed = _adg_local_changed;
159 entity_class->invalidate = _adg_invalidate;
160 entity_class->arrange = _adg_arrange;
162 klass->quote_angle = _adg_quote_angle;
163 klass->default_value = _adg_default_value;
165 param = adg_param_spec_dress("dim-dress",
166 P_("Dimension Dress"),
167 P_("The dress to use for rendering this dimension"),
168 ADG_DRESS_DIMENSION,
169 G_PARAM_READWRITE);
170 g_object_class_install_property(gobject_class, PROP_DIM_DRESS, param);
172 param = g_param_spec_boxed("ref1",
173 P_("First Reference"),
174 P_("First reference point of the dimension"),
175 ADG_TYPE_POINT,
176 G_PARAM_READWRITE);
177 g_object_class_install_property(gobject_class, PROP_REF1, param);
179 param = g_param_spec_boxed("ref2",
180 P_("Second Reference"),
181 P_("Second reference point of the dimension"),
182 ADG_TYPE_POINT,
183 G_PARAM_READWRITE);
184 g_object_class_install_property(gobject_class, PROP_REF2, param);
186 param = g_param_spec_boxed("pos",
187 P_("Position"),
188 P_("The reference position of the quote: it will be combined with \"level\" to get the real quote position"),
189 ADG_TYPE_POINT,
190 G_PARAM_READWRITE);
191 g_object_class_install_property(gobject_class, PROP_POS, param);
193 param = g_param_spec_double("level",
194 P_("Level"),
195 P_("The dimension level, that is the factor to multiply the baseline spacing (defined in the dimension style) to get the offset from pos where the quote should be rendered"),
196 -G_MAXDOUBLE, G_MAXDOUBLE, 1.0,
197 G_PARAM_READWRITE);
198 g_object_class_install_property(gobject_class, PROP_LEVEL, param);
200 param = g_param_spec_enum("outside",
201 P_("Outside"),
202 P_("Whether the arrows must be inside the extension lines (ADG_THREE_STATE_OFF), must be extended outside the extension lines (ADG_THREE_STATE_ON) or should be automatically handled depending on the available space"),
203 ADG_TYPE_THREE_STATE, ADG_THREE_STATE_UNKNOWN,
204 G_PARAM_READWRITE);
205 g_object_class_install_property(gobject_class, PROP_OUTSIDE, param);
207 param = g_param_spec_enum("detached",
208 P_("Detached Quote"),
209 P_("Where the quote must be positioned: in the middle of the base line (ADG_THREE_STATE_OFF), near the pos point (ADG_THREE_STATE_ON) or should be automatically deducted depending on the available space"),
210 ADG_TYPE_THREE_STATE, ADG_THREE_STATE_UNKNOWN,
211 G_PARAM_READWRITE);
212 g_object_class_install_property(gobject_class, PROP_DETACHED, param);
214 param = g_param_spec_string("value",
215 P_("Value Template"),
216 P_("The template string to be used for generating the set value of the quote"),
217 NULL,
218 G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
219 g_object_class_install_property(gobject_class, PROP_VALUE, param);
221 param = g_param_spec_string("min",
222 P_("Minimum Value or Low Tolerance"),
223 P_("The minimum value allowed or the lowest tolerance from value (depending of the dimension style): set to NULL to suppress"),
224 NULL,
225 G_PARAM_READWRITE);
226 g_object_class_install_property(gobject_class, PROP_MIN, param);
228 param = g_param_spec_string("max",
229 P_("Maximum Value or High Tolerance"),
230 P_("The maximum value allowed or the highest tolerance from value (depending of the dimension style): set to NULL to suppress"),
231 NULL,
232 G_PARAM_READWRITE);
233 g_object_class_install_property(gobject_class, PROP_MAX, param);
236 static void
237 adg_dim_init(AdgDim *dim)
239 AdgDimPrivate *data = G_TYPE_INSTANCE_GET_PRIVATE(dim, ADG_TYPE_DIM,
240 AdgDimPrivate);
242 data->dim_dress = ADG_DRESS_DIMENSION;
243 data->ref1 = NULL;
244 data->ref2 = NULL;
245 data->pos = NULL;
246 data->level = 1;
247 data->outside = ADG_THREE_STATE_UNKNOWN;
248 data->detached = ADG_THREE_STATE_UNKNOWN;
249 data->min = NULL;
250 data->max = NULL;
252 #if 0
253 /* This one is G_PARAM_CONSTRUCT, so set by property inizialization */
254 data->value = NULL
255 #endif
257 dim->data = data;
260 static void
261 _adg_dispose(GObject *object)
263 AdgEntity *entity;
264 AdgDimPrivate *data;
266 entity = (AdgEntity *) object;
267 data = ((AdgDim *) object)->data;
269 if (data->quote.entity) {
270 g_object_unref(data->quote.entity);
271 data->quote.entity = NULL;
274 if (data->ref1)
275 data->ref1 = adg_entity_point(entity, data->ref1, NULL);
277 if (data->ref2)
278 data->ref2 = adg_entity_point(entity, data->ref2, NULL);
280 if (data->pos)
281 data->pos = adg_entity_point(entity, data->pos, NULL);
283 if (_ADG_OLD_OBJECT_CLASS->dispose)
284 _ADG_OLD_OBJECT_CLASS->dispose(object);
287 static void
288 _adg_finalize(GObject *object)
290 AdgDimPrivate *data = ((AdgDim *) object)->data;
292 g_free(data->value);
293 g_free(data->min);
294 g_free(data->max);
296 if (_ADG_OLD_OBJECT_CLASS->finalize)
297 _ADG_OLD_OBJECT_CLASS->finalize(object);
300 static void
301 _adg_get_property(GObject *object, guint prop_id,
302 GValue *value, GParamSpec *pspec)
304 AdgDimPrivate *data = ((AdgDim *) object)->data;
306 switch (prop_id) {
307 case PROP_DIM_DRESS:
308 g_value_set_enum(value, data->dim_dress);
309 break;
310 case PROP_REF1:
311 g_value_set_boxed(value, data->ref1);
312 break;
313 case PROP_REF2:
314 g_value_set_boxed(value, data->ref2);
315 break;
316 case PROP_POS:
317 g_value_set_boxed(value, data->pos);
318 break;
319 case PROP_LEVEL:
320 g_value_set_double(value, data->level);
321 break;
322 case PROP_OUTSIDE:
323 g_value_set_enum(value, data->outside);
324 break;
325 case PROP_DETACHED:
326 g_value_set_enum(value, data->detached);
327 break;
328 case PROP_VALUE:
329 g_value_set_string(value, data->value);
330 break;
331 case PROP_MIN:
332 g_value_set_string(value, data->min);
333 break;
334 case PROP_MAX:
335 g_value_set_string(value, data->max);
336 break;
337 default:
338 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
339 break;
343 static void
344 _adg_set_property(GObject *object, guint prop_id,
345 const GValue *value, GParamSpec *pspec)
347 AdgEntity *entity;
348 AdgDim *dim;
349 AdgDimPrivate *data;
351 entity = (AdgEntity *) object;
352 dim = (AdgDim *) object;
353 data = dim->data;
355 switch (prop_id) {
356 case PROP_DIM_DRESS:
357 data->dim_dress = g_value_get_enum(value);
358 break;
359 case PROP_REF1:
360 data->ref1 = adg_entity_point(entity, data->ref1,
361 g_value_get_boxed(value));
362 break;
363 case PROP_REF2:
364 data->ref2 = adg_entity_point(entity, data->ref2,
365 g_value_get_boxed(value));
366 break;
367 case PROP_POS:
368 data->pos = adg_entity_point(entity, data->pos,
369 g_value_get_boxed(value));
370 break;
371 case PROP_LEVEL:
372 data->level = g_value_get_double(value);
373 break;
374 case PROP_OUTSIDE:
375 _adg_set_outside(dim, g_value_get_enum(value));
376 break;
377 case PROP_DETACHED:
378 _adg_set_detached(dim, g_value_get_enum(value));
379 break;
380 case PROP_VALUE:
381 _adg_set_value(dim, g_value_get_string(value));
382 break;
383 case PROP_MIN:
384 _adg_set_min(dim, g_value_get_string(value));
385 break;
386 case PROP_MAX:
387 _adg_set_max(dim, g_value_get_string(value));
388 break;
389 default:
390 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
391 break;
397 * adg_dim_set_dim_dress:
398 * @dim: an #AdgDim
399 * @dress: the new #AdgDress to use
401 * Sets a new dimension dress to @dim. The new dress must be
402 * related to the original dress for this property: you cannot
403 * set a dress used for line styles to a dress managing fonts.
405 * The check is done by calling adg_dress_are_related() with
406 * @dress and the previous dress as arguments. Check out its
407 * documentation for details on what is a related dress.
409 * Since: 1.0
411 void
412 adg_dim_set_dim_dress(AdgDim *dim, AdgDress dress)
414 g_return_if_fail(ADG_IS_DIM(dim));
415 g_object_set(dim, "dim-dress", dress, NULL);
419 * adg_dim_get_dim_dress:
420 * @dim: an #AdgDim
422 * Gets the dimension dress to be used in rendering @dim.
424 * Returns: (transfer none): the current dimension dress.
426 * Since: 1.0
428 AdgDress
429 adg_dim_get_dim_dress(AdgDim *dim)
431 AdgDimPrivate *data;
433 g_return_val_if_fail(ADG_IS_DIM(dim), ADG_DRESS_UNDEFINED);
435 data = dim->data;
437 return data->dim_dress;
441 * adg_dim_set_ref1:
442 * @dim: an #AdgDim
443 * @ref1: the new point to use as first reference
445 * Sets the #AdgDim:ref1 property to @ref1. The old point
446 * is silently discarded, unreferencing its model if that
447 * point was bound to a named pair (hence, possibly destroying
448 * the model if this was the last reference).
450 * @ref1 can be <constant>NULL</constant>, in which case the
451 * point is destroyed.
453 * Since: 1.0
455 void
456 adg_dim_set_ref1(AdgDim *dim, const AdgPoint *ref1)
458 g_return_if_fail(ADG_IS_DIM(dim));
459 g_object_set(dim, "ref1", ref1, NULL);
463 * adg_dim_set_ref1_explicit:
464 * @dim: an #AdgDim
465 * @x: x coordinate of the first reference point
466 * @y: y coordinate of the first reference point
468 * Sets the #AdgDim:ref1 property to the (@x, @y) explicit
469 * coordinates. The old point is silently discarded,
470 * unreferencing its model if that point was bound to a named
471 * pair (hence, possibly destroying the model if this was the
472 * last reference).
474 * Since: 1.0
476 void
477 adg_dim_set_ref1_explicit(AdgDim *dim, gdouble x, gdouble y)
479 AdgPoint *point = adg_point_new();
481 adg_point_set_pair_explicit(point, x, y);
482 adg_dim_set_ref1(dim, point);
484 adg_point_destroy(point);
488 * adg_dim_set_ref1_from_pair:
489 * @dim: an #AdgDim
490 * @ref1: the coordinates pair of the first reference point
492 * Convenient function to set the #AdgDim:ref1 property using a
493 * pair instead of explicit coordinates.
495 * Since: 1.0
497 void
498 adg_dim_set_ref1_from_pair(AdgDim *dim, const CpmlPair *ref1)
500 g_return_if_fail(ref1 != NULL);
502 adg_dim_set_ref1_explicit(dim, ref1->x, ref1->y);
506 * adg_dim_set_ref1_from_model:
507 * @dim: an #AdgDim
508 * @model: the source #AdgModel
509 * @ref1: a named pair in @model
511 * Binds #AdgDim:ref1 to the @ref1 named pair of @model. If @model
512 * is <constant>NULL</constant>, the point will be unset. In any case,
513 * the old point is silently discarded, unreferencing its model
514 * if that point was bound to a named pair (hence, possibly destroying
515 * the model if this was the last reference).
517 * The assignment is lazy so @ref1 could be not be present in @model.
518 * Anyway, at the first access to this point an error will be raised
519 * if the named pair is still missing.
521 * Since: 1.0
523 void
524 adg_dim_set_ref1_from_model(AdgDim *dim, AdgModel *model, const gchar *ref1)
526 AdgPoint *point = adg_point_new();
528 adg_point_set_pair_from_model(point, model, ref1);
529 adg_dim_set_ref1(dim, point);
531 adg_point_destroy(point);
535 * adg_dim_get_ref1:
536 * @dim: an #AdgDim
538 * Gets the #AdgDim:ref1 point of @dim.
540 * The returned point is internally owned and must not be freed
541 * or modified. Anyway it is not const because a call to
542 * adg_point_update() with the returned value must be able to
543 * modify the internal cache.
545 * Returns: (transfer none): the first reference point.
547 * Since: 1.0
549 AdgPoint *
550 adg_dim_get_ref1(AdgDim *dim)
552 AdgDimPrivate *data;
554 g_return_val_if_fail(ADG_IS_DIM(dim), NULL);
556 data = dim->data;
558 return data->ref1;
562 * adg_dim_set_ref2:
563 * @dim: an #AdgDim
564 * @ref2: the new point to use as second reference
566 * Sets the #AdgDim:ref2 property to @ref2. The old point
567 * is silently discarded, unreferencing its model if that
568 * point was bound to a named pair (hence, possibly destroying
569 * the model if it was the last reference).
571 * @ref2 can be <constant>NULL</constant>, in which case
572 * the point is destroyed.
574 * Since: 1.0
576 void
577 adg_dim_set_ref2(AdgDim *dim, const AdgPoint *ref2)
579 g_return_if_fail(ADG_IS_DIM(dim));
580 g_object_set(dim, "ref2", ref2, NULL);
584 * adg_dim_set_ref2_explicit:
585 * @dim: an #AdgDim
586 * @x: x coordinate of the second reference point
587 * @y: y coordinate of the second reference point
589 * Sets the #AdgDim:ref2 property to the (@x, @y) explicit
590 * coordinates. The old point is silently discarded,
591 * unreferencing its model if that point was bound to a named
592 * pair (hence, possibly destroying the model if this was the
593 * last reference).
595 * Since: 1.0
597 void
598 adg_dim_set_ref2_explicit(AdgDim *dim, gdouble x, gdouble y)
600 AdgPoint *point = adg_point_new();
602 adg_point_set_pair_explicit(point, x, y);
603 adg_dim_set_ref2(dim, point);
605 adg_point_destroy(point);
609 * adg_dim_set_ref2_from_pair:
610 * @dim: an #AdgDim
611 * @ref2: the coordinates pair of the second reference point
613 * Convenient function to set the #AdgDim:ref2 property using a
614 * pair instead of explicit coordinates.
616 * Since: 1.0
618 void
619 adg_dim_set_ref2_from_pair(AdgDim *dim, const CpmlPair *ref2)
621 g_return_if_fail(ref2 != NULL);
623 adg_dim_set_ref2_explicit(dim, ref2->x, ref2->y);
627 * adg_dim_set_ref2_from_model:
628 * @dim: an #AdgDim
629 * @model: the source #AdgModel
630 * @ref2: a named pair in @model
632 * Binds #AdgDim:ref2 to the @ref2 named pair of @model. If @model
633 * is <constant>NULL</constant>, the point will be unset. In any
634 * case, the old point is silently discarded, unreferencing its
635 * model if that point was bound to a named pair (hence, possibly
636 * destroying the model if this was the last reference).
638 * The assignment is lazy so @ref2 could be not be present in @model.
639 * Anyway, at the first access to this point an error will be raised
640 * if the named pair is still missing.
642 * Since: 1.0
644 void
645 adg_dim_set_ref2_from_model(AdgDim *dim, AdgModel *model, const gchar *ref2)
647 AdgPoint *point = adg_point_new();
649 adg_point_set_pair_from_model(point, model, ref2);
650 adg_dim_set_ref2(dim, point);
652 adg_point_destroy(point);
656 * adg_dim_get_ref2:
657 * @dim: an #AdgDim
659 * Gets the #AdgDim:ref2 point of @dim.
661 * The returned point is internally owned and must not be freed
662 * or modified. Anyway it is not const because a call to
663 * adg_point_update() with the returned value must be able to
664 * modify the internal cache.
666 * Returns: (transfer none): the second reference point.
668 * Since: 1.0
670 AdgPoint *
671 adg_dim_get_ref2(AdgDim *dim)
673 AdgDimPrivate *data;
675 g_return_val_if_fail(ADG_IS_DIM(dim), NULL);
677 data = dim->data;
679 return data->ref2;
683 * adg_dim_set_pos:
684 * @dim: an #AdgDim
685 * @pos: the new point to use as position
687 * Sets the #AdgDim:pos property of @dim to @pos. The old point
688 * is silently discarded, unreferencing its model if that
689 * point was bound to a named pair (hence, possibly destroying
690 * the model if it was the last reference).
692 * @pos can be <constant>NULL</constant>, in which case the
693 * point is destroyed.
695 * Since: 1.0
697 void
698 adg_dim_set_pos(AdgDim *dim, const AdgPoint *pos)
700 g_return_if_fail(ADG_IS_DIM(dim));
701 g_object_set(dim, "pos", pos, NULL);
705 * adg_dim_set_pos_explicit:
706 * @dim: an #AdgDim
707 * @x: x coordinate of the position
708 * @y: y coordinate of the position
710 * Sets the #AdgDim:pos property to the (@x, @y) explicit
711 * coordinates. The old point is silently discarded,
712 * unreferencing its model if that point was bound to a named
713 * pair (hence, possibly destroying the model if this was the
714 * last reference).
716 * Since: 1.0
718 void
719 adg_dim_set_pos_explicit(AdgDim *dim, gdouble x, gdouble y)
721 AdgPoint *point = adg_point_new();
723 adg_point_set_pair_explicit(point, x, y);
724 adg_dim_set_pos(dim, point);
726 adg_point_destroy(point);
730 * adg_dim_set_pos_from_pair:
731 * @dim: an #AdgDim
732 * @pos: the coordinates pair of the position point
734 * Convenient function to set the #AdgDim:pos property using a
735 * pair instead of explicit coordinates.
737 * Since: 1.0
739 void
740 adg_dim_set_pos_from_pair(AdgDim *dim, const CpmlPair *pos)
742 g_return_if_fail(pos != NULL);
744 adg_dim_set_pos_explicit(dim, pos->x, pos->y);
748 * adg_dim_set_pos_from_model:
749 * @dim: an #AdgDim
750 * @model: the source #AdgModel
751 * @pos: a named pair in @model
753 * Binds #AdgDim:pos to the @pos named pair of @model. If @model
754 * is <constant>NULL</constant>, the point will be unset. In any
755 * case, the old point is silently discarded, unreferencing its
756 * model if that point was bound to a named pair (hence,
757 * possibly destroying the model if this was the last reference).
759 * The assignment is lazy so @pos could be not be present in @model.
760 * Anyway, at the first access to this point an error will be raised
761 * if the named pair is still missing.
763 * Since: 1.0
765 void
766 adg_dim_set_pos_from_model(AdgDim *dim, AdgModel *model, const gchar *pos)
768 AdgPoint *point = adg_point_new();
770 adg_point_set_pair_from_model(point, model, pos);
771 adg_dim_set_pos(dim, point);
773 adg_point_destroy(point);
777 * adg_dim_get_pos:
778 * @dim: an #AdgDim
780 * Gets the #AdgDim:pos point of @dim.
782 * The returned point is internally owned and must not be freed
783 * or modified. Anyway it is not const because a call to
784 * adg_point_update() with the returned value must be able to
785 * modify the internal cache.
787 * Returns: (transfer none): the position point.
789 * Since: 1.0
791 AdgPoint *
792 adg_dim_get_pos(AdgDim *dim)
794 AdgDimPrivate *data;
796 g_return_val_if_fail(ADG_IS_DIM(dim), NULL);
798 data = dim->data;
800 return data->pos;
804 * adg_dim_set_level:
805 * @dim: an #AdgDim
806 * @level: the new level
808 * Sets a new level for this dimension. The level is used to
809 * stack the quotes using a spacing value from dim_style
810 * (specified in global space).
812 * Since: 1.0
814 void
815 adg_dim_set_level(AdgDim *dim, gdouble level)
817 AdgDimPrivate *data;
819 g_return_if_fail(ADG_IS_DIM(dim));
821 data = dim->data;
822 data->level = level;
824 g_object_notify((GObject *) dim, "level");
828 * adg_dim_get_level:
829 * @dim: an #AdgDim
831 * Gets the level of this dimension.
833 * Returns: the level value.
835 * Since: 1.0
837 gdouble
838 adg_dim_get_level(AdgDim *dim)
840 AdgDimPrivate *data;
842 g_return_val_if_fail(ADG_IS_DIM(dim), 0);
844 data = dim->data;
846 return data->level;
850 * adg_dim_set_outside:
851 * @dim: an #AdgDim
852 * @outside: the new outside state
854 * Sets a new state for the #AdgDim:outside flag: check the property
855 * documentation for further details.
857 * Since: 1.0
859 void
860 adg_dim_set_outside(AdgDim *dim, AdgThreeState outside)
862 g_return_if_fail(ADG_IS_DIM(dim));
864 if (_adg_set_outside(dim, outside))
865 g_object_notify((GObject *) dim, "outside");
869 * adg_dim_get_outside:
870 * @dim: an #AdgDim
872 * Gets the state of the #AdgDim:outside property: check the property
873 * documentation for further details.
875 * Returns: the current flag state.
877 * Since: 1.0
879 AdgThreeState
880 adg_dim_get_outside(AdgDim *dim)
882 AdgDimPrivate *data;
884 g_return_val_if_fail(ADG_IS_DIM(dim), ADG_THREE_STATE_UNKNOWN);
886 data = dim->data;
888 return data->outside;
892 * adg_dim_set_detached:
893 * @dim: an #AdgDim
894 * @detached: the new detached state
896 * Sets a new state for the #AdgDim:detached flag: check the property
897 * documentation for further details.
899 * This is used only by dimensions where detaching has meaning.
900 * In some cases, such as with #AdgRDim dimensions, this property is
901 * not used.
903 * Since: 1.0
905 void
906 adg_dim_set_detached(AdgDim *dim, AdgThreeState detached)
908 g_return_if_fail(ADG_IS_DIM(dim));
910 if (_adg_set_detached(dim, detached))
911 g_object_notify((GObject *) dim, "detached");
915 * adg_dim_get_detached:
916 * @dim: an #AdgDim
918 * Gets the state of the #AdgDim:detached property: check the property
919 * documentation for further details.
921 * Returns: the current flag state.
923 * Since: 1.0
925 AdgThreeState
926 adg_dim_get_detached(AdgDim *dim)
928 AdgDimPrivate *data;
930 g_return_val_if_fail(ADG_IS_DIM(dim), ADG_THREE_STATE_UNKNOWN);
932 data = dim->data;
934 return data->detached;
938 * adg_dim_set_value:
939 * @dim: an #AdgDim
940 * @value: (allow-none): the value text
942 * Explicitely sets the text to use as value. If @value
943 * is <constant>NULL</constant> or was never set, an automatic
944 * text is calculated using the format specified in the current
945 * #AdgDimStyle and getting its value by calling
946 * the <function>default_value</function> virtual method.
948 * Inside the template string, the "<>" tag (or whatever specified
949 * by the #AdgDimStyle:number-tag property) is substituted with the
950 * string returned by <function>default_value</function>.
952 * Since: 1.0
954 void
955 adg_dim_set_value(AdgDim *dim, const gchar *value)
957 g_return_if_fail(ADG_IS_DIM(dim));
959 if (_adg_set_value(dim, value))
960 g_object_notify((GObject *) dim, "value");
964 * adg_dim_get_value:
965 * @dim: an #AdgDim
967 * Gets the value text. The string is internally owned and
968 * must not be freed or modified.
970 * Returns: (transfer none): the value text.
972 * Since: 1.0
974 const gchar *
975 adg_dim_get_value(AdgDim *dim)
977 AdgDimPrivate *data;
979 g_return_val_if_fail(ADG_IS_DIM(dim), NULL);
981 data = dim->data;
983 return data->value;
987 * adg_dim_get_text:
988 * @dim: an #AdgDim
989 * @value: the raw value of the quote
991 * Gets the final text to show as nominal value into the quote. The string is
992 * the same returned by adg_dim_get_value() with the tag properly expanded.
994 * The string substituted to the tag is formatted according to the
995 * #AdgDimStyle:number-format and #AdgDimStyle:number-arguments properties.
996 * See the #AdgDimStyle documentation for further details.
998 * Returns: (transfer full): the final text of the quote.
1000 * Since: 1.0
1002 gchar *
1003 adg_dim_get_text(AdgDim *dim, gdouble value)
1005 AdgDimStyle *dim_style;
1006 const gchar *format;
1007 const gchar *arguments;
1008 AdgDimReplaceData data;
1009 gchar *raw, *result;
1010 GRegex *regex;
1012 g_return_val_if_fail(ADG_IS_DIM(dim), NULL);
1014 dim_style = _ADG_GET_DIM_STYLE(dim);
1015 if (dim_style == NULL) {
1016 dim_style = (AdgDimStyle *) adg_entity_style((AdgEntity *) dim,
1017 adg_dim_get_dim_dress(dim));
1020 format = adg_dim_style_get_number_format(dim_style);
1021 if (format == NULL) {
1022 return NULL;
1025 arguments = adg_dim_style_get_number_arguments(dim_style);
1026 if (arguments == NULL) {
1027 return g_strdup(format);
1030 /* Expand the values */
1031 data.dim_style = dim_style;
1032 data.value = value;
1033 data.format = format;
1034 data.argument = arguments;
1035 data.regex = g_regex_new("(?<!\\\\)%.*[eEfFgG]", G_REGEX_UNGREEDY, 0, NULL);
1036 raw = _adg_text_expand(&data);
1037 g_regex_unref(data.regex);
1039 /* Check that all format string has been parsed, otherwise there are
1040 * likely too many close parenthesis */
1041 if (*data.format != '\0') {
1042 g_free(raw);
1043 g_return_val_if_reached(NULL);
1044 return NULL;
1047 /* Substitute the escape sequences ("\%", "\(" and "\)") */
1048 regex = g_regex_new("\\\\([%()])", G_REGEX_UNGREEDY, 0, NULL);
1049 result = g_regex_replace(regex, raw, -1, 0, "\\1", 0, NULL);
1050 g_free(raw);
1051 g_regex_unref(regex);
1053 return result;
1057 * adg_dim_set_limits:
1058 * @dim: an #AdgDim
1059 * @min: (allow-none): the new minumum value
1060 * @max: (allow-none): the new maximum value
1062 * Shortcut to set both the limits at once.
1064 * Since: 1.0
1066 void
1067 adg_dim_set_limits(AdgDim *dim, const gchar *min, const gchar *max)
1069 g_return_if_fail(ADG_IS_DIM(dim));
1071 g_object_freeze_notify((GObject *) dim);
1072 adg_dim_set_min(dim, min);
1073 adg_dim_set_max(dim, max);
1074 g_object_thaw_notify((GObject *) dim);
1078 * adg_dim_set_min:
1079 * @dim: an #AdgDim
1080 * @min: (allow-none): the new minimum limit
1082 * Sets the minimum value. Use <constant>NULL</constant>
1083 * as @min to disable it.
1085 * Since: 1.0
1087 void
1088 adg_dim_set_min(AdgDim *dim, const gchar *min)
1090 g_return_if_fail(ADG_IS_DIM(dim));
1092 if (_adg_set_min(dim, min))
1093 g_object_notify((GObject *) dim, "min");
1097 * adg_dim_get_min:
1098 * @dim: an #AdgDim
1100 * Gets the minimum value text or <constant>NULL</constant>
1101 * on minimum value disabled.
1103 * The string is internally owned and must not be freed or modified.
1105 * Returns: (transfer none): the mimimum value text.
1107 * Since: 1.0
1109 const gchar *
1110 adg_dim_get_min(AdgDim *dim)
1112 AdgDimPrivate *data;
1114 g_return_val_if_fail(ADG_IS_DIM(dim), NULL);
1116 data = dim->data;
1118 return data->min;
1122 * adg_dim_set_max:
1123 * @dim: an #AdgDim
1124 * @max: (allow-none): the new maximum value
1126 * Sets the maximum value. Use <constant>NULL</constant>
1127 * as @max to disable it.
1129 * Since: 1.0
1131 void
1132 adg_dim_set_max(AdgDim *dim, const gchar *max)
1134 g_return_if_fail(ADG_IS_DIM(dim));
1136 if (_adg_set_max(dim, max))
1137 g_object_notify((GObject *) dim, "max");
1141 * adg_dim_get_max:
1142 * @dim: an #AdgDim
1144 * Gets the maximum value text or <constant>NULL</constant>
1145 * on maximum value disabled.
1147 * The string is internally owned and must not be freed or modified.
1149 * Returns: (transfer none): the maximum value text.
1151 * Since: 1.0
1153 const gchar *
1154 adg_dim_get_max(AdgDim *dim)
1156 AdgDimPrivate *data;
1158 g_return_val_if_fail(ADG_IS_DIM(dim), NULL);
1160 data = dim->data;
1162 return data->max;
1166 * adg_dim_get_quote:
1167 * @dim: an #AdgDim
1169 * Gets the quote entity, if any. This function is valid only after
1170 * the #AdgDim implementation of the arrange() virtual method has
1171 * been called.
1173 * The returned entity is owned by @dim and should not be
1174 * modified or freed.
1176 * <note><para>
1177 * This function is only useful in new dimension implementations.
1178 * </para></note>
1180 * Returns: (transfer none): the quote entity.
1182 * Since: 1.0
1184 AdgAlignment *
1185 adg_dim_get_quote(AdgDim *dim)
1187 AdgDimPrivate *data;
1189 g_return_val_if_fail(ADG_IS_DIM(dim), NULL);
1191 data = dim->data;
1193 return data->quote.entity;
1197 * adg_dim_quote_angle:
1198 * @dim: an #AdgDim
1199 * @angle: an angle (in radians)
1201 * <note><para>
1202 * This function is only useful in new dimension implementations.
1203 * </para></note>
1205 * Converts @angle accordling to the style of @dim. Any quote angle
1206 * should be validated by this method because every dimensioning
1207 * style has its own convention regardling the text rotation.
1209 * Returns: the angle to use (always in radians).
1211 * Since: 1.0
1213 gdouble
1214 adg_dim_quote_angle(AdgDim *dim, gdouble angle)
1216 AdgDimClass *klass;
1218 g_return_val_if_fail(ADG_IS_DIM(dim), angle);
1220 klass = ADG_DIM_GET_CLASS(dim);
1222 if (klass->quote_angle == NULL)
1223 return angle;
1225 return klass->quote_angle(angle);
1229 static void
1230 _adg_global_changed(AdgEntity *entity)
1232 AdgDimPrivate *data = ((AdgDim *) entity)->data;
1234 if (_ADG_OLD_ENTITY_CLASS->global_changed)
1235 _ADG_OLD_ENTITY_CLASS->global_changed(entity);
1237 if (data->quote.entity)
1238 adg_entity_global_changed((AdgEntity *) data->quote.entity);
1241 static void
1242 _adg_local_changed(AdgEntity *entity)
1244 AdgDimPrivate *data = ((AdgDim *) entity)->data;
1246 if (_ADG_OLD_ENTITY_CLASS->local_changed)
1247 _ADG_OLD_ENTITY_CLASS->local_changed(entity);
1249 if (data->quote.entity)
1250 adg_entity_local_changed((AdgEntity *) data->quote.entity);
1253 static void
1254 _adg_invalidate(AdgEntity *entity)
1256 AdgDimPrivate *data = ((AdgDim *) entity)->data;
1258 if (data->quote.value) {
1259 g_object_unref(data->quote.value);
1260 data->quote.value = NULL;
1262 if (data->quote.entity)
1263 adg_entity_invalidate((AdgEntity *) data->quote.entity);
1264 if (data->ref1)
1265 adg_point_invalidate(data->ref1);
1266 if (data->ref2)
1267 adg_point_invalidate(data->ref2);
1268 if (data->pos)
1269 adg_point_invalidate(data->pos);
1271 if (_ADG_OLD_ENTITY_CLASS->invalidate)
1272 _ADG_OLD_ENTITY_CLASS->invalidate(entity);
1275 static void
1276 _adg_arrange(AdgEntity *entity)
1278 AdgDim *dim;
1279 AdgDimPrivate *data;
1280 AdgEntity *quote_entity;
1281 AdgContainer *quote_container;
1282 AdgEntity *value_entity;
1283 AdgEntity *min_entity;
1284 AdgEntity *max_entity;
1285 const CpmlPair *shift;
1286 cairo_matrix_t map;
1288 dim = (AdgDim *) entity;
1289 data = dim->data;
1291 /* Resolve the dim style */
1292 if (data->dim_style == NULL)
1293 data->dim_style = (AdgDimStyle *)
1294 adg_entity_style(entity, data->dim_dress);
1296 if (data->quote.entity == NULL)
1297 data->quote.entity = g_object_new(ADG_TYPE_ALIGNMENT,
1298 "local-mix", ADG_MIX_NONE,
1299 "parent", dim, NULL);
1301 quote_entity = (AdgEntity *) data->quote.entity;
1302 quote_container = (AdgContainer *) data->quote.entity;
1304 if (data->quote.value == NULL) {
1305 AdgDimClass *klass;
1306 AdgDress dress;
1307 const gchar *tag;
1308 gchar *value;
1309 gchar *text;
1311 klass = ADG_DIM_GET_CLASS(dim);
1312 dress = adg_dim_style_get_value_dress(data->dim_style);
1313 tag = adg_dim_style_get_number_tag(data->dim_style);
1314 value = klass->default_value ? klass->default_value(dim) : NULL;
1316 data->quote.value = g_object_new(ADG_TYPE_BEST_TEXT,
1317 "local-mix", ADG_MIX_PARENT,
1318 "font-dress", dress, NULL);
1319 adg_container_add(quote_container, (AdgEntity *) data->quote.value);
1321 if (data->value)
1322 text = adg_string_replace(data->value, tag, value);
1323 else
1324 text = g_strdup(value);
1326 g_free(value);
1328 adg_textual_set_text(data->quote.value, text);
1329 g_free(text);
1332 if (data->quote.min == NULL && data->min != NULL) {
1333 AdgDress dress = adg_dim_style_get_min_dress(data->dim_style);
1335 data->quote.min = g_object_new(ADG_TYPE_BEST_TEXT,
1336 "local-mix", ADG_MIX_PARENT,
1337 "font-dress", dress, NULL);
1339 adg_container_add(quote_container, (AdgEntity *) data->quote.min);
1340 adg_textual_set_text(data->quote.min, data->min);
1343 if (data->quote.max == NULL && data->max != NULL) {
1344 AdgDress dress = adg_dim_style_get_max_dress(data->dim_style);
1346 data->quote.max = g_object_new(ADG_TYPE_BEST_TEXT,
1347 "local-mix", ADG_MIX_PARENT,
1348 "font-dress", dress, NULL);
1350 adg_container_add(quote_container, (AdgEntity *) data->quote.max);
1351 adg_textual_set_text(data->quote.max, data->max);
1354 value_entity = (AdgEntity *) data->quote.value;
1355 min_entity = (AdgEntity *) data->quote.min;
1356 max_entity = (AdgEntity *) data->quote.max;
1357 shift = adg_dim_style_get_quote_shift(data->dim_style);
1359 adg_entity_set_global_map(quote_entity, adg_matrix_identity());
1360 adg_entity_global_changed(quote_entity);
1362 cairo_matrix_init_translate(&map, 0, shift->y);
1363 adg_entity_set_global_map(value_entity, &map);
1364 adg_entity_arrange(value_entity);
1366 /* Limit values (min and max) */
1367 if (min_entity != NULL || max_entity != NULL) {
1368 const CpmlPair *limits_shift;
1369 gdouble spacing;
1370 CpmlPair size;
1371 CpmlPair org_min, org_max;
1373 limits_shift = adg_dim_style_get_limits_shift(data->dim_style);
1374 spacing = adg_dim_style_get_limits_spacing(data->dim_style);
1375 size = adg_entity_get_extents(value_entity)->size;
1376 org_min.x = size.x + limits_shift->x;
1377 org_min.y = -size.y / 2 + limits_shift->y;
1378 org_max = org_min;
1380 if (min_entity && max_entity) {
1381 /* Prearrange the min entity to get its extents */
1382 adg_entity_arrange(min_entity);
1383 size = adg_entity_get_extents(min_entity)->size;
1385 org_min.y += spacing / 2;
1386 org_max.y = org_min.y - size.y - spacing / 2;
1389 if (min_entity != NULL) {
1390 cairo_matrix_init_translate(&map, org_min.x, org_min.y);
1391 adg_entity_set_global_map(min_entity, &map);
1392 adg_entity_arrange(min_entity);
1395 if (max_entity != NULL) {
1396 cairo_matrix_init_translate(&map, org_max.x, org_max.y);
1397 adg_entity_set_global_map(max_entity, &map);
1398 adg_entity_arrange(max_entity);
1403 static gchar *
1404 _adg_default_value(AdgDim *dim)
1406 g_warning(_("AdgDim::default_value not implemented for '%s'"),
1407 g_type_name(G_TYPE_FROM_INSTANCE(dim)));
1408 return g_strdup("undef");
1411 static gdouble
1412 _adg_quote_angle(gdouble angle)
1414 angle = cpml_angle(angle);
1416 if (angle > G_PI_4 * 4 / 3 || angle <= -G_PI_4 * 3)
1417 angle = cpml_angle(angle + G_PI);
1419 return angle;
1422 static gboolean
1423 _adg_set_outside(AdgDim *dim, AdgThreeState outside)
1425 AdgDimPrivate *data;
1427 g_return_val_if_fail(adg_is_enum_value(outside, ADG_TYPE_THREE_STATE),
1428 FALSE);
1430 data = dim->data;
1432 if (data->outside == outside)
1433 return FALSE;
1435 data->outside = outside;
1437 return TRUE;
1440 static gboolean
1441 _adg_set_detached(AdgDim *dim, AdgThreeState detached)
1443 AdgDimPrivate *data;
1445 g_return_val_if_fail(adg_is_enum_value(detached, ADG_TYPE_THREE_STATE),
1446 FALSE);
1448 data = dim->data;
1450 if (data->detached == detached)
1451 return FALSE;
1453 data->detached = detached;
1455 return TRUE;
1458 static gboolean
1459 _adg_set_value(AdgDim *dim, const gchar *value)
1461 AdgDimPrivate *data;
1463 data = dim->data;
1465 if (g_strcmp0(value, data->value) == 0)
1466 return FALSE;
1468 g_free(data->value);
1469 data->value = g_strdup(value);
1471 if (data->quote.value) {
1472 g_object_unref(data->quote.value);
1473 data->quote.value = NULL;
1476 return TRUE;
1479 static gboolean
1480 _adg_set_min(AdgDim *dim, const gchar *min)
1482 AdgDimPrivate *data = dim->data;
1484 if (g_strcmp0(min, data->min) == 0)
1485 return FALSE;
1487 g_free(data->min);
1488 data->min = g_strdup(min);
1490 if (data->quote.min) {
1491 g_object_unref(data->quote.min);
1492 data->quote.min = NULL;
1495 return TRUE;
1498 static gboolean
1499 _adg_set_max(AdgDim *dim, const gchar *max)
1501 AdgDimPrivate *data = dim->data;
1503 if (g_strcmp0(max, data->max) == 0)
1504 return FALSE;
1506 g_free(data->max);
1507 data->max = g_strdup(max);
1509 if (data->quote.max) {
1510 g_object_unref(data->quote.max);
1511 data->quote.max = NULL;
1514 return TRUE;
1517 static gboolean
1518 _adg_replace(const GMatchInfo *match_info, GString *result, gpointer user_data)
1520 AdgDimReplaceData *data;
1521 gdouble value;
1522 gchar *format;
1523 gchar buffer[256];
1525 data = (AdgDimReplaceData *) user_data;
1526 value = data->value;
1528 if (! adg_dim_style_convert(data->dim_style, &value, *data->argument)) {
1529 /* Conversion failed: invalid argument? */
1530 g_return_val_if_reached(TRUE);
1531 return TRUE;
1534 format = g_match_info_fetch(match_info, 0);
1536 /* This should never happen */
1537 g_return_val_if_fail(format != NULL, TRUE);
1539 /* Consume the recently used argument */
1540 ++ data->argument;
1542 g_ascii_formatd(buffer, 256, format, value);
1543 g_free(format);
1544 g_string_append(result, buffer);
1546 /* Set the valorized flag */
1547 if (value != 0) {
1548 data->valorized = ADG_THREE_STATE_ON;
1549 } else if (data->valorized == ADG_THREE_STATE_UNKNOWN) {
1550 data->valorized = ADG_THREE_STATE_OFF;
1553 return FALSE;
1556 static gchar *
1557 _adg_text_expand(AdgDimReplaceData *data)
1559 GString *result;
1560 const gchar *bog, *eog;
1561 gchar *string;
1562 AdgThreeState valorized;
1563 gssize len;
1565 valorized = ADG_THREE_STATE_UNKNOWN;
1566 result = g_string_new("");
1567 eog = adg_unescaped_strchr(data->format, ')');
1569 /* Expand eventual groups found in the same nesting level */
1570 while ((bog = adg_unescaped_strchr(data->format, '(')) != NULL) {
1571 /* If eog precedes bog, it means that bog is in another nest */
1572 if (eog != NULL && eog < bog) {
1573 break;
1576 len = bog - data->format;
1578 /* Parse template before the bog */
1579 data->valorized = ADG_THREE_STATE_UNKNOWN;
1580 string = g_regex_replace_eval(data->regex,
1581 data->format, len, 0,
1583 _adg_replace, data,
1584 NULL);
1585 valorized = OR_3S(valorized, data->valorized);
1587 data->format += len+1;
1588 g_string_append(result, string);
1589 g_free(string);
1591 /* Recursively expand the group */
1592 string = _adg_text_expand(data);
1593 valorized = OR_3S(valorized, data->valorized);
1595 g_string_append(result, string);
1596 g_free(string);
1598 /* Ensure there is a matching closing parenthesis */
1599 if (*data->format != ')') {
1600 g_string_free(result, TRUE);
1601 g_return_val_if_reached(NULL);
1602 return NULL;
1605 /* Skip the closing parenthesis */
1606 ++ data->format;
1607 eog = adg_unescaped_strchr(data->format, ')');
1610 /* Expand until closing parenthesis (End Of Group) or '\0' */
1611 len = eog == NULL ? strlen(data->format) : (eog - data->format);
1612 data->valorized = ADG_THREE_STATE_UNKNOWN;
1613 string = g_regex_replace_eval(data->regex,
1614 data->format, len, 0,
1616 _adg_replace, data,
1617 NULL);
1619 data->format += len;
1620 g_string_append(result, string);
1621 g_free(string);
1623 /* Store the final valorized state */
1624 valorized = OR_3S(valorized, data->valorized);
1625 data->valorized = valorized;
1627 /* Drop the result only if we are inside a group */
1628 if (*data->format && valorized == ADG_THREE_STATE_OFF) {
1629 g_string_free(result, TRUE);
1630 return g_strdup("");
1633 return g_string_free(result, FALSE);