7feeb7c34b
The initial approach for providing public access to native interfaces via T::nativeInteface<I>() was based on the template not being defined, and then having explicit instantiations of the supported types in a source file, so that the accessors were exported and available to the user. This worked fine for "simple" types such as QOpenGLContext and QOffscreenSurface, but presented a problem in the context of classes with subclasses, such as Q{Core,Gui}Application. To ensure that a native interface for QCoreApplication was accessible both from QCoreApplication and its subclasses, while at the same time preventing a native interface for QGuiApplication to be accessible for QCoreApplication, the nativeInterface() template function had to be declared in each subclass. Which in turn meant specializing each native interface once for each subclass it was available in. This quickly became tedious to manage, and the requirements for exposing a new native interface wasn't very clear with all these template specializations and explicit instantiations spread around. To improve on this situation, while also squashing a few other birds at the same time, we change the approach to use type erasure. The definition of T::nativeInteface<I>() is now inline, passing on the requested interface to a per type (T, not I) helper function, with the interface type flattened to a std::type_info. The type_info requested by the user is then compared to the available types in a single per-type (T) "switch statement", which is a lot easier to follow for someone trying to trace the logic of how a native interface is resolved. We can safely rely on type_info being stable between the user application and the Qt library as a result of exporting the type info for each native interface, by explicitly ensuring they have a key function. This is the same mechanism that ensures we can safely dynamic_cast these interfaces, even across library boundaries. The use of a free standing templated helper function instead of a member function in the type T, is to avoid shadowing issues, and to not pollute the class namespace of T with the helper function. Since we are already changing the plumbing for how a user resolves a native interface for a type T, we take the opportunity to add a few extra safeguards to the machinery. First, we add a static assert in the T::nativeInteface<I>() definition, that ensures that only compatible interfaces, as declared by the interface themselves, are allowed. This ensures a compile time error when an incompatible interface is requested, which improves on the link time errors we had prior to this patch, and also offsets the one downside of type erasure, namely that errors are only caught at runtime. Secondly, each interface meant for public consumption through T::nativeInteface<I>() is declared with a revision, which is checked when requesting the interface. This allows us to bump the revision when we make breaking changes to the interface that would have otherwise been binary incompatible. Since the user will never see this interface due to the revision check, they will not end up calling methods that have been removed or renamed. One advantage of moving to a type-erased approach for the plumbing is that we're not longer exposing the native interface types as part of the T::nativeInteface symbols. This means that if we ever want to rename a native interface, the only exported symbol that the user code relies on is the type info. Renaming is then possible by just exporting the type info for the old interface, but leaving it empty. Since no class in Qt implements the old native interface, the user will just get a nullptr back, similarly to bumping the revision of an interface. Change-Id: Ie50d8fb536aafe2836370caacb22afbcfaf1712a Reviewed-by: Friedemann Kleint <Friedemann.Kleint@qt.io>
698 lines
23 KiB
C++
698 lines
23 KiB
C++
/****************************************************************************
|
|
**
|
|
** 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>
|
|
|
|
#ifdef Q_OS_WIN
|
|
# include <QCheckBox>
|
|
# include <QDialog>
|
|
# include <QDialogButtonBox>
|
|
# include <QVBoxLayout>
|
|
# include <QtGui/private/qguiapplication_p.h>
|
|
# include <QtGui/qpa/qplatformintegration.h>
|
|
#endif
|
|
|
|
static bool optIgnoreTouch = false;
|
|
static QList<Qt::GestureType> optGestures;
|
|
|
|
static QWidgetList mainWindows;
|
|
|
|
static inline void drawEllipse(const QPointF ¢er, 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 ¢er, 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 ¢er, 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->position(),
|
|
type == QEvent::MouseButtonPress ? MousePress : MouseRelease,
|
|
me->source()));
|
|
update();
|
|
}
|
|
break;
|
|
case QEvent::TouchBegin:
|
|
case QEvent::TouchUpdate:
|
|
if (m_drawPoints) {
|
|
for (const QEventPoint &p : static_cast<const QPointerEvent *>(event)->points())
|
|
m_points.append(Point(p.position(), 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);
|
|
}
|
|
|
|
#ifdef Q_OS_WIN
|
|
class SettingsDialog : public QDialog
|
|
{
|
|
Q_OBJECT
|
|
public:
|
|
explicit SettingsDialog(QWidget *parent);
|
|
|
|
private slots:
|
|
void touchTypeToggled();
|
|
|
|
private:
|
|
using QWindowsApplication = QNativeInterface::Private::QWindowsApplication;
|
|
using TouchWindowTouchType = QWindowsApplication::TouchWindowTouchType;
|
|
using TouchWindowTouchTypes = QWindowsApplication::QWindowsApplication::TouchWindowTouchTypes;
|
|
|
|
QCheckBox *m_fineCheckBox;
|
|
QCheckBox *m_palmCheckBox;
|
|
};
|
|
|
|
SettingsDialog::SettingsDialog(QWidget *parent) : QDialog(parent)
|
|
{
|
|
setWindowTitle("Settings");
|
|
auto layout = new QVBoxLayout(this);
|
|
|
|
TouchWindowTouchTypes touchTypes;
|
|
if (auto nativeWindowsApp = qGuiApp->nativeInterface<QWindowsApplication>())
|
|
touchTypes = nativeWindowsApp->touchWindowTouchType();
|
|
|
|
m_fineCheckBox = new QCheckBox("Fine Touch", this);
|
|
m_fineCheckBox->setChecked(touchTypes.testFlag(TouchWindowTouchType::FineTouch));
|
|
layout->addWidget(m_fineCheckBox);
|
|
connect(m_fineCheckBox, &QAbstractButton::toggled, this, &SettingsDialog::touchTypeToggled);
|
|
m_palmCheckBox = new QCheckBox("Palm Touch", this);
|
|
connect(m_palmCheckBox, &QAbstractButton::toggled, this, &SettingsDialog::touchTypeToggled);
|
|
m_palmCheckBox->setChecked(touchTypes.testFlag(TouchWindowTouchType::WantPalmTouch));
|
|
layout->addWidget(m_palmCheckBox);
|
|
|
|
auto box = new QDialogButtonBox(QDialogButtonBox::Close);
|
|
connect(box, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
|
layout->addWidget(box);
|
|
}
|
|
|
|
void SettingsDialog::touchTypeToggled()
|
|
{
|
|
TouchWindowTouchTypes types;
|
|
if (m_fineCheckBox->isChecked())
|
|
types.setFlag(TouchWindowTouchType::FineTouch);
|
|
if (m_palmCheckBox->isChecked())
|
|
types.setFlag(TouchWindowTouchType::WantPalmTouch);
|
|
if (auto nativeWindowsApp = qGuiApp->nativeInterface<QWindowsApplication>())
|
|
nativeWindowsApp->setTouchWindowTouchType(types);
|
|
else
|
|
qWarning("Missing Interface QWindowsApplication");
|
|
}
|
|
#endif // Q_OS_WIN
|
|
|
|
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 slots:
|
|
void settingsDialog();
|
|
|
|
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);
|
|
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);
|
|
|
|
auto settingsMenu = menuBar()->addMenu("Settings");
|
|
auto settingsAction = settingsMenu->addAction("Settings",
|
|
this, &MainWindow::settingsDialog);
|
|
#ifdef Q_OS_WIN
|
|
Q_UNUSED(settingsAction);
|
|
#else
|
|
settingsAction->setEnabled(false);
|
|
#endif
|
|
|
|
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::settingsDialog()
|
|
{
|
|
#ifdef Q_OS_WIN
|
|
SettingsDialog dialog(this);
|
|
dialog.exec();
|
|
#endif
|
|
}
|
|
|
|
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);
|
|
const auto 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"
|