diff options
Diffstat (limited to 'src/designer/src/components/objectinspector/objectinspectormodel.cpp')
-rw-r--r-- | src/designer/src/components/objectinspector/objectinspectormodel.cpp | 516 |
1 files changed, 516 insertions, 0 deletions
diff --git a/src/designer/src/components/objectinspector/objectinspectormodel.cpp b/src/designer/src/components/objectinspector/objectinspectormodel.cpp new file mode 100644 index 000000000..20f0ff18f --- /dev/null +++ b/src/designer/src/components/objectinspector/objectinspectormodel.cpp @@ -0,0 +1,516 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Designer of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "objectinspectormodel_p.h" + +#include <qlayout_widget_p.h> +#include <layout_p.h> +#include <qdesigner_propertycommand_p.h> +#include <qdesigner_utils_p.h> +#include <iconloader_p.h> + +#include <QtDesigner/QDesignerFormEditorInterface> +#include <QtDesigner/QDesignerFormWindowInterface> +#include <QtDesigner/QDesignerWidgetDataBaseInterface> +#include <QtDesigner/QDesignerContainerExtension> +#include <QtDesigner/QDesignerMetaDataBaseInterface> +#include <QtDesigner/QExtensionManager> +#include <QtGui/QLayout> +#include <QtGui/QAction> +#include <QtGui/QLayoutItem> +#include <QtGui/QMenu> +#include <QtGui/QButtonGroup> +#include <QtCore/QSet> +#include <QtCore/QDebug> +#include <QtCore/QCoreApplication> + +QT_BEGIN_NAMESPACE + +namespace { + enum { DataRole = 1000 }; +} + +static inline QObject *objectOfItem(const QStandardItem *item) { + return qvariant_cast<QObject *>(item->data(DataRole)); +} + +static bool sortEntry(const QObject *a, const QObject *b) +{ + return a->objectName() < b->objectName(); +} + +static bool sameIcon(const QIcon &i1, const QIcon &i2) +{ + if (i1.isNull() && i2.isNull()) + return true; + if (i1.isNull() != i2.isNull()) + return false; + return i1.serialNumber() == i2.serialNumber(); +} + +static inline bool isNameColumnEditable(const QObject *) +{ + return true; +} + +static qdesigner_internal::ObjectData::StandardItemList createModelRow(const QObject *o) +{ + qdesigner_internal::ObjectData::StandardItemList rc; + const Qt::ItemFlags baseFlags = Qt::ItemIsSelectable|Qt::ItemIsDropEnabled|Qt::ItemIsEnabled; + for (int i = 0; i < qdesigner_internal::ObjectInspectorModel::NumColumns; i++) { + QStandardItem *item = new QStandardItem; + Qt::ItemFlags flags = baseFlags; + if (i == qdesigner_internal::ObjectInspectorModel::ObjectNameColumn && isNameColumnEditable(o)) + flags |= Qt::ItemIsEditable; + item->setFlags(flags); + rc += item; + } + return rc; +} + +static inline bool isQLayoutWidget(const QObject *o) +{ + return o->metaObject() == &QLayoutWidget::staticMetaObject; +} + +namespace qdesigner_internal { + + // context kept while building a model, just there to reduce string allocations + struct ModelRecursionContext { + explicit ModelRecursionContext(QDesignerFormEditorInterface *core, const QString &sepName); + + const QString designerPrefix; + const QString separator; + + QDesignerFormEditorInterface *core; + const QDesignerWidgetDataBaseInterface *db; + const QDesignerMetaDataBaseInterface *mdb; + }; + + ModelRecursionContext::ModelRecursionContext(QDesignerFormEditorInterface *c, const QString &sepName) : + designerPrefix(QLatin1String("QDesigner")), + separator(sepName), + core(c), + db(c->widgetDataBase()), + mdb(c->metaDataBase()) + { + } + + // ------------ ObjectData/ ObjectModel: + // Whenever the selection changes, ObjectInspector::setFormWindow is + // called. To avoid rebuilding the tree every time (loosing expanded state) + // a model is first built from the object tree by recursion. + // As a tree is difficult to represent, a flat list of entries (ObjectData) + // containing object and parent object is used. + // ObjectData has an overloaded operator== that compares the object pointers. + // Structural changes which cause a rebuild can be detected by + // comparing the lists of ObjectData. If it is the same, only the item data (class name [changed by promotion], + // object name and icon) are checked and the existing items are updated. + + ObjectData::ObjectData() : + m_parent(0), + m_object(0), + m_type(Object), + m_managedLayoutType(LayoutInfo::NoLayout) + { + } + + ObjectData::ObjectData(QObject *parent, QObject *object, const ModelRecursionContext &ctx) : + m_parent(parent), + m_object(object), + m_type(Object), + m_className(QLatin1String(object->metaObject()->className())), + m_objectName(object->objectName()), + m_managedLayoutType(LayoutInfo::NoLayout) + { + + // 1) set entry + if (object->isWidgetType()) { + initWidget(static_cast<QWidget*>(object), ctx); + } else { + initObject(ctx); + } + if (m_className.startsWith(ctx.designerPrefix)) + m_className.remove(1, ctx.designerPrefix.size() - 1); + } + + void ObjectData::initObject(const ModelRecursionContext &ctx) + { + // Check objects: Action? + if (const QAction *act = qobject_cast<const QAction*>(m_object)) { + if (act->isSeparator()) { // separator is reserved + m_objectName = ctx.separator; + m_type = SeparatorAction; + } else { + m_type = Action; + } + m_classIcon = act->icon(); + } else { + m_type = Object; + } + } + + void ObjectData::initWidget(QWidget *w, const ModelRecursionContext &ctx) + { + // Check for extension container, QLayoutwidget, or normal container + bool isContainer = false; + if (const QDesignerWidgetDataBaseItemInterface *widgetItem = ctx.db->item(ctx.db->indexOfObject(w, true))) { + m_classIcon = widgetItem->icon(); + m_className = widgetItem->name(); + isContainer = widgetItem->isContainer(); + } + + // We might encounter temporary states with no layouts when re-layouting. + // Just default to Widget handling for the moment. + if (isQLayoutWidget(w)) { + if (const QLayout *layout = w->layout()) { + m_type = LayoutWidget; + m_managedLayoutType = LayoutInfo::layoutType(ctx.core, layout); + m_className = QLatin1String(layout->metaObject()->className()); + m_objectName = layout->objectName(); + } + return; + } + + if (qt_extension<QDesignerContainerExtension*>(ctx.core->extensionManager(), w)) { + m_type = ExtensionContainer; + return; + } + if (isContainer) { + m_type = LayoutableContainer; + m_managedLayoutType = LayoutInfo::managedLayoutType(ctx.core, w); + return; + } + m_type = ChildWidget; + } + + bool ObjectData::equals(const ObjectData & me) const + { + return m_parent == me.m_parent && m_object == me.m_object; + } + + unsigned ObjectData::compare(const ObjectData & rhs) const + { + unsigned rc = 0; + if (m_className != rhs.m_className) + rc |= ClassNameChanged; + if (m_objectName != rhs.m_objectName) + rc |= ObjectNameChanged; + if (!sameIcon(m_classIcon, rhs.m_classIcon)) + rc |= ClassIconChanged; + if (m_type != rhs.m_type) + rc |= TypeChanged; + if (m_managedLayoutType != rhs.m_managedLayoutType) + rc |= LayoutTypeChanged; + return rc; + } + + void ObjectData::setItemsDisplayData(const StandardItemList &row, const ObjectInspectorIcons &icons, unsigned mask) const + { + if (mask & ObjectNameChanged) + row[ObjectInspectorModel::ObjectNameColumn]->setText(m_objectName); + if (mask & ClassNameChanged) { + row[ObjectInspectorModel::ClassNameColumn]->setText(m_className); + row[ObjectInspectorModel::ClassNameColumn]->setToolTip(m_className); + } + // Set a layout icon only for containers. Note that QLayoutWidget don't have + // real class icons + if (mask & (ClassIconChanged|TypeChanged|LayoutTypeChanged)) { + switch (m_type) { + case LayoutWidget: + row[ObjectInspectorModel::ObjectNameColumn]->setIcon(icons.layoutIcons[m_managedLayoutType]); + row[ObjectInspectorModel::ClassNameColumn]->setIcon(icons.layoutIcons[m_managedLayoutType]); + break; + case LayoutableContainer: + row[ObjectInspectorModel::ObjectNameColumn]->setIcon(icons.layoutIcons[m_managedLayoutType]); + row[ObjectInspectorModel::ClassNameColumn]->setIcon(m_classIcon); + break; + default: + row[ObjectInspectorModel::ObjectNameColumn]->setIcon(QIcon()); + row[ObjectInspectorModel::ClassNameColumn]->setIcon(m_classIcon); + break; + } + } + } + + void ObjectData::setItems(const StandardItemList &row, const ObjectInspectorIcons &icons) const + { + const QVariant object = QVariant::fromValue(m_object); + row[ObjectInspectorModel::ObjectNameColumn]->setData(object, DataRole); + row[ObjectInspectorModel::ClassNameColumn]->setData(object, DataRole); + setItemsDisplayData(row, icons, ClassNameChanged|ObjectNameChanged|ClassIconChanged|TypeChanged|LayoutTypeChanged); + } + + typedef QList<ObjectData> ObjectModel; + + // Recursive routine that creates the model by traversing the form window object tree. + void createModelRecursion(const QDesignerFormWindowInterface *fwi, + QObject *parent, + QObject *object, + ObjectModel &model, + const ModelRecursionContext &ctx) + { + typedef QList<QButtonGroup *> ButtonGroupList; + typedef QList<QAction *> ActionList; + + // 1) Create entry + const ObjectData entry(parent, object, ctx); + model.push_back(entry); + + // 2) recurse over widget children via container extension or children list + const QDesignerContainerExtension *containerExtension = 0; + if (entry.type() == ObjectData::ExtensionContainer) { + containerExtension = qt_extension<QDesignerContainerExtension*>(fwi->core()->extensionManager(), object); + Q_ASSERT(containerExtension); + const int count = containerExtension->count(); + for (int i=0; i < count; ++i) { + QObject *page = containerExtension->widget(i); + Q_ASSERT(page != 0); + createModelRecursion(fwi, object, page, model, ctx); + } + } + + QObjectList children = object->children(); + if (!children.empty()) { + ButtonGroupList buttonGroups; + qSort(children.begin(), children.end(), sortEntry); + const QObjectList::const_iterator cend = children.constEnd(); + for (QObjectList::const_iterator it = children.constBegin(); it != cend; ++it) { + // Managed child widgets unless we had a container extension + if ((*it)->isWidgetType()) { + if (!containerExtension) { + QWidget *widget = qobject_cast<QWidget*>(*it); + if (fwi->isManaged(widget)) + createModelRecursion(fwi, object, widget, model, ctx); + } + } else { + if (ctx.mdb->item(*it)) { + if (QButtonGroup *bg = qobject_cast<QButtonGroup*>(*it)) + buttonGroups.push_back(bg); + } // Has MetaDataBase entry + } + } + // Add button groups + if (!buttonGroups.empty()) { + const ButtonGroupList::const_iterator bgcend = buttonGroups.constEnd(); + for (ButtonGroupList::const_iterator bgit = buttonGroups.constBegin(); bgit != bgcend; ++bgit) + createModelRecursion(fwi, object, *bgit, model, ctx); + } + } // has children + if (object->isWidgetType()) { + // Add actions + const ActionList actions = static_cast<QWidget*>(object)->actions(); + if (!actions.empty()) { + const ActionList::const_iterator cend = actions.constEnd(); + for (ActionList::const_iterator it = actions.constBegin(); it != cend; ++it) + if (ctx.mdb->item(*it)) { + QAction *action = *it; + QObject *obj = action; + if (action->menu()) + obj = action->menu(); + createModelRecursion(fwi, object, obj, model, ctx); + } + } + } + } + + // ------------ ObjectInspectorModel + ObjectInspectorModel::ObjectInspectorModel(QObject *parent) : + QStandardItemModel(0, NumColumns, parent) + { + QStringList headers; + headers += QCoreApplication::translate("ObjectInspectorModel", "Object"); + headers += QCoreApplication::translate("ObjectInspectorModel", "Class"); + Q_ASSERT(headers.size() == NumColumns); + setColumnCount(NumColumns); + setHorizontalHeaderLabels(headers); + // Icons + m_icons.layoutIcons[LayoutInfo::NoLayout] = createIconSet(QLatin1String("editbreaklayout.png")); + m_icons.layoutIcons[LayoutInfo::HSplitter] = createIconSet(QLatin1String("edithlayoutsplit.png")); + m_icons.layoutIcons[LayoutInfo::VSplitter] = createIconSet(QLatin1String("editvlayoutsplit.png")); + m_icons.layoutIcons[LayoutInfo::HBox] = createIconSet(QLatin1String("edithlayout.png")); + m_icons.layoutIcons[LayoutInfo::VBox] = createIconSet(QLatin1String("editvlayout.png")); + m_icons.layoutIcons[LayoutInfo::Grid] = createIconSet(QLatin1String("editgrid.png")); + m_icons.layoutIcons[LayoutInfo::Form] = createIconSet(QLatin1String("editform.png")); + } + + void ObjectInspectorModel::clearItems() + { + m_objectIndexMultiMap.clear(); + m_model.clear(); + reset(); // force editors to be closed in views + removeRow(0); + } + + ObjectInspectorModel::UpdateResult ObjectInspectorModel::update(QDesignerFormWindowInterface *fw) + { + QWidget *mainContainer = fw ? fw->mainContainer() : static_cast<QWidget*>(0); + if (!mainContainer) { + clearItems(); + m_formWindow = 0; + return NoForm; + } + m_formWindow = fw; + // Build new model and compare to previous one. If the structure is + // identical, just update, else rebuild + ObjectModel newModel; + + static const QString separator = QCoreApplication::translate("ObjectInspectorModel", "separator"); + const ModelRecursionContext ctx(fw->core(), separator); + createModelRecursion(fw, 0, mainContainer, newModel, ctx); + + if (newModel == m_model) { + updateItemContents(m_model, newModel); + return Updated; + } + + rebuild(newModel); + m_model = newModel; + return Rebuilt; + } + + QObject *ObjectInspectorModel::objectAt(const QModelIndex &index) const + { + if (index.isValid()) + if (const QStandardItem *item = itemFromIndex(index)) + return objectOfItem(item); + return 0; + } + + // Missing Qt API: get a row + ObjectInspectorModel::StandardItemList ObjectInspectorModel::rowAt(QModelIndex index) const + { + StandardItemList rc; + while (true) { + rc += itemFromIndex(index); + const int nextColumn = index.column() + 1; + if (nextColumn >= NumColumns) + break; + index = index.sibling(index.row(), nextColumn); + } + return rc; + } + + // Rebuild the tree in case the model has completely changed. + void ObjectInspectorModel::rebuild(const ObjectModel &newModel) + { + clearItems(); + if (newModel.empty()) + return; + + const ObjectModel::const_iterator mcend = newModel.constEnd(); + ObjectModel::const_iterator it = newModel.constBegin(); + // Set up root element + StandardItemList rootRow = createModelRow(it->object()); + it->setItems(rootRow, m_icons); + appendRow(rootRow); + m_objectIndexMultiMap.insert(it->object(), indexFromItem(rootRow.front())); + for (++it; it != mcend; ++it) { + // Add to parent item, found via map + const QModelIndex parentIndex = m_objectIndexMultiMap.value(it->parent(), QModelIndex()); + Q_ASSERT(parentIndex.isValid()); + QStandardItem *parentItem = itemFromIndex(parentIndex); + StandardItemList row = createModelRow(it->object()); + it->setItems(row, m_icons); + parentItem->appendRow(row); + m_objectIndexMultiMap.insert(it->object(), indexFromItem(row.front())); + } + } + + // Update item data in case the model has the same structure + void ObjectInspectorModel::updateItemContents(ObjectModel &oldModel, const ObjectModel &newModel) + { + // Change text and icon. Keep a set of changed object + // as for example actions might occur several times in the tree. + typedef QSet<QObject *> QObjectSet; + + QObjectSet changedObjects; + + const int size = newModel.size(); + Q_ASSERT(oldModel.size() == size); + for (int i = 0; i < size; i++) { + const ObjectData &newEntry = newModel[i]; + ObjectData &entry = oldModel[i]; + // Has some data changed? + if (const unsigned changedMask = entry.compare(newEntry)) { + entry = newEntry; + QObject * o = entry.object(); + if (!changedObjects.contains(o)) { + changedObjects.insert(o); + const QModelIndexList indexes = m_objectIndexMultiMap.values(o); + foreach (const QModelIndex &index, indexes) + entry.setItemsDisplayData(rowAt(index), m_icons, changedMask); + } + } + } + } + + QVariant ObjectInspectorModel::data(const QModelIndex &index, int role) const + { + const QVariant rc = QStandardItemModel::data(index, role); + // Return <noname> if the string is empty for the display role + // only (else, editing starts with <noname>). + if (role == Qt::DisplayRole && rc.type() == QVariant::String) { + const QString s = rc.toString(); + if (s.isEmpty()) { + static const QString noName = QCoreApplication::translate("ObjectInspectorModel", "<noname>"); + return QVariant(noName); + } + } + return rc; + } + + bool ObjectInspectorModel::setData(const QModelIndex &index, const QVariant &value, int role) + { + if (role != Qt::EditRole || !m_formWindow) + return false; + + QObject *object = objectAt(index); + if (!object) + return false; + // Is this a layout widget? + const QString nameProperty = isQLayoutWidget(object) ? QLatin1String("layoutName") : QLatin1String("objectName"); + m_formWindow->commandHistory()->push(createTextPropertyCommand(nameProperty, value.toString(), object, m_formWindow)); + return true; + } +} + +QT_END_NAMESPACE |