Files
tubestation/widget/windows/nsWinGesture.cpp
Carsten "Tomcat" Book 89becff57d Backed out 14 changesets (bug 1165515) for linux x64 e10s m2 test failures
Backed out changeset d68dcf2ef372 (bug 1165515)
Backed out changeset 7c3b45a47811 (bug 1165515)
Backed out changeset b668b617bef2 (bug 1165515)
Backed out changeset d0916e1283a2 (bug 1165515)
Backed out changeset ac4dc7489942 (bug 1165515)
Backed out changeset e9632ce8bc65 (bug 1165515)
Backed out changeset c16d215cc7e4 (bug 1165515)
Backed out changeset e4d474f3c51a (bug 1165515)
Backed out changeset d87680bf9f7c (bug 1165515)
Backed out changeset b3c0a45ba99e (bug 1165515)
Backed out changeset 9370fa197674 (bug 1165515)
Backed out changeset 50970d668ca1 (bug 1165515)
Backed out changeset ffa4eb6d24b9 (bug 1165515)
Backed out changeset 5fcf1203cc1d (bug 1165515)
2015-06-02 13:05:56 +02:00

594 lines
17 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/*
* nsWinGesture - Touch input handling for tablet displays.
*/
#include "nscore.h"
#include "nsWinGesture.h"
#include "nsUXThemeData.h"
#include "nsIDOMSimpleGestureEvent.h"
#include "nsIDOMWheelEvent.h"
#include "mozilla/Constants.h"
#include "mozilla/MouseEvents.h"
#include "mozilla/Preferences.h"
#include "mozilla/TouchEvents.h"
using namespace mozilla;
using namespace mozilla::widget;
extern PRLogModuleInfo* gWindowsLog;
const wchar_t nsWinGesture::kGestureLibraryName[] = L"user32.dll";
HMODULE nsWinGesture::sLibraryHandle = nullptr;
nsWinGesture::GetGestureInfoPtr nsWinGesture::getGestureInfo = nullptr;
nsWinGesture::CloseGestureInfoHandlePtr nsWinGesture::closeGestureInfoHandle = nullptr;
nsWinGesture::GetGestureExtraArgsPtr nsWinGesture::getGestureExtraArgs = nullptr;
nsWinGesture::SetGestureConfigPtr nsWinGesture::setGestureConfig = nullptr;
nsWinGesture::GetGestureConfigPtr nsWinGesture::getGestureConfig = nullptr;
nsWinGesture::BeginPanningFeedbackPtr nsWinGesture::beginPanningFeedback = nullptr;
nsWinGesture::EndPanningFeedbackPtr nsWinGesture::endPanningFeedback = nullptr;
nsWinGesture::UpdatePanningFeedbackPtr nsWinGesture::updatePanningFeedback = nullptr;
nsWinGesture::RegisterTouchWindowPtr nsWinGesture::registerTouchWindow = nullptr;
nsWinGesture::UnregisterTouchWindowPtr nsWinGesture::unregisterTouchWindow = nullptr;
nsWinGesture::GetTouchInputInfoPtr nsWinGesture::getTouchInputInfo = nullptr;
nsWinGesture::CloseTouchInputHandlePtr nsWinGesture::closeTouchInputHandle = nullptr;
static bool gEnableSingleFingerPanEvents = false;
nsWinGesture::nsWinGesture() :
mPanActive(false),
mFeedbackActive(false),
mXAxisFeedback(false),
mYAxisFeedback(false),
mPanInertiaActive(false)
{
(void)InitLibrary();
mPixelScrollOverflow = 0;
}
/* Load and shutdown */
bool nsWinGesture::InitLibrary()
{
if (getGestureInfo) {
return true;
} else if (sLibraryHandle) {
return false;
}
sLibraryHandle = ::LoadLibraryW(kGestureLibraryName);
HMODULE hTheme = nsUXThemeData::GetThemeDLL();
// gesture interfaces
if (sLibraryHandle) {
getGestureInfo = (GetGestureInfoPtr)GetProcAddress(sLibraryHandle, "GetGestureInfo");
closeGestureInfoHandle = (CloseGestureInfoHandlePtr)GetProcAddress(sLibraryHandle, "CloseGestureInfoHandle");
getGestureExtraArgs = (GetGestureExtraArgsPtr)GetProcAddress(sLibraryHandle, "GetGestureExtraArgs");
setGestureConfig = (SetGestureConfigPtr)GetProcAddress(sLibraryHandle, "SetGestureConfig");
getGestureConfig = (GetGestureConfigPtr)GetProcAddress(sLibraryHandle, "GetGestureConfig");
registerTouchWindow = (RegisterTouchWindowPtr)GetProcAddress(sLibraryHandle, "RegisterTouchWindow");
unregisterTouchWindow = (UnregisterTouchWindowPtr)GetProcAddress(sLibraryHandle, "UnregisterTouchWindow");
getTouchInputInfo = (GetTouchInputInfoPtr)GetProcAddress(sLibraryHandle, "GetTouchInputInfo");
closeTouchInputHandle = (CloseTouchInputHandlePtr)GetProcAddress(sLibraryHandle, "CloseTouchInputHandle");
}
if (!getGestureInfo || !closeGestureInfoHandle || !getGestureExtraArgs ||
!setGestureConfig || !getGestureConfig) {
getGestureInfo = nullptr;
closeGestureInfoHandle = nullptr;
getGestureExtraArgs = nullptr;
setGestureConfig = nullptr;
getGestureConfig = nullptr;
return false;
}
if (!registerTouchWindow || !unregisterTouchWindow || !getTouchInputInfo || !closeTouchInputHandle) {
registerTouchWindow = nullptr;
unregisterTouchWindow = nullptr;
getTouchInputInfo = nullptr;
closeTouchInputHandle = nullptr;
}
// panning feedback interfaces
if (hTheme) {
beginPanningFeedback = (BeginPanningFeedbackPtr)GetProcAddress(hTheme, "BeginPanningFeedback");
endPanningFeedback = (EndPanningFeedbackPtr)GetProcAddress(hTheme, "EndPanningFeedback");
updatePanningFeedback = (UpdatePanningFeedbackPtr)GetProcAddress(hTheme, "UpdatePanningFeedback");
}
if (!beginPanningFeedback || !endPanningFeedback || !updatePanningFeedback) {
beginPanningFeedback = nullptr;
endPanningFeedback = nullptr;
updatePanningFeedback = nullptr;
}
// Check to see if we want single finger gesture input. Only do this once
// for the app so we don't have to look it up on every window create.
gEnableSingleFingerPanEvents =
Preferences::GetBool("gestures.enable_single_finger_input", false);
return true;
}
#define GCOUNT 5
bool nsWinGesture::SetWinGestureSupport(HWND hWnd,
WidgetGestureNotifyEvent::ePanDirection aDirection)
{
if (!getGestureInfo)
return false;
GESTURECONFIG config[GCOUNT];
memset(&config, 0, sizeof(config));
config[0].dwID = GID_ZOOM;
config[0].dwWant = GC_ZOOM;
config[0].dwBlock = 0;
config[1].dwID = GID_ROTATE;
config[1].dwWant = GC_ROTATE;
config[1].dwBlock = 0;
config[2].dwID = GID_PAN;
config[2].dwWant = GC_PAN|GC_PAN_WITH_INERTIA|
GC_PAN_WITH_GUTTER;
config[2].dwBlock = GC_PAN_WITH_SINGLE_FINGER_VERTICALLY|
GC_PAN_WITH_SINGLE_FINGER_HORIZONTALLY;
if (gEnableSingleFingerPanEvents) {
if (aDirection == WidgetGestureNotifyEvent::ePanVertical ||
aDirection == WidgetGestureNotifyEvent::ePanBoth)
{
config[2].dwWant |= GC_PAN_WITH_SINGLE_FINGER_VERTICALLY;
config[2].dwBlock -= GC_PAN_WITH_SINGLE_FINGER_VERTICALLY;
}
if (aDirection == WidgetGestureNotifyEvent::ePanHorizontal ||
aDirection == WidgetGestureNotifyEvent::ePanBoth)
{
config[2].dwWant |= GC_PAN_WITH_SINGLE_FINGER_HORIZONTALLY;
config[2].dwBlock -= GC_PAN_WITH_SINGLE_FINGER_HORIZONTALLY;
}
}
config[3].dwWant = GC_TWOFINGERTAP;
config[3].dwID = GID_TWOFINGERTAP;
config[3].dwBlock = 0;
config[4].dwWant = GC_PRESSANDTAP;
config[4].dwID = GID_PRESSANDTAP;
config[4].dwBlock = 0;
return SetGestureConfig(hWnd, GCOUNT, (PGESTURECONFIG)&config);
}
/* Helpers */
bool nsWinGesture::IsAvailable()
{
return getGestureInfo != nullptr;
}
bool nsWinGesture::RegisterTouchWindow(HWND hWnd)
{
if (!registerTouchWindow)
return false;
return registerTouchWindow(hWnd, TWF_WANTPALM);
}
bool nsWinGesture::UnregisterTouchWindow(HWND hWnd)
{
if (!unregisterTouchWindow)
return false;
return unregisterTouchWindow(hWnd);
}
bool nsWinGesture::GetTouchInputInfo(HTOUCHINPUT hTouchInput, uint32_t cInputs, PTOUCHINPUT pInputs)
{
if (!getTouchInputInfo)
return false;
return getTouchInputInfo(hTouchInput, cInputs, pInputs, sizeof(TOUCHINPUT));
}
bool nsWinGesture::CloseTouchInputHandle(HTOUCHINPUT hTouchInput)
{
if (!closeTouchInputHandle)
return false;
return closeTouchInputHandle(hTouchInput);
}
bool nsWinGesture::GetGestureInfo(HGESTUREINFO hGestureInfo, PGESTUREINFO pGestureInfo)
{
if (!getGestureInfo || !hGestureInfo || !pGestureInfo)
return false;
ZeroMemory(pGestureInfo, sizeof(GESTUREINFO));
pGestureInfo->cbSize = sizeof(GESTUREINFO);
return getGestureInfo(hGestureInfo, pGestureInfo);
}
bool nsWinGesture::CloseGestureInfoHandle(HGESTUREINFO hGestureInfo)
{
if (!getGestureInfo || !hGestureInfo)
return false;
return closeGestureInfoHandle(hGestureInfo);
}
bool nsWinGesture::GetGestureExtraArgs(HGESTUREINFO hGestureInfo, UINT cbExtraArgs, PBYTE pExtraArgs)
{
if (!getGestureInfo || !hGestureInfo || !pExtraArgs)
return false;
return getGestureExtraArgs(hGestureInfo, cbExtraArgs, pExtraArgs);
}
bool nsWinGesture::SetGestureConfig(HWND hWnd, UINT cIDs, PGESTURECONFIG pGestureConfig)
{
if (!getGestureInfo || !pGestureConfig)
return false;
return setGestureConfig(hWnd, 0, cIDs, pGestureConfig, sizeof(GESTURECONFIG));
}
bool nsWinGesture::GetGestureConfig(HWND hWnd, DWORD dwFlags, PUINT pcIDs, PGESTURECONFIG pGestureConfig)
{
if (!getGestureInfo || !pGestureConfig)
return false;
return getGestureConfig(hWnd, 0, dwFlags, pcIDs, pGestureConfig, sizeof(GESTURECONFIG));
}
bool nsWinGesture::BeginPanningFeedback(HWND hWnd)
{
if (!beginPanningFeedback)
return false;
return beginPanningFeedback(hWnd);
}
bool nsWinGesture::EndPanningFeedback(HWND hWnd)
{
if (!beginPanningFeedback)
return false;
return endPanningFeedback(hWnd, TRUE);
}
bool nsWinGesture::UpdatePanningFeedback(HWND hWnd, LONG offsetX, LONG offsetY, BOOL fInInertia)
{
if (!beginPanningFeedback)
return false;
return updatePanningFeedback(hWnd, offsetX, offsetY, fInInertia);
}
bool nsWinGesture::IsPanEvent(LPARAM lParam)
{
GESTUREINFO gi;
ZeroMemory(&gi,sizeof(GESTUREINFO));
gi.cbSize = sizeof(GESTUREINFO);
BOOL result = GetGestureInfo((HGESTUREINFO)lParam, &gi);
if (!result)
return false;
if (gi.dwID == GID_PAN)
return true;
return false;
}
/* Gesture event processing */
bool
nsWinGesture::ProcessGestureMessage(HWND hWnd, WPARAM wParam, LPARAM lParam,
WidgetSimpleGestureEvent& evt)
{
GESTUREINFO gi;
ZeroMemory(&gi,sizeof(GESTUREINFO));
gi.cbSize = sizeof(GESTUREINFO);
BOOL result = GetGestureInfo((HGESTUREINFO)lParam, &gi);
if (!result)
return false;
// The coordinates of this event
nsPointWin coord;
coord = gi.ptsLocation;
coord.ScreenToClient(hWnd);
evt.refPoint.x = coord.x;
evt.refPoint.y = coord.y;
// Multiple gesture can occur at the same time so gesture state
// info can't be shared.
switch(gi.dwID)
{
case GID_BEGIN:
case GID_END:
// These should always fall through to DefWndProc
return false;
break;
case GID_ZOOM:
{
if (gi.dwFlags & GF_BEGIN) {
// Send a zoom start event
// The low 32 bits are the distance in pixels.
mZoomIntermediate = (float)gi.ullArguments;
evt.message = NS_SIMPLE_GESTURE_MAGNIFY_START;
evt.delta = 0.0;
}
else if (gi.dwFlags & GF_END) {
// Send a zoom end event, the delta is the change
// in touch points.
evt.message = NS_SIMPLE_GESTURE_MAGNIFY;
// (positive for a "zoom in")
evt.delta = -1.0 * (mZoomIntermediate - (float)gi.ullArguments);
mZoomIntermediate = (float)gi.ullArguments;
}
else {
// Send a zoom intermediate event, the delta is the change
// in touch points.
evt.message = NS_SIMPLE_GESTURE_MAGNIFY_UPDATE;
// (positive for a "zoom in")
evt.delta = -1.0 * (mZoomIntermediate - (float)gi.ullArguments);
mZoomIntermediate = (float)gi.ullArguments;
}
}
break;
case GID_ROTATE:
{
// Send a rotate start event
double radians = 0.0;
// On GF_BEGIN, ullArguments contains the absolute rotation at the
// start of the gesture. In later events it contains the offset from
// the start angle.
if (gi.ullArguments != 0)
radians = GID_ROTATE_ANGLE_FROM_ARGUMENT(gi.ullArguments);
double degrees = -1 * radians * (180/M_PI);
if (gi.dwFlags & GF_BEGIN) {
// At some point we should pass the initial angle in
// along with delta. It's useful.
degrees = mRotateIntermediate = 0.0;
}
evt.direction = 0;
evt.delta = degrees - mRotateIntermediate;
mRotateIntermediate = degrees;
if (evt.delta > 0)
evt.direction = nsIDOMSimpleGestureEvent::ROTATION_COUNTERCLOCKWISE;
else if (evt.delta < 0)
evt.direction = nsIDOMSimpleGestureEvent::ROTATION_CLOCKWISE;
if (gi.dwFlags & GF_BEGIN)
evt.message = NS_SIMPLE_GESTURE_ROTATE_START;
else if (gi.dwFlags & GF_END)
evt.message = NS_SIMPLE_GESTURE_ROTATE;
else
evt.message = NS_SIMPLE_GESTURE_ROTATE_UPDATE;
}
break;
case GID_TWOFINGERTAP:
{
// Normally maps to "restore" from whatever you may have recently changed. A simple
// double click.
evt.message = NS_SIMPLE_GESTURE_TAP;
evt.clickCount = 1;
}
break;
case GID_PRESSANDTAP:
{
// Two finger right click. Defaults to right click if it falls through.
evt.message = NS_SIMPLE_GESTURE_PRESSTAP;
evt.clickCount = 1;
}
break;
}
return true;
}
bool
nsWinGesture::ProcessPanMessage(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
GESTUREINFO gi;
ZeroMemory(&gi,sizeof(GESTUREINFO));
gi.cbSize = sizeof(GESTUREINFO);
BOOL result = GetGestureInfo((HGESTUREINFO)lParam, &gi);
if (!result)
return false;
// The coordinates of this event
nsPointWin coord;
coord = mPanRefPoint = gi.ptsLocation;
// We want screen coordinates in our local offsets as client coordinates will change
// when feedback is taking place. Gui events though require client coordinates.
mPanRefPoint.ScreenToClient(hWnd);
switch(gi.dwID)
{
case GID_BEGIN:
case GID_END:
// These should always fall through to DefWndProc
return false;
break;
// Setup pixel scroll events for both axis
case GID_PAN:
{
if (gi.dwFlags & GF_BEGIN) {
mPanIntermediate = coord;
mPixelScrollDelta = 0;
mPanActive = true;
mPanInertiaActive = false;
}
else {
#ifdef DBG_jimm
int32_t deltaX = mPanIntermediate.x - coord.x;
int32_t deltaY = mPanIntermediate.y - coord.y;
MOZ_LOG(gWindowsLog, PR_LOG_ALWAYS,
("coordX=%d coordY=%d deltaX=%d deltaY=%d x:%d y:%d\n", coord.x,
coord.y, deltaX, deltaY, mXAxisFeedback, mYAxisFeedback));
#endif
mPixelScrollDelta.x = mPanIntermediate.x - coord.x;
mPixelScrollDelta.y = mPanIntermediate.y - coord.y;
mPanIntermediate = coord;
if (gi.dwFlags & GF_INERTIA)
mPanInertiaActive = true;
if (gi.dwFlags & GF_END) {
mPanActive = false;
mPanInertiaActive = false;
PanFeedbackFinalize(hWnd, true);
}
}
}
break;
}
return true;
}
inline bool TestTransition(int32_t a, int32_t b)
{
// If a is zero, overflow is zero, implying the cursor has moved back to the start position.
// If b is zero, cached overscroll is zero, implying feedback just begun.
if (a == 0 || b == 0) return true;
// Test for different signs.
return (a < 0) == (b < 0);
}
void
nsWinGesture::UpdatePanFeedbackX(HWND hWnd, int32_t scrollOverflow, bool& endFeedback)
{
// If scroll overflow was returned indicating we panned past the bounds of
// the scrollable view port, start feeback.
if (scrollOverflow != 0) {
if (!mFeedbackActive) {
BeginPanningFeedback(hWnd);
mFeedbackActive = true;
}
endFeedback = false;
mXAxisFeedback = true;
return;
}
if (mXAxisFeedback) {
int32_t newOverflow = mPixelScrollOverflow.x - mPixelScrollDelta.x;
// Detect a reverse transition past the starting drag point. This tells us the user
// has panned all the way back so we can stop providing feedback for this axis.
if (!TestTransition(newOverflow, mPixelScrollOverflow.x) || newOverflow == 0)
return;
// Cache the total over scroll in pixels.
mPixelScrollOverflow.x = newOverflow;
endFeedback = false;
}
}
void
nsWinGesture::UpdatePanFeedbackY(HWND hWnd, int32_t scrollOverflow, bool& endFeedback)
{
// If scroll overflow was returned indicating we panned past the bounds of
// the scrollable view port, start feeback.
if (scrollOverflow != 0) {
if (!mFeedbackActive) {
BeginPanningFeedback(hWnd);
mFeedbackActive = true;
}
endFeedback = false;
mYAxisFeedback = true;
return;
}
if (mYAxisFeedback) {
int32_t newOverflow = mPixelScrollOverflow.y - mPixelScrollDelta.y;
// Detect a reverse transition past the starting drag point. This tells us the user
// has panned all the way back so we can stop providing feedback for this axis.
if (!TestTransition(newOverflow, mPixelScrollOverflow.y) || newOverflow == 0)
return;
// Cache the total over scroll in pixels.
mPixelScrollOverflow.y = newOverflow;
endFeedback = false;
}
}
void
nsWinGesture::PanFeedbackFinalize(HWND hWnd, bool endFeedback)
{
if (!mFeedbackActive)
return;
if (endFeedback) {
mFeedbackActive = false;
mXAxisFeedback = false;
mYAxisFeedback = false;
mPixelScrollOverflow = 0;
EndPanningFeedback(hWnd);
return;
}
UpdatePanningFeedback(hWnd, mPixelScrollOverflow.x, mPixelScrollOverflow.y, mPanInertiaActive);
}
bool
nsWinGesture::PanDeltaToPixelScroll(WidgetWheelEvent& aWheelEvent)
{
aWheelEvent.deltaX = aWheelEvent.deltaY = aWheelEvent.deltaZ = 0.0;
aWheelEvent.lineOrPageDeltaX = aWheelEvent.lineOrPageDeltaY = 0;
aWheelEvent.refPoint.x = mPanRefPoint.x;
aWheelEvent.refPoint.y = mPanRefPoint.y;
aWheelEvent.deltaMode = nsIDOMWheelEvent::DOM_DELTA_PIXEL;
aWheelEvent.scrollType = WidgetWheelEvent::SCROLL_SYNCHRONOUSLY;
aWheelEvent.mIsNoLineOrPageDelta = true;
aWheelEvent.overflowDeltaX = 0.0;
aWheelEvent.overflowDeltaY = 0.0;
// Don't scroll the view if we are currently at a bounds, or, if we are
// panning back from a max feedback position. This keeps the original drag point
// constant.
if (!mXAxisFeedback) {
aWheelEvent.deltaX = mPixelScrollDelta.x;
}
if (!mYAxisFeedback) {
aWheelEvent.deltaY = mPixelScrollDelta.y;
}
return (aWheelEvent.deltaX != 0 || aWheelEvent.deltaY != 0);
}