diff options
Diffstat (limited to 'tools/qml/main.cpp')
-rw-r--r-- | tools/qml/main.cpp | 466 |
1 files changed, 466 insertions, 0 deletions
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" |