// SPDX-License-Identifier: GPL-2.0-or-later /* Authors: * Krzysztof KosiƄski * Jon A. Cruz * * 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 #include #include #include #include #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: * - grabbed() * - dragged() * - dragged() * - dragged() * - ... * - dragged() * - ungrabbed() * * 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 signal_dragged 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 invisible_cset 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 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 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); /** * 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 :