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());
|
||||
|
||||
rewriterView->writeAuxiliaryData();
|
||||
return rewriterView->extractText({rewriterNode}).value(rewriterNode) + rewriterView->getRawAuxiliaryData();
|
||||
//get the text of the root item without imports
|
||||
return rewriterView->extractText({rewriterNode}).value(rewriterNode);
|
||||
}
|
||||
|
||||
void DesignDocumentView::fromText(QString text)
|
||||
@@ -151,6 +152,8 @@ void DesignDocumentView::fromText(QString text)
|
||||
rewriterView->setTextModifier(&modifier);
|
||||
inputModel->setRewriterView(rewriterView.data());
|
||||
|
||||
rewriterView->restoreAuxiliaryData();
|
||||
|
||||
if (rewriterView->errors().isEmpty() && rewriterView->rootModelNode().isValid()) {
|
||||
ModelMerger merger(this);
|
||||
merger.replaceModel(rewriterView->rootModelNode());
|
||||
|
@@ -163,6 +163,12 @@ public:
|
||||
void qmlTextChanged();
|
||||
void delayedSetup();
|
||||
|
||||
void writeAuxiliaryData();
|
||||
void restoreAuxiliaryData();
|
||||
|
||||
QString getRawAuxiliaryData() const;
|
||||
QString auxiliaryDataAsQML() const;
|
||||
|
||||
protected: // functions
|
||||
void importAdded(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)
|
||||
{
|
||||
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());
|
||||
ModelNode newNode(view->createModelNode(modelNode.type(), nodeMetaInfo.majorVersion(), nodeMetaInfo.minorVersion(),
|
||||
propertyList, variantPropertyList, modelNode.nodeSource(), modelNode.nodeSourceType()));
|
||||
syncAuxiliaryProperties(newNode, modelNode);
|
||||
syncBindingProperties(newNode, modelNode, idRenamingHash);
|
||||
syncId(newNode, modelNode, idRenamingHash);
|
||||
syncNodeProperties(newNode, modelNode, idRenamingHash, view);
|
||||
@@ -165,7 +173,6 @@ ModelNode ModelMerger::insertModel(const ModelNode &modelNode)
|
||||
|
||||
return newNode;
|
||||
}
|
||||
|
||||
void ModelMerger::replaceModel(const ModelNode &modelNode)
|
||||
{
|
||||
view()->model()->changeImports(modelNode.model()->imports(), {});
|
||||
@@ -182,6 +189,7 @@ void ModelMerger::replaceModel(const ModelNode &modelNode)
|
||||
QHash<QString, QString> idRenamingHash;
|
||||
setupIdRenamingHash(modelNode, idRenamingHash, view());
|
||||
|
||||
syncAuxiliaryProperties(rootNode, modelNode);
|
||||
syncVariantProperties(rootNode, modelNode);
|
||||
syncBindingProperties(rootNode, modelNode, idRenamingHash);
|
||||
syncId(rootNode, modelNode, idRenamingHash);
|
||||
|
@@ -42,11 +42,17 @@
|
||||
|
||||
#include <qmljs/parser/qmljsengine_p.h>
|
||||
#include <qmljs/qmljsmodelmanagerinterface.h>
|
||||
#include <qmljs/qmljssimplereader.h>
|
||||
|
||||
#include <utils/changeset.h>
|
||||
#include <utils/qtcassert.h>
|
||||
|
||||
using namespace QmlDesigner::Internal;
|
||||
|
||||
namespace QmlDesigner {
|
||||
|
||||
const char annotationsEscapeSequence[] = "##^##";
|
||||
|
||||
RewriterView::RewriterView(DifferenceHandling differenceHandling, QObject *parent):
|
||||
AbstractView(parent),
|
||||
m_differenceHandling(differenceHandling),
|
||||
@@ -442,6 +448,56 @@ void RewriterView::notifyErrorsAndWarnings(const QList<DocumentMessage> &errors)
|
||||
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
|
||||
{
|
||||
return m_positionStorage.data();
|
||||
@@ -820,4 +876,108 @@ void RewriterView::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
|
||||
|
@@ -57,6 +57,8 @@ public:
|
||||
bool isModificationGroupActive() const;
|
||||
void setModificationGroupActive(bool active);
|
||||
void applyModificationGroupChanges();
|
||||
|
||||
using RewriterView::auxiliaryDataAsQML;
|
||||
};
|
||||
|
||||
} // QmlDesigner
|
||||
|
@@ -59,6 +59,7 @@
|
||||
#include <utils/fileutils.h>
|
||||
|
||||
#include <qmljs/qmljsinterpreter.h>
|
||||
#include <qmljs/qmljssimplereader.h>
|
||||
#include <extensionsystem/pluginmanager.h>
|
||||
|
||||
#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);
|
||||
|
@@ -229,4 +229,8 @@ private slots:
|
||||
// Object bindings as properties:
|
||||
void loadGradient();
|
||||
void changeGradientId();
|
||||
|
||||
// QMLAnnotations
|
||||
void writeAnnotations();
|
||||
void readAnnotations();
|
||||
};
|
||||
|
Reference in New Issue
Block a user