Code

Some consistency cleanup for descriptions.
[inkscape.git] / src / sp-rect.cpp
1 /*
2  * SVG <rect> implementation
3  *
4  * Authors:
5  *   Lauris Kaplinski <lauris@kaplinski.com>
6  *   bulia byak <buliabyak@users.sf.net>
7  *
8  * Copyright (C) 1999-2002 Lauris Kaplinski
9  * Copyright (C) 2000-2001 Ximian, Inc.
10  *
11  * Released under GNU GPL, read the file 'COPYING' for more information
12  */
14 #ifdef HAVE_CONFIG_H
15 # include "config.h"
16 #endif
19 #include <display/curve.h>
20 #include <libnr/nr-matrix-ops.h>
21 #include <libnr/nr-matrix-fns.h>
23 #include "inkscape.h"
24 #include "document.h"
25 #include "attributes.h"
26 #include "style.h"
27 #include "sp-rect.h"
28 #include <glibmm/i18n.h>
29 #include "xml/repr.h"
30 #include "sp-guide.h"
31 #include "preferences.h"
33 #define noRECT_VERBOSE
35 static void sp_rect_class_init(SPRectClass *klass);
36 static void sp_rect_init(SPRect *rect);
38 static void sp_rect_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr);
39 static void sp_rect_set(SPObject *object, unsigned key, gchar const *value);
40 static void sp_rect_update(SPObject *object, SPCtx *ctx, guint flags);
41 static Inkscape::XML::Node *sp_rect_write(SPObject *object, Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, guint flags);
43 static gchar *sp_rect_description(SPItem *item);
44 static Geom::Matrix sp_rect_set_transform(SPItem *item, Geom::Matrix const &xform);
45 static void sp_rect_convert_to_guides(SPItem *item);
47 static void sp_rect_set_shape(SPShape *shape);
48 static void sp_rect_snappoints(SPItem const *item, SnapPointsIter p, Inkscape::SnapPreferences const *snapprefs);
50 static SPShapeClass *parent_class;
52 GType
53 sp_rect_get_type(void)
54 {
55     static GType type = 0;
57     if (!type) {
58         GTypeInfo info = {
59             sizeof(SPRectClass),
60             NULL,   /* base_init */
61             NULL,   /* base_finalize */
62             (GClassInitFunc) sp_rect_class_init,
63             NULL,   /* class_finalize */
64             NULL,   /* class_data */
65             sizeof(SPRect),
66             16,     /* n_preallocs */
67             (GInstanceInitFunc) sp_rect_init,
68             NULL,   /* value_table */
69         };
70         type = g_type_register_static(SP_TYPE_SHAPE, "SPRect", &info, (GTypeFlags)0);
71     }
72     return type;
73 }
75 static void
76 sp_rect_class_init(SPRectClass *klass)
77 {
78     SPObjectClass *sp_object_class = (SPObjectClass *) klass;
79     SPItemClass *item_class = (SPItemClass *) klass;
80     SPShapeClass *shape_class = (SPShapeClass *) klass;
82     parent_class = (SPShapeClass *)g_type_class_ref(SP_TYPE_SHAPE);
84     sp_object_class->build = sp_rect_build;
85     sp_object_class->write = sp_rect_write;
86     sp_object_class->set = sp_rect_set;
87     sp_object_class->update = sp_rect_update;
89     item_class->description = sp_rect_description;
90     item_class->set_transform = sp_rect_set_transform;
91     item_class->convert_to_guides = sp_rect_convert_to_guides;
92     item_class->snappoints = sp_rect_snappoints; //override the default sp_shape_snappoints; see sp_rect_snappoints for details
94     shape_class->set_shape = sp_rect_set_shape;
95 }
97 static void
98 sp_rect_init(SPRect */*rect*/)
99 {
100     /* Initializing to zero is automatic */
101     /* sp_svg_length_unset(&rect->x, SP_SVG_UNIT_NONE, 0.0, 0.0); */
102     /* sp_svg_length_unset(&rect->y, SP_SVG_UNIT_NONE, 0.0, 0.0); */
103     /* sp_svg_length_unset(&rect->width, SP_SVG_UNIT_NONE, 0.0, 0.0); */
104     /* sp_svg_length_unset(&rect->height, SP_SVG_UNIT_NONE, 0.0, 0.0); */
105     /* sp_svg_length_unset(&rect->rx, SP_SVG_UNIT_NONE, 0.0, 0.0); */
106     /* sp_svg_length_unset(&rect->ry, SP_SVG_UNIT_NONE, 0.0, 0.0); */
109 static void
110 sp_rect_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr)
112     if (((SPObjectClass *) parent_class)->build)
113         ((SPObjectClass *) parent_class)->build(object, document, repr);
115     sp_object_read_attr(object, "x");
116     sp_object_read_attr(object, "y");
117     sp_object_read_attr(object, "width");
118     sp_object_read_attr(object, "height");
119     sp_object_read_attr(object, "rx");
120     sp_object_read_attr(object, "ry");
123 static void
124 sp_rect_set(SPObject *object, unsigned key, gchar const *value)
126     SPRect *rect = SP_RECT(object);
128     /* fixme: We need real error processing some time */
130     switch (key) {
131         case SP_ATTR_X:
132             rect->x.readOrUnset(value);
133             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
134             break;
135         case SP_ATTR_Y:
136             rect->y.readOrUnset(value);
137             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
138             break;
139         case SP_ATTR_WIDTH:
140             if (!rect->width.read(value) || rect->width.value < 0.0) {
141                 rect->width.unset();
142             }
143             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
144             break;
145         case SP_ATTR_HEIGHT:
146             if (!rect->height.read(value) || rect->height.value < 0.0) {
147                 rect->height.unset();
148             }
149             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
150             break;
151         case SP_ATTR_RX:
152             if (!rect->rx.read(value) || rect->rx.value < 0.0) {
153                 rect->rx.unset();
154             }
155             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
156             break;
157         case SP_ATTR_RY:
158             if (!rect->ry.read(value) || rect->ry.value < 0.0) {
159                 rect->ry.unset();
160             }
161             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
162             break;
163         default:
164             if (((SPObjectClass *) parent_class)->set)
165                 ((SPObjectClass *) parent_class)->set(object, key, value);
166             break;
167     }
170 static void
171 sp_rect_update(SPObject *object, SPCtx *ctx, guint flags)
173     if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) {
174         SPRect *rect = (SPRect *) object;
175         SPStyle *style = object->style;
176         SPItemCtx const *ictx = (SPItemCtx const *) ctx;
177         double const w = (ictx->vp.x1 - ictx->vp.x0);
178         double const h = (ictx->vp.y1 - ictx->vp.y0);
179         double const em = style->font_size.computed;
180         double const ex = 0.5 * em;  // fixme: get x height from pango or libnrtype.
181         rect->x.update(em, ex, w);
182         rect->y.update(em, ex, h);
183         rect->width.update(em, ex, w);
184         rect->height.update(em, ex, h);
185         rect->rx.update(em, ex, w);
186         rect->ry.update(em, ex, h);
187         sp_shape_set_shape((SPShape *) object);
188         flags &= ~SP_OBJECT_USER_MODIFIED_FLAG_B; // since we change the description, it's not a "just translation" anymore
189     }
191     if (((SPObjectClass *) parent_class)->update)
192         ((SPObjectClass *) parent_class)->update(object, ctx, flags);
195 static Inkscape::XML::Node *
196 sp_rect_write(SPObject *object, Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags)
198     SPRect *rect = SP_RECT(object);
200     if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
201         repr = xml_doc->createElement("svg:rect");
202     }
204     sp_repr_set_svg_double(repr, "width", rect->width.computed);
205     sp_repr_set_svg_double(repr, "height", rect->height.computed);
206     if (rect->rx._set) sp_repr_set_svg_double(repr, "rx", rect->rx.computed);
207     if (rect->ry._set) sp_repr_set_svg_double(repr, "ry", rect->ry.computed);
208     sp_repr_set_svg_double(repr, "x", rect->x.computed);
209     sp_repr_set_svg_double(repr, "y", rect->y.computed);
211     if (((SPObjectClass *) parent_class)->write)
212         ((SPObjectClass *) parent_class)->write(object, xml_doc, repr, flags);
214     return repr;
217 static gchar *
218 sp_rect_description(SPItem *item)
220     g_return_val_if_fail(SP_IS_RECT(item), NULL);
222     return g_strdup(_("<b>Rectangle</b>"));
225 #define C1 0.554
227 static void
228 sp_rect_set_shape(SPShape *shape)
230     SPRect *rect = (SPRect *) shape;
232     if ((rect->height.computed < 1e-18) || (rect->width.computed < 1e-18)) {
233         sp_shape_set_curve_insync(SP_SHAPE(rect), NULL, TRUE);
234         return;
235     }
237     SPCurve *c = new SPCurve();
239     double const x = rect->x.computed;
240     double const y = rect->y.computed;
241     double const w = rect->width.computed;
242     double const h = rect->height.computed;
243     double const w2 = w / 2;
244     double const h2 = h / 2;
245     double const rx = std::min(( rect->rx._set
246                                  ? rect->rx.computed
247                                  : ( rect->ry._set
248                                      ? rect->ry.computed
249                                      : 0.0 ) ),
250                                .5 * rect->width.computed);
251     double const ry = std::min(( rect->ry._set
252                                  ? rect->ry.computed
253                                  : ( rect->rx._set
254                                      ? rect->rx.computed
255                                      : 0.0 ) ),
256                                .5 * rect->height.computed);
257     /* TODO: Handle negative rx or ry as per
258      * http://www.w3.org/TR/SVG11/shapes.html#RectElementRXAttribute once Inkscape has proper error
259      * handling (see http://www.w3.org/TR/SVG11/implnote.html#ErrorProcessing).
260      */
262     /* We don't use proper circular/elliptical arcs, but bezier curves can approximate a 90-degree
263      * arc fairly well.
264      */
265     if ((rx > 1e-18) && (ry > 1e-18)) {
266         c->moveto(x + rx, y);
267         if (rx < w2) c->lineto(x + w - rx, y);
268         c->curveto(x + w - rx * (1 - C1), y,     x + w, y + ry * (1 - C1),       x + w, y + ry);
269         if (ry < h2) c->lineto(x + w, y + h - ry);
270         c->curveto(x + w, y + h - ry * (1 - C1),     x + w - rx * (1 - C1), y + h,       x + w - rx, y + h);
271         if (rx < w2) c->lineto(x + rx, y + h);
272         c->curveto(x + rx * (1 - C1), y + h,     x, y + h - ry * (1 - C1),       x, y + h - ry);
273         if (ry < h2) c->lineto(x, y + ry);
274         c->curveto(x, y + ry * (1 - C1),     x + rx * (1 - C1), y,       x + rx, y);
275     } else {
276         c->moveto(x + 0.0, y + 0.0);
277         c->lineto(x + w, y + 0.0);
278         c->lineto(x + w, y + h);
279         c->lineto(x + 0.0, y + h);
280         c->lineto(x + 0.0, y + 0.0);
281     }
283     c->closepath_current();
284     sp_shape_set_curve_insync(SP_SHAPE(rect), c, TRUE);
285     c->unref();
288 /* fixme: Think (Lauris) */
290 void
291 sp_rect_position_set(SPRect *rect, gdouble x, gdouble y, gdouble width, gdouble height)
293     g_return_if_fail(rect != NULL);
294     g_return_if_fail(SP_IS_RECT(rect));
296     rect->x.computed = x;
297     rect->y.computed = y;
298     rect->width.computed = width;
299     rect->height.computed = height;
301     SP_OBJECT(rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
304 void
305 sp_rect_set_rx(SPRect *rect, gboolean set, gdouble value)
307     g_return_if_fail(rect != NULL);
308     g_return_if_fail(SP_IS_RECT(rect));
310     rect->rx._set = set;
311     if (set) rect->rx.computed = value;
313     SP_OBJECT(rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
316 void
317 sp_rect_set_ry(SPRect *rect, gboolean set, gdouble value)
319     g_return_if_fail(rect != NULL);
320     g_return_if_fail(SP_IS_RECT(rect));
322     rect->ry._set = set;
323     if (set) rect->ry.computed = value;
325     SP_OBJECT(rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
328 /*
329  * Initially we'll do:
330  * Transform x, y, set x, y, clear translation
331  */
333 /* fixme: Use preferred units somehow (Lauris) */
334 /* fixme: Alternately preserve whatever units there are (lauris) */
336 static Geom::Matrix
337 sp_rect_set_transform(SPItem *item, Geom::Matrix const &xform)
339     SPRect *rect = SP_RECT(item);
341     /* Calculate rect start in parent coords. */
342     Geom::Point pos( Geom::Point(rect->x.computed, rect->y.computed) * xform );
344     /* This function takes care of translation and scaling, we return whatever parts we can't
345        handle. */
346     Geom::Matrix ret(Geom::Matrix(xform).without_translation());
347     gdouble const sw = hypot(ret[0], ret[1]);
348     gdouble const sh = hypot(ret[2], ret[3]);
349     if (sw > 1e-9) {
350         ret[0] /= sw;
351         ret[1] /= sw;
352     } else {
353         ret[0] = 1.0;
354         ret[1] = 0.0;
355     }
356     if (sh > 1e-9) {
357         ret[2] /= sh;
358         ret[3] /= sh;
359     } else {
360         ret[2] = 0.0;
361         ret[3] = 1.0;
362     }
364     /* fixme: Would be nice to preserve units here */
365     rect->width = rect->width.computed * sw;
366     rect->height = rect->height.computed * sh;
367     if (rect->rx._set) {
368         rect->rx = rect->rx.computed * sw;
369     }
370     if (rect->ry._set) {
371         rect->ry = rect->ry.computed * sh;
372     }
374     /* Find start in item coords */
375     pos = pos * ret.inverse();
376     rect->x = pos[Geom::X];
377     rect->y = pos[Geom::Y];
379     sp_rect_set_shape(rect);
381     // Adjust stroke width
382     sp_item_adjust_stroke(item, sqrt(fabs(sw * sh)));
384     // Adjust pattern fill
385     sp_item_adjust_pattern(item, xform * ret.inverse());
387     // Adjust gradient fill
388     sp_item_adjust_gradient(item, xform * ret.inverse());
390     item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
392     return ret;
396 /**
397 Returns the ratio in which the vector from p0 to p1 is stretched by transform
398  */
399 static gdouble
400 vector_stretch(Geom::Point p0, Geom::Point p1, Geom::Matrix xform)
402     if (p0 == p1)
403         return 0;
404     return (Geom::distance(p0 * xform, p1 * xform) / Geom::distance(p0, p1));
407 void
408 sp_rect_set_visible_rx(SPRect *rect, gdouble rx)
410     if (rx == 0) {
411         rect->rx.computed = 0;
412         rect->rx._set = false;
413     } else {
414         rect->rx.computed = rx / vector_stretch(
415             Geom::Point(rect->x.computed + 1, rect->y.computed),
416             Geom::Point(rect->x.computed, rect->y.computed),
417             SP_ITEM(rect)->transform);
418         rect->rx._set = true;
419     }
420     SP_OBJECT(rect)->updateRepr();
423 void
424 sp_rect_set_visible_ry(SPRect *rect, gdouble ry)
426     if (ry == 0) {
427         rect->ry.computed = 0;
428         rect->ry._set = false;
429     } else {
430         rect->ry.computed = ry / vector_stretch(
431             Geom::Point(rect->x.computed, rect->y.computed + 1),
432             Geom::Point(rect->x.computed, rect->y.computed),
433             SP_ITEM(rect)->transform);
434         rect->ry._set = true;
435     }
436     SP_OBJECT(rect)->updateRepr();
439 gdouble
440 sp_rect_get_visible_rx(SPRect *rect)
442     if (!rect->rx._set)
443         return 0;
444     return rect->rx.computed * vector_stretch(
445         Geom::Point(rect->x.computed + 1, rect->y.computed),
446         Geom::Point(rect->x.computed, rect->y.computed),
447         SP_ITEM(rect)->transform);
450 gdouble
451 sp_rect_get_visible_ry(SPRect *rect)
453     if (!rect->ry._set)
454         return 0;
455     return rect->ry.computed * vector_stretch(
456         Geom::Point(rect->x.computed, rect->y.computed + 1),
457         Geom::Point(rect->x.computed, rect->y.computed),
458         SP_ITEM(rect)->transform);
461 void
462 sp_rect_compensate_rxry(SPRect *rect, Geom::Matrix xform)
464     if (rect->rx.computed == 0 && rect->ry.computed == 0)
465         return; // nothing to compensate
467     // test unit vectors to find out compensation:
468     Geom::Point c(rect->x.computed, rect->y.computed);
469     Geom::Point cx = c + Geom::Point(1, 0);
470     Geom::Point cy = c + Geom::Point(0, 1);
472     // apply previous transform if any
473     c *= SP_ITEM(rect)->transform;
474     cx *= SP_ITEM(rect)->transform;
475     cy *= SP_ITEM(rect)->transform;
477     // find out stretches that we need to compensate
478     gdouble eX = vector_stretch(cx, c, xform);
479     gdouble eY = vector_stretch(cy, c, xform);
481     // If only one of the radii is set, set both radii so they have the same visible length
482     // This is needed because if we just set them the same length in SVG, they might end up unequal because of transform
483     if ((rect->rx._set && !rect->ry._set) || (rect->ry._set && !rect->rx._set)) {
484         gdouble r = MAX(rect->rx.computed, rect->ry.computed);
485         rect->rx.computed = r / eX;
486         rect->ry.computed = r / eY;
487     } else {
488         rect->rx.computed = rect->rx.computed / eX;
489         rect->ry.computed = rect->ry.computed / eY;
490     }
492     // Note that a radius may end up larger than half-side if the rect is scaled down;
493     // that's ok because this preserves the intended radii in case the rect is enlarged again,
494     // and set_shape will take care of trimming too large radii when generating d=
496     rect->rx._set = rect->ry._set = true;
499 void
500 sp_rect_set_visible_width(SPRect *rect, gdouble width)
502     rect->width.computed = width / vector_stretch(
503         Geom::Point(rect->x.computed + 1, rect->y.computed),
504         Geom::Point(rect->x.computed, rect->y.computed),
505         SP_ITEM(rect)->transform);
506     rect->width._set = true;
507     SP_OBJECT(rect)->updateRepr();
510 void
511 sp_rect_set_visible_height(SPRect *rect, gdouble height)
513     rect->height.computed = height / vector_stretch(
514         Geom::Point(rect->x.computed, rect->y.computed + 1),
515         Geom::Point(rect->x.computed, rect->y.computed),
516         SP_ITEM(rect)->transform);
517     rect->height._set = true;
518     SP_OBJECT(rect)->updateRepr();
521 gdouble
522 sp_rect_get_visible_width(SPRect *rect)
524     if (!rect->width._set)
525         return 0;
526     return rect->width.computed * vector_stretch(
527         Geom::Point(rect->x.computed + 1, rect->y.computed),
528         Geom::Point(rect->x.computed, rect->y.computed),
529         SP_ITEM(rect)->transform);
532 gdouble
533 sp_rect_get_visible_height(SPRect *rect)
535     if (!rect->height._set)
536         return 0;
537     return rect->height.computed * vector_stretch(
538         Geom::Point(rect->x.computed, rect->y.computed + 1),
539         Geom::Point(rect->x.computed, rect->y.computed),
540         SP_ITEM(rect)->transform);
543 /**
544  * Sets the snappoint p to the unrounded corners of the rectangle
545  */
546 static void sp_rect_snappoints(SPItem const *item, SnapPointsIter p, Inkscape::SnapPreferences const *snapprefs)
548     /* This method overrides sp_shape_snappoints, which is the default for any shape. The default method
549     returns all eight points along the path of a rounded rectangle, but not the real corners. Snapping
550     the startpoint and endpoint of each rounded corner is not very useful and really confusing. Instead
551     we could snap either the real corners, or not snap at all. Bulia Byak opted to snap the real corners,
552     but it should be noted that this might be confusing in some cases with relatively large radii. With
553     small radii though the user will easily understand which point is snapping. */
555     g_assert(item != NULL);
556     g_assert(SP_IS_RECT(item));
558     // Help enforcing strict snapping, i.e. only return nodes when we're snapping nodes to nodes or a guide to nodes
559         if (!(snapprefs->getSnapModeNode() || snapprefs->getSnapModeGuide())) {
560                 return;
561         }
563     SPRect *rect = SP_RECT(item);
565     Geom::Matrix const i2d (sp_item_i2d_affine (item));
567     Geom::Point p0 = Geom::Point(rect->x.computed, rect->y.computed) * i2d;
568     Geom::Point p1 = Geom::Point(rect->x.computed, rect->y.computed + rect->height.computed) * i2d;
569     Geom::Point p2 = Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed) * i2d;
570     Geom::Point p3 = Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed) * i2d;
572     if (snapprefs->getSnapToItemNode()) {
573                 *p = p0;
574                 *p = p1;
575                 *p = p2;
576                 *p = p3;
577     }
579         if (snapprefs->getSnapLineMidpoints()) { // only do this when we're snapping nodes (enforce strict snapping)
580                 *p = (p0 + p1)/2;
581                 *p = (p1 + p2)/2;
582                 *p = (p2 + p3)/2;
583                 *p = (p3 + p0)/2;
584         }
586         if (snapprefs->getSnapObjectMidpoints()) { // only do this when we're snapping nodes (enforce strict snapping)
587                 *p = (p0 + p2)/2;
588         }
592 void
593 sp_rect_convert_to_guides(SPItem *item) {
594     SPRect *rect = SP_RECT(item);
596     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
597     if (!prefs->getBool("/tools/shapes/rect/convertguides", true)) {
598         sp_item_convert_to_guides(SP_ITEM(rect));
599         return;
600     }
602     std::list<std::pair<Geom::Point, Geom::Point> > pts;
604     Geom::Matrix const i2d (sp_item_i2d_affine(SP_ITEM(rect)));
606     Geom::Point A1(Geom::Point(rect->x.computed, rect->y.computed) * i2d);
607     Geom::Point A2(Geom::Point(rect->x.computed, rect->y.computed + rect->height.computed) * i2d);
608     Geom::Point A3(Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed) * i2d);
609     Geom::Point A4(Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed) * i2d);
611     pts.push_back(std::make_pair(A1, A2));
612     pts.push_back(std::make_pair(A2, A3));
613     pts.push_back(std::make_pair(A3, A4));
614     pts.push_back(std::make_pair(A4, A1));
616     sp_guide_pt_pairs_to_guides(inkscape_active_desktop(), pts);
619 /*
620   Local Variables:
621   mode:c++
622   c-file-style:"stroustrup"
623   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
624   indent-tabs-mode:nil
625   fill-column:99
626   End:
627 */
628 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :