File indexing completed on 2024-05-26 04:30:16

0001 /*
0002  *  SPDX-FileCopyrightText: 2015 Michael Abrahams <miabraha@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "kis_input_manager_p.h"
0008 
0009 #include <QMap>
0010 #include <QApplication>
0011 #include <QScopedPointer>
0012 #include <QtGlobal>
0013 
0014 #include <boost/preprocessor/repeat_from_to.hpp>
0015 
0016 #include "kis_input_manager.h"
0017 #include "kis_config.h"
0018 #include "kis_abstract_input_action.h"
0019 #include "kis_tool_invocation_action.h"
0020 #include "kis_stroke_shortcut.h"
0021 #include "kis_touch_shortcut.h"
0022 #include "kis_native_gesture_shortcut.h"
0023 #include "kis_input_profile_manager.h"
0024 #include "kis_extended_modifiers_mapper.h"
0025 
0026 #include "kis_zoom_and_rotate_action.h"
0027 #include "kis_popup_palette.h"
0028 
0029 /**
0030  * This hungry class EventEater encapsulates event masking logic.
0031  *
0032  * Its basic role is to kill synthetic mouseMove events sent by Xorg or Qt after
0033  * tablet events. Those events are sent in order to allow widgets that haven't
0034  * implemented tablet specific functionality to seamlessly behave as if one were
0035  * using a mouse. These synthetic events are *supposed* to be optional, or at
0036  * least come with a flag saying "This is a fake event!!" but neither of those
0037  * methods is trustworthy. (This is correct as of Qt 5.4 + Xorg.)
0038  *
0039  * Qt 5.4 provides no reliable way to see if a user's tablet is being hovered
0040  * over the pad, since it converts all tablethover events into mousemove, with
0041  * no option to turn this off. Moreover, sometimes the MouseButtonPress event
0042  * from the tapping their tablet happens BEFORE the TabletPress event. This
0043  * means we have to resort to a somewhat complicated logic. What makes this
0044  * truly a joke is that we are not guaranteed to observe TabletProximityEnter
0045  * events when we're using a tablet, either, you may only see an Enter event.
0046  *
0047  * Once we see tablet events heading our way, we can say pretty confidently that
0048  * every mouse event is fake. There are two painful cases to consider - a
0049  * mousePress event could arrive before the tabletPress event, or it could
0050  * arrive much later, e.g. after tabletRelease. The first was only seen on Linux
0051  * with Qt's XInput2 code, the solution was to hold onto mousePress events
0052  * temporarily and wait for tabletPress later, this is contained in git history
0053  * but is now removed. The second case is currently handled by the
0054  * eatOneMousePress function, which waits as long as necessary to detect and
0055  * block a single mouse press event.
0056  */
0057 
0058 static bool isMouseEventType(QEvent::Type t)
0059 {
0060     return (t == QEvent::MouseMove ||
0061             t == QEvent::MouseButtonPress ||
0062             t == QEvent::MouseButtonRelease ||
0063             t == QEvent::MouseButtonDblClick);
0064 }
0065 
0066 KisInputManager::Private::EventEater::EventEater()
0067 {
0068     KisConfig cfg(true);
0069     activateSecondaryButtonsWorkaround = cfg.useRightMiddleTabletButtonWorkaround();
0070 }
0071 
0072 bool KisInputManager::Private::EventEater::eventFilter(QObject* target, QEvent* event )
0073 {
0074     Q_UNUSED(target);
0075 
0076     auto debugEvent = [&](int i) {
0077         if (KisTabletDebugger::instance()->debugEnabled()) {
0078             QString pre = QString("[BLOCKED %1:]").arg(i);
0079             QMouseEvent *ev = static_cast<QMouseEvent*>(event);
0080             dbgTablet << KisTabletDebugger::instance()->eventToString(*ev, pre);
0081         }
0082     };
0083 
0084     auto debugTabletEvent = [&](int i) {
0085         if (KisTabletDebugger::instance()->debugEnabled()) {
0086             QString pre = QString("[BLOCKED %1:]").arg(i);
0087             QTabletEvent *ev = static_cast<QTabletEvent*>(event);
0088             dbgTablet << KisTabletDebugger::instance()->eventToString(*ev, pre);
0089         }
0090     };
0091 
0092     auto debugTouchEvent = [&](int i) {
0093         if (KisTabletDebugger::instance()->debugEnabled()) {
0094             QString pre = QString("[BLOCKED %1:]").arg(i);
0095             QTouchEvent *ev = static_cast<QTouchEvent*>(event);
0096             dbgTablet << KisTabletDebugger::instance()->eventToString(*ev, pre);
0097         }
0098     };
0099 
0100     if (peckish && event->type() == QEvent::MouseButtonPress
0101         // Drop one mouse press following tabletPress or touchBegin
0102         && (static_cast<QMouseEvent*>(event)->button() == Qt::LeftButton)) {
0103         peckish = false;
0104         debugEvent(1);
0105         return true;
0106     }
0107 
0108     if (activateSecondaryButtonsWorkaround) {
0109         if (event->type() == QEvent::TabletPress ||
0110                 event->type() == QEvent::TabletRelease) {
0111 
0112             QTabletEvent *te = static_cast<QTabletEvent*>(event);
0113             if (te->button() != Qt::LeftButton) {
0114                 debugTabletEvent(3);
0115                 return true;
0116             }
0117         } else if (event->type() == QEvent::MouseButtonPress ||
0118                    event->type() == QEvent::MouseButtonRelease ||
0119                    event->type() == QEvent::MouseButtonDblClick) {
0120 
0121             QMouseEvent *me = static_cast<QMouseEvent*>(event);
0122             if (me->button() != Qt::LeftButton) {
0123                 return false;
0124             }
0125         }
0126     }
0127 
0128     if (isMouseEventType(event->type()) &&
0129                (hungry
0130             // On Mac, we need mouse events when the tablet is in proximity, but not pressed down
0131             // since tablet move events are not generated until after tablet press.
0132             #ifndef Q_OS_MAC
0133                 || (eatSyntheticEvents && static_cast<QMouseEvent*>(event)->source() != Qt::MouseEventNotSynthesized)
0134             #endif
0135                 )) {
0136         // Drop mouse events if enabled or event was synthetic & synthetic events are disabled
0137         debugEvent(2);
0138         return true;
0139     }
0140 
0141     if (eatTouchEvents && event->type() == QEvent::TouchBegin) {
0142         // Drop touch events. If QEvent::TouchBegin is ignored, we won't
0143         // receive further touch events until the next TouchBegin.
0144         debugTouchEvent(3);
0145         event->ignore();
0146         return true;
0147     }
0148 
0149     return false; // All clear - let this one through!
0150 }
0151 
0152 
0153 void KisInputManager::Private::EventEater::activate()
0154 {
0155     if (!hungry && (KisTabletDebugger::instance()->debugEnabled())) {
0156         dbgTablet << "Start blocking mouse events";
0157     }
0158     hungry = true;
0159 }
0160 
0161 void KisInputManager::Private::EventEater::deactivate()
0162 {
0163     if (hungry && (KisTabletDebugger::instance()->debugEnabled())) {
0164         dbgTablet << "Stop blocking mouse events";
0165     }
0166     hungry = false;
0167 }
0168 
0169 void KisInputManager::Private::EventEater::eatOneMousePress()
0170 {
0171     // Enable on other platforms if getting full-pressure splotches
0172     peckish = true;
0173 }
0174 
0175 void KisInputManager::Private::EventEater::startBlockingTouch()
0176 {
0177     eatTouchEvents = true;
0178 }
0179 
0180 void KisInputManager::Private::EventEater::stopBlockingTouch()
0181 {
0182     eatTouchEvents = false;
0183 }
0184 
0185 bool KisInputManager::Private::ignoringQtCursorEvents()
0186 {
0187     return eventEater.hungry;
0188 }
0189 
0190 void KisInputManager::Private::setMaskSyntheticEvents(bool value)
0191 {
0192     eventEater.eatSyntheticEvents = value;
0193 }
0194 
0195 KisInputManager::Private::Private(KisInputManager *qq)
0196     : q(qq)
0197     , moveEventCompressor(10 /* ms */,
0198                           KisSignalCompressor::FIRST_ACTIVE,
0199                           KisSignalCompressor::ADDITIVE_INTERVAL)
0200     , priorityEventFilterSeqNo(0)
0201     , popupWidget(nullptr)
0202     , canvasSwitcher(this, qq)
0203 {
0204     KisConfig cfg(true);
0205 
0206     moveEventCompressor.setDelay(cfg.tabletEventsDelay());
0207     testingAcceptCompressedTabletEvents = cfg.testingAcceptCompressedTabletEvents();
0208     testingCompressBrushEvents = cfg.testingCompressBrushEvents();
0209 
0210     if (cfg.trackTabletEventLatency()) {
0211         tabletLatencyTracker = new TabletLatencyTracker();
0212     }
0213 
0214     matcher.setInputActionGroupsMaskCallback(
0215         [this] () {
0216             return this->canvas ? this->canvas->inputActionGroupsMaskInterface()->inputActionGroupsMask() : AllActionGroup;
0217         });
0218 
0219     /**
0220      * On Windows and Linux we have a proper fix for this bug
0221      * patched into our local version of Qt. We don't have a fix
0222      * for macOS
0223      */
0224 #ifdef Q_OS_MACOS
0225     useUnbalancedKeyPressEventWorkaround = true;
0226 #endif
0227 
0228     /**
0229      * In Linux distributions Qt is not patched, so we should
0230      * use workaround for them
0231      */
0232 #if defined Q_OS_LINUX &&  !defined QT_HAS_ENTER_LEAVE_PATCH
0233     useUnbalancedKeyPressEventWorkaround = true;
0234 #endif
0235 
0236     if (qEnvironmentVariableIsSet("KRITA_FIX_UNBALANCED_KEY_EVENTS")) {
0237         useUnbalancedKeyPressEventWorkaround = qEnvironmentVariableIntValue("KRITA_FIX_UNBALANCED_KEY_EVENTS");
0238     }
0239 }
0240 
0241 static const int InputWidgetsThreshold = 2000;
0242 static const int OtherWidgetsThreshold = 400;
0243 
0244 KisInputManager::Private::CanvasSwitcher::CanvasSwitcher(Private *_d, QObject *p)
0245     : QObject(p),
0246       d(_d),
0247       eatOneMouseStroke(false),
0248       focusSwitchThreshold(InputWidgetsThreshold)
0249 {
0250 }
0251 
0252 void KisInputManager::Private::CanvasSwitcher::setupFocusThreshold(QObject* object)
0253 {
0254     QWidget *widget = qobject_cast<QWidget*>(object);
0255     KIS_SAFE_ASSERT_RECOVER_RETURN(widget);
0256 
0257     thresholdConnections.clear();
0258     thresholdConnections.addConnection(&focusSwitchThreshold, SIGNAL(timeout()), widget, SLOT(setFocus()));
0259 }
0260 
0261 void KisInputManager::Private::CanvasSwitcher::addCanvas(KisCanvas2 *canvas)
0262 {
0263     if (!canvas) return;
0264 
0265     QObject *canvasWidget = canvas->canvasWidget();
0266 
0267     if (!canvasResolver.contains(canvasWidget)) {
0268         canvasResolver.insert(canvasWidget, canvas);
0269     } else {
0270         // just a sanity cheek to find out if we are
0271         // trying to add two canvases concurrently.
0272         KIS_SAFE_ASSERT_RECOVER_NOOP(d->canvas == canvas);
0273     }
0274 
0275     if (canvas != d->canvas) {
0276         d->q->setupAsEventFilter(canvasWidget);
0277         canvasWidget->installEventFilter(this);
0278 
0279         setupFocusThreshold(canvasWidget);
0280         focusSwitchThreshold.setEnabled(false);
0281 
0282         d->canvas = canvas;
0283         d->toolProxy = qobject_cast<KisToolProxy*>(canvas->toolProxy());
0284     }
0285 }
0286 
0287 void KisInputManager::Private::CanvasSwitcher::removeCanvas(KisCanvas2 *canvas)
0288 {
0289     QObject *widget = canvas->canvasWidget();
0290 
0291     canvasResolver.remove(widget);
0292 
0293     if (d->eventsReceiver == widget) {
0294         d->q->setupAsEventFilter(0);
0295     }
0296 
0297     widget->removeEventFilter(this);
0298 
0299     if (d->canvas == canvas) {
0300         d->canvas = 0;
0301         d->toolProxy = 0;
0302     }
0303 }
0304 
0305 bool isInputWidget(QWidget *w)
0306 {
0307     if (!w) return false;
0308 
0309 
0310     QList<QLatin1String> types;
0311     types << QLatin1String("QAbstractSlider");
0312     types << QLatin1String("QAbstractSpinBox");
0313     types << QLatin1String("QLineEdit");
0314     types << QLatin1String("QTextEdit");
0315     types << QLatin1String("QPlainTextEdit");
0316     types << QLatin1String("QComboBox");
0317     types << QLatin1String("QKeySequenceEdit");
0318 
0319     Q_FOREACH (const QLatin1String &type, types) {
0320         if (w->inherits(type.data())) {
0321             return true;
0322         }
0323     }
0324 
0325     return false;
0326 }
0327 
0328 bool KisInputManager::Private::CanvasSwitcher::eventFilter(QObject* object, QEvent* event )
0329 {
0330     if (canvasResolver.contains(object)) {
0331         switch (event->type()) {
0332         case QEvent::FocusIn: {
0333             QFocusEvent *fevent = static_cast<QFocusEvent*>(event);
0334             KisCanvas2 *canvas = canvasResolver.value(object);
0335 
0336             // only relevant canvases from the same main window should be
0337             // registered in the switcher
0338             KIS_SAFE_ASSERT_RECOVER_BREAK(canvas);
0339 
0340             if (canvas != d->canvas) {
0341                 eatOneMouseStroke = 2 * (fevent->reason() == Qt::MouseFocusReason);
0342             }
0343 
0344             d->canvas = canvas;
0345             d->toolProxy = qobject_cast<KisToolProxy*>(canvas->toolProxy());
0346 
0347             d->q->setupAsEventFilter(object);
0348 
0349             object->removeEventFilter(this);
0350             object->installEventFilter(this);
0351 
0352             setupFocusThreshold(object);
0353             focusSwitchThreshold.setEnabled(false);
0354 
0355             const QPoint globalPos = QCursor::pos();
0356             const QPoint localPos = d->canvas->canvasWidget()->mapFromGlobal(globalPos);
0357             QWidget *canvasWindow = d->canvas->canvasWidget()->window();
0358             const QPoint windowsPos = canvasWindow ? canvasWindow->mapFromGlobal(globalPos) : localPos;
0359 
0360             QEnterEvent event(localPos, windowsPos, globalPos);
0361             d->q->eventFilter(object, &event);
0362             break;
0363         }
0364         case QEvent::FocusOut: {
0365             focusSwitchThreshold.setEnabled(true);
0366             break;
0367         }
0368         case QEvent::Enter: {
0369             break;
0370         }
0371         case QEvent::Leave: {
0372             focusSwitchThreshold.stop();
0373             break;
0374         }
0375         case QEvent::Wheel: {
0376             QWidget *widget = static_cast<QWidget*>(object);
0377             widget->setFocus();
0378             break;
0379         }
0380         case QEvent::MouseButtonPress:
0381         case QEvent::MouseButtonRelease:
0382         case QEvent::TabletPress:
0383         case QEvent::TabletRelease:
0384             focusSwitchThreshold.forceDone();
0385 
0386             if (eatOneMouseStroke) {
0387                 eatOneMouseStroke--;
0388                 return true;
0389             }
0390             break;
0391         case QEvent::MouseButtonDblClick:
0392             focusSwitchThreshold.forceDone();
0393             if (eatOneMouseStroke) {
0394                 return true;
0395             }
0396             break;
0397         case QEvent::MouseMove:
0398         case QEvent::TabletMove: {
0399             QWidget *widget = static_cast<QWidget*>(object);
0400 
0401             if (!widget->hasFocus()) {
0402                 const int delay =
0403                     isInputWidget(QApplication::focusWidget()) ?
0404                     InputWidgetsThreshold : OtherWidgetsThreshold;
0405 
0406                 focusSwitchThreshold.setDelayThreshold(delay);
0407                 focusSwitchThreshold.start();
0408             }
0409         }
0410             break;
0411         default:
0412             break;
0413         }
0414     }
0415     return QObject::eventFilter(object, event);
0416 }
0417 
0418 KisInputManager::Private::ProximityNotifier::ProximityNotifier(KisInputManager::Private *_d, QObject *p)
0419     : QObject(p), d(_d)
0420 {}
0421 
0422 bool KisInputManager::Private::ProximityNotifier::eventFilter(QObject* object, QEvent* event )
0423 {
0424     /**
0425      * All Qt builds in range 5.7.0...5.11.X on X11 had a problem that made all
0426      * the tablet events be accepted by default. It meant that no mouse
0427      * events were synthesized, and, therefore, no Enter/Leave were generated.
0428      *
0429      * The fix for this bug has been added only in Qt 5.12.0:
0430      * https://codereview.qt-project.org/#/c/239918/
0431      *
0432      * To avoid this problem we should explicitly ignore all the tablet events.
0433      */
0434 #if defined Q_OS_LINUX && \
0435     QT_VERSION >= QT_VERSION_CHECK(5, 9, 0) && \
0436     QT_VERSION < QT_VERSION_CHECK(5, 12, 0)
0437 
0438     if (event->type() == QEvent::TabletMove ||
0439         event->type() == QEvent::TabletPress ||
0440         event->type() == QEvent::TabletRelease) {
0441 
0442         event->ignore();
0443     }
0444 #endif
0445 
0446     switch (event->type()) {
0447     case QEvent::TabletEnterProximity:
0448         d->debugEvent<QEvent, false>(event);
0449         // Tablet proximity events are unreliable AND fake mouse events do not
0450         // necessarily come after tablet events, so this is insufficient.
0451         // d->eventEater.eatOneMousePress();
0452 
0453         // Qt sends fake mouse events instead of hover events, so not very useful.
0454         // Don't block mouse events on tablet since tablet move events are not generated until
0455         // after tablet press.
0456 #ifndef Q_OS_MACOS
0457         d->blockMouseEvents();
0458 #endif
0459         break;
0460     case QEvent::TabletLeaveProximity:
0461         d->debugEvent<QEvent, false>(event);
0462         d->allowMouseEvents();
0463         break;
0464     default:
0465         break;
0466     }
0467     return QObject::eventFilter(object, event);
0468 }
0469 
0470 #define EXTRA_BUTTON(z, n, _) \
0471     if(buttons & Qt::ExtraButton##n) { \
0472         buttonSet << Qt::ExtraButton##n; \
0473     }
0474 
0475 void KisInputManager::Private::addStrokeShortcut(KisAbstractInputAction* action, int index,
0476                                                  const QList<Qt::Key> &modifiers,
0477                                                  Qt::MouseButtons buttons)
0478 {
0479     KisStrokeShortcut *strokeShortcut =
0480         new KisStrokeShortcut(action, index);
0481 
0482     QSet<Qt::MouseButton> buttonSet;
0483     if(buttons & Qt::LeftButton) {
0484         buttonSet << Qt::LeftButton;
0485     }
0486     if(buttons & Qt::RightButton) {
0487         buttonSet << Qt::RightButton;
0488     }
0489     if(buttons & Qt::MiddleButton) {
0490         buttonSet << Qt::MiddleButton;
0491     }
0492 
0493 BOOST_PP_REPEAT_FROM_TO(1, 25, EXTRA_BUTTON, _)
0494 
0495     if (!buttonSet.empty()) {
0496 #if QT_VERSION >= QT_VERSION_CHECK(5,14,0)
0497         strokeShortcut->setButtons(QSet<Qt::Key>(modifiers.cbegin(), modifiers.cend()), buttonSet);
0498 #else
0499         strokeShortcut->setButtons(QSet<Qt::Key>::fromList(modifiers), buttonSet);
0500 #endif
0501         matcher.addShortcut(strokeShortcut);
0502     }
0503     else {
0504         delete strokeShortcut;
0505     }
0506 }
0507 
0508 void KisInputManager::Private::addKeyShortcut(KisAbstractInputAction* action, int index,
0509                                               const QList<Qt::Key> &keys)
0510 {
0511     if (keys.size() == 0) return;
0512 
0513     KisSingleActionShortcut *keyShortcut =
0514         new KisSingleActionShortcut(action, index);
0515 
0516     //Note: Ordering is important here, Shift + V is different from V + Shift,
0517     //which is the reason we use the last key here since most users will enter
0518     //shortcuts as "Shift + V". Ideally this should not happen, but this is
0519     //the way the shortcut matcher is currently implemented.
0520     QList<Qt::Key> allKeys = keys;
0521     Qt::Key key = allKeys.takeLast();
0522 #if QT_VERSION >= QT_VERSION_CHECK(5,14,0)
0523     QSet<Qt::Key> modifiers = QSet<Qt::Key>(allKeys.begin(), allKeys.end());
0524 #else
0525     QSet<Qt::Key> modifiers = QSet<Qt::Key>::fromList(allKeys);
0526 #endif
0527     keyShortcut->setKey(modifiers, key);
0528     matcher.addShortcut(keyShortcut);
0529 }
0530 
0531 void KisInputManager::Private::addWheelShortcut(KisAbstractInputAction* action, int index,
0532                                                 const QList<Qt::Key> &modifiers,
0533                                                 KisShortcutConfiguration::MouseWheelMovement wheelAction)
0534 {
0535     QScopedPointer<KisSingleActionShortcut> keyShortcut(
0536         new KisSingleActionShortcut(action, index));
0537 
0538     KisSingleActionShortcut::WheelAction a;
0539     switch(wheelAction) {
0540     case KisShortcutConfiguration::WheelUp:
0541         a = KisSingleActionShortcut::WheelUp;
0542         break;
0543     case KisShortcutConfiguration::WheelDown:
0544         a = KisSingleActionShortcut::WheelDown;
0545         break;
0546     case KisShortcutConfiguration::WheelLeft:
0547         a = KisSingleActionShortcut::WheelLeft;
0548         break;
0549     case KisShortcutConfiguration::WheelRight:
0550         a = KisSingleActionShortcut::WheelRight;
0551         break;
0552     case KisShortcutConfiguration::WheelTrackpad:
0553         a = KisSingleActionShortcut::WheelTrackpad;
0554         break;
0555     default:
0556         return;
0557     }
0558 #if QT_VERSION >= QT_VERSION_CHECK(5,14,0)
0559     keyShortcut->setWheel(QSet<Qt::Key>(modifiers.begin(), modifiers.end()), a);
0560 #else
0561     keyShortcut->setWheel(QSet<Qt::Key>::fromList(modifiers), a);
0562 #endif
0563     matcher.addShortcut(keyShortcut.take());
0564 }
0565 
0566 void KisInputManager::Private::addTouchShortcut(KisAbstractInputAction* action, int index, KisShortcutConfiguration::GestureAction gesture)
0567 {
0568     KisTouchShortcut *shortcut = new KisTouchShortcut(action, index, gesture);
0569     dbgKrita << "TouchAction:" << action->name();
0570     switch(gesture) {
0571 #ifndef Q_OS_MACOS
0572     case KisShortcutConfiguration::OneFingerTap:
0573     case KisShortcutConfiguration::OneFingerDrag:
0574         // Allow single finger panning if touch drawing is disabled
0575         if (KisConfig(true).disableTouchOnCanvas()) { 
0576             shortcut->setMinimumTouchPoints(1);
0577             shortcut->setMaximumTouchPoints(1);
0578         }
0579         break;
0580     case KisShortcutConfiguration::TwoFingerTap:
0581     case KisShortcutConfiguration::TwoFingerDrag:
0582         shortcut->setMinimumTouchPoints(2);
0583         shortcut->setMaximumTouchPoints(2);
0584         break;
0585     case KisShortcutConfiguration::ThreeFingerTap:
0586     case KisShortcutConfiguration::ThreeFingerDrag:
0587         shortcut->setMinimumTouchPoints(3);
0588         shortcut->setMaximumTouchPoints(3);
0589         break;
0590     case KisShortcutConfiguration::FourFingerTap:
0591     case KisShortcutConfiguration::FourFingerDrag:
0592         shortcut->setMinimumTouchPoints(4);
0593         shortcut->setMaximumTouchPoints(4);
0594         break;
0595     case KisShortcutConfiguration::FiveFingerTap:
0596     case KisShortcutConfiguration::FiveFingerDrag:
0597         shortcut->setMinimumTouchPoints(5);
0598         shortcut->setMaximumTouchPoints(5);
0599 #endif
0600     default:
0601         break;
0602     }
0603     matcher.addShortcut(shortcut);
0604 }
0605 
0606 bool KisInputManager::Private::addNativeGestureShortcut(KisAbstractInputAction* action, int index, KisShortcutConfiguration::GestureAction gesture)
0607 {
0608     // Qt5 only implements QNativeGestureEvent for macOS
0609     Qt::NativeGestureType type;
0610     switch (gesture) {
0611 #ifdef Q_OS_MACOS
0612         case KisShortcutConfiguration::PinchGesture:
0613             type = Qt::ZoomNativeGesture;
0614             break;
0615         case KisShortcutConfiguration::PanGesture:
0616             type = Qt::PanNativeGesture;
0617             break;
0618         case KisShortcutConfiguration::RotateGesture:
0619             type = Qt::RotateNativeGesture;
0620             break;
0621         case KisShortcutConfiguration::SmartZoomGesture:
0622             type = Qt::SmartZoomNativeGesture;
0623             break;
0624 #endif
0625         default:
0626             return false;
0627     }
0628 
0629     KisNativeGestureShortcut *shortcut = new KisNativeGestureShortcut(action, index, type);
0630     matcher.addShortcut(shortcut);
0631     return true;
0632 }
0633 
0634 void KisInputManager::Private::setupActions()
0635 {
0636     QList<KisAbstractInputAction*> actions = KisInputProfileManager::instance()->actions();
0637     Q_FOREACH (KisAbstractInputAction *action, actions) {
0638         KisToolInvocationAction *toolAction =
0639             dynamic_cast<KisToolInvocationAction*>(action);
0640 
0641         if(toolAction) {
0642             defaultInputAction = toolAction;
0643         }
0644     }
0645 
0646     connect(KisInputProfileManager::instance(), SIGNAL(currentProfileChanged()), q, SLOT(profileChanged()));
0647     if(KisInputProfileManager::instance()->currentProfile()) {
0648         q->profileChanged();
0649     }
0650 }
0651 
0652 bool KisInputManager::Private::processUnhandledEvent(QEvent *event)
0653 {
0654     bool retval = false;
0655 
0656     if (forwardAllEventsToTool ||
0657         event->type() == QEvent::KeyPress ||
0658         event->type() == QEvent::KeyRelease) {
0659 
0660         defaultInputAction->processUnhandledEvent(event);
0661         retval = true;
0662     }
0663 
0664     return retval && !forwardAllEventsToTool;
0665 }
0666 
0667 #ifdef HAVE_X11
0668 inline QPointF dividePoints(const QPointF &pt1, const QPointF &pt2) {
0669     return QPointF(pt1.x() / pt2.x(), pt1.y() / pt2.y());
0670 }
0671 
0672 inline QPointF multiplyPoints(const QPointF &pt1, const QPointF &pt2) {
0673     return QPointF(pt1.x() * pt2.x(), pt1.y() * pt2.y());
0674 }
0675 #endif
0676 
0677 void KisInputManager::Private::blockMouseEvents()
0678 {
0679     eventEater.activate();
0680 }
0681 
0682 void KisInputManager::Private::allowMouseEvents()
0683 {
0684     /**
0685      * On Windows tablet events may arrive asynchronously to the
0686      * mouse events (in WinTab mode). The problem is that Qt
0687      * generates Enter/Leave and FocusIn/Out events via mouse
0688      * events only. It means that TabletPress may come much before
0689      * Enter and FocusIn event and start the stroke. In such a case
0690      * we shouldn't unblock mouse events.
0691      *
0692      * See https://bugs.kde.org/show_bug.cgi?id=417040
0693      *
0694      * PS:
0695      * Ideally, we should fix Qt to generate Enter/Leave and
0696      * FocusIn/Out events based on tablet events as well, but
0697      * it is a lot of work.
0698      */
0699 #ifdef Q_OS_WIN32
0700     if (eventEater.hungry && matcher.hasRunningShortcut()) {
0701         return;
0702     }
0703 #endif
0704 
0705     eventEater.deactivate();
0706 }
0707 
0708 void KisInputManager::Private::eatOneMousePress()
0709 {
0710     eventEater.eatOneMousePress();
0711 }
0712 
0713 void KisInputManager::Private::resetCompressor() {
0714     compressedMoveEvent.reset();
0715     moveEventCompressor.stop();
0716 }
0717 
0718 void KisInputManager::Private::startBlockingTouch()
0719 {
0720     eventEater.startBlockingTouch();
0721 }
0722 
0723 void KisInputManager::Private::stopBlockingTouch()
0724 {
0725     eventEater.stopBlockingTouch();
0726 }
0727 
0728 bool KisInputManager::Private::handleCompressedTabletEvent(QEvent *event)
0729 {
0730     bool retval = false;
0731 
0732     if (event->type() == QTouchEvent::TouchUpdate && touchHasBlockedPressEvents) {
0733         matcher.touchUpdateEvent((QTouchEvent *)event);
0734     } else if (!matcher.pointerMoved(event) && toolProxy && event->type() != QTouchEvent::TouchUpdate) {
0735         toolProxy->forwardHoverEvent(event);
0736     }
0737     retval = true;
0738     event->setAccepted(true);
0739 
0740     return retval;
0741 }
0742 
0743 void KisInputManager::Private::fixShortcutMatcherModifiersState()
0744 {
0745     KisExtendedModifiersMapper mapper;
0746 
0747     QVector<Qt::Key> newKeys;
0748     Qt::KeyboardModifiers modifiers = mapper.queryStandardModifiers();
0749     Q_FOREACH (Qt::Key key, mapper.queryExtendedModifiers()) {
0750         QKeyEvent kevent(QEvent::ShortcutOverride, key, modifiers);
0751         newKeys << KisExtendedModifiersMapper::workaroundShiftAltMetaHell(&kevent);
0752     }
0753 
0754     fixShortcutMatcherModifiersState(newKeys, modifiers);
0755 }
0756 
0757 void KisInputManager::Private::fixShortcutMatcherModifiersState(QVector<Qt::Key> newKeys, Qt::KeyboardModifiers modifiers)
0758 {
0759     QVector<Qt::Key> danglingKeys = matcher.debugPressedKeys();
0760 
0761     matcher.handlePolledKeys(newKeys);
0762 
0763     for (auto it = danglingKeys.begin(); it != danglingKeys.end();) {
0764         if (newKeys.contains(*it)) {
0765             newKeys.removeOne(*it);
0766             it = danglingKeys.erase(it);
0767         } else {
0768             ++it;
0769         }
0770     }
0771 
0772     Q_FOREACH (Qt::Key key, danglingKeys) {
0773         QKeyEvent kevent(QEvent::KeyRelease, key, modifiers);
0774         processUnhandledEvent(&kevent);
0775     }
0776 
0777     Q_FOREACH (Qt::Key key, newKeys) {
0778         // just replay the whole sequence
0779         {
0780             QKeyEvent kevent(QEvent::ShortcutOverride, key, modifiers);
0781             processUnhandledEvent(&kevent);
0782         }
0783         {
0784             QKeyEvent kevent(QEvent::KeyPress, key, modifiers);
0785             processUnhandledEvent(&kevent);
0786         }
0787     }
0788 }
0789 
0790 qint64 KisInputManager::Private::TabletLatencyTracker::currentTimestamp() const
0791 {
0792     // on OS X, we need to compute the timestamp that compares correctly against the native event timestamp,
0793     // which seems to be the msecs since system startup. On Linux with WinTab, we produce the timestamp that
0794     // we compare against ourselves in QWindowSystemInterface.
0795 
0796     QElapsedTimer elapsed;
0797     elapsed.start();
0798     return elapsed.msecsSinceReference();
0799 }
0800 
0801 void KisInputManager::Private::TabletLatencyTracker::print(const QString &message)
0802 {
0803     dbgTablet << qUtf8Printable(message);
0804 }