forked from qt-creator/qt-creator
QmlDesigner: Add persistent auxiliary storage
The persistent storage is saving the data in-between program executions. A sqlite database is providing the backend. The model nodes need an id to be identified. Change-Id: I24e4ea5184c04cb6a9e3828059ca593ee41d271e Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
This commit is contained in:
@@ -20,7 +20,8 @@ enum class AuxiliaryDataType {
|
||||
Temporary,
|
||||
Document,
|
||||
NodeInstancePropertyOverwrite,
|
||||
NodeInstanceAuxiliary
|
||||
NodeInstanceAuxiliary,
|
||||
Persistent
|
||||
};
|
||||
|
||||
enum class View3DActionType {
|
||||
|
@@ -361,6 +361,7 @@ extend_qtc_library(QmlDesignerCore
|
||||
abstractview.cpp
|
||||
anchorline.cpp
|
||||
annotation.cpp
|
||||
auxiliarypropertystorageview.cpp auxiliarypropertystorageview.h
|
||||
bindingproperty.cpp
|
||||
componenttextmodifier.cpp
|
||||
documentmessage.cpp
|
||||
|
@@ -20,17 +20,19 @@
|
||||
#include <itemlibraryview.h>
|
||||
#include <materialbrowserview.h>
|
||||
#include <materialeditorview.h>
|
||||
#include <model/auxiliarypropertystorageview.h>
|
||||
#include <navigatorview.h>
|
||||
#include <nodeinstanceview.h>
|
||||
#include <propertyeditorview.h>
|
||||
#include <qmldesignerplugin.h>
|
||||
#include <rewriterview.h>
|
||||
#include <stateseditorview.h>
|
||||
#include <texteditorview.h>
|
||||
#include <textureeditorview.h>
|
||||
#include <qmldesignerplugin.h>
|
||||
|
||||
#include <coreplugin/icore.h>
|
||||
|
||||
#include <sqlitedatabase.h>
|
||||
#include <utils/algorithm.h>
|
||||
|
||||
#include <advanceddockingsystem/dockwidget.h>
|
||||
@@ -49,6 +51,7 @@ public:
|
||||
ViewManagerData(AsynchronousImageCache &imageCache,
|
||||
ExternalDependenciesInterface &externalDependencies)
|
||||
: debugView{externalDependencies}
|
||||
, auxiliaryDataKeyView{auxiliaryDataDatabase, externalDependencies}
|
||||
, designerActionManagerView{externalDependencies}
|
||||
, nodeInstanceView(QCoreApplication::arguments().contains("-capture-puppet-stream")
|
||||
? capturingConnectionManager
|
||||
@@ -78,6 +81,11 @@ public:
|
||||
CapturingConnectionManager capturingConnectionManager;
|
||||
QmlModelState savedState;
|
||||
Internal::DebugView debugView;
|
||||
Sqlite::Database auxiliaryDataDatabase{
|
||||
Utils::PathString{Core::ICore::userResourcePath("auxiliary_data.db").toString()},
|
||||
Sqlite::JournalMode::Wal,
|
||||
Sqlite::LockingMode::Normal};
|
||||
AuxiliaryPropertyStorageView auxiliaryDataKeyView;
|
||||
DesignerActionManagerView designerActionManagerView;
|
||||
NodeInstanceView nodeInstanceView;
|
||||
ContentLibraryView contentLibraryView;
|
||||
@@ -201,7 +209,8 @@ QList<AbstractView *> ViewManager::views() const
|
||||
QList<AbstractView *> ViewManager::standardViews() const
|
||||
{
|
||||
#ifndef QTC_USE_QML_DESIGNER_LITE
|
||||
QList<AbstractView *> list = {&d->edit3DView,
|
||||
QList<AbstractView *> list = {&d->auxiliaryDataKeyView,
|
||||
&d->edit3DView,
|
||||
&d->formEditorView,
|
||||
&d->textEditorView,
|
||||
&d->assetsLibraryView,
|
||||
|
@@ -244,6 +244,9 @@ QTextStream &operator<<(QTextStream &stream, AuxiliaryDataType type)
|
||||
case AuxiliaryDataType::Temporary:
|
||||
stream << "Temporary";
|
||||
break;
|
||||
case AuxiliaryDataType::Persistent:
|
||||
stream << "Persistent";
|
||||
break;
|
||||
}
|
||||
|
||||
return stream;
|
||||
|
@@ -249,6 +249,7 @@ public:
|
||||
const QList<DocumentMessage> &warnings);
|
||||
|
||||
QList<ModelNode> selectedNodes(AbstractView *view) const;
|
||||
void setSelectedModelNodes(const QList<ModelNode> &selectedNodeList);
|
||||
|
||||
void clearMetaInfoCache();
|
||||
|
||||
|
@@ -152,10 +152,10 @@ public:
|
||||
void destroy();
|
||||
|
||||
QString id() const;
|
||||
void ensureIdExists();
|
||||
[[nodiscard]] QString validId();
|
||||
void setIdWithRefactoring(const QString &id);
|
||||
void setIdWithoutRefactoring(const QString &id);
|
||||
void ensureIdExists() const;
|
||||
[[nodiscard]] QString validId() const;
|
||||
void setIdWithRefactoring(const QString &id) const;
|
||||
void setIdWithoutRefactoring(const QString &id) const;
|
||||
static bool isValidId(const QString &id);
|
||||
static QString getIdValidityErrorMessage(const QString &id);
|
||||
|
||||
@@ -192,6 +192,7 @@ public:
|
||||
void removeAuxiliaryData(AuxiliaryDataType type, Utils::SmallStringView name) const;
|
||||
bool hasAuxiliaryData(AuxiliaryDataKeyView key) const;
|
||||
bool hasAuxiliaryData(AuxiliaryDataType type, Utils::SmallStringView name) const;
|
||||
bool hasAuxiliaryData(AuxiliaryDataType type) const;
|
||||
AuxiliaryDatasForType auxiliaryData(AuxiliaryDataType type) const;
|
||||
AuxiliaryDatasView auxiliaryData() const;
|
||||
|
||||
|
@@ -427,14 +427,7 @@ QList<Internal::InternalNode::Pointer> toInternalNodeList(const QList<ModelNode>
|
||||
*/
|
||||
void AbstractView::setSelectedModelNodes(const QList<ModelNode> &selectedNodeList)
|
||||
{
|
||||
QList<ModelNode> unlockedNodes;
|
||||
|
||||
for (const auto &modelNode : selectedNodeList) {
|
||||
if (!ModelUtils::isThisOrAncestorLocked(modelNode))
|
||||
unlockedNodes.push_back(modelNode);
|
||||
}
|
||||
|
||||
model()->d->setSelectedNodes(toInternalNodeList(unlockedNodes));
|
||||
model()->setSelectedModelNodes(selectedNodeList);
|
||||
}
|
||||
|
||||
void AbstractView::setSelectedModelNode(const ModelNode &modelNode)
|
||||
|
@@ -0,0 +1,201 @@
|
||||
// Copyright (C) 2024 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
#include "auxiliarypropertystorageview.h"
|
||||
|
||||
#include <sqlitedatabase.h>
|
||||
|
||||
#include <QDataStream>
|
||||
#include <QIODevice>
|
||||
|
||||
namespace QmlDesigner {
|
||||
|
||||
namespace {
|
||||
struct Initializer
|
||||
{
|
||||
Initializer(Sqlite::Database &database, bool isInitialized)
|
||||
{
|
||||
if (!isInitialized) {
|
||||
createAuxiliaryPropertiesTable(database);
|
||||
}
|
||||
database.setIsInitialized(true);
|
||||
}
|
||||
|
||||
void createAuxiliaryPropertiesTable(Sqlite::Database &database)
|
||||
{
|
||||
Sqlite::StrictTable table;
|
||||
table.setUseIfNotExists(true);
|
||||
table.setUseWithoutRowId(true);
|
||||
table.setName("auxiliaryProperties");
|
||||
auto &filePathColumn = table.addColumn("filePath", Sqlite::StrictColumnType::Text);
|
||||
auto &idColumn = table.addColumn("id", Sqlite::StrictColumnType::Text);
|
||||
auto &keyColumn = table.addColumn("key", Sqlite::StrictColumnType::Text);
|
||||
table.addColumn("value", Sqlite::StrictColumnType::Blob);
|
||||
|
||||
table.addPrimaryKeyContraint({filePathColumn, idColumn, keyColumn});
|
||||
|
||||
table.initialize(database);
|
||||
}
|
||||
};
|
||||
|
||||
struct Entry
|
||||
{
|
||||
Entry(Utils::SmallStringView nodeId, Utils::SmallStringView key, Sqlite::BlobView value)
|
||||
: nodeId{nodeId}
|
||||
, key{key}
|
||||
, value{value}
|
||||
{}
|
||||
|
||||
Utils::SmallStringView nodeId;
|
||||
Utils::SmallStringView key;
|
||||
Sqlite::BlobView value;
|
||||
};
|
||||
|
||||
struct ChangeEntry
|
||||
{
|
||||
ChangeEntry(Utils::SmallStringView id, Utils::SmallStringView name)
|
||||
: id{id}
|
||||
, name{name}
|
||||
{}
|
||||
|
||||
friend bool operator<(const ChangeEntry &first, const ChangeEntry &second)
|
||||
{
|
||||
return std::tie(first.id, first.name) < std::tie(second.id, second.name);
|
||||
}
|
||||
|
||||
Utils::SmallString id;
|
||||
Utils::SmallString name;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
struct AuxiliaryPropertyStorageView::Data
|
||||
{
|
||||
Data(Sqlite::Database &database)
|
||||
: database{database}
|
||||
{
|
||||
exclusiveTransaction.commit();
|
||||
}
|
||||
|
||||
Sqlite::Database &database;
|
||||
Sqlite::ExclusiveNonThrowingDestructorTransaction<Sqlite::Database> exclusiveTransaction{database};
|
||||
Initializer initializer{database, database.isInitialized()};
|
||||
Sqlite::WriteStatement<4> upsertValue{
|
||||
"INSERT INTO auxiliaryProperties(filePath, id, key, value) "
|
||||
"VALUES(?1, ?2, ?3, ?4) "
|
||||
"ON CONFLICT DO UPDATE SET value=excluded.value",
|
||||
database};
|
||||
Sqlite::ReadStatement<3, 1> selectValues{
|
||||
"SELECT id, key, value FROM auxiliaryProperties WHERE filePath=?1", database};
|
||||
Sqlite::WriteStatement<0> removeValues{"DELETE FROM auxiliaryProperties", database};
|
||||
Sqlite::WriteStatement<3> removeValue{
|
||||
"DELETE FROM auxiliaryProperties WHERE filePath=?1 AND id=?2 AND key=?3", database};
|
||||
std::set<ChangeEntry, std::less<>> changeEntries;
|
||||
};
|
||||
|
||||
AuxiliaryPropertyStorageView::AuxiliaryPropertyStorageView(
|
||||
Sqlite::Database &database, ExternalDependenciesInterface &externalDependencies)
|
||||
: AbstractView{externalDependencies}
|
||||
, d{std::make_unique<Data>(database)}
|
||||
{}
|
||||
|
||||
AuxiliaryPropertyStorageView::~AuxiliaryPropertyStorageView() = default;
|
||||
|
||||
void AuxiliaryPropertyStorageView::modelAttached(Model *model)
|
||||
{
|
||||
auto entryRange = d->selectValues.rangeWithTransaction<Entry>(
|
||||
Utils::PathString{model->fileUrl().path()});
|
||||
|
||||
for (const auto entry : entryRange) {
|
||||
auto node = model->modelNodeForId(QString{entry.nodeId});
|
||||
|
||||
if (!node)
|
||||
continue;
|
||||
|
||||
auto array = QByteArray::fromRawData(entry.value.cdata(), entry.value.ssize());
|
||||
QDataStream dataStream{array};
|
||||
|
||||
QVariant value;
|
||||
dataStream >> value;
|
||||
|
||||
node.setAuxiliaryDataWithoutLock({AuxiliaryDataType::Persistent, entry.key}, value);
|
||||
}
|
||||
|
||||
AbstractView::modelAttached(model);
|
||||
}
|
||||
|
||||
void AuxiliaryPropertyStorageView::modelAboutToBeDetached(Model *model)
|
||||
{
|
||||
Utils::PathString filePath{model->fileUrl().path()};
|
||||
|
||||
auto update = [&] {
|
||||
for (const auto &[id, name] : d->changeEntries) {
|
||||
QByteArray array;
|
||||
QDataStream dataStream{&array, QIODevice::WriteOnly};
|
||||
auto node = model->modelNodeForId(id.toQString());
|
||||
|
||||
auto value = node.auxiliaryData(AuxiliaryDataType::Persistent, name);
|
||||
if (value) {
|
||||
dataStream << *value;
|
||||
|
||||
Sqlite::BlobView blob{array};
|
||||
|
||||
d->upsertValue.write(filePath, id, name, blob);
|
||||
|
||||
} else {
|
||||
d->removeValue.write(filePath, id, name);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (d->changeEntries.size()) {
|
||||
Sqlite::withImmediateTransaction(d->database, update);
|
||||
|
||||
d->changeEntries.clear();
|
||||
}
|
||||
|
||||
AbstractView::modelAboutToBeDetached(model);
|
||||
}
|
||||
|
||||
void AuxiliaryPropertyStorageView::nodeIdChanged(const ModelNode &node,
|
||||
const QString &newIdArg,
|
||||
const QString &oldIdArg)
|
||||
{
|
||||
Utils::SmallString newId = newIdArg;
|
||||
Utils::SmallString oldId = oldIdArg;
|
||||
for (auto entry : node.auxiliaryData()) {
|
||||
if (entry.first.type != AuxiliaryDataType::Persistent)
|
||||
continue;
|
||||
|
||||
d->changeEntries.emplace(oldId, entry.first.name);
|
||||
d->changeEntries.emplace(newId, entry.first.name);
|
||||
}
|
||||
}
|
||||
|
||||
void AuxiliaryPropertyStorageView::nodeAboutToBeRemoved(const ModelNode &removedNode)
|
||||
{
|
||||
if (removedNode.hasId()) {
|
||||
Utils::SmallString id = removedNode.id();
|
||||
for (auto entry : removedNode.auxiliaryData()) {
|
||||
if (entry.first.type != AuxiliaryDataType::Persistent)
|
||||
continue;
|
||||
|
||||
d->changeEntries.emplace(id, entry.first.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AuxiliaryPropertyStorageView::auxiliaryDataChanged(const ModelNode &node,
|
||||
AuxiliaryDataKeyView key,
|
||||
const QVariant &)
|
||||
{
|
||||
if (isAttached() && key.type == AuxiliaryDataType::Persistent)
|
||||
d->changeEntries.emplace(Utils::SmallString{node.id()}, key.name);
|
||||
}
|
||||
|
||||
void AuxiliaryPropertyStorageView::resetForTestsOnly()
|
||||
{
|
||||
Sqlite::withImmediateTransaction(d->database, [&] { d->removeValues.execute(); });
|
||||
}
|
||||
|
||||
} // namespace QmlDesigner
|
@@ -0,0 +1,34 @@
|
||||
// Copyright (C) 2024 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
#include <abstractview.h>
|
||||
|
||||
#include <memory.h>
|
||||
|
||||
namespace QmlDesigner {
|
||||
class AuxiliaryPropertyStorageView final : public AbstractView
|
||||
{
|
||||
public:
|
||||
AuxiliaryPropertyStorageView(Sqlite::Database &database,
|
||||
ExternalDependenciesInterface &externalDependencies);
|
||||
~AuxiliaryPropertyStorageView();
|
||||
|
||||
void modelAttached(Model *model) override;
|
||||
void modelAboutToBeDetached(Model *model) override;
|
||||
|
||||
void nodeIdChanged(const ModelNode &node, const QString &newId, const QString &oldId) override;
|
||||
|
||||
void nodeAboutToBeRemoved(const ModelNode &removedNode) override;
|
||||
|
||||
void auxiliaryDataChanged(const ModelNode &node,
|
||||
AuxiliaryDataKeyView type,
|
||||
const QVariant &data) override;
|
||||
|
||||
struct Data;
|
||||
|
||||
void resetForTestsOnly();
|
||||
|
||||
private:
|
||||
std::unique_ptr<Data> d;
|
||||
};
|
||||
} // namespace QmlDesigner
|
@@ -51,6 +51,14 @@ auto find(Type &&auxiliaryDatas, AuxiliaryDataKeyView key)
|
||||
});
|
||||
}
|
||||
|
||||
template<typename Type>
|
||||
auto find(Type &&auxiliaryDatas, AuxiliaryDataType type)
|
||||
{
|
||||
return std::find_if(auxiliaryDatas.begin(), auxiliaryDatas.end(), [&](const auto &element) {
|
||||
return element.first.type == type;
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::optional<QVariant> InternalNode::auxiliaryData(AuxiliaryDataKeyView key) const
|
||||
@@ -99,6 +107,13 @@ bool InternalNode::hasAuxiliaryData(AuxiliaryDataKeyView key) const
|
||||
return found != m_auxiliaryDatas.end();
|
||||
}
|
||||
|
||||
bool InternalNode::hasAuxiliaryData(AuxiliaryDataType type) const
|
||||
{
|
||||
auto found = find(m_auxiliaryDatas, type);
|
||||
|
||||
return found != m_auxiliaryDatas.end();
|
||||
}
|
||||
|
||||
AuxiliaryDatasForType InternalNode::auxiliaryData(AuxiliaryDataType type) const
|
||||
{
|
||||
AuxiliaryDatasForType data;
|
||||
|
@@ -76,6 +76,7 @@ public:
|
||||
bool setAuxiliaryData(AuxiliaryDataKeyView key, const QVariant &data);
|
||||
bool removeAuxiliaryData(AuxiliaryDataKeyView key);
|
||||
bool hasAuxiliaryData(AuxiliaryDataKeyView key) const;
|
||||
bool hasAuxiliaryData(AuxiliaryDataType type) const;
|
||||
AuxiliaryDatasForType auxiliaryData(AuxiliaryDataType type) const;
|
||||
AuxiliaryDatasView auxiliaryData() const { return std::as_const(m_auxiliaryDatas); }
|
||||
|
||||
|
@@ -4,6 +4,7 @@
|
||||
#include "model.h"
|
||||
#include "internalnode_p.h"
|
||||
#include "model_p.h"
|
||||
#include "modelutils.h"
|
||||
#include <modelnode.h>
|
||||
|
||||
#include "../projectstorage/sourcepath.h"
|
||||
@@ -2039,6 +2040,18 @@ QList<ModelNode> Model::selectedNodes(AbstractView *view) const
|
||||
return d->toModelNodeList(d->selectedNodes(), view);
|
||||
}
|
||||
|
||||
void Model::setSelectedModelNodes(const QList<ModelNode> &selectedNodeList)
|
||||
{
|
||||
QList<ModelNode> unlockedNodes;
|
||||
|
||||
for (const auto &modelNode : selectedNodeList) {
|
||||
if (!ModelUtils::isThisOrAncestorLocked(modelNode))
|
||||
unlockedNodes.push_back(modelNode);
|
||||
}
|
||||
|
||||
d->setSelectedNodes(toInternalNodeList(unlockedNodes));
|
||||
}
|
||||
|
||||
void Model::clearMetaInfoCache()
|
||||
{
|
||||
d->m_nodeMetaInfoCache.clear();
|
||||
@@ -2064,7 +2077,6 @@ SourceId Model::fileUrlSourceId() const
|
||||
*/
|
||||
void Model::setFileUrl(const QUrl &url)
|
||||
{
|
||||
QTC_ASSERT(url.isValid() && url.isLocalFile(), qDebug() << "url:" << url; return);
|
||||
Internal::WriteLocker locker(d.get());
|
||||
d->setFileUrl(url);
|
||||
}
|
||||
|
@@ -80,13 +80,13 @@ QString ModelNode::id() const
|
||||
return m_internalNode->id;
|
||||
}
|
||||
|
||||
void ModelNode::ensureIdExists()
|
||||
void ModelNode::ensureIdExists() const
|
||||
{
|
||||
if (!hasId())
|
||||
setIdWithoutRefactoring(model()->generateNewId(simplifiedTypeName()));
|
||||
}
|
||||
|
||||
QString ModelNode::validId()
|
||||
QString ModelNode::validId() const
|
||||
{
|
||||
ensureIdExists();
|
||||
|
||||
@@ -162,7 +162,7 @@ bool ModelNode::hasId() const
|
||||
return !m_internalNode->id.isEmpty();
|
||||
}
|
||||
|
||||
void ModelNode::setIdWithRefactoring(const QString &id)
|
||||
void ModelNode::setIdWithRefactoring(const QString &id) const
|
||||
{
|
||||
if (isValid()) {
|
||||
if (model()->rewriterView() && !id.isEmpty()
|
||||
@@ -174,7 +174,7 @@ void ModelNode::setIdWithRefactoring(const QString &id)
|
||||
}
|
||||
}
|
||||
|
||||
void ModelNode::setIdWithoutRefactoring(const QString &id)
|
||||
void ModelNode::setIdWithoutRefactoring(const QString &id) const
|
||||
{
|
||||
Internal::WriteLocker locker(m_model.data());
|
||||
if (!isValid())
|
||||
@@ -601,7 +601,8 @@ static QList<ModelNode> descendantNodes(const ModelNode &node)
|
||||
static void removeModelNodeFromSelection(const ModelNode &node)
|
||||
{
|
||||
// remove nodes from the active selection
|
||||
QList<ModelNode> selectedList = node.view()->selectedModelNodes();
|
||||
auto model = node.model();
|
||||
QList<ModelNode> selectedList = model->selectedNodes(node.view());
|
||||
|
||||
const QList<ModelNode> descendants = descendantNodes(node);
|
||||
for (const ModelNode &descendantNode : descendants)
|
||||
@@ -609,7 +610,7 @@ static void removeModelNodeFromSelection(const ModelNode &node)
|
||||
|
||||
selectedList.removeAll(node);
|
||||
|
||||
node.view()->setSelectedModelNodes(selectedList);
|
||||
model->setSelectedModelNodes(selectedList);
|
||||
}
|
||||
|
||||
/*! \brief complete removes this ModelNode from the Model
|
||||
@@ -995,6 +996,8 @@ void ModelNode::setAuxiliaryData(AuxiliaryDataType type,
|
||||
void ModelNode::setAuxiliaryData(AuxiliaryDataKeyView key, const QVariant &data) const
|
||||
{
|
||||
if (isValid()) {
|
||||
if (key.type == AuxiliaryDataType::Persistent)
|
||||
ensureIdExists();
|
||||
Internal::WriteLocker locker(m_model.data());
|
||||
m_model->d->setAuxiliaryData(internalNode(), key, data);
|
||||
}
|
||||
@@ -1002,21 +1005,32 @@ void ModelNode::setAuxiliaryData(AuxiliaryDataKeyView key, const QVariant &data)
|
||||
|
||||
void ModelNode::setAuxiliaryDataWithoutLock(AuxiliaryDataKeyView key, const QVariant &data) const
|
||||
{
|
||||
if (isValid())
|
||||
if (isValid()) {
|
||||
if (key.type == AuxiliaryDataType::Persistent)
|
||||
ensureIdExists();
|
||||
|
||||
m_model->d->setAuxiliaryData(internalNode(), key, data);
|
||||
}
|
||||
}
|
||||
|
||||
void ModelNode::setAuxiliaryDataWithoutLock(AuxiliaryDataType type,
|
||||
Utils::SmallStringView name,
|
||||
const QVariant &data) const
|
||||
{
|
||||
if (isValid())
|
||||
if (isValid()) {
|
||||
if (type == AuxiliaryDataType::Persistent)
|
||||
ensureIdExists();
|
||||
|
||||
m_model->d->setAuxiliaryData(internalNode(), {type, name}, data);
|
||||
}
|
||||
}
|
||||
|
||||
void ModelNode::removeAuxiliaryData(AuxiliaryDataKeyView key) const
|
||||
{
|
||||
if (isValid()) {
|
||||
if (key.type == AuxiliaryDataType::Persistent)
|
||||
ensureIdExists();
|
||||
|
||||
Internal::WriteLocker locker(m_model.data());
|
||||
m_model->d->removeAuxiliaryData(internalNode(), key);
|
||||
}
|
||||
@@ -1040,6 +1054,14 @@ bool ModelNode::hasAuxiliaryData(AuxiliaryDataType type, Utils::SmallStringView
|
||||
return hasAuxiliaryData({type, name});
|
||||
}
|
||||
|
||||
bool ModelNode::hasAuxiliaryData(AuxiliaryDataType type) const
|
||||
{
|
||||
if (!isValid())
|
||||
return false;
|
||||
|
||||
return m_internalNode->hasAuxiliaryData(type);
|
||||
}
|
||||
|
||||
AuxiliaryDatasForType ModelNode::auxiliaryData(AuxiliaryDataType type) const
|
||||
{
|
||||
if (!isValid())
|
||||
|
@@ -534,6 +534,42 @@ std::ostream &operator<<(std::ostream &out, FlagIs flagIs)
|
||||
return out;
|
||||
}
|
||||
|
||||
std::ostream &operator<<(std::ostream &out, const BasicAuxiliaryDataKey<Utils::SmallStringView> &key)
|
||||
{
|
||||
return out << "(" << key.name << ", " << key.type << ")";
|
||||
}
|
||||
|
||||
std::ostream &operator<<(std::ostream &out, const BasicAuxiliaryDataKey<Utils::SmallString> &key)
|
||||
{
|
||||
return out << "(" << key.name << ", " << key.type << ")";
|
||||
}
|
||||
|
||||
std::ostream &operator<<(std::ostream &out, AuxiliaryDataType type)
|
||||
{
|
||||
switch (type) {
|
||||
case AuxiliaryDataType::None:
|
||||
out << "None";
|
||||
break;
|
||||
case AuxiliaryDataType::Temporary:
|
||||
out << "Temporary";
|
||||
break;
|
||||
case AuxiliaryDataType::Document:
|
||||
out << "Document";
|
||||
break;
|
||||
case AuxiliaryDataType::NodeInstancePropertyOverwrite:
|
||||
out << "NodeInstancePropertyOverwrite";
|
||||
break;
|
||||
case AuxiliaryDataType::NodeInstanceAuxiliary:
|
||||
out << "NodeInstanceAuxiliary";
|
||||
break;
|
||||
case AuxiliaryDataType::Persistent:
|
||||
out << "Persistent";
|
||||
break;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
namespace Cache {
|
||||
|
||||
std::ostream &operator<<(std::ostream &out, const SourceContext &sourceContext)
|
||||
|
@@ -4,6 +4,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <designercore/model/modelresourcemanagementfwd.h>
|
||||
#include <qmlpuppetcommunication/interfaces/nodeinstanceglobal.h>
|
||||
#include <utils/cpplanguage_details.h>
|
||||
#include <utils/smallstringio.h>
|
||||
|
||||
@@ -126,6 +127,8 @@ class NodeMetaInfo;
|
||||
class PropertyMetaInfo;
|
||||
struct CompoundPropertyMetaInfo;
|
||||
enum class FlagIs : unsigned int;
|
||||
template<typename NameType>
|
||||
class BasicAuxiliaryDataKey;
|
||||
|
||||
std::ostream &operator<<(std::ostream &out, const ModelNode &node);
|
||||
std::ostream &operator<<(std::ostream &out, const VariantProperty &property);
|
||||
@@ -142,6 +145,9 @@ std::ostream &operator<<(std::ostream &out, const NodeMetaInfo &metaInfo);
|
||||
std::ostream &operator<<(std::ostream &out, const PropertyMetaInfo &metaInfo);
|
||||
std::ostream &operator<<(std::ostream &out, const CompoundPropertyMetaInfo &metaInfo);
|
||||
std::ostream &operator<<(std::ostream &out, FlagIs flagIs);
|
||||
std::ostream &operator<<(std::ostream &out, const BasicAuxiliaryDataKey<Utils::SmallStringView> &key);
|
||||
std::ostream &operator<<(std::ostream &out, const BasicAuxiliaryDataKey<Utils::SmallString> &key);
|
||||
std::ostream &operator<<(std::ostream &out, AuxiliaryDataType type);
|
||||
|
||||
namespace Cache {
|
||||
class SourceContext;
|
||||
|
@@ -4,6 +4,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <ostream>
|
||||
#include <span>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace std {
|
||||
@@ -24,4 +26,41 @@ ostream &operator<<(ostream &out, const vector<T> &vector)
|
||||
return out;
|
||||
}
|
||||
|
||||
template<typename T, size_t Extent>
|
||||
ostream &operator<<(ostream &out, const span<T, Extent> &span)
|
||||
{
|
||||
out << "[";
|
||||
|
||||
for (auto current = span.begin(); current != span.end(); ++current) {
|
||||
out << *current;
|
||||
|
||||
if (std::next(current) != span.end())
|
||||
out << ", ";
|
||||
}
|
||||
|
||||
out << "]";
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
template<typename First, typename Second>
|
||||
ostream &operator<<(ostream &out, const pair<First, Second> &pair)
|
||||
{
|
||||
out << "{";
|
||||
|
||||
out << pair.first;
|
||||
|
||||
out << ", ";
|
||||
|
||||
out << pair.second;
|
||||
|
||||
out << "}";
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
inline ostream &operator<<(ostream &out, std::byte byte)
|
||||
{
|
||||
return out << std::hex << static_cast<int>(byte) << std::dec;
|
||||
}
|
||||
} // namespace std
|
||||
|
@@ -80,6 +80,7 @@ add_qtc_library(TestDesignerCore OBJECT
|
||||
metainfo/nodemetainfo.cpp
|
||||
model/abstractproperty.cpp
|
||||
model/abstractview.cpp
|
||||
model/auxiliarypropertystorageview.cpp model/auxiliarypropertystorageview.h
|
||||
model/annotation.cpp
|
||||
model/bindingproperty.cpp
|
||||
model/import.cpp
|
||||
|
@@ -1,6 +1,7 @@
|
||||
# qmldesigner/designercore/model
|
||||
extend_qtc_test(unittest
|
||||
SOURCES
|
||||
auxiliarypropertystorageview-test.cpp
|
||||
import-test.cpp
|
||||
model-test.cpp
|
||||
modelnode-test.cpp
|
||||
|
@@ -0,0 +1,230 @@
|
||||
// Copyright (C) 2023 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
#include <googletest.h>
|
||||
|
||||
#include <mocks/externaldependenciesmock.h>
|
||||
#include <mocks/modelresourcemanagementmock.h>
|
||||
#include <mocks/projectstoragemock.h>
|
||||
#include <mocks/sourcepathcachemock.h>
|
||||
|
||||
#include <model/auxiliarypropertystorageview.h>
|
||||
|
||||
namespace {
|
||||
|
||||
using QmlDesigner::AuxiliaryDataKey;
|
||||
using QmlDesigner::AuxiliaryDataType;
|
||||
using QmlDesigner::ModelNode;
|
||||
|
||||
template<typename NameMatcher>
|
||||
auto AuxiliaryProperty(AuxiliaryDataType type, const NameMatcher &nameMatcher, const QVariant &value)
|
||||
{
|
||||
return Pair(AllOf(Field(&AuxiliaryDataKey::type, type),
|
||||
Field(&AuxiliaryDataKey::name, nameMatcher)),
|
||||
value);
|
||||
}
|
||||
|
||||
class AuxiliaryPropertyStorageView : public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
struct StaticData
|
||||
{
|
||||
Sqlite::Database database{":memory:", Sqlite::JournalMode::Memory};
|
||||
};
|
||||
|
||||
~AuxiliaryPropertyStorageView() { view.resetForTestsOnly(); }
|
||||
|
||||
static void SetUpTestSuite() { staticData = std::make_unique<StaticData>(); }
|
||||
|
||||
static void TearDownTestSuite() { staticData.reset(); }
|
||||
|
||||
inline static std::unique_ptr<StaticData> staticData;
|
||||
Sqlite::Database &database = staticData->database;
|
||||
NiceMock<SourcePathCacheMockWithPaths> pathCacheMock{"/path/foo.qml"};
|
||||
NiceMock<ProjectStorageMockWithQtQtuick> projectStorageMock{pathCacheMock.sourceId};
|
||||
NiceMock<ModelResourceManagementMock> resourceManagementMock;
|
||||
QmlDesigner::Imports imports = {QmlDesigner::Import::createLibraryImport("QtQuick")};
|
||||
QmlDesigner::Model model{{projectStorageMock, pathCacheMock},
|
||||
"Item",
|
||||
imports,
|
||||
pathCacheMock.path.toQString(),
|
||||
std::make_unique<ModelResourceManagementMockWrapper>(
|
||||
resourceManagementMock)};
|
||||
QmlDesigner::Model model2{{projectStorageMock, pathCacheMock},
|
||||
"Item",
|
||||
imports,
|
||||
pathCacheMock.path.toQString(),
|
||||
std::make_unique<ModelResourceManagementMockWrapper>(
|
||||
resourceManagementMock)};
|
||||
NiceMock<ExternalDependenciesMock> externalDependenciesMock;
|
||||
QmlDesigner::AuxiliaryPropertyStorageView view{database, externalDependenciesMock};
|
||||
ModelNode rootNode = model.rootModelNode();
|
||||
ModelNode rootNode2 = model2.rootModelNode();
|
||||
QmlDesigner::AuxiliaryDataKeyView persistentFooKey{AuxiliaryDataType::Persistent, "foo"};
|
||||
QmlDesigner::AuxiliaryDataKeyView temporaryFooKey{AuxiliaryDataType::Temporary, "foo"};
|
||||
};
|
||||
|
||||
TEST_F(AuxiliaryPropertyStorageView, store_persistent_auxiliary_property)
|
||||
{
|
||||
// arrange
|
||||
model.attachView(&view);
|
||||
rootNode.setIdWithoutRefactoring("root");
|
||||
rootNode2.setIdWithoutRefactoring("root");
|
||||
|
||||
// act
|
||||
rootNode.setAuxiliaryData(persistentFooKey, "text");
|
||||
|
||||
// assert
|
||||
model.detachView(&view);
|
||||
model2.attachView(&view);
|
||||
ASSERT_THAT(rootNode2.auxiliaryData(),
|
||||
Contains(AuxiliaryProperty(AuxiliaryDataType::Persistent, "foo", "text")));
|
||||
}
|
||||
|
||||
TEST_F(AuxiliaryPropertyStorageView, store_color_auxiliary_property)
|
||||
{
|
||||
// arrange
|
||||
model.attachView(&view);
|
||||
rootNode.setIdWithoutRefactoring("root");
|
||||
rootNode2.setIdWithoutRefactoring("root");
|
||||
QColor color{Qt::red};
|
||||
|
||||
// act
|
||||
rootNode.setAuxiliaryData(persistentFooKey, color);
|
||||
|
||||
// assert
|
||||
model.detachView(&view);
|
||||
model2.attachView(&view);
|
||||
ASSERT_THAT(rootNode2.auxiliaryData(),
|
||||
Contains(AuxiliaryProperty(AuxiliaryDataType::Persistent, "foo", color)));
|
||||
}
|
||||
|
||||
TEST_F(AuxiliaryPropertyStorageView, do_not_store_temporary_auxiliary_property)
|
||||
{
|
||||
// arrange
|
||||
model.attachView(&view);
|
||||
rootNode.setIdWithoutRefactoring("root");
|
||||
rootNode2.setIdWithoutRefactoring("root");
|
||||
|
||||
// act
|
||||
rootNode.setAuxiliaryData(temporaryFooKey, "text");
|
||||
|
||||
// assert
|
||||
model.detachView(&view);
|
||||
model2.attachView(&view);
|
||||
ASSERT_THAT(rootNode2.auxiliaryData(),
|
||||
Not(Contains(AuxiliaryProperty(AuxiliaryDataType::Temporary, "foo", "text"))));
|
||||
}
|
||||
|
||||
TEST_F(AuxiliaryPropertyStorageView, do_not_load_node_without_id)
|
||||
{
|
||||
// arrange
|
||||
model.attachView(&view);
|
||||
rootNode.setIdWithoutRefactoring("root");
|
||||
|
||||
// act
|
||||
rootNode.setAuxiliaryData(persistentFooKey, "text");
|
||||
|
||||
// assert
|
||||
model.detachView(&view);
|
||||
model2.attachView(&view);
|
||||
ASSERT_THAT(rootNode2.auxiliaryData(),
|
||||
Not(Contains(AuxiliaryProperty(AuxiliaryDataType::Persistent, "foo", "text"))));
|
||||
}
|
||||
|
||||
TEST_F(AuxiliaryPropertyStorageView, do_not_store_node_without_id)
|
||||
{
|
||||
// arrange
|
||||
model.attachView(&view);
|
||||
rootNode2.setIdWithoutRefactoring("root");
|
||||
|
||||
// act
|
||||
rootNode.setAuxiliaryData(persistentFooKey, "text");
|
||||
|
||||
// assert
|
||||
model.detachView(&view);
|
||||
model2.attachView(&view);
|
||||
ASSERT_THAT(rootNode2.auxiliaryData(),
|
||||
Not(Contains(AuxiliaryProperty(AuxiliaryDataType::Persistent, "foo", "text"))));
|
||||
}
|
||||
|
||||
TEST_F(AuxiliaryPropertyStorageView, do_not_load_from_different_document)
|
||||
{
|
||||
// arrange
|
||||
model2.setFileUrl(QUrl{"/path/foo2.qml"});
|
||||
model.attachView(&view);
|
||||
rootNode.setIdWithoutRefactoring("root");
|
||||
rootNode2.setIdWithoutRefactoring("root");
|
||||
|
||||
// act
|
||||
rootNode.setAuxiliaryData(persistentFooKey, "text");
|
||||
|
||||
// assert
|
||||
model.detachView(&view);
|
||||
model2.attachView(&view);
|
||||
ASSERT_THAT(rootNode2.auxiliaryData(),
|
||||
Not(Contains(AuxiliaryProperty(AuxiliaryDataType::Persistent, "foo", "text"))));
|
||||
}
|
||||
|
||||
TEST_F(AuxiliaryPropertyStorageView, removing_node_erases_entries)
|
||||
{
|
||||
// arrange
|
||||
auto node = model.createModelNode("Item");
|
||||
node.setIdWithoutRefactoring("node1");
|
||||
node.setAuxiliaryData(persistentFooKey, "some value");
|
||||
auto node2 = model2.createModelNode("Item");
|
||||
node2.setIdWithoutRefactoring("node1");
|
||||
model.attachView(&view);
|
||||
rootNode.setIdWithoutRefactoring("root");
|
||||
rootNode2.setIdWithoutRefactoring("root");
|
||||
|
||||
// act
|
||||
node.destroy();
|
||||
|
||||
// assert
|
||||
model.detachView(&view);
|
||||
model2.attachView(&view);
|
||||
ASSERT_THAT(node2.auxiliaryData(),
|
||||
Not(Contains(AuxiliaryProperty(AuxiliaryDataType::Persistent, "foo", "some value"))));
|
||||
}
|
||||
|
||||
TEST_F(AuxiliaryPropertyStorageView, changing_node_id_is_saving_data_under_new_id)
|
||||
{
|
||||
// arrange
|
||||
model.attachView(&view);
|
||||
rootNode.setIdWithoutRefactoring("root");
|
||||
rootNode2.setIdWithoutRefactoring("root2");
|
||||
rootNode.setAuxiliaryData(persistentFooKey, "text");
|
||||
model.detachView(&view);
|
||||
model.attachView(&view);
|
||||
|
||||
// act
|
||||
rootNode.setIdWithoutRefactoring("root2");
|
||||
|
||||
// assert
|
||||
model.detachView(&view);
|
||||
model2.attachView(&view);
|
||||
ASSERT_THAT(rootNode2.auxiliaryData(),
|
||||
Contains(AuxiliaryProperty(AuxiliaryDataType::Persistent, "foo", "text")));
|
||||
}
|
||||
|
||||
TEST_F(AuxiliaryPropertyStorageView, changing_node_id_is_removing_data_from_old_id)
|
||||
{
|
||||
// arrange
|
||||
model.attachView(&view);
|
||||
rootNode.setIdWithoutRefactoring("root");
|
||||
rootNode2.setIdWithoutRefactoring("root");
|
||||
rootNode.setAuxiliaryData(persistentFooKey, "text");
|
||||
model.detachView(&view);
|
||||
model.attachView(&view);
|
||||
|
||||
// act
|
||||
rootNode.setIdWithoutRefactoring("root2");
|
||||
|
||||
// assert
|
||||
model.detachView(&view);
|
||||
model2.attachView(&view);
|
||||
ASSERT_THAT(rootNode2.auxiliaryData(),
|
||||
Not(Contains(AuxiliaryProperty(AuxiliaryDataType::Persistent, "foo", "text"))));
|
||||
}
|
||||
} // namespace
|
@@ -9,10 +9,12 @@
|
||||
|
||||
#include "conditionally-disabled-tests.h"
|
||||
|
||||
#include "../printers/gtest-creator-printing.h"
|
||||
#include "../printers/gtest-qt-printing.h"
|
||||
#include "../printers/gtest-std-printing.h"
|
||||
|
||||
#include "../printers/gtest-qt-printing.h"
|
||||
|
||||
#include "../printers/gtest-creator-printing.h"
|
||||
|
||||
#include "../utils/google-using-declarations.h"
|
||||
|
||||
#include "../matchers/unittest-matchers.h"
|
||||
|
Reference in New Issue
Block a user