index 6b245702a1fbc36d2e37f4586f7fbaf1e26d74bd..c34ef066e48d448a88ce52b642e1830e9315715e 100644 (file)
/** @file
- * Path manipulator - implementation
+ * Multi path manipulator - implementation
*/
/* Authors:
* Krzysztof KosiĆski <tweenk.pl@gmail.com>
#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"
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;
// 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);
} // 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(
}
}
-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"));
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;
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);
}
_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"));
}
}
/** 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);
}
}
- _doneWithCleanup("Join segment");
+ if (joins.empty()) {
+ invokeForAll(&PathManipulator::weldSegments);
+ }
+ _doneWithCleanup("Join segments");
}
void MultiPathManipulator::deleteSegments()
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)
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;
}
_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) {
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;
}
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;
}
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;
}
case GDK_s:
case GDK_S:
if (held_only_shift(event->key)) {
+ // Shift+S - make nodes smooth
setNodeType(NODE_SMOOTH);
return true;
}
case GDK_a:
case GDK_A:
if (held_only_shift(event->key)) {
+ // Shift+A - make nodes auto-smooth
setNodeType(NODE_AUTO);
return true;
}
case GDK_y:
case GDK_Y:
if (held_only_shift(event->key)) {
+ // Shift+Y - make nodes symmetric
setNodeType(NODE_SYMMETRIC);
return true;
}
case GDK_r:
case GDK_R:
if (held_only_shift(event->key)) {
+ // Shift+R - reverse subpaths
reverseSubpaths();
- break;
}
break;
default:
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;
_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