multi-path-manipulator.cpp revision 4aeaa765c1779bb282e9cb7b8ad14cc1fae248ef
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk/**
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * @file
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * Multi path manipulator - implementation.
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk */
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk/* Authors:
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * Krzysztof KosiƄski <tweenk.pl@gmail.com>
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * Abhishek Sharma
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk *
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * Copyright (C) 2009 Authors
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * Released under GNU GPL, read the file 'COPYING' for more information
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk */
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk#include <boost/shared_ptr.hpp>
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk#include "node.h"
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk#include <glibmm/i18n.h>
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk#include "desktop.h"
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk#include "desktop-handles.h"
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk#include "document.h"
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk#include "document-undo.h"
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk#include "live_effects/lpeobject.h"
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk#include "message-stack.h"
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk#include "preferences.h"
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk#include "sp-path.h"
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk#include "ui/tool/control-point-selection.h"
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk#include "ui/tool/event-utils.h"
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk#include "ui/tool/multi-path-manipulator.h"
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk#include "ui/tool/path-manipulator.h"
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk#include "util/unordered-containers.h"
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk#include "verbs.h"
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk#include <gdk/gdkkeysyms.h>
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenknamespace Inkscape {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenknamespace UI {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenknamespace {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenkstruct hash_nodelist_iterator
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk : public std::unary_function<NodeList::iterator, std::size_t>
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk{
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk std::size_t operator()(NodeList::iterator i) const {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk return INK_HASH<NodeList::iterator::pointer>()(&*i);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk }
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk};
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenktypedef std::pair<NodeList::iterator, NodeList::iterator> IterPair;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenktypedef std::vector<IterPair> IterPairList;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenktypedef INK_UNORDERED_SET<NodeList::iterator, hash_nodelist_iterator> IterSet;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenktypedef std::multimap<double, IterPair> DistanceMap;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenktypedef std::pair<double, IterPair> DistanceMapItem;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk/** Find pairs of selected endnodes suitable for joining. */
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenkvoid find_join_iterators(ControlPointSelection &sel, IterPairList &pairs)
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk{
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk IterSet join_iters;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk // find all endnodes in selection
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk for (ControlPointSelection::iterator i = sel.begin(); i != sel.end(); ++i) {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk Node *node = dynamic_cast<Node*>(*i);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk if (!node) continue;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk NodeList::iterator iter = NodeList::get_iterator(node);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk if (!iter.next() || !iter.prev()) join_iters.insert(iter);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk }
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk if (join_iters.size() < 2) return;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk // Below we find the closest pairs. The algorithm is O(N^3).
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk // We can go down to O(N^2 log N) by using O(N^2) memory, by putting all pairs
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk // with their distances in a multimap (not worth it IMO).
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk while (join_iters.size() >= 2) {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk double closest = DBL_MAX;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk IterPair closest_pair;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk for (IterSet::iterator i = join_iters.begin(); i != join_iters.end(); ++i) {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk for (IterSet::iterator j = join_iters.begin(); j != i; ++j) {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk double dist = Geom::distance(**i, **j);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk if (dist < closest) {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk closest = dist;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk closest_pair = std::make_pair(*i, *j);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk }
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk }
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk }
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk pairs.push_back(closest_pair);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk join_iters.erase(closest_pair.first);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk join_iters.erase(closest_pair.second);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk }
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk}
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk/** After this function, first should be at the end of path and second at the beginnning.
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * @returns True if the nodes are in the same subpath */
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenkbool prepare_join(IterPair &join_iters)
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk{
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk if (&NodeList::get(join_iters.first) == &NodeList::get(join_iters.second)) {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk if (join_iters.first.next()) // if first is begin, swap the iterators
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk std::swap(join_iters.first, join_iters.second);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk return true;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk }
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk NodeList &sp_first = NodeList::get(join_iters.first);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk NodeList &sp_second = NodeList::get(join_iters.second);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk if (join_iters.first.next()) { // first is begin
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk if (join_iters.second.next()) { // second is begin
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk sp_first.reverse();
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk } else { // second is end
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk std::swap(join_iters.first, join_iters.second);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk }
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk } else { // first is end
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk if (join_iters.second.next()) { // second is begin
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk // do nothing
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk } else { // second is end
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk sp_second.reverse();
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk }
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk }
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk return false;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk}
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk} // anonymous namespace
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenkMultiPathManipulator::MultiPathManipulator(PathSharedData &data, sigc::connection &chg)
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk : PointManipulator(data.node_data.desktop, *data.node_data.selection)
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk , _path_data(data)
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk , _changed(chg)
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk{
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk _selection.signal_commit.connect(
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk sigc::mem_fun(*this, &MultiPathManipulator::_commit));
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk _selection.signal_point_changed.connect(
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk sigc::hide( sigc::hide(
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk signal_coords_changed.make_slot())));
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk}
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenkMultiPathManipulator::~MultiPathManipulator()
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk{
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk _mmap.clear();
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk}
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk/** Remove empty manipulators. */
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenkvoid MultiPathManipulator::cleanup()
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk{
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk for (MapType::iterator i = _mmap.begin(); i != _mmap.end(); ) {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk if (i->second->empty()) _mmap.erase(i++);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk else ++i;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk }
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk}
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk/**
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * Change the set of items to edit.
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk *
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * This method attempts to preserve as much of the state as possible.
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk */
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenkvoid MultiPathManipulator::setItems(std::set<ShapeRecord> const &s)
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk{
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk std::set<ShapeRecord> shapes(s);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk // iterate over currently edited items, modifying / removing them as necessary
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk for (MapType::iterator i = _mmap.begin(); i != _mmap.end();) {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk std::set<ShapeRecord>::iterator si = shapes.find(i->first);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk if (si == shapes.end()) {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk // This item is no longer supposed to be edited - remove its manipulator
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk _mmap.erase(i++);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk } else {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk ShapeRecord const &sr = i->first;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk ShapeRecord const &sr_new = *si;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk // if the shape record differs, replace the key only and modify other values
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk if (sr.edit_transform != sr_new.edit_transform ||
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk sr.role != sr_new.role)
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk boost::shared_ptr<PathManipulator> hold(i->second);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk if (sr.edit_transform != sr_new.edit_transform)
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk hold->setControlsTransform(sr_new.edit_transform);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk if (sr.role != sr_new.role) {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk //hold->setOutlineColor(_getOutlineColor(sr_new.role));
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk }
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk _mmap.erase(sr);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk _mmap.insert(std::make_pair(sr_new, hold));
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk }
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk shapes.erase(si); // remove the processed record
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk ++i;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk }
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk }
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk // add newly selected items
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk for (std::set<ShapeRecord>::iterator i = shapes.begin(); i != shapes.end(); ++i) {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk ShapeRecord const &r = *i;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk if (!SP_IS_PATH(r.item) && !IS_LIVEPATHEFFECT(r.item)) continue;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk boost::shared_ptr<PathManipulator> newpm(new PathManipulator(*this, (SPPath*) r.item,
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk r.edit_transform, _getOutlineColor(r.role), r.lpe_key));
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk newpm->showHandles(_show_handles);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk // always show outlines for clips and masks
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk newpm->showOutline(_show_outline || r.role != SHAPE_ROLE_NORMAL);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk newpm->showPathDirection(_show_path_direction);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk newpm->setLiveOutline(_live_outline);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk newpm->setLiveObjects(_live_objects);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk _mmap.insert(std::make_pair(r, newpm));
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk }
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk}
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenkvoid MultiPathManipulator::selectSubpaths()
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk{
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk if (_selection.empty()) {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk _selection.selectAll();
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk } else {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk invokeForAll(&PathManipulator::selectSubpaths);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk }
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk}
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenkvoid MultiPathManipulator::shiftSelection(int dir)
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk{
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk if (empty()) return;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk // 1. find last selected node
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk // 2. select the next node; if the last node or nothing is selected,
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk // select first node
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk MapType::iterator last_i;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk SubpathList::iterator last_j;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk NodeList::iterator last_k;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk bool anything_found = false;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk bool anynode_found = false;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk for (MapType::iterator i = _mmap.begin(); i != _mmap.end(); ++i) {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk SubpathList &sp = i->second->subpathList();
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk for (SubpathList::iterator j = sp.begin(); j != sp.end(); ++j) {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk anynode_found = true;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk for (NodeList::iterator k = (*j)->begin(); k != (*j)->end(); ++k) {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk if (k->selected()) {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk last_i = i;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk last_j = j;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk last_k = k;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk anything_found = true;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk // when tabbing backwards, we want the first node
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk if (dir == -1) goto exit_loop;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk }
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk }
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk }
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk }
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk exit_loop:
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk // NOTE: we should not assume the _selection contains only nodes
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk // in future it might also contain handles and other types of control points
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk // this is why we use a flag instead in the loop above, instead of calling
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk // selection.empty()
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk if (!anything_found) {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk // select first / last node
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk // this should never fail because there must be at least 1 non-empty manipulator
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk if (anynode_found) {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk if (dir == 1) {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk _selection.insert((*_mmap.begin()->second->subpathList().begin())->begin().ptr());
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk } else {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk _selection.insert((--(*--(--_mmap.end())->second->subpathList().end())->end()).ptr());
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk }
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk }
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk return;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk }
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk // three levels deep - w00t!
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk if (dir == 1) {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk if (++last_k == (*last_j)->end()) {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk // here, last_k points to the node to be selected
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk ++last_j;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk if (last_j == last_i->second->subpathList().end()) {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk ++last_i;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk if (last_i == _mmap.end()) {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk last_i = _mmap.begin();
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk }
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk last_j = last_i->second->subpathList().begin();
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk }
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk last_k = (*last_j)->begin();
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk }
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk } else {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk if (!last_k || last_k == (*last_j)->begin()) {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk if (last_j == last_i->second->subpathList().begin()) {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk if (last_i == _mmap.begin()) {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk last_i = _mmap.end();
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk }
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk --last_i;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk last_j = last_i->second->subpathList().end();
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk }
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk --last_j;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk last_k = (*last_j)->end();
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk }
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk --last_k;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk }
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk _selection.clear();
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk _selection.insert(last_k.ptr());
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk}
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenkvoid MultiPathManipulator::invertSelectionInSubpaths()
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk{
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk invokeForAll(&PathManipulator::invertSelectionInSubpaths);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk}
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenkvoid MultiPathManipulator::setNodeType(NodeType type)
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk{
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk if (_selection.empty()) return;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk // When all selected nodes are already cusp, retract their handles
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk bool retract_handles = (type == NODE_CUSP);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk for (ControlPointSelection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk Node *node = dynamic_cast<Node*>(*i);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk if (node) {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk retract_handles &= (node->type() == NODE_CUSP);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk node->setType(type);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk }
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk }
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk if (retract_handles) {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk for (ControlPointSelection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk Node *node = dynamic_cast<Node*>(*i);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk if (node) {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk node->front()->retract();
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk node->back()->retract();
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk }
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk }
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk }
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk _done(retract_handles ? _("Retract handles") : _("Change node type"));
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk}
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenkvoid MultiPathManipulator::setSegmentType(SegmentType type)
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk{
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk if (_selection.empty()) return;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk invokeForAll(&PathManipulator::setSegmentType, type);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk if (type == SEGMENT_STRAIGHT) {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk _done(_("Straighten segments"));
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk } else {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk _done(_("Make segments curves"));
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk }
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk}
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenkvoid MultiPathManipulator::insertNodes()
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk{
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk if (_selection.empty()) return;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk invokeForAll(&PathManipulator::insertNodes);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk _done(_("Add nodes"));
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk}
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenkvoid MultiPathManipulator::insertNodesAtExtrema(ExtremumType extremum)
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk{
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk if (_selection.empty()) return;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk invokeForAll(&PathManipulator::insertNodeAtExtremum, extremum);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk _done(_("Add extremum nodes"));
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk}
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenkvoid MultiPathManipulator::duplicateNodes()
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk{
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk if (_selection.empty()) return;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk invokeForAll(&PathManipulator::duplicateNodes);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk _done(_("Duplicate nodes"));
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk}
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenkvoid MultiPathManipulator::joinNodes()
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk{
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk if (_selection.empty()) return;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk invokeForAll(&PathManipulator::hideDragPoint);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk // Node join has two parts. In the first one we join two subpaths by fusing endpoints
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk // into one. In the second we fuse nodes in each subpath.
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk IterPairList joins;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk NodeList::iterator preserve_pos;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk Node *mouseover_node = dynamic_cast<Node*>(ControlPoint::mouseovered_point);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk if (mouseover_node) {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk preserve_pos = NodeList::get_iterator(mouseover_node);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk }
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk find_join_iterators(_selection, joins);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk for (IterPairList::iterator i = joins.begin(); i != joins.end(); ++i) {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk bool same_path = prepare_join(*i);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk NodeList &sp_first = NodeList::get(i->first);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk NodeList &sp_second = NodeList::get(i->second);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk i->first->setType(NODE_CUSP, false);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk Geom::Point joined_pos, pos_handle_front, pos_handle_back;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk pos_handle_front = *i->second->front();
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk pos_handle_back = *i->first->back();
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk // When we encounter the mouseover node, we unset the iterator - it will be invalidated
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk if (i->first == preserve_pos) {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk joined_pos = *i->first;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk preserve_pos = NodeList::iterator();
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk } else if (i->second == preserve_pos) {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk joined_pos = *i->second;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk preserve_pos = NodeList::iterator();
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk } else {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk joined_pos = Geom::middle_point(*i->first, *i->second);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk }
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk // if the handles aren't degenerate, don't move them
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk i->first->move(joined_pos);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk Node *joined_node = i->first.ptr();
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk if (!i->second->front()->isDegenerate()) {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk joined_node->front()->setPosition(pos_handle_front);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk }
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk if (!i->first->back()->isDegenerate()) {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk joined_node->back()->setPosition(pos_handle_back);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk }
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk sp_second.erase(i->second);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk if (same_path) {
sp_first.setClosed(true);
} else {
sp_first.splice(sp_first.end(), sp_second);
sp_second.kill();
}
_selection.insert(i->first.ptr());
}
if (joins.empty()) {
// Second part replaces contiguous selections of nodes with single nodes
invokeForAll(&PathManipulator::weldNodes, preserve_pos);
}
_doneWithCleanup(_("Join nodes"), true);
}
void MultiPathManipulator::breakNodes()
{
if (_selection.empty()) return;
invokeForAll(&PathManipulator::breakNodes);
_done(_("Break nodes"), true);
}
void MultiPathManipulator::deleteNodes(bool keep_shape)
{
if (_selection.empty()) return;
invokeForAll(&PathManipulator::deleteNodes, keep_shape);
_doneWithCleanup(_("Delete nodes"), true);
}
/** Join selected endpoints to create segments. */
void MultiPathManipulator::joinSegments()
{
if (_selection.empty()) return;
IterPairList joins;
find_join_iterators(_selection, joins);
for (IterPairList::iterator i = joins.begin(); i != joins.end(); ++i) {
bool same_path = prepare_join(*i);
NodeList &sp_first = NodeList::get(i->first);
NodeList &sp_second = NodeList::get(i->second);
i->first->setType(NODE_CUSP, false);
i->second->setType(NODE_CUSP, false);
if (same_path) {
sp_first.setClosed(true);
} else {
sp_first.splice(sp_first.end(), sp_second);
sp_second.kill();
}
}
if (joins.empty()) {
invokeForAll(&PathManipulator::weldSegments);
}
_doneWithCleanup("Join segments", true);
}
void MultiPathManipulator::deleteSegments()
{
if (_selection.empty()) return;
invokeForAll(&PathManipulator::deleteSegments);
_doneWithCleanup("Delete segments", true);
}
void MultiPathManipulator::alignNodes(Geom::Dim2 d)
{
if (_selection.empty()) return;
_selection.align(d);
if (d == Geom::X) {
_done("Align nodes to a horizontal line");
} else {
_done("Align nodes to a vertical line");
}
}
void MultiPathManipulator::distributeNodes(Geom::Dim2 d)
{
if (_selection.empty()) return;
_selection.distribute(d);
if (d == Geom::X) {
_done("Distrubute nodes horizontally");
} else {
_done("Distribute nodes vertically");
}
}
void MultiPathManipulator::reverseSubpaths()
{
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)
{
if (_selection.empty()) return;
_selection.transform(Geom::Translate(delta));
_done("Move nodes");
}
void MultiPathManipulator::showOutline(bool 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;
}
void MultiPathManipulator::showHandles(bool show)
{
invokeForAll(&PathManipulator::showHandles, show);
_show_handles = show;
}
void MultiPathManipulator::showPathDirection(bool show)
{
invokeForAll(&PathManipulator::showPathDirection, show);
_show_path_direction = show;
}
/**
* 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;
}
/**
* 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));
//}
}
void MultiPathManipulator::updateHandles()
{
invokeForAll(&PathManipulator::updateHandles);
}
bool MultiPathManipulator::event(Inkscape::UI::Tools::ToolBase *event_context, 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_KEY_bracketleft:
case GDK_KEY_braceleft:
pm.rotateHandle(n, which, 1, one_pixel);
break;
case GDK_KEY_bracketright:
case GDK_KEY_braceright:
pm.rotateHandle(n, which, -1, one_pixel);
break;
// adjust length
case GDK_KEY_period:
case GDK_KEY_greater:
pm.scaleHandle(n, which, 1, one_pixel);
break;
case GDK_KEY_comma:
case GDK_KEY_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 (key) {
case GDK_KEY_Insert:
case GDK_KEY_KP_Insert:
// Insert - insert nodes in the middle of selected segments
insertNodes();
return true;
case GDK_KEY_i:
case GDK_KEY_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_KEY_d:
case GDK_KEY_D:
if (held_only_shift(event->key)) {
duplicateNodes();
return true;
}
case GDK_KEY_j:
case GDK_KEY_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;
}
break;
case GDK_KEY_b:
case GDK_KEY_B:
if (held_only_shift(event->key)) {
// Shift+B - break nodes
breakNodes();
return true;
}
break;
case GDK_KEY_Delete:
case GDK_KEY_KP_Delete:
case GDK_KEY_BackSpace:
if (held_shift(event->key)) break;
if (held_alt(event->key)) {
// Alt+Delete - delete segments
deleteSegments();
} else {
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
guint mode = prefs->getInt("/tools/freehand/pen/freehand-mode", 0);
//if the trace is bspline ( mode 2)
if(mode==2){
// is this correct ?
if(del_preserves_shape ^ held_control(event->key))
deleteNodes(false);
else
deleteNodes(true);
}
else
deleteNodes(del_preserves_shape ^ held_control(event->key));
// Delete any selected gradient nodes as well
event_context->deleteSelectedDrag(held_control(event->key));
}
return true;
case GDK_KEY_c:
case GDK_KEY_C:
if (held_only_shift(event->key)) {
// Shift+C - make nodes cusp
setNodeType(NODE_CUSP);
return true;
}
break;
case GDK_KEY_s:
case GDK_KEY_S:
if (held_only_shift(event->key)) {
// Shift+S - make nodes smooth
setNodeType(NODE_SMOOTH);
return true;
}
break;
case GDK_KEY_a:
case GDK_KEY_A:
if (held_only_shift(event->key)) {
// Shift+A - make nodes auto-smooth
setNodeType(NODE_AUTO);
return true;
}
break;
case GDK_KEY_y:
case GDK_KEY_Y:
if (held_only_shift(event->key)) {
// Shift+Y - make nodes symmetric
setNodeType(NODE_SYMMETRIC);
return true;
}
break;
case GDK_KEY_r:
case GDK_KEY_R:
if (held_only_shift(event->key)) {
// Shift+R - reverse subpaths
reverseSubpaths();
return true;
}
break;
case GDK_KEY_l:
case GDK_KEY_L:
if (held_only_shift(event->key)) {
// Shift+L - make segments linear
setSegmentType(SEGMENT_STRAIGHT);
return true;
}
case GDK_KEY_u:
case GDK_KEY_U:
if (held_only_shift(event->key)) {
// Shift+U - 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_context, event)) return true;
}
break;
default: break;
}
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;
gchar const *key = NULL;
switch(cps) {
case COMMIT_MOUSE_MOVE:
reason = _("Move nodes");
break;
case COMMIT_KEYBOARD_MOVE_X:
reason = _("Move nodes horizontally");
key = "node:move:x";
break;
case COMMIT_KEYBOARD_MOVE_Y:
reason = _("Move nodes vertically");
key = "node:move:y";
break;
case COMMIT_MOUSE_ROTATE:
reason = _("Rotate nodes");
break;
case COMMIT_KEYBOARD_ROTATE:
reason = _("Rotate nodes");
key = "node:rotate";
break;
case COMMIT_MOUSE_SCALE_UNIFORM:
reason = _("Scale nodes uniformly");
break;
case COMMIT_MOUSE_SCALE:
reason = _("Scale nodes");
break;
case COMMIT_KEYBOARD_SCALE_UNIFORM:
reason = _("Scale nodes uniformly");
key = "node:scale:uniform";
break;
case COMMIT_KEYBOARD_SCALE_X:
reason = _("Scale nodes horizontally");
key = "node:scale:x";
break;
case COMMIT_KEYBOARD_SCALE_Y:
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;
case COMMIT_FLIP_Y:
reason = _("Flip nodes vertically");
break;
default: return;
}
_selection.signal_update.emit();
invokeForAll(&PathManipulator::writeXML);
if (key) {
DocumentUndo::maybeDone(sp_desktop_document(_desktop), key, SP_VERB_CONTEXT_NODE, reason);
} else {
DocumentUndo::done(sp_desktop_document(_desktop), SP_VERB_CONTEXT_NODE, reason);
}
signal_coords_changed.emit();
}
/** Commits changes to XML and adds undo stack entry. */
void MultiPathManipulator::_done(gchar const *reason, bool alert_LPE) {
invokeForAll(&PathManipulator::update, alert_LPE);
invokeForAll(&PathManipulator::writeXML);
DocumentUndo::done(sp_desktop_document(_desktop), SP_VERB_CONTEXT_NODE, reason);
signal_coords_changed.emit();
}
/** Commits changes to XML, adds undo stack entry and removes empty manipulators. */
void MultiPathManipulator::_doneWithCleanup(gchar const *reason, bool alert_LPE) {
_changed.block();
_done(reason, alert_LPE);
cleanup();
_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
/*
Local Variables:
mode:c++
c-file-style:"stroustrup"
c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
indent-tabs-mode:nil
fill-column:99
End:
*/
// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :