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 }