Bug 503541 - Fine grained control of gesture registration on widgets (Win7). r=smaug+roc, sr=smaug

This commit is contained in:
Felipe Gomes 2009-08-13 13:54:09 -07:00
parent 0479aa9647
commit 972bf3dc41
9 changed files with 218 additions and 27 deletions

View File

@ -61,6 +61,7 @@
#include "nsIEditorDocShell.h"
#include "nsIFormControl.h"
#include "nsIComboboxControlFrame.h"
#include "nsIScrollableFrame.h"
#include "nsIDOMNSHTMLElement.h"
#include "nsIDOMHTMLAnchorElement.h"
#include "nsIDOMHTMLInputElement.h"
@ -2492,6 +2493,115 @@ nsEventStateManager::DoScrollText(nsPresContext* aPresContext,
return NS_OK;
}
void
nsEventStateManager::DecideGestureEvent(nsGestureNotifyEvent* aEvent,
nsIFrame* targetFrame)
{
NS_ASSERTION(aEvent->message == NS_GESTURENOTIFY_EVENT_START,
"DecideGestureEvent called with a non-gesture event");
/* Check the ancestor tree to decide if any frame is willing* to receive
* a MozPixelScroll event. If that's the case, the current touch gesture
* will be used as a pan gesture; otherwise it will be a regular
* mousedown/mousemove/click event.
*
* *willing: determine if it makes sense to pan the element using scroll events:
* - For web content: if there are any visible scrollbars on the touch point
* - For XUL: if it's an scrollable element that can currently scroll in some
* direction.
*
* Note: we'll have to one-off various cases to ensure a good usable behavior
*/
nsGestureNotifyEvent::ePanDirection panDirection = nsGestureNotifyEvent::ePanNone;
PRBool displayPanFeedback = PR_FALSE;
for (nsIFrame* current = targetFrame; current;
current = nsLayoutUtils::GetCrossDocParentFrame(current)) {
nsIAtom* currentFrameType = current->GetType();
// Scrollbars should always be draggable
if (currentFrameType == nsGkAtoms::scrollbarFrame) {
panDirection = nsGestureNotifyEvent::ePanNone;
break;
}
#ifdef MOZ_XUL
// Special check for trees
nsTreeBodyFrame* treeFrame = do_QueryFrame(current);
if (treeFrame) {
if (treeFrame->GetHorizontalOverflow()) {
panDirection = nsGestureNotifyEvent::ePanHorizontal;
}
if (treeFrame->GetVerticalOverflow()) {
panDirection = nsGestureNotifyEvent::ePanVertical;
}
break;
}
#endif
nsIScrollableFrame* scrollableFrame = do_QueryFrame(current);
if (scrollableFrame) {
if (current->IsFrameOfType(nsIFrame::eXULBox)) {
nsIScrollableView* scrollableView = scrollableFrame->GetScrollableView();
if (scrollableView) {
displayPanFeedback = PR_TRUE;
PRBool canScrollUp, canScrollDown, canScrollLeft, canScrollRight;
scrollableView->CanScroll(PR_FALSE, PR_TRUE, canScrollDown);
scrollableView->CanScroll(PR_FALSE, PR_FALSE, canScrollUp);
scrollableView->CanScroll(PR_TRUE, PR_TRUE, canScrollRight);
scrollableView->CanScroll(PR_TRUE, PR_FALSE, canScrollLeft);
if (targetFrame->GetType() == nsGkAtoms::menuFrame) {
// menu frames report horizontal scroll when they have submenus
// and we don't want that
canScrollRight = PR_FALSE;
canScrollLeft = PR_FALSE;
displayPanFeedback = PR_FALSE;
}
//Vertical panning has priority over horizontal panning, so
//when a vertical movement is detected we can just finish the loop.
if (canScrollUp || canScrollDown) {
panDirection = nsGestureNotifyEvent::ePanVertical;
break;
}
if (canScrollLeft || canScrollRight) {
panDirection = nsGestureNotifyEvent::ePanHorizontal;
displayPanFeedback = PR_FALSE;
}
}
} else { //Not a XUL box
nsMargin scrollbarSizes = scrollableFrame->GetActualScrollbarSizes();
//Check if we have visible scrollbars
if (scrollbarSizes.LeftRight()) {
panDirection = nsGestureNotifyEvent::ePanVertical;
displayPanFeedback = PR_TRUE;
break;
}
if (scrollbarSizes.TopBottom()) {
panDirection = nsGestureNotifyEvent::ePanHorizontal;
displayPanFeedback = PR_TRUE;
}
}
} //scrollableFrame
} //ancestor chain
aEvent->displayPanFeedback = displayPanFeedback;
aEvent->panDirection = panDirection;
}
nsresult
nsEventStateManager::GetParentScrollingView(nsInputEvent *aEvent,
nsPresContext* aPresContext,
@ -2798,6 +2908,13 @@ nsEventStateManager::PostHandleEvent(nsPresContext* aPresContext,
}
break;
case NS_GESTURENOTIFY_EVENT_START:
{
if (nsEventStatus_eConsumeNoDefault != *aStatus)
DecideGestureEvent(static_cast<nsGestureNotifyEvent*>(aEvent), mCurrentTarget);
}
break;
case NS_DRAGDROP_ENTER:
case NS_DRAGDROP_OVER:
{

View File

@ -41,6 +41,7 @@
#include "nsIEventStateManager.h"
#include "nsEvent.h"
#include "nsGUIEvent.h"
#include "nsIContent.h"
#include "nsIObserver.h"
#include "nsWeakReference.h"
@ -278,6 +279,16 @@ protected:
nsresult ChangeFullZoom(PRInt32 change);
// end mousewheel functions
/*
* When a touch gesture is about to start, this function determines what
* kind of gesture interaction we will want to use, based on what is
* underneath the initial touch point.
* Currently it decides between panning (finger scrolling) or dragging
* the target element, as well as the orientation to trigger panning and
* display visual boundary feedback. The decision is stored back in aEvent.
*/
void DecideGestureEvent(nsGestureNotifyEvent* aEvent, nsIFrame* targetFrame);
// routines for the d&d gesture tracking state machine
void BeginTrackingDragGesture ( nsPresContext* aPresContext, nsMouseEvent* inDownEvent,
nsIFrame* inDownFrame ) ;

View File

@ -648,6 +648,7 @@ nsLayoutUtils::GetEventCoordinatesRelativeTo(const nsEvent* aEvent, nsIFrame* aF
aEvent->eventStructType != NS_MOUSE_SCROLL_EVENT &&
aEvent->eventStructType != NS_DRAG_EVENT &&
aEvent->eventStructType != NS_SIMPLE_GESTURE_EVENT &&
aEvent->eventStructType != NS_GESTURENOTIFY_EVENT &&
aEvent->eventStructType != NS_QUERY_CONTENT_EVENT))
return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);

View File

@ -186,6 +186,9 @@ public:
nsITreeBoxObject* GetTreeBoxObject() const { return mTreeBoxObject; }
PRBool GetVerticalOverflow() const { return mVerticalOverflow; }
PRBool GetHorizontalOverflow() const {return mHorizontalOverflow; }
protected:
friend class nsOverflowChecker;

View File

@ -105,6 +105,7 @@ class nsHashKey;
#define NS_SIMPLE_GESTURE_EVENT 37
#define NS_SELECTION_EVENT 38
#define NS_CONTENT_COMMAND_EVENT 39
#define NS_GESTURENOTIFY_EVENT 40
// These flags are sort of a mess. They're sort of shared between event
// listener flags and event flags, but only some of them. You've been
@ -429,6 +430,9 @@ class nsHashKey;
#define NS_CONTENT_COMMAND_UNDO (NS_CONTENT_COMMAND_EVENT_START+4)
#define NS_CONTENT_COMMAND_REDO (NS_CONTENT_COMMAND_EVENT_START+5)
// Event to gesture notification
#define NS_GESTURENOTIFY_EVENT_START 3900
/**
* Return status for event processors, nsEventStatus, is defined in
* nsEvent.h.
@ -1074,6 +1078,38 @@ public:
PRInt32 scrollOverflow;
};
/*
* Gesture Notify Event:
*
* This event is the first event generated when the user touches
* the screen with a finger, and it's meant to decide what kind
* of action we'll use for that touch interaction.
*
* The event is dispatched to the layout and based on what is underneath
* the initial contact point it's then decided if we should pan
* (finger scrolling) or drag the target element.
*/
class nsGestureNotifyEvent : public nsGUIEvent
{
public:
enum ePanDirection {
ePanNone,
ePanVertical,
ePanHorizontal,
ePanBoth
};
ePanDirection panDirection;
PRPackedBool displayPanFeedback;
nsGestureNotifyEvent(PRBool aIsTrusted, PRUint32 aMsg, nsIWidget *aWidget):
nsGUIEvent(aIsTrusted, aMsg, aWidget, NS_GESTURENOTIFY_EVENT),
panDirection(ePanNone),
displayPanFeedback(PR_FALSE)
{
}
};
class nsQueryContentEvent : public nsGUIEvent
{
public:

View File

@ -144,7 +144,7 @@ PRBool nsWinGesture::InitLibrary()
#define GCOUNT 5
PRBool nsWinGesture::InitWinGestureSupport(HWND hWnd)
PRBool nsWinGesture::SetWinGestureSupport(HWND hWnd, nsGestureNotifyEvent::ePanDirection aDirection)
{
if (!getGestureInfo)
return PR_FALSE;
@ -162,17 +162,27 @@ PRBool nsWinGesture::InitWinGestureSupport(HWND hWnd)
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) {
config[2].dwWant = GC_PAN|GC_PAN_WITH_INERTIA|
GC_PAN_WITH_GUTTER|
GC_PAN_WITH_SINGLE_FINGER_VERTICALLY;
config[2].dwBlock = GC_PAN_WITH_SINGLE_FINGER_HORIZONTALLY;
}
else {
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 (aDirection == nsGestureNotifyEvent::ePanVertical ||
aDirection == nsGestureNotifyEvent::ePanBoth)
{
config[2].dwWant |= GC_PAN_WITH_SINGLE_FINGER_VERTICALLY;
config[2].dwBlock -= GC_PAN_WITH_SINGLE_FINGER_VERTICALLY;
}
if (aDirection == nsGestureNotifyEvent::ePanHorizontal ||
aDirection == nsGestureNotifyEvent::ePanBoth)
{
config[2].dwWant |= GC_PAN_WITH_SINGLE_FINGER_HORIZONTALLY;
config[2].dwBlock -= GC_PAN_WITH_SINGLE_FINGER_HORIZONTALLY;
}
}
config[3].dwWant = GC_TWOFINGERTAP;

View File

@ -194,7 +194,7 @@ public:
nsWinGesture();
public:
PRBool InitWinGestureSupport(HWND hWnd);
PRBool SetWinGestureSupport(HWND hWnd, nsGestureNotifyEvent::ePanDirection aDirection);
PRBool ShutdownWinGestureSupport();
PRBool IsAvailable();

View File

@ -436,6 +436,7 @@ nsWindow::nsWindow() : nsBaseWidget()
mWindowType = eWindowType_child;
mBorderStyle = eBorderStyle_default;
mPopupType = ePopupTypeAny;
mDisplayPanFeedback = PR_FALSE;
mLastPoint.x = 0;
mLastPoint.y = 0;
mLastSize.width = 0;
@ -715,20 +716,6 @@ nsWindow::StandardWindowCreate(nsIWidget *aParent,
nsWindowCE::CreateSoftKeyMenuBar(mWnd);
#endif
#if !defined(WINCE)
// Enable gesture support for this window.
if (mWindowType != eWindowType_invisible &&
mWindowType != eWindowType_plugin &&
mWindowType != eWindowType_java &&
mWindowType != eWindowType_toplevel) {
// eWindowType_toplevel is the top level main frame window. Gesture support
// there prevents the user from interacting with the title bar or nc
// areas using a single finger. Java and plugin windows can make their
// own calls.
mGesture.InitWinGestureSupport(mWnd);
}
#endif // !defined(WINCE)
return NS_OK;
}
@ -4148,6 +4135,31 @@ PRBool nsWindow::ProcessMessage(UINT msg, WPARAM &wParam, LPARAM &lParam,
case WM_GESTURE:
result = OnGesture(wParam, lParam);
break;
case WM_GESTURENOTIFY:
{
if (mWindowType != eWindowType_invisible &&
mWindowType != eWindowType_plugin &&
mWindowType != eWindowType_java &&
mWindowType != eWindowType_toplevel) {
// eWindowType_toplevel is the top level main frame window. Gesture support
// there prevents the user from interacting with the title bar or nc
// areas using a single finger. Java and plugin windows can make their
// own calls.
GESTURENOTIFYSTRUCT * gestureinfo = (GESTURENOTIFYSTRUCT*)lParam;
nsPointWin touchPoint;
touchPoint = gestureinfo->ptsLocation;
touchPoint.ScreenToClient(mWnd);
nsGestureNotifyEvent gestureNotifyEvent(PR_TRUE, NS_GESTURENOTIFY_EVENT_START, this);
gestureNotifyEvent.refPoint = touchPoint;
nsEventStatus status;
DispatchEvent(&gestureNotifyEvent, status);
mDisplayPanFeedback = gestureNotifyEvent.displayPanFeedback;
mGesture.SetWinGestureSupport(mWnd, gestureNotifyEvent.panDirection);
}
result = PR_FALSE; //should always bubble to DefWindowProc
}
break;
#endif // !defined(WINCE)
case WM_CLEAR:
@ -4735,7 +4747,7 @@ PRBool nsWindow::OnGesture(WPARAM wParam, LPARAM lParam)
scrollOverflowY = event.scrollOverflow;
}
if (mWindowType != eWindowType_popup) {
if (mDisplayPanFeedback) {
mGesture.UpdatePanFeedbackX(mWnd, scrollOverflowX, endFeedback);
mGesture.UpdatePanFeedbackY(mWnd, scrollOverflowY, endFeedback);
mGesture.PanFeedbackFinalize(mWnd, endFeedback);

View File

@ -414,6 +414,7 @@ protected:
HKL mLastKeyboardLayout;
nsPopupType mPopupType;
int mScrollSeriesCounter;
PRPackedBool mDisplayPanFeedback;
static PRUint32 sInstanceCount;
static TriStateBool sCanQuit;