-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathqobjecttreemodel.h
More file actions
423 lines (372 loc) · 17.1 KB
/
qobjecttreemodel.h
File metadata and controls
423 lines (372 loc) · 17.1 KB
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
#pragma once
#include <memory>
#include <unordered_map>
#include <QtCore/QAbstractItemModel>
#ifdef QT_QML_LIB
#include <QtQml/qqmlregistration.h>
#endif
class QObjectTreeModelTest;
/**
* @class QObjectTreeModel
* @brief A QAbstractItemModel that reactively mirrors a QObject parent–child
* hierarchy as a tree model for Qt Views and QML.
*
* The QObjectTreeModel monitors a QObject parent/child tree reactively via
* event filters on every object in the tree. This way it can be attached to
* existing QObjects to expose their child hierarchy for view for debugging.
* Reparenting or deleting a QObject automatically causes the model to update so
* no manual insert/remove calls required.
*
* @par Object deletion
* Both @c deleteLater and raw @c delete are supported for tracked objects.
* The @c DeferredDelete event can remove the object cleanly before destruction
* and raw @c delete is handled via the @c QObject::destroyed signal.
*
* @par Thread affinity
* The root QObject must have the same thread affinity as the model. Because
* Qt enforces that all children live in the same thread as their parent
* (checked at construction and in @c setParent), this single check is
* sufficient to guarantee the entire tracked subtree is on the model's thread.
* If an external root object is moved off the models thread then the model will
* detect the QEvent::ThreadChange event and unset the root object.
*/
class QObjectTreeModel : public QAbstractItemModel
{
Q_OBJECT
#ifdef QT_QML_LIB
QML_ELEMENT
#endif
Q_CLASSINFO("RegisterEnumClassesUnscoped", "false")
Q_DISABLE_COPY_MOVE(QObjectTreeModel);
Q_PROPERTY(QObject *root READ root WRITE setRoot NOTIFY rootChanged FINAL)
public:
/**
* @brief Construct a new QObjectTreeModel with an internally managed root,
* optionally parented to @p parent.
*
* An internal root QObject is created and owned by the model. Children can
* be added by reparenting QObjects to the internal root (see root()).
*
* To create a model with an external root, use CreateWithRoot().
**/
explicit QObjectTreeModel(QObject *parent = nullptr);
/**
* @brief Factory method that creates a QObjectTreeModel whose root is the
* externally-owned @p root, optionally parented to @p parent.
*
* The model does not take ownership of @p root, it is the caller's
* responsibility to ensure @p root outlives the model. If @p root is
* nullptr an internal root is created (equivalent to the constructor).
*
* @pre @p root must have the same thread affinity as @p parent (or the
* calling thread if @p parent is nullptr).
**/
[[nodiscard]] static std::unique_ptr<QObjectTreeModel> CreateWithRoot(QObject *root,
QObject *parent = nullptr);
~QObjectTreeModel() override;
enum class Roles : int {
ObjectName = Qt::UserRole,
MetaTypeName,
};
Q_ENUM(Roles)
/**
* @brief Returns data for the given @p index and @p role.
*
* Supported roles:
* - Qt::DisplayRole — the objectName of the QObject at the index.
* - Roles::ObjectName — the objectName of the QObject at the index.
* - Roles::MetaTypeName — the QMetaObject::className of the QObject at
* the index (e.g. "QObject", "QWidget", "MyCustomClass").
*
* Returns an invalid QVariant if the index is out of range.
**/
QVariant data(const QModelIndex &index, int role) const override;
/**
* @brief Returns the number of children under @p parent.
*
* If @p parent is an invalid index the children of the root object are
* counted. Returns 0 if the parent index refers to an object not tracked
* by the model.
**/
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
/**
* @brief Returns the number of columns for the given @p parent. Always
* returns 1.
**/
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
/**
* @brief Returns whether the item at @p parent has any children.
**/
bool hasChildren(const QModelIndex &parent = QModelIndex()) const override;
/**
* @brief Returns the parent index of the given @p child index.
*
* If @p child is the root or is invalid, an invalid QModelIndex is
* returned.
**/
QModelIndex parent(const QModelIndex &child) const override;
/**
* @brief Returns the model index for the item at (@p row, @p column) under
* @p parent.
*
* Returns an invalid index if the row or column is out of range, or if
* the parent is not tracked by the model.
**/
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
/**
* @brief Returns the sibling at @p row and @p column for the item at @p idx.
*
* Overridden for performance: the default implementation calls
* @c index(row, column, parent(idx)), which requires redundant map lookups
* and a linear scan. This implementation resolves the sibling in two hash
* lookups with no linear search.
**/
QModelIndex sibling(int row, int column, const QModelIndex &idx) const override;
/**
* @brief Returns a map of all data roles and their values for the item at
* @p index.
*
* This is a convenience override that populates the map with every role
* returned by roleNames().
**/
QMap<int, QVariant> itemData(const QModelIndex &index) const override;
/**
* @brief Returns the model's role names.
*
* Extends the default role names with Roles::ObjectName mapped to
* @c "objectName" and Roles::MetaTypeName mapped to @c "metaTypeName".
**/
QHash<int, QByteArray> roleNames() const override;
/**
* @brief Removes @p count rows starting at @p row under the given @p parent
* from the model.
*
* @warning This is a **destructive** operation. Every QObject removed
* (including the entire subtree rooted at each removed child) is scheduled
* for deletion via QObject::deleteLater(). If you need to remove objects
* from the model without destroying them, use takeRows() instead.
*
* Returns true on success. Returns false if the parent index is invalid
* (removing the root is not supported), if row or count are negative, or if
* the range exceeds the number of children.
*
* @sa takeRows(), takeAllRows()
**/
bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override;
/**
* @brief Moves a single row from @p sourceRow under @p sourceParent to
* @p destinationRow under @p destinationParent.
*
* Only a @p count of 1 is supported; any other value will emit a warning
* and return false.
*
* The move is performed by reparenting the underlying QObject. Returns
* true on success. Returns false if either parent index is invalid, if
* the source or destination row is out of bounds, if @p count is not 1,
* or if the move would be a no-op.
**/
bool moveRows(const QModelIndex &sourceParent,
int sourceRow,
int count,
const QModelIndex &destinationParent,
int destinationRow) override;
/**
* @brief Removes @p count rows starting at @p row under @p parent and
* returns the taken QObjects.
*
* The taken objects are unparented (parent set to nullptr) and removed
* from the model. Ownership is transferred to the caller. Returns an
* empty QObjectList if the arguments are out of range or the parent index
* is invalid.
*
* Unlike removeRows(), this does @b not destroy the removed objects.
*
* @sa removeRows(), takeAllRows()
**/
[[nodiscard]] QObjectList takeRows(int row,
int count,
const QModelIndex &parent = QModelIndex());
/**
* @brief Removes all child rows under @p parent and returns the taken
* QObjects.
*
* Equivalent to calling takeRows(0, rowCount(parent), parent). Ownership
* of the returned objects is transferred to the caller.
**/
[[nodiscard]] QObjectList takeAllRows(const QModelIndex &parent = QModelIndex());
/**
* @brief Returns the model index corresponding to the given @p object.
*
* If @p object is the root, or is not tracked by the model, an invalid
* QModelIndex is returned.
**/
Q_INVOKABLE QModelIndex indexOf(QObject *object) const;
/**
* @brief Returns the current root QObject of the model.
**/
QObject *root() const;
/**
* @brief Sets the root QObject of the model. If @p root is nullptr, an
* internal root QObject will be created and managed by the model (parented
* to the model itself).
*
* If the previous root was internally created (i.e. from a prior call with
* nullptr), it will be scheduled for deletion via deleteLater(). Any
* QObjects that have been inserted into the model at this point will be
* destroyed with the internal parent.
*
* The model does not take ownership of an externally provided root. It is
* the caller's responsibility to ensure the root QObject outlives the
* model.
*
* @pre If @p root is non-null it must have the same thread affinity as the
* model.
*
* Setting the root to its current value is a no-op.
**/
void setRoot(QObject *root);
Q_SIGNAL void rootChanged();
/**
* @brief Returns the current QObject associated with the provided @p index.
**/
Q_INVOKABLE QObject *objectFromIndex(const QModelIndex &index) const;
/**
* @brief Prints diagnostic information about @p object's signal
* connections and event filters to the debug output.
*
* Convenience wrapper that exposes QObject::dumpObjectInfo to QML.
**/
Q_INVOKABLE static void dumpObjectInfo(QObject *object);
/**
* @brief Recursively prints @p object and its children as an indented tree
* to the debug output.
*
* Convenience wrapper that exposes QObject::dumpObjectTree to QML.
**/
Q_INVOKABLE static void dumpObjectTree(QObject *object);
/**
* @brief Gets the parent of a QObject. Exposed to allow QML to access the
* QObject::parent property of a QObject.
**/
Q_INVOKABLE static QObject *GetParent(QObject *object);
/**
* @brief Returns true if @p ancestor is an ancestor of @p descendant
* (or is @p descendant itself).
*
* Walks up the QObject::parent() chain from @p descendant. Returns false
* if either argument is nullptr.
**/
Q_INVOKABLE static bool IsAncestorOf(QObject *ancestor, QObject *descendant);
/**
* @brief Sets the parent of a QObject to a new parent at a specific index.
*
* Use this method when precise control over the child insertion index is
* required or when you need the model to emit @c moveRows signals for both
* same-parent reorders and cross-parent moves.
*
* For QWidget objects, the model also detects reparents performed via the
* standard @c QWidget::setParent(QWidget*) API (through @c ParentChange
* event handling), so using @c SetParent is not strictly required for
* widgets. However, @c QWidget::setParent does not allow specifying the
* child index and always appends.
*
* Both QObject and QWidget children are supported. For QWidgets the method
* delegates to QWidget::setParent internally to ensure proper window-system
* setup, visibility propagation, and style inheritance. A QWidget can only
* be parented to another QWidget; passing a non-widget parent for a QWidget
* child is a programming error (asserts in debug, returns in release).
* A plain QObject can be a child of either a QObject or a QWidget.
*
* @param object The object to reparent.
* @param parent The new parent (may be nullptr to unparent). Must be a
* QWidget if @p object is a QWidget.
* @param index The position among the new parent's children. Negative values
* index from the end (-1 = last, -2 = second-to-last, etc.).
* Values beyond the end are clamped.
* @return true if the reparent succeeded, false if the operation was
* rejected (e.g. circular hierarchy, thread mismatch, QWidget
* parented to a non-QWidget).
*/
Q_INVOKABLE static bool SetParent(QObject *object, QObject *parent, int index);
/**
* @brief Same as above but will append rather than insert at a given index.
*/
Q_INVOKABLE static bool SetParent(QObject *object, QObject *parent);
protected:
/**
* QAbstractItemModel::{insertRows/insertColumns} should never be called.
* Item insertion is controlled by reparenting the QObjects in the model
* either by |QObject::setParent| or |QObject::deleteLater|.
* |QObjectTreeModel::SetParent| can also be used for more control over the
* child order.
**/
bool insertRows(int, int, const QModelIndex &index = QModelIndex()) override;
bool insertColumns(int, int, const QModelIndex &index = QModelIndex()) override;
bool eventFilter(QObject *object, QEvent *event) override;
private:
friend class ::QObjectTreeModelTest;
// Used to setup the model during construction.
void populateFromObjectRecursive(QObject *object);
// Called after a QObject is added to the model to track state changed on the QObject.
void connectObject(QObject *object);
// Called when a QObject is removed from the model but not deleted.
void disconnectObject(QObject *object);
// Handles ChildAdded events for QObjects in the model.
void handleChildAddedEvent(QObject *child);
// Handles ChildRemoved events for QObjects in the model. Detects
// reparenting (move) vs true removal. For raw-deleted objects this is a
// no-op because handleObjectDestroyed already removed the subtree before
// ~QObject sends the ChildRemoved event. |eventParent| is the QObject that
// received the ChildRemoved event (i.e. the parent that lost the child).
void handleChildRemovedEvent(QObject *child, QObject *eventParent);
// Handles DeferredDelete events for QObjects in the model. This will remove
// the object from the model before it is deleted.
void handleDeferredDeleteEvent(QObject *object);
// Handles ParentChange events for QWidgets in the model. ParentChange is
// sent by QWidget::setParent after the reparent completes. This provides a
// fallback for detecting reparents when QWidget::setParent() suppresses the
// ChildAdded/ChildRemoved events that the model normally relies on. The
// handler is idempotent: if the reparent was already processed via
// ChildRemoved/ChildAdded, this is a no-op.
void handleParentChangeEvent(QObject *object);
// Slots that model QObjects are connected up to by connectObject.
// Handles QObject::destroyed for raw delete. The destroyed signal fires at
// the start of ~QObject while all descendants are still alive, allowing
// safe removal of the entire subtree. For the deleteLater path the object
// is already removed by handleDeferredDeleteEvent, so this is a no-op. See
// the implementation comment for the full destruction sequence.
Q_SLOT void handleObjectDestroyed(QObject *object);
void handleObjectNameChanged(QObject *object);
// Handles the special case where the root QObject is destroyed while the
// model is still alive. Resets the model and creates a new internal root.
void handleRootDestroyed();
/**
* @brief Appends the provided list of children into the model. All children
* must have the same parent. The parent must already be known to the model.
**/
void appendChildObjects(const QObjectList &children);
/**
* @brief Inserts the provided list of children into the model. All children
* must have the same parent. The parent must already be known to the model.
* "insertIndex" specifies the index the first child in the list should be
* inserted into, if the list contains more than one child then subsequent
* children will be inserted after the first child maintaining the provided
* list order.
**/
void insertChildObjects(const QObjectList &children, qsizetype insertIndex);
// Overloads that accept a pre-computed parent index to avoid redundant
// indexOf lookups during recursive tree insertion.
void appendChildObjects(const QObjectList &children, const QModelIndex &parentIndex);
void insertChildObjects(const QObjectList &children,
qsizetype insertIndex,
const QModelIndex &parentIndex);
/**
* @brief Removes the provided object and all of its children recursively
* from the internal maps. |disconnectObject| will also be run on every
* object in the tree.
**/
void removeAndDisconnectSubtree(QObject *object);
QObject *m_root = nullptr;
std::unordered_map<QObject *, QObject *> m_childParentMap;
std::unordered_map<QObject *, QObjectList> m_parentChildMap;
};