[AdgLDim] Improvements on the arrange() phase
[adg.git] / src / adg / adg-ldim.c
blob9e605c5de05ee0cce3c6e2c6ae973a63a333e95d
1 /* ADG - Automatic Drawing Generation
2 * Copyright (C) 2007,2008,2009,2010,2011 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-ldim
23 * @short_description: Linear dimensions
25 * The #AdgLDim entity represents a linear dimension.
26 **/
28 /**
29 * AdgLDim:
31 * All fields are private and should not be used directly.
32 * Use its public methods instead.
33 **/
36 #include "adg-internal.h"
37 #include "adg-container.h"
38 #include "adg-alignment.h"
39 #include "adg-model.h"
40 #include "adg-point.h"
41 #include "adg-trail.h"
42 #include "adg-marker.h"
43 #include "adg-style.h"
44 #include "adg-dim-style.h"
45 #include "adg-textual.h"
46 #include "adg-toy-text.h"
47 #include "adg-dim.h"
48 #include "adg-dim-private.h"
50 #include "adg-ldim.h"
51 #include "adg-ldim-private.h"
54 #define _ADG_OLD_OBJECT_CLASS ((GObjectClass *) adg_ldim_parent_class)
55 #define _ADG_OLD_ENTITY_CLASS ((AdgEntityClass *) adg_ldim_parent_class)
58 G_DEFINE_TYPE(AdgLDim, adg_ldim, ADG_TYPE_DIM)
60 enum {
61 PROP_0,
62 PROP_DIRECTION,
63 PROP_HAS_EXTENSION1,
64 PROP_HAS_EXTENSION2
68 static void _adg_dispose (GObject *object);
69 static void _adg_get_property (GObject *object,
70 guint param_id,
71 GValue *value,
72 GParamSpec *pspec);
73 static void _adg_set_property (GObject *object,
74 guint param_id,
75 const GValue *value,
76 GParamSpec *pspec);
77 static void _adg_global_changed (AdgEntity *entity);
78 static void _adg_local_changed (AdgEntity *entity);
79 static void _adg_invalidate (AdgEntity *entity);
80 static void _adg_arrange (AdgEntity *entity);
81 static void _adg_render (AdgEntity *entity,
82 cairo_t *cr);
83 static gchar * _adg_default_value (AdgDim *dim);
84 static gboolean _adg_update_geometry (AdgLDim *ldim);
85 static void _adg_update_shift (AdgLDim *ldim);
86 static void _adg_update_entities (AdgLDim *ldim);
87 static void _adg_choose_flags (AdgLDim *ldim,
88 gboolean *to_outside,
89 gboolean *to_detach);
90 static void _adg_update_quote (AdgLDim *ldim,
91 AdgPair *base1,
92 AdgPair *base2,
93 AdgPair *pos,
94 gboolean detach,
95 gboolean outside,
96 gdouble gap);
97 static void _adg_update_extents (AdgLDim *ldim);
98 static void _adg_unset_trail (AdgLDim *ldim);
99 static void _adg_dispose_trail (AdgLDim *ldim);
100 static void _adg_dispose_markers (AdgLDim *ldim);
101 static CpmlPath * _adg_trail_callback (AdgTrail *trail,
102 gpointer user_data);
105 static void
106 adg_ldim_class_init(AdgLDimClass *klass)
108 GObjectClass *gobject_class;
109 AdgEntityClass *entity_class;
110 AdgDimClass *dim_class;
111 GParamSpec *param;
113 gobject_class = (GObjectClass *) klass;
114 entity_class = (AdgEntityClass *) klass;
115 dim_class = (AdgDimClass *) klass;
117 g_type_class_add_private(klass, sizeof(AdgLDimPrivate));
119 gobject_class->dispose = _adg_dispose;
120 gobject_class->get_property = _adg_get_property;
121 gobject_class->set_property = _adg_set_property;
123 entity_class->global_changed = _adg_global_changed;
124 entity_class->local_changed = _adg_local_changed;
125 entity_class->invalidate = _adg_invalidate;
126 entity_class->arrange = _adg_arrange;
127 entity_class->render = _adg_render;
129 dim_class->default_value = _adg_default_value;
131 param = g_param_spec_double("direction",
132 P_("Direction"),
133 P_("The inclination angle of the extension lines"),
134 -G_PI, G_PI, ADG_DIR_RIGHT,
135 G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
136 g_object_class_install_property(gobject_class, PROP_DIRECTION, param);
138 param = g_param_spec_boolean("has-extension1",
139 P_("Has First Extension Line flag"),
140 P_("Show (TRUE) or hide (FALSE) the first extension line"),
141 TRUE, G_PARAM_READWRITE);
142 g_object_class_install_property(gobject_class, PROP_HAS_EXTENSION1, param);
144 param = g_param_spec_boolean("has-extension2",
145 P_("Has Second Extension Line flag"),
146 P_("Show (TRUE) or hide (FALSE) the second extension line"),
147 TRUE, G_PARAM_READWRITE);
148 g_object_class_install_property(gobject_class, PROP_HAS_EXTENSION2, param);
151 static void
152 adg_ldim_init(AdgLDim *ldim)
154 AdgLDimPrivate *data;
155 cairo_path_data_t move_to, line_to;
157 data = G_TYPE_INSTANCE_GET_PRIVATE(ldim, ADG_TYPE_LDIM, AdgLDimPrivate);
158 move_to.header.type = CPML_MOVE;
159 move_to.header.length = 2;
160 line_to.header.type = CPML_LINE;
161 line_to.header.length = 2;
163 data->direction = ADG_DIR_RIGHT;
164 data->has_extension1 = TRUE;
165 data->has_extension2 = TRUE;
167 data->cpml.path.status = CAIRO_STATUS_INVALID_PATH_DATA;
168 data->cpml.path.data = data->cpml.data;
169 data->cpml.path.num_data = G_N_ELEMENTS(data->cpml.data);
170 data->cpml.path.data[0] = move_to;
171 data->cpml.path.data[2] = line_to;
172 data->cpml.path.data[4] = move_to;
173 data->cpml.path.data[6] = line_to;
174 data->cpml.path.data[8] = move_to;
175 data->cpml.path.data[10] = line_to;
176 data->cpml.path.data[12] = move_to;
177 data->cpml.path.data[14] = line_to;
178 data->cpml.path.data[16] = move_to;
179 data->cpml.path.data[18] = line_to;
181 data->trail = NULL;
182 data->marker1 = NULL;
183 data->marker2 = NULL;
185 data->geometry.is_arranged = FALSE;
186 data->shift.is_arranged = FALSE;
188 ldim->data = data;
191 static void
192 _adg_dispose(GObject *object)
194 AdgLDim *ldim = (AdgLDim *) object;
196 _adg_dispose_trail(ldim);
197 _adg_dispose_markers(ldim);
199 if (_ADG_OLD_OBJECT_CLASS->dispose)
200 _ADG_OLD_OBJECT_CLASS->dispose(object);
203 static void
204 _adg_get_property(GObject *object, guint prop_id,
205 GValue *value, GParamSpec *pspec)
207 AdgLDimPrivate *data = ((AdgLDim *) object)->data;
209 switch (prop_id) {
210 case PROP_DIRECTION:
211 g_value_set_double(value, data->direction);
212 break;
213 case PROP_HAS_EXTENSION1:
214 g_value_set_boolean(value, data->has_extension1);
215 break;
216 case PROP_HAS_EXTENSION2:
217 g_value_set_boolean(value, data->has_extension2);
218 break;
219 default:
220 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
221 break;
225 static void
226 _adg_set_property(GObject *object, guint prop_id,
227 const GValue *value, GParamSpec *pspec)
229 AdgLDim *ldim;
230 AdgLDimPrivate *data;
232 ldim = (AdgLDim *) object;
233 data = ldim->data;
235 switch (prop_id) {
236 case PROP_DIRECTION:
237 data->direction = cpml_angle(g_value_get_double(value));
238 break;
239 case PROP_HAS_EXTENSION1:
240 data->has_extension1 = g_value_get_boolean(value);
241 break;
242 case PROP_HAS_EXTENSION2:
243 data->has_extension2 = g_value_get_boolean(value);
244 break;
245 default:
246 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
247 break;
253 * adg_ldim_new:
255 * Creates a new - undefined - linear dimension. You must, at least,
256 * define the start of the dimension in #AdgDim:ref1, the end in
257 * #AdgDim:ref2 and the position of the quote in #AdgDim:pos using
258 * any valid #AdgDim method. The director of the dimension (that is,
259 * if it is horizontal, vertical or oblique at a specific angle)
260 * should be specified with adg_ldim_set_direction().
262 * Returns: the newly created linear dimension entity
264 AdgLDim *
265 adg_ldim_new(void)
267 return g_object_new(ADG_TYPE_LDIM, NULL);
271 * adg_ldim_new_full:
272 * @ref1: the first reference point
273 * @ref2: the second reference point
274 * @pos: the position reference
275 * @direction: angle where to extend the dimension
277 * Creates a new linear dimension, specifing all the needed properties in
278 * one shot.
280 * Returns: the newly created linear dimension entity
282 AdgLDim *
283 adg_ldim_new_full(const AdgPair *ref1, const AdgPair *ref2,
284 const AdgPair *pos, gdouble direction)
286 AdgLDim *ldim;
287 AdgDim *dim;
289 ldim = g_object_new(ADG_TYPE_LDIM, "direction", direction, NULL);
290 dim = (AdgDim *) ldim;
292 adg_dim_set_ref1_from_pair(dim, ref1);
293 adg_dim_set_ref2_from_pair(dim, ref2);
294 adg_dim_set_pos_from_pair(dim, pos);
296 return ldim;
300 * adg_ldim_new_full_explicit:
301 * @ref1_x: the x coordinate of the first reference point
302 * @ref1_y: the y coordinate of the first reference point
303 * @ref2_x: the x coordinate of the second reference point
304 * @ref2_y: the y coordinate of the second reference point
305 * @pos_x: the x coordinate of the position reference
306 * @pos_y: the y coordinate of the position reference
307 * @direction: angle where to extend the dimension
309 * Wrappes adg_ldim_new_full() with explicit values.
311 * Returns: the newly created linear dimension entity
313 AdgLDim *
314 adg_ldim_new_full_explicit(gdouble ref1_x, gdouble ref1_y,
315 gdouble ref2_x, gdouble ref2_y,
316 gdouble pos_x, gdouble pos_y, gdouble direction)
318 AdgPair ref1;
319 AdgPair ref2;
320 AdgPair pos;
322 ref1.x = ref1_x;
323 ref1.y = ref1_y;
324 ref2.x = ref2_x;
325 ref2.y = ref2_y;
326 pos.x = pos_x;
327 pos.y = pos_y;
329 return adg_ldim_new_full(&ref1, &ref2, &pos, direction);
333 * adg_ldim_new_full_from_model:
334 * @model: the model from which the named pairs are taken
335 * @ref1: the first reference point
336 * @ref2: the second reference point
337 * @pos: the position reference
338 * @direction: angle where to extend the dimension
340 * Creates a new linear dimension, specifing all the needed properties in
341 * one shot and using named pairs from @model.
343 * Returns: the newly created linear dimension entity
345 AdgLDim *
346 adg_ldim_new_full_from_model(AdgModel *model,
347 const gchar *ref1, const gchar *ref2,
348 const gchar *pos, gdouble direction)
350 AdgLDim *ldim;
351 AdgDim *dim;
353 ldim = g_object_new(ADG_TYPE_LDIM, "direction", direction, NULL);
354 dim = (AdgDim *) ldim;
356 adg_dim_set_ref1_from_model(dim, model, ref1);
357 adg_dim_set_ref2_from_model(dim, model, ref2);
358 adg_dim_set_pos_from_model(dim, model, pos);
360 return ldim;
364 * adg_ldim_set_direction:
365 * @ldim: an #AdgLDim entity
366 * @direction: an angle value, in radians
368 * Sets the direction angle where to extend @ldim.
369 * @direction is normalized by cpml_angle() before being used.
371 void
372 adg_ldim_set_direction(AdgLDim *ldim, gdouble direction)
374 g_return_if_fail(ADG_IS_LDIM(ldim));
375 g_object_set(ldim, "direction", direction, NULL);
379 * adg_ldim_get_direction:
380 * @ldim: an #AdgLDim entity
382 * Gets the direction where @ldim will extend.
384 * Returns: the direction angle in radians
386 gdouble
387 adg_ldim_get_direction(AdgLDim *ldim)
389 AdgLDimPrivate *data;
391 g_return_val_if_fail(ADG_IS_LDIM(ldim), 0);
393 data = ldim->data;
395 return data->direction;
399 * adg_ldim_switch_extension1:
400 * @ldim: an #AdgLDim entity
401 * @new_state: the new state
403 * Shows (if @new_state is %TRUE) or hide (if @new_state is %FALSE)
404 * the first extension line of @ldim.
406 void
407 adg_ldim_switch_extension1(AdgLDim *ldim, gboolean new_state)
409 g_return_if_fail(ADG_IS_LDIM(ldim));
410 g_return_if_fail(adg_is_boolean_value(new_state));
411 g_object_set(ldim, "has-extension1", new_state, NULL);
415 * adg_ldim_has_extension1:
416 * @ldim: an #AdgLDim entity
418 * Checks if @ldim should render also the first extension line.
420 * Returns: %TRUE on first extension line presents, %FALSE otherwise
422 gboolean
423 adg_ldim_has_extension1(AdgLDim *ldim)
425 AdgLDimPrivate *data;
427 g_return_val_if_fail(ADG_IS_LDIM(ldim), FALSE);
429 data = ldim->data;
431 return data->has_extension1;
435 * adg_ldim_switch_extension2:
436 * @ldim: an #AdgLDim entity
437 * @new_state: the new new_state
439 * Shows (if @new_state is %TRUE) or hide (if @new_state is %FALSE)
440 * the second extension line of @ldim.
442 void
443 adg_ldim_switch_extension2(AdgLDim *ldim, gboolean new_state)
445 g_return_if_fail(ADG_IS_LDIM(ldim));
446 g_return_if_fail(adg_is_boolean_value(new_state));
447 g_object_set(ldim, "has-extension2", new_state, NULL);
451 * adg_ldim_has_extension2:
452 * @ldim: an #AdgLDim entity
454 * Checks if @ldim should render also the second extension line.
456 * Returns: %TRUE on first extension line presents, %FALSE otherwise
458 gboolean
459 adg_ldim_has_extension2(AdgLDim *ldim)
461 AdgLDimPrivate *data;
463 g_return_val_if_fail(ADG_IS_LDIM(ldim), FALSE);
465 data = ldim->data;
467 return data->has_extension2;
471 static void
472 _adg_global_changed(AdgEntity *entity)
474 AdgLDimPrivate *data = ((AdgLDim *) entity)->data;
476 _adg_unset_trail((AdgLDim *) entity);
478 if (_ADG_OLD_ENTITY_CLASS->global_changed)
479 _ADG_OLD_ENTITY_CLASS->global_changed(entity);
481 if (data->marker1)
482 adg_entity_global_changed((AdgEntity *) data->marker1);
484 if (data->marker2)
485 adg_entity_global_changed((AdgEntity *) data->marker2);
488 static void
489 _adg_local_changed(AdgEntity *entity)
491 _adg_unset_trail((AdgLDim *) entity);
493 if (_ADG_OLD_ENTITY_CLASS->local_changed)
494 _ADG_OLD_ENTITY_CLASS->local_changed(entity);
497 static void
498 _adg_invalidate(AdgEntity *entity)
500 AdgLDim *ldim;
501 AdgLDimPrivate *data;
503 ldim = (AdgLDim *) entity;
504 data = ldim->data;
506 _adg_dispose_trail(ldim);
507 _adg_dispose_markers(ldim);
508 data->geometry.is_arranged = FALSE;
509 data->shift.is_arranged = FALSE;
510 _adg_unset_trail(ldim);
512 if (_ADG_OLD_ENTITY_CLASS->invalidate)
513 _ADG_OLD_ENTITY_CLASS->invalidate(entity);
516 static void
517 _adg_arrange(AdgEntity *entity)
519 AdgLDim *ldim;
520 AdgDim *dim;
521 AdgLDimPrivate *data;
522 AdgAlignment *quote;
523 AdgDimStyle *dim_style;
524 gboolean outside, detach;
525 const AdgMatrix *local;
526 AdgPair ref1, ref2, pos, base1, base2;
527 AdgPair pair;
529 if (_ADG_OLD_ENTITY_CLASS->arrange)
530 _ADG_OLD_ENTITY_CLASS->arrange(entity);
532 ldim = (AdgLDim *) entity;
533 if (! _adg_update_geometry(ldim))
534 return;
536 dim = (AdgDim *) ldim;
537 data = ldim->data;
538 quote = adg_dim_get_quote(dim);
540 _adg_update_shift(ldim);
541 _adg_update_entities(ldim);
543 /* Check for cached result */
544 if (data->cpml.path.status == CAIRO_STATUS_SUCCESS) {
545 adg_entity_set_global_map((AdgEntity *) quote, &data->quote.global_map);
546 return;
549 _adg_choose_flags(ldim, &outside, &detach);
551 dim_style = _ADG_GET_DIM_STYLE(dim);
552 local = adg_entity_get_local_matrix(entity);
554 cpml_pair_copy(&ref1, adg_point_get_pair(adg_dim_get_ref1(dim)));
555 cpml_pair_copy(&ref2, adg_point_get_pair(adg_dim_get_ref2(dim)));
556 cpml_pair_copy(&pos, adg_point_get_pair(adg_dim_get_pos(dim)));
557 cpml_pair_copy(&base1, &data->geometry.base1);
558 cpml_pair_copy(&base2, &data->geometry.base2);
560 cpml_pair_transform(&ref1, local);
561 cpml_pair_transform(&ref2, local);
562 cpml_pair_transform(&pos, local);
563 cpml_pair_transform(&base1, local);
564 base1.x += data->shift.base.x;
565 base1.y += data->shift.base.y;
566 cpml_pair_transform(&base2, local);
567 base2.x += data->shift.base.x;
568 base2.y += data->shift.base.y;
570 pair.x = ref1.x + data->shift.from.x;
571 pair.y = ref1.y + data->shift.from.y;
572 cpml_pair_to_cairo(&pair, &data->cpml.data[13]);
574 cpml_pair_to_cairo(&base1, &data->cpml.data[1]);
576 pair.x = base1.x + data->shift.to.x;
577 pair.y = base1.y + data->shift.to.y;
578 cpml_pair_to_cairo(&pair, &data->cpml.data[15]);
580 pair.x = ref2.x + data->shift.from.x;
581 pair.y = ref2.y + data->shift.from.y;
582 cpml_pair_to_cairo(&pair, &data->cpml.data[17]);
584 cpml_pair_to_cairo(&base2, &data->cpml.data[3]);
586 pair.x = base2.x + data->shift.to.x;
587 pair.y = base2.y + data->shift.to.y;
588 cpml_pair_to_cairo(&pair, &data->cpml.data[19]);
590 /* Calculate the outside segments */
591 if (outside) {
592 gdouble beyond;
593 CpmlVector vector;
595 beyond = adg_dim_style_get_beyond(dim_style);
596 cpml_pair_from_cairo(&pair, &data->cpml.data[1]);
598 cpml_pair_from_cairo(&vector, &data->cpml.data[3]);
599 vector.x -= pair.x;
600 vector.y -= pair.y;
601 cpml_vector_set_length(&vector, beyond);
603 cpml_pair_from_cairo(&pair, &data->cpml.data[1]);
604 cpml_pair_to_cairo(&pair, &data->cpml.data[5]);
606 pair.x -= vector.x;
607 pair.y -= vector.y;
608 cpml_pair_to_cairo(&pair, &data->cpml.data[7]);
610 cpml_pair_from_cairo(&pair, &data->cpml.data[3]);
611 cpml_pair_to_cairo(&pair, &data->cpml.data[11]);
613 pair.x += vector.x;
614 pair.y += vector.y;
615 cpml_pair_to_cairo(&pair, &data->cpml.data[9]);
617 data->cpml.data[2].header.length = 2;
618 } else {
619 data->cpml.data[2].header.length = 10;
621 data->cpml.data[10].header.length = 2;
623 _adg_update_quote(ldim, &base1, &base2, &pos, detach, outside,
624 adg_dim_style_get_quote_shift(dim_style)->x);
626 /* Play with header lengths to show or hide the extension lines */
627 if (data->has_extension1) {
628 data->cpml.data[14].header.length = data->has_extension2 ? 2 : 6;
629 } else {
630 gint n = ! outside && ! detach ? 2 : 10;
631 data->cpml.data[14].header.length = 2;
632 data->cpml.data[n].header.length += 4;
633 if (!data->has_extension2)
634 data->cpml.data[n].header.length += 4;
637 data->cpml.path.status = CAIRO_STATUS_SUCCESS;
639 /* Update the markers, if present */
640 if (data->trail != NULL) {
641 if (data->marker1 != NULL)
642 adg_marker_set_segment(data->marker1, data->trail, outside ? 2 : 1);
643 if (data->marker2 != NULL)
644 adg_marker_set_segment(data->marker2, data->trail, outside ? 3 : 1);
645 } else {
646 _adg_dispose_markers(ldim);
649 _adg_update_extents(ldim);
652 static void
653 _adg_render(AdgEntity *entity, cairo_t *cr)
655 AdgLDim *ldim;
656 AdgDim *dim;
657 AdgLDimPrivate *data;
658 AdgDimStyle *dim_style;
659 AdgDress dress;
660 const cairo_path_t *cairo_path;
662 ldim = (AdgLDim *) entity;
663 dim = (AdgDim *) entity;
664 data = ldim->data;
666 if (!data->geometry.is_arranged) {
667 /* Entity not arranged, probably due to undefined pair found */
668 return;
671 dim_style = _ADG_GET_DIM_STYLE(dim);
673 adg_style_apply((AdgStyle *) dim_style, entity, cr);
674 adg_entity_render((AdgEntity *) adg_dim_get_quote(dim), cr);
676 if (data->marker1)
677 adg_entity_render((AdgEntity *) data->marker1, cr);
678 if (data->marker2)
679 adg_entity_render((AdgEntity *) data->marker2, cr);
681 cairo_transform(cr, adg_entity_get_global_matrix(entity));
682 dress = adg_dim_style_get_line_dress(dim_style);
683 adg_entity_apply_dress(entity, dress, cr);
685 cairo_path = adg_trail_get_cairo_path(data->trail);
686 cairo_append_path(cr, cairo_path);
687 cairo_stroke(cr);
690 static gchar *
691 _adg_default_value(AdgDim *dim)
693 AdgLDim *ldim;
694 AdgLDimPrivate *data;
695 AdgDimStyle *dim_style;
696 const gchar *format;
698 ldim = (AdgLDim *) dim;
699 data = ldim->data;
700 dim_style = _ADG_GET_DIM_STYLE(dim);
701 format = adg_dim_style_get_number_format(dim_style);
703 if (! _adg_update_geometry(ldim))
704 return g_strdup("undef");
706 return g_strdup_printf(format, data->geometry.distance);
709 static gboolean
710 _adg_update_geometry(AdgLDim *ldim)
712 AdgLDimPrivate *data;
713 AdgDim *dim;
714 const AdgPair *ref1, *ref2;
715 const AdgPair *pos;
716 CpmlVector baseline, extension;
717 gdouble d, k;
719 data = ldim->data;
720 dim = (AdgDim *) ldim;
721 ref1 = adg_point_get_pair(adg_dim_get_ref1(dim));
722 ref2 = adg_point_get_pair(adg_dim_get_ref2(dim));
723 pos = adg_point_get_pair(adg_dim_get_pos(dim));
725 /* Check if the needed pairs are properly defined */
726 if (ref1 == NULL || ref2 == NULL || pos == NULL) {
727 data->geometry.is_arranged = FALSE;
728 return FALSE;
729 } else if (data->geometry.is_arranged) {
730 return TRUE;
733 cpml_vector_from_angle(&extension, data->direction);
734 cpml_pair_copy(&baseline, &extension);
735 cpml_vector_normal(&baseline);
737 d = extension.y * baseline.x -
738 extension.x * baseline.y;
739 g_return_val_if_fail(d != 0, FALSE);
741 k = ((pos->y - ref1->y) * baseline.x -
742 (pos->x - ref1->x) * baseline.y) / d;
743 data->geometry.base1.x = ref1->x + k * extension.x;
744 data->geometry.base1.y = ref1->y + k * extension.y;
746 k = ((pos->y - ref2->y) * baseline.x -
747 (pos->x - ref2->x) * baseline.y) / d;
748 data->geometry.base2.x = ref2->x + k * extension.x;
749 data->geometry.base2.y = ref2->y + k * extension.y;
751 data->geometry.distance = cpml_pair_distance(&data->geometry.base1,
752 &data->geometry.base2);
754 data->geometry.is_arranged = TRUE;
756 return TRUE;
759 static void
760 _adg_update_shift(AdgLDim *ldim)
762 AdgLDimPrivate *data;
763 AdgDimStyle *dim_style;
764 gdouble from_offset, to_offset;
765 gdouble baseline_spacing, level;
766 CpmlVector vector;
768 data = ldim->data;
770 if (data->shift.is_arranged)
771 return;
773 dim_style = _ADG_GET_DIM_STYLE(ldim);
774 from_offset = adg_dim_style_get_from_offset(dim_style);
775 to_offset = adg_dim_style_get_to_offset(dim_style);
776 baseline_spacing = adg_dim_style_get_baseline_spacing(dim_style);
777 level = adg_dim_get_level((AdgDim *) ldim);
779 cpml_vector_from_angle(&vector, data->direction);
781 cpml_vector_set_length(&vector, from_offset);
782 cpml_pair_copy(&data->shift.from, &vector);
784 cpml_vector_set_length(&vector, to_offset);
785 cpml_pair_copy(&data->shift.to, &vector);
787 cpml_vector_set_length(&vector, level * baseline_spacing);
788 cpml_pair_copy(&data->shift.base, &vector);
790 data->shift.is_arranged = TRUE;
793 static void
794 _adg_update_entities(AdgLDim *ldim)
796 AdgEntity *entity;
797 AdgLDimPrivate *data;
798 AdgDimStyle *dim_style;
800 entity = (AdgEntity *) ldim;
801 data = ldim->data;
802 dim_style = _ADG_GET_DIM_STYLE(ldim);
804 if (data->trail == NULL)
805 data->trail = adg_trail_new(_adg_trail_callback, ldim);
807 if (data->marker1 == NULL) {
808 data->marker1 = adg_dim_style_marker1_new(dim_style);
809 adg_entity_set_parent((AdgEntity *) data->marker1, entity);
812 if (data->marker2 == NULL) {
813 data->marker2 = adg_dim_style_marker2_new(dim_style);
814 adg_entity_set_parent((AdgEntity *) data->marker2, entity);
818 static void
819 _adg_choose_flags(AdgLDim *ldim, gboolean *to_outside, gboolean *to_detach)
821 AdgDim *dim;
822 AdgThreeState outside, detached;
823 AdgLDimPrivate *data;
824 const AdgMatrix *local, *global;
825 gdouble local_factor, global_factor;
826 gdouble available_space, markers_space, quote_space;
828 dim = (AdgDim *) ldim;
829 outside = adg_dim_get_outside(dim);
830 detached = adg_dim_get_detached(dim);
832 *to_outside = outside == ADG_THREE_STATE_ON;
833 *to_detach = detached == ADG_THREE_STATE_ON;
835 /* On explicit flags, no further investigation is required */
836 if (outside != ADG_THREE_STATE_UNKNOWN &&
837 detached != ADG_THREE_STATE_UNKNOWN)
838 return;
840 data = ldim->data;
841 local = adg_entity_get_local_matrix((AdgEntity *) ldim);
842 global = adg_entity_get_global_matrix((AdgEntity *) ldim);
843 local_factor = (local->xx + local->yy) / 2;
844 global_factor = (global->xx + global->yy) / 2;
845 available_space = data->geometry.distance * local_factor * global_factor;
847 markers_space = 0;
848 if (outside != ADG_THREE_STATE_ON) {
849 if (data->marker1 != NULL)
850 markers_space += adg_marker_get_size(data->marker1);
851 if (data->marker2 != NULL)
852 markers_space += adg_marker_get_size(data->marker2);
854 markers_space *= global_factor;
857 if (detached == ADG_THREE_STATE_ON) {
858 /* Leave at least 0.25 markers_space between the markers */
859 quote_space = markers_space * 0.25;
860 } else {
861 AdgEntity *quote = (AdgEntity *) adg_dim_get_quote(dim);
862 adg_entity_arrange(quote);
863 quote_space = adg_entity_get_extents(quote)->size.x;
866 if (outside == ADG_THREE_STATE_UNKNOWN &&
867 detached == ADG_THREE_STATE_UNKNOWN) {
868 /* Both flags need to be choosed */
869 if (quote_space + markers_space < available_space) {
870 *to_detach = FALSE;
871 *to_outside = FALSE;
872 } else if (quote_space < available_space) {
873 *to_detach = FALSE;
874 *to_outside = TRUE;
875 } else {
876 *to_detach = TRUE;
877 *to_outside = markers_space * 1.25 > available_space;
879 } else if (outside == ADG_THREE_STATE_UNKNOWN) {
880 /* Only the outside flag may be guessed */
881 *to_outside = quote_space + markers_space > available_space;
882 } else {
883 /* Only the detached flag may be guessed */
884 *to_detach = quote_space + markers_space > available_space;
888 static void
889 _adg_update_quote(AdgLDim *ldim, AdgPair *base1, AdgPair *base2, AdgPair *pos,
890 gboolean detach, gboolean outside, gdouble gap)
892 AdgLDimPrivate *data;
893 AdgEntity *quote_entity;
894 AdgPair middle, factor, org;
895 CpmlVector dir;
896 AdgMatrix map;
898 quote_entity = (AdgEntity *) adg_dim_get_quote((AdgDim *) ldim);
899 if (quote_entity == NULL)
900 return;
902 data = (AdgLDimPrivate *) ldim->data;
903 middle.x = (base1->x + base2->x) / 2;
904 middle.y = (base1->y + base2->y) / 2;
905 dir.x = base2->x - base1->x;
906 dir.y = base2->y - base1->y;
907 cpml_vector_set_length(&dir, 1);
909 if (detach) {
910 /* Detached quote: position the quote at "pos" */
911 AdgPair tmp_pair, quote_end;
912 CpmlVector vector;
913 gdouble distance1, distance2, quote_size;
914 gboolean on_side1;
915 cairo_path_data_t *to_extend;
917 /* Set "org" to the properly converted "pos" coordinates */
918 org.x = pos->x + data->shift.base.x;
919 org.y = pos->y + data->shift.base.y;
921 /* Check in which side of the base line the quote must be rendered.
922 * This is done by checking if the (pos..middle) vector is closer
923 * to the base line vector or to its inverse, that is if they are
924 * concordants or discordants. For this purpose, an algorithm
925 * based on squared distances is used. */
926 tmp_pair.x = org.x - middle.x;
927 tmp_pair.y = org.y - middle.y;
928 vector.x = -dir.x;
929 vector.y = -dir.y;
930 distance2 = cpml_pair_squared_distance(&dir, &tmp_pair);
931 distance1 = cpml_pair_squared_distance(&vector, &tmp_pair);
932 on_side1 = distance2 > distance1;
934 /* Properly align the quote, depending on its side */
935 factor.x = on_side1 ? 1 : 0;
936 if (! on_side1)
937 vector = dir;
939 /* Add a gap between the quote and the extension line */
940 org.x += vector.x * gap;
941 org.y += vector.y * gap;
943 /* Calculate the end point (on the base line) of the quote */
944 quote_size = adg_entity_get_extents(quote_entity)->size.x;
945 quote_end.x = org.x + vector.x * quote_size;
946 quote_end.y = org.y + vector.y * quote_size;
948 /* Extends the base line to include the "quote_end" pair,
949 * so a detached quote is properly underlined */
950 if (outside) {
951 to_extend = &data->cpml.data[on_side1 ? 7 : 9];
952 } else {
953 to_extend = &data->cpml.data[9];
954 cpml_pair_to_cairo(&middle, &data->cpml.data[9]);
955 cpml_pair_to_cairo(on_side1 ? base1 : base2, &data->cpml.data[11]);
956 data->cpml.data[2].header.length = 6;
959 /* Extend the base line only if needed */
960 cpml_pair_from_cairo(&tmp_pair, to_extend);
961 distance1 = cpml_pair_squared_distance(&quote_end, &middle);
962 distance2 = cpml_pair_squared_distance(&tmp_pair, &middle);
963 if (distance1 > distance2)
964 cpml_pair_to_cairo(&quote_end, to_extend);
965 } else {
966 /* Center the quote in the middle of the base line */
967 factor.x = 0.5;
968 cpml_pair_copy(&org, &middle);
971 factor.y = 0;
972 adg_alignment_set_factor((AdgAlignment *) quote_entity, &factor);
973 cairo_matrix_init(&map, dir.x, dir.y, -dir.y, dir.x, org.x, org.y);
974 adg_entity_set_global_map(quote_entity, &map);
975 adg_entity_arrange(quote_entity);
977 adg_matrix_copy(&data->quote.global_map, &map);
980 static void
981 _adg_update_extents(AdgLDim *ldim)
983 AdgLDimPrivate *data;
984 CpmlExtents new_extents;
985 const CpmlExtents *extents;
986 AdgEntity *quote;
987 AdgMarker *marker;
988 AdgEntity *marker_entity;
990 data = ldim->data;
991 quote = (AdgEntity *) adg_dim_get_quote((AdgDim *) ldim);
992 new_extents.is_defined = FALSE;
994 /* The quote is always present (otherwise something bad happened) */
995 extents = adg_entity_get_extents(quote);
996 if (extents != NULL)
997 cpml_extents_add(&new_extents, extents);
999 if (data->trail != NULL) {
1000 extents = adg_trail_get_extents(data->trail);
1001 if (extents != NULL)
1002 cpml_extents_add(&new_extents, extents);
1005 if (data->marker1 != NULL) {
1006 marker_entity = (AdgEntity *) data->marker1;
1007 adg_entity_local_changed(marker_entity);
1008 adg_entity_arrange(marker_entity);
1009 extents = adg_entity_get_extents(marker_entity);
1010 if (extents != NULL)
1011 cpml_extents_add(&new_extents, extents);
1014 if (data->marker2 != NULL) {
1015 marker_entity = (AdgEntity *) data->marker2;
1016 adg_entity_local_changed(marker_entity);
1017 adg_entity_arrange(marker_entity);
1018 extents = adg_entity_get_extents(marker_entity);
1019 if (extents != NULL)
1020 cpml_extents_add(&new_extents, extents);
1023 adg_entity_set_extents((AdgEntity *) ldim, &new_extents);
1026 static void
1027 _adg_unset_trail(AdgLDim *ldim)
1029 AdgLDimPrivate *data = ldim->data;
1031 if (data->trail != NULL)
1032 adg_model_clear((AdgModel *) data->trail);
1034 data->cpml.path.status = CAIRO_STATUS_INVALID_PATH_DATA;
1037 static void
1038 _adg_dispose_trail(AdgLDim *ldim)
1040 AdgLDimPrivate *data = ldim->data;
1042 if (data->trail != NULL) {
1043 g_object_unref(data->trail);
1044 data->trail = NULL;
1048 static void
1049 _adg_dispose_markers(AdgLDim *ldim)
1051 AdgLDimPrivate *data = ldim->data;
1053 if (data->marker1 != NULL) {
1054 g_object_unref(data->marker1);
1055 data->marker1 = NULL;
1058 if (data->marker2 != NULL) {
1059 g_object_unref(data->marker2);
1060 data->marker2 = NULL;
1064 static CpmlPath *
1065 _adg_trail_callback(AdgTrail *trail, gpointer user_data)
1067 AdgLDim *ldim;
1068 AdgLDimPrivate *data;
1070 ldim = (AdgLDim *) user_data;
1071 data = ldim->data;
1073 return &data->cpml.path;