diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt index 53a3e4de84d..f9a6dc94cbf 100644 --- a/src/plugins/qmldesigner/CMakeLists.txt +++ b/src/plugins/qmldesigner/CMakeLists.txt @@ -457,6 +457,7 @@ extend_qtc_plugin(QmlDesigner include/rewriterview.h include/rewritingexception.h include/signalhandlerproperty.h + include/stylesheetmerger.h include/subcomponentmanager.h include/textmodifier.h include/variantproperty.h @@ -530,6 +531,7 @@ extend_qtc_plugin(QmlDesigner model/rewriteactioncompressor.cpp model/rewriteactioncompressor.h model/rewriterview.cpp model/signalhandlerproperty.cpp + model/stylesheetmerger.cpp model/textmodifier.cpp model/texttomodelmerger.cpp model/texttomodelmerger.h model/variantproperty.cpp diff --git a/src/plugins/qmldesigner/designercore/designercore-lib.pri b/src/plugins/qmldesigner/designercore/designercore-lib.pri index 5e99009cdb3..7fc663f4af0 100644 --- a/src/plugins/qmldesigner/designercore/designercore-lib.pri +++ b/src/plugins/qmldesigner/designercore/designercore-lib.pri @@ -83,7 +83,8 @@ SOURCES += $$PWD/model/abstractview.cpp \ $$PWD/instances/puppetdialog.cpp \ $$PWD/model/qmltimeline.cpp \ $$PWD/model/qmltimelinekeyframegroup.cpp \ - $$PWD/model/annotation.cpp + $$PWD/model/annotation.cpp \ + $$PWD/model/stylesheetmerger.cpp HEADERS += $$PWD/include/qmldesignercorelib_global.h \ $$PWD/include/abstractview.h \ @@ -160,7 +161,8 @@ HEADERS += $$PWD/include/qmldesignercorelib_global.h \ $$PWD/instances/puppetdialog.h \ $$PWD/include/qmltimeline.h \ $$PWD/include/qmltimelinekeyframegroup.h \ - $$PWD/include/annotation.h + $$PWD/include/annotation.h \ + $$PWD/include/stylesheetmerger.h FORMS += \ $$PWD/instances/puppetdialog.ui diff --git a/src/plugins/qmldesigner/designercore/include/stylesheetmerger.h b/src/plugins/qmldesigner/designercore/include/stylesheetmerger.h new file mode 100644 index 00000000000..f752e504191 --- /dev/null +++ b/src/plugins/qmldesigner/designercore/include/stylesheetmerger.h @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ +#ifndef STYLESHEETMERGER_H +#define STYLESHEETMERGER_H + +#include +#include +#include + +namespace QmlDesigner { + +class AbstractView; + +struct ReparentInfo { + QString generatedId; + QString templateId; + QString templateParentId; + int parentIndex; + bool alreadyReparented; +}; + +class StylesheetMerger { +public: + StylesheetMerger(AbstractView*, AbstractView*); + void merge(); +private: + void preprocessStyleSheet(); + bool idExistsInBothModels(const QString& id); + void replaceNode(ModelNode&, ModelNode&); + void replaceRootNode(ModelNode& templateRootNode); + void applyStyleProperties(ModelNode &templateNode, const ModelNode &styleNode); + void adjustNodeIndex(ModelNode &node); + void setupIdRenamingHash(); + ModelNode createReplacementNode(const ModelNode &styleNode, ModelNode &modelNode); + void syncNodeProperties(ModelNode &outputNode, const ModelNode &inputNode, bool skipDuplicates = false); + void syncNodeListProperties(ModelNode &outputNode, const ModelNode &inputNode, bool skipDuplicates = false); + void syncId(ModelNode &outputNode, ModelNode &inputNode); + void syncBindingProperties(ModelNode &outputNode, const ModelNode &inputNode); + void syncAuxiliaryProperties(ModelNode &outputNode, const ModelNode &inputNode); + void syncVariantProperties(ModelNode &outputNode, const ModelNode &inputNode); + + AbstractView *m_templateView; + AbstractView *m_styleView; + QHash m_reparentInfoHash; + QHash m_idReplacementHash; +}; + +} +#endif // STYLESHEETMERGER_H diff --git a/src/plugins/qmldesigner/designercore/model/stylesheetmerger.cpp b/src/plugins/qmldesigner/designercore/model/stylesheetmerger.cpp new file mode 100644 index 00000000000..29990a5eca3 --- /dev/null +++ b/src/plugins/qmldesigner/designercore/model/stylesheetmerger.cpp @@ -0,0 +1,404 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ +#include "stylesheetmerger.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace QmlDesigner { + +static void splitIdInBaseNameAndNumber(const QString &id, QString *baseId, int *number) +{ + + int counter = 0; + while (counter < id.count()) { + bool canConvertToInteger = false; + int newNumber = id.rightRef(counter +1).toInt(&canConvertToInteger); + if (canConvertToInteger) + *number = newNumber; + else + break; + + counter++; + } + + *baseId = id.left(id.count() - counter); +} + +void StylesheetMerger::syncNodeProperties(ModelNode &outputNode, const ModelNode &inputNode, bool skipDuplicates) +{ + foreach (const NodeProperty &nodeProperty, inputNode.nodeProperties()) { + ModelNode oldNode = nodeProperty.modelNode(); + if (m_templateView->hasId(oldNode.id()) && skipDuplicates) + continue; + ModelNode newNode = createReplacementNode(oldNode, oldNode); + // cache the property name as removing it will invalidate it + PropertyName propertyName = nodeProperty.name(); + // remove property first to prevent invalid reparenting situation + outputNode.removeProperty(propertyName); + outputNode.nodeProperty(propertyName).reparentHere(newNode); + } +} + +void StylesheetMerger::syncNodeListProperties(ModelNode &outputNode, const ModelNode &inputNode, bool skipDuplicates) +{ + foreach (const NodeListProperty &nodeListProperty, inputNode.nodeListProperties()) { + foreach (ModelNode node, nodeListProperty.toModelNodeList()) { + if (m_templateView->hasId(node.id()) && skipDuplicates) + continue; + ModelNode newNode = createReplacementNode(node, node); + outputNode.nodeListProperty(nodeListProperty.name()).reparentHere(newNode); + } + } +} + +void StylesheetMerger::syncVariantProperties(ModelNode &outputNode, const ModelNode &inputNode) +{ + foreach (const VariantProperty &variantProperty, inputNode.variantProperties()) { + outputNode.variantProperty(variantProperty.name()).setValue(variantProperty.value()); + } +} + +void StylesheetMerger::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()); +} + +void StylesheetMerger::syncBindingProperties(ModelNode &outputNode, const ModelNode &inputNode) +{ + foreach (const BindingProperty &bindingProperty, inputNode.bindingProperties()) { + outputNode.bindingProperty(bindingProperty.name()).setExpression(bindingProperty.expression()); + } +} + +void StylesheetMerger::syncId(ModelNode &outputNode, ModelNode &inputNode) +{ + if (!inputNode.id().isEmpty()) { + QString id = inputNode.id(); + QString renamedId = m_idReplacementHash.value(inputNode.id()); + inputNode.setIdWithoutRefactoring(renamedId); + outputNode.setIdWithoutRefactoring(id); + } +} + +void StylesheetMerger::setupIdRenamingHash() +{ + foreach (const ModelNode &node, m_templateView->rootModelNode().allSubModelNodesAndThisNode()) { + if (!node.id().isEmpty()) { + QString newId = node.id(); + QString baseId; + int number = 1; + splitIdInBaseNameAndNumber(newId, &baseId, &number); + + while (m_templateView->hasId(newId) || m_idReplacementHash.values().contains(newId)) { + newId = "stylesheet_auto_merge_" + baseId + QString::number(number); + number++; + } + + m_idReplacementHash.insert(node.id(), newId); + } + } +} + +ModelNode StylesheetMerger::createReplacementNode(const ModelNode& styleNode, ModelNode &modelNode) +{ + QList > propertyList; + QList > variantPropertyList; + foreach (const VariantProperty &variantProperty, modelNode.variantProperties()) { + propertyList.append(QPair(variantProperty.name(), variantProperty.value())); + } + NodeMetaInfo nodeMetaInfo = m_templateView->model()->metaInfo(styleNode.type()); + ModelNode newNode(m_templateView->createModelNode(styleNode.type(), nodeMetaInfo.majorVersion(), nodeMetaInfo.minorVersion(), + propertyList, variantPropertyList, styleNode.nodeSource(), styleNode.nodeSourceType())); + + syncAuxiliaryProperties(newNode, modelNode); + syncBindingProperties(newNode, modelNode); + syncId(newNode, modelNode); + syncNodeProperties(newNode, modelNode); + syncNodeListProperties(newNode, modelNode); + + return newNode; +} + +StylesheetMerger::StylesheetMerger(AbstractView *templateView, AbstractView *styleView) + : m_templateView(templateView) + , m_styleView(styleView) +{ +} + +bool StylesheetMerger::idExistsInBothModels(const QString& id) +{ + return m_templateView->hasId(id) && m_styleView->hasId(id); +} + +QPoint pointForModelNode(const ModelNode &node) +{ + int x = 0; + if (node.hasVariantProperty("x")) + x = node.variantProperty("x").value().toInt(); + + int y = 0; + if (node.hasVariantProperty("y")) + y = node.variantProperty("y").value().toInt(); + + return QPoint(x, y); +} + +QPoint parentPosition(const ModelNode &node) +{ + QPoint p; + + ModelNode currentNode = node; + while (currentNode.hasParentProperty()) { + currentNode = currentNode.parentProperty().parentModelNode(); + p += pointForModelNode(currentNode); + } + + return p; +} + +void StylesheetMerger::preprocessStyleSheet() +{ + for (ModelNode currentStyleNode : m_styleView->rootModelNode().directSubModelNodes()) { + QString id = currentStyleNode.id(); + + if (!idExistsInBothModels(id)) + continue; + + ModelNode templateNode = m_templateView->modelNodeForId(id); + NodeAbstractProperty templateParentProperty = templateNode.parentProperty(); + if (!templateNode.hasParentProperty() + || templateParentProperty.parentModelNode().isRootNode()) + continue; + + ModelNode templateParentNode = templateParentProperty.parentModelNode(); + const QString parentId = templateParentNode.id(); + if (!idExistsInBothModels(parentId)) + continue; + + // Only get the position properties as the node should have a global + // position in the style sheet. + const QPoint oldGlobalPos = pointForModelNode(currentStyleNode); + + ModelNode newStyleParent = m_styleView->modelNodeForId(parentId); + NodeListProperty newParentProperty = newStyleParent.defaultNodeListProperty(); + newParentProperty.reparentHere(currentStyleNode); + + // Get the parent position in global coordinates. + QPoint parentGlobalPos = parentPosition(currentStyleNode); + + const QPoint newGlobalPos = oldGlobalPos - parentGlobalPos; + + currentStyleNode.variantProperty("x").setValue(newGlobalPos.x()); + currentStyleNode.variantProperty("y").setValue(newGlobalPos.y()); + + int templateParentIndex = templateParentProperty.isNodeListProperty() + ? templateParentProperty.indexOf(templateNode) : -1; + int styleParentIndex = newParentProperty.indexOf(currentStyleNode); + if (templateParentIndex >= 0 && styleParentIndex != templateParentIndex) + newParentProperty.slide(styleParentIndex, templateParentIndex); + } +} + +void StylesheetMerger::replaceNode(ModelNode &replacedNode, ModelNode &newNode) +{ + NodeListProperty replacedNodeParent; + ModelNode parentModelNode = replacedNode.parentProperty().parentModelNode(); + if (replacedNode.parentProperty().isNodeListProperty()) + replacedNodeParent = replacedNode.parentProperty().toNodeListProperty(); + bool isNodeProperty = false; + + PropertyName reparentName; + for (NodeProperty prop : parentModelNode.nodeProperties()) { + if (prop.modelNode().id() == replacedNode.id()) { + isNodeProperty = true; + reparentName = prop.name(); + } + } + ReparentInfo info; + info.parentIndex = replacedNodeParent.isValid() ? replacedNodeParent.indexOf(replacedNode) : -1; + info.templateId = replacedNode.id(); + info.templateParentId = parentModelNode.id(); + info.generatedId = newNode.id(); + + if (!isNodeProperty) { + replacedNodeParent.reparentHere(newNode); + replacedNode.destroy(); + info.alreadyReparented = true; + } else { + parentModelNode.removeProperty(reparentName); + parentModelNode.nodeProperty(reparentName).reparentHere(newNode); + } + m_reparentInfoHash.insert(newNode.id(), info); +} + +void StylesheetMerger::replaceRootNode(ModelNode& templateRootNode) +{ + ModelMerger merger(m_templateView); + QString rootId = templateRootNode.id(); + // If we shall replace the root node of the template with the style, + // we first replace the whole model. + ModelNode rootReplacer = m_styleView->modelNodeForId(rootId); + merger.replaceModel(rootReplacer); + + // Then reset the id to the old root's one. + ModelNode newRoot = m_templateView->rootModelNode(); + newRoot.setIdWithoutRefactoring(rootId); +} + +// Move the newly created nodes to the correct position in the parent node +void StylesheetMerger::adjustNodeIndex(ModelNode &node) +{ + if (!m_reparentInfoHash.contains(node.id())) + return; + + ReparentInfo info = m_reparentInfoHash.value(node.id()); + if (info.parentIndex < 0) + return; + + if (!node.parentProperty().isNodeListProperty()) + return; + + NodeListProperty parentListProperty = node.parentProperty().toNodeListProperty(); + int currentIndex = parentListProperty.indexOf(node); + if (currentIndex == info.parentIndex) + return; + + parentListProperty.slide(currentIndex, info.parentIndex); +} + +void StylesheetMerger::applyStyleProperties(ModelNode &templateNode, const ModelNode &styleNode) +{ + QRegExp regEx("[a-z]", Qt::CaseInsensitive); + foreach (const VariantProperty &variantProperty, styleNode.variantProperties()) { + // check for existing bindings with that property name + if (templateNode.hasBindingProperty(variantProperty.name())) { + // if the binding does not contain any alpha letters (i.e. binds to a term rather than a property, + // replace it with the corresponding variant property. + if (!templateNode.bindingProperty(variantProperty.name()).expression().contains(regEx)) { + templateNode.removeProperty(variantProperty.name()); + templateNode.variantProperty(variantProperty.name()).setValue(variantProperty.value()); + } + } else { + templateNode.variantProperty(variantProperty.name()).setValue(variantProperty.value()); + } + } + syncBindingProperties(templateNode, styleNode); + syncNodeProperties(templateNode, styleNode, true); + syncNodeListProperties(templateNode, styleNode, true); +} + +static void removePropertyIfExists(ModelNode node, const PropertyName &propertyName) +{ + if (node.hasProperty(propertyName)) + node.removeProperty(propertyName); + +} + +void StylesheetMerger::merge() +{ + ModelNode templateRootNode = m_templateView->rootModelNode(); + ModelNode styleRootNode = m_styleView->rootModelNode(); + + // first, build up the hierarchy in the style sheet as we have it in the template + preprocessStyleSheet(); + + // build a hash of generated replacement ids + setupIdRenamingHash(); + + //in case we are replacing the root node, just do that and exit + if (m_styleView->hasId(templateRootNode.id())) { + replaceRootNode(templateRootNode); + return; + } + + QQueue replacementNodes; + + QList directRootSubNodes = styleRootNode.directSubModelNodes(); + if (directRootSubNodes.length() == 0 && m_templateView->hasId(styleRootNode.id())) { + // if the style sheet has only one node, just replace that one + replacementNodes.enqueue(styleRootNode); + } + // otherwise, the nodes to replace are the direct sub nodes of the style sheet's root + for (ModelNode subNode : styleRootNode.allSubModelNodes()) { + if (m_templateView->hasId(subNode.id())) { + replacementNodes.enqueue(subNode); + } + } + + for (ModelNode currentNode : replacementNodes) { + + bool hasPos = false; + + // create the replacement nodes for the styled nodes + { + RewriterTransaction transaction(m_templateView, "create-replacement-node"); + + ModelNode replacedNode = m_templateView->modelNodeForId(currentNode.id()); + hasPos = replacedNode.hasProperty("x") || replacedNode.hasProperty("y"); + + ModelNode replacementNode = createReplacementNode(currentNode, replacedNode); + + replaceNode(replacedNode, replacementNode); + transaction.commit(); + } + // sync the properties from the stylesheet + { + RewriterTransaction transaction(m_templateView, "sync-style-node-properties"); + ModelNode templateNode = m_templateView->modelNodeForId(currentNode.id()); + applyStyleProperties(templateNode, currentNode); + adjustNodeIndex(templateNode); + + /* This we want to do if the parent node in the style is not in the template */ + if (!currentNode.hasParentProperty() || + !m_templateView->modelNodeForId(currentNode.parentProperty().parentModelNode().id()).isValid()) { + + if (!hasPos) { //If template had postition retain it + removePropertyIfExists(templateNode, "x"); + removePropertyIfExists(templateNode, "y"); + } + if (templateNode.hasProperty("anchors.fill")) { + /* Unfortuntly there are cases were width and height have to be defined - see Button + * Most likely we need options for this */ + //removePropertyIfExists(templateNode, "width"); + //removePropertyIfExists(templateNode, "height"); + } + } + + } + } +} +} diff --git a/src/plugins/qmldesigner/qmldesignerplugin.qbs b/src/plugins/qmldesigner/qmldesignerplugin.qbs index 810eb4d62c5..2e695b90e13 100644 --- a/src/plugins/qmldesigner/qmldesignerplugin.qbs +++ b/src/plugins/qmldesigner/qmldesignerplugin.qbs @@ -297,6 +297,7 @@ Project { "include/rewriterview.h", "include/rewritingexception.h", "include/signalhandlerproperty.h", + "include/stylesheetmerger.h", "include/viewmanager.h", "include/subcomponentmanager.h", "include/textmodifier.h", @@ -378,6 +379,7 @@ Project { "model/documentmessage.cpp", "model/rewriterview.cpp", "model/signalhandlerproperty.cpp", + "model/stylesheetmerger.cpp", "model/textmodifier.cpp", "model/texttomodelmerger.cpp", "model/texttomodelmerger.h", diff --git a/tests/auto/qml/qmldesigner/coretests/coretests.pro b/tests/auto/qml/qmldesigner/coretests/coretests.pro index 8a86681e62c..dd0f071b0e9 100644 --- a/tests/auto/qml/qmldesigner/coretests/coretests.pro +++ b/tests/auto/qml/qmldesigner/coretests/coretests.pro @@ -61,8 +61,10 @@ SOURCES += \ ../testview.cpp \ testrewriterview.cpp \ tst_testcore.cpp + HEADERS += \ ../testview.h \ testrewriterview.h \ tst_testcore.h + RESOURCES += ../data/testfiles.qrc diff --git a/tests/auto/qml/qmldesigner/coretests/tst_testcore.cpp b/tests/auto/qml/qmldesigner/coretests/tst_testcore.cpp index 266dc92508f..cb47a5f2fd4 100644 --- a/tests/auto/qml/qmldesigner/coretests/tst_testcore.cpp +++ b/tests/auto/qml/qmldesigner/coretests/tst_testcore.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include @@ -42,6 +43,7 @@ #include #include #include +#include #include #include "../testview.h" @@ -3704,6 +3706,131 @@ void tst_TestCore::testCopyModelRewriter1() QCOMPARE(textEdit1.toPlainText(), expected); } +static void adjustPreservedProperties(const ModelNode& replacedNode, ModelNode& newNode) { + QSet preservedProperties; + preservedProperties.insert("x"); + preservedProperties.insert("y"); + preservedProperties.insert("width"); + preservedProperties.insert("height"); + preservedProperties.insert("anchors"); + // preservedProperties.insert("text "); + + for (VariantProperty originalProperty : replacedNode.variantProperties()) { + VariantProperty prop; + if (preservedProperties.contains(originalProperty.name())) { + newNode.variantProperty(originalProperty.name()).setValue(originalProperty.value()); + } + } +} + +static QString readQmlFromFile(const QString& fileName) +{ + QFile qmlFile(fileName); + qmlFile.open(QIODevice::ReadOnly | QIODevice::Text); + QString qmlFileContents = QString::fromUtf8(qmlFile.readAll()); + return qmlFileContents; +} + +void tst_TestCore::testMergeModelRewriter1_data() +{ + QTest::addColumn("qmlTemplateString"); + QTest::addColumn("qmlStyleString"); + QTest::addColumn("qmlExpectedString"); + + QString simpleTemplateQmlContents = readQmlFromFile(QString(TESTSRCDIR) + "/../data/merging/SimpleTemplate.qml"); + QString simpleStyleQmlContents = readQmlFromFile(QString(TESTSRCDIR) + "/../data/merging/SimpleStyle.qml"); + QString simpleExpectedQmlContents = readQmlFromFile(QString(TESTSRCDIR) + "/../data/merging/SimpleExpected.qml"); + + QString complexTemplateQmlContents = readQmlFromFile(QString(TESTSRCDIR) + "/../data/merging/ComplexTemplate.qml"); + QString complexStyleQmlContents = readQmlFromFile(QString(TESTSRCDIR) + "/../data/merging/ComplexStyle.qml"); + QString complexExpectedQmlContents = readQmlFromFile(QString(TESTSRCDIR) + "/../data/merging/ComplexExpected.qml"); + + QString emptyTemplateQmlContents = readQmlFromFile(QString(TESTSRCDIR) + "/../data/merging/EmptyTemplate.qml"); + QString emptyStyleQmlContents = readQmlFromFile(QString(TESTSRCDIR) + "/../data/merging/EmptyStyle.qml"); + QString emptyExpectedQmlContents = readQmlFromFile(QString(TESTSRCDIR) + "/../data/merging/EmptyExpected.qml"); + + QString rootReplacementTemplateQmlContents = readQmlFromFile(QString(TESTSRCDIR) + "/../data/merging/RootReplacementTemplate.qml"); + QString rootReplacementStyleQmlContents = readQmlFromFile(QString(TESTSRCDIR) + "/../data/merging/RootReplacementStyle.qml"); + QString rootReplacementExpectedQmlContents = readQmlFromFile(QString(TESTSRCDIR) + "/../data/merging/RootReplacementExpected.qml"); + + QString switchTemplateQmlContents = readQmlFromFile(QString(TESTSRCDIR) + "/../data/merging/SwitchTemplate.qml"); + QString switchStyleQmlContents = readQmlFromFile(QString(TESTSRCDIR) + "/../data/merging/SwitchStyle.qml"); + QString switchExpectedQmlContents = readQmlFromFile(QString(TESTSRCDIR) + "/../data/merging/SwitchExpected.qml"); + + QString sliderTemplateQmlContents = readQmlFromFile(QString(TESTSRCDIR) + "/../data/merging/SliderTemplate.qml"); + QString sliderStyleQmlContents = readQmlFromFile(QString(TESTSRCDIR) + "/../data/merging/SliderStyle.qml"); + QString sliderExpectedQmlContents = readQmlFromFile(QString(TESTSRCDIR) + "/../data/merging/SliderExpected.qml"); + + QString listViewTemplateQmlContents = readQmlFromFile(QString(TESTSRCDIR) + "/../data/merging/ListViewTemplate.qml"); + QString listViewStyleQmlContents = readQmlFromFile(QString(TESTSRCDIR) + "/../data/merging/ListViewStyle.qml"); + QString listViewExpectedQmlContents = readQmlFromFile(QString(TESTSRCDIR) + "/../data/merging/ListViewExpected.qml"); + + QString buttonTemplateQmlContents = readQmlFromFile(QString(TESTSRCDIR) + "/../data/merging/ButtonTemplate.qml"); + QString buttonInlineStyleQmlContents = readQmlFromFile(QString(TESTSRCDIR) + "/../data/merging/ButtonStyleInline.qml"); + QString buttonInlineExpectedQmlContents = readQmlFromFile(QString(TESTSRCDIR) + "/../data/merging/ButtonInlineExpected.qml"); + + QString buttonAbsoluteTemplateQmlContents = readQmlFromFile(QString(TESTSRCDIR) + "/../data/merging/ButtonAbsoluteTemplate.qml"); + QString buttonOutlineStyleQmlContents = readQmlFromFile(QString(TESTSRCDIR) + "/../data/merging/ButtonStyleOutline.qml"); + QString buttonOutlineExpectedQmlContents = readQmlFromFile(QString(TESTSRCDIR) + "/../data/merging/ButtonOutlineExpected.qml"); + + QTest::newRow("Simple style replacement") << simpleTemplateQmlContents << simpleStyleQmlContents << simpleExpectedQmlContents; + //QTest::newRow("Complex style replacement") << complexTemplateQmlContents << complexStyleQmlContents << complexExpectedQmlContents; + QTest::newRow("Empty stylesheet") << emptyTemplateQmlContents << emptyStyleQmlContents << emptyExpectedQmlContents; + QTest::newRow("Root node replacement") << rootReplacementTemplateQmlContents << rootReplacementStyleQmlContents << rootReplacementExpectedQmlContents; + QTest::newRow("Switch styling") << switchTemplateQmlContents << switchStyleQmlContents << switchExpectedQmlContents; + QTest::newRow("Slider styling") << sliderTemplateQmlContents << sliderStyleQmlContents << sliderExpectedQmlContents; + QTest::newRow("List View styling") << listViewTemplateQmlContents << listViewStyleQmlContents << listViewExpectedQmlContents; + QTest::newRow("Button Inline styling") << buttonTemplateQmlContents << buttonInlineStyleQmlContents << buttonInlineExpectedQmlContents; + + QTest::newRow("Button Outline styling") << buttonAbsoluteTemplateQmlContents << buttonOutlineStyleQmlContents << buttonOutlineExpectedQmlContents; + +} + +void tst_TestCore::testMergeModelRewriter1() +{ + QFETCH(QString, qmlTemplateString); + QFETCH(QString, qmlStyleString); + QFETCH(QString, qmlExpectedString); + + QPlainTextEdit textEdit1; + textEdit1.setPlainText(qmlTemplateString); + NotIndentingTextEditModifier textModifier1(&textEdit1); + + QScopedPointer templateModel(Model::create("QtQuick.Item", 2, 1)); + QVERIFY(templateModel.data()); + + QScopedPointer templateView(new TestView(templateModel.data())); + templateModel->attachView(templateView.data()); + + // read in 1 + QScopedPointer templateRewriterView(new TestRewriterView()); + templateRewriterView->setTextModifier(&textModifier1); + templateModel->attachView(templateRewriterView.data()); + + ModelNode templateRootNode = templateView->rootModelNode(); + QVERIFY(templateRootNode.isValid()); + + QPlainTextEdit textEdit2; + textEdit2.setPlainText(qmlStyleString); + NotIndentingTextEditModifier textModifier2(&textEdit2); + + QScopedPointer styleModel(Model::create("QtQuick.Item", 2, 1)); + QVERIFY(styleModel.data()); + + QScopedPointer styleView(new TestView(styleModel.data())); + styleModel->attachView(styleView.data()); + + // read in 2 + QScopedPointer styleRewriterView(new TestRewriterView()); + styleRewriterView->setTextModifier(&textModifier2); + styleModel->attachView(styleRewriterView.data()); + + StylesheetMerger merger(templateView.data(), styleView.data()); + merger.merge(); + + QCOMPARE(textEdit1.toPlainText().trimmed(), qmlExpectedString.trimmed()); +} + void tst_TestCore::testCopyModelRewriter2() { const QLatin1String qmlString1("\n" @@ -3756,37 +3883,35 @@ void tst_TestCore::testCopyModelRewriter2() textEdit1.setPlainText(qmlString1); NotIndentingTextEditModifier textModifier1(&textEdit1); - QScopedPointer model1(Model::create("QtQuick.Item", 2, 1)); - QVERIFY(model1.data()); + QScopedPointer templateModel(Model::create("QtQuick.Item", 2, 1)); + QVERIFY(templateModel.data()); - QScopedPointer view1(new TestView(model1.data())); - model1->attachView(view1.data()); + QScopedPointer view1(new TestView(templateModel.data())); + templateModel->attachView(view1.data()); // read in 1 QScopedPointer testRewriterView1(new TestRewriterView()); testRewriterView1->setTextModifier(&textModifier1); - model1->attachView(testRewriterView1.data()); + templateModel->attachView(testRewriterView1.data()); ModelNode rootNode1 = view1->rootModelNode(); QVERIFY(rootNode1.isValid()); QCOMPARE(rootNode1.type(), QmlDesigner::TypeName("QtQuick.Rectangle")); - - // read in 2 - + // read in 2 QPlainTextEdit textEdit2; textEdit2.setPlainText(qmlString2); NotIndentingTextEditModifier textModifier2(&textEdit2); - QScopedPointer model2(Model::create("QtQuick.Item", 2, 1)); - QVERIFY(model2.data()); + QScopedPointer styleModel(Model::create("QtQuick.Item", 2, 1)); + QVERIFY(styleModel.data()); - QScopedPointer view2(new TestView(model2.data())); - model2->attachView(view2.data()); + QScopedPointer view2(new TestView(styleModel.data())); + styleModel->attachView(view2.data()); - QScopedPointer testRewriterView2(new TestRewriterView()); - testRewriterView2->setTextModifier(&textModifier2); - model2->attachView(testRewriterView2.data()); + QScopedPointer styleRewriterView(new TestRewriterView()); + styleRewriterView->setTextModifier(&textModifier2); + styleModel->attachView(styleRewriterView.data()); ModelNode rootNode2 = view2->rootModelNode(); QVERIFY(rootNode2.isValid()); diff --git a/tests/auto/qml/qmldesigner/coretests/tst_testcore.h b/tests/auto/qml/qmldesigner/coretests/tst_testcore.h index 92f3b171618..92d4ac00aa9 100644 --- a/tests/auto/qml/qmldesigner/coretests/tst_testcore.h +++ b/tests/auto/qml/qmldesigner/coretests/tst_testcore.h @@ -184,6 +184,8 @@ private slots: void testRewriterTransactionRewriter(); void testCopyModelRewriter1(); void testCopyModelRewriter2(); + void testMergeModelRewriter1_data(); + void testMergeModelRewriter1(); void testSubComponentManager(); void testAnchorsAndRewriting(); void testAnchorsAndRewritingCenter(); diff --git a/tests/auto/qml/qmldesigner/data/merging/ButtonAbsoluteTemplate.qml b/tests/auto/qml/qmldesigner/data/merging/ButtonAbsoluteTemplate.qml new file mode 100644 index 00000000000..88d66014ac3 --- /dev/null +++ b/tests/auto/qml/qmldesigner/data/merging/ButtonAbsoluteTemplate.qml @@ -0,0 +1,104 @@ +import QtQuick 2.10 +import QtQuick.Templates 2.1 as T + +T.Button { + id: control + + implicitWidth: Math.max( + background ? background.implicitWidth : 0, + contentItem.implicitWidth + leftPadding + rightPadding) + implicitHeight: Math.max( + background ? background.implicitHeight : 0, + contentItem.implicitHeight + topPadding + bottomPadding) + leftPadding: 4 + rightPadding: 4 + + text: "My Button" + + background: Item { + implicitWidth: buttonNormal.width + implicitHeight: buttonNormal.height + opacity: enabled ? 1 : 0.3 + + Rectangle { + + id: buttonNormal + color: "#d4d4d4" + width: 100 //Bit of black magic to define the default size + height: 40 + + border.color: "gray" + border.width: 1 + radius: 2 + anchors.fill: parent //binding has to be preserved + + Text { + id: normalText + x: 26 + y: 14 //id only required to preserve binding + text: control.text //binding has to be preserved + //anchors.fill: parent + color: "gray" + + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + } + } + + Rectangle { + id: buttonPressed + color: "#d4d4d4" + width: 100 //Bit of black magic to define the default size + height: 40 + + border.color: "gray" + border.width: 1 + radius: 2 + anchors.fill: parent //binding has to be preserved + + Text { + x: 26 + y: 14 + id: pressedText //id only required to preserve binding + text: control.text //binding has to be preserved + //anchors.fill: parent + color: "black" + + horizontalAlignment: Text.AlignHCenter // should not be preserved + verticalAlignment: Text.AlignVCenter // should not be preserved + elide: Text.ElideRight // should not be preserved + } + } + } + + contentItem: Item {} + + states: [ + State { + name: "normal" + when: !control.down + PropertyChanges { + target: buttonPressed + visible: false + } + PropertyChanges { + target: buttonNormal + visible: true + } + }, + State { + name: "down" + when: control.down + PropertyChanges { + target: buttonPressed + visible: true + } + PropertyChanges { + target: buttonNormal + visible: false + } + } + ] +} + diff --git a/tests/auto/qml/qmldesigner/data/merging/ButtonInlineExpected.qml b/tests/auto/qml/qmldesigner/data/merging/ButtonInlineExpected.qml new file mode 100644 index 00000000000..de131d2e648 --- /dev/null +++ b/tests/auto/qml/qmldesigner/data/merging/ButtonInlineExpected.qml @@ -0,0 +1,93 @@ +import QtQuick 2.10 +import QtQuick.Templates 2.1 as T + +T.Button { + id: control + + implicitWidth: Math.max( + background ? background.implicitWidth : 0, + contentItem.implicitWidth + leftPadding + rightPadding) + implicitHeight: Math.max( + background ? background.implicitHeight : 0, + contentItem.implicitHeight + topPadding + bottomPadding) + leftPadding: 4 + rightPadding: 4 + + text: "My Button" + + background: Item { + implicitWidth: buttonNormal.width + implicitHeight: buttonNormal.height + opacity: enabled ? 1 : 0.3 + + Rectangle { + id: buttonNormal + width: 100 + height: 60 + color: "#d4d4d4" + radius: 2 + border.color: "#808080" + border.width: 1 + anchors.fill: parent + Text { + id: normalText + color: "#808080" + text: control.text + elide: Text.ElideRight + anchors.fill: parent + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + } + + Rectangle { + id: buttonPressed + width: 100 + height: 60 + color: "#69b5ec" + radius: 2 + border.color: "#808080" + border.width: 1 + anchors.fill: parent + Text { + id: pressedText + color: "#000000" + text: control.text + elide: Text.ElideRight + anchors.fill: parent + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + } + + } + + contentItem: Item {} + + states: [ + State { + name: "normal" + when: !control.down + PropertyChanges { + target: buttonPressed + visible: false + } + PropertyChanges { + target: buttonNormal + visible: true + } + }, + State { + name: "down" + when: control.down + PropertyChanges { + target: buttonPressed + visible: true + } + PropertyChanges { + target: buttonNormal + visible: false + } + } + ] +} \ No newline at end of file diff --git a/tests/auto/qml/qmldesigner/data/merging/ButtonOutlineExpected.qml b/tests/auto/qml/qmldesigner/data/merging/ButtonOutlineExpected.qml new file mode 100644 index 00000000000..ddbdd780961 --- /dev/null +++ b/tests/auto/qml/qmldesigner/data/merging/ButtonOutlineExpected.qml @@ -0,0 +1,97 @@ +import QtQuick 2.10 +import QtQuick.Templates 2.1 as T + +T.Button { + id: control + + implicitWidth: Math.max( + background ? background.implicitWidth : 0, + contentItem.implicitWidth + leftPadding + rightPadding) + implicitHeight: Math.max( + background ? background.implicitHeight : 0, + contentItem.implicitHeight + topPadding + bottomPadding) + leftPadding: 4 + rightPadding: 4 + + text: "My Button" + + background: Item { + implicitWidth: buttonNormal.width + implicitHeight: buttonNormal.height + opacity: enabled ? 1 : 0.3 + + Rectangle { + id: buttonNormal + width: 100 + height: 60 + color: "#d4d4d4" + radius: 2 + border.color: "#808080" + border.width: 1 + anchors.fill: parent + Text { + id: normalText + x: 20 + y: 20 + color: "#808080" + text: control.text + elide: Text.ElideRight + anchors.fill: parent + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + } + + Rectangle { + id: buttonPressed + width: 100 + height: 60 + color: "#69b5ec" + radius: 2 + border.color: "#808080" + border.width: 1 + anchors.fill: parent + Text { + id: pressedText + x: 20 + y: 40 + color: "#000000" + text: control.text + elide: Text.ElideRight + anchors.fill: parent + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + } + + } + + contentItem: Item {} + + states: [ + State { + name: "normal" + when: !control.down + PropertyChanges { + target: buttonPressed + visible: false + } + PropertyChanges { + target: buttonNormal + visible: true + } + }, + State { + name: "down" + when: control.down + PropertyChanges { + target: buttonPressed + visible: true + } + PropertyChanges { + target: buttonNormal + visible: false + } + } + ] +} \ No newline at end of file diff --git a/tests/auto/qml/qmldesigner/data/merging/ButtonStyleInline.qml b/tests/auto/qml/qmldesigner/data/merging/ButtonStyleInline.qml new file mode 100644 index 00000000000..f33450bf7c7 --- /dev/null +++ b/tests/auto/qml/qmldesigner/data/merging/ButtonStyleInline.qml @@ -0,0 +1,79 @@ +import QtQuick 2.12 + + +Rectangle { + id: artboard + width: 640 + height: 480 + color: "#ee4040" + + Rectangle { + + id: buttonNormal + x: 286 + y: 62 + color: "#d4d4d4" + width: 100 //Bit of black magic to define the default size + height: 60 + + border.color: "gray" + border.width: 1 + radius: 2 + + Text { + id: normalText //id only required to preserve binding + //binding has to be preserved + anchors.fill: parent //binding has to be preserved + color: "gray" + text: "Normal" + + horizontalAlignment: Text.AlignHCenter + //luckily enums are interpreted as variant properties and not bindings + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + } + } + + Rectangle { + id: buttonPressed + x: 123 + y: 62 + color: "#69b5ec" + width: 100 //Bit of black magic to define the default size + height: 60 + + border.color: "gray" + border.width: 1 + radius: 2 + + Text { + id: pressedText //id only required to preserve binding + //binding has to be preserved + anchors.fill: parent + color: "black" + text: "pressed" + + horizontalAlignment: Text.AlignHCenter // should not be preserved - + //luckily enums are interpreted as variant properties and not bindings + verticalAlignment: Text.AlignVCenter // should not be preserved + elide: Text.ElideRight // should not be preserved + } + } + + Text { + id: element + x: 1 + y: 362 + color: "#eaeaea" + text: qsTrId("Some stuff for reference that is thrown away") + font.pixelSize: 32 + } + + +} + +/*##^## +Designer { + D{i:0;formeditorColor:"#000000"} +} +##^##*/ diff --git a/tests/auto/qml/qmldesigner/data/merging/ButtonStyleOutline.qml b/tests/auto/qml/qmldesigner/data/merging/ButtonStyleOutline.qml new file mode 100644 index 00000000000..a9a7021383b --- /dev/null +++ b/tests/auto/qml/qmldesigner/data/merging/ButtonStyleOutline.qml @@ -0,0 +1,83 @@ +import QtQuick 2.12 + + +Rectangle { + id: artboard + width: 640 + height: 480 + color: "#ee4040" + + Rectangle { + + id: buttonNormal + x: 286 + y: 62 + color: "#d4d4d4" + width: 100 //Bit of black magic to define the default size + height: 60 + + border.color: "gray" + border.width: 1 + radius: 2 + } + + Text { + x: 319 + y: 86 + id: normalText //id only required to preserve binding + //binding has to be preserved + + color: "gray" + text: "Normal" + + horizontalAlignment: Text.AlignHCenter + //luckily enums are interpreted as variant properties and not bindings + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + } + + Rectangle { + id: buttonPressed + x: 123 + y: 62 + color: "#69b5ec" + width: 100 //Bit of black magic to define the default size + height: 60 + + border.color: "gray" + border.width: 1 + radius: 2 + } + + Text { + x: 154 + y: 86 + id: pressedText //id only required to preserve binding + //binding has to be preserved + //anchors.fill: parent + color: "black" + text: "pressed" + + horizontalAlignment: Text.AlignHCenter // should not be preserved - + //luckily enums are interpreted as variant properties and not bindings + verticalAlignment: Text.AlignVCenter // should not be preserved + elide: Text.ElideRight // should not be preserved + } + + Text { + id: element + x: 1 + y: 362 + color: "#eaeaea" + text: qsTrId("Some stuff for reference that is thrown away") + font.pixelSize: 32 + } + + +} + +/*##^## +Designer { + D{i:0;formeditorColor:"#000000"} +} +##^##*/ diff --git a/tests/auto/qml/qmldesigner/data/merging/ButtonTemplate.qml b/tests/auto/qml/qmldesigner/data/merging/ButtonTemplate.qml new file mode 100644 index 00000000000..cd4b376a578 --- /dev/null +++ b/tests/auto/qml/qmldesigner/data/merging/ButtonTemplate.qml @@ -0,0 +1,100 @@ +import QtQuick 2.10 +import QtQuick.Templates 2.1 as T + +T.Button { + id: control + + implicitWidth: Math.max( + background ? background.implicitWidth : 0, + contentItem.implicitWidth + leftPadding + rightPadding) + implicitHeight: Math.max( + background ? background.implicitHeight : 0, + contentItem.implicitHeight + topPadding + bottomPadding) + leftPadding: 4 + rightPadding: 4 + + text: "My Button" + + background: Item { + implicitWidth: buttonNormal.width + implicitHeight: buttonNormal.height + opacity: enabled ? 1 : 0.3 + + Rectangle { + + id: buttonNormal + color: "#d4d4d4" + width: 100 //Bit of black magic to define the default size + height: 40 + + border.color: "gray" + border.width: 1 + radius: 2 + anchors.fill: parent //binding has to be preserved + + Text { + id: normalText //id only required to preserve binding + text: control.text //binding has to be preserved + anchors.fill: parent + color: "gray" + + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + } + } + + Rectangle { + id: buttonPressed + color: "#d4d4d4" + width: 100 //Bit of black magic to define the default size + height: 40 + + border.color: "gray" + border.width: 1 + radius: 2 + anchors.fill: parent //binding has to be preserved + + Text { + id: pressedText //id only required to preserve binding + text: control.text //binding has to be preserved + anchors.fill: parent + color: "black" + + horizontalAlignment: Text.AlignHCenter // should not be preserved + verticalAlignment: Text.AlignVCenter // should not be preserved + elide: Text.ElideRight // should not be preserved + } + } + } + + contentItem: Item {} + + states: [ + State { + name: "normal" + when: !control.down + PropertyChanges { + target: buttonPressed + visible: false + } + PropertyChanges { + target: buttonNormal + visible: true + } + }, + State { + name: "down" + when: control.down + PropertyChanges { + target: buttonPressed + visible: true + } + PropertyChanges { + target: buttonNormal + visible: false + } + } + ] +} + diff --git a/tests/auto/qml/qmldesigner/data/merging/ComplexExpected.qml b/tests/auto/qml/qmldesigner/data/merging/ComplexExpected.qml new file mode 100644 index 00000000000..eb0ca78fad5 --- /dev/null +++ b/tests/auto/qml/qmldesigner/data/merging/ComplexExpected.qml @@ -0,0 +1,62 @@ +import QtQuick 2.1 + +Rectangle { + id: root + x: 10; + y: 10; + Rectangle { + id: rectangle0 + x: 10 + y: 10 + width: 200 + height: 150 + + Image { + id: rectangle1 + x: 10 + y: 10 + width: 100 + height: 150 + source: "qt/icon.png" + } + } + + Rectangle { + id: rectangle2 + x: 100; + y: 100; + anchors.fill: root + } + + Rectangle { + id: rectangle3 + x: 140; + y: 180; + gradient: Gradient { + GradientStop { + position: 0 + color: "white" + } + GradientStop { + position: 1 + color: "black" + } + } + Image { + id: rectangle4 + x: 10 + y: 20 + width: 200 + height: 50 + Rectangle { + id: rectangle5 + x: 10 + y: 20 + width: 200 + height: 50 + } + source: "qt/realcool.jpg" + } + } + +} diff --git a/tests/auto/qml/qmldesigner/data/merging/ComplexStyle.qml b/tests/auto/qml/qmldesigner/data/merging/ComplexStyle.qml new file mode 100644 index 00000000000..9bf50d8f0fc --- /dev/null +++ b/tests/auto/qml/qmldesigner/data/merging/ComplexStyle.qml @@ -0,0 +1,45 @@ +import QtQuick 2.1 + +Item { + Rectangle { + id: rectangle0 + Image { + id: rectangle1 + x: 10 + y: 10 + height: 150 + width: 100 + source: "qt/icon.png" + } + } + Rectangle { + id: rectangle3 + x: 140; + y: 180; + gradient: Gradient { + GradientStop { + position: 0 + color: "white" + } + GradientStop { + position: 1 + color: "black" + } + } + } + Rectangle { + id: rectangle5 + x: 10 + y: 20 + width: 200 + height: 50 + } + Image { + id: rectangle4 + x: 10; + y: 10; + height: 150 + width: 100 + source: "qt/realcool.jpg" + } +} diff --git a/tests/auto/qml/qmldesigner/data/merging/ComplexTemplate.qml b/tests/auto/qml/qmldesigner/data/merging/ComplexTemplate.qml new file mode 100644 index 00000000000..058b9698238 --- /dev/null +++ b/tests/auto/qml/qmldesigner/data/merging/ComplexTemplate.qml @@ -0,0 +1,49 @@ +import QtQuick 2.1 + +Rectangle { + id: root + x: 10; + y: 10; + Rectangle { + id: rectangle0 + x: 10; + y: 10; + height: 150 + width: 200 + } + Rectangle { + id: rectangle2 + x: 100; + y: 100; + anchors.fill: root + } + Rectangle { + id: rectangle3 + x: 140; + y: 180; + gradient: Gradient { + GradientStop { + position: 0 + color: "black" + } + GradientStop { + position: 1 + color: "white" + } + } + Rectangle { + id: rectangle4 + x: 10 + y: 20 + width: 200 + height: 50 + Rectangle { + id: rectangle5 + x: 10 + y: 20 + width: 200 + height: 50 + } + } + } +} diff --git a/tests/auto/qml/qmldesigner/data/merging/EmptyStyleExpected.qml b/tests/auto/qml/qmldesigner/data/merging/EmptyStyleExpected.qml new file mode 100644 index 00000000000..122ccd87671 --- /dev/null +++ b/tests/auto/qml/qmldesigner/data/merging/EmptyStyleExpected.qml @@ -0,0 +1,43 @@ +// Test that an empty style sheet will leave the original template untouched. +import QtQuick 2.1 + +Rectangle { + id: root + x: 10; + y: 10; + Rectangle { + id: rectangle0 + x: 10; + y: 10; + height: 150 + width: 200 + } + Rectangle { + id: rectangle2 + x: 100; + y: 100; + anchors.fill: root + } + Rectangle { + id: rectangle3 + x: 140; + y: 180; + gradient: Gradient { + GradientStop { + position: 0 + color: "white" + } + GradientStop { + position: 1 + color: "black" + } + } + Rectangle { + id: rectangle4 + x: 10 + y: 20 + width: 200 + height: 50 + } + } +} diff --git a/tests/auto/qml/qmldesigner/data/merging/EmptyStyleStyle.qml b/tests/auto/qml/qmldesigner/data/merging/EmptyStyleStyle.qml new file mode 100644 index 00000000000..8040be26976 --- /dev/null +++ b/tests/auto/qml/qmldesigner/data/merging/EmptyStyleStyle.qml @@ -0,0 +1 @@ +import QtQuick 2.1 diff --git a/tests/auto/qml/qmldesigner/data/merging/EmptyStyleTemplate.qml b/tests/auto/qml/qmldesigner/data/merging/EmptyStyleTemplate.qml new file mode 100644 index 00000000000..122ccd87671 --- /dev/null +++ b/tests/auto/qml/qmldesigner/data/merging/EmptyStyleTemplate.qml @@ -0,0 +1,43 @@ +// Test that an empty style sheet will leave the original template untouched. +import QtQuick 2.1 + +Rectangle { + id: root + x: 10; + y: 10; + Rectangle { + id: rectangle0 + x: 10; + y: 10; + height: 150 + width: 200 + } + Rectangle { + id: rectangle2 + x: 100; + y: 100; + anchors.fill: root + } + Rectangle { + id: rectangle3 + x: 140; + y: 180; + gradient: Gradient { + GradientStop { + position: 0 + color: "white" + } + GradientStop { + position: 1 + color: "black" + } + } + Rectangle { + id: rectangle4 + x: 10 + y: 20 + width: 200 + height: 50 + } + } +} diff --git a/tests/auto/qml/qmldesigner/data/merging/ListViewExpected.qml b/tests/auto/qml/qmldesigner/data/merging/ListViewExpected.qml new file mode 100644 index 00000000000..a8da56b1dff --- /dev/null +++ b/tests/auto/qml/qmldesigner/data/merging/ListViewExpected.qml @@ -0,0 +1,135 @@ +import QtQuick 2.10 + +ListView { + id: view + width: listViewBackground.width + height: listViewBackground.height + highlight: Rectangle { + id: listViewHighLight + width: view.width + height: 120 + color: "#343434" + radius: 4 + border.color: "#0d52a4" + border.width: 8 + } + + highlightMoveDuration: 0 + + children: [ + Item { + z: -1 + anchors.fill: parent + + Rectangle { + id: listViewBackground + width: 420 + height: 420 + color: "#69b5ec" + anchors.fill: parent + } + } + ] + + model: ListModel { + ListElement { + name: "Music" + } + ListElement { + name: "Movies" + } + ListElement { + name: "Camera" + } + ListElement { + name: "Map" + } + ListElement { + name: "Calendar" + } + ListElement { + name: "Messaging" + } + ListElement { + name: "Todo List" + } + ListElement { + name: "Contacts" + } + ListElement { + name: "Settings" + } + } + + + delegate: Item { + id: delegate + width: ListView.view.width + height: delegateNormal.height + + Rectangle { + id: delegateNormal + width: 420 + height: 120 + visible: true + color: "#bdbdbd" + radius: 4 + anchors.fill: parent + anchors.margins: 12 + Text { + id: labelNormal + color: "#343434" + text: name + anchors.top: parent.top + anchors.margins: 24 + anchors.horizontalCenter: parent.horizontalCenter + } + } + + + Rectangle { + id: delegateHighlighted + width: 420 + height: 120 + visible: false + color: "#8125eb29" + radius: 4 + anchors.fill: parent + anchors.margins: 12 + Text { + id: labelHighlighted + color: "#efefef" + text: name + anchors.top: parent.top + anchors.margins: 52 + anchors.horizontalCenter: parent.horizontalCenter + } + } + + MouseArea { + anchors.fill: parent + onClicked: delegate.ListView.view.currentIndex = index + } + + + states: [ + State { + name: "Highlighted" + + when: delegate.ListView.isCurrentItem + PropertyChanges { + target: delegateHighlighted + visible: true + } + + PropertyChanges { + target: delegateNormal + visible: false + } + + + } + ] + } + +} \ No newline at end of file diff --git a/tests/auto/qml/qmldesigner/data/merging/ListViewStyle.qml b/tests/auto/qml/qmldesigner/data/merging/ListViewStyle.qml new file mode 100644 index 00000000000..4e68326f0a7 --- /dev/null +++ b/tests/auto/qml/qmldesigner/data/merging/ListViewStyle.qml @@ -0,0 +1,82 @@ +import QtQuick 2.12 + + +Rectangle { + id: artboard + width: 800 + height: 600 + color: "#ee4040" + + Rectangle { + id: listViewBackground + x: 19 + y: 34 + width: 420 + height: 420 + + color: "#69b5ec" + + } + + Rectangle { + id: delegateNormal + x: 19 + y: 51 + color: "#bdbdbd" + + height: 120 + + width: 420 + + radius: 4 + Text { + id: labelNormal //id required for binding preservation + color: "#343434" + text: "some text" + anchors.top: parent.top + anchors.horizontalCenter: parent.horizontalCenter + + anchors.margins: 24 + } + } + + + Rectangle { + id: delegateHighlighted + x: 19 + y: 177 + color: "#8125eb29" + height: 120 + + width: 420 + + radius: 4 + Text { + id: labelHighlighted //id required for binding preservation + color: "#efefef" + text: "some text" + anchors.top: parent.top + anchors.horizontalCenter: parent.horizontalCenter + + anchors.margins: 52 + } + } + + + Rectangle { + id: listViewHighLight + x: 19 + y: 323 + width: 420 + height: 120 + color: "#343434" + radius: 4 + border.color: "#0d52a4" + border.width: 8 + } + + + +} + + diff --git a/tests/auto/qml/qmldesigner/data/merging/ListViewTemplate.qml b/tests/auto/qml/qmldesigner/data/merging/ListViewTemplate.qml new file mode 100644 index 00000000000..6c59ca4d395 --- /dev/null +++ b/tests/auto/qml/qmldesigner/data/merging/ListViewTemplate.qml @@ -0,0 +1,133 @@ +import QtQuick 2.10 + +ListView { + id: view + width: listViewBackground.width + height: listViewBackground.height + + highlightMoveDuration: 0 + + children: [ + Item { + z: -1 + anchors.fill: parent + + Rectangle { + id: listViewBackground + width: 420 + height: 420 + + color: "#d80e0e" + anchors.fill: parent // hsa to be preserved + } + } + ] + + model: ListModel { + ListElement { + name: "Music" + } + ListElement { + name: "Movies" + } + ListElement { + name: "Camera" + } + ListElement { + name: "Map" + } + ListElement { + name: "Calendar" + } + ListElement { + name: "Messaging" + } + ListElement { + name: "Todo List" + } + ListElement { + name: "Contacts" + } + ListElement { + name: "Settings" + } + } + + highlight: Rectangle { + id: listViewHighLight + width: view.width // has to be preserved + height: 120 + color: "#343434" + radius: 4 + border.color: "#0d52a4" + border.width: 8 + } + + delegate: Item { + id: delegate + width: ListView.view.width + height: delegateNormal.height + + Rectangle { + id: delegateNormal + color: "#bdbdbd" + anchors.fill: parent + height: 140 + anchors.margins: 12 + visible: true + radius: 4 + Text { + id: labelNormal //id required for binding preservation + color: "#343434" + anchors.top: parent.top + anchors.horizontalCenter: parent.horizontalCenter + + text: name + anchors.margins: 24 + } + } + + Rectangle { + id: delegateHighlighted + color: "#36bdbdbd" + anchors.fill: parent + anchors.margins: 12 + visible: false + radius: 4 + Text { + id: labelHighlighted //id required for binding preservation + color: "#efefef" + anchors.top: parent.top + anchors.horizontalCenter: parent.horizontalCenter + + text: name + anchors.margins: 32 + } + } + + + MouseArea { + anchors.fill: parent + onClicked: delegate.ListView.view.currentIndex = index + } + states: [ + State { + name: "Highlighted" + + when: delegate.ListView.isCurrentItem + PropertyChanges { + target: delegateHighlighted + visible: true + } + + PropertyChanges { + target: delegateNormal + visible: false + } + + + } + ] + } + +} diff --git a/tests/auto/qml/qmldesigner/data/merging/RootReplacementExpected.qml b/tests/auto/qml/qmldesigner/data/merging/RootReplacementExpected.qml new file mode 100644 index 00000000000..02f75103f4e --- /dev/null +++ b/tests/auto/qml/qmldesigner/data/merging/RootReplacementExpected.qml @@ -0,0 +1,26 @@ +import QtQuick 2.1 + +Item { + id: root + + Rectangle { + id: rectangle0 + Image { + id: rectangle1 + x: 10 + y: 10 + width: 100 + height: 150 + source: "qt/icon.png" + } + } + + Image { + id: rectangle4 + x: 10 + y: 10 + width: 100 + height: 150 + source: "qt/realcool.jpg" + } +} diff --git a/tests/auto/qml/qmldesigner/data/merging/RootReplacementStyle.qml b/tests/auto/qml/qmldesigner/data/merging/RootReplacementStyle.qml new file mode 100644 index 00000000000..0432a21b78b --- /dev/null +++ b/tests/auto/qml/qmldesigner/data/merging/RootReplacementStyle.qml @@ -0,0 +1,24 @@ +import QtQuick 2.1 + +Item { + id: root + Rectangle { + id: rectangle0 + Image { + id: rectangle1 + x: 10 + y: 10 + height: 150 + width: 100 + source: "qt/icon.png" + } + } + Image { + id: rectangle4 + x: 10; + y: 10; + height: 150 + width: 100 + source: "qt/realcool.jpg" + } +} diff --git a/tests/auto/qml/qmldesigner/data/merging/RootReplacementTemplate.qml b/tests/auto/qml/qmldesigner/data/merging/RootReplacementTemplate.qml new file mode 100644 index 00000000000..0524162afaf --- /dev/null +++ b/tests/auto/qml/qmldesigner/data/merging/RootReplacementTemplate.qml @@ -0,0 +1,35 @@ +import QtQuick 2.1 + +Rectangle { + id: root + x: 10; + y: 10; + Rectangle { + id: rectangle1 + x: 10; + y: 10; + height: 150 + width: 200 + } + Rectangle { + id: rectangle2 + x: 100; + y: 100; + anchors.fill: root + } + Rectangle { + id: rectangle3 + x: 140; + y: 180; + gradient: Gradient { + GradientStop { + position: 0 + color: "white" + } + GradientStop { + position: 1 + color: "black" + } + } + } +} diff --git a/tests/auto/qml/qmldesigner/data/merging/SimpleExpected.qml b/tests/auto/qml/qmldesigner/data/merging/SimpleExpected.qml new file mode 100644 index 00000000000..9b7c6ff02bb --- /dev/null +++ b/tests/auto/qml/qmldesigner/data/merging/SimpleExpected.qml @@ -0,0 +1,38 @@ +import QtQuick 2.1 + +Rectangle { + id: root + x: 10; + y: 10; + Image { + id: rectangle1 + x: 10 + y: 10 + width: 100 + height: 150 + source: "qt/icon.png" + } + + Rectangle { + id: rectangle2 + x: 100; + y: 100; + anchors.fill: root + } + Rectangle { + id: rectangle3 + x: 140; + y: 180; + gradient: Gradient { + GradientStop { + position: 0 + color: "white" + } + GradientStop { + position: 1 + color: "black" + } + } + } + +} diff --git a/tests/auto/qml/qmldesigner/data/merging/SimpleStyle.qml b/tests/auto/qml/qmldesigner/data/merging/SimpleStyle.qml new file mode 100644 index 00000000000..92e2e4cc5f9 --- /dev/null +++ b/tests/auto/qml/qmldesigner/data/merging/SimpleStyle.qml @@ -0,0 +1,10 @@ +import QtQuick 2.1 + + Image { + id: rectangle1 + x: 10; + y: 10; + height: 150 + width: 100 + source: "qt/icon.png" + } diff --git a/tests/auto/qml/qmldesigner/data/merging/SimpleTemplate.qml b/tests/auto/qml/qmldesigner/data/merging/SimpleTemplate.qml new file mode 100644 index 00000000000..22ac72ed94e --- /dev/null +++ b/tests/auto/qml/qmldesigner/data/merging/SimpleTemplate.qml @@ -0,0 +1,38 @@ +import QtQuick 2.1 + +Rectangle { + id: root + x: 10; + y: 10; + Rectangle { + id: rectangle1 + x: 10; + y: 10; + height: 150 + width: 200 + } + Rectangle { + id: rectangle2 + x: 100; + y: 100; + anchors.fill: root + } + Rectangle { + id: rectangle3 + x: 140; + y: 180; + gradient: Gradient { + GradientStop { + position: 0 + color: "white" + } + GradientStop { + position: 1 + color: "black" + } + } + } +} + + + diff --git a/tests/auto/qml/qmldesigner/data/merging/SliderExpected.qml b/tests/auto/qml/qmldesigner/data/merging/SliderExpected.qml new file mode 100644 index 00000000000..aa80d5d355e --- /dev/null +++ b/tests/auto/qml/qmldesigner/data/merging/SliderExpected.qml @@ -0,0 +1,68 @@ +import QtQuick 2.6 +import QtQuick.Controls 2.0 + +Slider { + id: control + value: 0.5 + + background: Item { + x: control.leftPadding + y: control.topPadding + control.availableHeight / 2 - height / 2 + implicitWidth: sliderGroove.width + implicitHeight: sliderGroove.height + height: implicitHeight + width: control.availableWidth + + Rectangle { + id: sliderGroove + width: 200 + height: 6 + color: "#bdbebf" + radius: 2 + anchors.fill: parent + } + + Item { + width: control.visualPosition * sliderGroove.width // should be preserved + height: sliderGrooveLeft.height + clip: true + + Rectangle { + id: sliderGrooveLeft + width: 200 + height: 6 + color: "#21be2b" + radius: 2 + } + } + + } + + handle: Item { + x: control.leftPadding + control.visualPosition * (control.availableWidth - width) + y: control.topPadding + control.availableHeight / 2 - height / 2 + + implicitWidth: handleNormal.width + implicitHeight: handleNormal.height + Rectangle { + id: handleNormal + width: 32 + height: 32 + visible: !control.pressed + color: "#f6f6f6" + radius: 13 + border.color: "#bdbebf" + } + + Rectangle { + id: handlePressed + width: 32 + height: 32 + visible: control.pressed + color: "#221bdb" + radius: 13 + border.color: "#bdbebf" + } + + } +} \ No newline at end of file diff --git a/tests/auto/qml/qmldesigner/data/merging/SliderStyle.qml b/tests/auto/qml/qmldesigner/data/merging/SliderStyle.qml new file mode 100644 index 00000000000..8af1ab1e3f7 --- /dev/null +++ b/tests/auto/qml/qmldesigner/data/merging/SliderStyle.qml @@ -0,0 +1,65 @@ +import QtQuick 2.12 + + +Item { + id: artboard + width: 640 + height: 480 + + Rectangle { + id: sliderGroove + x: 78 + y: 127 + width: 200 + height: 6 + color: "#bdbebf" + } + + Rectangle { + id: sliderGrooveLeft + x: 78 + y: 165 + width: 200 + height: 6 + color: "#21be2b" + radius: 2 + } + + Rectangle { + id: handleNormal + x: 130 + y: 74 + width: 32 + height: 32 + radius: 13 + color: "#f6f6f6" + border.color: "#bdbebf" + } + Rectangle { + id: handlePressed + x: 78 + y: 74 + width: 32 + height: 32 + radius: 13 + color: "#221bdb" + border.color: "#bdbebf" + } + + Text { + id: element + x: 8 + y: 320 + color: "#eaeaea" + text: qsTrId("Some stuff for reference that is thrown away") + font.pixelSize: 32 + } + + +} + +/*##^## +Designer { + D{i:0;formeditorColor:"#000000"} +} +##^##*/ diff --git a/tests/auto/qml/qmldesigner/data/merging/SliderTemplate.qml b/tests/auto/qml/qmldesigner/data/merging/SliderTemplate.qml new file mode 100644 index 00000000000..f78a00ef665 --- /dev/null +++ b/tests/auto/qml/qmldesigner/data/merging/SliderTemplate.qml @@ -0,0 +1,66 @@ +import QtQuick 2.6 +import QtQuick.Controls 2.0 + +Slider { + id: control + value: 0.5 + + background: Item { + x: control.leftPadding + y: control.topPadding + control.availableHeight / 2 - height / 2 + implicitWidth: sliderGroove.width + implicitHeight: sliderGroove.height + height: implicitHeight + width: control.availableWidth + Rectangle { + id: sliderGroove + + width: 200 + height: 4 + + anchors.fill: parent // has to be preserved + radius: 2 + color: "#bdbebf" + } + + Item { + width: control.visualPosition * sliderGroove.width // should be preserved + height: sliderGrooveLeft.height + clip: true + + Rectangle { + id: sliderGrooveLeft + width: 200 + height: 4 + color: "#21be2b" + radius: 2 + } + } + } + + handle: Item { + x: control.leftPadding + control.visualPosition * (control.availableWidth - width) + y: control.topPadding + control.availableHeight / 2 - height / 2 + + implicitWidth: handleNormal.width + implicitHeight: handleNormal.height + Rectangle { + id: handleNormal + width: 26 + height: 26 + radius: 13 + color: "#f6f6f6" + visible: !control.pressed //has to be preserved + border.color: "#bdbebf" + } + Rectangle { + id: handlePressed + width: 26 + height: 26 + radius: 13 + visible: control.pressed //has to be preserved + color: "#f0f0f0" + border.color: "#bdbebf" + } + } +} diff --git a/tests/auto/qml/qmldesigner/data/merging/SwitchExpected.qml b/tests/auto/qml/qmldesigner/data/merging/SwitchExpected.qml new file mode 100644 index 00000000000..7cb59ec1036 --- /dev/null +++ b/tests/auto/qml/qmldesigner/data/merging/SwitchExpected.qml @@ -0,0 +1,111 @@ +import QtQuick 2.10 +import QtQuick.Templates 2.1 as T +import TemplateMerging 1.0 + +T.Switch { + id: control + + implicitWidth: background.implicitWidth + implicitHeight: background.implicitHeight + + text: "test" + indicator: Rectangle { + id: switchIndicator + x: control.leftPadding + y: 34 + width: 64 + height: 44 + color: "#e9e9e9" + radius: 16 + border.color: "#dddddd" + anchors.verticalCenter: parent.verticalCenter + Rectangle { + id: switchHandle + width: 31 + height: 44 + color: "#e9e9e9" + radius: 16 + border.color: "#808080" + } + } + + background: Item { + implicitWidth: switchBackground.width + implicitHeight: switchBackground.height + + Rectangle { + id: switchBackground + width: 144 + height: 52 + color: "#c2c2c2" + border.color: "#808080" + anchors.fill: parent + + Text { + text: "background" + anchors.verticalCenter: parent.verticalCenter + anchors.right: parent.right + anchors.rightMargin: 12 + } + } + } + + leftPadding: 4 + + contentItem: Item { //designer want to edit the label as part of background + } + + + states: [ + State { + name: "off" + when: !control.checked && !control.down + }, + State { + name: "on" + when: control.checked && !control.down + + PropertyChanges { + target: switchIndicator + color: "#1713de" + border.color: "#1713de" + } + + PropertyChanges { + target: switchHandle + x: parent.width - width + } + }, + State { + name: "off_down" + when: !control.checked && control.down + + PropertyChanges { + target: switchIndicator + color: "#e9e9e9" + } + + PropertyChanges { + target: switchHandle + color: "#d2d2d2" + border.color: "#d2d2d2" + } + }, + State { + name: "on_down" + when: control.checked && control.down + + PropertyChanges { + target: switchHandle + x: parent.width - width + color: "#e9e9e9" + } + + PropertyChanges { + target: switchIndicator + color: "#030381" + border.color: "#030381" + } + } + ] +} \ No newline at end of file diff --git a/tests/auto/qml/qmldesigner/data/merging/SwitchStyle.qml b/tests/auto/qml/qmldesigner/data/merging/SwitchStyle.qml new file mode 100644 index 00000000000..9f8cffc9b70 --- /dev/null +++ b/tests/auto/qml/qmldesigner/data/merging/SwitchStyle.qml @@ -0,0 +1,66 @@ +import QtQuick 2.12 + +Item { + width: 640 + height: 480 + + Rectangle { + id: switchIndicator + x: 219 + y: 34 + width: 64 + height: 44 + + color: "#e9e9e9" + + radius: 16 + border.color: "#dddddd" + + Rectangle { + id: switchHandle //id is required for states + + width: 31 + height: 44 + radius: 16 + color: "#e9e9e9" + border.color: "#808080" + } + } + + Rectangle { + id: switchBackground + x: 346 + y: 27 + width: 144 + height: 52 + color: "#c2c2c2" + border.color: "#808080" + + Text { + id: switchBackgroundText + text: "background" + anchors.right: parent.right + + anchors.verticalCenter: parent.verticalCenter + anchors.rightMargin: 12 + } + } + + Text { + id: element + x: 1 + y: 362 + color: "#eaeaea" + text: qsTrId("Some stuff for reference that is thrown away") + font.pixelSize: 32 + } + + Rectangle { //This is ignored when merging + id: weirdStuff02 + x: 8 + y: 87 + width: 624 + height: 200 + color: "#ffffff" + } +} diff --git a/tests/auto/qml/qmldesigner/data/merging/SwitchTemplate.qml b/tests/auto/qml/qmldesigner/data/merging/SwitchTemplate.qml new file mode 100644 index 00000000000..d45756494b8 --- /dev/null +++ b/tests/auto/qml/qmldesigner/data/merging/SwitchTemplate.qml @@ -0,0 +1,120 @@ +import QtQuick 2.10 +import QtQuick.Templates 2.1 as T +import TemplateMerging 1.0 + +T.Switch { + id: control + + implicitWidth: background.implicitWidth + implicitHeight: background.implicitHeight + + text: "test" + + background: Item { + implicitWidth: switchBackground.width + implicitHeight: switchBackground.height + Rectangle { + id: switchBackground + color: "#ef1d1d" + border.color: "#808080" + width: 12 * 6.0 + height: 12 * 3.8 + anchors.fill: parent // has to be preserved + Text { + id: switchBackgroundText + anchors.right: parent.right // does have to be preserved -- how to handle this? - anchors preference from style if not "root"? + + anchors.verticalCenter: parent.verticalCenter // does have to be preserved -- how to handle this? - anchors preference from style if not "root"? + text: control.text // has to be preserved + anchors.rightMargin: 12 * 5 + } + Rectangle { + id: nonSenseRectangle + width: 5 * 12.0 + height: 6 * 49.0 + color: "#ff0000" + } + } + } + + leftPadding: 4 + + contentItem: Item { //designer want to edit the label as part of background + } + + + indicator: Rectangle { + id: switchIndicator + width: 58 + height: 31 + x: control.leftPadding // has to be preserved + color: "#e9e9e9" + anchors.verticalCenter: parent.verticalCenter // has to be preserved + radius: 16 + border.color: "#dddddd" + + Rectangle { + id: switchHandle //id is required for states + + width: 31 + height: 31 + radius: 16 + color: "#e9e9e9" + border.color: "#808080" + } + } + states: [ + State { + name: "off" + when: !control.checked && !control.down + }, + State { + name: "on" + when: control.checked && !control.down + + PropertyChanges { + target: switchIndicator + color: "#1713de" + border.color: "#1713de" + } + + PropertyChanges { + target: switchHandle + x: parent.width - width + } + }, + State { + name: "off_down" + when: !control.checked && control.down + + PropertyChanges { + target: switchIndicator + color: "#e9e9e9" + } + + PropertyChanges { + target: switchHandle + color: "#d2d2d2" + border.color: "#d2d2d2" + } + }, + State { + name: "on_down" + when: control.checked && control.down + + PropertyChanges { + target: switchHandle + x: parent.width - width + color: "#e9e9e9" + } + + PropertyChanges { + target: switchIndicator + color: "#030381" + border.color: "#030381" + } + } + ] +} + +