diff --git a/src/imports/folderlistmodel/qquickfolderlistmodel.cpp b/src/imports/folderlistmodel/qquickfolderlistmodel.cpp index b937fbdbc3592bed6ffd9812af2208735a9184a4..218d7955e1955da5a851119d24f4fd3d0b4ee970 100644 --- a/src/imports/folderlistmodel/qquickfolderlistmodel.cpp +++ b/src/imports/folderlistmodel/qquickfolderlistmodel.cpp @@ -44,7 +44,7 @@ #include "fileinfothread_p.h" #include "fileproperty_p.h" #include <qqmlcontext.h> -#include <private/qqmlengine_p.h> +#include <qqmlfile.h> QT_BEGIN_NAMESPACE @@ -369,7 +369,7 @@ void QQuickFolderListModel::setFolder(const QUrl &folder) if (folder == d->currentDir) return; - QString localPath = QQmlEnginePrivate::urlToLocalFileOrQrc(folder); + QString localPath = QQmlFile::urlToLocalFileOrQrc(folder); QString resolvedPath = QDir::cleanPath(QUrl(localPath).path()); beginResetModel(); @@ -412,7 +412,7 @@ void QQuickFolderListModel::setRootFolder(const QUrl &path) if (path.isEmpty()) return; - QString localPath = QQmlEnginePrivate::urlToLocalFileOrQrc(path); + QString localPath = QQmlFile::urlToLocalFileOrQrc(path); QString resolvedPath = QDir::cleanPath(QUrl(localPath).path()); QFileInfo info(resolvedPath); diff --git a/src/qml/qml/parser/parser.pri b/src/qml/qml/parser/parser.pri index 6be85ba85a311d5f28f39d30de36bd102c25cd97..7dba8bc92ac3bde934702ad1aabd53e350c46890 100644 --- a/src/qml/qml/parser/parser.pri +++ b/src/qml/qml/parser/parser.pri @@ -8,7 +8,7 @@ HEADERS += \ $$PWD/qqmljsmemorypool_p.h \ $$PWD/qqmljsparser_p.h \ $$PWD/qqmljsglobal_p.h \ - $$PWD/qqmljskeywords_p.h + $$PWD/qqmljskeywords_p.h \ SOURCES += \ $$PWD/qqmljsast.cpp \ @@ -16,4 +16,4 @@ SOURCES += \ $$PWD/qqmljsengine_p.cpp \ $$PWD/qqmljsgrammar.cpp \ $$PWD/qqmljslexer.cpp \ - $$PWD/qqmljsparser.cpp + $$PWD/qqmljsparser.cpp \ diff --git a/src/qml/qml/qml.pri b/src/qml/qml/qml.pri index 1237740f31fd2f539e9db81c8ad3586c0f44777b..30acea132130e06965fe5864c5b383143f293af7 100644 --- a/src/qml/qml/qml.pri +++ b/src/qml/qml/qml.pri @@ -51,6 +51,8 @@ SOURCES += \ $$PWD/qqmlabstractbinding.cpp \ $$PWD/qqmlvaluetypeproxybinding.cpp \ $$PWD/qqmlglobal.cpp \ + $$PWD/qqmlfile.cpp \ + $$PWD/qqmlbundle.cpp \ HEADERS += \ $$PWD/qqmlglobal_p.h \ @@ -122,6 +124,8 @@ HEADERS += \ $$PWD/qqmljavascriptexpression_p.h \ $$PWD/qqmlabstractbinding_p.h \ $$PWD/qqmlvaluetypeproxybinding_p.h \ + $$PWD/qqmlfile.h \ + $$PWD/qqmlbundle_p.h \ include(parser/parser.pri) include(rewriter/rewriter.pri) diff --git a/src/qml/qml/qqmlbundle.cpp b/src/qml/qml/qqmlbundle.cpp new file mode 100644 index 0000000000000000000000000000000000000000..fa47cb505d22f3f0f9e408c3f08238ded5f2a864 --- /dev/null +++ b/src/qml/qml/qqmlbundle.cpp @@ -0,0 +1,319 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qqmlbundle_p.h" +#include <QtCore/QtCore> +#include <iostream> +#include <cstdlib> + +static const unsigned char qmlBundleHeaderData[] = { 255, 'q', 'm', 'l', 'd', 'i', 'r', 255 }; +static const unsigned int qmlBundleHeaderLength = 8; + +// +// Entries +// +QString QQmlBundle::FileEntry::fileName() const +{ + return QString((QChar *)&data[0], fileNameLength / sizeof(QChar)); +} + +bool QQmlBundle::FileEntry::isFileName(const QString &fileName) const +{ + return fileName.length() * sizeof(QChar) == (unsigned)fileNameLength && + 0 == ::memcmp(fileName.constData(), &data[0], fileNameLength); +} + +const char *QQmlBundle::FileEntry::contents() const { + return &data[fileNameLength]; +} + +quint64 QQmlBundle::FileEntry::fileSize() const +{ + return size - (sizeof(FileEntry) + fileNameLength); +} + + +// +// QQmlBundle +// +QQmlBundle::QQmlBundle(const QString &fileName) +: file(fileName), + buffer(0), + bufferSize(0), + opened(false), + headerWritten(false) +{ +} + +QQmlBundle::~QQmlBundle() +{ + close(); +} + +bool QQmlBundle::open(QIODevice::OpenMode mode) +{ + if (!opened) { + if (!file.open(mode)) + return false; + + bufferSize = file.size(); + buffer = file.map(0, bufferSize); + + if (bufferSize == 0 || + (bufferSize >= 8 && 0 == ::memcmp(buffer, qmlBundleHeaderData, qmlBundleHeaderLength))) { + opened = true; + headerWritten = false; + return true; + } else { + close(); + return false; + } + } + return true; +} + +void QQmlBundle::close() +{ + if (opened) { + opened = false; + headerWritten = false; + file.unmap(buffer); + file.close(); + } +} + +QList<const QQmlBundle::FileEntry *> QQmlBundle::files() const +{ + QList<const FileEntry *> files; + const char *ptr = (const char *) buffer + qmlBundleHeaderLength; + const char *end = (const char *) buffer + bufferSize; + + while (ptr < end) { + const Entry *cmd = (const Entry *) ptr; + + switch (static_cast<Entry::Kind>(cmd->kind)) { + case Entry::File: { + const FileEntry *f = reinterpret_cast<const FileEntry *>(cmd); + files.append(f); + } break; + + case Entry::Link: + case Entry::Skip: { + // Skip + } break; + + default: + // throw an error + return QList<const FileEntry *>(); + } // switch + + ptr += cmd->size; + Q_ASSERT(ptr <= end); // throw an error + } + return files; +} + +void QQmlBundle::remove(const FileEntry *entry) +{ + Q_ASSERT(entry->kind == Entry::File); // ### throw an error + Q_ASSERT(file.isWritable()); + const_cast<FileEntry *>(entry)->kind = Entry::Skip; +} + +int QQmlBundle::bundleHeaderLength() +{ + return qmlBundleHeaderLength; +} + +bool QQmlBundle::isBundleHeader(const char *data, int size) +{ + if ((unsigned int)size < qmlBundleHeaderLength) + return false; + + return 0 == ::memcmp(data, qmlBundleHeaderData, qmlBundleHeaderLength); +} + +// +// find a some empty space we can use to insert new entries. +// +const QQmlBundle::Entry *QQmlBundle::findInsertPoint(quint64 size, qint64 *offset) +{ + const char *ptr = (const char *) buffer + qmlBundleHeaderLength; + const char *end = (const char *) buffer + bufferSize; + + while (ptr < end) { + const Entry *cmd = (const Entry *) ptr; + + if (cmd->kind == Entry::Skip && size + sizeof(RawEntry) < cmd->size) { + *offset = ptr - ((const char *) buffer + qmlBundleHeaderLength); + return cmd; + } + + ptr += cmd->size; + Q_ASSERT(ptr <= end); // throw an error + } + + return 0; +} + +const QQmlBundle::FileEntry *QQmlBundle::find(const QString &fileName) const +{ + const char *ptr = (const char *) buffer + qmlBundleHeaderLength; + const char *end = (const char *) buffer + bufferSize; + + while (ptr < end) { + const Entry *cmd = (const Entry *) ptr; + + if (cmd->kind == Entry::File) { + const FileEntry *fileEntry = static_cast<const FileEntry *>(cmd); + + if (fileEntry->isFileName(fileName)) + return fileEntry; + } + + ptr += cmd->size; + Q_ASSERT(ptr <= end); // throw an error + } + + return 0; +} + +const QQmlBundle::FileEntry *QQmlBundle::link(const FileEntry *entry, const QString &linkName) const +{ + const char *ptr = (const char *) buffer + entry->link; + + while (ptr != (const char *)buffer) { + const Entry *cmd = (const Entry *) ptr; + Q_ASSERT(cmd->kind == Entry::Link); + + const FileEntry *fileEntry = static_cast<const FileEntry *>(cmd); + if (fileEntry->fileName() == linkName) + return fileEntry; + + ptr = (const char *) buffer + fileEntry->link; + } + + return 0; +} + +const QQmlBundle::FileEntry *QQmlBundle::find(const QChar *fileName, int length) const +{ + return find(QString::fromRawData(fileName, length)); +} + +bool QQmlBundle::add(const QString &name, const QString &fileName) +{ + if (!file.isWritable()) + return false; + else if (find(fileName)) + return false; + + QFile inputFile(fileName); + if (!inputFile.open(QFile::ReadOnly)) + return false; + + // ### use best-fit algorithm + if (!file.atEnd()) + file.seek(file.size()); + + FileEntry cmd; + const quint64 inputFileSize = inputFile.size(); + + cmd.kind = Entry::File; + cmd.link = 0; + cmd.size = sizeof(FileEntry) + name.length() * sizeof(QChar) + inputFileSize; + cmd.fileNameLength = name.length() * sizeof(QChar); + + if (bufferSize == 0 && headerWritten == false) { + file.write((const char *)qmlBundleHeaderData, qmlBundleHeaderLength); + headerWritten = true; + } + + file.write((const char *) &cmd, sizeof(FileEntry)); + file.write((const char *) name.constData(), name.length() * sizeof(QChar)); + + uchar *source = inputFile.map(0, inputFileSize); + file.write((const char *) source, inputFileSize); + inputFile.unmap(source); + return true; +} + +bool QQmlBundle::add(const QString &fileName) +{ + return add(fileName, fileName); +} + +bool QQmlBundle::addMetaLink(const QString &fileName, + const QString &linkName, + const QByteArray &data) +{ + if (!file.isWritable()) + return false; + + const FileEntry *fileEntry = find(fileName); + if (!fileEntry) + return false; + + // ### use best-fit algorithm + if (!file.atEnd()) + file.seek(file.size()); + + FileEntry cmd; + + const quint64 inputFileSize = data.size(); + + cmd.kind = Entry::Link; + cmd.link = fileEntry->link; + cmd.size = sizeof(FileEntry) + linkName.length() * sizeof(QChar) + inputFileSize; + cmd.fileNameLength = linkName.length() * sizeof(QChar); + + if (bufferSize == 0 && headerWritten == false) { + file.write((const char *)qmlBundleHeaderData, qmlBundleHeaderLength); + headerWritten = true; + } + + const_cast<FileEntry *>(fileEntry)->link = file.size(); + + file.write((const char *) &cmd, sizeof(FileEntry)); + file.write((const char *) linkName.constData(), linkName.length() * sizeof(QChar)); + file.write((const char *) data.constData(), inputFileSize); + return true; +} diff --git a/src/qml/qml/qqmlbundle_p.h b/src/qml/qml/qqmlbundle_p.h new file mode 100644 index 0000000000000000000000000000000000000000..029acf1f1ba99976526a043eecd9ecac20f85250 --- /dev/null +++ b/src/qml/qml/qqmlbundle_p.h @@ -0,0 +1,122 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQMLBUNDLE_P_H +#define QQMLBUNDLE_P_H + +#include <QtCore/qfile.h> +#include <QtCore/qstring.h> +#include <QtQml/qtqmlglobal.h> + +QT_BEGIN_NAMESPACE + +class Q_QML_EXPORT QQmlBundle +{ + Q_DISABLE_COPY(QQmlBundle) +public: + struct Q_PACKED Q_QML_EXPORT Entry + { + enum Kind { + File = 123, // Normal file + Skip, // Empty space + Link // A meta data linked file + + // ### add entries for qmldir, index, ... + }; + + int kind; + quint64 size; + }; + + struct Q_PACKED Q_QML_EXPORT RawEntry : public Entry + { + char data[]; // trailing data + }; + + struct Q_PACKED Q_QML_EXPORT FileEntry : public Entry + { + quint64 link; + int fileNameLength; + char data[]; // trailing data + + QString fileName() const; + bool isFileName(const QString &) const; + + quint64 fileSize() const; + const char *contents() const; + }; + + QQmlBundle(const QString &fileName); + ~QQmlBundle(); + + bool open(QIODevice::OpenMode mode = QIODevice::ReadWrite); + void close(); + + QList<const FileEntry *> files() const; + void remove(const FileEntry *entry); + bool add(const QString &fileName); + bool add(const QString &name, const QString &fileName); + + bool addMetaLink(const QString &fileName, + const QString &linkName, + const QByteArray &data); + + const FileEntry *find(const QString &fileName) const; + const FileEntry *find(const QChar *fileName, int length) const; + + const FileEntry *link(const FileEntry *, const QString &linkName) const; + + static int bundleHeaderLength(); + static bool isBundleHeader(const char *, int size); +private: + const Entry *findInsertPoint(quint64 size, qint64 *offset); + +private: + QFile file; + uchar *buffer; + quint64 bufferSize; + bool opened:1; + bool headerWritten:1; +}; + +QT_END_NAMESPACE + +#endif // QQMLBUNDLE_P_H diff --git a/src/qml/qml/qqmlcompiler.cpp b/src/qml/qml/qqmlcompiler.cpp index 8c47c7c0c5b9ea5bb8aeeff45d55841c2bc4c067..5708a84fd998a113eae3d124062750fffd290108 100644 --- a/src/qml/qml/qqmlcompiler.cpp +++ b/src/qml/qml/qqmlcompiler.cpp @@ -1742,7 +1742,7 @@ bool QQmlCompiler::buildProperty(QQmlScript::Property *prop, } QQmlType *type = 0; - QQmlImportedNamespace *typeNamespace = 0; + QQmlImportNamespace *typeNamespace = 0; unit->imports().resolveType(prop->name().toString(), &type, 0, 0, 0, &typeNamespace); if (typeNamespace) { @@ -1849,10 +1849,10 @@ bool QQmlCompiler::buildProperty(QQmlScript::Property *prop, return true; } -bool QQmlCompiler::buildPropertyInNamespace(QQmlImportedNamespace *ns, - QQmlScript::Property *nsProp, - QQmlScript::Object *obj, - const BindingContext &ctxt) +bool QQmlCompiler::buildPropertyInNamespace(QQmlImportNamespace *ns, + QQmlScript::Property *nsProp, + QQmlScript::Object *obj, + const BindingContext &ctxt) { if (!nsProp->value) COMPILE_EXCEPTION(nsProp, tr("Invalid use of namespace")); diff --git a/src/qml/qml/qqmlcompiler_p.h b/src/qml/qml/qqmlcompiler_p.h index 3b6fdf1473c99a7dbc8e6197578467547666482f..5a52dcf4031039f70af773768ff72bf49772ac08 100644 --- a/src/qml/qml/qqmlcompiler_p.h +++ b/src/qml/qml/qqmlcompiler_p.h @@ -318,7 +318,7 @@ private: const QQmlCompilerTypes::BindingContext &); bool buildProperty(QQmlScript::Property *prop, QQmlScript::Object *obj, const QQmlCompilerTypes::BindingContext &); - bool buildPropertyInNamespace(QQmlImportedNamespace *ns, + bool buildPropertyInNamespace(QQmlImportNamespace *ns, QQmlScript::Property *prop, QQmlScript::Object *obj, const QQmlCompilerTypes::BindingContext &); diff --git a/src/qml/qml/qqmldirparser.cpp b/src/qml/qml/qqmldirparser.cpp index 82289311c9528a54085e1f600415ef1ade1bd7c7..2da4a3300b46e2e2d10702f2bf2ad1a0cacee457 100644 --- a/src/qml/qml/qqmldirparser.cpp +++ b/src/qml/qml/qqmldirparser.cpp @@ -43,6 +43,7 @@ #include "qqmlerror.h" #include "qqmlglobal_p.h" +#include <QtQml/qqmlfile.h> #include <QtCore/QTextStream> #include <QtCore/QFile> #include <QtCore/QtDebug> @@ -58,26 +59,6 @@ QQmlDirParser::~QQmlDirParser() { } -QUrl QQmlDirParser::url() const -{ - return _url; -} - -void QQmlDirParser::setUrl(const QUrl &url) -{ - _url = url; -} - -QString QQmlDirParser::fileSource() const -{ - return _filePathSouce; -} - -void QQmlDirParser::setFileSource(const QString &filePath) -{ - _filePathSouce = filePath; -} - QString QQmlDirParser::source() const { return _source; @@ -94,6 +75,9 @@ bool QQmlDirParser::isParsed() const return _isParsed; } +/*! +\a url is used for generating errors. +*/ bool QQmlDirParser::parse() { if (_isParsed) @@ -105,23 +89,6 @@ bool QQmlDirParser::parse() _components.clear(); _scripts.clear(); - if (_source.isEmpty() && !_filePathSouce.isEmpty()) { - QFile file(_filePathSouce); - if (!QQml_isFileCaseCorrect(_filePathSouce)) { - QQmlError error; - error.setDescription(QString::fromUtf8("cannot load module \"$$URI$$\": File name case mismatch for \"%1\"").arg(_filePathSouce)); - _errors.prepend(error); - return false; - } else if (file.open(QFile::ReadOnly)) { - _source = QString::fromUtf8(file.readAll()); - } else { - QQmlError error; - error.setDescription(QString::fromUtf8("module \"$$URI$$\" definition \"%1\" not readable").arg(_filePathSouce)); - _errors.prepend(error); - return false; - } - } - QTextStream stream(&_source); int lineNumber = 0; @@ -246,7 +213,6 @@ bool QQmlDirParser::parse() void QQmlDirParser::reportError(int line, int column, const QString &description) { QQmlError error; - error.setUrl(_url); error.setLine(line); error.setColumn(column); error.setDescription(description); @@ -261,6 +227,12 @@ bool QQmlDirParser::hasError() const return false; } +void QQmlDirParser::setError(const QQmlError &e) +{ + _errors.clear(); + _errors.append(e); +} + QList<QQmlError> QQmlDirParser::errors(const QString &uri) const { QList<QQmlError> errors = _errors; diff --git a/src/qml/qml/qqmldirparser_p.h b/src/qml/qml/qqmldirparser_p.h index f46e1781da1bb1cccc5947b3bb180f9e1f92b01c..77fe277a7e52d556f799623814e4866f85a1bc66 100644 --- a/src/qml/qml/qqmldirparser_p.h +++ b/src/qml/qml/qqmldirparser_p.h @@ -60,6 +60,7 @@ QT_BEGIN_NAMESPACE class QQmlError; +class QQmlEngine; class QQmlDirParser { Q_DISABLE_COPY(QQmlDirParser) @@ -68,12 +69,6 @@ public: QQmlDirParser(); ~QQmlDirParser(); - QUrl url() const; - void setUrl(const QUrl &url); - - QString fileSource() const; - void setFileSource(const QString &filePath); - QString source() const; void setSource(const QString &source); @@ -81,6 +76,7 @@ public: bool parse(); bool hasError() const; + void setError(const QQmlError &); QList<QQmlError> errors(const QString &uri) const; struct Plugin @@ -146,9 +142,7 @@ private: private: QList<QQmlError> _errors; - QUrl _url; QString _source; - QString _filePathSouce; QList<Component> _components; QList<Script> _scripts; QList<Plugin> _plugins; diff --git a/src/qml/qml/qqmlengine.cpp b/src/qml/qml/qqmlengine.cpp index 91c86a249a77761f6c167d4fcfd0cf930f22b8f7..1c0026981d554e312c19d77a400fd60d8f868c84 100644 --- a/src/qml/qml/qqmlengine.cpp +++ b/src/qml/qml/qqmlengine.cpp @@ -1303,44 +1303,6 @@ void QQmlData::setBindingBit(QObject *obj, int bit) bindingBits[bit / 32] |= (1 << (bit % 32)); } -QString QQmlEnginePrivate::urlToLocalFileOrQrc(const QUrl& url) -{ - if (url.scheme().compare(QLatin1String("qrc"), Qt::CaseInsensitive) == 0) { - if (url.authority().isEmpty()) - return QLatin1Char(':') + url.path(); - return QString(); - } - return url.toLocalFile(); -} - - -static QString toLocalFile(const QString &url) -{ - if (!url.startsWith(QLatin1String("file://"), Qt::CaseInsensitive)) - return QString(); - - QString file = url.mid(7); - - //XXX TODO: handle windows hostnames: "//servername/path/to/file.txt" - - // magic for drives on windows - if (file.length() > 2 && file.at(0) == QLatin1Char('/') && file.at(2) == QLatin1Char(':')) - file.remove(0, 1); - - return file; -} - -QString QQmlEnginePrivate::urlToLocalFileOrQrc(const QString& url) -{ - if (url.startsWith(QLatin1String("qrc:"), Qt::CaseInsensitive)) { - if (url.length() > 4) - return QLatin1Char(':') + url.mid(4); - return QString(); - } - - return toLocalFile(url); -} - void QQmlEnginePrivate::sendQuit() { Q_Q(QQmlEngine); diff --git a/src/qml/qml/qqmlengine.h b/src/qml/qml/qqmlengine.h index 21a03d63ce8843006060417267412b1715f292ac..8d250ed209c22e3932e701f2c1d1bc29f6eb9f27 100644 --- a/src/qml/qml/qqmlengine.h +++ b/src/qml/qml/qqmlengine.h @@ -104,6 +104,8 @@ public: void setPluginPathList(const QStringList &paths); void addPluginPath(const QString& dir); + bool addNamedBundle(const QString &name, const QString &fileName); + bool importPlugin(const QString &filePath, const QString &uri, QString *errorString); // XXX: Qt 5: Remove this function bool importPlugin(const QString &filePath, const QString &uri, QList<QQmlError> *errors); @@ -136,7 +138,6 @@ public: enum ObjectOwnership { CppOwnership, JavaScriptOwnership }; static void setObjectOwnership(QObject *, ObjectOwnership); static ObjectOwnership objectOwnership(QObject *); - protected: virtual bool event(QEvent *); diff --git a/src/qml/qml/qqmlengine_p.h b/src/qml/qml/qqmlengine_p.h index 2521888668a31cab77239480683ea002b3386749..4cfb8c772a832ad57ac26c80a994e8b8e3dece35 100644 --- a/src/qml/qml/qqmlengine_p.h +++ b/src/qml/qml/qqmlengine_p.h @@ -256,9 +256,6 @@ public: inline static QQmlEnginePrivate *get(QQmlContextData *c); inline static QQmlEngine *get(QQmlEnginePrivate *p); - static QString urlToLocalFileOrQrc(const QUrl& url); - static QString urlToLocalFileOrQrc(const QString& url); - static void registerBaseTypes(const char *uri, int versionMajor, int versionMinor); static void defineModule(); diff --git a/src/qml/qml/qqmlfile.cpp b/src/qml/qml/qqmlfile.cpp new file mode 100644 index 0000000000000000000000000000000000000000..6d48536b2b78787e1d1f3217d86977aa3f0268e1 --- /dev/null +++ b/src/qml/qml/qqmlfile.cpp @@ -0,0 +1,804 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qqmlfile.h" + +#include <QtCore/qurl.h> +#include <QtCore/qobject.h> +#include <private/qqmlengine_p.h> +#include <private/qqmlglobal_p.h> + +/*! +\class The QQmlFile class gives access to local and remote files. + +Supports file://, qrc://, bundle:// uris and whatever QNetworkAccessManager supports. +*/ + +#define QQMLFILE_MAX_REDIRECT_RECURSION 16 + +QT_BEGIN_NAMESPACE + +static QString qrc_string(QLatin1String("qrc")); +static QString file_string(QLatin1String("file")); +static QString bundle_string(QLatin1String("bundle")); + +class QQmlFilePrivate; +class QQmlFileNetworkReply : public QObject +{ +Q_OBJECT +public: + QQmlFileNetworkReply(QQmlEngine *, QQmlFilePrivate *, const QUrl &); + ~QQmlFileNetworkReply(); + +signals: + void finished(); + void downloadProgress(qint64, qint64); + +public slots: + void networkFinished(); + void networkDownloadProgress(qint64, qint64); + +public: + static int finishedIndex; + static int downloadProgressIndex; + static int networkFinishedIndex; + static int networkDownloadProgressIndex; + static int replyFinishedIndex; + static int replyDownloadProgressIndex; + +private: + QQmlEngine *m_engine; + QQmlFilePrivate *m_p; + + int m_redirectCount; + QNetworkReply *m_reply; +}; + +class QQmlFilePrivate +{ +public: + QQmlFilePrivate(); + + mutable QUrl url; + mutable QString urlString; + + QQmlBundleData *bundle; + const QQmlBundle::FileEntry *file; + + QByteArray data; + + enum Error { + None, NotFound, CaseMismatch, Network + }; + + Error error; + QString errorString; + + QQmlFileNetworkReply *reply; +}; + +int QQmlFileNetworkReply::finishedIndex = -1; +int QQmlFileNetworkReply::downloadProgressIndex = -1; +int QQmlFileNetworkReply::networkFinishedIndex = -1; +int QQmlFileNetworkReply::networkDownloadProgressIndex = -1; +int QQmlFileNetworkReply::replyFinishedIndex = -1; +int QQmlFileNetworkReply::replyDownloadProgressIndex = -1; + +QQmlFileNetworkReply::QQmlFileNetworkReply(QQmlEngine *e, QQmlFilePrivate *p, const QUrl &url) +: m_engine(e), m_p(p), m_redirectCount(0), m_reply(0) +{ + if (finishedIndex == -1) { + const QMetaObject *smo = &staticMetaObject; + finishedIndex = smo->indexOfSignal("finished()"); + downloadProgressIndex = smo->indexOfSignal("downloadProgress(qint64,qint64)"); + networkFinishedIndex = smo->indexOfMethod("networkFinished()"); + networkDownloadProgressIndex = smo->indexOfMethod("networkDownloadProgress(qint64,qint64)"); + + const QMetaObject *rsmo = &QNetworkReply::staticMetaObject; + replyFinishedIndex = rsmo->indexOfSignal("finished()"); + replyDownloadProgressIndex = rsmo->indexOfSignal("downloadProgress(qint64,qint64)"); + } + Q_ASSERT(finishedIndex != -1 && downloadProgressIndex != -1 && + networkFinishedIndex != -1 && networkDownloadProgressIndex != -1 && + replyFinishedIndex != -1 && replyDownloadProgressIndex != -1); + + QNetworkRequest req(url); + req.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true); + + m_reply = m_engine->networkAccessManager()->get(req); + QMetaObject::connect(m_reply, replyFinishedIndex, this, networkFinishedIndex); + QMetaObject::connect(m_reply, replyDownloadProgressIndex, this, networkDownloadProgressIndex); +} + +QQmlFileNetworkReply::~QQmlFileNetworkReply() +{ + if (m_reply) { + m_reply->disconnect(); + m_reply->deleteLater(); + } +} + +void QQmlFileNetworkReply::networkFinished() +{ + ++m_redirectCount; + if (m_redirectCount < QQMLFILE_MAX_REDIRECT_RECURSION) { + QVariant redirect = m_reply->attribute(QNetworkRequest::RedirectionTargetAttribute); + if (redirect.isValid()) { + QUrl url = m_reply->url().resolved(redirect.toUrl()); + + QNetworkRequest req(url); + req.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true); + + m_reply->deleteLater(); + m_reply = m_engine->networkAccessManager()->get(req); + + QMetaObject::connect(m_reply, replyFinishedIndex, + this, networkFinishedIndex); + QMetaObject::connect(m_reply, replyDownloadProgressIndex, + this, networkDownloadProgressIndex); + + return; + } + } + + if (m_reply->error()) { + m_p->errorString = m_reply->errorString(); + m_p->error = QQmlFilePrivate::Network; + } else { + m_p->data = m_reply->readAll(); + } + + m_reply->deleteLater(); + m_reply = 0; + + m_p->reply = 0; + emit finished(); + delete this; +} + +void QQmlFileNetworkReply::networkDownloadProgress(qint64 a, qint64 b) +{ + emit downloadProgress(a, b); +} + +QQmlFilePrivate::QQmlFilePrivate() +: bundle(0), file(0), error(None), reply(0) +{ +} + +QQmlFile::QQmlFile() +: d(new QQmlFilePrivate) +{ +} + +QQmlFile::QQmlFile(QQmlEngine *e, const QUrl &url) +: d(new QQmlFilePrivate) +{ + load(e, url); +} + +QQmlFile::QQmlFile(QQmlEngine *e, const QString &url) +: d(new QQmlFilePrivate) +{ + load(e, url); +} + +QQmlFile::~QQmlFile() +{ + if (d->reply) + delete d->reply; + if (d->bundle) + d->bundle->release(); + + delete d; + d = 0; +} + +bool QQmlFile::isNull() const +{ + return status() == Null; +} + +bool QQmlFile::isReady() const +{ + return status() == Ready; +} + +bool QQmlFile::isError() const +{ + return status() == Error; +} + +bool QQmlFile::isLoading() const +{ + return status() == Loading; +} + +QUrl QQmlFile::url() const +{ + if (!d->urlString.isEmpty()) { + d->url = QUrl(d->urlString); + d->urlString = QString(); + } + return d->url; +} + +QQmlFile::Status QQmlFile::status() const +{ + if (d->url.isEmpty() && d->urlString.isEmpty()) + return Null; + else if (d->reply) + return Loading; + else if (d->error != QQmlFilePrivate::None) + return Error; + else + return Ready; +} + +QString QQmlFile::error() const +{ + switch (d->error) { + default: + case QQmlFilePrivate::None: + return QString(); + case QQmlFilePrivate::NotFound: + return QLatin1String("File not found"); + case QQmlFilePrivate::CaseMismatch: + return QLatin1String("File name case mismatch"); + } +} + +qint64 QQmlFile::size() const +{ + if (d->file) return d->file->fileSize(); + else return d->data.size(); +} + +const char *QQmlFile::data() const +{ + if (d->file) return d->file->contents(); + else return d->data.constData(); +} + +QByteArray QQmlFile::dataByteArray() const +{ + if (d->file) return QByteArray(d->file->contents(), d->file->fileSize()); + else return d->data; +} + +QByteArray QQmlFile::metaData(const QString &name) const +{ + if (d->file) { + Q_ASSERT(d->bundle); + const QQmlBundle::FileEntry *meta = d->bundle->link(d->file, name); + if (meta) + return QByteArray::fromRawData(meta->contents(), meta->fileSize()); + } + return QByteArray(); +} + +void QQmlFile::load(QQmlEngine *engine, const QUrl &url) +{ + Q_ASSERT(engine); + + QString scheme = url.scheme(); + + clear(); + d->url = url; + + if (isBundle(url)) { + // Bundle + QQmlEnginePrivate *p = QQmlEnginePrivate::get(engine); + QQmlBundleData *bundle = p->typeLoader.getBundle(url.host()); + + d->error = QQmlFilePrivate::NotFound; + + if (bundle) { + QString filename = url.path().mid(1); + const QQmlBundle::FileEntry *entry = bundle->find(filename); + if (entry) { + d->file = entry; + d->bundle = bundle; + d->bundle->addref(); + d->error = QQmlFilePrivate::None; + } + bundle->release(); + } + } else if (isLocalFile(url)) { + QString lf = urlToLocalFileOrQrc(url); + + if (!QQml_isFileCaseCorrect(lf)) { + d->error = QQmlFilePrivate::CaseMismatch; + return; + } + + QFile file(lf); + if (file.open(QFile::ReadOnly)) { + d->data = file.readAll(); + } else { + d->error = QQmlFilePrivate::NotFound; + } + } else { + d->reply = new QQmlFileNetworkReply(engine, d, url); + } +} + +void QQmlFile::load(QQmlEngine *engine, const QString &url) +{ + Q_ASSERT(engine); + + clear(); + + d->urlString = url; + + if (isBundle(url)) { + // Bundle + QQmlEnginePrivate *p = QQmlEnginePrivate::get(engine); + + d->error = QQmlFilePrivate::NotFound; + + int index = url.indexOf(QLatin1Char('/'), 9); + if (index == -1) + return; + + QStringRef identifier(&url, 9, index - 9); + + QQmlBundleData *bundle = p->typeLoader.getBundle(identifier); + + d->error = QQmlFilePrivate::NotFound; + + if (bundle) { + QString filename = url.mid(index); + const QQmlBundle::FileEntry *entry = bundle->find(filename); + if (entry) { + d->data = QByteArray(entry->contents(), entry->fileSize()); + d->error = QQmlFilePrivate::None; + } + bundle->release(); + } + + } else if (isLocalFile(url)) { + QString lf = urlToLocalFileOrQrc(url); + + if (!QQml_isFileCaseCorrect(lf)) { + d->error = QQmlFilePrivate::CaseMismatch; + return; + } + + QFile file(lf); + if (file.open(QFile::ReadOnly)) { + d->data = file.readAll(); + } else { + d->error = QQmlFilePrivate::NotFound; + } + } else { + QUrl qurl(url); + d->url = qurl; + d->urlString = QString(); + d->reply = new QQmlFileNetworkReply(engine, d, qurl); + } +} + +void QQmlFile::clear() +{ + d->url = QUrl(); + d->urlString = QString(); + d->data = QByteArray(); + if (d->bundle) d->bundle->release(); + d->bundle = 0; + d->file = 0; + d->error = QQmlFilePrivate::None; +} + +void QQmlFile::clear(QObject *) +{ + clear(); +} + +bool QQmlFile::connectFinished(QObject *object, const char *method) +{ + if (!d || !d->reply) { + qWarning("QQmlFile: connectFinished() called when not loading."); + return false; + } + + return QObject::connect(d->reply, SIGNAL(finished()), + object, method); +} + +bool QQmlFile::connectFinished(QObject *object, int method) +{ + if (!d || !d->reply) { + qWarning("QQmlFile: connectFinished() called when not loading."); + return false; + } + + return QMetaObject::connect(d->reply, QQmlFileNetworkReply::finishedIndex, + object, method); +} + +bool QQmlFile::connectDownloadProgress(QObject *object, const char *method) +{ + if (!d || !d->reply) { + qWarning("QQmlFile: connectDownloadProgress() called when not loading."); + return false; + } + + return QObject::connect(d->reply, SIGNAL(downloadProgress(qint64,qint64)), + object, method); +} + +bool QQmlFile::connectDownloadProgress(QObject *object, int method) +{ + if (!d || !d->reply) { + qWarning("QQmlFile: connectDownloadProgress() called when not loading."); + return false; + } + + return QMetaObject::connect(d->reply, QQmlFileNetworkReply::downloadProgressIndex, + object, method); +} + +/*! +Returns true if QQmlFile will open \a url synchronously. + +Synchronous urls have a qrc://, file://, or bundle:// scheme. +*/ +bool QQmlFile::isSynchronous(const QUrl &url) +{ + QString scheme = url.scheme(); + + if ((scheme.length() == 4 && 0 == scheme.compare(file_string, Qt::CaseInsensitive)) || + (scheme.length() == 6 && 0 == scheme.compare(bundle_string, Qt::CaseInsensitive)) || + (scheme.length() == 3 && 0 == scheme.compare(qrc_string, Qt::CaseInsensitive))) + return true; + else + return false; +} + +/*! +Returns true if QQmlFile will open \a url synchronously. + +Synchronous urls have a qrc://, file://, or bundle:// scheme. +*/ +bool QQmlFile::isSynchronous(const QString &url) +{ + if (url.length() < 6 /* qrc:// */) + return false; + + QChar f = url[0]; + + if (f == QLatin1Char('f') || f == QLatin1Char('F')) { + + return url.length() >= 7 /* file:// */ && + url.startsWith(file_string, Qt::CaseInsensitive) && + url[4] == QLatin1Char(':') && url[5] == QLatin1Char('/') && url[6] == QLatin1Char('/'); + + } else if (f == QLatin1Char('b') || f == QLatin1Char('B')) { + + return url.length() >= 9 /* bundle:// */ && + url.startsWith(bundle_string, Qt::CaseInsensitive) && + url[6] == QLatin1Char(':') && url[7] == QLatin1Char('/') && url[8] == QLatin1Char('/'); + + } else if (f == QLatin1Char('q') || f == QLatin1Char('Q')) { + + return url.length() >= 6 /* bundle:// */ && + url.startsWith(qrc_string, Qt::CaseInsensitive) && + url[3] == QLatin1Char(':') && url[4] == QLatin1Char('/') && url[5] == QLatin1Char('/'); + + } + + return false; +} + +/*! +Returns true if \a url is a bundle. + +Bundle urls have a bundle:// scheme. +*/ +bool QQmlFile::isBundle(const QString &url) +{ + return url.length() >= 9 && url.startsWith(bundle_string, Qt::CaseInsensitive) && + url[6] == QLatin1Char(':') && url[7] == QLatin1Char('/') && url[8] == QLatin1Char('/'); +} + +/*! +Returns true if \a url is a bundle. + +Bundle urls have a bundle:// scheme. +*/ +bool QQmlFile::isBundle(const QUrl &url) +{ + QString scheme = url.scheme(); + + return scheme.length() == 6 && 0 == scheme.compare(bundle_string, Qt::CaseInsensitive); +} + +/*! +Returns true if \a url is a local file that can be opened with QFile. + +Local file urls have either a qrc:// or file:// scheme. +*/ +bool QQmlFile::isLocalFile(const QUrl &url) +{ + QString scheme = url.scheme(); + + if ((scheme.length() == 4 && 0 == scheme.compare(file_string, Qt::CaseInsensitive)) || + (scheme.length() == 3 && 0 == scheme.compare(qrc_string, Qt::CaseInsensitive))) + return true; + else + return false; +} + +/*! +Returns true if \a url is a local file that can be opened with QFile. + +Local file urls have either a qrc:// or file:// scheme. +*/ +bool QQmlFile::isLocalFile(const QString &url) +{ + if (url.length() < 6 /* qrc:// */) + return false; + + QChar f = url[0]; + + if (f == QLatin1Char('f') || f == QLatin1Char('F')) { + + return url.length() >= 7 /* file:// */ && + url.startsWith(file_string, Qt::CaseInsensitive) && + url[4] == QLatin1Char(':') && url[5] == QLatin1Char('/') && url[6] == QLatin1Char('/'); + + } else if (f == QLatin1Char('q') || f == QLatin1Char('Q')) { + + return url.length() >= 6 /* bundle:// */ && + url.startsWith(qrc_string, Qt::CaseInsensitive) && + url[3] == QLatin1Char(':') && url[4] == QLatin1Char('/') && url[5] == QLatin1Char('/'); + + } + + return false; +} + +/*! +If \a url is a local file returns a path suitable for passing to QFile. Otherwise returns an +empty string. +*/ +QString QQmlFile::urlToLocalFileOrQrc(const QUrl& url) +{ + if (url.scheme().compare(QLatin1String("qrc"), Qt::CaseInsensitive) == 0) { + if (url.authority().isEmpty()) + return QLatin1Char(':') + url.path(); + return QString(); + } + return url.toLocalFile(); +} + +static QString toLocalFile(const QString &url) +{ + if (!url.startsWith(QLatin1String("file://"), Qt::CaseInsensitive)) + return QString(); + + QString file = url.mid(7); + + //XXX TODO: handle windows hostnames: "//servername/path/to/file.txt" + + // magic for drives on windows + if (file.length() > 2 && file.at(0) == QLatin1Char('/') && file.at(2) == QLatin1Char(':')) + file.remove(0, 1); + + return file; +} + +/*! +If \a url is a local file returns a path suitable for passing to QFile. Otherwise returns an +empty string. +*/ +QString QQmlFile::urlToLocalFileOrQrc(const QString& url) +{ + if (url.startsWith(QLatin1String("qrc:"), Qt::CaseInsensitive)) { + if (url.length() > 4) + return QLatin1Char(':') + url.mid(4); + return QString(); + } + + return toLocalFile(url); +} + +bool QQmlFile::bundleDirectoryExists(const QString &dir, QQmlEngine *e) +{ + if (!isBundle(dir)) + return false; + + int index = dir.indexOf(QLatin1Char('/'), 9); + + if (index == -1 && dir.length() > 9) // We accept "bundle://<blah>" with no extra path + index = dir.length(); + + if (index == -1) + return false; + + QStringRef identifier(&dir, 9, index - 9); + QStringRef path(&dir, index + 1, dir.length() - index - 1); + + QQmlBundleData *bundle = QQmlEnginePrivate::get(e)->typeLoader.getBundle(identifier); + + if (bundle) { + int lastIndex = dir.lastIndexOf(QLatin1Char('/')); + + if (lastIndex <= index) { + bundle->release(); + return true; + } + + QStringRef d(&dir, index + 1, lastIndex - index); + + QList<const QQmlBundle::FileEntry *> entries = bundle->files(); + + for (int ii = 0; ii < entries.count(); ++ii) { + QString name = entries.at(ii)->fileName(); + if (name.startsWith(d)) { + bundle->release(); + return true; + } + } + + bundle->release(); + } + + return false; +} + +bool QQmlFile::bundleDirectoryExists(const QUrl &url, QQmlEngine *e) +{ + if (!isBundle(url)) + return false; + + QQmlBundleData *bundle = QQmlEnginePrivate::get(e)->typeLoader.getBundle(url.host()); + + if (bundle) { + QString path = url.path(); + + int lastIndex = path.lastIndexOf(QLatin1Char('/')); + + if (lastIndex == -1) { + bundle->release(); + return true; + } + + QStringRef d(&path, 0, lastIndex); + + QList<const QQmlBundle::FileEntry *> entries = bundle->files(); + + for (int ii = 0; ii < entries.count(); ++ii) { + QString name = entries.at(ii)->fileName(); + if (name.startsWith(d)) { + bundle->release(); + return true; + } + } + + bundle->release(); + } + + return false; +} + +bool QQmlFile::bundleFileExists(const QString &file, QQmlEngine *e) +{ + if (!isBundle(file)) + return false; + + int index = file.indexOf(QLatin1Char('/'), 9); + + if (index == -1) + return false; + + QStringRef identifier(&file, 9, index - 9); + QStringRef path(&file, index + 1, file.length() - index - 1); + + QQmlBundleData *bundle = QQmlEnginePrivate::get(e)->typeLoader.getBundle(identifier); + + if (bundle) { + const QQmlBundle::FileEntry *entry = bundle->find(path.constData(), path.length()); + bundle->release(); + + return entry != 0; + } + + return false; +} + +bool QQmlFile::bundleFileExists(const QUrl &, QQmlEngine *) +{ + qFatal("Not implemented"); + return true; +} + +/*! +Returns the file name for the bundle file referenced by \a url or an +empty string if \a url isn't a bundle url. +*/ +QString QQmlFile::bundleFileName(const QString &url, QQmlEngine *e) +{ + if (!isBundle(url)) + return QString(); + + int index = url.indexOf(QLatin1Char('/'), 9); + + if (index == -1) + index = url.length(); + + QStringRef identifier(&url, 9, index - 9); + + QQmlBundleData *bundle = QQmlEnginePrivate::get(e)->typeLoader.getBundle(identifier); + + if (bundle) { + QString rv = bundle->fileName; + bundle->release(); + return rv; + } + + return QString(); +} + +/*! +Returns the file name for the bundle file referenced by \a url or an +empty string if \a url isn't a bundle url. +*/ +QString QQmlFile::bundleFileName(const QUrl &url, QQmlEngine *e) +{ + if (!isBundle(url)) + return QString(); + + QQmlBundleData *bundle = QQmlEnginePrivate::get(e)->typeLoader.getBundle(url.host()); + + if (bundle) { + QString rv = bundle->fileName; + bundle->release(); + return rv; + } + + return QString(); +} + +QT_END_NAMESPACE + +#include "qqmlfile.moc" diff --git a/src/qml/qml/qqmlfile.h b/src/qml/qml/qqmlfile.h new file mode 100644 index 0000000000000000000000000000000000000000..cfa833c6b7d4e378b5671c6483568fc00322e208 --- /dev/null +++ b/src/qml/qml/qqmlfile.h @@ -0,0 +1,124 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQMLFILE_H +#define QQMLFILE_H + +#include <QtQml/qtqmlglobal.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +class QUrl; +class QString; +class QObject; +class QQmlEngine; +class QQmlFilePrivate; + +class Q_QML_EXPORT QQmlFile +{ +public: + QQmlFile(); + QQmlFile(QQmlEngine *, const QUrl &); + QQmlFile(QQmlEngine *, const QString &); + ~QQmlFile(); + + enum Status { Null, Ready, Error, Loading }; + + bool isNull() const; + bool isReady() const; + bool isError() const; + bool isLoading() const; + + QUrl url() const; + + Status status() const; + QString error() const; + + qint64 size() const; + const char *data() const; + QByteArray dataByteArray() const; + + QByteArray metaData(const QString &) const; + + void load(QQmlEngine *, const QUrl &); + void load(QQmlEngine *, const QString &); + + void clear(); + void clear(QObject *); + + bool connectFinished(QObject *, const char *); + bool connectFinished(QObject *, int); + bool connectDownloadProgress(QObject *, const char *); + bool connectDownloadProgress(QObject *, int); + + static bool isSynchronous(const QString &url); + static bool isSynchronous(const QUrl &url); + + static bool isBundle(const QString &url); + static bool isBundle(const QUrl &url); + + static bool isLocalFile(const QString &url); + static bool isLocalFile(const QUrl &url); + + static QString urlToLocalFileOrQrc(const QString &); + static QString urlToLocalFileOrQrc(const QUrl &); + + static bool bundleDirectoryExists(const QString &, QQmlEngine *); + static bool bundleDirectoryExists(const QUrl &, QQmlEngine *); + + static bool bundleFileExists(const QString &, QQmlEngine *); + static bool bundleFileExists(const QUrl &, QQmlEngine *); + + static QString bundleFileName(const QString &, QQmlEngine *); + static QString bundleFileName(const QUrl &, QQmlEngine *); + +private: + Q_DISABLE_COPY(QQmlFile) + QQmlFilePrivate *d; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QQMLFILE_H diff --git a/src/qml/qml/qqmlimport.cpp b/src/qml/qml/qqmlimport.cpp index a8a3ed0a4bea22ef43c4f1aa71f72036c0f5f5ef..1417e3dff7205e675e17beb60c4b556ece812ab8 100644 --- a/src/qml/qml/qqmlimport.cpp +++ b/src/qml/qml/qqmlimport.cpp @@ -43,6 +43,7 @@ #include <QtCore/qdebug.h> #include <QtCore/qdir.h> +#include <QtQml/qqmlfile.h> #include <QtCore/qfileinfo.h> #include <QtCore/qpluginloader.h> #include <QtCore/qlibraryinfo.h> @@ -56,11 +57,6 @@ QT_BEGIN_NAMESPACE DEFINE_BOOL_CONFIG_OPTION(qmlImportTrace, QML_IMPORT_TRACE) DEFINE_BOOL_CONFIG_OPTION(qmlCheckTypes, QML_CHECK_TYPES) -static bool greaterThan(const QString &s1, const QString &s2) -{ - return s1 > s2; -} - QString resolveLocalUrl(const QString &url, const QString &relative) { if (relative.contains(QLatin1Char(':'))) { @@ -109,15 +105,13 @@ QString resolveLocalUrl(const QString &url, const QString &relative) } } - - typedef QMap<QString, QString> StringStringMap; Q_GLOBAL_STATIC(StringStringMap, qmlEnginePluginsWithRegisteredTypes); // stores the uri -class QQmlImportedNamespace +class QQmlImportNamespace { public: - struct Data { + struct Import { QString uri; QString url; int majversion; @@ -125,45 +119,58 @@ public: bool isLibrary; QQmlDirComponents qmlDirComponents; QQmlDirScripts qmlDirScripts; - }; - QList<Data> imports; + bool resolveType(QQmlTypeLoader *typeLoader, const QString& type, + int *vmajor, int *vminor, + QQmlType** type_return, QString* url_return, + QString *base = 0, bool *typeRecursionDetected = 0) const; + }; + QList<Import> imports; - bool find_helper(QQmlTypeLoader *typeLoader, const Data &data, const QString& type, int *vmajor, int *vminor, - QQmlType** type_return, QString* url_return, - QString *base = 0, bool *typeRecursionDetected = 0); - bool find(QQmlTypeLoader *typeLoader, const QString& type, int *vmajor, int *vminor, QQmlType** type_return, - QString* url_return, QString *base = 0, QList<QQmlError> *errors = 0); + bool resolveType(QQmlTypeLoader *typeLoader, const QString& type, + int *vmajor, int *vminor, + QQmlType** type_return, QString* url_return, + QString *base = 0, QList<QQmlError> *errors = 0); }; -class QQmlImportsPrivate { +class QQmlImportsPrivate +{ public: QQmlImportsPrivate(QQmlTypeLoader *loader); ~QQmlImportsPrivate(); - bool importExtension(const QString &absoluteFilePath, const QString &uri, - QQmlImportDatabase *database, QQmlDirComponents* components, - QQmlDirScripts *scripts, - QList<QQmlError> *errors); - - QString resolvedUri(const QString &dir_arg, QQmlImportDatabase *database); - QString add(const QQmlDirComponents &qmldircomponentsnetwork, - const QString& uri_arg, const QString& prefix, - int vmaj, int vmin, QQmlScript::Import::Type importType, - QQmlImportDatabase *database, QList<QQmlError> *errors); - bool find(const QString& type, int *vmajor, int *vminor, - QQmlType** type_return, QString* url_return, QList<QQmlError> *errors); + bool addImport(const QQmlDirComponents &qmldircomponentsnetwork, + const QString &importedUri, const QString& prefix, + int vmaj, int vmin, QQmlScript::Import::Type importType, + bool isImplicitImport, QQmlImportDatabase *database, + QString *, QList<QQmlError> *errors); - QQmlImportedNamespace *findNamespace(const QString& type); + bool resolveType(const QString& type, int *vmajor, int *vminor, + QQmlType** type_return, QString* url_return, + QList<QQmlError> *errors); QUrl baseUrl; QString base; int ref; QSet<QString> qmlDirFilesForWhichPluginsHaveBeenLoaded; - QQmlImportedNamespace unqualifiedset; - QHash<QString,QQmlImportedNamespace* > set; + QQmlImportNamespace unqualifiedset; + QHash<QString, QQmlImportNamespace *> set; QQmlTypeLoader *typeLoader; + + static inline QString tr(const char *str) { + return QQmlImportDatabase::tr(str); + } + +private: + static bool locateQmldir(const QString &uri, int vmaj, int vmin, + QQmlImportDatabase *database, + QString *outQmldirFilePath, QString *outUrl); + bool importExtension(const QString &absoluteFilePath, const QString &uri, + QQmlImportDatabase *database, QQmlDirComponents* components, + QQmlDirScripts *scripts, + QString *url, QList<QQmlError> *errors); + QString resolvedUri(const QString &dir_arg, QQmlImportDatabase *database); }; /*! @@ -223,29 +230,30 @@ QUrl QQmlImports::baseUrl() const void QQmlImports::populateCache(QQmlTypeNameCache *cache, QQmlEngine *engine) const { - const QQmlImportedNamespace &set = d->unqualifiedset; + const QQmlImportNamespace &set = d->unqualifiedset; for (int ii = set.imports.count() - 1; ii >= 0; --ii) { - const QQmlImportedNamespace::Data &data = set.imports.at(ii); - QQmlTypeModule *module = QQmlMetaType::typeModule(data.uri, data.majversion); + const QQmlImportNamespace::Import &import = set.imports.at(ii); + QQmlTypeModule *module = QQmlMetaType::typeModule(import.uri, import.majversion); if (module) - cache->m_anonymousImports.append(QQmlTypeModuleVersion(module, data.minversion)); + cache->m_anonymousImports.append(QQmlTypeModuleVersion(module, import.minversion)); } - for (QHash<QString,QQmlImportedNamespace* >::ConstIterator iter = d->set.begin(); + for (QHash<QString, QQmlImportNamespace* >::ConstIterator iter = d->set.begin(); iter != d->set.end(); ++iter) { - const QQmlImportedNamespace &set = *iter.value(); + const QQmlImportNamespace &set = *iter.value(); for (int ii = set.imports.count() - 1; ii >= 0; --ii) { - const QQmlImportedNamespace::Data &data = set.imports.at(ii); - QQmlTypeModule *module = QQmlMetaType::typeModule(data.uri, data.majversion); + const QQmlImportNamespace::Import &import = set.imports.at(ii); + QQmlTypeModule *module = QQmlMetaType::typeModule(import.uri, import.majversion); if (module) { - QQmlTypeNameCache::Import &import = cache->m_namedImports[iter.key()]; - import.modules.append(QQmlTypeModuleVersion(module, data.minversion)); + QQmlTypeNameCache::Import &typeimport = cache->m_namedImports[iter.key()]; + typeimport.modules.append(QQmlTypeModuleVersion(module, import.minversion)); } - QQmlMetaType::ModuleApi moduleApi = QQmlMetaType::moduleApi(data.uri, data.majversion, data.minversion); + QQmlMetaType::ModuleApi moduleApi = QQmlMetaType::moduleApi(import.uri, import.majversion, + import.minversion); if (moduleApi.script || moduleApi.qobject) { QQmlTypeNameCache::Import &import = cache->m_namedImports[iter.key()]; QQmlEnginePrivate *ep = QQmlEnginePrivate::get(engine); @@ -259,32 +267,32 @@ QList<QQmlImports::ScriptReference> QQmlImports::resolvedScripts() const { QList<QQmlImports::ScriptReference> scripts; - const QQmlImportedNamespace &set = d->unqualifiedset; + const QQmlImportNamespace &set = d->unqualifiedset; for (int ii = set.imports.count() - 1; ii >= 0; --ii) { - const QQmlImportedNamespace::Data &data = set.imports.at(ii); + const QQmlImportNamespace::Import &import = set.imports.at(ii); - foreach (const QQmlDirParser::Script &script, data.qmlDirScripts) { + foreach (const QQmlDirParser::Script &script, import.qmlDirScripts) { ScriptReference ref; ref.nameSpace = script.nameSpace; - ref.location = QUrl(data.url).resolved(QUrl(script.fileName)); + ref.location = QUrl(import.url).resolved(QUrl(script.fileName)); scripts.append(ref); } } - for (QHash<QString,QQmlImportedNamespace* >::ConstIterator iter = d->set.constBegin(); + for (QHash<QString, QQmlImportNamespace* >::ConstIterator iter = d->set.constBegin(); iter != d->set.constEnd(); ++iter) { - const QQmlImportedNamespace &set = *iter.value(); + const QQmlImportNamespace &set = *iter.value(); for (int ii = set.imports.count() - 1; ii >= 0; --ii) { - const QQmlImportedNamespace::Data &data = set.imports.at(ii); + const QQmlImportNamespace::Import &import = set.imports.at(ii); - foreach (const QQmlDirParser::Script &script, data.qmlDirScripts) { + foreach (const QQmlDirParser::Script &script, import.qmlDirScripts) { ScriptReference ref; ref.nameSpace = script.nameSpace; ref.qualifier = iter.key(); - ref.location = QUrl(data.url).resolved(QUrl(script.fileName)); + ref.location = QUrl(import.url).resolved(QUrl(script.fileName)); scripts.append(ref); } } @@ -298,7 +306,7 @@ QList<QQmlImports::ScriptReference> QQmlImports::resolvedScripts() const The given (namespace qualified) \a type is resolved to either \list - \li a QQmlImportedNamespace stored at \a ns_return, + \li a QQmlImportNamespace stored at \a ns_return, \li a QQmlType stored at \a type_return, or \li a component located at \a url_return. \endlist @@ -308,27 +316,29 @@ QList<QQmlImports::ScriptReference> QQmlImports::resolvedScripts() const \sa addImport() */ bool QQmlImports::resolveType(const QString& type, - QQmlType** type_return, QString* url_return, int *vmaj, int *vmin, - QQmlImportedNamespace** ns_return, QList<QQmlError> *errors) const + QQmlType** type_return, QString* url_return, int *vmaj, int *vmin, + QQmlImportNamespace** ns_return, QList<QQmlError> *errors) const { - QQmlImportedNamespace* ns = d->findNamespace(type); + QQmlImportNamespace* ns = d->set.value(type); if (ns) { if (ns_return) *ns_return = ns; return true; } if (type_return || url_return) { - if (d->find(type,vmaj,vmin,type_return,url_return, errors)) { + if (d->resolveType(type,vmaj,vmin,type_return,url_return, errors)) { if (qmlImportTrace()) { +#define RESOLVE_TYPE_DEBUG qDebug().nospace() << "QQmlImports(" << qPrintable(baseUrl().toString()) \ + << ")" << "::resolveType: " << type << " => " + if (type_return && *type_return && url_return && !url_return->isEmpty()) - qDebug().nospace() << "QQmlImports(" << qPrintable(baseUrl().toString()) << ")" << "::resolveType: " - << type << " => " << (*type_return)->typeName() << " " << *url_return; + RESOLVE_TYPE_DEBUG << (*type_return)->typeName() << " " << *url_return; if (type_return && *type_return) - qDebug().nospace() << "QQmlImports(" << qPrintable(baseUrl().toString()) << ")" << "::resolveType: " - << type << " => " << (*type_return)->typeName(); + RESOLVE_TYPE_DEBUG << (*type_return)->typeName(); if (url_return && !url_return->isEmpty()) - qDebug().nospace() << "QQmlImports(" << qPrintable(baseUrl().toString()) << ")" << "::resolveType: " - << type << " => " << *url_return; + RESOLVE_TYPE_DEBUG << *url_return; + +#undef RESOLVE_TYPE_DEBUG } return true; } @@ -346,42 +356,40 @@ bool QQmlImports::resolveType(const QString& type, If either return pointer is 0, the corresponding search is not done. */ -bool QQmlImports::resolveType(QQmlImportedNamespace* ns, const QString& type, - QQmlType** type_return, QString* url_return, - int *vmaj, int *vmin) const +bool QQmlImports::resolveType(QQmlImportNamespace* ns, const QString& type, + QQmlType** type_return, QString* url_return, + int *vmaj, int *vmin) const { - return ns->find(d->typeLoader,type,vmaj,vmin,type_return,url_return); + return ns->resolveType(d->typeLoader,type,vmaj,vmin,type_return,url_return); } -bool QQmlImportedNamespace::find_helper(QQmlTypeLoader *typeLoader, const Data &data, const QString& type, int *vmajor, int *vminor, - QQmlType** type_return, QString* url_return, - QString *base, bool *typeRecursionDetected) +bool QQmlImportNamespace::Import::resolveType(QQmlTypeLoader *typeLoader, + const QString& type, int *vmajor, int *vminor, + QQmlType** type_return, QString* url_return, + QString *base, bool *typeRecursionDetected) const { - int vmaj = data.majversion; - int vmin = data.minversion; - - if (vmaj >= 0 && vmin >= 0) { - QString qt = data.uri + QLatin1Char('/') + type; - QQmlType *t = QQmlMetaType::qmlType(qt,vmaj,vmin); + if (majversion >= 0 && minversion >= 0) { + QString qt = uri + QLatin1Char('/') + type; + QQmlType *t = QQmlMetaType::qmlType(qt, majversion, minversion); if (t) { - if (vmajor) *vmajor = vmaj; - if (vminor) *vminor = vmin; + if (vmajor) *vmajor = majversion; + if (vminor) *vminor = minversion; if (type_return) *type_return = t; return true; } } - const QQmlDirComponents &qmldircomponents = data.qmlDirComponents; bool typeWasDeclaredInQmldir = false; - if (!qmldircomponents.isEmpty()) { - foreach (const QQmlDirParser::Component &c, qmldircomponents) { + if (!qmlDirComponents.isEmpty()) { + foreach (const QQmlDirParser::Component &c, qmlDirComponents) { if (c.typeName == type) { typeWasDeclaredInQmldir = true; // importing version -1 means import ALL versions - if ((vmaj == -1) || (c.majorVersion == vmaj && vmin >= c.minorVersion)) { - QString url(data.url + type + QLatin1String(".qml")); - QString candidate = resolveLocalUrl(url, c.fileName); + if ((majversion == -1) || (c.majorVersion == majversion && + minversion >= c.minorVersion)) { + QString candidate = resolveLocalUrl(QString(url + type + QLatin1String(".qml")), + c.fileName); if (c.internal && base) { if (resolveLocalUrl(*base, c.fileName) != candidate) continue; // failed attempt to access an internal type @@ -399,65 +407,205 @@ bool QQmlImportedNamespace::find_helper(QQmlTypeLoader *typeLoader, const Data & } } - if (!typeWasDeclaredInQmldir && !data.isLibrary) { - // XXX search non-files too! (eg. zip files, see QT-524) - QString url(data.url + type + QLatin1String(".qml")); - QString file = QQmlEnginePrivate::urlToLocalFileOrQrc(url); - if (!typeLoader->absoluteFilePath(file).isEmpty()) { - if (base && *base == url) { // no recursion + if (!typeWasDeclaredInQmldir && !isLibrary) { + QString qmlUrl(url + type + QLatin1String(".qml")); + + bool exists = false; + + if (QQmlFile::isBundle(qmlUrl)) { + exists = QQmlFile::bundleFileExists(qmlUrl, typeLoader->engine()); + } else { + QString file = QQmlFile::urlToLocalFileOrQrc(qmlUrl); + exists = !typeLoader->absoluteFilePath(QQmlFile::urlToLocalFileOrQrc(qmlUrl)).isEmpty(); + } + + if (exists) { + if (base && *base == qmlUrl) { // no recursion if (typeRecursionDetected) *typeRecursionDetected = true; } else { if (url_return) - *url_return = url; + *url_return = qmlUrl; return true; } } } + return false; } -QQmlImportsPrivate::QQmlImportsPrivate(QQmlTypeLoader *loader) - : ref(1), typeLoader(loader) +bool QQmlImportsPrivate::resolveType(const QString& type, int *vmajor, int *vminor, + QQmlType** type_return, QString* url_return, + QList<QQmlError> *errors) +{ + QQmlImportNamespace *s = 0; + int slash = type.indexOf(QLatin1Char('/')); + if (slash >= 0) { + QString namespaceName = type.left(slash); + s = set.value(namespaceName); + if (!s) { + if (errors) { + QQmlError error; + error.setDescription(QQmlImportDatabase::tr("- %1 is not a namespace").arg(namespaceName)); + errors->prepend(error); + } + return false; + } + int nslash = type.indexOf(QLatin1Char('/'),slash+1); + if (nslash > 0) { + if (errors) { + QQmlError error; + error.setDescription(QQmlImportDatabase::tr("- nested namespaces not allowed")); + errors->prepend(error); + } + return false; + } + } else { + s = &unqualifiedset; + } + QString unqualifiedtype = slash < 0 ? type : type.mid(slash+1); // common-case opt (QString::mid works fine, but slower) + if (s) { + if (s->resolveType(typeLoader,unqualifiedtype,vmajor,vminor,type_return,url_return, &base, errors)) + return true; + if (s->imports.count() == 1 && !s->imports.at(0).isLibrary && url_return && s != &unqualifiedset) { + // qualified, and only 1 url + *url_return = resolveLocalUrl(s->imports.at(0).url, unqualifiedtype + QLatin1String(".qml")); + return true; + } + } + + return false; +} + +bool QQmlImportNamespace::resolveType(QQmlTypeLoader *typeLoader, const QString& type, int *vmajor, + int *vminor, QQmlType** type_return, + QString* url_return, QString *base, QList<QQmlError> *errors) { + bool typeRecursionDetected = false; + for (int i=0; i<imports.count(); ++i) { + const Import &import = imports.at(i); + if (import.resolveType(typeLoader, type, vmajor, vminor, type_return, url_return, + base, &typeRecursionDetected)) { + if (qmlCheckTypes()) { + // check for type clashes + for (int j = i+1; j<imports.count(); ++j) { + const Import &import2 = imports.at(j); + if (import2.resolveType(typeLoader, type, vmajor, vminor, 0, 0, base)) { + if (errors) { + QString u1 = imports.at(i).url; + QString u2 = imports.at(j).url; + if (base) { + QString b = *base; + int slash = b.lastIndexOf(QLatin1Char('/')); + if (slash >= 0) { + b = b.left(slash+1); + QString l = b.left(slash); + if (u1.startsWith(b)) + u1 = u1.mid(b.count()); + else if (u1 == l) + u1 = QQmlImportDatabase::tr("local directory"); + if (u2.startsWith(b)) + u2 = u2.mid(b.count()); + else if (u2 == l) + u2 = QQmlImportDatabase::tr("local directory"); + } + } + + QQmlError error; + if (u1 != u2) { + error.setDescription(QQmlImportDatabase::tr("is ambiguous. Found in %1 and in %2").arg(u1).arg(u2)); + } else { + error.setDescription(QQmlImportDatabase::tr("is ambiguous. Found in %1 in version %2.%3 and %4.%5") + .arg(u1) + .arg(imports.at(i).majversion).arg(imports.at(i).minversion) + .arg(imports.at(j).majversion).arg(imports.at(j).minversion)); + } + errors->prepend(error); + } + return false; + } + } + } + return true; + } + } + if (errors) { + QQmlError error; + if (typeRecursionDetected) + error.setDescription(QQmlImportDatabase::tr("is instantiated recursively")); + else + error.setDescription(QQmlImportDatabase::tr("is not a type")); + errors->prepend(error); + } + return false; +} + +QQmlImportsPrivate::QQmlImportsPrivate(QQmlTypeLoader *loader) +: ref(1), typeLoader(loader) { } QQmlImportsPrivate::~QQmlImportsPrivate() { - foreach (QQmlImportedNamespace* s, set.values()) + foreach (QQmlImportNamespace* s, set.values()) delete s; } -bool QQmlImportsPrivate::importExtension(const QString &absoluteFilePath, const QString &uri, - QQmlImportDatabase *database, - QQmlDirComponents* components, - QQmlDirScripts* scripts, - QList<QQmlError> *errors) +/*! +Import an extension defined by a qmldir file. + +\a qmldirFilePath is either a raw file path, or a bundle url. + +This call will modify the \a url parameter if importing the extension redirects to +a bundle path. +*/ +bool QQmlImportsPrivate::importExtension(const QString &qmldirFilePath, + const QString &uri, + QQmlImportDatabase *database, + QQmlDirComponents* components, + QQmlDirScripts* scripts, + QString *url, + QList<QQmlError> *errors) { - const QQmlDirParser *qmldirParser = typeLoader->qmlDirParser(absoluteFilePath); + // As qmldirFilePath is always local, this method can always return synchronously + const QQmlDirParser *qmldirParser = typeLoader->qmlDirParser(qmldirFilePath, uri, url); if (qmldirParser->hasError()) { if (errors) { + QUrl url; + + if (QQmlFile::isBundle(qmldirFilePath)) + url = QUrl(qmldirFilePath); + else + url = QUrl::fromLocalFile(qmldirFilePath); + const QList<QQmlError> qmldirErrors = qmldirParser->errors(uri); - for (int i = 0; i < qmldirErrors.size(); ++i) - errors->prepend(qmldirErrors.at(i)); + for (int i = 0; i < qmldirErrors.size(); ++i) { + QQmlError error = qmldirErrors.at(i); + error.setUrl(url); + errors->append(error); + } } return false; } if (qmlImportTrace()) qDebug().nospace() << "QQmlImports(" << qPrintable(base) << "::importExtension: " - << "loaded " << absoluteFilePath; + << "loaded " << qmldirFilePath; + + if (!qmlDirFilesForWhichPluginsHaveBeenLoaded.contains(qmldirFilePath)) { + qmlDirFilesForWhichPluginsHaveBeenLoaded.insert(qmldirFilePath); - if (! qmlDirFilesForWhichPluginsHaveBeenLoaded.contains(absoluteFilePath)) { - qmlDirFilesForWhichPluginsHaveBeenLoaded.insert(absoluteFilePath); + QString qmldirPath = qmldirFilePath; - QString qmldirPath = absoluteFilePath; - int slash = absoluteFilePath.lastIndexOf(QLatin1Char('/')); + if (QQmlFile::isBundle(*url)) + qmldirPath = QQmlFile::bundleFileName(*url, typeLoader->engine()); + + int slash = qmldirFilePath.lastIndexOf(QLatin1Char('/')); if (slash > 0) qmldirPath.truncate(slash); - foreach (const QQmlDirParser::Plugin &plugin, qmldirParser->plugins()) { - QString resolvedFilePath = database->resolvePlugin(typeLoader, qmldirPath, plugin.path, plugin.name); + foreach (const QQmlDirParser::Plugin &plugin, qmldirParser->plugins()) { + QString resolvedFilePath = database->resolvePlugin(typeLoader, qmldirPath, + plugin.path, plugin.name); if (!resolvedFilePath.isEmpty()) { if (!database->importPlugin(resolvedFilePath, uri, errors)) { if (errors) { @@ -466,8 +614,8 @@ bool QQmlImportsPrivate::importExtension(const QString &absoluteFilePath, const // The reason is that the lower level may add url and line/column numbering information. QQmlError poppedError = errors->takeFirst(); QQmlError error; - error.setDescription(QQmlImportDatabase::tr("plugin cannot be loaded for module \"%1\": %2").arg(uri).arg(poppedError.description())); - error.setUrl(QUrl::fromLocalFile(absoluteFilePath)); + error.setDescription(tr("plugin cannot be loaded for module \"%1\": %2").arg(uri).arg(poppedError.description())); + error.setUrl(QUrl::fromLocalFile(qmldirFilePath)); errors->prepend(error); } return false; @@ -475,8 +623,8 @@ bool QQmlImportsPrivate::importExtension(const QString &absoluteFilePath, const } else { if (errors) { QQmlError error; - error.setDescription(QQmlImportDatabase::tr("module \"%1\" plugin \"%2\" not found").arg(uri).arg(plugin.name)); - error.setUrl(QUrl::fromLocalFile(absoluteFilePath)); + error.setDescription(tr("module \"%1\" plugin \"%2\" not found").arg(uri).arg(plugin.name)); + error.setUrl(QUrl::fromLocalFile(qmldirFilePath)); errors->prepend(error); } return false; @@ -494,12 +642,16 @@ bool QQmlImportsPrivate::importExtension(const QString &absoluteFilePath, const QString QQmlImportsPrivate::resolvedUri(const QString &dir_arg, QQmlImportDatabase *database) { + struct I { static bool greaterThan(const QString &s1, const QString &s2) { + return s1 > s2; + } }; + QString dir = dir_arg; if (dir.endsWith(QLatin1Char('/')) || dir.endsWith(QLatin1Char('\\'))) dir.chop(1); QStringList paths = database->fileImportPath; - qSort(paths.begin(), paths.end(), greaterThan); // Ensure subdirs preceed their parents. + qSort(paths.begin(), paths.end(), I::greaterThan); // Ensure subdirs preceed their parents. QString stableRelativePath = dir; foreach(const QString &path, paths) { @@ -520,208 +672,285 @@ QString QQmlImportsPrivate::resolvedUri(const QString &dir_arg, QQmlImportDataba } stableRelativePath.replace(QLatin1Char('/'), QLatin1Char('.')); + return stableRelativePath; } -QString QQmlImportsPrivate::add(const QQmlDirComponents &qmldircomponentsnetwork, - const QString& uri_arg, const QString& prefix, int vmaj, int vmin, - QQmlScript::Import::Type importType, - QQmlImportDatabase *database, QList<QQmlError> *errors) +/* +Locates the qmldir file for \a uri version \a vmaj.vmin. Returns true if found, +and fills in outQmldirFilePath and outQmldirUrl appropriately. Otherwise returns +false. +*/ +bool QQmlImportsPrivate::locateQmldir(const QString &uri, int vmaj, int vmin, + QQmlImportDatabase *database, + QString *outQmldirFilePath, + QString *outQmldirPathUrl) { - static QLatin1String Slash_qmldir("/qmldir"); - static QLatin1Char Slash('/'); - - QQmlDirComponents qmldircomponents = qmldircomponentsnetwork; - QQmlDirScripts qmldirscripts; - QString uri = uri_arg; - QQmlImportedNamespace *s; - if (prefix.isEmpty()) { - s = &unqualifiedset; - } else { - s = set.value(prefix); - if (!s) - set.insert(prefix,(s=new QQmlImportedNamespace)); + Q_ASSERT(vmaj >= 0 && vmin >= 0); // Versions are always specified for libraries + + // Check cache first + + QQmlImportDatabase::QmldirCache *cacheHead = 0; + { + QQmlImportDatabase::QmldirCache **cachePtr = database->qmldirCache.value(uri); + if (cachePtr) { + cacheHead = *cachePtr; + QQmlImportDatabase::QmldirCache *cache = cacheHead; + while (cache) { + if (cache->versionMajor == vmaj && cache->versionMinor == vmin) { + *outQmldirFilePath = cache->qmldirFilePath; + *outQmldirPathUrl = cache->qmldirPathUrl; + return !cache->qmldirFilePath.isEmpty(); + } + cache = cache->next; + } + } } - QString url = uri; - bool versionFound = false; - if (importType == QQmlScript::Import::Library) { - Q_ASSERT(vmaj >= 0 && vmin >= 0); // Versions are always specified for libraries + static QLatin1Char Slash('/'); + static QLatin1String Slash_qmldir("/qmldir"); - url.replace(QLatin1Char('.'), Slash); - bool found = false; - QString dir; - QString qmldir; + QString url = uri; + url.replace(QLatin1Char('.'), Slash); - // step 1: search for extension with fully encoded version number + // step 0: search for extension with fully encoded version number (eg. MyModule.3.2) + // step 1: search for extension with encoded version major (eg. MyModule.3) + // step 2: search for extension without version number (eg. MyModule) + for (int step = 0; step <= 2; ++step) { foreach (const QString &p, database->fileImportPath) { - dir = p+Slash+url; - - QFileInfo fi(dir+QString(QLatin1String(".%1.%2")).arg(vmaj).arg(vmin)+QLatin1String("/qmldir")); - const QString absoluteFilePath = fi.absoluteFilePath(); + QString dir = p + Slash + url; - if (fi.isFile()) { - found = true; + QString qmldirFile = dir; + if (step == 0) qmldirFile += QString(QLatin1String(".%1.%2")).arg(vmaj).arg(vmin); + else if (step == 1) qmldirFile += QString(QLatin1String(".%1")).arg(vmaj); + qmldirFile += Slash_qmldir; - const QString absolutePath = fi.absolutePath(); + QQmlTypeLoader &typeLoader = QQmlEnginePrivate::get(database->engine)->typeLoader; + QString absoluteFilePath = typeLoader.absoluteFilePath(qmldirFile); + if (!absoluteFilePath.isEmpty()) { + QString absolutePath = absoluteFilePath.left(absoluteFilePath.lastIndexOf(Slash)+1); if (absolutePath.at(0) == QLatin1Char(':')) url = QLatin1String("qrc://") + absolutePath.mid(1); else - url = QUrl::fromLocalFile(fi.absolutePath()).toString(); - uri = resolvedUri(dir, database); - if (!importExtension(absoluteFilePath, uri, database, &qmldircomponents, &qmldirscripts, errors)) - return QString(); - break; + url = QUrl::fromLocalFile(absolutePath).toString(); + + QQmlImportDatabase::QmldirCache *cache = new QQmlImportDatabase::QmldirCache; + cache->versionMajor = vmaj; + cache->versionMinor = vmin; + cache->qmldirFilePath = absoluteFilePath; + cache->qmldirPathUrl = url; + cache->next = cacheHead; + database->qmldirCache.insert(uri, cache); + + *outQmldirFilePath = absoluteFilePath; + *outQmldirPathUrl = url; + + return true; } } + } - if (!found) { - // step 2: search for extension with encoded version major - foreach (const QString &p, database->fileImportPath) { - dir = p+Slash+url; - - QFileInfo fi(dir+QString(QLatin1String(".%1")).arg(vmaj)+QLatin1String("/qmldir")); - const QString absoluteFilePath = fi.absoluteFilePath(); - - if (fi.isFile()) { - found = true; - - const QString absolutePath = fi.absolutePath(); - if (absolutePath.at(0) == QLatin1Char(':')) - url = QLatin1String("qrc://") + absolutePath.mid(1); - else - url = QUrl::fromLocalFile(fi.absolutePath()).toString(); - uri = resolvedUri(dir, database); - if (!importExtension(absoluteFilePath, uri, database, &qmldircomponents, &qmldirscripts, errors)) - return QString(); - break; - } - } + QQmlImportDatabase::QmldirCache *cache = new QQmlImportDatabase::QmldirCache; + cache->versionMajor = vmaj; + cache->versionMinor = vmin; + cache->next = cacheHead; + database->qmldirCache.insert(uri, cache); + + return false; +} + +bool QQmlImportsPrivate::addImport(const QQmlDirComponents &qmldircomponentsnetwork, + const QString& importedUri, const QString& prefix, + int vmaj, int vmin, QQmlScript::Import::Type importType, + bool isImplicitImport, QQmlImportDatabase *database, + QString *outUrl, QList<QQmlError> *errors) +{ + Q_ASSERT(errors); + Q_ASSERT(importType == QQmlScript::Import::File || importType == QQmlScript::Import::Library); + + static QLatin1String String_qmldir("qmldir"); + static QLatin1String Slash_qmldir("/qmldir"); + static QLatin1Char Slash('/'); + + // The list of components defined by a qmldir file for this import. + QQmlDirComponents qmldircomponents; + // The list of scripts defined by a qmldir file for this import. + QQmlDirScripts qmldirscripts; + // The namespace that this import affects. + QQmlImportNamespace *importSet = 0; + // The uri for this import. For library imports this is the same as importedUri + // specified by the user, but it may be different in the case of file imports. + QString uri; + // The url for the path containing files for this import. + QString url; + + qmldircomponents = qmldircomponentsnetwork; + uri = importedUri; + if (prefix.isEmpty()) { + importSet = &unqualifiedset; + } else { + importSet = set.value(prefix); + if (!importSet) { + importSet = new QQmlImportNamespace; + set.insert(prefix, importSet); } + } + + if (importType == QQmlScript::Import::Library) { + Q_ASSERT(vmaj >= 0 && vmin >= 0); + + QString qmldirFilePath; + + if (locateQmldir(uri, vmaj, vmin, database, &qmldirFilePath, &url)) { + + if (!importExtension(qmldirFilePath, uri, database, &qmldircomponents, + &qmldirscripts, &url, errors)) + return false; - if (!found) { - // step 3: search for extension without version number - - foreach (const QString &p, database->fileImportPath) { - dir = p+Slash+url; - qmldir = dir+Slash_qmldir; - - QString absoluteFilePath = typeLoader->absoluteFilePath(qmldir); - if (!absoluteFilePath.isEmpty()) { - found = true; - QString absolutePath = absoluteFilePath.left(absoluteFilePath.lastIndexOf(Slash)+1); - if (absolutePath.at(0) == QLatin1Char(':')) - url = QLatin1String("qrc://") + absolutePath.mid(1); - else - url = QUrl::fromLocalFile(absolutePath).toString(); - uri = resolvedUri(dir, database); - if (!importExtension(absoluteFilePath, uri, database, &qmldircomponents, &qmldirscripts, errors)) - return QString(); - break; - } - } } - if (QQmlMetaType::isModule(uri, vmaj, vmin)) - versionFound = true; + if (!QQmlMetaType::isModule(uri, vmaj, vmin)) { - if (!versionFound && qmldircomponents.isEmpty() && qmldirscripts.isEmpty()) { - if (errors) { - QQmlError error; // we don't set the url or line or column as these will be set by the loader. + if (qmldircomponents.isEmpty() && qmldirscripts.isEmpty()) { + QQmlError error; if (QQmlMetaType::isAnyModule(uri)) - error.setDescription(QQmlImportDatabase::tr("module \"%1\" version %2.%3 is not installed").arg(uri_arg).arg(vmaj).arg(vmin)); + error.setDescription(tr("module \"%1\" version %2.%3 is not installed").arg(importedUri).arg(vmaj).arg(vmin)); else - error.setDescription(QQmlImportDatabase::tr("module \"%1\" is not installed").arg(uri_arg)); + error.setDescription(tr("module \"%1\" is not installed").arg(importedUri)); errors->prepend(error); + return false; + } else { + int lowest_min = INT_MAX; + int highest_min = INT_MIN; + typedef QList<QQmlDirParser::Component>::const_iterator ConstIterator; + typedef QList<QQmlDirParser::Script>::const_iterator SConstIterator; + + for (ConstIterator cit = qmldircomponents.constBegin(); + cit != qmldircomponents.constEnd(); ++cit) { + if (cit->majorVersion == vmaj) { + lowest_min = qMin(lowest_min, cit->minorVersion); + highest_min = qMax(highest_min, cit->minorVersion); + } + } + + for (SConstIterator cit = qmldirscripts.constBegin(); + cit != qmldirscripts.constEnd(); ++cit) { + if (cit->majorVersion == vmaj) { + lowest_min = qMin(lowest_min, cit->minorVersion); + highest_min = qMax(highest_min, cit->minorVersion); + } + } + + if (lowest_min > vmin || highest_min < vmin) { + QQmlError error; + error.setDescription(tr("module \"%1\" version %2.%3 is not installed").arg(importedUri).arg(vmaj).arg(vmin)); + errors->prepend(error); + return false; + } } - return QString(); + } } else { - if (importType == QQmlScript::Import::File && qmldircomponents.isEmpty()) { - QString importUrl = resolveLocalUrl(base, uri + Slash_qmldir); - QString localFileOrQrc = QQmlEnginePrivate::urlToLocalFileOrQrc(importUrl); - if (!localFileOrQrc.isEmpty()) { - QString dir = QQmlEnginePrivate::urlToLocalFileOrQrc(resolveLocalUrl(base, uri)); - if (!typeLoader->directoryExists(dir)) { - if (errors) { - QQmlError error; // we don't set the line or column as these will be set by the loader. - error.setDescription(QQmlImportDatabase::tr("\"%1\": no such directory").arg(uri_arg)); - error.setUrl(QUrl(importUrl)); + + Q_ASSERT(importType == QQmlScript::Import::File); + + if (qmldircomponents.isEmpty()) { + + QString qmldirPath = uri; + if (uri.endsWith(Slash)) qmldirPath += String_qmldir; + else qmldirPath += Slash_qmldir; + QString qmldirUrl = resolveLocalUrl(base, qmldirPath); + + if (QQmlFile::isBundle(qmldirUrl)) { + + QString dir = resolveLocalUrl(base, uri); + Q_ASSERT(QQmlFile::isBundle(dir)); + if (!QQmlFile::bundleDirectoryExists(dir, typeLoader->engine())) { + if (!isImplicitImport) { + QQmlError error; + error.setDescription(tr("\"%1\": no such directory").arg(importedUri)); + error.setUrl(QUrl(qmldirUrl)); errors->prepend(error); } - return QString(); // local import dirs must exist + return false; } + + // Transforms the (possible relative) uri into our best guess relative to the + // import paths. uri = resolvedUri(dir, database); + if (uri.endsWith(Slash)) uri.chop(1); - if (!typeLoader->absoluteFilePath(localFileOrQrc).isEmpty()) { - if (!importExtension(localFileOrQrc,uri,database,&qmldircomponents,&qmldirscripts,errors)) - return QString(); + if (QQmlFile::bundleFileExists(qmldirUrl, typeLoader->engine())) { + if (!importExtension(qmldirUrl, uri, database, &qmldircomponents, + &qmldirscripts, &url, errors)) + return false; } - } else { - if (prefix.isEmpty()) { - // directory must at least exist for valid import - QString localFileOrQrc = QQmlEnginePrivate::urlToLocalFileOrQrc(resolveLocalUrl(base, uri)); - if (!typeLoader->directoryExists(localFileOrQrc)) { - if (errors) { - QQmlError error; // we don't set the line or column as these will be set by the loader. - if (localFileOrQrc.isEmpty()) - error.setDescription(QQmlImportDatabase::tr("import \"%1\" has no qmldir and no namespace").arg(uri)); - else - error.setDescription(QQmlImportDatabase::tr("\"%1\": no such directory").arg(uri)); - error.setUrl(QUrl(importUrl)); - errors->prepend(error); - } - return QString(); + + } else if (QQmlFile::isLocalFile(qmldirUrl)) { + + QString localFileOrQrc = QQmlFile::urlToLocalFileOrQrc(qmldirUrl); + Q_ASSERT(!localFileOrQrc.isEmpty()); + + QString dir = QQmlFile::urlToLocalFileOrQrc(resolveLocalUrl(base, uri)); + if (!typeLoader->directoryExists(dir)) { + if (!isImplicitImport) { + QQmlError error; + error.setDescription(tr("\"%1\": no such directory").arg(importedUri)); + error.setUrl(QUrl(qmldirUrl)); + errors->prepend(error); } + return false; } - } - } - url = resolveLocalUrl(base, url); - } + // Transforms the (possible relative) uri into our best guess relative to the + // import paths. + uri = resolvedUri(dir, database); + + if (uri.endsWith(Slash)) + uri.chop(1); + if (!typeLoader->absoluteFilePath(localFileOrQrc).isEmpty()) { + if (!importExtension(localFileOrQrc, uri, database, &qmldircomponents, + &qmldirscripts, &url, errors)) + return false; + } - if (!versionFound && (vmaj > -1) && (vmin > -1) && !qmldircomponents.isEmpty()) { - int lowest_min = INT_MAX; - int highest_min = INT_MIN; + } else if (prefix.isEmpty()) { - QList<QQmlDirParser::Component>::const_iterator cend = qmldircomponents.constEnd(); - for (QList<QQmlDirParser::Component>::const_iterator cit = qmldircomponents.constBegin(); cit != cend; ++cit) { - if (cit->majorVersion == vmaj) { - lowest_min = qMin(lowest_min, cit->minorVersion); - highest_min = qMax(highest_min, cit->minorVersion); - } - } + if (!isImplicitImport) { + QQmlError error; + error.setDescription(tr("import \"%1\" has no qmldir and no namespace").arg(uri)); + error.setUrl(QUrl(qmldirUrl)); + errors->prepend(error); + } + + return false; - if (lowest_min > vmin || highest_min < vmin) { - if (errors) { - QQmlError error; // we don't set the url or line or column information, as these will be set by the loader. - error.setDescription(QQmlImportDatabase::tr("module \"%1\" version %2.%3 is not installed").arg(uri_arg).arg(vmaj).arg(vmin)); - errors->prepend(error); } - return QString(); } + + url = resolveLocalUrl(base, importedUri); + if (!url.endsWith(Slash)) + url += Slash; } - if (!url.endsWith(Slash)) - url += Slash; + Q_ASSERT(url.isEmpty() || url.endsWith(Slash)); QMap<QString, QQmlDirParser::Script> scripts; - if (!qmldirscripts.isEmpty()) { // Verify that we haven't imported these scripts already - QList<QQmlImportedNamespace::Data>::const_iterator end = s->imports.constEnd(); - for (QList<QQmlImportedNamespace::Data>::const_iterator it = s->imports.constBegin(); it != end; ++it) { + for (QList<QQmlImportNamespace::Import>::const_iterator it = importSet->imports.constBegin(); + it != importSet->imports.constEnd(); ++it) { if (it->uri == uri) { QQmlError error; - error.setDescription(QQmlImportDatabase::tr("\"%1\" is ambiguous. Found in %2 and in %3").arg(uri).arg(url).arg(it->url)); + error.setDescription(tr("\"%1\" is ambiguous. Found in %2 and in %3").arg(uri).arg(url).arg(it->url)); errors->prepend(error); - return QString(); + return false; } } - QList<QQmlDirParser::Script>::const_iterator send = qmldirscripts.constEnd(); - for (QList<QQmlDirParser::Script>::const_iterator sit = qmldirscripts.constBegin(); sit != send; ++sit) { + for (QList<QQmlDirParser::Script>::const_iterator sit = qmldirscripts.constBegin(); + sit != qmldirscripts.constEnd(); ++sit) { // Only include scripts that match our requested version if (((vmaj == -1) || (sit->majorVersion == vmaj)) && ((vmin == -1) || (sit->minorVersion <= vmin))) { @@ -735,124 +964,80 @@ QString QQmlImportsPrivate::add(const QQmlDirComponents &qmldircomponentsnetwork } } - QQmlImportedNamespace::Data data; - data.uri = uri; - data.url = url; - data.majversion = vmaj; - data.minversion = vmin; - data.isLibrary = importType == QQmlScript::Import::Library; - data.qmlDirComponents = qmldircomponents; - data.qmlDirScripts = scripts.values(); + QQmlImportNamespace::Import import; + import.uri = uri; + import.url = url; + import.majversion = vmaj; + import.minversion = vmin; + import.isLibrary = importType == QQmlScript::Import::Library; + import.qmlDirComponents = qmldircomponents; + import.qmlDirScripts = scripts.values(); + + importSet->imports.prepend(import); - s->imports.prepend(data); + if (outUrl) *outUrl = url; - return data.url; + return true; } -bool QQmlImportsPrivate::find(const QString& type, int *vmajor, int *vminor, QQmlType** type_return, - QString* url_return, QList<QQmlError> *errors) +/*! + \internal + + Adds an implicit "." file import. This is equivalent to calling addImport(), but error + messages related to the path or qmldir file not existing are suppressed. +*/ +bool QQmlImports::addImplicitImport(QQmlImportDatabase *importDb, + const QQmlDirComponents &qmldircomponentsnetwork, + QList<QQmlError> *errors) { - QQmlImportedNamespace *s = 0; - int slash = type.indexOf(QLatin1Char('/')); - if (slash >= 0) { - QString namespaceName = type.left(slash); - s = set.value(namespaceName); - if (!s) { - if (errors) { - QQmlError error; - error.setDescription(QQmlImportDatabase::tr("- %1 is not a namespace").arg(namespaceName)); - errors->prepend(error); - } - return false; - } - int nslash = type.indexOf(QLatin1Char('/'),slash+1); - if (nslash > 0) { - if (errors) { - QQmlError error; - error.setDescription(QQmlImportDatabase::tr("- nested namespaces not allowed")); - errors->prepend(error); - } - return false; - } - } else { - s = &unqualifiedset; - } - QString unqualifiedtype = slash < 0 ? type : type.mid(slash+1); // common-case opt (QString::mid works fine, but slower) - if (s) { - if (s->find(typeLoader,unqualifiedtype,vmajor,vminor,type_return,url_return, &base, errors)) - return true; - if (s->imports.count() == 1 && !s->imports.at(0).isLibrary && url_return && s != &unqualifiedset) { - // qualified, and only 1 url - *url_return = resolveLocalUrl(s->imports.at(0).url, unqualifiedtype + QLatin1String(".qml")); - return true; - } - } + Q_ASSERT(errors); - return false; -} + if (qmlImportTrace()) + qDebug().nospace() << "QQmlImports(" << qPrintable(baseUrl().toString()) + << ")::addImplicitImport"; -QQmlImportedNamespace *QQmlImportsPrivate::findNamespace(const QString& type) -{ - return set.value(type); + + return d->addImport(qmldircomponentsnetwork, QLatin1String("."), QString(), -1, -1, + QQmlScript::Import::File, true, importDb, 0, errors); } -bool QQmlImportedNamespace::find(QQmlTypeLoader *typeLoader, const QString& type, int *vmajor, int *vminor, QQmlType** type_return, - QString* url_return, QString *base, QList<QQmlError> *errors) +/*! + \internal + + Adds information to \a imports such that subsequent calls to resolveType() + will resolve types qualified by \a prefix by considering types found at the given \a uri. + + The uri is either a directory (if importType is FileImport), or a URI resolved using paths + added via addImportPath() (if importType is LibraryImport). + + The \a prefix may be empty, in which case the import location is considered for + unqualified types. + + The base URL must already have been set with Import::setBaseUrl(). + + Optionally, the url the import resolved to can be returned by providing the url parameter. + Not all imports will result in an output url being generated, in which case the url will + be set to an empty string. + + Returns true on success, and false on failure. In case of failure, the errors array will + filled appropriately. +*/ +bool QQmlImports::addImport(QQmlImportDatabase *importDb, + const QString& uri, const QString& prefix, int vmaj, int vmin, + QQmlScript::Import::Type importType, + const QQmlDirComponents &qmldircomponentsnetwork, + QString *url, QList<QQmlError> *errors) { - bool typeRecursionDetected = false; - for (int i=0; i<imports.count(); ++i) { - if (find_helper(typeLoader, imports.at(i), type, vmajor, vminor, type_return, url_return, base, &typeRecursionDetected)) { - if (qmlCheckTypes()) { - // check for type clashes - for (int j = i+1; j<imports.count(); ++j) { - if (find_helper(typeLoader, imports.at(j), type, vmajor, vminor, 0, 0, base)) { - if (errors) { - QString u1 = imports.at(i).url; - QString u2 = imports.at(j).url; - if (base) { - QString b = *base; - int slash = b.lastIndexOf(QLatin1Char('/')); - if (slash >= 0) { - b = b.left(slash+1); - QString l = b.left(slash); - if (u1.startsWith(b)) - u1 = u1.mid(b.count()); - else if (u1 == l) - u1 = QQmlImportDatabase::tr("local directory"); - if (u2.startsWith(b)) - u2 = u2.mid(b.count()); - else if (u2 == l) - u2 = QQmlImportDatabase::tr("local directory"); - } - } + Q_ASSERT(errors); - QQmlError error; - if (u1 != u2) { - error.setDescription(QQmlImportDatabase::tr("is ambiguous. Found in %1 and in %2").arg(u1).arg(u2)); - } else { - error.setDescription(QQmlImportDatabase::tr("is ambiguous. Found in %1 in version %2.%3 and %4.%5") - .arg(u1) - .arg(imports.at(i).majversion).arg(imports.at(i).minversion) - .arg(imports.at(j).majversion).arg(imports.at(j).minversion)); - } - errors->prepend(error); - } - return false; - } - } - } - return true; - } - } - if (errors) { - QQmlError error; - if (typeRecursionDetected) - error.setDescription(QQmlImportDatabase::tr("is instantiated recursively")); - else - error.setDescription(QQmlImportDatabase::tr("is not a type")); - errors->prepend(error); - } - return false; + if (qmlImportTrace()) + qDebug().nospace() << "QQmlImports(" << qPrintable(baseUrl().toString()) << ")" << "::addImport: " + << uri << " " << vmaj << '.' << vmin << " " + << (importType==QQmlScript::Import::Library? "Library" : "File") + << " as " << prefix; + + return d->addImport(qmldircomponentsnetwork, uri, prefix, vmaj, vmin, importType, false, + importDb, url, errors); } /*! @@ -890,37 +1075,16 @@ QQmlImportDatabase::QQmlImportDatabase(QQmlEngine *e) QQmlImportDatabase::~QQmlImportDatabase() { -} - -/*! - \internal - - Adds information to \a imports such that subsequent calls to resolveType() - will resolve types qualified by \a prefix by considering types found at the given \a uri. - - The uri is either a directory (if importType is FileImport), or a URI resolved using paths - added via addImportPath() (if importType is LibraryImport). - - The \a prefix may be empty, in which case the import location is considered for - unqualified types. - - Returns the resolved URL of the import on success. - - The base URL must already have been set with Import::setBaseUrl(). -*/ -QString QQmlImports::addImport(QQmlImportDatabase *importDb, - const QString& uri, const QString& prefix, int vmaj, int vmin, - QQmlScript::Import::Type importType, - const QQmlDirComponents &qmldircomponentsnetwork, - QList<QQmlError> *errors) -{ - if (qmlImportTrace()) - qDebug().nospace() << "QQmlImports(" << qPrintable(baseUrl().toString()) << ")" << "::addImport: " - << uri << " " << vmaj << '.' << vmin << " " - << (importType==QQmlScript::Import::Library? "Library" : "File") - << " as " << prefix; - - return d->add(qmldircomponentsnetwork, uri, prefix, vmaj, vmin, importType, importDb, errors); + for (QStringHash<QmldirCache *>::ConstIterator iter = qmldirCache.begin(); + iter != qmldirCache.end(); ++iter) { + + QmldirCache *c = *iter; + while (c) { + QmldirCache *n = c->next; + delete c; + c = n; + } + } } /*! @@ -932,9 +1096,10 @@ QString QQmlImports::addImport(QQmlImportDatabase *importDb, \a qmldirPath is the location of the qmldir file. */ QString QQmlImportDatabase::resolvePlugin(QQmlTypeLoader *typeLoader, - const QString &qmldirPath, const QString &qmldirPluginPath, - const QString &baseName, const QStringList &suffixes, - const QString &prefix) + const QString &qmldirPath, + const QString &qmldirPluginPath, + const QString &baseName, const QStringList &suffixes, + const QString &prefix) { QStringList searchPaths = filePluginPath; bool qmldirPluginPathIsRelative = QDir::isRelativePath(qmldirPluginPath); diff --git a/src/qml/qml/qqmlimport_p.h b/src/qml/qml/qqmlimport_p.h index 422f2429a0bd20aef9985b1dc58afe50057ff559..d673b64766f17b95a87a444cb5fabf334a0eb9b4 100644 --- a/src/qml/qml/qqmlimport_p.h +++ b/src/qml/qml/qqmlimport_p.h @@ -66,7 +66,7 @@ QT_BEGIN_NAMESPACE class QQmlTypeNameCache; class QQmlEngine; class QDir; -class QQmlImportedNamespace; +class QQmlImportNamespace; class QQmlImportsPrivate; class QQmlImportDatabase; class QQmlTypeLoader; @@ -86,18 +86,22 @@ public: bool resolveType(const QString& type, QQmlType** type_return, QString* url_return, int *version_major, int *version_minor, - QQmlImportedNamespace** ns_return, + QQmlImportNamespace** ns_return, QList<QQmlError> *errors = 0) const; - bool resolveType(QQmlImportedNamespace*, + bool resolveType(QQmlImportNamespace*, const QString& type, QQmlType** type_return, QString* url_return, int *version_major, int *version_minor) const; - QString addImport(QQmlImportDatabase *, + bool addImplicitImport(QQmlImportDatabase *importDb, + const QQmlDirComponents &qmldircomponentsnetwork, + QList<QQmlError> *errors); + + bool addImport(QQmlImportDatabase *, const QString& uri, const QString& prefix, int vmaj, int vmin, QQmlScript::Import::Type importType, const QQmlDirComponents &qmldircomponentsnetwork, - QList<QQmlError> *errors); + QString *url, QList<QQmlError> *errors); void populateCache(QQmlTypeNameCache *cache, QQmlEngine *) const; @@ -142,6 +146,16 @@ private: const QString &qmldirPath, const QString &qmldirPluginPath, const QString &baseName); + struct QmldirCache { + int versionMajor; + int versionMinor; + QString qmldirFilePath; + QString qmldirPathUrl; + QmldirCache *next; + }; + // Maps from an import to a linked list of qmldir info. + // Used in QQmlImportsPrivate::locateQmldir() + QStringHash<QmldirCache *> qmldirCache; // XXX thread QStringList filePluginPath; diff --git a/src/qml/qml/qqmlscript.cpp b/src/qml/qml/qqmlscript.cpp index 05882f313d4de14fbc0f095f34ba4042c5271d34..b1a50a4f9a63bd362918737aa420488bca49e05d 100644 --- a/src/qml/qml/qqmlscript.cpp +++ b/src/qml/qml/qqmlscript.cpp @@ -451,22 +451,6 @@ QStringList QQmlScript::Variant::asStringList() const // // Actual parser classes // -void QQmlScript::Import::extractVersion(int *maj, int *min) const -{ - *maj = -1; *min = -1; - - if (!version.isEmpty()) { - int dot = version.indexOf(QLatin1Char('.')); - if (dot < 0) { - *maj = version.toInt(); - *min = 0; - } else { - *maj = version.left(dot).toInt(); - *min = version.mid(dot+1).toInt(); - } - } -} - namespace { class ProcessAST: protected AST::Visitor @@ -526,6 +510,8 @@ public: void operator()(const QString &code, AST::Node *node); + static void extractVersion(QStringRef string, int *maj, int *min); + protected: QQmlScript::Object *defineObjectBinding(AST::UiQualifiedId *propertyName, bool onAssignment, @@ -661,6 +647,26 @@ QString ProcessAST::qualifiedNameId() const return _scope.join(QLatin1String("/")); } +void ProcessAST::extractVersion(QStringRef string, int *maj, int *min) +{ + *maj = -1; *min = -1; + + if (!string.isEmpty()) { + + int dot = string.indexOf(QLatin1Char('.')); + + if (dot < 0) { + *maj = string.toString().toInt(); + *min = 0; + } else { + const QString *s = string.string(); + int p = string.position(); + *maj = QStringRef(s, p, dot).toString().toInt(); + *min = QStringRef(s, p + dot + 1, string.size() - dot - 1).toString().toInt(); + } + } +} + QString ProcessAST::asString(AST::UiQualifiedId *node) const { QString s; @@ -898,7 +904,7 @@ bool ProcessAST::visit(AST::UiImport *node) } if (node->versionToken.isValid()) { - import.version = textAt(node->versionToken); + extractVersion(textRefAt(node->versionToken), &import.majorVersion, &import.minorVersion); } else if (import.type == QQmlScript::Import::Library) { QQmlError error; error.setDescription(QCoreApplication::translate("QQmlParser","Library import requires a version")); @@ -1290,8 +1296,13 @@ public: }; } -bool QQmlScript::Parser::parse(const QByteArray &qmldata, const QUrl &url, - const QString &urlString) +QByteArray QQmlScript::Parser::preparseData() const +{ + return QByteArray(); +} + +bool QQmlScript::Parser::parse(const QString &qmlcode, const QByteArray &preparseData, + const QUrl &url, const QString &urlString) { clear(); @@ -1302,11 +1313,7 @@ bool QQmlScript::Parser::parse(const QByteArray &qmldata, const QUrl &url, _scriptFile = urlString; } - QTextStream stream(qmldata, QIODevice::ReadOnly); -#ifndef QT_NO_TEXTCODEC - stream.setCodec("UTF-8"); -#endif - QString *code = _pool.NewString(stream.readAll()); + QString *code = _pool.NewString(qmlcode); data = new QQmlScript::ParserJsASTData(_scriptFile); @@ -1573,7 +1580,6 @@ QQmlScript::Parser::JavaScriptMetaData QQmlScript::Parser::extractMetaData(QStri } else { // URI QString uri; - QString version; while (true) { if (!isUriToken(token)) @@ -1593,7 +1599,9 @@ QQmlScript::Parser::JavaScriptMetaData QQmlScript::Parser::extractMetaData(QStri } CHECK_TOKEN(T_NUMERIC_LITERAL); - version = script.mid(l.tokenOffset(), l.tokenLength()); + int vmaj, vmin; + ProcessAST::extractVersion(QStringRef(&script, l.tokenOffset(), l.tokenLength()), + &vmaj, &vmin); token = l.lex(); @@ -1624,7 +1632,8 @@ QQmlScript::Parser::JavaScriptMetaData QQmlScript::Parser::extractMetaData(QStri Import import; import.type = Import::Library; import.uri = uri; - import.version = version; + import.majorVersion = vmaj; + import.minorVersion = vmin; import.qualifier = importId; import.location = location; diff --git a/src/qml/qml/qqmlscript_p.h b/src/qml/qml/qqmlscript_p.h index 8705f2aef961dfbb28f47e9837f74bd38ab46f95..1e46e6b655e372ae777125a85e825f871728cb70 100644 --- a/src/qml/qml/qqmlscript_p.h +++ b/src/qml/qml/qqmlscript_p.h @@ -109,16 +109,16 @@ struct LocationSpan class Import { public: - Import() : type(Library) {} + Import() : type(Library), majorVersion(-1), minorVersion(-1) {} enum Type { Library, File, Script }; Type type; QString uri; QString qualifier; - QString version; - void extractVersion(int *maj, int *min) const; + int majorVersion; + int minorVersion; QQmlScript::LocationSpan location; }; @@ -469,14 +469,16 @@ public: }; class ParserJsASTData; -class Q_AUTOTEST_EXPORT Parser +class Q_QML_EXPORT Parser { public: Parser(); ~Parser(); - bool parse(const QByteArray &data, const QUrl &url = QUrl(), - const QString &urlString = QString()); + bool parse(const QString &data, const QByteArray &preparseData, + const QUrl &url = QUrl(), const QString &urlString = QString()); + + QByteArray preparseData() const; QList<TypeReference*> referencedTypes() const; diff --git a/src/qml/qml/qqmltypeloader.cpp b/src/qml/qml/qqmltypeloader.cpp index abe2c0bfa4628d0bc1fa943c182b4de659759a2c..2e8f17e6cd0e07525da4e9fd2239d11eb17f0e23 100644 --- a/src/qml/qml/qqmltypeloader.cpp +++ b/src/qml/qml/qqmltypeloader.cpp @@ -53,9 +53,10 @@ #include <QtCore/qdebug.h> #include <QtCore/qmutex.h> #include <QtCore/qthread.h> +#include <QtQml/qqmlfile.h> #include <QtCore/qdiriterator.h> -#include <QtCore/qwaitcondition.h> #include <QtQml/qqmlcomponent.h> +#include <QtCore/qwaitcondition.h> #include <QtQml/qqmlextensioninterface.h> #if defined (Q_OS_UNIX) @@ -92,6 +93,8 @@ #endif +DEFINE_BOOL_CONFIG_OPTION(dumpErrors, QML_DUMP_ERRORS); + QT_BEGIN_NAMESPACE // This is a lame object that we need to ensure that slots connected to @@ -453,6 +456,11 @@ void QQmlDataBlob::setError(const QList<QQmlError> &errors) m_errors = errors; // Must be set before the m_data fence m_data.setStatus(Error); + if (dumpErrors()) { + qWarning().nospace() << "Errors for " << m_finalUrl.toString(); + for (int ii = 0; ii < errors.count(); ++ii) + qWarning().nospace() << " " << qPrintable(errors.at(ii).toString()); + } cancelAllWaitingFor(); if (!m_inCallback) @@ -486,7 +494,7 @@ void QQmlDataBlob::addDependency(QQmlDataBlob *blob) } /*! -\fn void QQmlDataBlob::dataReceived(const QByteArray &data) +\fn void QQmlDataBlob::dataReceived(const Data &data) Invoked when data for the blob is received. Implementors should use this callback to determine a blob's dependencies. Within this callback you may call setError() @@ -1018,28 +1026,22 @@ void QQmlDataLoader::loadThread(QQmlDataBlob *blob) return; } - QString lf = QQmlEnginePrivate::urlToLocalFileOrQrc(blob->m_url); + if (QQmlFile::isSynchronous(blob->m_url)) { + QQmlFile file(m_engine, blob->m_url); - if (!lf.isEmpty()) { - if (!QQml_isFileCaseCorrect(lf)) { + if (file.isError()) { QQmlError error; error.setUrl(blob->m_url); - error.setDescription(QLatin1String("File name case mismatch")); + error.setDescription(file.error()); blob->setError(error); return; } - QFile file(lf); - if (file.open(QFile::ReadOnly)) { - QByteArray data = file.readAll(); - blob->m_data.setProgress(0xFF); - if (blob->m_data.isAsync()) - m_thread->callDownloadProgressChanged(blob, 1.); + blob->m_data.setProgress(0xFF); + if (blob->m_data.isAsync()) + m_thread->callDownloadProgressChanged(blob, 1.); - setData(blob, data); - } else { - blob->networkError(QNetworkReply::ContentNotFoundError); - } + setData(blob, &file); } else { @@ -1137,10 +1139,24 @@ void QQmlDataLoader::initializeEngine(QQmlExtensionInterface *iface, void QQmlDataLoader::setData(QQmlDataBlob *blob, const QByteArray &data) +{ + QQmlDataBlob::Data d; + d.d = &data; + setData(blob, d); +} + +void QQmlDataLoader::setData(QQmlDataBlob *blob, QQmlFile *file) +{ + QQmlDataBlob::Data d; + d.d = file; + setData(blob, d); +} + +void QQmlDataLoader::setData(QQmlDataBlob *blob, const QQmlDataBlob::Data &d) { blob->m_inCallback = true; - blob->dataReceived(data); + blob->dataReceived(d); if (!blob->isError() && !blob->isWaiting()) blob->allDependenciesDone(); @@ -1187,8 +1203,8 @@ Returns a QQmlTypeData for the specified \a url. The QQmlTypeData may be cached QQmlTypeData *QQmlTypeLoader::get(const QUrl &url, Mode mode) { Q_ASSERT(!url.isRelative() && - (QQmlEnginePrivate::urlToLocalFileOrQrc(url).isEmpty() || - !QDir::isRelativePath(QQmlEnginePrivate::urlToLocalFileOrQrc(url)))); + (QQmlFile::urlToLocalFileOrQrc(url).isEmpty() || + !QDir::isRelativePath(QQmlFile::urlToLocalFileOrQrc(url)))); lock(); @@ -1231,8 +1247,8 @@ Return a QQmlScriptBlob for \a url. The QQmlScriptData may be cached. QQmlScriptBlob *QQmlTypeLoader::getScript(const QUrl &url) { Q_ASSERT(!url.isRelative() && - (QQmlEnginePrivate::urlToLocalFileOrQrc(url).isEmpty() || - !QDir::isRelativePath(QQmlEnginePrivate::urlToLocalFileOrQrc(url)))); + (QQmlFile::urlToLocalFileOrQrc(url).isEmpty() || + !QDir::isRelativePath(QQmlFile::urlToLocalFileOrQrc(url)))); lock(); @@ -1257,8 +1273,8 @@ Returns a QQmlQmldirData for \a url. The QQmlQmldirData may be cached. QQmlQmldirData *QQmlTypeLoader::getQmldir(const QUrl &url) { Q_ASSERT(!url.isRelative() && - (QQmlEnginePrivate::urlToLocalFileOrQrc(url).isEmpty() || - !QDir::isRelativePath(QQmlEnginePrivate::urlToLocalFileOrQrc(url)))); + (QQmlFile::urlToLocalFileOrQrc(url).isEmpty() || + !QDir::isRelativePath(QQmlFile::urlToLocalFileOrQrc(url)))); lock(); @@ -1277,6 +1293,84 @@ QQmlQmldirData *QQmlTypeLoader::getQmldir(const QUrl &url) return qmldirData; } +/*! +Returns a QQmlBundleData for \a identifier. +*/ +QQmlBundleData *QQmlTypeLoader::getBundle(const QString &identifier) +{ + return getBundle(QHashedStringRef(identifier)); +} + +QQmlBundleData *QQmlTypeLoader::getBundle(const QHashedStringRef &identifier) +{ + lock(); + + QQmlBundleData *rv = 0; + QQmlBundleData **bundle = m_bundleCache.value(identifier); + if (bundle) { + rv = *bundle; + rv->addref(); + } + + unlock(); + + return rv; +} + +QQmlBundleData::QQmlBundleData(const QString &file) +: QQmlBundle(file), fileName(file) +{ +} + +// XXX check for errors etc. +void QQmlTypeLoader::addBundle(const QString &identifier, const QString &fileName) +{ + lock(); + addBundleNoLock(identifier, fileName); + unlock(); +} + +void QQmlTypeLoader::addBundleNoLock(const QString &identifier, const QString &fileName) +{ + QQmlBundleData *data = new QQmlBundleData(fileName); + if (data->open()) { + + m_bundleCache.insert(identifier, data); + + } else { + data->release(); + } +} + +QString QQmlTypeLoader::bundleIdForQmldir(const QString &name, const QString &uriHint) +{ + lock(); + QString *bundleId = m_qmldirBundleIdCache.value(name); + if (!bundleId) { + QString newBundleId = QLatin1String("qml.") + uriHint.toLower() /* XXX toLower()? */; + if (m_qmldirBundleIdCache.contains(newBundleId)) + newBundleId += QString::number(m_qmldirBundleIdCache.count()); + m_qmldirBundleIdCache.insert(name, newBundleId); + addBundleNoLock(newBundleId, name); + unlock(); + return newBundleId; + } else { + unlock(); + return *bundleId; + } +} + +bool QQmlEngine::addNamedBundle(const QString &name, const QString &fileName) +{ + Q_D(QQmlEngine); + + if (name.startsWith(QLatin1String("qml."))) // reserved + return false; + + d->typeLoader.addBundle(name, fileName); + return true; +} + /*! Returns the absolute filename of path via a directory cache for files named "qmldir", "*.qml", "*.js", and plugins. @@ -1366,21 +1460,79 @@ bool QQmlTypeLoader::directoryExists(const QString &path) /*! Return a QQmlDirParser for absoluteFilePath. The QQmlDirParser may be cached. + +\a filePath is either a bundle URL, or a local file path. */ -const QQmlDirParser *QQmlTypeLoader::qmlDirParser(const QString &absoluteFilePath) +const QQmlDirParser *QQmlTypeLoader::qmlDirParser(const QString &filePath, + const QString &uriHint, + QString *outUrl) { - QQmlDirParser *qmldirParser; - QQmlDirParser **val = m_importQmlDirCache.value(absoluteFilePath); + DirParser *qmldirParser; + DirParser **val = m_importQmlDirCache.value(filePath); if (!val) { - qmldirParser = new QQmlDirParser; - qmldirParser->setFileSource(absoluteFilePath); - qmldirParser->setUrl(QUrl::fromLocalFile(absoluteFilePath)); - qmldirParser->parse(); - m_importQmlDirCache.insert(absoluteFilePath, qmldirParser); + qmldirParser = new DirParser; + +#define ERROR(description) { QQmlError e; e.setDescription(description); qmldirParser->setError(e); } +#define NOT_READABLE_ERROR QString(QLatin1String("module \"$$URI$$\" definition \"%1\" not readable")) +#define CASE_MISMATCH_ERROR QString(QLatin1String("cannot load module \"$$URI$$\": File name case mismatch for \"%1\"")) + + if (QQmlFile::isBundle(filePath)) { + + QUrl url(filePath); + + QQmlFile file(engine(), url); + if (file.isError()) { + ERROR(NOT_READABLE_ERROR.arg(filePath)); + } else { + qmldirParser->setSource(QString::fromUtf8(file.data(), file.size())); + qmldirParser->parse(); + } + + } else { + + QFile file(filePath); + if (!QQml_isFileCaseCorrect(filePath)) { + ERROR(CASE_MISMATCH_ERROR.arg(filePath)); + } else if (file.open(QFile::ReadOnly)) { + QByteArray data = file.read(QQmlBundle::bundleHeaderLength()); + + if (QQmlBundle::isBundleHeader(data.constData(), data.length())) { + QString id = bundleIdForQmldir(filePath, uriHint); + + QString bundleUrl = QLatin1String("bundle://") + id + QLatin1String("/"); + qmldirParser->adjustedUrl = bundleUrl; + + QUrl url(bundleUrl + QLatin1String("qmldir")); + + QQmlFile file(engine(), url); + if (file.isError()) { + ERROR(NOT_READABLE_ERROR.arg(filePath)); + } else { + qmldirParser->setSource(QString::fromUtf8(file.data(), file.size())); + qmldirParser->parse(); + } + } else { + data += file.readAll(); + qmldirParser->setSource(QString::fromUtf8(data)); + qmldirParser->parse(); + } + } else { + ERROR(NOT_READABLE_ERROR.arg(filePath)); + } + + } + +#undef ERROR +#undef NOT_READABLE_ERROR +#undef CASE_MISMATCH_ERROR + + m_importQmlDirCache.insert(filePath, qmldirParser); } else { qmldirParser = *val; } + if (!qmldirParser->adjustedUrl.isEmpty()) + *outUrl = qmldirParser->adjustedUrl; return qmldirParser; } @@ -1531,9 +1683,14 @@ void QQmlTypeData::completed() } } -void QQmlTypeData::dataReceived(const QByteArray &data) +void QQmlTypeData::dataReceived(const Data &data) { - if (!scriptParser.parse(data, finalUrl(), finalUrlString())) { + QString code = QString::fromUtf8(data.data(), data.size()); + QByteArray preparseData; + + if (data.isFile()) preparseData = data.asFile()->metaData(QLatin1String("qml:preparse")); + + if (!scriptParser.parse(code, preparseData, finalUrl(), finalUrlString())) { setError(scriptParser.errors()); return; } @@ -1543,7 +1700,7 @@ void QQmlTypeData::dataReceived(const QByteArray &data) foreach (const QQmlScript::Import &import, scriptParser.imports()) { if (import.type == QQmlScript::Import::File && import.qualifier.isEmpty()) { QUrl importUrl = finalUrl().resolved(QUrl(import.uri + QLatin1String("/qmldir"))); - if (QQmlEnginePrivate::urlToLocalFileOrQrc(importUrl).isEmpty()) { + if (QQmlFile::urlToLocalFileOrQrc(importUrl).isEmpty()) { QQmlQmldirData *data = typeLoader()->getQmldir(importUrl); addDependency(data); m_qmldirs << data; @@ -1563,7 +1720,7 @@ void QQmlTypeData::dataReceived(const QByteArray &data) if (!finalUrl().scheme().isEmpty()) { QUrl importUrl = finalUrl().resolved(QUrl(QLatin1String("qmldir"))); - if (QQmlEnginePrivate::urlToLocalFileOrQrc(importUrl).isEmpty()) { + if (QQmlFile::urlToLocalFileOrQrc(importUrl).isEmpty()) { QQmlQmldirData *data = typeLoader()->getQmldir(importUrl); addDependency(data); m_qmldirs << data; @@ -1613,30 +1770,15 @@ void QQmlTypeData::resolveTypes() // For local urls, add an implicit import "." as first (most overridden) lookup. // This will also trigger the loading of the qmldir and the import of any native // types from available plugins. - QList<QQmlError> errors; + QList<QQmlError> implicitImportErrors; if (QQmlQmldirData *qmldir = qmldirForUrl(finalUrl().resolved(QUrl(QLatin1String("./qmldir"))))) { - m_imports.addImport(importDatabase, QLatin1String("."), - QString(), -1, -1, QQmlScript::Import::File, - qmldir->dirComponents(), &errors); + m_imports.addImplicitImport(importDatabase, qmldir->dirComponents(), &implicitImportErrors); } else { - m_imports.addImport(importDatabase, QLatin1String("."), - QString(), -1, -1, QQmlScript::Import::File, - QQmlDirComponents(), &errors); - } - - // remove any errors which are due to the implicit import which aren't real errors. - // for example, if the implicitly included qmldir file doesn't exist, that is not an error. - QList<QQmlError> realErrors; - for (int i = 0; i < errors.size(); ++i) { - if (errors.at(i).description() != QQmlImportDatabase::tr("import \".\" has no qmldir and no namespace") - && errors.at(i).description() != QQmlImportDatabase::tr("\".\": no such directory")) { - realErrors.prepend(errors.at(i)); // this is a real error. - } + m_imports.addImplicitImport(importDatabase, QQmlDirComponents(), &implicitImportErrors); } - // report any real errors which occurred during plugin loading or qmldir parsing. - if (!realErrors.isEmpty()) { - setError(realErrors); + if (!implicitImportErrors.isEmpty()) { + setError(implicitImportErrors); return; } @@ -1651,13 +1793,10 @@ void QQmlTypeData::resolveTypes() qmldircomponentsnetwork = qmldir->dirComponents(); } - int vmaj = -1; - int vmin = -1; - import.extractVersion(&vmaj, &vmin); - QList<QQmlError> errors; - if (m_imports.addImport(importDatabase, import.uri, import.qualifier, vmaj, vmin, - import.type, qmldircomponentsnetwork, &errors).isNull()) { + if (!m_imports.addImport(importDatabase, import.uri, import.qualifier, import.majorVersion, + import.minorVersion, import.type, qmldircomponentsnetwork, 0, + &errors)) { QQmlError error; if (errors.size()) { error = errors.takeFirst(); @@ -1703,7 +1842,7 @@ void QQmlTypeData::resolveTypes() QString url; int majorVersion; int minorVersion; - QQmlImportedNamespace *typeNamespace = 0; + QQmlImportNamespace *typeNamespace = 0; QList<QQmlError> errors; if (!m_imports.resolveType(parserRef->name, &ref.type, &url, &majorVersion, &minorVersion, @@ -1824,12 +1963,12 @@ QQmlScriptData *QQmlScriptBlob::scriptData() const return m_scriptData; } -void QQmlScriptBlob::dataReceived(const QByteArray &data) +void QQmlScriptBlob::dataReceived(const Data &data) { QQmlEnginePrivate *ep = QQmlEnginePrivate::get(m_typeLoader->engine()); QQmlImportDatabase *importDatabase = &ep->importDatabase; - m_source = QString::fromUtf8(data); + m_source = QString::fromUtf8(data.data(), data.size()); QQmlScript::Parser::JavaScriptMetaData metadata = QQmlScript::Parser::extractMetaData(m_source); @@ -1853,14 +1992,11 @@ void QQmlScriptBlob::dataReceived(const QByteArray &data) m_scripts << ref; } else { Q_ASSERT(import.type == QQmlScript::Import::Library); - int vmaj = -1; - int vmin = -1; - import.extractVersion(&vmaj, &vmin); - QList<QQmlError> errors; - QString importUrl = m_imports.addImport(importDatabase, import.uri, import.qualifier, vmaj, vmin, - import.type, QQmlDirComponents(), &errors); - if (importUrl.isNull()) { + QString importUrl; + if (!m_imports.addImport(importDatabase, import.uri, import.qualifier, import.majorVersion, + import.minorVersion, import.type, QQmlDirComponents(), &importUrl, + &errors)) { QQmlError error = errors.takeFirst(); // description should be set by addImport(). error.setUrl(m_imports.baseUrl()); @@ -1873,19 +2009,23 @@ void QQmlScriptBlob::dataReceived(const QByteArray &data) } // Does this library contain any scripts? - QUrl libraryUrl(importUrl); - const QQmlDirParser *dirParser = typeLoader()->qmlDirParser(libraryUrl.path() + QLatin1String("qmldir")); - foreach (const QQmlDirParser::Script &script, dirParser->scripts()) { - QUrl scriptUrl = libraryUrl.resolved(QUrl(script.fileName)); - QQmlScriptBlob *blob = typeLoader()->getScript(scriptUrl); - addDependency(blob); - - ScriptReference ref; - ref.location = import.location.start; - ref.qualifier = script.nameSpace; - ref.nameSpace = import.qualifier; - ref.script = blob; - m_scripts << ref; + if (!importUrl.isEmpty()) { + QUrl libraryUrl(importUrl); + // XXX is this logic even correct??? + QString adjustedUrl; + const QQmlDirParser *dirParser = typeLoader()->qmlDirParser(libraryUrl.path() + QLatin1String("qmldir"), QString(), &adjustedUrl); + foreach (const QQmlDirParser::Script &script, dirParser->scripts()) { + QUrl scriptUrl = libraryUrl.resolved(QUrl(script.fileName)); + QQmlScriptBlob *blob = typeLoader()->getScript(scriptUrl); + addDependency(blob); + + ScriptReference ref; + ref.location = import.location.start; + ref.qualifier = script.nameSpace; + ref.nameSpace = import.qualifier; + ref.script = blob; + m_scripts << ref; + } } } } @@ -1951,10 +2091,10 @@ const QQmlDirComponents &QQmlQmldirData::dirComponents() const return m_components; } -void QQmlQmldirData::dataReceived(const QByteArray &data) +void QQmlQmldirData::dataReceived(const Data &data) { QQmlDirParser parser; - parser.setSource(QString::fromUtf8(data)); + parser.setSource(QString::fromUtf8(data.data(), data.size())); parser.parse(); m_components = parser.components(); } diff --git a/src/qml/qml/qqmltypeloader_p.h b/src/qml/qml/qqmltypeloader_p.h index 319b2e7a1004d5d012cc153471b9fcf7ec882a6e..66f4fd5081a1fe2a781c044715873b1414954e51 100644 --- a/src/qml/qml/qqmltypeloader_p.h +++ b/src/qml/qml/qqmltypeloader_p.h @@ -58,6 +58,7 @@ #include <QtNetwork/qnetworkreply.h> #include <QtQml/qqmlerror.h> #include <QtQml/qqmlengine.h> +#include <QtQml/qqmlfile.h> #include <private/qv8_p.h> #include <private/qhashedstring_p.h> @@ -65,6 +66,8 @@ #include <private/qqmlimport_p.h> #include <private/qqmlcleanup_p.h> #include <private/qqmldirparser_p.h> +#include <private/qqmlbundle_p.h> +#include <private/qflagpointer_p.h> QT_BEGIN_NAMESPACE @@ -117,6 +120,25 @@ public: QList<QQmlError> errors() const; + class Data { + public: + inline const char *data() const; + inline int size() const; + + inline QByteArray asByteArray() const; + + inline bool isFile() const; + inline QQmlFile *asFile() const; + + private: + friend class QQmlDataBlob; + friend class QQmlDataLoader; + inline Data(); + Data(const Data &); + Data &operator=(const Data &); + QBiPointer<const QByteArray, QQmlFile> d; + }; + protected: // Can be called from within callbacks void setError(const QQmlError &); @@ -124,7 +146,7 @@ protected: void addDependency(QQmlDataBlob *); // Callbacks made in load thread - virtual void dataReceived(const QByteArray &) = 0; + virtual void dataReceived(const Data &) = 0; virtual void done(); virtual void networkError(QNetworkReply::NetworkError); virtual void dependencyError(QQmlDataBlob *); @@ -216,12 +238,22 @@ private: typedef QHash<QNetworkReply *, QQmlDataBlob *> NetworkReplies; void setData(QQmlDataBlob *, const QByteArray &); + void setData(QQmlDataBlob *, QQmlFile *); + void setData(QQmlDataBlob *, const QQmlDataBlob::Data &); QQmlEngine *m_engine; QQmlDataLoaderThread *m_thread; NetworkReplies m_networkReplies; }; +class QQmlBundleData : public QQmlBundle, + public QQmlRefCount +{ +public: + QQmlBundleData(const QString &); + QString fileName; +}; + // Exported for QtQuick1 class Q_QML_PRIVATE_EXPORT QQmlTypeLoader : public QQmlDataLoader { @@ -243,22 +275,36 @@ public: QQmlScriptBlob *getScript(const QUrl &); QQmlQmldirData *getQmldir(const QUrl &); + QQmlBundleData *getBundle(const QString &); + QQmlBundleData *getBundle(const QHashedStringRef &); + void addBundle(const QString &, const QString &); + QString absoluteFilePath(const QString &path); bool directoryExists(const QString &path); - const QQmlDirParser *qmlDirParser(const QString &absoluteFilePath); + const QQmlDirParser *qmlDirParser(const QString &filePath, const QString &uriHint, QString *outUrl); + private: + void addBundleNoLock(const QString &, const QString &); + QString bundleIdForQmldir(const QString &qmldir, const QString &uriHint); + + struct DirParser : public QQmlDirParser { QString adjustedUrl; }; + typedef QHash<QUrl, QQmlTypeData *> TypeCache; typedef QHash<QUrl, QQmlScriptBlob *> ScriptCache; typedef QHash<QUrl, QQmlQmldirData *> QmldirCache; typedef QStringHash<bool> StringSet; typedef QStringHash<StringSet*> ImportDirCache; - typedef QStringHash<QQmlDirParser*> ImportQmlDirCache; + typedef QStringHash<DirParser*> ImportQmlDirCache; + typedef QStringHash<QQmlBundleData *> BundleCache; + typedef QStringHash<QString> QmldirBundleIdCache; TypeCache m_typeCache; ScriptCache m_scriptCache; QmldirCache m_qmldirCache; ImportDirCache m_importDirCache; ImportQmlDirCache m_importQmlDirCache; + BundleCache m_bundleCache; + QmldirBundleIdCache m_qmldirBundleIdCache; }; Q_DECLARE_OPERATORS_FOR_FLAGS(QQmlTypeLoader::Options) @@ -312,7 +358,7 @@ public: protected: virtual void done(); virtual void completed(); - virtual void dataReceived(const QByteArray &); + virtual void dataReceived(const Data &); virtual void allDependenciesDone(); virtual void downloadProgressChanged(qreal); @@ -349,8 +395,7 @@ private: // reference that was created is released but final deletion only occurs once all the // references as released. This is all intended to ensure that the v8 resources are // only created and destroyed in the main thread :) -class Q_AUTOTEST_EXPORT QQmlScriptData : public QQmlCleanup, - public QQmlRefCount +class Q_AUTOTEST_EXPORT QQmlScriptData : public QQmlCleanup, public QQmlRefCount { public: QQmlScriptData(); @@ -402,7 +447,7 @@ public: QQmlScriptData *scriptData() const; protected: - virtual void dataReceived(const QByteArray &); + virtual void dataReceived(const Data &); virtual void done(); private: @@ -424,13 +469,52 @@ public: const QQmlDirComponents &dirComponents() const; protected: - virtual void dataReceived(const QByteArray &); + virtual void dataReceived(const Data &); private: QQmlDirComponents m_components; }; +QQmlDataBlob::Data::Data() +{ +} + +const char *QQmlDataBlob::Data::data() const +{ + Q_ASSERT(!d.isNull()); + + if (d.isT1()) return d.asT1()->constData(); + else return d.asT2()->data(); +} + +int QQmlDataBlob::Data::size() const +{ + Q_ASSERT(!d.isNull()); + + if (d.isT1()) return d.asT1()->size(); + else return d.asT2()->size(); +} + +bool QQmlDataBlob::Data::isFile() const +{ + return d.isT2(); +} + +QByteArray QQmlDataBlob::Data::asByteArray() const +{ + Q_ASSERT(!d.isNull()); + + if (d.isT1()) return *d.asT1(); + else return d.asT2()->dataByteArray(); +} + +QQmlFile *QQmlDataBlob::Data::asFile() const +{ + if (d.isT2()) return d.asT2(); + else return 0; +} + QT_END_NAMESPACE #endif // QQMLTYPELOADER_P_H diff --git a/src/qml/qml/qquickworkerscript.cpp b/src/qml/qml/qquickworkerscript.cpp index b00847abcbbd04b510683c4038a5da875774cb1f..b0d39fd0f8a84ae228dcaa8908ca5cca36ccda69 100644 --- a/src/qml/qml/qquickworkerscript.cpp +++ b/src/qml/qml/qquickworkerscript.cpp @@ -55,6 +55,7 @@ #include <QtCore/qdatetime.h> #include <QtNetwork/qnetworkaccessmanager.h> #include <QtQml/qqmlinfo.h> +#include <QtQml/qqmlfile.h> #include "qqmlnetworkaccessmanagerfactory.h" #include <private/qv8engine_p.h> @@ -362,7 +363,7 @@ void QQuickWorkerScriptEnginePrivate::processLoad(int id, const QUrl &url) if (url.isRelative()) return; - QString fileName = QQmlEnginePrivate::urlToLocalFileOrQrc(url); + QString fileName = QQmlFile::urlToLocalFileOrQrc(url); QFile f(fileName); if (f.open(QIODevice::ReadOnly)) { diff --git a/src/qml/qml/v8/qv8include.cpp b/src/qml/qml/v8/qv8include.cpp index 89f60f256e40494844b12743a4dd347948b0695c..01a670aab7297bdc5cf2c329e372c15857b7f9f0 100644 --- a/src/qml/qml/v8/qv8include.cpp +++ b/src/qml/qml/v8/qv8include.cpp @@ -45,6 +45,7 @@ #include <QtNetwork/qnetworkrequest.h> #include <QtNetwork/qnetworkreply.h> #include <QtCore/qfile.h> +#include <QtQml/qqmlfile.h> #include <private/qqmlengine_p.h> @@ -185,7 +186,7 @@ v8::Handle<v8::Value> QV8Include::include(const v8::Arguments &args) if (args.Length() >= 2 && args[1]->IsFunction()) callbackFunction = v8::Local<v8::Function>::Cast(args[1]); - QString localFile = QQmlEnginePrivate::urlToLocalFileOrQrc(url); + QString localFile = QQmlFile::urlToLocalFileOrQrc(url); v8::Local<v8::Object> result; diff --git a/src/quick/items/qquickanimatedimage.cpp b/src/quick/items/qquickanimatedimage.cpp index 9319cf12525501c70056d6235423223e7a2437de..1d4adf9b83e477c9e18b4dc50de8193382216e30 100644 --- a/src/quick/items/qquickanimatedimage.cpp +++ b/src/quick/items/qquickanimatedimage.cpp @@ -45,12 +45,12 @@ #ifndef QT_NO_MOVIE #include <QtQml/qqmlinfo.h> +#include <QtQml/qqmlfile.h> +#include <QtQml/qqmlengine.h> #include <QtGui/qmovie.h> #include <QtNetwork/qnetworkrequest.h> #include <QtNetwork/qnetworkreply.h> -#include <private/qqmlengine_p.h> - QT_BEGIN_NAMESPACE /*! \qmlclass AnimatedImage QQuickAnimatedImage @@ -264,7 +264,7 @@ void QQuickAnimatedImage::load() if (d->progress != oldProgress) emit progressChanged(d->progress); } else { - QString lf = QQmlEnginePrivate::urlToLocalFileOrQrc(d->url); + QString lf = QQmlFile::urlToLocalFileOrQrc(d->url); if (!lf.isEmpty()) { //### should be unified with movieRequestFinished d->_movie = new QMovie(lf); diff --git a/src/quick/items/qquickborderimage.cpp b/src/quick/items/qquickborderimage.cpp index b99a2f4cae95d8e313f706152363507876b66149..67ae3fa73903077da52e4adf19b3e33fd789a0dd 100644 --- a/src/quick/items/qquickborderimage.cpp +++ b/src/quick/items/qquickborderimage.cpp @@ -44,10 +44,12 @@ #include "qquickninepatchnode_p.h" #include <QtQml/qqmlinfo.h> +#include <QtQml/qqmlfile.h> +#include <QtQml/qqmlengine.h> +#include <QtNetwork/qnetworkreply.h> #include <QtCore/qfile.h> #include <private/qqmlglobal_p.h> -#include <private/qqmlengine_p.h> QT_BEGIN_NAMESPACE @@ -306,7 +308,7 @@ void QQuickBorderImage::load() } else { d->status = Loading; if (d->url.path().endsWith(QLatin1String("sci"))) { - QString lf = QQmlEnginePrivate::urlToLocalFileOrQrc(d->url); + QString lf = QQmlFile::urlToLocalFileOrQrc(d->url); if (!lf.isEmpty()) { QFile file(lf); file.open(QIODevice::ReadOnly); diff --git a/src/quick/util/qquickfontloader.cpp b/src/quick/util/qquickfontloader.cpp index 55830f0cc87bb83b08296831e54792cabb577902..28682701214e0555c718355c3b9425b955b5c3b7 100644 --- a/src/quick/util/qquickfontloader.cpp +++ b/src/quick/util/qquickfontloader.cpp @@ -52,8 +52,8 @@ #include <QFontDatabase> #include <private/qobject_p.h> -#include <private/qqmlengine_p.h> #include <qqmlinfo.h> +#include <qqmlfile.h> QT_BEGIN_NAMESPACE @@ -196,7 +196,7 @@ void QQuickFontLoader::setSource(const QUrl &url) d->url = url; emit sourceChanged(); - QString localFile = QQmlEnginePrivate::urlToLocalFileOrQrc(d->url); + QString localFile = QQmlFile::urlToLocalFileOrQrc(d->url); if (!localFile.isEmpty()) { if (!d->fonts.contains(d->url)) { int id = QFontDatabase::addApplicationFont(localFile); diff --git a/src/quick/util/qquickpixmapcache.cpp b/src/quick/util/qquickpixmapcache.cpp index 801b007cdd84f80baf74004c5e0d7fc64427c7de..0400c9580c6e504a1aeef5357cc90c8c236470da 100644 --- a/src/quick/util/qquickpixmapcache.cpp +++ b/src/quick/util/qquickpixmapcache.cpp @@ -65,6 +65,7 @@ #include <QtCore/qdebug.h> #include <private/qobject_p.h> #include <QSslError> +#include <QQmlFile> #define IMAGEREQUEST_MAX_REQUEST_COUNT 8 #define IMAGEREQUEST_MAX_REDIRECT_RECURSION 16 @@ -556,7 +557,7 @@ void QQuickPixmapReader::processJob(QQuickPixmapReply *runningJob, const QUrl &u } } else { - QString lf = QQmlEnginePrivate::urlToLocalFileOrQrc(url); + QString lf = QQmlFile::urlToLocalFileOrQrc(url); if (!lf.isEmpty()) { // Image is local - load/decode immediately QImage image; @@ -975,7 +976,7 @@ static QQuickPixmapData* createPixmapDataSync(QQuickPixmap *declarativePixmap, Q QQuickPixmap::tr("Failed to get image from provider: %1").arg(url.toString())); } - QString localFile = QQmlEnginePrivate::urlToLocalFileOrQrc(url); + QString localFile = QQmlFile::urlToLocalFileOrQrc(url); if (localFile.isEmpty()) return 0; diff --git a/tests/auto/qml/qml.pro b/tests/auto/qml/qml.pro index 6f226a8e3486ba562cbba2e3924066e911d528e2..d99d18c152f635ae5d10478efae742ebe770222e 100644 --- a/tests/auto/qml/qml.pro +++ b/tests/auto/qml/qml.pro @@ -47,6 +47,7 @@ PRIVATETESTS += \ qquicklistmodel \ qquicklistmodelworkerscript \ qquickworkerscript \ + qqmlbundle \ v4 SUBDIRS += $$PUBLICTESTS diff --git a/tests/auto/qml/qqmlbundle/data/bundleImport/bundleImport.1.qml b/tests/auto/qml/qqmlbundle/data/bundleImport/bundleImport.1.qml new file mode 100644 index 0000000000000000000000000000000000000000..b87ba9c8085b4ebe4f2f23b8ce90472f687670b3 --- /dev/null +++ b/tests/auto/qml/qqmlbundle/data/bundleImport/bundleImport.1.qml @@ -0,0 +1,4 @@ +import "bundle://mybundle" + +MyType { +} diff --git a/tests/auto/qml/qqmlbundle/data/bundleImport/bundleImport.2.qml b/tests/auto/qml/qqmlbundle/data/bundleImport/bundleImport.2.qml new file mode 100644 index 0000000000000000000000000000000000000000..0c0622e95df7ec12ca53e7d35708f664391d2cfc --- /dev/null +++ b/tests/auto/qml/qqmlbundle/data/bundleImport/bundleImport.2.qml @@ -0,0 +1,4 @@ +import "bundle://mybundle/subdir" + +MySubType { +} diff --git a/tests/auto/qml/qqmlbundle/data/bundleImport/bundledata/MyType.qml b/tests/auto/qml/qqmlbundle/data/bundleImport/bundledata/MyType.qml new file mode 100644 index 0000000000000000000000000000000000000000..c4735608491b24500d1ae0a81ea42eb91c4599b1 --- /dev/null +++ b/tests/auto/qml/qqmlbundle/data/bundleImport/bundledata/MyType.qml @@ -0,0 +1,6 @@ +import QtQuick 2.0 + +QtObject { + property real test1: 1918 + property string test2: "Hello world!" +} diff --git a/tests/auto/qml/qqmlbundle/data/bundleImport/bundledata/subdir/MySubType.qml b/tests/auto/qml/qqmlbundle/data/bundleImport/bundledata/subdir/MySubType.qml new file mode 100644 index 0000000000000000000000000000000000000000..ce136f213bc399763cf9f1740e068e1c9fc1218a --- /dev/null +++ b/tests/auto/qml/qqmlbundle/data/bundleImport/bundledata/subdir/MySubType.qml @@ -0,0 +1,7 @@ +import QtQuick 2.0 + +QtObject { + property real test1: 1432 + property string test2: "Jeronimo" +} + diff --git a/tests/auto/qml/qqmlbundle/data/componentFromBundle/bundledata/test.qml b/tests/auto/qml/qqmlbundle/data/componentFromBundle/bundledata/test.qml new file mode 100644 index 0000000000000000000000000000000000000000..ce81efe86bcb18c2ad0f5c77b2628aaec6ec5059 --- /dev/null +++ b/tests/auto/qml/qqmlbundle/data/componentFromBundle/bundledata/test.qml @@ -0,0 +1,6 @@ +import QtQuick 2.0 + +QtObject { + property int test1: 11 + property bool test2: true +} diff --git a/tests/auto/qml/qqmlbundle/data/import.qml b/tests/auto/qml/qqmlbundle/data/import.qml new file mode 100644 index 0000000000000000000000000000000000000000..af527199a7f11b5a3fae922e466552b3c7d8306d --- /dev/null +++ b/tests/auto/qml/qqmlbundle/data/import.qml @@ -0,0 +1,4 @@ +import bundletest 2.0 + +MyPluginType { +} diff --git a/tests/auto/qml/qqmlbundle/data/imports/bundletest/bundledata/qmldir b/tests/auto/qml/qqmlbundle/data/imports/bundletest/bundledata/qmldir new file mode 100644 index 0000000000000000000000000000000000000000..db8aabfefddbe62011545580ac8ff3ab07c85cd5 --- /dev/null +++ b/tests/auto/qml/qqmlbundle/data/imports/bundletest/bundledata/qmldir @@ -0,0 +1 @@ +plugin plugin1 diff --git a/tests/auto/qml/qqmlbundle/data/imports/bundletest/bundledata/subdir/qmldir b/tests/auto/qml/qqmlbundle/data/imports/bundletest/bundledata/subdir/qmldir new file mode 100644 index 0000000000000000000000000000000000000000..305d075dc7639fedf687ac5616bda9f064ac9d21 --- /dev/null +++ b/tests/auto/qml/qqmlbundle/data/imports/bundletest/bundledata/subdir/qmldir @@ -0,0 +1 @@ +plugin plugin2 diff --git a/tests/auto/qml/qqmlbundle/data/imports/bundletest/empty.json b/tests/auto/qml/qqmlbundle/data/imports/bundletest/empty.json new file mode 100644 index 0000000000000000000000000000000000000000..0967ef424bce6791893e9a57bb952f80fd536e93 --- /dev/null +++ b/tests/auto/qml/qqmlbundle/data/imports/bundletest/empty.json @@ -0,0 +1 @@ +{} diff --git a/tests/auto/qml/qqmlbundle/data/imports/bundletest/plugin.cpp b/tests/auto/qml/qqmlbundle/data/imports/bundletest/plugin.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8f94e7e3938c449f58af0c177e50e3baea635cae --- /dev/null +++ b/tests/auto/qml/qqmlbundle/data/imports/bundletest/plugin.cpp @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include <QStringList> +#include <QtQml/qqmlextensionplugin.h> +#include <QtQml/qqml.h> +#include <QDebug> + +class MyPluginType : public QObject +{ + Q_OBJECT + Q_PROPERTY(int value READ value WRITE setValue) + +public: + MyPluginType(QObject *parent=0) : QObject(parent), v(32) + { + } + + int value() const { return v; } + void setValue(int i) { v = i; } + +private: + int v; +}; + + +class MyPlugin : public QQmlExtensionPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface" FILE "empty.json") + +public: + MyPlugin() + { + } + + void registerTypes(const char *uri) + { + qmlRegisterType<MyPluginType>(uri, 2, 0, "MyPluginType"); + } +}; + +#include "plugin.moc" + diff --git a/tests/auto/qml/qqmlbundle/data/imports/bundletest/plugin1.pro b/tests/auto/qml/qqmlbundle/data/imports/bundletest/plugin1.pro new file mode 100644 index 0000000000000000000000000000000000000000..d91cc245d4989b345027ce4868e2fd61db42241c --- /dev/null +++ b/tests/auto/qml/qqmlbundle/data/imports/bundletest/plugin1.pro @@ -0,0 +1,5 @@ +TEMPLATE = lib +CONFIG += plugin +SOURCES += plugin.cpp +QT = core qml + diff --git a/tests/auto/qml/qqmlbundle/data/relativeQmldir/bundledata/subdir/qmldir b/tests/auto/qml/qqmlbundle/data/relativeQmldir/bundledata/subdir/qmldir new file mode 100644 index 0000000000000000000000000000000000000000..628edcbfa37ba9cd98edc5c15dae49b35beb2b49 --- /dev/null +++ b/tests/auto/qml/qqmlbundle/data/relativeQmldir/bundledata/subdir/qmldir @@ -0,0 +1 @@ +MySubdirType 1.0 st.qml diff --git a/tests/auto/qml/qqmlbundle/data/relativeQmldir/bundledata/subdir/st.qml b/tests/auto/qml/qqmlbundle/data/relativeQmldir/bundledata/subdir/st.qml new file mode 100644 index 0000000000000000000000000000000000000000..496eda83d1593e2602dba83a01b5000b956b5885 --- /dev/null +++ b/tests/auto/qml/qqmlbundle/data/relativeQmldir/bundledata/subdir/st.qml @@ -0,0 +1,6 @@ +import QtQuick 2.0 + +QtObject { + property int test1: 67 + property real test2: 88 +} diff --git a/tests/auto/qml/qqmlbundle/data/relativeQmldir/bundledata/test.qml b/tests/auto/qml/qqmlbundle/data/relativeQmldir/bundledata/test.qml new file mode 100644 index 0000000000000000000000000000000000000000..1046b8859fbd6a6cf3e6c0f274ac2d74028dfb67 --- /dev/null +++ b/tests/auto/qml/qqmlbundle/data/relativeQmldir/bundledata/test.qml @@ -0,0 +1,4 @@ +import "subdir" + +MySubdirType { +} diff --git a/tests/auto/qml/qqmlbundle/data/relativeResolution.1/bundledata/MyType.qml b/tests/auto/qml/qqmlbundle/data/relativeResolution.1/bundledata/MyType.qml new file mode 100644 index 0000000000000000000000000000000000000000..ce81efe86bcb18c2ad0f5c77b2628aaec6ec5059 --- /dev/null +++ b/tests/auto/qml/qqmlbundle/data/relativeResolution.1/bundledata/MyType.qml @@ -0,0 +1,6 @@ +import QtQuick 2.0 + +QtObject { + property int test1: 11 + property bool test2: true +} diff --git a/tests/auto/qml/qqmlbundle/data/relativeResolution.1/bundledata/test.qml b/tests/auto/qml/qqmlbundle/data/relativeResolution.1/bundledata/test.qml new file mode 100644 index 0000000000000000000000000000000000000000..2e753d2dbc8e5999f77f60f18ab39cb099d88635 --- /dev/null +++ b/tests/auto/qml/qqmlbundle/data/relativeResolution.1/bundledata/test.qml @@ -0,0 +1,4 @@ +import QtQuick 2.0 + +MyType { +} diff --git a/tests/auto/qml/qqmlbundle/data/relativeResolution.2/bundledata/subdir/MyType.qml b/tests/auto/qml/qqmlbundle/data/relativeResolution.2/bundledata/subdir/MyType.qml new file mode 100644 index 0000000000000000000000000000000000000000..ce81efe86bcb18c2ad0f5c77b2628aaec6ec5059 --- /dev/null +++ b/tests/auto/qml/qqmlbundle/data/relativeResolution.2/bundledata/subdir/MyType.qml @@ -0,0 +1,6 @@ +import QtQuick 2.0 + +QtObject { + property int test1: 11 + property bool test2: true +} diff --git a/tests/auto/qml/qqmlbundle/data/relativeResolution.2/bundledata/subdir/test.qml b/tests/auto/qml/qqmlbundle/data/relativeResolution.2/bundledata/subdir/test.qml new file mode 100644 index 0000000000000000000000000000000000000000..2e753d2dbc8e5999f77f60f18ab39cb099d88635 --- /dev/null +++ b/tests/auto/qml/qqmlbundle/data/relativeResolution.2/bundledata/subdir/test.qml @@ -0,0 +1,4 @@ +import QtQuick 2.0 + +MyType { +} diff --git a/tests/auto/qml/qqmlbundle/qqmlbundle.pro b/tests/auto/qml/qqmlbundle/qqmlbundle.pro new file mode 100644 index 0000000000000000000000000000000000000000..ec81e3f2348b053d8ae86ed7eba60bf47a851824 --- /dev/null +++ b/tests/auto/qml/qqmlbundle/qqmlbundle.pro @@ -0,0 +1,2 @@ +TEMPLATE = subdirs +SUBDIRS += tst_qqmlbundle.pro data/imports/bundletest/plugin1.pro diff --git a/tests/auto/qml/qqmlbundle/tst_qqmlbundle.cpp b/tests/auto/qml/qqmlbundle/tst_qqmlbundle.cpp new file mode 100644 index 0000000000000000000000000000000000000000..fee01c2bf38cfede13a7e82737a705280c6a7700 --- /dev/null +++ b/tests/auto/qml/qqmlbundle/tst_qqmlbundle.cpp @@ -0,0 +1,260 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// Lookup of libraries +// Test bundle as a qmldir + +#include <qtest.h> +#include <QDebug> +#include <QQmlEngine> +#include <QQmlComponent> +#include "../../shared/util.h" +#include <private/qqmlbundle_p.h> + +class tst_qqmlbundle : public QQmlDataTest +{ + Q_OBJECT +public: + tst_qqmlbundle() {} + +private slots: + void initTestCase(); + + void componentFromBundle(); + void relativeResolution(); + void bundleImport(); + void relativeQmldir(); + + void import(); + +private: + QStringList findFiles(const QDir &d); + bool makeBundle(const QString &path, const QString &name); +}; + +void tst_qqmlbundle::initTestCase() +{ + QQmlDataTest::initTestCase(); +} + +// Test we create a QQmlComponent for a file inside a bundle +void tst_qqmlbundle::componentFromBundle() +{ + QVERIFY(makeBundle(testFile("componentFromBundle"), "my.bundle")); + + QQmlEngine engine; + engine.addNamedBundle("mybundle", testFile("componentFromBundle/my.bundle")); + + QQmlComponent component(&engine, QUrl("bundle://mybundle/test.qml")); + QVERIFY(component.isReady()); + + QObject *o = component.create(); + QVERIFY(o != 0); + + QCOMPARE(o->property("test1").toInt(), 11); + QCOMPARE(o->property("test2").toBool(), true); + + delete o; +} + +// Tests that relative QML components are resolved without a qmldir +void tst_qqmlbundle::relativeResolution() +{ + // Root of the bundle + { + QVERIFY(makeBundle(testFile("relativeResolution.1"), "my.bundle")); + + QQmlEngine engine; + engine.addNamedBundle("mybundle", testFile("relativeResolution.1/my.bundle")); + + QQmlComponent component(&engine, QUrl("bundle://mybundle/test.qml")); + QVERIFY(component.isReady()); + + QObject *o = component.create(); + QVERIFY(o != 0); + + QCOMPARE(o->property("test1").toInt(), 11); + QCOMPARE(o->property("test2").toBool(), true); + + delete o; + } + + // Non-root of the bundle + { + QVERIFY(makeBundle(testFile("relativeResolution.2"), "my.bundle")); + + QQmlEngine engine; + engine.addNamedBundle("mybundle", testFile("relativeResolution.2/my.bundle")); + + QQmlComponent component(&engine, QUrl("bundle://mybundle/subdir/test.qml")); + QVERIFY(component.isReady()); + + QObject *o = component.create(); + QVERIFY(o != 0); + + QCOMPARE(o->property("test1").toInt(), 11); + QCOMPARE(o->property("test2").toBool(), true); + + delete o; + } +} + +// Test that a bundle can be imported explicitly from outside a bundle +void tst_qqmlbundle::bundleImport() +{ + QVERIFY(makeBundle(testFile("bundleImport"), "my.bundle")); + + QQmlEngine engine; + engine.addNamedBundle("mybundle", testFile("bundleImport/my.bundle")); + + { + QQmlComponent component(&engine, testFileUrl("bundleImport/bundleImport.1.qml")); + QVERIFY(component.isReady()); + + QObject *o = component.create(); + QVERIFY(o != 0); + + QCOMPARE(o->property("test1").toReal(), qreal(1918)); + QCOMPARE(o->property("test2").toString(), QString("Hello world!")); + + delete o; + } + + { + QQmlComponent component(&engine, testFileUrl("bundleImport/bundleImport.2.qml")); + QVERIFY(component.isReady()); + + QObject *o = component.create(); + QVERIFY(o != 0); + + QCOMPARE(o->property("test1").toReal(), qreal(1432)); + QCOMPARE(o->property("test2").toString(), QString("Jeronimo")); + + delete o; + } +} + +// Test a relative import inside a bundle uses qmldir +void tst_qqmlbundle::relativeQmldir() +{ + QVERIFY(makeBundle(testFile("relativeQmldir"), "my.bundle")); + + QQmlEngine engine; + engine.addNamedBundle("mybundle", testFile("relativeQmldir/my.bundle")); + + QQmlComponent component(&engine, QUrl("bundle://mybundle/test.qml")); + QVERIFY(component.isReady()); + + QObject *o = component.create(); + QVERIFY(o != 0); + + QCOMPARE(o->property("test1").toReal(), qreal(67)); + QCOMPARE(o->property("test2").toReal(), qreal(88)); + + delete o; +} + +// Test C++ plugins are resolved relative to the bundle container file +void tst_qqmlbundle::import() +{ + QVERIFY(makeBundle(testFile("imports/bundletest"), "qmldir")); + + QQmlEngine engine; + engine.addImportPath(testFile("imports")); + + QQmlComponent component(&engine, testFileUrl("import.qml")); + QVERIFY(component.isReady()); + + QObject *o = component.create(); + QVERIFY(o != 0); + + QCOMPARE(o->property("value").toInt(), 32); + + delete o; +} + +// Transform the data available under <path>/bundledata to a bundle named <path>/<name> +bool tst_qqmlbundle::makeBundle(const QString &path, const QString &name) +{ + QDir dir(path); + dir.remove(name); + + QDir bundleDir = dir; + if (!bundleDir.cd("bundledata")) + return false; + + QStringList fileNames = findFiles(bundleDir); + + QString bundleFile = dir.absolutePath() + QDir::separator() + name; + + QQmlBundle bundle(bundleFile); + if (!bundle.open(QFile::WriteOnly)) + return false; + + foreach (const QString &fileName, fileNames) { + QString shortFileName = fileName.mid(bundleDir.absolutePath().length() + 1); + bundle.add(shortFileName, fileName); + } + + return true; +} + +QStringList tst_qqmlbundle::findFiles(const QDir &d) +{ + QStringList rv; + + QStringList files = d.entryList(QDir::Files); + foreach (const QString &file, files) + rv << d.absoluteFilePath(file); + + QStringList dirs = d.entryList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::NoSymLinks); + foreach (const QString &dir, dirs) { + QDir sub = d; + sub.cd(dir); + rv << findFiles(sub); + } + + return rv; +} + +QTEST_MAIN(tst_qqmlbundle) + +#include "tst_qqmlbundle.moc" diff --git a/tests/auto/qml/qqmlbundle/tst_qqmlbundle.pro b/tests/auto/qml/qqmlbundle/tst_qqmlbundle.pro new file mode 100644 index 0000000000000000000000000000000000000000..4e9c627135bc6d3c34364c77cdc1b5c48db0e77d --- /dev/null +++ b/tests/auto/qml/qqmlbundle/tst_qqmlbundle.pro @@ -0,0 +1,15 @@ +CONFIG += testcase +TARGET = tst_qqmlbundle +macx:CONFIG -= app_bundle + +SOURCES += tst_qqmlbundle.cpp +HEADERS += + +include (../../shared/util.pri) + +TESTDATA = data/* + +CONFIG += parallel_test + +QT += qml-private testlib + diff --git a/tools/qmlbundle/main.cpp b/tools/qmlbundle/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..16bf20e2cbe3e1b91ca3502ce23fd6e245abd20c --- /dev/null +++ b/tools/qmlbundle/main.cpp @@ -0,0 +1,220 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <private/qqmlbundle_p.h> +#include <private/qqmlscript_p.h> +#include <QtCore/QtCore> +#include <iostream> + +static bool createBundle(const QString &fileName, const QStringList &fileNames) +{ + QQmlBundle bundle(fileName); + if (!bundle.open(QFile::WriteOnly)) + return false; + foreach (const QString &fileName, fileNames) + bundle.add(fileName); + return true; +} + +static bool removeFiles(const QString &fileName, const QStringList &fileNames) +{ + const QSet<QString> filesToRemove = QSet<QString>::fromList(fileNames); + + QQmlBundle bundle(fileName); + bundle.open(QFile::ReadWrite); + foreach (const QQmlBundle::FileEntry *entry, bundle.files()) { + if (filesToRemove.contains(entry->fileName())) + bundle.remove(entry); + } + return true; +} + +static void showHelp() +{ + std::cerr << "Usage: qmlbundle <command> [<args>]" << std::endl + << std::endl + << "The commands are:" << std::endl + << " create Create a new bundle" << std::endl + << " add Add files to the bundle" << std::endl + << " rm Remove files from the bundle" << std::endl + << " update Add files to the bundle or update them if they are already added" << std::endl + << " ls List the files in the bundle" << std::endl + << " cat Concatenates files and print on the standard output" << std::endl + << " optimize Insert optimization data for all recognised content" << std::endl + << std::endl + << "See 'qmlbundle help <command>' for more information on a specific command." << std::endl; +} + +static void usage(const QString &action, const QString &error = QString()) +{ + if (! error.isEmpty()) + std::cerr << qPrintable(error) << std::endl << std::endl; + + if (action == QLatin1String("create")) { + std::cerr << "usage: qmlbundle create <bundle name> [files]" << std::endl; + } else if (action == QLatin1String("add")) { + std::cerr << "usage: qmlbundle add <bundle name> [files]" << std::endl; + } else if (action == QLatin1String("rm")) { + std::cerr << "usage: qmlbundle rm <bundle name> [files]" << std::endl; + } else if (action == QLatin1String("update")) { + std::cerr << "usage: qmlbundle update <bundle name> [files]" << std::endl; + } else if (action == QLatin1String("ls")) { + std::cerr << "usage: qmlbundle ls <bundle name>" << std::endl; + } else if (action == QLatin1String("cat")) { + std::cerr << "usage: qmlbundle cat <bundle name> [files]" << std::endl; + } else { + showHelp(); + } +} + +int main(int argc, char *argv[]) +{ + QCoreApplication app(argc, argv); + + QStringList args = app.arguments(); + /*const QString exeName =*/ args.takeFirst(); + + if (args.isEmpty()) { + showHelp(); + return 0; + } + + const QString action = args.takeFirst(); + + if (action == QLatin1String("help")) { + if (args.empty()) + showHelp(); + else + usage(args.takeFirst()); + } else if (action == QLatin1String("ls")) { + if (args.isEmpty()) { + usage(action, "You must specify a bundle"); + return EXIT_FAILURE; + } + QQmlBundle bundle(args.takeFirst()); + if (bundle.open(QFile::ReadOnly)) { + foreach (const QQmlBundle::FileEntry *fileEntry, bundle.files()) + std::cout << qPrintable(fileEntry->fileName()) << std::endl; + } + } else if (action == QLatin1String("create")) { + if (args.isEmpty()) { + usage(action, "You must specify a bundle"); + return EXIT_FAILURE; + } + const QString bundleFileName = args.takeFirst(); + createBundle(bundleFileName, args); + } else if (action == QLatin1String("add")) { + if (args.isEmpty()) { + usage(action, "You must specify a bundle"); + return EXIT_FAILURE; + } + const QString bundleFileName = args.takeFirst(); + QQmlBundle bundle(bundleFileName); + bundle.open(); + foreach (const QString &fileName, args) { + if (! bundle.add(fileName)) + std::cerr << "cannot add file " << qPrintable(fileName) << " to " << qPrintable(bundleFileName) << std::endl; + } + } else if (action == QLatin1String("rm")) { + if (args.isEmpty()) { + usage(action, "You must specify a bundle"); + return EXIT_FAILURE; + } + const QString bundleFileName = args.takeFirst(); + removeFiles(bundleFileName, args); + } else if (action == QLatin1String("update")) { + if (args.isEmpty()) { + usage(action, "You must specify a bundle"); + return EXIT_FAILURE; + } + const QString bundleFileName = args.takeFirst(); + removeFiles(bundleFileName, args); + QQmlBundle bundle(bundleFileName); + bundle.open(); + foreach (const QString &fileName, args) { + if (! bundle.add(fileName)) + std::cerr << "cannot add file " << qPrintable(fileName) << " to " << qPrintable(bundleFileName) << std::endl; + } + } else if (action == QLatin1String("cat")) { + if (args.isEmpty()) { + usage(action, "You must specify a bundle"); + return EXIT_FAILURE; + } + const QString bundleFileName = args.takeFirst(); + QQmlBundle bundle(bundleFileName); + if (bundle.open(QFile::ReadOnly)) { + const QSet<QString> filesToShow = QSet<QString>::fromList(args); + + foreach (const QQmlBundle::FileEntry *fileEntry, bundle.files()) { + if (filesToShow.contains(fileEntry->fileName())) + std::cout.write(fileEntry->contents(), fileEntry->fileSize()); + } + } + } else if (action == QLatin1String("optimize")) { + if (args.isEmpty()) { + usage(action, "You must specify a bundle"); + return EXIT_FAILURE; + } + const QString bundleFileName = args.takeFirst(); + QQmlBundle bundle(bundleFileName); + if (bundle.open(QFile::ReadWrite)) { + QList<const QQmlBundle::FileEntry *> files = bundle.files(); + for (int ii = 0; ii < files.count(); ++ii) { + const QQmlBundle::FileEntry *file = files.at(ii); + + if (!file->fileName().endsWith(".qml")) + continue; + + QQmlScript::Parser parser; + QString data = QString::fromUtf8(file->contents(), file->fileSize()); + parser.parse(data, QByteArray()); + QByteArray preparse = parser.preparseData(); + + if (!preparse.isEmpty()) + bundle.addMetaLink(file->fileName(), QLatin1String("qml:preparse"), preparse); + } + } + } else { + showHelp(); + } + + return 0; +} diff --git a/tools/qmlbundle/qmlbundle.pro b/tools/qmlbundle/qmlbundle.pro new file mode 100644 index 0000000000000000000000000000000000000000..6a8941863037c728fc83dbb7fff2e73e911ffb67 --- /dev/null +++ b/tools/qmlbundle/qmlbundle.pro @@ -0,0 +1,12 @@ +TEMPLATE = app +TARGET = qmlbundle +DESTDIR= $$QT.qml.bins + +QT = core qml-private v8-private core-private +CONFIG += console +CONFIG -= app_bundle + +SOURCES += main.cpp + +target.path = $$[QT_INSTALL_BINS] +INSTALLS += target diff --git a/tools/qmlscene/main.cpp b/tools/qmlscene/main.cpp index 32c9be7f46387e0e12ad2c3dc7b8e61e6dc4f453..0d97ba190bc1a66057186ad77d368123a84e26ca 100644 --- a/tools/qmlscene/main.cpp +++ b/tools/qmlscene/main.cpp @@ -350,6 +350,8 @@ static void usage() qWarning(" --no-version-detection .................... Do not try to detect the version of the .qml file"); qWarning(" --slow-animations ......................... Run all animations in slow motion"); qWarning(" --quit .................................... Quit immediately after starting"); + qWarning(" -I <path> ................................. Add <path> to the list of import paths"); + qWarning(" -B <name> <file> .......................... Add a named bundle"); qWarning(" "); exit(1); @@ -360,6 +362,7 @@ int main(int argc, char ** argv) Options options; QStringList imports; + QList<QPair<QString, QString> > bundles; for (int i = 1; i < argc; ++i) { if (*argv[i] != '-' && QFileInfo(QFile::decodeName(argv[i])).exists()) { options.file = QUrl::fromLocalFile(argv[i]); @@ -379,7 +382,11 @@ int main(int argc, char ** argv) options.quitImmediately = true; else if (lowerArgument == QLatin1String("-i") && i + 1 < argc) imports.append(QString::fromLatin1(argv[++i])); - else if (lowerArgument == QLatin1String("--help") + else if (lowerArgument == QLatin1String("-b") && i + 2 < argc) { + QString name = QString::fromLatin1(argv[++i]); + QString file = QString::fromLatin1(argv[++i]); + bundles.append(qMakePair(name, file)); + } else if (lowerArgument == QLatin1String("--help") || lowerArgument == QLatin1String("-help") || lowerArgument == QLatin1String("--h") || lowerArgument == QLatin1String("-h")) @@ -416,6 +423,8 @@ int main(int argc, char ** argv) engine = qxView->engine(); for (int i = 0; i < imports.size(); ++i) engine->addImportPath(imports.at(i)); + for (int i = 0; i < bundles.size(); ++i) + engine->addNamedBundle(bundles.at(i).first, bundles.at(i).second); window = qxView; if (options.file.isLocalFile()) { QFileInfo fi(options.file.toLocalFile()); diff --git a/tools/tools.pro b/tools/tools.pro index 70aebf48f65009c5eed2554b349e62c78f941b58..1ce5667a19a71e002219a4c0380474de182171fe 100644 --- a/tools/tools.pro +++ b/tools/tools.pro @@ -1,5 +1,5 @@ TEMPLATE = subdirs -SUBDIRS += qmlscene qmlplugindump qmlmin qmleasing qmlprofiler +SUBDIRS += qmlscene qmlplugindump qmlmin qmleasing qmlprofiler qmlbundle !contains(QT_CONFIG, no-widgets):SUBDIRS += easingcurveeditor contains(QT_CONFIG, qmltest): SUBDIRS += qmltestrunner