1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
6 ** This file is part of the tools applications of the Qt Toolkit.
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
42 #include <QtQuick1/qdeclarativeview.h>
47 # include "ui_recopts.h"
49 #include "qmlruntime.h"
50 #include <qdeclarativecontext.h>
51 #include <qdeclarativeengine.h>
52 #include <qdeclarativenetworkaccessmanagerfactory.h>
53 #include "qdeclarative.h"
54 #include <QAbstractAnimation>
55 #include <private/qabstractanimation_p.h>
59 #include <QXmlStreamReader>
61 #include <QNetworkReply>
62 #include <QNetworkCookie>
63 #include <QNetworkCookieJar>
64 #include <QNetworkDiskCache>
65 #include <QNetworkAccessManager>
66 #include <QSignalMapper>
67 #include <QDeclarativeComponent>
69 #include <QApplication>
70 #include <QTranslator>
72 #include <QTextBrowser>
75 #include <QVBoxLayout>
76 #include <QProgressDialog>
81 #include <QFileDialog>
82 #include <QInputDialog>
84 #include <QGraphicsObject>
85 #include <QNetworkProxyFactory>
89 #include <QMutexLocker>
90 #include "proxysettings.h"
91 #include "deviceorientation.h"
92 #include <qdeclarativetester.h>
100 class DragAndDropView : public QDeclarativeView
104 DragAndDropView(QDeclarativeViewer *parent = 0)
105 : QDeclarativeView(parent)
107 setAcceptDrops(true);
110 void dragEnterEvent(QDragEnterEvent *event)
112 const QMimeData *mimeData = event->mimeData();
113 if (mimeData->hasUrls())
114 event->acceptProposedAction();
117 void dragMoveEvent(QDragMoveEvent *event)
119 event->acceptProposedAction();
122 void dragLeaveEvent(QDragLeaveEvent *event)
127 void dropEvent(QDropEvent *event)
129 const QMimeData *mimeData = event->mimeData();
130 if (!mimeData->hasUrls())
132 const QList<QUrl> urlList = mimeData->urls();
133 foreach (const QUrl &url, urlList) {
134 if (url.scheme() == QLatin1String("file")) {
135 static_cast<QDeclarativeViewer *>(parent())->open(url.toLocalFile());
143 class Runtime : public QObject
147 Q_PROPERTY(bool isActiveWindow READ isActiveWindow NOTIFY isActiveWindowChanged)
148 Q_PROPERTY(DeviceOrientation::Orientation orientation READ orientation NOTIFY orientationChanged)
151 static Runtime* instance()
153 static Runtime *instance = 0;
155 instance = new Runtime;
159 bool isActiveWindow() const { return activeWindow; }
160 void setActiveWindow(bool active)
162 if (active == activeWindow)
164 activeWindow = active;
165 emit isActiveWindowChanged();
168 DeviceOrientation::Orientation orientation() const { return DeviceOrientation::instance()->orientation(); }
171 void isActiveWindowChanged();
172 void orientationChanged();
175 Runtime(QObject *parent=0) : QObject(parent), activeWindow(false)
177 connect(DeviceOrientation::instance(), SIGNAL(orientationChanged()),
178 this, SIGNAL(orientationChanged()));
184 static struct { const char *name, *args; } ffmpegprofiles[] = {
185 {"Maximum Quality", "-sameq"},
186 {"High Quality", "-qmax 2"},
187 {"Medium Quality", "-qmax 6"},
188 {"Low Quality", "-qmax 16"},
189 {"Custom ffmpeg arguments", ""},
193 class RecordingDialog : public QDialog, public Ui::RecordingOptions {
197 RecordingDialog(QWidget *parent) : QDialog(parent)
200 hz->setValidator(new QDoubleValidator(hz));
201 for (int i=0; ffmpegprofiles[i].name; ++i) {
202 profile->addItem(QString::fromAscii(ffmpegprofiles[i].name));
206 void setArguments(QString a)
209 for (i=0; ffmpegprofiles[i].args[0]; ++i) {
210 if (QString::fromAscii(ffmpegprofiles[i].args) == a) {
211 profile->setCurrentIndex(i);
212 args->setText(QString::fromAscii(ffmpegprofiles[i].args));
218 profile->setCurrentIndex(i);
221 QString arguments() const
223 int i = profile->currentIndex();
224 return ffmpegprofiles[i].args[0] ? QLatin1String(ffmpegprofiles[i].args) : customargs;
227 void setOriginalSize(const QSize &s)
229 QString str = tr("Original (%1x%2)").arg(s.width()).arg(s.height());
231 sizeOriginal->setText(str);
232 if (sizeWidth->value()<=1) {
233 sizeWidth->setValue(s.width());
234 sizeHeight->setValue(s.height());
238 void showffmpegOptions(bool b)
240 ffmpegOptions->setVisible(b);
243 void showRateOptions(bool b)
245 rateOptions->setVisible(b);
248 void setVideoRate(int rate)
251 hz24->setChecked(true);
253 hz25->setChecked(true);
255 hz50->setChecked(true);
257 hz60->setChecked(true);
259 hzCustom->setChecked(true);
260 hz->setText(QString::number(rate));
264 int videoRate() const
266 if (hz24->isChecked())
268 else if (hz25->isChecked())
270 else if (hz50->isChecked())
272 else if (hz60->isChecked())
275 return hz->text().toInt();
279 QSize videoSize() const
281 if (sizeOriginal->isChecked())
283 else if (size720p->isChecked())
284 return QSize(1280,720);
285 else if (sizeVGA->isChecked())
286 return QSize(640,480);
287 else if (sizeQVGA->isChecked())
288 return QSize(320,240);
290 return QSize(sizeWidth->value(), sizeHeight->value());
296 void pickProfile(int i)
298 if (ffmpegprofiles[i].args[0]) {
299 args->setText(QLatin1String(ffmpegprofiles[i].args));
301 args->setText(customargs);
305 void storeCustomArgs(QString s)
314 class PersistentCookieJar : public QNetworkCookieJar {
316 PersistentCookieJar(QObject *parent) : QNetworkCookieJar(parent) { load(); }
317 ~PersistentCookieJar() { save(); }
319 virtual QList<QNetworkCookie> cookiesForUrl(const QUrl &url) const
321 QMutexLocker lock(&mutex);
322 return QNetworkCookieJar::cookiesForUrl(url);
325 virtual bool setCookiesFromUrl(const QList<QNetworkCookie> &cookieList, const QUrl &url)
327 QMutexLocker lock(&mutex);
328 return QNetworkCookieJar::setCookiesFromUrl(cookieList, url);
334 QMutexLocker lock(&mutex);
335 QList<QNetworkCookie> list = allCookies();
337 foreach (QNetworkCookie cookie, list) {
338 if (!cookie.isSessionCookie()) {
339 data.append(cookie.toRawForm());
344 settings.setValue(QLatin1String("Cookies"), data);
349 QMutexLocker lock(&mutex);
351 QByteArray data = settings.value(QLatin1String("Cookies")).toByteArray();
352 setAllCookies(QNetworkCookie::parseCookies(data));
355 mutable QMutex mutex;
358 class SystemProxyFactory : public QNetworkProxyFactory
361 SystemProxyFactory() : proxyDirty(true), httpProxyInUse(false) {
364 virtual QList<QNetworkProxy> queryProxy(const QNetworkProxyQuery &query)
368 QString protocolTag = query.protocolTag();
369 if (httpProxyInUse && (protocolTag == QLatin1String("http") || protocolTag == QLatin1String("https"))) {
370 QList<QNetworkProxy> ret;
375 // systemProxyForQuery can take insanely long on Windows (QTBUG-10106)
376 return QNetworkProxyFactory::proxyForQuery(query);
378 return QNetworkProxyFactory::systemProxyForQuery(query);
383 // Don't bother locking because we know that the proxy only
384 // changes in response to the settings dialog and that
385 // the view will be reloaded.
387 httpProxyInUse = ProxySettings::httpProxyInUse();
389 httpProxy = ProxySettings::httpProxy();
392 void proxyChanged() {
397 volatile bool proxyDirty;
399 QNetworkProxy httpProxy;
402 class NetworkAccessManagerFactory : public QObject, public QDeclarativeNetworkAccessManagerFactory
406 NetworkAccessManagerFactory() : cacheSize(0) {}
407 ~NetworkAccessManagerFactory() {}
409 QNetworkAccessManager *create(QObject *parent);
411 void setCacheSize(int size) {
412 if (size != cacheSize) {
417 void proxyChanged() {
418 foreach (QNetworkAccessManager *nam, namList) {
419 static_cast<SystemProxyFactory*>(nam->proxyFactory())->proxyChanged();
423 static PersistentCookieJar *cookieJar;
426 void managerDestroyed(QObject *obj) {
427 namList.removeOne(static_cast<QNetworkAccessManager*>(obj));
433 QList<QNetworkAccessManager*> namList;
436 PersistentCookieJar *NetworkAccessManagerFactory::cookieJar = 0;
438 static void cleanup_cookieJar()
440 delete NetworkAccessManagerFactory::cookieJar;
441 NetworkAccessManagerFactory::cookieJar = 0;
444 QNetworkAccessManager *NetworkAccessManagerFactory::create(QObject *parent)
446 QMutexLocker lock(&mutex);
447 QNetworkAccessManager *manager = new QNetworkAccessManager(parent);
449 qAddPostRoutine(cleanup_cookieJar);
450 cookieJar = new PersistentCookieJar(0);
452 manager->setCookieJar(cookieJar);
453 cookieJar->setParent(0);
454 manager->setProxyFactory(new SystemProxyFactory);
456 QNetworkDiskCache *cache = new QNetworkDiskCache;
457 cache->setCacheDirectory(QDir::tempPath()+QLatin1String("/qml-viewer-network-cache"));
458 cache->setMaximumCacheSize(cacheSize);
459 manager->setCache(cache);
461 manager->setCache(0);
463 connect(manager, SIGNAL(destroyed(QObject*)), this, SLOT(managerDestroyed(QObject*)));
464 namList.append(manager);
468 QString QDeclarativeViewer::getVideoFileName()
470 QString title = convertAvailable || ffmpegAvailable ? tr("Save Video File") : tr("Save PNG Frames");
472 if (ffmpegAvailable) types += tr("Common Video files")+QLatin1String(" (*.avi *.mpeg *.mov)");
473 if (convertAvailable) types += tr("GIF Animation")+QLatin1String(" (*.gif)");
474 types += tr("Individual PNG frames")+QLatin1String(" (*.png)");
475 if (ffmpegAvailable) types += tr("All ffmpeg formats (*.*)");
476 return QFileDialog::getSaveFileName(this, title, QString(), types.join(QLatin1String(";; ")));
479 QDeclarativeViewer::QDeclarativeViewer(QWidget *parent, Qt::WindowFlags flags)
480 : QMainWindow(parent, flags)
481 , loggerWindow(new LoggerWidget(this))
485 , showWarningsWindow(0)
488 , useQmlFileBrowser(true)
491 QDeclarativeViewer::registerTypes();
492 setWindowTitle(tr("Qt QML Viewer"));
497 record_args += QLatin1String("-sameq");
499 recdlg = new RecordingDialog(this);
500 connect(recdlg->pickfile, SIGNAL(clicked()), this, SLOT(pickRecordingFile()));
503 if (!ffmpegAvailable)
504 recdlg->showffmpegOptions(false);
505 if (!ffmpegAvailable && !convertAvailable)
506 recdlg->showRateOptions(false);
508 if (!ffmpegAvailable) {
509 if (!convertAvailable)
510 warn = tr("ffmpeg and ImageMagick not available - no video output");
512 warn = tr("ffmpeg not available - GIF and PNG outputs only");
513 recdlg->warning->setText(warn);
515 recdlg->warning->hide();
518 canvas = new DragAndDropView(this);
520 canvas->setAttribute(Qt::WA_OpaquePaintEvent);
521 canvas->setAttribute(Qt::WA_NoSystemBackground);
525 QObject::connect(canvas, SIGNAL(initialSizeChanged(QSize)), this, SLOT(initialSizeChanged(QSize)));
526 QObject::connect(canvas, SIGNAL(statusChanged(QDeclarativeView::Status)), this, SLOT(statusChanged()));
527 QObject::connect(canvas->engine(), SIGNAL(quit()), this, SLOT(close()));
529 QObject::connect(warningsWidget(), SIGNAL(opened()), this, SLOT(warningsWidgetOpened()));
530 QObject::connect(warningsWidget(), SIGNAL(closed()), this, SLOT(warningsWidgetClosed()));
532 if (!(flags & Qt::FramelessWindowHint)) {
534 changeOrientation(orientation->actions().value(0));
539 setCentralWidget(canvas);
541 namFactory = new NetworkAccessManagerFactory;
542 canvas->engine()->setNetworkAccessManagerFactory(namFactory);
544 connect(&autoStartTimer, SIGNAL(timeout()), this, SLOT(autoStartRecording()));
545 connect(&autoStopTimer, SIGNAL(timeout()), this, SLOT(autoStopRecording()));
546 connect(&recordTimer, SIGNAL(timeout()), this, SLOT(recordFrame()));
547 connect(DeviceOrientation::instance(), SIGNAL(orientationChanged()),
548 this, SLOT(orientationChanged()), Qt::QueuedConnection);
549 autoStartTimer.setSingleShot(true);
550 autoStopTimer.setSingleShot(true);
551 recordTimer.setSingleShot(false);
553 QObject::connect(qApp, SIGNAL(aboutToQuit()), this, SLOT(appAboutToQuit()));
556 QDeclarativeViewer::~QDeclarativeViewer()
559 canvas->engine()->setNetworkAccessManagerFactory(0);
563 void QDeclarativeViewer::enableExperimentalGestures()
565 #ifndef QT_NO_GESTURES
566 canvas->viewport()->grabGesture(Qt::TapGesture,Qt::DontStartGestureOnChildren|Qt::ReceivePartialGestures|Qt::IgnoredGesturesPropagateToParent);
567 canvas->viewport()->grabGesture(Qt::TapAndHoldGesture,Qt::DontStartGestureOnChildren|Qt::ReceivePartialGestures|Qt::IgnoredGesturesPropagateToParent);
568 canvas->viewport()->grabGesture(Qt::PanGesture,Qt::DontStartGestureOnChildren|Qt::ReceivePartialGestures|Qt::IgnoredGesturesPropagateToParent);
569 canvas->viewport()->grabGesture(Qt::PinchGesture,Qt::DontStartGestureOnChildren|Qt::ReceivePartialGestures|Qt::IgnoredGesturesPropagateToParent);
570 canvas->viewport()->grabGesture(Qt::SwipeGesture,Qt::DontStartGestureOnChildren|Qt::ReceivePartialGestures|Qt::IgnoredGesturesPropagateToParent);
571 canvas->viewport()->setAttribute(Qt::WA_AcceptTouchEvents);
575 QDeclarativeView *QDeclarativeViewer::view() const
580 LoggerWidget *QDeclarativeViewer::warningsWidget() const
585 void QDeclarativeViewer::createMenu()
587 QAction *openAction = new QAction(tr("&Open..."), this);
588 openAction->setShortcuts(QKeySequence::Open);
589 connect(openAction, SIGNAL(triggered()), this, SLOT(openFile()));
591 QAction *openUrlAction = new QAction(tr("Open &URL..."), this);
592 connect(openUrlAction, SIGNAL(triggered()), this, SLOT(openUrl()));
594 QAction *reloadAction = new QAction(tr("&Reload"), this);
595 reloadAction->setShortcuts(QKeySequence::Refresh);
596 connect(reloadAction, SIGNAL(triggered()), this, SLOT(reload()));
598 QAction *snapshotAction = new QAction(tr("&Take Snapshot"), this);
599 snapshotAction->setShortcut(QKeySequence(tr("F3")));
600 connect(snapshotAction, SIGNAL(triggered()), this, SLOT(takeSnapShot()));
602 recordAction = new QAction(tr("Start Recording &Video"), this);
603 recordAction->setShortcut(QKeySequence(tr("F9")));
604 connect(recordAction, SIGNAL(triggered()), this, SLOT(toggleRecordingWithSelection()));
606 QAction *recordOptions = new QAction(tr("Video &Options..."), this);
607 connect(recordOptions, SIGNAL(triggered()), this, SLOT(chooseRecordingOptions()));
609 QAction *slowAction = new QAction(tr("&Slow Down Animations"), this);
610 slowAction->setShortcut(QKeySequence(tr("Ctrl+.")));
611 slowAction->setCheckable(true);
612 connect(slowAction, SIGNAL(triggered(bool)), this, SLOT(setSlowMode(bool)));
614 showWarningsWindow = new QAction(tr("Show Warnings"), this);
615 showWarningsWindow->setCheckable((true));
616 showWarningsWindow->setChecked(loggerWindow->isVisible());
617 connect(showWarningsWindow, SIGNAL(triggered(bool)), this, SLOT(showWarnings(bool)));
619 QAction *proxyAction = new QAction(tr("HTTP &Proxy..."), this);
620 connect(proxyAction, SIGNAL(triggered()), this, SLOT(showProxySettings()));
622 QAction *fullscreenAction = new QAction(tr("Full Screen"), this);
623 fullscreenAction->setCheckable(true);
624 connect(fullscreenAction, SIGNAL(triggered()), this, SLOT(toggleFullScreen()));
626 rotateAction = new QAction(tr("Rotate orientation"), this);
627 rotateAction->setShortcut(QKeySequence(tr("Ctrl+T")));
628 connect(rotateAction, SIGNAL(triggered()), this, SLOT(rotateOrientation()));
630 orientation = new QActionGroup(this);
631 orientation->setExclusive(true);
632 connect(orientation, SIGNAL(triggered(QAction*)), this, SLOT(changeOrientation(QAction*)));
634 QAction *portraitAction = new QAction(tr("Portrait"), this);
635 portraitAction->setCheckable(true);
636 QAction *landscapeAction = new QAction(tr("Landscape"), this);
637 landscapeAction->setCheckable(true);
638 QAction *portraitInvAction = new QAction(tr("Portrait (inverted)"), this);
639 portraitInvAction->setCheckable(true);
640 QAction *landscapeInvAction = new QAction(tr("Landscape (inverted)"), this);
641 landscapeInvAction->setCheckable(true);
643 QAction *aboutAction = new QAction(tr("&About Qt..."), this);
644 aboutAction->setMenuRole(QAction::AboutQtRole);
645 connect(aboutAction, SIGNAL(triggered()), qApp, SLOT(aboutQt()));
647 QAction *closeAction = new QAction(tr("&Close"), this);
648 closeAction->setShortcuts(QKeySequence::Close);
649 connect(closeAction, SIGNAL(triggered()), this, SLOT(close()));
651 QAction *quitAction = new QAction(tr("&Quit"), this);
652 quitAction->setMenuRole(QAction::QuitRole);
653 quitAction->setShortcuts(QKeySequence::Quit);
654 connect(quitAction, SIGNAL(triggered()), qApp, SLOT(quit()));
656 QMenuBar *menu = menuBar();
660 QMenu *fileMenu = menu->addMenu(tr("&File"));
661 fileMenu->addAction(openAction);
662 fileMenu->addAction(openUrlAction);
663 fileMenu->addAction(reloadAction);
664 fileMenu->addSeparator();
665 fileMenu->addAction(closeAction);
666 fileMenu->addAction(quitAction);
668 QMenu *recordMenu = menu->addMenu(tr("&Recording"));
669 recordMenu->addAction(snapshotAction);
670 recordMenu->addAction(recordAction);
672 QMenu *debugMenu = menu->addMenu(tr("&Debugging"));
673 debugMenu->addAction(slowAction);
674 debugMenu->addAction(showWarningsWindow);
676 QMenu *settingsMenu = menu->addMenu(tr("&Settings"));
677 settingsMenu->addAction(proxyAction);
678 settingsMenu->addAction(recordOptions);
679 settingsMenu->addMenu(loggerWindow->preferencesMenu());
680 settingsMenu->addAction(rotateAction);
682 QMenu *propertiesMenu = settingsMenu->addMenu(tr("Properties"));
684 orientation->addAction(portraitAction);
685 orientation->addAction(landscapeAction);
686 orientation->addAction(portraitInvAction);
687 orientation->addAction(landscapeInvAction);
688 propertiesMenu->addActions(orientation->actions());
690 QMenu *helpMenu = menu->addMenu(tr("&Help"));
691 helpMenu->addAction(aboutAction);
694 void QDeclarativeViewer::showProxySettings()
696 ProxySettings settingsDlg (this);
698 connect (&settingsDlg, SIGNAL (accepted()), this, SLOT (proxySettingsChanged ()));
703 void QDeclarativeViewer::proxySettingsChanged()
705 namFactory->proxyChanged();
709 void QDeclarativeViewer::rotateOrientation()
711 QAction *current = orientation->checkedAction();
712 QList<QAction *> actions = orientation->actions();
713 int index = actions.indexOf(current);
717 QAction *newOrientation = actions[(index + 1) % actions.count()];
718 changeOrientation(newOrientation);
721 void QDeclarativeViewer::toggleFullScreen()
729 void QDeclarativeViewer::showWarnings(bool show)
731 loggerWindow->setVisible(show);
734 void QDeclarativeViewer::warningsWidgetOpened()
736 showWarningsWindow->setChecked(true);
739 void QDeclarativeViewer::warningsWidgetClosed()
741 showWarningsWindow->setChecked(false);
744 void QDeclarativeViewer::takeSnapShot()
746 static int snapshotcount = 1;
747 QString snapFileName = QString(QLatin1String("snapshot%1.png")).arg(snapshotcount);
748 QPixmap::grabWidget(canvas).save(snapFileName);
749 qDebug() << "Wrote" << snapFileName;
753 void QDeclarativeViewer::pickRecordingFile()
755 QString fileName = getVideoFileName();
756 if (!fileName.isEmpty())
757 recdlg->file->setText(fileName);
760 void QDeclarativeViewer::chooseRecordingOptions()
763 recdlg->file->setText(record_file);
766 recdlg->setOriginalSize(canvas->size());
769 recdlg->setVideoRate(record_rate);
773 recdlg->setArguments(record_args.join(QLatin1String(" ")));
774 if (recdlg->exec()) {
776 record_file = recdlg->file->text();
778 record_outsize = recdlg->videoSize();
780 record_rate = recdlg->videoRate();
782 record_args = recdlg->arguments().split(QLatin1Char(' '),QString::SkipEmptyParts);
786 void QDeclarativeViewer::toggleRecordingWithSelection()
788 if (!recordTimer.isActive()) {
789 if (record_file.isEmpty()) {
790 QString fileName = getVideoFileName();
791 if (fileName.isEmpty())
793 if (!fileName.contains(QRegExp(QLatin1String(".[^\\/]*$"))))
794 fileName += QLatin1String(".avi");
795 setRecordFile(fileName);
801 void QDeclarativeViewer::toggleRecording()
803 if (record_file.isEmpty()) {
804 toggleRecordingWithSelection();
807 bool recording = !recordTimer.isActive();
808 recordAction->setText(recording ? tr("&Stop Recording Video\tF9") : tr("&Start Recording Video\tF9"));
809 setRecording(recording);
812 void QDeclarativeViewer::setSlowMode(bool enable)
814 QUnifiedTimer::instance()->setSlowModeEnabled(enable);
817 void QDeclarativeViewer::addLibraryPath(const QString& lib)
819 canvas->engine()->addImportPath(lib);
822 void QDeclarativeViewer::addPluginPath(const QString& plugin)
824 canvas->engine()->addPluginPath(plugin);
827 void QDeclarativeViewer::reload()
829 launch(currentFileOrUrl);
832 void QDeclarativeViewer::openFile()
834 QString cur = canvas->source().toLocalFile();
835 if (useQmlFileBrowser) {
836 open(QLatin1String("qrc:/browser/Browser.qml"));
838 QString fileName = QFileDialog::getOpenFileName(this, tr("Open QML file"), cur, tr("QML Files (*.qml)"));
839 if (!fileName.isEmpty()) {
840 QFileInfo fi(fileName);
841 open(fi.absoluteFilePath());
846 void QDeclarativeViewer::openUrl()
848 QString cur = canvas->source().toLocalFile();
849 QString url= QInputDialog::getText(this, tr("Open QML file"), tr("URL of main QML file:"), QLineEdit::Normal, cur);
854 void QDeclarativeViewer::statusChanged()
856 if (canvas->status() == QDeclarativeView::Error && tester)
857 tester->executefailure();
859 if (canvas->status() == QDeclarativeView::Ready) {
860 initialSize = canvas->initialSize();
861 updateSizeHints(true);
862 QObject::connect(canvas, SIGNAL(sceneResized(QSize)), this, SLOT(sceneResized(QSize)));
866 void QDeclarativeViewer::launch(const QString& file_or_url)
868 QMetaObject::invokeMethod(this, "open", Qt::QueuedConnection, Q_ARG(QString, file_or_url));
871 void QDeclarativeViewer::loadTranslationFile(const QString& directory)
874 translator = new QTranslator(this);
875 QApplication::installTranslator(translator);
878 translator->load(QLatin1String("qml_" )+QLocale::system().name(), directory + QLatin1String("/i18n"));
881 void QDeclarativeViewer::loadDummyDataFiles(const QString& directory)
883 QDir dir(directory + QLatin1String("/dummydata"), QLatin1String("*.qml"));
884 QStringList list = dir.entryList();
885 for (int i = 0; i < list.size(); ++i) {
886 QString qml = list.at(i);
887 QDeclarativeComponent comp(canvas->engine(), dir.filePath(qml));
888 QObject *dummyData = comp.create();
891 QList<QDeclarativeError> errors = comp.errors();
892 foreach (const QDeclarativeError &error, errors) {
895 if (tester) tester->executefailure();
899 qWarning() << "Loaded dummy data:" << dir.filePath(qml);
900 qml.truncate(qml.length()-4);
901 canvas->rootContext()->setContextProperty(qml, dummyData);
902 dummyData->setParent(this);
907 bool QDeclarativeViewer::open(const QString& file_or_url)
909 currentFileOrUrl = file_or_url;
912 QFileInfo fi(file_or_url);
914 url = QUrl::fromLocalFile(fi.absoluteFilePath());
916 url = QUrl(file_or_url);
917 setWindowTitle(tr("%1 - Qt QML Viewer").arg(file_or_url));
919 if (!m_script.isEmpty())
920 tester = new QDeclarativeTester(m_script, m_scriptOptions, canvas);
922 delete canvas->rootObject();
923 canvas->engine()->clearComponentCache();
924 QDeclarativeContext *ctxt = canvas->rootContext();
925 ctxt->setContextProperty(QLatin1String("qmlViewer"), this);
926 ctxt->setContextProperty(QLatin1String("qmlViewerFolder"), QDir::currentPath());
927 ctxt->setContextProperty(QLatin1String("runtime"), Runtime::instance());
929 QString fileName = url.toLocalFile();
930 if (!fileName.isEmpty()) {
931 fi.setFile(fileName);
933 if (fi.suffix().toLower() != QLatin1String("qml")) {
934 qWarning() << "qml cannot open non-QML file" << fileName;
938 QFileInfo fi(fileName);
939 loadTranslationFile(fi.path());
940 loadDummyDataFiles(fi.path());
942 qWarning() << "qml cannot find file:" << fileName;
950 QObject::disconnect(canvas, SIGNAL(sceneResized(QSize)), this, SLOT(sceneResized(QSize)));
951 canvas->setSource(url);
956 void QDeclarativeViewer::setAutoRecord(int from, int to)
958 if (from==0) from=1; // ensure resized
959 record_autotime = to-from;
960 autoStartTimer.setInterval(from);
961 autoStartTimer.start();
964 void QDeclarativeViewer::setRecordArgs(const QStringList& a)
969 void QDeclarativeViewer::setRecordFile(const QString& f)
974 void QDeclarativeViewer::setRecordRate(int fps)
979 void QDeclarativeViewer::sceneResized(QSize)
984 void QDeclarativeViewer::initialSizeChanged(QSize size)
986 if (!isFullScreen() && !isMaximized()) {
987 canvas->setGeometry(0,0,size.width(),size.height());
988 layout()->setSizeConstraint(QLayout::SetFixedSize);
989 layout()->activate();
993 void QDeclarativeViewer::keyPressEvent(QKeyEvent *event)
995 if (event->key() == Qt::Key_0 && devicemode)
997 else if (event->key() == Qt::Key_F1 || (event->key() == Qt::Key_1 && devicemode)) {
998 qDebug() << "F1 - help\n"
999 << "F2 - save test script\n"
1000 << "F3 - take PNG snapshot\n"
1001 << "F4 - show items and state\n"
1002 << "F5 - reload QML\n"
1003 << "F6 - show object tree\n"
1004 << "F7 - show timing\n"
1005 << "F9 - toggle video recording\n"
1006 << "F10 - toggle orientation\n"
1007 << "device keys: 0=quit, 1..8=F1..F8"
1009 } else if (event->key() == Qt::Key_F2 || (event->key() == Qt::Key_2 && devicemode)) {
1010 if (tester && m_scriptOptions & Record)
1012 } else if (event->key() == Qt::Key_F3 || (event->key() == Qt::Key_3 && devicemode)) {
1014 } else if (event->key() == Qt::Key_F5 || (event->key() == Qt::Key_5 && devicemode)) {
1016 } else if (event->key() == Qt::Key_F9 || (event->key() == Qt::Key_9 && devicemode)) {
1018 } else if (event->key() == Qt::Key_F10) {
1019 rotateOrientation();
1022 QWidget::keyPressEvent(event);
1025 bool QDeclarativeViewer::event(QEvent *event)
1027 if (event->type() == QEvent::WindowActivate) {
1028 Runtime::instance()->setActiveWindow(true);
1029 DeviceOrientation::instance()->resumeListening();
1030 } else if (event->type() == QEvent::WindowDeactivate) {
1031 Runtime::instance()->setActiveWindow(false);
1032 DeviceOrientation::instance()->pauseListening();
1034 return QWidget::event(event);
1037 void QDeclarativeViewer::senseImageMagick()
1040 proc.start(QLatin1String("convert"), QStringList() << QLatin1String("-h"));
1041 proc.waitForFinished(2000);
1042 QString help = QString::fromAscii(proc.readAllStandardOutput());
1043 convertAvailable = help.contains(QLatin1String("ImageMagick"));
1046 void QDeclarativeViewer::senseFfmpeg()
1049 proc.start(QLatin1String("ffmpeg"), QStringList() << QLatin1String("-h"));
1050 proc.waitForFinished(2000);
1051 QString ffmpegHelp = QString::fromAscii(proc.readAllStandardOutput());
1052 ffmpegAvailable = ffmpegHelp.contains(QLatin1String("-s "));
1053 ffmpegHelp = tr("Video recording uses ffmpeg:") + QLatin1String("\n\n") + ffmpegHelp;
1055 QDialog *d = new QDialog(recdlg);
1056 QVBoxLayout *l = new QVBoxLayout(d);
1057 QTextBrowser *b = new QTextBrowser(d);
1058 QFont f = b->font();
1059 f.setFamily(QLatin1String("courier"));
1061 b->setText(ffmpegHelp);
1064 ffmpegHelpWindow = d;
1065 connect(recdlg->ffmpegHelp,SIGNAL(clicked()), ffmpegHelpWindow, SLOT(show()));
1068 void QDeclarativeViewer::setRecording(bool on)
1070 if (on == recordTimer.isActive())
1073 int period = int(1000/record_rate+0.5);
1074 QUnifiedTimer::instance()->setTimingInterval(on ? period:16);
1075 QUnifiedTimer::instance()->setConsistentTiming(on);
1077 canvas->setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
1078 recordTimer.setInterval(period);
1079 recordTimer.start();
1080 frame_fmt = record_file.right(4).toLower();
1081 frame = QImage(canvas->width(),canvas->height(),QImage::Format_RGB32);
1082 if (frame_fmt != QLatin1String(".png") && (!convertAvailable || frame_fmt != QLatin1String(".gif"))) {
1083 // Stream video to ffmpeg
1085 QProcess *proc = new QProcess(this);
1086 connect(proc, SIGNAL(finished(int)), this, SLOT(ffmpegFinished(int)));
1087 frame_stream = proc;
1090 args << QLatin1String("-y");
1091 args << QLatin1String("-r") << QString::number(record_rate);
1092 args << QLatin1String("-f") << QLatin1String("rawvideo");
1093 args << QLatin1String("-pix_fmt") << (frame_fmt == QLatin1String(".gif") ? QLatin1String("rgb24") : QLatin1String("rgb32"));
1094 args << QLatin1String("-s") << QString::fromAscii("%1x%2").arg(canvas->width()).arg(canvas->height());
1095 args << QLatin1String("-i") << QLatin1String("-");
1096 if (record_outsize.isValid()) {
1097 args << QLatin1String("-s") << QString::fromAscii("%1x%2").arg(record_outsize.width()).arg(record_outsize.height());
1098 args << QLatin1String("-aspect") << QString::number(double(canvas->width())/canvas->height());
1100 args += record_args;
1101 args << record_file;
1102 proc->start(QLatin1String("ffmpeg"), args);
1105 // Store frames, save to GIF/PNG
1109 canvas->setViewportUpdateMode(QGraphicsView::MinimalViewportUpdate);
1112 qDebug() << "Saving video...";
1113 frame_stream->close();
1114 qDebug() << "Wrote" << record_file;
1116 QProgressDialog progress(tr("Saving frames..."), tr("Cancel"), 0, frames.count()+10, this);
1117 progress.setWindowModality(Qt::WindowModal);
1121 qDebug() << "Saving frames...";
1124 bool png_output = false;
1125 if (record_file.right(4).toLower() == QLatin1String(".png")) {
1126 if (record_file.contains(QLatin1Char('%')))
1127 framename = record_file;
1129 framename = record_file.left(record_file.length()-4) + QLatin1String("%04d") + record_file.right(4);
1132 framename = QLatin1String("tmp-frame%04d.png");
1135 foreach (QImage* img, frames) {
1136 progress.setValue(progress.value()+1);
1137 if (progress.wasCanceled())
1140 name.sprintf(framename.toLocal8Bit(),frame++);
1141 if (record_outsize.isValid())
1142 *img = img->scaled(record_outsize,Qt::IgnoreAspectRatio,Qt::SmoothTransformation);
1143 if (record_dither==QLatin1String("ordered"))
1144 img->convertToFormat(QImage::Format_Indexed8,Qt::PreferDither|Qt::OrderedDither).save(name);
1145 else if (record_dither==QLatin1String("threshold"))
1146 img->convertToFormat(QImage::Format_Indexed8,Qt::PreferDither|Qt::ThresholdDither).save(name);
1147 else if (record_dither==QLatin1String("floyd"))
1148 img->convertToFormat(QImage::Format_Indexed8,Qt::PreferDither).save(name);
1155 if (!progress.wasCanceled()) {
1157 framename.replace(QRegExp(QLatin1String("%\\d*.")), QLatin1String("*"));
1158 qDebug() << "Wrote frames" << framename;
1159 inputs.clear(); // don't remove them
1161 // ImageMagick and gifsicle for GIF encoding
1162 progress.setLabelText(tr("Converting frames to GIF file..."));
1164 args << QLatin1String("-delay") << QString::number(period/10);
1166 args << record_file;
1167 qDebug() << "Converting..." << record_file << "(this may take a while)";
1168 if (0!=QProcess::execute(QLatin1String("convert"), args)) {
1169 qWarning() << "Cannot run ImageMagick 'convert' - recorded frames not converted";
1170 inputs.clear(); // don't remove them
1171 qDebug() << "Wrote frames tmp-frame*.png";
1173 if (record_file.right(4).toLower() == QLatin1String(".gif")) {
1174 qDebug() << "Compressing..." << record_file;
1175 if (0!=QProcess::execute(QLatin1String("gifsicle"), QStringList() << QLatin1String("-O2")
1176 << QLatin1String("-o") << record_file << record_file))
1177 qWarning() << "Cannot run 'gifsicle' - not compressed";
1179 qDebug() << "Wrote" << record_file;
1184 progress.setValue(progress.maximum()-1);
1185 foreach (QString name, inputs)
1186 QFile::remove(name);
1191 qDebug() << "Recording: " << (recordTimer.isActive()?"ON":"OFF");
1194 void QDeclarativeViewer::ffmpegFinished(int code)
1196 qDebug() << "ffmpeg returned" << code << frame_stream->readAllStandardError();
1199 void QDeclarativeViewer::appAboutToQuit()
1201 // avoid QGLContext errors about invalid contexts on exit
1202 canvas->setViewport(0);
1204 // avoid crashes if messages are received after app has closed
1205 delete loggerWindow;
1212 void QDeclarativeViewer::autoStartRecording()
1215 autoStopTimer.setInterval(record_autotime);
1216 autoStopTimer.start();
1219 void QDeclarativeViewer::autoStopRecording()
1221 setRecording(false);
1224 void QDeclarativeViewer::recordFrame()
1226 canvas->QWidget::render(&frame);
1228 if (frame_fmt == QLatin1String(".gif")) {
1229 // ffmpeg can't do 32bpp with gif
1230 QImage rgb24 = frame.convertToFormat(QImage::Format_RGB888);
1231 frame_stream->write((char*)rgb24.bits(),rgb24.byteCount());
1233 frame_stream->write((char*)frame.bits(),frame.byteCount());
1236 frames.append(new QImage(frame));
1240 void QDeclarativeViewer::changeOrientation(QAction *action)
1244 QString o = action->text();
1245 action->setChecked(true);
1246 if (o == QLatin1String("Portrait"))
1247 DeviceOrientation::instance()->setOrientation(DeviceOrientation::Portrait);
1248 else if (o == QLatin1String("Landscape"))
1249 DeviceOrientation::instance()->setOrientation(DeviceOrientation::Landscape);
1250 else if (o == QLatin1String("Portrait (inverted)"))
1251 DeviceOrientation::instance()->setOrientation(DeviceOrientation::PortraitInverted);
1252 else if (o == QLatin1String("Landscape (inverted)"))
1253 DeviceOrientation::instance()->setOrientation(DeviceOrientation::LandscapeInverted);
1256 void QDeclarativeViewer::orientationChanged()
1261 void QDeclarativeViewer::setDeviceKeys(bool on)
1266 void QDeclarativeViewer::setNetworkCacheSize(int size)
1268 namFactory->setCacheSize(size);
1271 void QDeclarativeViewer::setUseGL(bool useGL)
1275 QGLFormat format = QGLFormat::defaultFormat();
1277 format.setSampleBuffers(true);
1278 format.setSwapInterval(1);
1280 format.setSampleBuffers(false);
1283 QGLWidget *glWidget = new QGLWidget(format);
1284 //### potentially faster, but causes junk to appear if top-level is Item, not Rectangle
1285 //glWidget->setAutoFillBackground(false);
1287 canvas->setViewport(glWidget);
1294 void QDeclarativeViewer::setUseNativeFileBrowser(bool use)
1296 useQmlFileBrowser = !use;
1299 void QDeclarativeViewer::setSizeToView(bool sizeToView)
1301 QDeclarativeView::ResizeMode resizeMode = sizeToView ? QDeclarativeView::SizeRootObjectToView : QDeclarativeView::SizeViewToRootObject;
1302 if (resizeMode != canvas->resizeMode()) {
1303 canvas->setResizeMode(resizeMode);
1308 void QDeclarativeViewer::updateSizeHints(bool initial)
1310 static bool isRecursive = false;
1316 if (initial || (canvas->resizeMode() == QDeclarativeView::SizeViewToRootObject)) {
1317 QSize newWindowSize = initial ? initialSize : canvas->sizeHint();
1318 //qWarning() << "USH:" << (initial ? "INIT:" : "V2R:") << "setting fixed size " << newWindowSize;
1319 if (!isFullScreen() && !isMaximized()) {
1320 canvas->setGeometry(0,0,newWindowSize.width(),newWindowSize.height());
1322 layout()->setSizeConstraint(QLayout::SetFixedSize);
1323 layout()->activate();
1326 //qWarning() << "USH: R2V: setting free size ";
1327 layout()->setSizeConstraint(QLayout::SetNoConstraint);
1328 layout()->activate();
1329 setMinimumSize(minimumSizeHint());
1330 setMaximumSize(QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX));
1331 canvas->setMinimumSize(QSize(0,0));
1332 canvas->setMaximumSize(QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX));
1334 isRecursive = false;
1337 void QDeclarativeViewer::registerTypes()
1339 static bool registered = false;
1342 // registering only for exposing the DeviceOrientation::Orientation enum
1343 qmlRegisterUncreatableType<DeviceOrientation>("Qt", 4, 7, "Orientation", QString());
1344 qmlRegisterUncreatableType<DeviceOrientation>("QtQuick", 1, 0, "Orientation", QString());
1351 #include "qmlruntime.moc"