1 #define __SP_GUIDE_C__
3 /*
4 * Inkscape guideline implementation
5 *
6 * Authors:
7 * Lauris Kaplinski <lauris@kaplinski.com>
8 * Peter Moulder <pmoulder@mail.csse.monash.edu.au>
9 * Johan Engelen
10 *
11 * Copyright (C) 2000-2002 authors
12 * Copyright (C) 2004 Monash University
13 * Copyright (C) 2007 Johan Engelen
14 *
15 * Released under GNU GPL, read the file 'COPYING' for more information
16 */
18 #ifdef HAVE_CONFIG_H
19 # include <config.h>
20 #endif
21 #include "display/guideline.h"
22 #include "svg/svg.h"
23 #include "svg/stringstream.h"
24 #include "attributes.h"
25 #include "sp-guide.h"
26 #include <sp-item-notify-moveto.h>
27 #include <sp-item.h>
28 #include <sp-guide-constraint.h>
29 #include <glibmm/i18n.h>
30 #include <xml/repr.h>
31 #include <remove-last.h>
32 #include "sp-metrics.h"
33 #include "inkscape.h"
34 #include "desktop.h"
35 #include "sp-namedview.h"
36 #include <2geom/angle.h>
38 using std::vector;
40 enum {
41 PROP_0,
42 PROP_COLOR,
43 PROP_HICOLOR
44 };
46 static void sp_guide_class_init(SPGuideClass *gc);
47 static void sp_guide_init(SPGuide *guide);
48 static void sp_guide_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec);
49 static void sp_guide_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec);
51 static void sp_guide_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr);
52 static void sp_guide_release(SPObject *object);
53 static void sp_guide_set(SPObject *object, unsigned int key, const gchar *value);
55 static SPObjectClass *parent_class;
57 GType sp_guide_get_type(void)
58 {
59 static GType guide_type = 0;
61 if (!guide_type) {
62 GTypeInfo guide_info = {
63 sizeof(SPGuideClass),
64 NULL, NULL,
65 (GClassInitFunc) sp_guide_class_init,
66 NULL, NULL,
67 sizeof(SPGuide),
68 16,
69 (GInstanceInitFunc) sp_guide_init,
70 NULL, /* value_table */
71 };
72 guide_type = g_type_register_static(SP_TYPE_OBJECT, "SPGuide", &guide_info, (GTypeFlags) 0);
73 }
75 return guide_type;
76 }
78 static void sp_guide_class_init(SPGuideClass *gc)
79 {
80 GObjectClass *gobject_class = (GObjectClass *) gc;
81 SPObjectClass *sp_object_class = (SPObjectClass *) gc;
83 parent_class = (SPObjectClass*) g_type_class_ref(SP_TYPE_OBJECT);
85 gobject_class->set_property = sp_guide_set_property;
86 gobject_class->get_property = sp_guide_get_property;
88 sp_object_class->build = sp_guide_build;
89 sp_object_class->release = sp_guide_release;
90 sp_object_class->set = sp_guide_set;
92 g_object_class_install_property(gobject_class,
93 PROP_COLOR,
94 g_param_spec_uint("color", "Color", "Color",
95 0,
96 0xffffffff,
97 0xff000000,
98 (GParamFlags) G_PARAM_READWRITE));
100 g_object_class_install_property(gobject_class,
101 PROP_HICOLOR,
102 g_param_spec_uint("hicolor", "HiColor", "HiColor",
103 0,
104 0xffffffff,
105 0xff000000,
106 (GParamFlags) G_PARAM_READWRITE));
107 }
109 static void sp_guide_init(SPGuide *guide)
110 {
111 guide->normal_to_line = component_vectors[NR::Y];
112 guide->point_on_line = Geom::Point(0.,0.);
113 guide->color = 0x0000ff7f;
114 guide->hicolor = 0xff00007f;
115 }
117 static void sp_guide_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec */*pspec*/)
118 {
119 SPGuide &guide = *SP_GUIDE(object);
121 switch (prop_id) {
122 case PROP_COLOR:
123 guide.color = g_value_get_uint(value);
124 for (GSList *l = guide.views; l != NULL; l = l->next) {
125 sp_guideline_set_color(SP_GUIDELINE(l->data), guide.color);
126 }
127 break;
129 case PROP_HICOLOR:
130 guide.hicolor = g_value_get_uint(value);
131 break;
132 }
133 }
135 static void sp_guide_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec */*pspec*/)
136 {
137 SPGuide const &guide = *SP_GUIDE(object);
139 switch (prop_id) {
140 case PROP_COLOR:
141 g_value_set_uint(value, guide.color);
142 break;
143 case PROP_HICOLOR:
144 g_value_set_uint(value, guide.hicolor);
145 break;
146 }
147 }
149 static void sp_guide_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr)
150 {
151 if (((SPObjectClass *) (parent_class))->build) {
152 (* ((SPObjectClass *) (parent_class))->build)(object, document, repr);
153 }
155 sp_object_read_attr(object, "orientation");
156 sp_object_read_attr(object, "position");
157 }
159 static void sp_guide_release(SPObject *object)
160 {
161 SPGuide *guide = (SPGuide *) object;
163 while (guide->views) {
164 gtk_object_destroy(GTK_OBJECT(guide->views->data));
165 guide->views = g_slist_remove(guide->views, guide->views->data);
166 }
168 if (((SPObjectClass *) parent_class)->release) {
169 ((SPObjectClass *) parent_class)->release(object);
170 }
171 }
173 static void sp_guide_set(SPObject *object, unsigned int key, const gchar *value)
174 {
175 SPGuide *guide = SP_GUIDE(object);
177 switch (key) {
178 case SP_ATTR_ORIENTATION:
179 {
180 if (value && !strcmp(value, "horizontal")) {
181 /* Visual representation of a horizontal line, constrain vertically (y coordinate). */
182 guide->normal_to_line = component_vectors[NR::Y];
183 } else if (value && !strcmp(value, "vertical")) {
184 guide->normal_to_line = component_vectors[NR::X];
185 } else if (value) {
186 gchar ** strarray = g_strsplit(value, ",", 2);
187 double newx, newy;
188 unsigned int success = sp_svg_number_read_d(strarray[0], &newx);
189 success += sp_svg_number_read_d(strarray[1], &newy);
190 g_strfreev (strarray);
191 if (success == 2) {
192 Geom::Point direction(newx, newy);
193 direction.normalize();
194 guide->normal_to_line = direction;
195 } else {
196 // default to vertical line for bad arguments
197 guide->normal_to_line = component_vectors[NR::X];
198 }
199 } else {
200 // default to vertical line for bad arguments
201 guide->normal_to_line = component_vectors[NR::X];
202 }
203 sp_guide_set_normal(*guide, guide->normal_to_line.to_2geom(), false);
204 }
205 break;
206 case SP_ATTR_POSITION:
207 {
208 gchar ** strarray = g_strsplit(value, ",", 2);
209 double newx, newy;
210 unsigned int success = sp_svg_number_read_d(strarray[0], &newx);
211 success += sp_svg_number_read_d(strarray[1], &newy);
212 g_strfreev (strarray);
213 if (success == 2) {
214 guide->point_on_line = Geom::Point(newx, newy);
215 } else if (success == 1) {
216 // before 0.46 style guideline definition.
217 const gchar *attr = SP_OBJECT_REPR(object)->attribute("orientation");
218 if (attr && !strcmp(attr, "horizontal")) {
219 guide->point_on_line = Geom::Point(0, newx);
220 } else {
221 guide->point_on_line = Geom::Point(newx, 0);
222 }
223 }
225 // update position in non-committing way
226 // fixme: perhaps we need to add an update method instead, and request_update here
227 sp_guide_moveto(*guide, guide->point_on_line, false);
228 }
229 break;
230 default:
231 if (((SPObjectClass *) (parent_class))->set) {
232 ((SPObjectClass *) (parent_class))->set(object, key, value);
233 }
234 break;
235 }
236 }
238 void sp_guide_show(SPGuide *guide, SPCanvasGroup *group, GCallback handler)
239 {
240 SPCanvasItem *item = sp_guideline_new(group, guide->point_on_line, guide->normal_to_line.to_2geom());
241 sp_guideline_set_color(SP_GUIDELINE(item), guide->color);
243 g_signal_connect(G_OBJECT(item), "event", G_CALLBACK(handler), guide);
245 guide->views = g_slist_prepend(guide->views, item);
246 }
248 void sp_guide_hide(SPGuide *guide, SPCanvas *canvas)
249 {
250 g_assert(guide != NULL);
251 g_assert(SP_IS_GUIDE(guide));
252 g_assert(canvas != NULL);
253 g_assert(SP_IS_CANVAS(canvas));
255 for (GSList *l = guide->views; l != NULL; l = l->next) {
256 if (canvas == SP_CANVAS_ITEM(l->data)->canvas) {
257 gtk_object_destroy(GTK_OBJECT(l->data));
258 guide->views = g_slist_remove(guide->views, l->data);
259 return;
260 }
261 }
263 g_assert_not_reached();
264 }
266 void sp_guide_sensitize(SPGuide *guide, SPCanvas *canvas, gboolean sensitive)
267 {
268 g_assert(guide != NULL);
269 g_assert(SP_IS_GUIDE(guide));
270 g_assert(canvas != NULL);
271 g_assert(SP_IS_CANVAS(canvas));
273 for (GSList *l = guide->views; l != NULL; l = l->next) {
274 if (canvas == SP_CANVAS_ITEM(l->data)->canvas) {
275 sp_guideline_set_sensitive(SP_GUIDELINE(l->data), sensitive);
276 return;
277 }
278 }
280 g_assert_not_reached();
281 }
283 Geom::Point sp_guide_position_from_pt(SPGuide const *guide, NR::Point const &pt)
284 {
285 return -(pt.to_2geom() - guide->point_on_line);
286 }
288 double sp_guide_distance_from_pt(SPGuide const *guide, Geom::Point const &pt)
289 {
290 return dot(pt - guide->point_on_line, guide->normal_to_line);
291 }
293 /**
294 * \arg commit False indicates temporary moveto in response to motion event while dragging,
295 * true indicates a "committing" version: in response to button release event after
296 * dragging a guideline, or clicking OK in guide editing dialog.
297 */
298 void sp_guide_moveto(SPGuide const &guide, Geom::Point const point_on_line, bool const commit)
299 {
300 g_assert(SP_IS_GUIDE(&guide));
302 for (GSList *l = guide.views; l != NULL; l = l->next) {
303 sp_guideline_set_position(SP_GUIDELINE(l->data), point_on_line);
304 }
306 /* Calling sp_repr_set_point must precede calling sp_item_notify_moveto in the commit
307 case, so that the guide's new position is available for sp_item_rm_unsatisfied_cns. */
308 if (commit) {
309 sp_repr_set_point(SP_OBJECT(&guide)->repr, "position", point_on_line);
310 }
312 /* DISABLED CODE BECAUSE SPGuideAttachment IS NOT USE AT THE MOMENT (johan)
313 for (vector<SPGuideAttachment>::const_iterator i(guide.attached_items.begin()),
314 iEnd(guide.attached_items.end());
315 i != iEnd; ++i)
316 {
317 SPGuideAttachment const &att = *i;
318 sp_item_notify_moveto(*att.item, guide, att.snappoint_ix, position, commit);
319 }
320 */
321 }
323 /**
324 * \arg commit False indicates temporary moveto in response to motion event while dragging,
325 * true indicates a "committing" version: in response to button release event after
326 * dragging a guideline, or clicking OK in guide editing dialog.
327 */
328 void sp_guide_set_normal(SPGuide const &guide, Geom::Point const normal_to_line, bool const commit)
329 {
330 g_assert(SP_IS_GUIDE(&guide));
332 for (GSList *l = guide.views; l != NULL; l = l->next) {
333 sp_guideline_set_normal(SP_GUIDELINE(l->data), normal_to_line);
334 }
336 /* Calling sp_repr_set_svg_point must precede calling sp_item_notify_moveto in the commit
337 case, so that the guide's new position is available for sp_item_rm_unsatisfied_cns. */
338 if (commit) {
339 sp_repr_set_point(SP_OBJECT(&guide)->repr, "orientation", normal_to_line);
340 }
342 /* DISABLED CODE BECAUSE SPGuideAttachment IS NOT USE AT THE MOMENT (johan)
343 for (vector<SPGuideAttachment>::const_iterator i(guide.attached_items.begin()),
344 iEnd(guide.attached_items.end());
345 i != iEnd; ++i)
346 {
347 SPGuideAttachment const &att = *i;
348 sp_item_notify_moveto(*att.item, guide, att.snappoint_ix, position, commit);
349 }
350 */
351 }
353 /**
354 * Returns a human-readable description of the guideline for use in dialog boxes and status bar.
355 *
356 * The caller is responsible for freeing the string.
357 */
358 char *sp_guide_description(SPGuide const *guide)
359 {
360 using NR::X;
361 using NR::Y;
363 GString *position_string_x = SP_PX_TO_METRIC_STRING(guide->point_on_line[X], SP_ACTIVE_DESKTOP->namedview->getDefaultMetric());
364 GString *position_string_y = SP_PX_TO_METRIC_STRING(guide->point_on_line[Y], SP_ACTIVE_DESKTOP->namedview->getDefaultMetric());
366 if ( guide->normal_to_line == component_vectors[X] ) {
367 return g_strdup_printf(_("vertical guideline at %s"), position_string_x->str);
368 } else if ( guide->normal_to_line == component_vectors[Y] ) {
369 return g_strdup_printf(_("horizontal guideline at %s"), position_string_y->str);
370 } else {
371 double const radians = guide->angle();
372 double const degrees = Geom::rad_to_deg(radians);
373 int const degrees_int = (int) round(degrees);
374 return g_strdup_printf("%d degree guideline at (%s,%s)", degrees_int, position_string_x->str, position_string_y->str);
375 /* Alternative suggestion: "angled guideline". */
376 }
378 g_string_free(position_string_x, TRUE);
379 g_string_free(position_string_y, TRUE);
380 }
382 void sp_guide_remove(SPGuide *guide)
383 {
384 g_assert(SP_IS_GUIDE(guide));
386 for (vector<SPGuideAttachment>::const_iterator i(guide->attached_items.begin()),
387 iEnd(guide->attached_items.end());
388 i != iEnd; ++i)
389 {
390 SPGuideAttachment const &att = *i;
391 remove_last(att.item->constraints, SPGuideConstraint(guide, att.snappoint_ix));
392 }
393 guide->attached_items.clear();
395 sp_repr_unparent(SP_OBJECT(guide)->repr);
396 }
398 /*
399 Local Variables:
400 mode:c++
401 c-file-style:"stroustrup"
402 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
403 indent-tabs-mode:nil
404 fill-column:99
405 End:
406 */
407 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :