Code

Snapping: improve calculation of metrics for scaling, modify some comments, and remov...
[inkscape.git] / src / sp-rect.cpp
index e286648889273a24710c3390dc08c62b3c334a48..94be7551b3a00a5db40c24ada82fdb4ff99be567 100644 (file)
@@ -1,5 +1,3 @@
-#define __SP_RECT_C__
-
 /*
  * SVG <rect> implementation
  *
 
 
 #include <display/curve.h>
-#include <libnr/nr-matrix-div.h>
+#include <libnr/nr-matrix-ops.h>
 #include <libnr/nr-matrix-fns.h>
+#include <2geom/rect.h>
 
+#include "inkscape.h"
+#include "document.h"
 #include "attributes.h"
 #include "style.h"
 #include "sp-rect.h"
 #include <glibmm/i18n.h>
 #include "xml/repr.h"
+#include "sp-guide.h"
+#include "preferences.h"
 
 #define noRECT_VERBOSE
 
@@ -36,12 +39,14 @@ static void sp_rect_init(SPRect *rect);
 static void sp_rect_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr);
 static void sp_rect_set(SPObject *object, unsigned key, gchar const *value);
 static void sp_rect_update(SPObject *object, SPCtx *ctx, guint flags);
-static Inkscape::XML::Node *sp_rect_write(SPObject *object, Inkscape::XML::Node *repr, guint flags);
+static Inkscape::XML::Node *sp_rect_write(SPObject *object, Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, guint flags);
 
 static gchar *sp_rect_description(SPItem *item);
-static NR::Matrix sp_rect_set_transform(SPItem *item, NR::Matrix const &xform);
+static Geom::Matrix sp_rect_set_transform(SPItem *item, Geom::Matrix const &xform);
+static void sp_rect_convert_to_guides(SPItem *item);
 
 static void sp_rect_set_shape(SPShape *shape);
+static void sp_rect_snappoints(SPItem const *item, std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const *snapprefs);
 
 static SPShapeClass *parent_class;
 
@@ -84,12 +89,14 @@ sp_rect_class_init(SPRectClass *klass)
 
     item_class->description = sp_rect_description;
     item_class->set_transform = sp_rect_set_transform;
+    item_class->convert_to_guides = sp_rect_convert_to_guides;
+    item_class->snappoints = sp_rect_snappoints; //override the default sp_shape_snappoints; see sp_rect_snappoints for details
 
     shape_class->set_shape = sp_rect_set_shape;
 }
 
 static void
-sp_rect_init(SPRect *rect)
+sp_rect_init(SPRect */*rect*/)
 {
     /* Initializing to zero is automatic */
     /* sp_svg_length_unset(&rect->x, SP_SVG_UNIT_NONE, 0.0, 0.0); */
@@ -103,32 +110,15 @@ sp_rect_init(SPRect *rect)
 static void
 sp_rect_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr)
 {
-    SPRect *rect = SP_RECT(object);
-
     if (((SPObjectClass *) parent_class)->build)
         ((SPObjectClass *) parent_class)->build(object, document, repr);
 
-    sp_object_read_attr(object, "x");
-    sp_object_read_attr(object, "y");
-    sp_object_read_attr(object, "width");
-    sp_object_read_attr(object, "height");
-    sp_object_read_attr(object, "rx");
-    sp_object_read_attr(object, "ry");
-
-    Inkscape::Version const version = sp_object_get_sodipodi_version(object);
-
-    if ( version.major == 0 && version.minor == 29 ) {
-        if (rect->rx._set && rect->ry._set) {
-            /* 0.29 treated 0.0 radius as missing value */
-            if ((rect->rx.value != 0.0) && (rect->ry.value == 0.0)) {
-                repr->setAttribute("ry", NULL);
-                sp_object_read_attr(object, "ry");
-            } else if ((rect->ry.value != 0.0) && (rect->rx.value == 0.0)) {
-                repr->setAttribute("rx", NULL);
-                sp_object_read_attr(object, "rx");
-            }
-        }
-    }
+    object->readAttr( "x" );
+    object->readAttr( "y" );
+    object->readAttr( "width" );
+    object->readAttr( "height" );
+    object->readAttr( "rx" );
+    object->readAttr( "ry" );
 }
 
 static void
@@ -185,9 +175,8 @@ sp_rect_update(SPObject *object, SPCtx *ctx, guint flags)
         SPRect *rect = (SPRect *) object;
         SPStyle *style = object->style;
         SPItemCtx const *ictx = (SPItemCtx const *) ctx;
-        double const d = NR::expansion(ictx->i2vp);
-        double const w = (ictx->vp.x1 - ictx->vp.x0) / d;
-        double const h = (ictx->vp.y1 - ictx->vp.y0) / d;
+        double const w = (ictx->vp.x1 - ictx->vp.x0);
+        double const h = (ictx->vp.y1 - ictx->vp.y0);
         double const em = style->font_size.computed;
         double const ex = 0.5 * em;  // fixme: get x height from pango or libnrtype.
         rect->x.update(em, ex, w);
@@ -196,7 +185,7 @@ sp_rect_update(SPObject *object, SPCtx *ctx, guint flags)
         rect->height.update(em, ex, h);
         rect->rx.update(em, ex, w);
         rect->ry.update(em, ex, h);
-        sp_shape_set_shape((SPShape *) object);
+        ((SPShape *) object)->setShape();
         flags &= ~SP_OBJECT_USER_MODIFIED_FLAG_B; // since we change the description, it's not a "just translation" anymore
     }
 
@@ -205,12 +194,12 @@ sp_rect_update(SPObject *object, SPCtx *ctx, guint flags)
 }
 
 static Inkscape::XML::Node *
-sp_rect_write(SPObject *object, Inkscape::XML::Node *repr, guint flags)
+sp_rect_write(SPObject *object, Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags)
 {
     SPRect *rect = SP_RECT(object);
 
     if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
-        repr = sp_repr_new("svg:rect");
+        repr = xml_doc->createElement("svg:rect");
     }
 
     sp_repr_set_svg_double(repr, "width", rect->width.computed);
@@ -221,7 +210,7 @@ sp_rect_write(SPObject *object, Inkscape::XML::Node *repr, guint flags)
     sp_repr_set_svg_double(repr, "y", rect->y.computed);
 
     if (((SPObjectClass *) parent_class)->write)
-        ((SPObjectClass *) parent_class)->write(object, repr, flags);
+        ((SPObjectClass *) parent_class)->write(object, xml_doc, repr, flags);
 
     return repr;
 }
@@ -241,9 +230,12 @@ sp_rect_set_shape(SPShape *shape)
 {
     SPRect *rect = (SPRect *) shape;
 
-    if ((rect->height.computed < 1e-18) || (rect->width.computed < 1e-18)) return;
+    if ((rect->height.computed < 1e-18) || (rect->width.computed < 1e-18)) {
+        SP_SHAPE(rect)->setCurveInsync( NULL, TRUE);
+        return;
+    }
 
-    SPCurve *c = sp_curve_new();
+    SPCurve *c = new SPCurve();
 
     double const x = rect->x.computed;
     double const y = rect->y.computed;
@@ -272,26 +264,25 @@ sp_rect_set_shape(SPShape *shape)
      * arc fairly well.
      */
     if ((rx > 1e-18) && (ry > 1e-18)) {
-        sp_curve_moveto(c, x + rx, y);
-        if (rx < w2) sp_curve_lineto(c, x + w - rx, y);
-        sp_curve_curveto(c, x + w - rx * (1 - C1), y,     x + w, y + ry * (1 - C1),       x + w, y + ry);
-        if (ry < h2) sp_curve_lineto(c, x + w, y + h - ry);
-        sp_curve_curveto(c, x + w, y + h - ry * (1 - C1),     x + w - rx * (1 - C1), y + h,       x + w - rx, y + h);
-        if (rx < w2) sp_curve_lineto(c, x + rx, y + h);
-        sp_curve_curveto(c, x + rx * (1 - C1), y + h,     x, y + h - ry * (1 - C1),       x, y + h - ry);
-        if (ry < h2) sp_curve_lineto(c, x, y + ry);
-        sp_curve_curveto(c, x, y + ry * (1 - C1),     x + rx * (1 - C1), y,       x + rx, y);
+        c->moveto(x + rx, y);
+        if (rx < w2) c->lineto(x + w - rx, y);
+        c->curveto(x + w - rx * (1 - C1), y,     x + w, y + ry * (1 - C1),       x + w, y + ry);
+        if (ry < h2) c->lineto(x + w, y + h - ry);
+        c->curveto(x + w, y + h - ry * (1 - C1),     x + w - rx * (1 - C1), y + h,       x + w - rx, y + h);
+        if (rx < w2) c->lineto(x + rx, y + h);
+        c->curveto(x + rx * (1 - C1), y + h,     x, y + h - ry * (1 - C1),       x, y + h - ry);
+        if (ry < h2) c->lineto(x, y + ry);
+        c->curveto(x, y + ry * (1 - C1),     x + rx * (1 - C1), y,       x + rx, y);
     } else {
-        sp_curve_moveto(c, x + 0.0, y + 0.0);
-        sp_curve_lineto(c, x + w, y + 0.0);
-        sp_curve_lineto(c, x + w, y + h);
-        sp_curve_lineto(c, x + 0.0, y + h);
-        sp_curve_lineto(c, x + 0.0, y + 0.0);
+        c->moveto(x + 0.0, y + 0.0);
+        c->lineto(x + w, y + 0.0);
+        c->lineto(x + w, y + h);
+        c->lineto(x + 0.0, y + h);
     }
 
-    sp_curve_closepath_current(c);
-    sp_shape_set_curve_insync(SP_SHAPE(rect), c, TRUE);
-    sp_curve_unref(c);
+    c->closepath();
+    SP_SHAPE(rect)->setCurveInsync( c, TRUE);
+    c->unref();
 }
 
 /* fixme: Think (Lauris) */
@@ -311,7 +302,7 @@ sp_rect_position_set(SPRect *rect, gdouble x, gdouble y, gdouble width, gdouble
 }
 
 void
-sp_rect_set_rx(SPRect *rect, bool set, gdouble value)
+sp_rect_set_rx(SPRect *rect, gboolean set, gdouble value)
 {
     g_return_if_fail(rect != NULL);
     g_return_if_fail(SP_IS_RECT(rect));
@@ -323,7 +314,7 @@ sp_rect_set_rx(SPRect *rect, bool set, gdouble value)
 }
 
 void
-sp_rect_set_ry(SPRect *rect, bool set, gdouble value)
+sp_rect_set_ry(SPRect *rect, gboolean set, gdouble value)
 {
     g_return_if_fail(rect != NULL);
     g_return_if_fail(SP_IS_RECT(rect));
@@ -342,17 +333,17 @@ sp_rect_set_ry(SPRect *rect, bool set, gdouble value)
 /* fixme: Use preferred units somehow (Lauris) */
 /* fixme: Alternately preserve whatever units there are (lauris) */
 
-static NR::Matrix
-sp_rect_set_transform(SPItem *item, NR::Matrix const &xform)
+static Geom::Matrix
+sp_rect_set_transform(SPItem *item, Geom::Matrix const &xform)
 {
     SPRect *rect = SP_RECT(item);
 
     /* Calculate rect start in parent coords. */
-    NR::Point pos( NR::Point(rect->x.computed, rect->y.computed) * xform );
+    Geom::Point pos( Geom::Point(rect->x.computed, rect->y.computed) * xform );
 
     /* This function takes care of translation and scaling, we return whatever parts we can't
        handle. */
-    NR::Matrix ret(NR::transform(xform));
+    Geom::Matrix ret(Geom::Matrix(xform).without_translation());
     gdouble const sw = hypot(ret[0], ret[1]);
     gdouble const sh = hypot(ret[2], ret[3]);
     if (sw > 1e-9) {
@@ -382,19 +373,19 @@ sp_rect_set_transform(SPItem *item, NR::Matrix const &xform)
 
     /* Find start in item coords */
     pos = pos * ret.inverse();
-    rect->x = pos[NR::X];
-    rect->y = pos[NR::Y];
+    rect->x = pos[Geom::X];
+    rect->y = pos[Geom::Y];
 
     sp_rect_set_shape(rect);
 
     // Adjust stroke width
-    sp_item_adjust_stroke(item, sqrt(fabs(sw * sh)));
+    item->adjust_stroke(sqrt(fabs(sw * sh)));
 
     // Adjust pattern fill
-    sp_item_adjust_pattern(item, xform / ret);
+    item->adjust_pattern(xform * ret.inverse());
 
     // Adjust gradient fill
-    sp_item_adjust_gradient(item, xform / ret);
+    item->adjust_gradient(xform * ret.inverse());
 
     item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
 
@@ -406,11 +397,11 @@ sp_rect_set_transform(SPItem *item, NR::Matrix const &xform)
 Returns the ratio in which the vector from p0 to p1 is stretched by transform
  */
 static gdouble
-vector_stretch(NR::Point p0, NR::Point p1, NR::Matrix xform)
+vector_stretch(Geom::Point p0, Geom::Point p1, Geom::Matrix xform)
 {
     if (p0 == p1)
         return 0;
-    return (NR::distance(p0 * xform, p1 * xform) / NR::distance(p0, p1));
+    return (Geom::distance(p0 * xform, p1 * xform) / Geom::distance(p0, p1));
 }
 
 void
@@ -421,8 +412,8 @@ sp_rect_set_visible_rx(SPRect *rect, gdouble rx)
         rect->rx._set = false;
     } else {
         rect->rx.computed = rx / vector_stretch(
-            NR::Point(rect->x.computed + 1, rect->y.computed),
-            NR::Point(rect->x.computed, rect->y.computed),
+            Geom::Point(rect->x.computed + 1, rect->y.computed),
+            Geom::Point(rect->x.computed, rect->y.computed),
             SP_ITEM(rect)->transform);
         rect->rx._set = true;
     }
@@ -437,8 +428,8 @@ sp_rect_set_visible_ry(SPRect *rect, gdouble ry)
         rect->ry._set = false;
     } else {
         rect->ry.computed = ry / vector_stretch(
-            NR::Point(rect->x.computed, rect->y.computed + 1),
-            NR::Point(rect->x.computed, rect->y.computed),
+            Geom::Point(rect->x.computed, rect->y.computed + 1),
+            Geom::Point(rect->x.computed, rect->y.computed),
             SP_ITEM(rect)->transform);
         rect->ry._set = true;
     }
@@ -451,8 +442,8 @@ sp_rect_get_visible_rx(SPRect *rect)
     if (!rect->rx._set)
         return 0;
     return rect->rx.computed * vector_stretch(
-        NR::Point(rect->x.computed + 1, rect->y.computed),
-        NR::Point(rect->x.computed, rect->y.computed),
+        Geom::Point(rect->x.computed + 1, rect->y.computed),
+        Geom::Point(rect->x.computed, rect->y.computed),
         SP_ITEM(rect)->transform);
 }
 
@@ -462,21 +453,29 @@ sp_rect_get_visible_ry(SPRect *rect)
     if (!rect->ry._set)
         return 0;
     return rect->ry.computed * vector_stretch(
-        NR::Point(rect->x.computed, rect->y.computed + 1),
-        NR::Point(rect->x.computed, rect->y.computed),
+        Geom::Point(rect->x.computed, rect->y.computed + 1),
+        Geom::Point(rect->x.computed, rect->y.computed),
         SP_ITEM(rect)->transform);
 }
 
+Geom::Rect
+sp_rect_get_rect (SPRect *rect)
+{
+    Geom::Point p0 = Geom::Point(rect->x.computed, rect->y.computed);
+    Geom::Point p2 = Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed);
+    return Geom::Rect(p0, p2);
+}
+
 void
-sp_rect_compensate_rxry(SPRect *rect, NR::Matrix xform)
+sp_rect_compensate_rxry(SPRect *rect, Geom::Matrix xform)
 {
     if (rect->rx.computed == 0 && rect->ry.computed == 0)
         return; // nothing to compensate
 
     // test unit vectors to find out compensation:
-    NR::Point c(rect->x.computed, rect->y.computed);
-    NR::Point cx = c + NR::Point(1, 0);
-    NR::Point cy = c + NR::Point(0, 1);
+    Geom::Point c(rect->x.computed, rect->y.computed);
+    Geom::Point cx = c + Geom::Point(1, 0);
+    Geom::Point cy = c + Geom::Point(0, 1);
 
     // apply previous transform if any
     c *= SP_ITEM(rect)->transform;
@@ -509,8 +508,8 @@ void
 sp_rect_set_visible_width(SPRect *rect, gdouble width)
 {
     rect->width.computed = width / vector_stretch(
-        NR::Point(rect->x.computed + 1, rect->y.computed),
-        NR::Point(rect->x.computed, rect->y.computed),
+        Geom::Point(rect->x.computed + 1, rect->y.computed),
+        Geom::Point(rect->x.computed, rect->y.computed),
         SP_ITEM(rect)->transform);
     rect->width._set = true;
     SP_OBJECT(rect)->updateRepr();
@@ -520,8 +519,8 @@ void
 sp_rect_set_visible_height(SPRect *rect, gdouble height)
 {
     rect->height.computed = height / vector_stretch(
-        NR::Point(rect->x.computed, rect->y.computed + 1),
-        NR::Point(rect->x.computed, rect->y.computed),
+        Geom::Point(rect->x.computed, rect->y.computed + 1),
+        Geom::Point(rect->x.computed, rect->y.computed),
         SP_ITEM(rect)->transform);
     rect->height._set = true;
     SP_OBJECT(rect)->updateRepr();
@@ -533,8 +532,8 @@ sp_rect_get_visible_width(SPRect *rect)
     if (!rect->width._set)
         return 0;
     return rect->width.computed * vector_stretch(
-        NR::Point(rect->x.computed + 1, rect->y.computed),
-        NR::Point(rect->x.computed, rect->y.computed),
+        Geom::Point(rect->x.computed + 1, rect->y.computed),
+        Geom::Point(rect->x.computed, rect->y.computed),
         SP_ITEM(rect)->transform);
 }
 
@@ -544,11 +543,87 @@ sp_rect_get_visible_height(SPRect *rect)
     if (!rect->height._set)
         return 0;
     return rect->height.computed * vector_stretch(
-        NR::Point(rect->x.computed, rect->y.computed + 1),
-        NR::Point(rect->x.computed, rect->y.computed),
+        Geom::Point(rect->x.computed, rect->y.computed + 1),
+        Geom::Point(rect->x.computed, rect->y.computed),
         SP_ITEM(rect)->transform);
 }
 
+/**
+ * Sets the snappoint p to the unrounded corners of the rectangle
+ */
+static void sp_rect_snappoints(SPItem const *item, std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const *snapprefs)
+{
+    /* This method overrides sp_shape_snappoints, which is the default for any shape. The default method
+    returns all eight points along the path of a rounded rectangle, but not the real corners. Snapping
+    the startpoint and endpoint of each rounded corner is not very useful and really confusing. Instead
+    we could snap either the real corners, or not snap at all. Bulia Byak opted to snap the real corners,
+    but it should be noted that this might be confusing in some cases with relatively large radii. With
+    small radii though the user will easily understand which point is snapping. */
+
+    g_assert(item != NULL);
+    g_assert(SP_IS_RECT(item));
+
+    // Help enforcing strict snapping, i.e. only return nodes when we're snapping nodes to nodes or a guide to nodes
+    if (!(snapprefs->getSnapModeNode() || snapprefs->getSnapModeGuide())) {
+        return;
+    }
+
+    SPRect *rect = SP_RECT(item);
+
+    Geom::Matrix const i2d (item->i2d_affine ());
+
+    Geom::Point p0 = Geom::Point(rect->x.computed, rect->y.computed) * i2d;
+    Geom::Point p1 = Geom::Point(rect->x.computed, rect->y.computed + rect->height.computed) * i2d;
+    Geom::Point p2 = Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed) * i2d;
+    Geom::Point p3 = Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed) * i2d;
+
+    if (snapprefs->getSnapToItemNode()) {
+        p.push_back(Inkscape::SnapCandidatePoint(p0, Inkscape::SNAPSOURCE_CORNER, Inkscape::SNAPTARGET_CORNER));
+        p.push_back(Inkscape::SnapCandidatePoint(p1, Inkscape::SNAPSOURCE_CORNER, Inkscape::SNAPTARGET_CORNER));
+        p.push_back(Inkscape::SnapCandidatePoint(p2, Inkscape::SNAPSOURCE_CORNER, Inkscape::SNAPTARGET_CORNER));
+        p.push_back(Inkscape::SnapCandidatePoint(p3, Inkscape::SNAPSOURCE_CORNER, Inkscape::SNAPTARGET_CORNER));
+    }
+
+    if (snapprefs->getSnapLineMidpoints()) { // only do this when we're snapping nodes (enforce strict snapping)
+        p.push_back(Inkscape::SnapCandidatePoint((p0 + p1)/2, Inkscape::SNAPSOURCE_LINE_MIDPOINT, Inkscape::SNAPTARGET_LINE_MIDPOINT));
+        p.push_back(Inkscape::SnapCandidatePoint((p1 + p2)/2, Inkscape::SNAPSOURCE_LINE_MIDPOINT, Inkscape::SNAPTARGET_LINE_MIDPOINT));
+        p.push_back(Inkscape::SnapCandidatePoint((p2 + p3)/2, Inkscape::SNAPSOURCE_LINE_MIDPOINT, Inkscape::SNAPTARGET_LINE_MIDPOINT));
+        p.push_back(Inkscape::SnapCandidatePoint((p3 + p0)/2, Inkscape::SNAPSOURCE_LINE_MIDPOINT, Inkscape::SNAPTARGET_LINE_MIDPOINT));
+    }
+
+    if (snapprefs->getSnapObjectMidpoints()) { // only do this when we're snapping nodes (enforce strict snapping)
+        p.push_back(Inkscape::SnapCandidatePoint((p0 + p2)/2, Inkscape::SNAPSOURCE_OBJECT_MIDPOINT, Inkscape::SNAPTARGET_OBJECT_MIDPOINT));
+    }
+
+}
+
+void
+sp_rect_convert_to_guides(SPItem *item) {
+    SPRect *rect = SP_RECT(item);
+
+    Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+    if (!prefs->getBool("/tools/shapes/rect/convertguides", true)) {
+        SP_ITEM(rect)->convert_to_guides();
+        return;
+    }
+
+    std::list<std::pair<Geom::Point, Geom::Point> > pts;
+
+    Geom::Matrix const i2d (SP_ITEM(rect)->i2d_affine());
+
+    Geom::Point A1(Geom::Point(rect->x.computed, rect->y.computed) * i2d);
+    Geom::Point A2(Geom::Point(rect->x.computed, rect->y.computed + rect->height.computed) * i2d);
+    Geom::Point A3(Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed) * i2d);
+    Geom::Point A4(Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed) * i2d);
+
+    pts.push_back(std::make_pair(A1, A2));
+    pts.push_back(std::make_pair(A2, A3));
+    pts.push_back(std::make_pair(A3, A4));
+    pts.push_back(std::make_pair(A4, A1));
+
+    sp_guide_pt_pairs_to_guides(inkscape_active_desktop(), pts);
+}
+
 /*
   Local Variables:
   mode:c++