summaryrefslogtreecommitdiffstats
path: root/src/ui/tool/control-point.h
blob: 7cc39778a652801c71cd8a346a2049772a9d93ed (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
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 :