summaryrefslogtreecommitdiffstats
path: root/src/object-hierarchy.h
blob: e515a3db561e55ab9bf80a9966f1281f385fc2e6 (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
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Authors:
 *   MenTaLguY <mental@rydia.net>
 *
 * Copyright (C) 2004 MenTaLguY
 *
 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
 */

#ifndef SEEN_INKSCAPE_OBJECT_HIERARCHY_H
#define SEEN_INKSCAPE_OBJECT_HIERARCHY_H

#include <cstddef>
#include <exception>
#include <list>
#include <sigc++/connection.h>
#include <sigc++/signal.h>

class SPObject;

namespace Inkscape {

/**
 * An Inkscape::ObjectHierarchy is useful for situations where one wishes
 * to keep a reference to an SPObject, but fall back on one of its ancestors
 * when that object is removed.
 *
 * That cannot be accomplished simply by hooking the "release" signal of the
 * SPObject, as by the time that signal is emitted, the object's parent
 * field has already been cleared.
 *
 * There are also some subtle refcounting issues to take into account.
 *
 * @see SPObject
 */
class ObjectHierarchy {
public:

    /**
     * Create new object hierarchy.
     * @param top The first entry if non-NULL.
     */
    ObjectHierarchy(SPObject *top=nullptr);

    ~ObjectHierarchy();

    bool contains(SPObject *object);

    sigc::connection connectAdded(const sigc::slot<void, SPObject *> &slot) {
        return _added_signal.connect(slot);
    }
    sigc::connection connectRemoved(const sigc::slot<void, SPObject *> &slot) {
        return _removed_signal.connect(slot);
    }
    sigc::connection connectChanged(const sigc::slot<void, SPObject *, SPObject *> &slot)
    {
        return _changed_signal.connect(slot);
    }

    /**
     * Remove all entries.
     */
    void clear();

    SPObject *top() {
        return !_hierarchy.empty() ? _hierarchy.back().object : nullptr;
    }

    /**
     * Trim or expand hierarchy on top such that object becomes top entry.
     */
    void setTop(SPObject *object);

    SPObject *bottom() {
        return !_hierarchy.empty() ? _hierarchy.front().object : nullptr;
    }

    /**
     * Trim or expand hierarchy at bottom such that object becomes bottom entry.
     */
    void setBottom(SPObject *object);

private:
    struct Record {
        Record(SPObject *o, sigc::connection c)
        : object(o), connection(c) {}

        SPObject *object;
        sigc::connection connection;
    };

    ObjectHierarchy(ObjectHierarchy const &); // no copy

    void operator=(ObjectHierarchy const &); // no assign

    /**
     * Add hierarchy from junior's parent to senior to this
     * hierarchy's top.
     */
    void _addTop(SPObject *senior, SPObject *junior);

    /**
     * Add object to top of hierarchy.
     * \pre object!=NULL.
     */
    void _addTop(SPObject *object);

    /**
     * Remove all objects above limit from hierarchy.
     */
    void _trimAbove(SPObject *limit);

    /**
     * Add hierarchy from senior to junior, in range (senior, junior], to this hierarchy's bottom.
     */
    void _addBottom(SPObject *senior, SPObject *junior);

    /**
     * Add object at bottom of hierarchy.
     * \pre object!=NULL
     */
    void _addBottom(SPObject *object);

    /**
     * Remove all objects under given object.
     * @param limit If NULL, remove all.
     */
    void _trimBelow(SPObject *limit);

    Record _attach(SPObject *object);

    void _detach(Record &record);

    void _clear() { _trimBelow(nullptr); }

    void _trim_for_release(SPObject *released);

    std::list<Record> _hierarchy;
    sigc::signal<void, SPObject *> _added_signal;
    sigc::signal<void, SPObject *> _removed_signal;
    sigc::signal<void, SPObject *, SPObject *> _changed_signal;
};

}

#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 :