From 0103e7871000f224a1048649b4eca18a7840fe3b Mon Sep 17 00:00:00 2001 From: "Bradley T. Hughes" Date: Wed, 25 Mar 2009 13:30:38 +0100 Subject: initial implementation of multitouch support on Windows 7 WM_TOUCH* messages are translated to QTouchEvents and sent to the appropriate widget. we set the qt_tabletChokeMouse variable according to the result of the event processing to make sure that mouse events are sent when touch events are not handled. --- src/gui/kernel/qapplication_p.h | 27 ++++- src/gui/kernel/qapplication_win.cpp | 192 ++++++++++++++++++++++++++++++++++++ src/gui/kernel/qwidget_win.cpp | 4 + 3 files changed, 220 insertions(+), 3 deletions(-) diff --git a/src/gui/kernel/qapplication_p.h b/src/gui/kernel/qapplication_p.h index 252d0cb..ccd9f41 100644 --- a/src/gui/kernel/qapplication_p.h +++ b/src/gui/kernel/qapplication_p.h @@ -55,6 +55,7 @@ // #include "QtGui/qapplication.h" +#include "QtGui/qevent.h" #include "QtGui/qfont.h" #include "QtGui/qcursor.h" #include "QtGui/qregion.h" @@ -77,10 +78,7 @@ class QClipboard; class QGraphicsScene; class QGraphicsSystem; class QInputContext; -class QKeyEvent; -class QMouseEvent; class QObject; -class QWheelEvent; class QWidget; extern bool qt_is_gui_used; @@ -189,6 +187,12 @@ extern "C" { } #endif +#if defined(Q_WS_WIN) +typedef BOOL (WINAPI *qt_RegisterTouchWindowPtr)(HWND, ULONG); +typedef BOOL (WINAPI *qt_GetTouchInputInfoPtr)(HANDLE, UINT, PVOID, int); +typedef BOOL (WINAPI *qt_CloseTouchInputHandlePtr)(HANDLE); +#endif + class QScopedLoopLevelCounter { QThreadData *threadData; @@ -427,6 +431,23 @@ public: QPointer currentMultitouchWidget; static void updateTouchPointsForWidget(QWidget *widget, QTouchEvent *touchEvent); +#if defined(Q_WS_WIN) + static qt_RegisterTouchWindowPtr RegisterTouchWindow; + static qt_GetTouchInputInfoPtr GetTouchInputInfo; + static qt_CloseTouchInputHandlePtr CloseTouchInputHandle; + + QMap touchInputIDToTouchPointID; + QVector allTouchPoints; + QList currentTouchPoints, activeTouchPoints; + + static bool sendTouchEvent(QWidget *widget, QTouchEvent *touchEvent); + + void initializeMultitouch(); + void insertActiveTouch(QTouchEvent::TouchPoint *touchPoint); + void removeActiveTouch(QTouchEvent::TouchPoint *touchPoint); + bool translateTouchEvent(const MSG &msg); +#endif + private: #ifdef Q_WS_QWS QMap maxWindowRects; diff --git a/src/gui/kernel/qapplication_win.cpp b/src/gui/kernel/qapplication_win.cpp index fae0335..7c7f7f6 100644 --- a/src/gui/kernel/qapplication_win.cpp +++ b/src/gui/kernel/qapplication_win.cpp @@ -88,6 +88,7 @@ extern void qt_wince_hide_taskbar(HWND hwnd); //defined in qguifunctions_wince.c #include "qdebug.h" #include #include +#include "qevent_p.h" //#define ALIEN_DEBUG @@ -112,6 +113,39 @@ extern void qt_wince_hide_taskbar(HWND hwnd); //defined in qguifunctions_wince.c # include #endif +#ifndef WM_TOUCHMOVE + +# define WM_TOUCHMOVE 0x0240 +# define WM_TOUCHDOWN 0x0241 +# define WM_TOUCHUP 0x0242 + +# define TOUCHEVENTF_MOVE 0x0001 +# define TOUCHEVENTF_DOWN 0x0002 +# define TOUCHEVENTF_UP 0x0004 +# define TOUCHEVENTF_INRANGE 0x0008 +# define TOUCHEVENTF_PRIMARY 0x0010 +# define TOUCHEVENTF_NOCOALESCE 0x0020 +# define TOUCHEVENTF_PEN 0x0040 + +# define TOUCHINPUTMASKF_TIMEFROMSYSTEM 0x0001 +# define TOUCHINPUTMASKF_EXTRAINFO 0x0002 +# define TOUCHINPUTMASKF_CONTACTAREA 0x0004 + +typedef struct tagTOUCHINPUT +{ + LONG x; + LONG y; + HANDLE hSource; + DWORD dwID; + DWORD dwFlags; + DWORD dwMask; + DWORD dwTime; + ULONG_PTR dwExtraInfo; + DWORD cxContact; + DWORD cyContact; +} TOUCHINPUT, *PTOUCHINPUT; + +#endif #ifndef FLASHW_STOP typedef struct { @@ -856,6 +890,8 @@ void qt_init(QApplicationPrivate *priv, int) if (ptrUpdateLayeredWindow && !ptrUpdateLayeredWindowIndirect) ptrUpdateLayeredWindowIndirect = qt_updateLayeredWindowIndirect; #endif + + priv->initializeMultitouch(); } /***************************************************************************** @@ -1726,6 +1762,11 @@ LRESULT CALLBACK QtWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam result = widget->translateWheelEvent(msg); } else { switch (message) { + case WM_TOUCHMOVE: + case WM_TOUCHDOWN: + case WM_TOUCHUP: + result = getQApplicationPrivateInternal()->translateTouchEvent(msg); + break; case WM_KEYDOWN: // keyboard event case WM_SYSKEYDOWN: qt_keymapper_private()->updateKeyMap(msg); @@ -3953,4 +3994,155 @@ void QSessionManager::cancel() #endif //QT_NO_SESSIONMANAGER +qt_RegisterTouchWindowPtr QApplicationPrivate::RegisterTouchWindow = 0; +qt_GetTouchInputInfoPtr QApplicationPrivate::GetTouchInputInfo = 0; +qt_CloseTouchInputHandlePtr QApplicationPrivate::CloseTouchInputHandle = 0; + +void QApplicationPrivate::initializeMultitouch() +{ + QLibrary library(QLatin1String("user32")); + RegisterTouchWindow = static_cast(library.resolve("RegisterTouchWindow")); + GetTouchInputInfo = static_cast(library.resolve("GetTouchInputInfo")); + CloseTouchInputHandle = static_cast(library.resolve("CloseTouchInputHandle")); + + currentMultitouchWidget = 0; + touchInputIDToTouchPointID.clear(); + allTouchPoints.clear(); + currentTouchPoints.clear(); + activeTouchPoints.clear(); +} + +void QApplicationPrivate::insertActiveTouch(QTouchEvent::TouchPoint *touchPoint) +{ + // insort deviceNumber + int at = 0; + for (; at < currentTouchPoints.count(); ++at) { + if (currentTouchPoints.at(at)->id() > touchPoint->id()) + break; + } + currentTouchPoints.insert(at, touchPoint); + activeTouchPoints = currentTouchPoints; + + if (currentTouchPoints.count() > allTouchPoints.count()) { + qFatal("Qt: INTERNAL ERROR: currentTouchPoints.count() (%d) > allTouchPoints.count() (%d)", + currentTouchPoints.count(), + allTouchPoints.count()); + } +} + +void QApplicationPrivate::removeActiveTouch(QTouchEvent::TouchPoint *touchPoint) +{ + for (int i = qMin(currentTouchPoints.count() - 1, touchPoint->id()); i >= 0; --i) { + if (currentTouchPoints.at(i) == touchPoint) { + currentTouchPoints.removeAt(i); + break; + } + } + + // leave activeTouchPoints unchanged, since we need to make sure + // that the Release for deviceNumber is sent +} + +bool QApplicationPrivate::translateTouchEvent(const MSG &msg) +{ + Q_Q(QApplication); + + bool sendTouchBegin = currentTouchPoints.isEmpty(); + activeTouchPoints = currentTouchPoints; + + QVector winTouchInputs(msg.wParam); + memset(winTouchInputs.data(), 0, sizeof(TOUCHINPUT) * winTouchInputs.count()); + QApplicationPrivate::GetTouchInputInfo((HANDLE) msg.lParam, msg.wParam, winTouchInputs.data(), sizeof(TOUCHINPUT)); + for (int i = 0; i < winTouchInputs.count(); ++i) { + const TOUCHINPUT &touchInput = winTouchInputs.at(i); + + int touchPointID = touchInputIDToTouchPointID.value(touchInput.dwID, -1); + if (touchPointID == -1) { + touchPointID = touchInputIDToTouchPointID.count(); + touchInputIDToTouchPointID.insert(touchInput.dwID, touchPointID); + } + + if (allTouchPoints.count() <= touchPointID) + allTouchPoints.resize(touchPointID + 1); + + QTouchEvent::TouchPoint *touchPoint = allTouchPoints.at(touchPointID); + if (!touchPoint) + touchPoint = allTouchPoints[touchPointID] = new QTouchEvent::TouchPoint(touchPointID); + + // update state + bool down = touchPoint->d->state != Qt::TouchPointReleased; + QPointF globalPos(qreal(touchInput.x) / qreal(100.), qreal(touchInput.y) / qreal(100.)); + if (!down && (touchInput.dwFlags & TOUCHEVENTF_DOWN)) { + insertActiveTouch(touchPoint); + touchPoint->d->state = Qt::TouchPointPressed; + touchPoint->d->globalPos = touchPoint->d->startGlobalPos = touchPoint->d->lastGlobalPos = globalPos; + touchPoint->d->pressure = qreal(1.); + } else if (down && (touchInput.dwFlags & TOUCHEVENTF_UP)) { + removeActiveTouch(touchPoint); + touchPoint->d->state = Qt::TouchPointReleased; + touchPoint->d->lastGlobalPos = touchPoint->d->globalPos; + touchPoint->d->globalPos = globalPos; + touchPoint->d->pressure = qreal(0.); + } else if (down) { + touchPoint->d->state = globalPos == touchPoint->d->globalPos + ? Qt::TouchPointStationary + : Qt::TouchPointMoved; + touchPoint->d->lastGlobalPos = touchPoint->d->globalPos; + touchPoint->d->globalPos = globalPos; + // pressure should still be 1. + } + } + QApplicationPrivate::CloseTouchInputHandle((HANDLE) msg.lParam); + + bool sendTouchEnd = currentTouchPoints.isEmpty(); + + if (activeTouchPoints.isEmpty()) + return false; + + if (sendTouchBegin) { + // the window under the first touch point gets the touch event + int firstTouchId = activeTouchPoints.first()->id(); + const QPoint &globalPos = allTouchPoints.at(firstTouchId)->d->globalPos.toPoint(); + QWidget *window = q->topLevelAt(globalPos); + if (window) { + QWidget *child = window->childAt(window->mapFromGlobal(globalPos)); + if (!child) + child = window; + currentMultitouchWidget = child; + } + } + + QWidget *widget = q->activePopupWidget(); + if (!widget) + widget = currentMultitouchWidget; + + if (sendTouchEnd) { + // reset currentMultitouchWindow when the last touch is released + currentMultitouchWidget = 0; + if (!currentTouchPoints.isEmpty()) { + qFatal("Qt: INTERNAL ERROR, currentTouchPoints should be empty!"); + } + } + + // deliver the event + if (widget && QApplicationPrivate::tryModalHelper(widget, 0)) { + QTouchEvent touchEvent((sendTouchBegin + ? QEvent::TouchBegin + : sendTouchEnd + ? QEvent::TouchEnd + : QEvent::TouchUpdate), + q->keyboardModifiers(), activeTouchPoints); + updateTouchPointsForWidget(widget, &touchEvent); + return (qt_tabletChokeMouse = sendTouchEvent(widget, &touchEvent)); + } + + return false; +} + +bool QApplicationPrivate::sendTouchEvent(QWidget *widget, QTouchEvent *touchEvent) +{ + bool res = QApplication::sendSpontaneousEvent(widget, touchEvent); + return res && touchEvent->isAccepted(); +} + QT_END_NAMESPACE diff --git a/src/gui/kernel/qwidget_win.cpp b/src/gui/kernel/qwidget_win.cpp index ffbb341..b55f1c8 100644 --- a/src/gui/kernel/qwidget_win.cpp +++ b/src/gui/kernel/qwidget_win.cpp @@ -503,6 +503,10 @@ void QWidgetPrivate::create_sys(WId window, bool initializeWindow, bool destroyO } } + // ### don't always register for touch events + if (QApplicationPrivate::RegisterTouchWindow && !desktop) + QApplicationPrivate::RegisterTouchWindow(id, 0); + q->setAttribute(Qt::WA_WState_Created); // accept move/resize events hd = 0; // no display context -- cgit v0.12