blob: b3bbd7c83dbf1bf545d9bbc4d7276297c5b437e4 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2017 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: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";
const auto capturedEvents = eventCapturer.capturedEvents();
for (CapturedEvent event : 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(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)
{
qDebug() << "captured" << event->type();
CapturedEvent capturedEvent(*event, mDelayTimer.elapsed() - mLastCaptureTime);
mEvents.append(capturedEvent);
mLastCaptureTime = mDelayTimer.elapsed();
}