aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlan Alpert <aalpert@rim.com>2012-12-21 15:42:47 -0800
committerThe Qt Project <gerrit-noreply@qt-project.org>2013-09-17 03:04:56 +0200
commit9aec6efd1f7e0c4922253163efc2e7ea7e751f0f (patch)
tree0dd84012338b6b8c4c2db235b7f06b6f7b568de1
parentf94c14821b5a4bede0ad1a5e41cb355880f87f97 (diff)
Add qml tool
This tool simply runs QML files using a QQmlApplicationEngine. It is configurable so as to behave, by default, like qmlscene in that it will automatically place non-Window QtQuick 2 Items inside a QQuickWindow with the size of the root item. The configuration is extensible so that other GUI scenes can also use it by altering the configuration files in their installation. On OS X, it is an app bundle, and handles the QFileOpenEvent so that it can be the tool with which qml files are usually launched by double-clicking. (This does not break the ability to use it on the command line too: the options still work, you just have to give the path to the executable inside the bundle.) Change-Id: I6bac813ce188be54842a78d7b532fcf2d54dc443 Reviewed-by: Alan Alpert (Personal) <416365416c@gmail.com>
-rw-r--r--tests/auto/qml/debugger/qdebugmessageservice/tst_qdebugmessageservice.cpp5
-rw-r--r--tests/auto/qml/debugger/qqmldebugjs/tst_qqmldebugjs.cpp7
-rw-r--r--tests/auto/qml/debugger/qqmlenginedebuginspectorintegrationtest/tst_qqmlenginedebuginspectorintegration.cpp3
-rw-r--r--tests/auto/qml/debugger/qqmlinspector/tst_qqmlinspector.cpp5
-rw-r--r--tests/auto/qml/debugger/qqmlprofilerservice/tst_qqmlprofilerservice.cpp4
-rw-r--r--tests/auto/qml/debugger/qv8profilerservice/tst_qv8profilerservice.cpp3
-rw-r--r--tests/auto/qml/debugger/shared/debugutil.cpp16
-rw-r--r--tools/qml/Info.plist49
-rw-r--r--tools/qml/conf.h99
-rw-r--r--tools/qml/conf/configuration.qml47
-rw-r--r--tools/qml/conf/qtquick.qml55
-rw-r--r--tools/qml/main.cpp466
-rw-r--r--tools/qml/qml.icnsbin0 -> 196156 bytes
-rw-r--r--tools/qml/qml.pro14
-rw-r--r--tools/qml/qml.qrc6
-rw-r--r--tools/tools.pro1
16 files changed, 769 insertions, 11 deletions
diff --git a/tests/auto/qml/debugger/qdebugmessageservice/tst_qdebugmessageservice.cpp b/tests/auto/qml/debugger/qdebugmessageservice/tst_qdebugmessageservice.cpp
index e6cbd41b8e..9318372e2c 100644
--- a/tests/auto/qml/debugger/qdebugmessageservice/tst_qdebugmessageservice.cpp
+++ b/tests/auto/qml/debugger/qdebugmessageservice/tst_qdebugmessageservice.cpp
@@ -48,6 +48,7 @@
#include <QtCore/QString>
#include <QtTest/QtTest>
+const char *ENABLE_DEBUG= "-enable-debugger";
const char *NORMALMODE = "-qmljsdebugger=port:3777,3787,block";
const char *QMLFILE = "test.qml";
@@ -179,10 +180,10 @@ void tst_QDebugMessageService::cleanupTestCase()
void tst_QDebugMessageService::init()
{
m_connection = new QQmlDebugConnection();
- m_process = new QQmlDebugProcess(QLibraryInfo::location(QLibraryInfo::BinariesPath) + "/qmlscene", this);
+ m_process = new QQmlDebugProcess(QLibraryInfo::location(QLibraryInfo::BinariesPath) + "/qml", this);
m_client = new QQmlDebugMsgClient(m_connection);
- m_process->start(QStringList() << QLatin1String(NORMALMODE) << QQmlDataTest::instance()->testFile(QMLFILE));
+ m_process->start(QStringList() << QLatin1String(ENABLE_DEBUG) << QLatin1String(NORMALMODE) << QQmlDataTest::instance()->testFile(QMLFILE));
QVERIFY2(m_process->waitForSessionStart(),
"Could not launch application, or did not get 'Waiting for connection'.");
diff --git a/tests/auto/qml/debugger/qqmldebugjs/tst_qqmldebugjs.cpp b/tests/auto/qml/debugger/qqmldebugjs/tst_qqmldebugjs.cpp
index e5a7af630a..9bf2d8849e 100644
--- a/tests/auto/qml/debugger/qqmldebugjs/tst_qqmldebugjs.cpp
+++ b/tests/auto/qml/debugger/qqmldebugjs/tst_qqmldebugjs.cpp
@@ -129,6 +129,7 @@ const char *UNCAUGHT = "uncaught";
//const char *PAUSE = "pause";
//const char *RESUME = "resume";
+const char *ENABLE_DEBUG= "-enable-debugger";//flag needed for debugger with qml binary
const char *BLOCKMODE = "-qmljsdebugger=port:3771,3800,block";
const char *NORMALMODE = "-qmljsdebugger=port:3771,3800";
const char *TEST_QMLFILE = "test.qml";
@@ -1009,13 +1010,13 @@ void tst_QQmlDebugJS::cleanupTestCase()
bool tst_QQmlDebugJS::init(const QString &qmlFile, bool blockMode)
{
connection = new QQmlDebugConnection();
- process = new QQmlDebugProcess(QLibraryInfo::location(QLibraryInfo::BinariesPath) + "/qmlscene", this);
+ process = new QQmlDebugProcess(QLibraryInfo::location(QLibraryInfo::BinariesPath) + "/qml", this);
client = new QJSDebugClient(connection);
if (blockMode)
- process->start(QStringList() << QLatin1String(BLOCKMODE) << testFile(qmlFile));
+ process->start(QStringList() << QLatin1String(ENABLE_DEBUG) << QLatin1String(BLOCKMODE) << testFile(qmlFile));
else
- process->start(QStringList() << QLatin1String(NORMALMODE) << testFile(qmlFile));
+ process->start(QStringList() << QLatin1String(ENABLE_DEBUG) << QLatin1String(NORMALMODE) << testFile(qmlFile));
if (!process->waitForSessionStart()) {
qDebug() << "could not launch application, or did not get 'Waiting for connection'.";
diff --git a/tests/auto/qml/debugger/qqmlenginedebuginspectorintegrationtest/tst_qqmlenginedebuginspectorintegration.cpp b/tests/auto/qml/debugger/qqmlenginedebuginspectorintegrationtest/tst_qqmlenginedebuginspectorintegration.cpp
index 5badcaa3ae..231e37c6fb 100644
--- a/tests/auto/qml/debugger/qqmlenginedebuginspectorintegrationtest/tst_qqmlenginedebuginspectorintegration.cpp
+++ b/tests/auto/qml/debugger/qqmlenginedebuginspectorintegrationtest/tst_qqmlenginedebuginspectorintegration.cpp
@@ -107,9 +107,10 @@ void tst_QQmlEngineDebugInspectorIntegration::init()
{
const QString argument = "-qmljsdebugger=port:" STR_PORT_FROM "," STR_PORT_TO ",block";
+ // ### Still using qmlscene because of QTBUG-33376
m_process = new QQmlDebugProcess(QLibraryInfo::location(QLibraryInfo::BinariesPath)
+ "/qmlscene", this);
- m_process->start(QStringList() << argument << testFile("qtquick2.qml"));
+ m_process->start(QStringList() << QLatin1String("-enable-debugger") << argument << testFile("qtquick2.qml"));
QVERIFY2(m_process->waitForSessionStart(),
"Could not launch application, or did not get 'Waiting for connection'.");
diff --git a/tests/auto/qml/debugger/qqmlinspector/tst_qqmlinspector.cpp b/tests/auto/qml/debugger/qqmlinspector/tst_qqmlinspector.cpp
index e430875355..2eeb4ce5b2 100644
--- a/tests/auto/qml/debugger/qqmlinspector/tst_qqmlinspector.cpp
+++ b/tests/auto/qml/debugger/qqmlinspector/tst_qqmlinspector.cpp
@@ -89,8 +89,9 @@ void tst_QQmlInspector::startQmlsceneProcess(const char * /* qmlFile */)
{
const QString argument = "-qmljsdebugger=port:" STR_PORT_FROM "," STR_PORT_TO ",block";
+ // ### This should be using qml instead of qmlscene, but can't because of QTBUG-33376 (same as the XFAIL testcase)
m_process = new QQmlDebugProcess(QLibraryInfo::location(QLibraryInfo::BinariesPath) + "/qmlscene", this);
- m_process->start(QStringList() << argument << testFile("qtquick2.qml"));
+ m_process->start(QStringList() << QLatin1String("-enable-debugger") << argument << testFile("qtquick2.qml"));
QVERIFY2(m_process->waitForSessionStart(),
"Could not launch application, or did not get 'Waiting for connection'.");
@@ -179,7 +180,7 @@ void tst_QQmlInspector::reloadQmlWindow()
m_client->reloadQml(changesHash);
QVERIFY(QQmlDebugTest::waitForSignal(m_client, SIGNAL(responseReceived())));
- QEXPECT_FAIL("", "cannot debug with a QML file containing a top-level Window", Abort);
+ QEXPECT_FAIL("", "cannot debug with a QML file containing a top-level Window", Abort); // QTBUG-33376
QTRY_COMPARE(m_process->output().contains(
QString("version 2.0")), true);
diff --git a/tests/auto/qml/debugger/qqmlprofilerservice/tst_qqmlprofilerservice.cpp b/tests/auto/qml/debugger/qqmlprofilerservice/tst_qqmlprofilerservice.cpp
index e4f886f7ce..4cf8fa64c5 100644
--- a/tests/auto/qml/debugger/qqmlprofilerservice/tst_qqmlprofilerservice.cpp
+++ b/tests/auto/qml/debugger/qqmlprofilerservice/tst_qqmlprofilerservice.cpp
@@ -198,8 +198,6 @@ void QQmlProfilerClient::messageReceived(const QByteArray &message)
stream >> data.time >> data.messageType;
- QVERIFY(data.time >= -1);
-
switch (data.messageType) {
case (QQmlProfilerClient::Event): {
stream >> data.detailType;
@@ -299,8 +297,10 @@ void QQmlProfilerClient::messageReceived(const QByteArray &message)
void tst_QQmlProfilerService::connect(bool block, const QString &testFile)
{
+ // ### Still using qmlscene due to QTBUG-33377
const QString executable = QLibraryInfo::location(QLibraryInfo::BinariesPath) + "/qmlscene";
QStringList arguments;
+ arguments << QLatin1String("-enable-debugger");
if (block)
arguments << QString("-qmljsdebugger=port:" STR_PORT_FROM "," STR_PORT_TO ",block");
diff --git a/tests/auto/qml/debugger/qv8profilerservice/tst_qv8profilerservice.cpp b/tests/auto/qml/debugger/qv8profilerservice/tst_qv8profilerservice.cpp
index f33ee55c46..f0485fb68a 100644
--- a/tests/auto/qml/debugger/qv8profilerservice/tst_qv8profilerservice.cpp
+++ b/tests/auto/qml/debugger/qv8profilerservice/tst_qv8profilerservice.cpp
@@ -204,8 +204,9 @@ void QV8ProfilerClient::messageReceived(const QByteArray &message)
bool tst_QV8ProfilerService::connect(bool block, const QString &testFile,
QString *error)
{
- const QString executable = QLibraryInfo::location(QLibraryInfo::BinariesPath) + "/qmlscene";
+ const QString executable = QLibraryInfo::location(QLibraryInfo::BinariesPath) + "/qml";
QStringList arguments;
+ arguments << QLatin1String("-enable-debugger");
if (block)
arguments << QString("-qmljsdebugger=port:" STR_PORT_FROM "," STR_PORT_TO ",block");
diff --git a/tests/auto/qml/debugger/shared/debugutil.cpp b/tests/auto/qml/debugger/shared/debugutil.cpp
index ab27337d41..99647cda11 100644
--- a/tests/auto/qml/debugger/shared/debugutil.cpp
+++ b/tests/auto/qml/debugger/shared/debugutil.cpp
@@ -43,6 +43,8 @@
#include <QEventLoop>
#include <QTimer>
+#include <QFileInfo>
+#include <QDir>
bool QQmlDebugTest::waitForSignal(QObject *receiver, const char *member, int timeout) {
QEventLoop loop;
@@ -123,6 +125,17 @@ QString QQmlDebugProcess::state()
void QQmlDebugProcess::start(const QStringList &arguments)
{
+#ifdef Q_OS_MAC
+ // make sure m_executable points to the actual binary even if it's inside an app bundle
+ QFileInfo binFile(m_executable);
+ if (!binFile.isExecutable()) {
+ QDir bundleDir(m_executable + ".app");
+ if (bundleDir.exists()) {
+ m_executable = bundleDir.absoluteFilePath("Contents/MacOS/" + binFile.baseName());
+ //qDebug() << Q_FUNC_INFO << "found bundled binary" << m_executable;
+ }
+ }
+#endif
m_mutex.lock();
m_port = 0;
m_process.setEnvironment(m_environment);
@@ -220,6 +233,9 @@ void QQmlDebugProcess::processAppOutput()
m_eventLoop.quit();
continue;
}
+ } else if (line.startsWith("qml:")) {
+ // ### Can't enable quiet mode because that also suppresses application output
+ continue; //We don't use these, but they aren't output from the app either
} else {
// set to true if there is output not coming from the debugger
outputFromAppItself = true;
diff --git a/tools/qml/Info.plist b/tools/qml/Info.plist
new file mode 100644
index 0000000000..42d074a3af
--- /dev/null
+++ b/tools/qml/Info.plist
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist SYSTEM "file://localhost/System/Library/DTDs/PropertyList.dtd">
+<plist version="0.1">
+<dict>
+ <key>CFBundleIconFile</key>
+ <string>@ICON@</string>
+ <key>CFBundleIdentifier</key>
+ <string>org.qt-project.qml</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleGetInfoString</key>
+ <string>Created by Qt/QMake</string>
+ <key>CFBundleSignature</key>
+ <string>@TYPEINFO@</string>
+ <key>CFBundleExecutable</key>
+ <string>@EXECUTABLE@</string>
+ <key>UTExportedTypeDeclarations</key>
+ <array>
+ <dict>
+ <key>UTTypeIdentifier</key>
+ <string>org.qt-project.qml</string>
+ <key>UTTypeDescription</key>
+ <string>Qt Markup Language</string>
+ <key>UTTypeConformsTo</key>
+ <array>
+ <string>public.plain-text</string>
+ </array>
+ <key>UTTypeTagSpecification</key>
+ <dict>
+ <key>public.filename-extension</key>
+ <array>
+ <string>qml</string>
+ </array>
+ </dict>
+ </dict>
+ </array>
+ <key>CFBundleDocumentTypes</key>
+ <array>
+ <dict>
+ <key>LSItemContentTypes</key>
+ <array>
+ <string>org.qt-project.qml</string>
+ </array>
+ <key>CFBundleTypeRole</key>
+ <string>Viewer</string>
+ </dict>
+ </array>
+</dict>
+</plist>
diff --git a/tools/qml/conf.h b/tools/qml/conf.h
new file mode 100644
index 0000000000..24ea44edb9
--- /dev/null
+++ b/tools/qml/conf.h
@@ -0,0 +1,99 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Research In Motion.
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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.
+**
+** 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.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#ifndef CONF_H
+#define CONF_H
+
+#include <QtQml>
+#include <QObject>
+#include <QUrl>
+
+class PartialScene : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QUrl container READ container WRITE setContainer NOTIFY containerChanged)
+ Q_PROPERTY(QString itemType READ itemType WRITE setItemType NOTIFY itemTypeChanged)
+public:
+ PartialScene(QObject *parent = 0) : QObject(parent)
+ {}
+
+ const QUrl container() const { return m_container; }
+ const QString itemType() const { return m_itemType; }
+
+ void setContainer(const QUrl &a) {
+ if (a==m_container)
+ return;
+ m_container = a;
+ emit containerChanged();
+ }
+ void setItemType(const QString &a) {
+ if (a==m_itemType)
+ return;
+ m_itemType = a;
+ emit itemTypeChanged();
+ }
+
+signals:
+ void containerChanged();
+ void itemTypeChanged();
+
+private:
+ QUrl m_container;
+ QString m_itemType;
+};
+
+class Config : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QQmlListProperty<PartialScene> sceneCompleters READ sceneCompleters)
+ Q_CLASSINFO("DefaultProperty", "sceneCompleters")
+public:
+ Config (QObject* parent=0) : QObject(parent)
+ {}
+
+ QQmlListProperty<PartialScene> sceneCompleters()
+ {
+ return QQmlListProperty<PartialScene>(this, completers);
+ }
+
+ QList<PartialScene*> completers;
+};
+
+#endif
diff --git a/tools/qml/conf/configuration.qml b/tools/qml/conf/configuration.qml
new file mode 100644
index 0000000000..4a9494e8b5
--- /dev/null
+++ b/tools/qml/conf/configuration.qml
@@ -0,0 +1,47 @@
+/*****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the QtQuick module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
+** of its contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+*****************************************************************************/
+import QmlRuntime.Config 1.0
+
+Configuration {
+ PartialScene {
+ itemType: "QQuickItem"
+ container: "qtquick.qml"
+ }
+}
diff --git a/tools/qml/conf/qtquick.qml b/tools/qml/conf/qtquick.qml
new file mode 100644
index 0000000000..3da5c09d41
--- /dev/null
+++ b/tools/qml/conf/qtquick.qml
@@ -0,0 +1,55 @@
+/*****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the QtQuick module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
+** of its contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+*****************************************************************************/
+import QtQuick.Window 2.0
+import QtQuick 2.0
+
+Window {
+ property Item containedObject: null
+ onContainedObjectChanged: {
+ if (containedObject == undefined || containedObject == null) {
+ visible = false;
+ return;
+ }
+ width = containedObject.width;
+ height = containedObject.height;
+ containedObject.parent = contentItem;
+ visible = true;
+ }
+}
diff --git a/tools/qml/main.cpp b/tools/qml/main.cpp
new file mode 100644
index 0000000000..c059373143
--- /dev/null
+++ b/tools/qml/main.cpp
@@ -0,0 +1,466 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Research In Motion.
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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.
+**
+** 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.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "conf.h"
+
+#include <QCoreApplication>
+#include <QGuiApplication>
+#ifdef QT_WIDGETS_LIB
+#include <QApplication>
+#endif
+#include <QWindow>
+#include <QQmlApplicationEngine>
+#include <QFileOpenEvent>
+#include <QFile>
+#include <QFileInfo>
+#include <QRegularExpression>
+#include <QStringList>
+#include <QDebug>
+#include <QStandardPaths>
+#include <QtGlobal>
+#include <qqml.h>
+#include <qqmldebug.h>
+#include <private/qabstractanimation_p.h>
+
+#include <cstdio>
+#include <cstring>
+#include <cstdlib>
+
+#define VERSION_MAJ 1
+#define VERSION_MIN 0
+#define VERSION_STR "1.0"
+
+static Config *conf = 0;
+static QQmlApplicationEngine *qae = 0;
+
+static void loadConf(const QString &override, bool quiet) // Terminates app on failure
+{
+ const QString defaultFileName = QLatin1String("configuration.qml");
+ QUrl settingsUrl;
+ bool builtIn = false; //just for keeping track of the warning
+ if (override.isEmpty()) {
+ QFileInfo fi;
+ fi.setFile(QStandardPaths::locate(QStandardPaths::DataLocation, defaultFileName));
+ if (fi.exists()) {
+ settingsUrl = QUrl::fromLocalFile(fi.absoluteFilePath());
+ } else {
+ // ### If different built-in configs are needed per-platform, just apply QFileSelector to the qrc conf.qml path
+ settingsUrl = QUrl(QLatin1String("qrc:///qt-project.org/QmlRuntime/conf/") + defaultFileName);
+ builtIn = true;
+ }
+ } else {
+ QFileInfo fi;
+ fi.setFile(override);
+ if (!fi.exists()) {
+ qCritical() << QObject::tr("qml: Couldn't find required configuration file:") << fi.absoluteFilePath();
+ exit(1);
+ }
+ settingsUrl = QUrl::fromLocalFile(fi.absoluteFilePath());
+ }
+
+ if (!quiet) {
+ if (builtIn)
+ qWarning() << QObject::tr("qml: Using built-in configuration.");
+ else
+ qWarning() << QObject::tr("qml: Using configuration file:") << settingsUrl;
+ }
+
+ // TODO: When we have better engine control, ban QtQuick* imports on this engine
+ QQmlEngine e2;
+ QQmlComponent c2(&e2, settingsUrl);
+ conf = qobject_cast<Config*>(c2.create());
+
+ if (!conf){
+ qCritical() << QObject::tr("qml: Error loading configuration file:") << c2.errorString();
+ exit(1);
+ }
+}
+
+void contain(QObject *o, const QUrl &containPath)
+{
+ QQmlComponent c(qae, containPath);
+ QObject *o2 = c.create();
+ if (!o2)
+ return;
+ bool success = false;
+ int idx;
+ if ((idx = o2->metaObject()->indexOfProperty("containedObject")) != -1)
+ success = o2->metaObject()->property(idx).write(o2, QVariant::fromValue<QObject*>(o));
+ if (!success)
+ o->setParent(o2); //Set QObject parent, and assume container will react as needed
+}
+
+// Loads qml after receiving a QFileOpenEvent
+class LoaderApplication : public QGuiApplication
+{
+public:
+ LoaderApplication(int& argc, char **argv) : QGuiApplication(argc, argv) {}
+
+ bool event(QEvent *ev)
+ {
+ if (ev->type() == QEvent::FileOpen)
+ qae->load(static_cast<QFileOpenEvent *>(ev)->url());
+ else
+ return QGuiApplication::event(ev);
+ return true;
+ }
+};
+
+// Listens to the appEngine signals to determine if all files failed to load
+class LoadWatcher : public QObject
+{
+ Q_OBJECT
+public:
+ LoadWatcher(QQmlApplicationEngine *e, int expected)
+ : QObject(e)
+ , expect(expected)
+ , haveOne(false)
+ {
+ connect(e, SIGNAL(objectCreated(QObject*,QUrl)),
+ this, SLOT(checkFinished(QObject*)));
+ }
+
+private:
+ int expect;
+ bool haveOne;
+
+public Q_SLOTS:
+ void checkFinished(QObject *o)
+ {
+ if (o) {
+ haveOne = true;
+ if (conf && qae)
+ foreach (PartialScene *ps, conf->completers)
+ if (o->inherits(ps->itemType().toUtf8().constData()))
+ contain(o, ps->container());
+ }
+ if (haveOne)
+ return;
+
+ if (! --expect) {
+ qCritical() << QObject::tr("qml: Did not load any objects, exiting.");
+ exit(2);//Different return code from qFatal
+ }
+ }
+};
+
+void quietMessageHandler(QtMsgType type, const QMessageLogContext &ctxt, const QString &msg)
+{
+ Q_UNUSED(ctxt);
+ Q_UNUSED(msg);
+ //Doesn't print anything
+ switch (type) {
+ case QtFatalMsg:
+ abort();
+ case QtCriticalMsg:
+ case QtDebugMsg:
+ case QtWarningMsg:
+ ;
+ }
+}
+
+
+// ### Should command line arguments have translations? Qt creator doesn't, so maybe it's not worth it.
+bool useCoreApp = false;
+bool useWidgetApp = false;
+bool quietMode = false;
+void printVersion()
+{
+ printf("qml binary version ");
+ printf(VERSION_STR);
+ printf("\nbuilt with Qt version ");
+ printf(QT_VERSION_STR);
+ printf("\n");
+ exit(0);
+}
+
+void printUsage()
+{
+ printf("Usage: qml [options] [files]\n");
+ printf("\n");
+ printf("Any argument ending in .qml will be treated as a QML file to be loaded.\n");
+ printf("Any number of QML files can be loaded. They will share the same engine.\n");
+ printf("Any argument which is not a recognized option and which does not end in .qml will be ignored.\n");
+ printf("'widget' application type is only available if the QtWidgets module is avaialble.\n");
+ printf("\n");
+ printf("General Options:\n");
+ printf("\t-h, -help..................... Print this usage information and exit.\n");
+ printf("\t-v, -version.................. Print the version information and exit.\n");
+ printf("\t-apptype [core|gui|widget] ... Select which application class to use. Default is gui.\n");
+ printf("\t-quiet ....................... Suppress all output.\n");
+ printf("\t-I [path] .................... Prepend the given path to the import paths.\n");
+ printf("\t-f [file] .................... Load the given file as a QML file.\n");
+ printf("\t-config [file] ............... Load the given file as the configuration file.\n");
+ printf("\t-- ........................... Arguments after this one are ignored by the launcher, but may be used within the QML application.\n");
+ printf("\tDebugging options:\n");
+ printf("\t-enable-debugger ............. Allow the QML debugger to connect to the application (also requires debugger arguments).\n");
+ printf("\t-translation [file] .......... Load the given file as the translations file.\n");
+ printf("\t-dummy-data [directory] ...... Load QML files from the given directory as context properties.\n");
+ printf("\t-slow-animations ............. Run all animations in slow motion.\n");
+ printf("\t-fixed-animations ............ Run animations off animation tick rather than wall time.\n");
+ exit(0);
+}
+
+//Called before application initialization, removes arguments it uses
+void getAppFlags(int &argc, char **argv)
+{
+ for (int i=0; i<argc; i++) {
+ if (!strcmp(argv[i], "-apptype")) { // Must be done before application, as it selects application
+ int type = 0;
+ if (i+1 < argc) {
+ if (!strcmp(argv[i+1], "core"))
+ type = 1;
+ else if (!strcmp(argv[i+1], "gui"))
+ type = 2;
+#ifdef QT_WIDGETS_LIB
+ else if (!strcmp(argv[i+1], "widget"))
+ type = 3;
+#endif
+ }
+
+ if (!type) {
+#ifdef QT_WIDGETS_LIB
+ printf("-apptype must be followed by one of the following: core gui widget\n");
+#else
+ printf("-apptype must be followed by one of the following: core gui\n");
+#endif
+ printUsage();
+ }
+
+ switch (type) {
+ case 1: useCoreApp = true; break;
+ case 2: useCoreApp = false; break;
+#ifdef QT_WIDGETS_LIB
+ case 3: useWidgetApp = true; break;
+#endif
+ }
+ for (int j=i; j<argc-2; j++)
+ argv[j] = argv[j+2];
+ argc -= 2;
+ } else if (!strcmp(argv[i], "-enable-debugger")) { // Normally done via a define in the include, so expects to be before application (and must be before engine)
+ static QQmlDebuggingEnabler qmlEnableDebuggingHelper(true);
+ for (int j=i; j<argc-1; j++)
+ argv[j] = argv[j+1];
+ argc --;
+ }
+ }
+}
+
+void getFileSansBangLine(const QString &path, QByteArray &output)
+{
+ QFile f(path);
+ if (!f.open(QFile::ReadOnly | QFile::Text))
+ return;
+ output = f.readAll();
+ if (output.startsWith("#!"))//Remove first line in this case (except \n, to avoid disturbing line count)
+ output.remove(0, output.indexOf('\n'));
+}
+
+static void loadDummyDataFiles(QQmlEngine &engine, const QString& directory)
+{
+ QDir dir(directory+"/dummydata", "*.qml");
+ QStringList list = dir.entryList();
+ for (int i = 0; i < list.size(); ++i) {
+ QString qml = list.at(i);
+ QFile f(dir.filePath(qml));
+ f.open(QIODevice::ReadOnly);
+ QByteArray data = f.readAll();
+ QQmlComponent comp(&engine);
+ comp.setData(data, QUrl());
+ QObject *dummyData = comp.create();
+
+ if (comp.isError()) {
+ QList<QQmlError> errors = comp.errors();
+ foreach (const QQmlError &error, errors)
+ qWarning() << error;
+ }
+
+ if (dummyData && !quietMode) {
+ qWarning() << QObject::tr("qml: Loaded dummy data:") << dir.filePath(qml);
+ qml.truncate(qml.length()-4);
+ engine.rootContext()->setContextProperty(qml, dummyData);
+ dummyData->setParent(&engine);
+ }
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ getAppFlags(argc, argv);
+ QCoreApplication *app;
+ if (useCoreApp)
+ app = new QCoreApplication(argc, argv);
+#ifdef QT_WIDGETS_LIB
+ else if (useWidgetApp)
+ app = new QApplication(argc, argv);
+#endif
+ else
+ app = new LoaderApplication(argc, argv);
+
+ app->setApplicationName("Qml Runtime");
+ app->setOrganizationName("Qt Project");
+ app->setOrganizationDomain("qt-project.org");
+
+ qmlRegisterType<Config>("QmlRuntime.Config", VERSION_MAJ, VERSION_MIN, "Configuration");
+ qmlRegisterType<PartialScene>("QmlRuntime.Config", VERSION_MAJ, VERSION_MIN, "PartialScene");
+ QQmlApplicationEngine e;
+ QStringList files;
+ QString confFile;
+ QString translationFile;
+ QString dummyDir;
+
+ //Handle main arguments
+ QStringList argList = app->arguments();
+ for (int i = 0; i < argList.count(); i++) {
+ const QString &arg = argList[i];
+ if (arg == QLatin1String("-quiet"))
+ quietMode = true;
+ else if (arg == QLatin1String("-v") || arg == QLatin1String("-version"))
+ printVersion();
+ else if (arg == QLatin1String("-h") || arg == QLatin1String("-help"))
+ printUsage();
+ else if (arg == QLatin1String("--"))
+ break;
+ else if (arg == QLatin1String("-slow-animations"))
+ QUnifiedTimer::instance()->setSlowModeEnabled(true);
+ else if (arg == QLatin1String("-fixed-animations"))
+ QUnifiedTimer::instance()->setConsistentTiming(true);
+ else if (arg == QLatin1String("-I")) {
+ if (i+1 == argList.count())
+ continue;//Invalid usage, but just ignore it
+ e.addImportPath(argList[i+1]);
+ i++;
+ } else if (arg == QLatin1String("-f")) {
+ if (i+1 == argList.count())
+ continue;//Invalid usage, but just ignore it
+ files << argList[i+1];
+ i++;
+ } else if (arg == QLatin1String("-config")){
+ if (i+1 == argList.count())
+ continue;//Invalid usage, but just ignore it
+ confFile = argList[i+1];
+ i++;
+ } else if (arg == QLatin1String("-translation")){
+ if (i+1 == argList.count())
+ continue;//Invalid usage, but just ignore it
+ translationFile = argList[i+1];
+ i++;
+ } else if (arg == QLatin1String("-dummy-data")){
+ if (i+1 == argList.count())
+ continue;//Invalid usage, but just ignore it
+ dummyDir = argList[i+1];
+ i++;
+ } else {
+ //If it ends in .qml, treat it as a file. Else ignore it
+ if (arg.endsWith(".qml"))
+ files << arg;
+ }
+ }
+
+#ifndef QT_NO_TRANSLATION
+ //qt_ translations loaded by QQmlApplicationEngine
+ QTranslator qmlTranslator;
+ QString sysLocale = QLocale::system().name();
+ if (qmlTranslator.load(QLatin1String("qml_") + sysLocale, QLibraryInfo::location(QLibraryInfo::TranslationsPath)))
+ app->installTranslator(&qmlTranslator);
+
+ if (!translationFile.isEmpty()) { //Note: installed before QQmlApplicationEngine's automatic translation loading
+ QTranslator translator;
+
+ if (translator.load(translationFile)) {
+ app->installTranslator(&translator);
+ } else {
+ if (!quietMode)
+ qWarning() << "qml: Could not load the translation file" << translationFile;
+ }
+ }
+#else
+ if (!translationFile.isEmpty() && !quietMode)
+ qWarning() << "qml: Translation file specified, but Qt built without translation support.";
+#endif
+
+ if (quietMode)
+ qInstallMessageHandler(quietMessageHandler);
+
+ if (files.count() <= 0) {
+ if (!quietMode)
+ qCritical() << QObject::tr("qml: No files specified. Terminating.");
+ exit(1);
+ }
+
+ qae = &e;
+ loadConf(confFile, quietMode);
+
+ //Load files
+ LoadWatcher lw(&e, files.count());
+
+ foreach (const QString &path, files) {
+ //QUrl::fromUserInput doesn't treat no scheme as relative file paths
+ QRegularExpression urlRe("[[:word:]]+://.*");
+ if (urlRe.match(path).hasMatch()) { //Treat as a URL
+ QUrl url = QUrl::fromUserInput(path);
+ if (!quietMode)
+ qDebug() << QObject::tr("qml: loading ") << url;
+ e.load(url);
+ } else { //Local file path
+ if (!quietMode) {
+ qDebug() << QObject::tr("qml: loading ") << path;
+ QByteArray strippedFile;
+ getFileSansBangLine(path, strippedFile);
+ if (strippedFile.isEmpty())
+ // If there's an error opening the file, this will give us the right error message
+ e.load(path);
+ else
+ e.loadData(strippedFile, QUrl::fromLocalFile(path));
+ } else {
+ e.load(path);
+ }
+ }
+ }
+
+
+ if (!dummyDir.isEmpty() && QFileInfo (dummyDir).isDir())
+ loadDummyDataFiles(e, dummyDir);
+
+ return app->exec();
+}
+
+#include "main.moc"
diff --git a/tools/qml/qml.icns b/tools/qml/qml.icns
new file mode 100644
index 0000000000..c76051626a
--- /dev/null
+++ b/tools/qml/qml.icns
Binary files differ
diff --git a/tools/qml/qml.pro b/tools/qml/qml.pro
new file mode 100644
index 0000000000..fd4021c340
--- /dev/null
+++ b/tools/qml/qml.pro
@@ -0,0 +1,14 @@
+QT += qml gui core-private
+qtHaveModule(widgets): QT += widgets
+
+HEADERS += conf.h
+SOURCES += main.cpp
+RESOURCES += qml.qrc
+
+mac {
+ OTHER_FILES += Info.plist
+ QMAKE_INFO_PLIST = Info.plist
+ ICON = qml.icns
+}
+
+load(qt_tool)
diff --git a/tools/qml/qml.qrc b/tools/qml/qml.qrc
new file mode 100644
index 0000000000..1f0ffdace2
--- /dev/null
+++ b/tools/qml/qml.qrc
@@ -0,0 +1,6 @@
+<RCC>
+ <qresource prefix="qt-project.org/QmlRuntime">
+ <file>conf/configuration.qml</file>
+ <file>conf/qtquick.qml</file>
+ </qresource>
+</RCC>
diff --git a/tools/tools.pro b/tools/tools.pro
index 43b6c14022..ecca12d266 100644
--- a/tools/tools.pro
+++ b/tools/tools.pro
@@ -2,6 +2,7 @@ TEMPLATE = subdirs
qtHaveModule(quick): SUBDIRS += qmlscene qmlplugindump
qtHaveModule(qmltest): SUBDIRS += qmltestrunner
SUBDIRS += \
+ qml \
qmlmin \
qmlprofiler \
qmlbundle \