1 /****************************************************************************
2 **
3 ** Copyright (C) 2020 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25 #include "componentexporter.h"
26 #include "assetexporter.h"
27 #include "assetexportpluginconstants.h"
28 #include "exportnotification.h"
29 #include "dumpers/nodedumper.h"
30 
31 #include "model.h"
32 #include "nodeabstractproperty.h"
33 #include "nodemetainfo.h"
34 #include "qmlitemnode.h"
35 #include "rewriterview.h"
36 
37 #include "utils/qtcassert.h"
38 
39 #include <QJsonArray>
40 #include <QJsonObject>
41 #include <QLoggingCategory>
42 #include <QPainter>
43 
44 namespace {
45 Q_LOGGING_CATEGORY(loggerInfo, "qtc.designer.assetExportPlugin.modelExporter", QtInfoMsg)
46 
populateLineage(const QmlDesigner::ModelNode & node)47 static QByteArrayList populateLineage(const QmlDesigner::ModelNode &node)
48 {
49     QByteArrayList lineage;
50     if (!node.isValid() || node.type().isEmpty())
51         return {};
52 
53     for (auto &info : node.metaInfo().superClasses())
54         lineage.append(info.typeName());
55 
56     return lineage;
57 }
58 
59 }
60 
61 namespace QmlDesigner {
62 using namespace Constants;
63 
64 std::vector<std::unique_ptr<Internal::NodeDumperCreatorBase>> Component::m_readers;
Component(AssetExporter & exporter,const ModelNode & rootNode)65 Component::Component(AssetExporter &exporter, const ModelNode &rootNode):
66     m_exporter(exporter),
67     m_rootNode(rootNode)
68 {
69     m_name = m_rootNode.id();
70     if (m_name.isEmpty())
71         m_name = QString::fromUtf8(m_rootNode.type());
72 }
73 
json() const74 QJsonObject Component::json() const
75 {
76     return m_json;
77 }
78 
exporter()79 AssetExporter &Component::exporter()
80 {
81     return m_exporter;
82 }
83 
exportComponent()84 void Component::exportComponent()
85 {
86     QTC_ASSERT(m_rootNode.isValid(), return);
87     m_json = nodeToJson(m_rootNode);
88     // Change the export type to component
89     QJsonObject metadata = m_json.value(MetadataTag).toObject();
90     metadata.insert(ExportTypeTag, ExportTypeComponent);
91     addReferenceAsset(metadata);
92     m_json.insert(MetadataTag, metadata);
93     addImports();
94 }
95 
name() const96 const QString &Component::name() const
97 {
98     return m_name;
99 }
100 
createNodeDumper(const ModelNode & node) const101 NodeDumper *Component::createNodeDumper(const ModelNode &node) const
102 {
103     QByteArrayList lineage = populateLineage(node);
104     std::unique_ptr<NodeDumper> reader;
105     for (auto &dumperCreator: m_readers) {
106         std::unique_ptr<NodeDumper> r(dumperCreator->instance(lineage, node));
107         if (r->isExportable()) {
108             if (reader) {
109                 if (reader->priority() < r->priority())
110                     reader = std::move(r);
111             } else {
112                 reader = std::move(r);
113             }
114         }
115     }
116 
117     if (!reader)
118         qCDebug(loggerInfo()) << "No dumper for node" << node;
119 
120     return reader.release();
121 }
122 
nodeToJson(const ModelNode & node)123 QJsonObject Component::nodeToJson(const ModelNode &node)
124 {
125     QJsonObject jsonObject;
126 
127     // Don't export States, Connection, Timeline etc nodes.
128     if (!node.isSubclassOf("QtQuick.Item"))
129         return {};
130 
131     std::unique_ptr<NodeDumper> dumper(createNodeDumper(node));
132     if (dumper) {
133         jsonObject = dumper->json(*this);
134     } else {
135         ExportNotification::addError(tr("Error exporting node %1. Cannot parse type %2.")
136                                      .arg(node.id()).arg(QString::fromUtf8(node.type())));
137     }
138 
139     QJsonArray children;
140     for (const ModelNode &childnode : node.directSubModelNodes()) {
141         const QJsonObject childJson = nodeToJson(childnode);
142         if (!childJson.isEmpty())
143             children.append(childJson);
144     }
145 
146     if (!children.isEmpty())
147         jsonObject.insert(ChildrenTag, children);
148 
149     return jsonObject;
150 }
151 
addReferenceAsset(QJsonObject & metadataObject) const152 void Component::addReferenceAsset(QJsonObject &metadataObject) const
153 {
154     QPixmap refAsset = m_exporter.generateAsset(m_rootNode);
155     stichChildrendAssets(m_rootNode, refAsset);
156     Utils::FilePath refAssetPath = m_exporter.assetPath(m_rootNode, this, "_ref");
157     m_exporter.exportAsset(refAsset, refAssetPath);
158     QJsonObject assetData;
159     if (metadataObject.contains(AssetDataTag))
160         assetData = metadataObject[AssetDataTag].toObject();
161     assetData.insert(ReferenceAssetTag, refAssetPath.toString());
162     metadataObject.insert(AssetDataTag, assetData);
163 }
164 
stichChildrendAssets(const ModelNode & node,QPixmap & parentPixmap) const165 void Component::stichChildrendAssets(const ModelNode &node, QPixmap &parentPixmap) const
166 {
167     if (!node.hasAnySubModelNodes())
168         return;
169 
170     QPainter painter(&parentPixmap);
171     for (const ModelNode &child : node.directSubModelNodes()) {
172         QPixmap childPixmap = m_exporter.generateAsset(child);
173         if (childPixmap.isNull())
174             continue;
175         stichChildrendAssets(child, childPixmap);
176         QTransform cTransform = QmlObjectNode(child).toQmlItemNode().instanceTransform();
177         painter.setTransform(cTransform);
178         painter.drawPixmap(QPoint(0, 0), childPixmap);
179     }
180     painter.end();
181 }
182 
addImports()183 void Component::addImports()
184 {
185     QJsonArray importsArray;
186     for (const Import &import : m_rootNode.model()->imports())
187         importsArray.append(import.toString());
188 
189     if (!importsArray.empty())
190         m_json.insert(Constants::ImportsTag, importsArray);
191 }
192 
193 
194 } // namespace QmlDesigner
195