| 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 | |