aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMichael BrĂ¼ning <michael.bruning@qt.io>2020-05-07 16:43:39 +0200
committerThomas Hartmann <thomas.hartmann@qt.io>2020-05-27 18:26:12 +0000
commitde8eb93637d31e474fdada8ca8199b35841a7f7a (patch)
treeeb5d9775a9ee67c5831e3eec6d691eb63c996be3
parent5b0040185085704ea284cc7fdb6a95603caca203 (diff)
Add stylesheet merger
Adds classes to merge a template qml file and a qml stylesheet that have been exported from other design tools into a resulting qml file that can be used for further processing in Qt Design Studio. Current issues: * Sometimes it makes sense to define width and height if an anchor is present, but most of the time not. * Actually if the hierachy was defined (e.g. Text item not child of background) most likely the anchors should be ignored. But this would be just a "dirty" heuristic. I suggest to let the template decide. If the template has anchors those have "precedence". It is always possible to define templates without anchors. Task-number: QDS-2071 Change-Id: I9159514a8e884b7ffc31897aef4551b5efbbcb87 Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
-rw-r--r--src/plugins/qmldesigner/CMakeLists.txt2
-rw-r--r--src/plugins/qmldesigner/designercore/designercore-lib.pri6
-rw-r--r--src/plugins/qmldesigner/designercore/include/stylesheetmerger.h71
-rw-r--r--src/plugins/qmldesigner/designercore/model/stylesheetmerger.cpp404
-rw-r--r--src/plugins/qmldesigner/qmldesignerplugin.qbs2
-rw-r--r--tests/auto/qml/qmldesigner/coretests/coretests.pro2
-rw-r--r--tests/auto/qml/qmldesigner/coretests/tst_testcore.cpp155
-rw-r--r--tests/auto/qml/qmldesigner/coretests/tst_testcore.h2
-rw-r--r--tests/auto/qml/qmldesigner/data/merging/ButtonAbsoluteTemplate.qml104
-rw-r--r--tests/auto/qml/qmldesigner/data/merging/ButtonInlineExpected.qml93
-rw-r--r--tests/auto/qml/qmldesigner/data/merging/ButtonOutlineExpected.qml97
-rw-r--r--tests/auto/qml/qmldesigner/data/merging/ButtonStyleInline.qml79
-rw-r--r--tests/auto/qml/qmldesigner/data/merging/ButtonStyleOutline.qml83
-rw-r--r--tests/auto/qml/qmldesigner/data/merging/ButtonTemplate.qml100
-rw-r--r--tests/auto/qml/qmldesigner/data/merging/ComplexExpected.qml62
-rw-r--r--tests/auto/qml/qmldesigner/data/merging/ComplexStyle.qml45
-rw-r--r--tests/auto/qml/qmldesigner/data/merging/ComplexTemplate.qml49
-rw-r--r--tests/auto/qml/qmldesigner/data/merging/EmptyStyleExpected.qml43
-rw-r--r--tests/auto/qml/qmldesigner/data/merging/EmptyStyleStyle.qml1
-rw-r--r--tests/auto/qml/qmldesigner/data/merging/EmptyStyleTemplate.qml43
-rw-r--r--tests/auto/qml/qmldesigner/data/merging/ListViewExpected.qml135
-rw-r--r--tests/auto/qml/qmldesigner/data/merging/ListViewStyle.qml82
-rw-r--r--tests/auto/qml/qmldesigner/data/merging/ListViewTemplate.qml133
-rw-r--r--tests/auto/qml/qmldesigner/data/merging/RootReplacementExpected.qml26
-rw-r--r--tests/auto/qml/qmldesigner/data/merging/RootReplacementStyle.qml24
-rw-r--r--tests/auto/qml/qmldesigner/data/merging/RootReplacementTemplate.qml35
-rw-r--r--tests/auto/qml/qmldesigner/data/merging/SimpleExpected.qml38
-rw-r--r--tests/auto/qml/qmldesigner/data/merging/SimpleStyle.qml10
-rw-r--r--tests/auto/qml/qmldesigner/data/merging/SimpleTemplate.qml38
-rw-r--r--tests/auto/qml/qmldesigner/data/merging/SliderExpected.qml68
-rw-r--r--tests/auto/qml/qmldesigner/data/merging/SliderStyle.qml65
-rw-r--r--tests/auto/qml/qmldesigner/data/merging/SliderTemplate.qml66
-rw-r--r--tests/auto/qml/qmldesigner/data/merging/SwitchExpected.qml111
-rw-r--r--tests/auto/qml/qmldesigner/data/merging/SwitchStyle.qml66
-rw-r--r--tests/auto/qml/qmldesigner/data/merging/SwitchTemplate.qml120
35 files changed, 2443 insertions, 17 deletions
diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt
index 53a3e4de84..f9a6dc94cb 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 5e99009cdb..7fc663f4af 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 0000000000..f752e50419
--- /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 <QString>
+#include <QHash>
+#include <modelnode.h>
+
+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<QString, ReparentInfo> m_reparentInfoHash;
+ QHash<QString, QString> 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 0000000000..29990a5eca
--- /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 <abstractview.h>
+#include <bindingproperty.h>
+#include <modelmerger.h>
+#include <nodeabstractproperty.h>
+#include <nodelistproperty.h>
+#include <nodemetainfo.h>
+#include <nodeproperty.h>
+#include <variantproperty.h>
+
+#include <QQueue>
+#include <QRegularExpression>
+
+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<QPair<PropertyName, QVariant> > propertyList;
+ QList<QPair<PropertyName, QVariant> > variantPropertyList;
+ foreach (const VariantProperty &variantProperty, modelNode.variantProperties()) {
+ propertyList.append(QPair<PropertyName, QVariant>(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<ModelNode> replacementNodes;
+
+ QList<ModelNode> 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 810eb4d62c..2e695b90e1 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 8a86681e62..dd0f071b0e 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 266dc92508..cb47a5f2fd 100644
--- a/tests/auto/qml/qmldesigner/coretests/tst_testcore.cpp
+++ b/tests/auto/qml/qmldesigner/coretests/tst_testcore.cpp
@@ -28,6 +28,7 @@
#include <QScopedPointer>
#include <QLatin1String>
#include <QGraphicsObject>
+#include <QQueue>
#include <QTest>
#include <QVariant>
@@ -42,6 +43,7 @@
#include <nodeinstanceview.h>
#include <nodeinstance.h>
#include <subcomponentmanager.h>
+#include <stylesheetmerger.h>
#include <QDebug>
#include "../testview.h"
@@ -3704,6 +3706,131 @@ void tst_TestCore::testCopyModelRewriter1()
QCOMPARE(textEdit1.toPlainText(), expected);
}
+static void adjustPreservedProperties(const ModelNode& replacedNode, ModelNode& newNode) {
+ QSet<PropertyName> 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<QString>("qmlTemplateString");
+ QTest::addColumn<QString>("qmlStyleString");
+ QTest::addColumn<QString>("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<Model> templateModel(Model::create("QtQuick.Item", 2, 1));
+ QVERIFY(templateModel.data());
+
+ QScopedPointer<TestView> templateView(new TestView(templateModel.data()));
+ templateModel->attachView(templateView.data());
+
+ // read in 1
+ QScopedPointer<TestRewriterView> 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<Model> styleModel(Model::create("QtQuick.Item", 2, 1));
+ QVERIFY(styleModel.data());
+
+ QScopedPointer<TestView> styleView(new TestView(styleModel.data()));
+ styleModel->attachView(styleView.data());
+
+ // read in 2
+ QScopedPointer<TestRewriterView> 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<Model> model1(Model::create("QtQuick.Item", 2, 1));
- QVERIFY(model1.data());
+ QScopedPointer<Model> templateModel(Model::create("QtQuick.Item", 2, 1));
+ QVERIFY(templateModel.data());
- QScopedPointer<TestView> view1(new TestView(model1.data()));
- model1->attachView(view1.data());
+ QScopedPointer<TestView> view1(new TestView(templateModel.data()));
+ templateModel->attachView(view1.data());
// read in 1
QScopedPointer<TestRewriterView> 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<Model> model2(Model::create("QtQuick.Item", 2, 1));
- QVERIFY(model2.data());
+ QScopedPointer<Model> styleModel(Model::create("QtQuick.Item", 2, 1));
+ QVERIFY(styleModel.data());
- QScopedPointer<TestView> view2(new TestView(model2.data()));
- model2->attachView(view2.data());
+ QScopedPointer<TestView> view2(new TestView(styleModel.data()));
+ styleModel->attachView(view2.data());
- QScopedPointer<TestRewriterView> testRewriterView2(new TestRewriterView());
- testRewriterView2->setTextModifier(&textModifier2);
- model2->attachView(testRewriterView2.data());
+ QScopedPointer<TestRewriterView> 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 92f3b17161..92d4ac00aa 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 0000000000..88d66014ac
--- /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 0000000000..de131d2e64
--- /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 0000000000..ddbdd78096
--- /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 0000000000..f33450bf7c
--- /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 0000000000..a9a7021383
--- /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 0000000000..cd4b376a57
--- /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 0000000000..eb0ca78fad
--- /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 0000000000..9bf50d8f0f
--- /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 0000000000..058b969823
--- /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 0000000000..122ccd8767
--- /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 0000000000..8040be2697
--- /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 0000000000..122ccd8767
--- /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 0000000000..a8da56b1df
--- /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 0000000000..4e68326f0a
--- /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 0000000000..6c59ca4d39
--- /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 0000000000..02f75103f4
--- /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 0000000000..0432a21b78
--- /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 0000000000..0524162afa
--- /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 0000000000..9b7c6ff02b
--- /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 0000000000..92e2e4cc5f
--- /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 0000000000..22ac72ed94
--- /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 0000000000..aa80d5d355
--- /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 0000000000..8af1ab1e3f
--- /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 0000000000..f78a00ef66
--- /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 0000000000..7cb59ec103
--- /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 0000000000..9f8cffc9b7
--- /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 0000000000..d45756494b
--- /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"
+ }
+ }
+ ]
+}
+
+