diff options
Diffstat (limited to 'src/ui/tool/control-point.h')
-rw-r--r-- | src/ui/tool/control-point.h | 414 |
1 files changed, 414 insertions, 0 deletions
diff --git a/src/ui/tool/control-point.h b/src/ui/tool/control-point.h new file mode 100644 index 0000000..7cc3977 --- /dev/null +++ b/src/ui/tool/control-point.h @@ -0,0 +1,414 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* 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 v2+, read the file 'COPYING' for more information. + */ + +#ifndef SEEN_UI_TOOL_CONTROL_POINT_H +#define SEEN_UI_TOOL_CONTROL_POINT_H + +#include <gdkmm/pixbuf.h> +#include <boost/utility.hpp> +#include <cstddef> +#include <sigc++/signal.h> +#include <sigc++/trackable.h> +#include <2geom/point.h> + +// #include "ui/control-types.h" +#include "display/control/canvas-item-ctrl.h" +#include "display/control/canvas-item-enums.h" + +#include "enums.h" // TEMP TEMP + +class SPDesktop; + +namespace Inkscape { +namespace UI { +namespace Tools { + +class ToolBase; + +} +} +} + +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: + + /** + * 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); + + /** + * Apply any node repairs, by default no fixing is applied but Nodes will update + * smooth nodes to make sure nodes are kept consistent. + */ + virtual void fixNeighbors() {}; + + /// @} + + /// @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); + /// @} + + /// @name Inspect the state of the control 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(Inkscape::UI::Tools::ToolBase *event_context, GdkEvent *event); + SPDesktop *const _desktop; ///< The desktop this control point resides on. + + bool doubleClicked() {return _double_clicked;} + +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, + Inkscape::CanvasItemCtrlType type, + ColorSet const &cset = _default_color_set, + Inkscape::CanvasItemGroup *group = nullptr); + + /** + * 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 const &cset = _default_color_set, + Inkscape::CanvasItemGroup *group = nullptr); + + /// @name Handle control point events in subclasses + /// @{ + /** + * Called when the user moves the point beyond the drag tolerance with the first button held + * 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. + */ + virtual void dragged(Geom::Point &new_pos, GdkEventMotion *event); + + /** + * 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. + * 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 + * @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 *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); + + void _setSize(unsigned int size); + + void _setControlType(Inkscape::CanvasItemCtrlType type); + + void _setAnchor(SPAnchorType anchor); + + void _setPixbuf(Glib::RefPtr<Gdk::Pixbuf>); + + /** + * Determines 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*/) const { return ""; } + + virtual Glib::ustring _getDragTip(GdkEventMotion */*event*/) const { return ""; } + + virtual bool _hasDragTips() const { return false; } + + + Inkscape::CanvasItemCtrl * _canvas_item_ctrl = nullptr; ///< Visual representation of the control point. + + ColorSet const &_cset; ///< Colors used to represent the point + + State _state = STATE_NORMAL; + + 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 Gdk::EventMask const _grab_event_mask; + + static bool _drag_initiated; + +private: + + ControlPoint(ControlPoint const &other); + + void operator=(ControlPoint const &other); + + static bool _event_handler(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 + + sigc::connection _event_handler_connection; + + bool _lurking = false; + + 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; + + bool _double_clicked = false; +}; + + +} // namespace UI +} // namespace Inkscape + +#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 : |