X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=src%2Fui%2Ftool%2Fpath-manipulator.cpp;h=ea7f3412d0de9fae0f60e6f434972fe4c8330525;hb=c580a96092327174256078a5ca30582608e82be7;hp=0b0254108d37e2851a7fa45a1189cd098902b109;hpb=14ac5d5ac7f9ee938d810194eefdba7bcfda3722;p=inkscape.git diff --git a/src/ui/tool/path-manipulator.cpp b/src/ui/tool/path-manipulator.cpp index 0b0254108..ea7f3412d 100644 --- a/src/ui/tool/path-manipulator.cpp +++ b/src/ui/tool/path-manipulator.cpp @@ -3,6 +3,7 @@ */ /* Authors: * Krzysztof Kosiński + * Abhishek Sharma * * Copyright (C) 2009 Authors * Released under GNU GPL, read the file 'COPYING' for more information @@ -108,7 +109,7 @@ PathManipulator::PathManipulator(MultiPathManipulator &mpm, SPPath *path, , _path(path) , _spcurve(new SPCurve()) , _dragpoint(new CurveDragPoint(*this)) - , _observer(new PathManipulatorObserver(this, SP_OBJECT(path)->repr)) + , /* XML Tree being used here directly while it shouldn't be*/_observer(new PathManipulatorObserver(this, SP_OBJECT(path)->getRepr())) , _edit_transform(et) , _num_selected(0) , _show_handles(true) @@ -119,7 +120,7 @@ PathManipulator::PathManipulator(MultiPathManipulator &mpm, SPPath *path, , _lpe_key(lpe_key) { if (_lpe_key.empty()) { - _i2d_transform = sp_item_i2d_affine(SP_ITEM(path)); + _i2d_transform = SP_ITEM(path)->i2d_affine(); } else { _i2d_transform = Geom::identity(); } @@ -214,7 +215,7 @@ void PathManipulator::clear() /** Select all nodes in subpaths that have something selected. */ void PathManipulator::selectSubpaths() { - for (std::list::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) { + for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) { NodeList::iterator sp_start = (*i)->begin(), sp_end = (*i)->end(); for (NodeList::iterator j = sp_start; j != sp_end; ++j) { if (j->selected()) { @@ -228,63 +229,6 @@ void PathManipulator::selectSubpaths() } } -/** Move the selection forward or backward by one node in each subpath, based on the sign - * of the parameter. */ -void PathManipulator::shiftSelection(int dir) -{ - if (dir == 0) return; - if (_num_selected == 0) { - // select the first node of the path. - SubpathList::iterator s = _subpaths.begin(); - if (s == _subpaths.end()) return; - NodeList::iterator n = (*s)->begin(); - if (n != (*s)->end()) - _selection.insert(n.ptr()); - return; - } - // We cannot do any tricks here, like iterating in different directions based on - // the sign and only setting the selection of nodes behind us, because it would break - // for closed paths. - for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) { - std::deque sels; // I hope this is specialized for bools! - unsigned num = 0; - - for (NodeList::iterator j = (*i)->begin(); j != (*i)->end(); ++j) { - sels.push_back(j->selected()); - _selection.erase(j.ptr()); - ++num; - } - if (num == 0) continue; // should never happen! zero-node subpaths are not allowed - - num = 0; - // In closed subpath, shift the selection cyclically. In an open one, - // let the selection 'slide into nothing' at ends. - if (dir > 0) { - if ((*i)->closed()) { - bool last = sels.back(); - sels.pop_back(); - sels.push_front(last); - } else { - sels.push_front(false); - } - } else { - if ((*i)->closed()) { - bool first = sels.front(); - sels.pop_front(); - sels.push_back(first); - } else { - sels.push_back(false); - num = 1; - } - } - - for (NodeList::iterator j = (*i)->begin(); j != (*i)->end(); ++j) { - if (sels[num]) _selection.insert(j.ptr()); - ++num; - } - } -} - /** Invert selection in the selected subpaths. */ void PathManipulator::invertSelectionInSubpaths() { @@ -319,6 +263,48 @@ void PathManipulator::insertNodes() } } +/** Insert new nodes exactly at the positions of selected nodes while preserving shape. + * This is equivalent to breaking, except that it doesn't split into subpaths. */ +void PathManipulator::duplicateNodes() +{ + if (_num_selected == 0) return; + + for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) { + for (NodeList::iterator j = (*i)->begin(); j != (*i)->end(); ++j) { + if (j->selected()) { + NodeList::iterator k = j.next(); + Node *n = new Node(_multi_path_manipulator._path_data.node_data, *j); + + if (k) { + // Move the new node to the bottom of the Z-order. This way you can drag all + // nodes that were selected before this operation without deselecting + // everything because there is a new node above. + n->sink(); + } + + n->front()->setPosition(*j->front()); + j->front()->retract(); + j->setType(NODE_CUSP, false); + (*i)->insert(k, n); + + if (k) { + // We need to manually call the selection change callback to refresh + // the handle display correctly. + // This call changes num_selected, but we call this once for a selected node + // and once for an unselected node, so in the end the number stays correct. + _selectionChanged(j.ptr(), true); + _selectionChanged(n, false); + } else { + // select the new end node instead of the node just before it + _selection.erase(j.ptr()); + _selection.insert(n); + break; // this was the end node, nothing more to do + } + } + } + } +} + /** Replace contiguous selections of nodes in each subpath with one node. */ void PathManipulator::weldNodes(NodeList::iterator preserve_pos) { @@ -598,10 +584,10 @@ unsigned PathManipulator::_deleteStretch(NodeList::iterator start, NodeList::ite // We can't use nl->erase(start, end), because it would break when the stretch // crosses the beginning of a closed subpath - NodeList *nl = start->list(); + NodeList &nl = start->nodeList(); while (start != end) { NodeList::iterator next = start.next(); - nl->erase(start); + nl.erase(start); start = next; } @@ -720,6 +706,7 @@ void PathManipulator::setSegmentType(SegmentType type) case SEGMENT_CUBIC_BEZIER: if (!j->front()->isDegenerate() || !k->back()->isDegenerate()) break; + // move both handles to 1/3 of the line j->front()->move(j->position() + (k->position() - j->position()) / 3); k->back()->move(k->position() + (j->position() - k->position()) / 3); break; @@ -728,6 +715,90 @@ void PathManipulator::setSegmentType(SegmentType type) } } +void PathManipulator::scaleHandle(Node *n, int which, int dir, bool pixel) +{ + if (n->type() == NODE_SYMMETRIC || n->type() == NODE_AUTO) { + n->setType(NODE_SMOOTH); + } + Handle *h = _chooseHandle(n, which); + double length_change; + + if (pixel) { + length_change = 1.0 / _desktop->current_zoom() * dir; + } else { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + length_change = prefs->getDoubleLimited("/options/defaultscale/value", 2, 1, 1000); + length_change *= dir; + } + + Geom::Point relpos; + if (h->isDegenerate()) { + if (dir < 0) return; + Node *nh = n->nodeToward(h); + if (!nh) return; + relpos = Geom::unit_vector(nh->position() - n->position()) * length_change; + } else { + relpos = h->relativePos(); + double rellen = relpos.length(); + relpos *= ((rellen + length_change) / rellen); + } + h->setRelativePos(relpos); + update(); + + gchar const *key = which < 0 ? "handle:scale:left" : "handle:scale:right"; + _commit(_("Scale handle"), key); +} + +void PathManipulator::rotateHandle(Node *n, int which, int dir, bool pixel) +{ + if (n->type() != NODE_CUSP) { + n->setType(NODE_CUSP); + } + Handle *h = _chooseHandle(n, which); + if (h->isDegenerate()) return; + + double angle; + if (pixel) { + // Rotate by "one pixel" + angle = atan2(1.0 / _desktop->current_zoom(), h->length()) * dir; + } else { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + int snaps = prefs->getIntLimited("/options/rotationsnapsperpi/value", 12, 1, 1000); + angle = M_PI * dir / snaps; + } + + h->setRelativePos(h->relativePos() * Geom::Rotate(angle)); + update(); + gchar const *key = which < 0 ? "handle:rotate:left" : "handle:rotate:right"; + _commit(_("Rotate handle"), key); +} + +Handle *PathManipulator::_chooseHandle(Node *n, int which) +{ + NodeList::iterator i = NodeList::get_iterator(n); + Node *prev = i.prev().ptr(); + Node *next = i.next().ptr(); + + // on an endnode, the remaining handle automatically wins + if (!next) return n->back(); + if (!prev) return n->front(); + + // compare X coord ofline segments + Geom::Point npos = next->position(); + Geom::Point ppos = prev->position(); + if (which < 0) { + // pick left handle. + // we just swap the handles and pick the right handle below. + std::swap(npos, ppos); + } + + if (npos[Geom::X] >= ppos[Geom::X]) { + return n->front(); + } else { + return n->back(); + } +} + /** Set the visibility of handles. */ void PathManipulator::showHandles(bool show) { @@ -874,6 +945,8 @@ NodeList::iterator PathManipulator::extremeNode(NodeList::iterator origin, bool /** Called by the XML observer when something else than us modifies the path. */ void PathManipulator::_externalChange(unsigned type) { + hideDragPoint(); + switch (type) { case PATH_CHANGE_D: { _getGeometry(); @@ -903,7 +976,7 @@ void PathManipulator::_externalChange(unsigned type) } break; case PATH_CHANGE_TRANSFORM: { Geom::Matrix i2d_change = _d2i_transform; - _i2d_transform = sp_item_i2d_affine(SP_ITEM(_path)); + _i2d_transform = SP_ITEM(_path)->i2d_affine(); _d2i_transform = _i2d_transform.inverse(); i2d_change *= _i2d_transform; for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) { @@ -983,7 +1056,10 @@ void PathManipulator::_createControlPointsFromGeometry() // we need to set the nodetypes after all the handles are in place, // so that pickBestType works correctly // TODO maybe migrate to inkscape:node-types? - gchar const *nts_raw = _path ? _path->repr->attribute(_nodetypesKey().data()) : 0; + // TODO move this into SPPath - do not manipulate directly + + //XML Tree being used here directly while it shouldn't be. + gchar const *nts_raw = _path ? _path->getRepr()->attribute(_nodetypesKey().data()) : 0; std::string nodetype_string = nts_raw ? nts_raw : ""; /* Calculate the needed length of the nodetype string. * For closed paths, the entry is duplicated for the starting node, @@ -1158,10 +1234,11 @@ void PathManipulator::_setGeometry() LIVEPATHEFFECT(_path)->requestModified(SP_OBJECT_MODIFIED_FLAG); } } else { - if (_path->repr->attribute("inkscape:original-d")) + //XML Tree being used here directly while it shouldn't be. + if (_path->getRepr()->attribute("inkscape:original-d")) sp_path_set_original_curve(_path, _spcurve, false, false); else - sp_shape_set_curve(SP_SHAPE(_path), _spcurve, false); + SP_SHAPE(_path)->setCurve(_spcurve, false); } } @@ -1176,8 +1253,10 @@ Glib::ustring PathManipulator::_nodetypesKey() * This method is wrong but necessary at the moment. */ Inkscape::XML::Node *PathManipulator::_getXMLNode() { - if (_lpe_key.empty()) return _path->repr; - return LIVEPATHEFFECT(_path)->repr; + //XML Tree being used here directly while it shouldn't be. + if (_lpe_key.empty()) return _path->getRepr(); + //XML Tree being used here directly while it shouldn't be. + return LIVEPATHEFFECT(_path)->getRepr(); } bool PathManipulator::_nodeClicked(Node *n, GdkEventButton *event) @@ -1187,11 +1266,11 @@ bool PathManipulator::_nodeClicked(Node *n, GdkEventButton *event) // Ctrl+Alt+click: delete nodes hideDragPoint(); NodeList::iterator iter = NodeList::get_iterator(n); - NodeList *nl = iter->list(); + NodeList &nl = iter->nodeList(); - if (nl->size() <= 1 || (nl->size() <= 2 && !nl->closed())) { + if (nl.size() <= 1 || (nl.size() <= 2 && !nl.closed())) { // Removing last node of closed path - delete it - nl->kill(); + nl.kill(); } else { // In other cases, delete the node under cursor _deleteStretch(iter, iter.next(), true); @@ -1206,17 +1285,11 @@ bool PathManipulator::_nodeClicked(Node *n, GdkEventButton *event) return true; } else if (held_control(*event)) { // Ctrl+click: cycle between node types - if (n->isEndNode()) { - if (n->type() == NODE_CUSP) { - n->setType(NODE_SMOOTH); - } else { - n->setType(NODE_CUSP); - } - } else { + if (!n->isEndNode()) { n->setType(static_cast((n->type() + 1) % NODE_LAST_REAL_TYPE)); + update(); + _commit(_("Cycle node type")); } - update(); - _commit(_("Cycle node type")); return true; } return false; @@ -1301,7 +1374,14 @@ void PathManipulator::_removeNodesFromSelection() void PathManipulator::_commit(Glib::ustring const &annotation) { writeXML(); - sp_document_done(sp_desktop_document(_desktop), SP_VERB_CONTEXT_NODE, annotation.data()); + DocumentUndo::done(sp_desktop_document(_desktop), SP_VERB_CONTEXT_NODE, annotation.data()); +} + +void PathManipulator::_commit(Glib::ustring const &annotation, gchar const *key) +{ + writeXML(); + DocumentUndo::maybeDone(sp_desktop_document(_desktop), key, SP_VERB_CONTEXT_NODE, + annotation.data()); } /** Update the position of the curve drag point such that it is over the nearest @@ -1321,7 +1401,10 @@ void PathManipulator::_updateDragPoint(Geom::Point const &evp) NodeList::iterator first = (*spi)->before(pvp->t, &fracpart); double stroke_tolerance = _getStrokeTolerance(); - if (Geom::distance(evp, nearest_point) < stroke_tolerance) { + if (first && first.next() && + fracpart != 0.0 && + Geom::distance(evp, nearest_point) < stroke_tolerance) + { _dragpoint->setVisible(true); _dragpoint->setPosition(_desktop->w2d(nearest_point)); _dragpoint->setSize(2 * stroke_tolerance); @@ -1366,4 +1449,4 @@ double PathManipulator::_getStrokeTolerance() 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 :