multi-path-manipulator.cpp revision 4aeaa765c1779bb282e9cb7b8ad14cc1fae248ef
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * Multi path manipulator - implementation.
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * Krzysztof KosiĆski <tweenk.pl@gmail.com>
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * Abhishek Sharma
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * Copyright (C) 2009 Authors
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * Released under GNU GPL, read the file 'COPYING' for more information
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk : public std::unary_function<NodeList::iterator, std::size_t>
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk std::size_t operator()(NodeList::iterator i) const {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk return INK_HASH<NodeList::iterator::pointer>()(&*i);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenktypedef std::pair<NodeList::iterator, NodeList::iterator> IterPair;
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/** Find pairs of selected endnodes suitable for joining. */
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenkvoid find_join_iterators(ControlPointSelection &sel, IterPairList &pairs)
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk // find all endnodes in selection
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk for (ControlPointSelection::iterator i = sel.begin(); i != sel.end(); ++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 // 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 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/** 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.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 return true;
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 } else { // second is end
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 return false;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk} // anonymous namespace
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenkMultiPathManipulator::MultiPathManipulator(PathSharedData &data, sigc::connection &chg)
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk : PointManipulator(data.node_data.desktop, *data.node_data.selection)
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk sigc::mem_fun(*this, &MultiPathManipulator::_commit));
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk/** Remove empty manipulators. */
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk for (MapType::iterator i = _mmap.begin(); i != _mmap.end(); ) {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * Change the set of items to edit.
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk * This method attempts to preserve as much of the state as possible.
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenkvoid MultiPathManipulator::setItems(std::set<ShapeRecord> const &s)
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 // This item is no longer supposed to be edited - remove its manipulator
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 boost::shared_ptr<PathManipulator> hold(i->second);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk hold->setControlsTransform(sr_new.edit_transform);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk //hold->setOutlineColor(_getOutlineColor(sr_new.role));
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk shapes.erase(si); // remove the processed record
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 // always show outlines for clips and masks
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk newpm->showOutline(_show_outline || r.role != SHAPE_ROLE_NORMAL);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenkvoid MultiPathManipulator::shiftSelection(int dir)
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk if (empty()) return;
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 for (MapType::iterator i = _mmap.begin(); i != _mmap.end(); ++i) {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk for (SubpathList::iterator j = sp.begin(); j != sp.end(); ++j) {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk for (NodeList::iterator k = (*j)->begin(); k != (*j)->end(); ++k) {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk // when tabbing backwards, we want the first node
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 // select first / last node
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk // this should never fail because there must be at least 1 non-empty manipulator
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk _selection.insert((*_mmap.begin()->second->subpathList().begin())->begin().ptr());
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk _selection.insert((--(*--(--_mmap.end())->second->subpathList().end())->end()).ptr());
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk // three levels deep - w00t!
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk // here, last_k points to the node to be selected
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk if (last_j == last_i->second->subpathList().end()) {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk if (last_j == last_i->second->subpathList().begin()) {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenkvoid MultiPathManipulator::invertSelectionInSubpaths()
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk invokeForAll(&PathManipulator::invertSelectionInSubpaths);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenkvoid MultiPathManipulator::setNodeType(NodeType type)
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk // When all selected nodes are already cusp, retract their handles
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk for (ControlPointSelection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk for (ControlPointSelection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk _done(retract_handles ? _("Retract handles") : _("Change node type"));
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenkvoid MultiPathManipulator::setSegmentType(SegmentType type)
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk invokeForAll(&PathManipulator::setSegmentType, type);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenkvoid MultiPathManipulator::insertNodesAtExtrema(ExtremumType extremum)
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk invokeForAll(&PathManipulator::insertNodeAtExtremum, extremum);
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 Node *mouseover_node = dynamic_cast<Node*>(ControlPoint::mouseovered_point);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk preserve_pos = NodeList::get_iterator(mouseover_node);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk for (IterPairList::iterator i = joins.begin(); i != joins.end(); ++i) {
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk Geom::Point joined_pos, pos_handle_front, pos_handle_back;
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk // When we encounter the mouseover node, we unset the iterator - it will be invalidated
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk joined_pos = Geom::middle_point(*i->first, *i->second);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk // if the handles aren't degenerate, don't move them
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk joined_node->front()->setPosition(pos_handle_front);
4b8d88eb610aa1e0bb6ec632f792744b3d6b5f22jeff.schenk joined_node->back()->setPosition(pos_handle_back);
if (same_path) {
if (d == Geom::X) {
if (d == Geom::X) {
int which = 0;
bool handled = true;
switch (key) {
case GDK_KEY_bracketleft:
case GDK_KEY_braceleft:
case GDK_KEY_bracketright:
case GDK_KEY_braceright:
case GDK_KEY_period:
case GDK_KEY_greater:
case GDK_KEY_comma:
case GDK_KEY_less:
handled = false;
if (handled) return true;
case GDK_KEY_PRESS:
switch (key) {
case GDK_KEY_Insert:
case GDK_KEY_KP_Insert:
insertNodes();
case GDK_KEY_i:
case GDK_KEY_I:
insertNodes();
case GDK_KEY_d:
case GDK_KEY_D:
case GDK_KEY_j:
case GDK_KEY_J:
joinNodes();
joinSegments();
case GDK_KEY_b:
case GDK_KEY_B:
breakNodes();
case GDK_KEY_Delete:
case GDK_KEY_KP_Delete:
case GDK_KEY_BackSpace:
deleteNodes(false);
deleteNodes(true);
case GDK_KEY_c:
case GDK_KEY_C:
case GDK_KEY_s:
case GDK_KEY_S:
case GDK_KEY_a:
case GDK_KEY_A:
case GDK_KEY_y:
case GDK_KEY_Y:
case GDK_KEY_r:
case GDK_KEY_R:
case GDK_KEY_l:
case GDK_KEY_L:
case GDK_KEY_u:
case GDK_KEY_U:
case GDK_MOTION_NOTIFY:
switch(cps) {
case COMMIT_MOUSE_MOVE:
case COMMIT_KEYBOARD_MOVE_X:
case COMMIT_KEYBOARD_MOVE_Y:
case COMMIT_MOUSE_ROTATE:
case COMMIT_KEYBOARD_ROTATE:
case COMMIT_MOUSE_SCALE:
case COMMIT_KEYBOARD_SCALE_X:
case COMMIT_KEYBOARD_SCALE_Y:
case COMMIT_MOUSE_SKEW_X:
case COMMIT_MOUSE_SKEW_Y:
case COMMIT_FLIP_X:
case COMMIT_FLIP_Y:
if (key) {
cleanup();
switch(role) {
case SHAPE_ROLE_CLIPPING_PATH:
case SHAPE_ROLE_MASK:
case SHAPE_ROLE_LPE_PARAM:
case SHAPE_ROLE_NORMAL: