Code

Allow snapping from and to ellipses
[inkscape.git] / src / sp-guide.cpp
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>
37 #include "document.h"
39 using std::vector;
41 enum {
42     PROP_0,
43     PROP_COLOR,
44     PROP_HICOLOR
45 };
47 static void sp_guide_class_init(SPGuideClass *gc);
48 static void sp_guide_init(SPGuide *guide);
49 static void sp_guide_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec);
50 static void sp_guide_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec);
52 static void sp_guide_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr);
53 static void sp_guide_release(SPObject *object);
54 static void sp_guide_set(SPObject *object, unsigned int key, const gchar *value);
56 static SPObjectClass *parent_class;
58 GType sp_guide_get_type(void)
59 {
60     static GType guide_type = 0;
62     if (!guide_type) {
63         GTypeInfo guide_info = {
64             sizeof(SPGuideClass),
65             NULL, NULL,
66             (GClassInitFunc) sp_guide_class_init,
67             NULL, NULL,
68             sizeof(SPGuide),
69             16,
70             (GInstanceInitFunc) sp_guide_init,
71             NULL,       /* value_table */
72         };
73         guide_type = g_type_register_static(SP_TYPE_OBJECT, "SPGuide", &guide_info, (GTypeFlags) 0);
74     }
76     return guide_type;
77 }
79 static void sp_guide_class_init(SPGuideClass *gc)
80 {
81     GObjectClass *gobject_class = (GObjectClass *) gc;
82     SPObjectClass *sp_object_class = (SPObjectClass *) gc;
84     parent_class = (SPObjectClass*) g_type_class_ref(SP_TYPE_OBJECT);
86     gobject_class->set_property = sp_guide_set_property;
87     gobject_class->get_property = sp_guide_get_property;
89     sp_object_class->build = sp_guide_build;
90     sp_object_class->release = sp_guide_release;
91     sp_object_class->set = sp_guide_set;
93     g_object_class_install_property(gobject_class,
94                                     PROP_COLOR,
95                                     g_param_spec_uint("color", "Color", "Color",
96                                                       0,
97                                                       0xffffffff,
98                                                       0xff000000,
99                                                       (GParamFlags) G_PARAM_READWRITE));
101     g_object_class_install_property(gobject_class,
102                                     PROP_HICOLOR,
103                                     g_param_spec_uint("hicolor", "HiColor", "HiColor",
104                                                       0,
105                                                       0xffffffff,
106                                                       0xff000000,
107                                                       (GParamFlags) G_PARAM_READWRITE));
110 static void sp_guide_init(SPGuide *guide)
112     guide->normal_to_line = component_vectors[NR::Y];
113     guide->point_on_line = Geom::Point(0.,0.);
114     guide->color = 0x0000ff7f;
115     guide->hicolor = 0xff00007f;
118 static void sp_guide_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec */*pspec*/)
120     SPGuide &guide = *SP_GUIDE(object);
122     switch (prop_id) {
123         case PROP_COLOR:
124             guide.color = g_value_get_uint(value);
125             for (GSList *l = guide.views; l != NULL; l = l->next) {
126                 sp_guideline_set_color(SP_GUIDELINE(l->data), guide.color);
127             }
128             break;
130         case PROP_HICOLOR:
131             guide.hicolor = g_value_get_uint(value);
132             break;
133     }
136 static void sp_guide_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec */*pspec*/)
138     SPGuide const &guide = *SP_GUIDE(object);
140     switch (prop_id) {
141         case PROP_COLOR:
142             g_value_set_uint(value, guide.color);
143             break;
144         case PROP_HICOLOR:
145             g_value_set_uint(value, guide.hicolor);
146             break;
147     }
150 static void sp_guide_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr)
152     if (((SPObjectClass *) (parent_class))->build) {
153         (* ((SPObjectClass *) (parent_class))->build)(object, document, repr);
154     }
156     sp_object_read_attr(object, "orientation");
157     sp_object_read_attr(object, "position");
160 static void sp_guide_release(SPObject *object)
162     SPGuide *guide = (SPGuide *) object;
164     while (guide->views) {
165         gtk_object_destroy(GTK_OBJECT(guide->views->data));
166         guide->views = g_slist_remove(guide->views, guide->views->data);
167     }
169     if (((SPObjectClass *) parent_class)->release) {
170         ((SPObjectClass *) parent_class)->release(object);
171     }
174 static void sp_guide_set(SPObject *object, unsigned int key, const gchar *value)
176     SPGuide *guide = SP_GUIDE(object);
178     switch (key) {
179     case SP_ATTR_ORIENTATION:
180         {
181             if (value && !strcmp(value, "horizontal")) {
182                 /* Visual representation of a horizontal line, constrain vertically (y coordinate). */
183                 guide->normal_to_line = component_vectors[NR::Y];
184             } else if (value && !strcmp(value, "vertical")) {
185                 guide->normal_to_line = component_vectors[NR::X];
186             } else if (value) {
187                 gchar ** strarray = g_strsplit(value, ",", 2);
188                 double newx, newy;
189                 unsigned int success = sp_svg_number_read_d(strarray[0], &newx);
190                 success += sp_svg_number_read_d(strarray[1], &newy);
191                 g_strfreev (strarray);
192                 if (success == 2) {
193                     Geom::Point direction(newx, newy);
194                     direction.normalize();
195                     guide->normal_to_line = direction;
196                 } else {
197                     // default to vertical line for bad arguments
198                     guide->normal_to_line = component_vectors[NR::X];
199                 }
200             } else {
201                 // default to vertical line for bad arguments
202                 guide->normal_to_line = component_vectors[NR::X];
203             }
204             sp_guide_set_normal(*guide, guide->normal_to_line.to_2geom(), false);
205         }
206         break;
207     case SP_ATTR_POSITION:
208         {
209             gchar ** strarray = g_strsplit(value, ",", 2);
210             double newx, newy;
211             unsigned int success = sp_svg_number_read_d(strarray[0], &newx);
212             success += sp_svg_number_read_d(strarray[1], &newy);
213             g_strfreev (strarray);
214             if (success == 2) {
215                 guide->point_on_line = Geom::Point(newx, newy);
216             } else if (success == 1) {
217                 // before 0.46 style guideline definition.
218                 const gchar *attr = SP_OBJECT_REPR(object)->attribute("orientation");
219                 if (attr && !strcmp(attr, "horizontal")) {
220                     guide->point_on_line = Geom::Point(0, newx);
221                 } else {
222                     guide->point_on_line = Geom::Point(newx, 0);
223                 }
224             }
226             // update position in non-committing way
227             // fixme: perhaps we need to add an update method instead, and request_update here
228             sp_guide_moveto(*guide, guide->point_on_line, false);
229         }
230         break;
231     default:
232             if (((SPObjectClass *) (parent_class))->set) {
233                 ((SPObjectClass *) (parent_class))->set(object, key, value);
234             }
235             break;
236     }
239 SPGuide *
240 sp_guide_create(SPDocument *doc, Geom::Point const &pt1, Geom::Point const &pt2) {
241     SPDesktop *desktop = inkscape_active_desktop();
242     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(doc);
244     Inkscape::XML::Node *repr = xml_doc->createElement("sodipodi:guide");
246     Geom::Point n = Geom::rot90(pt2 - pt1);
248     sp_repr_set_point(repr, "position", pt1);
249     sp_repr_set_point(repr, "orientation", n);
251     SP_OBJECT_REPR(desktop->namedview)->appendChild(repr);
252     Inkscape::GC::release(repr);
254     SPGuide *guide= SP_GUIDE(doc->getObjectByRepr(repr));
255     return guide;
258 void
259 sp_guide_pt_pairs_to_guides(SPDocument *doc, std::list<std::pair<Geom::Point, Geom::Point> > &pts) {
260     for (std::list<std::pair<Geom::Point, Geom::Point> >::iterator i = pts.begin(); i != pts.end(); ++i) {
261         sp_guide_create(doc, (*i).first, (*i).second);
262     }
265 void sp_guide_show(SPGuide *guide, SPCanvasGroup *group, GCallback handler)
267     SPCanvasItem *item = sp_guideline_new(group, guide->point_on_line, guide->normal_to_line.to_2geom());
268     sp_guideline_set_color(SP_GUIDELINE(item), guide->color);
270     g_signal_connect(G_OBJECT(item), "event", G_CALLBACK(handler), guide);
272     guide->views = g_slist_prepend(guide->views, item);
275 void sp_guide_hide(SPGuide *guide, SPCanvas *canvas)
277     g_assert(guide != NULL);
278     g_assert(SP_IS_GUIDE(guide));
279     g_assert(canvas != NULL);
280     g_assert(SP_IS_CANVAS(canvas));
282     for (GSList *l = guide->views; l != NULL; l = l->next) {
283         if (canvas == SP_CANVAS_ITEM(l->data)->canvas) {
284             gtk_object_destroy(GTK_OBJECT(l->data));
285             guide->views = g_slist_remove(guide->views, l->data);
286             return;
287         }
288     }
290     g_assert_not_reached();
293 void sp_guide_sensitize(SPGuide *guide, SPCanvas *canvas, gboolean sensitive)
295     g_assert(guide != NULL);
296     g_assert(SP_IS_GUIDE(guide));
297     g_assert(canvas != NULL);
298     g_assert(SP_IS_CANVAS(canvas));
300     for (GSList *l = guide->views; l != NULL; l = l->next) {
301         if (canvas == SP_CANVAS_ITEM(l->data)->canvas) {
302             sp_guideline_set_sensitive(SP_GUIDELINE(l->data), sensitive);
303             return;
304         }
305     }
307     g_assert_not_reached();
310 Geom::Point sp_guide_position_from_pt(SPGuide const *guide, NR::Point const &pt)
312     return -(pt.to_2geom() - guide->point_on_line);
315 double sp_guide_distance_from_pt(SPGuide const *guide, Geom::Point const &pt)
317     return dot(pt - guide->point_on_line, guide->normal_to_line);
320 /**
321  * \arg commit False indicates temporary moveto in response to motion event while dragging,
322  *      true indicates a "committing" version: in response to button release event after
323  *      dragging a guideline, or clicking OK in guide editing dialog.
324  */
325 void sp_guide_moveto(SPGuide const &guide, Geom::Point const point_on_line, bool const commit)
327     g_assert(SP_IS_GUIDE(&guide));
329     for (GSList *l = guide.views; l != NULL; l = l->next) {
330         sp_guideline_set_position(SP_GUIDELINE(l->data), point_on_line);
331     }
333     /* Calling sp_repr_set_point must precede calling sp_item_notify_moveto in the commit
334        case, so that the guide's new position is available for sp_item_rm_unsatisfied_cns. */
335     if (commit) {
336         sp_repr_set_point(SP_OBJECT(&guide)->repr, "position", point_on_line);
337     }
339 /*  DISABLED CODE BECAUSE  SPGuideAttachment  IS NOT USE AT THE MOMENT (johan)
340     for (vector<SPGuideAttachment>::const_iterator i(guide.attached_items.begin()),
341              iEnd(guide.attached_items.end());
342          i != iEnd; ++i)
343     {
344         SPGuideAttachment const &att = *i;
345         sp_item_notify_moveto(*att.item, guide, att.snappoint_ix, position, commit);
346     }
347 */
350 /**
351  * \arg commit False indicates temporary moveto in response to motion event while dragging,
352  *      true indicates a "committing" version: in response to button release event after
353  *      dragging a guideline, or clicking OK in guide editing dialog.
354  */
355 void sp_guide_set_normal(SPGuide const &guide, Geom::Point const normal_to_line, bool const commit)
357     g_assert(SP_IS_GUIDE(&guide));
359     for (GSList *l = guide.views; l != NULL; l = l->next) {
360         sp_guideline_set_normal(SP_GUIDELINE(l->data), normal_to_line);
361     }
363     /* Calling sp_repr_set_svg_point must precede calling sp_item_notify_moveto in the commit
364        case, so that the guide's new position is available for sp_item_rm_unsatisfied_cns. */
365     if (commit) {
366         sp_repr_set_point(SP_OBJECT(&guide)->repr, "orientation", normal_to_line);
367     }
369 /*  DISABLED CODE BECAUSE  SPGuideAttachment  IS NOT USE AT THE MOMENT (johan)
370     for (vector<SPGuideAttachment>::const_iterator i(guide.attached_items.begin()),
371              iEnd(guide.attached_items.end());
372          i != iEnd; ++i)
373     {
374         SPGuideAttachment const &att = *i;
375         sp_item_notify_moveto(*att.item, guide, att.snappoint_ix, position, commit);
376     }
377 */
380 /**
381  * Returns a human-readable description of the guideline for use in dialog boxes and status bar.
382  *
383  * The caller is responsible for freeing the string.
384  */
385 char *sp_guide_description(SPGuide const *guide)
387     using NR::X;
388     using NR::Y;
389             
390     GString *position_string_x = SP_PX_TO_METRIC_STRING(guide->point_on_line[X], SP_ACTIVE_DESKTOP->namedview->getDefaultMetric());
391     GString *position_string_y = SP_PX_TO_METRIC_STRING(guide->point_on_line[Y], SP_ACTIVE_DESKTOP->namedview->getDefaultMetric());
393     if ( guide->normal_to_line == component_vectors[X] ) {
394         return g_strdup_printf(_("vertical guideline at %s"), position_string_x->str);
395     } else if ( guide->normal_to_line == component_vectors[Y] ) {
396         return g_strdup_printf(_("horizontal guideline at %s"), position_string_y->str);
397     } else {
398         double const radians = guide->angle();
399         double const degrees = Geom::rad_to_deg(radians);
400         int const degrees_int = (int) round(degrees);
401         return g_strdup_printf("%d degree guideline at (%s,%s)", degrees_int, position_string_x->str, position_string_y->str);
402         /* Alternative suggestion: "angled guideline". */
403     }
405     g_string_free(position_string_x, TRUE);
406     g_string_free(position_string_y, TRUE);
409 void sp_guide_remove(SPGuide *guide)
411     g_assert(SP_IS_GUIDE(guide));
413     for (vector<SPGuideAttachment>::const_iterator i(guide->attached_items.begin()),
414              iEnd(guide->attached_items.end());
415          i != iEnd; ++i)
416     {
417         SPGuideAttachment const &att = *i;
418         remove_last(att.item->constraints, SPGuideConstraint(guide, att.snappoint_ix));
419     }
420     guide->attached_items.clear();
422     sp_repr_unparent(SP_OBJECT(guide)->repr);
425 /*
426   Local Variables:
427   mode:c++
428   c-file-style:"stroustrup"
429   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
430   indent-tabs-mode:nil
431   fill-column:99
432   End:
433 */
434 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :