Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
objectinspectormodel.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3// Qt-Security score:significant reason:default
4
6
7#include <qlayout_widget_p.h>
8#include <layout_p.h>
9#include <qdesigner_propertycommand_p.h>
10#include <qdesigner_utils_p.h>
11#include <iconloader_p.h>
12
13#include <QtDesigner/abstractformeditor.h>
14#include <QtDesigner/abstractformwindow.h>
15#include <QtDesigner/abstractwidgetdatabase.h>
16#include <QtDesigner/container.h>
17#include <QtDesigner/abstractmetadatabase.h>
18#include <QtDesigner/qextensionmanager.h>
19#include <QtWidgets/qlayout.h>
20#include <QtWidgets/qlayoutitem.h>
21#include <QtWidgets/qmenu.h>
22#include <QtWidgets/qbuttongroup.h>
23
24#include <QtGui/qaction.h>
25
26#include <QtCore/qset.h>
27#include <QtCore/qdebug.h>
28#include <QtCore/qcoreapplication.h>
29
30#include <algorithm>
31
32QT_BEGIN_NAMESPACE
33
34using namespace Qt::StringLiterals;
35
36namespace {
37 enum { DataRole = 1000 };
38}
39
40static inline QObject *objectOfItem(const QStandardItem *item) {
41 return qvariant_cast<QObject *>(item->data(DataRole));
42}
43
44static bool sameIcon(const QIcon &i1, const QIcon &i2)
45{
46 if (i1.isNull() && i2.isNull())
47 return true;
48 if (i1.isNull() != i2.isNull())
49 return false;
50 return i1.cacheKey() == i2.cacheKey();
51}
52
53static inline bool isNameColumnEditable(const QObject *o)
54{
55 if (auto *action = qobject_cast<const QAction *>(o))
56 return !action->isSeparator();
57 return true;
58}
59
61{
62 qdesigner_internal::ObjectData::StandardItemList rc;
63 const Qt::ItemFlags baseFlags = Qt::ItemIsSelectable|Qt::ItemIsDropEnabled|Qt::ItemIsEnabled;
65 QStandardItem *item = new QStandardItem;
66 Qt::ItemFlags flags = baseFlags;
67 if (i == qdesigner_internal::ObjectInspectorModel::ObjectNameColumn && isNameColumnEditable(o))
68 flags |= Qt::ItemIsEditable;
69 item->setFlags(flags);
70 rc += item;
71 }
72 return rc;
73}
74
75static inline bool isQLayoutWidget(const QObject *o)
76{
77 return o->metaObject() == &QLayoutWidget::staticMetaObject;
78}
79
80namespace qdesigner_internal {
81
82 // context kept while building a model, just there to reduce string allocations
84 explicit ModelRecursionContext(QDesignerFormEditorInterface *core, const QString &sepName);
85
86 const QString designerPrefix;
87 const QString separator;
88
89 QDesignerFormEditorInterface *core;
92 };
93
94 ModelRecursionContext::ModelRecursionContext(QDesignerFormEditorInterface *c, const QString &sepName) :
95 designerPrefix(u"QDesigner"_s),
97 core(c),
100 {
101 }
102
103 // ------------ ObjectData/ ObjectModel:
104 // Whenever the selection changes, ObjectInspector::setFormWindow is
105 // called. To avoid rebuilding the tree every time (loosing expanded state)
106 // a model is first built from the object tree by recursion.
107 // As a tree is difficult to represent, a flat list of entries (ObjectData)
108 // containing object and parent object is used.
109 // ObjectData has an overloaded operator== that compares the object pointers.
110 // Structural changes which cause a rebuild can be detected by
111 // comparing the lists of ObjectData. If it is the same, only the item data (class name [changed by promotion],
112 // object name and icon) are checked and the existing items are updated.
113
114 ObjectData::ObjectData() = default;
115
116 ObjectData::ObjectData(QObject *parent, QObject *object, const ModelRecursionContext &ctx) :
121 {
122
123 // 1) set entry
124 if (object->isWidgetType()) {
125 initWidget(static_cast<QWidget*>(object), ctx);
126 } else {
127 initObject(ctx);
128 }
129 if (m_className.startsWith(ctx.designerPrefix))
130 m_className.remove(1, ctx.designerPrefix.size() - 1);
131 }
132
133 void ObjectData::initObject(const ModelRecursionContext &ctx)
134 {
135 // Check objects: Action?
136 if (const QAction *act = qobject_cast<const QAction*>(m_object)) {
137 if (act->isSeparator()) { // separator is reserved
138 m_objectName = ctx.separator;
139 m_type = SeparatorAction;
140 } else {
141 m_type = Action;
142 }
143 m_classIcon = act->icon();
144 } else {
145 m_type = Object;
146 }
147 }
148
149 void ObjectData::initWidget(QWidget *w, const ModelRecursionContext &ctx)
150 {
151 // Check for extension container, QLayoutwidget, or normal container
152 bool isContainer = false;
153 if (const QDesignerWidgetDataBaseItemInterface *widgetItem = ctx.db->item(ctx.db->indexOfObject(w, true))) {
154 m_classIcon = widgetItem->icon();
155 m_className = widgetItem->name();
156 isContainer = widgetItem->isContainer();
157 }
158
159 // We might encounter temporary states with no layouts when re-layouting.
160 // Just default to Widget handling for the moment.
161 if (isQLayoutWidget(w)) {
162 if (const QLayout *layout = w->layout()) {
163 m_type = LayoutWidget;
164 m_managedLayoutType = LayoutInfo::layoutType(ctx.core, layout);
165 m_className = QLatin1StringView(layout->metaObject()->className());
166 m_objectName = layout->objectName();
167 }
168 return;
169 }
170
171 if (qt_extension<QDesignerContainerExtension*>(ctx.core->extensionManager(), w)) {
172 m_type = ExtensionContainer;
173 return;
174 }
175 if (isContainer) {
176 m_type = LayoutableContainer;
177 m_managedLayoutType = LayoutInfo::managedLayoutType(ctx.core, w);
178 return;
179 }
180 m_type = ChildWidget;
181 }
182
183 bool ObjectData::equals(const ObjectData & me) const
184 {
185 return m_parent == me.m_parent && m_object == me.m_object;
186 }
187
188 unsigned ObjectData::compare(const ObjectData & rhs) const
189 {
190 unsigned rc = 0;
191 if (m_className != rhs.m_className)
192 rc |= ClassNameChanged;
193 if (m_objectName != rhs.m_objectName)
194 rc |= ObjectNameChanged;
195 if (!sameIcon(m_classIcon, rhs.m_classIcon))
196 rc |= ClassIconChanged;
197 if (m_type != rhs.m_type)
198 rc |= TypeChanged;
199 if (m_managedLayoutType != rhs.m_managedLayoutType)
200 rc |= LayoutTypeChanged;
201 return rc;
202 }
203
204 void ObjectData::setItemsDisplayData(const StandardItemList &row, const ObjectInspectorIcons &icons, unsigned mask) const
205 {
206 if (mask & ObjectNameChanged)
207 row[ObjectInspectorModel::ObjectNameColumn]->setText(m_objectName);
208 if (mask & ClassNameChanged) {
209 row[ObjectInspectorModel::ClassNameColumn]->setText(m_className);
210 row[ObjectInspectorModel::ClassNameColumn]->setToolTip(m_className);
211 }
212 // Set a layout icon only for containers. Note that QLayoutWidget don't have
213 // real class icons
215 switch (m_type) {
216 case LayoutWidget:
217 row[ObjectInspectorModel::ObjectNameColumn]->setIcon(icons.layoutIcons[m_managedLayoutType]);
218 row[ObjectInspectorModel::ClassNameColumn]->setIcon(icons.layoutIcons[m_managedLayoutType]);
219 break;
221 row[ObjectInspectorModel::ObjectNameColumn]->setIcon(icons.layoutIcons[m_managedLayoutType]);
222 row[ObjectInspectorModel::ClassNameColumn]->setIcon(m_classIcon);
223 break;
224 default:
225 row[ObjectInspectorModel::ObjectNameColumn]->setIcon(QIcon());
226 row[ObjectInspectorModel::ClassNameColumn]->setIcon(m_classIcon);
227 break;
228 }
229 }
230 }
231
232 void ObjectData::setItems(const StandardItemList &row, const ObjectInspectorIcons &icons) const
233 {
234 const QVariant object = QVariant::fromValue(m_object);
235 row[ObjectInspectorModel::ObjectNameColumn]->setData(object, DataRole);
236 row[ObjectInspectorModel::ClassNameColumn]->setData(object, DataRole);
237 setItemsDisplayData(row, icons, ClassNameChanged|ObjectNameChanged|ClassIconChanged|TypeChanged|LayoutTypeChanged);
238 }
239
240 // Recursive routine that creates the model by traversing the form window object tree.
241 void createModelRecursion(const QDesignerFormWindowInterface *fwi,
242 QObject *parent,
243 QObject *object,
244 ObjectModel &model,
245 const ModelRecursionContext &ctx)
246 {
247 using ButtonGroupList = QList<QButtonGroup *>;
248 // 1) Create entry
249 const ObjectData entry(parent, object, ctx);
250 model.push_back(entry);
251
252 // 2) recurse over widget children via container extension or children list
253 const QDesignerContainerExtension *containerExtension = nullptr;
255 containerExtension = qt_extension<QDesignerContainerExtension*>(fwi->core()->extensionManager(), object);
256 Q_ASSERT(containerExtension);
257 const int count = containerExtension->count();
258 for (int i=0; i < count; ++i) {
259 QObject *page = containerExtension->widget(i);
260 Q_ASSERT(page != nullptr);
261 createModelRecursion(fwi, object, page, model, ctx);
262 }
263 }
264
265 if (!object->children().isEmpty()) {
266 ButtonGroupList buttonGroups;
267 for (QObject *childObject : object->children()) {
268 // Managed child widgets unless we had a container extension
269 if (childObject->isWidgetType()) {
270 if (!containerExtension) {
271 QWidget *widget = qobject_cast<QWidget*>(childObject);
272 if (fwi->isManaged(widget))
273 createModelRecursion(fwi, object, widget, model, ctx);
274 }
275 } else {
276 if (ctx.mdb->item(childObject)) {
277 if (auto bg = qobject_cast<QButtonGroup*>(childObject))
278 buttonGroups.push_back(bg);
279 } // Has MetaDataBase entry
280 }
281 }
282 // Add button groups
283 if (!buttonGroups.isEmpty()) {
284 for (QButtonGroup *group : std::as_const(buttonGroups))
285 createModelRecursion(fwi, object, group, model, ctx);
286 }
287 } // has children
288 if (object->isWidgetType()) {
289 // Add actions
290 const auto actions = static_cast<QWidget*>(object)->actions();
291 for (QAction *action : actions) {
292 if (ctx.mdb->item(action)) {
293 QObject *childObject = action;
294 if (auto menu = action->menu())
295 childObject = menu;
296 createModelRecursion(fwi, object, childObject, model, ctx);
297 }
298 }
299 }
300 }
301
302 // ------------ ObjectInspectorModel
305 {
306 QStringList headers;
307 headers += QCoreApplication::translate("ObjectInspectorModel", "Object");
308 headers += QCoreApplication::translate("ObjectInspectorModel", "Class");
309 Q_ASSERT(headers.size() == NumColumns);
310 setColumnCount(NumColumns);
311 setHorizontalHeaderLabels(headers);
312 // Icons
313 m_icons.layoutIcons[LayoutInfo::NoLayout] = createIconSet("editbreaklayout.png"_L1);
314 m_icons.layoutIcons[LayoutInfo::HSplitter] = createIconSet("edithlayoutsplit.png"_L1);
315 m_icons.layoutIcons[LayoutInfo::VSplitter] = createIconSet("editvlayoutsplit.png"_L1);
316 m_icons.layoutIcons[LayoutInfo::HBox] = createIconSet("edithlayout.png"_L1);
317 m_icons.layoutIcons[LayoutInfo::VBox] = createIconSet("editvlayout.png"_L1);
318 m_icons.layoutIcons[LayoutInfo::Grid] = createIconSet("editgrid.png"_L1);
319 m_icons.layoutIcons[LayoutInfo::Form] = createIconSet("editform.png"_L1);
320 }
321
322 void ObjectInspectorModel::clearItems()
323 {
324 beginResetModel();
325 m_objectIndexMultiMap.clear();
326 m_model.clear();
327 endResetModel(); // force editors to be closed in views
328 removeRow(0);
329 }
330
332 {
333 QWidget *mainContainer = fw ? fw->mainContainer() : nullptr;
334 if (!mainContainer) {
335 clearItems();
336 m_formWindow = nullptr;
337 return NoForm;
338 }
339 m_formWindow = fw;
340 // Build new model and compare to previous one. If the structure is
341 // identical, just update, else rebuild
342 ObjectModel newModel;
343
344 static const QString separator = QCoreApplication::translate("ObjectInspectorModel", "separator");
345 const ModelRecursionContext ctx(fw->core(), separator);
346 createModelRecursion(fw, nullptr, mainContainer, newModel, ctx);
347
348 if (newModel == m_model) {
349 updateItemContents(m_model, newModel);
350 return Updated;
351 }
352
353 rebuild(newModel);
354 m_model = newModel;
355 return Rebuilt;
356 }
357
358 QObject *ObjectInspectorModel::objectAt(const QModelIndex &index) const
359 {
360 if (index.isValid())
361 if (const QStandardItem *item = itemFromIndex(index))
362 return objectOfItem(item);
363 return nullptr;
364 }
365
366 // Missing Qt API: get a row
367 ObjectInspectorModel::StandardItemList ObjectInspectorModel::rowAt(QModelIndex index) const
368 {
369 StandardItemList rc;
370 while (true) {
371 rc += itemFromIndex(index);
372 const int nextColumn = index.column() + 1;
373 if (nextColumn >= NumColumns)
374 break;
375 index = index.sibling(index.row(), nextColumn);
376 }
377 return rc;
378 }
379
380 // Rebuild the tree in case the model has completely changed.
381 void ObjectInspectorModel::rebuild(const ObjectModel &newModel)
382 {
383 clearItems();
384 if (newModel.isEmpty())
385 return;
386
387 const auto mcend = newModel.cend();
388 auto it = newModel.cbegin();
389 // Set up root element
390 StandardItemList rootRow = createModelRow(it->object());
391 it->setItems(rootRow, m_icons);
392 appendRow(rootRow);
393 m_objectIndexMultiMap.insert(it->object(), indexFromItem(rootRow.constFirst()));
394 for (++it; it != mcend; ++it) {
395 // Add to parent item, found via map
396 const QModelIndex parentIndex = m_objectIndexMultiMap.value(it->parent(), QModelIndex());
397 Q_ASSERT(parentIndex.isValid());
398 QStandardItem *parentItem = itemFromIndex(parentIndex);
399 StandardItemList row = createModelRow(it->object());
400 it->setItems(row, m_icons);
401 parentItem->appendRow(row);
402 m_objectIndexMultiMap.insert(it->object(), indexFromItem(row.constFirst()));
403 }
404 }
405
406 // Update item data in case the model has the same structure
407 void ObjectInspectorModel::updateItemContents(ObjectModel &oldModel, const ObjectModel &newModel)
408 {
409 // Change text and icon. Keep a set of changed object
410 // as for example actions might occur several times in the tree.
411 using QObjectSet = QSet<QObject *>;
412
413 QObjectSet changedObjects;
414
415 const auto size = newModel.size();
416 Q_ASSERT(oldModel.size() == size);
417 for (qsizetype i = 0; i < size; ++i) {
418 const ObjectData &newEntry = newModel.at(i);
419 ObjectData &entry = oldModel[i];
420 // Has some data changed?
421 if (const unsigned changedMask = entry.compare(newEntry)) {
422 entry = newEntry;
423 QObject * o = entry.object();
424 if (!changedObjects.contains(o)) {
425 changedObjects.insert(o);
426 const QModelIndexList indexes = m_objectIndexMultiMap.values(o);
427 for (const QModelIndex &index : indexes)
428 entry.setItemsDisplayData(rowAt(index), m_icons, changedMask);
429 }
430 }
431 }
432 }
433
434 QVariant ObjectInspectorModel::data(const QModelIndex &index, int role) const
435 {
436 const QVariant rc = QStandardItemModel::data(index, role);
437 // Return <noname> if the string is empty for the display role
438 // only (else, editing starts with <noname>).
439 if (role == Qt::DisplayRole && rc.metaType().id() == QMetaType::QString) {
440 const QString s = rc.toString();
441 if (s.isEmpty()) {
442 static const QString noName = QCoreApplication::translate("ObjectInspectorModel", "<noname>");
443 return QVariant(noName);
444 }
445 }
446 return rc;
447 }
448
449 bool ObjectInspectorModel::setData(const QModelIndex &index, const QVariant &value, int role)
450 {
451 if (role != Qt::EditRole || !m_formWindow)
452 return false;
453
454 QObject *object = objectAt(index);
455 if (!object)
456 return false;
457 // Is this a layout widget?
458 const QString nameProperty = isQLayoutWidget(object) ? u"layoutName"_s : u"objectName"_s;
459 m_formWindow->commandHistory()->push(createTextPropertyCommand(nameProperty, value.toString(), object, m_formWindow));
460 return true;
461 }
462}
463
464QT_END_NAMESPACE
friend class QWidget
Definition qpainter.h:432
ObjectData(QObject *parent, QObject *object, const ModelRecursionContext &ctx)
bool equals(const ObjectData &me) const
void setItemsDisplayData(const StandardItemList &row, const ObjectInspectorIcons &icons, unsigned mask) const
void setItems(const StandardItemList &row, const ObjectInspectorIcons &icons) const
unsigned compare(const ObjectData &me) const
QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const override
Returns the data stored under the given role for the item referred to by the index.
bool setData(const QModelIndex &index, const QVariant &value, int role=Qt::EditRole) override
Sets the role data for the item at index to value.
UpdateResult update(QDesignerFormWindowInterface *fw)
QObject * objectAt(const QModelIndex &index) const
Auxiliary methods to store/retrieve settings.
void createModelRecursion(const QDesignerFormWindowInterface *fwi, QObject *parent, QObject *object, ObjectModel &model, const ModelRecursionContext &ctx)
static bool sameIcon(const QIcon &i1, const QIcon &i2)
static bool isQLayoutWidget(const QObject *o)
static bool isNameColumnEditable(const QObject *o)
static qdesigner_internal::ObjectData::StandardItemList createModelRow(const QObject *o)
static QObject * objectOfItem(const QStandardItem *item)
const QDesignerWidgetDataBaseInterface * db
const QDesignerMetaDataBaseInterface * mdb
ModelRecursionContext(QDesignerFormEditorInterface *core, const QString &sepName)