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