Code

Node tool: correctly save node skewing to undo history
[inkscape.git] / src / ui / tool / multi-path-manipulator.cpp
index 3ae7e4d2495264cb8c3f4a32fc870d4d4c7faa6b..5f60f117a3d0ceb32aa4dfb5e03d58799613e9dc 100644 (file)
@@ -1,14 +1,14 @@
 /** @file
- * Path manipulator - implementation
+ * Multi path manipulator - implementation
  */
 /* Authors:
  *   Krzysztof KosiƄski <tweenk.pl@gmail.com>
+ *   Abhishek Sharma
  *
  * Copyright (C) 2009 Authors
  * Released under GNU GPL, read the file 'COPYING' for more information
  */
 
-#include <tr1/unordered_set>
 #include <boost/shared_ptr.hpp>
 #include <glib.h>
 #include <glibmm/i18n.h>
 #include "ui/tool/node.h"
 #include "ui/tool/multi-path-manipulator.h"
 #include "ui/tool/path-manipulator.h"
-
-namespace std { using namespace tr1; }
+#include "util/unordered-containers.h"
+
+#ifdef USE_GNU_HASHES
+namespace __gnu_cxx {
+template<>
+struct hash<Inkscape::UI::NodeList::iterator> {
+    size_t operator()(Inkscape::UI::NodeList::iterator const &n) const {
+        return reinterpret_cast<size_t>(n.ptr());
+    }
+};
+} // namespace __gnu_cxx
+#endif // USE_GNU_HASHES
 
 namespace Inkscape {
 namespace UI {
 
 namespace {
+
+struct hash_nodelist_iterator
+    : public std::unary_function<NodeList::iterator, std::size_t>
+{
+    std::size_t operator()(NodeList::iterator i) const {
+        return INK_HASH<NodeList::iterator::pointer>()(&*i);
+    }
+};
+
 typedef std::pair<NodeList::iterator, NodeList::iterator> IterPair;
 typedef std::vector<IterPair> IterPairList;
-typedef std::unordered_set<NodeList::iterator> IterSet;
+typedef INK_UNORDERED_SET<NodeList::iterator, hash_nodelist_iterator> IterSet;
 typedef std::multimap<double, IterPair> DistanceMap;
 typedef std::pair<double, IterPair> DistanceMapItem;
 
@@ -45,7 +64,7 @@ void find_join_iterators(ControlPointSelection &sel, IterPairList &pairs)
 
     // find all endnodes in selection
     for (ControlPointSelection::iterator i = sel.begin(); i != sel.end(); ++i) {
-        Node *node = dynamic_cast<Node*>(i->first);
+        Node *node = dynamic_cast<Node*>(*i);
         if (!node) continue;
         NodeList::iterator iter = NodeList::get_iterator(node);
         if (!iter.next() || !iter.prev()) join_iters.insert(iter);
@@ -174,6 +193,8 @@ void MultiPathManipulator::setItems(std::set<ShapeRecord> const &s)
         // always show outlines for clips and masks
         newpm->showOutline(_show_outline || r.role != SHAPE_ROLE_NORMAL);
         newpm->showPathDirection(_show_path_direction);
+        newpm->setLiveOutline(_live_outline);
+        newpm->setLiveObjects(_live_objects);
         _mmap.insert(std::make_pair(r, newpm));
     }
 }
@@ -200,11 +221,29 @@ void MultiPathManipulator::invertSelectionInSubpaths()
 void MultiPathManipulator::setNodeType(NodeType type)
 {
     if (_selection.empty()) return;
+
+    // When all selected nodes are already cusp, retract their handles
+    bool retract_handles = (type == NODE_CUSP);
+
     for (ControlPointSelection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
-        Node *node = dynamic_cast<Node*>(i->first);
-        if (node) node->setType(type);
+        Node *node = dynamic_cast<Node*>(*i);
+        if (node) {
+            retract_handles &= (node->type() == NODE_CUSP);
+            node->setType(type);
+        }
+    }
+
+    if (retract_handles) {
+        for (ControlPointSelection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
+            Node *node = dynamic_cast<Node*>(*i);
+            if (node) {
+                node->front()->retract();
+                node->back()->retract();
+            }
+        }
     }
-    _done(_("Change node type"));
+
+    _done(retract_handles ? _("Retract handles") : _("Change node type"));
 }
 
 void MultiPathManipulator::setSegmentType(SegmentType type)
@@ -224,6 +263,12 @@ void MultiPathManipulator::insertNodes()
     _done(_("Add nodes"));
 }
 
+void MultiPathManipulator::duplicateNodes()
+{
+    invokeForAll(&PathManipulator::duplicateNodes);
+    _done(_("Duplicate nodes"));
+}
+
 void MultiPathManipulator::joinNodes()
 {
     invokeForAll(&PathManipulator::hideDragPoint);
@@ -239,36 +284,33 @@ void MultiPathManipulator::joinNodes()
 
     for (IterPairList::iterator i = joins.begin(); i != joins.end(); ++i) {
         bool same_path = prepare_join(*i);
-        bool mouseover = true;
         NodeList &sp_first = NodeList::get(i->first);
         NodeList &sp_second = NodeList::get(i->second);
         i->first->setType(NODE_CUSP, false);
 
-        Geom::Point joined_pos, pos_front, pos_back;
-        pos_front = *i->second->front();
-        pos_back = *i->first->back();
+        Geom::Point joined_pos, pos_handle_front, pos_handle_back;
+        pos_handle_front = *i->second->front();
+        pos_handle_back = *i->first->back();
+
+        // When we encounter the mouseover node, we unset the iterator - it will be invalidated
         if (i->first == preserve_pos) {
             joined_pos = *i->first;
+            preserve_pos = NodeList::iterator();
         } else if (i->second == preserve_pos) {
             joined_pos = *i->second;
+            preserve_pos = NodeList::iterator();
         } else {
-            joined_pos = Geom::middle_point(pos_back, pos_front);
-            mouseover = false;
+            joined_pos = Geom::middle_point(*i->first, *i->second);
         }
 
         // if the handles aren't degenerate, don't move them
         i->first->move(joined_pos);
         Node *joined_node = i->first.ptr();
         if (!i->second->front()->isDegenerate()) {
-            joined_node->front()->setPosition(pos_front);
+            joined_node->front()->setPosition(pos_handle_front);
         }
         if (!i->first->back()->isDegenerate()) {
-            joined_node->back()->setPosition(pos_back);
-        }
-        if (mouseover) {
-            // Second node could be mouseovered, but it will be deleted, so we must change
-            // the preserve_pos iterator to the first node.
-            preserve_pos = i->first;
+            joined_node->back()->setPosition(pos_handle_back);
         }
         sp_second.erase(i->second);
 
@@ -358,8 +400,13 @@ void MultiPathManipulator::distributeNodes(Geom::Dim2 d)
 
 void MultiPathManipulator::reverseSubpaths()
 {
-    invokeForAll(&PathManipulator::reverseSubpaths);
-    _done("Reverse selected subpaths");
+    if (_selection.empty()) {
+        invokeForAll(&PathManipulator::reverseSubpaths, false);
+        _done("Reverse subpaths");
+    } else {
+        invokeForAll(&PathManipulator::reverseSubpaths, true);
+        _done("Reverse selected subpaths");
+    }
 }
 
 void MultiPathManipulator::move(Geom::Point const &delta)
@@ -389,6 +436,26 @@ void MultiPathManipulator::showPathDirection(bool show)
     _show_path_direction = show;
 }
 
+/** @brief Set live outline update status
+ * When set to true, outline will be updated continuously when dragging
+ * or transforming nodes. Otherwise it will only update when changes are committed
+ * to XML. */
+void MultiPathManipulator::setLiveOutline(bool set)
+{
+    invokeForAll(&PathManipulator::setLiveOutline, set);
+    _live_outline = set;
+}
+
+/** @brief Set live object update status
+ * When set to true, objects will be updated continuously when dragging
+ * or transforming nodes. Otherwise they will only update when changes are committed
+ * to XML. */
+void MultiPathManipulator::setLiveObjects(bool set)
+{
+    invokeForAll(&PathManipulator::setLiveObjects, set);
+    _live_objects = set;
+}
+
 void MultiPathManipulator::updateOutlineColors()
 {
     //for (MapType::iterator i = _mmap.begin(); i != _mmap.end(); ++i) {
@@ -398,27 +465,94 @@ void MultiPathManipulator::updateOutlineColors()
 
 bool MultiPathManipulator::event(GdkEvent *event)
 {
+    _tracker.event(event);
+    guint key = 0;
+    if (event->type == GDK_KEY_PRESS) {
+        key = shortcut_key(event->key);
+    }
+
+    // Single handle adjustments go here.
+    if (_selection.size() == 1 && event->type == GDK_KEY_PRESS) {
+        do {
+            Node *n = dynamic_cast<Node *>(*_selection.begin());
+            if (!n) break;
+
+            PathManipulator &pm = n->nodeList().subpathList().pm();
+
+            int which = 0;
+            if (_tracker.rightAlt() || _tracker.rightControl()) {
+                which = 1;
+            }
+            if (_tracker.leftAlt() || _tracker.leftControl()) {
+                if (which != 0) break; // ambiguous
+                which = -1;
+            }
+            if (which == 0) break; // no handle chosen
+            bool one_pixel = _tracker.leftAlt() || _tracker.rightAlt();
+            bool handled = true;
+
+            switch (key) {
+            // single handle functions
+            // rotation
+            case GDK_bracketleft:
+            case GDK_braceleft:
+                pm.rotateHandle(n, which, 1, one_pixel);
+                break;
+            case GDK_bracketright:
+            case GDK_braceright:
+                pm.rotateHandle(n, which, -1, one_pixel);
+                break;
+            // adjust length
+            case GDK_period:
+            case GDK_greater:
+                pm.scaleHandle(n, which, 1, one_pixel);
+                break;
+            case GDK_comma:
+            case GDK_less:
+                pm.scaleHandle(n, which, -1, one_pixel);
+                break;
+            default:
+                handled = false;
+                break;
+            }
+
+            if (handled) return true;
+        } while(0);
+    }
+
+
     switch (event->type) {
     case GDK_KEY_PRESS:
-        switch (shortcut_key(event->key)) {
+        switch (key) {
         case GDK_Insert:
         case GDK_KP_Insert:
+            // Insert - insert nodes in the middle of selected segments
             insertNodes();
             return true;
         case GDK_i:
         case GDK_I:
             if (held_only_shift(event->key)) {
+                // Shift+I - insert nodes (alternate keybinding for Mac keyboards
+                //           that don't have the Insert key)
                 insertNodes();
                 return true;
             }
             break;
+        case GDK_d:
+        case GDK_D:
+            if (held_only_shift(event->key)) {
+                duplicateNodes();
+                return true;
+            }
         case GDK_j:
         case GDK_J:
             if (held_only_shift(event->key)) {
+                // Shift+J - join nodes
                 joinNodes();
                 return true;
             }
             if (held_only_alt(event->key)) {
+                // Alt+J - join segments
                 joinSegments();
                 return true;
             }
@@ -426,6 +560,7 @@ bool MultiPathManipulator::event(GdkEvent *event)
         case GDK_b:
         case GDK_B:
             if (held_only_shift(event->key)) {
+                // Shift+B - break nodes
                 breakNodes();
                 return true;
             }
@@ -435,14 +570,22 @@ bool MultiPathManipulator::event(GdkEvent *event)
         case GDK_BackSpace:
             if (held_shift(event->key)) break;
             if (held_alt(event->key)) {
+                // Alt+Delete - delete segments
                 deleteSegments();
             } else {
-                deleteNodes(!held_control(event->key));
+                Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+                bool del_preserves_shape = prefs->getBool("/tools/nodes/delete_preserves_shape", true);
+                // pass keep_shape = true when:
+                // a) del preserves shape, and control is not pressed
+                // b) ctrl+del preserves shape (del_preserves_shape is false), and control is pressed
+                // Hence xor
+                deleteNodes(del_preserves_shape ^ held_control(event->key));
             }
             return true;
         case GDK_c:
         case GDK_C:
             if (held_only_shift(event->key)) {
+                // Shift+C - make nodes cusp
                 setNodeType(NODE_CUSP);
                 return true;
             }
@@ -450,6 +593,7 @@ bool MultiPathManipulator::event(GdkEvent *event)
         case GDK_s:
         case GDK_S:
             if (held_only_shift(event->key)) {
+                // Shift+S - make nodes smooth
                 setNodeType(NODE_SMOOTH);
                 return true;
             }
@@ -457,6 +601,7 @@ bool MultiPathManipulator::event(GdkEvent *event)
         case GDK_a:
         case GDK_A:
             if (held_only_shift(event->key)) {
+                // Shift+A - make nodes auto-smooth
                 setNodeType(NODE_AUTO);
                 return true;
             }
@@ -464,6 +609,7 @@ bool MultiPathManipulator::event(GdkEvent *event)
         case GDK_y:
         case GDK_Y:
             if (held_only_shift(event->key)) {
+                // Shift+Y - make nodes symmetric
                 setNodeType(NODE_SYMMETRIC);
                 return true;
             }
@@ -471,20 +617,38 @@ bool MultiPathManipulator::event(GdkEvent *event)
         case GDK_r:
         case GDK_R:
             if (held_only_shift(event->key)) {
+                // Shift+R - reverse subpaths
                 reverseSubpaths();
-                break;
+                return true;
             }
             break;
+        case GDK_l:
+        case GDK_L:
+            if (held_only_shift(event->key)) {
+                // Shift+L - make segments linear
+                setSegmentType(SEGMENT_STRAIGHT);
+                return true;
+            }
+        case GDK_u:
+        case GDK_U:
+            if (held_only_shift(event->key)) {
+                // Shift+L - make segments curves
+                setSegmentType(SEGMENT_CUBIC_BEZIER);
+                return true;
+            }
         default:
             break;
         }
         break;
+    case GDK_MOTION_NOTIFY:
+        combine_motion_events(_desktop->canvas, event->motion, 0);
+        for (MapType::iterator i = _mmap.begin(); i != _mmap.end(); ++i) {
+            if (i->second->event(event)) return true;
+        }
+        break;
     default: break;
     }
 
-    for (MapType::iterator i = _mmap.begin(); i != _mmap.end(); ++i) {
-        if (i->second->event(event)) return true;
-    }
     return false;
 }
 
@@ -531,6 +695,14 @@ void MultiPathManipulator::_commit(CommitEvent cps)
         reason = _("Scale nodes vertically");
         key = "node:scale:y";
         break;
+    case COMMIT_MOUSE_SKEW_X:
+        reason = _("Skew nodes horizontally");
+        key = "node:skew:x";
+        break;
+    case COMMIT_MOUSE_SKEW_Y:
+        reason = _("Skew nodes vertically");
+        key = "node:skew:y";
+        break;
     case COMMIT_FLIP_X:
         reason = _("Flip nodes horizontally");
         break;
@@ -543,9 +715,9 @@ void MultiPathManipulator::_commit(CommitEvent cps)
     _selection.signal_update.emit();
     invokeForAll(&PathManipulator::writeXML);
     if (key) {
-        sp_document_maybe_done(sp_desktop_document(_desktop), key, SP_VERB_CONTEXT_NODE, reason);
+        DocumentUndo::maybeDone(sp_desktop_document(_desktop), key, SP_VERB_CONTEXT_NODE, reason);
     } else {
-        sp_document_done(sp_desktop_document(_desktop), SP_VERB_CONTEXT_NODE, reason);
+        DocumentUndo::done(sp_desktop_document(_desktop), SP_VERB_CONTEXT_NODE, reason);
     }
     signal_coords_changed.emit();
 }
@@ -554,7 +726,7 @@ void MultiPathManipulator::_commit(CommitEvent cps)
 void MultiPathManipulator::_done(gchar const *reason) {
     invokeForAll(&PathManipulator::update);
     invokeForAll(&PathManipulator::writeXML);
-    sp_document_done(sp_desktop_document(_desktop), SP_VERB_CONTEXT_NODE, reason);
+    DocumentUndo::done(sp_desktop_document(_desktop), SP_VERB_CONTEXT_NODE, reason);
     signal_coords_changed.emit();
 }
 
@@ -595,4 +767,4 @@ guint32 MultiPathManipulator::_getOutlineColor(ShapeRole role)
   fill-column:99
   End:
 */
-// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :