Bug 590565 - Multitouch swipe gestures for MeeGo/Qt r=wolfiR a=blocking-fennec

This commit is contained in:
Tatiana Meshkova 2010-10-09 09:08:42 +03:00
parent 433dab0fe9
commit 4f9a20161e
6 changed files with 411 additions and 41 deletions

View File

@ -73,6 +73,7 @@ CPPSRCS = \
nsDragService.cpp \
nsNativeThemeQt.cpp \
mozqwidget.cpp \
mozSwipeGesture.cpp \
nsSound.cpp \
nsFilePicker.cpp \
nsPrintOptionsQt.cpp \

View File

@ -0,0 +1,200 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* vim:expandtab:shiftwidth=4:tabstop=4:
*/
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Initial Developer of the Original Code is Nokia Corporation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Miika Jarvinen <mjarvin@gmail.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "mozSwipeGesture.h"
#include <QTouchEvent>
#include <prtypes.h>
#include <nsIDOMSimpleGestureEvent.h>
#include <math.h>
// Percent of screen size
static const float TRIGGER_DISTANCE = 0.3;
// It would be nice to get platform defines for these values
// Maximum finger distance in pixels
const int MAX_FINGER_DISTANCE = 250;
// We could define it as 2*QT_GUI_DOUBLE_CLICK_RADIUS, but it's not available
// due to QTBUG-9630
const int FINGER_DISTANCE_MISTAKE = 50;
QGesture*
MozSwipeGestureRecognizer::create(QObject* target)
{
return new MozSwipeGesture();
}
QGestureRecognizer::Result
MozSwipeGestureRecognizer::recognize(QGesture* aState,
QObject* aWatched,
QEvent* aEvent)
{
const QTouchEvent* ev = static_cast<const QTouchEvent *>(aEvent);
MozSwipeGesture* swipe = static_cast<MozSwipeGesture *>(aState);
QGestureRecognizer::Result result = QGestureRecognizer::Ignore;
switch (aEvent->type()) {
case QEvent::TouchBegin:
swipe->mSwipeState = MozSwipeGesture::NOT_STARTED;
result = QGestureRecognizer::MayBeGesture;
break;
case QEvent::TouchEnd:
if (swipe->state() != Qt::NoGesture &&
swipe->mSwipeState == MozSwipeGesture::TRIGGERED) {
result = QGestureRecognizer::FinishGesture;
}
else {
result = QGestureRecognizer::CancelGesture;
}
break;
case QEvent::TouchUpdate:
// We have already handled this swipe
if (swipe->mSwipeState > MozSwipeGesture::STARTED) {
break;
}
if (ev->touchPoints().count() > 2) {
swipe->mSwipeState = MozSwipeGesture::CANCELLED;
result = QGestureRecognizer::CancelGesture;
break;
}
if (ev->touchPoints().count() == 2) {
swipe->mSwipeState = MozSwipeGesture::STARTED;
QList <QTouchEvent::TouchPoint> touchPoints = ev->touchPoints();
if (!swipe->Update(touchPoints[0], touchPoints[1])) {
result = QGestureRecognizer::CancelGesture;
swipe->mSwipeState = MozSwipeGesture::CANCELLED;
}
if (swipe->mSwipeState == MozSwipeGesture::TRIGGERED) {
result = QGestureRecognizer::TriggerGesture;
}
else {
result = QGestureRecognizer::MayBeGesture;
}
}
break;
default:
result = QGestureRecognizer::Ignore;
}
return result;
}
void
MozSwipeGestureRecognizer::reset(QGesture* aState)
{
MozSwipeGesture* swipe = static_cast<MozSwipeGesture *>(aState);
swipe->mHorizontalDirection = 0;
swipe->mVerticalDirection = 0;
QGestureRecognizer::reset(aState);
}
MozSwipeGesture::MozSwipeGesture()
: mHorizontalDirection(0)
, mVerticalDirection(0)
, mSwipeState(MozSwipeGesture::NOT_STARTED)
{
}
int MozSwipeGesture::Direction()
{
return mHorizontalDirection | mVerticalDirection;
}
bool
MozSwipeGesture::Update(const QTouchEvent::TouchPoint& aFirstPoint,
const QTouchEvent::TouchPoint& aSecondPoint)
{
// Check that fingers are not too far away
QPointF distance = aFirstPoint.pos() - aSecondPoint.pos();
if (distance.manhattanLength() > MAX_FINGER_DISTANCE) {
return false;
}
QPointF startPositions[] = {aFirstPoint.startNormalizedPos(),
aSecondPoint.startNormalizedPos()
};
QPointF currentPositions[] = {aFirstPoint.normalizedPos(),
aSecondPoint.normalizedPos()
};
float xDistance = fabs(currentPositions[0].x()-startPositions[0].x());
float yDistance = fabs(currentPositions[0].y()-startPositions[0].y());
QPointF startFingerDistance = aFirstPoint.startPos()-aSecondPoint.startPos();
QPointF fingerDistance = aFirstPoint.pos()-aSecondPoint.pos();
// Check that fingers doesn't move too much from the original distance
if ((startFingerDistance-fingerDistance).manhattanLength()
> FINGER_DISTANCE_MISTAKE) {
return false;
}
mVerticalDirection = nsIDOMSimpleGestureEvent::DIRECTION_UP;
if (currentPositions[0].y() > startPositions[0].y()) {
mVerticalDirection = nsIDOMSimpleGestureEvent::DIRECTION_DOWN;
}
mHorizontalDirection = nsIDOMSimpleGestureEvent::DIRECTION_LEFT;
if (currentPositions[0].x() > startPositions[0].y()) {
mHorizontalDirection = nsIDOMSimpleGestureEvent::DIRECTION_RIGHT;
}
if (xDistance > TRIGGER_DISTANCE) {
if (yDistance < TRIGGER_DISTANCE/2) {
mVerticalDirection = 0;
}
mSwipeState = TRIGGERED;
}
if (yDistance > TRIGGER_DISTANCE) {
if (xDistance < TRIGGER_DISTANCE/2) {
mHorizontalDirection = 0;
}
mSwipeState = TRIGGERED;
}
// Use center of touchpoints as hotspot
QPointF hotspot = aFirstPoint.scenePos() + aSecondPoint.scenePos();
hotspot /= 2;
setHotSpot(hotspot);
return true;
}

View File

@ -0,0 +1,82 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* vim:expandtab:shiftwidth=4:tabstop=4:
*/
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mozilla.org code.
*
* The Initial Developer of the Original Code is
* Nokia Corporation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Miika Jarvinen <mjarvin@gmail.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#ifndef MOZSWIPEGESTURE_H
#define MOZSWIPEGESTURE_H
#include <QGestureRecognizer>
#include <QGesture>
#include <QTouchEvent>
class MozSwipeGestureRecognizer: public QGestureRecognizer
{
public:
virtual QGesture* create(QObject* aTarget);
virtual QGestureRecognizer::Result recognize(QGesture* aState,
QObject* aWatched,
QEvent* aEvent);
virtual void reset(QGesture* aState);
};
class MozSwipeGesture: public QGesture
{
public:
int Direction();
private:
enum SwipeState {
NOT_STARTED = 0,
STARTED,
CANCELLED,
TRIGGERED
};
MozSwipeGesture();
bool Update(const QTouchEvent::TouchPoint& aFirstPoint,
const QTouchEvent::TouchPoint& aSecondPoint);
int mHorizontalDirection;
int mVerticalDirection;
SwipeState mSwipeState;
friend class MozSwipeGestureRecognizer;
};
#endif

View File

@ -80,9 +80,7 @@ MozQWidget::MozQWidget(nsWindow* aReceiver, QGraphicsItem* aParent)
{
#if (QT_VERSION >= QT_VERSION_CHECK(4, 6, 0))
setFlag(QGraphicsItem::ItemAcceptsInputMethod);
setAcceptTouchEvents(true);
grabGesture(Qt::PinchGesture);
#endif
}

View File

@ -63,6 +63,13 @@
#include <QtCore/QVariant>
#if (QT_VERSION >= QT_VERSION_CHECK(4, 6, 0))
#include <QPinchGesture>
#include <QGestureRecognizer>
#include "mozSwipeGesture.h"
static Qt::GestureType gSwipeGestureId = Qt::CustomGesture;
// How many milliseconds mouseevents are blocked after receiving
// multitouch.
static const float GESTURES_BLOCK_MOUSE_FOR = 200;
#endif // QT version check
#ifdef MOZ_X11
@ -192,9 +199,6 @@ nsWindow::nsWindow()
mIsDestroyed = PR_FALSE;
mIsShown = PR_FALSE;
mEnabled = PR_TRUE;
#if (QT_VERSION >= QT_VERSION_CHECK(4, 6, 0))
mMouseEventsDisabled = PR_FALSE;
#endif // qt version check
mWidget = nsnull;
mIsVisible = PR_FALSE;
mActivatePending = PR_FALSE;
@ -207,6 +211,7 @@ nsWindow::nsWindow()
mNeedsMove = PR_FALSE;
mListenForResizes = PR_FALSE;
mNeedsShow = PR_FALSE;
mGesturesCancelled = PR_FALSE;
if (!gGlobalsInitialized) {
gGlobalsInitialized = PR_TRUE;
@ -222,6 +227,14 @@ nsWindow::nsWindow()
mCursor = eCursor_standard;
gBufferPixmapUsageCount++;
#if (QT_VERSION > QT_VERSION_CHECK(4,6,0))
if (gSwipeGestureId == Qt::CustomGesture) {
// QGestureRecognizer takes ownership
MozSwipeGestureRecognizer* swipeRecognizer = new MozSwipeGestureRecognizer;
gSwipeGestureId = QGestureRecognizer::registerRecognizer(swipeRecognizer);
}
#endif
}
static inline gfxASurface::gfxImageFormat
@ -1125,16 +1138,27 @@ nsWindow::OnLeaveNotifyEvent(QGraphicsSceneHoverEvent *aEvent)
return DispatchEvent(&event);
}
// Block the mouse events if user was recently executing gestures;
// otherwise there will be also some panning during/after gesture
#if (QT_VERSION >= QT_VERSION_CHECK(4, 6, 0))
#define CHECK_MOUSE_BLOCKED { \
if (mLastMultiTouchTime.isValid()) { \
if (mLastMultiTouchTime.elapsed() < GESTURES_BLOCK_MOUSE_FOR) \
return nsEventStatus_eIgnore; \
else \
mLastMultiTouchTime = QTime(); \
} \
}
#else
define CHECK_MOUSE_BLOCKED {}
#endif
nsEventStatus
nsWindow::OnMotionNotifyEvent(QGraphicsSceneMouseEvent *aEvent)
{
#if (QT_VERSION >= QT_VERSION_CHECK(4, 6, 0))
if (mMouseEventsDisabled) {
// Block the mouse events if currently executing pinch gesture; otherwise there
// will be also some panning during the zooming
return nsEventStatus_eIgnore;
}
#endif
UserActivity();
CHECK_MOUSE_BLOCKED
nsMouseEvent event(PR_TRUE, NS_MOUSE_MOVE, this, nsMouseEvent::eReal);
@ -1172,6 +1196,8 @@ nsWindow::OnButtonPressEvent(QGraphicsSceneMouseEvent *aEvent)
// The user has done something.
UserActivity();
CHECK_MOUSE_BLOCKED
QPointF pos = aEvent->pos();
// we check against the widgets geometry, so use parent coordinates
@ -1219,6 +1245,9 @@ nsWindow::OnButtonPressEvent(QGraphicsSceneMouseEvent *aEvent)
nsEventStatus
nsWindow::OnButtonReleaseEvent(QGraphicsSceneMouseEvent *aEvent)
{
UserActivity();
CHECK_MOUSE_BLOCKED
// The user has done something.
UserActivity();
@ -1772,6 +1801,10 @@ nsEventStatus nsWindow::OnTouchEvent(QTouchEvent *event, PRBool &handled)
DispatchEvent(&gestureNotifyEvent);
}
}
else if (event->type() == QEvent::TouchEnd) {
mGesturesCancelled = PR_FALSE;
}
if (touchPoints.count() == 2) {
mTouchPointDistance = DistanceBetweenPoints(touchPoints.at(0).scenePos(),
touchPoints.at(1).scenePos());
@ -1782,59 +1815,104 @@ nsEventStatus nsWindow::OnTouchEvent(QTouchEvent *event, PRBool &handled)
//Disable mouse events when gestures are used, because they cause problems with
//Fennec
mMouseEventsDisabled = touchPoints.count() >= 2;
if (touchPoints.count() > 1) {
mLastMultiTouchTime.start();
}
return nsEventStatus_eIgnore;
}
nsEventStatus nsWindow::OnGestureEvent(QGestureEvent *event, PRBool &handled)
{
handled = PR_FALSE;
nsSimpleGestureEvent mozGesture(PR_TRUE, 0, this, 0, 0.0);
nsEventStatus
nsWindow::OnGestureEvent(QGestureEvent* event, PRBool &handled) {
if (QGesture *gesture = event->gesture(Qt::PinchGesture)) {
QPinchGesture *pinch = static_cast<QPinchGesture*>(gesture);
handled = PR_FALSE;
if (mGesturesCancelled) {
return nsEventStatus_eIgnore;
}
nsEventStatus result = nsEventStatus_eIgnore;
QGesture* gesture = event->gesture(Qt::PinchGesture);
if (gesture) {
QPinchGesture* pinch = static_cast<QPinchGesture*>(gesture);
handled = PR_TRUE;
QPointF centerPoint = pinch->centerPoint();
mozGesture.refPoint.x = nscoord(centerPoint.x());
mozGesture.refPoint.y = nscoord(centerPoint.y());
nsIntPoint centerPoint(pinch->centerPoint().x(), pinch->centerPoint().y());
if (pinch->state() == Qt::GestureStarted) {
mozGesture.message = NS_SIMPLE_GESTURE_MAGNIFY_START;
mozGesture.delta = 0.0;
event->accept();
mPinchStartDistance = mTouchPointDistance;
result = DispatchGestureEvent(NS_SIMPLE_GESTURE_MAGNIFY_START,
0, 0, centerPoint);
}
else if (pinch->state() == Qt::GestureUpdated) {
mozGesture.message = NS_SIMPLE_GESTURE_MAGNIFY_UPDATE;
mozGesture.delta = mTouchPointDistance - mLastPinchDistance;
double delta = mTouchPointDistance - mLastPinchDistance;
result = DispatchGestureEvent(NS_SIMPLE_GESTURE_MAGNIFY_UPDATE,
0, delta, centerPoint);
}
else if (pinch->state() == Qt::GestureFinished) {
mozGesture.message = NS_SIMPLE_GESTURE_MAGNIFY;
mozGesture.delta = 0.0;
double delta =mTouchPointDistance - mPinchStartDistance;
result = DispatchGestureEvent(NS_SIMPLE_GESTURE_MAGNIFY,
0, delta, centerPoint);
}
else {
handled = PR_FALSE;
handled = false;
}
mLastPinchDistance = mTouchPointDistance;
}
if (handled) {
Qt::KeyboardModifiers modifiers = QApplication::keyboardModifiers();
gesture = event->gesture(gSwipeGestureId);
if (gesture) {
if (gesture->state() == Qt::GestureStarted) {
event->accept();
}
if (gesture->state() == Qt::GestureFinished) {
event->accept();
handled = PR_TRUE;
mozGesture.isShift = (modifiers & Qt::ShiftModifier) ? PR_TRUE : PR_FALSE;
mozGesture.isControl = (modifiers & Qt::ControlModifier) ? PR_TRUE : PR_FALSE;
mozGesture.isMeta = PR_FALSE;
mozGesture.isAlt = (modifiers & Qt::AltModifier) ? PR_TRUE : PR_FALSE;
mozGesture.button = 0;
mozGesture.time = 0;
MozSwipeGesture* swipe = static_cast<MozSwipeGesture*>(gesture);
nsIntPoint hotspot;
hotspot.x = swipe->hotSpot().x();
hotspot.y = swipe->hotSpot().y();
return DispatchEvent(&mozGesture);
// Cancel pinch gesture
mGesturesCancelled = PR_TRUE;
PRFloat64 delta = mTouchPointDistance - mPinchStartDistance;
DispatchGestureEvent(NS_SIMPLE_GESTURE_MAGNIFY, 0, delta/2, hotspot);
result = DispatchGestureEvent(NS_SIMPLE_GESTURE_SWIPE,
swipe->Direction(), 0, hotspot);
}
}
return nsEventStatus_eIgnore;
return result;
}
nsEventStatus
nsWindow::DispatchGestureEvent(PRUint32 aMsg, PRUint32 aDirection,
double aDelta, const nsIntPoint& aRefPoint)
{
nsSimpleGestureEvent mozGesture(PR_TRUE, aMsg, this, 0, 0.0);
mozGesture.direction = aDirection;
mozGesture.delta = aDelta;
mozGesture.refPoint = aRefPoint;
Qt::KeyboardModifiers modifiers = QApplication::keyboardModifiers();
mozGesture.isShift = (modifiers & Qt::ShiftModifier) ? PR_TRUE : PR_FALSE;
mozGesture.isControl = (modifiers & Qt::ControlModifier) ? PR_TRUE : PR_FALSE;
mozGesture.isMeta = PR_FALSE;
mozGesture.isAlt = (modifiers & Qt::AltModifier) ? PR_TRUE : PR_FALSE;
mozGesture.button = 0;
mozGesture.time = 0;
return DispatchEvent(&mozGesture);
}
double
nsWindow::DistanceBetweenPoints(const QPointF &aFirstPoint, const QPointF &aSecondPoint)
{
@ -2397,8 +2475,8 @@ nsWindow::createQWidget(MozQWidget *parent, nsWidgetInitData *aInitData)
// Enable gestures:
#if (QT_VERSION >= QT_VERSION_CHECK(4, 6, 0))
newView->grabGesture(Qt::PinchGesture);
newView->viewport()->grabGesture(Qt::PinchGesture);
newView->viewport()->grabGesture(gSwipeGestureId);
#endif
newView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
newView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
@ -2420,6 +2498,10 @@ nsWindow::createQWidget(MozQWidget *parent, nsWidgetInitData *aInitData)
} else if (mIsTopLevel) {
SetDefaultIcon();
}
#if (QT_VERSION >= QT_VERSION_CHECK(4, 6, 0))
widget->grabGesture(Qt::PinchGesture);
widget->grabGesture(gSwipeGestureId);
#endif
return widget;
}

View File

@ -43,6 +43,7 @@
#include <QKeyEvent>
#include <qgraphicswidget.h>
#include <QTime>
#include "nsAutoPtr.h"
@ -289,7 +290,11 @@ protected:
//Gestures are only supported in qt > 4.6
#if (QT_VERSION >= QT_VERSION_CHECK(4, 6, 0))
virtual nsEventStatus OnTouchEvent(QTouchEvent *event, PRBool &handled);
virtual nsEventStatus OnGestureEvent(QGestureEvent *event, PRBool &handled);
nsEventStatus DispatchGestureEvent(PRUint32 aMsg, PRUint32 aDirection,
double aDelta, const nsIntPoint& aRefPoint);
double DistanceBetweenPoints(const QPointF &aFirstPoint, const QPointF &aSecondPoint);
#endif
@ -388,13 +393,15 @@ private:
#if (QT_VERSION >= QT_VERSION_CHECK(4, 6, 0))
double mTouchPointDistance;
double mLastPinchDistance;
PRBool mMouseEventsDisabled;
double mPinchStartDistance;
QTime mLastMultiTouchTime;
#endif
PRPackedBool mNeedsResize;
PRPackedBool mNeedsMove;
PRPackedBool mListenForResizes;
PRPackedBool mNeedsShow;
PRPackedBool mGesturesCancelled;
};
class nsChildWindow : public nsWindow