qt5base-lts/tests/manual/touch/main.cpp

614 lines
21 KiB
C++
Raw Normal View History

/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** 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 https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <QApplication>
#include <QAction>
#include <QCommandLineOption>
#include <QCommandLineParser>
#include <QDebug>
#include <QGesture>
#include <QLabel>
#include <QList>
#include <QMainWindow>
#include <QMenu>
#include <QMenuBar>
#include <QPainter>
#include <QPainterPath>
#include <QPaintEvent>
#include <QPlainTextEdit>
#include <QScreen>
#include <QSharedPointer>
#include <QSplitter>
#include <QStatusBar>
#include <QTextStream>
#include <QToolBar>
#include <QWindow>
static bool optIgnoreTouch = false;
static QList<Qt::GestureType> optGestures;
static QWidgetList mainWindows;
static inline void drawEllipse(const QPointF &center, qreal hDiameter, qreal vDiameter, const QColor &color, QPainter &painter)
{
const QPen oldPen = painter.pen();
QPen pen = oldPen;
pen.setColor(color);
painter.setPen(pen);
painter.drawEllipse(center, hDiameter / 2, vDiameter / 2);
painter.setPen(oldPen);
}
static inline void fillEllipse(const QPointF &center, qreal hDiameter, qreal vDiameter, const QColor &color, QPainter &painter)
{
QPainterPath painterPath;
painterPath.addEllipse(center, hDiameter / 2, vDiameter / 2);
painter.fillPath(painterPath, color);
}
// Draws an arrow assuming a mathematical coordinate system, Y axis pointing
// upwards, angle counterclockwise (that is, 45' is pointing up/right).
static void drawArrow(const QPointF &center, qreal length, qreal angleDegrees,
const QColor &color, int arrowSize, QPainter &painter)
{
painter.save();
painter.translate(center); // Transform center to (0,0) rotate and draw arrow pointing right.
painter.rotate(-angleDegrees);
QPen pen = painter.pen();
pen.setColor(color);
pen.setWidth(2);
painter.setPen(pen);
const QPointF endPoint(length, 0);
painter.drawLine(QPointF(0, 0), endPoint);
painter.drawLine(endPoint, endPoint + QPoint(-arrowSize, -arrowSize));
painter.drawLine(endPoint, endPoint + QPoint(-arrowSize, arrowSize));
painter.restore();
}
// Hierarchy of classes containing gesture parameters and drawing functionality.
class Gesture {
Q_DISABLE_COPY(Gesture)
public:
static Gesture *fromQGesture(const QWidget *w, const QGesture *source);
virtual ~Gesture() {}
virtual void draw(const QRectF &rect, QPainter &painter) const = 0;
protected:
explicit Gesture(const QWidget *w, const QGesture *source) : m_type(source->gestureType())
, m_hotSpot(w->mapFromGlobal(source->hotSpot().toPoint()))
, m_hasHotSpot(source->hasHotSpot()) {}
QPointF drawHotSpot(const QRectF &rect, QPainter &painter) const
{
const QPointF h = m_hasHotSpot ? m_hotSpot : rect.center();
painter.drawEllipse(h, 15, 15);
return h;
}
private:
Qt::GestureType m_type;
QPointF m_hotSpot;
bool m_hasHotSpot;
};
class PanGesture : public Gesture {
public:
explicit PanGesture(const QWidget *w, const QPanGesture *source) : Gesture(w, source)
, m_offset(source->offset()) {}
void draw(const QRectF &rect, QPainter &painter) const override
{
const QPointF hotSpot = drawHotSpot(rect, painter);
painter.drawLine(hotSpot, hotSpot + m_offset);
}
private:
QPointF m_offset;
};
class SwipeGesture : public Gesture {
public:
explicit SwipeGesture(const QWidget *w, const QSwipeGesture *source) : Gesture(w, source)
, m_horizontal(source->horizontalDirection()), m_vertical(source->verticalDirection())
, m_angle(source->swipeAngle()) {}
void draw(const QRectF &rect, QPainter &painter) const override;
private:
QSwipeGesture::SwipeDirection m_horizontal;
QSwipeGesture::SwipeDirection m_vertical;
qreal m_angle;
};
static qreal swipeDirectionAngle(QSwipeGesture::SwipeDirection d)
{
switch (d) {
case QSwipeGesture::NoDirection:
case QSwipeGesture::Right:
break;
case QSwipeGesture::Left:
return 180;
case QSwipeGesture::Up:
return 90;
case QSwipeGesture::Down:
return 270;
}
return 0;
}
void SwipeGesture::draw(const QRectF &rect, QPainter &painter) const
{
enum { arrowLength = 50, arrowHeadSize = 10 };
const QPointF hotSpot = drawHotSpot(rect, painter);
drawArrow(hotSpot, arrowLength, swipeDirectionAngle(m_horizontal), Qt::red, arrowHeadSize, painter);
drawArrow(hotSpot, arrowLength, swipeDirectionAngle(m_vertical), Qt::green, arrowHeadSize, painter);
drawArrow(hotSpot, arrowLength, m_angle, Qt::blue, arrowHeadSize, painter);
}
Gesture *Gesture::fromQGesture(const QWidget *w, const QGesture *source)
{
Gesture *result = nullptr;
switch (source->gestureType()) {
case Qt::TapGesture:
case Qt::TapAndHoldGesture:
case Qt::PanGesture:
result = new PanGesture(w, static_cast<const QPanGesture *>(source));
break;
case Qt::PinchGesture:
case Qt::CustomGesture:
case Qt::LastGestureType:
break;
case Qt::SwipeGesture:
result = new SwipeGesture(w, static_cast<const QSwipeGesture *>(source));
break;
}
return result;
}
typedef QSharedPointer<Gesture> GesturePtr;
typedef QList<GesturePtr> GesturePtrs;
typedef QList<QEvent::Type> EventTypeVector;
static EventTypeVector eventTypes;
class EventFilter : public QObject {
Q_OBJECT
public:
explicit EventFilter(const EventTypeVector &types, QObject *p) : QObject(p), m_types(types) {}
bool eventFilter(QObject *, QEvent *) override;
signals:
void eventReceived(const QString &);
private:
const EventTypeVector m_types;
};
static EventFilter *globalEventFilter = nullptr;
bool EventFilter::eventFilter(QObject *o, QEvent *e)
{
static int n = 0;
if (m_types.contains(e->type())) {
QString message;
QDebug debug(&message);
debug << '#' << n++ << ' ' << o->objectName() << ' ';
switch (e->type()) {
case QEvent::Gesture:
case QEvent::GestureOverride:
debug << static_cast<const QGestureEvent *>(e); // Special operator
break;
default:
debug << e;
break;
}
emit eventReceived(message);
}
return false;
}
enum PointType {
TouchPoint,
MousePress,
MouseRelease
};
struct Point
{
Point(const QPointF &p = QPoint(), PointType t = TouchPoint,
Qt::MouseEventSource s = Qt::MouseEventNotSynthesized, QSizeF diameters = QSizeF(4, 4)) :
pos(p), horizontalDiameter(qMax(2., diameters.width())),
verticalDiameter(qMax(2., diameters.height())), type(t), source(s) {}
QColor color() const;
QPointF pos;
qreal horizontalDiameter;
qreal verticalDiameter;
PointType type;
Qt::MouseEventSource source;
};
QColor Point::color() const
{
Qt::GlobalColor globalColor = Qt::black;
if (type != TouchPoint) {
switch (source) {
case Qt::MouseEventSynthesizedBySystem:
globalColor = Qt::red;
break;
case Qt::MouseEventSynthesizedByQt:
globalColor = Qt::blue;
break;
case Qt::MouseEventSynthesizedByApplication:
globalColor = Qt::green;
break;
case Qt::MouseEventNotSynthesized:
break;
}
}
const QColor result(globalColor);
return type == MousePress ? result.lighter() : result;
}
class TouchTestWidget : public QWidget {
Q_OBJECT
Q_PROPERTY(bool drawPoints READ drawPoints WRITE setDrawPoints)
public:
explicit TouchTestWidget(QWidget *parent = nullptr) : QWidget(parent), m_drawPoints(true)
{
setAttribute(Qt::WA_AcceptTouchEvents);
for (Qt::GestureType t : optGestures)
grabGesture(t);
}
bool drawPoints() const { return m_drawPoints; }
public slots:
void clearPoints();
void setDrawPoints(bool drawPoints);
signals:
void logMessage(const QString &);
protected:
bool event(QEvent *event) override;
void paintEvent(QPaintEvent *) override;
private:
void handleGestureEvent(QGestureEvent *gestureEvent);
QList<Point> m_points;
GesturePtrs m_gestures;
bool m_drawPoints;
};
void TouchTestWidget::clearPoints()
{
if (!m_points.isEmpty() || !m_gestures.isEmpty()) {
m_points.clear();
m_gestures.clear();
update();
}
}
void TouchTestWidget::setDrawPoints(bool drawPoints)
{
if (m_drawPoints != drawPoints) {
clearPoints();
m_drawPoints = drawPoints;
}
}
bool TouchTestWidget::event(QEvent *event)
{
const QEvent::Type type = event->type();
switch (type) {
case QEvent::MouseButtonPress:
case QEvent::MouseButtonRelease:
if (m_drawPoints) {
const QMouseEvent *me = static_cast<const QMouseEvent *>(event);
m_points.append(Point(me->localPos(),
type == QEvent::MouseButtonPress ? MousePress : MouseRelease,
me->source()));
update();
}
break;
case QEvent::TouchBegin:
case QEvent::TouchUpdate:
if (m_drawPoints) {
for (const QTouchEvent::TouchPoint &p : static_cast<const QTouchEvent *>(event)->touchPoints())
m_points.append(Point(p.pos(), TouchPoint, Qt::MouseEventNotSynthesized, p.ellipseDiameters()));
update();
}
Q_FALLTHROUGH();
case QEvent::TouchEnd:
if (optIgnoreTouch)
event->ignore();
else
event->accept();
return true;
case QEvent::Gesture:
handleGestureEvent(static_cast<QGestureEvent *>(event));
break;
default:
break;
}
return QWidget::event(event);
}
void TouchTestWidget::handleGestureEvent(QGestureEvent *gestureEvent)
{
const auto gestures = gestureEvent->gestures();
for (QGesture *gesture : gestures) {
if (optGestures.contains(gesture->gestureType())) {
switch (gesture->state()) {
case Qt::NoGesture:
break;
case Qt::GestureStarted:
case Qt::GestureUpdated:
gestureEvent->accept(gesture);
break;
case Qt::GestureFinished:
gestureEvent->accept(gesture);
if (Gesture *g = Gesture::fromQGesture(this, gesture)) {
m_gestures.append(GesturePtr(g));
update();
}
break;
case Qt::GestureCanceled:
emit logMessage(QLatin1String("=== Qt::GestureCanceled ==="));
break;
}
}
}
}
void TouchTestWidget::paintEvent(QPaintEvent *)
{
// Draw touch points as dots, mouse press as light filled circles, mouse release as circles.
QPainter painter(this);
const QRectF geom = QRectF(QPointF(0, 0), QSizeF(size()));
painter.fillRect(geom, Qt::white);
painter.drawRect(QRectF(geom.topLeft(), geom.bottomRight() - QPointF(1, 1)));
for (const Point &point : qAsConst(m_points)) {
if (geom.contains(point.pos)) {
if (point.type == MouseRelease)
drawEllipse(point.pos, point.horizontalDiameter, point.verticalDiameter, point.color(), painter);
else
fillEllipse(point.pos, point.horizontalDiameter, point.verticalDiameter, point.color(), painter);
}
}
for (const GesturePtr &gp : qAsConst(m_gestures))
gp->draw(geom, painter);
}
class MainWindow : public QMainWindow
{
Q_OBJECT
MainWindow();
public:
static MainWindow *createMainWindow();
QWidget *touchWidget() const { return m_touchWidget; }
void setVisible(bool visible) override;
public slots:
void appendToLog(const QString &text) { m_logTextEdit->appendPlainText(text); }
void dumpTouchDevices();
private:
void updateScreenLabel();
void newWindow() { MainWindow::createMainWindow(); }
TouchTestWidget *m_touchWidget;
QPlainTextEdit *m_logTextEdit;
QLabel *m_screenLabel;
};
MainWindow *MainWindow::createMainWindow()
{
MainWindow *result = new MainWindow;
const QSize screenSize = QGuiApplication::primaryScreen()->availableGeometry().size();
result->resize(screenSize / 2);
const QSize sizeDiff = screenSize - result->size();
const QPoint pos = QPoint(sizeDiff.width() / 2, sizeDiff.height() / 2)
+ mainWindows.size() * QPoint(30, 10);
result->move(pos);
result->show();
EventFilter *eventFilter = globalEventFilter;
if (!eventFilter) {
eventFilter = new EventFilter(eventTypes, result->touchWidget());
result->touchWidget()->installEventFilter(eventFilter);
}
QObject::connect(eventFilter, &EventFilter::eventReceived, result, &MainWindow::appendToLog);
mainWindows.append(result);
return result;
}
MainWindow::MainWindow()
: m_touchWidget(new TouchTestWidget)
, m_logTextEdit(new QPlainTextEdit)
, m_screenLabel(new QLabel)
{
QString title;
QTextStream(&title) << "Touch Event Tester " << QT_VERSION_STR << ' '
<< qApp->platformName() << " #" << (mainWindows.size() + 1);
setWindowTitle(title);
setObjectName("MainWin");
QToolBar *toolBar = new QToolBar(this);
addToolBar(Qt::TopToolBarArea, toolBar);
QMenu *fileMenu = menuBar()->addMenu("File");
QAction *newWindowAction = fileMenu->addAction(QStringLiteral("New Window"), this, &MainWindow::newWindow);
newWindowAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_N));
toolBar->addAction(newWindowAction);
fileMenu->addSeparator();
QAction *dumpDeviceAction = fileMenu->addAction(QStringLiteral("Dump devices"));
dumpDeviceAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_D));
connect(dumpDeviceAction, &QAction::triggered, this, &MainWindow::dumpTouchDevices);
toolBar->addAction(dumpDeviceAction);
QAction *clearLogAction = fileMenu->addAction(QStringLiteral("Clear Log"));
clearLogAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_L));
connect(clearLogAction, &QAction::triggered, m_logTextEdit, &QPlainTextEdit::clear);
toolBar->addAction(clearLogAction);
QAction *toggleDrawPointAction = fileMenu->addAction(QStringLiteral("Draw Points"));
toggleDrawPointAction->setCheckable(true);
toggleDrawPointAction->setChecked(m_touchWidget->drawPoints());
connect(toggleDrawPointAction, &QAction::toggled, m_touchWidget, &TouchTestWidget::setDrawPoints);
toolBar->addAction(toggleDrawPointAction);
QAction *clearPointAction = fileMenu->addAction(QStringLiteral("Clear Points"));
clearPointAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_P));
connect(clearPointAction, &QAction::triggered, m_touchWidget, &TouchTestWidget::clearPoints);
toolBar->addAction(clearPointAction);
QAction *quitAction = fileMenu->addAction(QStringLiteral("Quit"));
quitAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_Q));
connect(quitAction, &QAction::triggered, qApp, &QCoreApplication::quit);
toolBar->addAction(quitAction);
QSplitter *mainSplitter = new QSplitter(Qt::Vertical, this);
m_touchWidget->setObjectName(QStringLiteral("TouchWidget"));
mainSplitter->addWidget(m_touchWidget);
connect(m_touchWidget, &TouchTestWidget::logMessage, this, &MainWindow::appendToLog);
m_logTextEdit->setObjectName(QStringLiteral("LogTextEdit"));
mainSplitter->addWidget(m_logTextEdit);
setCentralWidget(mainSplitter);
statusBar()->addPermanentWidget(m_screenLabel);
dumpTouchDevices();
}
void MainWindow::setVisible(bool visible)
{
QMainWindow::setVisible(visible);
connect(windowHandle(), &QWindow::screenChanged, this, &MainWindow::updateScreenLabel);
updateScreenLabel();
}
void MainWindow::updateScreenLabel()
{
QString text;
QTextStream str(&text);
const QScreen *screen = windowHandle()->screen();
const QRect geometry = screen->geometry();
const qreal dpr = screen->devicePixelRatio();
str << '"' << screen->name() << "\" " << geometry.width() << 'x' << geometry.height()
<< Qt::forcesign << geometry.x() << geometry.y() << Qt::noforcesign;
if (!qFuzzyCompare(dpr, qreal(1)))
str << ", dpr=" << dpr;
m_screenLabel->setText(text);
}
void MainWindow::dumpTouchDevices()
{
QString message;
QDebug debug(&message);
Introduce QInputDevice hierarchy; replace QTouchDevice We have seen during the Qt 5 series that QMouseEvent::source() does not provide enough information: if it is synthesized, it could have come from any device for which mouse events are synthesized, not only from a touchscreen. By providing in every QInputEvent as complete information about the actual source device as possible, we will enable very fine-tuned behavior in the object that handles each event. Further, we would like to support multiple keyboards, pointing devices, and named groups of devices that are known as "seats" in Wayland. In Qt 5, QPA plugins registered each touchscreen as it was discovered. Now we extend this pattern to all input devices. This new requirement can be implemented gradually; for now, if a QTWSI input event is received wtihout a device pointer, a default "core" device will be created on-the-fly, and a warning emitted. In Qt 5, QTouchEvent::TouchPoint::id() was forced to be unique even when multiple devices were in use simultaneously. Now that each event identifies the device it came from, this hack is no longer needed. A stub of the new QPointerEvent is added; it will be developed further in subsequent patches. [ChangeLog][QtGui][QInputEvent] Every QInputEvent now carries a pointer to an instance of QInputDevice, or the subclass QPointingDevice in case of mouse, touch and tablet events. Each platform plugin is expected to create the device instances, register them, and provide valid pointers with all input events. If this is not done, warnings are emitted and default devices are created as necessary. When the device has accurate information, it provides the opportunity to fine-tune behavior depending on device type and capabilities: for example if a QMouseEvent is synthesized from a touchscreen, the recipient can see which touchscreen it came from. Each device also has a seatName to distinguish users on multi-user windowing systems. Touchpoint IDs are no longer unique on their own, but the combination of ID and device is. Fixes: QTBUG-46412 Fixes: QTBUG-72167 Task-number: QTBUG-69433 Task-number: QTBUG-52430 Change-Id: I933fb2b86182efa722037b7a33e404c5daf5292a Reviewed-by: Shawn Rutledge <shawn.rutledge@qt.io>
2019-05-31 06:38:16 +00:00
const QList<const QPointingDevice *> devices = QPointingDevice::devices();
debug << devices.size() << "Device(s):\n";
for (int i = 0; i < devices.size(); ++i)
debug << "Device #" << i << devices.at(i) << '\n';
appendToLog(message);
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QCommandLineParser parser;
parser.setApplicationDescription(QStringLiteral("Touch/Mouse tester"));
parser.addHelpOption();
const QCommandLineOption mouseMoveOption(QStringLiteral("mousemove"),
QStringLiteral("Log mouse move events"));
parser.addOption(mouseMoveOption);
const QCommandLineOption globalFilterOption(QStringLiteral("global"),
QStringLiteral("Global event filter"));
parser.addOption(globalFilterOption);
const QCommandLineOption ignoreTouchOption(QStringLiteral("ignore"),
QStringLiteral("Ignore touch events (for testing mouse emulation)."));
parser.addOption(ignoreTouchOption);
const QCommandLineOption noTouchLogOption(QStringLiteral("notouchlog"),
QStringLiteral("Do not log touch events (for testing gestures)."));
parser.addOption(noTouchLogOption);
const QCommandLineOption noMouseLogOption(QStringLiteral("nomouselog"),
QStringLiteral("Do not log mouse events (for testing gestures)."));
parser.addOption(noMouseLogOption);
const QCommandLineOption tapGestureOption(QStringLiteral("tap"), QStringLiteral("Grab tap gesture."));
parser.addOption(tapGestureOption);
const QCommandLineOption tapAndHoldGestureOption(QStringLiteral("tap-and-hold"),
QStringLiteral("Grab tap-and-hold gesture."));
parser.addOption(tapAndHoldGestureOption);
const QCommandLineOption panGestureOption(QStringLiteral("pan"), QStringLiteral("Grab pan gesture."));
parser.addOption(panGestureOption);
const QCommandLineOption pinchGestureOption(QStringLiteral("pinch"), QStringLiteral("Grab pinch gesture."));
parser.addOption(pinchGestureOption);
const QCommandLineOption swipeGestureOption(QStringLiteral("swipe"), QStringLiteral("Grab swipe gesture."));
parser.addOption(swipeGestureOption);
parser.process(QApplication::arguments());
optIgnoreTouch = parser.isSet(ignoreTouchOption);
if (parser.isSet(tapGestureOption))
optGestures.append(Qt::TapGesture);
if (parser.isSet(tapAndHoldGestureOption))
optGestures.append(Qt::TapAndHoldGesture);
if (parser.isSet(panGestureOption))
optGestures.append(Qt::PanGesture);
if (parser.isSet(pinchGestureOption))
optGestures.append(Qt::PinchGesture);
if (parser.isSet(swipeGestureOption))
optGestures.append(Qt::SwipeGesture);
if (!parser.isSet(noMouseLogOption))
eventTypes << QEvent::MouseButtonPress << QEvent::MouseButtonRelease << QEvent::MouseButtonDblClick;
if (parser.isSet(mouseMoveOption))
eventTypes << QEvent::MouseMove;
if (!parser.isSet(noTouchLogOption))
eventTypes << QEvent::TouchBegin << QEvent::TouchUpdate << QEvent::TouchEnd;
if (!optGestures.isEmpty())
eventTypes << QEvent::Gesture << QEvent::GestureOverride;
if (parser.isSet(globalFilterOption)) {
globalEventFilter = new EventFilter(eventTypes, &a);
a.installEventFilter(globalEventFilter);
}
MainWindow::createMainWindow();
const int exitCode = a.exec();
qDeleteAll(mainWindows);
return exitCode;
}
#include "main.moc"