Code

Reduce libsigc++ usage to partially fix performance regressions
[inkscape.git] / src / ui / tool / multi-path-manipulator.cpp
index 6b245702a1fbc36d2e37f4586f7fbaf1e26d74bd..c34ef066e48d448a88ce52b642e1830e9315715e 100644 (file)
@@ -1,5 +1,5 @@
 /** @file
- * Path manipulator - implementation
+ * Multi path manipulator - implementation
  */
 /* Authors:
  *   Krzysztof KosiƄski <tweenk.pl@gmail.com>
@@ -15,7 +15,9 @@
 #include "desktop.h"
 #include "desktop-handles.h"
 #include "document.h"
+#include "live_effects/lpeobject.h"
 #include "message-stack.h"
+#include "preferences.h"
 #include "sp-path.h"
 #include "ui/tool/control-point-selection.h"
 #include "ui/tool/event-utils.h"
@@ -35,8 +37,7 @@ typedef std::unordered_set<NodeList::iterator> IterSet;
 typedef std::multimap<double, IterPair> DistanceMap;
 typedef std::pair<double, IterPair> DistanceMapItem;
 
-/** Find two selected endnodes.
- * @returns -1 if not enough endnodes selected, 1 if too many, 0 if OK */
+/** Find pairs of selected endnodes suitable for joining. */
 void find_join_iterators(ControlPointSelection &sel, IterPairList &pairs)
 {
     IterSet join_iters;
@@ -44,7 +45,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);
@@ -103,12 +104,11 @@ bool prepare_join(IterPair &join_iters)
 } // anonymous namespace
 
 
-MultiPathManipulator::MultiPathManipulator(PathSharedData const &data, sigc::connection &chg)
+MultiPathManipulator::MultiPathManipulator(PathSharedData &data, sigc::connection &chg)
     : PointManipulator(data.node_data.desktop, *data.node_data.selection)
     , _path_data(data)
     , _changed(chg)
 {
-    //
     _selection.signal_commit.connect(
         sigc::mem_fun(*this, &MultiPathManipulator::_commit));
     _selection.signal_point_changed.connect(
@@ -130,80 +130,80 @@ void MultiPathManipulator::cleanup()
     }
 }
 
-void MultiPathManipulator::setItems(std::map<SPPath*,
-    std::pair<Geom::Matrix, guint32> > const &items)
+/** @brief Change the set of items to edit.
+ *
+ * This method attempts to preserve as much of the state as possible. */
+void MultiPathManipulator::setItems(std::set<ShapeRecord> const &s)
 {
-    typedef std::map<SPPath*, std::pair<Geom::Matrix, guint32> > TransMap;
-    typedef std::set<SPPath*> ItemSet;
-    ItemSet to_remove, to_add, current, new_items;
-
-    for (MapType::iterator i = _mmap.begin(); i != _mmap.end(); ++i) {
-        current.insert(i->first);
-    }
-    for (TransMap::const_iterator i = items.begin(); i != items.end(); ++i) {
-        new_items.insert(i->first);
+    std::set<ShapeRecord> shapes(s);
+
+    // iterate over currently edited items, modifying / removing them as necessary
+    for (MapType::iterator i = _mmap.begin(); i != _mmap.end();) {
+        std::set<ShapeRecord>::iterator si = shapes.find(i->first);
+        if (si == shapes.end()) {
+            // This item is no longer supposed to be edited - remove its manipulator
+            _mmap.erase(i++);
+        } else {
+            ShapeRecord const &sr = i->first;
+            ShapeRecord const &sr_new = *si;
+            // if the shape record differs, replace the key only and modify other values
+            if (sr.edit_transform != sr_new.edit_transform ||
+                sr.role != sr_new.role)
+            {
+                boost::shared_ptr<PathManipulator> hold(i->second);
+                if (sr.edit_transform != sr_new.edit_transform)
+                    hold->setControlsTransform(sr_new.edit_transform);
+                if (sr.role != sr_new.role) {
+                    //hold->setOutlineColor(_getOutlineColor(sr_new.role));
+                }
+                _mmap.erase(sr);
+                _mmap.insert(std::make_pair(sr_new, hold));
+            }
+            shapes.erase(si); // remove the processed record
+            ++i;
+        }
     }
 
-    std::set_difference(current.begin(), current.end(), new_items.begin(), new_items.end(),
-        std::inserter(to_remove, to_remove.end()));
-    std::set_difference(new_items.begin(), new_items.end(), current.begin(), current.end(),
-        std::inserter(to_add, to_add.end()));
-
-    for (ItemSet::iterator i = to_remove.begin(); i != to_remove.end(); ++i) {
-        _mmap.erase(*i);
-    }
-    for (ItemSet::iterator i = to_add.begin(); i != to_add.end(); ++i) {
-        boost::shared_ptr<PathManipulator> pm;
-        TransMap::const_iterator f = items.find(*i);
-        pm.reset(new PathManipulator(_path_data, *i, f->second.first, f->second.second));
-        pm->showHandles(_show_handles);
-        pm->showOutline(_show_outline);
-        pm->showPathDirection(_show_path_direction);
-        _mmap.insert(std::make_pair(*i, pm));
+    // add newly selected items
+    for (std::set<ShapeRecord>::iterator i = shapes.begin(); i != shapes.end(); ++i) {
+        ShapeRecord const &r = *i;
+        if (!SP_IS_PATH(r.item) && !IS_LIVEPATHEFFECT(r.item)) continue;
+        boost::shared_ptr<PathManipulator> newpm(new PathManipulator(*this, (SPPath*) r.item,
+            r.edit_transform, _getOutlineColor(r.role), r.lpe_key));
+        newpm->showHandles(_show_handles);
+        // 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));
     }
 }
 
 void MultiPathManipulator::selectSubpaths()
 {
     if (_selection.empty()) {
-        invokeForAll(&PathManipulator::selectAll);
+        _selection.selectAll();
     } else {
         invokeForAll(&PathManipulator::selectSubpaths);
     }
 }
-void MultiPathManipulator::selectAll()
-{
-    invokeForAll(&PathManipulator::selectAll);
-}
-
-void MultiPathManipulator::selectArea(Geom::Rect const &area, bool take)
-{
-    if (take) _selection.clear();
-    invokeForAll(&PathManipulator::selectArea, area);
-}
 
 void MultiPathManipulator::shiftSelection(int dir)
 {
     invokeForAll(&PathManipulator::shiftSelection, dir);
 }
-void MultiPathManipulator::invertSelection()
-{
-    invokeForAll(&PathManipulator::invertSelection);
-}
+
 void MultiPathManipulator::invertSelectionInSubpaths()
 {
     invokeForAll(&PathManipulator::invertSelectionInSubpaths);
 }
-void MultiPathManipulator::deselect()
-{
-    _selection.clear();
-}
 
 void MultiPathManipulator::setNodeType(NodeType type)
 {
     if (_selection.empty()) return;
     for (ControlPointSelection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
-        Node *node = dynamic_cast<Node*>(i->first);
+        Node *node = dynamic_cast<Node*>(*i);
         if (node) node->setType(type);
     }
     _done(_("Change node type"));
@@ -228,6 +228,7 @@ void MultiPathManipulator::insertNodes()
 
 void MultiPathManipulator::joinNodes()
 {
+    invokeForAll(&PathManipulator::hideDragPoint);
     // Node join has two parts. In the first one we join two subpaths by fusing endpoints
     // into one. In the second we fuse nodes in each subpath.
     IterPairList joins;
@@ -240,36 +241,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);
 
@@ -281,8 +279,12 @@ void MultiPathManipulator::joinNodes()
         }
         _selection.insert(i->first.ptr());
     }
-    // Second part replaces contiguous selections of nodes with single nodes
-    invokeForAll(&PathManipulator::weldNodes, preserve_pos);
+
+    if (joins.empty()) {
+        // Second part replaces contiguous selections of nodes with single nodes
+        invokeForAll(&PathManipulator::weldNodes, preserve_pos);
+    }
+
     _doneWithCleanup(_("Join nodes"));
 }
 
@@ -301,15 +303,10 @@ void MultiPathManipulator::deleteNodes(bool keep_shape)
 }
 
 /** Join selected endpoints to create segments. */
-void MultiPathManipulator::joinSegment()
+void MultiPathManipulator::joinSegments()
 {
     IterPairList joins;
     find_join_iterators(_selection, joins);
-    if (joins.empty()) {
-        _desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE,
-            _("There must be at least 2 endnodes in selection"));
-        return;
-    }
 
     for (IterPairList::iterator i = joins.begin(); i != joins.end(); ++i) {
         bool same_path = prepare_join(*i);
@@ -325,7 +322,10 @@ void MultiPathManipulator::joinSegment()
         }
     }
 
-    _doneWithCleanup("Join segment");
+    if (joins.empty()) {
+        invokeForAll(&PathManipulator::weldSegments);
+    }
+    _doneWithCleanup("Join segments");
 }
 
 void MultiPathManipulator::deleteSegments()
@@ -357,8 +357,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)
@@ -369,7 +374,10 @@ void MultiPathManipulator::move(Geom::Point const &delta)
 
 void MultiPathManipulator::showOutline(bool show)
 {
-    invokeForAll(&PathManipulator::showOutline, show);
+    for (MapType::iterator i = _mmap.begin(); i != _mmap.end(); ++i) {
+        // always show outlines for clipping paths and masks
+        i->second->showOutline(show || i->first.role != SHAPE_ROLE_NORMAL);
+    }
     _show_outline = show;
 }
 
@@ -385,6 +393,33 @@ 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) {
+    //    i->second->setOutlineColor(_getOutlineColor(i->first.role));
+    //}
+}
+
 bool MultiPathManipulator::event(GdkEvent *event)
 {
     switch (event->type) {
@@ -392,11 +427,14 @@ bool MultiPathManipulator::event(GdkEvent *event)
         switch (shortcut_key(event->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;
             }
@@ -404,17 +442,20 @@ bool MultiPathManipulator::event(GdkEvent *event)
         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)) {
-                joinSegment();
+                // Alt+J - join segments
+                joinSegments();
                 return true;
             }
             break;
         case GDK_b:
         case GDK_B:
             if (held_only_shift(event->key)) {
+                // Shift+B - break nodes
                 breakNodes();
                 return true;
             }
@@ -424,14 +465,18 @@ 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 {
+                // Control+Delete - delete nodes
+                // Delete - delete nodes preserving shape
                 deleteNodes(!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;
             }
@@ -439,6 +484,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;
             }
@@ -446,6 +492,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;
             }
@@ -453,6 +500,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;
             }
@@ -460,8 +508,8 @@ bool MultiPathManipulator::event(GdkEvent *event)
         case GDK_r:
         case GDK_R:
             if (held_only_shift(event->key)) {
+                // Shift+R - reverse subpaths
                 reverseSubpaths();
-                break;
             }
             break;
         default:
@@ -477,6 +525,8 @@ bool MultiPathManipulator::event(GdkEvent *event)
     return false;
 }
 
+/** Commit changes to XML and add undo stack entry based on the action that was done. Invoked
+ * by sub-manipulators, for example TransformHandleSet and ControlPointSelection. */
 void MultiPathManipulator::_commit(CommitEvent cps)
 {
     gchar const *reason = NULL;
@@ -553,6 +603,23 @@ void MultiPathManipulator::_doneWithCleanup(gchar const *reason) {
     _changed.unblock();
 }
 
+/** Get an outline color based on the shape's role (normal, mask, LPE parameter, etc.). */
+guint32 MultiPathManipulator::_getOutlineColor(ShapeRole role)
+{
+    Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+    switch(role) {
+    case SHAPE_ROLE_CLIPPING_PATH:
+        return prefs->getColor("/tools/nodes/clipping_path_color", 0x00ff00ff);
+    case SHAPE_ROLE_MASK:
+        return prefs->getColor("/tools/nodes/mask_color", 0x0000ffff);
+    case SHAPE_ROLE_LPE_PARAM:
+        return prefs->getColor("/tools/nodes/lpe_param_color", 0x009000ff);
+    case SHAPE_ROLE_NORMAL:
+    default:
+        return prefs->getColor("/tools/nodes/outline_color", 0xff0000ff);
+    }
+}
+
 } // namespace UI
 } // namespace Inkscape