diff --git a/libs/ui/canvas/kis_tool_proxy.cpp b/libs/ui/canvas/kis_tool_proxy.cpp index 5e98626a89..f887f8bc01 100644 --- a/libs/ui/canvas/kis_tool_proxy.cpp +++ b/libs/ui/canvas/kis_tool_proxy.cpp @@ -1,259 +1,260 @@ /* * 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."; + qWarning() << "forwardHoverEvent encountered unknown event type:" + << 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_rotate_canvas_action.cpp b/libs/ui/input/kis_rotate_canvas_action.cpp index b5852172b2..e19a611f49 100644 --- a/libs/ui/input/kis_rotate_canvas_action.cpp +++ b/libs/ui/input/kis_rotate_canvas_action.cpp @@ -1,200 +1,229 @@ /* 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. */ #include "kis_rotate_canvas_action.h" #include #include #include #include "kis_cursor.h" #include "kis_canvas_controller.h" #include #include "kis_input_manager.h" #include class KisRotateCanvasAction::Private { public: Private() : previousAngle(0) {} Shortcut mode; qreal previousAngle; qreal startRotation; qreal previousRotation; }; KisRotateCanvasAction::KisRotateCanvasAction() : KisAbstractInputAction("Rotate Canvas") , d(new Private()) { setName(i18n("Rotate Canvas")); setDescription(i18n("The Rotate Canvas action rotates the canvas.")); QHash shortcuts; shortcuts.insert(i18n("Rotate Mode"), RotateModeShortcut); shortcuts.insert(i18n("Discrete Rotate Mode"), DiscreteRotateModeShortcut); shortcuts.insert(i18n("Rotate Left"), RotateLeftShortcut); shortcuts.insert(i18n("Rotate Right"), RotateRightShortcut); shortcuts.insert(i18n("Reset Rotation"), RotateResetShortcut); setShortcutIndexes(shortcuts); } KisRotateCanvasAction::~KisRotateCanvasAction() { delete d; } int KisRotateCanvasAction::priority() const { return 3; } void KisRotateCanvasAction::activate(int shortcut) { if (shortcut == DiscreteRotateModeShortcut) { QApplication::setOverrideCursor(KisCursor::rotateCanvasDiscreteCursor()); } else /* if (shortcut == SmoothRotateModeShortcut) */ { QApplication::setOverrideCursor(KisCursor::rotateCanvasSmoothCursor()); } } void KisRotateCanvasAction::deactivate(int shortcut) { Q_UNUSED(shortcut); QApplication::restoreOverrideCursor(); } void KisRotateCanvasAction::begin(int shortcut, QEvent *event) { KisAbstractInputAction::begin(shortcut, event); d->previousAngle = 0; KisCanvasController *canvasController = dynamic_cast(inputManager()->canvas()->canvasController()); switch(shortcut) { case RotateModeShortcut: case DiscreteRotateModeShortcut: d->mode = (Shortcut)shortcut; d->startRotation = inputManager()->canvas()->rotationAngle(); d->previousRotation = 0; break; case RotateLeftShortcut: canvasController->rotateCanvasLeft15(); break; case RotateRightShortcut: canvasController->rotateCanvasRight15(); break; case RotateResetShortcut: canvasController->resetCanvasRotation(); break; } } void KisRotateCanvasAction::cursorMovedAbsolute(const QPointF &startPos, const QPointF &pos) { const KisCoordinatesConverter *converter = inputManager()->canvas()->coordinatesConverter(); const QPointF centerPoint = converter->flakeToWidget(converter->flakeCenterPoint()); const QPointF startPoint = startPos - centerPoint; const QPointF newPoint = pos - centerPoint; const qreal oldAngle = atan2(startPoint.y(), startPoint.x()); const qreal newAngle = atan2(newPoint.y(), newPoint.x()); qreal newRotation = (180 / M_PI) * (newAngle - oldAngle); if (d->mode == DiscreteRotateModeShortcut) { const qreal angleStep = 15; // avoid jumps at the beginning of the rotation action if (qAbs(newRotation) > 0.5 * angleStep) { const qreal currentCanvasRotation = converter->rotationAngle(); const qreal desiredOffset = newRotation - d->previousRotation; newRotation = qRound((currentCanvasRotation + desiredOffset) / angleStep) * angleStep - currentCanvasRotation + d->previousRotation; } else { newRotation = d->previousRotation; } } KisCanvasController *canvasController = dynamic_cast(inputManager()->canvas()->canvasController()); canvasController->rotateCanvas(newRotation - d->previousRotation); d->previousRotation = newRotation; } void KisRotateCanvasAction::inputEvent(QEvent* event) { switch (event->type()) { case QEvent::NativeGesture: { QNativeGestureEvent *gevent = static_cast(event); KisCanvas2 *canvas = inputManager()->canvas(); KisCanvasController *controller = static_cast(canvas->canvasController()); const float angle = gevent->value(); QPoint widgetPos = canvas->canvasWidget()->mapFromGlobal(gevent->globalPos()); controller->rotateCanvas(angle, widgetPos); return; } case QEvent::TouchUpdate: { QTouchEvent *touchEvent = static_cast(event); if (touchEvent->touchPoints().count() != 2) break; - QPointF p0 = touchEvent->touchPoints().at(0).pos(); - QPointF p1 = touchEvent->touchPoints().at(1).pos(); + QTouchEvent::TouchPoint tp0 = touchEvent->touchPoints().at(0); + QTouchEvent::TouchPoint tp1 = touchEvent->touchPoints().at(1); + + if (tp0.state() == Qt::TouchPointReleased || + tp1.state() == Qt::TouchPointReleased) + { + // Workaround: on some devices, the coordinates of TouchPoints + // in state TouchPointReleased are not reliable, and can + // "jump" by a significant distance. So we just stop handling + // the rotation as soon as the user's finger leaves the tablet. + break; + } + + QPointF p0 = tp0.pos(); + QPointF p1 = tp1.pos(); + + if ((p0-p1).manhattanLength() < 10) + { + // The TouchPoints are too close together. Don't update the + // rotation as the angle will likely be off. This also deals + // with a glitch where a newly pressed TouchPoint incorrectly + // reports the existing TouchPoint's coordinates instead of its + // own. + break; + } // high school (y2 - y1) / (x2 - x1) QPointF slope = p1 - p0; qreal newAngle = atan2(slope.y(), slope.x()); + qreal delta = (180 / M_PI) * (newAngle - d->previousAngle); - if (!d->previousAngle) - { + // Workaround: So, TouchPoint coordinates are not 100% reliable. + // Freshly pressed TouchPoints are sometimes not accurate for + // multiple events. So if the computed rotation angle is out of + // whack, we just ignore it: it probably means the previous + // position was off. + if (qAbs(delta) > 15) { + // TouchPoint coordinates tend to converge toward correct + // values, so assume the previous angle was off but this one is + // better and should be the new basis for the next event. d->previousAngle = newAngle; - return; + break; } - qreal delta = (180 / M_PI) * (newAngle - d->previousAngle); - KisCanvas2 *canvas = inputManager()->canvas(); KisCanvasController *controller = static_cast(canvas->canvasController()); controller->rotateCanvas(delta); d->previousAngle = newAngle; return; } default: break; } KisAbstractInputAction::inputEvent(event); } KisInputActionGroup KisRotateCanvasAction::inputActionGroup(int shortcut) const { Q_UNUSED(shortcut); return ViewTransformActionGroup; } bool KisRotateCanvasAction::supportsHiResInputEvents() const { return true; } diff --git a/libs/ui/input/kis_zoom_action.cpp b/libs/ui/input/kis_zoom_action.cpp index 3f86d20c7b..aa624dbdb7 100644 --- a/libs/ui/input/kis_zoom_action.cpp +++ b/libs/ui/input/kis_zoom_action.cpp @@ -1,323 +1,363 @@ /* 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. */ #include "kis_zoom_action.h" #include #include #include #include #include #include #include #include "kis_cursor.h" #include "KisViewManager.h" #include "kis_input_manager.h" #include "kis_config.h" inline QPoint pointFromEvent(QEvent *event) { if (!event) { return QPoint(); } else if (QMouseEvent *mouseEvent = dynamic_cast(event)) { return mouseEvent->pos(); } else if (QTabletEvent *tabletEvent = dynamic_cast(event)) { return tabletEvent->pos(); } else if (QWheelEvent *wheelEvent = dynamic_cast(event)) { return wheelEvent->pos(); } return QPoint(); } class KisZoomAction::Private { public: Private(KisZoomAction *qq) : q(qq), lastDistance(0.f) {} QPointF centerPoint(QTouchEvent* event); KisZoomAction *q; Shortcuts mode; QPointF lastPosition; float lastDistance; qreal startZoom = 1.0; qreal lastDescreteZoomDistance = 0.0; void zoomTo(bool zoomIn, const QPoint &pos); }; QPointF KisZoomAction::Private::centerPoint(QTouchEvent* event) { QPointF result; int count = 0; Q_FOREACH (QTouchEvent::TouchPoint point, event->touchPoints()) { if (point.state() != Qt::TouchPointReleased) { result += point.pos(); count++; } } if (count > 0) { return result / count; } else { return QPointF(); } } void KisZoomAction::Private::zoomTo(bool zoomIn, const QPoint &point) { KoZoomAction *zoomAction = q->inputManager()->canvas()->viewManager()->zoomController()->zoomAction(); if (!point.isNull()) { float oldZoom = zoomAction->effectiveZoom(); float newZoom = zoomIn ? zoomAction->nextZoomLevel() : zoomAction->prevZoomLevel(); KoCanvasControllerWidget *controller = dynamic_cast( q->inputManager()->canvas()->canvasController()); controller->zoomRelativeToPoint(point, newZoom / oldZoom); } else { if (zoomIn) { zoomAction->zoomIn(); } else { zoomAction->zoomOut(); } } } KisZoomAction::KisZoomAction() : KisAbstractInputAction("Zoom Canvas") , d(new Private(this)) { setName(i18n("Zoom Canvas")); setDescription(i18n("The Zoom Canvas action zooms the canvas.")); QHash< QString, int > shortcuts; shortcuts.insert(i18n("Zoom Mode"), ZoomModeShortcut); shortcuts.insert(i18n("Discrete Zoom Mode"), DiscreteZoomModeShortcut); shortcuts.insert(i18n("Relative Zoom Mode"), RelativeZoomModeShortcut); shortcuts.insert(i18n("Relative Discrete Zoom Mode"), RelativeDiscreteZoomModeShortcut); shortcuts.insert(i18n("Zoom In"), ZoomInShortcut); shortcuts.insert(i18n("Zoom Out"), ZoomOutShortcut); shortcuts.insert(i18n("Reset Zoom to 100%"), ZoomResetShortcut); shortcuts.insert(i18n("Fit to Page"), ZoomToPageShortcut); shortcuts.insert(i18n("Fit to Width"), ZoomToWidthShortcut); setShortcutIndexes(shortcuts); } KisZoomAction::~KisZoomAction() { delete d; } int KisZoomAction::priority() const { return 4; } void KisZoomAction::activate(int shortcut) { if (shortcut == DiscreteZoomModeShortcut || shortcut == RelativeDiscreteZoomModeShortcut) { QApplication::setOverrideCursor(KisCursor::zoomDiscreteCursor()); } else /* if (shortcut == SmoothZoomModeShortcut) */ { QApplication::setOverrideCursor(KisCursor::zoomSmoothCursor()); } } void KisZoomAction::deactivate(int shortcut) { Q_UNUSED(shortcut); QApplication::restoreOverrideCursor(); } void KisZoomAction::begin(int shortcut, QEvent *event) { KisAbstractInputAction::begin(shortcut, event); d->lastDistance = 0.f; switch(shortcut) { case ZoomModeShortcut: case RelativeZoomModeShortcut: { d->startZoom = inputManager()->canvas()->viewManager()->zoomController()->zoomAction()->effectiveZoom(); d->mode = (Shortcuts)shortcut; - QTouchEvent *tevent = dynamic_cast(event); - if(tevent) - d->lastPosition = d->centerPoint(tevent); + d->lastPosition = QPoint(); break; } case DiscreteZoomModeShortcut: case RelativeDiscreteZoomModeShortcut: d->startZoom = inputManager()->canvas()->viewManager()->zoomController()->zoomAction()->effectiveZoom(); d->lastDescreteZoomDistance = 0; d->mode = (Shortcuts)shortcut; break; case ZoomInShortcut: d->zoomTo(true, pointFromEvent(event)); break; case ZoomOutShortcut: d->zoomTo(false, pointFromEvent(event)); break; case ZoomResetShortcut: inputManager()->canvas()->viewManager()->zoomController()->setZoom(KoZoomMode::ZOOM_CONSTANT, 1.0); break; case ZoomToPageShortcut: inputManager()->canvas()->viewManager()->zoomController()->setZoom(KoZoomMode::ZOOM_PAGE, 1.0); break; case ZoomToWidthShortcut: inputManager()->canvas()->viewManager()->zoomController()->setZoom(KoZoomMode::ZOOM_WIDTH, 1.0); break; } } void KisZoomAction::inputEvent( QEvent* event ) { switch (event->type()) { case QEvent::TouchUpdate: { QTouchEvent *tevent = static_cast(event); - QPointF center = d->centerPoint(tevent); - int count = 0; - float dist = 0.0f; - Q_FOREACH (const QTouchEvent::TouchPoint &point, tevent->touchPoints()) { - if (point.state() != Qt::TouchPointReleased) { - count++; - dist += (point.pos() - center).manhattanLength(); - } + if (tevent->touchPoints().count() != 2) { + // Sanity check. The input state machine should only invoke + // this action if there are 2 TouchPoints in the event. + return; + } + + // First, let's determine if we want to handle this event. Sadly + // the coordinates of TouchPoints reported by Qt are not always + // dependable. TouchPoints that are just getting released can be + // off by a significant amount. So we stop the zoom as soon as the + // user lifts a finger. + + QTouchEvent::TouchPoint tp0 = tevent->touchPoints().at(0); + QTouchEvent::TouchPoint tp1 = tevent->touchPoints().at(1); + if (tp0.state() == Qt::TouchPointReleased || + tp1.state() == Qt::TouchPointReleased) { + // Force a recomputation of the position on the next event. + d->lastPosition = QPoint(); + return; + } + + QPointF p0 = tp0.pos(); + QPointF p1 = tp1.pos(); + + // Make sure none of the TouchPoints are too close together, which + // throws off the zoom calculations. This also addresses a glitch + // where a newly pressed TouchPoint can incorrectly report another + // existing TouchPoint's coordinates instead of its own. + + if ((p0-p1).manhattanLength() < 10) { + d->lastPosition = QPointF(); + return; } - if (count == 0) { - count = 1; + + // If this is the first valid set of points that we are getting, + // then use that as the reference for the zoom. + + QPointF center = d->centerPoint(tevent); + if (d->lastPosition.isNull()) { + d->lastPosition = center; + d->lastDistance = 0; + return; } - dist /= count; + float dist = ((p0 - center).manhattanLength() + (p1 - center).manhattanLength()) / 2; float delta = qFuzzyCompare(1.0f, 1.0f + d->lastDistance) ? 1.f : dist / d->lastDistance; - if(qAbs(delta) > 0.1f) { - qreal zoom = inputManager()->canvas()->viewManager()->zoomController()->zoomAction()->effectiveZoom(); - Q_UNUSED(zoom); - static_cast(inputManager()->canvas()->canvasController())->zoomRelativeToPoint(center.toPoint(), delta); - d->lastDistance = dist; - // Also do panning here, as doing it later requires a further check for validity - QPointF moveDelta = center - d->lastPosition; - inputManager()->canvas()->canvasController()->pan(-moveDelta.toPoint()); + // Workaround: only apply the zoom delta if it's not too + // outlandish. As explained above, TouchPoint coordinates are not + // always 100% reliable. + + if(qAbs(delta) < 0.8f || qAbs(delta) > 1.2f) { + // TouchPoint coordinates tend to converge toward correct + // values over time, so assume that the new position is + // likelier to be correct than the last and use that as the new + // reference. d->lastPosition = center; + return; } + + qreal zoom = inputManager()->canvas()->viewManager()->zoomController()->zoomAction()->effectiveZoom(); + Q_UNUSED(zoom); + static_cast(inputManager()->canvas()->canvasController())->zoomRelativeToPoint(center.toPoint(), delta); + d->lastDistance = dist; + // Also do panning here, as doing it later requires a further check for validity + QPointF moveDelta = center - d->lastPosition; + inputManager()->canvas()->canvasController()->pan(-moveDelta.toPoint()); + d->lastPosition = center; return; // Don't try to update the cursor during a pinch-zoom } case QEvent::NativeGesture: { QNativeGestureEvent *gevent = static_cast(event); if (gevent->gestureType() == Qt::ZoomNativeGesture) { KisCanvas2 *canvas = inputManager()->canvas(); KisCanvasController *controller = static_cast(canvas->canvasController()); const float delta = 1.0f + gevent->value(); controller->zoomRelativeToPoint(canvas->canvasWidget()->mapFromGlobal(gevent->globalPos()), delta); } else if (gevent->gestureType() == Qt::SmartZoomNativeGesture) { KisCanvas2 *canvas = inputManager()->canvas(); KoZoomController *controller = canvas->viewManager()->zoomController(); if (controller->zoomMode() != KoZoomMode::ZOOM_WIDTH) { controller->setZoom(KoZoomMode::ZOOM_WIDTH, 1.0); } else { controller->setZoom(KoZoomMode::ZOOM_CONSTANT, 1.0); } } return; } default: break; } KisAbstractInputAction::inputEvent(event); } void KisZoomAction::cursorMovedAbsolute(const QPointF &startPos, const QPointF &pos) { QPointF diff = -(pos - startPos); const int stepCont = 100; const int stepDisc = 50; if (d->mode == ZoomModeShortcut || d->mode == RelativeZoomModeShortcut) { const qreal zoom = inputManager()->canvas()->viewManager()->zoomController()->zoomAction()->effectiveZoom(); const qreal logDistance = std::pow(2.0, qreal(diff.y()) / qreal(stepCont)); KisConfig cfg(true); qreal newZoom = zoom; if (cfg.readEntry("InvertMiddleClickZoom", false)) { newZoom = d->startZoom / logDistance; } else { newZoom = d->startZoom * logDistance; } if (d->mode == ZoomModeShortcut) { inputManager()->canvas()->viewManager()->zoomController()->setZoom(KoZoomMode::ZOOM_CONSTANT, newZoom); } else { const qreal coeff = newZoom / zoom; KoCanvasControllerWidget *controller = dynamic_cast( inputManager()->canvas()->canvasController()); controller->zoomRelativeToPoint(startPos.toPoint(), coeff); } } else if (d->mode == DiscreteZoomModeShortcut || d->mode == RelativeDiscreteZoomModeShortcut) { QPoint stillPoint = d->mode == RelativeDiscreteZoomModeShortcut ? startPos.toPoint() : QPoint(); qreal currentDiff = qreal(diff.y()) / stepDisc - d->lastDescreteZoomDistance; bool zoomIn = currentDiff > 0; while (qAbs(currentDiff) > 1.0) { d->zoomTo(zoomIn, stillPoint); d->lastDescreteZoomDistance += zoomIn ? 1.0 : -1.0; currentDiff = qreal(diff.y()) / stepDisc - d->lastDescreteZoomDistance; } } } bool KisZoomAction::isShortcutRequired(int shortcut) const { return shortcut == ZoomModeShortcut; } bool KisZoomAction::supportsHiResInputEvents() const { return true; } KisInputActionGroup KisZoomAction::inputActionGroup(int shortcut) const { Q_UNUSED(shortcut); return ViewTransformActionGroup; }