diff options
| author | Martin Owens <doctormo@gmail.com> | 2014-03-27 01:33:44 +0000 |
|---|---|---|
| committer | Martin Owens <doctormo@gmail.com> | 2014-03-27 01:33:44 +0000 |
| commit | 5a4fb2325f60d292b47330f540b26a3279341c90 (patch) | |
| tree | d2aa7967be25450b83e625025366c618101ae49f /src/ui/tool | |
| parent | The Polar Arrange Tab of the Arrange Dialog now hides the parametric (diff) | |
| parent | Remove Snap menu item and improve grid menu item text (diff) | |
| download | inkscape-5a4fb2325f60d292b47330f540b26a3279341c90.tar.gz inkscape-5a4fb2325f60d292b47330f540b26a3279341c90.zip | |
Commit a merge to trunk, with probabal errors
(bzr r11073.1.36)
Diffstat (limited to 'src/ui/tool')
24 files changed, 1018 insertions, 1425 deletions
diff --git a/src/ui/tool/Makefile_insert b/src/ui/tool/Makefile_insert index 2a72dd645..f46f48b72 100644 --- a/src/ui/tool/Makefile_insert +++ b/src/ui/tool/Makefile_insert @@ -19,8 +19,6 @@ ink_common_sources += \ ui/tool/node.cpp \ ui/tool/node.h \ ui/tool/node-types.h \ - ui/tool/node-tool.cpp \ - ui/tool/node-tool.h \ ui/tool/path-manipulator.cpp \ ui/tool/path-manipulator.h \ ui/tool/selectable-control-point.cpp \ diff --git a/src/ui/tool/control-point-selection.cpp b/src/ui/tool/control-point-selection.cpp index 1c66b91b6..d10ed0f0d 100644 --- a/src/ui/tool/control-point-selection.cpp +++ b/src/ui/tool/control-point-selection.cpp @@ -21,10 +21,6 @@ #include <gdk/gdkkeysyms.h> -#if !GTK_CHECK_VERSION(2,22,0) -#include "compat-key-syms.h" -#endif - namespace Inkscape { namespace UI { @@ -198,6 +194,8 @@ void ControlPointSelection::align(Geom::Dim2 axis) bound.unionWith(Geom::OptInterval((*i)->position()[d])); } + if (!bound) { return; } + double new_coord = bound->middle(); for (iterator i = _points.begin(); i != _points.end(); ++i) { Geom::Point pos = (*i)->position(); @@ -224,6 +222,8 @@ void ControlPointSelection::distribute(Geom::Dim2 d) bound.unionWith(Geom::OptInterval(pos[d])); } + if (!bound) { return; } + // now we iterate over the multimap and set aligned positions. double step = size() == 1 ? 0 : bound->extent() / (size() - 1); double start = bound->min(); @@ -268,7 +268,9 @@ void ControlPointSelection::toggleTransformHandlesMode() { if (_handles->mode() == TransformHandleSet::MODE_SCALE) { _handles->setMode(TransformHandleSet::MODE_ROTATE_SKEW); - if (size() == 1) _handles->rotationCenter().setVisible(false); + if (size() == 1) { + _handles->rotationCenter().setVisible(false); + } } else { _handles->setMode(TransformHandleSet::MODE_SCALE); } @@ -383,8 +385,9 @@ void ControlPointSelection::_pointChanged(SelectableControlPoint *p, bool select { _updateBounds(); _updateTransformHandles(false); - if (_bounds) + if (_bounds) { _handles->rotationCenter().move(_bounds->midpoint()); + } signal_point_changed.emit(p, selected); } @@ -583,7 +586,7 @@ void ControlPointSelection::_commitHandlesTransform(CommitEvent ce) signal_commit.emit(ce); } -bool ControlPointSelection::event(SPEventContext * /*event_context*/, GdkEvent *event) +bool ControlPointSelection::event(Inkscape::UI::Tools::ToolBase * /*event_context*/, GdkEvent *event) { // implement generic event handling that should apply for all control point selections here; // for example, keyboard moves and transformations. This way this functionality doesn't need diff --git a/src/ui/tool/control-point-selection.h b/src/ui/tool/control-point-selection.h index f5eb47026..a087e0455 100644 --- a/src/ui/tool/control-point-selection.h +++ b/src/ui/tool/control-point-selection.h @@ -90,7 +90,7 @@ public: void invertSelection(); void spatialGrow(SelectableControlPoint *origin, int dir); - virtual bool event(SPEventContext *, GdkEvent *); + virtual bool event(Inkscape::UI::Tools::ToolBase *, GdkEvent *); void transform(Geom::Affine const &m); void align(Geom::Dim2 d); diff --git a/src/ui/tool/control-point.cpp b/src/ui/tool/control-point.cpp index 68749cdff..e98c7b2a2 100644 --- a/src/ui/tool/control-point.cpp +++ b/src/ui/tool/control-point.cpp @@ -1,7 +1,3 @@ -/** - * @file - * Desktop-bound visual control object - implementation. - */ /* Authors: * Krzysztof Kosiński <tweenk.pl@gmail.com> * Jon A. Cruz <jon@joncruz.org> @@ -11,103 +7,46 @@ */ #include <iostream> +#include <gdk/gdkkeysyms.h> #include <gdkmm.h> -#include <gtkmm.h> #include <2geom/point.h> #include "desktop.h" #include "desktop-handles.h" #include "display/sp-canvas.h" #include "display/snap-indicator.h" -#include "event-context.h" +#include "ui/tools/tool-base.h" #include "message-context.h" #include "preferences.h" #include "snap-preferences.h" #include "sp-namedview.h" +#include "ui/control-manager.h" #include "ui/tool/control-point.h" #include "ui/tool/event-utils.h" #include "ui/tool/transform-handle-set.h" -#if !GTK_CHECK_VERSION(2,22,0) -#include "compat-key-syms.h" -#endif - namespace Inkscape { namespace UI { -// class and member documentation goes here... - -/** - * @class ControlPoint - * Draggable point, the workhorse of on-canvas editing. - * - * Control points (formerly known as knots) are graphical representations of some significant - * point in the drawing. The drawing can be changed by dragging the point and the things that are - * attached to it with the mouse. Example things that could be edited with draggable points - * are gradient stops, the place where text is attached to a path, text kerns, nodes and handles - * in a path, and many more. - * - * @par Control point event handlers - * @par - * The control point has several virtual methods which allow you to react to things that - * happen to it. The most important ones are the grabbed, dragged, ungrabbed and moved functions. - * When a drag happens, the order of calls is as follows: - * - <tt>grabbed()</tt> - * - <tt>dragged()</tt> - * - <tt>dragged()</tt> - * - <tt>dragged()</tt> - * - ... - * - <tt>dragged()</tt> - * - <tt>ungrabbed()</tt> - * - * The control point can also respond to clicks and double clicks. On a double click, - * clicked() is called, followed by doubleclicked(). When deriving from SelectableControlPoint, - * you need to manually call the superclass version at the appropriate point in your handler. - * - * @par Which method to override? - * @par - * You might wonder which hook to use when you want to do things when the point is relocated. - * Here are some tips: - * - If the point is used to edit an object, override the move() method. - * - If the point can usually be dragged wherever you like but can optionally be constrained - * to axes or the like, add a handler for <tt>signal_dragged</tt> that modifies its new - * position argument. - * - If the point has additional canvas items tied to it (like handle lines), override - * the setPosition() method. - */ - -/** - * @enum ControlPoint::State - * Enumeration representing the possible states of the control point, used to determine - * its appearance. - * @var ControlPoint::STATE_NORMAL - * Normal state - * @var ControlPoint::STATE_MOUSEOVER - * Mouse is hovering over the control point - * @var ControlPoint::STATE_CLICKED - * First mouse button pressed over the control point - */ // Default colors for control points -static ControlPoint::ColorSet default_color_set = { +ControlPoint::ColorSet ControlPoint::_default_color_set = { {0xffffff00, 0x01000000}, // normal fill, stroke {0xff0000ff, 0x01000000}, // mouseover fill, stroke - {0x0000ffff, 0x01000000} // clicked fill, stroke + {0x0000ffff, 0x01000000}, // clicked fill, stroke + // + {0x0000ffff, 0x000000ff}, // normal fill, stroke when selected + {0xff000000, 0x000000ff}, // mouseover fill, stroke when selected + {0xff000000, 0x000000ff} // clicked fill, stroke when selected }; -/** Holds the currently mouseovered control point. */ ControlPoint *ControlPoint::mouseovered_point = 0; -/** Emitted when the mouseovered point changes. The parameter is the new mouseovered point. - * When a point ceases to be mouseovered, the parameter will be NULL. */ sigc::signal<void, ControlPoint*> ControlPoint::signal_mouseover_change; -/** Stores the window point over which the cursor was during the last mouse button press */ Geom::Point ControlPoint::_drag_event_origin(Geom::infinity(), Geom::infinity()); -/** Stores the desktop point from which the last drag was initiated */ Geom::Point ControlPoint::_drag_origin(Geom::infinity(), Geom::infinity()); -/** Events which should be captured when a handle is being dragged. */ int const ControlPoint::_grab_event_mask = (GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK); @@ -115,69 +54,51 @@ int const ControlPoint::_grab_event_mask = (GDK_BUTTON_PRESS_MASK | GDK_BUTTON_R bool ControlPoint::_drag_initiated = false; bool ControlPoint::_event_grab = false; -/** A color set which you can use to create an invisible control that can still receive events. - * @relates ControlPoint */ -ControlPoint::ColorSet invisible_cset = { +ControlPoint::ColorSet ControlPoint::invisible_cset = { + {0x00000000, 0x00000000}, + {0x00000000, 0x00000000}, + {0x00000000, 0x00000000}, {0x00000000, 0x00000000}, {0x00000000, 0x00000000}, {0x00000000, 0x00000000} }; -/** - * Create a regular control point. - * Derive to have constructors with a reasonable number of parameters. - * - * @param d Desktop for this control - * @param initial_pos Initial position of the control point in desktop coordinates - * @param anchor Where is the control point rendered relative to its desktop coordinates - * @param shape Shape of the control point: square, diamond, circle... - * @param size Pixel size of the visual representation - * @param cset Colors of the point - * @param group The canvas group the point's canvas item should be created in - */ -ControlPoint::ControlPoint(SPDesktop *d, Geom::Point const &initial_pos, - SPAnchorType anchor, SPCtrlShapeType shape, - unsigned int size, ColorSet *cset, SPCanvasGroup *group) - : _desktop (d) - , _canvas_item (NULL) - , _cset (cset ? cset : &default_color_set) - , _state (STATE_NORMAL) - , _position (initial_pos) +ControlPoint::ControlPoint(SPDesktop *d, Geom::Point const &initial_pos, SPAnchorType anchor, + Glib::RefPtr<Gdk::Pixbuf> pixbuf, + ColorSet const &cset, SPCanvasGroup *group) : + _desktop(d), + _canvas_item(NULL), + _cset(cset), + _state(STATE_NORMAL), + _position(initial_pos), + _lurking(false) { _canvas_item = sp_canvas_item_new( - group ? group : sp_desktop_controls (_desktop), SP_TYPE_CTRL, - "anchor", (SPAnchorType) anchor, "size", (gdouble) size, "shape", shape, - "filled", TRUE, "fill_color", _cset->normal.fill, - "stroked", TRUE, "stroke_color", _cset->normal.stroke, + group ? group : sp_desktop_controls(_desktop), SP_TYPE_CTRL, + "anchor", (SPAnchorType) anchor, "size", (gdouble) pixbuf->get_width(), + "shape", SP_CTRL_SHAPE_BITMAP, "pixbuf", pixbuf->gobj(), + "filled", TRUE, "fill_color", _cset.normal.fill, + "stroked", TRUE, "stroke_color", _cset.normal.stroke, "mode", SP_CTRL_MODE_XOR, NULL); _commonInit(); } -/** - * Create a control point with a pixbuf-based visual representation. - * - * @param d Desktop for this control - * @param initial_pos Initial position of the control point in desktop coordinates - * @param anchor Where is the control point rendered relative to its desktop coordinates - * @param pixbuf Pixbuf to be used as the visual representation - * @param cset Colors of the point - * @param group The canvas group the point's canvas item should be created in - */ -ControlPoint::ControlPoint(SPDesktop *d, Geom::Point const &initial_pos, - SPAnchorType anchor, Glib::RefPtr<Gdk::Pixbuf> pixbuf, - ColorSet *cset, SPCanvasGroup *group) - : _desktop (d) - , _canvas_item (NULL) - , _cset(cset ? cset : &default_color_set) - , _position (initial_pos) +ControlPoint::ControlPoint(SPDesktop *d, Geom::Point const &initial_pos, SPAnchorType anchor, + ControlType type, + ColorSet const &cset, SPCanvasGroup *group) : + _desktop(d), + _canvas_item(NULL), + _cset(cset), + _state(STATE_NORMAL), + _position(initial_pos), + _lurking(false) { - _canvas_item = sp_canvas_item_new( - group ? group : sp_desktop_controls(_desktop), SP_TYPE_CTRL, - "anchor", (SPAnchorType) anchor, "size", (gdouble) pixbuf->get_width(), - "shape", SP_CTRL_SHAPE_BITMAP, "pixbuf", pixbuf->gobj(), - "filled", TRUE, "fill_color", _cset->normal.fill, - "stroked", TRUE, "stroke_color", _cset->normal.stroke, - "mode", SP_CTRL_MODE_XOR, NULL); + _canvas_item = ControlManager::getManager().createControl(group ? group : sp_desktop_controls(_desktop), type); + g_object_set(_canvas_item, + "anchor", anchor, + "filled", TRUE, "fill_color", _cset.normal.fill, + "stroked", TRUE, "stroke_color", _cset.normal.stroke, + "mode", SP_CTRL_MODE_XOR, NULL); _commonInit(); } @@ -190,7 +111,7 @@ ControlPoint::~ControlPoint() g_signal_handler_disconnect(G_OBJECT(_canvas_item), _event_handler_connection); //sp_canvas_item_hide(_canvas_item); - gtk_object_destroy(_canvas_item); + sp_canvas_item_destroy(_canvas_item); } void ControlPoint::_commonInit() @@ -200,29 +121,17 @@ void ControlPoint::_commonInit() G_CALLBACK(_event_handler), this); } -/** Relocate the control point without side effects. - * Overload this method only if there is an additional graphical representation - * that must be updated (like the lines that connect handles to nodes). If you override it, - * you must also call the superclass implementation of the method. - * @todo Investigate whether this method should be protected */ void ControlPoint::setPosition(Geom::Point const &pos) { _position = pos; SP_CTRL(_canvas_item)->moveto(pos); } -/** Move the control point to new position with side effects. - * This is called after each drag. Override this method if only some positions make sense - * for a control point (like a point that must always be on a path and can't modify it), - * or when moving a control point changes the positions of other points. */ void ControlPoint::move(Geom::Point const &pos) { setPosition(pos); } -/** Apply an arbitrary affine transformation to a control point. This is used - * by ControlPointSelection, and is important for things like nodes with handles. - * The default implementation simply moves the point according to the transform. */ void ControlPoint::transform(Geom::Affine const &m) { move(position() * m); } @@ -232,9 +141,6 @@ bool ControlPoint::visible() const return sp_canvas_item_is_visible(_canvas_item); } -/** Set the visibility of the control point. An invisible point is not drawn on the canvas - * and cannot receive any events. If you want to have an invisible point that can respond - * to events, use <tt>invisible_cset</tt> as its color set. */ void ControlPoint::setVisible(bool v) { if (v) sp_canvas_item_show(_canvas_item); @@ -287,9 +193,9 @@ void ControlPoint::_setSize(unsigned int size) g_object_set(_canvas_item, "size", (gdouble) size, NULL); } -void ControlPoint::_setShape(SPCtrlShapeType shape) +bool ControlPoint::_setControlType(Inkscape::ControlType type) { - g_object_set(_canvas_item, "shape", shape, NULL); + return ControlManager::getManager().setControlType(_canvas_item, type); } void ControlPoint::_setAnchor(SPAnchorType anchor) @@ -305,15 +211,36 @@ void ControlPoint::_setPixbuf(Glib::RefPtr<Gdk::Pixbuf> p) // re-routes events into the virtual function int ControlPoint::_event_handler(SPCanvasItem */*item*/, GdkEvent *event, ControlPoint *point) { + if ((point == NULL) || (point->_desktop == NULL)) { + return FALSE; + } return point->_eventHandler(point->_desktop->event_context, event) ? TRUE : FALSE; } // main event callback, which emits all other callbacks. -bool ControlPoint::_eventHandler(SPEventContext *event_context, GdkEvent *event) +bool ControlPoint::_eventHandler(Inkscape::UI::Tools::ToolBase *event_context, GdkEvent *event) { // NOTE the static variables below are shared for all points! // TODO handle clicks and drags from other buttons too + if (event == NULL) + { + return false; + } + + if (event_context == NULL) + { + return false; + } + if (_desktop == NULL) + { + return false; + } + if(event_context->desktop !=_desktop) + { + g_warning ("ControlPoint: desktop pointers not equal!"); + //return false; + } // offset from the pointer hotspot to the center of the grabbed knot in desktop coords static Geom::Point pointer_offset; // number of last doubleclicked button @@ -321,7 +248,8 @@ bool ControlPoint::_eventHandler(SPEventContext *event_context, GdkEvent *event) Inkscape::Preferences *prefs = Inkscape::Preferences::get(); int drag_tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100); - + GdkEventMotion em; + SPCanvas* Ca; switch(event->type) { case GDK_BUTTON_PRESS: @@ -347,18 +275,22 @@ bool ControlPoint::_eventHandler(SPEventContext *event_context, GdkEvent *event) return true; case GDK_MOTION_NOTIFY: - combine_motion_events(_desktop->canvas, event->motion, 0); - if (_event_grab && !_desktop->event_context->space_panning) { + Ca = _desktop->canvas; + em = event->motion; + combine_motion_events(Ca, em, 0); + if (_event_grab && ! event_context->space_panning) { _desktop->snapindicator->remove_snaptarget(); bool transferred = false; if (!_drag_initiated) { - bool t = fabs(event->motion.x - _drag_event_origin[Geom::X]) <= drag_tolerance && - fabs(event->motion.y - _drag_event_origin[Geom::Y]) <= drag_tolerance; - if (t) return true; + bool t = fabs(em.x - _drag_event_origin[Geom::X]) <= drag_tolerance && + fabs(em.y - _drag_event_origin[Geom::Y]) <= drag_tolerance; + if (t){ + return true; + } // if we are here, it means the tolerance was just exceeded. _drag_origin = _position; - transferred = grabbed(&event->motion); + transferred = grabbed(&em); // _drag_initiated might change during the above virtual call if (!_drag_initiated) { // this guarantees smooth redraws while dragging @@ -369,17 +301,16 @@ bool ControlPoint::_eventHandler(SPEventContext *event_context, GdkEvent *event) if (!transferred) { // dragging in progress Geom::Point new_pos = _desktop->w2d(event_point(event->motion)) + pointer_offset; - // the new position is passed by reference and can be changed in the handlers. - dragged(new_pos, &event->motion); + dragged(new_pos, &em); move(new_pos); - _updateDragTip(&event->motion); // update dragging tip after moving to new position + _updateDragTip(&em); // update dragging tip after moving to new position _desktop->scroll_to_point(new_pos); _desktop->set_coordinate_status(_position); - sp_event_context_snap_delay_handler(_desktop->event_context, NULL, + sp_event_context_snap_delay_handler(event_context, NULL, (gpointer) this, &event->motion, - DelayedSnapEvent::CONTROL_POINT_HANDLER); + Inkscape::UI::Tools::DelayedSnapEvent::CONTROL_POINT_HANDLER); } return true; } @@ -393,8 +324,9 @@ bool ControlPoint::_eventHandler(SPEventContext *event_context, GdkEvent *event) // We must snap at some point in time though, and this is our last chance) // PS: For other contexts this is handled already in sp_event_context_item_handler or // sp_event_context_root_handler - if (_desktop->event_context->_delayed_snap_event) { - sp_event_context_snap_watchdog_callback(_desktop->event_context->_delayed_snap_event); + //if (_desktop && _desktop->event_context && _desktop->event_context->_delayed_snap_event) { + if (event_context->_delayed_snap_event) { + sp_event_context_snap_watchdog_callback(event_context->_delayed_snap_event); } sp_canvas_item_ungrab(_canvas_item, event->button.time); @@ -429,8 +361,9 @@ bool ControlPoint::_eventHandler(SPEventContext *event_context, GdkEvent *event) if (_event_grab && !event->grab_broken.keyboard) { { ungrabbed(NULL); - if (_drag_initiated) + if (_drag_initiated) { _desktop->canvas->endForcedFullRedraws(); + } } _setState(STATE_NORMAL); _event_grab = false; @@ -442,14 +375,14 @@ bool ControlPoint::_eventHandler(SPEventContext *event_context, GdkEvent *event) // update tips on modifier state change // TODO add ESC keybinding as drag cancel case GDK_KEY_PRESS: - switch (get_group0_keyval(&event->key)) + switch (Inkscape::UI::Tools::get_group0_keyval(&event->key)) { case GDK_KEY_Escape: { // ignore Escape if this is not a drag if (!_drag_initiated) break; // temporarily disable snapping - we might snap to a different place than we were initially - sp_event_context_discard_delayed_snap_event(_desktop->event_context); + sp_event_context_discard_delayed_snap_event(event_context); SnapPreferences &snapprefs = _desktop->namedview->snap_manager.snapprefs; bool snap_save = snapprefs.getSnapEnabledGlobally(); snapprefs.setSnapEnabledGlobally(false); @@ -483,7 +416,8 @@ bool ControlPoint::_eventHandler(SPEventContext *event_context, GdkEvent *event) ungrabbed(NULL); // ungrabbed handlers can handle a NULL event snapprefs.setSnapEnabledGlobally(snap_save); - } return true; + } + return true; case GDK_KEY_Tab: {// Downcast from ControlPoint to TransformHandle, if possible // This is an ugly hack; we should have the transform handle intercept the keystrokes itself @@ -509,7 +443,9 @@ bool ControlPoint::_eventHandler(SPEventContext *event_context, GdkEvent *event) } // Do not break here, to allow for updating tooltips and such case GDK_KEY_RELEASE: - if (mouseovered_point != this) return false; + if (mouseovered_point != this){ + return false; + } if (_drag_initiated) { return true; // this prevents the tool from overwriting the drag tip } else { @@ -559,7 +495,9 @@ bool ControlPoint::_updateTip(unsigned state) bool ControlPoint::_updateDragTip(GdkEventMotion *event) { - if (!_hasDragTips()) return false; + if (!_hasDragTips()) { + return false; + } Glib::ustring tip = _getDragTip(event); if (!tip.empty()) { _desktop->event_context->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, @@ -581,16 +519,6 @@ void ControlPoint::_clearMouseover() } } -/** Transfer the grab to another point. This method allows one to create a draggable point - * that should be dragged instead of the one that received the grabbed signal. - * This is used to implement dragging out handles in the new node tool, for example. - * - * This method will NOT emit the ungrab signal of @c prev_point, because this would complicate - * using it with selectable control points. If you use this method while dragging, you must emit - * the ungrab signal yourself. - * - * Note that this will break horribly if you try to transfer grab between points in different - * desktops, which doesn't make much sense anyway. */ void ControlPoint::transferGrab(ControlPoint *prev_point, GdkEventMotion *event) { if (!_event_grab) return; @@ -608,42 +536,80 @@ void ControlPoint::transferGrab(ControlPoint *prev_point, GdkEventMotion *event) _setMouseover(this, event->state); } -/** - * Change the state of the knot. - * Alters the appearance of the knot to match one of the states: normal, mouseover - * or clicked. - */ void ControlPoint::_setState(State state) { ColorEntry current = {0, 0}; + ColorSet const &activeCset = (_isLurking()) ? invisible_cset : _cset; switch(state) { - case STATE_NORMAL: - current = _cset->normal; break; - case STATE_MOUSEOVER: - current = _cset->mouseover; break; - case STATE_CLICKED: - current = _cset->clicked; break; + case STATE_NORMAL: + current = activeCset.normal; + break; + case STATE_MOUSEOVER: + current = activeCset.mouseover; + break; + case STATE_CLICKED: + current = activeCset.clicked; + break; }; _setColors(current); _state = state; } + +void ControlPoint::_handleControlStyling() +{ + if (_canvas_item->ctrlType != CTRL_TYPE_UNKNOWN) { + ControlManager::getManager().updateItem(_canvas_item); + } +} + void ControlPoint::_setColors(ColorEntry colors) { g_object_set(_canvas_item, "fill_color", colors.fill, "stroke_color", colors.stroke, NULL); } +bool ControlPoint::_isLurking() +{ + return _lurking; +} + +void ControlPoint::_setLurking(bool lurking) +{ + if (lurking != _lurking) { + _lurking = lurking; + _setState(_state); // TODO refactor out common part + } +} + + bool ControlPoint::_is_drag_cancelled(GdkEventMotion *event) { return !event || event->x_root == -1; } // dummy implementations for handlers -// they are here to avoid unused param warnings -bool ControlPoint::grabbed(GdkEventMotion *) { return false; } -void ControlPoint::dragged(Geom::Point &, GdkEventMotion *) {} -void ControlPoint::ungrabbed(GdkEventButton *) {} -bool ControlPoint::clicked(GdkEventButton *) { return false; } -bool ControlPoint::doubleclicked(GdkEventButton *) { return false; } + +bool ControlPoint::grabbed(GdkEventMotion * /*event*/) +{ + return false; +} + +void ControlPoint::dragged(Geom::Point &/*new_pos*/, GdkEventMotion * /*event*/) +{ +} + +void ControlPoint::ungrabbed(GdkEventButton * /*event*/) +{ +} + +bool ControlPoint::clicked(GdkEventButton * /*event*/) +{ + return false; +} + +bool ControlPoint::doubleclicked(GdkEventButton * /*event*/) +{ + return false; +} } // namespace UI } // namespace Inkscape diff --git a/src/ui/tool/control-point.h b/src/ui/tool/control-point.h index 8d1b778a7..b3ed9545e 100644 --- a/src/ui/tool/control-point.h +++ b/src/ui/tool/control-point.h @@ -1,9 +1,8 @@ -/** @file - * Desktop-bound visual control object - */ /* Authors: * Krzysztof Kosiński <tweenk.pl@gmail.com> + * Jon A. Cruz <jon@joncruz.org> * + * Copyright (C) 2012 Authors * Copyright (C) 2009 Authors * Released under GNU GPL, read the file 'COPYING' for more information */ @@ -17,59 +16,149 @@ #include <sigc++/signal.h> #include <sigc++/trackable.h> #include <2geom/point.h> + +#include "ui/control-types.h" #include "util/accumulators.h" #include "display/sodipodi-ctrl.h" #include "enums.h" class SPDesktop; -class SPEventContext; namespace Inkscape { namespace UI { +namespace Tools { + +class ToolBase; + +} +} +} -// most of the documentation is in the .cpp file +namespace Inkscape { +namespace UI { +/** + * Draggable point, the workhorse of on-canvas editing. + * + * Control points (formerly known as knots) are graphical representations of some significant + * point in the drawing. The drawing can be changed by dragging the point and the things that are + * attached to it with the mouse. Example things that could be edited with draggable points + * are gradient stops, the place where text is attached to a path, text kerns, nodes and handles + * in a path, and many more. + * + * @par Control point event handlers + * @par + * The control point has several virtual methods which allow you to react to things that + * happen to it. The most important ones are the grabbed, dragged, ungrabbed and moved functions. + * When a drag happens, the order of calls is as follows: + * - <tt>grabbed()</tt> + * - <tt>dragged()</tt> + * - <tt>dragged()</tt> + * - <tt>dragged()</tt> + * - ... + * - <tt>dragged()</tt> + * - <tt>ungrabbed()</tt> + * + * The control point can also respond to clicks and double clicks. On a double click, + * clicked() is called, followed by doubleclicked(). When deriving from SelectableControlPoint, + * you need to manually call the superclass version at the appropriate point in your handler. + * + * @par Which method to override? + * @par + * You might wonder which hook to use when you want to do things when the point is relocated. + * Here are some tips: + * - If the point is used to edit an object, override the move() method. + * - If the point can usually be dragged wherever you like but can optionally be constrained + * to axes or the like, add a handler for <tt>signal_dragged</tt> that modifies its new + * position argument. + * - If the point has additional canvas items tied to it (like handle lines), override + * the setPosition() method. + */ class ControlPoint : boost::noncopyable, public sigc::trackable { public: typedef Inkscape::Util::ReverseInterruptible RInt; typedef Inkscape::Util::Interruptible Int; - // these have to be public, because GCC doesn't allow protected types in constructors, - // even if the constructors are protected themselves. - struct ColorEntry { - guint32 fill; - guint32 stroke; - }; - struct ColorSet { - ColorEntry normal; - ColorEntry mouseover; - ColorEntry clicked; - }; + + /** + * Enumeration representing the possible states of the control point, used to determine + * its appearance. + * + * @todo resolve this to be in sync with the five standard GTK states. + */ enum State { + /** Normal state. */ STATE_NORMAL, + + /** Mouse is hovering over the control point. */ STATE_MOUSEOVER, + + /** First mouse button pressed over the control point. */ STATE_CLICKED }; + /** + * Destructor + */ virtual ~ControlPoint(); /// @name Adjust the position of the control point /// @{ /** Current position of the control point. */ Geom::Point const &position() const { return _position; } + operator Geom::Point const &() { return _position; } + + /** + * Move the control point to new position with side effects. + * This is called after each drag. Override this method if only some positions make sense + * for a control point (like a point that must always be on a path and can't modify it), + * or when moving a control point changes the positions of other points. + */ virtual void move(Geom::Point const &pos); + + /** + * Relocate the control point without side effects. + * Overload this method only if there is an additional graphical representation + * that must be updated (like the lines that connect handles to nodes). If you override it, + * you must also call the superclass implementation of the method. + * @todo Investigate whether this method should be protected + */ virtual void setPosition(Geom::Point const &pos); + + /** + * Apply an arbitrary affine transformation to a control point. This is used + * by ControlPointSelection, and is important for things like nodes with handles. + * The default implementation simply moves the point according to the transform. + */ virtual void transform(Geom::Affine const &m); /// @} /// @name Toggle the point's visibility /// @{ bool visible() const; + + /** + * Set the visibility of the control point. An invisible point is not drawn on the canvas + * and cannot receive any events. If you want to have an invisible point that can respond + * to events, use <tt>invisible_cset</tt> as its color set. + */ virtual void setVisible(bool v); /// @} /// @name Transfer grab from another event handler /// @{ + /** + * Transfer the grab to another point. This method allows one to create a draggable point + * that should be dragged instead of the one that received the grabbed signal. + * This is used to implement dragging out handles in the new node tool, for example. + * + * This method will NOT emit the ungrab signal of @c prev_point, because this would complicate + * using it with selectable control points. If you use this method while dragging, you must emit + * the ungrab signal yourself. + * + * Note that this will break horribly if you try to transfer grab between points in different + * desktops, which doesn't make much sense anyway. + */ void transferGrab(ControlPoint *from, GdkEventMotion *event); /// @} @@ -84,109 +173,233 @@ public: /// @name Inspect the state of the control point /// @{ - State state() { return _state; } - bool mouseovered() { return this == mouseovered_point; } + State state() const { return _state; } + + bool mouseovered() const { return this == mouseovered_point; } /// @} + /** Holds the currently mouseovered control point. */ static ControlPoint *mouseovered_point; + + /** + * Emitted when the mouseovered point changes. The parameter is the new mouseovered point. + * When a point ceases to be mouseovered, the parameter will be NULL. + */ static sigc::signal<void, ControlPoint*> signal_mouseover_change; + static Glib::ustring format_tip(char const *format, ...) G_GNUC_PRINTF(1,2); // temporarily public, until snap delay is refactored a little - virtual bool _eventHandler(SPEventContext *event_context, GdkEvent *event); + virtual bool _eventHandler(Inkscape::UI::Tools::ToolBase *event_context, GdkEvent *event); + SPDesktop *const _desktop; ///< The desktop this control point resides on. protected: + + struct ColorEntry { + guint32 fill; + guint32 stroke; + }; + + /** + * Color entries for each possible state. + * @todo resolve this to be in sync with the five standard GTK states. + */ + struct ColorSet { + ColorEntry normal; + ColorEntry mouseover; + ColorEntry clicked; + ColorEntry selected_normal; + ColorEntry selected_mouseover; + ColorEntry selected_clicked; + }; + + /** + * A color set which you can use to create an invisible control that can still receive events. + */ + static ColorSet invisible_cset; + + /** + * Create a regular control point. + * Derive to have constructors with a reasonable number of parameters. + * + * @param d Desktop for this control + * @param initial_pos Initial position of the control point in desktop coordinates + * @param anchor Where is the control point rendered relative to its desktop coordinates + * @param type Logical type of the control point. + * @param cset Colors of the point + * @param group The canvas group the point's canvas item should be created in + */ ControlPoint(SPDesktop *d, Geom::Point const &initial_pos, SPAnchorType anchor, - SPCtrlShapeType shape, unsigned int size, ColorSet *cset = 0, SPCanvasGroup *group = 0); + ControlType type, + ColorSet const &cset = _default_color_set, SPCanvasGroup *group = 0); + + /** + * Create a control point with a pixbuf-based visual representation. + * + * @param d Desktop for this control + * @param initial_pos Initial position of the control point in desktop coordinates + * @param anchor Where is the control point rendered relative to its desktop coordinates + * @param pixbuf Pixbuf to be used as the visual representation + * @param cset Colors of the point + * @param group The canvas group the point's canvas item should be created in + */ ControlPoint(SPDesktop *d, Geom::Point const &initial_pos, SPAnchorType anchor, - Glib::RefPtr<Gdk::Pixbuf> pixbuf, ColorSet *cset = 0, SPCanvasGroup *group = 0); + Glib::RefPtr<Gdk::Pixbuf> pixbuf, + ColorSet const &cset = _default_color_set, SPCanvasGroup *group = 0); /// @name Handle control point events in subclasses /// @{ /** * Called when the user moves the point beyond the drag tolerance with the first button held - * down. Return true if you called transferGrab() during this method. - * @param event Motion event when drag tolerance was exceeded */ + * down. + * + * @param event Motion event when drag tolerance was exceeded. + * @return true if you called transferGrab() during this method. + */ virtual bool grabbed(GdkEventMotion *event); + /** * Called while dragging, but before moving the knot to new position. + * * @param pos Old position, always equal to position() * @param new_pos New position (after drag). This is passed as a non-const reference, * so you can change it from the handler - that's how constrained dragging is implemented. - * @param event Motion event */ + * @param event Motion event. + */ virtual void dragged(Geom::Point &new_pos, GdkEventMotion *event); + /** - * @var ControlPoint::signal_ungrabbed - * Emitted when the control point finishes a drag. + * Called when the control point finishes a drag. + * * @param event Button release event */ virtual void ungrabbed(GdkEventButton *event); + /** - * Called when the control point is clicked, at mouse button release. Your override should - * return true if the click had some effect. If it did nothing, return false. Improperly - * implementing this method can cause the default context menu not to appear when a control + * Called when the control point is clicked, at mouse button release. + * Improperly implementing this method can cause the default context menu not to appear when a control * point is right-clicked. - * @param event Button release event */ + * + * @param event Button release event + * @return true if the click had some effect, false if it did nothing. + */ virtual bool clicked(GdkEventButton *event); + /** * Called when the control point is doubleclicked, at mouse button release. - * @param event Button release event */ - virtual bool doubleclicked(GdkEventButton *); + * + * @param event Button release event + */ + virtual bool doubleclicked(GdkEventButton *event); /// @} /// @name Manipulate the control point's appearance in subclasses /// @{ + + /** + * Change the state of the knot. + * Alters the appearance of the knot to match one of the states: normal, mouseover + * or clicked. + */ virtual void _setState(State state); + + void _handleControlStyling(); + void _setColors(ColorEntry c); unsigned int _size() const; + SPCtrlShapeType _shape() const; + SPAnchorType _anchor() const; + Glib::RefPtr<Gdk::Pixbuf> _pixbuf(); void _setSize(unsigned int size); - void _setShape(SPCtrlShapeType shape); + + bool _setControlType(Inkscape::ControlType type); + void _setAnchor(SPAnchorType anchor); + void _setPixbuf(Glib::RefPtr<Gdk::Pixbuf>); + + /** + * Determins if the control point is not visible yet still reacting to events. + * + * @return true if non-visible, false otherwise. + */ + bool _isLurking(); + + /** + * Sets the control point to be non-visible yet still reacting to events. + * + * @param lurking true to make non-visible, false otherwise. + */ + void _setLurking(bool lurking); + /// @} - virtual Glib::ustring _getTip(unsigned /*state*/) { return ""; } - virtual Glib::ustring _getDragTip(GdkEventMotion */*event*/) { return ""; } - virtual bool _hasDragTips() { return false; } + virtual Glib::ustring _getTip(unsigned /*state*/) const { return ""; } + + virtual Glib::ustring _getDragTip(GdkEventMotion */*event*/) const { return ""; } + + virtual bool _hasDragTips() const { return false; } + - SPDesktop *const _desktop; ///< The desktop this control point resides on. SPCanvasItem * _canvas_item; ///< Visual representation of the control point. - ColorSet *_cset; ///< Colors used to represent the point + + ColorSet const &_cset; ///< Colors used to represent the point + State _state; static Geom::Point const &_last_click_event_point() { return _drag_event_origin; } + static Geom::Point const &_last_drag_origin() { return _drag_origin; } + static bool _is_drag_cancelled(GdkEventMotion *event); + + /** Events which should be captured when a handle is being dragged. */ static int const _grab_event_mask; private: + ControlPoint(ControlPoint const &other); + void operator=(ControlPoint const &other); static int _event_handler(SPCanvasItem *item, GdkEvent *event, ControlPoint *point); + static void _setMouseover(ControlPoint *, unsigned state); + static void _clearMouseover(); + bool _updateTip(unsigned state); + bool _updateDragTip(GdkEventMotion *event); + void _setDefaultColors(); + void _commonInit(); Geom::Point _position; ///< Current position in desktop coordinates + gulong _event_handler_connection; + bool _lurking; + + static ColorSet _default_color_set; + + /** Stores the window point over which the cursor was during the last mouse button press. */ static Geom::Point _drag_event_origin; + + /** Stores the desktop point from which the last drag was initiated. */ static Geom::Point _drag_origin; + static bool _event_grab; + static bool _drag_initiated; }; -extern ControlPoint::ColorSet invisible_cset; - } // namespace UI } // namespace Inkscape diff --git a/src/ui/tool/curve-drag-point.cpp b/src/ui/tool/curve-drag-point.cpp index 8754681df..4ca736f80 100644 --- a/src/ui/tool/curve-drag-point.cpp +++ b/src/ui/tool/curve-drag-point.cpp @@ -1,8 +1,6 @@ -/** @file - * Control point that is dragged during path drag - */ /* Authors: * Krzysztof Kosiński <tweenk.pl@gmail.com> + * Jon A. Cruz <jon@joncruz.org> * * Copyright (C) 2009 Authors * Released under GNU GPL, read the file 'COPYING' for more information @@ -21,31 +19,20 @@ namespace Inkscape { namespace UI { -/** - * @class CurveDragPoint - * An invisible point used to drag curves. This point is used by PathManipulator to allow editing - * of path segments by dragging them. It is defined in a separate file so that the node tool - * can check if the mouseovered control point is a curve drag point and update the cursor - * accordingly, without the need to drag in the full PathManipulator header. - */ - -// This point should be invisible to the user - use the invisible_cset from control-point.h -// TODO make some methods from path-manipulator.cpp public so that this point doesn't have -// to be declared as a friend bool CurveDragPoint::_drags_stroke = false; bool CurveDragPoint::_segment_was_degenerate = false; -CurveDragPoint::CurveDragPoint(PathManipulator &pm) - : ControlPoint(pm._multi_path_manipulator._path_data.node_data.desktop, Geom::Point(), - SP_ANCHOR_CENTER, SP_CTRL_SHAPE_CIRCLE, 1.0, &invisible_cset, - pm._multi_path_manipulator._path_data.dragpoint_group) - , _pm(pm) +CurveDragPoint::CurveDragPoint(PathManipulator &pm) : + ControlPoint(pm._multi_path_manipulator._path_data.node_data.desktop, Geom::Point(), SP_ANCHOR_CENTER, + CTRL_TYPE_INVISIPOINT, + invisible_cset, pm._multi_path_manipulator._path_data.dragpoint_group), + _pm(pm) { setVisible(false); } -bool CurveDragPoint::_eventHandler(SPEventContext *event_context, GdkEvent *event) +bool CurveDragPoint::_eventHandler(Inkscape::UI::Tools::ToolBase *event_context, GdkEvent *event) { // do not process any events when the manipulator is empty if (_pm.empty()) { @@ -170,7 +157,7 @@ void CurveDragPoint::_insertNode(bool take_selection) _pm._commit(_("Add node")); } -Glib::ustring CurveDragPoint::_getTip(unsigned state) +Glib::ustring CurveDragPoint::_getTip(unsigned state) const { if (_pm.empty()) return ""; if (!first || !first.next()) return ""; diff --git a/src/ui/tool/curve-drag-point.h b/src/ui/tool/curve-drag-point.h index 939047634..ea83978e0 100644 --- a/src/ui/tool/curve-drag-point.h +++ b/src/ui/tool/curve-drag-point.h @@ -1,8 +1,6 @@ -/** @file - * Control point that is dragged during path drag - */ /* Authors: * Krzysztof Kosiński <tweenk.pl@gmail.com> + * Jon A. Cruz <jon@joncruz.org> * * Copyright (C) 2009 Authors * Released under GNU GPL, read the file 'COPYING' for more information @@ -21,15 +19,27 @@ namespace UI { class PathManipulator; struct PathSharedData; +// This point should be invisible to the user - use the invisible_cset from control-point.h +// TODO make some methods from path-manipulator.cpp public so that this point doesn't have +// to be declared as a friend +/** + * An invisible point used to drag curves. This point is used by PathManipulator to allow editing + * of path segments by dragging them. It is defined in a separate file so that the node tool + * can check if the mouseovered control point is a curve drag point and update the cursor + * accordingly, without the need to drag in the full PathManipulator header. + */ class CurveDragPoint : public ControlPoint { public: + CurveDragPoint(PathManipulator &pm); void setSize(double sz) { _setSize(sz); } void setTimeValue(double t) { _t = t; } void setIterator(NodeList::iterator i) { first = i; } - virtual bool _eventHandler(SPEventContext *event_context, GdkEvent *event); + virtual bool _eventHandler(Inkscape::UI::Tools::ToolBase *event_context, GdkEvent *event); + protected: - virtual Glib::ustring _getTip(unsigned state); + + virtual Glib::ustring _getTip(unsigned state) const; virtual void dragged(Geom::Point &, GdkEventMotion *); virtual bool grabbed(GdkEventMotion *); virtual void ungrabbed(GdkEventButton *); @@ -37,10 +47,13 @@ protected: virtual bool doubleclicked(GdkEventButton *); private: + void _insertNode(bool take_selection); + double _t; PathManipulator &_pm; NodeList::iterator first; + static bool _drags_stroke; static bool _segment_was_degenerate; static Geom::Point _stroke_drag_origin; diff --git a/src/ui/tool/event-utils.cpp b/src/ui/tool/event-utils.cpp index 6b067a3ce..079275d63 100644 --- a/src/ui/tool/event-utils.cpp +++ b/src/ui/tool/event-utils.cpp @@ -14,10 +14,6 @@ #include "display/sp-canvas.h" #include "ui/tool/event-utils.h" -#if !GTK_CHECK_VERSION(2,22,0) -#include "compat-key-syms.h" -#endif - namespace Inkscape { namespace UI { @@ -59,6 +55,9 @@ unsigned combine_key_events(guint keyval, gint mask) unsigned combine_motion_events(SPCanvas *canvas, GdkEventMotion &event, gint mask) { + if (canvas == NULL) { + return false; + } GdkEvent *event_next; gint i = 0; event.x -= canvas->x0; @@ -66,7 +65,7 @@ unsigned combine_motion_events(SPCanvas *canvas, GdkEventMotion &event, gint mas event_next = gdk_event_get(); // while the next event is also a motion notify - while (event_next && event_next->type == GDK_MOTION_NOTIFY + while (event_next && (event_next->type == GDK_MOTION_NOTIFY) && (!mask || event_next->motion.state & mask)) { if (event_next->motion.device == event.device) { @@ -80,11 +79,7 @@ unsigned combine_motion_events(SPCanvas *canvas, GdkEventMotion &event, gint mas event.x_root = next.x_root; event.y_root = next.y_root; if (event.axes && next.axes) { -#if GTK_CHECK_VERSION(2,22,0) memcpy(event.axes, next.axes, gdk_device_get_n_axes(event.device)); -#else - memcpy(event.axes, next.axes, event.device->num_axes); -#endif } } @@ -94,8 +89,9 @@ unsigned combine_motion_events(SPCanvas *canvas, GdkEventMotion &event, gint mas i++; } // otherwise, put it back onto the queue - if (event_next) + if (event_next) { gdk_event_put(event_next); + } event.x += canvas->x0; event.y += canvas->y0; diff --git a/src/ui/tool/manipulator.h b/src/ui/tool/manipulator.h index ffed370d3..07e01a656 100644 --- a/src/ui/tool/manipulator.h +++ b/src/ui/tool/manipulator.h @@ -18,7 +18,7 @@ #include <glib.h> #include <gdk/gdk.h> #include <boost/shared_ptr.hpp> -#include "event-context.h" +#include "ui/tools/tool-base.h" class SPDesktop; namespace Inkscape { @@ -40,7 +40,7 @@ public: virtual ~Manipulator() {} /// Handle input event. Returns true if handled. - virtual bool event(SPEventContext *, GdkEvent *)=0; + virtual bool event(Inkscape::UI::Tools::ToolBase *, GdkEvent *)=0; SPDesktop *const _desktop; }; @@ -145,7 +145,7 @@ public: } } - virtual bool event(SPEventContext *event_context, GdkEvent *event) { + virtual bool event(Inkscape::UI::Tools::ToolBase *event_context, GdkEvent *event) { for (typename MapType::iterator i = _mmap.begin(); i != _mmap.end(); ++i) { if ((*i).second->event(event_context, event)) return true; } diff --git a/src/ui/tool/modifier-tracker.cpp b/src/ui/tool/modifier-tracker.cpp index 601c8334f..cc4e4d0b2 100644 --- a/src/ui/tool/modifier-tracker.cpp +++ b/src/ui/tool/modifier-tracker.cpp @@ -14,10 +14,6 @@ #include "ui/tool/modifier-tracker.h" #include <gtk/gtk.h> -#if !GTK_CHECK_VERSION(2,22,0) -#include "compat-key-syms.h" -#endif - namespace Inkscape { namespace UI { diff --git a/src/ui/tool/multi-path-manipulator.cpp b/src/ui/tool/multi-path-manipulator.cpp index 3fc202b69..65987ad52 100644 --- a/src/ui/tool/multi-path-manipulator.cpp +++ b/src/ui/tool/multi-path-manipulator.cpp @@ -11,9 +11,8 @@ */ #include <boost/shared_ptr.hpp> -#include <glib.h> -#include <glibmm/i18n.h> #include "node.h" +#include <glibmm/i18n.h> #include "desktop.h" #include "desktop-handles.h" #include "document.h" @@ -31,21 +30,6 @@ #include <gdk/gdkkeysyms.h> -#if !GTK_CHECK_VERSION(2,22,0) -#include "compat-key-syms.h" -#endif - -#ifdef USE_GNU_HASHES -namespace __gnu_cxx { -template<> -struct hash<Inkscape::UI::NodeList::iterator> { - size_t operator()(Inkscape::UI::NodeList::iterator const &n) const { - return reinterpret_cast<size_t>(n.ptr()); - } -}; -} // namespace __gnu_cxx -#endif // USE_GNU_HASHES - namespace Inkscape { namespace UI { @@ -69,7 +53,6 @@ typedef std::pair<double, IterPair> DistanceMapItem; void find_join_iterators(ControlPointSelection &sel, IterPairList &pairs) { IterSet join_iters; - DistanceMap dists; // find all endnodes in selection for (ControlPointSelection::iterator i = sel.begin(); i != sel.end(); ++i) { @@ -230,10 +213,12 @@ void MultiPathManipulator::shiftSelection(int dir) SubpathList::iterator last_j; NodeList::iterator last_k; bool anything_found = false; + bool anynode_found = false; for (MapType::iterator i = _mmap.begin(); i != _mmap.end(); ++i) { SubpathList &sp = i->second->subpathList(); for (SubpathList::iterator j = sp.begin(); j != sp.end(); ++j) { + anynode_found = true; for (NodeList::iterator k = (*j)->begin(); k != (*j)->end(); ++k) { if (k->selected()) { last_i = i; @@ -255,10 +240,12 @@ void MultiPathManipulator::shiftSelection(int dir) if (!anything_found) { // select first / last node // this should never fail because there must be at least 1 non-empty manipulator - if (dir == 1) { + if (anynode_found) { + if (dir == 1) { _selection.insert((*_mmap.begin()->second->subpathList().begin())->begin().ptr()); - } else { + } else { _selection.insert((--(*--(--_mmap.end())->second->subpathList().end())->end()).ptr()); + } } return; } @@ -341,23 +328,27 @@ void MultiPathManipulator::setSegmentType(SegmentType type) void MultiPathManipulator::insertNodes() { + if (_selection.empty()) return; invokeForAll(&PathManipulator::insertNodes); _done(_("Add nodes")); } void MultiPathManipulator::insertNodesAtExtrema(ExtremumType extremum) { + if (_selection.empty()) return; invokeForAll(&PathManipulator::insertNodeAtExtremum, extremum); _done(_("Add extremum nodes")); } void MultiPathManipulator::duplicateNodes() { + if (_selection.empty()) return; invokeForAll(&PathManipulator::duplicateNodes); _done(_("Duplicate nodes")); } void MultiPathManipulator::joinNodes() { + if (_selection.empty()) return; 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. @@ -435,6 +426,7 @@ void MultiPathManipulator::deleteNodes(bool keep_shape) /** Join selected endpoints to create segments. */ void MultiPathManipulator::joinSegments() { + if (_selection.empty()) return; IterPairList joins; find_join_iterators(_selection, joins); @@ -467,6 +459,7 @@ void MultiPathManipulator::deleteSegments() void MultiPathManipulator::alignNodes(Geom::Dim2 d) { + if (_selection.empty()) return; _selection.align(d); if (d == Geom::X) { _done("Align nodes to a horizontal line"); @@ -477,6 +470,7 @@ void MultiPathManipulator::alignNodes(Geom::Dim2 d) void MultiPathManipulator::distributeNodes(Geom::Dim2 d) { + if (_selection.empty()) return; _selection.distribute(d); if (d == Geom::X) { _done("Distrubute nodes horizontally"); @@ -498,6 +492,7 @@ void MultiPathManipulator::reverseSubpaths() void MultiPathManipulator::move(Geom::Point const &delta) { + if (_selection.empty()) return; _selection.transform(Geom::Translate(delta)); _done("Move nodes"); } @@ -554,7 +549,12 @@ void MultiPathManipulator::updateOutlineColors() //} } -bool MultiPathManipulator::event(SPEventContext *event_context, GdkEvent *event) +void MultiPathManipulator::updateHandles() +{ + invokeForAll(&PathManipulator::updateHandles); +} + +bool MultiPathManipulator::event(Inkscape::UI::Tools::ToolBase *event_context, GdkEvent *event) { _tracker.event(event); guint key = 0; @@ -671,6 +671,9 @@ bool MultiPathManipulator::event(SPEventContext *event_context, GdkEvent *event) // b) ctrl+del preserves shape (del_preserves_shape is false), and control is pressed // Hence xor 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: @@ -723,7 +726,7 @@ bool MultiPathManipulator::event(SPEventContext *event_context, GdkEvent *event) case GDK_KEY_u: case GDK_KEY_U: if (held_only_shift(event->key)) { - // Shift+L - make segments curves + // Shift+U - make segments curves setSegmentType(SEGMENT_CUBIC_BEZIER); return true; } diff --git a/src/ui/tool/multi-path-manipulator.h b/src/ui/tool/multi-path-manipulator.h index 2c05a1252..1328372c6 100644 --- a/src/ui/tool/multi-path-manipulator.h +++ b/src/ui/tool/multi-path-manipulator.h @@ -36,7 +36,7 @@ class MultiPathManipulator : public PointManipulator { public: MultiPathManipulator(PathSharedData &data, sigc::connection &chg); virtual ~MultiPathManipulator(); - virtual bool event(SPEventContext *, GdkEvent *event); + virtual bool event(Inkscape::UI::Tools::ToolBase *, GdkEvent *event); bool empty() { return _mmap.empty(); } unsigned size() { return _mmap.empty(); } @@ -70,6 +70,7 @@ public: void setLiveOutline(bool set); void setLiveObjects(bool set); void updateOutlineColors(); + void updateHandles(); sigc::signal<void> signal_coords_changed; /// Emitted whenever the coordinates /// shown in the status bar need updating diff --git a/src/ui/tool/node-tool.cpp b/src/ui/tool/node-tool.cpp deleted file mode 100644 index 7127ae968..000000000 --- a/src/ui/tool/node-tool.cpp +++ /dev/null @@ -1,695 +0,0 @@ -/** - * @file - * New node tool - implementation. - */ -/* Authors: - * Krzysztof Kosiński <tweenk@gmail.com> - * Abhishek Sharma - * - * Copyright (C) 2009 Authors - * Released under GNU GPL, read the file 'COPYING' for more information - */ - -#include "curve-drag-point.h" -#include <glib/gi18n.h> -#include "desktop.h" -#include "desktop-handles.h" -#include "display/sp-canvas-group.h" -#include "display/canvas-bpath.h" -#include "display/curve.h" -#include "display/sp-canvas.h" -#include "document.h" -#include "live_effects/lpeobject.h" -#include "message-context.h" -#include "selection.h" -#include "shape-editor.h" // temporary! -#include "sp-clippath.h" -#include "sp-item-group.h" -#include "sp-mask.h" -#include "sp-object-group.h" -#include "sp-path.h" -#include "sp-text.h" -#include "ui/tool/node-tool.h" -#include "ui/tool/control-point-selection.h" -#include "ui/tool/event-utils.h" -#include "ui/tool/manipulator.h" -#include "ui/tool/multi-path-manipulator.h" -#include "ui/tool/path-manipulator.h" -#include "ui/tool/selector.h" -#include "ui/tool/shape-record.h" - -#include "pixmaps/cursor-node.xpm" -#include "pixmaps/cursor-node-d.xpm" - -#include <gdk/gdkkeysyms.h> - -#if !GTK_CHECK_VERSION(2,22,0) -#include "compat-key-syms.h" -#endif - -/** @struct InkNodeTool - * - * Node tool event context. - * - * @par Architectural overview of the tool - * @par - * Here's a breakdown of what each object does. - * - Handle: shows a handle and keeps the node type constraint (smooth / symmetric) by updating - * the other handle's position when dragged. Its move() method cannot violate the constraints. - * - Node: keeps node type constraints for auto nodes and smooth nodes at ends of linear segments. - * Its move() method cannot violate constraints. Handles linear grow and dispatches spatial grow - * to MultiPathManipulator. Keeps a reference to its NodeList. - * - NodeList: exposes an iterator-based interface to nodes. It is possible to obtain an iterator - * to a node from the node. Keeps a reference to its SubpathList. - * - SubpathList: list of NodeLists that represents an editable pathvector. Keeps a reference - * to its PathManipulator. - * - PathManipulator: performs most of the single-path actions like reverse subpaths, - * delete segment, shift selection, etc. Keeps a reference to MultiPathManipulator. - * - MultiPathManipulator: performs additional operations for actions that are not per-path, - * for example node joins and segment joins. Tracks the control transforms for PMs that edit - * clipping paths and masks. It is more or less equivalent to ShapeEditor and in the future - * it might handle all shapes. Handles XML commit of actions that affect all paths or - * the node selection and removes PathManipulators that have no nodes left after e.g. node - * deletes. - * - ControlPointSelection: keeps track of node selection and a set of nodes that can potentially - * be selected. There can be more than one selection. Performs actions that require no - * knowledge about the path, only about the nodes, like dragging and transforms. It is not - * specific to nodes and can accomodate any control point derived from SelectableControlPoint. - * Transforms nodes in response to transform handle events. - * - TransformHandleSet: displays nodeset transform handles and emits transform events. The aim - * is to eventually use a common class for object and control point transforms. - * - SelectableControlPoint: base for any type of selectable point. It can belong to only one - * selection. - * - * @par Functionality that resides in weird places - * @par - * - * This list is probably incomplete. - * - Curve dragging: CurveDragPoint, controlled by PathManipulator - * - Single handle shortcuts: MultiPathManipulator::event(), ModifierTracker - * - Linear and spatial grow: Node, spatial grow routed to ControlPointSelection - * - Committing handle actions performed with the mouse: PathManipulator - * - Sculpting: ControlPointSelection - * - * @par Plans for the future - * @par - * - MultiPathManipulator should become a generic shape editor that manages all active manipulator, - * more or less like the old ShapeEditor. - * - Knotholder should be rewritten into one manipulator class per shape, using the control point - * classes. Interesting features like dragging rectangle sides could be added along the way. - * - Better handling of clip and mask editing, particularly in response to undo. - * - High level refactoring of the event context hierarchy. All aspects of tools, like toolbox - * controls, icons, event handling should be collected in one class, though each aspect - * of a tool might be in an separate class for better modularity. The long term goal is to allow - * tools to be defined in extensions or shared library plugins. - */ - -namespace { -SPCanvasGroup *create_control_group(SPDesktop *d); -void ink_node_tool_class_init(InkNodeToolClass *klass); -void ink_node_tool_init(InkNodeTool *node_context); -void ink_node_tool_dispose(GObject *object); - -void ink_node_tool_setup(SPEventContext *ec); -gint ink_node_tool_root_handler(SPEventContext *event_context, GdkEvent *event); -gint ink_node_tool_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event); -void ink_node_tool_set(SPEventContext *ec, Inkscape::Preferences::Entry *value); - -void ink_node_tool_update_tip(InkNodeTool *nt, GdkEvent *event); -void ink_node_tool_selection_changed(InkNodeTool *nt, Inkscape::Selection *sel); -void ink_node_tool_select_area(InkNodeTool *nt, Geom::Rect const &, GdkEventButton *); -void ink_node_tool_select_point(InkNodeTool *nt, Geom::Point const &, GdkEventButton *); -void ink_node_tool_mouseover_changed(InkNodeTool *nt, Inkscape::UI::ControlPoint *p); -} // anonymous namespace - -GType ink_node_tool_get_type() -{ - static GType type = 0; - if (!type) { - GTypeInfo info = { - sizeof(InkNodeToolClass), - NULL, NULL, - (GClassInitFunc) ink_node_tool_class_init, - NULL, NULL, - sizeof(InkNodeTool), - 4, - (GInstanceInitFunc) ink_node_tool_init, - NULL, /* value_table */ - }; - type = g_type_register_static(SP_TYPE_EVENT_CONTEXT, "InkNodeTool", &info, (GTypeFlags)0); - } - return type; -} - -namespace { - -SPCanvasGroup *create_control_group(SPDesktop *d) -{ - return reinterpret_cast<SPCanvasGroup*>(sp_canvas_item_new( - sp_desktop_controls(d), SP_TYPE_CANVAS_GROUP, NULL)); -} - -void destroy_group(SPCanvasGroup *g) -{ - gtk_object_destroy(GTK_OBJECT(g)); -} - -void ink_node_tool_class_init(InkNodeToolClass *klass) -{ - GObjectClass *object_class = (GObjectClass *) klass; - SPEventContextClass *event_context_class = (SPEventContextClass *) klass; - - object_class->dispose = ink_node_tool_dispose; - - event_context_class->setup = ink_node_tool_setup; - event_context_class->set = ink_node_tool_set; - event_context_class->root_handler = ink_node_tool_root_handler; - event_context_class->item_handler = ink_node_tool_item_handler; -} - -void ink_node_tool_init(InkNodeTool *nt) -{ - SPEventContext *event_context = SP_EVENT_CONTEXT(nt); - - event_context->cursor_shape = cursor_node_xpm; - event_context->hot_x = 1; - event_context->hot_y = 1; - - new (&nt->_selection_changed_connection) sigc::connection(); - new (&nt->_selection_modified_connection) sigc::connection(); - new (&nt->_mouseover_changed_connection) sigc::connection(); - //new (&nt->_mgroup) Inkscape::UI::ManipulatorGroup(nt->desktop); - new (&nt->_selected_nodes) CSelPtr(); - new (&nt->_multipath) MultiPathPtr(); - new (&nt->_selector) SelectorPtr(); - new (&nt->_path_data) PathSharedDataPtr(); - new (&nt->_shape_editors) ShapeEditors(); -} - -void ink_node_tool_dispose(GObject *object) -{ - InkNodeTool *nt = INK_NODE_TOOL(object); - - nt->enableGrDrag(false); - - nt->_selection_changed_connection.disconnect(); - nt->_selection_modified_connection.disconnect(); - nt->_mouseover_changed_connection.disconnect(); - nt->_multipath.~MultiPathPtr(); - nt->_selected_nodes.~CSelPtr(); - nt->_selector.~SelectorPtr(); - nt->_shape_editors.~ShapeEditors(); - - Inkscape::UI::PathSharedData &data = *nt->_path_data; - destroy_group(data.node_data.node_group); - destroy_group(data.node_data.handle_group); - destroy_group(data.node_data.handle_line_group); - destroy_group(data.outline_group); - destroy_group(data.dragpoint_group); - destroy_group(nt->_transform_handle_group); - - nt->_path_data.~PathSharedDataPtr(); - nt->_selection_changed_connection.~connection(); - nt->_selection_modified_connection.~connection(); - nt->_mouseover_changed_connection.~connection(); - - if (nt->_node_message_context) { - delete nt->_node_message_context; - } - - G_OBJECT_CLASS(g_type_class_peek(g_type_parent(INK_TYPE_NODE_TOOL)))->dispose(object); -} - -void ink_node_tool_setup(SPEventContext *ec) -{ - InkNodeTool *nt = INK_NODE_TOOL(ec); - - SPEventContextClass *parent = (SPEventContextClass *) g_type_class_peek(g_type_parent(INK_TYPE_NODE_TOOL)); - if (parent->setup) parent->setup(ec); - - nt->_node_message_context = new Inkscape::MessageContext((ec->desktop)->messageStack()); - - nt->_path_data.reset(new Inkscape::UI::PathSharedData()); - Inkscape::UI::PathSharedData &data = *nt->_path_data; - data.node_data.desktop = nt->desktop; - - // selector has to be created here, so that its hidden control point is on the bottom - nt->_selector.reset(new Inkscape::UI::Selector(nt->desktop)); - - // Prepare canvas groups for controls. This guarantees correct z-order, so that - // for example a dragpoint won't obscure a node - data.outline_group = create_control_group(nt->desktop); - data.node_data.handle_line_group = create_control_group(nt->desktop); - data.dragpoint_group = create_control_group(nt->desktop); - nt->_transform_handle_group = create_control_group(nt->desktop); - data.node_data.node_group = create_control_group(nt->desktop); - data.node_data.handle_group = create_control_group(nt->desktop); - - Inkscape::Selection *selection = sp_desktop_selection (ec->desktop); - nt->_selection_changed_connection.disconnect(); - nt->_selection_changed_connection = - selection->connectChanged( - sigc::bind<0>( - sigc::ptr_fun(&ink_node_tool_selection_changed), - nt)); - /*nt->_selection_modified_connection.disconnect(); - nt->_selection_modified_connection = - selection->connectModified( - sigc::hide(sigc::bind<0>( - sigc::ptr_fun(&ink_node_tool_selection_modified), - nt)));*/ - nt->_mouseover_changed_connection.disconnect(); - nt->_mouseover_changed_connection = - Inkscape::UI::ControlPoint::signal_mouseover_change.connect( - sigc::bind<0>( - sigc::ptr_fun(&ink_node_tool_mouseover_changed), - nt)); - - nt->_selected_nodes.reset( - new Inkscape::UI::ControlPointSelection(nt->desktop, nt->_transform_handle_group)); - data.node_data.selection = nt->_selected_nodes.get(); - nt->_multipath.reset(new Inkscape::UI::MultiPathManipulator(data, - nt->_selection_changed_connection)); - - nt->_selector->signal_point.connect( - sigc::bind<0>( - sigc::ptr_fun(&ink_node_tool_select_point), - nt)); - nt->_selector->signal_area.connect( - sigc::bind<0>( - sigc::ptr_fun(&ink_node_tool_select_area), - nt)); - - nt->_multipath->signal_coords_changed.connect( - sigc::bind( - sigc::mem_fun(*nt->desktop, &SPDesktop::emitToolSubselectionChanged), - (void*) 0)); - nt->_selected_nodes->signal_point_changed.connect( - sigc::hide( sigc::hide( - sigc::bind( - sigc::bind( - sigc::ptr_fun(ink_node_tool_update_tip), - (GdkEvent*)0), - nt)))); - - nt->cursor_drag = false; - nt->show_transform_handles = true; - nt->single_node_transform_handles = false; - nt->flash_tempitem = NULL; - nt->flashed_item = NULL; - nt->_last_over = NULL; - - // read prefs before adding items to selection to prevent momentarily showing the outline - sp_event_context_read(nt, "show_handles"); - sp_event_context_read(nt, "show_outline"); - sp_event_context_read(nt, "live_outline"); - sp_event_context_read(nt, "live_objects"); - sp_event_context_read(nt, "show_path_direction"); - sp_event_context_read(nt, "show_transform_handles"); - sp_event_context_read(nt, "single_node_transform_handles"); - sp_event_context_read(nt, "edit_clipping_paths"); - sp_event_context_read(nt, "edit_masks"); - - ink_node_tool_selection_changed(nt, selection); - ink_node_tool_update_tip(nt, NULL); - - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - if (prefs->getBool("/tools/nodes/selcue")) { - ec->enableSelectionCue(); - } - if (prefs->getBool("/tools/nodes/gradientdrag")) { - ec->enableGrDrag(); - } - - nt->desktop->emitToolSubselectionChanged(NULL); // sets the coord entry fields to inactive -} - -void ink_node_tool_set(SPEventContext *ec, Inkscape::Preferences::Entry *value) -{ - InkNodeTool *nt = INK_NODE_TOOL(ec); - Glib::ustring entry_name = value->getEntryName(); - - if (entry_name == "show_handles") { - nt->show_handles = value->getBool(true); - nt->_multipath->showHandles(nt->show_handles); - } else if (entry_name == "show_outline") { - nt->show_outline = value->getBool(); - nt->_multipath->showOutline(nt->show_outline); - } else if (entry_name == "live_outline") { - nt->live_outline = value->getBool(); - nt->_multipath->setLiveOutline(nt->live_outline); - } else if (entry_name == "live_objects") { - nt->live_objects = value->getBool(); - nt->_multipath->setLiveObjects(nt->live_objects); - } else if (entry_name == "show_path_direction") { - nt->show_path_direction = value->getBool(); - nt->_multipath->showPathDirection(nt->show_path_direction); - } else if (entry_name == "show_transform_handles") { - nt->show_transform_handles = value->getBool(true); - nt->_selected_nodes->showTransformHandles( - nt->show_transform_handles, nt->single_node_transform_handles); - } else if (entry_name == "single_node_transform_handles") { - nt->single_node_transform_handles = value->getBool(); - nt->_selected_nodes->showTransformHandles( - nt->show_transform_handles, nt->single_node_transform_handles); - } else if (entry_name == "edit_clipping_paths") { - nt->edit_clipping_paths = value->getBool(); - ink_node_tool_selection_changed(nt, nt->desktop->selection); - } else if (entry_name == "edit_masks") { - nt->edit_masks = value->getBool(); - ink_node_tool_selection_changed(nt, nt->desktop->selection); - } else { - SPEventContextClass *parent_class = - (SPEventContextClass *) g_type_class_peek(g_type_parent(INK_TYPE_NODE_TOOL)); - if (parent_class->set) - parent_class->set(ec, value); - } -} - -/** Recursively collect ShapeRecords */ -void gather_items(InkNodeTool *nt, SPItem *base, SPObject *obj, Inkscape::UI::ShapeRole role, - std::set<Inkscape::UI::ShapeRecord> &s) -{ - using namespace Inkscape::UI; - if (!obj) return; - - //XML Tree being used directly here while it shouldn't be. - if (SP_IS_PATH(obj) && obj->getRepr()->attribute("inkscape:original-d") != NULL) { - ShapeRecord r; - r.item = static_cast<SPItem*>(obj); - r.edit_transform = Geom::identity(); // TODO wrong? - r.role = role; - s.insert(r); - } else if (role != SHAPE_ROLE_NORMAL && (SP_IS_GROUP(obj) || SP_IS_OBJECTGROUP(obj))) { - for (SPObject *c = obj->children; c; c = c->next) { - gather_items(nt, base, c, role, s); - } - } else if (SP_IS_ITEM(obj)) { - SPItem *item = static_cast<SPItem*>(obj); - ShapeRecord r; - r.item = item; - // TODO add support for objectBoundingBox - r.edit_transform = base ? base->i2doc_affine() : Geom::identity(); - r.role = role; - if (s.insert(r).second) { - // this item was encountered the first time - if (nt->edit_clipping_paths && item->clip_ref) { - gather_items(nt, item, item->clip_ref->getObject(), SHAPE_ROLE_CLIPPING_PATH, s); - } - if (nt->edit_masks && item->mask_ref) { - gather_items(nt, item, item->mask_ref->getObject(), SHAPE_ROLE_MASK, s); - } - } - } -} - -void ink_node_tool_selection_changed(InkNodeTool *nt, Inkscape::Selection *sel) -{ - using namespace Inkscape::UI; - - std::set<ShapeRecord> shapes; - - GSList const *ilist = sel->itemList(); - - for (GSList *i = const_cast<GSList*>(ilist); i; i = i->next) { - SPObject *obj = static_cast<SPObject*>(i->data); - if (SP_IS_ITEM(obj)) { - gather_items(nt, NULL, static_cast<SPItem*>(obj), SHAPE_ROLE_NORMAL, shapes); - } - } - - // use multiple ShapeEditors for now, to allow editing many shapes at once - // needs to be rethought - for (ShapeEditors::iterator i = nt->_shape_editors.begin(); - i != nt->_shape_editors.end(); ) - { - ShapeRecord s; - s.item = i->first; - if (shapes.find(s) == shapes.end()) { - nt->_shape_editors.erase(i++); - } else { - ++i; - } - } - - for (std::set<ShapeRecord>::iterator i = shapes.begin(); i != shapes.end(); ++i) { - ShapeRecord const &r = *i; - if ((SP_IS_SHAPE(r.item) || SP_IS_TEXT(r.item)) && - nt->_shape_editors.find(r.item) == nt->_shape_editors.end()) - { - ShapeEditor *si = new ShapeEditor(nt->desktop); - si->set_item(r.item, SH_KNOTHOLDER); - nt->_shape_editors.insert(const_cast<SPItem*&>(r.item), si); - } - } - - nt->_multipath->setItems(shapes); - ink_node_tool_update_tip(nt, NULL); - nt->desktop->updateNow(); -} - -gint ink_node_tool_root_handler(SPEventContext *event_context, GdkEvent *event) -{ - /* things to handle here: - * 1. selection of items - * 2. passing events to manipulators - * 3. some keybindings - */ - using namespace Inkscape::UI; // pull in event helpers - - SPDesktop *desktop = event_context->desktop; - Inkscape::Selection *selection = desktop->selection; - InkNodeTool *nt = static_cast<InkNodeTool*>(event_context); - static Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - - if (nt->_multipath->event(event_context, event)) return true; - if (nt->_selector->event(event_context, event)) return true; - if (nt->_selected_nodes->event(event_context, event)) return true; - - switch (event->type) - { - case GDK_MOTION_NOTIFY: { - combine_motion_events(desktop->canvas, event->motion, 0); - SPItem *over_item = sp_event_context_find_item (desktop, event_point(event->button), - FALSE, TRUE); - if (over_item != nt->_last_over) { - nt->_last_over = over_item; - ink_node_tool_update_tip(nt, event); - } - - // create pathflash outline - if (prefs->getBool("/tools/nodes/pathflash_enabled")) { - if (over_item == nt->flashed_item) break; - if (!prefs->getBool("/tools/nodes/pathflash_selected") && selection->includes(over_item)) break; - if (nt->flash_tempitem) { - desktop->remove_temporary_canvasitem(nt->flash_tempitem); - nt->flash_tempitem = NULL; - nt->flashed_item = NULL; - } - if (!SP_IS_SHAPE(over_item)) break; // for now, handle only shapes - - nt->flashed_item = over_item; - SPCurve *c = SP_SHAPE(over_item)->getCurveBeforeLPE(); - if (!c) break; // break out when curve doesn't exist - c->transform(over_item->i2dt_affine()); - SPCanvasItem *flash = sp_canvas_bpath_new(sp_desktop_tempgroup(desktop), c); - sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(flash), - prefs->getInt("/tools/nodes/highlight_color", 0xff0000ff), 1.0, - SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT); - sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(flash), 0, SP_WIND_RULE_NONZERO); - nt->flash_tempitem = desktop->add_temporary_canvasitem(flash, - prefs->getInt("/tools/nodes/pathflash_timeout", 500)); - c->unref(); - } - } break; // do not return true, because we need to pass this event to the parent context - // otherwise some features cease to work - - case GDK_KEY_PRESS: - switch (get_group0_keyval(&event->key)) - { - case GDK_KEY_Escape: // deselect everything - if (nt->_selected_nodes->empty()) { - selection->clear(); - } else { - nt->_selected_nodes->clear(); - } - ink_node_tool_update_tip(nt, event); - return TRUE; - case GDK_KEY_a: - case GDK_KEY_A: - if (held_control(event->key) && held_alt(event->key)) { - nt->_selected_nodes->selectAll(); - // Ctrl+A is handled in selection-chemistry.cpp via verb - ink_node_tool_update_tip(nt, event); - return TRUE; - } - break; - case GDK_KEY_h: - case GDK_KEY_H: - if (held_only_control(event->key)) { - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - prefs->setBool("/tools/nodes/show_handles", !nt->show_handles); - return TRUE; - } - break; - default: - break; - } - ink_node_tool_update_tip(nt, event); - break; - case GDK_KEY_RELEASE: - ink_node_tool_update_tip(nt, event); - break; - default: break; - } - - SPEventContextClass *parent_class = (SPEventContextClass *) g_type_class_peek(g_type_parent(INK_TYPE_NODE_TOOL)); - if (parent_class->root_handler) - return parent_class->root_handler(event_context, event); - return FALSE; -} - -void ink_node_tool_update_tip(InkNodeTool *nt, GdkEvent *event) -{ - using namespace Inkscape::UI; - if (event && (event->type == GDK_KEY_PRESS || event->type == GDK_KEY_RELEASE)) { - unsigned new_state = state_after_event(event); - if (new_state == event->key.state) return; - if (state_held_shift(new_state)) { - if (nt->_last_over) { - nt->_node_message_context->set(Inkscape::NORMAL_MESSAGE, - C_("Node tool tip", "<b>Shift</b>: drag to add nodes to the selection, " - "click to toggle object selection")); - } else { - nt->_node_message_context->set(Inkscape::NORMAL_MESSAGE, - C_("Node tool tip", "<b>Shift</b>: drag to add nodes to the selection")); - } - return; - } - } - unsigned sz = nt->_selected_nodes->size(); - unsigned total = nt->_selected_nodes->allPoints().size(); - if (sz != 0) { - char *nodestring = g_strdup_printf( - ngettext("<b>%u of %u</b> node selected.", "<b>%u of %u</b> nodes selected.", total), - sz, total); - if (nt->_last_over) { - // TRANSLATORS: The %s below is where the "%u of %u nodes selected" sentence gets put - char *dyntip = g_strdup_printf(C_("Node tool tip", - "%s Drag to select nodes, click to edit only this object (more: Shift)"), - nodestring); - nt->_node_message_context->set(Inkscape::NORMAL_MESSAGE, dyntip); - g_free(dyntip); - } else { - char *dyntip = g_strdup_printf(C_("Node tool tip", - "%s Drag to select nodes, click clear the selection"), - nodestring); - nt->_node_message_context->set(Inkscape::NORMAL_MESSAGE, dyntip); - g_free(dyntip); - } - g_free(nodestring); - } else if (!nt->_multipath->empty()) { - if (nt->_last_over) { - nt->_node_message_context->set(Inkscape::NORMAL_MESSAGE, C_("Node tool tip", - "Drag to select nodes, click to edit only this object")); - } else { - nt->_node_message_context->set(Inkscape::NORMAL_MESSAGE, C_("Node tool tip", - "Drag to select nodes, click to clear the selection")); - } - } else { - if (nt->_last_over) { - nt->_node_message_context->set(Inkscape::NORMAL_MESSAGE, C_("Node tool tip", - "Drag to select objects to edit, click to edit this object (more: Shift)")); - } else { - nt->_node_message_context->set(Inkscape::NORMAL_MESSAGE, C_("Node tool tip", - "Drag to select objects to edit")); - } - } -} - -gint ink_node_tool_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event) -{ - SPEventContextClass *parent_class = - (SPEventContextClass *) g_type_class_peek(g_type_parent(INK_TYPE_NODE_TOOL)); - if (parent_class->item_handler) - return parent_class->item_handler(event_context, item, event); - return FALSE; -} - -void ink_node_tool_select_area(InkNodeTool *nt, Geom::Rect const &sel, GdkEventButton *event) -{ - using namespace Inkscape::UI; - if (nt->_multipath->empty()) { - // if multipath is empty, select rubberbanded items rather than nodes - Inkscape::Selection *selection = nt->desktop->selection; - GSList *items = sp_desktop_document(nt->desktop)->getItemsInBox(nt->desktop->dkey, sel); - selection->setList(items); - g_slist_free(items); - } else { - if (!held_shift(*event)) nt->_selected_nodes->clear(); - nt->_selected_nodes->selectArea(sel); - } -} -void ink_node_tool_select_point(InkNodeTool *nt, Geom::Point const &/*sel*/, GdkEventButton *event) -{ - using namespace Inkscape::UI; // pull in event helpers - if (!event) return; - if (event->button != 1) return; - - Inkscape::Selection *selection = nt->desktop->selection; - - SPItem *item_clicked = sp_event_context_find_item (nt->desktop, event_point(*event), - (event->state & GDK_MOD1_MASK) && !(event->state & GDK_CONTROL_MASK), TRUE); - - if (item_clicked == NULL) { // nothing under cursor - // if no Shift, deselect - // if there are nodes selected, the first click should deselect the nodes - // and the second should deselect the items - if (!state_held_shift(event->state)) { - if (nt->_selected_nodes->empty()) { - selection->clear(); - } else { - nt->_selected_nodes->clear(); - } - } - } else { - if (held_shift(*event)) { - selection->toggle(item_clicked); - } else { - selection->set(item_clicked); - } - nt->desktop->updateNow(); - } -} - -void ink_node_tool_mouseover_changed(InkNodeTool *nt, Inkscape::UI::ControlPoint *p) -{ - using Inkscape::UI::CurveDragPoint; - CurveDragPoint *cdp = dynamic_cast<CurveDragPoint*>(p); - if (cdp && !nt->cursor_drag) { - nt->cursor_shape = cursor_node_d_xpm; - nt->hot_x = 1; - nt->hot_y = 1; - sp_event_context_update_cursor(nt); - nt->cursor_drag = true; - } else if (!cdp && nt->cursor_drag) { - nt->cursor_shape = cursor_node_xpm; - nt->hot_x = 1; - nt->hot_y = 1; - sp_event_context_update_cursor(nt); - nt->cursor_drag = false; - } -} - -} // anonymous namespace - -/* - 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 : diff --git a/src/ui/tool/node-tool.h b/src/ui/tool/node-tool.h deleted file mode 100644 index 6f7ab01d4..000000000 --- a/src/ui/tool/node-tool.h +++ /dev/null @@ -1,95 +0,0 @@ -/** @file - * @brief New node tool with support for multiple path editing - */ -/* Authors: - * Krzysztof Kosiński <tweenk@gmail.com> - * - * Copyright (C) 2009 Authors - * Released under GNU GPL, read the file 'COPYING' for more information - */ - -#ifndef SEEN_UI_TOOL_NODE_TOOL_H -#define SEEN_UI_TOOL_NODE_TOOL_H - -#include <memory> -#include <boost/ptr_container/ptr_map.hpp> -#include <glib.h> -#include <stddef.h> -#include <sigc++/sigc++.h> -#include "event-context.h" -#include "ui/tool/node-types.h" - -#define INK_TYPE_NODE_TOOL (ink_node_tool_get_type ()) -#define INK_NODE_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), INK_TYPE_NODE_TOOL, InkNodeTool)) -#define INK_NODE_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), INK_TYPE_NODE_TOOL, InkNodeToolClass)) -#define INK_IS_NODE_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), INK_TYPE_NODE_TOOL)) -#define INK_IS_NODE_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), INK_TYPE_NODE_TOOL)) - -class InkNodeTool; -class InkNodeToolClass; - -namespace Inkscape { - -namespace Display { -class TemporaryItem; -} // namespace Display -namespace UI { -class MultiPathManipulator; -class ControlPointSelection; -class Selector; -struct PathSharedData; -} // namespace UI -} // namespace Inkscape - -typedef std::auto_ptr<Inkscape::UI::MultiPathManipulator> MultiPathPtr; -typedef std::auto_ptr<Inkscape::UI::ControlPointSelection> CSelPtr; -typedef std::auto_ptr<Inkscape::UI::Selector> SelectorPtr; -typedef std::auto_ptr<Inkscape::UI::PathSharedData> PathSharedDataPtr; -typedef boost::ptr_map<SPItem*, ShapeEditor> ShapeEditors; - -struct InkNodeTool : public SPEventContext -{ - sigc::connection _selection_changed_connection; - sigc::connection _mouseover_changed_connection; - sigc::connection _selection_modified_connection; - Inkscape::MessageContext *_node_message_context; - SPItem *flashed_item; - Inkscape::Display::TemporaryItem *flash_tempitem; - CSelPtr _selected_nodes; - MultiPathPtr _multipath; - SelectorPtr _selector; - PathSharedDataPtr _path_data; - SPCanvasGroup *_transform_handle_group; - SPItem *_last_over; - ShapeEditors _shape_editors; - - unsigned cursor_drag : 1; - unsigned show_handles : 1; - unsigned show_outline : 1; - unsigned live_outline : 1; - unsigned live_objects : 1; - unsigned show_path_direction : 1; - unsigned show_transform_handles : 1; - unsigned single_node_transform_handles : 1; - unsigned edit_clipping_paths : 1; - unsigned edit_masks : 1; -}; - -struct InkNodeToolClass { - SPEventContextClass parent_class; -}; - -GType ink_node_tool_get_type (void); - -#endif - -/* - 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 : diff --git a/src/ui/tool/node.cpp b/src/ui/tool/node.cpp index ad48b523b..fbbc4be64 100644 --- a/src/ui/tool/node.cpp +++ b/src/ui/tool/node.cpp @@ -1,7 +1,3 @@ -/** - * @file - * Editable node - implementation. - */ /* Authors: * Krzysztof Kosiński <tweenk.pl@gmail.com> * Jon A. Cruz <jon@joncruz.org> @@ -26,33 +22,57 @@ #include "preferences.h" #include "snap.h" #include "snap-preferences.h" -#include "sp-metrics.h" #include "sp-namedview.h" +#include "ui/control-manager.h" #include "ui/tool/control-point-selection.h" #include "ui/tool/event-utils.h" #include "ui/tool/node.h" #include "ui/tool/path-manipulator.h" #include <gdk/gdkkeysyms.h> -#if !GTK_CHECK_VERSION(2,22,0) -#include "compat-key-syms.h" -#endif +namespace { + +Inkscape::ControlType nodeTypeToCtrlType(Inkscape::UI::NodeType type) +{ + Inkscape::ControlType result = Inkscape::CTRL_TYPE_NODE_CUSP; + switch(type) { + case Inkscape::UI::NODE_SMOOTH: + result = Inkscape::CTRL_TYPE_NODE_SMOOTH; + break; + case Inkscape::UI::NODE_AUTO: + result = Inkscape::CTRL_TYPE_NODE_AUTO; + break; + case Inkscape::UI::NODE_SYMMETRIC: + result = Inkscape::CTRL_TYPE_NODE_SYMETRICAL; + break; + case Inkscape::UI::NODE_CUSP: + default: + result = Inkscape::CTRL_TYPE_NODE_CUSP; + break; + } + return result; +} + +} // namespace namespace Inkscape { namespace UI { -static SelectableControlPoint::ColorSet node_colors = { - { - {0xbfbfbf00, 0x000000ff}, // normal fill, stroke - {0xff000000, 0x000000ff}, // mouseover fill, stroke - {0xff000000, 0x000000ff} // clicked fill, stroke - }, +ControlPoint::ColorSet Node::node_colors = { + {0xbfbfbf00, 0x000000ff}, // normal fill, stroke + {0xff000000, 0x000000ff}, // mouseover fill, stroke + {0xff000000, 0x000000ff}, // clicked fill, stroke + // {0x0000ffff, 0x000000ff}, // normal fill, stroke when selected {0xff000000, 0x000000ff}, // mouseover fill, stroke when selected {0xff000000, 0x000000ff} // clicked fill, stroke when selected }; -static ControlPoint::ColorSet handle_colors = { +ControlPoint::ColorSet Handle::_handle_colors = { + {0xffffffff, 0x000000ff}, // normal fill, stroke + {0xff000000, 0x000000ff}, // mouseover fill, stroke + {0xff000000, 0x000000ff}, // clicked fill, stroke + // {0xffffffff, 0x000000ff}, // normal fill, stroke {0xff000000, 0x000000ff}, // mouseover fill, stroke {0xff000000, 0x000000ff} // clicked fill, stroke @@ -75,30 +95,27 @@ static Geom::Point direction(Geom::Point const &first, Geom::Point const &second return Geom::unit_vector(second - first); } -/** - * Control point of a cubic Bezier curve in a path. - * - * Handle keeps the node type invariant only for the opposite handle of the same node. - * Keeping the invariant on node moves is left to the %Node class. - */ Geom::Point Handle::_saved_other_pos(0, 0); + double Handle::_saved_length = 0.0; + bool Handle::_drag_out = false; -Handle::Handle(NodeSharedData const &data, Geom::Point const &initial_pos, Node *parent) - : ControlPoint(data.desktop, initial_pos, SP_ANCHOR_CENTER, SP_CTRL_SHAPE_CIRCLE, 7.0, - &handle_colors, data.handle_group) - , _parent(parent) - , _degenerate(true) +Handle::Handle(NodeSharedData const &data, Geom::Point const &initial_pos, Node *parent) : + ControlPoint(data.desktop, initial_pos, SP_ANCHOR_CENTER, + CTRL_TYPE_ADJ_HANDLE, + _handle_colors, data.handle_group), + _parent(parent), + _handle_line(ControlManager::getManager().createControlLine(data.handle_line_group)), + _degenerate(true) { - _cset = &handle_colors; - _handle_line = SP_CTRLLINE(sp_canvas_item_new(data.handle_line_group, SP_TYPE_CTRLLINE, NULL)); setVisible(false); } + Handle::~Handle() { //sp_canvas_item_hide(_handle_line); - gtk_object_destroy(_handle_line); + sp_canvas_item_destroy(_handle_line); } void Handle::setVisible(bool v) @@ -233,7 +250,7 @@ char const *Handle::handle_type_to_localized_string(NodeType type) } } -bool Handle::_eventHandler(SPEventContext *event_context, GdkEvent *event) +bool Handle::_eventHandler(Inkscape::UI::Tools::ToolBase *event_context, GdkEvent *event) { switch (event->type) { @@ -275,7 +292,7 @@ void Handle::dragged(Geom::Point &new_pos, GdkEventMotion *event) Geom::Point parent_pos = _parent->position(); Geom::Point origin = _last_drag_origin(); SnapManager &sm = _desktop->namedview->snap_manager; - bool snap = sm.someSnapperMightSnap(); + bool snap = held_shift(*event) ? false : sm.someSnapperMightSnap(); boost::optional<Inkscape::Snapper::SnapConstraint> ctrl_constraint; // with Alt, preserve length @@ -387,10 +404,18 @@ bool Handle::clicked(GdkEventButton *event) return true; } +Handle const *Handle::other() const +{ + return const_cast<Handle *>(this)->other(); +} + Handle *Handle::other() { - if (this == &_parent->_front) return &_parent->_back; - return &_parent->_front; + if (this == &_parent->_front) { + return &_parent->_back; + } else { + return &_parent->_front; + } } static double snap_increment_degrees() { @@ -399,7 +424,7 @@ static double snap_increment_degrees() { return 180.0 / snaps; } -Glib::ustring Handle::_getTip(unsigned state) +Glib::ustring Handle::_getTip(unsigned state) const { char const *more; bool can_shift_rotate = _parent->type() == NODE_CUSP && !other()->isDegenerate(); @@ -457,16 +482,20 @@ Glib::ustring Handle::_getTip(unsigned state) } } -Glib::ustring Handle::_getDragTip(GdkEventMotion */*event*/) +Glib::ustring Handle::_getDragTip(GdkEventMotion */*event*/) const { Geom::Point dist = position() - _last_drag_origin(); // report angle in mathematical convention double angle = Geom::angle_between(Geom::Point(-1,0), position() - _parent->position()); angle += M_PI; // angle is (-M_PI...M_PI] - offset by +pi and scale to 0...360 angle *= 360.0 / (2 * M_PI); - GString *x = SP_PX_TO_METRIC_STRING(dist[Geom::X], _desktop->namedview->getDefaultMetric()); - GString *y = SP_PX_TO_METRIC_STRING(dist[Geom::Y], _desktop->namedview->getDefaultMetric()); - GString *len = SP_PX_TO_METRIC_STRING(length(), _desktop->namedview->getDefaultMetric()); + + Inkscape::Util::Quantity x_q = Inkscape::Util::Quantity(dist[Geom::X], "px"); + Inkscape::Util::Quantity y_q = Inkscape::Util::Quantity(dist[Geom::Y], "px"); + Inkscape::Util::Quantity len_q = Inkscape::Util::Quantity(length(), "px"); + GString *x = g_string_new(x_q.string(_desktop->namedview->doc_units).c_str()); + GString *y = g_string_new(y_q.string(_desktop->namedview->doc_units).c_str()); + GString *len = g_string_new(len_q.string(_desktop->namedview->doc_units).c_str()); Glib::ustring ret = format_tip(C_("Path handle tip", "Move handle by %s, %s; angle %.2f°, length %s"), x->str, y->str, angle, len->str); g_string_free(x, TRUE); @@ -475,34 +504,48 @@ Glib::ustring Handle::_getDragTip(GdkEventMotion */*event*/) return ret; } -/** - * Curve endpoint in an editable path. - * - * The method move() keeps node type invariants during translations. - */ -Node::Node(NodeSharedData const &data, Geom::Point const &initial_pos) - : SelectableControlPoint(data.desktop, initial_pos, SP_ANCHOR_CENTER, - SP_CTRL_SHAPE_DIAMOND, 9.0, *data.selection, &node_colors, data.node_group) - , _front(data, initial_pos, this) - , _back(data, initial_pos, this) - , _type(NODE_CUSP) - , _handles_shown(false) +Node::Node(NodeSharedData const &data, Geom::Point const &initial_pos) : + SelectableControlPoint(data.desktop, initial_pos, SP_ANCHOR_CENTER, + CTRL_TYPE_NODE_CUSP, + *data.selection, + node_colors, data.node_group), + _front(data, initial_pos, this), + _back(data, initial_pos, this), + _type(NODE_CUSP), + _handles_shown(false) { // NOTE we do not set type here, because the handles are still degenerate } +Node const *Node::_next() const +{ + return const_cast<Node*>(this)->_next(); +} + // NOTE: not using iterators won't make this much quicker because iterators can be 100% inlined. Node *Node::_next() { NodeList::iterator n = NodeList::get_iterator(this).next(); - if (n) return n.ptr(); - return NULL; + if (n) { + return n.ptr(); + } else { + return NULL; + } +} + +Node const *Node::_prev() const +{ + return const_cast<Node *>(this)->_prev(); } + Node *Node::_prev() { NodeList::iterator p = NodeList::get_iterator(this).prev(); - if (p) return p.ptr(); - return NULL; + if (p) { + return p.ptr(); + } else { + return NULL; + } } void Node::move(Geom::Point const &new_pos) @@ -531,7 +574,7 @@ void Node::transform(Geom::Affine const &m) _fixNeighbors(old_pos, position()); } -Geom::Rect Node::bounds() +Geom::Rect Node::bounds() const { Geom::Rect b(position(), position()); b.expandTo(_front.position()); @@ -608,17 +651,23 @@ void Node::_updateAutoHandles() void Node::showHandles(bool v) { _handles_shown = v; - if (!_front.isDegenerate()) _front.setVisible(v); - if (!_back.isDegenerate()) _back.setVisible(v); + if (!_front.isDegenerate()) { + _front.setVisible(v); + } + if (!_back.isDegenerate()) { + _back.setVisible(v); + } } -/** Sets the node type and optionally restores the invariants associated with the given type. - * @param type The type to set - * @param update_handles Whether to restore invariants associated with the given type. - * Passing false is useful e.g. wen initially creating the path, - * and when making cusp nodes during some node algorithms. - * Pass true when used in response to an UI node type button. - */ +void Node::updateHandles() +{ + _handleControlStyling(); + + _front._handleControlStyling(); + _back._handleControlStyling(); +} + + void Node::setType(NodeType type, bool update_handles) { if (type == NODE_PICK_BEST) { @@ -711,12 +760,10 @@ void Node::setType(NodeType type, bool update_handles) } } _type = type; - _setShape(_node_type_to_shape(type)); + _setControlType(nodeTypeToCtrlType(_type)); updateState(); } -/** Pick the best type for this node, based on the position of its handles. - * This is what assigns types to nodes created using the pen tool. */ void Node::pickBestType() { _type = NODE_CUSP; @@ -762,17 +809,15 @@ void Node::pickBestType() } } } while (false); - _setShape(_node_type_to_shape(_type)); + _setControlType(nodeTypeToCtrlType(_type)); updateState(); } -bool Node::isEndNode() +bool Node::isEndNode() const { return !_prev() || !_next(); } -/** Move the node to the bottom of its canvas group. Useful for node break, to ensure that - * the selected nodes are above the unselected ones. */ void Node::sink() { sp_canvas_item_move_to_z(_canvas_item, 0); @@ -789,8 +834,7 @@ NodeType Node::parse_nodetype(char x) } } -/** Customized event handler to catch scroll events needed for selection grow/shrink. */ -bool Node::_eventHandler(SPEventContext *event_context, GdkEvent *event) +bool Node::_eventHandler(Inkscape::UI::Tools::ToolBase *event_context, GdkEvent *event) { int dir = 0; @@ -834,8 +878,6 @@ bool Node::_eventHandler(SPEventContext *event_context, GdkEvent *event) return ControlPoint::_eventHandler(event_context, event); } -/** Select or deselect a node in this node's subpath based on its path distance from this node. - * @param dir If negative, shrink selection by one node; if positive, grow by one node */ void Node::_linearGrow(int dir) { // Interestingly, we do not need any help from PathManipulator when doing linear grow. @@ -948,29 +990,36 @@ void Node::_linearGrow(int dir) void Node::_setState(State state) { // change node size to match type and selection state - switch (_type) { - case NODE_AUTO: - case NODE_CUSP: - if (selected()) _setSize(11); - else _setSize(9); - break; - default: - if(selected()) _setSize(9); - else _setSize(7); - break; + ControlManager &mgr = ControlManager::getManager(); + mgr.setSelected(_canvas_item, selected()); + switch (state) { + case STATE_NORMAL: + mgr.setActive(_canvas_item, false); + mgr.setPrelight(_canvas_item, false); + break; + case STATE_MOUSEOVER: + mgr.setActive(_canvas_item, false); + mgr.setPrelight(_canvas_item, true); + break; + case STATE_CLICKED: + mgr.setActive(_canvas_item, true); + mgr.setPrelight(_canvas_item, false); + break; } SelectableControlPoint::_setState(state); } bool Node::grabbed(GdkEventMotion *event) { - if (SelectableControlPoint::grabbed(event)) + if (SelectableControlPoint::grabbed(event)) { return true; + } // Dragging out handles with Shift + drag on a node. - if (!held_shift(*event)) return false; + if (!held_shift(*event)) { + return false; + } - Handle *h; Geom::Point evp = event_point(*event); Geom::Point rel_evp = evp - _last_click_event_point(); @@ -991,8 +1040,11 @@ bool Node::grabbed(GdkEventMotion *event) angle_prev = fabs(Geom::angle_between(rel_evp, prev_relpos)); has_degenerate = true; } - if (!has_degenerate) return false; - h = angle_next < angle_prev ? &_front : &_back; + if (!has_degenerate) { + return false; + } + + Handle *h = angle_next < angle_prev ? &_front : &_back; h->setPosition(_desktop->w2d(evp)); h->setVisible(true); @@ -1138,13 +1190,13 @@ bool Node::clicked(GdkEventButton *event) return SelectableControlPoint::clicked(event); } -Inkscape::SnapSourceType Node::_snapSourceType() +Inkscape::SnapSourceType Node::_snapSourceType() const { if (_type == NODE_SMOOTH || _type == NODE_AUTO) return SNAPSOURCE_NODE_SMOOTH; return SNAPSOURCE_NODE_CUSP; } -Inkscape::SnapTargetType Node::_snapTargetType() +Inkscape::SnapTargetType Node::_snapTargetType() const { if (_type == NODE_SMOOTH || _type == NODE_AUTO) return SNAPTARGET_NODE_SMOOTH; @@ -1156,10 +1208,6 @@ Inkscape::SnapCandidatePoint Node::snapCandidatePoint() return SnapCandidatePoint(position(), _snapSourceType(), _snapTargetType()); } -/** - * Gets the handle that faces the given adjacent node. - * Will abort with error if the given node is not adjacent. - */ Handle *Node::handleToward(Node *to) { if (_next() == to) { @@ -1169,12 +1217,9 @@ Handle *Node::handleToward(Node *to) return back(); } g_error("Node::handleToward(): second node is not adjacent!"); + return NULL; } -/** - * Gets the node in the direction of the given handle. - * Will abort with error if the handle doesn't belong to this node. - */ Node *Node::nodeToward(Handle *dir) { if (front() == dir) { @@ -1184,12 +1229,9 @@ Node *Node::nodeToward(Handle *dir) return _prev(); } g_error("Node::nodeToward(): handle is not a child of this node!"); + return NULL; } -/** - * Gets the handle that goes in the direction opposite to the given adjacent node. - * Will abort with error if the given node is not adjacent. - */ Handle *Node::handleAwayFrom(Node *to) { if (_next() == to) { @@ -1199,12 +1241,9 @@ Handle *Node::handleAwayFrom(Node *to) return front(); } g_error("Node::handleAwayFrom(): second node is not adjacent!"); + return NULL; } -/** - * Gets the node in the direction opposite to the given handle. - * Will abort with error if the handle doesn't belong to this node. - */ Node *Node::nodeAwayFrom(Handle *h) { if (front() == h) { @@ -1214,9 +1253,10 @@ Node *Node::nodeAwayFrom(Handle *h) return _next(); } g_error("Node::nodeAwayFrom(): handle is not a child of this node!"); + return NULL; } -Glib::ustring Node::_getTip(unsigned state) +Glib::ustring Node::_getTip(unsigned state) const { if (state_held_shift(state)) { bool can_drag_out = (_next() && _front.isDegenerate()) || (_prev() && _back.isDegenerate()); @@ -1258,13 +1298,15 @@ Glib::ustring Node::_getTip(unsigned state) "<b>%s</b>: drag to shape the path, click to select only this node (more: Shift, Ctrl, Alt)"), nodetype); } -Glib::ustring Node::_getDragTip(GdkEventMotion */*event*/) +Glib::ustring Node::_getDragTip(GdkEventMotion */*event*/) const { Geom::Point dist = position() - _last_drag_origin(); - GString *x = SP_PX_TO_METRIC_STRING(dist[Geom::X], _desktop->namedview->getDefaultMetric()); - GString *y = SP_PX_TO_METRIC_STRING(dist[Geom::Y], _desktop->namedview->getDefaultMetric()); - Glib::ustring ret = format_tip(C_("Path node tip", "Move node by %s, %s"), - x->str, y->str); + + Inkscape::Util::Quantity x_q = Inkscape::Util::Quantity(dist[Geom::X], "px"); + Inkscape::Util::Quantity y_q = Inkscape::Util::Quantity(dist[Geom::Y], "px"); + GString *x = g_string_new(x_q.string(_desktop->namedview->doc_units).c_str()); + GString *y = g_string_new(y_q.string(_desktop->namedview->doc_units).c_str()); + Glib::ustring ret = format_tip(C_("Path node tip", "Move node by %s, %s"), x->str, y->str); g_string_free(x, TRUE); g_string_free(y, TRUE); return ret; @@ -1281,7 +1323,6 @@ char const *Node::node_type_to_localized_string(NodeType type) } } -/** Determine whether two nodes are joined by a linear segment. */ bool Node::_is_line_segment(Node *first, Node *second) { if (!first || !second) return false; @@ -1292,25 +1333,6 @@ bool Node::_is_line_segment(Node *first, Node *second) return false; } -SPCtrlShapeType Node::_node_type_to_shape(NodeType type) -{ - switch(type) { - case NODE_CUSP: return SP_CTRL_SHAPE_DIAMOND; - case NODE_SMOOTH: return SP_CTRL_SHAPE_SQUARE; - case NODE_AUTO: return SP_CTRL_SHAPE_CIRCLE; - case NODE_SYMMETRIC: return SP_CTRL_SHAPE_SQUARE; - default: return SP_CTRL_SHAPE_DIAMOND; - } -} - - -/** - * An editable list of nodes representing a subpath. - * - * It can optionally be cyclic to represent a closed path. - * The list has iterators that act like plain node iterators, but can also be used - * to obtain shared pointers to nodes. - */ NodeList::NodeList(SubpathList &splist) : _list(splist) , _closed(false) @@ -1342,8 +1364,6 @@ bool NodeList::closed() return _closed; } -/** A subpath is degenerate if it has no segments - either one node in an open path - * or no nodes in a closed path */ bool NodeList::degenerate() { return closed() ? empty() : ++begin() == end(); @@ -1360,10 +1380,9 @@ NodeList::iterator NodeList::before(double t, double *fracpart) return ret; } -// insert a node before i -NodeList::iterator NodeList::insert(iterator i, Node *x) +NodeList::iterator NodeList::insert(iterator pos, Node *x) { - ListNode *ins = i._node; + ListNode *ins = pos._node; x->ln_next = ins; x->ln_prev = ins->ln_prev; ins->ln_prev->ln_next = x; @@ -1469,11 +1488,6 @@ NodeList &NodeList::get(iterator const &i) { } -/** - * @class SubpathList - * Editable path composed of one or more subpaths. - */ - } // namespace UI } // namespace Inkscape diff --git a/src/ui/tool/node.h b/src/ui/tool/node.h index f2e31e018..b874949f5 100644 --- a/src/ui/tool/node.h +++ b/src/ui/tool/node.h @@ -3,6 +3,7 @@ */ /* Authors: * Krzysztof Kosiński <tweenk.pl@gmail.com> + * Jon A. Cruz <jon@joncruz.org> * * Copyright (C) 2009 Authors * Released under GNU GPL, read the file 'COPYING' for more information @@ -15,7 +16,13 @@ #include <iosfwd> #include <stdexcept> #include <cstddef> + +#if __cplusplus >= 201103L +#include <functional> +#else #include <tr1/functional> +#endif + #include <boost/enable_shared_from_this.hpp> #include <boost/shared_ptr.hpp> #include "ui/tool/selectable-control-point.h" @@ -30,11 +37,13 @@ template <typename> class NodeIterator; } } +#if __cplusplus < 201103L namespace std { namespace tr1 { template <typename N> struct hash< Inkscape::UI::NodeIterator<N> >; } } +#endif namespace Inkscape { namespace UI { @@ -80,10 +89,11 @@ struct NodeSharedData { class Handle : public ControlPoint { public: + virtual ~Handle(); - inline Geom::Point relativePos(); - inline double length(); - bool isDegenerate() { return _degenerate; } // True if the handle is retracted, i.e. has zero length. + inline Geom::Point relativePos() const; + inline double length() const; + bool isDegenerate() const { return _degenerate; } // True if the handle is retracted, i.e. has zero length. virtual void setVisible(bool); virtual void move(Geom::Point const &p); @@ -96,80 +106,161 @@ public: void setDirection(Geom::Point const &dir); Node *parent() { return _parent; } Handle *other(); + Handle const *other() const; static char const *handle_type_to_localized_string(NodeType type); + protected: + Handle(NodeSharedData const &data, Geom::Point const &initial_pos, Node *parent); - virtual bool _eventHandler(SPEventContext *event_context, GdkEvent *event); - virtual void dragged(Geom::Point &, GdkEventMotion *); - virtual bool grabbed(GdkEventMotion *); - virtual void ungrabbed(GdkEventButton *); - virtual bool clicked(GdkEventButton *); + virtual bool _eventHandler(Inkscape::UI::Tools::ToolBase *event_context, GdkEvent *event); + virtual void dragged(Geom::Point &new_pos, GdkEventMotion *event); + virtual bool grabbed(GdkEventMotion *event); + virtual void ungrabbed(GdkEventButton *event); + virtual bool clicked(GdkEventButton *event); + + virtual Glib::ustring _getTip(unsigned state) const; + virtual Glib::ustring _getDragTip(GdkEventMotion *event) const; + virtual bool _hasDragTips() const { return true; } - virtual Glib::ustring _getTip(unsigned state); - virtual Glib::ustring _getDragTip(GdkEventMotion *event); - virtual bool _hasDragTips() { return true; } private: + inline PathManipulator &_pm(); Node *_parent; // the handle's lifetime does not extend beyond that of the parent node, // so a naked pointer is OK and allows setting it during Node's construction SPCtrlLine *_handle_line; bool _degenerate; // True if the handle is retracted, i.e. has zero length. This is used often internally so it makes sense to cache this + /** + * Control point of a cubic Bezier curve in a path. + * + * Handle keeps the node type invariant only for the opposite handle of the same node. + * Keeping the invariant on node moves is left to the %Node class. + */ static Geom::Point _saved_other_pos; + static double _saved_length; static bool _drag_out; + static ColorSet _handle_colors; friend class Node; }; class Node : ListNode, public SelectableControlPoint { public: + + /** + * Curve endpoint in an editable path. + * + * The method move() keeps node type invariants during translations. + */ Node(NodeSharedData const &data, Geom::Point const &pos); + virtual void move(Geom::Point const &p); virtual void transform(Geom::Affine const &m); - virtual Geom::Rect bounds(); - - NodeType type() { return _type; } + virtual Geom::Rect bounds() const; + + NodeType type() const { return _type; } + + /** + * Sets the node type and optionally restores the invariants associated with the given type. + * @param type The type to set. + * @param update_handles Whether to restore invariants associated with the given type. + * Passing false is useful e.g. wen initially creating the path, + * and when making cusp nodes during some node algorithms. + * Pass true when used in response to an UI node type button. + */ void setType(NodeType type, bool update_handles = true); + void showHandles(bool v); + + void updateHandles(); + + + /** + * Pick the best type for this node, based on the position of its handles. + * This is what assigns types to nodes created using the pen tool. + */ void pickBestType(); // automatically determine the type from handle positions - bool isDegenerate() { return _front.isDegenerate() && _back.isDegenerate(); } - bool isEndNode(); + + bool isDegenerate() const { return _front.isDegenerate() && _back.isDegenerate(); } + bool isEndNode() const; Handle *front() { return &_front; } Handle *back() { return &_back; } + + /** + * Gets the handle that faces the given adjacent node. + * Will abort with error if the given node is not adjacent. + */ Handle *handleToward(Node *to); + + /** + * Gets the node in the direction of the given handle. + * Will abort with error if the handle doesn't belong to this node. + */ Node *nodeToward(Handle *h); + + /** + * Gets the handle that goes in the direction opposite to the given adjacent node. + * Will abort with error if the given node is not adjacent. + */ Handle *handleAwayFrom(Node *to); + + /** + * Gets the node in the direction opposite to the given handle. + * Will abort with error if the handle doesn't belong to this node. + */ Node *nodeAwayFrom(Handle *h); + NodeList &nodeList() { return *(static_cast<ListNode*>(this)->ln_list); } + + /** + * Move the node to the bottom of its canvas group. + * Useful for node break, to ensure that the selected nodes are above the unselected ones. + */ void sink(); static NodeType parse_nodetype(char x); static char const *node_type_to_localized_string(NodeType type); + // temporarily public - virtual bool _eventHandler(SPEventContext *event_context, GdkEvent *event); + /** Customized event handler to catch scroll events needed for selection grow/shrink. */ + virtual bool _eventHandler(Inkscape::UI::Tools::ToolBase *event_context, GdkEvent *event); + Inkscape::SnapCandidatePoint snapCandidatePoint(); + protected: - virtual void dragged(Geom::Point &, GdkEventMotion *); - virtual bool grabbed(GdkEventMotion *); - virtual bool clicked(GdkEventButton *); + + virtual void dragged(Geom::Point &new_pos, GdkEventMotion *event); + virtual bool grabbed(GdkEventMotion *event); + virtual bool clicked(GdkEventButton *event); virtual void _setState(State state); - virtual Glib::ustring _getTip(unsigned state); - virtual Glib::ustring _getDragTip(GdkEventMotion *event); - virtual bool _hasDragTips() { return true; } + virtual Glib::ustring _getTip(unsigned state) const; + virtual Glib::ustring _getDragTip(GdkEventMotion *event) const; + virtual bool _hasDragTips() const { return true; } + private: + Node(Node const &); void _fixNeighbors(Geom::Point const &old_pos, Geom::Point const &new_pos); void _updateAutoHandles(); + + /** + * Select or deselect a node in this node's subpath based on its path distance from this node. + * @param dir If negative, shrink selection by one node; if positive, grow by one node. + */ void _linearGrow(int dir); + Node *_next(); + Node const *_next() const; Node *_prev(); - Inkscape::SnapSourceType _snapSourceType(); - Inkscape::SnapTargetType _snapTargetType(); + Node const *_prev() const; + Inkscape::SnapSourceType _snapSourceType() const; + Inkscape::SnapTargetType _snapTargetType() const; inline PathManipulator &_pm(); - static SPCtrlShapeType _node_type_to_shape(NodeType type); + + /** Determine whether two nodes are joined by a linear segment. */ static bool _is_line_segment(Node *first, Node *second); // Handles are always present, but are not visible if they coincide with the node @@ -179,6 +270,8 @@ private: Handle _back; ///< Node handle in the forward direction of the path NodeType _type; ///< Type of node - cusp, smooth... bool _handles_shown; + static ColorSet node_colors; + friend class Handle; friend class NodeList; friend class NodeIterator<Node>; @@ -224,14 +317,14 @@ public: // default copy, default assign self &operator++() { - _node = _node->ln_next; + _node = (_node?_node->ln_next:NULL); return *this; } self &operator--() { - _node = _node->ln_prev; + _node = (_node?_node->ln_prev:NULL); return *this; } - bool operator==(self const &other) const { return _node == other._node; } + bool operator==(self const &other) const { if(&other){return _node == other._node;} else{return false;} } N &operator*() const { return *static_cast<N*>(_node); } inline operator bool() const; // define after NodeList /// Get a pointer to the underlying node. Equivalent to <code>&*i</code>. @@ -271,7 +364,15 @@ public: typedef NodeIterator<value_type const> const_iterator; // TODO Lame. Make this private and make SubpathList a factory + /** + * An editable list of nodes representing a subpath. + * + * It can optionally be cyclic to represent a closed path. + * The list has iterators that act like plain node iterators, but can also be used + * to obtain shared pointers to nodes. + */ NodeList(SubpathList &_list); + ~NodeList(); // iterators @@ -286,7 +387,13 @@ public: // extra node-specific methods bool closed(); + + /** + * A subpath is degenerate if it has no segments - either one node in an open path + * or no nodes in a closed path. + */ bool degenerate(); + void setClosed(bool c) { _closed = c; } iterator before(double t, double *fracpart = NULL); const_iterator before(double t, double *fracpart = NULL) const { @@ -294,7 +401,10 @@ public: } // list operations + + /** insert a node before pos. */ iterator insert(iterator pos, Node *x); + template <class InputIterator> void insert(iterator pos, InputIterator first, InputIterator last) { for (; first != last; ++first) insert(pos, *first); @@ -342,7 +452,10 @@ private: friend class NodeIterator<Node const>; }; -/** List of node lists. Represents an editable path. */ +/** + * List of node lists. Represents an editable path. + * Editable path composed of one or more subpaths. + */ class SubpathList : public std::list< boost::shared_ptr<NodeList> > { public: typedef std::list< boost::shared_ptr<NodeList> > list_type; @@ -361,13 +474,13 @@ private: // define inline Handle funcs after definition of Node -inline Geom::Point Handle::relativePos() { +inline Geom::Point Handle::relativePos() const { return position() - _parent->position(); } inline void Handle::setRelativePos(Geom::Point const &p) { setPosition(_parent->position() + p); } -inline double Handle::length() { +inline double Handle::length() const { return relativePos().length(); } inline PathManipulator &Handle::_pm() { diff --git a/src/ui/tool/path-manipulator.cpp b/src/ui/tool/path-manipulator.cpp index 97e25069f..338499672 100644 --- a/src/ui/tool/path-manipulator.cpp +++ b/src/ui/tool/path-manipulator.cpp @@ -18,8 +18,7 @@ #include <boost/shared_ptr.hpp> #include <2geom/bezier-curve.h> #include <2geom/bezier-utils.h> -#include <2geom/svg-path.h> -#include <glibmm.h> +#include <2geom/path-sink.h> #include <glibmm/i18n.h> #include "ui/tool/path-manipulator.h" #include "desktop.h" @@ -152,13 +151,13 @@ PathManipulator::~PathManipulator() { delete _dragpoint; delete _observer; - gtk_object_destroy(_outline); + sp_canvas_item_destroy(_outline); _spcurve->unref(); clear(); } /** Handle motion events to update the position of the curve drag point. */ -bool PathManipulator::event(SPEventContext * /*event_context*/, GdkEvent *event) +bool PathManipulator::event(Inkscape::UI::Tools::ToolBase * /*event_context*/, GdkEvent *event) { if (empty()) return false; @@ -204,7 +203,7 @@ void PathManipulator::writeXML() sp_object_ref(_path); _path->deleteObject(true, true); sp_object_unref(_path); - _path = 0; + _path = NULL; } _observer->unblock(); } @@ -460,7 +459,10 @@ void PathManipulator::weldSegments() if (j->selected()) ++num_selected; else ++num_unselected; } - if (num_selected < 3) continue; + + // if 2 or fewer nodes are selected, there can't be any middle points to remove. + if (num_selected <= 2) continue; + if (num_unselected == 0 && sp->closed()) { // if all nodes in a closed subpath are selected, the operation doesn't make much sense continue; @@ -490,14 +492,16 @@ void PathManipulator::weldSegments() } if (num_points > 2) { // remove nodes in the middle + // TODO: fit bezier to the former shape sel_beg = sel_beg.next(); while (sel_beg != sel_end.prev()) { NodeList::iterator next = sel_beg.next(); sp->erase(sel_beg); sel_beg = next; } - sel_beg = sel_end; } + sel_beg = sel_end; + // decrease num_selected by the number of points processed num_selected -= num_points; } } @@ -915,6 +919,15 @@ void PathManipulator::setLiveObjects(bool set) _live_objects = set; } +void PathManipulator::updateHandles() +{ + for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) { + for (NodeList::iterator j = (*i)->begin(); j != (*i)->end(); ++j) { + j->updateHandles(); + } + } +} + void PathManipulator::setControlsTransform(Geom::Affine const &tnew) { Geom::Affine delta = _i2d_transform.inverse() * _edit_transform.inverse() * tnew * _i2d_transform; @@ -1189,12 +1202,13 @@ void PathManipulator::_createGeometryFromControlPoints(bool alert_LPE) } ++spi; } - builder.finish(); + builder.flush(); Geom::PathVector pathv = builder.peek() * (_edit_transform * _i2d_transform).inverse(); _spcurve->set_pathvector(pathv); if (alert_LPE) { - if (SP_IS_LPE_ITEM(_path) && sp_lpe_item_has_path_effect(SP_LPE_ITEM(_path))) { - PathEffectList effect_list = sp_lpe_item_get_effect_list(SP_LPE_ITEM(_path)); + /// \todo note that _path can be an Inkscape::LivePathEffect::Effect* too, kind of confusing, rework member naming? + if (SP_IS_LPE_ITEM(_path) && _path->hasPathEffect()) { + PathEffectList effect_list = _path->getEffectList(); LivePathEffect::LPEPowerStroke *lpe_pwr = dynamic_cast<LivePathEffect::LPEPowerStroke*>( effect_list.front()->lpeobject->get_lpe() ); if (lpe_pwr) { lpe_pwr->adjustForNewPath(pathv); @@ -1296,6 +1310,10 @@ void PathManipulator::_getGeometry() } else { _spcurve->unref(); _spcurve = _path->get_curve_for_edit(); + // never allow NULL to sneak in here! + if (_spcurve == NULL) { + _spcurve = new SPCurve(); + } } } @@ -1320,7 +1338,7 @@ void PathManipulator::_setGeometry() if (_path->getRepr()->attribute("inkscape:original-d")) _path->set_original_curve(_spcurve, false, false); else - SP_SHAPE(_path)->setCurve(_spcurve, false); + _path->setCurve(_spcurve, false); } } diff --git a/src/ui/tool/path-manipulator.h b/src/ui/tool/path-manipulator.h index fe73555c8..7a13ce723 100644 --- a/src/ui/tool/path-manipulator.h +++ b/src/ui/tool/path-manipulator.h @@ -21,8 +21,8 @@ #include "ui/tool/manipulator.h" struct SPCanvasItem; -struct SPCurve; -struct SPPath; +class SPCurve; +class SPPath; namespace Inkscape { namespace XML { class Node; } @@ -56,7 +56,7 @@ public: PathManipulator(MultiPathManipulator &mpm, SPPath *path, Geom::Affine const &edit_trans, guint32 outline_color, Glib::ustring lpe_key); ~PathManipulator(); - virtual bool event(SPEventContext *, GdkEvent *); + virtual bool event(Inkscape::UI::Tools::ToolBase *, GdkEvent *); bool empty(); void writeXML(); @@ -86,6 +86,7 @@ public: void showPathDirection(bool show); void setLiveOutline(bool set); void setLiveObjects(bool set); + void updateHandles(); void setControlsTransform(Geom::Affine const &); void hideDragPoint(); MultiPathManipulator &mpm() { return _multi_path_manipulator; } @@ -130,7 +131,7 @@ private: SubpathList _subpaths; MultiPathManipulator &_multi_path_manipulator; - SPPath *_path; + SPPath *_path; ///< can be an SPPath or an Inkscape::LivePathEffect::Effect !!! SPCurve *_spcurve; // in item coordinates SPCanvasItem *_outline; CurveDragPoint *_dragpoint; // an invisible control point hoverng over curve diff --git a/src/ui/tool/selectable-control-point.cpp b/src/ui/tool/selectable-control-point.cpp index cec2e7b07..f3f9c0e1e 100644 --- a/src/ui/tool/selectable-control-point.cpp +++ b/src/ui/tool/selectable-control-point.cpp @@ -1,8 +1,6 @@ -/** @file - * Desktop-bound selectable control object - implementation - */ /* Authors: * Krzysztof Kosiński <tweenk.pl@gmail.com> + * Jon A. Cruz <jon@joncruz.org> * * Copyright (C) 2009 Authors * Released under GNU GPL, read the file 'COPYING' for more information @@ -15,34 +13,32 @@ namespace Inkscape { namespace UI { -static SelectableControlPoint::ColorSet default_scp_color_set = { - { - {0xffffff00, 0x01000000}, // normal fill, stroke - {0xff0000ff, 0x01000000}, // mouseover fill, stroke - {0x0000ffff, 0x01000000} // clicked fill, stroke - }, +ControlPoint::ColorSet SelectableControlPoint::_default_scp_color_set = { + {0xffffff00, 0x01000000}, // normal fill, stroke + {0xff0000ff, 0x01000000}, // mouseover fill, stroke + {0x0000ffff, 0x01000000}, // clicked fill, stroke + // {0x0000ffff, 0x000000ff}, // normal fill, stroke when selected {0xff000000, 0x000000ff}, // mouseover fill, stroke when selected {0xff000000, 0x000000ff} // clicked fill, stroke when selected }; -SelectableControlPoint::SelectableControlPoint(SPDesktop *d, Geom::Point const &initial_pos, - SPAnchorType anchor, SPCtrlShapeType shape, unsigned int size, - ControlPointSelection &sel, ColorSet *cset, SPCanvasGroup *group) - : ControlPoint (d, initial_pos, anchor, shape, size, - cset ? reinterpret_cast<ControlPoint::ColorSet*>(cset) - : reinterpret_cast<ControlPoint::ColorSet*>(&default_scp_color_set), group) - , _selection (sel) +SelectableControlPoint::SelectableControlPoint(SPDesktop *d, Geom::Point const &initial_pos, SPAnchorType anchor, + Inkscape::ControlType type, + ControlPointSelection &sel, + ColorSet const &cset, SPCanvasGroup *group) : + ControlPoint(d, initial_pos, anchor, type, cset, group), + _selection(sel) { _selection.allPoints().insert(this); } -SelectableControlPoint::SelectableControlPoint(SPDesktop *d, Geom::Point const &initial_pos, - SPAnchorType anchor, Glib::RefPtr<Gdk::Pixbuf> pixbuf, - ControlPointSelection &sel, ColorSet *cset, SPCanvasGroup *group) - : ControlPoint (d, initial_pos, anchor, pixbuf, - cset ? reinterpret_cast<ControlPoint::ColorSet*>(cset) - : reinterpret_cast<ControlPoint::ColorSet*>(&default_scp_color_set), group) - , _selection (sel) + +SelectableControlPoint::SelectableControlPoint(SPDesktop *d, Geom::Point const &initial_pos, SPAnchorType anchor, + Glib::RefPtr<Gdk::Pixbuf> pixbuf, + ControlPointSelection &sel, + ColorSet const &cset, SPCanvasGroup *group) : + ControlPoint(d, initial_pos, anchor, pixbuf, cset, group), + _selection (sel) { _selection.allPoints().insert(this); } @@ -107,21 +103,23 @@ void SelectableControlPoint::_setState(State state) { if (!selected()) { ControlPoint::_setState(state); - return; - } - - ColorSet *cset = reinterpret_cast<ColorSet*>(_cset); - ColorEntry current = {0, 0}; - switch (state) { - case STATE_NORMAL: - current = cset->selected_normal; break; - case STATE_MOUSEOVER: - current = cset->selected_mouseover; break; - case STATE_CLICKED: - current = cset->selected_clicked; break; + } else { + ColorEntry current = {0, 0}; + ColorSet const &activeCset = (_isLurking()) ? invisible_cset : _cset; + switch (state) { + case STATE_NORMAL: + current = activeCset.selected_normal; + break; + case STATE_MOUSEOVER: + current = activeCset.selected_mouseover; + break; + case STATE_CLICKED: + current = activeCset.selected_clicked; + break; + } + _setColors(current); + _state = state; } - _setColors(current); - _state = state; } } // namespace UI diff --git a/src/ui/tool/selectable-control-point.h b/src/ui/tool/selectable-control-point.h index 8ecb39a79..8acfc1168 100644 --- a/src/ui/tool/selectable-control-point.h +++ b/src/ui/tool/selectable-control-point.h @@ -1,8 +1,6 @@ -/** @file - * Desktop-bound selectable control object - */ /* Authors: * Krzysztof Kosiński <tweenk.pl@gmail.com> + * Jon A. Cruz <jon@joncruz.org> * * Copyright (C) 2009 Authors * Released under GNU GPL, read the file 'COPYING' for more information @@ -18,40 +16,45 @@ namespace UI { class ControlPointSelection; +/** + * Desktop-bound selectable control object. + */ class SelectableControlPoint : public ControlPoint { public: - struct ColorSet { - ControlPoint::ColorSet cpset; - ColorEntry selected_normal; - ColorEntry selected_mouseover; - ColorEntry selected_clicked; - }; ~SelectableControlPoint(); bool selected() const; - void updateState() const { const_cast<SelectableControlPoint*>(this)->_setState(_state); } - virtual Geom::Rect bounds() { + void updateState() { _setState(_state); } + virtual Geom::Rect bounds() const { return Geom::Rect(position(), position()); } + protected: - SelectableControlPoint(SPDesktop *d, Geom::Point const &initial_pos, - SPAnchorType anchor, SPCtrlShapeType shape, - unsigned int size, ControlPointSelection &sel, ColorSet *cset = 0, - SPCanvasGroup *group = 0); - SelectableControlPoint(SPDesktop *d, Geom::Point const &initial_pos, - SPAnchorType anchor, Glib::RefPtr<Gdk::Pixbuf> pixbuf, - ControlPointSelection &sel, ColorSet *cset = 0, SPCanvasGroup *group = 0); + + SelectableControlPoint(SPDesktop *d, Geom::Point const &initial_pos, SPAnchorType anchor, + Inkscape::ControlType type, + ControlPointSelection &sel, + ColorSet const &cset = _default_scp_color_set, SPCanvasGroup *group = 0); + + SelectableControlPoint(SPDesktop *d, Geom::Point const &initial_pos, SPAnchorType anchor, + Glib::RefPtr<Gdk::Pixbuf> pixbuf, + ControlPointSelection &sel, + ColorSet const &cset = _default_scp_color_set, SPCanvasGroup *group = 0); virtual void _setState(State state); - virtual void dragged(Geom::Point &, GdkEventMotion *); - virtual bool grabbed(GdkEventMotion *); - virtual void ungrabbed(GdkEventButton *); - virtual bool clicked(GdkEventButton *); + virtual void dragged(Geom::Point &new_pos, GdkEventMotion *event); + virtual bool grabbed(GdkEventMotion *event); + virtual void ungrabbed(GdkEventButton *event); + virtual bool clicked(GdkEventButton *event); ControlPointSelection &_selection; + private: + void _takeSelection(); + + static ColorSet _default_scp_color_set; }; } // namespace UI diff --git a/src/ui/tool/selector.cpp b/src/ui/tool/selector.cpp index 364776ce4..bdeacadc9 100644 --- a/src/ui/tool/selector.cpp +++ b/src/ui/tool/selector.cpp @@ -3,6 +3,7 @@ */ /* Authors: * Krzysztof Kosiński <tweenk.pl@gmail.com> + * Jon A. Cruz <jon@joncruz.org> * * Copyright (C) 2009 Authors * Released under GNU GPL, read the file 'COPYING' for more information @@ -12,17 +13,13 @@ #include "desktop.h" #include "desktop-handles.h" #include "display/sodipodi-ctrlrect.h" -#include "event-context.h" +#include "ui/tools/tool-base.h" #include "preferences.h" #include "ui/tool/event-utils.h" #include "ui/tool/selector.h" #include <gdk/gdkkeysyms.h> -#if !GTK_CHECK_VERSION(2,22,0) -#include "compat-key-syms.h" -#endif - namespace Inkscape { namespace UI { @@ -32,27 +29,31 @@ namespace UI { * is pressed, it grabs events and handles drags and clicks in the usual way. */ class SelectorPoint : public ControlPoint { public: - SelectorPoint(SPDesktop *d, SPCanvasGroup *group, Selector *s) - : ControlPoint(d, Geom::Point(0,0), SP_ANCHOR_CENTER, SP_CTRL_SHAPE_SQUARE, - 1, &invisible_cset, group) - , _selector(s) - , _cancel(false) + SelectorPoint(SPDesktop *d, SPCanvasGroup *group, Selector *s) : + ControlPoint(d, Geom::Point(0,0), SP_ANCHOR_CENTER, + CTRL_TYPE_INVISIPOINT, + invisible_cset, group), + _selector(s), + _cancel(false) { setVisible(false); _rubber = static_cast<CtrlRect*>(sp_canvas_item_new(sp_desktop_controls(_desktop), SP_TYPE_CTRLRECT, NULL)); sp_canvas_item_hide(_rubber); } + ~SelectorPoint() { - gtk_object_destroy(_rubber); + sp_canvas_item_destroy(_rubber); } + SPDesktop *desktop() { return _desktop; } - bool event(SPEventContext *ec, GdkEvent *e) { + + bool event(Inkscape::UI::Tools::ToolBase *ec, GdkEvent *e) { return _eventHandler(ec, e); } protected: - virtual bool _eventHandler(SPEventContext *event_context, GdkEvent *event) { + virtual bool _eventHandler(Inkscape::UI::Tools::ToolBase *event_context, GdkEvent *event) { if (event->type == GDK_KEY_PRESS && shortcut_key(event->key) == GDK_KEY_Escape && sp_canvas_item_is_visible(_rubber)) { @@ -70,22 +71,26 @@ private: sp_canvas_item_show(_rubber); return false; } + virtual void dragged(Geom::Point &new_pos, GdkEventMotion *) { if (_cancel) return; Geom::Rect sel(_start, new_pos); _rubber->setRectangle(sel); } + virtual void ungrabbed(GdkEventButton *event) { if (_cancel) return; sp_canvas_item_hide(_rubber); Geom::Rect sel(_start, position()); _selector->signal_area.emit(sel, event); } + virtual bool clicked(GdkEventButton *event) { if (event->button != 1) return false; _selector->signal_point.emit(position(), event); return true; } + CtrlRect *_rubber; Selector *_selector; Geom::Point _start; @@ -105,7 +110,7 @@ Selector::~Selector() delete _dragger; } -bool Selector::event(SPEventContext *event_context, GdkEvent *event) +bool Selector::event(Inkscape::UI::Tools::ToolBase *event_context, GdkEvent *event) { // The hidden control point will capture all events after it obtains the grab, // but it relies on this function to initiate it. If we pass only first button @@ -113,7 +118,7 @@ bool Selector::event(SPEventContext *event_context, GdkEvent *event) switch (event->type) { case GDK_BUTTON_PRESS: // Do not pass button presses other than left button to the control point. - // This way middle click and right click can be handled in SPEventContext. + // This way middle click and right click can be handled in ToolBase. if (event->button.button == 1 && !event_context->space_panning) { _dragger->setPosition(_desktop->w2d(event_point(event->motion))); return _dragger->event(event_context, event); diff --git a/src/ui/tool/selector.h b/src/ui/tool/selector.h index 7e2759b69..dbe751ede 100644 --- a/src/ui/tool/selector.h +++ b/src/ui/tool/selector.h @@ -28,7 +28,7 @@ class Selector : public Manipulator { public: Selector(SPDesktop *d); virtual ~Selector(); - virtual bool event(SPEventContext *, GdkEvent *); + virtual bool event(Inkscape::UI::Tools::ToolBase *, GdkEvent *); sigc::signal<void, Geom::Rect const &, GdkEventButton*> signal_area; sigc::signal<void, Geom::Point const &, GdkEventButton*> signal_point; diff --git a/src/ui/tool/transform-handle-set.cpp b/src/ui/tool/transform-handle-set.cpp index b4a27170a..7d5c9bf0c 100644 --- a/src/ui/tool/transform-handle-set.cpp +++ b/src/ui/tool/transform-handle-set.cpp @@ -3,6 +3,7 @@ */ /* Authors: * Krzysztof Kosiński <tweenk.pl@gmail.com> + * Jon A. Cruz <jon@joncruz.org> * * Copyright (C) 2009 Authors * Released under GNU GPL, read the file 'COPYING' for more information @@ -25,7 +26,7 @@ #include "ui/tool/selectable-control-point.h" #include "ui/tool/event-utils.h" #include "ui/tool/transform-handle-set.h" -#include "ui/tool/node-tool.h" +#include "ui/tools/node-tool.h" #include "ui/tool/node.h" #include "seltrans.h" @@ -38,6 +39,7 @@ namespace Inkscape { namespace UI { namespace { + SPAnchorType corner_to_anchor(unsigned c) { switch (c % 4) { case 0: return SP_ANCHOR_NE; @@ -46,6 +48,7 @@ SPAnchorType corner_to_anchor(unsigned c) { default: return SP_ANCHOR_SE; } } + SPAnchorType side_to_anchor(unsigned s) { switch (s % 4) { case 0: return SP_ANCHOR_N; @@ -62,30 +65,30 @@ double snap_angle(double a) { double unit_angle = M_PI / snaps; return CLAMP(unit_angle * round(a / unit_angle), -M_PI, M_PI); } + double snap_increment_degrees() { Inkscape::Preferences *prefs = Inkscape::Preferences::get(); int snaps = prefs->getIntLimited("/options/rotationsnapsperpi/value", 12, 1, 1000); return 180.0 / snaps; } -ControlPoint::ColorSet thandle_cset = { +} // anonymous namespace + +ControlPoint::ColorSet TransformHandle::thandle_cset = { + {0x000000ff, 0x000000ff}, + {0x00ff6600, 0x000000ff}, + {0x00ff6600, 0x000000ff}, + // {0x000000ff, 0x000000ff}, {0x00ff6600, 0x000000ff}, {0x00ff6600, 0x000000ff} }; -ControlPoint::ColorSet center_cset = { - {0x00000000, 0x000000ff}, - {0x00000000, 0xff0000b0}, - {0x00000000, 0xff0000b0} -}; -} // anonymous namespace - -/** Base class for node transform handles to simplify implementation */ -TransformHandle::TransformHandle(TransformHandleSet &th, SPAnchorType anchor, Glib::RefPtr<Gdk::Pixbuf> pb) - : ControlPoint(th._desktop, Geom::Point(), anchor, pb, &thandle_cset, - th._transform_handle_group) - , _th(th) +TransformHandle::TransformHandle(TransformHandleSet &th, SPAnchorType anchor, Glib::RefPtr<Gdk::Pixbuf> pb) : + ControlPoint(th._desktop, Geom::Point(), anchor, + pb, + thandle_cset, th._transform_handle_group), + _th(th) { setVisible(false); } @@ -121,12 +124,13 @@ bool TransformHandle::grabbed(GdkEventMotion *) startTransform(); _th._setActiveHandle(this); - _cset = &invisible_cset; + _setLurking(true); _setState(_state); // Collect the snap-candidates, one for each selected node. These will be stored in the _snap_points vector. - InkNodeTool *nt = INK_NODE_TOOL(_th._desktop->event_context); - ControlPointSelection *selection = nt->_selected_nodes.get(); + Inkscape::UI::Tools::NodeTool *nt = INK_NODE_TOOL(_th._desktop->event_context); + //ControlPointSelection *selection = nt->_selected_nodes.get(); + ControlPointSelection* selection = nt->_selected_nodes; selection->setOriginalPoints(); selection->getOriginalPoints(_snap_points); @@ -171,7 +175,7 @@ void TransformHandle::ungrabbed(GdkEventButton *) { _snap_points.clear(); _th._clearActiveHandle(); - _cset = &thandle_cset; + _setLurking(false); _setState(_state); endTransform(); _th.signal_commit.emit(getCommitEvent()); @@ -184,7 +188,7 @@ public: : TransformHandle(th, anchor, pb) {} protected: - virtual Glib::ustring _getTip(unsigned state) { + virtual Glib::ustring _getTip(unsigned state) const { if (state_held_control(state)) { if (state_held_shift(state)) { return C_("Transform handle tip", @@ -205,31 +209,36 @@ protected: return C_("Transform handle tip", "<b>Scale handle</b>: drag to scale the selection"); } - virtual Glib::ustring _getDragTip(GdkEventMotion */*event*/) { + virtual Glib::ustring _getDragTip(GdkEventMotion */*event*/) const { return format_tip(C_("Transform handle tip", "Scale by %.2f%% x %.2f%%"), _last_scale_x * 100, _last_scale_y * 100); } - virtual bool _hasDragTips() { return true; } + virtual bool _hasDragTips() const { return true; } static double _last_scale_x, _last_scale_y; }; double ScaleHandle::_last_scale_x = 1.0; double ScaleHandle::_last_scale_y = 1.0; -/// Corner scaling handle for node transforms +/** + * Corner scaling handle for node transforms. + */ class ScaleCornerHandle : public ScaleHandle { public: - ScaleCornerHandle(TransformHandleSet &th, unsigned corner) - : ScaleHandle(th, corner_to_anchor(corner), _corner_to_pixbuf(corner)) - , _corner(corner) + + ScaleCornerHandle(TransformHandleSet &th, unsigned corner) : + ScaleHandle(th, corner_to_anchor(corner), _corner_to_pixbuf(corner)), + _corner(corner) {} + protected: virtual void startTransform() { _sc_center = _th.rotationCenter(); _sc_opposite = _th.bounds().corner(_corner + 2); _last_scale_x = _last_scale_y = 1.0; } + virtual Geom::Affine computeTransform(Geom::Point const &new_pos, GdkEventMotion *event) { Geom::Point scc = held_shift(*event) ? _sc_center : _sc_opposite; Geom::Point vold = _origin - scc, vnew = new_pos - scc; @@ -275,25 +284,35 @@ protected: * Geom::Translate(scc); return t; } + virtual CommitEvent getCommitEvent() { return _last_transform.isUniformScale() ? COMMIT_MOUSE_SCALE_UNIFORM : COMMIT_MOUSE_SCALE; } + private: + static Glib::RefPtr<Gdk::Pixbuf> _corner_to_pixbuf(unsigned c) { - sp_select_context_get_type(); + //sp_select_context_get_type(); switch (c % 2) { - case 0: return Glib::wrap(handles[1], true); - default: return Glib::wrap(handles[0], true); + case 0: + return Glib::wrap(handles[1], true); + break; + default: + return Glib::wrap(handles[0], true); + break; } } + Geom::Point _sc_center; Geom::Point _sc_opposite; unsigned _corner; }; -/// Side scaling handle for node transforms +/** + * Side scaling handle for node transforms. + */ class ScaleSideHandle : public ScaleHandle { public: ScaleSideHandle(TransformHandleSet &th, unsigned side) @@ -358,7 +377,7 @@ protected: } private: static Glib::RefPtr<Gdk::Pixbuf> _side_to_pixbuf(unsigned c) { - sp_select_context_get_type(); + //sp_select_context_get_type(); switch (c % 2) { case 0: return Glib::wrap(handles[3], true); default: return Glib::wrap(handles[2], true); @@ -369,7 +388,9 @@ private: unsigned _side; }; -/// Rotation handle for node transforms +/** + * Rotation handle for node transforms. + */ class RotateHandle : public TransformHandle { public: RotateHandle(TransformHandleSet &th, unsigned corner) @@ -410,7 +431,7 @@ protected: virtual CommitEvent getCommitEvent() { return COMMIT_MOUSE_ROTATE; } - virtual Glib::ustring _getTip(unsigned state) { + virtual Glib::ustring _getTip(unsigned state) const { if (state_held_shift(state)) { if (state_held_control(state)) { return format_tip(C_("Transform handle tip", @@ -427,20 +448,20 @@ protected: "the selection around the rotation center"); } - virtual Glib::ustring _getDragTip(GdkEventMotion */*event*/) { + virtual Glib::ustring _getDragTip(GdkEventMotion */*event*/) const { return format_tip(C_("Transform handle tip", "Rotate by %.2f°"), - _last_angle * 360.0); + _last_angle * 180.0 / M_PI); } - virtual bool _hasDragTips() { return true; } + virtual bool _hasDragTips() const { return true; } private: static Glib::RefPtr<Gdk::Pixbuf> _corner_to_pixbuf(unsigned c) { - sp_select_context_get_type(); + //sp_select_context_get_type(); switch (c % 4) { - case 0: return Glib::wrap(handles[10], true); - case 1: return Glib::wrap(handles[8], true); - case 2: return Glib::wrap(handles[6], true); + case 0: return Glib::wrap(handles[7], true); + case 1: return Glib::wrap(handles[6], true); + case 2: return Glib::wrap(handles[5], true); default: return Glib::wrap(handles[4], true); } } @@ -550,7 +571,7 @@ protected: : COMMIT_MOUSE_SKEW_X; } - virtual Glib::ustring _getTip(unsigned state) { + virtual Glib::ustring _getTip(unsigned state) const { if (state_held_shift(state)) { if (state_held_control(state)) { return format_tip(C_("Transform handle tip", @@ -568,7 +589,7 @@ protected: "the opposite handle"); } - virtual Glib::ustring _getDragTip(GdkEventMotion */*event*/) { + virtual Glib::ustring _getDragTip(GdkEventMotion */*event*/) const { if (_last_horizontal) { return format_tip(C_("Transform handle tip", "Skew horizontally by %.2f°"), _last_angle * 360.0); @@ -578,16 +599,16 @@ protected: } } - virtual bool _hasDragTips() { return true; } + virtual bool _hasDragTips() const { return true; } private: static Glib::RefPtr<Gdk::Pixbuf> _side_to_pixbuf(unsigned s) { - sp_select_context_get_type(); + //sp_select_context_get_type(); switch (s % 4) { - case 0: return Glib::wrap(handles[9], true); - case 1: return Glib::wrap(handles[7], true); - case 2: return Glib::wrap(handles[5], true); + case 0: return Glib::wrap(handles[10], true); + case 1: return Glib::wrap(handles[9], true); + case 2: return Glib::wrap(handles[8], true); default: return Glib::wrap(handles[11], true); } } @@ -601,11 +622,13 @@ bool SkewHandle::_last_horizontal = false; double SkewHandle::_last_angle = 0; class RotationCenter : public ControlPoint { + public: - RotationCenter(TransformHandleSet &th) - : ControlPoint(th._desktop, Geom::Point(), SP_ANCHOR_CENTER, _get_pixbuf(), - ¢er_cset, th._transform_handle_group) - , _th(th) + RotationCenter(TransformHandleSet &th) : + ControlPoint(th._desktop, Geom::Point(), SP_ANCHOR_CENTER, + _get_pixbuf(), + _center_cset, th._transform_handle_group), + _th(th) { setVisible(false); } @@ -628,7 +651,7 @@ protected: } sm.unSetup(); } - virtual Glib::ustring _getTip(unsigned /*state*/) { + virtual Glib::ustring _getTip(unsigned /*state*/) const { return C_("Transform handle tip", "<b>Rotation center</b>: drag to change the origin of transforms"); } @@ -636,13 +659,25 @@ protected: private: static Glib::RefPtr<Gdk::Pixbuf> _get_pixbuf() { - sp_select_context_get_type(); + //sp_select_context_get_type(); return Glib::wrap(handles[12], true); } + static ColorSet _center_cset; TransformHandleSet &_th; }; +ControlPoint::ColorSet RotationCenter::_center_cset = { + {0x00000000, 0x000000ff}, + {0x00000000, 0xff0000b0}, + {0x00000000, 0xff0000b0}, + // + {0x00000000, 0x000000ff}, + {0x00000000, 0xff0000b0}, + {0x00000000, 0xff0000b0} +}; + + TransformHandleSet::TransformHandleSet(SPDesktop *d, SPCanvasGroup *th_group) : Manipulator(d) , _active(0) @@ -674,18 +709,22 @@ TransformHandleSet::~TransformHandleSet() } } -/** Sets the mode of transform handles (scale or rotate). */ void TransformHandleSet::setMode(Mode m) { _mode = m; _updateVisibility(_visible); } -Geom::Rect TransformHandleSet::bounds() +Geom::Rect TransformHandleSet::bounds() const { return Geom::Rect(*_scale_corners[0], *_scale_corners[2]); } +ControlPoint const &TransformHandleSet::rotationCenter() const +{ + return *_center; +} + ControlPoint &TransformHandleSet::rotationCenter() { return *_center; @@ -715,7 +754,7 @@ void TransformHandleSet::setBounds(Geom::Rect const &r, bool preserve_center) } } -bool TransformHandleSet::event(SPEventContext *, GdkEvent*) +bool TransformHandleSet::event(Inkscape::UI::Tools::ToolBase *, GdkEvent*) { return false; } @@ -746,9 +785,6 @@ void TransformHandleSet::_clearActiveHandle() _updateVisibility(_visible); } -/** Update the visibility of transformation handles according to settings and the dimensions - * of the bounding box. It hides the handles that would have no effect or lead to - * discontinuities. Additionally, side handles for which there is no space are not shown. */ void TransformHandleSet::_updateVisibility(bool v) { if (v) { diff --git a/src/ui/tool/transform-handle-set.h b/src/ui/tool/transform-handle-set.h index cd1a61da2..54f81adf5 100644 --- a/src/ui/tool/transform-handle-set.h +++ b/src/ui/tool/transform-handle-set.h @@ -3,6 +3,7 @@ */ /* Authors: * Krzysztof Kosiński <tweenk.pl@gmail.com> + * Jon A. Cruz <jon@joncruz.org> * * Copyright (C) 2009 Authors * Released under GNU GPL, read the file 'COPYING' for more information @@ -23,7 +24,6 @@ class CtrlRect; namespace Inkscape { namespace UI { -//class TransformHandle; class RotateHandle; class SkewHandle; class ScaleCornerHandle; @@ -32,6 +32,7 @@ class RotationCenter; class TransformHandleSet : public Manipulator { public: + enum Mode { MODE_SCALE, MODE_ROTATE_SKEW @@ -39,25 +40,39 @@ public: TransformHandleSet(SPDesktop *d, SPCanvasGroup *th_group); virtual ~TransformHandleSet(); - virtual bool event(SPEventContext *, GdkEvent *); + virtual bool event(Inkscape::UI::Tools::ToolBase *, GdkEvent *); - bool visible() { return _visible; } - Mode mode() { return _mode; } - Geom::Rect bounds(); + bool visible() const { return _visible; } + Mode mode() const { return _mode; } + Geom::Rect bounds() const; void setVisible(bool v); - void setMode(Mode); + + /** Sets the mode of transform handles (scale or rotate). */ + void setMode(Mode m); + void setBounds(Geom::Rect const &, bool preserve_center = false); bool transforming() { return _in_transform; } + + ControlPoint const &rotationCenter() const; ControlPoint &rotationCenter(); sigc::signal<void, Geom::Affine const &> signal_transform; sigc::signal<void, CommitEvent> signal_commit; + private: + void _emitTransform(Geom::Affine const &); void _setActiveHandle(ControlPoint *h); void _clearActiveHandle(); + + /** Update the visibility of transformation handles according to settings and the dimensions + * of the bounding box. It hides the handles that would have no effect or lead to + * discontinuities. Additionally, side handles for which there is no space are not shown. + */ void _updateVisibility(bool v); + + // TODO unions must GO AWAY: union { ControlPoint *_handles[17]; struct { @@ -68,6 +83,7 @@ private: RotationCenter *_center; }; }; + ControlPoint *_active; SPCanvasGroup *_transform_handle_group; CtrlRect *_trans_outline; @@ -79,8 +95,9 @@ private: friend class RotationCenter; }; -/** Base class for node transform handles to simplify implementation */ -class TransformHandle : public ControlPoint { +/** Base class for node transform handles to simplify implementation. */ +class TransformHandle : public ControlPoint +{ public: TransformHandle(TransformHandleSet &th, SPAnchorType anchor, Glib::RefPtr<Gdk::Pixbuf> pb); void getNextClosestPoint(bool reverse); @@ -103,6 +120,8 @@ private: virtual bool grabbed(GdkEventMotion *); virtual void dragged(Geom::Point &new_pos, GdkEventMotion *event); virtual void ungrabbed(GdkEventButton *); + + static ColorSet thandle_cset; }; } // namespace UI |
