diff options
-rw-r--r-- | src/extras/doc/images/qtquickextras2-tumbler-wrap.gif | bin | 28883 -> 38931 bytes | |||
-rw-r--r-- | tests/manual/gifs/capturedevent.cpp | 113 | ||||
-rw-r--r-- | tests/manual/gifs/capturedevent.h | 67 | ||||
-rw-r--r-- | tests/manual/gifs/data/qtquickextras2-tumbler-wrap.qml | 119 | ||||
-rw-r--r-- | tests/manual/gifs/eventcapturer.cpp | 234 | ||||
-rw-r--r-- | tests/manual/gifs/eventcapturer.h | 97 | ||||
-rw-r--r-- | tests/manual/gifs/gifrecorder.cpp | 170 | ||||
-rw-r--r-- | tests/manual/gifs/gifrecorder.h | 83 | ||||
-rw-r--r-- | tests/manual/gifs/gifs.pro | 20 | ||||
-rw-r--r-- | tests/manual/gifs/tst_gifs.cpp | 148 | ||||
-rw-r--r-- | tests/manual/manual.pro | 3 |
11 files changed, 1054 insertions, 0 deletions
diff --git a/src/extras/doc/images/qtquickextras2-tumbler-wrap.gif b/src/extras/doc/images/qtquickextras2-tumbler-wrap.gif Binary files differindex c7624599..2a7e435d 100644 --- a/src/extras/doc/images/qtquickextras2-tumbler-wrap.gif +++ b/src/extras/doc/images/qtquickextras2-tumbler-wrap.gif diff --git a/tests/manual/gifs/capturedevent.cpp b/tests/manual/gifs/capturedevent.cpp new file mode 100644 index 00000000..a36086c8 --- /dev/null +++ b/tests/manual/gifs/capturedevent.cpp @@ -0,0 +1,113 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/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 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later 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 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "capturedevent.h" + +#include <QMetaEnum> +#include <QMouseEvent> + +namespace { + static inline bool isMouseEvent(const QEvent &event) + { + return event.type() >= QEvent::MouseButtonPress && event.type() <= QEvent::MouseMove; + } +} + +CapturedEvent::CapturedEvent() +{ +} + +CapturedEvent::CapturedEvent(const QEvent &event, int delay) +{ + setEvent(event); + setDelay(delay); +} + +void CapturedEvent::setEvent(const QEvent &event) +{ + mType = event.type(); + + if (isMouseEvent(event)) { + const QMouseEvent *mouseEvent = static_cast<const QMouseEvent*>(&event); + mPos = mouseEvent->pos(); + mMouseButton = mouseEvent->button(); + } +} + +QEvent::Type CapturedEvent::type() const +{ + return mType; +} + +int CapturedEvent::delay() const +{ + return mDelay; +} + +void CapturedEvent::setDelay(int delay) +{ + mDelay = delay; + + mCppCommand.clear(); + + // We generate the C++ command here instead of when the event is captured, + // because events() might trim some events, causing the delay of some events to change. + // If we did it earlier, the events wouldn't have correct delays. + if (mType == QEvent::MouseMove) { + mCppCommand = QString::fromLatin1("QTest::mouseMove(&view, QPoint(%1, %2), %3);") + .arg(mPos.x()) + .arg(mPos.y()) + .arg(mDelay); + + } else if (mType >= QEvent::MouseButtonPress && mType <= QEvent::MouseButtonDblClick) { + QString eventTestFunctionName = (mType == QEvent::MouseButtonPress + ? "mousePress" : (mType == QEvent::MouseButtonRelease + ? "mouseRelease" : "mouseDClick")); + QString buttonStr = QMetaEnum::fromType<Qt::MouseButtons>().valueToKey(mMouseButton); + mCppCommand = QString::fromLatin1("QTest::%1(&view, Qt::%2, Qt::NoModifier, QPoint(%3, %4), %5);") + .arg(eventTestFunctionName) + .arg(buttonStr) + .arg(mPos.x()) + .arg(mPos.y()) + .arg(mDelay); + } +} + +QString CapturedEvent::cppCommand() const +{ + return mCppCommand; +} + diff --git a/tests/manual/gifs/capturedevent.h b/tests/manual/gifs/capturedevent.h new file mode 100644 index 00000000..dd495c2d --- /dev/null +++ b/tests/manual/gifs/capturedevent.h @@ -0,0 +1,67 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/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 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later 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 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef CAPTUREDEVENT_H +#define CAPTUREDEVENT_H + +#include <QEvent> +#include <QPoint> +#include <QString> + +class CapturedEvent +{ +public: + CapturedEvent(); + CapturedEvent(const QEvent &event, int delay); + + void setEvent(const QEvent &event); + + int delay() const; + void setDelay(int delay); + + QEvent::Type type() const; + + QString cppCommand() const; + +private: + QEvent::Type mType; + QPoint mPos; + Qt::MouseButton mMouseButton; + int mDelay; + QString mCppCommand; +}; + +#endif // CAPTUREDEVENT_H diff --git a/tests/manual/gifs/data/qtquickextras2-tumbler-wrap.qml b/tests/manual/gifs/data/qtquickextras2-tumbler-wrap.qml new file mode 100644 index 00000000..e2517d53 --- /dev/null +++ b/tests/manual/gifs/data/qtquickextras2-tumbler-wrap.qml @@ -0,0 +1,119 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the test suite 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 The Qt Company Ltd 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 2.6 +import QtQuick.Controls 2.0 +import QtQuick.Extras 2.0 + +Item { + width: 200 + height: 200 + + Frame { + padding: 0 + anchors.centerIn: parent + width: row.implicitWidth + height: row.implicitHeight + + FontMetrics { + id: fontMetrics + } + + Component { + id: delegateComponent + + Text { + text: modelData.toString().length < 2 ? "0" + modelData : modelData + color: "#666666" + opacity: 0.4 + Math.max(0, 1 - Math.abs(AbstractTumbler.displacement)) * 0.6 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.pixelSize: fontMetrics.font.pixelSize * 1.25 + } + } + + Row { + id: row + + Tumbler { + id: hoursTumbler + model: 12 + delegate: Text { + text: (modelData.toString().length < 2 ? "0" : "") + (modelData + 1) + color: "#666666" + opacity: 0.4 + Math.max(0, 1 - Math.abs(AbstractTumbler.displacement)) * 0.6 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.pixelSize: fontMetrics.font.pixelSize * 1.25 + } + width: 50 + height: 150 + } + + Tumbler { + id: minutesTumbler + currentIndex: 6 + model: 60 + delegate: delegateComponent + width: 50 + height: 150 + } + + Tumbler { + id: amPmTumbler + model: ["AM", "PM"] + delegate: delegateComponent + width: 50 + height: 150 + contentItem: ListView { + anchors.fill: parent + model: amPmTumbler.model + delegate: amPmTumbler.delegate + + snapMode: ListView.SnapToItem + highlightRangeMode: ListView.StrictlyEnforceRange + preferredHighlightBegin: height / 2 - (height / 3 / 2) + preferredHighlightEnd: height / 2 + (height / 3 / 2) + clip: true + } + } + } + } +} diff --git a/tests/manual/gifs/eventcapturer.cpp b/tests/manual/gifs/eventcapturer.cpp new file mode 100644 index 00000000..f84d7840 --- /dev/null +++ b/tests/manual/gifs/eventcapturer.cpp @@ -0,0 +1,234 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/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 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later 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 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "eventcapturer.h" + +#include <QDebug> +#include <QMetaEnum> +#include <QMouseEvent> +#include <QTimer> + +/*! + Installs an event filter on a particular object to record specific events + that can be retrieved as C++ source code. + + For example: + + \code + EventCapturer eventCapturer; + + view.show(); + + eventCapturer.startCapturing(&view, 5000); + + // interact with the view here, in order for the events to be captured + + qDebug() << "\n"; + foreach (CapturedEvent event, eventCapturer.capturedEvents()) + qDebug().noquote() << event.cppCommand(); + \endcode + + It is recommended to set the \c Qt::FramelessWindowHint flag on the view + (this code has not been tested under other usage): + + view.setFlags(view.flags() | Qt::FramelessWindowHint); +*/ + +EventCapturer::EventCapturer(QObject *parent) : + QObject(parent), + mEventSource(Q_NULLPTR), + mStopCaptureKey(Qt::Key_Escape), + mMoveEventTrimFlags(TrimNone), + mDuration(0), + mLastCaptureTime(0) +{ + mCapturedEventTypes << QEvent::MouseButtonPress << QEvent::MouseButtonRelease << QEvent::MouseButtonDblClick << QEvent::MouseMove; +} + +void EventCapturer::startCapturing(QObject *eventSource, int duration) +{ + mEventSource = eventSource; + + if (!mEventSource) + return; + + mEventSource->installEventFilter(this); + mDelayTimer.start(); + mDuration = duration; + mLastCaptureTime = 0; + + QTimer::singleShot(mDuration, this, SLOT(stopCapturing())); +} + +void EventCapturer::setStopCaptureKey(Qt::Key stopCaptureKey) +{ + mStopCaptureKey = stopCaptureKey; +} + +/*! + Move events generate a lot of clutter, and for most cases they're not + necessary. Here's a list of scenarios where various trim flags make sense: + + Scenario Flags + + Record the mouse cursor TrimNone + Record mouseover/hover effects TrimNone + Dragging/flicking TrimAll +*/ +void EventCapturer::setMoveEventTrimFlags(MoveEventTrimFlags trimFlags) +{ + mMoveEventTrimFlags = trimFlags; +} + +QSet<QEvent::Type> EventCapturer::capturedEventTypes() +{ + return mCapturedEventTypes; +} + +void EventCapturer::setCapturedEventTypes(QSet<QEvent::Type> types) +{ + mCapturedEventTypes = types; +} + +QVector<CapturedEvent> EventCapturer::capturedEvents() const +{ + if (mMoveEventTrimFlags == TrimNone || mEvents.isEmpty()) + return mEvents; + + // We can't easily trim "trailing" move events as they come in without + // storing them in some form, so we just do it all here. + + int firstEventIndex = 0; + int lastEventIndex = mEvents.size() - 1; + // The accumulated delay of all of the move events that we remove. + // We keep this in order to maintain the correct timing between events. + int accumulatedDelay = 0; + + bool encounteredNonMoveEvent = false; + if (mMoveEventTrimFlags.testFlag(TrimLeading)) { + for (int eventIndex = 0; !encounteredNonMoveEvent && eventIndex < mEvents.size(); ++eventIndex) { + const CapturedEvent event = mEvents.at(eventIndex); + if (event.type() != QEvent::MouseMove) { + encounteredNonMoveEvent = true; + firstEventIndex = eventIndex; + } else { + accumulatedDelay += event.delay(); + } + } + } + + if (mMoveEventTrimFlags.testFlag(TrimTrailing)) { + encounteredNonMoveEvent = false; + for (int eventIndex = mEvents.size() - 1; !encounteredNonMoveEvent && eventIndex >= 0; --eventIndex) { + const CapturedEvent event = mEvents.at(eventIndex); + if (event.type() != QEvent::MouseMove) { + encounteredNonMoveEvent = true; + lastEventIndex = eventIndex; + // Don't need to bother with delays for trailing mouse moves, as there is nothing after them. + } + } + } + + // Before we go any further, we need to copy the subset of commands while + // the indices are still valid - we could be removing from the middle of + // the commands next. Also, the function is const, so we can't remove from + // mEvents anyway. :) + QVector<CapturedEvent> events = mEvents.mid(firstEventIndex, (lastEventIndex - firstEventIndex) + 1); + + if (mMoveEventTrimFlags.testFlag(TrimAfterReleases)) { + bool lastNonMoveEventWasRelease = false; + for (int eventIndex = 0; eventIndex < events.size(); ) { + CapturedEvent &event = events[eventIndex]; + if (event.type() == QEvent::MouseMove && lastNonMoveEventWasRelease) { + accumulatedDelay += event.delay(); + events.remove(eventIndex); + } else { + lastNonMoveEventWasRelease = event.type() == QEvent::MouseButtonRelease; + if (event.type() == QEvent::MouseButtonPress) { + event.setDelay(event.delay() + accumulatedDelay); + accumulatedDelay = 0; + } + ++eventIndex; + } + } + } + + return events; +} + +bool EventCapturer::eventFilter(QObject *object, QEvent *event) +{ + if (event->type() == QEvent::KeyPress && static_cast<QKeyEvent*>(event)->key() == mStopCaptureKey) { + stopCapturing(); + return true; + } + + if (object != mEventSource) + return false; + + if (!mCapturedEventTypes.contains(event->type())) + return false; + + if (event->type() == QEvent::MouseButtonPress) { + captureEvent(event); + } else if (event->type() == QEvent::MouseButtonRelease) { + captureEvent(event); + } else if (event->type() == QEvent::MouseButtonDblClick) { + captureEvent(event); + } else if (event->type() == QEvent::MouseMove) { + captureEvent(event); + } else { + qWarning() << "No support for event type" << QMetaEnum::fromType<QEvent::Type>().valueToKey(event->type()); + } + return false; +} + +void EventCapturer::stopCapturing() +{ + if (mEventSource) { + mEventSource->removeEventFilter(this); + mEventSource = 0; + mDuration = 0; + mLastCaptureTime = 0; + } +} + +void EventCapturer::captureEvent(const QEvent *event) +{ + CapturedEvent capturedEvent(*event, mDelayTimer.elapsed() - mLastCaptureTime); + mEvents.append(capturedEvent); + mLastCaptureTime = mDelayTimer.elapsed(); +} diff --git a/tests/manual/gifs/eventcapturer.h b/tests/manual/gifs/eventcapturer.h new file mode 100644 index 00000000..a1f48d32 --- /dev/null +++ b/tests/manual/gifs/eventcapturer.h @@ -0,0 +1,97 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/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 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later 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 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef EVENTCAPTURER_H +#define EVENTCAPTURER_H + +#include <QObject> +#include <QElapsedTimer> +#include <QEvent> +#include <QPoint> +#include <QSet> +#include <QVector> + +#include "capturedevent.h" + +class EventCapturer : public QObject +{ + Q_OBJECT + +public: + EventCapturer(QObject *parent = 0); + + enum MoveEventTrimFlag + { + TrimNone = 0x0, + TrimLeading = 0x1, + TrimTrailing = 0x2, + TrimAfterReleases = 0x4, + TrimAll = TrimLeading | TrimTrailing | TrimAfterReleases + }; + + Q_DECLARE_FLAGS(MoveEventTrimFlags, MoveEventTrimFlag) + + void setStopCaptureKey(Qt::Key stopCaptureKey); + void setMoveEventTrimFlags(MoveEventTrimFlags trimFlags); + + void startCapturing(QObject *eventSource, int duration); + + QSet<QEvent::Type> capturedEventTypes(); + void setCapturedEventTypes(QSet<QEvent::Type> types); + + QVector<CapturedEvent> capturedEvents() const; +protected: + bool eventFilter(QObject *object, QEvent *event) Q_DECL_OVERRIDE; + +private slots: + void stopCapturing(); + +private: + void captureEvent(const QEvent *event); + + QObject *mEventSource; + QSet<QEvent::Type> mCapturedEventTypes; + Qt::Key mStopCaptureKey; + MoveEventTrimFlags mMoveEventTrimFlags; + QElapsedTimer mDelayTimer; + QVector<CapturedEvent> mEvents; + int mDuration; + int mLastCaptureTime; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(EventCapturer::MoveEventTrimFlags) + +#endif // EVENTCAPTURER_H diff --git a/tests/manual/gifs/gifrecorder.cpp b/tests/manual/gifs/gifrecorder.cpp new file mode 100644 index 00000000..e684181c --- /dev/null +++ b/tests/manual/gifs/gifrecorder.cpp @@ -0,0 +1,170 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/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 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later 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 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "gifrecorder.h" + +#include <QtTest> + +/*! + QProcess wrapper around byzanz-record (sudo apt-get install byzanz). + + It is recommended to set the \c Qt::FramelessWindowHint flag on the view + (this code has not been tested under other usage): + + view.setFlags(view.flags() | Qt::FramelessWindowHint); +*/ + +GifRecorder::GifRecorder() : + QObject(Q_NULLPTR), + mProcessName(QStringLiteral("byzanz-record")), + mRecordingDuration(0), + mRecordCursor(false), + mView(Q_NULLPTR), + mStarted(false) +{ + // Ensures output from the process goes directly into the console. + mProcess.setProcessChannelMode(QProcess::ForwardedChannels); + + connect(&mProcess, SIGNAL(error(QProcess::ProcessError)), this, SLOT(onError())); + connect(&mProcess, SIGNAL(finished(int)), this, SLOT(onFinished())); +} + +void GifRecorder::setRecordingDuration(int duration) +{ + QVERIFY2(duration >= 1, qPrintable(QString::fromLatin1("Recording duration %1 must be larger than 1 second").arg(duration))); + QVERIFY2(duration < 10, qPrintable(QString::fromLatin1("Recording duration %1 must be less than 10 seconds").arg(duration))); + + mRecordingDuration = duration; +} + +void GifRecorder::setRecordCursor(bool recordCursor) +{ + mRecordCursor = recordCursor; +} + +void GifRecorder::setDataDirPath(const QString &path) +{ + QVERIFY2(!path.isEmpty(), "Data directory path cannot be empty"); + mDataDirPath = path; +} + +void GifRecorder::setOutputDir(const QDir &dir) +{ + QVERIFY2(dir.exists(), "Output directory must exist"); + mOutputDir = dir; +} + +void GifRecorder::setOutputFileName(const QString &fileName) +{ + QVERIFY2(!fileName.isEmpty(), "Output file name cannot be empty"); + mOutputFileName = fileName; +} + +void GifRecorder::setQmlFileName(const QString &fileName) +{ + QVERIFY2(!fileName.isEmpty(), "QML file name cannot be empty"); + mQmlInputFileName = fileName; +} + +void GifRecorder::setView(QQuickView *view) +{ + this->mView = view; +} + +void GifRecorder::start() +{ + QVERIFY2(mView, "Must have a view to record"); + + QDir gifQmlDir(mDataDirPath); + QVERIFY(gifQmlDir.entryList().contains(mQmlInputFileName)); + + const QString qmlPath = gifQmlDir.absoluteFilePath(mQmlInputFileName); + mView->setSource(QUrl::fromLocalFile(qmlPath)); + QVERIFY(mView->rootObject()); + + mView->show(); + mView->requestActivate(); + QVERIFY(QTest::qWaitForWindowActive(mView, 500)); + + mOutputFileName = mOutputDir.absoluteFilePath(mQmlInputFileName); + mOutputFileName.replace(".qml", ".gif"); + + QStringList args; + args << "-d" << QString::number(mRecordingDuration) << "-v"; + if (mRecordCursor) + args << "-c"; + args << "-x" << QString::number(mView->x()) << "-y" << QString::number(mView->y()); + args << "-w" << QString::number(mView->width()) << "-h" << QString::number(mView->height()); + args << mOutputFileName; + qInfo() << "Starting" << mProcessName << "with the following arguments:" << args; + mProcess.start(mProcessName, args); + + if (!mProcess.waitForStarted(1000)) { + QString message = QString::fromLatin1("Could not launch %1 with the following arguments: %2\nError:\n%3"); + message = message.arg(mProcessName).arg(args.join(QLatin1Char(' '))).arg(mProcess.errorString()); + QFAIL(qPrintable(message)); + } + + mStarted = true; +} + +bool GifRecorder::hasStarted() const +{ + return mStarted; +} + +void GifRecorder::waitForFinish() +{ + // Give it an extra couple of seconds on top of its recording duration. + QTRY_VERIFY_WITH_TIMEOUT(mFinished, (mRecordingDuration + 2) * 1000); + + if (!QFileInfo::exists(mOutputFileName)) { + const QString message = QString::fromLatin1( + "The process said it finished successfully, but no GIF was generated."); + QFAIL(qPrintable(message)); + } +} + +void GifRecorder::onError() +{ + const QString message = QString::fromLatin1("%1 failed to finish: %2"); + QFAIL(qPrintable(message.arg(mProcessName).arg(mProcess.errorString()))); +} + +void GifRecorder::onFinished() +{ + mFinished = true; +} diff --git a/tests/manual/gifs/gifrecorder.h b/tests/manual/gifs/gifrecorder.h new file mode 100644 index 00000000..546c8669 --- /dev/null +++ b/tests/manual/gifs/gifrecorder.h @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/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 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later 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 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef GIFRECORDER_H +#define GIFRECORDER_H + +#include <QObject> +#include <QProcess> +#include <QQuickView> +#include <QDir> +#include <QString> + +class GifRecorder : public QObject +{ + Q_OBJECT + +public: + GifRecorder(); + + void setRecordingDuration(int duration); + void setRecordCursor(bool recordCursor); + void setDataDirPath(const QString &path); + void setOutputDir(const QDir &dir); + void setOutputFileName(const QString &fileName); + void setQmlFileName(const QString &fileName); + void setView(QQuickView *mView); + + void start(); + bool hasStarted() const; + void waitForFinish(); + +private slots: + void onError(); + void onFinished(); + +private: + const QString mProcessName; + QProcess mProcess; + int mRecordingDuration; + bool mRecordCursor; + QString mDataDirPath; + QDir mOutputDir; + QString mOutputFileName; + QString mQmlInputFileName; + QQuickView *mView; + bool mStarted; + bool mFinished; +}; + +#endif // GIFRECORDER_H diff --git a/tests/manual/gifs/gifs.pro b/tests/manual/gifs/gifs.pro new file mode 100644 index 00000000..197b83d5 --- /dev/null +++ b/tests/manual/gifs/gifs.pro @@ -0,0 +1,20 @@ +TEMPLATE = app +TARGET = tst_gifs + +QT += quick testlib +CONFIG += testcase +osx:CONFIG -= app_bundle + +HEADERS += \ + $$PWD/gifrecorder.h \ + $$PWD/eventcapturer.h \ + capturedevent.h + +SOURCES += \ + $$PWD/tst_gifs.cpp \ + $$PWD/gifrecorder.cpp \ + $$PWD/eventcapturer.cpp \ + capturedevent.cpp + +TESTDATA += \ + $$PWD/data/* diff --git a/tests/manual/gifs/tst_gifs.cpp b/tests/manual/gifs/tst_gifs.cpp new file mode 100644 index 00000000..d2ff8d50 --- /dev/null +++ b/tests/manual/gifs/tst_gifs.cpp @@ -0,0 +1,148 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/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 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later 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 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtTest> +#include <QtQuick> + +#include "gifrecorder.h" +#include "eventcapturer.h" + +//#define GENERATE_EVENT_CODE + +class tst_Gifs : public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + + void tumblerWrap(); + +private: + QQuickView view; + QString dataDirPath; + QDir outputDir; +}; + +void tst_Gifs::initTestCase() +{ + dataDirPath = QFINDTESTDATA("data"); + QVERIFY(!dataDirPath.isEmpty()); + qInfo() << "data directory:" << dataDirPath; + + outputDir = QDir(QDir::current().filePath("gifs")); + QVERIFY(outputDir.exists() || QDir::current().mkpath("gifs")); + qInfo() << "output directory:" << outputDir.absolutePath(); + + view.setFlags(view.flags() | Qt::FramelessWindowHint); +} + +void tst_Gifs::tumblerWrap() +{ + GifRecorder gifRecorder; + gifRecorder.setDataDirPath(dataDirPath); + gifRecorder.setOutputDir(outputDir); + gifRecorder.setRecordingDuration(4); + gifRecorder.setQmlFileName("qtquickextras2-tumbler-wrap.qml"); + gifRecorder.setOutputFileName("wrap.gif"); + gifRecorder.setView(&view); + + view.show(); + + gifRecorder.start(); + + // Left as an example. Usually EventCapturer code would be removed after + // the GIF has been generated. + EventCapturer eventCapturer; +#ifdef GENERATE_EVENT_CODE + eventCapturer.setMoveEventTrimFlags(EventCapturer::TrimAll); + eventCapturer.startCapturing(&view, 4000); +#else + QTest::mousePress(&view, Qt::LeftButton, Qt::NoModifier, QPoint(89, 75), 326); + QTest::mouseMove(&view, QPoint(89, 76), 31); + QTest::mouseMove(&view, QPoint(89, 80), 10); + QTest::mouseMove(&view, QPoint(93, 93), 10); + QTest::mouseMove(&view, QPoint(95, 101), 10); + QTest::mouseMove(&view, QPoint(97, 109), 11); + QTest::mouseMove(&view, QPoint(101, 125), 10); + QTest::mouseMove(&view, QPoint(103, 133), 11); + QTest::mouseMove(&view, QPoint(103, 141), 11); + QTest::mouseMove(&view, QPoint(105, 158), 10); + QTest::mouseMove(&view, QPoint(105, 162), 13); + QTest::mouseRelease(&view, Qt::LeftButton, Qt::NoModifier, QPoint(105, 162), 0); + QTest::mousePress(&view, Qt::LeftButton, Qt::NoModifier, QPoint(154, 130), 1098); + QTest::mouseMove(&view, QPoint(154, 129), 50); + QTest::mouseMove(&view, QPoint(153, 128), 0); + QTest::mouseMove(&view, QPoint(153, 125), 16); + QTest::mouseMove(&view, QPoint(152, 121), 0); + QTest::mouseMove(&view, QPoint(152, 117), 17); + QTest::mouseMove(&view, QPoint(151, 113), 0); + QTest::mouseMove(&view, QPoint(151, 106), 16); + QTest::mouseMove(&view, QPoint(150, 99), 1); + QTest::mouseMove(&view, QPoint(148, 93), 16); + QTest::mouseMove(&view, QPoint(148, 88), 0); + QTest::mouseMove(&view, QPoint(148, 84), 17); + QTest::mouseMove(&view, QPoint(147, 81), 0); + QTest::mouseRelease(&view, Qt::LeftButton, Qt::NoModifier, QPoint(147, 81), 0); + QTest::mousePress(&view, Qt::LeftButton, Qt::NoModifier, QPoint(147, 74), 550); + QTest::mouseMove(&view, QPoint(147, 75), 17); + QTest::mouseMove(&view, QPoint(147, 76), 17); + QTest::mouseMove(&view, QPoint(147, 80), 0); + QTest::mouseMove(&view, QPoint(148, 85), 16); + QTest::mouseMove(&view, QPoint(148, 92), 0); + QTest::mouseMove(&view, QPoint(148, 103), 17); + QTest::mouseMove(&view, QPoint(150, 119), 17); + QTest::mouseMove(&view, QPoint(151, 138), 16); + QTest::mouseMove(&view, QPoint(151, 145), 1); + QTest::mouseMove(&view, QPoint(153, 151), 16); + QTest::mouseMove(&view, QPoint(153, 157), 0); + QTest::mouseMove(&view, QPoint(153, 163), 17); + QTest::mouseMove(&view, QPoint(153, 167), 0); + QTest::mouseMove(&view, QPoint(155, 171), 17); + QTest::mouseMove(&view, QPoint(155, 175), 0); + QTest::mouseRelease(&view, Qt::LeftButton, Qt::NoModifier, QPoint(155, 175), 0); +#endif + + gifRecorder.waitForFinish(); + + foreach (CapturedEvent event, eventCapturer.capturedEvents()) + qDebug().noquote() << event.cppCommand(); + +} + +QTEST_MAIN(tst_Gifs) + +#include "tst_gifs.moc" diff --git a/tests/manual/manual.pro b/tests/manual/manual.pro new file mode 100644 index 00000000..787b99e1 --- /dev/null +++ b/tests/manual/manual.pro @@ -0,0 +1,3 @@ +TEMPLATE = subdirs +SUBDIRS += \ + gifs |