[AdgTrail] Avoid recursion caused by bad callbacks
[adg.git] / adg / adg-ruled-fill.c
blobe6e2f95a6919374072b37939195fe9f70e140082
1 /* ADG - Automatic Drawing Generation
2 * Copyright (C) 2007,2008,2009 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-ruled-fill
23 * @short_description: A style composed of evenly spaced lines.
25 * The spacing between the lines could be changed using the
26 * adg_ruled_fill_set_spacing() method. The angle of the lines should
27 * be changed with adg_ruled_fill_set_angle().
28 **/
30 /**
31 * AdgRuledFill:
33 * All fields are private and should not be used directly.
34 * Use its public methods instead.
35 **/
38 #include "adg-ruled-fill.h"
39 #include "adg-ruled-fill-private.h"
40 #include "adg-dress-builtins.h"
41 #include "adg-intl.h"
42 #include <math.h>
44 #define PARENT_STYLE_CLASS ((AdgStyleClass *) adg_ruled_fill_parent_class)
45 #define PARENT_FILL_STYLE_CLASS ((AdgFillStyleClass *) adg_ruled_fill_parent_class)
48 enum {
49 PROP_0,
50 PROP_LINE_DRESS,
51 PROP_SPACING,
52 PROP_ANGLE
56 static void get_property (GObject *object,
57 guint prop_id,
58 GValue *value,
59 GParamSpec *pspec);
60 static void set_property (GObject *object,
61 guint prop_id,
62 const GValue *value,
63 GParamSpec *pspec);
64 static void apply (AdgStyle *style,
65 AdgEntity *entity,
66 cairo_t *cr);
67 static void set_extents (AdgFillStyle *fill_style,
68 const CpmlExtents *extents);
69 static gboolean set_spacing (AdgRuledFill *ruled_fill,
70 gdouble spacing);
71 static gboolean set_angle (AdgRuledFill *ruled_fill,
72 gdouble angle);
73 static cairo_pattern_t *create_pattern (AdgRuledFill *ruled_fill,
74 AdgEntity *entity,
75 cairo_t *cr);
76 static void draw_lines (const CpmlPair *spacing,
77 const CpmlPair *size,
78 cairo_t *cr);
81 G_DEFINE_TYPE(AdgRuledFill, adg_ruled_fill, ADG_TYPE_FILL_STYLE);
84 static void
85 adg_ruled_fill_class_init(AdgRuledFillClass *klass)
87 GObjectClass *gobject_class;
88 AdgStyleClass *style_class;
89 AdgFillStyleClass *fill_style_class;
90 GParamSpec *param;
92 gobject_class = (GObjectClass *) klass;
93 style_class = (AdgStyleClass *) klass;
94 fill_style_class = (AdgFillStyleClass *) klass;
96 g_type_class_add_private(klass, sizeof(AdgRuledFillPrivate));
98 gobject_class->get_property = get_property;
99 gobject_class->set_property = set_property;
101 style_class->apply = apply;
103 fill_style_class->set_extents = set_extents;
105 param = adg_param_spec_dress("line-dress",
106 P_("Line Dress"),
107 P_("Dress to be used for rendering the lines"),
108 ADG_DRESS_LINE_HATCH,
109 G_PARAM_READWRITE);
110 g_object_class_install_property(gobject_class, PROP_LINE_DRESS, param);
112 param = g_param_spec_double("spacing",
113 P_("Spacing"),
114 P_("The spacing in global spaces between the lines"),
115 0, G_MAXDOUBLE, 16,
116 G_PARAM_READWRITE);
117 g_object_class_install_property(gobject_class, PROP_SPACING, param);
119 param = g_param_spec_double("angle",
120 P_("Angle"),
121 P_("The angle (in radians) of the lines"),
122 0, G_PI, G_PI_4,
123 G_PARAM_READWRITE);
124 g_object_class_install_property(gobject_class, PROP_ANGLE, param);
127 static void
128 adg_ruled_fill_init(AdgRuledFill *ruled_fill)
130 AdgRuledFillPrivate *data = G_TYPE_INSTANCE_GET_PRIVATE(ruled_fill,
131 ADG_TYPE_RULED_FILL,
132 AdgRuledFillPrivate);
134 data->line_dress = ADG_DRESS_LINE_HATCH;
135 data->angle = G_PI_4;
136 data->spacing = 16;
138 ruled_fill->data = data;
141 static void
142 get_property(GObject *object,
143 guint prop_id, GValue *value, GParamSpec *pspec)
145 AdgRuledFillPrivate *data = ((AdgRuledFill *) object)->data;
147 switch (prop_id) {
148 case PROP_LINE_DRESS:
149 g_value_set_int(value, data->line_dress);
150 break;
151 case PROP_SPACING:
152 g_value_set_double(value, data->spacing);
153 break;
154 case PROP_ANGLE:
155 g_value_set_double(value, data->angle);
156 break;
157 default:
158 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
159 break;
163 static void
164 set_property(GObject *object,
165 guint prop_id, const GValue *value, GParamSpec *pspec)
167 AdgRuledFill *ruled_fill;
168 AdgRuledFillPrivate *data;
170 ruled_fill = (AdgRuledFill *) object;
171 data = ruled_fill->data;
173 switch (prop_id) {
174 case PROP_LINE_DRESS:
175 adg_dress_set(&data->line_dress, g_value_get_int(value));
176 break;
177 case PROP_SPACING:
178 set_spacing(ruled_fill, g_value_get_double(value));
179 break;
180 case PROP_ANGLE:
181 set_angle(ruled_fill, g_value_get_double(value));
182 break;
183 default:
184 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
185 break;
191 * adg_ruled_fill_set_line_dress:
192 * @ruled_fill: an #AdgRuledFill object
193 * @dress: the new line dress
195 * Sets a new line dress on @ruled_fill.
197 void
198 adg_ruled_fill_set_line_dress(AdgRuledFill *ruled_fill, AdgDress dress)
200 AdgRuledFillPrivate *data;
202 g_return_if_fail(ADG_IS_RULED_FILL(ruled_fill));
204 data = ruled_fill->data;
206 if (adg_dress_set(&data->line_dress, dress))
207 g_object_notify((GObject *) ruled_fill, "line-dress");
211 * adg_ruled_fill_get_line_dress:
212 * @ruled_fill: an #AdgRuledFill object
214 * Gets the @ruled_fill dress to be used for rendering the lines.
216 * Returns: the line dress
218 AdgDress
219 adg_ruled_fill_get_line_dress(AdgRuledFill *ruled_fill)
221 AdgRuledFillPrivate *data;
223 g_return_val_if_fail(ADG_IS_RULED_FILL(ruled_fill), ADG_DRESS_UNDEFINED);
225 data = ruled_fill->data;
227 return data->line_dress;
231 * adg_ruled_fill_set_spacing:
232 * @ruled_fill: an #AdgRuledFill
233 * @spacing: the new spacing
235 * Sets a new spacing on @ruled_fill.
237 void
238 adg_ruled_fill_set_spacing(AdgRuledFill *ruled_fill, gdouble spacing)
240 g_return_if_fail(ADG_IS_RULED_FILL(ruled_fill));
242 if (set_spacing(ruled_fill, spacing))
243 g_object_notify((GObject *) ruled_fill, "spacing");
247 * adg_ruled_fill_get_spacing:
248 * @ruled_fill: an #AdgRuledFill
250 * Gets the current spacing of @ruled_fill.
252 * Returns: the spacing (in global space)
254 gdouble
255 adg_ruled_fill_get_spacing(AdgRuledFill *ruled_fill)
257 AdgRuledFillPrivate *data;
259 g_return_val_if_fail(ADG_IS_RULED_FILL(ruled_fill), 0);
261 data = ruled_fill->data;
263 return data->spacing;
267 * adg_ruled_fill_set_angle:
268 * @ruled_fill: an #AdgRuledFill
269 * @angle: the new angle
271 * Sets a new angle on @ruled_fill.
273 void
274 adg_ruled_fill_set_angle(AdgRuledFill *ruled_fill, gdouble angle)
276 g_return_if_fail(ADG_IS_RULED_FILL(ruled_fill));
278 if (set_angle(ruled_fill, angle))
279 g_object_notify((GObject *) ruled_fill, "angle");
283 * adg_ruled_fill_get_angle:
284 * @ruled_fill: an #AdgRuledFill
286 * Gets the current angle of @ruled_fill.
288 * Returns: the angle (in radians)
290 gdouble
291 adg_ruled_fill_get_angle(AdgRuledFill *ruled_fill)
293 AdgRuledFillPrivate *data;
295 g_return_val_if_fail(ADG_IS_RULED_FILL(ruled_fill), 0);
297 data = ruled_fill->data;
299 return data->angle;
303 static void
304 apply(AdgStyle *style, AdgEntity *entity, cairo_t *cr)
306 AdgFillStyle *fill_style;
307 AdgPattern *pattern;
308 const CpmlExtents *extents;
309 cairo_matrix_t matrix;
311 fill_style = (AdgFillStyle *) style;
312 pattern = adg_fill_style_get_pattern(fill_style);
313 extents = adg_fill_style_get_extents(fill_style);
315 if (pattern == NULL) {
316 pattern = create_pattern((AdgRuledFill *) style, entity, cr);
317 if (pattern == NULL)
318 return;
320 adg_fill_style_set_pattern(fill_style, pattern);
321 cairo_pattern_destroy(pattern);
324 cairo_matrix_init_translate(&matrix, -extents->org.x, -extents->org.y);
325 cairo_pattern_set_matrix(pattern, &matrix);
327 if (PARENT_STYLE_CLASS->apply)
328 PARENT_STYLE_CLASS->apply(style, entity, cr);
331 static void
332 set_extents(AdgFillStyle *fill_style, const CpmlExtents *extents)
334 CpmlExtents old, new;
336 cpml_extents_copy(&old, adg_fill_style_get_extents(fill_style));
338 /* The pattern is invalidated (and thus regenerated) only
339 * when the new extents are wider than the old ones */
340 if (old.size.x >= extents->size.x && old.size.y >= extents->size.y) {
341 new.org = extents->org;
342 new.size = old.size;
343 } else {
344 cpml_extents_copy(&new, extents);
345 adg_fill_style_set_pattern(fill_style, NULL);
348 if (PARENT_FILL_STYLE_CLASS->set_extents)
349 PARENT_FILL_STYLE_CLASS->set_extents(fill_style, &new);
352 static gboolean
353 set_spacing(AdgRuledFill *ruled_fill, gdouble spacing)
355 AdgRuledFillPrivate *data = ruled_fill->data;
357 if (spacing == data->spacing)
358 return FALSE;
360 data->spacing = spacing;
361 adg_fill_style_set_pattern((AdgFillStyle *) ruled_fill, NULL);
363 return TRUE;
366 static gboolean
367 set_angle(AdgRuledFill *ruled_fill, gdouble angle)
369 AdgRuledFillPrivate *data = ruled_fill->data;
371 if (angle == data->angle)
372 return FALSE;
374 data->angle = angle;
375 adg_fill_style_set_pattern((AdgFillStyle *) ruled_fill, NULL);
377 return TRUE;
380 static cairo_pattern_t *
381 create_pattern(AdgRuledFill *ruled_fill, AdgEntity *entity, cairo_t *cr)
383 AdgFillStyle *fill_style;
384 const CpmlExtents *extents;
385 AdgRuledFillPrivate *data;
386 AdgStyle *line_style;
387 cairo_pattern_t *pattern;
388 cairo_surface_t *surface;
389 CpmlPair spacing;
390 cairo_t *context;
392 fill_style = (AdgFillStyle *) ruled_fill;
393 extents = adg_fill_style_get_extents(fill_style);
395 /* Check for valid extents */
396 if (!extents->is_defined)
397 return NULL;
399 data = ruled_fill->data;
400 line_style = adg_entity_style(entity, data->line_dress);
401 surface = cairo_surface_create_similar(cairo_get_target(cr),
402 CAIRO_CONTENT_COLOR_ALPHA,
403 extents->size.x, extents->size.y);
404 pattern = cairo_pattern_create_for_surface(surface);
406 /* The pattern holds a reference to the surface, so the
407 * surface should be unreferenced once */
408 cairo_surface_destroy(surface);
410 spacing.x = cos(data->angle) * data->spacing;
411 spacing.y = sin(data->angle) * data->spacing;
413 context = cairo_create(surface);
414 adg_style_apply(line_style, entity, context);
415 draw_lines(&spacing, &extents->size, context);
416 cairo_destroy(context);
418 return pattern;
421 static void
422 draw_lines(const CpmlPair *spacing, const CpmlPair *size, cairo_t *cr)
424 CpmlPair step, step1, step2;
425 CpmlPair p1, p2;
427 /* There should be some sort of spacing and a destination area */
428 if ((spacing->x == 0 && spacing->y == 0) ||
429 (size->x <= 0 && size->y <= 0))
430 return;
432 /* Revert spacings if needed to inspect only the x >= 0 cases */
433 cpml_pair_copy(&step, spacing);
434 if (spacing->x < 0 || (spacing->x == 0 && spacing->y < 0))
435 cpml_pair_negate(&step);
437 p1.x = step.x / 2;
438 p2.y = step.y / 2;
439 p1.y = step.y == 0 ? p2.y : 0;
440 p2.x = step.x == 0 ? p1.x : 0;
441 if (step.y < 0) {
442 p1.y += size->y;
443 p2.y += size->y;
446 step2.x = 0;
447 step2.y = step.y;
449 if (step.x != 0) {
450 step1.x = step.x;
451 step1.y = 0;
453 while (p1.x < size->x) {
454 if (p2.y <= 0 || p2.y >= size->y) {
455 step2.x = step.x;
456 step2.y = 0;
458 cairo_move_to(cr, p1.x, p1.y);
459 cairo_line_to(cr, p2.x, p2.y);
460 cpml_pair_add(&p1, &step1);
461 cpml_pair_add(&p2, &step2);
465 if (step.y != 0) {
466 step1.x = 0;
467 step1.y = step.y;
469 while (p1.y >= 0 && p1.y <= size->y) {
470 if (p2.y <= 0 || p2.y >= size->y) {
471 step2.x = step.x;
472 step2.y = 0;
474 cairo_move_to(cr, p1.x, p1.y);
475 cairo_line_to(cr, p2.x, p2.y);
476 cpml_pair_add(&p1, &step1);
477 cpml_pair_add(&p2, &step2);
481 cairo_stroke(cr);