/**************************************************************************** ** ** 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 #include #include #include /*! 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 EventCapturer::capturedEventTypes() { return mCapturedEventTypes; } void EventCapturer::setCapturedEventTypes(QSet types) { mCapturedEventTypes = types; } QList 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. :) QList 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(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().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(); }