1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2019 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the QtQml module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
21 | ** included in the packaging of this file. Please review the following |
22 | ** information to ensure the GNU General Public License requirements will |
23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
24 | ** |
25 | ** $QT_END_LICENSE$ |
26 | ** |
27 | ****************************************************************************/ |
28 | |
29 | #include "qmltypescreator.h" |
30 | #include "qmlstreamwriter.h" |
31 | #include "qmltypesclassdescription.h" |
32 | |
33 | #include <QtCore/qset.h> |
34 | #include <QtCore/qjsonarray.h> |
35 | #include <QtCore/qsavefile.h> |
36 | #include <QtCore/qfile.h> |
37 | #include <QtCore/qjsondocument.h> |
38 | |
39 | static QString enquote(const QString &string) |
40 | { |
41 | QString s = string; |
42 | return QString::fromLatin1(str: "\"%1\"" ).arg(a: s.replace(c: QLatin1Char('\\'), after: QLatin1String("\\\\" )) |
43 | .replace(c: QLatin1Char('"'),after: QLatin1String("\\\"" ))); |
44 | } |
45 | |
46 | void QmlTypesCreator::writeClassProperties(const QmlTypesClassDescription &collector) |
47 | { |
48 | if (!collector.file.isEmpty()) |
49 | m_qml.writeScriptBinding(name: QLatin1String("file" ), rhs: enquote(string: collector.file)); |
50 | m_qml.writeScriptBinding( |
51 | name: QLatin1String("name" ), |
52 | rhs: enquote(string: collector.resolvedClass->value( |
53 | key: QLatin1String("qualifiedClassName" )).toString())); |
54 | |
55 | if (!collector.defaultProp.isEmpty()) |
56 | m_qml.writeScriptBinding(name: QLatin1String("defaultProperty" ), rhs: enquote(string: collector.defaultProp)); |
57 | |
58 | if (!collector.superClass.isEmpty()) |
59 | m_qml.writeScriptBinding(name: QLatin1String("prototype" ), rhs: enquote(string: collector.superClass)); |
60 | |
61 | if (collector.elementName.isEmpty()) |
62 | return; |
63 | |
64 | QStringList exports; |
65 | QStringList metaObjects; |
66 | |
67 | for (auto it = collector.revisions.begin(), end = collector.revisions.end(); it != end; ++it) { |
68 | const int revision = *it; |
69 | if (revision < collector.addedInRevision) |
70 | continue; |
71 | if (collector.removedInRevision > collector.addedInRevision |
72 | && revision >= collector.removedInRevision) { |
73 | break; |
74 | } |
75 | |
76 | if (collector.isBuiltin) { |
77 | exports.append(t: enquote(string: QString::fromLatin1(str: "QML/%1 1.0" ).arg(a: collector.elementName))); |
78 | metaObjects.append(t: QLatin1String("0" )); |
79 | } |
80 | |
81 | exports.append(t: enquote(string: QString::fromLatin1(str: "%1/%2 %3.%4" ) |
82 | .arg(a: m_module).arg(a: collector.elementName) |
83 | .arg(a: m_majorVersion).arg(a: revision))); |
84 | metaObjects.append(t: QString::number(revision)); |
85 | } |
86 | |
87 | m_qml.writeArrayBinding(name: QLatin1String("exports" ), elements: exports); |
88 | |
89 | if (!collector.isCreatable || collector.isSingleton) |
90 | m_qml.writeScriptBinding(name: QLatin1String("isCreatable" ), rhs: QLatin1String("false" )); |
91 | |
92 | if (collector.isSingleton) |
93 | m_qml.writeScriptBinding(name: QLatin1String("isSingleton" ), rhs: QLatin1String("true" )); |
94 | |
95 | m_qml.writeArrayBinding(name: QLatin1String("exportMetaObjectRevisions" ), elements: metaObjects); |
96 | |
97 | if (!collector.attachedType.isEmpty()) |
98 | m_qml.writeScriptBinding(name: QLatin1String("attachedType" ), rhs: enquote(string: collector.attachedType)); |
99 | } |
100 | |
101 | void QmlTypesCreator::writeType(const QJsonObject &property, const QString &key, bool isReadonly, |
102 | bool parsePointer) |
103 | { |
104 | auto it = property.find(key); |
105 | if (it == property.end()) |
106 | return; |
107 | |
108 | QString type = (*it).toString(); |
109 | if (type.isEmpty() || type == QLatin1String("void" )) |
110 | return; |
111 | |
112 | const QLatin1String typeKey("type" ); |
113 | |
114 | bool isList = false; |
115 | bool isPointer = false; |
116 | |
117 | if (type == QLatin1String("QString" )) { |
118 | type = QLatin1String("string" ); |
119 | } else if (type == QLatin1String("qreal" )) { |
120 | type = QLatin1String("double" ); |
121 | } else if (type == QLatin1String("qint32" )) { |
122 | type = QLatin1String("int" ); |
123 | } else if (type == QLatin1String("quint32" )) { |
124 | type = QLatin1String("uint" ); |
125 | } else if (type == QLatin1String("qint64" )) { |
126 | type = QLatin1String("qlonglong" ); |
127 | } else if (type == QLatin1String("quint64" )) { |
128 | type = QLatin1String("qulonglong" ); |
129 | } else { |
130 | |
131 | const QLatin1String listProperty("QQmlListProperty<" ); |
132 | if (type.startsWith(s: listProperty)) { |
133 | isList = true; |
134 | const int listPropertySize = listProperty.size(); |
135 | type = type.mid(position: listPropertySize, n: type.size() - listPropertySize - 1); |
136 | } |
137 | |
138 | if (parsePointer && type.endsWith(c: QLatin1Char('*'))) { |
139 | isPointer = true; |
140 | type = type.left(n: type.size() - 1); |
141 | } |
142 | } |
143 | |
144 | m_qml.writeScriptBinding(name: typeKey, rhs: enquote(string: type)); |
145 | const QLatin1String trueString("true" ); |
146 | if (isList) |
147 | m_qml.writeScriptBinding(name: QLatin1String("isList" ), rhs: trueString); |
148 | if (isReadonly) |
149 | m_qml.writeScriptBinding(name: QLatin1String("isReadonly" ), rhs: trueString); |
150 | if (isPointer) |
151 | m_qml.writeScriptBinding(name: QLatin1String("isPointer" ), rhs: trueString); |
152 | } |
153 | |
154 | void QmlTypesCreator::writeProperties(const QJsonArray &properties, QSet<QString> ¬ifySignals) |
155 | { |
156 | for (const QJsonValue &property : properties) { |
157 | const QJsonObject obj = property.toObject(); |
158 | const QString name = obj[QLatin1String("name" )].toString(); |
159 | m_qml.writeStartObject(component: QLatin1String("Property" )); |
160 | m_qml.writeScriptBinding(name: QLatin1String("name" ), rhs: enquote(string: name)); |
161 | const auto it = obj.find(key: QLatin1String("revision" )); |
162 | if (it != obj.end()) |
163 | m_qml.writeScriptBinding(name: QLatin1String("revision" ), rhs: QString::number(it.value().toInt())); |
164 | writeType(property: obj, key: QLatin1String("type" ), isReadonly: !obj.contains(key: QLatin1String("write" )), parsePointer: true); |
165 | m_qml.writeEndObject(); |
166 | |
167 | const QString notify = obj[QLatin1String("notify" )].toString(); |
168 | if (notify == name + QLatin1String("Changed" )) |
169 | notifySignals.insert(value: notify); |
170 | } |
171 | } |
172 | |
173 | void QmlTypesCreator::writeMethods(const QJsonArray &methods, const QString &type, |
174 | const QSet<QString> ¬ifySignals) |
175 | { |
176 | for (const QJsonValue &method : methods) { |
177 | const QJsonObject obj = method.toObject(); |
178 | const QString name = obj[QLatin1String("name" )].toString(); |
179 | if (name.isEmpty()) |
180 | continue; |
181 | const QJsonArray arguments = method[QLatin1String("arguments" )].toArray(); |
182 | const auto revision = obj.find(key: QLatin1String("revision" )); |
183 | if (notifySignals.contains(value: name) && arguments.isEmpty() && revision == obj.end()) |
184 | continue; |
185 | m_qml.writeStartObject(component: type); |
186 | m_qml.writeScriptBinding(name: QLatin1String("name" ), rhs: enquote(string: name)); |
187 | if (revision != obj.end()) |
188 | m_qml.writeScriptBinding(name: QLatin1String("revision" ), rhs: QString::number(revision.value().toInt())); |
189 | writeType(property: obj, key: QLatin1String("returnType" ), isReadonly: false, parsePointer: false); |
190 | for (const QJsonValue &argument : arguments) { |
191 | const QJsonObject obj = argument.toObject(); |
192 | m_qml.writeStartObject(component: QLatin1String("Parameter" )); |
193 | const QString name = obj[QLatin1String("name" )].toString(); |
194 | if (!name.isEmpty()) |
195 | m_qml.writeScriptBinding(name: QLatin1String("name" ), rhs: enquote(string: name)); |
196 | writeType(property: obj, key: QLatin1String("type" ), isReadonly: false, parsePointer: true); |
197 | m_qml.writeEndObject(); |
198 | } |
199 | m_qml.writeEndObject(); |
200 | } |
201 | } |
202 | |
203 | void QmlTypesCreator::writeEnums(const QJsonArray &enums) |
204 | { |
205 | for (const auto &item : enums) { |
206 | const QJsonObject obj = item.toObject(); |
207 | const QJsonArray values = obj.value(key: QLatin1String("values" )).toArray(); |
208 | QStringList valueList; |
209 | |
210 | for (const QJsonValue &value : values) |
211 | valueList.append(t: enquote(string: value.toString())); |
212 | |
213 | m_qml.writeStartObject(component: QLatin1String("Enum" )); |
214 | m_qml.writeScriptBinding(name: QLatin1String("name" ), |
215 | rhs: enquote(string: obj.value(key: QLatin1String("name" )).toString())); |
216 | auto alias = obj.find(key: QLatin1String("alias" )); |
217 | if (alias != obj.end()) |
218 | m_qml.writeScriptBinding(name: alias.key(), rhs: enquote(string: alias->toString())); |
219 | auto isFlag = obj.find(key: QLatin1String("isFlag" )); |
220 | if (isFlag != obj.end() && isFlag->toBool()) |
221 | m_qml.writeBooleanBinding(name: isFlag.key(), value: true); |
222 | m_qml.writeArrayBinding(name: QLatin1String("values" ), elements: valueList); |
223 | m_qml.writeEndObject(); |
224 | } |
225 | } |
226 | |
227 | static QJsonArray members(const QJsonObject *classDef, const QJsonObject *origClassDef, const QString &key) |
228 | { |
229 | QJsonArray classDefMembers = classDef->value(key).toArray(); |
230 | |
231 | if (classDef != origClassDef) { |
232 | const QJsonArray origClassDefMembers = origClassDef->value(key).toArray(); |
233 | for (const auto &member : origClassDefMembers) |
234 | classDefMembers.append(value: member); |
235 | } |
236 | |
237 | return classDefMembers; |
238 | } |
239 | |
240 | void QmlTypesCreator::writeComponents() |
241 | { |
242 | const QLatin1String nameKey("name" ); |
243 | const QLatin1String signalsKey("signals" ); |
244 | const QLatin1String enumsKey("enums" ); |
245 | const QLatin1String propertiesKey("properties" ); |
246 | const QLatin1String slotsKey("slots" ); |
247 | const QLatin1String methodsKey("methods" ); |
248 | const QLatin1String accessKey("access" ); |
249 | const QLatin1String typeKey("type" ); |
250 | const QLatin1String argumentsKey("arguments" ); |
251 | |
252 | const QLatin1String destroyedName("destroyed" ); |
253 | const QLatin1String deleteLaterName("deleteLater" ); |
254 | const QLatin1String toStringName("toString" ); |
255 | const QLatin1String destroyName("destroy" ); |
256 | const QLatin1String delayName("delay" ); |
257 | |
258 | const QLatin1String signalElement("Signal" ); |
259 | const QLatin1String componentElement("Component" ); |
260 | const QLatin1String methodElement("Method" ); |
261 | |
262 | const QLatin1String publicAccess("public" ); |
263 | const QLatin1String intType("int" ); |
264 | |
265 | for (const QJsonObject &component : m_ownTypes) { |
266 | m_qml.writeStartObject(component: componentElement); |
267 | |
268 | QmlTypesClassDescription collector; |
269 | collector.collect(classDef: &component, types: m_ownTypes, foreign: m_foreignTypes, |
270 | mode: QmlTypesClassDescription::TopLevel); |
271 | |
272 | writeClassProperties(collector); |
273 | |
274 | const QJsonObject *classDef = collector.resolvedClass; |
275 | writeEnums(enums: members(classDef, origClassDef: &component, key: enumsKey)); |
276 | |
277 | QSet<QString> notifySignals; |
278 | writeProperties(properties: members(classDef, origClassDef: &component, key: propertiesKey), notifySignals); |
279 | |
280 | if (collector.isRootClass) { |
281 | |
282 | // Hide destroyed() signals |
283 | QJsonArray componentSignals = members(classDef, origClassDef: &component, key: signalsKey); |
284 | for (auto it = componentSignals.begin(); it != componentSignals.end();) { |
285 | if (it->toObject().value(key: nameKey).toString() == destroyedName) |
286 | it = componentSignals.erase(it); |
287 | else |
288 | ++it; |
289 | } |
290 | writeMethods(methods: componentSignals, type: signalElement, notifySignals); |
291 | |
292 | // Hide deleteLater() methods |
293 | QJsonArray componentMethods = members(classDef, origClassDef: &component, key: methodsKey); |
294 | const QJsonArray componentSlots = members(classDef, origClassDef: &component, key: slotsKey); |
295 | for (const QJsonValue &componentSlot : componentSlots) |
296 | componentMethods.append(value: componentSlot); |
297 | for (auto it = componentMethods.begin(); it != componentMethods.end();) { |
298 | if (it->toObject().value(key: nameKey).toString() == deleteLaterName) |
299 | it = componentMethods.erase(it); |
300 | else |
301 | ++it; |
302 | } |
303 | |
304 | // Add toString() |
305 | QJsonObject toStringMethod; |
306 | toStringMethod.insert(key: nameKey, value: toStringName); |
307 | toStringMethod.insert(key: accessKey, value: publicAccess); |
308 | componentMethods.append(value: toStringMethod); |
309 | |
310 | // Add destroy() |
311 | QJsonObject destroyMethod; |
312 | destroyMethod.insert(key: nameKey, value: destroyName); |
313 | destroyMethod.insert(key: accessKey, value: publicAccess); |
314 | componentMethods.append(value: destroyMethod); |
315 | |
316 | // Add destroy(int) |
317 | QJsonObject destroyMethodWithArgument; |
318 | destroyMethodWithArgument.insert(key: nameKey, value: destroyName); |
319 | destroyMethodWithArgument.insert(key: accessKey, value: publicAccess); |
320 | QJsonObject delayArgument; |
321 | delayArgument.insert(key: nameKey, value: delayName); |
322 | delayArgument.insert(key: typeKey, value: intType); |
323 | QJsonArray destroyArguments; |
324 | destroyArguments.append(value: delayArgument); |
325 | destroyMethodWithArgument.insert(key: argumentsKey, value: destroyArguments); |
326 | componentMethods.append(value: destroyMethodWithArgument); |
327 | |
328 | writeMethods(methods: componentMethods, type: methodElement); |
329 | } else { |
330 | writeMethods(methods: members(classDef, origClassDef: &component, key: signalsKey), type: signalElement, notifySignals); |
331 | writeMethods(methods: members(classDef, origClassDef: &component, key: slotsKey), type: methodElement); |
332 | writeMethods(methods: members(classDef, origClassDef: &component, key: methodsKey), type: methodElement); |
333 | } |
334 | m_qml.writeEndObject(); |
335 | } |
336 | } |
337 | |
338 | void QmlTypesCreator::generate(const QString &outFileName, const QString &dependenciesFileName) |
339 | { |
340 | m_qml.writeStartDocument(); |
341 | m_qml.writeLibraryImport(uri: QLatin1String("QtQuick.tooling" ), majorVersion: 1, minorVersion: 2); |
342 | m_qml.write(data: QString::fromLatin1( |
343 | str: "\n// This file describes the plugin-supplied types contained in the library." |
344 | "\n// It is used for QML tooling purposes only." |
345 | "\n//" |
346 | "\n// This file was auto-generated by qmltyperegistrar.\n\n" )); |
347 | m_qml.writeStartObject(component: QLatin1String("Module" )); |
348 | |
349 | QStringList dependencies; |
350 | if (!dependenciesFileName.isEmpty()) { |
351 | QFile file(dependenciesFileName); |
352 | if (!file.open(flags: QIODevice::ReadOnly)) { |
353 | fprintf(stderr, format: "Failed to open %s\n" , qPrintable(dependenciesFileName)); |
354 | } else { |
355 | QJsonParseError error { .offset: -1, .error: QJsonParseError::NoError }; |
356 | QJsonDocument doc = QJsonDocument::fromJson(json: file.readAll(), error: &error); |
357 | if (error.error != QJsonParseError::NoError) { |
358 | fprintf(stderr, format: "Failed to parse %s\n" , qPrintable(dependenciesFileName)); |
359 | } else { |
360 | const QJsonArray array = doc.array(); |
361 | for (const QJsonValue &value : array) |
362 | dependencies.append(t: enquote(string: value.toString())); |
363 | } |
364 | } |
365 | } else { |
366 | // Default dependency is QtQuick 2.0 |
367 | dependencies.append(t: enquote(string: QLatin1String("QtQuick 2.0" ))); |
368 | } |
369 | |
370 | m_qml.writeArrayBinding(name: QLatin1String("dependencies" ), elements: dependencies); |
371 | |
372 | writeComponents(); |
373 | |
374 | m_qml.writeEndObject(); |
375 | |
376 | QSaveFile file(outFileName); |
377 | file.open(flags: QIODevice::WriteOnly); |
378 | file.write(data: m_output); |
379 | file.commit(); |
380 | } |
381 | |
382 | |