aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/libs/qmljs/qmljs-lib.pri6
-rw-r--r--src/libs/qmljs/qmljs.pro2
-rw-r--r--src/libs/qmljs/qmljs.qbs4
-rw-r--r--src/libs/qmljs/qmljsdocument.cpp16
-rw-r--r--src/libs/qmljs/qmljsdocument.h1
-rw-r--r--src/libs/qmljs/qmljsinterpreter.cpp27
-rw-r--r--src/libs/qmljs/qmljsinterpreter.h2
-rw-r--r--src/libs/qmljs/qmljslink.cpp35
-rw-r--r--src/libs/qmljs/qmljsmodelmanagerinterface.h15
-rw-r--r--src/libs/qmljs/qmljsqrcparser.cpp517
-rw-r--r--src/libs/qmljs/qmljsqrcparser.h87
-rw-r--r--src/libs/qmljs/qmljsscopechain.cpp6
-rw-r--r--src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp8
-rw-r--r--src/plugins/qmljseditor/qmljshoverhandler.cpp7
-rw-r--r--src/plugins/qmljstools/qmljsmodelmanager.cpp142
-rw-r--r--src/plugins/qmljstools/qmljsmodelmanager.h16
-rw-r--r--src/plugins/qt4projectmanager/qt4nodes.cpp4
-rw-r--r--src/plugins/qt4projectmanager/qt4nodes.h2
-rw-r--r--src/plugins/qt4projectmanager/qt4project.cpp4
-rw-r--r--tests/auto/qml/qml.pro3
-rw-r--r--tests/auto/qml/qrcparser/qrcparser.pro11
-rw-r--r--tests/auto/qml/qrcparser/simple.qrc17
-rw-r--r--tests/auto/qml/qrcparser/tst_qrcparser.cpp196
23 files changed, 1109 insertions, 19 deletions
diff --git a/src/libs/qmljs/qmljs-lib.pri b/src/libs/qmljs/qmljs-lib.pri
index f31879a6f0..12913de0a5 100644
--- a/src/libs/qmljs/qmljs-lib.pri
+++ b/src/libs/qmljs/qmljs-lib.pri
@@ -39,7 +39,8 @@ HEADERS += \
$$PWD/consoleitem.h \
$$PWD/iscriptevaluator.h \
$$PWD/qmljssimplereader.h \
- $$PWD/persistenttrie.h
+ $$PWD/persistenttrie.h \
+ $$PWD/qmljsqrcparser.h
SOURCES += \
$$PWD/qmljsbind.cpp \
@@ -69,7 +70,8 @@ SOURCES += \
$$PWD/consolemanagerinterface.cpp \
$$PWD/consoleitem.cpp \
$$PWD/qmljssimplereader.cpp \
- $$PWD/persistenttrie.cpp
+ $$PWD/persistenttrie.cpp \
+ $$PWD/qmljsqrcparser.cpp
RESOURCES += \
$$PWD/qmljs.qrc
diff --git a/src/libs/qmljs/qmljs.pro b/src/libs/qmljs/qmljs.pro
index c9c018de5c..5db252b290 100644
--- a/src/libs/qmljs/qmljs.pro
+++ b/src/libs/qmljs/qmljs.pro
@@ -1,5 +1,5 @@
DEFINES += QMLJS_BUILD_DIR
-QT +=script
+QT +=script xml
include(../../qtcreatorlibrary.pri)
include(qmljs-lib.pri)
diff --git a/src/libs/qmljs/qmljs.qbs b/src/libs/qmljs/qmljs.qbs
index 2bef9864c4..7029b1149f 100644
--- a/src/libs/qmljs/qmljs.qbs
+++ b/src/libs/qmljs/qmljs.qbs
@@ -12,7 +12,7 @@ QtcLibrary {
Depends { name: "Utils" }
Depends { name: "LanguageUtils" }
- Depends { name: "Qt"; submodules: ["widgets", "script"] }
+ Depends { name: "Qt"; submodules: ["widgets", "script", "xml"] }
files: [
"jsoncheck.cpp",
@@ -52,6 +52,8 @@ QtcLibrary {
"qmljsmodelmanagerinterface.h",
"qmljspropertyreader.cpp",
"qmljspropertyreader.h",
+ "qmljsqrcparser.cpp",
+ "qmljsqrcparser.h",
"qmljsreformatter.cpp",
"qmljsreformatter.h",
"qmljsrewriter.cpp",
diff --git a/src/libs/qmljs/qmljsdocument.cpp b/src/libs/qmljs/qmljsdocument.cpp
index 3e55ccc42a..a7eb00c354 100644
--- a/src/libs/qmljs/qmljsdocument.cpp
+++ b/src/libs/qmljs/qmljsdocument.cpp
@@ -115,6 +115,22 @@ bool Document::isFullySupportedLanguage(Document::Language language)
return false;
}
+bool Document::isQmlLikeOrJsLanguage(Document::Language language)
+{
+ switch (language) {
+ case QmlLanguage:
+ case QmlQtQuick1Language:
+ case QmlQtQuick2Language:
+ case QmlQbsLanguage:
+ case QmlProjectLanguage:
+ case QmlTypeInfoLanguage:
+ case JavaScriptLanguage:
+ return true;
+ default:
+ return false;
+ }
+}
+
Document::Document(const QString &fileName, Language language)
: _engine(0)
, _ast(0)
diff --git a/src/libs/qmljs/qmljsdocument.h b/src/libs/qmljs/qmljsdocument.h
index e18f95d475..56ebb7061e 100644
--- a/src/libs/qmljs/qmljsdocument.h
+++ b/src/libs/qmljs/qmljsdocument.h
@@ -65,6 +65,7 @@ public:
static bool isQmlLikeLanguage(Language languge);
static bool isFullySupportedLanguage(Language language);
+ static bool isQmlLikeOrJsLanguage(Language language);
protected:
Document(const QString &fileName, Language language);
diff --git a/src/libs/qmljs/qmljsinterpreter.cpp b/src/libs/qmljs/qmljsinterpreter.cpp
index 24d68d4948..c9b1f4381a 100644
--- a/src/libs/qmljs/qmljsinterpreter.cpp
+++ b/src/libs/qmljs/qmljsinterpreter.cpp
@@ -35,6 +35,7 @@
#include "qmljstypedescriptionreader.h"
#include "qmljsvalueowner.h"
#include "qmljscontext.h"
+#include "qmljsmodelmanagerinterface.h"
#include "parser/qmljsast_p.h"
#include <utils/qtcassert.h>
@@ -2107,13 +2108,19 @@ ImportInfo ImportInfo::pathImport(const QString &docPath, const QString &path,
importFileInfo = QFileInfo(docPath + QDir::separator() + path);
info._path = importFileInfo.absoluteFilePath();
- if (importFileInfo.isFile())
+ if (importFileInfo.isFile()) {
info._type = FileImport;
- else if (importFileInfo.isDir())
+ } else if (importFileInfo.isDir()) {
info._type = DirectoryImport;
- else
+ } else if (path.startsWith(QLatin1String("qrc:"))) {
+ info._path = path;
+ if (ModelManagerInterface::instance()->filesAtQrcPath(info.path()).isEmpty())
+ info._type = QrcDirectoryImport;
+ else
+ info._type = QrcFileImport;
+ } else {
info._type = UnknownFileImport;
-
+ }
info._version = version;
info._as = as;
info._ast = ast;
@@ -2192,7 +2199,7 @@ const Value *TypeScope::lookupMember(const QString &name, const Context *context
const ImportInfo &info = i.info;
// JS import has no types
- if (info.type() == ImportInfo::FileImport)
+ if (info.type() == ImportInfo::FileImport || info.type() == ImportInfo::QrcFileImport)
continue;
if (!info.as().isEmpty()) {
@@ -2222,7 +2229,7 @@ void TypeScope::processMembers(MemberProcessor *processor) const
const ImportInfo &info = i.info;
// JS import has no types
- if (info.type() == ImportInfo::FileImport)
+ if (info.type() == ImportInfo::FileImport || info.type() == ImportInfo::QrcFileImport)
continue;
if (!info.as().isEmpty())
@@ -2249,7 +2256,7 @@ const Value *JSImportScope::lookupMember(const QString &name, const Context *,
const ImportInfo &info = i.info;
// JS imports are always: import "somefile.js" as Foo
- if (info.type() != ImportInfo::FileImport)
+ if (info.type() != ImportInfo::FileImport && info.type() != ImportInfo::QrcFileImport)
continue;
if (info.as() == name) {
@@ -2272,7 +2279,7 @@ void JSImportScope::processMembers(MemberProcessor *processor) const
const ObjectValue *import = i.object;
const ImportInfo &info = i.info;
- if (info.type() == ImportInfo::FileImport)
+ if (info.type() == ImportInfo::FileImport || info.type() == ImportInfo::QrcFileImport)
processor->processProperty(info.as(), import);
}
}
@@ -2329,7 +2336,7 @@ ImportInfo Imports::info(const QString &name, const Context *context) const
continue;
}
- if (info.type() == ImportInfo::FileImport) {
+ if (info.type() == ImportInfo::FileImport || info.type() == ImportInfo::QrcFileImport) {
if (import->className() == firstId)
return info;
} else {
@@ -2349,7 +2356,7 @@ QString Imports::nameForImportedObject(const ObjectValue *value, const Context *
const ObjectValue *import = i.object;
const ImportInfo &info = i.info;
- if (info.type() == ImportInfo::FileImport) {
+ if (info.type() == ImportInfo::FileImport || info.type() == ImportInfo::QrcFileImport) {
if (import == value)
return import->className();
} else {
diff --git a/src/libs/qmljs/qmljsinterpreter.h b/src/libs/qmljs/qmljsinterpreter.h
index 56f914713f..356f52bb38 100644
--- a/src/libs/qmljs/qmljsinterpreter.h
+++ b/src/libs/qmljs/qmljsinterpreter.h
@@ -872,6 +872,8 @@ public:
LibraryImport,
FileImport,
DirectoryImport,
+ QrcFileImport,
+ QrcDirectoryImport,
UnknownFileImport // refers a file/directory that wasn't found
};
diff --git a/src/libs/qmljs/qmljslink.cpp b/src/libs/qmljs/qmljslink.cpp
index e90dd9a9ae..2d5672fa20 100644
--- a/src/libs/qmljs/qmljslink.cpp
+++ b/src/libs/qmljs/qmljslink.cpp
@@ -34,6 +34,7 @@
#include "qmljsbind.h"
#include "qmljsutils.h"
#include "qmljsmodelmanagerinterface.h"
+#include <qmljs/qmljsqrcparser.h>
#include <QDir>
#include <QDebug>
@@ -245,6 +246,8 @@ void LinkPrivate::populateImportedTypes(Imports *imports, Document::Ptr doc)
switch (info.type()) {
case ImportInfo::FileImport:
case ImportInfo::DirectoryImport:
+ case ImportInfo::QrcFileImport:
+ case ImportInfo::QrcDirectoryImport:
import = importFileOrDirectory(doc, info);
break;
case ImportInfo::LibraryImport:
@@ -285,7 +288,7 @@ Import LinkPrivate::importFileOrDirectory(Document::Ptr doc, const ImportInfo &i
import.object = 0;
import.valid = true;
- const QString &path = importInfo.path();
+ QString path = importInfo.path();
if (importInfo.type() == ImportInfo::DirectoryImport
|| importInfo.type() == ImportInfo::ImplicitDirectoryImport) {
@@ -304,8 +307,36 @@ Import LinkPrivate::importFileOrDirectory(Document::Ptr doc, const ImportInfo &i
Document::Ptr importedDoc = snapshot.document(path);
if (importedDoc)
import.object = importedDoc->bind()->rootObjectValue();
- }
+ } else if (importInfo.type() == ImportInfo::QrcFileImport) {
+ QLocale locale;
+ QStringList filePaths = ModelManagerInterface::instance()
+ ->filesAtQrcPath(path, &locale, 0, ModelManagerInterface::ActiveQrcResources);
+ if (filePaths.isEmpty()) {
+ filePaths = ModelManagerInterface::instance()->filesAtQrcPath(path);
+ }
+ if (!filePaths.isEmpty()) {
+ Document::Ptr importedDoc = snapshot.document(filePaths.at(0));
+ if (importedDoc)
+ import.object = importedDoc->bind()->rootObjectValue();
+ }
+ } else if (importInfo.type() == ImportInfo::QrcDirectoryImport){
+ import.object = new ObjectValue(valueOwner);
+
+ importLibrary(doc, path, &import);
+ QMapIterator<QString,QStringList> iter(ModelManagerInterface::instance()
+ ->filesInQrcPath(path));
+ while (iter.hasNext()) {
+ iter.next();
+ if (Document::isQmlLikeLanguage(Document::guessLanguageFromSuffix(iter.key()))) {
+ Document::Ptr importedDoc = snapshot.document(iter.value().at(0));
+ if (importedDoc && importedDoc->bind()->rootObjectValue()) {
+ const QString targetName = QFileInfo(iter.key()).baseName();
+ import.object->setMember(targetName, importedDoc->bind()->rootObjectValue());
+ }
+ }
+ }
+ }
return import;
}
diff --git a/src/libs/qmljs/qmljsmodelmanagerinterface.h b/src/libs/qmljs/qmljsmodelmanagerinterface.h
index b708c3ca41..cdcaef362d 100644
--- a/src/libs/qmljs/qmljsmodelmanagerinterface.h
+++ b/src/libs/qmljs/qmljsmodelmanagerinterface.h
@@ -53,6 +53,11 @@ class QMLJS_EXPORT ModelManagerInterface: public QObject
Q_OBJECT
public:
+ enum QrcResourceSelector {
+ ActiveQrcResources,
+ AllQrcResources
+ };
+
class ProjectInfo
{
public:
@@ -80,6 +85,8 @@ public:
QPointer<ProjectExplorer::Project> project;
QStringList sourceFiles;
QStringList importPaths;
+ QStringList activeResourceFiles;
+ QStringList allResourceFiles;
// whether trying to run qmldump makes sense
bool tryQmlDump;
@@ -142,6 +149,14 @@ public:
bool emitDocumentOnDiskChanged) = 0;
virtual void fileChangedOnDisk(const QString &path) = 0;
virtual void removeFiles(const QStringList &files) = 0;
+ virtual QStringList filesAtQrcPath(const QString &path, const QLocale *locale = 0,
+ ProjectExplorer::Project *project = 0,
+ QrcResourceSelector resources = AllQrcResources) = 0;
+ virtual QMap<QString,QStringList> filesInQrcPath(const QString &path,
+ const QLocale *locale = 0,
+ ProjectExplorer::Project *project = 0,
+ bool addDirs = false,
+ QrcResourceSelector resources = AllQrcResources) = 0;
virtual QList<ProjectInfo> projectInfos() const = 0;
virtual ProjectInfo projectInfo(ProjectExplorer::Project *project) const = 0;
diff --git a/src/libs/qmljs/qmljsqrcparser.cpp b/src/libs/qmljs/qmljsqrcparser.cpp
new file mode 100644
index 0000000000..cdecbcd735
--- /dev/null
+++ b/src/libs/qmljs/qmljsqrcparser.cpp
@@ -0,0 +1,517 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+****************************************************************************/
+
+#include "qmljsqrcparser.h"
+#include <QFile>
+#include <QDir>
+#include <QFileInfo>
+#include <QStringList>
+#include <QDomDocument>
+#include <QLocale>
+#include <QMutex>
+#include <QSet>
+#include <QMutexLocker>
+#include <QMultiHash>
+#include <QCoreApplication>
+#include <utils/qtcassert.h>
+#include <QDebug>
+
+namespace QmlJS {
+
+namespace Internal {
+/*!
+ * \class QrcParser
+ * \brief Parses one or more qrc files, and keeps their content cached
+ *
+ * A Qrc resource contains files read from the filesystem but organized in a possibly different way.
+ *
+ * To easily describe that with a simple structure we use a map from qrc paths to the paths in the
+ * filesystem.
+ * By using a map we can easily find all qrc paths that start with a given prefix, and thus loop
+ * on a qrc directory.
+ *
+ * Qrc files also support languages, those are mapped to a prefix of the qrc path.
+ * For example the french /image/bla.png (lang=fr) will have the path "fr/image/bla.png".
+ * The empty language represent the default resource.
+ * Languages are looked up using the locale uiLanguages() property
+ *
+ * For a single qrc a given path maps to a single file, but when one has multiple
+ * (platform specific exclusive) qrc files, then multiple files match, so QStringList are used.
+ *
+ * Especially the collect* methods are thought as low level interface.
+ */
+class QrcParserPrivate
+{
+ Q_DECLARE_TR_FUNCTIONS(QmlJS::QrcParser)
+public:
+ typedef QMap<QString,QStringList> SMap;
+ QrcParserPrivate(QrcParser *q);
+ bool parseFile(const QString &path);
+ QString firstFileAtPath(const QString &path, const QLocale &locale) const;
+ void collectFilesAtPath(const QString &path, QStringList *res, const QLocale *locale = 0) const;
+ bool hasDirAtPath(const QString &path, const QLocale *locale = 0) const;
+ void collectFilesInPath(const QString &path, QMap<QString,QStringList> *res, bool addDirs = false,
+ const QLocale *locale = 0) const;
+ QStringList errorMessages() const;
+ QStringList languages() const;
+private:
+ static QString fixPrefix(const QString &prefix);
+ QStringList allUiLanguages(const QLocale *locale) const;
+
+ SMap m_resources;
+ QStringList m_languages;
+ QStringList m_errorMessages;
+};
+
+class QrcCachePrivate
+{
+ Q_DECLARE_TR_FUNCTIONS(QmlJS::QrcCachePrivate)
+public:
+ QrcCachePrivate(QrcCache *q);
+ QrcParser::Ptr addPath(const QString &path);
+ void removePath(const QString &path);
+ QrcParser::Ptr updatePath(const QString &path);
+ QrcParser::Ptr parsedPath(const QString &path);
+ void clear();
+private:
+ QHash<QString, QPair<QrcParser::Ptr,int> > m_cache;
+ QMutex m_mutex;
+};
+} // namespace Internal
+
+/*! \brief normalizes the path to a file in a qrc resource by dropping the "qrc:/" or ":" and
+ * any extra slashes at the beginning
+ */
+QString QrcParser::normalizedQrcFilePath(const QString &path) {
+ QString normPath = path;
+ int endPrefix = 0;
+ if (path.startsWith(QLatin1String("qrc:/"))) {
+ endPrefix = 4;
+ } else if (path.startsWith(QLatin1String(":/"))) {
+ endPrefix = 1;
+ }
+ if (endPrefix < path.size() && path.at(endPrefix) == QLatin1Char('/'))
+ while (endPrefix + 1 < path.size() && path.at(endPrefix+1) == QLatin1Char('/'))
+ ++endPrefix;
+ normPath = path.right(path.size()-endPrefix);
+ if (!normPath.startsWith(QLatin1String("/")))
+ normPath.insert(0, QLatin1Char('/'));
+ return normPath;
+}
+
+/*! \brief normalizes the path to a directory in a qrc resource by dropping the "qrc:/" or ":" and
+ * any extra slashes at the beginning, and ensuring it ends with a slash
+ */
+QString QrcParser::normalizedQrcDirectoryPath(const QString &path) {
+ QString normPath = normalizedQrcFilePath(path);
+ if (!normPath.endsWith(QLatin1Char('/')))
+ normPath.append(QLatin1Char('/'));
+ return normPath;
+}
+
+QrcParser::QrcParser()
+{
+ d = new Internal::QrcParserPrivate(this);
+}
+
+QrcParser::~QrcParser()
+{
+ delete d;
+}
+
+bool QrcParser::parseFile(const QString &path)
+{
+ return d->parseFile(path);
+}
+
+/*! \brief returns fs path of the first (active) file at the given qrc path
+ */
+QString QrcParser::firstFileAtPath(const QString &path, const QLocale &locale) const
+{
+ return d->firstFileAtPath(path, locale);
+}
+
+/*! \brief adds al the fs paths for the given qrc path to *res
+ * If locale is null all possible files are added, otherwise just the first match
+ * using that locale.
+ */
+void QrcParser::collectFilesAtPath(const QString &path, QStringList *res, const QLocale *locale) const
+{
+ d->collectFilesAtPath(path, res, locale);
+}
+
+/*! \brief returns true if the given path is a non empty directory
+ */
+bool QrcParser::hasDirAtPath(const QString &path, const QLocale *locale) const
+{
+ return d->hasDirAtPath(path, locale);
+}
+
+/*! \brief adds the directory contents of the given qrc path to res
+ *
+ * adds the qrcFileName => fs paths associations contained in the given qrc path
+ * to res. If addDirs is true directories are also added.
+ * If locale is null all possible files are added, otherwise just the first match
+ * using that locale.
+ */
+void QrcParser::collectFilesInPath(const QString &path, QMap<QString,QStringList> *res, bool addDirs,
+ const QLocale *locale) const
+{
+ d->collectFilesInPath(path, res, addDirs, locale);
+}
+
+/*! \brief returns the errors found while parsing
+ */
+QStringList QrcParser::errorMessages() const
+{
+ return d->errorMessages();
+}
+
+/*! \brief returns all languages used in this qrc resource
+ */
+QStringList QrcParser::languages() const
+{
+ return d->languages();
+}
+
+/*! \brief if the contents are valid
+ */
+bool QrcParser::isValid() const
+{
+ return errorMessages().isEmpty();
+}
+
+QrcParser::Ptr QrcParser::parseQrcFile(const QString &path)
+{
+ Ptr res(new QrcParser);
+ if (!path.isEmpty())
+ res->parseFile(path);
+ return res;
+}
+
+// ----------------
+
+QrcCache::QrcCache()
+{
+ d = new Internal::QrcCachePrivate(this);
+}
+
+QrcCache::~QrcCache()
+{
+ delete d;
+}
+
+QrcParser::ConstPtr QrcCache::addPath(const QString &path)
+{
+ return d->addPath(path);
+}
+
+void QrcCache::removePath(const QString &path)
+{
+ d->removePath(path);
+}
+
+QrcParser::ConstPtr QrcCache::updatePath(const QString &path)
+{
+ return d->updatePath(path);
+}
+
+QrcParser::ConstPtr QrcCache::parsedPath(const QString &path)
+{
+ return d->parsedPath(path);
+}
+
+void QrcCache::clear()
+{
+ d->clear();
+}
+
+// --------------------
+
+namespace Internal {
+
+QrcParserPrivate::QrcParserPrivate(QrcParser *)
+{ }
+
+bool QrcParserPrivate::parseFile(const QString &path)
+{
+ QFile file(path);
+ if (!file.open(QIODevice::ReadOnly)) {
+ m_errorMessages.append(file.errorString());
+ return false;
+ }
+
+ QDomDocument doc;
+
+ QString error_msg;
+ int error_line, error_col;
+ if (!doc.setContent(&file, &error_msg, &error_line, &error_col)) {
+ m_errorMessages.append(tr("XML error on line %1, col %2: %3")
+ .arg(error_line).arg(error_col).arg(error_msg));
+ return false;
+ }
+
+ QDomElement root = doc.firstChildElement(QLatin1String("RCC"));
+ if (root.isNull()) {
+ m_errorMessages.append(tr("The <RCC> root element is missing."));
+ return false;
+ }
+
+ QDomElement relt = root.firstChildElement(QLatin1String("qresource"));
+ for (; !relt.isNull(); relt = relt.nextSiblingElement(QLatin1String("qresource"))) {
+
+ QString prefix = fixPrefix(relt.attribute(QLatin1String("prefix")));
+ const QString language = relt.attribute(QLatin1String("lang"));
+ if (!m_languages.contains(language))
+ m_languages.append(language);
+
+ QDomElement felt = relt.firstChildElement(QLatin1String("file"));
+ for (; !felt.isNull(); felt = felt.nextSiblingElement(QLatin1String("file"))) {
+ const QString fileName = felt.text();
+ QTC_CHECK(!QDir::isAbsolutePath(fileName));
+ const QString alias = felt.attribute(QLatin1String("alias"));
+ QString filePath = QFileInfo(path).path() + QLatin1Char('/') + fileName;
+ QString accessPath;
+ if (!alias.isEmpty())
+ accessPath = language + prefix + alias;
+ else
+ accessPath = language + prefix + fileName;
+ if (m_resources.contains(accessPath)) {
+ QStringList &val = m_resources[accessPath];
+ if (!val.contains(filePath))
+ val.append(filePath);
+ } else {
+ m_resources.insert(accessPath, QStringList(filePath));
+ }
+ }
+ }
+ return true;
+}
+
+// path is assumed to be a normalized absolute path
+QString QrcParserPrivate::firstFileAtPath(const QString &path, const QLocale &locale) const
+{
+ QTC_CHECK(path.startsWith(QLatin1Char('/')));
+ QStringList langs = allUiLanguages(&locale);
+ foreach (const QString &language, langs) {
+ if (m_languages.contains(language)) {
+ SMap::const_iterator res = m_resources.find(language + path);
+ if (res != m_resources.end())
+ return res.value().at(0);
+ }
+ }
+ return QString();
+}
+
+void QrcParserPrivate::collectFilesAtPath(const QString &path, QStringList *files,
+ const QLocale *locale) const
+{
+ QTC_CHECK(path.startsWith(QLatin1Char('/')));
+ QStringList langs = allUiLanguages(locale);
+ foreach (const QString &language, langs) {
+ if (m_languages.contains(language)) {
+ SMap::const_iterator res = m_resources.find(language + path);
+ if (res != m_resources.end())
+ (*files) << res.value();
+ }
+ }
+}
+
+// path is expected to be normalized and start and end with a slash
+bool QrcParserPrivate::hasDirAtPath(const QString &path, const QLocale *locale) const
+{
+ QTC_CHECK(path.startsWith(QLatin1Char('/')));
+ QTC_CHECK(path.endsWith(QLatin1Char('/')));
+ QStringList langs = allUiLanguages(locale);
+ foreach (const QString &language, langs) {
+ if (m_languages.contains(language)) {
+ QString key = language + path;
+ SMap::const_iterator res = m_resources.lowerBound(key);
+ if (res != m_resources.end() && res.key().startsWith(key))
+ return true;
+ }
+ }
+ return false;
+}
+
+void QrcParserPrivate::collectFilesInPath(const QString &path, QMap<QString,QStringList> *contents,
+ bool addDirs, const QLocale *locale) const
+{
+ QTC_CHECK(path.startsWith(QLatin1Char('/')));
+ QTC_CHECK(path.endsWith(QLatin1Char('/')));
+ SMap::const_iterator end = m_resources.end();
+ QStringList langs = allUiLanguages(locale);
+ foreach (const QString &language, langs) {
+ QString key = language + path;
+ SMap::const_iterator res = m_resources.lowerBound(key);
+ while (res != end && res.key().startsWith(key)) {
+ const QString &actualKey = res.key();
+ int endDir = actualKey.indexOf(QLatin1Char('/'), key.size());
+ if (endDir == -1) {
+ QString fileName = res.key().right(res.key().size()-key.size());
+ QStringList &els = (*contents)[fileName];
+ foreach (const QString &val, res.value())
+ if (!els.contains(val)){
+ els << val;
+ }
+ ++res;
+ } else {
+ QString dirName = res.key().mid(key.size(), endDir - key.size() + 1);
+ if (addDirs)
+ contents->insert(dirName, QStringList());
+ QString key2 = key + dirName;
+ do {
+ ++res;
+ } while (res != end && res.key().startsWith(key2));
+ }
+ }
+ }
+}
+
+QStringList QrcParserPrivate::errorMessages() const
+{
+ return m_errorMessages;
+}
+
+QStringList QrcParserPrivate::languages() const
+{
+ return m_languages;
+}
+
+QString QrcParserPrivate::fixPrefix(const QString &prefix)
+{
+ const QChar slash = QLatin1Char('/');
+ QString result = QString(slash);
+ for (int i = 0; i < prefix.size(); ++i) {
+ const QChar c = prefix.at(i);
+ if (c == slash && result.at(result.size() - 1) == slash)
+ continue;
+ result.append(c);
+ }
+
+ if (!result.endsWith(slash))
+ result.append(slash);
+
+ return result;
+}
+
+QStringList QrcParserPrivate::allUiLanguages(const QLocale *locale) const
+{
+ if (!locale)
+ return languages();
+ QStringList langs = locale->uiLanguages();
+ foreach (const QString &language, langs) { // qt4 support
+ if (language.contains(QLatin1Char('_')) || language.contains(QLatin1Char('-'))) {
+ QStringList splits = QString(language).replace(QLatin1Char('_'), QLatin1Char('-'))
+ .split(QLatin1Char('-'));
+ if (splits.size() > 1 && !langs.contains(splits.at(0)))
+ langs.append(splits.at(0));
+ }
+ }
+ if (!langs.contains(QString()))
+ langs.append(QString());
+ return langs;
+}
+
+// ----------------
+
+QrcCachePrivate::QrcCachePrivate(QrcCache *)
+{ }
+
+QrcParser::Ptr QrcCachePrivate::addPath(const QString &path)
+{
+ QPair<QrcParser::Ptr,int> currentValue;
+ {
+ QMutexLocker l(&m_mutex);
+ currentValue = m_cache.value(path, qMakePair(QrcParser::Ptr(0), 0));
+ currentValue.second += 1;
+ if (currentValue.second > 1) {
+ m_cache.insert(path, currentValue);
+ return currentValue.first;
+ }
+ }
+ QrcParser::Ptr newParser = QrcParser::parseQrcFile(path);
+ if (!newParser->isValid())
+ qDebug() << "adding invalid qrc " << path << " to the cache:" << newParser->errorMessages();
+ {
+ QMutexLocker l(&m_mutex);
+ QPair<QrcParser::Ptr,int> currentValue = m_cache.value(path, qMakePair(QrcParser::Ptr(0), 0));
+ if (currentValue.first.isNull())
+ currentValue.first = newParser;
+ currentValue.second += 1;
+ m_cache.insert(path, currentValue);
+ return currentValue.first;
+ }
+}
+
+void QrcCachePrivate::removePath(const QString &path)
+{
+ QPair<QrcParser::Ptr,int> currentValue;
+ {
+ QMutexLocker l(&m_mutex);
+ currentValue = m_cache.value(path, qMakePair(QrcParser::Ptr(0), 0));
+ if (currentValue.second == 1) {
+ m_cache.remove(path);
+ } else if (currentValue.second > 1) {
+ currentValue.second -= 1;
+ m_cache.insert(path, currentValue);
+ } else {
+ QTC_CHECK(!m_cache.contains(path));
+ }
+ }
+}
+
+QrcParser::Ptr QrcCachePrivate::updatePath(const QString &path)
+{
+ QrcParser::Ptr newParser = QrcParser::parseQrcFile(path);
+ {
+ QMutexLocker l(&m_mutex);
+ QPair<QrcParser::Ptr,int> currentValue = m_cache.value(path, qMakePair(QrcParser::Ptr(0), 0));
+ QTC_CHECK(currentValue.second > 0);
+ currentValue.first = newParser;
+ m_cache.insert(path, currentValue);
+ return currentValue.first;
+ }
+}
+
+QrcParser::Ptr QrcCachePrivate::parsedPath(const QString &path)
+{
+ QMutexLocker l(&m_mutex);
+ QPair<QrcParser::Ptr,int> currentValue = m_cache.value(path, qMakePair(QrcParser::Ptr(0), 0));
+ return currentValue.first;
+}
+
+void QrcCachePrivate::clear()
+{
+ QMutexLocker l(&m_mutex);
+ m_cache.clear();
+}
+
+} // namespace Internal
+} // namespace QmlJS
diff --git a/src/libs/qmljs/qmljsqrcparser.h b/src/libs/qmljs/qmljsqrcparser.h
new file mode 100644
index 0000000000..e25baa2c73
--- /dev/null
+++ b/src/libs/qmljs/qmljsqrcparser.h
@@ -0,0 +1,87 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+****************************************************************************/
+
+#ifndef QMLJSQRCPARSER_H
+#define QMLJSQRCPARSER_H
+#include "qmljs_global.h"
+
+#include <QMap>
+#include <QSharedPointer>
+#include <QString>
+#include <QStringList>
+
+QT_FORWARD_DECLARE_CLASS(QLocale)
+
+namespace QmlJS {
+
+namespace Internal {
+class QrcParserPrivate;
+class QrcCachePrivate;
+}
+
+class QMLJS_EXPORT QrcParser
+{
+public:
+ typedef QSharedPointer<QrcParser> Ptr;
+ typedef QSharedPointer<const QrcParser> ConstPtr;
+ ~QrcParser();
+ bool parseFile(const QString &path);
+ QString firstFileAtPath(const QString &path, const QLocale &locale) const;
+ void collectFilesAtPath(const QString &path, QStringList *res, const QLocale *locale = 0) const;
+ bool hasDirAtPath(const QString &path, const QLocale *locale = 0) const;
+ void collectFilesInPath(const QString &path, QMap<QString,QStringList> *res, bool addDirs = false,
+ const QLocale *locale = 0) const;
+ QStringList errorMessages() const;
+ QStringList languages() const;
+ bool isValid() const;
+
+ static Ptr parseQrcFile(const QString &path);
+ static QString normalizedQrcFilePath(const QString &path);
+ static QString normalizedQrcDirectoryPath(const QString &path);
+private:
+ QrcParser();
+ QrcParser(const QrcParser &);
+ Internal::QrcParserPrivate *d;
+};
+
+class QMLJS_EXPORT QrcCache
+{
+public:
+ QrcCache();
+ ~QrcCache();
+ QrcParser::ConstPtr addPath(const QString &path);
+ void removePath(const QString &path);
+ QrcParser::ConstPtr updatePath(const QString &path);
+ QrcParser::ConstPtr parsedPath(const QString &path);
+ void clear();
+private:
+ Internal::QrcCachePrivate *d;
+};
+}
+#endif // QMLJSQRCPARSER_H
diff --git a/src/libs/qmljs/qmljsscopechain.cpp b/src/libs/qmljs/qmljsscopechain.cpp
index 4c6ff96c93..a0a9bf0f91 100644
--- a/src/libs/qmljs/qmljsscopechain.cpp
+++ b/src/libs/qmljs/qmljsscopechain.cpp
@@ -30,6 +30,7 @@
#include "qmljsscopechain.h"
#include "qmljsbind.h"
#include "qmljsevaluate.h"
+#include "qmljsmodelmanagerinterface.h"
using namespace QmlJS;
@@ -310,7 +311,10 @@ void ScopeChain::initializeRootScope()
if (!m_document->bind()->isJsLibrary()) {
foreach (Document::Ptr otherDoc, snapshot) {
foreach (const ImportInfo &import, otherDoc->bind()->imports()) {
- if (import.type() == ImportInfo::FileImport && m_document->fileName() == import.path()) {
+ if ((import.type() == ImportInfo::FileImport && m_document->fileName() == import.path())
+ || (import.type() == ImportInfo::QrcFileImport
+ && ModelManagerInterface::instance()->filesAtQrcPath(import.path())
+ .contains(m_document->fileName()))) {
QmlComponentChain *component = new QmlComponentChain(otherDoc);
componentScopes.insert(otherDoc.data(), component);
chain->addInstantiatingComponent(component);
diff --git a/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp b/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp
index 50120f9fa4..9956ecab1a 100644
--- a/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp
+++ b/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp
@@ -47,6 +47,7 @@
#include <qmljs/qmljscheck.h>
#include <qmljs/qmljsutils.h>
#include <qmljs/qmljsmodelmanagerinterface.h>
+#include <qmljs/qmljsqrcparser.h>
#include <QSet>
#include <QDir>
@@ -362,10 +363,17 @@ public:
} else if (importInfo.isValid() && importInfo.type() == ImportInfo::DirectoryImport) {
QString path = importInfo.path();
QDir dir(m_doc->path());
+ // should probably try to make it relatve to some import path, not to the document path
QString relativeDir = dir.relativeFilePath(path);
QString name = relativeDir.replace(QLatin1Char('/'), QLatin1Char('.'));
if (!name.isEmpty())
typeName.prepend(name + QLatin1Char('.'));
+ } else if (importInfo.isValid() && importInfo.type() == ImportInfo::QrcDirectoryImport) {
+ QString path = QrcParser::normalizedQrcDirectoryPath(importInfo.path());
+ path = path.mid(1, path.size() - ((path.size() > 1) ? 2 : 1));
+ const QString name = path.replace(QLatin1Char('/'), QLatin1Char('.'));
+ if (!name.isEmpty())
+ typeName.prepend(name + QLatin1Char('.'));
}
}
}
diff --git a/src/plugins/qmljseditor/qmljshoverhandler.cpp b/src/plugins/qmljseditor/qmljshoverhandler.cpp
index 7e816cf397..c33c1d13cd 100644
--- a/src/plugins/qmljseditor/qmljshoverhandler.cpp
+++ b/src/plugins/qmljseditor/qmljshoverhandler.cpp
@@ -43,6 +43,7 @@
#include <qmljs/parser/qmljsast_p.h>
#include <qmljs/parser/qmljsastfwd_p.h>
#include <qmljs/qmljsutils.h>
+#include <qmljs/qmljsqrcparser.h>
#include <texteditor/itexteditor.h>
#include <texteditor/basetexteditor.h>
#include <texteditor/helpitem.h>
@@ -134,9 +135,15 @@ static inline QString getModuleName(const ScopeChain &scopeChain, const Document
} else if (importInfo.isValid() && importInfo.type() == ImportInfo::DirectoryImport) {
const QString path = importInfo.path();
const QDir dir(qmlDocument->path());
+ // should probably try to make it relatve to some import path, not to the document path
QString relativeDir = dir.relativeFilePath(path);
const QString name = relativeDir.replace(QLatin1Char('/'), QLatin1Char('.'));
return name;
+ } else if (importInfo.isValid() && importInfo.type() == ImportInfo::QrcDirectoryImport) {
+ QString path = QrcParser::normalizedQrcDirectoryPath(importInfo.path());
+ path = path.mid(1, path.size() - ((path.size() > 1) ? 2 : 1));
+ const QString name = path.replace(QLatin1Char('/'), QLatin1Char('.'));
+ return name;
}
}
return QString();
diff --git a/src/plugins/qmljstools/qmljsmodelmanager.cpp b/src/plugins/qmljstools/qmljsmodelmanager.cpp
index 07e9ec06c1..d2c25d49f7 100644
--- a/src/plugins/qmljstools/qmljsmodelmanager.cpp
+++ b/src/plugins/qmljstools/qmljsmodelmanager.cpp
@@ -58,6 +58,7 @@
#include <QTextStream>
#include <QTimer>
#include <QRegExp>
+#include <QtAlgorithms>
#include <QDebug>
@@ -406,6 +407,114 @@ void ModelManager::removeFiles(const QStringList &files)
}
}
+namespace {
+bool pInfoLessThanActive(const ModelManager::ProjectInfo &p1, const ModelManager::ProjectInfo &p2)
+{
+ QStringList s1 = p1.activeResourceFiles;
+ QStringList s2 = p2.activeResourceFiles;
+ if (s1.size() < s2.size())
+ return true;
+ if (s1.size() > s2.size())
+ return false;
+ for (int i = 0; i < s1.size(); ++i) {
+ if (s1.at(i) < s2.at(i))
+ return true;
+ else if (s1.at(i) > s2.at(i))
+ return false;
+ }
+ return false;
+}
+
+bool pInfoLessThanAll(const ModelManager::ProjectInfo &p1, const ModelManager::ProjectInfo &p2)
+{
+ QStringList s1 = p1.allResourceFiles;
+ QStringList s2 = p2.allResourceFiles;
+ if (s1.size() < s2.size())
+ return true;
+ if (s1.size() > s2.size())
+ return false;
+ for (int i = 0; i < s1.size(); ++i) {
+ if (s1.at(i) < s2.at(i))
+ return true;
+ else if (s1.at(i) > s2.at(i))
+ return false;
+ }
+ return false;
+}
+}
+
+QStringList ModelManager::filesAtQrcPath(const QString &path, const QLocale *locale,
+ ProjectExplorer::Project *project,
+ QrcResourceSelector resources)
+{
+ QString normPath = QrcParser::normalizedQrcFilePath(path);
+ QList<ProjectInfo> pInfos;
+ if (project)
+ pInfos.append(projectInfo(project));
+ else
+ pInfos = projectInfos();
+
+ QStringList res;
+ QSet<QString> pathsChecked;
+ foreach (const ModelManager::ProjectInfo &pInfo, pInfos) {
+ QStringList qrcFilePaths;
+ if (resources == ActiveQrcResources)
+ qrcFilePaths = pInfo.activeResourceFiles;
+ else
+ qrcFilePaths = pInfo.allResourceFiles;
+ foreach (const QString &qrcFilePath, qrcFilePaths) {
+ if (pathsChecked.contains(qrcFilePath))
+ continue;
+ pathsChecked.insert(qrcFilePath);
+ QrcParser::ConstPtr qrcFile = m_qrcCache.parsedPath(qrcFilePath);
+ if (qrcFile.isNull())
+ continue;
+ qrcFile->collectFilesAtPath(normPath, &res, locale);
+ }
+ }
+ res.sort(); // make the result predictable
+ return res;
+}
+
+QMap<QString, QStringList> ModelManager::filesInQrcPath(const QString &path,
+ const QLocale *locale,
+ ProjectExplorer::Project *project,
+ bool addDirs,
+ QrcResourceSelector resources)
+{
+ QString normPath = QrcParser::normalizedQrcDirectoryPath(path);
+ QList<ProjectInfo> pInfos;
+ if (project) {
+ pInfos.append(projectInfo(project));
+ } else {
+ pInfos = projectInfos();
+ if (resources == ActiveQrcResources) // make the result predictable
+ qSort(pInfos.begin(), pInfos.end(), &pInfoLessThanActive);
+ else
+ qSort(pInfos.begin(), pInfos.end(), &pInfoLessThanAll);
+ }
+ QMap<QString, QStringList> res;
+ QSet<QString> pathsChecked;
+ foreach (const ModelManager::ProjectInfo &pInfo, pInfos) {
+ QStringList qrcFilePaths;
+ if (resources == ActiveQrcResources)
+ qrcFilePaths = pInfo.activeResourceFiles;
+ else
+ qrcFilePaths = pInfo.allResourceFiles;
+ foreach (const QString &qrcFilePath, qrcFilePaths) {
+ if (pathsChecked.contains(qrcFilePath))
+ continue;
+ pathsChecked.insert(qrcFilePath);
+ QrcParser::ConstPtr qrcFile = m_qrcCache.parsedPath(qrcFilePath);
+
+ if (qrcFile.isNull())
+ continue;
+ qrcFile->collectFilesInPath(normPath, &res, addDirs, locale);
+ }
+ }
+ return res;
+}
+
QList<ModelManager::ProjectInfo> ModelManager::projectInfos() const
{
QMutexLocker locker(&m_mutex);
@@ -462,6 +571,12 @@ void ModelManager::updateProjectInfo(const ProjectInfo &pinfo)
}
updateSourceFiles(newFiles, false);
+ // update qrc cache
+ foreach (const QString &newQrc, pinfo.allResourceFiles)
+ m_qrcCache.addPath(newQrc);
+ foreach (const QString &oldQrc, oldInfo.allResourceFiles)
+ m_qrcCache.removePath(oldQrc);
+
// dump builtin types if the shipped definitions are probably outdated and the
// Qt version ships qmlplugindump
if (QtSupport::QtVersionNumber(pinfo.qtVersionString) > QtSupport::QtVersionNumber(4, 8, 5))
@@ -497,6 +612,11 @@ ModelManagerInterface::ProjectInfo ModelManager::projectInfoForPath(QString path
void ModelManager::emitDocumentChangedOnDisk(Document::Ptr doc)
{ emit documentChangedOnDisk(doc); }
+void ModelManager::updateQrcFile(const QString &path)
+{
+ m_qrcCache.updatePath(path);
+}
+
void ModelManager::updateDocument(Document::Ptr doc)
{
{
@@ -560,6 +680,23 @@ static void findNewFileImports(const Document::Ptr &doc, const Snapshot &snapsho
scannedPaths->insert(importName);
}
}
+ } else if (import.type() == ImportInfo::QrcFileImport) {
+ QStringList importPaths = ModelManagerInterface::instance()->filesAtQrcPath(importName);
+ foreach (const QString &importPath, importPaths) {
+ if (! snapshot.document(importPath))
+ *importedFiles += importPath;
+ }
+ } else if (import.type() == ImportInfo::QrcDirectoryImport) {
+ QMapIterator<QString,QStringList> dirContents(ModelManagerInterface::instance()->filesInQrcPath(importName));
+ while (dirContents.hasNext()) {
+ dirContents.next();
+ if (Document::isQmlLikeOrJsLanguage(Document::guessLanguageFromSuffix(dirContents.key()))) {
+ foreach (const QString &filePath, dirContents.value()) {
+ if (! snapshot.document(filePath))
+ *importedFiles += filePath;
+ }
+ }
+ }
}
}
}
@@ -697,8 +834,11 @@ void ModelManager::parse(QFutureInterface<void> &future,
const QString fileName = files.at(i);
Document::Language language = languageOfFile(fileName);
- if (language == Document::UnknownLanguage)
+ if (language == Document::UnknownLanguage) {
+ if (fileName.endsWith(QLatin1String(".qrc")))
+ modelManager->updateQrcFile(fileName);
continue;
+ }
QString contents;
int documentRevision = 0;
diff --git a/src/plugins/qmljstools/qmljsmodelmanager.h b/src/plugins/qmljstools/qmljsmodelmanager.h
index 3b0763f904..1c42c03882 100644
--- a/src/plugins/qmljstools/qmljsmodelmanager.h
+++ b/src/plugins/qmljstools/qmljsmodelmanager.h
@@ -33,6 +33,7 @@
#include "qmljstools_global.h"
#include <qmljs/qmljsmodelmanagerinterface.h>
+#include <qmljs/qmljsqrcparser.h>
#include <cplusplus/CppDocument.h>
@@ -41,6 +42,7 @@
#include <QMutex>
QT_FORWARD_DECLARE_CLASS(QTimer)
+QT_FORWARD_DECLARE_CLASS(QLocale)
namespace Core {
class MimeType;
@@ -50,6 +52,10 @@ namespace CPlusPlus {
class CppModelManagerInterface;
}
+namespace QmlJS {
+class QrcParser;
+}
+
namespace QmlJSTools {
QMLJSTOOLS_EXPORT QmlJS::Document::Language languageOfFile(const QString &fileName);
@@ -77,6 +83,14 @@ public:
bool emitDocumentOnDiskChanged);
virtual void fileChangedOnDisk(const QString &path);
virtual void removeFiles(const QStringList &files);
+ virtual QStringList filesAtQrcPath(const QString &path, const QLocale *locale = 0,
+ ProjectExplorer::Project *project = 0,
+ QrcResourceSelector resources = AllQrcResources);
+ virtual QMap<QString,QStringList> filesInQrcPath(const QString &path,
+ const QLocale *locale = 0,
+ ProjectExplorer::Project *project = 0,
+ bool addDirs = false,
+ QrcResourceSelector resources = AllQrcResources);
virtual QList<ProjectInfo> projectInfos() const;
virtual ProjectInfo projectInfo(ProjectExplorer::Project *project) const;
@@ -87,6 +101,7 @@ public:
void updateDocument(QmlJS::Document::Ptr doc);
void updateLibraryInfo(const QString &path, const QmlJS::LibraryInfo &info);
void emitDocumentChangedOnDisk(QmlJS::Document::Ptr doc);
+ void updateQrcFile(const QString &path);
virtual QStringList importPaths() const;
virtual QmlJS::QmlLanguageBundles activeBundles() const;
@@ -147,6 +162,7 @@ private:
QTimer *m_updateCppQmlTypesTimer;
QHash<QString, QPair<CPlusPlus::Document::Ptr, bool> > m_queuedCppDocuments;
QFuture<void> m_cppQmlTypesUpdater;
+ QmlJS::QrcCache m_qrcCache;
CppDataHash m_cppDataHash;
mutable QMutex m_cppDataMutex;
diff --git a/src/plugins/qt4projectmanager/qt4nodes.cpp b/src/plugins/qt4projectmanager/qt4nodes.cpp
index 92954b6be8..2553c1f25d 100644
--- a/src/plugins/qt4projectmanager/qt4nodes.cpp
+++ b/src/plugins/qt4projectmanager/qt4nodes.cpp
@@ -1886,6 +1886,10 @@ void Qt4ProFileNode::applyEvaluate(EvalResult evalResult, bool async)
QLatin1String("OBJECTIVE_HEADERS"), m_projectDir, buildDirectory);
newVarValues[UiDirVar] = QStringList() << uiDirPath(m_readerExact);
newVarValues[MocDirVar] = QStringList() << mocDirPath(m_readerExact);
+ newVarValues[ResourceVar] = fileListForVar(m_readerExact, m_readerCumulative,
+ QLatin1String("RESOURCES"), m_projectDir, buildDirectory);
+ newVarValues[ExactResourceVar] = fileListForVar(m_readerExact, 0,
+ QLatin1String("RESOURCES"), m_projectDir, buildDirectory);
newVarValues[PkgConfigVar] = m_readerExact->values(QLatin1String("PKGCONFIG"));
newVarValues[PrecompiledHeaderVar] =
m_readerExact->absoluteFileValues(QLatin1String("PRECOMPILED_HEADER"),
diff --git a/src/plugins/qt4projectmanager/qt4nodes.h b/src/plugins/qt4projectmanager/qt4nodes.h
index c4d01190bb..745b745966 100644
--- a/src/plugins/qt4projectmanager/qt4nodes.h
+++ b/src/plugins/qt4projectmanager/qt4nodes.h
@@ -86,6 +86,8 @@ enum Qt4Variable {
CppSourceVar,
ObjCSourceVar,
ObjCHeaderVar,
+ ResourceVar,
+ ExactResourceVar,
UiDirVar,
MocDirVar,
PkgConfigVar,
diff --git a/src/plugins/qt4projectmanager/qt4project.cpp b/src/plugins/qt4projectmanager/qt4project.cpp
index 123dbca22d..b2efd28323 100644
--- a/src/plugins/qt4projectmanager/qt4project.cpp
+++ b/src/plugins/qt4projectmanager/qt4project.cpp
@@ -627,6 +627,8 @@ void Qt4Project::updateQmlJSCodeModel()
bool hasQmlLib = false;
foreach (Qt4ProFileNode *node, proFiles) {
projectInfo.importPaths.append(node->variableValue(QmlImportPathVar));
+ projectInfo.activeResourceFiles.append(node->variableValue(ExactResourceVar));
+ projectInfo.allResourceFiles.append(node->variableValue(ResourceVar));
if (!hasQmlLib) {
QStringList qtLibs = node->variableValue(QtVar);
hasQmlLib = qtLibs.contains(QLatin1String("declarative")) ||
@@ -645,6 +647,8 @@ void Qt4Project::updateQmlJSCodeModel()
setProjectLanguages(pl);
projectInfo.importPaths.removeDuplicates();
+ projectInfo.activeResourceFiles.removeDuplicates();
+ projectInfo.allResourceFiles.removeDuplicates();
setProjectLanguage(ProjectExplorer::Constants::LANG_QMLJS, !projectInfo.sourceFiles.isEmpty());
diff --git a/tests/auto/qml/qml.pro b/tests/auto/qml/qml.pro
index 466be4b27b..5b320abd5e 100644
--- a/tests/auto/qml/qml.pro
+++ b/tests/auto/qml/qml.pro
@@ -4,4 +4,5 @@ SUBDIRS += qmldesigner \
qmleditor \
qmlprojectmanager \
codemodel \
- reformatter
+ reformatter \
+ qrcparser
diff --git a/tests/auto/qml/qrcparser/qrcparser.pro b/tests/auto/qml/qrcparser/qrcparser.pro
new file mode 100644
index 0000000000..a051c8926c
--- /dev/null
+++ b/tests/auto/qml/qrcparser/qrcparser.pro
@@ -0,0 +1,11 @@
+#include(../shared/shared.pri)
+QTC_PLUGIN_DEPENDS += qmljstools
+include(../../qttest.pri)
+#DEFINES+=CPLUSPLUS_BUILD_STATIC_LIB
+include($$IDE_SOURCE_TREE/src/rpath.pri)
+
+LIBS += -L$$IDE_PLUGIN_PATH/QtProject
+#DEFINES += Q_PLUGIN_PATH=\"\\\"$$IDE_PLUGIN_PATH/QtProject\\\"\"
+
+DEFINES += TESTSRCDIR=\\\"$$PWD\\\"
+SOURCES += $$PWD/tst_qrcparser.cpp
diff --git a/tests/auto/qml/qrcparser/simple.qrc b/tests/auto/qml/qrcparser/simple.qrc
new file mode 100644
index 0000000000..add0b4e0cf
--- /dev/null
+++ b/tests/auto/qml/qrcparser/simple.qrc
@@ -0,0 +1,17 @@
+ <!DOCTYPE RCC><RCC version="1.0">
+ <qresource>
+ <file>images/copy.png</file>
+ <file>images/cut.png</file>
+ <file>images/new.png</file>
+ <file>images/open.png</file>
+ <file>images/paste.png</file>
+ <file>images/save.png</file>
+ <file>cut.jpg</file>
+</qresource>
+ <qresource lang="fr">
+ <file alias="cut.jpg">cut_fr.jpg</file>
+ </qresource>
+ <qresource prefix="/myresources">
+ <file alias="cut-img.png">images/cut.png</file>
+ </qresource>
+ </RCC> \ No newline at end of file
diff --git a/tests/auto/qml/qrcparser/tst_qrcparser.cpp b/tests/auto/qml/qrcparser/tst_qrcparser.cpp
new file mode 100644
index 0000000000..b642e5a163
--- /dev/null
+++ b/tests/auto/qml/qrcparser/tst_qrcparser.cpp
@@ -0,0 +1,196 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+****************************************************************************/
+
+#include <QtTest>
+#include <QDebug>
+#include <QLocale>
+
+#include <qmljs/qmljsqrcparser.h>
+
+using namespace QmlJS;
+
+class tst_QrcParser: public QObject
+{
+ Q_OBJECT
+public:
+ void readInData();
+ QStringList allPaths(QrcParser::ConstPtr p);
+private slots:
+ void firstAtTest_data() { readInData(); }
+ void firstInTest_data() { readInData(); }
+ void cacheTest_data() { readInData(); }
+ void firstAtTest();
+ void firstInTest();
+ void cacheTest();
+ void simpleTest();
+ void cleanupTestCase();
+private:
+ QLocale m_locale;
+ QrcCache m_cache;
+};
+
+void tst_QrcParser::readInData()
+{
+ QTest::addColumn<QString>("path");
+
+ QDirIterator it(TESTSRCDIR, QStringList("*.qrc"), QDir::Files);
+ while (it.hasNext()) {
+ const QString fileName = it.next();
+ QTest::newRow(fileName.toLatin1()) << it.filePath();
+ }
+}
+
+QStringList tst_QrcParser::allPaths(QrcParser::ConstPtr p)
+{
+ QStringList res;
+ res << QLatin1String("/");
+ int iPos = 0;
+ while (iPos < res.size()) {
+ QString pAtt = res.at(iPos++);
+ if (!pAtt.endsWith(QLatin1Char('/')))
+ continue;
+ QMap<QString,QStringList> content;
+ p->collectFilesInPath(pAtt, &content, true);
+ foreach (const QString &fileName, content.keys())
+ res.append(pAtt+fileName);
+ }
+ return res;
+}
+
+void tst_QrcParser::firstAtTest()
+{
+ QFETCH(QString, path);
+ QrcParser::Ptr p = QrcParser::parseQrcFile(path);
+ foreach (const QString &qrcPath, allPaths(p)) {
+ QString s1 = p->firstFileAtPath(qrcPath, m_locale);
+ if (s1.isEmpty())
+ continue;
+ QStringList l;
+ p->collectFilesAtPath(qrcPath, &l, &m_locale);
+ QCOMPARE(l.value(0), s1);
+ l.clear();
+ p->collectFilesAtPath(qrcPath, &l);
+ QVERIFY(l.contains(s1));
+ }
+}
+
+void tst_QrcParser::firstInTest()
+{
+ QFETCH(QString, path);
+ QrcParser::Ptr p = QrcParser::parseQrcFile(path);
+ foreach (const QString &qrcPath, allPaths(p)) {
+ if (!qrcPath.endsWith(QLatin1Char('/')))
+ continue;
+ for (int addDirs = 0; addDirs < 2; ++addDirs) {
+ QMap<QString,QStringList> s1;
+ p->collectFilesInPath(qrcPath, &s1, addDirs, &m_locale);
+ foreach (const QString &k, s1.keys()) {
+ if (!k.endsWith(QLatin1Char('/'))) {
+ QCOMPARE(s1.value(k).value(0), p->firstFileAtPath(qrcPath + k, m_locale));
+ }
+ }
+ QMap<QString,QStringList> s2;
+ p->collectFilesInPath(qrcPath, &s2, addDirs);
+ foreach (const QString &k, s1.keys()) {
+ if (!k.endsWith(QLatin1Char('/'))) {
+ QVERIFY(s2.value(k).contains(s1.value(k).at(0)));
+ } else {
+ QVERIFY(s2.contains(k));
+ }
+ }
+ foreach (const QString &k, s2.keys()) {
+ if (!k.endsWith(QLatin1Char('/'))) {
+ QStringList l;
+ p->collectFilesAtPath(qrcPath + k, &l);
+ QCOMPARE(s2.value(k), l);
+ } else {
+ QVERIFY(s2.value(k).isEmpty());
+ }
+ }
+ }
+ }
+}
+
+void tst_QrcParser::cacheTest()
+{
+ QFETCH(QString, path);
+ QVERIFY(m_cache.parsedPath(path).isNull());
+ QrcParser::ConstPtr p0 = m_cache.addPath(path);
+ QVERIFY(!p0.isNull());
+ QrcParser::ConstPtr p1 = m_cache.parsedPath(path);
+ QVERIFY(p1.data() == p0.data());
+ QrcParser::ConstPtr p2 = m_cache.addPath(path);
+ QVERIFY(p2.data() == p1.data());
+ QrcParser::ConstPtr p3 = m_cache.parsedPath(path);
+ QVERIFY(p3.data() == p2.data());
+ QrcParser::ConstPtr p4 = m_cache.updatePath(path);
+ QVERIFY(p4.data() != p3.data());
+ QrcParser::ConstPtr p5 = m_cache.parsedPath(path);
+ QVERIFY(p5.data() == p4.data());
+ m_cache.removePath(path);
+ QrcParser::ConstPtr p6 = m_cache.parsedPath(path);
+ QVERIFY(p6.data() == p5.data());
+ m_cache.removePath(path);
+ QrcParser::ConstPtr p7 = m_cache.parsedPath(path);
+ QVERIFY(p7.isNull());
+}
+
+void tst_QrcParser::simpleTest()
+{
+ QrcParser::Ptr p = QrcParser::parseQrcFile(QString::fromLatin1(TESTSRCDIR).append(QLatin1String("/simple.qrc")));
+ QStringList paths = allPaths(p);
+ paths.sort();
+ QVERIFY(paths == QStringList()
+ << QLatin1String("/")
+ << QLatin1String("/cut.jpg")
+ << QLatin1String("/images/")
+ << QLatin1String("/images/copy.png")
+ << QLatin1String("/images/cut.png")
+ << QLatin1String("/images/new.png")
+ << QLatin1String("/images/open.png")
+ << QLatin1String("/images/paste.png")
+ << QLatin1String("/images/save.png")
+ << QLatin1String("/myresources/")
+ << QLatin1String("/myresources/cut-img.png"));
+ QString frPath = p->firstFileAtPath(QLatin1String("/cut.jpg"), QLocale(QLatin1String("fr_FR")));
+ QString refFrPath = QString::fromLatin1(TESTSRCDIR).append(QLatin1String("/cut_fr.jpg"));
+ QCOMPARE(frPath, refFrPath);
+ QString dePath = p->firstFileAtPath(QLatin1String("/cut.jpg"), QLocale(QLatin1String("de_DE")));
+ QString refDePath = QString::fromLatin1(TESTSRCDIR).append(QLatin1String("/cut.jpg"));
+ QCOMPARE(dePath, refDePath);
+}
+
+void tst_QrcParser::cleanupTestCase()
+{
+ m_cache.clear();
+}
+
+QTEST_APPLESS_MAIN(tst_QrcParser)
+
+#include "tst_qrcparser.moc"