forked from qt-creator/qt-creator
QmlDesigner: Allow annotations in comments
This patch allows to store the auxiliary data of model nodes as meta data in the QML file. The meta data is encoded in a comment at the end of the QML file. By default such meta data is attached to the clipboard. Change-Id: I794d2c1297d270c5c1099c6c1be98b6b7a7f650b Reviewed-by: Tim Jenssen <tim.jenssen@qt.io>
This commit is contained in:
@@ -130,8 +130,9 @@ QString DesignDocumentView::toText() const
|
|||||||
|
|
||||||
ModelNode rewriterNode(rewriterView->rootModelNode());
|
ModelNode rewriterNode(rewriterView->rootModelNode());
|
||||||
|
|
||||||
|
rewriterView->writeAuxiliaryData();
|
||||||
|
return rewriterView->extractText({rewriterNode}).value(rewriterNode) + rewriterView->getRawAuxiliaryData();
|
||||||
//get the text of the root item without imports
|
//get the text of the root item without imports
|
||||||
return rewriterView->extractText({rewriterNode}).value(rewriterNode);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DesignDocumentView::fromText(QString text)
|
void DesignDocumentView::fromText(QString text)
|
||||||
@@ -151,6 +152,8 @@ void DesignDocumentView::fromText(QString text)
|
|||||||
rewriterView->setTextModifier(&modifier);
|
rewriterView->setTextModifier(&modifier);
|
||||||
inputModel->setRewriterView(rewriterView.data());
|
inputModel->setRewriterView(rewriterView.data());
|
||||||
|
|
||||||
|
rewriterView->restoreAuxiliaryData();
|
||||||
|
|
||||||
if (rewriterView->errors().isEmpty() && rewriterView->rootModelNode().isValid()) {
|
if (rewriterView->errors().isEmpty() && rewriterView->rootModelNode().isValid()) {
|
||||||
ModelMerger merger(this);
|
ModelMerger merger(this);
|
||||||
merger.replaceModel(rewriterView->rootModelNode());
|
merger.replaceModel(rewriterView->rootModelNode());
|
||||||
|
@@ -163,6 +163,12 @@ public:
|
|||||||
void qmlTextChanged();
|
void qmlTextChanged();
|
||||||
void delayedSetup();
|
void delayedSetup();
|
||||||
|
|
||||||
|
void writeAuxiliaryData();
|
||||||
|
void restoreAuxiliaryData();
|
||||||
|
|
||||||
|
QString getRawAuxiliaryData() const;
|
||||||
|
QString auxiliaryDataAsQML() const;
|
||||||
|
|
||||||
protected: // functions
|
protected: // functions
|
||||||
void importAdded(const Import &import);
|
void importAdded(const Import &import);
|
||||||
void importRemoved(const Import &import);
|
void importRemoved(const Import &import);
|
||||||
|
@@ -60,6 +60,13 @@ static void syncVariantProperties(ModelNode &outputNode, const ModelNode &inputN
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void syncAuxiliaryProperties(ModelNode &outputNode, const ModelNode &inputNode)
|
||||||
|
{
|
||||||
|
auto tmp = inputNode.auxiliaryData();
|
||||||
|
for (auto iter = tmp.begin(); iter != tmp.end(); ++iter)
|
||||||
|
outputNode.setAuxiliaryData(iter.key(), iter.value());
|
||||||
|
}
|
||||||
|
|
||||||
static void syncBindingProperties(ModelNode &outputNode, const ModelNode &inputNode, const QHash<QString, QString> &idRenamingHash)
|
static void syncBindingProperties(ModelNode &outputNode, const ModelNode &inputNode, const QHash<QString, QString> &idRenamingHash)
|
||||||
{
|
{
|
||||||
foreach (const BindingProperty &bindingProperty, inputNode.bindingProperties()) {
|
foreach (const BindingProperty &bindingProperty, inputNode.bindingProperties()) {
|
||||||
@@ -138,6 +145,7 @@ static ModelNode createNodeFromNode(const ModelNode &modelNode,const QHash<QStri
|
|||||||
NodeMetaInfo nodeMetaInfo = view->model()->metaInfo(modelNode.type());
|
NodeMetaInfo nodeMetaInfo = view->model()->metaInfo(modelNode.type());
|
||||||
ModelNode newNode(view->createModelNode(modelNode.type(), nodeMetaInfo.majorVersion(), nodeMetaInfo.minorVersion(),
|
ModelNode newNode(view->createModelNode(modelNode.type(), nodeMetaInfo.majorVersion(), nodeMetaInfo.minorVersion(),
|
||||||
propertyList, variantPropertyList, modelNode.nodeSource(), modelNode.nodeSourceType()));
|
propertyList, variantPropertyList, modelNode.nodeSource(), modelNode.nodeSourceType()));
|
||||||
|
syncAuxiliaryProperties(newNode, modelNode);
|
||||||
syncBindingProperties(newNode, modelNode, idRenamingHash);
|
syncBindingProperties(newNode, modelNode, idRenamingHash);
|
||||||
syncId(newNode, modelNode, idRenamingHash);
|
syncId(newNode, modelNode, idRenamingHash);
|
||||||
syncNodeProperties(newNode, modelNode, idRenamingHash, view);
|
syncNodeProperties(newNode, modelNode, idRenamingHash, view);
|
||||||
@@ -165,7 +173,6 @@ ModelNode ModelMerger::insertModel(const ModelNode &modelNode)
|
|||||||
|
|
||||||
return newNode;
|
return newNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModelMerger::replaceModel(const ModelNode &modelNode)
|
void ModelMerger::replaceModel(const ModelNode &modelNode)
|
||||||
{
|
{
|
||||||
view()->model()->changeImports(modelNode.model()->imports(), {});
|
view()->model()->changeImports(modelNode.model()->imports(), {});
|
||||||
@@ -182,6 +189,7 @@ void ModelMerger::replaceModel(const ModelNode &modelNode)
|
|||||||
QHash<QString, QString> idRenamingHash;
|
QHash<QString, QString> idRenamingHash;
|
||||||
setupIdRenamingHash(modelNode, idRenamingHash, view());
|
setupIdRenamingHash(modelNode, idRenamingHash, view());
|
||||||
|
|
||||||
|
syncAuxiliaryProperties(rootNode, modelNode);
|
||||||
syncVariantProperties(rootNode, modelNode);
|
syncVariantProperties(rootNode, modelNode);
|
||||||
syncBindingProperties(rootNode, modelNode, idRenamingHash);
|
syncBindingProperties(rootNode, modelNode, idRenamingHash);
|
||||||
syncId(rootNode, modelNode, idRenamingHash);
|
syncId(rootNode, modelNode, idRenamingHash);
|
||||||
|
@@ -42,11 +42,17 @@
|
|||||||
|
|
||||||
#include <qmljs/parser/qmljsengine_p.h>
|
#include <qmljs/parser/qmljsengine_p.h>
|
||||||
#include <qmljs/qmljsmodelmanagerinterface.h>
|
#include <qmljs/qmljsmodelmanagerinterface.h>
|
||||||
|
#include <qmljs/qmljssimplereader.h>
|
||||||
|
|
||||||
|
#include <utils/changeset.h>
|
||||||
|
#include <utils/qtcassert.h>
|
||||||
|
|
||||||
using namespace QmlDesigner::Internal;
|
using namespace QmlDesigner::Internal;
|
||||||
|
|
||||||
namespace QmlDesigner {
|
namespace QmlDesigner {
|
||||||
|
|
||||||
|
const char annotationsEscapeSequence[] = "##^##";
|
||||||
|
|
||||||
RewriterView::RewriterView(DifferenceHandling differenceHandling, QObject *parent):
|
RewriterView::RewriterView(DifferenceHandling differenceHandling, QObject *parent):
|
||||||
AbstractView(parent),
|
AbstractView(parent),
|
||||||
m_differenceHandling(differenceHandling),
|
m_differenceHandling(differenceHandling),
|
||||||
@@ -442,6 +448,56 @@ void RewriterView::notifyErrorsAndWarnings(const QList<DocumentMessage> &errors)
|
|||||||
emitDocumentMessage(errors, m_warnings);
|
emitDocumentMessage(errors, m_warnings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString RewriterView::auxiliaryDataAsQML() const
|
||||||
|
{
|
||||||
|
bool hasAuxData = false;
|
||||||
|
|
||||||
|
QString str = "Designer {\n ";
|
||||||
|
|
||||||
|
int columnCount = 0;
|
||||||
|
for (const auto node : allModelNodes()) {
|
||||||
|
QHash<PropertyName, QVariant> data = node.auxiliaryData();
|
||||||
|
if (!data.isEmpty()) {
|
||||||
|
hasAuxData = true;
|
||||||
|
if (columnCount > 80) {
|
||||||
|
str += "\n";
|
||||||
|
columnCount = 0;
|
||||||
|
}
|
||||||
|
const int startLen = str.length();
|
||||||
|
str += "D{";
|
||||||
|
str += "i:";
|
||||||
|
str += QString::number(node.internalId());
|
||||||
|
str += ";";
|
||||||
|
|
||||||
|
for (auto i = data.begin(); i != data.end(); ++i) {
|
||||||
|
const QVariant value = i.value();
|
||||||
|
QString strValue = value.toString();
|
||||||
|
if (value.type() == QMetaType::QString)
|
||||||
|
strValue = "\"" + strValue + "\"";
|
||||||
|
|
||||||
|
if (!strValue.isEmpty()) {
|
||||||
|
str += QString::fromUtf8(i.key()) + ":";
|
||||||
|
str += strValue;
|
||||||
|
str += ";";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (str.back() == ';')
|
||||||
|
str.chop(1);
|
||||||
|
|
||||||
|
str += "}";
|
||||||
|
columnCount += str.length() - startLen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
str += "\n}\n";
|
||||||
|
|
||||||
|
if (hasAuxData)
|
||||||
|
return str;
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
Internal::ModelNodePositionStorage *RewriterView::positionStorage() const
|
Internal::ModelNodePositionStorage *RewriterView::positionStorage() const
|
||||||
{
|
{
|
||||||
return m_positionStorage.data();
|
return m_positionStorage.data();
|
||||||
@@ -820,4 +876,108 @@ void RewriterView::delayedSetup()
|
|||||||
m_textToModelMerger->delayedSetup();
|
m_textToModelMerger->delayedSetup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static QString annotationsEnd()
|
||||||
|
{
|
||||||
|
const static QString end = QString(" %1*/\n").arg(annotationsEscapeSequence);
|
||||||
|
return end;
|
||||||
|
}
|
||||||
|
|
||||||
|
static QString annotationsStart()
|
||||||
|
{
|
||||||
|
const static QString start = QString("\n/*%1 ").arg(annotationsEscapeSequence);
|
||||||
|
return start;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString RewriterView::getRawAuxiliaryData() const
|
||||||
|
{
|
||||||
|
QTC_ASSERT(m_textModifier, return {});
|
||||||
|
|
||||||
|
const QString oldText = m_textModifier->text();
|
||||||
|
|
||||||
|
QString newText = oldText;
|
||||||
|
|
||||||
|
int startIndex = newText.indexOf(annotationsStart());
|
||||||
|
int endIndex = newText.indexOf(annotationsEnd());
|
||||||
|
|
||||||
|
if (startIndex > 0 && endIndex > 0)
|
||||||
|
return newText.mid(startIndex, endIndex - startIndex + annotationsEnd().length());
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
void RewriterView::writeAuxiliaryData()
|
||||||
|
{
|
||||||
|
QTC_ASSERT(m_textModifier, return);
|
||||||
|
|
||||||
|
const QString oldText = m_textModifier->text();
|
||||||
|
|
||||||
|
QString newText = oldText;
|
||||||
|
|
||||||
|
int startIndex = newText.indexOf(annotationsStart());
|
||||||
|
int endIndex = newText.indexOf(annotationsEnd());
|
||||||
|
|
||||||
|
if (startIndex > 0 && endIndex > 0)
|
||||||
|
newText.remove(startIndex, endIndex - startIndex + annotationsEnd().length());
|
||||||
|
|
||||||
|
QString auxData = auxiliaryDataAsQML();
|
||||||
|
|
||||||
|
if (!auxData.isEmpty()) {
|
||||||
|
auxData.prepend(annotationsStart());
|
||||||
|
auxData.append(annotationsEnd());
|
||||||
|
newText.append(auxData);
|
||||||
|
|
||||||
|
QTextCursor tc(m_textModifier->textDocument());
|
||||||
|
Utils::ChangeSet changeSet;
|
||||||
|
changeSet.replace(0, oldText.length(), newText);
|
||||||
|
changeSet.apply(&tc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void checkNode(QmlJS::SimpleReaderNode::Ptr node, RewriterView *view);
|
||||||
|
|
||||||
|
void static checkChildNodes(QmlJS::SimpleReaderNode::Ptr node, RewriterView *view)
|
||||||
|
{
|
||||||
|
for (auto child : node->children())
|
||||||
|
checkNode(child, view);
|
||||||
|
}
|
||||||
|
void static checkNode(QmlJS::SimpleReaderNode::Ptr node, RewriterView *view)
|
||||||
|
{
|
||||||
|
if (!node)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!node->propertyNames().contains("i"))
|
||||||
|
return;
|
||||||
|
|
||||||
|
const int internalId = node->property("i").toInt();
|
||||||
|
const ModelNode modelNode = view->modelNodeForInternalId(internalId);
|
||||||
|
if (!modelNode.isValid())
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto properties = node->properties();
|
||||||
|
|
||||||
|
for (auto i = properties.begin(); i != properties.end(); ++i) {
|
||||||
|
if (i.key() != "i")
|
||||||
|
modelNode.setAuxiliaryData(i.key().toUtf8(), i.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
checkChildNodes(node, view);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RewriterView::restoreAuxiliaryData()
|
||||||
|
{
|
||||||
|
QTC_ASSERT(m_textModifier, return);
|
||||||
|
|
||||||
|
const QString text = m_textModifier->text();
|
||||||
|
|
||||||
|
int startIndex = text.indexOf(annotationsStart());
|
||||||
|
int endIndex = text.indexOf(annotationsEnd());
|
||||||
|
|
||||||
|
if (startIndex > 0 && endIndex > 0) {
|
||||||
|
const QString auxSource = text.mid(startIndex + annotationsStart().length(),
|
||||||
|
endIndex - startIndex - annotationsStart().length());
|
||||||
|
QmlJS::SimpleReader reader;
|
||||||
|
checkChildNodes(reader.readFromSource(auxSource), this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} //QmlDesigner
|
} //QmlDesigner
|
||||||
|
@@ -57,6 +57,8 @@ public:
|
|||||||
bool isModificationGroupActive() const;
|
bool isModificationGroupActive() const;
|
||||||
void setModificationGroupActive(bool active);
|
void setModificationGroupActive(bool active);
|
||||||
void applyModificationGroupChanges();
|
void applyModificationGroupChanges();
|
||||||
|
|
||||||
|
using RewriterView::auxiliaryDataAsQML;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // QmlDesigner
|
} // QmlDesigner
|
||||||
|
@@ -59,6 +59,7 @@
|
|||||||
#include <utils/fileutils.h>
|
#include <utils/fileutils.h>
|
||||||
|
|
||||||
#include <qmljs/qmljsinterpreter.h>
|
#include <qmljs/qmljsinterpreter.h>
|
||||||
|
#include <qmljs/qmljssimplereader.h>
|
||||||
#include <extensionsystem/pluginmanager.h>
|
#include <extensionsystem/pluginmanager.h>
|
||||||
|
|
||||||
#include <QPlainTextEdit>
|
#include <QPlainTextEdit>
|
||||||
@@ -8208,5 +8209,123 @@ void tst_TestCore::changeGradientId()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void checkNode(QmlJS::SimpleReaderNode::Ptr node, TestRewriterView *view);
|
||||||
|
|
||||||
|
void static checkChildNodes(QmlJS::SimpleReaderNode::Ptr node, TestRewriterView *view)
|
||||||
|
{
|
||||||
|
for (auto child : node->children())
|
||||||
|
checkNode(child, view);
|
||||||
|
}
|
||||||
|
|
||||||
|
void static checkNode(QmlJS::SimpleReaderNode::Ptr node, TestRewriterView *view)
|
||||||
|
{
|
||||||
|
QVERIFY(node);
|
||||||
|
QVERIFY(node->propertyNames().contains("i"));
|
||||||
|
const int internalId = node->property("i").toInt();
|
||||||
|
const ModelNode modelNode = view->modelNodeForInternalId(internalId);
|
||||||
|
QVERIFY(modelNode.isValid());
|
||||||
|
auto properties = node->properties();
|
||||||
|
|
||||||
|
for (auto i = properties.begin(); i != properties.end(); ++i) {
|
||||||
|
if (i.key() != "i")
|
||||||
|
QCOMPARE(i.value(), modelNode.auxiliaryData(i.key().toUtf8()));
|
||||||
|
}
|
||||||
|
|
||||||
|
checkChildNodes(node, view);
|
||||||
|
}
|
||||||
|
|
||||||
|
void tst_TestCore::writeAnnotations()
|
||||||
|
{
|
||||||
|
const QLatin1String qmlCode("\n"
|
||||||
|
"import QtQuick 2.1\n"
|
||||||
|
"\n"
|
||||||
|
"Rectangle {\n"
|
||||||
|
" Item {\n"
|
||||||
|
" }\n"
|
||||||
|
"\n"
|
||||||
|
" MouseArea {\n"
|
||||||
|
" x: 3\n"
|
||||||
|
" y: 3\n"
|
||||||
|
" }\n"
|
||||||
|
"}");
|
||||||
|
|
||||||
|
const QLatin1String metaCode("\n/*##^## Designer {\n D{i:0;x:10}D{i:1;test:true;x:10;test2:\"string\"}"
|
||||||
|
"D{i:2;test:true;x:10;test2:\"string\"}\n}\n ##^##*/\n");
|
||||||
|
|
||||||
|
QPlainTextEdit textEdit;
|
||||||
|
textEdit.setPlainText(qmlCode);
|
||||||
|
NotIndentingTextEditModifier textModifier(&textEdit);
|
||||||
|
|
||||||
|
QScopedPointer<Model> model(Model::create("QtQuick.Item", 2, 1));
|
||||||
|
QVERIFY(model.data());
|
||||||
|
|
||||||
|
QScopedPointer<TestRewriterView> testRewriterView(new TestRewriterView());
|
||||||
|
testRewriterView->setTextModifier(&textModifier);
|
||||||
|
model->attachView(testRewriterView.data());
|
||||||
|
|
||||||
|
QVERIFY(model.data());
|
||||||
|
ModelNode rootModelNode(testRewriterView->rootModelNode());
|
||||||
|
QVERIFY(rootModelNode.isValid());
|
||||||
|
|
||||||
|
rootModelNode.setAuxiliaryData("x", 10);
|
||||||
|
for (const auto child : rootModelNode.allSubModelNodes()) {
|
||||||
|
child.setAuxiliaryData("x", 10);
|
||||||
|
child.setAuxiliaryData("test", true);
|
||||||
|
child.setAuxiliaryData("test2", "string");
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString metaSource = testRewriterView->auxiliaryDataAsQML();
|
||||||
|
|
||||||
|
QmlJS::SimpleReader reader;
|
||||||
|
checkChildNodes(reader.readFromSource(metaSource), testRewriterView.data());
|
||||||
|
|
||||||
|
testRewriterView->writeAuxiliaryData();
|
||||||
|
const QString textWithMeta = testRewriterView->textModifier()->text();
|
||||||
|
testRewriterView->writeAuxiliaryData();
|
||||||
|
QCOMPARE(textWithMeta.length(), testRewriterView->textModifier()->text().length());
|
||||||
|
}
|
||||||
|
|
||||||
|
void tst_TestCore::readAnnotations()
|
||||||
|
{
|
||||||
|
const QLatin1String qmlCode("\n"
|
||||||
|
"import QtQuick 2.1\n"
|
||||||
|
"\n"
|
||||||
|
"Rectangle {\n"
|
||||||
|
" Item {\n"
|
||||||
|
" }\n"
|
||||||
|
"\n"
|
||||||
|
" MouseArea {\n"
|
||||||
|
" x: 3\n"
|
||||||
|
" y: 3\n"
|
||||||
|
" }\n"
|
||||||
|
"}");
|
||||||
|
|
||||||
|
const QLatin1String metaCode("\n/*##^## Designer {\n D{i:0;x:10}D{i:1;test:true;x:10;test2:\"string\"}"
|
||||||
|
"D{i:2;test:true;x:10;test2:\"string\"}\n}\n ##^##*/\n");
|
||||||
|
|
||||||
|
const QLatin1String metaCodeQmlCode("Designer {\n D{i:0;x:10}D{i:1;test2:\"string\";x:10;test:true}"
|
||||||
|
"D{i:2;test2:\"string\";x:10;test:true}\n}\n");
|
||||||
|
|
||||||
|
QPlainTextEdit textEdit;
|
||||||
|
textEdit.setPlainText(qmlCode + metaCode);
|
||||||
|
NotIndentingTextEditModifier textModifier(&textEdit);
|
||||||
|
|
||||||
|
QScopedPointer<Model> model(Model::create("QtQuick.Item", 2, 1));
|
||||||
|
QVERIFY(model.data());
|
||||||
|
|
||||||
|
QScopedPointer<TestRewriterView> testRewriterView(new TestRewriterView());
|
||||||
|
testRewriterView->setTextModifier(&textModifier);
|
||||||
|
model->attachView(testRewriterView.data());
|
||||||
|
|
||||||
|
QVERIFY(model.data());
|
||||||
|
ModelNode rootModelNode(testRewriterView->rootModelNode());
|
||||||
|
QVERIFY(rootModelNode.isValid());
|
||||||
|
|
||||||
|
testRewriterView->restoreAuxiliaryData();
|
||||||
|
|
||||||
|
const QString metaSource = testRewriterView->auxiliaryDataAsQML();
|
||||||
|
QCOMPARE(metaSource.length(), QString(metaCodeQmlCode).length());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
QTEST_MAIN(tst_TestCore);
|
QTEST_MAIN(tst_TestCore);
|
||||||
|
@@ -229,4 +229,8 @@ private slots:
|
|||||||
// Object bindings as properties:
|
// Object bindings as properties:
|
||||||
void loadGradient();
|
void loadGradient();
|
||||||
void changeGradientId();
|
void changeGradientId();
|
||||||
|
|
||||||
|
// QMLAnnotations
|
||||||
|
void writeAnnotations();
|
||||||
|
void readAnnotations();
|
||||||
};
|
};
|
||||||
|
Reference in New Issue
Block a user