From b0e97d0c8b1fe66aa9b9f9bd7c46183edc0def04 Mon Sep 17 00:00:00 2001 From: Felipe Gomes Date: Thu, 13 Aug 2009 13:54:09 -0700 Subject: [PATCH] Bug 503541 - Fine grained control of gesture registration on widgets (Win7). r=smaug+roc, sr=smaug --- content/events/src/nsEventStateManager.cpp | 117 ++++++++++++++++++ content/events/src/nsEventStateManager.h | 11 ++ layout/base/nsLayoutUtils.cpp | 1 + .../xul/base/src/tree/src/nsTreeBodyFrame.h | 3 + widget/public/nsGUIEvent.h | 36 ++++++ widget/src/windows/nsWinGesture.cpp | 32 +++-- widget/src/windows/nsWinGesture.h | 2 +- widget/src/windows/nsWindow.cpp | 42 ++++--- widget/src/windows/nsWindow.h | 1 + 9 files changed, 218 insertions(+), 27 deletions(-) diff --git a/content/events/src/nsEventStateManager.cpp b/content/events/src/nsEventStateManager.cpp index b3e4d01e72ba..6bd916843892 100644 --- a/content/events/src/nsEventStateManager.cpp +++ b/content/events/src/nsEventStateManager.cpp @@ -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(aEvent), mCurrentTarget); + } + break; + case NS_DRAGDROP_ENTER: case NS_DRAGDROP_OVER: { diff --git a/content/events/src/nsEventStateManager.h b/content/events/src/nsEventStateManager.h index 477efe253613..c3f14b5d9a5c 100644 --- a/content/events/src/nsEventStateManager.h +++ b/content/events/src/nsEventStateManager.h @@ -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 ) ; diff --git a/layout/base/nsLayoutUtils.cpp b/layout/base/nsLayoutUtils.cpp index d04241d3b4c1..97f457382f08 100644 --- a/layout/base/nsLayoutUtils.cpp +++ b/layout/base/nsLayoutUtils.cpp @@ -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); diff --git a/layout/xul/base/src/tree/src/nsTreeBodyFrame.h b/layout/xul/base/src/tree/src/nsTreeBodyFrame.h index e49fe018f288..44b8eef9f99e 100644 --- a/layout/xul/base/src/tree/src/nsTreeBodyFrame.h +++ b/layout/xul/base/src/tree/src/nsTreeBodyFrame.h @@ -186,6 +186,9 @@ public: nsITreeBoxObject* GetTreeBoxObject() const { return mTreeBoxObject; } + PRBool GetVerticalOverflow() const { return mVerticalOverflow; } + PRBool GetHorizontalOverflow() const {return mHorizontalOverflow; } + protected: friend class nsOverflowChecker; diff --git a/widget/public/nsGUIEvent.h b/widget/public/nsGUIEvent.h index 8b2cc479b139..6efc72769d17 100644 --- a/widget/public/nsGUIEvent.h +++ b/widget/public/nsGUIEvent.h @@ -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: diff --git a/widget/src/windows/nsWinGesture.cpp b/widget/src/windows/nsWinGesture.cpp index 24cf2b23fb15..80d07f95aaa7 100644 --- a/widget/src/windows/nsWinGesture.cpp +++ b/widget/src/windows/nsWinGesture.cpp @@ -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; diff --git a/widget/src/windows/nsWinGesture.h b/widget/src/windows/nsWinGesture.h index c8b3c9d4786e..77b382b6e263 100644 --- a/widget/src/windows/nsWinGesture.h +++ b/widget/src/windows/nsWinGesture.h @@ -194,7 +194,7 @@ public: nsWinGesture(); public: - PRBool InitWinGestureSupport(HWND hWnd); + PRBool SetWinGestureSupport(HWND hWnd, nsGestureNotifyEvent::ePanDirection aDirection); PRBool ShutdownWinGestureSupport(); PRBool IsAvailable(); diff --git a/widget/src/windows/nsWindow.cpp b/widget/src/windows/nsWindow.cpp index 74323f15c6fd..7fd59083e71a 100644 --- a/widget/src/windows/nsWindow.cpp +++ b/widget/src/windows/nsWindow.cpp @@ -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); diff --git a/widget/src/windows/nsWindow.h b/widget/src/windows/nsWindow.h index 40bcf3b9ba85..d9733f0fb226 100644 --- a/widget/src/windows/nsWindow.h +++ b/widget/src/windows/nsWindow.h @@ -414,6 +414,7 @@ protected: HKL mLastKeyboardLayout; nsPopupType mPopupType; int mScrollSeriesCounter; + PRPackedBool mDisplayPanFeedback; static PRUint32 sInstanceCount; static TriStateBool sCanQuit;