qt5-ukui-platformtheme/ukui-styles/gesture-helper.cpp

372 lines
13 KiB
C++

/*
* Qt5-UKUI's Library
*
* Copyright (C) 2023, KylinSoft Co., Ltd.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this library. If not, see <https://www.gnu.org/licenses/>.
*
* Authors: Yue Lan <lanyue@kylinos.cn>
*
*/
#include "gesture-helper.h"
#include "ukui-two-finger-slide-gesture.h"
#include "ukui-two-finger-zoom-gesture.h"
#include <QWidget>
#include <QGestureEvent>
#include <QTapAndHoldGesture>
#include <QContextMenuEvent>
#include <QApplication>
#include <QMenu>
#include <QTouchEvent>
#include <QScroller>
#include <QMouseEvent>
#include <QAbstractItemView>
#include <QItemSelectionModel>
#include <QScroller>
#include <QWheelEvent>
#include <QtMath>
#include <QDebug>
GestureHelper::GestureHelper(QObject *parent) : QObject(parent)
{
// we translate event by ourselves
qApp->setAttribute(Qt::AA_SynthesizeTouchForUnhandledMouseEvents, false);
//qApp->setAttribute(Qt::AA_SynthesizeMouseForUnhandledTouchEvents, false);
m_menu_popped_timer.setInterval(500);
m_menu_popped_timer.setSingleShot(true);
m_pinch_operation_delayer.setInterval(200);
m_pinch_operation_delayer.setSingleShot(true);
m_slider = new UKUI::TwoFingerSlideGestureRecognizer;
m_zoomer = new UKUI::TwoFingerZoomGestureRecognizer;
m_slide_type = QGestureRecognizer::registerRecognizer(m_slider);
m_zoom_type = QGestureRecognizer::registerRecognizer(m_zoomer);
}
GestureHelper::~GestureHelper()
{
QGestureRecognizer::unregisterRecognizer(m_slide_type);
QGestureRecognizer::unregisterRecognizer(m_zoom_type);
}
void GestureHelper::registerWidget(QWidget *widget)
{
if (!widget)
return;
if (widget->contextMenuPolicy() == Qt::NoContextMenu)
return;
//widget->setAttribute(Qt::WA_AcceptTouchEvents, false);
widget->removeEventFilter(this);
//widget->grabGesture(Qt::TapGesture);
widget->grabGesture(Qt::TapAndHoldGesture);
//widget->grabGesture(Qt::PanGesture);
//widget->grabGesture(Qt::PinchGesture);
//widget->grabGesture(Qt::SwipeGesture);
//widget->grabGesture(m_slide_type);
//widget->grabGesture(m_zoom_type);
widget->installEventFilter(this);
}
void GestureHelper::unregisterWidget(QWidget *widget)
{
if (!widget)
return;
if (widget->contextMenuPolicy() == Qt::NoContextMenu)
return;
//widget->setAttribute(Qt::WA_AcceptTouchEvents, true);
widget->removeEventFilter(this);
//widget->ungrabGesture(Qt::TapGesture);
widget->ungrabGesture(Qt::TapAndHoldGesture);
//widget->ungrabGesture(Qt::PanGesture);
//widget->ungrabGesture(Qt::PinchGesture);
//widget->ungrabGesture(Qt::SwipeGesture);
//widget->ungrabGesture(m_slide_type);
//widget->ungrabGesture(m_zoom_type);
}
bool GestureHelper::eventFilter(QObject *watched, QEvent *event)
{
switch (event->type()) {
case QEvent::ActivationChange: {
// do not grab inactive window's gesture.
auto widget = qobject_cast<QWidget *>(watched);
if (widget->isActiveWindow()) {
widget->grabGesture(Qt::TapAndHoldGesture);
} else {
widget->ungrabGesture(Qt::TapAndHoldGesture);
}
return false;
}
case QEvent::TouchBegin: {
m_is_touching = true;
auto te = static_cast<QTouchEvent *>(event);
m_touch_points = te->touchPoints();
m_finger_count = te->touchPoints().count();
if (m_finger_count == 1)
m_hold_and_tap_pos = te->touchPoints().first().pos();
else
m_hold_and_tap_pos = QPointF();
if (m_touch_points.count() > 2) {
m_menu_popped_timer.stop();
}
break;
}
case QEvent::TouchUpdate: {
auto te = static_cast<QTouchEvent *>(event);
m_touch_points = te->touchPoints();
m_finger_count = te->touchPoints().count();
break;
}
case QEvent::TouchCancel:
case QEvent::TouchEnd: {
m_touch_points.clear();
m_is_touching = false;
m_finger_count = 0;
break;
}
case QEvent::Gesture: {
auto e = static_cast<QGestureEvent *>(event);
auto widget = qobject_cast<QWidget *>(watched);
if (!widget->isActiveWindow())
return false;
if (auto twoFingerSlideGesture = static_cast<UKUI::TwoFingerSlideGesture *>(e->gesture(m_slide_type))) {
//qDebug()<<"has slide gesture";
//qDebug()<<"slide"<<twoFingerSlideGesture->state()<<twoFingerSlideGesture->direction()<<twoFingerSlideGesture->totalDelta();
if (twoFingerSlideGesture->state() == Qt::GestureUpdated) {
// some view doesn't have scroller but also can scroll, such as process list view in ukui-system-monitor.
// we send the wheel event to trigger the scroll when slide gesture updated.
if (!QScroller::hasScroller(widget)) {
bool isHorizal = twoFingerSlideGesture->direction() == UKUI::TwoFingerSlideGesture::Horizal;
bool isVertical = twoFingerSlideGesture->direction() == UKUI::TwoFingerSlideGesture::Vertical;
if (isVertical) {
QWheelEvent we(twoFingerSlideGesture->hotSpot(), twoFingerSlideGesture->delta() * 10, Qt::NoButton, Qt::NoModifier, Qt::Vertical);
qApp->sendEvent(widget, &we);
} else if (isHorizal) {
QWheelEvent we(twoFingerSlideGesture->hotSpot(), twoFingerSlideGesture->delta() * 10, Qt::NoButton, Qt::NoModifier, Qt::Horizontal);
qApp->sendEvent(widget, &we);
}
}
}
}
if (auto twoFingerZoomGesture = static_cast<UKUI::TwoFingerZoomGesture *>(e->gesture(m_zoom_type))) {
//qDebug()<<"has zoom gesture"<<twoFingerZoomGesture->state()<<twoFingerZoomGesture->zoomDirection()<<twoFingerZoomGesture->startPoints()<<twoFingerZoomGesture->lastPoints()<<twoFingerZoomGesture->currentPoints();
switch (twoFingerZoomGesture->zoomDirection()) {
case UKUI::TwoFingerZoomGesture::ZoomIn: {
QWheelEvent we(twoFingerZoomGesture->hotSpot(), 100, Qt::NoButton, Qt::ControlModifier);
qApp->sendEvent(watched, &we);
break;
}
case UKUI::TwoFingerZoomGesture::ZoomOut: {
QWheelEvent we(twoFingerZoomGesture->hotSpot(), -100, Qt::NoButton, Qt::ControlModifier);
qApp->sendEvent(watched, &we);
break;
}
default:
break;
}
}
if (auto tapGesture = static_cast<QTapGesture *>(e->gesture(Qt::TapGesture))) {
//qDebug()<<tapGesture->gestureType()<<tapGesture->position()<<tapGesture->state();
switch (tapGesture->state()) {
case Qt::GestureStarted:
//mouse press event
break;
case Qt::GestureCanceled:
case Qt::GestureFinished: {
//mouse release event
break;
}
default:
break;
}
}
if (auto tapAndHoldGesture = static_cast<QTapAndHoldGesture*>(e->gesture(Qt::TapAndHoldGesture))) {
//qDebug()<<tapAndHoldGesture->gestureType()<<tapAndHoldGesture->position()<<tapAndHoldGesture->state();
if (m_menu_popped_timer.isActive())
return false;
m_hold_and_tap_pos = QPoint();
// check if there is a menu popped.
// note that the right click in a widget which has context menu
// will trigger the tap and hold gesture. I have no idea to deal
// with this case, because i didn't find how to recognize if this
// event is translated from mouse event.
if (qobject_cast<QMenu *>(watched) || qobject_cast<QMenu *>(qApp->activePopupWidget())) {
qDebug()<<"menu popped, may be right click";
m_menu_popped = true;
}
switch (tapAndHoldGesture->state()) {
case Qt::GestureStarted: {
if (m_menu_popped || m_is_mouse_pressed) {
return false;
} else {
m_menu_popped = true;
m_menu_popped_timer.start();
auto pos = widget->mapFromGlobal(tapAndHoldGesture->position().toPoint());
auto gpos = tapAndHoldGesture->position().toPoint();
if (m_is_native_mouse_move)
return false;
QMouseEvent me(QMouseEvent::MouseButtonRelease, pos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
qApp->sendEvent(widget, &me);
QContextMenuEvent ce(QContextMenuEvent::Other, pos, gpos, Qt::NoModifier);
qApp->sendEvent(widget, &ce);
}
break;
}
case Qt::GestureCanceled:
case Qt::GestureFinished: {
m_menu_popped = false;
break;
}
default:
break;
}
}
if (auto swipeGesture = static_cast<QSwipeGesture *>(e->gesture(Qt::SwipeGesture))) {
//qDebug()<<swipeGesture->gestureType()<<swipeGesture->state()<<swipeGesture->horizontalDirection()<<swipeGesture->verticalDirection()<<swipeGesture->swipeAngle();
}
if (auto panGesture = static_cast<QPanGesture*>(e->gesture(Qt::PanGesture))) {
//qDebug()<<panGesture->gestureType()<<panGesture->state()<<panGesture->delta()<<panGesture->hotSpot();
switch (panGesture->state()) {
case Qt::GestureStarted: {
auto widget = qobject_cast<QWidget *>(watched);
auto pos = widget->mapFromGlobal(QCursor::pos());
QMouseEvent me(QMouseEvent::MouseButtonRelease, pos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
qApp->sendEvent(widget, &me);
if (m_is_paning) {
return false;
} else {
m_is_paning = true;
}
break;
}
case Qt::GestureCanceled:
case Qt::GestureFinished: {
m_is_paning = false;
break;
}
default:
break;
}
return false;
}
break;
}
case QEvent::MouseMove: {
if (m_finger_count > 1)
return true;
QMouseEvent *me = static_cast<QMouseEvent *>(event);
auto widget = static_cast<QWidget *>(watched);
if (!widget)
return false;
//qDebug()<<me->type()<<me->pos();
bool isTranslatedFromTouch = me->source() == Qt::MouseEventSynthesizedByQt;
if (!isTranslatedFromTouch) {
// do not trigger tap and hold gesture handler
m_is_native_mouse_move = true;
return false;
}
m_is_native_mouse_move = false;
// if we do a hold and tap gesture, we should not trigger mouse move event.
// so we have to ignore the small offset of finger move.
auto lastTapPoint = m_hold_and_tap_pos.toPoint();
auto currentPoint = widget->mapTo(widget->topLevelWidget(), me->pos());
if (!lastTapPoint.isNull()) {
//qDebug()<<lastTapPoint<<currentPoint<<lenthSquared;
if (QRect(-50, -50, 100, 100).contains(lastTapPoint - currentPoint)) {
if (qobject_cast<QAbstractScrollArea *>(widget) || qobject_cast<QAbstractScrollArea *>(widget->parent()))
return true;
}
}
if (m_is_paning) {
return true;
}
return false;
}
case QEvent::DragEnter:
case QEvent::DragMove: {
if (m_is_paning)
return true;
}
case QEvent::MouseButtonPress: {
auto me = static_cast<QMouseEvent *>(event);
if (me->source() == Qt::MouseEventNotSynthesized)
m_is_mouse_pressed = true;
else
m_is_mouse_pressed = false;
break;
}
case QEvent::MouseButtonRelease: {
auto me = static_cast<QMouseEvent *>(event);
if (me->source() == Qt::MouseEventNotSynthesized)
m_is_mouse_pressed = false;
break;
}
default:
break;
}
return false;
}