Code

Fix scaling of degenerate handles using keybard shortcuts.
authorKrzysztof Kosiński <tweenk.pl@gmail.com>
Thu, 18 Mar 2010 02:18:56 +0000 (03:18 +0100)
committerKrzysztof Kosiński <tweenk.pl@gmail.com>
Thu, 18 Mar 2010 02:18:56 +0000 (03:18 +0100)
src/ui/tool/node.cpp
src/ui/tool/node.h
src/ui/tool/path-manipulator.cpp

index c82b0c7d6a7466d6ca390a50ac433c5d6c645f2d..e9fa79fb3bb4d3c1b8ac603675245a51e6cd6984 100644 (file)
@@ -106,22 +106,11 @@ void Handle::setVisible(bool v)
 
 void Handle::move(Geom::Point const &new_pos)
 {
-    Handle *other, *towards, *towards_second;
-    Node *node_towards; // node in direction of this handle
-    Node *node_away; // node in the opposite direction
-    if (this == &_parent->_front) {
-        other = &_parent->_back;
-        node_towards = _parent->_next();
-        node_away = _parent->_prev();
-        towards = node_towards ? &node_towards->_back : 0;
-        towards_second = node_towards ? &node_towards->_front : 0;
-    } else {
-        other = &_parent->_front;
-        node_towards = _parent->_prev();
-        node_away = _parent->_next();
-        towards = node_towards ? &node_towards->_front : 0;
-        towards_second = node_towards ? &node_towards->_back : 0;
-    }
+    Handle *other = this->other();
+    Node *node_towards = _parent->nodeToward(this); // node in direction of this handle
+    Node *node_away = _parent->nodeAwayFrom(this); // node in the opposite direction
+    Handle *towards = node_towards ? node_towards->handleAwayFrom(_parent) : NULL;
+    Handle *towards_second = node_towards ? node_towards->handleToward(_parent) : NULL;
 
     if (Geom::are_near(new_pos, _parent->position())) {
         // The handle becomes degenerate. If the segment between it and the node
@@ -225,7 +214,7 @@ char const *Handle::handle_type_to_localized_string(NodeType type)
 
 bool Handle::grabbed(GdkEventMotion *)
 {
-    _saved_other_pos = other().position();
+    _saved_other_pos = other()->position();
     _saved_length = _drag_out ? 0 : length();
     _pm()._handleGrabbed();
     return false;
@@ -294,10 +283,10 @@ void Handle::dragged(Geom::Point &new_pos, GdkEventMotion *event)
         if (held_shift(*event)) {
             Geom::Point other_relpos = _saved_other_pos - parent_pos;
             other_relpos *= Geom::Rotate(Geom::angle_between(origin - parent_pos, new_pos - parent_pos));
-            other().setRelativePos(other_relpos);
+            other()->setRelativePos(other_relpos);
         } else {
             // restore the position
-            other().setPosition(_saved_other_pos);
+            other()->setPosition(_saved_other_pos);
         }
     }
     move(new_pos); // needed for correct update, even though it's redundant
@@ -332,10 +321,10 @@ bool Handle::clicked(GdkEventButton *event)
     return true;
 }
 
-Handle &Handle::other()
+Handle *Handle::other()
 {
-    if (this == &_parent->_front) return _parent->_back;
-    return _parent->_front;
+    if (this == &_parent->_front) return &_parent->_back;
+    return &_parent->_front;
 }
 
 static double snap_increment_degrees() {
@@ -347,7 +336,7 @@ static double snap_increment_degrees() {
 Glib::ustring Handle::_getTip(unsigned state)
 {
     char const *more;
-    bool can_shift_rotate = _parent->type() == NODE_CUSP && !other().isDegenerate();
+    bool can_shift_rotate = _parent->type() == NODE_CUSP && !other()->isDegenerate();
     if (can_shift_rotate) {
         more = C_("Path handle tip", "more: Shift, Ctrl, Alt");
     } else {
@@ -1091,6 +1080,58 @@ Inkscape::SnapTargetType Node::_snapTargetType()
     return SNAPTARGET_NODE_CUSP;
 }
 
+/** @brief Gets the handle that faces the given adjacent node.
+ * Will abort with error if the given node is not adjacent. */
+Handle *Node::handleToward(Node *to)
+{
+    if (_next() == to) {
+        return front();
+    }
+    if (_prev() == to) {
+        return back();
+    }
+    g_error("Node::handleToward(): second node is not adjacent!");
+}
+
+/** @brief Gets the node in the direction of the given handle.
+ * Will abort with error if the handle doesn't belong to this node. */
+Node *Node::nodeToward(Handle *dir)
+{
+    if (front() == dir) {
+        return _next();
+    }
+    if (back() == dir) {
+        return _prev();
+    }
+    g_error("Node::nodeToward(): handle is not a child of this node!");
+}
+
+/** @brief Gets the handle that goes in the direction opposite to the given adjacent node.
+ * Will abort with error if the given node is not adjacent. */
+Handle *Node::handleAwayFrom(Node *to)
+{
+    if (_next() == to) {
+        return back();
+    }
+    if (_prev() == to) {
+        return front();
+    }
+    g_error("Node::handleAwayFrom(): second node is not adjacent!");
+}
+
+/** @brief Gets the node in the direction opposite to the given handle.
+ * Will abort with error if the handle doesn't belong to this node. */
+Node *Node::nodeAwayFrom(Handle *h)
+{
+    if (front() == h) {
+        return _prev();
+    }
+    if (back() == h) {
+        return _next();
+    }
+    g_error("Node::nodeAwayFrom(): handle is not a child of this node!");
+}
+
 Glib::ustring Node::_getTip(unsigned state)
 {
     if (state_held_shift(state)) {
@@ -1115,7 +1156,11 @@ Glib::ustring Node::_getTip(unsigned state)
             "<b>Ctrl:</b> move along axes, click to change node type");
     }
 
-    // assemble tip from node name
+    if (state_held_alt(state)) {
+        return C_("Path node tip", "<b>Alt:</b> sculpt nodes");
+    }
+
+    // No modifiers: assemble tip from node type
     char const *nodetype = node_type_to_localized_string(_type);
     if (_selection.transformHandlesEnabled() && selected()) {
         if (_selection.size() == 1) {
index d04b879761faf99adb3d4db7f8093b5311c054c5..af4cd7e3af1c5b09f4d3bbd67b204cfda14480fa 100644 (file)
@@ -96,7 +96,7 @@ public:
     void setDirection(Geom::Point const &from, Geom::Point const &to);
     void setDirection(Geom::Point const &dir);
     Node *parent() { return _parent; }
-    Handle &other();
+    Handle *other();
 
     static char const *handle_type_to_localized_string(NodeType type);
 protected:
@@ -138,10 +138,14 @@ public:
     bool isEndNode();
     Handle *front() { return &_front; }
     Handle *back()  { return &_back;  }
-    static NodeType parse_nodetype(char x);
+    Handle *handleToward(Node *to);
+    Node *nodeToward(Handle *h);
+    Handle *handleAwayFrom(Node *to);
+    Node *nodeAwayFrom(Handle *h);
     NodeList &nodeList() { return *(static_cast<ListNode*>(this)->ln_list); }
     void sink();
 
+    static NodeType parse_nodetype(char x);
     static char const *node_type_to_localized_string(NodeType type);
     // temporarily public
     virtual bool _eventHandler(GdkEvent *event);
index ebf0f3828c07291159a4242c376aa0a18b3ce274..f6d5bde37124654d85b22d03773f55a8be6b108b 100644 (file)
@@ -720,6 +720,7 @@ void PathManipulator::setSegmentType(SegmentType type)
             case SEGMENT_CUBIC_BEZIER:
                 if (!j->front()->isDegenerate() || !k->back()->isDegenerate())
                     break;
+                // move both handles to 1/3 of the line
                 j->front()->move(j->position() + (k->position() - j->position()) / 3);
                 k->back()->move(k->position() + (j->position() - k->position()) / 3);
                 break;
@@ -744,9 +745,17 @@ void PathManipulator::scaleHandle(Node *n, int which, int dir, bool pixel)
         length_change *= dir;
     }
 
-    Geom::Point relpos = h->relativePos();
-    double rellen = relpos.length();
-    h->setRelativePos(relpos * ((rellen + length_change) / rellen));
+    Geom::Point relpos;
+    if (h->isDegenerate()) {
+        Node *nh = n->nodeToward(h);
+        if (!nh) return;
+        relpos = Geom::unit_vector(nh->position() - n->position()) * length_change;
+    } else {
+        relpos = h->relativePos();
+        double rellen = relpos.length();
+        relpos *= ((rellen + length_change) / rellen);
+    }
+    h->setRelativePos(relpos);
     update();
 
     gchar const *key = which < 0 ? "handle:scale:left" : "handle:scale:right";
@@ -759,8 +768,9 @@ void PathManipulator::rotateHandle(Node *n, int which, int dir, bool pixel)
         n->setType(NODE_CUSP);
     }
     Handle *h = _chooseHandle(n, which);
-    double angle;
+    if (h->isDegenerate()) return;
 
+    double angle;
     if (pixel) {
         // Rotate by "one pixel"
         angle = atan2(1.0 / _desktop->current_zoom(), h->length()) * dir;
@@ -769,6 +779,7 @@ void PathManipulator::rotateHandle(Node *n, int which, int dir, bool pixel)
         int snaps = prefs->getIntLimited("/options/rotationsnapsperpi/value", 12, 1, 1000);
         angle = M_PI * dir / snaps;
     }
+
     h->setRelativePos(h->relativePos() * Geom::Rotate(angle));
     update();
     gchar const *key = which < 0 ? "handle:rotate:left" : "handle:rotate:right";
@@ -777,7 +788,14 @@ void PathManipulator::rotateHandle(Node *n, int which, int dir, bool pixel)
 
 Handle *PathManipulator::_chooseHandle(Node *n, int which)
 {
-    Geom::Point f = n->front()->position(), b = n->back()->position();
+    // Rationale for this choice:
+    // Imagine you have two handles pointing right, where one of them is only slighty higher
+    // than the other. Extending one of the handles could make its X coord larger than
+    // the second one, and keeping the shortcut pressed would result in two handles being
+    // extended alternately. This appears like extending both handles at once and is confusing.
+    // Using the unit vector avoids this problem and remains fairly intuitive.
+    Geom::Point f = Geom::unit_vector(n->front()->position());
+    Geom::Point b = Geom::unit_vector(n->back()->position());
     if (which < 0) {
         // pick left handle.
         // we just swap the handles and pick the right handle below.