diff --git a/libs/flake/KoPointerEvent.cpp b/libs/flake/KoPointerEvent.cpp index f59cefc543..8b3bd714bd 100644 --- a/libs/flake/KoPointerEvent.cpp +++ b/libs/flake/KoPointerEvent.cpp @@ -1,256 +1,268 @@ /* This file is part of the KDE project Copyright (C) 2006 Thorsten Zachmann Copyright (C) 2006 C. Boemann Rasmussen Copyright (C) 2006-2007 Thomas Zander This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoPointerEvent.h" #include "KoInputDeviceHandlerEvent.h" #include #include #include #include class Q_DECL_HIDDEN KoPointerEvent::Private { public: Private() : tabletEvent(0) , mouseEvent(0) + , touchEvent(0) , deviceEvent(0) , tabletButton(Qt::NoButton) , globalPos(0, 0) , pos(0, 0) , posZ(0) , rotationX(0) , rotationY(0) , rotationZ(0) {} QTabletEvent *tabletEvent; QMouseEvent *mouseEvent; + QTouchEvent *touchEvent; KoInputDeviceHandlerEvent *deviceEvent; Qt::MouseButton tabletButton; QPoint globalPos, pos; int posZ; int rotationX, rotationY, rotationZ; }; KoPointerEvent::KoPointerEvent(QMouseEvent *ev, const QPointF &pnt) : point(pnt), m_event(ev), d(new Private()) { Q_ASSERT(m_event); d->mouseEvent = ev; } KoPointerEvent::KoPointerEvent(QTabletEvent *ev, const QPointF &pnt) : point(pnt), m_event(ev), d(new Private()) { Q_ASSERT(m_event); d->tabletEvent = ev; } +KoPointerEvent::KoPointerEvent(QTouchEvent* ev, const QPointF &pnt) + : point(pnt) + , m_event(ev) + , d(new Private) +{ + Q_ASSERT(m_event); + d->touchEvent = ev; + d->pos = ev->touchPoints().at(0).pos().toPoint(); +} + KoPointerEvent::KoPointerEvent(KoInputDeviceHandlerEvent * ev, int x, int y, int z, int rx, int ry, int rz) : m_event(ev) , d(new Private()) { Q_ASSERT(m_event); d->deviceEvent = ev; d->pos = QPoint(x, y); d->posZ = z; d->rotationX = rx; d->rotationY = ry; d->rotationZ = rz; } KoPointerEvent::KoPointerEvent(KoPointerEvent *event, const QPointF &point) : point(point) , touchPoints(event->touchPoints) , m_event(event->m_event) , d(new Private(*(event->d))) { Q_ASSERT(m_event); } KoPointerEvent::KoPointerEvent(const KoPointerEvent &rhs) : point(rhs.point) , touchPoints(rhs.touchPoints) , m_event(rhs.m_event) , d(new Private(*rhs.d)) { } KoPointerEvent::~KoPointerEvent() { delete d; } Qt::MouseButton KoPointerEvent::button() const { if (d->mouseEvent) return d->mouseEvent->button(); else if (d->tabletEvent) return d->tabletButton; else if (d->deviceEvent) return d->deviceEvent->button(); else return Qt::NoButton; } Qt::MouseButtons KoPointerEvent::buttons() const { if (d->mouseEvent) return d->mouseEvent->buttons(); else if (d->tabletEvent) return d->tabletButton; else if (d->deviceEvent) return d->deviceEvent->buttons(); return Qt::NoButton; } QPoint KoPointerEvent::globalPos() const { if (d->mouseEvent) return d->mouseEvent->globalPos(); else if (d->tabletEvent) return d->tabletEvent->globalPos(); else return d->globalPos; } QPoint KoPointerEvent::pos() const { if (d->mouseEvent) return d->mouseEvent->pos(); else if (d->tabletEvent) return d->tabletEvent->pos(); else return d->pos; } qreal KoPointerEvent::pressure() const { if (d->tabletEvent) return d->tabletEvent->pressure(); else return 1.0; } qreal KoPointerEvent::rotation() const { if (d->tabletEvent) return d->tabletEvent->rotation(); else return 0.0; } qreal KoPointerEvent::tangentialPressure() const { if (d->tabletEvent) return std::fmod((d->tabletEvent->tangentialPressure() - (-1.0)) / (1.0 - (-1.0)), 2.0); else return 0.0; } int KoPointerEvent::x() const { if (d->tabletEvent) return d->tabletEvent->x(); else if (d->mouseEvent) return d->mouseEvent->x(); else return pos().x(); } int KoPointerEvent::xTilt() const { if (d->tabletEvent) return d->tabletEvent->xTilt(); else return 0; } int KoPointerEvent::y() const { if (d->tabletEvent) return d->tabletEvent->y(); else if (d->mouseEvent) return d->mouseEvent->y(); else return pos().y(); } int KoPointerEvent::yTilt() const { if (d->tabletEvent) return d->tabletEvent->yTilt(); else return 0; } int KoPointerEvent::z() const { if (d->tabletEvent) return d->tabletEvent->z(); else if (d->deviceEvent) return d->posZ; else return 0; } int KoPointerEvent::rotationX() const { return d->rotationX; } int KoPointerEvent::rotationY() const { return d->rotationY; } int KoPointerEvent::rotationZ() const { return d->rotationZ; } bool KoPointerEvent::isTabletEvent() { return dynamic_cast(m_event) != 0; } void KoPointerEvent::setTabletButton(Qt::MouseButton button) { d->tabletButton = button; } Qt::KeyboardModifiers KoPointerEvent::modifiers() const { if (d->tabletEvent) return d->tabletEvent->modifiers(); else if (d->mouseEvent) return d->mouseEvent->modifiers(); else if (d->deviceEvent) return d->deviceEvent->modifiers(); else return Qt::NoModifier; } diff --git a/libs/flake/KoPointerEvent.h b/libs/flake/KoPointerEvent.h index 0dc796ab0d..2dbd3d47c5 100644 --- a/libs/flake/KoPointerEvent.h +++ b/libs/flake/KoPointerEvent.h @@ -1,223 +1,225 @@ /* This file is part of the KDE project Copyright (C) 2006 Thorsten Zachmann Copyright (C) 2006 C. Boemann Rasmussen Copyright (C) 2006-2007 Thomas Zander Copyright (C) 2012 Boudewijn Rempt This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOPOINTEREVENT_H #define KOPOINTEREVENT_H #include class QTabletEvent; class QMouseEvent; class QWheelEvent; class KoInputDeviceHandlerEvent; #include "kritaflake_export.h" struct KoTouchPoint { QTouchEvent::TouchPoint touchPoint; // the point in document coordinates QPointF lastPoint; QPointF point; }; /** * KoPointerEvent is a synthetic event that can be built from a mouse, * touch or tablet event. In addition to always providing tools with tablet * pressure characteristics, KoPointerEvent has both the original * (canvas based) position as well as the normalized position, that is, * the position of the event _in_ the document coordinates. */ class KRITAFLAKE_EXPORT KoPointerEvent { public: /** * Constructor. * * @param event the mouse event that is the base of this event. * @param point the zoomed point in the normal coordinate system. */ KoPointerEvent(QMouseEvent *event, const QPointF &point); /** * Constructor. * * @param event the tablet event that is the base of this event. * @param point the zoomed point in the normal coordinate system. */ KoPointerEvent(QTabletEvent *event, const QPointF &point); + KoPointerEvent(QTouchEvent* ev, const QPointF& pnt); + KoPointerEvent(KoInputDeviceHandlerEvent *event, int x, int y, int z = 0, int rx = 0, int ry = 0, int rz = 0); KoPointerEvent(KoPointerEvent *event, const QPointF& point); KoPointerEvent(const KoPointerEvent &rhs); ~KoPointerEvent(); /** * For classes that are handed this event, you can choose to accept (default) this event. * Acceptance signifies that you have handled this event and found it useful, the effect * of that will be that the event will not be handled to other event handlers. */ inline void accept() { m_event->accept(); } /** * For classes that are handed this event, you can choose to ignore this event. * Ignoring this event means you have not handled it and want to allow other event * handlers to try to handle it. */ inline void ignore() { m_event->ignore(); } /** * Returns the keyboard modifier flags that existed immediately before the event occurred. * See also QApplication::keyboardModifiers(). */ Qt::KeyboardModifiers modifiers() const; /// return if the event has been accepted. inline bool isAccepted() const { return m_event->isAccepted(); } /// return if this event was spontaneous (see QMouseEvent::spontaneous()) inline bool spontaneous() const { return m_event->spontaneous(); } /// return button pressed (see QMouseEvent::button()); Qt::MouseButton button() const; /// return buttons pressed (see QMouseEvent::buttons()); Qt::MouseButtons buttons() const; /// Return the position screen coordinates QPoint globalPos() const; /// return the position in widget coordinates QPoint pos() const; /** * return the pressure (or a default value). The range is 0.0 - 1.0 * and the default pressure (this is the pressure that will be given * when you use something like the mouse) is 1.0 */ qreal pressure() const; /// return the rotation (or a default value) qreal rotation() const; /** * return the tangential pressure (or a default value) * This is typically given by a finger wheel on an airbrush tool. The range * is from -1.0 to 1.0. 0.0 indicates a neutral position. Current airbrushes can * only move in the positive direction from the neutral position. If the device * does not support tangential pressure, this value is always 0.0. */ qreal tangentialPressure() const; /** * Return the x position in widget coordinates. * @see point */ int x() const; /** * Returns the angle between the device (a pen, for example) and the * perpendicular in the direction of the x axis. Positive values are * towards the tablet's physical right. The angle is in the range -60 * to +60 degrees. The default value is 0. */ int xTilt() const; /** * Return the y position in widget coordinates. * @see point */ int y() const; /** * Returns the angle between the device (a pen, for example) and the * perpendicular in the direction of the x axis. Positive values are * towards the tablet's physical right. The angle is in the range -60 * to +60 degrees. The default value is 0. */ int yTilt() const; /** * Returns the z position of the device. Typically this is represented * by a wheel on a 4D Mouse. If the device does not support a Z-axis, * this value is always zero. This is not the same as pressure. */ int z() const; /** * Returns the rotation around the X-axis. If the device does not support * this, the value is always zero. */ int rotationX() const; /** * Returns the rotation around the X-axis. If the device does not support * this, the value is always zero. */ int rotationY() const; /** * Returns the rotation around the Z-axis. If the device does not support * this, the value is always zero. */ int rotationZ() const; /// The point in document coordinates. const QPointF point; const QList touchPoints; /** * Returns if the event comes from a tablet */ bool isTabletEvent(); protected: friend class KoToolProxy; friend class KisToolProxy; friend class KisScratchPadEventFilter; /// called by KoToolProxy to set which button was pressed. void setTabletButton(Qt::MouseButton button); private: KoPointerEvent& operator=(const KoPointerEvent &rhs); // for the d-pointer police; we want to make accessors to the event inline, so this one stays here. QEvent *m_event; class Private; Private * const d; }; #endif diff --git a/libs/flake/KoToolProxy.cpp b/libs/flake/KoToolProxy.cpp index 6f5480edc0..55d4a4b29a 100644 --- a/libs/flake/KoToolProxy.cpp +++ b/libs/flake/KoToolProxy.cpp @@ -1,473 +1,496 @@ /* This file is part of the KDE project * Copyright (C) 2006-2007 Thomas Zander * Copyright (c) 2006-2011 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoToolProxy.h" #include "KoToolProxy_p.h" #include #include #include #include #include #include #include #include #include #include #include "KoToolBase.h" #include "KoPointerEvent.h" #include "KoInputDevice.h" #include "KoToolManager_p.h" #include "KoToolSelection.h" #include "KoCanvasBase.h" #include "KoCanvasController.h" #include "KoShapeManager.h" #include "KoSelection.h" #include "KoShapeLayer.h" #include "KoShapeRegistry.h" #include "KoShapeController.h" #include "KoOdf.h" #include "KoViewConverter.h" #include "KoShapeFactoryBase.h" KoToolProxyPrivate::KoToolProxyPrivate(KoToolProxy *p) : parent(p) { scrollTimer.setInterval(100); } void KoToolProxyPrivate::timeout() // Auto scroll the canvas { Q_ASSERT(controller); QPoint offset = QPoint(controller->canvasOffsetX(), controller->canvasOffsetY()); QPoint origin = controller->canvas()->documentOrigin(); QPoint viewPoint = widgetScrollPoint - origin - offset; QRectF mouseArea(viewPoint, QSizeF(10, 10)); mouseArea.setTopLeft(mouseArea.center()); controller->ensureVisible(mouseArea, true); QPoint newOffset = QPoint(controller->canvasOffsetX(), controller->canvasOffsetY()); QPoint moved = offset - newOffset; if (moved.isNull()) return; widgetScrollPoint += moved; QPointF documentPoint = parent->widgetToDocument(widgetScrollPoint); QMouseEvent event(QEvent::MouseMove, widgetScrollPoint, Qt::LeftButton, Qt::LeftButton, 0); KoPointerEvent ev(&event, documentPoint); activeTool->mouseMoveEvent(&ev); } void KoToolProxyPrivate::checkAutoScroll(const KoPointerEvent &event) { if (controller == 0) return; if (!activeTool) return; if (!activeTool->wantsAutoScroll()) return; if (!event.isAccepted()) return; if (!isToolPressed) return; if (event.buttons() != Qt::LeftButton) return; widgetScrollPoint = event.pos(); if (! scrollTimer.isActive()) scrollTimer.start(); } void KoToolProxyPrivate::selectionChanged(bool newSelection) { if (hasSelection == newSelection) return; hasSelection = newSelection; emit parent->selectionChanged(hasSelection); } bool KoToolProxyPrivate::isActiveLayerEditable() { if (!activeTool) return false; KoShapeManager * shapeManager = activeTool->canvas()->shapeManager(); KoShapeLayer * activeLayer = shapeManager->selection()->activeLayer(); if (activeLayer && !activeLayer->isShapeEditable()) return false; return true; } KoToolProxy::KoToolProxy(KoCanvasBase *canvas, QObject *parent) : QObject(parent), d(new KoToolProxyPrivate(this)) { KoToolManager::instance()->priv()->registerToolProxy(this, canvas); connect(&d->scrollTimer, SIGNAL(timeout()), this, SLOT(timeout())); } KoToolProxy::~KoToolProxy() { delete d; } void KoToolProxy::paint(QPainter &painter, const KoViewConverter &converter) { if (d->activeTool) d->activeTool->paint(painter, converter); } void KoToolProxy::repaintDecorations() { if (d->activeTool) d->activeTool->repaintDecorations(); } QPointF KoToolProxy::widgetToDocument(const QPointF &widgetPoint) const { QPoint offset = QPoint(d->controller->canvasOffsetX(), d->controller->canvasOffsetY()); QPoint origin = d->controller->canvas()->documentOrigin(); QPointF viewPoint = widgetPoint.toPoint() - QPointF(origin - offset); return d->controller->canvas()->viewConverter()->viewToDocument(viewPoint); } KoCanvasBase* KoToolProxy::canvas() const { return d->controller->canvas(); } void KoToolProxy::tabletEvent(QTabletEvent *event, const QPointF &point) { // We get these events exclusively from KisToolProxy - accept them event->accept(); KoInputDevice id(event->device(), event->pointerType(), event->uniqueId()); KoToolManager::instance()->priv()->switchInputDevice(id); KoPointerEvent ev(event, point); switch (event->type()) { case QEvent::TabletPress: ev.setTabletButton(Qt::LeftButton); if (!d->tabletPressed && d->activeTool) d->activeTool->mousePressEvent(&ev); d->tabletPressed = true; break; case QEvent::TabletRelease: ev.setTabletButton(Qt::LeftButton); d->tabletPressed = false; d->scrollTimer.stop(); if (d->activeTool) d->activeTool->mouseReleaseEvent(&ev); break; case QEvent::TabletMove: if (d->tabletPressed) ev.setTabletButton(Qt::LeftButton); if (d->activeTool) d->activeTool->mouseMoveEvent(&ev); d->checkAutoScroll(ev); default: ; // ignore the rest. } d->mouseLeaveWorkaround = true; } void KoToolProxy::mousePressEvent(KoPointerEvent *ev) { d->mouseLeaveWorkaround = false; KoInputDevice id; KoToolManager::instance()->priv()->switchInputDevice(id); d->mouseDownPoint = ev->pos(); // this tries to make sure another mouse press event doesn't happen // before a release event happens if (d->isToolPressed) { mouseReleaseEvent(ev); d->tabletPressed = false; d->scrollTimer.stop(); if (d->activeTool) { d->activeTool->mouseReleaseEvent(ev); } d->isToolPressed = false; return; } QPointF globalPoint = ev->globalPos(); if (d->multiClickGlobalPoint != globalPoint) { if (qAbs(globalPoint.x() - d->multiClickGlobalPoint.x()) > 5|| qAbs(globalPoint.y() - d->multiClickGlobalPoint.y()) > 5) { d->multiClickCount = 0; } d->multiClickGlobalPoint = globalPoint; } if (d->multiClickCount && d->multiClickTimeStamp.elapsed() < QApplication::doubleClickInterval()) { // One more multiclick; d->multiClickCount++; } else { d->multiClickTimeStamp.start(); d->multiClickCount = 1; } if (d->activeTool) { switch (d->multiClickCount) { case 0: case 1: d->activeTool->mousePressEvent(ev); break; case 2: d->activeTool->mouseDoubleClickEvent(ev); break; case 3: default: d->activeTool->mouseTripleClickEvent(ev); break; } } else { d->multiClickCount = 0; ev->ignore(); } d->isToolPressed = true; } void KoToolProxy::mousePressEvent(QMouseEvent *event, const QPointF &point) { KoPointerEvent ev(event, point); mousePressEvent(&ev); } void KoToolProxy::mouseDoubleClickEvent(QMouseEvent *event, const QPointF &point) { KoPointerEvent ev(event, point); mouseDoubleClickEvent(&ev); } void KoToolProxy::mouseDoubleClickEvent(KoPointerEvent *event) { // let us handle it as any other mousepress (where we then detect multi clicks mousePressEvent(event); } void KoToolProxy::mouseMoveEvent(QMouseEvent *event, const QPointF &point) { KoPointerEvent ev(event, point); mouseMoveEvent(&ev); } void KoToolProxy::mouseMoveEvent(KoPointerEvent *event) { if (d->mouseLeaveWorkaround) { d->mouseLeaveWorkaround = false; return; } KoInputDevice id; KoToolManager::instance()->priv()->switchInputDevice(id); if (d->activeTool == 0) { event->ignore(); return; } d->activeTool->mouseMoveEvent(event); d->checkAutoScroll(*event); } void KoToolProxy::mouseReleaseEvent(QMouseEvent *event, const QPointF &point) { KoPointerEvent ev(event, point); mouseReleaseEvent(&ev); } void KoToolProxy::mouseReleaseEvent(KoPointerEvent* event) { d->mouseLeaveWorkaround = false; KoInputDevice id; KoToolManager::instance()->priv()->switchInputDevice(id); d->scrollTimer.stop(); if (d->activeTool) { d->activeTool->mouseReleaseEvent(event); } else { event->ignore(); } d->isToolPressed = false; } void KoToolProxy::keyPressEvent(QKeyEvent *event) { if (d->activeTool) d->activeTool->keyPressEvent(event); else event->ignore(); } void KoToolProxy::keyReleaseEvent(QKeyEvent *event) { if (d->activeTool) d->activeTool->keyReleaseEvent(event); else event->ignore(); d->isToolPressed = false; } void KoToolProxy::explicitUserStrokeEndRequest() { if (d->activeTool) { d->activeTool->explicitUserStrokeEndRequest(); } } QVariant KoToolProxy::inputMethodQuery(Qt::InputMethodQuery query, const KoViewConverter &converter) const { if (d->activeTool) return d->activeTool->inputMethodQuery(query, converter); return QVariant(); } void KoToolProxy::inputMethodEvent(QInputMethodEvent *event) { if (d->activeTool) d->activeTool->inputMethodEvent(event); } QMenu *KoToolProxy::popupActionsMenu() { return d->activeTool ? d->activeTool->popupActionsMenu() : 0; } void KoToolProxy::setActiveTool(KoToolBase *tool) { if (d->activeTool) disconnect(d->activeTool, SIGNAL(selectionChanged(bool)), this, SLOT(selectionChanged(bool))); d->activeTool = tool; if (tool) { connect(d->activeTool, SIGNAL(selectionChanged(bool)), this, SLOT(selectionChanged(bool))); d->selectionChanged(hasSelection()); emit toolChanged(tool->toolId()); } } +void KoToolProxy::touchEvent(QTouchEvent* event, const QPointF& point) +{ + // only one "touchpoint" events should be here + KoPointerEvent ev(event, point); + + if (!d->activeTool) return; + + switch (event->touchPointStates()) + { + case Qt::TouchPointPressed: + d->activeTool->mousePressEvent(&ev); + break; + case Qt::TouchPointMoved: + d->activeTool->mouseMoveEvent(&ev); + break; + case Qt::TouchPointReleased: + d->activeTool->mouseReleaseEvent(&ev); + break; + default: // don't care + ; + } +} + void KoToolProxyPrivate::setCanvasController(KoCanvasController *c) { controller = c; } bool KoToolProxy::hasSelection() const { return d->activeTool ? d->activeTool->hasSelection() : false; } void KoToolProxy::cut() { if (d->activeTool && d->isActiveLayerEditable()) d->activeTool->cut(); } void KoToolProxy::copy() const { if (d->activeTool) d->activeTool->copy(); } bool KoToolProxy::paste() { bool success = false; if (d->activeTool && d->isActiveLayerEditable()) { success = d->activeTool->paste(); } return success; } void KoToolProxy::dragMoveEvent(QDragMoveEvent *event, const QPointF &point) { if (d->activeTool) d->activeTool->dragMoveEvent(event, point); } void KoToolProxy::dragLeaveEvent(QDragLeaveEvent *event) { if (d->activeTool) d->activeTool->dragLeaveEvent(event); } void KoToolProxy::dropEvent(QDropEvent *event, const QPointF &point) { if (d->activeTool) d->activeTool->dropEvent(event, point); } void KoToolProxy::deleteSelection() { if (d->activeTool) d->activeTool->deleteSelection(); } void KoToolProxy::processEvent(QEvent *e) const { if(e->type()==QEvent::ShortcutOverride && d->activeTool && d->activeTool->isInTextMode() && (static_cast(e)->modifiers()==Qt::NoModifier || static_cast(e)->modifiers()==Qt::ShiftModifier)) { e->accept(); } } void KoToolProxy::requestUndoDuringStroke() { if (d->activeTool) { d->activeTool->requestUndoDuringStroke(); } } void KoToolProxy::requestStrokeCancellation() { if (d->activeTool) { d->activeTool->requestStrokeCancellation(); } } void KoToolProxy::requestStrokeEnd() { if (d->activeTool) { d->activeTool->requestStrokeEnd(); } } KoToolProxyPrivate *KoToolProxy::priv() { return d; } //have to include this because of Q_PRIVATE_SLOT #include "moc_KoToolProxy.cpp" diff --git a/libs/flake/KoToolProxy.h b/libs/flake/KoToolProxy.h index 64999d31f5..bef679cd28 100644 --- a/libs/flake/KoToolProxy.h +++ b/libs/flake/KoToolProxy.h @@ -1,184 +1,186 @@ /* This file is part of the KDE project * * Copyright (c) 2006, 2010 Boudewijn Rempt * Copyright (C) 2006-2010 Thomas Zander * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef _KO_TOOL_PROXY_H_ #define _KO_TOOL_PROXY_H_ #include "kritaflake_export.h" #include #include class QAction; class QAction; class QMouseEvent; class QKeyEvent; class QWheelEvent; class QTabletEvent; class KoCanvasBase; class KoViewConverter; class KoToolBase; class KoToolProxyPrivate; class QInputMethodEvent; class KoPointerEvent; class QDragMoveEvent; class QDragLeaveEvent; class QDropEvent; class QTouchEvent; class QPainter; class QPointF; class QMenu; /** * Tool proxy object which allows an application to address the current tool. * * Applications typically have a canvas and a canvas requires a tool for * the user to do anything. Since the flake system is responsible for handling * tools and also to change the active tool when needed we provide one class * that can be used by an application canvas to route all the native events too * which will transparently be routed to the active tool. Without the application * having to bother about which tool is active. */ class KRITAFLAKE_EXPORT KoToolProxy : public QObject { Q_OBJECT public: /** * Constructor * @param canvas Each canvas has 1 toolProxy. Pass the parent here. * @param parent a parent QObject for memory management purposes. */ explicit KoToolProxy(KoCanvasBase *canvas, QObject *parent = 0); ~KoToolProxy() override; /// Forwarded to the current KoToolBase void paint(QPainter &painter, const KoViewConverter &converter); /// Forwarded to the current KoToolBase void repaintDecorations(); /// Forwarded to the current KoToolBase void tabletEvent(QTabletEvent *event, const QPointF &point); /// Forwarded to the current KoToolBase void mousePressEvent(QMouseEvent *event, const QPointF &point); void mousePressEvent(KoPointerEvent *event); /// Forwarded to the current KoToolBase void mouseDoubleClickEvent(QMouseEvent *event, const QPointF &point); void mouseDoubleClickEvent(KoPointerEvent *event); /// Forwarded to the current KoToolBase void mouseMoveEvent(QMouseEvent *event, const QPointF &point); void mouseMoveEvent(KoPointerEvent *event); /// Forwarded to the current KoToolBase void mouseReleaseEvent(QMouseEvent *event, const QPointF &point); void mouseReleaseEvent(KoPointerEvent *event); /// Forwarded to the current KoToolBase void keyPressEvent(QKeyEvent *event); /// Forwarded to the current KoToolBase void keyReleaseEvent(QKeyEvent *event); /// Forwarded to the current KoToolBase void explicitUserStrokeEndRequest(); /// Forwarded to the current KoToolBase QVariant inputMethodQuery(Qt::InputMethodQuery query, const KoViewConverter &converter) const; /// Forwarded to the current KoToolBase void inputMethodEvent(QInputMethodEvent *event); /// Forwarded to the current KoToolBase QMenu* popupActionsMenu(); /// Forwarded to the current KoToolBase void deleteSelection(); /// This method gives the proxy a chance to do things. for example it is need to have working singlekey /// shortcuts. call it from the canvas' event function and forward it to QWidget::event() later. void processEvent(QEvent *) const; /// returns true if the current tool holds a selection bool hasSelection() const; /// Forwarded to the current KoToolBase void cut(); /// Forwarded to the current KoToolBase void copy() const; /// Forwarded to the current KoToolBase bool paste(); /// Forwarded to the current KoToolBase void dragMoveEvent(QDragMoveEvent *event, const QPointF &point); /// Forwarded to the current KoToolBase void dragLeaveEvent(QDragLeaveEvent *event); /// Forwarded to the current KoToolBase void dropEvent(QDropEvent *event, const QPointF &point); /// Set the new active tool. virtual void setActiveTool(KoToolBase *tool); + void touchEvent(QTouchEvent* event, const QPointF& point); + /// \internal KoToolProxyPrivate *priv(); protected Q_SLOTS: /// Forwarded to the current KoToolBase void requestUndoDuringStroke(); /// Forwarded to the current KoToolBase void requestStrokeCancellation(); /// Forwarded to the current KoToolBase void requestStrokeEnd(); Q_SIGNALS: /** * A tool can have a selection that is copy-able, this signal is emitted when that status changes. * @param hasSelection is true when the tool holds selected data. */ void selectionChanged(bool hasSelection); /** * Emitted every time a tool is changed. * @param toolId the id of the tool. * @see KoToolBase::toolId() */ void toolChanged(const QString &toolId); protected: virtual QPointF widgetToDocument(const QPointF &widgetPoint) const; KoCanvasBase* canvas() const; private: Q_PRIVATE_SLOT(d, void timeout()) Q_PRIVATE_SLOT(d, void selectionChanged(bool)) friend class KoToolProxyPrivate; KoToolProxyPrivate * const d; }; #endif // _KO_TOOL_PROXY_H_ diff --git a/libs/ui/canvas/kis_tool_proxy.cpp b/libs/ui/canvas/kis_tool_proxy.cpp index 5c054df964..5e98626a89 100644 --- a/libs/ui/canvas/kis_tool_proxy.cpp +++ b/libs/ui/canvas/kis_tool_proxy.cpp @@ -1,243 +1,259 @@ /* * Copyright (c) 2011 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_tool_proxy.h" #include "kis_canvas2.h" #include "input/kis_tablet_debugger.h" #include KisToolProxy::KisToolProxy(KoCanvasBase *canvas, QObject *parent) : KoToolProxy(canvas, parent), m_isActionActivated(false), m_lastAction(KisTool::Primary) { } void KisToolProxy::initializeImage(KisImageSP image) { connect(image, SIGNAL(sigUndoDuringStrokeRequested()), SLOT(requestUndoDuringStroke()), Qt::UniqueConnection); connect(image, SIGNAL(sigStrokeCancellationRequested()), SLOT(requestStrokeCancellation()), Qt::UniqueConnection); connect(image, SIGNAL(sigStrokeEndRequested()), SLOT(requestStrokeEnd()), Qt::UniqueConnection); } QPointF KisToolProxy::tabletToDocument(const QPointF &globalPos) { const QPointF pos = globalPos - QPointF(canvas()->canvasWidget()->mapToGlobal(QPoint(0, 0))); return widgetToDocument(pos); } QPointF KisToolProxy::widgetToDocument(const QPointF &widgetPoint) const { KisCanvas2 *kritaCanvas = dynamic_cast(canvas()); Q_ASSERT(kritaCanvas); return kritaCanvas->coordinatesConverter()->widgetToDocument(widgetPoint); } KoPointerEvent KisToolProxy::convertEventToPointerEvent(QEvent *event, const QPointF &docPoint, bool *result) { switch (event->type()) { case QEvent::TabletPress: case QEvent::TabletRelease: case QEvent::TabletMove: { *result = true; QTabletEvent *tabletEvent = static_cast(event); KoPointerEvent ev(tabletEvent, docPoint); ev.setTabletButton(Qt::LeftButton); return ev; } case QEvent::MouseButtonPress: case QEvent::MouseButtonDblClick: case QEvent::MouseButtonRelease: case QEvent::MouseMove: { *result = true; QMouseEvent *mouseEvent = static_cast(event); return KoPointerEvent(mouseEvent, docPoint); } + case QEvent::TouchBegin: + case QEvent::TouchUpdate: + case QEvent::TouchEnd: + { + *result = true; + QTouchEvent *touchEvent = static_cast (event); + return KoPointerEvent(touchEvent, docPoint); + } default: ; } *result = false; QMouseEvent fakeEvent(QEvent::MouseMove, QPoint(), Qt::NoButton, Qt::NoButton, Qt::NoModifier); return KoPointerEvent(&fakeEvent, QPointF()); } void KisToolProxy::forwardHoverEvent(QEvent *event) { switch (event->type()) { case QEvent::TabletMove: { QTabletEvent *tabletEvent = static_cast(event); QPointF docPoint = widgetToDocument(tabletEvent->posF()); this->tabletEvent(tabletEvent, docPoint); return; } case QEvent::MouseMove: { QMouseEvent *mouseEvent = static_cast(event); QPointF docPoint = widgetToDocument(mouseEvent->localPos()); mouseMoveEvent(mouseEvent, docPoint); return; } default: { qWarning() << "forwardHoverEvent encountered unknown event type."; return; } } } bool KisToolProxy::forwardEvent(ActionState state, KisTool::ToolAction action, QEvent *event, QEvent *originalEvent) { bool retval = true; QTabletEvent *tabletEvent = dynamic_cast(event); QMouseEvent *mouseEvent = dynamic_cast(event); + QTouchEvent *touchEvent = dynamic_cast (event); if (tabletEvent) { QPointF docPoint = widgetToDocument(tabletEvent->posF()); tabletEvent->accept(); this->tabletEvent(tabletEvent, docPoint); forwardToTool(state, action, tabletEvent, docPoint); retval = tabletEvent->isAccepted(); } else if (mouseEvent) { QPointF docPoint = widgetToDocument(mouseEvent->localPos()); mouseEvent->accept(); if (mouseEvent->type() == QEvent::MouseButtonPress) { mousePressEvent(mouseEvent, docPoint); } else if (mouseEvent->type() == QEvent::MouseButtonDblClick) { mouseDoubleClickEvent(mouseEvent, docPoint); } else if (mouseEvent->type() == QEvent::MouseButtonRelease) { mouseReleaseEvent(mouseEvent, docPoint); } else if (mouseEvent->type() == QEvent::MouseMove) { mouseMoveEvent(mouseEvent, docPoint); } forwardToTool(state, action, originalEvent, docPoint); retval = mouseEvent->isAccepted(); } + else if (touchEvent) { + QPointF docPoint = widgetToDocument(touchEvent->touchPoints().at(0).pos()); + touchEvent->accept(); + this->touchEvent(touchEvent, docPoint); + forwardToTool(state, action, touchEvent, docPoint); + retval = touchEvent->isAccepted(); + } else if (event && event->type() == QEvent::KeyPress) { QKeyEvent* kevent = static_cast(event); keyPressEvent(kevent); } else if (event && event->type() == QEvent::KeyRelease) { QKeyEvent* kevent = static_cast(event); keyReleaseEvent(kevent); } return retval; } void KisToolProxy::forwardToTool(ActionState state, KisTool::ToolAction action, QEvent *event, const QPointF &docPoint) { bool eventValid = false; KoPointerEvent ev = convertEventToPointerEvent(event, docPoint, &eventValid); KisTool *activeTool = dynamic_cast(priv()->activeTool); if (!eventValid || !activeTool) return; switch (state) { case BEGIN: if (action == KisTool::Primary) { if (event->type() == QEvent::MouseButtonDblClick) { activeTool->beginPrimaryDoubleClickAction(&ev); } else { activeTool->beginPrimaryAction(&ev); } } else { if (event->type() == QEvent::MouseButtonDblClick) { activeTool->beginAlternateDoubleClickAction(&ev, KisTool::actionToAlternateAction(action)); } else { activeTool->beginAlternateAction(&ev, KisTool::actionToAlternateAction(action)); } } break; case CONTINUE: if (action == KisTool::Primary) { activeTool->continuePrimaryAction(&ev); } else { activeTool->continueAlternateAction(&ev, KisTool::actionToAlternateAction(action)); } break; case END: if (action == KisTool::Primary) { activeTool->endPrimaryAction(&ev); } else { activeTool->endAlternateAction(&ev, KisTool::actionToAlternateAction(action)); } break; } } bool KisToolProxy::primaryActionSupportsHiResEvents() const { KisTool *activeTool = dynamic_cast(const_cast(this)->priv()->activeTool); return activeTool && activeTool->primaryActionSupportsHiResEvents(); } void KisToolProxy::setActiveTool(KoToolBase *tool) { if (!tool) return; if (m_isActionActivated) { deactivateToolAction(m_lastAction); KoToolProxy::setActiveTool(tool); activateToolAction(m_lastAction); } else { KoToolProxy::setActiveTool(tool); } } void KisToolProxy::activateToolAction(KisTool::ToolAction action) { KisTool *activeTool = dynamic_cast(const_cast(this)->priv()->activeTool); if (activeTool) { if (action == KisTool::Primary) { activeTool->activatePrimaryAction(); } else { activeTool->activateAlternateAction(KisTool::actionToAlternateAction(action)); } } m_isActionActivated = true; m_lastAction = action; } void KisToolProxy::deactivateToolAction(KisTool::ToolAction action) { KisTool *activeTool = dynamic_cast(const_cast(this)->priv()->activeTool); if (activeTool) { if (action == KisTool::Primary) { activeTool->deactivatePrimaryAction(); } else { activeTool->deactivateAlternateAction(KisTool::actionToAlternateAction(action)); } } m_isActionActivated = false; m_lastAction = action; } diff --git a/libs/ui/input/kis_input_manager.cpp b/libs/ui/input/kis_input_manager.cpp index 88d2331ab4..bd4424ee08 100644 --- a/libs/ui/input/kis_input_manager.cpp +++ b/libs/ui/input/kis_input_manager.cpp @@ -1,761 +1,815 @@ /* This file is part of the KDE project * * Copyright (C) 2012 Arjen Hiemstra * Copyright (C) 2015 Michael Abrahams * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_input_manager.h" #include #include #include #include #include #include #include #include "kis_tool_proxy.h" #include #include #include #include #include #include #include "kis_abstract_input_action.h" #include "kis_tool_invocation_action.h" #include "kis_pan_action.h" #include "kis_alternate_invocation_action.h" #include "kis_rotate_canvas_action.h" #include "kis_zoom_action.h" #include "kis_show_palette_action.h" #include "kis_change_primary_setting_action.h" #include "kis_shortcut_matcher.h" #include "kis_stroke_shortcut.h" #include "kis_single_action_shortcut.h" #include "kis_touch_shortcut.h" #include "kis_input_profile.h" #include "kis_input_profile_manager.h" #include "kis_shortcut_configuration.h" #include #include #include "kis_extended_modifiers_mapper.h" #include "kis_input_manager_p.h" template uint qHash(QPointer value) { return reinterpret_cast(value.data()); } KisInputManager::KisInputManager(QObject *parent) : QObject(parent), d(new Private(this)) { d->setupActions(); connect(KoToolManager::instance(), SIGNAL(aboutToChangeTool(KoCanvasController*)), SLOT(slotAboutToChangeTool())); connect(KoToolManager::instance(), SIGNAL(changedTool(KoCanvasController*,int)), SLOT(slotToolChanged())); connect(&d->moveEventCompressor, SIGNAL(timeout()), SLOT(slotCompressedMoveEvent())); QApplication::instance()-> installEventFilter(new Private::ProximityNotifier(d, this)); } KisInputManager::~KisInputManager() { delete d; } void KisInputManager::addTrackedCanvas(KisCanvas2 *canvas) { d->canvasSwitcher.addCanvas(canvas); } void KisInputManager::removeTrackedCanvas(KisCanvas2 *canvas) { d->canvasSwitcher.removeCanvas(canvas); } void KisInputManager::toggleTabletLogger() { KisTabletDebugger::instance()->toggleDebugging(); } void KisInputManager::attachPriorityEventFilter(QObject *filter, int priority) { Private::PriorityList::iterator begin = d->priorityEventFilter.begin(); Private::PriorityList::iterator it = begin; Private::PriorityList::iterator end = d->priorityEventFilter.end(); it = std::find_if(begin, end, [filter] (const Private::PriorityPair &a) { return a.second == filter; }); if (it != end) return; it = std::find_if(begin, end, [priority] (const Private::PriorityPair &a) { return a.first > priority; }); d->priorityEventFilter.insert(it, qMakePair(priority, filter)); d->priorityEventFilterSeqNo++; } void KisInputManager::detachPriorityEventFilter(QObject *filter) { Private::PriorityList::iterator it = d->priorityEventFilter.begin(); Private::PriorityList::iterator end = d->priorityEventFilter.end(); it = std::find_if(it, end, [filter] (const Private::PriorityPair &a) { return a.second == filter; }); if (it != end) { d->priorityEventFilter.erase(it); } } void KisInputManager::setupAsEventFilter(QObject *receiver) { if (d->eventsReceiver) { d->eventsReceiver->removeEventFilter(this); } d->eventsReceiver = receiver; if (d->eventsReceiver) { d->eventsReceiver->installEventFilter(this); } } void KisInputManager::stopIgnoringEvents() { d->allowMouseEvents(); } #if defined (__clang__) #pragma GCC diagnostic ignored "-Wswitch" #endif bool KisInputManager::eventFilter(QObject* object, QEvent* event) { if (object != d->eventsReceiver) return false; if (d->eventEater.eventFilter(object, event)) return false; if (!d->matcher.hasRunningShortcut()) { int savedPriorityEventFilterSeqNo = d->priorityEventFilterSeqNo; for (auto it = d->priorityEventFilter.begin(); it != d->priorityEventFilter.end(); /*noop*/) { const QPointer &filter = it->second; if (filter.isNull()) { it = d->priorityEventFilter.erase(it); d->priorityEventFilterSeqNo++; savedPriorityEventFilterSeqNo++; continue; } if (filter->eventFilter(object, event)) return true; /** * If the filter removed itself from the filters list or * added something there, just exit the loop */ if (d->priorityEventFilterSeqNo != savedPriorityEventFilterSeqNo) { return true; } ++it; } // KoToolProxy needs to pre-process some events to ensure the // global shortcuts (not the input manager's ones) are not // executed, in particular, this line will accept events when the // tool is in text editing, preventing shortcut triggering if (d->toolProxy) { d->toolProxy->processEvent(event); } } // Continue with the actual switch statement... return eventFilterImpl(event); } // Qt's events do not have copy-ctors yes, so we should emulate them // See https://bugreports.qt.io/browse/QTBUG-72488 template void copyEventHack(Event *src, QScopedPointer &dst); template<> void copyEventHack(QMouseEvent *src, QScopedPointer &dst) { QMouseEvent *tmp = new QMouseEvent(src->type(), src->localPos(), src->windowPos(), src->screenPos(), src->button(), src->buttons(), src->modifiers(), src->source()); tmp->setTimestamp(src->timestamp()); dst.reset(tmp); } template<> void copyEventHack(QTabletEvent *src, QScopedPointer &dst) { QTabletEvent *tmp = new QTabletEvent(src->type(), src->posF(), src->globalPosF(), src->device(), src->pointerType(), src->pressure(), src->xTilt(), src->yTilt(), src->tangentialPressure(), src->rotation(), src->z(), src->modifiers(), src->uniqueId(), src->button(), src->buttons()); tmp->setTimestamp(src->timestamp()); dst.reset(tmp); } +template<> void copyEventHack(QTouchEvent *src, QScopedPointer &dst) { + QTouchEvent *tmp = new QTouchEvent(src->type(), + src->device(), + src->modifiers(), + src->touchPointStates(), + src->touchPoints()); + tmp->setTimestamp(src->timestamp()); + dst.reset(tmp); +} template bool KisInputManager::compressMoveEventCommon(Event *event) { /** * We construct a copy of this event object, so we must ensure it * has a correct type. */ static_assert(std::is_same::value || - std::is_same::value, + std::is_same::value || + std::is_same::value, "event should be a mouse or a tablet event"); bool retval = false; /** * Compress the events if the tool doesn't need high resolution input */ if ((event->type() == QEvent::MouseMove || - event->type() == QEvent::TabletMove) && + event->type() == QEvent::TabletMove || + event->type() == QEvent::TouchUpdate) && (!d->matcher.supportsHiResInputEvents() || d->testingCompressBrushEvents)) { copyEventHack(event, d->compressedMoveEvent); d->moveEventCompressor.start(); /** * On Linux Qt eats the rest of unneeded events if we * ignore the first of the chunk of tablet events. So * generally we should never activate this feature. Only * for testing purposes! */ if (d->testingAcceptCompressedTabletEvents) { event->setAccepted(true); } retval = true; } else { slotCompressedMoveEvent(); retval = d->handleCompressedTabletEvent(event); } return retval; } bool shouldResetWheelDelta(QEvent * event) { return event->type() == QEvent::FocusIn || event->type() == QEvent::FocusOut || event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonRelease || event->type() == QEvent::MouseButtonDblClick || event->type() == QEvent::TabletPress || event->type() == QEvent::TabletRelease || event->type() == QEvent::Enter || event->type() == QEvent::Leave || event->type() == QEvent::TouchBegin || event->type() == QEvent::TouchEnd || event->type() == QEvent::TouchCancel || event->type() == QEvent::NativeGesture; } bool KisInputManager::eventFilterImpl(QEvent * event) { bool retval = false; if (shouldResetWheelDelta(event)) { d->accumulatedScrollDelta = 0; } + switch (event->type()) { case QEvent::MouseButtonPress: case QEvent::MouseButtonDblClick: { d->debugEvent(event); if (d->touchHasBlockedPressEvents) break; QMouseEvent *mouseEvent = static_cast(event); if (d->tryHidePopupPalette()) { retval = true; } else { //Make sure the input actions know we are active. KisAbstractInputAction::setInputManager(this); retval = d->matcher.buttonPressed(mouseEvent->button(), mouseEvent); } //Reset signal compressor to prevent processing events before press late d->resetCompressor(); event->setAccepted(retval); break; } case QEvent::MouseButtonRelease: { d->debugEvent(event); if (d->touchHasBlockedPressEvents) break; QMouseEvent *mouseEvent = static_cast(event); retval = d->matcher.buttonReleased(mouseEvent->button(), mouseEvent); event->setAccepted(retval); break; } case QEvent::ShortcutOverride: { d->debugEvent(event); QKeyEvent *keyEvent = static_cast(event); Qt::Key key = KisExtendedModifiersMapper::workaroundShiftAltMetaHell(keyEvent); if (!keyEvent->isAutoRepeat()) { retval = d->matcher.keyPressed(key); } else { retval = d->matcher.autoRepeatedKeyPressed(key); } /** * Workaround for temporary switching of tools by * KoCanvasControllerWidget. We don't need this switch because * we handle it ourselves. */ retval |= !d->forwardAllEventsToTool && (keyEvent->key() == Qt::Key_Space || keyEvent->key() == Qt::Key_Escape); break; } case QEvent::KeyRelease: { d->debugEvent(event); QKeyEvent *keyEvent = static_cast(event); if (!keyEvent->isAutoRepeat()) { Qt::Key key = KisExtendedModifiersMapper::workaroundShiftAltMetaHell(keyEvent); retval = d->matcher.keyReleased(key); } break; } case QEvent::MouseMove: { d->debugEvent(event); QMouseEvent *mouseEvent = static_cast(event); retval = compressMoveEventCommon(mouseEvent); break; } case QEvent::Wheel: { d->debugEvent(event); QWheelEvent *wheelEvent = static_cast(event); #ifdef Q_OS_MACOS // Some QT wheel events are actually touch pad pan events. From the QT docs: // "Wheel events are generated for both mouse wheels and trackpad scroll gestures." // We differentiate between touchpad events and real mouse wheels by inspecting the // event source. if (wheelEvent->source() == Qt::MouseEventSource::MouseEventSynthesizedBySystem) { KisAbstractInputAction::setInputManager(this); retval = d->matcher.wheelEvent(KisSingleActionShortcut::WheelTrackpad, wheelEvent); break; } #endif d->accumulatedScrollDelta += wheelEvent->delta(); KisSingleActionShortcut::WheelAction action; /** * Ignore delta 0 events on OSX, since they are triggered by tablet * proximity when using Wacom devices. */ #ifdef Q_OS_MACOS if(wheelEvent->delta() == 0) { retval = true; break; } #endif if (wheelEvent->orientation() == Qt::Horizontal) { if(wheelEvent->delta() < 0) { action = KisSingleActionShortcut::WheelRight; } else { action = KisSingleActionShortcut::WheelLeft; } } else { if(wheelEvent->delta() > 0) { action = KisSingleActionShortcut::WheelUp; } else { action = KisSingleActionShortcut::WheelDown; } } if (qAbs(d->accumulatedScrollDelta) >= QWheelEvent::DefaultDeltasPerStep) { //Make sure the input actions know we are active. KisAbstractInputAction::setInputManager(this); retval = d->matcher.wheelEvent(action, wheelEvent); d->accumulatedScrollDelta = 0; } else { retval = true; } break; } case QEvent::Enter: d->debugEvent(event); //Make sure the input actions know we are active. KisAbstractInputAction::setInputManager(this); if (!d->containsPointer) { d->containsPointer = true; d->allowMouseEvents(); d->touchHasBlockedPressEvents = false; } d->matcher.enterEvent(); break; case QEvent::Leave: d->debugEvent(event); d->containsPointer = false; /** * We won't get a TabletProximityLeave event when the tablet * is hovering above some other widget, so restore cursor * events processing right now. */ d->allowMouseEvents(); d->touchHasBlockedPressEvents = false; d->matcher.leaveEvent(); break; case QEvent::FocusIn: d->debugEvent(event); KisAbstractInputAction::setInputManager(this); //Clear all state so we don't have half-matched shortcuts dangling around. d->matcher.reinitialize(); { // Emulate pressing of the key that are already pressed KisExtendedModifiersMapper mapper; Qt::KeyboardModifiers modifiers = mapper.queryStandardModifiers(); Q_FOREACH (Qt::Key key, mapper.queryExtendedModifiers()) { QKeyEvent kevent(QEvent::ShortcutOverride, key, modifiers); eventFilterImpl(&kevent); } } d->allowMouseEvents(); break; case QEvent::FocusOut: { d->debugEvent(event); KisAbstractInputAction::setInputManager(this); QPointF currentLocalPos = canvas()->canvasWidget()->mapFromGlobal(QCursor::pos()); d->matcher.lostFocusEvent(currentLocalPos); break; } case QEvent::TabletPress: { d->debugEvent(event); QTabletEvent *tabletEvent = static_cast(event); if (d->tryHidePopupPalette()) { retval = true; } else { //Make sure the input actions know we are active. KisAbstractInputAction::setInputManager(this); retval = d->matcher.buttonPressed(tabletEvent->button(), tabletEvent); if (!d->containsPointer) { d->containsPointer = true; d->touchHasBlockedPressEvents = false; } } event->setAccepted(true); retval = true; d->blockMouseEvents(); //Reset signal compressor to prevent processing events before press late d->resetCompressor(); #if defined Q_OS_LINUX && !defined QT_HAS_ENTER_LEAVE_PATCH // remove this hack when this patch is integrated: // https://codereview.qt-project.org/#/c/255384/ event->setAccepted(false); d->eatOneMousePress(); #elif defined Q_OS_WIN32 /** * Windows is the only platform that synthesizes mouse events for * the tablet on OS-level, that is, even when we accept the event */ d->eatOneMousePress(); #endif break; } case QEvent::TabletMove: { d->debugEvent(event); QTabletEvent *tabletEvent = static_cast(event); retval = compressMoveEventCommon(tabletEvent); if (d->tabletLatencyTracker) { d->tabletLatencyTracker->push(tabletEvent->timestamp()); } /** * The flow of tablet events means the tablet is in the * proximity area, so activate it even when the * TabletEnterProximity event was missed (may happen when * changing focus of the window with tablet in the proximity * area) */ d->blockMouseEvents(); #if defined Q_OS_LINUX && !defined QT_HAS_ENTER_LEAVE_PATCH // remove this hack when this patch is integrated: // https://codereview.qt-project.org/#/c/255384/ event->setAccepted(false); #endif break; } case QEvent::TabletRelease: { #ifdef Q_OS_MAC d->allowMouseEvents(); #endif d->debugEvent(event); QTabletEvent *tabletEvent = static_cast(event); retval = d->matcher.buttonReleased(tabletEvent->button(), tabletEvent); retval = true; event->setAccepted(true); #if defined Q_OS_LINUX && !defined QT_HAS_ENTER_LEAVE_PATCH // remove this hack when this patch is integrated: // https://codereview.qt-project.org/#/c/255384/ event->setAccepted(false); #endif break; } case QEvent::TouchBegin: { if (startTouch(retval)) { - QTouchEvent *tevent = static_cast(event); + QTouchEvent *touchEvent = static_cast (event); KisAbstractInputAction::setInputManager(this); - retval = d->matcher.touchBeginEvent(tevent); + + if (!KisConfig(true).disableTouchOnCanvas() + && touchEvent->touchPoints().count() == 1 + && touchEvent->touchPointStates() != Qt::TouchPointStationary) + { + retval = d->matcher.buttonPressed(Qt::LeftButton, touchEvent); + d->touchStrokeShortcut = true; + } + else { + retval = d->matcher.touchBeginEvent(touchEvent); + } event->accept(); } + + // if the event isn't handled, Qt starts to send MouseEvents + if (!KisConfig(true).disableTouchOnCanvas()) + retval = true; break; } + case QEvent::TouchUpdate: { - QTouchEvent *tevent = static_cast(event); + QTouchEvent *touchEvent = static_cast(event); + #ifdef Q_OS_MAC int count = 0; Q_FOREACH (const QTouchEvent::TouchPoint &point, tevent->touchPoints()) { if (point.state() != Qt::TouchPointReleased) { count++; } } if (count < 2 && tevent->touchPoints().length() > count) { d->touchHasBlockedPressEvents = false; retval = d->matcher.touchEndEvent(tevent); } else { #endif - d->touchHasBlockedPressEvents = KisConfig(true).disableTouchOnCanvas(); - KisAbstractInputAction::setInputManager(this); - retval = d->matcher.touchUpdateEvent(tevent); + if (!KisConfig(true).disableTouchOnCanvas() + && !d->touchHasBlockedPressEvents + && touchEvent->touchPoints().count() == 1 + && touchEvent->touchPointStates() != Qt::TouchPointStationary) + { + retval = compressMoveEventCommon(touchEvent); + d->blockMouseEvents(); + d->resetCompressor(); + } + else { + KisAbstractInputAction::setInputManager(this); + d->touchHasBlockedPressEvents = true; + + retval = d->matcher.touchUpdateEvent(touchEvent); + } #ifdef Q_OS_MACOS } #endif + // if the event isn't handled, Qt starts to send MouseEvents + if (!KisConfig(true).disableTouchOnCanvas()) + retval = true; + event->accept(); break; } + case QEvent::TouchEnd: { endTouch(); - QTouchEvent *tevent = static_cast(event); - retval = d->matcher.touchEndEvent(tevent); + QTouchEvent *touchEvent = static_cast(event); + retval = d->matcher.touchEndEvent(touchEvent); + if (d->touchStrokeShortcut) + { + retval = d->matcher.buttonReleased(Qt::LeftButton, touchEvent); + d->touchStrokeShortcut = false; + } + + // if the event isn't handled, Qt starts to send MouseEvents + if (!KisConfig(true).disableTouchOnCanvas()) + retval = true; + event->accept(); break; } case QEvent::NativeGesture: { QNativeGestureEvent *gevent = static_cast(event); switch (gevent->gestureType()) { case Qt::BeginNativeGesture: { if (startTouch(retval)) { KisAbstractInputAction::setInputManager(this); retval = d->matcher.nativeGestureBeginEvent(gevent); event->accept(); } break; } case Qt::EndNativeGesture: { endTouch(); retval = d->matcher.nativeGestureEndEvent(gevent); event->accept(); break; } default: { KisAbstractInputAction::setInputManager(this); retval = d->matcher.nativeGestureEvent(gevent); event->accept(); break; } } break; } default: break; } return !retval ? d->processUnhandledEvent(event) : true; } bool KisInputManager::startTouch(bool &retval) { - d->touchHasBlockedPressEvents = KisConfig(true).disableTouchOnCanvas(); // Touch rejection: if touch is disabled on canvas, no need to block mouse press events if (KisConfig(true).disableTouchOnCanvas()) { d->eatOneMousePress(); } if (d->tryHidePopupPalette()) { retval = true; return false; } else { return true; } } void KisInputManager::endTouch() { d->touchHasBlockedPressEvents = false; } void KisInputManager::slotCompressedMoveEvent() { if (d->compressedMoveEvent) { // d->touchHasBlockedPressEvents = false; (void) d->handleCompressedTabletEvent(d->compressedMoveEvent.data()); d->compressedMoveEvent.reset(); //dbgInput << "Compressed move event received."; } else { //dbgInput << "Unexpected empty move event"; } } KisCanvas2* KisInputManager::canvas() const { return d->canvas; } QPointer KisInputManager::toolProxy() const { return d->toolProxy; } void KisInputManager::slotAboutToChangeTool() { QPointF currentLocalPos; if (canvas() && canvas()->canvasWidget()) { currentLocalPos = canvas()->canvasWidget()->mapFromGlobal(QCursor::pos()); } d->matcher.lostFocusEvent(currentLocalPos); } void KisInputManager::slotToolChanged() { if (!d->canvas) return; KoToolManager *toolManager = KoToolManager::instance(); KoToolBase *tool = toolManager->toolById(canvas(), toolManager->activeToolId()); if (tool) { d->setMaskSyntheticEvents(tool->maskSyntheticEvents()); if (tool->isInTextMode()) { d->forwardAllEventsToTool = true; d->matcher.suppressAllActions(true); } else { d->forwardAllEventsToTool = false; d->matcher.suppressAllActions(false); } } } void KisInputManager::profileChanged() { d->matcher.clearShortcuts(); KisInputProfile *profile = KisInputProfileManager::instance()->currentProfile(); if (profile) { const QList shortcuts = profile->allShortcuts(); for (KisShortcutConfiguration * const shortcut : shortcuts) { dbgUI << "Adding shortcut" << shortcut->keys() << "for action" << shortcut->action()->name(); switch(shortcut->type()) { case KisShortcutConfiguration::KeyCombinationType: d->addKeyShortcut(shortcut->action(), shortcut->mode(), shortcut->keys()); break; case KisShortcutConfiguration::MouseButtonType: d->addStrokeShortcut(shortcut->action(), shortcut->mode(), shortcut->keys(), shortcut->buttons()); break; case KisShortcutConfiguration::MouseWheelType: d->addWheelShortcut(shortcut->action(), shortcut->mode(), shortcut->keys(), shortcut->wheel()); break; case KisShortcutConfiguration::GestureType: if (!d->addNativeGestureShortcut(shortcut->action(), shortcut->mode(), shortcut->gesture())) { d->addTouchShortcut(shortcut->action(), shortcut->mode(), shortcut->gesture()); } break; default: break; } } } else { dbgInput << "No Input Profile Found: canvas interaction will be impossible"; } } diff --git a/libs/ui/input/kis_input_manager_p.h b/libs/ui/input/kis_input_manager_p.h index b0ad408ec3..d936594296 100644 --- a/libs/ui/input/kis_input_manager_p.h +++ b/libs/ui/input/kis_input_manager_p.h @@ -1,159 +1,161 @@ /* * Copyright (C) 2015 Michael Abrahams * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include "kis_input_manager.h" #include "kis_shortcut_matcher.h" #include "kis_shortcut_configuration.h" #include "kis_canvas2.h" #include "kis_tool_proxy.h" #include "kis_signal_compressor.h" #include "input/kis_tablet_debugger.h" #include "kis_timed_signal_threshold.h" #include "kis_signal_auto_connection.h" #include "kis_latency_tracker.h" class KisToolInvocationAction; class KisInputManager::Private { public: Private(KisInputManager *qq); bool tryHidePopupPalette(); void addStrokeShortcut(KisAbstractInputAction* action, int index, const QList< Qt::Key >& modifiers, Qt::MouseButtons buttons); void addKeyShortcut(KisAbstractInputAction* action, int index,const QList &keys); void addTouchShortcut( KisAbstractInputAction* action, int index, KisShortcutConfiguration::GestureAction gesture ); bool addNativeGestureShortcut( KisAbstractInputAction* action, int index, KisShortcutConfiguration::GestureAction gesture ); void addWheelShortcut(KisAbstractInputAction* action, int index, const QList< Qt::Key >& modifiers, KisShortcutConfiguration::MouseWheelMovement wheelAction); bool processUnhandledEvent(QEvent *event); void setupActions(); bool handleCompressedTabletEvent(QEvent *event); KisInputManager *q; QPointer canvas; QPointer toolProxy; bool forwardAllEventsToTool = false; bool ignoringQtCursorEvents(); bool touchHasBlockedPressEvents = false; KisShortcutMatcher matcher; KisToolInvocationAction *defaultInputAction = 0; QObject *eventsReceiver = 0; KisSignalCompressor moveEventCompressor; QScopedPointer compressedMoveEvent; bool testingAcceptCompressedTabletEvents = false; bool testingCompressBrushEvents = false; typedef QPair > PriorityPair; typedef QList PriorityList; PriorityList priorityEventFilter; int priorityEventFilterSeqNo; + bool touchStrokeShortcut = false; + void blockMouseEvents(); void allowMouseEvents(); void eatOneMousePress(); void setMaskSyntheticEvents(bool value); void resetCompressor(); template void debugEvent(QEvent *event) { if (!KisTabletDebugger::instance()->debugEnabled()) return; QString msg1 = useBlocking && ignoringQtCursorEvents() ? "[BLOCKED] " : "[ ]"; Event *specificEvent = static_cast(event); dbgTablet << KisTabletDebugger::instance()->eventToString(*specificEvent, msg1); } class ProximityNotifier : public QObject { public: ProximityNotifier(Private *_d, QObject *p); bool eventFilter(QObject* object, QEvent* event ) override; private: KisInputManager::Private *d; }; class CanvasSwitcher : public QObject { public: CanvasSwitcher(Private *_d, QObject *p); void addCanvas(KisCanvas2 *canvas); void removeCanvas(KisCanvas2 *canvas); bool eventFilter(QObject* object, QEvent* event ) override; private: void setupFocusThreshold(QObject *object); private: KisInputManager::Private *d; QMap> canvasResolver; int eatOneMouseStroke; KisTimedSignalThreshold focusSwitchThreshold; KisSignalAutoConnectionsStore thresholdConnections; }; CanvasSwitcher canvasSwitcher; struct EventEater { EventEater(); bool eventFilter(QObject* target, QEvent* event); // This should be called after we're certain a tablet stroke has started. void activate(); // This should be called after a tablet stroke has ended. void deactivate(); // On Windows, we sometimes receive mouse events very late, so watch & wait. void eatOneMousePress(); bool hungry{false}; // Continue eating mouse strokes bool peckish{false}; // Eat a single mouse press event bool eatSyntheticEvents{false}; // Mask all synthetic events bool activateSecondaryButtonsWorkaround{false}; // Use mouse events for right- and middle-clicks }; EventEater eventEater; bool containsPointer = false; int accumulatedScrollDelta = 0; class TabletLatencyTracker : public KisLatencyTracker { protected: virtual qint64 currentTimestamp() const; virtual void print(const QString &message); }; KisSharedPtr tabletLatencyTracker; }; diff --git a/libs/ui/input/kis_shortcut_matcher.cpp b/libs/ui/input/kis_shortcut_matcher.cpp index 352bb2f653..183149d8e7 100644 --- a/libs/ui/input/kis_shortcut_matcher.cpp +++ b/libs/ui/input/kis_shortcut_matcher.cpp @@ -1,687 +1,690 @@ /* * Copyright (c) 2012 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_shortcut_matcher.h" #include #include #include #include "kis_assert.h" #include "kis_abstract_input_action.h" #include "kis_stroke_shortcut.h" #include "kis_touch_shortcut.h" #include "kis_native_gesture_shortcut.h" +#include "kis_config.h" #ifdef DEBUG_MATCHER #include #define DEBUG_ACTION(text) dbgInput << __FUNCTION__ << "-" << text; #define DEBUG_SHORTCUT(text, shortcut) dbgInput << __FUNCTION__ << "-" << text << "act:" << shortcut->action()->name(); #define DEBUG_KEY(text) dbgInput << __FUNCTION__ << "-" << text << "keys:" << m_d->keys; #define DEBUG_BUTTON_ACTION(text, button) dbgInput << __FUNCTION__ << "-" << text << "button:" << button << "btns:" << m_d->buttons << "keys:" << m_d->keys; #define DEBUG_EVENT_ACTION(text, event) if (event) {dbgInput << __FUNCTION__ << "-" << text << "type:" << event->type();} #else #define DEBUG_ACTION(text) #define DEBUG_KEY(text) #define DEBUG_SHORTCUT(text, shortcut) #define DEBUG_BUTTON_ACTION(text, button) #define DEBUG_EVENT_ACTION(text, event) #endif class Q_DECL_HIDDEN KisShortcutMatcher::Private { public: Private() : runningShortcut(0) , readyShortcut(0) , touchShortcut(0) , nativeGestureShortcut(0) , actionGroupMask([] () { return AllActionGroup; }) , suppressAllActions(false) , cursorEntered(false) , usingTouch(false) , usingNativeGesture(false) {} ~Private() { qDeleteAll(singleActionShortcuts); qDeleteAll(strokeShortcuts); qDeleteAll(touchShortcuts); } QList singleActionShortcuts; QList strokeShortcuts; QList touchShortcuts; QList nativeGestureShortcuts; QSet keys; // Model of currently pressed keys QSet buttons; // Model of currently pressed buttons KisStrokeShortcut *runningShortcut; KisStrokeShortcut *readyShortcut; QList candidateShortcuts; KisTouchShortcut *touchShortcut; KisNativeGestureShortcut *nativeGestureShortcut; std::function actionGroupMask; bool suppressAllActions; bool cursorEntered; bool usingTouch; bool usingNativeGesture; inline bool actionsSuppressed() const { - return suppressAllActions || !cursorEntered; + return (suppressAllActions || !cursorEntered) + && KisConfig(true).disableTouchOnCanvas(); } inline bool actionsSuppressedIgnoreFocus() const { return suppressAllActions; } + // only for touch events with touchPoints count >= 2 inline bool isUsingTouch() const { return usingTouch || usingNativeGesture; } }; KisShortcutMatcher::KisShortcutMatcher() : m_d(new Private) {} KisShortcutMatcher::~KisShortcutMatcher() { delete m_d; } bool KisShortcutMatcher::hasRunningShortcut() const { return m_d->runningShortcut; } void KisShortcutMatcher::addShortcut(KisSingleActionShortcut *shortcut) { m_d->singleActionShortcuts.append(shortcut); } void KisShortcutMatcher::addShortcut(KisStrokeShortcut *shortcut) { m_d->strokeShortcuts.append(shortcut); } void KisShortcutMatcher::addShortcut( KisTouchShortcut* shortcut ) { m_d->touchShortcuts.append(shortcut); } void KisShortcutMatcher::addShortcut(KisNativeGestureShortcut *shortcut) { m_d->nativeGestureShortcuts.append(shortcut); } bool KisShortcutMatcher::supportsHiResInputEvents() { return m_d->runningShortcut && m_d->runningShortcut->action() && m_d->runningShortcut->action()->supportsHiResInputEvents(); } bool KisShortcutMatcher::keyPressed(Qt::Key key) { bool retval = false; if (m_d->keys.contains(key)) { DEBUG_ACTION("Peculiar, records show key was already pressed"); } if (!m_d->runningShortcut) { retval = tryRunSingleActionShortcutImpl(key, (QEvent*)0, m_d->keys); } m_d->keys.insert(key); DEBUG_KEY("Pressed"); if (!m_d->runningShortcut) { prepareReadyShortcuts(); tryActivateReadyShortcut(); } return retval; } bool KisShortcutMatcher::autoRepeatedKeyPressed(Qt::Key key) { bool retval = false; if (!m_d->keys.contains(key)) { DEBUG_ACTION("Peculiar, autorepeated key but can't remember it was pressed"); } if (!m_d->runningShortcut) { // Autorepeated key should not be included in the shortcut QSet filteredKeys = m_d->keys; filteredKeys.remove(key); retval = tryRunSingleActionShortcutImpl(key, (QEvent*)0, filteredKeys); } return retval; } bool KisShortcutMatcher::keyReleased(Qt::Key key) { if (!m_d->keys.contains(key)) reset("Peculiar, key released but can't remember it was pressed"); else m_d->keys.remove(key); if (!m_d->runningShortcut) { prepareReadyShortcuts(); tryActivateReadyShortcut(); } return false; } bool KisShortcutMatcher::buttonPressed(Qt::MouseButton button, QEvent *event) { DEBUG_BUTTON_ACTION("entered", button); bool retval = false; if (m_d->isUsingTouch()) { return retval; } if (m_d->buttons.contains(button)) { DEBUG_ACTION("Peculiar, button was already pressed."); } - if (!m_d->runningShortcut) { + if (!hasRunningShortcut()) { prepareReadyShortcuts(); retval = tryRunReadyShortcut(button, event); } m_d->buttons.insert(button); - if (!m_d->runningShortcut) { + if (!hasRunningShortcut()) { prepareReadyShortcuts(); tryActivateReadyShortcut(); } return retval; } bool KisShortcutMatcher::buttonReleased(Qt::MouseButton button, QEvent *event) { DEBUG_BUTTON_ACTION("entered", button); bool retval = false; if (m_d->isUsingTouch()) { return retval; } if (m_d->runningShortcut && !m_d->readyShortcut) { retval = tryEndRunningShortcut(button, event); DEBUG_BUTTON_ACTION("ended", button); } if (!m_d->buttons.contains(button)) reset("Peculiar, button released but we can't remember it was pressed"); else m_d->buttons.remove(button); if (!m_d->runningShortcut) { prepareReadyShortcuts(); tryActivateReadyShortcut(); } return retval; } bool KisShortcutMatcher::wheelEvent(KisSingleActionShortcut::WheelAction wheelAction, QWheelEvent *event) { if (m_d->runningShortcut || m_d->isUsingTouch()) { DEBUG_ACTION("Wheel event canceled."); return false; } return tryRunWheelShortcut(wheelAction, event); } bool KisShortcutMatcher::pointerMoved(QEvent *event) { if (m_d->isUsingTouch() || !m_d->runningShortcut) { return false; } m_d->runningShortcut->action()->inputEvent(event); return true; } void KisShortcutMatcher::enterEvent() { m_d->cursorEntered = true; if (!m_d->runningShortcut) { prepareReadyShortcuts(); tryActivateReadyShortcut(); } } void KisShortcutMatcher::leaveEvent() { m_d->cursorEntered = false; if (!m_d->runningShortcut) { prepareReadyShortcuts(); tryActivateReadyShortcut(); } } bool KisShortcutMatcher::touchBeginEvent( QTouchEvent* event ) { Q_UNUSED(event) return true; } bool KisShortcutMatcher::touchUpdateEvent( QTouchEvent* event ) { bool retval = false; if (m_d->touchShortcut && !m_d->touchShortcut->match( event ) ) { retval = tryEndTouchShortcut( event ); } if (!m_d->touchShortcut ) { retval = tryRunTouchShortcut( event ); } else { m_d->touchShortcut->action()->inputEvent( event ); retval = true; } return retval; } bool KisShortcutMatcher::touchEndEvent( QTouchEvent* event ) { m_d->usingTouch = false; // we need to say we are done because qt will not send further event // we should try and end the shortcut too (it might be that there is none? (sketch)) if (tryEndTouchShortcut(event)) { return true; } return false; } bool KisShortcutMatcher::nativeGestureBeginEvent(QNativeGestureEvent *event) { Q_UNUSED(event) return true; } bool KisShortcutMatcher::nativeGestureEvent(QNativeGestureEvent *event) { bool retval = false; if ( !m_d->nativeGestureShortcut ) { retval = tryRunNativeGestureShortcut( event ); } else { m_d->nativeGestureShortcut->action()->inputEvent( event ); retval = true; } return retval; } bool KisShortcutMatcher::nativeGestureEndEvent(QNativeGestureEvent *event) { if ( m_d->nativeGestureShortcut && !m_d->nativeGestureShortcut->match( event ) ) { tryEndNativeGestureShortcut( event ); } m_d->usingNativeGesture = false; return true; } Qt::MouseButtons listToFlags(const QList &list) { Qt::MouseButtons flags; Q_FOREACH (Qt::MouseButton b, list) { flags |= b; } return flags; } void KisShortcutMatcher::reinitialize() { reset("reinitialize"); if (!m_d->runningShortcut) { prepareReadyShortcuts(); tryActivateReadyShortcut(); } } void KisShortcutMatcher::recoveryModifiersWithoutFocus(const QVector &keys) { Q_FOREACH (Qt::Key key, m_d->keys) { if (!keys.contains(key)) { keyReleased(key); } } Q_FOREACH (Qt::Key key, keys) { if (!m_d->keys.contains(key)) { keyPressed(key); } } if (!m_d->runningShortcut) { prepareReadyShortcuts(); tryActivateReadyShortcut(); } DEBUG_ACTION("recoverySyncModifiers"); } void KisShortcutMatcher::lostFocusEvent(const QPointF &localPos) { if (m_d->runningShortcut) { forceEndRunningShortcut(localPos); } } void KisShortcutMatcher::reset() { m_d->keys.clear(); m_d->buttons.clear(); DEBUG_ACTION("reset!"); } void KisShortcutMatcher::reset(QString msg) { m_d->keys.clear(); m_d->buttons.clear(); Q_UNUSED(msg); DEBUG_ACTION(msg); } void KisShortcutMatcher::suppressAllActions(bool value) { m_d->suppressAllActions = value; } void KisShortcutMatcher::clearShortcuts() { reset("Clearing shortcuts"); qDeleteAll(m_d->singleActionShortcuts); m_d->singleActionShortcuts.clear(); qDeleteAll(m_d->strokeShortcuts); qDeleteAll(m_d->touchShortcuts); m_d->strokeShortcuts.clear(); m_d->candidateShortcuts.clear(); m_d->touchShortcuts.clear(); m_d->runningShortcut = 0; m_d->readyShortcut = 0; } void KisShortcutMatcher::setInputActionGroupsMaskCallback(std::function func) { m_d->actionGroupMask = func; } bool KisShortcutMatcher::tryRunWheelShortcut(KisSingleActionShortcut::WheelAction wheelAction, QWheelEvent *event) { return tryRunSingleActionShortcutImpl(wheelAction, event, m_d->keys); } // Note: sometimes event can be zero!! template bool KisShortcutMatcher::tryRunSingleActionShortcutImpl(T param, U *event, const QSet &keysState) { if (m_d->actionsSuppressedIgnoreFocus()) { DEBUG_EVENT_ACTION("Event suppressed", event) return false; } KisSingleActionShortcut *goodCandidate = 0; Q_FOREACH (KisSingleActionShortcut *s, m_d->singleActionShortcuts) { if(s->isAvailable(m_d->actionGroupMask()) && s->match(keysState, param) && (!goodCandidate || s->priority() > goodCandidate->priority())) { goodCandidate = s; } } if (goodCandidate) { DEBUG_EVENT_ACTION("Beginning action for event", event) goodCandidate->action()->begin(goodCandidate->shortcutIndex(), event); goodCandidate->action()->end(0); } else { DEBUG_EVENT_ACTION("Could not match a candidate for event", event) } return goodCandidate; } void KisShortcutMatcher::prepareReadyShortcuts() { m_d->candidateShortcuts.clear(); if (m_d->actionsSuppressed()) return; Q_FOREACH (KisStrokeShortcut *s, m_d->strokeShortcuts) { if (s->matchReady(m_d->keys, m_d->buttons)) { m_d->candidateShortcuts.append(s); } } } bool KisShortcutMatcher::tryRunReadyShortcut( Qt::MouseButton button, QEvent* event ) { KisStrokeShortcut *goodCandidate = 0; Q_FOREACH (KisStrokeShortcut *s, m_d->candidateShortcuts) { if (s->isAvailable(m_d->actionGroupMask()) && s->matchBegin(button) && (!goodCandidate || s->priority() > goodCandidate->priority())) { goodCandidate = s; } } if (goodCandidate) { if (m_d->readyShortcut) { if (m_d->readyShortcut != goodCandidate) { m_d->readyShortcut->action()->deactivate(m_d->readyShortcut->shortcutIndex()); goodCandidate->action()->activate(goodCandidate->shortcutIndex()); } m_d->readyShortcut = 0; } else { DEBUG_EVENT_ACTION("Matched *new* shortcut for event", event); goodCandidate->action()->activate(goodCandidate->shortcutIndex()); } DEBUG_SHORTCUT("Starting new action", goodCandidate); goodCandidate->action()->begin(goodCandidate->shortcutIndex(), event); m_d->runningShortcut = goodCandidate; } return goodCandidate; } void KisShortcutMatcher::tryActivateReadyShortcut() { KisStrokeShortcut *goodCandidate = 0; Q_FOREACH (KisStrokeShortcut *s, m_d->candidateShortcuts) { if (!goodCandidate || s->priority() > goodCandidate->priority()) { goodCandidate = s; } } if (goodCandidate) { if (m_d->readyShortcut && m_d->readyShortcut != goodCandidate) { DEBUG_SHORTCUT("Deactivated previous shortcut action", m_d->readyShortcut); m_d->readyShortcut->action()->deactivate(m_d->readyShortcut->shortcutIndex()); m_d->readyShortcut = 0; } if (!m_d->readyShortcut) { DEBUG_SHORTCUT("Preparing new ready action", goodCandidate); goodCandidate->action()->activate(goodCandidate->shortcutIndex()); m_d->readyShortcut = goodCandidate; } } else if (m_d->readyShortcut) { DEBUG_SHORTCUT("Deactivating action", m_d->readyShortcut); m_d->readyShortcut->action()->deactivate(m_d->readyShortcut->shortcutIndex()); m_d->readyShortcut = 0; } } bool KisShortcutMatcher::tryEndRunningShortcut( Qt::MouseButton button, QEvent* event ) { Q_ASSERT(m_d->runningShortcut); Q_ASSERT(!m_d->readyShortcut); if (m_d->runningShortcut->matchBegin(button)) { // first reset running shortcut to avoid infinite recursion via end() KisStrokeShortcut *runningShortcut = m_d->runningShortcut; m_d->runningShortcut = 0; if (runningShortcut->action()) { DEBUG_EVENT_ACTION("Ending running shortcut at event", event); KisAbstractInputAction* action = runningShortcut->action(); int shortcutIndex = runningShortcut->shortcutIndex(); action->end(event); action->deactivate(shortcutIndex); } } return !m_d->runningShortcut; } void KisShortcutMatcher::forceEndRunningShortcut(const QPointF &localPos) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->runningShortcut); KIS_SAFE_ASSERT_RECOVER_RETURN(!m_d->readyShortcut); // first reset running shortcut to avoid infinite recursion via end() KisStrokeShortcut *runningShortcut = m_d->runningShortcut; m_d->runningShortcut = 0; if (runningShortcut->action()) { DEBUG_ACTION("Forced ending running shortcut at event"); KisAbstractInputAction* action = runningShortcut->action(); int shortcutIndex = runningShortcut->shortcutIndex(); QMouseEvent event = runningShortcut->fakeEndEvent(localPos); action->end(&event); action->deactivate(shortcutIndex); } } bool KisShortcutMatcher::tryRunTouchShortcut( QTouchEvent* event ) { KisTouchShortcut *goodCandidate = 0; if (m_d->actionsSuppressed()) return false; Q_FOREACH (KisTouchShortcut* shortcut, m_d->touchShortcuts) { if (shortcut->isAvailable(m_d->actionGroupMask()) && shortcut->match( event ) && (!goodCandidate || shortcut->priority() > goodCandidate->priority()) ) { goodCandidate = shortcut; } } if( goodCandidate ) { if( m_d->runningShortcut ) { - QMouseEvent mouseEvent(QEvent::MouseButtonRelease, - event->touchPoints().at(0).pos().toPoint(), - Qt::LeftButton, - Qt::LeftButton, - event->modifiers()); - tryEndRunningShortcut(Qt::LeftButton, &mouseEvent); + QTouchEvent touchEvent(QEvent::TouchEnd, + event->device(), + event->modifiers(), + Qt::TouchPointReleased, + event->touchPoints()); + tryEndRunningShortcut(Qt::LeftButton, &touchEvent); } goodCandidate->action()->activate(goodCandidate->shortcutIndex()); goodCandidate->action()->begin(goodCandidate->shortcutIndex(), event); m_d->touchShortcut = goodCandidate; m_d->usingTouch = true; } return goodCandidate; } bool KisShortcutMatcher::tryEndTouchShortcut( QTouchEvent* event ) { if(m_d->touchShortcut) { // first reset running shortcut to avoid infinite recursion via end() KisTouchShortcut *touchShortcut = m_d->touchShortcut; touchShortcut->action()->end(event); touchShortcut->action()->deactivate(m_d->touchShortcut->shortcutIndex()); m_d->touchShortcut = 0; // empty it out now that we are done with it return true; } return false; } bool KisShortcutMatcher::tryRunNativeGestureShortcut(QNativeGestureEvent* event) { KisNativeGestureShortcut *goodCandidate = 0; if (m_d->actionsSuppressed()) return false; Q_FOREACH (KisNativeGestureShortcut* shortcut, m_d->nativeGestureShortcuts) { if (shortcut->match(event) && (!goodCandidate || shortcut->priority() > goodCandidate->priority())) { goodCandidate = shortcut; } } if (goodCandidate) { goodCandidate->action()->activate(goodCandidate->shortcutIndex()); goodCandidate->action()->begin(goodCandidate->shortcutIndex(), event); m_d->nativeGestureShortcut = goodCandidate; m_d->usingNativeGesture = true; return true; } return false; } bool KisShortcutMatcher::tryEndNativeGestureShortcut(QNativeGestureEvent* event) { if (m_d->nativeGestureShortcut) { // first reset running shortcut to avoid infinite recursion via end() KisNativeGestureShortcut *nativeGestureShortcut = m_d->nativeGestureShortcut; nativeGestureShortcut->action()->end(event); nativeGestureShortcut->action()->deactivate(m_d->nativeGestureShortcut->shortcutIndex()); m_d->nativeGestureShortcut = 0; // empty it out now that we are done with it return true; } return false; } diff --git a/libs/ui/input/kis_touch_shortcut.h b/libs/ui/input/kis_touch_shortcut.h index d2b59d3b5e..b0ca049d5a 100644 --- a/libs/ui/input/kis_touch_shortcut.h +++ b/libs/ui/input/kis_touch_shortcut.h @@ -1,45 +1,50 @@ /* * This file is part of the KDE project * Copyright (C) 2012 Arjen Hiemstra * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #ifndef KISTOUCHSHORTCUT_H #define KISTOUCHSHORTCUT_H #include "kis_abstract_shortcut.h" class QTouchEvent; +/** + * @brief The KisTouchShortcut class only handles touch gestures + * it _does not_ handle tool invocation i.e painting (which is being + * handled in KisShortcutMatcher). + */ class KisTouchShortcut : public KisAbstractShortcut { public: KisTouchShortcut( KisAbstractInputAction* action, int index ); ~KisTouchShortcut() override; int priority() const override; void setMinimumTouchPoints( int min ); void setMaximumTouchPoints( int max ); bool match( QTouchEvent* event ); private: class Private; Private * const d; }; #endif // KISTOUCHSHORTCUT_H