summaryrefslogtreecommitdiffstats
path: root/src/ui/tool/node.h
blob: 0630e4f0d5df0ec00c954fad95703044b9dd6b62 (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
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
// SPDX-License-Identifier: GPL-2.0-or-later
/** @file
 * Editable node and associated data structures.
 */
/* Authors:
 *   Krzysztof Kosiński <tweenk.pl@gmail.com>
 *   Jon A. Cruz <jon@joncruz.org>
 *
 * Copyright (C) 2009 Authors
 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
 */

#ifndef SEEN_UI_TOOL_NODE_H
#define SEEN_UI_TOOL_NODE_H

#include <iterator>
#include <iosfwd>
#include <stdexcept>
#include <cstddef>
#include <functional>

#include "ui/tool/selectable-control-point.h"
#include "snapped-point.h"
#include "ui/tool/node-types.h"

namespace Inkscape {
class CanvasItemGroup;
class CanvasItemCurve;

namespace UI {
template <typename> class NodeIterator;
}
}

namespace Inkscape {
namespace UI {

class PathManipulator;
class MultiPathManipulator;

class Node;
class Handle;
class NodeList;
class SubpathList;
template <typename> class NodeIterator;

std::ostream &operator<<(std::ostream &, NodeType);

/*
template <typename T>
struct ListMember {
    T *next;
    T *prev;
};
struct SubpathMember : public ListMember<NodeListMember> {
    Subpath *list;
};
struct SubpathListMember : public ListMember<SubpathListMember> {
    SubpathList *list;
};
*/

struct ListNode {
    ListNode *ln_next;
    ListNode *ln_prev;
    NodeList *ln_list;
};

struct NodeSharedData {
    SPDesktop *desktop;
    ControlPointSelection *selection;
    Inkscape::CanvasItemGroup *node_group;
    Inkscape::CanvasItemGroup *handle_group;
    Inkscape::CanvasItemGroup *handle_line_group;
};

class Handle : public ControlPoint {
public:

    ~Handle() override;
    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.

    void setVisible(bool) override;
    void move(Geom::Point const &p) override;

    void setPosition(Geom::Point const &p) override;
    inline void setRelativePos(Geom::Point const &p);
    void setLength(double len);
    void retract();
    void setDirection(Geom::Point const &from, Geom::Point const &to);
    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 void handle_2button_press();
    bool _eventHandler(Inkscape::UI::Tools::ToolBase *event_context, GdkEvent *event) override;
    void dragged(Geom::Point &new_pos, GdkEventMotion *event) override;
    bool grabbed(GdkEventMotion *event) override;
    void ungrabbed(GdkEventButton *event) override;
    bool clicked(GdkEventButton *event) override;

    Glib::ustring _getTip(unsigned state) const override;
    Glib::ustring _getDragTip(GdkEventMotion *event) const override;
    bool _hasDragTips() const override { return true; }

private:

    inline PathManipulator &_pm();
    inline PathManipulator &_pm() const;
    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
    CanvasItemCurve *_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);

    Node(Node const &) = delete;

    void move(Geom::Point const &p) override;
    void transform(Geom::Affine const &m) override;
    void fixNeighbors() override;
    Geom::Rect bounds() const override;

    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. when 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() 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); }
    NodeList &nodeList() const { return *(static_cast<ListNode const*>(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
    /** Customized event handler to catch scroll events needed for selection grow/shrink. */
    bool _eventHandler(Inkscape::UI::Tools::ToolBase *event_context, GdkEvent *event) override;

    Inkscape::SnapCandidatePoint snapCandidatePoint();

protected:

    void dragged(Geom::Point &new_pos, GdkEventMotion *event) override;
    bool grabbed(GdkEventMotion *event) override;
    bool clicked(GdkEventButton *event) override;

    void _setState(State state) override;
    Glib::ustring _getTip(unsigned state) const override;
    Glib::ustring _getDragTip(GdkEventMotion *event) const override;
    bool _hasDragTips() const override { return true; }

private:

    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();
    Node const *_prev() const;
    Inkscape::SnapSourceType _snapSourceType() const;
    Inkscape::SnapTargetType _snapTargetType() const;
    inline PathManipulator &_pm();
    inline PathManipulator &_pm() const;

    /** 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
    // (are degenerate). A segment that has both handles degenerate is always treated
    // as a line segment
    Handle _front; ///< Node handle in the backward direction of the path
    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;

    // This is used by fixNeighbors to repair smooth nodes after all move
    // operations have been completed. If this is empty, no fixing is needed.
    std::optional<Geom::Point> _unfixed_pos;

    friend class Handle;
    friend class NodeList;
    friend class NodeIterator<Node>;
    friend class NodeIterator<Node const>;
};

/// Iterator for editable nodes
/** Use this class for all operations that require some knowledge about the node's
 * neighbors. It is a bidirectional iterator.
 *
 * Because paths can be cyclic, node iterators have two different ways to
 * increment and decrement them. When using ++/--, the end iterator will eventually
 * be returned. When using advance()/retreat(), the end iterator will only be returned
 * when the path is open. If it's closed, calling advance() will cycle indefinitely.
 * This is particularly useful for cases where the adjacency of nodes is more important
 * than their sequence order.
 *
 * When @a i is a node iterator, then:
 * - <code>++i</code> moves the iterator to the next node in sequence order;
 * - <code>--i</code> moves the iterator to the previous node in sequence order;
 * - <code>i.next()</code> returns the next node with wrap-around;
 * - <code>i.prev()</code> returns the previous node with wrap-around;
 * - <code>i.advance()</code> moves the iterator to the next node with wrap-around;
 * - <code>i.retreat()</code> moves the iterator to the previous node with wrap-around.
 *
 * next() and prev() do not change their iterator. They can return the end iterator
 * if the path is open.
 *
 * Unlike most other iterators, you can check whether you've reached the end of the list
 * without having access to the iterator's container.
 * Simply use <code>if (i) { ...</code>
 * */
template <typename N>
class NodeIterator
    : public boost::bidirectional_iterator_helper<NodeIterator<N>, N, std::ptrdiff_t,
        N *, N &>
{
public:
    typedef NodeIterator self;
    NodeIterator()
        : _node(nullptr)
    {}
    // default copy, default assign

    self &operator++() {
        _node = (_node?_node->ln_next:nullptr);
        return *this;
    }
    self &operator--() {
        _node = (_node?_node->ln_prev:nullptr);
        return *this;
    }
    bool operator==(self const &other) const { return _node == other._node; }
    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>.
    N *get_pointer() const { return static_cast<N*>(_node); }
    /// @see get_pointer()
    N *ptr() const { return static_cast<N*>(_node); }

    self next() const {
        self r(*this);
        r.advance();
        return r;
    }
    self prev() const {
        self r(*this);
        r.retreat();
        return r;
    }
    self &advance();
    self &retreat();
private:
    NodeIterator(ListNode const *n)
        : _node(const_cast<ListNode*>(n))
    {}
    ListNode *_node;
    friend class NodeList;
};

class NodeList : ListNode, boost::noncopyable {
public:
    typedef std::size_t size_type;
    typedef Node &reference;
    typedef Node const &const_reference;
    typedef Node *pointer;
    typedef Node const *const_pointer;
    typedef Node value_type;
    typedef NodeIterator<value_type> iterator;
    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();

    // no copy or assign
    NodeList(NodeList const &) = delete;
    void operator=(NodeList const &) = delete;

    // iterators
    iterator begin() { return iterator(ln_next); }
    iterator end() { return iterator(this); }
    const_iterator begin() const { return const_iterator(ln_next); }
    const_iterator end() const { return const_iterator(this); }

    // size
    bool empty();
    size_type size();

    // 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 = nullptr);
    iterator before(Geom::PathTime const &pvp);
    const_iterator before(double t, double *fracpart = nullptr) const {
        return const_cast<NodeList *>(this)->before(t, fracpart)._node;
    }
    const_iterator before(Geom::PathTime const &pvp) const {
        return const_cast<NodeList *>(this)->before(pvp)._node;
    }

    // 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);
    }
    void splice(iterator pos, NodeList &list);
    void splice(iterator pos, NodeList &list, iterator i);
    void splice(iterator pos, NodeList &list, iterator first, iterator last);
    void reverse();
    void shift(int n);
    void push_front(Node *x) { insert(begin(), x); }
    void pop_front() { erase(begin()); }
    void push_back(Node *x) { insert(end(), x); }
    void pop_back() { erase(--end()); }
    void clear();
    iterator erase(iterator pos);
    iterator erase(iterator first, iterator last) {
        NodeList::iterator ret = first;
        while (first != last) ret = erase(first++);
        return ret;
    }

    // member access - undefined results when the list is empty
    Node &front() { return *static_cast<Node*>(ln_next); }
    Node &back() { return *static_cast<Node*>(ln_prev); }

    // HACK remove this subpath from its path. This will be removed later.
    void kill();
    SubpathList &subpathList() { return _list; }

    static iterator get_iterator(Node *n) { return iterator(n); }
    static const_iterator get_iterator(Node const *n) { return const_iterator(n); }
    static NodeList &get(Node *n);
    static NodeList &get(iterator const &i);
private:

    SubpathList &_list;
    bool _closed;

    friend class Node;
    friend class Handle; // required to access handle and handle line groups
    friend class NodeIterator<Node>;
    friend class NodeIterator<Node const>;
};

/**
 * List of node lists. Represents an editable path.
 * Editable path composed of one or more subpaths.
 */
class SubpathList : public std::list< std::shared_ptr<NodeList> > {
public:
    typedef std::list< std::shared_ptr<NodeList> > list_type;

    SubpathList(PathManipulator &pm) : _path_manipulator(pm) {}
    PathManipulator &pm() { return _path_manipulator; }

private:
    list_type _nodelists;
    PathManipulator &_path_manipulator;
    friend class NodeList;
    friend class Node;
    friend class Handle;
};



// define inline Handle funcs after definition of Node
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() const {
    return relativePos().length();
}
inline PathManipulator &Handle::_pm() {
    return _parent->_pm();
}
inline PathManipulator &Handle::_pm() const {
    return _parent->_pm();
}
inline PathManipulator &Node::_pm() {
    return nodeList().subpathList().pm();
}

inline PathManipulator &Node::_pm() const {
    return nodeList().subpathList().pm();
}

// definitions for node iterator
template <typename N>
NodeIterator<N>::operator bool() const {
    return _node && static_cast<ListNode*>(_node->ln_list) != _node;
}
template <typename N>
NodeIterator<N> &NodeIterator<N>::advance() {
    ++(*this);
    if (G_UNLIKELY(!*this) && _node->ln_list->closed()) ++(*this);
    return *this;
}
template <typename N>
NodeIterator<N> &NodeIterator<N>::retreat() {
    --(*this);
    if (G_UNLIKELY(!*this) && _node->ln_list->closed()) --(*this);
    return *this;
}

} // 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 :