@@ -0,0 +1,543 @@
1+/****************************************************************************
2+**
3+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
4+** Contact: http://www.qt-project.org/legal
5+**
6+** This file is part of the tools applications of the Qt Toolkit.
7+**
8+** $QT_BEGIN_LICENSE:LGPL$
9+** Commercial License Usage
10+** Licensees holding valid commercial Qt licenses may use this file in
11+** accordance with the commercial license agreement provided with the
12+** Software or, alternatively, in accordance with the terms contained in
13+** a written agreement between you and Digia. For licensing terms and
14+** conditions see http://qt.digia.com/licensing. For further information
15+** use the contact form at http://qt.digia.com/contact-us.
16+**
17+** GNU Lesser General Public License Usage
18+** Alternatively, this file may be used under the terms of the GNU Lesser
19+** General Public License version 2.1 as published by the Free Software
20+** Foundation and appearing in the file LICENSE.LGPL included in the
21+** packaging of this file. Please review the following information to
22+** ensure the GNU Lesser General Public License version 2.1 requirements
23+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24+**
25+** In addition, as a special exception, Digia gives you certain additional
26+** rights. These rights are described in the Digia Qt LGPL Exception
27+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
28+**
29+** GNU General Public License Usage
30+** Alternatively, this file may be used under the terms of the GNU
31+** General Public License version 3.0 as published by the Free Software
32+** Foundation and appearing in the file LICENSE.GPL included in the
33+** packaging of this file. Please review the following information to
34+** ensure the GNU General Public License version 3.0 requirements will be
35+** met: http://www.gnu.org/copyleft/gpl.html.
36+**
37+**
38+** $QT_END_LICENSE$
39+**
40+****************************************************************************/
41+
42+#include <QtCore/qdebug.h>
43+#include <QtCore/qabstractanimation.h>
44+#include <QtCore/qdir.h>
45+#include <QtCore/qmath.h>
46+#include <QtCore/qdatetime.h>
47+
48+#include <QtGui/QGuiApplication>
49+
50+#include <QtQml/qqml.h>
51+#include <QtQml/qqmlengine.h>
52+#include <QtQml/qqmlcomponent.h>
53+#include <QtQml/qqmlcontext.h>
54+
55+#include <QtQuick/qquickitem.h>
56+#include <QtQuick/qquickview.h>
57+
58+#ifdef QT_WIDGETS_LIB
59+#include <QtWidgets/QApplication>
60+#include <QtWidgets/QFileDialog>
61+#endif
62+
63+#include <QtCore/QTranslator>
64+#include <QtCore/QLibraryInfo>
65+
66+#include "../../MouseTouchAdaptor.h"
67+
68+#ifdef QML_RUNTIME_TESTING
69+class RenderStatistics
70+{
71+public:
72+ static void updateStats();
73+ static void printTotalStats();
74+private:
75+ static QVector<qreal> timePerFrame;
76+ static QVector<int> timesPerFrames;
77+};
78+
79+QVector<qreal> RenderStatistics::timePerFrame;
80+QVector<int> RenderStatistics::timesPerFrames;
81+
82+void RenderStatistics::updateStats()
83+{
84+ static QTime time;
85+ static int frames;
86+ static int lastTime;
87+
88+ if (frames == 0) {
89+ time.start();
90+ } else {
91+ int elapsed = time.elapsed();
92+ timesPerFrames.append(elapsed - lastTime);
93+ lastTime = elapsed;
94+
95+ if (elapsed > 5000) {
96+ qreal avgtime = elapsed / (qreal) frames;
97+ qreal var = 0;
98+ for (int i = 0; i < timesPerFrames.size(); ++i) {
99+ qreal diff = timesPerFrames.at(i) - avgtime;
100+ var += diff * diff;
101+ }
102+ var /= timesPerFrames.size();
103+
104+ qDebug("Average time per frame: %f ms (%i fps), std.dev: %f ms", avgtime, qRound(1000. / avgtime), qSqrt(var));
105+
106+ timePerFrame.append(avgtime);
107+ timesPerFrames.clear();
108+ time.start();
109+ lastTime = 0;
110+ frames = 0;
111+ }
112+ }
113+ ++frames;
114+}
115+
116+void RenderStatistics::printTotalStats()
117+{
118+ int count = timePerFrame.count();
119+ if (count == 0)
120+ return;
121+
122+ qreal minTime = 0;
123+ qreal maxTime = 0;
124+ qreal avg = 0;
125+ for (int i = 0; i < count; ++i) {
126+ minTime = minTime == 0 ? timePerFrame.at(i) : qMin(minTime, timePerFrame.at(i));
127+ maxTime = qMax(maxTime, timePerFrame.at(i));
128+ avg += timePerFrame.at(i);
129+ }
130+ avg /= count;
131+
132+ qDebug(" ");
133+ qDebug("----- Statistics -----");
134+ qDebug("Average time per frame: %f ms (%i fps)", avg, qRound(1000. / avg));
135+ qDebug("Best time per frame: %f ms (%i fps)", minTime, int(1000 / minTime));
136+ qDebug("Worst time per frame: %f ms (%i fps)", maxTime, int(1000 / maxTime));
137+ qDebug("----------------------");
138+ qDebug(" ");
139+}
140+#endif
141+
142+struct Options
143+{
144+ Options()
145+ : originalQml(false)
146+ , originalQmlRaster(false)
147+ , maximized(false)
148+ , fullscreen(false)
149+ , transparent(false)
150+ , clip(false)
151+ , versionDetection(true)
152+ , quitImmediately(false)
153+ , resizeViewToRootItem(false)
154+ , multisample(false)
155+ {
156+ }
157+
158+ QUrl file;
159+ bool originalQml;
160+ bool originalQmlRaster;
161+ bool maximized;
162+ bool fullscreen;
163+ bool transparent;
164+ bool scenegraphOnGraphicsview;
165+ bool clip;
166+ bool versionDetection;
167+ bool quitImmediately;
168+ bool resizeViewToRootItem;
169+ bool multisample;
170+ QString translationFile;
171+};
172+
173+#if defined(QMLSCENE_BUNDLE)
174+QFileInfoList findQmlFiles(const QString &dirName)
175+{
176+ QDir dir(dirName);
177+
178+ QFileInfoList ret;
179+ if (dir.exists()) {
180+ QFileInfoList fileInfos = dir.entryInfoList(QStringList() << "*.qml",
181+ QDir::Files | QDir::AllDirs | QDir::NoDotAndDotDot);
182+
183+ Q_FOREACH (QFileInfo fileInfo, fileInfos) {
184+ if (fileInfo.isDir())
185+ ret += findQmlFiles(fileInfo.filePath());
186+ else if (fileInfo.fileName().length() > 0 && fileInfo.fileName().at(0).isLower())
187+ ret.append(fileInfo);
188+ }
189+ }
190+
191+ return ret;
192+}
193+
194+static int displayOptionsDialog(Options *options)
195+{
196+ QDialog dialog;
197+
198+ QFormLayout *layout = new QFormLayout(&dialog);
199+
200+ QComboBox *qmlFileComboBox = new QComboBox(&dialog);
201+ QFileInfoList fileInfos = findQmlFiles(":/bundle") + findQmlFiles("./qmlscene-resources");
202+
203+ Q_FOREACH (QFileInfo fileInfo, fileInfos)
204+ qmlFileComboBox->addItem(fileInfo.dir().dirName() + "/" + fileInfo.fileName(), QVariant::fromValue(fileInfo));
205+
206+ QCheckBox *originalCheckBox = new QCheckBox(&dialog);
207+ originalCheckBox->setText("Use original QML viewer");
208+ originalCheckBox->setChecked(options->originalQml);
209+
210+ QCheckBox *fullscreenCheckBox = new QCheckBox(&dialog);
211+ fullscreenCheckBox->setText("Start fullscreen");
212+ fullscreenCheckBox->setChecked(options->fullscreen);
213+
214+ QCheckBox *maximizedCheckBox = new QCheckBox(&dialog);
215+ maximizedCheckBox->setText("Start maximized");
216+ maximizedCheckBox->setChecked(options->maximized);
217+
218+ QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel,
219+ Qt::Horizontal,
220+ &dialog);
221+ QObject::connect(buttonBox, SIGNAL(accepted()), &dialog, SLOT(accept()));
222+ QObject::connect(buttonBox, SIGNAL(rejected()), &dialog, SLOT(reject()));
223+
224+ layout->addRow("Qml file:", qmlFileComboBox);
225+ layout->addWidget(originalCheckBox);
226+ layout->addWidget(maximizedCheckBox);
227+ layout->addWidget(fullscreenCheckBox);
228+ layout->addWidget(buttonBox);
229+
230+ int result = dialog.exec();
231+ if (result == QDialog::Accepted) {
232+ QVariant variant = qmlFileComboBox->itemData(qmlFileComboBox->currentIndex());
233+ QFileInfo fileInfo = variant.value<QFileInfo>();
234+
235+ if (fileInfo.canonicalFilePath().startsWith(":"))
236+ options->file = QUrl("qrc" + fileInfo.canonicalFilePath());
237+ else
238+ options->file = QUrl::fromLocalFile(fileInfo.canonicalFilePath());
239+ options->originalQml = originalCheckBox->isChecked();
240+ options->maximized = maximizedCheckBox->isChecked();
241+ options->fullscreen = fullscreenCheckBox->isChecked();
242+ }
243+ return result;
244+}
245+#endif
246+
247+static bool checkVersion(const QUrl &url)
248+{
249+ if (!qgetenv("QMLSCENE_IMPORT_NAME").isEmpty())
250+ qWarning("QMLSCENE_IMPORT_NAME is no longer supported.");
251+
252+ QString fileName = url.toLocalFile();
253+ if (fileName.isEmpty()) {
254+ qWarning("qmlscene: filename required.");
255+ return false;
256+ }
257+
258+ QFile f(fileName);
259+ if (!f.open(QFile::ReadOnly | QFile::Text)) {
260+ qWarning("qmlscene: failed to check version of file '%s', could not open...",
261+ qPrintable(fileName));
262+ return false;
263+ }
264+
265+ QRegExp quick1("^\\s*import +QtQuick +1\\.\\w*");
266+ QRegExp qt47("^\\s*import +Qt +4\\.7");
267+
268+ QTextStream stream(&f);
269+ bool codeFound= false;
270+ while (!codeFound) {
271+ QString line = stream.readLine();
272+ if (line.contains("{")) {
273+ codeFound = true;
274+ } else {
275+ QString import;
276+ if (quick1.indexIn(line) >= 0)
277+ import = quick1.cap(0).trimmed();
278+ else if (qt47.indexIn(line) >= 0)
279+ import = qt47.cap(0).trimmed();
280+
281+ if (!import.isNull()) {
282+ qWarning("qmlscene: '%s' is no longer supported.\n"
283+ "Use qmlviewer to load file '%s'.",
284+ qPrintable(import),
285+ qPrintable(fileName));
286+ return false;
287+ }
288+ }
289+ }
290+
291+ return true;
292+}
293+
294+static void displayFileDialog(Options *options)
295+{
296+#if defined(QT_WIDGETS_LIB) && !defined(QT_NO_FILEDIALOG)
297+ QString fileName = QFileDialog::getOpenFileName(0, "Open QML file", QString(), "QML Files (*.qml)");
298+ if (!fileName.isEmpty()) {
299+ QFileInfo fi(fileName);
300+ options->file = QUrl::fromLocalFile(fi.canonicalFilePath());
301+ }
302+#else
303+ Q_UNUSED(options);
304+ qWarning("No filename specified...");
305+#endif
306+}
307+
308+#ifndef QT_NO_TRANSLATION
309+static void loadTranslationFile(QTranslator &translator, const QString& directory)
310+{
311+ translator.load(QLatin1String("qml_" )+QLocale::system().name(), directory + QLatin1String("/i18n"));
312+ QCoreApplication::installTranslator(&translator);
313+}
314+#endif
315+
316+static void loadDummyDataFiles(QQmlEngine &engine, const QString& directory)
317+{
318+ QDir dir(directory+"/dummydata", "*.qml");
319+ QStringList list = dir.entryList();
320+ for (int i = 0; i < list.size(); ++i) {
321+ QString qml = list.at(i);
322+ QFile f(dir.filePath(qml));
323+ f.open(QIODevice::ReadOnly);
324+ QByteArray data = f.readAll();
325+ QQmlComponent comp(&engine);
326+ comp.setData(data, QUrl());
327+ QObject *dummyData = comp.create();
328+
329+ if(comp.isError()) {
330+ QList<QQmlError> errors = comp.errors();
331+ Q_FOREACH (const QQmlError &error, errors)
332+ qWarning() << error;
333+ }
334+
335+ if (dummyData) {
336+ qWarning() << "Loaded dummy data:" << dir.filePath(qml);
337+ qml.truncate(qml.length()-4);
338+ engine.rootContext()->setContextProperty(qml, dummyData);
339+ dummyData->setParent(&engine);
340+ }
341+ }
342+}
343+
344+static void usage()
345+{
346+ qWarning("Usage: uqmlscene [options] <filename>");
347+ qWarning(" ");
348+ qWarning(" Options:");
349+ qWarning(" --maximized ............................... Run maximized");
350+ qWarning(" --fullscreen .............................. Run fullscreen");
351+ qWarning(" --transparent ............................. Make the window transparent");
352+ qWarning(" --multisample ............................. Enable multisampling (OpenGL anti-aliasing)");
353+ qWarning(" --no-version-detection .................... Do not try to detect the version of the .qml file");
354+ qWarning(" --resize-to-root .......................... Resize the window to the size of the root item");
355+ qWarning(" --quit .................................... Quit immediately after starting");
356+ qWarning(" -I <path> ................................. Add <path> to the list of import paths");
357+ qWarning(" -B <name> <file> .......................... Add a named bundle");
358+ qWarning(" -translation <translationfile> ............ Set the language to run in");
359+
360+ qWarning(" ");
361+ exit(1);
362+}
363+
364+int main(int argc, char ** argv)
365+{
366+ Options options;
367+
368+ QStringList imports;
369+ QList<QPair<QString, QString> > bundles;
370+ for (int i = 1; i < argc; ++i) {
371+ if (*argv[i] != '-' && QFileInfo(QFile::decodeName(argv[i])).exists()) {
372+ options.file = QUrl::fromLocalFile(argv[i]);
373+ } else {
374+ const QString lowerArgument = QString::fromLatin1(argv[i]).toLower();
375+ if (lowerArgument == QLatin1String("--maximized"))
376+ options.maximized = true;
377+ else if (lowerArgument == QLatin1String("--fullscreen"))
378+ options.fullscreen = true;
379+ else if (lowerArgument == QLatin1String("--transparent"))
380+ options.transparent = true;
381+ else if (lowerArgument == QLatin1String("--clip"))
382+ options.clip = true;
383+ else if (lowerArgument == QLatin1String("--no-version-detection"))
384+ options.versionDetection = false;
385+ else if (lowerArgument == QLatin1String("--quit"))
386+ options.quitImmediately = true;
387+ else if (lowerArgument == QLatin1String("-translation"))
388+ options.translationFile = QLatin1String(argv[++i]);
389+ else if (lowerArgument == QLatin1String("--resize-to-root"))
390+ options.resizeViewToRootItem = true;
391+ else if (lowerArgument == QLatin1String("--multisample"))
392+ options.multisample = true;
393+ else if (lowerArgument == QLatin1String("-i") && i + 1 < argc)
394+ imports.append(QString::fromLatin1(argv[++i]));
395+ else if (lowerArgument == QLatin1String("-b") && i + 2 < argc) {
396+ QString name = QString::fromLatin1(argv[++i]);
397+ QString file = QString::fromLatin1(argv[++i]);
398+ bundles.append(qMakePair(name, file));
399+ } else if (lowerArgument == QLatin1String("--help")
400+ || lowerArgument == QLatin1String("-help")
401+ || lowerArgument == QLatin1String("--h")
402+ || lowerArgument == QLatin1String("-h"))
403+ usage();
404+ }
405+ }
406+
407+#ifdef QT_WIDGETS_LIB
408+ QApplication app(argc, argv);
409+#else
410+ QGuiApplication app(argc, argv);
411+#endif
412+ app.setApplicationName("Unity8 QtQmlViewer");
413+ app.setOrganizationName("Qt Project");
414+ app.setOrganizationDomain("qt-project.org");
415+
416+ MouseTouchAdaptor mouseTouchAdaptor;
417+ app.installNativeEventFilter(&mouseTouchAdaptor);
418+
419+#ifndef QT_NO_TRANSLATION
420+ QTranslator translator;
421+ QTranslator qtTranslator;
422+ QString sysLocale = QLocale::system().name();
423+ if (translator.load(QLatin1String("qmlscene_") + sysLocale, QLibraryInfo::location(QLibraryInfo::TranslationsPath))) {
424+ app.installTranslator(&translator);
425+ if (qtTranslator.load(QLatin1String("qt_") + sysLocale, QLibraryInfo::location(QLibraryInfo::TranslationsPath))) {
426+ app.installTranslator(&qtTranslator);
427+ } else {
428+ app.removeTranslator(&translator);
429+ }
430+ }
431+
432+ QTranslator qmlTranslator;
433+ if (!options.translationFile.isEmpty()) {
434+ if (qmlTranslator.load(options.translationFile)) {
435+ app.installTranslator(&qmlTranslator);
436+ } else {
437+ qWarning() << "Could not load the translation file" << options.translationFile;
438+ }
439+ }
440+#endif
441+
442+ if (options.file.isEmpty())
443+#if defined(QMLSCENE_BUNDLE)
444+ displayOptionsDialog(&options);
445+#else
446+ displayFileDialog(&options);
447+#endif
448+
449+ int exitCode = 0;
450+
451+ if (!options.file.isEmpty()) {
452+ if (!options.versionDetection || checkVersion(options.file)) {
453+#ifndef QT_NO_TRANSLATION
454+ QTranslator translator;
455+#endif
456+
457+ // TODO: as soon as the engine construction completes, the debug service is
458+ // listening for connections. But actually we aren't ready to debug anything.
459+ QQmlEngine engine;
460+ QQmlComponent *component = new QQmlComponent(&engine);
461+ for (int i = 0; i < imports.size(); ++i)
462+ engine.addImportPath(imports.at(i));
463+ for (int i = 0; i < bundles.size(); ++i)
464+ engine.addNamedBundle(bundles.at(i).first, bundles.at(i).second);
465+ if (options.file.isLocalFile()) {
466+ QFileInfo fi(options.file.toLocalFile());
467+#ifndef QT_NO_TRANSLATION
468+ loadTranslationFile(translator, fi.path());
469+#endif
470+ loadDummyDataFiles(engine, fi.path());
471+ }
472+ QObject::connect(&engine, SIGNAL(quit()), QCoreApplication::instance(), SLOT(quit()));
473+ component->loadUrl(options.file);
474+ if ( !component->isReady() ) {
475+ qWarning("%s", qPrintable(component->errorString()));
476+ return -1;
477+ }
478+
479+ QObject *topLevel = component->create();
480+ QQuickWindow *window = qobject_cast<QQuickWindow *>(topLevel);
481+ QQuickView* qxView = 0;
482+ if (!window) {
483+ QQuickItem *contentItem = qobject_cast<QQuickItem *>(topLevel);
484+ if (contentItem) {
485+ qxView = new QQuickView(&engine, NULL);
486+ window = qxView;
487+ // Set window default properties; the qml can still override them
488+ QString oname = contentItem->objectName();
489+ window->setTitle(oname.isEmpty() ? QString::fromLatin1("qmlscene") : QString::fromLatin1("qmlscene: ") + oname);
490+ window->setFlags(Qt::Window | Qt::WindowSystemMenuHint | Qt::WindowTitleHint | Qt::WindowMinMaxButtonsHint | Qt::WindowCloseButtonHint | Qt::WindowFullscreenButtonHint);
491+ if (options.resizeViewToRootItem)
492+ qxView->setResizeMode(QQuickView::SizeViewToRootObject);
493+ else
494+ qxView->setResizeMode(QQuickView::SizeRootObjectToView);
495+ qxView->setContent(options.file, component, contentItem);
496+ }
497+ }
498+
499+ if (window) {
500+ mouseTouchAdaptor.setTargetWindow(window);
501+
502+ QSurfaceFormat surfaceFormat = window->requestedFormat();
503+ if (options.multisample)
504+ surfaceFormat.setSamples(16);
505+ if (options.transparent) {
506+ surfaceFormat.setAlphaBufferSize(8);
507+ window->setClearBeforeRendering(true);
508+ window->setColor(QColor(Qt::transparent));
509+ window->setFlags(Qt::FramelessWindowHint);
510+ }
511+ window->setFormat(surfaceFormat);
512+
513+ if (options.fullscreen)
514+ window->showFullScreen();
515+ else if (options.maximized)
516+ window->showMaximized();
517+ else
518+ window->show();
519+ }
520+
521+ if (options.quitImmediately)
522+ QMetaObject::invokeMethod(QCoreApplication::instance(), "quit", Qt::QueuedConnection);
523+
524+ // Now would be a good time to inform the debug service to start listening.
525+
526+ exitCode = app.exec();
527+
528+#ifdef QML_RUNTIME_TESTING
529+ RenderStatistics::printTotalStats();
530+#endif
531+ // Ready to exit. If we created qxView, it owns the component;
532+ // otherwise, the ownership is still right here. Nobody deletes the engine
533+ // (which is odd since the container constructor takes the engine pointer),
534+ // but it's stack-allocated anyway.
535+ if (qxView)
536+ delete qxView;
537+ else
538+ delete component;
539+ }
540+ }
541+
542+ return exitCode;
543+}