forked from qt-creator/qt-creator
QmlDesigner: Add PropertyTreeModel
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 <ci_patchbuild_bot@qt.io> Reviewed-by: Henning Gründl <henning.gruendl@qt.io> Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
This commit is contained in:
@@ -13,7 +13,7 @@ add_compile_options("$<$<COMPILE_LANG_AND_ID:CXX,GNU>:-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 $<$<BOOL:${QTC_STATIC_BUILD}>: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
|
||||
|
@@ -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 <bindingproperty.h>
|
||||
#include <designeralgorithm.h>
|
||||
#include <exception.h>
|
||||
#include <model/modelutils.h>
|
||||
#include <nodeabstractproperty.h>
|
||||
#include <nodelistproperty.h>
|
||||
#include <nodemetainfo.h>
|
||||
#include <qmldesignerconstants.h>
|
||||
#include <qmldesignerplugin.h>
|
||||
#include <rewritertransaction.h>
|
||||
#include <rewriterview.h>
|
||||
#include <signalhandlerproperty.h>
|
||||
#include <variantproperty.h>
|
||||
|
||||
#include <utils/algorithm.h>
|
||||
#include <utils/qtcassert.h>
|
||||
|
||||
#include <QByteArrayView>
|
||||
#include <QMessageBox>
|
||||
#include <QTimer>
|
||||
|
||||
namespace QmlDesigner {
|
||||
|
||||
const std::vector<PropertyName> 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<PropertyName> blockListSlots = {"childAt",
|
||||
"contains",
|
||||
"destroy",
|
||||
"dumpItemTree",
|
||||
"ensurePolished",
|
||||
"grabToImage",
|
||||
"mapFromGlobal",
|
||||
"mapFromItem",
|
||||
"mapToGlobal",
|
||||
"mapToItem",
|
||||
"valueAt",
|
||||
"toString",
|
||||
"getText",
|
||||
"inputMethodQuery",
|
||||
"positionAt",
|
||||
"positionToRectangle",
|
||||
"isRightToLeft"
|
||||
|
||||
};
|
||||
|
||||
const std::vector<PropertyName> 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<PropertyName> 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<PropertyName> priorityListSlots = {"toggle",
|
||||
"increase",
|
||||
"decrease",
|
||||
"clear",
|
||||
"complete",
|
||||
"pause",
|
||||
"restart",
|
||||
"resume",
|
||||
"start",
|
||||
"stop",
|
||||
"forceActiveFocus"};
|
||||
|
||||
std::vector<PropertyName> properityLists()
|
||||
{
|
||||
std::vector<PropertyName> 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<PropertyName> 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<int>(subProbs.size());
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
return static_cast<int>(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<ModelNode> PropertyTreeModel::nodeList() const
|
||||
{
|
||||
return m_nodeList;
|
||||
}
|
||||
|
||||
const std::vector<PropertyName> 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<ModelNode> 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<PropertyName> PropertyTreeModel::sortedAndFilteredPropertyNamesSignalsSlots(
|
||||
const ModelNode &modelNode) const
|
||||
{
|
||||
std::vector<PropertyName> 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<PropertyName> PropertyTreeModel::getDynamicProperties(
|
||||
const ModelNode &modelNode) const
|
||||
{
|
||||
QList<PropertyName> list = Utils::transform(modelNode.dynamicProperties(),
|
||||
[](const AbstractProperty &property) {
|
||||
return property.name();
|
||||
});
|
||||
|
||||
QList<PropertyName> 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<PropertyName>(filtered.begin(), filtered.end()));
|
||||
}
|
||||
|
||||
const std::vector<PropertyName> 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<PropertyName> 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<PropertyName> final(set.begin(), set.end());
|
||||
|
||||
std::move(final.begin(), final.end(), std::back_inserter(checkedPriorityList));
|
||||
|
||||
return checkedPriorityList;
|
||||
}
|
||||
|
||||
const std::vector<PropertyName> PropertyTreeModel::sortedAndFilteredSignalNames(
|
||||
const NodeMetaInfo &metaInfo, bool recursive) const
|
||||
{
|
||||
Q_UNUSED(recursive);
|
||||
|
||||
const std::vector<PropertyName> 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<PropertyName> 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<PropertyName> finalPropertyList(set.begin(), set.end());
|
||||
|
||||
std::move(finalPropertyList.begin(),
|
||||
finalPropertyList.end(),
|
||||
std::back_inserter(checkedPriorityList));
|
||||
|
||||
return checkedPriorityList;
|
||||
}
|
||||
|
||||
const std::vector<PropertyName> 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<PropertyName> 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<PropertyName> final(set.begin(), set.end());
|
||||
|
||||
std::move(final.begin(), final.end(), std::back_inserter(checkedPriorityList));
|
||||
|
||||
return checkedPriorityList;
|
||||
}
|
||||
|
||||
const std::vector<PropertyName> 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<PropertyName> 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<PropertyName> 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<PropertyName> 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<int, QByteArray> PropertyTreeModel::roleNames() const
|
||||
{
|
||||
static QHash<int, QByteArray> 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<QString> 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
|
@@ -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 <modelnode.h>
|
||||
|
||||
#include <studioquickwidget.h>
|
||||
#include <QAbstractItemModel>
|
||||
#include <QAbstractListModel>
|
||||
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
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<ModelNode> nodeList() const;
|
||||
|
||||
const std::vector<PropertyName> getProperties(const ModelNode &modelNode) const;
|
||||
ModelNode getModelNodeForId(const QString &id) const;
|
||||
|
||||
protected:
|
||||
QHash<int, QByteArray> 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<ModelNode> allModelNodesWithIdsSortedByDisplayName() const;
|
||||
const std::vector<PropertyName> sortedAndFilteredPropertyNamesSignalsSlots(
|
||||
const ModelNode &modelNode) const;
|
||||
|
||||
const std::vector<PropertyName> getDynamicProperties(const ModelNode &modelNode) const;
|
||||
const std::vector<PropertyName> sortedAndFilteredPropertyNames(const NodeMetaInfo &metaInfo,
|
||||
bool recursive = false) const;
|
||||
|
||||
const std::vector<PropertyName> sortedAndFilteredSignalNames(const NodeMetaInfo &metaInfo,
|
||||
bool recursive = false) const;
|
||||
|
||||
const std::vector<PropertyName> sortedAndFilteredSlotNames(const NodeMetaInfo &metaInfo,
|
||||
bool recursive = false) const;
|
||||
|
||||
const std::vector<PropertyName> sortedDotPropertyNames(const NodeMetaInfo &metaInfo,
|
||||
const PropertyName &propertyName) const;
|
||||
|
||||
const std::vector<PropertyName> sortedDotPropertySignals(const NodeMetaInfo &metaInfo,
|
||||
const PropertyName &propertyName) const;
|
||||
|
||||
const std::vector<PropertyName> sortedDotPropertySlots(const NodeMetaInfo &metaInfo,
|
||||
const PropertyName &propertyName) const;
|
||||
|
||||
const std::vector<PropertyName> 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<DataCacheItem> m_indexCache;
|
||||
mutable std::vector<DataCacheItem> m_indexHash;
|
||||
mutable int m_indexCount = 0;
|
||||
QList<ModelNode> 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
|
@@ -3,6 +3,7 @@
|
||||
|
||||
#include "modelutils.h"
|
||||
|
||||
#include <abstractview.h>
|
||||
#include <nodemetainfo.h>
|
||||
#include <projectstorage/projectstorage.h>
|
||||
#include <projectstorage/sourcepathcache.h>
|
||||
@@ -154,4 +155,11 @@ QList<ModelNode> pruneChildren(const QList<ModelNode> &nodes)
|
||||
return backNodes;
|
||||
}
|
||||
|
||||
QList<ModelNode> allModelNodesWithId(AbstractView *view)
|
||||
{
|
||||
QTC_ASSERT(view->isAttached(), return {});
|
||||
return Utils::filtered(view->allModelNodes(),
|
||||
[&](const ModelNode &node) { return node.hasId(); });
|
||||
}
|
||||
|
||||
} // namespace QmlDesigner::ModelUtils
|
||||
|
@@ -36,4 +36,6 @@ QMLDESIGNERCORE_EXPORT QString componentFilePath(const ModelNode &node);
|
||||
|
||||
QMLDESIGNERCORE_EXPORT QList<ModelNode> pruneChildren(const QList<ModelNode> &nodes);
|
||||
|
||||
QMLDESIGNERCORE_EXPORT QList<ModelNode> allModelNodesWithId(AbstractView *view);
|
||||
|
||||
} // namespace QmlDesigner::ModelUtils
|
||||
|
Reference in New Issue
Block a user