From 3e0ae7951d6dd54d1fd5d5cef50230059e0ca67e Mon Sep 17 00:00:00 2001 From: Thomas Hartmann Date: Tue, 29 Aug 2023 12:16:26 +0200 Subject: [PATCH] QmlDesigner: Add PropertyTreeModel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This model represents all properties in the document in a tree model. PropertyListProxyModel allows to flatten a level into a list model. PropertyTreeModelDelegate exposed a single item to a combobox. Change-Id: I9b56f1ecc9aa57777356bc795b5a15b17559ae24 Reviewed-by: Qt CI Patch Build Bot Reviewed-by: Henning Gründl Reviewed-by: --- src/plugins/qmldesigner/CMakeLists.txt | 4 +- .../connectioneditor/propertytreemodel.cpp | 920 ++++++++++++++++++ .../connectioneditor/propertytreemodel.h | 186 ++++ .../designercore/model/modelutils.cpp | 8 + .../designercore/model/modelutils.h | 2 + 5 files changed, 1119 insertions(+), 1 deletion(-) create mode 100644 src/plugins/qmldesigner/components/connectioneditor/propertytreemodel.cpp create mode 100644 src/plugins/qmldesigner/components/connectioneditor/propertytreemodel.h diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt index 689c6bce9b0..7f94c8ff256 100644 --- a/src/plugins/qmldesigner/CMakeLists.txt +++ b/src/plugins/qmldesigner/CMakeLists.txt @@ -13,7 +13,7 @@ add_compile_options("$<$:-Wno-error=maybe-uninitial add_qtc_library(QmlDesignerUtils STATIC DEPENDS - Qt::Gui Utils Qt::QmlPrivate + Qt::Gui Utils Qt::QmlPrivate Core DEFINES QMLDESIGNERUTILS_LIBRARY PUBLIC_DEFINES $<$:QMLDESIGNER_STATIC_LIBRARY> @@ -486,6 +486,7 @@ add_qtc_plugin(QmlDesigner EXPLICIT_MOC components/propertyeditor/propertyeditorvalue.h components/connectioneditor/connectionviewwidget.h + qmldesignerplugin.h EXTRA_TRANSLATIONS "${PROJECT_SOURCE_DIR}/share/qtcreator/qmldesigner" PROPERTIES @@ -946,6 +947,7 @@ extend_qtc_plugin(QmlDesigner delegates.cpp delegates.h dynamicpropertiesmodel.cpp dynamicpropertiesmodel.h selectiondynamicpropertiesproxymodel.cpp selectiondynamicpropertiesproxymodel.h + propertytreemodel.cpp propertytreemodel.h ) extend_qtc_plugin(QmlDesigner diff --git a/src/plugins/qmldesigner/components/connectioneditor/propertytreemodel.cpp b/src/plugins/qmldesigner/components/connectioneditor/propertytreemodel.cpp new file mode 100644 index 00000000000..d5b5e754af3 --- /dev/null +++ b/src/plugins/qmldesigner/components/connectioneditor/propertytreemodel.cpp @@ -0,0 +1,920 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "propertytreemodel.h" +#include "connectionview.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +namespace QmlDesigner { + +const std::vector blockListProperties = {"children", + "data", + "childrenRect", + "icon", + "left", + "top", + "bottom", + "right", + "locale", + "objectName", + "transitions", + "states", + "resources", + "data", + "transformOrigin", + "transformOriginPoint", + "verticalCenter", + "horizontalCenter", + "anchors.bottom", + "anchors.top", + "anchors.left", + "anchors.right", + "anchors.fill", + "anchors.horizontalCenter", + "anchors.verticalCenter", + "anchors.centerIn", + "transform", + "visibleChildren"}; + +const std::vector blockListSlots = {"childAt", + "contains", + "destroy", + "dumpItemTree", + "ensurePolished", + "grabToImage", + "mapFromGlobal", + "mapFromItem", + "mapToGlobal", + "mapToItem", + "valueAt", + "toString", + "getText", + "inputMethodQuery", + "positionAt", + "positionToRectangle", + "isRightToLeft" + +}; + +const std::vector priorityListSignals = {"clicked", + "doubleClicked", + "pressed", + "released", + "toggled", + "valueModified", + "valueChanged", + "checkedChanged", + "moved", + "accepted", + "editingFinished", + "entered", + "exited", + "canceled", + "triggered", + "stateChanged", + "started", + "stopped", + "finished" + "enabledChanged", + "visibleChanged", + "opacityChanged", + "rotationChanged"}; + +const std::vector priorityListProperties + = {"opacity", "visible", "value", "x", "y", "width", "height", + "rotation", "color", "scale", "state", "enabled", "z", "text", + "pressed", "containsMouse", "checked", "hovered", "down", "clip", "parent"}; + +const std::vector priorityListSlots = {"toggle", + "increase", + "decrease", + "clear", + "complete", + "pause", + "restart", + "resume", + "start", + "stop", + "forceActiveFocus"}; + +std::vector properityLists() +{ + std::vector result; + + result.insert(result.end(), priorityListSignals.begin(), priorityListSignals.end()); + result.insert(result.end(), priorityListProperties.begin(), priorityListProperties.end()); + result.insert(result.end(), priorityListSlots.begin(), priorityListSlots.end()); + + return result; +} + +PropertyTreeModel::PropertyTreeModel(ConnectionView *parent) + : QAbstractItemModel(parent), m_connectionView(parent) +{} + +void PropertyTreeModel::resetModel() +{ + beginResetModel(); + + m_indexCache.clear(); + m_indexHash.clear(); + m_indexCount = 0; + m_nodeList = allModelNodesWithIdsSortedByDisplayName(); + + endResetModel(); + testModel(); +} + +QVariant PropertyTreeModel::data(const QModelIndex &index, int role) const +{ + int internalId = index.internalId(); + + if (role == InternalIdRole) + return internalId; + + if (role == RowRole) + return index.row(); + + if (role == PropertyNameRole || role == PropertyPriorityRole || role == ExpressionRole) { + if (!index.isValid()) + return {}; + + if (internalId < 0) + return {}; + + QTC_ASSERT(internalId < m_indexCount, return {"assert"}); + + DataCacheItem item = m_indexHash[index.internalId()]; + + if (item.propertyName.isEmpty()) { //node + if (role == PropertyNameRole) + return item.modelNode.displayName(); + + return true; //nodes are always shown + } + + if (role == ExpressionRole) + return QString(item.modelNode.id() + item.propertyName); + + if (role == PropertyNameRole) + return item.propertyName; + + static const auto priority = properityLists(); + if (std::find(priority.begin(), priority.end(), item.propertyName) != priority.end()) + return true; //listed priority properties + + auto dynamic = getDynamicProperties(item.modelNode); + if (std::find(dynamic.begin(), dynamic.end(), item.propertyName) != dynamic.end()) + return true; //dynamic properties have priority + + return false; + } + + // can be removed later since we only use the two roles above in QML + // just for testing + + if (!(role == Qt::DisplayRole || role == Qt::FontRole)) + return {}; + + if (!index.isValid()) + return {}; + + if (internalId < 0) + return {}; + + QTC_ASSERT(internalId < m_indexCount, return {"assert"}); + + DataCacheItem item = m_indexHash[index.internalId()]; + + if (item.propertyName.isEmpty()) { + const QString name = item.modelNode.displayName(); + if (role == Qt::DisplayRole) + return name; + QFont f; + f.setBold(true); + return f; + } + + if (role == Qt::DisplayRole) + return item.propertyName; + + QFont f; + auto priority = properityLists(); + if (std::find(priority.begin(), priority.end(), item.propertyName) != priority.end()) + f.setBold(true); + auto dynamic = getDynamicProperties(item.modelNode); + if (std::find(dynamic.begin(), dynamic.end(), item.propertyName) != dynamic.end()) + f.setBold(true); + + return f; +} + +Qt::ItemFlags PropertyTreeModel::flags(const QModelIndex &) const +{ + return Qt::ItemIsEnabled; +} + +QModelIndex PropertyTreeModel::index(int row, int column, const QModelIndex &parent) const +{ + int internalId = parent.internalId(); + if (!m_connectionView->isAttached()) + return {}; + + if (!hasIndex(row, column, parent)) + return QModelIndex(); + + const int rootId = -1; + + if (!parent.isValid()) + return createIndex(0, 0, rootId); + + if (internalId == rootId) { //root level model node + const ModelNode modelNode = m_nodeList[row]; + return ensureModelIndex(modelNode, row); + } + + //property + + QTC_ASSERT(internalId >= 0, return {}); + + DataCacheItem item = m_indexHash[internalId]; + QTC_ASSERT(item.modelNode.isValid(), return {}); + + if (!item.propertyName.isEmpty()) { + // "." aka sub property + auto properties = sortedDotPropertyNamesSignalsSlots(item.modelNode.metaInfo(), + item.propertyName); + PropertyName propertyName = properties[row]; + return ensureModelIndex(item.modelNode, propertyName, row); + } + + auto properties = sortedAndFilteredPropertyNamesSignalsSlots(item.modelNode); + + PropertyName propertyName = properties[row]; + + return ensureModelIndex(item.modelNode, propertyName, row); +} + +QModelIndex PropertyTreeModel::parent(const QModelIndex &index) const +{ + if (!index.isValid()) + return {}; + + int internalId = index.internalId(); + + if (internalId == -1) + return {}; + + QTC_ASSERT(internalId < m_indexCount, return {}); + + const DataCacheItem item = m_indexHash[index.internalId()]; + + // no property means the parent is the root item + if (item.propertyName.isEmpty()) + return createIndex(0, 0, -1); + + if (item.propertyName.contains(".")) { + auto list = item.propertyName.split('.'); + DataCacheItem parent; + parent.modelNode = item.modelNode; + parent.propertyName = list.first(); + if (auto iter = m_indexCache.find(parent); iter != m_indexCache.end()) { + const auto vector = sortedAndFilteredPropertyNamesSignalsSlots(item.modelNode); + QList list(vector.begin(), vector.end()); + int row = list.indexOf(parent.propertyName); + return createIndex(row, 0, iter->internalIndex); + } + } + + // find the parent + + int row = m_nodeList.indexOf(item.modelNode); + return ensureModelIndex(item.modelNode, row); +} + +QPersistentModelIndex PropertyTreeModel::indexForInernalIdAndRow(int internalId, int row) +{ + return createIndex(row, 0, internalId); +} + +int PropertyTreeModel::rowCount(const QModelIndex &parent) const +{ + if (!m_connectionView->isAttached() || parent.column() > 0) + return 0; + + if (!parent.isValid()) + return 1; + + int internalId = parent.internalId(); + + if (internalId == -1) + return m_nodeList.size(); + + QTC_ASSERT(internalId < m_indexCount, return 0); + + DataCacheItem item = m_indexHash[internalId]; + if (!item.propertyName.isEmpty()) { + if (item.modelNode.metaInfo().property(item.propertyName).isPointer()) { + auto subProbs = sortedDotPropertyNamesSignalsSlots(item.modelNode.metaInfo(), + item.propertyName); + + return static_cast(subProbs.size()); + } + + return 0; + } + + return static_cast(sortedAndFilteredPropertyNamesSignalsSlots(item.modelNode).size()); +} +int PropertyTreeModel::columnCount(const QModelIndex &) const +{ + return 1; +} + +void PropertyTreeModel::setPropertyType(PropertyTypes type) +{ + if (m_type == type) + return; + + m_type = type; + resetModel(); +} + +void PropertyTreeModel::setFilter(const QString &filter) +{ + if (m_filter == filter) + return; + + m_filter = filter; + resetModel(); +} + +QList PropertyTreeModel::nodeList() const +{ + return m_nodeList; +} + +const std::vector PropertyTreeModel::getProperties(const ModelNode &modelNode) const +{ + return sortedAndFilteredPropertyNamesSignalsSlots(modelNode); +} + +ModelNode PropertyTreeModel::getModelNodeForId(const QString &id) const +{ + if (!m_connectionView->isAttached()) + return {}; + + return m_connectionView->modelNodeForId(id); +} + +QModelIndex PropertyTreeModel::ensureModelIndex(const ModelNode &node, int row) const +{ + DataCacheItem item; + item.modelNode = node; + + auto iter = m_indexCache.find(item); + if (iter != m_indexCache.end()) + return createIndex(row, 0, iter->internalIndex); + + item.internalIndex = m_indexCount; + m_indexCount++; + m_indexHash.push_back(item); + m_indexCache.insert(item); + + return createIndex(row, 0, item.internalIndex); +} +QModelIndex PropertyTreeModel::ensureModelIndex(const ModelNode &node, + const PropertyName &name, + int row) const +{ + DataCacheItem item; + item.modelNode = node; + item.propertyName = name; + + auto iter = m_indexCache.find(item); + if (iter != m_indexCache.end()) + return createIndex(row, 0, iter->internalIndex); + + item.internalIndex = m_indexCount; + m_indexCount++; + m_indexHash.push_back(item); + m_indexCache.insert(item); + + return createIndex(row, 0, item.internalIndex); +} + +void PropertyTreeModel::testModel() +{ + qDebug() << Q_FUNC_INFO; + qDebug() << rowCount({}); + + QModelIndex rootIndex = index(0, 0); + + qDebug() << rowCount(rootIndex); + QModelIndex firstItem = index(0, 0, rootIndex); + + qDebug() << "fi" << data(firstItem, Qt::DisplayRole) << rowCount(firstItem); + + firstItem = index(1, 0, rootIndex); + qDebug() << "fi" << data(firstItem, Qt::DisplayRole) << rowCount(firstItem); + + firstItem = index(2, 0, rootIndex); + qDebug() << "fi" << data(firstItem, Qt::DisplayRole) << rowCount(firstItem); + + QModelIndex firstProperty = index(0, 0, firstItem); + + qDebug() << "fp" << data(firstProperty, Qt::DisplayRole) << rowCount(firstProperty); + + qDebug() << m_indexCount << m_indexHash.size() << m_indexCache.size(); +} + +const QList PropertyTreeModel::allModelNodesWithIdsSortedByDisplayName() const +{ + if (!m_connectionView->isAttached()) + return {}; + + return Utils::sorted(ModelUtils::allModelNodesWithId(m_connectionView), + [](const ModelNode &lhs, const ModelNode &rhs) { + return lhs.displayName() < rhs.displayName(); + }); +} + +const std::vector PropertyTreeModel::sortedAndFilteredPropertyNamesSignalsSlots( + const ModelNode &modelNode) const +{ + std::vector returnValue; + if (m_type == SignalType) { + returnValue = sortedAndFilteredSignalNames(modelNode.metaInfo()); + } else if (m_type == SlotType) { + returnValue = sortedAndFilteredSlotNames(modelNode.metaInfo()); + } else { + auto list = sortedAndFilteredPropertyNames(modelNode.metaInfo()); + returnValue = getDynamicProperties(modelNode); + std::move(list.begin(), list.end(), std::back_inserter(returnValue)); + } + + if (m_filter.isEmpty() || modelNode.displayName().contains(m_filter)) + return returnValue; + + return Utils::filtered(returnValue, [this](const PropertyName &name) { + return name.contains(m_filter.toUtf8()) || name == m_filter.toUtf8(); + }); +} + +const std::vector PropertyTreeModel::getDynamicProperties( + const ModelNode &modelNode) const +{ + QList list = Utils::transform(modelNode.dynamicProperties(), + [](const AbstractProperty &property) { + return property.name(); + }); + + QList filtered + = Utils::filtered(list, [this, modelNode](const PropertyName &propertyName) { + PropertyName propertyType = modelNode.property(propertyName).dynamicTypeName(); + switch (m_type) { + case AllTypes: + return true; + case NumberType: + return propertyType == "float" || propertyType == "double" + || propertyType == "int"; + case StringType: + return propertyType == "string"; + case UrlType: + return propertyType == "url"; + case ColorType: + return propertyType == "color"; + + case BoolType: + return propertyType == "bool"; + default: + break; + } + return true; + }); + + return Utils::sorted(std::vector(filtered.begin(), filtered.end())); +} + +const std::vector PropertyTreeModel::sortedAndFilteredPropertyNames( + const NodeMetaInfo &metaInfo, bool recursive) const +{ + auto filtered = Utils::filtered(metaInfo.properties(), + [this, recursive](const PropertyMetaInfo &metaInfo) { + // if (!metaInfo.isWritable()) - lhs/rhs + + const PropertyName name = metaInfo.name(); + + if (name.contains(".")) + return false; + + if (name.startsWith("icon.")) + return false; + if (name.startsWith("transformOriginPoint.")) + return false; + + return filterProperty(name, metaInfo, recursive); + }); + + auto sorted = Utils::sorted( + Utils::transform(filtered, [](const PropertyMetaInfo &metaInfo) -> PropertyName { + return metaInfo.name(); + })); + + std::set set(std::make_move_iterator(sorted.begin()), + std::make_move_iterator(sorted.end())); + + auto checkedPriorityList = Utils::filtered(priorityListProperties, + [&set](const PropertyName &name) { + auto it = set.find(name); + const bool b = it != set.end(); + if (b) + set.erase(it); + + return b; + }); + + //const int priorityLength = checkedPriorityList.size(); We eventually require this to get the prioproperties + + std::vector final(set.begin(), set.end()); + + std::move(final.begin(), final.end(), std::back_inserter(checkedPriorityList)); + + return checkedPriorityList; +} + +const std::vector PropertyTreeModel::sortedAndFilteredSignalNames( + const NodeMetaInfo &metaInfo, bool recursive) const +{ + Q_UNUSED(recursive); + + const std::vector priorityListSignals = {"clicked", + "doubleClicked", + "pressed", + "released", + "toggled", + "valueModified", + "valueChanged", + "checkedChanged", + "moved", + "accepted", + "editingFinished", + "entered", + "exited", + "canceled", + "triggered", + "stateChanged", + "started", + "stopped", + "finished" + "enabledChanged", + "visibleChanged", + "opacityChanged", + "rotationChanged"}; + + auto filtered + = Utils::filtered(metaInfo.signalNames(), [&priorityListSignals](const PropertyName &name) { + if (std::find(priorityListSignals.cbegin(), priorityListSignals.cend(), name) + != priorityListSignals.cend()) + return true; + + if (name.endsWith("Changed")) //option? + return false; + + return true; + }); + + auto sorted = Utils::sorted(filtered); + + std::set set(std::make_move_iterator(sorted.begin()), + std::make_move_iterator(sorted.end())); + + auto checkedPriorityList = Utils::filtered(priorityListSignals, + [&set](const PropertyName &name) { + auto it = set.find(name); + const bool b = it != set.end(); + if (b) + set.erase(it); + + return b; + }); + + //const int priorityLength = checkedPriorityList.size(); We eventually require this to get the prioproperties + + std::vector finalPropertyList(set.begin(), set.end()); + + std::move(finalPropertyList.begin(), + finalPropertyList.end(), + std::back_inserter(checkedPriorityList)); + + return checkedPriorityList; +} + +const std::vector PropertyTreeModel::sortedAndFilteredSlotNames( + const NodeMetaInfo &metaInfo, bool recursive) const +{ + Q_UNUSED(recursive); + + auto priorityList = priorityListSlots; + auto filtered = Utils::filtered(metaInfo.slotNames(), [priorityList](const PropertyName &name) { + if (std::find(priorityListSlots.begin(), priorityListSlots.end(), name) + != priorityListSlots.end()) + return true; + + if (name.startsWith("_")) + return false; + if (name.startsWith("q_")) + return false; + + if (name.endsWith("Changed")) //option? + return false; + + if (std::find(blockListSlots.begin(), blockListSlots.end(), name) != blockListSlots.end()) + return false; + + return true; + }); + + auto sorted = Utils::sorted(filtered); + + std::set set(std::make_move_iterator(sorted.begin()), + std::make_move_iterator(sorted.end())); + + auto checkedPriorityList = Utils::filtered(priorityListSlots, [&set](const PropertyName &name) { + auto it = set.find(name); + const bool b = it != set.end(); + if (b) + set.erase(it); + + return b; + }); + + //const int priorityLength = checkedPriorityList.size(); We eventually require this to get the prioproperties + + std::vector final(set.begin(), set.end()); + + std::move(final.begin(), final.end(), std::back_inserter(checkedPriorityList)); + + return checkedPriorityList; +} + +const std::vector PropertyTreeModel::sortedDotPropertyNames( + const NodeMetaInfo &metaInfo, const PropertyName &propertyName) const +{ + const PropertyName prefix = propertyName + '.'; + auto filtered = Utils::filtered(metaInfo.properties(), + [this, prefix](const PropertyMetaInfo &metaInfo) { + const PropertyName name = metaInfo.name(); + + if (!name.startsWith(prefix)) + return false; + + return filterProperty(name, metaInfo, true); + }); + + auto sorted = Utils::sorted( + Utils::transform(filtered, [](const PropertyMetaInfo &metaInfo) -> PropertyName { + return metaInfo.name(); + })); + + if (sorted.size() == 0 && metaInfo.property(propertyName).propertyType().isQtObject()) { + return Utils::transform(sortedAndFilteredPropertyNames(metaInfo.property(propertyName) + .propertyType(), + true), + [propertyName](const PropertyName &name) -> PropertyName { + return propertyName + "." + name; + }); + } + + return sorted; +} + +const std::vector PropertyTreeModel::sortedDotPropertySignals( + const NodeMetaInfo &metaInfo, const PropertyName &propertyName) const +{ + return Utils::transform(sortedAndFilteredSignalNames(metaInfo.property(propertyName) + .propertyType(), + true), + [propertyName](const PropertyName &name) -> PropertyName { + return propertyName + "." + name; + }); +} + +const std::vector PropertyTreeModel::sortedDotPropertySlots( + const NodeMetaInfo &metaInfo, const PropertyName &propertyName) const +{ + return Utils::transform(sortedAndFilteredSlotNames(metaInfo.property(propertyName).propertyType(), + true), + [propertyName](const PropertyName &name) -> PropertyName { + return propertyName + "." + name; + }); +} + +const std::vector PropertyTreeModel::sortedDotPropertyNamesSignalsSlots( + const NodeMetaInfo &metaInfo, const PropertyName &propertyName) const +{ + if (m_type == SignalType) { + return sortedDotPropertySignals(metaInfo, propertyName); + } else if (m_type == SlotType) { + return sortedDotPropertySlots(metaInfo, propertyName); + } else { + return sortedDotPropertyNames(metaInfo, propertyName); + } +} + +bool PropertyTreeModel::filterProperty(const PropertyName &name, + const PropertyMetaInfo &metaInfo, + bool recursive) const +{ + if (std::find(blockListProperties.begin(), blockListProperties.end(), name) + != blockListProperties.end()) + return false; + + const NodeMetaInfo propertyType = metaInfo.propertyType(); + + //We want to keep sub items with matching properties + if (!recursive && metaInfo.isPointer() + && sortedAndFilteredPropertyNames(propertyType, true).size() > 0) + return true; + + //TODO no type relaxation atm... + switch (m_type) { + case AllTypes: + return true; + case NumberType: + return propertyType.isNumber(); + case StringType: + return propertyType.isString(); + case UrlType: + return propertyType.isUrl(); + //return propertyType.isString() || propertyType.isUrl(); + case ColorType: + return propertyType.isColor(); + //return propertyType.isString() || propertyType.isColor(); + case BoolType: + return propertyType.isBool(); + default: + break; + } + return true; +} + +QHash PropertyTreeModel::roleNames() const +{ + static QHash roleNames{{PropertyNameRole, "propertyName"}, + {PropertyPriorityRole, "hasPriority"}, + {ExpressionRole, "expression"}}; + + return roleNames; +} + +PropertyListProxyModel::PropertyListProxyModel(PropertyTreeModel *parent) + : QAbstractListModel(), m_treeModel(parent) +{} + +void PropertyListProxyModel::setRowandInternalId(int row, int internalId) +{ + QTC_ASSERT(m_treeModel, return ); + + if (internalId == -1) + m_parentIndex = m_treeModel->index(0, 0); + else + m_parentIndex = m_treeModel->indexForInernalIdAndRow(internalId, row); + + beginResetModel(); + endResetModel(); +} + +int PropertyListProxyModel::rowCount(const QModelIndex &) const +{ + QTC_ASSERT(m_treeModel, return 0); + return m_treeModel->rowCount(m_parentIndex); +} + +QVariant PropertyListProxyModel::data(const QModelIndex &index, int role) const +{ + QTC_ASSERT(m_treeModel, return 0); + + auto treeIndex = m_treeModel->index(index.row(), 0, m_parentIndex); + + return m_treeModel->data(treeIndex, role); +} + +PropertyTreeModelDelegate::PropertyTreeModelDelegate(ConnectionView *parent) : m_model(parent) +{ + connect(&m_nameCombboBox, &StudioQmlComboBoxBackend::activated, this, [this]() { + handleNameChanged(); + }); + connect(&m_idCombboBox, &StudioQmlComboBoxBackend::activated, this, [this]() { + handleIdChanged(); + }); +} + +void PropertyTreeModelDelegate::setPropertyType(PropertyTreeModel::PropertyTypes type) +{ + m_model.setPropertyType(type); +} + +void PropertyTreeModelDelegate::setup(const QString &id, const QString &name, bool *nameExists) +{ + m_model.resetModel(); + QStringList idLists = Utils::transform(m_model.nodeList(), + [](const ModelNode &node) { return node.id(); }); + + if (!idLists.contains(id)) + idLists.prepend(id); + + m_idCombboBox.setModel(idLists); + m_idCombboBox.setCurrentText(id); + + const auto modelNode = m_model.getModelNodeForId(id); + //m_nameCombboBox + std::vector nameVector = Utils::transform(m_model.getProperties(modelNode), + [](const PropertyName &name) { + return QString::fromUtf8(name); + }); + QStringList nameList; + nameList.reserve(nameVector.size()); + std::copy(nameVector.begin(), nameVector.end(), std::back_inserter(nameList)); + + if (!nameList.contains(name)) { + if (!nameExists) + nameList.prepend(name); + else + *nameExists = false; + } + + m_nameCombboBox.setModel(nameList); + m_nameCombboBox.setCurrentText(name); +} + +QString PropertyTreeModelDelegate::id() const +{ + return m_idCombboBox.currentText(); +} + +QString PropertyTreeModelDelegate::name() const +{ + return m_nameCombboBox.currentText(); +} + +void PropertyTreeModelDelegate::handleNameChanged() +{ + const auto id = m_idCombboBox.currentText(); + const auto name = m_nameCombboBox.currentText(); + setup(id, name); + + emit commitData(); + + // commit data +} + +void PropertyTreeModelDelegate::handleIdChanged() +{ + const auto id = m_idCombboBox.currentText(); + const auto name = m_nameCombboBox.currentText(); + bool exists = true; + setup(id, name, &exists); + if (!exists) { + auto model = m_nameCombboBox.model(); + model.prepend("---"); + m_nameCombboBox.setModel(model); + m_nameCombboBox.setCurrentText("---"); + //We do not commit invalid name + } else { + emit commitData(); + //commit data + } +} + +StudioQmlComboBoxBackend *PropertyTreeModelDelegate::nameCombboBox() +{ + return &m_nameCombboBox; +} + +StudioQmlComboBoxBackend *PropertyTreeModelDelegate::idCombboBox() +{ + return &m_idCombboBox; +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/connectioneditor/propertytreemodel.h b/src/plugins/qmldesigner/components/connectioneditor/propertytreemodel.h new file mode 100644 index 00000000000..48a1c768b2d --- /dev/null +++ b/src/plugins/qmldesigner/components/connectioneditor/propertytreemodel.h @@ -0,0 +1,186 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "propertymetainfo.h" +#include + +#include +#include +#include + +#include +#include + +namespace QmlDesigner { + +class AbstractProperty; +class ModelNode; +class BindingProperty; +class SignalHandlerProperty; +class VariantProperty; + +class ConnectionView; + +class PropertyTreeModel : public QAbstractItemModel +{ + Q_OBJECT +public: + enum UserRoles { + PropertyNameRole = Qt::UserRole + 1, + PropertyPriorityRole, + ExpressionRole, + RowRole, + InternalIdRole + }; + + enum PropertyTypes { + AllTypes, + NumberType, + StringType, + ColorType, + SignalType, + SlotType, + UrlType, + BoolType + }; + + PropertyTreeModel(ConnectionView *parent = nullptr); + + void resetModel(); + + QVariant data(const QModelIndex &index, int role) const override; + + Qt::ItemFlags flags(const QModelIndex &index) const override; + + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex &index) const override; + + QPersistentModelIndex indexForInernalIdAndRow(int internalId, int row); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + + struct DataCacheItem + { + ModelNode modelNode; + PropertyName propertyName; + int internalIndex = -1; + }; + + void setPropertyType(PropertyTypes type); + void setFilter(const QString &filter); + + QList nodeList() const; + + const std::vector getProperties(const ModelNode &modelNode) const; + ModelNode getModelNodeForId(const QString &id) const; + +protected: + QHash roleNames() const override; + +private: + QModelIndex ensureModelIndex(const ModelNode &node, int row) const; + QModelIndex ensureModelIndex(const ModelNode &node, const PropertyName &name, int row) const; + void testModel(); + const QList allModelNodesWithIdsSortedByDisplayName() const; + const std::vector sortedAndFilteredPropertyNamesSignalsSlots( + const ModelNode &modelNode) const; + + const std::vector getDynamicProperties(const ModelNode &modelNode) const; + const std::vector sortedAndFilteredPropertyNames(const NodeMetaInfo &metaInfo, + bool recursive = false) const; + + const std::vector sortedAndFilteredSignalNames(const NodeMetaInfo &metaInfo, + bool recursive = false) const; + + const std::vector sortedAndFilteredSlotNames(const NodeMetaInfo &metaInfo, + bool recursive = false) const; + + const std::vector sortedDotPropertyNames(const NodeMetaInfo &metaInfo, + const PropertyName &propertyName) const; + + const std::vector sortedDotPropertySignals(const NodeMetaInfo &metaInfo, + const PropertyName &propertyName) const; + + const std::vector sortedDotPropertySlots(const NodeMetaInfo &metaInfo, + const PropertyName &propertyName) const; + + const std::vector sortedDotPropertyNamesSignalsSlots( + const NodeMetaInfo &metaInfo, const PropertyName &propertyName) const; + + bool filterProperty(const PropertyName &name, + const PropertyMetaInfo &metaInfo, + bool recursive) const; + + ConnectionView *m_connectionView; + + mutable std::set m_indexCache; + mutable std::vector m_indexHash; + mutable int m_indexCount = 0; + QList m_nodeList; + PropertyTypes m_type = AllTypes; + QString m_filter; +}; + +class PropertyListProxyModel : public QAbstractListModel +{ + Q_OBJECT +public: + PropertyListProxyModel(PropertyTreeModel *parent); + void setRowandInternalId(int row, int internalId); + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role) const override; + +private: + ModelNode m_modelNode; + PropertyName m_propertyName; + QPersistentModelIndex m_parentIndex; + + PropertyTreeModel *m_treeModel = nullptr; +}; + +inline bool operator==(const PropertyTreeModel::DataCacheItem &lhs, + const PropertyTreeModel::DataCacheItem &rhs) +{ + return lhs.modelNode == rhs.modelNode && lhs.propertyName == rhs.propertyName; +} + +inline bool operator<(const PropertyTreeModel::DataCacheItem &lhs, + const PropertyTreeModel::DataCacheItem &rhs) +{ + return (lhs.modelNode.id() + lhs.propertyName) < (rhs.modelNode.id() + rhs.propertyName); +} + +class PropertyTreeModelDelegate : public QObject +{ + Q_OBJECT + + Q_PROPERTY(StudioQmlComboBoxBackend *name READ nameCombboBox CONSTANT) + Q_PROPERTY(StudioQmlComboBoxBackend *id READ idCombboBox CONSTANT) + +public: + explicit PropertyTreeModelDelegate(ConnectionView *parent = nullptr); + void setPropertyType(PropertyTreeModel::PropertyTypes type); + void setup(const QString &id, const QString &name, bool *nameExists = nullptr); + QString id() const; + QString name() const; + +signals: + void commitData(); + +private: + void handleNameChanged(); + void handleIdChanged(); + + StudioQmlComboBoxBackend *nameCombboBox(); + StudioQmlComboBoxBackend *idCombboBox(); + + StudioQmlComboBoxBackend m_nameCombboBox; + StudioQmlComboBoxBackend m_idCombboBox; + PropertyTreeModel::PropertyTypes m_type; + PropertyTreeModel m_model; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/model/modelutils.cpp b/src/plugins/qmldesigner/designercore/model/modelutils.cpp index 387b9262b11..e1fe3f3cadc 100644 --- a/src/plugins/qmldesigner/designercore/model/modelutils.cpp +++ b/src/plugins/qmldesigner/designercore/model/modelutils.cpp @@ -3,6 +3,7 @@ #include "modelutils.h" +#include #include #include #include @@ -154,4 +155,11 @@ QList pruneChildren(const QList &nodes) return backNodes; } +QList allModelNodesWithId(AbstractView *view) +{ + QTC_ASSERT(view->isAttached(), return {}); + return Utils::filtered(view->allModelNodes(), + [&](const ModelNode &node) { return node.hasId(); }); +} + } // namespace QmlDesigner::ModelUtils diff --git a/src/plugins/qmldesigner/designercore/model/modelutils.h b/src/plugins/qmldesigner/designercore/model/modelutils.h index dd36cefa23e..ee6d3e41303 100644 --- a/src/plugins/qmldesigner/designercore/model/modelutils.h +++ b/src/plugins/qmldesigner/designercore/model/modelutils.h @@ -36,4 +36,6 @@ QMLDESIGNERCORE_EXPORT QString componentFilePath(const ModelNode &node); QMLDESIGNERCORE_EXPORT QList pruneChildren(const QList &nodes); +QMLDESIGNERCORE_EXPORT QList allModelNodesWithId(AbstractView *view); + } // namespace QmlDesigner::ModelUtils