From: Krzysztof Kosiński Date: Sun, 14 Mar 2010 17:38:50 +0000 (+0100) Subject: Implement keyboard shortcuts for single handle adjustments. X-Git-Url: https://git.tokkee.org/?a=commitdiff_plain;h=d144437b1df1ce29b32a1b45b682420a7708d294;p=inkscape.git Implement keyboard shortcuts for single handle adjustments. Minor disambiguating cleanup in node.h. --- diff --git a/src/ui/tool/Makefile_insert b/src/ui/tool/Makefile_insert index e14943021..4640a3cea 100644 --- a/src/ui/tool/Makefile_insert +++ b/src/ui/tool/Makefile_insert @@ -12,6 +12,8 @@ ink_common_sources += \ ui/tool/event-utils.h \ ui/tool/manipulator.cpp \ ui/tool/manipulator.h \ + ui/tool/modifier-tracker.cpp \ + ui/tool/modifier-tracker.h \ ui/tool/multi-path-manipulator.cpp \ ui/tool/multi-path-manipulator.h \ ui/tool/node.cpp \ diff --git a/src/ui/tool/modifier-tracker.cpp b/src/ui/tool/modifier-tracker.cpp new file mode 100644 index 000000000..8c6033bc7 --- /dev/null +++ b/src/ui/tool/modifier-tracker.cpp @@ -0,0 +1,93 @@ +/** @file + * Fine-grained modifier tracker for event handling. + */ +/* Authors: + * Krzysztof Kosiński + * + * Copyright (C) 2009 Authors + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include +#include "ui/tool/event-utils.h" +#include "ui/tool/modifier-tracker.h" + +namespace Inkscape { +namespace UI { + +ModifierTracker::ModifierTracker() + : _left_shift(false) + , _right_shift(false) + , _left_ctrl(false) + , _right_ctrl(false) + , _left_alt(false) + , _right_alt(false) +{} + +bool ModifierTracker::event(GdkEvent *event) +{ + switch (event->type) { + case GDK_KEY_PRESS: + switch (shortcut_key(event->key)) { + case GDK_Shift_L: + _left_shift = true; + break; + case GDK_Shift_R: + _right_shift = true; + break; + case GDK_Control_L: + _left_ctrl = true; + break; + case GDK_Control_R: + _right_ctrl = true; + break; + case GDK_Alt_L: + _left_alt = true; + break; + case GDK_Alt_R: + _right_alt = true; + break; + } + break; + case GDK_KEY_RELEASE: + switch (shortcut_key(event->key)) { + case GDK_Shift_L: + _left_shift = false; + break; + case GDK_Shift_R: + _right_shift = false; + break; + case GDK_Control_L: + _left_ctrl = false; + break; + case GDK_Control_R: + _right_ctrl = false; + break; + case GDK_Alt_L: + _left_alt = false; + break; + case GDK_Alt_R: + _right_alt = false; + break; + } + break; + default: break; + } + + return false; +} + +} // 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:encoding=utf-8:textwidth=99 : diff --git a/src/ui/tool/modifier-tracker.h b/src/ui/tool/modifier-tracker.h new file mode 100644 index 000000000..55538ead6 --- /dev/null +++ b/src/ui/tool/modifier-tracker.h @@ -0,0 +1,54 @@ +/** @file + * Fine-grained modifier tracker for event handling. + */ +/* Authors: + * Krzysztof Kosiński + * + * Copyright (C) 2009 Authors + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef SEEN_UI_TOOL_MODIFIER_TRACKER_H +#define SEEN_UI_TOOL_MODIFIER_TRACKER_H + +#include + +namespace Inkscape { +namespace UI { + +class ModifierTracker { +public: + ModifierTracker(); + bool event(GdkEvent *); + + bool leftShift() const { return _left_shift; } + bool rightShift() const { return _right_shift; } + bool leftControl() const { return _left_ctrl; } + bool rightControl() const { return _right_ctrl; } + bool leftAlt() const { return _left_alt; } + bool rightAlt() const { return _right_alt; } + +private: + bool _left_shift; + bool _right_shift; + bool _left_ctrl; + bool _right_ctrl; + bool _left_alt; + bool _right_alt; +}; + +} // namespace UI +} // namespace Inkscape + +#endif // SEEN_UI_TOOL_MODIFIER_TRACKER_H + +/* + 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:encoding=utf-8:textwidth=99 : diff --git a/src/ui/tool/multi-path-manipulator.cpp b/src/ui/tool/multi-path-manipulator.cpp index 9accbd0ae..fe97058c4 100644 --- a/src/ui/tool/multi-path-manipulator.cpp +++ b/src/ui/tool/multi-path-manipulator.cpp @@ -440,9 +440,60 @@ void MultiPathManipulator::updateOutlineColors() bool MultiPathManipulator::event(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(*_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(); + + switch (key) { + // single handle functions + // rotation + case GDK_bracketleft: + case GDK_braceleft: + pm.rotateHandle(n, which, 1, one_pixel); + break; + case GDK_bracketright: + case GDK_braceright: + pm.rotateHandle(n, which, -1, one_pixel); + break; + // adjust length + case GDK_period: + case GDK_greater: + pm.scaleHandle(n, which, 1, one_pixel); + break; + case GDK_comma: + case GDK_less: + pm.scaleHandle(n, which, -1, one_pixel); + break; + } + return true; + } while(0); + } + + switch (event->type) { case GDK_KEY_PRESS: - switch (shortcut_key(event->key)) { + switch (key) { case GDK_Insert: case GDK_KP_Insert: // Insert - insert nodes in the middle of selected segments diff --git a/src/ui/tool/multi-path-manipulator.h b/src/ui/tool/multi-path-manipulator.h index 121818f97..181ae6d1d 100644 --- a/src/ui/tool/multi-path-manipulator.h +++ b/src/ui/tool/multi-path-manipulator.h @@ -16,6 +16,7 @@ #include "forward.h" #include "ui/tool/commit-events.h" #include "ui/tool/manipulator.h" +#include "ui/tool/modifier-tracker.h" #include "ui/tool/node.h" #include "ui/tool/node-types.h" #include "ui/tool/shape-record.h" @@ -110,6 +111,7 @@ public: PathSharedData const &_path_data; private: sigc::connection &_changed; + ModifierTracker _tracker; bool _show_handles; bool _show_outline; bool _show_path_direction; diff --git a/src/ui/tool/node.cpp b/src/ui/tool/node.cpp index ae924f694..b72da1374 100644 --- a/src/ui/tool/node.cpp +++ b/src/ui/tool/node.cpp @@ -1149,9 +1149,9 @@ NodeList::NodeList(SubpathList &splist) : _list(splist) , _closed(false) { - this->list = this; - this->next = this; - this->prev = this; + this->ln_list = this; + this->ln_next = this; + this->ln_prev = this; } NodeList::~NodeList() @@ -1161,13 +1161,13 @@ NodeList::~NodeList() bool NodeList::empty() { - return next == this; + return ln_next == this; } NodeList::size_type NodeList::size() { size_type sz = 0; - for (ListNode *ln = next; ln != this; ln = ln->next) ++sz; + for (ListNode *ln = ln_next; ln != this; ln = ln->ln_next) ++sz; return sz; } @@ -1198,11 +1198,11 @@ NodeList::iterator NodeList::before(double t, double *fracpart) NodeList::iterator NodeList::insert(iterator i, Node *x) { ListNode *ins = i._node; - x->next = ins; - x->prev = ins->prev; - ins->prev->next = x; - ins->prev = x; - x->ListNode::list = this; + x->ln_next = ins; + x->ln_prev = ins->ln_prev; + ins->ln_prev->ln_next = x; + ins->ln_prev = x; + x->ln_list = this; return iterator(x); } @@ -1221,48 +1221,48 @@ void NodeList::splice(iterator pos, NodeList &list, iterator i) void NodeList::splice(iterator pos, NodeList &/*list*/, iterator first, iterator last) { ListNode *ins_beg = first._node, *ins_end = last._node, *at = pos._node; - for (ListNode *ln = ins_beg; ln != ins_end; ln = ln->next) { - ln->list = this; + for (ListNode *ln = ins_beg; ln != ins_end; ln = ln->ln_next) { + ln->ln_list = this; } - ins_beg->prev->next = ins_end; - ins_end->prev->next = at; - at->prev->next = ins_beg; + ins_beg->ln_prev->ln_next = ins_end; + ins_end->ln_prev->ln_next = at; + at->ln_prev->ln_next = ins_beg; - ListNode *atprev = at->prev; - at->prev = ins_end->prev; - ins_end->prev = ins_beg->prev; - ins_beg->prev = atprev; + ListNode *atprev = at->ln_prev; + at->ln_prev = ins_end->ln_prev; + ins_end->ln_prev = ins_beg->ln_prev; + ins_beg->ln_prev = atprev; } void NodeList::shift(int n) { // 1. make the list perfectly cyclic - next->prev = prev; - prev->next = next; + ln_next->ln_prev = ln_prev; + ln_prev->ln_next = ln_next; // 2. find new begin - ListNode *new_begin = next; + ListNode *new_begin = ln_next; if (n > 0) { - for (; n > 0; --n) new_begin = new_begin->next; + for (; n > 0; --n) new_begin = new_begin->ln_next; } else { - for (; n < 0; ++n) new_begin = new_begin->prev; + for (; n < 0; ++n) new_begin = new_begin->ln_prev; } // 3. relink begin to list - next = new_begin; - prev = new_begin->prev; - new_begin->prev->next = this; - new_begin->prev = this; + ln_next = new_begin; + ln_prev = new_begin->ln_prev; + new_begin->ln_prev->ln_next = this; + new_begin->ln_prev = this; } void NodeList::reverse() { - for (ListNode *ln = next; ln != this; ln = ln->prev) { - std::swap(ln->next, ln->prev); + for (ListNode *ln = ln_next; ln != this; ln = ln->ln_prev) { + std::swap(ln->ln_next, ln->ln_prev); Node *node = static_cast(ln); Geom::Point save_pos = node->front()->position(); node->front()->setPosition(node->back()->position()); node->back()->setPosition(save_pos); } - std::swap(next, prev); + std::swap(ln_next, ln_prev); } void NodeList::clear() @@ -1275,11 +1275,11 @@ NodeList::iterator NodeList::erase(iterator i) // some gymnastics are required to ensure that the node is valid when deleted; // otherwise the code that updates handle visibility will break Node *rm = static_cast(i._node); - ListNode *rmnext = rm->next, *rmprev = rm->prev; + ListNode *rmnext = rm->ln_next, *rmprev = rm->ln_prev; ++i; delete rm; - rmprev->next = rmnext; - rmnext->prev = rmprev; + rmprev->ln_next = rmnext; + rmnext->ln_prev = rmprev; return i; } @@ -1296,10 +1296,10 @@ void NodeList::kill() } NodeList &NodeList::get(Node *n) { - return *(n->list()); + return n->nodeList(); } NodeList &NodeList::get(iterator const &i) { - return *(i._node->list); + return *(i._node->ln_list); } diff --git a/src/ui/tool/node.h b/src/ui/tool/node.h index c798a1fdb..e502ddea1 100644 --- a/src/ui/tool/node.h +++ b/src/ui/tool/node.h @@ -66,9 +66,9 @@ struct SubpathListMember : public ListMember { */ struct ListNode { - ListNode *next; - ListNode *prev; - NodeList *list; + ListNode *ln_next; + ListNode *ln_prev; + NodeList *ln_list; }; struct NodeSharedData { @@ -139,7 +139,7 @@ public: Handle *front() { return &_front; } Handle *back() { return &_back; } static NodeType parse_nodetype(char x); - NodeList *list() { return static_cast(this)->list; } + NodeList &nodeList() { return *(static_cast(this)->ln_list); } void sink(); static char const *node_type_to_localized_string(NodeType type); @@ -182,24 +182,28 @@ private: /// Iterator for editable nodes /** Use this class for all operations that require some knowledge about the node's - * neighbors. It works like a bidirectional iterator. + * neighbors. It is a bidirectional iterator. * * Because paths can be cyclic, node iterators have two different ways to - * increment and decrement them. Nodes can be iterated over either in the - * sequence order, which always has a beginning and an end, or in the path order, - * which can be cyclic (moving to the next node never yields the end iterator). + * increment and decrement them. When using ++/--, the end iterator will eventually + * be returned. Whent using advance()/retreat(), the end iterator will only be returned + * when the path is open. If it's closed, calling advance() will cycle indefinitely. + * This is particularly useful for cases where the adjacency of nodes is more important + * than their sequence order. * * When @a i is a node iterator, then: * - ++i moves the iterator to the next node in sequence order; * - --i moves the iterator to the previous node in sequence order; - * - i.next() returns the next node with wrap-around if the path is cyclic; - * - i.prev() returns the previous node with wrap-around if the path is cyclic. + * - i.next() returns the next node with wrap-around; + * - i.prev() returns the previous node with wrap-around; + * - i.advance() moves the iterator to the next node with wrap-around; + * - i.retreat() moves the iterator to the previous node with wrap-around. * * next() and prev() do not change their iterator. They can return the end iterator * if the path is open. * - * Unlike most other iterators, you can check whether a node iterator is invalid - * (is an end iterator) without having access to the iterator's container. + * Unlike most other iterators, you can check whether you've reached the end of the list + * without having access to the iterator's container. * Simply use if (i) { ... * */ template @@ -215,11 +219,11 @@ public: // default copy, default assign self &operator++() { - _node = _node->next; + _node = _node->ln_next; return *this; } self &operator--() { - _node = _node->prev; + _node = _node->ln_prev; return *this; } bool operator==(self const &other) const { return _node == other._node; } @@ -232,13 +236,14 @@ public: self next() const; self prev() const; + self &advance(); + self &retreat(); private: NodeIterator(ListNode const *n) : _node(const_cast(n)) {} ListNode *_node; friend class NodeList; - friend class std::tr1::hash; }; class NodeList : ListNode, boost::noncopyable, public boost::enable_shared_from_this { @@ -259,9 +264,9 @@ public: ~NodeList(); // iterators - iterator begin() { return iterator(next); } + iterator begin() { return iterator(ln_next); } iterator end() { return iterator(this); } - const_iterator begin() const { return const_iterator(next); } + const_iterator begin() const { return const_iterator(ln_next); } const_iterator end() const { return const_iterator(this); } reverse_iterator rbegin() { return reverse_iterator(end()); } reverse_iterator rend() { return reverse_iterator(begin()); } @@ -305,11 +310,12 @@ public: } // member access - undefined results when the list is empty - Node &front() { return *static_cast(next); } - Node &back() { return *static_cast(prev); } + Node &front() { return *static_cast(ln_next); } + Node &back() { return *static_cast(ln_prev); } // HACK remove this subpath from its path. This will be removed later. void kill(); + SubpathList &subpathList() { return _list; } static iterator get_iterator(Node *n) { return iterator(n); } static const_iterator get_iterator(Node const *n) { return const_iterator(n); } @@ -335,6 +341,7 @@ public: typedef std::list< boost::shared_ptr > list_type; SubpathList(PathManipulator &pm) : _path_manipulator(pm) {} + PathManipulator &pm() { return _path_manipulator; } private: list_type _nodelists; @@ -360,43 +367,32 @@ inline PathManipulator &Handle::_pm() { return _parent->_pm(); } inline PathManipulator &Node::_pm() { - return list()->_list._path_manipulator; + return nodeList().subpathList().pm(); } // definitions for node iterator template NodeIterator::operator bool() const { - return _node && static_cast(_node->list) != _node; + return _node && static_cast(_node->ln_list) != _node; } template NodeIterator NodeIterator::next() const { NodeIterator ret(*this); ++ret; - if (G_UNLIKELY(!ret) && _node->list->closed()) ++ret; + if (G_UNLIKELY(!ret) && _node->ln_list->closed()) ++ret; return ret; } template NodeIterator NodeIterator::prev() const { NodeIterator ret(*this); --ret; - if (G_UNLIKELY(!ret) && _node->list->closed()) --ret; + if (G_UNLIKELY(!ret) && _node->ln_list->closed()) --ret; return ret; } } // namespace UI } // namespace Inkscape -namespace std { -namespace tr1 { -template -struct hash< Inkscape::UI::NodeIterator > : public unary_function, size_t> { - size_t operator()(Inkscape::UI::NodeIterator const &ni) const { - return reinterpret_cast(ni._node); - } -}; -} -} - #endif /* diff --git a/src/ui/tool/path-manipulator.cpp b/src/ui/tool/path-manipulator.cpp index 0b0254108..d395d0e0a 100644 --- a/src/ui/tool/path-manipulator.cpp +++ b/src/ui/tool/path-manipulator.cpp @@ -598,10 +598,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; } @@ -728,6 +728,68 @@ 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 = h->relativePos(); + double rellen = relpos.length(); + h->setRelativePos(relpos * ((rellen + length_change) / rellen)); + 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); + 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) +{ + Geom::Point f = n->front()->position(), b = n->back()->position(); + if (which < 0) { + // pick left handle. + // we just swap the handles and pick the right handle below. + std::swap(f, b); + } + if (f[Geom::X] >= b[Geom::X]) { + return n->front(); + } else { + return n->back(); + } +} + /** Set the visibility of handles. */ void PathManipulator::showHandles(bool show) { @@ -1187,11 +1249,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); @@ -1304,6 +1366,13 @@ void PathManipulator::_commit(Glib::ustring const &annotation) sp_document_done(sp_desktop_document(_desktop), SP_VERB_CONTEXT_NODE, annotation.data()); } +void PathManipulator::_commit(Glib::ustring const &annotation, gchar const *key) +{ + writeXML(); + sp_document_maybe_done(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 * point of the path. */ void PathManipulator::_updateDragPoint(Geom::Point const &evp) diff --git a/src/ui/tool/path-manipulator.h b/src/ui/tool/path-manipulator.h index 1e9e50575..a8f1c957e 100644 --- a/src/ui/tool/path-manipulator.h +++ b/src/ui/tool/path-manipulator.h @@ -36,6 +36,7 @@ class CurveDragPoint; class PathCanvasGroups; class MultiPathManipulator; class Node; +class Handle; struct PathSharedData { NodeSharedData node_data; @@ -76,6 +77,9 @@ public: void reverseSubpaths(bool selected_only); void setSegmentType(SegmentType); + void scaleHandle(Node *n, int which, int dir, bool pixel); + void rotateHandle(Node *n, int which, int dir, bool pixel); + void showOutline(bool show); void showHandles(bool show); void showPathDirection(bool show); @@ -83,6 +87,7 @@ public: void setLiveObjects(bool set); void setControlsTransform(Geom::Matrix const &); void hideDragPoint(); + MultiPathManipulator &mpm() { return _multi_path_manipulator; } NodeList::iterator subdivideSegment(NodeList::iterator after, double t); NodeList::iterator extremeNode(NodeList::iterator origin, bool search_selected, @@ -113,9 +118,11 @@ private: void _externalChange(unsigned type); void _removeNodesFromSelection(); void _commit(Glib::ustring const &annotation); + void _commit(Glib::ustring const &annotation, gchar const *key); void _updateDragPoint(Geom::Point const &); void _updateOutlineOnZoomChange(); double _getStrokeTolerance(); + Handle *_chooseHandle(Node *n, int which); SubpathList _subpaths; MultiPathManipulator &_multi_path_manipulator;