Code

widgets/select-toolbar.h: Supply missing #includes/declarations so that we don't...
[inkscape.git] / src / sp-rect.cpp
1 #define __SP_RECT_C__
3 /*
4  * SVG <rect> implementation
5  *
6  * Authors:
7  *   Lauris Kaplinski <lauris@kaplinski.com>
8  *   bulia byak <buliabyak@users.sf.net>
9  *
10  * Copyright (C) 1999-2002 Lauris Kaplinski
11  * Copyright (C) 2000-2001 Ximian, Inc.
12  *
13  * Released under GNU GPL, read the file 'COPYING' for more information
14  */
16 #ifdef HAVE_CONFIG_H
17 # include "config.h"
18 #endif
21 #include <display/curve.h>
22 #include <libnr/nr-matrix-div.h>
23 #include <libnr/nr-matrix-fns.h>
25 #include "document.h"
26 #include "attributes.h"
27 #include "style.h"
28 #include "sp-rect.h"
29 #include <glibmm/i18n.h>
30 #include "xml/repr.h"
31 #include "sp-guide.h"
32 #include "prefs-utils.h"
34 #define noRECT_VERBOSE
36 static void sp_rect_class_init(SPRectClass *klass);
37 static void sp_rect_init(SPRect *rect);
39 static void sp_rect_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr);
40 static void sp_rect_set(SPObject *object, unsigned key, gchar const *value);
41 static void sp_rect_update(SPObject *object, SPCtx *ctx, guint flags);
42 static Inkscape::XML::Node *sp_rect_write(SPObject *object, Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, guint flags);
44 static gchar *sp_rect_description(SPItem *item);
45 static NR::Matrix sp_rect_set_transform(SPItem *item, NR::Matrix const &xform);
46 static void sp_rect_convert_to_guides(SPItem *item);
48 static void sp_rect_set_shape(SPShape *shape);
49 static void sp_rect_snappoints(SPItem const *item, SnapPointsIter p);
51 static SPShapeClass *parent_class;
53 GType
54 sp_rect_get_type(void)
55 {
56     static GType type = 0;
58     if (!type) {
59         GTypeInfo info = {
60             sizeof(SPRectClass),
61             NULL,   /* base_init */
62             NULL,   /* base_finalize */
63             (GClassInitFunc) sp_rect_class_init,
64             NULL,   /* class_finalize */
65             NULL,   /* class_data */
66             sizeof(SPRect),
67             16,     /* n_preallocs */
68             (GInstanceInitFunc) sp_rect_init,
69             NULL,   /* value_table */
70         };
71         type = g_type_register_static(SP_TYPE_SHAPE, "SPRect", &info, (GTypeFlags)0);
72     }
73     return type;
74 }
76 static void
77 sp_rect_class_init(SPRectClass *klass)
78 {
79     SPObjectClass *sp_object_class = (SPObjectClass *) klass;
80     SPItemClass *item_class = (SPItemClass *) klass;
81     SPShapeClass *shape_class = (SPShapeClass *) klass;
83     parent_class = (SPShapeClass *)g_type_class_ref(SP_TYPE_SHAPE);
85     sp_object_class->build = sp_rect_build;
86     sp_object_class->write = sp_rect_write;
87     sp_object_class->set = sp_rect_set;
88     sp_object_class->update = sp_rect_update;
90     item_class->description = sp_rect_description;
91     item_class->set_transform = sp_rect_set_transform;
92     item_class->convert_to_guides = sp_rect_convert_to_guides;
93     item_class->snappoints = sp_rect_snappoints; //override the default sp_shape_snappoints; see sp_rect_snappoints for details
95     shape_class->set_shape = sp_rect_set_shape;
96 }
98 static void
99 sp_rect_init(SPRect */*rect*/)
101     /* Initializing to zero is automatic */
102     /* sp_svg_length_unset(&rect->x, SP_SVG_UNIT_NONE, 0.0, 0.0); */
103     /* sp_svg_length_unset(&rect->y, SP_SVG_UNIT_NONE, 0.0, 0.0); */
104     /* sp_svg_length_unset(&rect->width, SP_SVG_UNIT_NONE, 0.0, 0.0); */
105     /* sp_svg_length_unset(&rect->height, SP_SVG_UNIT_NONE, 0.0, 0.0); */
106     /* sp_svg_length_unset(&rect->rx, SP_SVG_UNIT_NONE, 0.0, 0.0); */
107     /* sp_svg_length_unset(&rect->ry, SP_SVG_UNIT_NONE, 0.0, 0.0); */
110 static void
111 sp_rect_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr)
113     SPRect *rect = SP_RECT(object);
115     if (((SPObjectClass *) parent_class)->build)
116         ((SPObjectClass *) parent_class)->build(object, document, repr);
118     sp_object_read_attr(object, "x");
119     sp_object_read_attr(object, "y");
120     sp_object_read_attr(object, "width");
121     sp_object_read_attr(object, "height");
122     sp_object_read_attr(object, "rx");
123     sp_object_read_attr(object, "ry");
126 static void
127 sp_rect_set(SPObject *object, unsigned key, gchar const *value)
129     SPRect *rect = SP_RECT(object);
131     /* fixme: We need real error processing some time */
133     switch (key) {
134         case SP_ATTR_X:
135             rect->x.readOrUnset(value);
136             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
137             break;
138         case SP_ATTR_Y:
139             rect->y.readOrUnset(value);
140             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
141             break;
142         case SP_ATTR_WIDTH:
143             if (!rect->width.read(value) || rect->width.value < 0.0) {
144                 rect->width.unset();
145             }
146             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
147             break;
148         case SP_ATTR_HEIGHT:
149             if (!rect->height.read(value) || rect->height.value < 0.0) {
150                 rect->height.unset();
151             }
152             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
153             break;
154         case SP_ATTR_RX:
155             if (!rect->rx.read(value) || rect->rx.value < 0.0) {
156                 rect->rx.unset();
157             }
158             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
159             break;
160         case SP_ATTR_RY:
161             if (!rect->ry.read(value) || rect->ry.value < 0.0) {
162                 rect->ry.unset();
163             }
164             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
165             break;
166         default:
167             if (((SPObjectClass *) parent_class)->set)
168                 ((SPObjectClass *) parent_class)->set(object, key, value);
169             break;
170     }
173 static void
174 sp_rect_update(SPObject *object, SPCtx *ctx, guint flags)
176     if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) {
177         SPRect *rect = (SPRect *) object;
178         SPStyle *style = object->style;
179         SPItemCtx const *ictx = (SPItemCtx const *) ctx;
180         double const d = NR::expansion(ictx->i2vp);
181         double const w = (ictx->vp.x1 - ictx->vp.x0) / d;
182         double const h = (ictx->vp.y1 - ictx->vp.y0) / d;
183         double const em = style->font_size.computed;
184         double const ex = 0.5 * em;  // fixme: get x height from pango or libnrtype.
185         rect->x.update(em, ex, w);
186         rect->y.update(em, ex, h);
187         rect->width.update(em, ex, w);
188         rect->height.update(em, ex, h);
189         rect->rx.update(em, ex, w);
190         rect->ry.update(em, ex, h);
191         sp_shape_set_shape((SPShape *) object);
192         flags &= ~SP_OBJECT_USER_MODIFIED_FLAG_B; // since we change the description, it's not a "just translation" anymore
193     }
195     if (((SPObjectClass *) parent_class)->update)
196         ((SPObjectClass *) parent_class)->update(object, ctx, flags);
199 static Inkscape::XML::Node *
200 sp_rect_write(SPObject *object, Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags)
202     SPRect *rect = SP_RECT(object);
204     if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
205         repr = xml_doc->createElement("svg:rect");
206     }
208     sp_repr_set_svg_double(repr, "width", rect->width.computed);
209     sp_repr_set_svg_double(repr, "height", rect->height.computed);
210     if (rect->rx._set) sp_repr_set_svg_double(repr, "rx", rect->rx.computed);
211     if (rect->ry._set) sp_repr_set_svg_double(repr, "ry", rect->ry.computed);
212     sp_repr_set_svg_double(repr, "x", rect->x.computed);
213     sp_repr_set_svg_double(repr, "y", rect->y.computed);
215     if (((SPObjectClass *) parent_class)->write)
216         ((SPObjectClass *) parent_class)->write(object, xml_doc, repr, flags);
218     return repr;
221 static gchar *
222 sp_rect_description(SPItem *item)
224     g_return_val_if_fail(SP_IS_RECT(item), NULL);
226     return g_strdup(_("<b>Rectangle</b>"));
229 #define C1 0.554
231 static void
232 sp_rect_set_shape(SPShape *shape)
234     SPRect *rect = (SPRect *) shape;
236     if ((rect->height.computed < 1e-18) || (rect->width.computed < 1e-18)) {
237         sp_shape_set_curve_insync(SP_SHAPE(rect), NULL, TRUE);
238         return;
239     }
241     SPCurve *c = new SPCurve();
243     double const x = rect->x.computed;
244     double const y = rect->y.computed;
245     double const w = rect->width.computed;
246     double const h = rect->height.computed;
247     double const w2 = w / 2;
248     double const h2 = h / 2;
249     double const rx = std::min(( rect->rx._set
250                                  ? rect->rx.computed
251                                  : ( rect->ry._set
252                                      ? rect->ry.computed
253                                      : 0.0 ) ),
254                                .5 * rect->width.computed);
255     double const ry = std::min(( rect->ry._set
256                                  ? rect->ry.computed
257                                  : ( rect->rx._set
258                                      ? rect->rx.computed
259                                      : 0.0 ) ),
260                                .5 * rect->height.computed);
261     /* TODO: Handle negative rx or ry as per
262      * http://www.w3.org/TR/SVG11/shapes.html#RectElementRXAttribute once Inkscape has proper error
263      * handling (see http://www.w3.org/TR/SVG11/implnote.html#ErrorProcessing).
264      */
266     /* We don't use proper circular/elliptical arcs, but bezier curves can approximate a 90-degree
267      * arc fairly well.
268      */
269     if ((rx > 1e-18) && (ry > 1e-18)) {
270         c->moveto(x + rx, y);
271         if (rx < w2) c->lineto(x + w - rx, y);
272         c->curveto(x + w - rx * (1 - C1), y,     x + w, y + ry * (1 - C1),       x + w, y + ry);
273         if (ry < h2) c->lineto(x + w, y + h - ry);
274         c->curveto(x + w, y + h - ry * (1 - C1),     x + w - rx * (1 - C1), y + h,       x + w - rx, y + h);
275         if (rx < w2) c->lineto(x + rx, y + h);
276         c->curveto(x + rx * (1 - C1), y + h,     x, y + h - ry * (1 - C1),       x, y + h - ry);
277         if (ry < h2) c->lineto(x, y + ry);
278         c->curveto(x, y + ry * (1 - C1),     x + rx * (1 - C1), y,       x + rx, y);
279     } else {
280         c->moveto(x + 0.0, y + 0.0);
281         c->lineto(x + w, y + 0.0);
282         c->lineto(x + w, y + h);
283         c->lineto(x + 0.0, y + h);
284         c->lineto(x + 0.0, y + 0.0);
285     }
287     c->closepath_current();
288     sp_shape_set_curve_insync(SP_SHAPE(rect), c, TRUE);
289     c->unref();
292 /* fixme: Think (Lauris) */
294 void
295 sp_rect_position_set(SPRect *rect, gdouble x, gdouble y, gdouble width, gdouble height)
297     g_return_if_fail(rect != NULL);
298     g_return_if_fail(SP_IS_RECT(rect));
300     rect->x.computed = x;
301     rect->y.computed = y;
302     rect->width.computed = width;
303     rect->height.computed = height;
305     SP_OBJECT(rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
308 void
309 sp_rect_set_rx(SPRect *rect, gboolean set, gdouble value)
311     g_return_if_fail(rect != NULL);
312     g_return_if_fail(SP_IS_RECT(rect));
314     rect->rx._set = set;
315     if (set) rect->rx.computed = value;
317     SP_OBJECT(rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
320 void
321 sp_rect_set_ry(SPRect *rect, gboolean set, gdouble value)
323     g_return_if_fail(rect != NULL);
324     g_return_if_fail(SP_IS_RECT(rect));
326     rect->ry._set = set;
327     if (set) rect->ry.computed = value;
329     SP_OBJECT(rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
332 /*
333  * Initially we'll do:
334  * Transform x, y, set x, y, clear translation
335  */
337 /* fixme: Use preferred units somehow (Lauris) */
338 /* fixme: Alternately preserve whatever units there are (lauris) */
340 static NR::Matrix
341 sp_rect_set_transform(SPItem *item, NR::Matrix const &xform)
343     SPRect *rect = SP_RECT(item);
345     /* Calculate rect start in parent coords. */
346     NR::Point pos( NR::Point(rect->x.computed, rect->y.computed) * xform );
348     /* This function takes care of translation and scaling, we return whatever parts we can't
349        handle. */
350     NR::Matrix ret(NR::transform(xform));
351     gdouble const sw = hypot(ret[0], ret[1]);
352     gdouble const sh = hypot(ret[2], ret[3]);
353     if (sw > 1e-9) {
354         ret[0] /= sw;
355         ret[1] /= sw;
356     } else {
357         ret[0] = 1.0;
358         ret[1] = 0.0;
359     }
360     if (sh > 1e-9) {
361         ret[2] /= sh;
362         ret[3] /= sh;
363     } else {
364         ret[2] = 0.0;
365         ret[3] = 1.0;
366     }
368     /* fixme: Would be nice to preserve units here */
369     rect->width = rect->width.computed * sw;
370     rect->height = rect->height.computed * sh;
371     if (rect->rx._set) {
372         rect->rx = rect->rx.computed * sw;
373     }
374     if (rect->ry._set) {
375         rect->ry = rect->ry.computed * sh;
376     }
378     /* Find start in item coords */
379     pos = pos * ret.inverse();
380     rect->x = pos[NR::X];
381     rect->y = pos[NR::Y];
383     sp_rect_set_shape(rect);
385     // Adjust stroke width
386     sp_item_adjust_stroke(item, sqrt(fabs(sw * sh)));
388     // Adjust pattern fill
389     sp_item_adjust_pattern(item, xform / ret);
391     // Adjust gradient fill
392     sp_item_adjust_gradient(item, xform / ret);
394     item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
396     return ret;
400 /**
401 Returns the ratio in which the vector from p0 to p1 is stretched by transform
402  */
403 static gdouble
404 vector_stretch(NR::Point p0, NR::Point p1, NR::Matrix xform)
406     if (p0 == p1)
407         return 0;
408     return (NR::distance(p0 * xform, p1 * xform) / NR::distance(p0, p1));
411 void
412 sp_rect_set_visible_rx(SPRect *rect, gdouble rx)
414     if (rx == 0) {
415         rect->rx.computed = 0;
416         rect->rx._set = false;
417     } else {
418         rect->rx.computed = rx / vector_stretch(
419             NR::Point(rect->x.computed + 1, rect->y.computed),
420             NR::Point(rect->x.computed, rect->y.computed),
421             SP_ITEM(rect)->transform);
422         rect->rx._set = true;
423     }
424     SP_OBJECT(rect)->updateRepr();
427 void
428 sp_rect_set_visible_ry(SPRect *rect, gdouble ry)
430     if (ry == 0) {
431         rect->ry.computed = 0;
432         rect->ry._set = false;
433     } else {
434         rect->ry.computed = ry / vector_stretch(
435             NR::Point(rect->x.computed, rect->y.computed + 1),
436             NR::Point(rect->x.computed, rect->y.computed),
437             SP_ITEM(rect)->transform);
438         rect->ry._set = true;
439     }
440     SP_OBJECT(rect)->updateRepr();
443 gdouble
444 sp_rect_get_visible_rx(SPRect *rect)
446     if (!rect->rx._set)
447         return 0;
448     return rect->rx.computed * vector_stretch(
449         NR::Point(rect->x.computed + 1, rect->y.computed),
450         NR::Point(rect->x.computed, rect->y.computed),
451         SP_ITEM(rect)->transform);
454 gdouble
455 sp_rect_get_visible_ry(SPRect *rect)
457     if (!rect->ry._set)
458         return 0;
459     return rect->ry.computed * vector_stretch(
460         NR::Point(rect->x.computed, rect->y.computed + 1),
461         NR::Point(rect->x.computed, rect->y.computed),
462         SP_ITEM(rect)->transform);
465 void
466 sp_rect_compensate_rxry(SPRect *rect, NR::Matrix xform)
468     if (rect->rx.computed == 0 && rect->ry.computed == 0)
469         return; // nothing to compensate
471     // test unit vectors to find out compensation:
472     NR::Point c(rect->x.computed, rect->y.computed);
473     NR::Point cx = c + NR::Point(1, 0);
474     NR::Point cy = c + NR::Point(0, 1);
476     // apply previous transform if any
477     c *= SP_ITEM(rect)->transform;
478     cx *= SP_ITEM(rect)->transform;
479     cy *= SP_ITEM(rect)->transform;
481     // find out stretches that we need to compensate
482     gdouble eX = vector_stretch(cx, c, xform);
483     gdouble eY = vector_stretch(cy, c, xform);
485     // If only one of the radii is set, set both radii so they have the same visible length
486     // This is needed because if we just set them the same length in SVG, they might end up unequal because of transform
487     if ((rect->rx._set && !rect->ry._set) || (rect->ry._set && !rect->rx._set)) {
488         gdouble r = MAX(rect->rx.computed, rect->ry.computed);
489         rect->rx.computed = r / eX;
490         rect->ry.computed = r / eY;
491     } else {
492         rect->rx.computed = rect->rx.computed / eX;
493         rect->ry.computed = rect->ry.computed / eY;
494     }
496     // Note that a radius may end up larger than half-side if the rect is scaled down;
497     // that's ok because this preserves the intended radii in case the rect is enlarged again,
498     // and set_shape will take care of trimming too large radii when generating d=
500     rect->rx._set = rect->ry._set = true;
503 void
504 sp_rect_set_visible_width(SPRect *rect, gdouble width)
506     rect->width.computed = width / vector_stretch(
507         NR::Point(rect->x.computed + 1, rect->y.computed),
508         NR::Point(rect->x.computed, rect->y.computed),
509         SP_ITEM(rect)->transform);
510     rect->width._set = true;
511     SP_OBJECT(rect)->updateRepr();
514 void
515 sp_rect_set_visible_height(SPRect *rect, gdouble height)
517     rect->height.computed = height / vector_stretch(
518         NR::Point(rect->x.computed, rect->y.computed + 1),
519         NR::Point(rect->x.computed, rect->y.computed),
520         SP_ITEM(rect)->transform);
521     rect->height._set = true;
522     SP_OBJECT(rect)->updateRepr();
525 gdouble
526 sp_rect_get_visible_width(SPRect *rect)
528     if (!rect->width._set)
529         return 0;
530     return rect->width.computed * vector_stretch(
531         NR::Point(rect->x.computed + 1, rect->y.computed),
532         NR::Point(rect->x.computed, rect->y.computed),
533         SP_ITEM(rect)->transform);
536 gdouble
537 sp_rect_get_visible_height(SPRect *rect)
539     if (!rect->height._set)
540         return 0;
541     return rect->height.computed * vector_stretch(
542         NR::Point(rect->x.computed, rect->y.computed + 1),
543         NR::Point(rect->x.computed, rect->y.computed),
544         SP_ITEM(rect)->transform);
547 /**
548  * Sets the snappoint p to the unrounded corners of the rectangle
549  */
550 static void sp_rect_snappoints(SPItem const *item, SnapPointsIter p)
552     /* This method overrides sp_shape_snappoints, which is the default for any shape. The default method
553     returns all eight points along the path of a rounded rectangle, but not the real corners. Snapping
554     the startpoint and endpoint of each rounded corner is not very usefull and really confusing. Instead 
555     we could snap either the real corners, or not snap at all. Bulia Byak opted to snap the real corners,
556     but it should be noted that this might be confusing in some cases with relatively large radii. With 
557     small radii though the user will easily understand which point is snapping. */
558     
559     g_assert(item != NULL);
560     g_assert(SP_IS_RECT(item));
562     SPRect *rect = SP_RECT(item);
564     NR::Matrix const i2d (from_2geom(sp_item_i2d_affine (item)));
566     *p = NR::Point(rect->x.computed, rect->y.computed) * i2d;
567     *p = NR::Point(rect->x.computed, rect->y.computed + rect->height.computed) * i2d;
568     *p = NR::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed) * i2d;
569     *p = NR::Point(rect->x.computed + rect->width.computed, rect->y.computed) * i2d;
572 void
573 sp_rect_convert_to_guides(SPItem *item) {
574     SPRect *rect = SP_RECT(item);
576     if (prefs_get_int_attribute("tools.shapes.rect", "convertguides", 1) == 0) {
577         sp_item_convert_to_guides(SP_ITEM(rect));
578         return;
579     }
581     SPDocument *doc = SP_OBJECT_DOCUMENT(rect);
582     std::list<std::pair<Geom::Point, Geom::Point> > pts;
584     NR::Matrix const i2d (from_2geom(sp_item_i2d_affine(SP_ITEM(rect))));
586     NR::Point A1(NR::Point(rect->x.computed, rect->y.computed) * i2d);
587     NR::Point A2(NR::Point(rect->x.computed, rect->y.computed + rect->height.computed) * i2d);
588     NR::Point A3(NR::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed) * i2d);
589     NR::Point A4(NR::Point(rect->x.computed + rect->width.computed, rect->y.computed) * i2d);
591     pts.push_back(std::make_pair(A1.to_2geom(), A2.to_2geom()));
592     pts.push_back(std::make_pair(A2.to_2geom(), A3.to_2geom()));
593     pts.push_back(std::make_pair(A3.to_2geom(), A4.to_2geom()));
594     pts.push_back(std::make_pair(A4.to_2geom(), A1.to_2geom()));
596     sp_guide_pt_pairs_to_guides(doc, pts);
599 /*
600   Local Variables:
601   mode:c++
602   c-file-style:"stroustrup"
603   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
604   indent-tabs-mode:nil
605   fill-column:99
606   End:
607 */
608 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :