diff options
author | Kai Koehne <kai.koehne@nokia.com> | 2011-05-12 14:19:07 (GMT) |
---|---|---|
committer | Kai Koehne <kai.koehne@nokia.com> | 2011-05-12 14:19:07 (GMT) |
commit | 21711d7ad54b0ee83e776ac3ad83121221fdf24b (patch) | |
tree | eeb331ea0a428d878b25bacbdb701aa0be493f4e | |
parent | 5c830ed2a612b940c377fb4e8a2372655f175707 (diff) | |
parent | 5e8f97047a31a53a98c8bc71159e00cc56bda94d (diff) | |
download | Qt-21711d7ad54b0ee83e776ac3ad83121221fdf24b.zip Qt-21711d7ad54b0ee83e776ac3ad83121221fdf24b.tar.gz Qt-21711d7ad54b0ee83e776ac3ad83121221fdf24b.tar.bz2 |
Merge branch 'master' of scm.dev.nokia.troll.no:qt/qt-qml-review into master-qml-staging
-rw-r--r-- | doc/src/declarative/modules.qdoc | 123 | ||||
-rw-r--r-- | src/declarative/qml/qdeclarativedirparser.cpp | 17 | ||||
-rw-r--r-- | src/declarative/qml/qdeclarativedirparser_p.h | 16 | ||||
-rw-r--r-- | tools/qmlplugindump/Info.plist | 16 | ||||
-rw-r--r-- | tools/qmlplugindump/main.cpp | 597 | ||||
-rw-r--r-- | tools/qmlplugindump/qmlplugindump.pro | 20 | ||||
-rw-r--r-- | tools/qmlplugindump/qmlstreamwriter.cpp | 183 | ||||
-rw-r--r-- | tools/qmlplugindump/qmlstreamwriter.h | 79 | ||||
-rw-r--r-- | tools/tools.pro | 5 |
9 files changed, 1055 insertions, 1 deletions
diff --git a/doc/src/declarative/modules.qdoc b/doc/src/declarative/modules.qdoc index dbc8806..f2e24f2 100644 --- a/doc/src/declarative/modules.qdoc +++ b/doc/src/declarative/modules.qdoc @@ -310,6 +310,7 @@ It is defined by a plain text file named "qmldir" that contains one or more line <TypeName> [<InitialVersion>] <File> internal <TypeName> <File> plugin <Name> [<Path>] +typeinfo <File> \endcode \bold {# <Comment>} lines are used for comments. They are ignored by the QML engine. @@ -350,6 +351,14 @@ plugin file, or a relative path from the directory containing the \c qmldir file containing the plugin file. By default the engine searches for the plugin library in the directory that contains the \c qmldir file. The plugin search path can be queried with QDeclarativeEngine::pluginPathList() and modified using QDeclarativeEngine::addPluginPath(). When running the \l {QML Viewer}, use the \c -P option to add paths to the plugin search path. +\bold {typeinfo <File>} lines add \l{Writing a qmltypes file}{type description files} to +the module that can be read by QML tools such as Qt Creator to get information about the +types defined by the module's plugins. <File> is the (relative) file name of a .qmltypes +file. + +Without such a file QML tools may be unable to offer features such as code completion +for the types defined in your plugins. + \section1 Debugging @@ -358,5 +367,119 @@ when there are problems with finding and loading modules. See \l{Debugging module imports} for more information. +\section1 Writing a qmltypes file + +QML modules may refer to one or more type information files in their +\l{Writing a qmldir file}{qmldir} file. These usually have the .qmltypes +extension and are read by external tools to gain information about +types defined in plugins. + +As such qmltypes files have no effect on the functionality of a QML module. +Their only use is to allow tools such as Qt Creator to provide code completion, +error checking and other functionality to users of your module. + +Any module that uses plugins should also ship a type description file. + +The best way to create a qmltypes file for your module is to generate it +using the \c qmlplugindump tool that is provided with Qt. + +Example: +If your module is in \c /tmp/imports/My/Module, you could run +\code +qmlplugindump My.Module 1.0 /tmp/imports > /tmp/imports/My/Module/mymodule.qmltypes +\endcode +to generate type information for your module. Afterwards, add the line +\code +typeinfo mymodule.qmltypes +\endcode +to \c /tmp/imports/My/Module/qmldir to register it. + +While the qmldump tool covers most cases, it does not work if: +\list +\o The plugin uses a \l{QDeclarativeCustomParser}. The component that uses + the custom parser will not get its members documented. +\o The plugin can not be loaded. In particular if you cross-compiled + the plugin for a different architecture, qmldump will not be able to + load it. +\endlist + +In case you have to create a qmltypes file manually or need to adjust +an existing one, this is the file format: + +\qml +import QtQuick.tooling 1.0 + +// There always is a single Module object that contains all +// Component objects. +Module { + // A Component object directly corresponds to a type exported + // in a plugin with a call to qmlRegisterType. + Component { + + // The name is a unique identifier used to refer to this type. + // It is recommended you simply use the C++ type name. + name: "QDeclarativeAbstractAnimation" + + // The name of the prototype Component. + prototype: "QObject" + + // The name of the default property. + defaultProperty: "animations" + + // The name of the type containing attached properties + // and methods. + attachedType: "QDeclarativeAnimationAttached" + + // The list of exports determines how a type can be imported. + // Each string has the format "URI/Name version" and matches the + // arguments to qmlRegisterType. Usually types are only exported + // once, if at all. + // If the "URI/" part of the string is missing that means the + // type should be put into the package defined by the URI the + // module was imported with. + // For example if this module was imported with 'import Foo 4.8' + // the Animation object would be found in the package Foo and + // QtQuick. + exports: [ + "Animation 4.7", + "QtQuick/Animation 1.0" + ] + + Property { + name: "animations"; + type: "QDeclarativeAbstractAnimation" + // defaults to false, whether this property is read only + isReadonly: true + // defaults to false, whether the type of this property was a pointer in C++ + isPointer: true + // defaults to false: whether the type actually is a QDeclarativeListProperty<type> + isList: true + } + Property { name: "loops"; type: "int" } + Property { name: "name"; type: "string" } + Property { name: "loopsEnum"; type: "Loops" } + + Enum { + name: "Loops" + values: { + "Infinite": -2, + "OnceOnly": 1 + } + } + + // Signal and Method work the same way. The inner Parameter + // declarations also support the isReadonly, isPointer and isList + // attributes which mean the same as for Property + Method { name: "restart" } + Signal { name: "started" } + Signal { + name: "runningChanged" + Parameter { type: "bool" } + Parameter { name: "foo"; type: "bool" } + } + } +} +\endqml + */ / diff --git a/src/declarative/qml/qdeclarativedirparser.cpp b/src/declarative/qml/qdeclarativedirparser.cpp index b5ad33d..362b99c 100644 --- a/src/declarative/qml/qdeclarativedirparser.cpp +++ b/src/declarative/qml/qdeclarativedirparser.cpp @@ -160,6 +160,16 @@ bool QDeclarativeDirParser::parse() Component entry(sections[1], sections[2], -1, -1); entry.internal = true; _components.append(entry); + } else if (sections[0] == QLatin1String("typeinfo")) { + if (sectionCount != 2) { + reportError(lineNumber, -1, + QString::fromUtf8("typeinfo requires 1 argument, but %1 were provided").arg(sectionCount - 1)); + continue; + } +#ifdef QT_CREATOR + TypeInfo typeInfo(sections[1]); + _typeInfos.append(typeInfo); +#endif } else if (sectionCount == 2) { // No version specified (should only be used for relative qmldir files) @@ -229,4 +239,11 @@ QList<QDeclarativeDirParser::Component> QDeclarativeDirParser::components() cons return _components; } +#ifdef QT_CREATOR +QList<TypeInfo> QDeclarativeDirParser::typeInfos() const +{ + return _typeInfos; +} +#endif + QT_END_NAMESPACE diff --git a/src/declarative/qml/qdeclarativedirparser_p.h b/src/declarative/qml/qdeclarativedirparser_p.h index 95f14bc..d09b90e 100644 --- a/src/declarative/qml/qdeclarativedirparser_p.h +++ b/src/declarative/qml/qdeclarativedirparser_p.h @@ -109,6 +109,19 @@ public: QList<Component> components() const; QList<Plugin> plugins() const; +#ifdef QT_CREATOR + struct TypeInfo + { + TypeInfo() {} + TypeInfo(const QString &fileName) + : fileName(fileName) {} + + QString fileName; + }; + + QList<TypeInfo> typeInfos() const; +#endif + private: void reportError(int line, int column, const QString &message); @@ -118,6 +131,9 @@ private: QString _source; QList<Component> _components; QList<Plugin> _plugins; +#ifdef QT_CREATOR + QList<TypeInfo> _typeInfos; +#endif unsigned _isParsed: 1; }; diff --git a/tools/qmlplugindump/Info.plist b/tools/qmlplugindump/Info.plist new file mode 100644 index 0000000..f35846d --- /dev/null +++ b/tools/qmlplugindump/Info.plist @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundlePackageType</key> + <string>APPL</string> + <key>CFBundleSignature</key> + <string>@TYPEINFO@</string> + <key>CFBundleExecutable</key> + <string>@EXECUTABLE@</string> + <key>CFBundleIdentifier</key> + <string>com.nokia.qt.qmlplugindump</string> + <key>LSUIElement</key> + <string>1</string> +</dict> +</plist> diff --git a/tools/qmlplugindump/main.cpp b/tools/qmlplugindump/main.cpp new file mode 100644 index 0000000..848b091 --- /dev/null +++ b/tools/qmlplugindump/main.cpp @@ -0,0 +1,597 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtDeclarative/QtDeclarative> +#include <QtDeclarative/private/qdeclarativemetatype_p.h> +#include <QtDeclarative/private/qdeclarativeopenmetaobject_p.h> +#include <QtDeclarative/QDeclarativeView> + +#include <QtGui/QApplication> + +#include <QtCore/QSet> +#include <QtCore/QMetaObject> +#include <QtCore/QMetaProperty> +#include <QtCore/QDebug> +#include <QtCore/private/qobject_p.h> +#include <QtCore/private/qmetaobject_p.h> + +#include <iostream> + +#include "qmlstreamwriter.h" + +#ifdef QT_SIMULATOR +#include <QtGui/private/qsimulatorconnection_p.h> +#endif + +#ifdef Q_OS_UNIX +#include <signal.h> +#endif + +void collectReachableMetaObjects(const QMetaObject *meta, QSet<const QMetaObject *> *metas) +{ + if (! meta || metas->contains(meta)) + return; + + // dynamic meta objects break things badly, so just ignore them + const QMetaObjectPrivate *mop = reinterpret_cast<const QMetaObjectPrivate *>(meta->d.data); + if (!(mop->flags & DynamicMetaObject)) + metas->insert(meta); + + collectReachableMetaObjects(meta->superClass(), metas); +} + +QString currentProperty; + +void collectReachableMetaObjects(QObject *object, QSet<const QMetaObject *> *metas) +{ + if (! object) + return; + + const QMetaObject *meta = object->metaObject(); + qDebug() << "Processing object" << meta->className(); + collectReachableMetaObjects(meta, metas); + + for (int index = 0; index < meta->propertyCount(); ++index) { + QMetaProperty prop = meta->property(index); + if (QDeclarativeMetaType::isQObject(prop.userType())) { + qDebug() << " Processing property" << prop.name(); + currentProperty = QString("%1::%2").arg(meta->className(), prop.name()); + + // if the property was not initialized during construction, + // accessing a member of oo is going to cause a segmentation fault + QObject *oo = QDeclarativeMetaType::toQObject(prop.read(object)); + if (oo && !metas->contains(oo->metaObject())) + collectReachableMetaObjects(oo, metas); + currentProperty.clear(); + } + } +} + +void collectReachableMetaObjects(const QDeclarativeType *ty, QSet<const QMetaObject *> *metas) +{ + collectReachableMetaObjects(ty->metaObject(), metas); + if (ty->attachedPropertiesType()) + collectReachableMetaObjects(ty->attachedPropertiesType(), metas); +} + +/* We want to add the MetaObject for 'Qt' to the list, this is a + simple way to access it. +*/ +class FriendlyQObject: public QObject +{ +public: + static const QMetaObject *qtMeta() { return &staticQtMetaObject; } +}; + +/* When we dump a QMetaObject, we want to list all the types it is exported as. + To do this, we need to find the QDeclarativeTypes associated with this + QMetaObject. +*/ +static QHash<QByteArray, QSet<const QDeclarativeType *> > qmlTypesByCppName; + +static QHash<QByteArray, QByteArray> cppToId; + +/* Takes a C++ type name, such as Qt::LayoutDirection or QString and + maps it to how it should appear in the description file. + + These names need to be unique globally, so we don't change the C++ symbol's + name much. It is mostly used to for explicit translations such as + QString->string and translations for extended QML objects. +*/ +QByteArray convertToId(const QByteArray &cppName) +{ + return cppToId.value(cppName, cppName); +} + +QSet<const QMetaObject *> collectReachableMetaObjects(const QString &importCode, QDeclarativeEngine *engine) +{ + QSet<const QMetaObject *> metas; + metas.insert(FriendlyQObject::qtMeta()); + + QHash<QByteArray, QSet<QByteArray> > extensions; + foreach (const QDeclarativeType *ty, QDeclarativeMetaType::qmlTypes()) { + qmlTypesByCppName[ty->metaObject()->className()].insert(ty); + if (ty->isExtendedType()) { + extensions[ty->typeName()].insert(ty->metaObject()->className()); + } + collectReachableMetaObjects(ty, &metas); + } + + // Adjust ids of extended objects. + // The chain ends up being: + // __extended__.originalname - the base object + // __extension_0_.originalname - first extension + // .. + // __extension_n-2_.originalname - second to last extension + // originalname - last extension + // ### does this actually work for multiple extensions? it seems like the prototypes might be wrong + foreach (const QByteArray &extendedCpp, extensions.keys()) { + cppToId.remove(extendedCpp); + const QByteArray extendedId = convertToId(extendedCpp); + cppToId.insert(extendedCpp, "__extended__." + extendedId); + QSet<QByteArray> extensionCppNames = extensions.value(extendedCpp); + int c = 0; + foreach (const QByteArray &extensionCppName, extensionCppNames) { + if (c != extensionCppNames.size() - 1) { + QByteArray adjustedName = QString("__extension__%1.%2").arg(QString::number(c), QString(extendedId)).toAscii(); + cppToId.insert(extensionCppName, adjustedName); + } else { + cppToId.insert(extensionCppName, extendedId); + } + ++c; + } + } + + // find even more QMetaObjects by instantiating QML types and running + // over the instances + foreach (const QDeclarativeType *ty, QDeclarativeMetaType::qmlTypes()) { + if (ty->isExtendedType()) + continue; + + QByteArray tyName = ty->qmlTypeName(); + tyName = tyName.mid(tyName.lastIndexOf('/') + 1); + + QByteArray code = importCode.toUtf8(); + code += tyName; + code += " {}\n"; + + QDeclarativeComponent c(engine); + c.setData(code, QUrl("typeinstance")); + + QObject *object = c.create(); + if (object) + collectReachableMetaObjects(object, &metas); + else + qDebug() << "Could not create" << tyName << ":" << c.errorString(); + } + + return metas; +} + + +class Dumper +{ + QmlStreamWriter *qml; + QString relocatableModuleUri; + +public: + Dumper(QmlStreamWriter *qml) : qml(qml) {} + + void setRelocatableModuleUri(const QString &uri) + { + relocatableModuleUri = uri; + } + + void dump(const QMetaObject *meta) + { + qml->writeStartObject("Component"); + + QByteArray id = convertToId(meta->className()); + qml->writeScriptBinding(QLatin1String("name"), enquote(id)); + + for (int index = meta->classInfoCount() - 1 ; index >= 0 ; --index) { + QMetaClassInfo classInfo = meta->classInfo(index); + if (QLatin1String(classInfo.name()) == QLatin1String("DefaultProperty")) { + qml->writeScriptBinding(QLatin1String("defaultProperty"), enquote(QLatin1String(classInfo.value()))); + break; + } + } + + if (meta->superClass()) + qml->writeScriptBinding(QLatin1String("prototype"), enquote(convertToId(meta->superClass()->className()))); + + QSet<const QDeclarativeType *> qmlTypes = qmlTypesByCppName.value(meta->className()); + if (!qmlTypes.isEmpty()) { + QStringList exports; + + foreach (const QDeclarativeType *qmlTy, qmlTypes) { + QString qmlTyName = qmlTy->qmlTypeName(); + // some qmltype names are missing the actual names, ignore that import + if (qmlTyName.endsWith('/')) + continue; + if (qmlTyName.startsWith(relocatableModuleUri + QLatin1Char('/'))) { + qmlTyName.remove(0, relocatableModuleUri.size() + 1); + } + exports += enquote(QString("%1 %2.%3").arg( + qmlTyName, + QString::number(qmlTy->majorVersion()), + QString::number(qmlTy->minorVersion()))); + } + + // ensure exports are sorted and don't change order when the plugin is dumped again + exports.removeDuplicates(); + qSort(exports); + + qml->writeArrayBinding(QLatin1String("exports"), exports); + + if (const QMetaObject *attachedType = (*qmlTypes.begin())->attachedPropertiesType()) { + qml->writeScriptBinding(QLatin1String("attachedType"), enquote( + convertToId(attachedType->className()))); + } + } + + for (int index = meta->enumeratorOffset(); index < meta->enumeratorCount(); ++index) + dump(meta->enumerator(index)); + + for (int index = meta->propertyOffset(); index < meta->propertyCount(); ++index) + dump(meta->property(index)); + + for (int index = meta->methodOffset(); index < meta->methodCount(); ++index) + dump(meta->method(index)); + + qml->writeEndObject(); + } + + void writeEasingCurve() + { + qml->writeStartObject("Component"); + qml->writeScriptBinding(QLatin1String("name"), enquote(QLatin1String("QEasingCurve"))); + qml->writeScriptBinding(QLatin1String("prototype"), enquote(QLatin1String("QDeclarativeEasingValueType"))); + qml->writeEndObject(); + } + +private: + static QString enquote(const QString &string) + { + return QString("\"%1\"").arg(string); + } + + /* Removes pointer and list annotations from a type name, returning + what was removed in isList and isPointer + */ + static void removePointerAndList(QByteArray *typeName, bool *isList, bool *isPointer) + { + static QByteArray declListPrefix = "QDeclarativeListProperty<"; + + if (typeName->endsWith('*')) { + *isPointer = true; + typeName->truncate(typeName->length() - 1); + removePointerAndList(typeName, isList, isPointer); + } else if (typeName->startsWith(declListPrefix)) { + *isList = true; + typeName->truncate(typeName->length() - 1); // get rid of the suffix '>' + *typeName = typeName->mid(declListPrefix.size()); + removePointerAndList(typeName, isList, isPointer); + } + + *typeName = convertToId(*typeName); + } + + void writeTypeProperties(QByteArray typeName, bool isWritable) + { + bool isList = false, isPointer = false; + removePointerAndList(&typeName, &isList, &isPointer); + + qml->writeScriptBinding(QLatin1String("type"), enquote(typeName)); + if (isList) + qml->writeScriptBinding(QLatin1String("isList"), QLatin1String("true")); + if (!isWritable) + qml->writeScriptBinding(QLatin1String("isReadonly"), QLatin1String("true")); + if (isPointer) + qml->writeScriptBinding(QLatin1String("isPointer"), QLatin1String("true")); + } + + void dump(const QMetaProperty &prop) + { + qml->writeStartObject("Property"); + + qml->writeScriptBinding(QLatin1String("name"), enquote(QString::fromUtf8(prop.name()))); + writeTypeProperties(prop.typeName(), prop.isWritable()); + + qml->writeEndObject(); + } + + void dump(const QMetaMethod &meth) + { + if (meth.methodType() == QMetaMethod::Signal) { + if (meth.access() != QMetaMethod::Protected) + return; // nothing to do. + } else if (meth.access() != QMetaMethod::Public) { + return; // nothing to do. + } + + QByteArray name = meth.signature(); + int lparenIndex = name.indexOf('('); + if (lparenIndex == -1) { + return; // invalid signature + } + name = name.left(lparenIndex); + + if (meth.methodType() == QMetaMethod::Signal) + qml->writeStartObject(QLatin1String("Signal")); + else + qml->writeStartObject(QLatin1String("Method")); + + qml->writeScriptBinding(QLatin1String("name"), enquote(name)); + + const QString typeName = convertToId(meth.typeName()); + if (! typeName.isEmpty()) + qml->writeScriptBinding(QLatin1String("type"), enquote(typeName)); + + for (int i = 0; i < meth.parameterTypes().size(); ++i) { + QByteArray argName = meth.parameterNames().at(i); + + qml->writeStartObject(QLatin1String("Parameter")); + if (! argName.isEmpty()) + qml->writeScriptBinding(QLatin1String("name"), enquote(argName)); + writeTypeProperties(meth.parameterTypes().at(i), true); + qml->writeEndObject(); + } + + qml->writeEndObject(); + } + + void dump(const QMetaEnum &e) + { + qml->writeStartObject(QLatin1String("Enum")); + qml->writeScriptBinding(QLatin1String("name"), enquote(QString::fromUtf8(e.name()))); + + QList<QPair<QString, QString> > namesValues; + for (int index = 0; index < e.keyCount(); ++index) { + namesValues.append(qMakePair(enquote(QString::fromUtf8(e.key(index))), QString::number(e.value(index)))); + } + + qml->writeScriptObjectLiteralBinding(QLatin1String("values"), namesValues); + qml->writeEndObject(); + } +}; + + +enum ExitCode { + EXIT_INVALIDARGUMENTS = 1, + EXIT_SEGV = 2, + EXIT_IMPORTERROR = 3 +}; + +#ifdef Q_OS_UNIX +void sigSegvHandler(int) { + fprintf(stderr, "Error: SEGV\n"); + if (!currentProperty.isEmpty()) + fprintf(stderr, "While processing the property '%s', which probably has uninitialized data.\n", currentProperty.toLatin1().constData()); + exit(EXIT_SEGV); +} +#endif + +void printUsage(const QString &appName) +{ + qWarning() << qPrintable(QString( + "Usage: %1 [-notrelocatable] module.uri version [module/import/path]\n" + " %1 -path path/to/qmldir/directory [version]\n" + " %1 -builtins\n" + "Example: %1 Qt.labs.particles 4.7 /home/user/dev/qt-install/imports").arg( + appName)); +} + +int main(int argc, char *argv[]) +{ +#ifdef Q_OS_UNIX + // qmldump may crash, but we don't want any crash handlers to pop up + // therefore we intercept the segfault and just exit() ourselves + struct sigaction action; + + sigemptyset(&action.sa_mask); + action.sa_handler = &sigSegvHandler; + action.sa_flags = 0; + + sigaction(SIGSEGV, &action, 0); +#endif + +#ifdef QT_SIMULATOR + // Running this application would bring up the Qt Simulator (since it links QtGui), avoid that! + QtSimulatorPrivate::SimulatorConnection::createStubInstance(); +#endif + QApplication app(argc, argv); + const QStringList args = app.arguments(); + const QString appName = QFileInfo(app.applicationFilePath()).baseName(); + if (!(args.size() >= 3 + || (args.size() == 2 + && (args.at(1) == QLatin1String("--builtins") + || args.at(1) == QLatin1String("-builtins"))))) { + printUsage(appName); + return EXIT_INVALIDARGUMENTS; + } + + QString pluginImportUri; + QString pluginImportVersion; + QString pluginImportPath; + bool relocatable = true; + bool pathImport = false; + if (args.size() >= 3) { + QStringList positionalArgs; + foreach (const QString &arg, args) { + if (!arg.startsWith(QLatin1Char('-'))) { + positionalArgs.append(arg); + continue; + } + + if (arg == QLatin1String("--notrelocatable") + || arg == QLatin1String("-notrelocatable")) { + relocatable = false; + } else if (arg == QLatin1String("--path") + || arg == QLatin1String("-path")) { + pathImport = true; + } else { + qWarning() << "Invalid argument: " << arg; + return EXIT_INVALIDARGUMENTS; + } + } + + if (!pathImport) { + if (positionalArgs.size() != 3 && positionalArgs.size() != 4) { + qWarning() << "Incorrect number of positional arguments"; + return EXIT_INVALIDARGUMENTS; + } + pluginImportUri = positionalArgs[1]; + pluginImportVersion = positionalArgs[2]; + if (positionalArgs.size() >= 4) + pluginImportPath = positionalArgs[3]; + } else { + if (positionalArgs.size() != 2 && positionalArgs.size() != 3) { + qWarning() << "Incorrect number of positional arguments"; + return EXIT_INVALIDARGUMENTS; + } + pluginImportPath = positionalArgs[1]; + if (positionalArgs.size() == 3) + pluginImportVersion = positionalArgs[2]; + } + } + + QDeclarativeView view; + QDeclarativeEngine *engine = view.engine(); + if (!pluginImportPath.isEmpty()) + engine->addImportPath(pluginImportPath); + + // find all QMetaObjects reachable from the builtin module + QByteArray importCode("import QtQuick 1.0\n"); + QSet<const QMetaObject *> defaultReachable = collectReachableMetaObjects(importCode, engine); + + // this will hold the meta objects we want to dump information of + QSet<const QMetaObject *> metas; + + if (pluginImportUri.isEmpty() && !pathImport) { + metas = defaultReachable; + } else { + // find all QMetaObjects reachable when the specified module is imported + if (!pathImport) { + importCode += QString("import %0 %1\n").arg(pluginImportUri, pluginImportVersion).toAscii(); + } else { + // pluginImportVersion can be empty + importCode += QString("import \"%1\" %2\n").arg(pluginImportPath, pluginImportVersion).toAscii(); + } + + // create a component with these imports to make sure the imports are valid + // and to populate the declarative meta type system + { + QByteArray code = importCode; + code += "QtObject {}"; + QDeclarativeComponent c(engine); + + c.setData(code, QUrl("typelist")); + c.create(); + if (!c.errors().isEmpty()) { + foreach (const QDeclarativeError &error, c.errors()) + qWarning() << error.toString(); + return EXIT_IMPORTERROR; + } + } + + QSet<const QMetaObject *> candidates = collectReachableMetaObjects(importCode, engine); + candidates.subtract(defaultReachable); + + // Also eliminate meta objects with the same classname. + // This is required because extended objects seem not to share + // a single meta object instance. + QSet<QByteArray> defaultReachableNames; + foreach (const QMetaObject *mo, defaultReachable) + defaultReachableNames.insert(QByteArray(mo->className())); + foreach (const QMetaObject *mo, candidates) { + if (!defaultReachableNames.contains(mo->className())) + metas.insert(mo); + } + } + + // setup static rewrites of type names + cppToId.insert("QString", "string"); + cppToId.insert("QDeclarativeEasingValueType::Type", "Type"); + + // start dumping data + QByteArray bytes; + QmlStreamWriter qml(&bytes); + + qml.writeStartDocument(); + qml.writeLibraryImport(QLatin1String("QtQuick.tooling"), 1, 0); + qml.write("\n" + "// This file describes the plugin-supplied types contained in the library.\n" + "// It is used for QML tooling purposes only.\n" + "\n"); + qml.writeStartObject("Module"); + + // put the metaobjects into a map so they are always dumped in the same order + QMap<QString, const QMetaObject *> nameToMeta; + foreach (const QMetaObject *meta, metas) + nameToMeta.insert(convertToId(meta->className()), meta); + + Dumper dumper(&qml); + if (relocatable) + dumper.setRelocatableModuleUri(pluginImportUri); + foreach (const QMetaObject *meta, nameToMeta) { + dumper.dump(meta); + } + + // define QEasingCurve as an extension of QDeclarativeEasingValueType, this way + // properties using the QEasingCurve type get useful type information. + if (pluginImportUri.isEmpty()) + dumper.writeEasingCurve(); + + qml.writeEndObject(); + qml.writeEndDocument(); + + std::cout << bytes.constData(); + + // workaround to avoid crashes on exit + QTimer timer; + timer.setSingleShot(true); + timer.setInterval(0); + QObject::connect(&timer, SIGNAL(timeout()), &app, SLOT(quit())); + timer.start(); + + return app.exec(); +} diff --git a/tools/qmlplugindump/qmlplugindump.pro b/tools/qmlplugindump/qmlplugindump.pro new file mode 100644 index 0000000..53827e2 --- /dev/null +++ b/tools/qmlplugindump/qmlplugindump.pro @@ -0,0 +1,20 @@ +TEMPLATE = app +CONFIG += qt uic console +DESTDIR = ../../bin + +QT += declarative + +TARGET = qmlplugindump + +SOURCES += \ + main.cpp \ + qmlstreamwriter.cpp + +HEADERS += \ + qmlstreamwriter.h + +OTHER_FILES += Info.plist +macx: QMAKE_INFO_PLIST = Info.plist + +target.path = $$[QT_INSTALL_BINS] +INSTALLS += target diff --git a/tools/qmlplugindump/qmlstreamwriter.cpp b/tools/qmlplugindump/qmlstreamwriter.cpp new file mode 100644 index 0000000..d083f7b --- /dev/null +++ b/tools/qmlplugindump/qmlstreamwriter.cpp @@ -0,0 +1,183 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qmlstreamwriter.h" + +#include <QtCore/QBuffer> +#include <QtCore/QStringList> + +QmlStreamWriter::QmlStreamWriter(QByteArray *array) + : m_indentDepth(0) + , m_pendingLineLength(0) + , m_maybeOneline(false) + , m_stream(new QBuffer(array)) +{ + m_stream->open(QIODevice::WriteOnly); +} + +void QmlStreamWriter::writeStartDocument() +{ +} + +void QmlStreamWriter::writeEndDocument() +{ +} + +void QmlStreamWriter::writeLibraryImport(const QString &uri, int majorVersion, int minorVersion, const QString &as) +{ + m_stream->write(QString("import %1 %2.%3").arg(uri, QString::number(majorVersion), QString::number(minorVersion)).toUtf8()); + if (!as.isEmpty()) + m_stream->write(QString(" as %1").arg(as).toUtf8()); + m_stream->write("\n"); +} + +void QmlStreamWriter::writeStartObject(const QString &component) +{ + flushPotentialLinesWithNewlines(); + writeIndent(); + m_stream->write(QString("%1 {").arg(component).toUtf8()); + ++m_indentDepth; + m_maybeOneline = true; +} + +void QmlStreamWriter::writeEndObject() +{ + if (m_maybeOneline && !m_pendingLines.isEmpty()) { + --m_indentDepth; + for (int i = 0; i < m_pendingLines.size(); ++i) { + m_stream->write(" "); + m_stream->write(m_pendingLines.at(i).trimmed()); + if (i != m_pendingLines.size() - 1) + m_stream->write(";"); + } + m_stream->write(" }\n"); + m_pendingLines.clear(); + m_pendingLineLength = 0; + m_maybeOneline = false; + } else { + if (m_maybeOneline) + flushPotentialLinesWithNewlines(); + --m_indentDepth; + writeIndent(); + m_stream->write("}\n"); + } +} + +void QmlStreamWriter::writeScriptBinding(const QString &name, const QString &rhs) +{ + writePotentialLine(QString("%1: %2").arg(name, rhs).toUtf8()); +} + +void QmlStreamWriter::writeArrayBinding(const QString &name, const QStringList &elements) +{ + flushPotentialLinesWithNewlines(); + writeIndent(); + m_stream->write(QString("%1: [\n").arg(name).toUtf8()); + ++m_indentDepth; + for (int i = 0; i < elements.size(); ++i) { + writeIndent(); + m_stream->write(elements.at(i).toUtf8()); + if (i != elements.size() - 1) { + m_stream->write(",\n"); + } else { + m_stream->write("\n"); + } + } + --m_indentDepth; + writeIndent(); + m_stream->write("]\n"); +} + +void QmlStreamWriter::write(const QString &data) +{ + flushPotentialLinesWithNewlines(); + m_stream->write(data.toUtf8()); +} + +void QmlStreamWriter::writeScriptObjectLiteralBinding(const QString &name, const QList<QPair<QString, QString> > &keyValue) +{ + flushPotentialLinesWithNewlines(); + writeIndent(); + m_stream->write(QString("%1: {\n").arg(name).toUtf8()); + ++m_indentDepth; + for (int i = 0; i < keyValue.size(); ++i) { + const QString key = keyValue.at(i).first; + const QString value = keyValue.at(i).second; + writeIndent(); + m_stream->write(QString("%1: %2").arg(key, value).toUtf8()); + if (i != keyValue.size() - 1) { + m_stream->write(",\n"); + } else { + m_stream->write("\n"); + } + } + --m_indentDepth; + writeIndent(); + m_stream->write("}\n"); +} + +void QmlStreamWriter::writeIndent() +{ + m_stream->write(QByteArray(m_indentDepth * 4, ' ')); +} + +void QmlStreamWriter::writePotentialLine(const QByteArray &line) +{ + m_pendingLines.append(line); + m_pendingLineLength += line.size(); + if (m_pendingLineLength >= 80) { + flushPotentialLinesWithNewlines(); + } +} + +void QmlStreamWriter::flushPotentialLinesWithNewlines() +{ + if (m_maybeOneline) + m_stream->write("\n"); + foreach (const QByteArray &line, m_pendingLines) { + writeIndent(); + m_stream->write(line); + m_stream->write("\n"); + } + m_pendingLines.clear(); + m_pendingLineLength = 0; + m_maybeOneline = false; +} diff --git a/tools/qmlplugindump/qmlstreamwriter.h b/tools/qmlplugindump/qmlstreamwriter.h new file mode 100644 index 0000000..cd73aad --- /dev/null +++ b/tools/qmlplugindump/qmlstreamwriter.h @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QMLSTREAMWRITER_H +#define QMLSTREAMWRITER_H + +#include <QtCore/QIODevice> +#include <QtCore/QList> +#include <QtCore/QString> +#include <QtCore/QScopedPointer> +#include <QtCore/QPair> + +class QmlStreamWriter +{ +public: + QmlStreamWriter(QByteArray *array); + + void writeStartDocument(); + void writeEndDocument(); + void writeLibraryImport(const QString &uri, int majorVersion, int minorVersion, const QString &as = QString()); + //void writeFilesystemImport(const QString &file, const QString &as = QString()); + void writeStartObject(const QString &component); + void writeEndObject(); + void writeScriptBinding(const QString &name, const QString &rhs); + void writeScriptObjectLiteralBinding(const QString &name, const QList<QPair<QString, QString> > &keyValue); + void writeArrayBinding(const QString &name, const QStringList &elements); + void write(const QString &data); + +private: + void writeIndent(); + void writePotentialLine(const QByteArray &line); + void flushPotentialLinesWithNewlines(); + + int m_indentDepth; + QList<QByteArray> m_pendingLines; + int m_pendingLineLength; + bool m_maybeOneline; + QScopedPointer<QIODevice> m_stream; +}; + +#endif // QMLSTREAMWRITER_H diff --git a/tools/tools.pro b/tools/tools.pro index f090b86..7eecebd 100644 --- a/tools/tools.pro +++ b/tools/tools.pro @@ -23,7 +23,10 @@ TEMPLATE = subdirs unix:!symbian:!mac:!embedded:!qpa:SUBDIRS += qtconfig win32:!wince*:SUBDIRS += activeqt } - contains(QT_CONFIG, declarative):SUBDIRS += qml + contains(QT_CONFIG, declarative) { + SUBDIRS += qml + !symbian: SUBDIRS += qmlplugindump + } } !wince*:!symbian:SUBDIRS += linguist |