Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_input_manager_p.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2015 Michael Abrahams <miabraha@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
8
9#include <QMap>
10#include <QApplication>
11#include <QScopedPointer>
12#include <QtGlobal>
13
14#include <boost/preprocessor/repeat_from_to.hpp>
15
16#include "kis_input_manager.h"
17#include "kis_config.h"
20#include "kis_stroke_shortcut.h"
21#include "kis_touch_shortcut.h"
25
27#include "kis_popup_palette.h"
28#include "config-qt-patches-present.h"
29
30
60static bool isMouseEventType(QEvent::Type t)
61{
62 return (t == QEvent::MouseMove ||
63 t == QEvent::MouseButtonPress ||
64 t == QEvent::MouseButtonRelease ||
65 t == QEvent::MouseButtonDblClick);
66}
67
73
75{
76 Q_UNUSED(target);
77
78 auto debugEvent = [&](int i) {
80 QString pre = QString("[BLOCKED %1:]").arg(i);
81 QMouseEvent *ev = static_cast<QMouseEvent*>(event);
83 }
84 };
85
86 auto debugTabletEvent = [&](int i) {
88 QString pre = QString("[BLOCKED %1:]").arg(i);
89 QTabletEvent *ev = static_cast<QTabletEvent*>(event);
91 }
92 };
93
94 auto debugTouchEvent = [&](int i) {
96 QString pre = QString("[BLOCKED %1:]").arg(i);
97 QTouchEvent *ev = static_cast<QTouchEvent*>(event);
99 }
100 };
101
102 if (peckish && event->type() == QEvent::MouseButtonPress
103 // Drop one mouse press following tabletPress or touchBegin
104 && (static_cast<QMouseEvent*>(event)->button() == Qt::LeftButton)) {
105 peckish = false;
106 debugEvent(1);
107 return true;
108 }
109
110 if (activateSecondaryButtonsWorkaround) {
111 if (event->type() == QEvent::TabletPress ||
112 event->type() == QEvent::TabletRelease) {
113
114 QTabletEvent *te = static_cast<QTabletEvent*>(event);
115 if (te->button() != Qt::LeftButton) {
116 debugTabletEvent(3);
117 return true;
118 }
119 } else if (event->type() == QEvent::MouseButtonPress ||
120 event->type() == QEvent::MouseButtonRelease ||
121 event->type() == QEvent::MouseButtonDblClick) {
122
123 QMouseEvent *me = static_cast<QMouseEvent*>(event);
124 if (me->button() != Qt::LeftButton) {
125 return false;
126 }
127 }
128 }
129
130 if (isMouseEventType(event->type()) &&
131 (hungry
132 // On Mac, we need mouse events when the tablet is in proximity, but not pressed down
133 // since tablet move events are not generated until after tablet press.
134 #ifndef Q_OS_MAC
135 || (eatSyntheticEvents && static_cast<QMouseEvent*>(event)->source() != Qt::MouseEventNotSynthesized)
136 #endif
137 )) {
138 // Drop mouse events if enabled or event was synthetic & synthetic events are disabled
139 debugEvent(2);
140 return true;
141 }
142
143 if (eatTouchEvents && event->type() == QEvent::TouchBegin) {
144 // Drop touch events. If QEvent::TouchBegin is ignored, we won't
145 // receive further touch events until the next TouchBegin.
146 debugTouchEvent(3);
147 event->ignore();
148 return true;
149 }
150
151 return false; // All clear - let this one through!
152}
153
154
156{
157 if (!hungry && (KisTabletDebugger::instance()->debugEnabled())) {
158 dbgTablet << "Start blocking mouse events";
159 }
160 hungry = true;
161}
162
164{
165 if (hungry && (KisTabletDebugger::instance()->debugEnabled())) {
166 dbgTablet << "Stop blocking mouse events";
167 }
168 hungry = false;
169}
170
172{
173 // Enable on other platforms if getting full-pressure splotches
174 peckish = true;
175}
176
178{
179 eatTouchEvents = true;
180}
181
183{
184 eatTouchEvents = false;
185}
186
191
196
198 : q(qq)
199 , moveEventCompressor(10 /* ms */,
200 KisSignalCompressor::FIRST_ACTIVE,
201 KisSignalCompressor::ADDITIVE_INTERVAL)
203 , popupWidget(nullptr)
204 , canvasSwitcher(this, qq)
205{
206 KisConfig cfg(true);
207
211
212 if (cfg.trackTabletEventLatency()) {
214 }
215
217 [this] () {
218 return this->canvas ? this->canvas->inputActionGroupsMaskInterface()->inputActionGroupsMask() : AllActionGroup;
219 });
220
226#ifdef Q_OS_MACOS
228#endif
229
234#if defined Q_OS_LINUX && !KRITA_QT_HAS_UNBALANCED_KEY_PRESS_RELEASE_PATCH
236#endif
237
238 if (qEnvironmentVariableIsSet("KRITA_FIX_UNBALANCED_KEY_EVENTS")) {
239 useUnbalancedKeyPressEventWorkaround = qEnvironmentVariableIntValue("KRITA_FIX_UNBALANCED_KEY_EVENTS");
240 }
241}
242
243static const int InputWidgetsThreshold = 2000;
244static const int OtherWidgetsThreshold = 400;
245
247 : QObject(p),
248 d(_d),
249 eatOneMouseStroke(false),
250 focusSwitchThreshold(InputWidgetsThreshold)
251{
252}
253
255{
256 QWidget *widget = qobject_cast<QWidget*>(object);
258
259 thresholdConnections.clear();
260 thresholdConnections.addConnection(&focusSwitchThreshold, SIGNAL(timeout()), widget, SLOT(setFocus()));
261}
262
264{
265 if (!canvas) return;
266
267 QObject *canvasWidget = canvas->canvasWidget();
268
269 if (!canvasResolver.contains(canvasWidget)) {
270 canvasResolver.insert(canvasWidget, canvas);
271 } else {
272 // just a sanity cheek to find out if we are
273 // trying to add two canvases concurrently.
275 }
276
277 if (canvas != d->canvas) {
278 d->q->setupAsEventFilter(canvasWidget);
279 canvasWidget->installEventFilter(this);
280
281 setupFocusThreshold(canvasWidget);
282 focusSwitchThreshold.setEnabled(false);
283
284 d->canvas = canvas;
285 d->toolProxy = qobject_cast<KisToolProxy*>(canvas->toolProxy());
286 }
287}
288
290{
291 QObject *widget = canvas->canvasWidget();
292
293 canvasResolver.remove(widget);
294
295 if (d->eventsReceiver == widget) {
297 }
298
299 widget->removeEventFilter(this);
300
301 if (d->canvas == canvas) {
302 d->canvas = 0;
303 d->toolProxy = 0;
304 }
305}
306
307bool isInputWidget(QWidget *w)
308{
309 if (!w) return false;
310
311
313 types << QLatin1String("QAbstractSlider");
314 types << QLatin1String("QAbstractSpinBox");
315 types << QLatin1String("QLineEdit");
316 types << QLatin1String("QTextEdit");
317 types << QLatin1String("QPlainTextEdit");
318 types << QLatin1String("QComboBox");
319 types << QLatin1String("QKeySequenceEdit");
320
321 Q_FOREACH (const QLatin1String &type, types) {
322 if (w->inherits(type.data())) {
323 return true;
324 }
325 }
326
327 return false;
328}
329
330bool KisInputManager::Private::CanvasSwitcher::eventFilter(QObject* object, QEvent* event )
331{
332 if (canvasResolver.contains(object)) {
333 switch (event->type()) {
334 case QEvent::FocusIn: {
335 QFocusEvent *fevent = static_cast<QFocusEvent*>(event);
336 KisCanvas2 *canvas = canvasResolver.value(object);
337
338 // only relevant canvases from the same main window should be
339 // registered in the switcher
341
342 if (canvas != d->canvas) {
343 eatOneMouseStroke = 2 * (fevent->reason() == Qt::MouseFocusReason);
344 }
345
346 d->canvas = canvas;
347 d->toolProxy = qobject_cast<KisToolProxy*>(canvas->toolProxy());
348
349 d->q->setupAsEventFilter(object);
350
351 object->removeEventFilter(this);
352 object->installEventFilter(this);
353
354 setupFocusThreshold(object);
355 focusSwitchThreshold.setEnabled(false);
356
357 const QPoint globalPos = QCursor::pos();
358 const QPoint localPos = d->canvas->canvasWidget()->mapFromGlobal(globalPos);
359 QWidget *canvasWindow = d->canvas->canvasWidget()->window();
360 const QPoint windowsPos = canvasWindow ? canvasWindow->mapFromGlobal(globalPos) : localPos;
361
362 QEnterEvent event(localPos, windowsPos, globalPos);
363 d->q->eventFilter(object, &event);
364 break;
365 }
366 case QEvent::FocusOut: {
367 focusSwitchThreshold.setEnabled(true);
368 break;
369 }
370 case QEvent::Enter: {
371 break;
372 }
373 case QEvent::Leave: {
374 focusSwitchThreshold.stop();
375 break;
376 }
377 case QEvent::Wheel: {
378 QWidget *widget = static_cast<QWidget*>(object);
379 widget->setFocus();
380 break;
381 }
382 case QEvent::MouseButtonPress:
383 case QEvent::MouseButtonRelease:
384 case QEvent::TabletPress:
385 case QEvent::TabletRelease:
386 focusSwitchThreshold.forceDone();
387
388 if (eatOneMouseStroke) {
389 eatOneMouseStroke--;
390 return true;
391 }
392 break;
393 case QEvent::MouseButtonDblClick:
394 focusSwitchThreshold.forceDone();
395 if (eatOneMouseStroke) {
396 return true;
397 }
398 break;
399 case QEvent::MouseMove:
400 case QEvent::TabletMove: {
401 QWidget *widget = static_cast<QWidget*>(object);
402
403 if (!widget->hasFocus()) {
404 const int delay =
405 isInputWidget(QApplication::focusWidget()) ?
407
408 focusSwitchThreshold.setDelayThreshold(delay);
409 focusSwitchThreshold.start();
410 }
411 }
412 break;
413 default:
414 break;
415 }
416 }
417 return QObject::eventFilter(object, event);
418}
419
423
425{
426 switch (event->type()) {
427 case QEvent::TabletEnterProximity:
428 d->debugEvent<QEvent, false>(event);
429 // Tablet proximity events are unreliable AND fake mouse events do not
430 // necessarily come after tablet events, so this is insufficient.
431 // d->eventEater.eatOneMousePress();
432
433 // Qt sends fake mouse events instead of hover events, so not very useful.
434 // Don't block mouse events on tablet since tablet move events are not generated until
435 // after tablet press.
436#ifndef Q_OS_MACOS
438#endif
439 break;
440 case QEvent::TabletLeaveProximity:
441 d->debugEvent<QEvent, false>(event);
443 break;
444#ifdef Q_OS_WIN
445 case QEvent::KeyPress:
446 case QEvent::KeyRelease:
447 case QEvent::ShortcutOverride:
448 if (d->ignoreHighFunctionKeys) {
449 QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
450 int key = keyEvent->key();
451
452 if (key >= Qt::Key_F13 && key <= Qt::Key_F35) {
454 const QString pre = "[BLOCKED HIGH F-KEY]";
456 }
457 return true;
458 }
459 break;
460 }
461#endif /* Q_OS_WIN */
462 default:
463 break;
464 }
465 return QObject::eventFilter(object, event);
466}
467
468#define EXTRA_BUTTON(z, n, _) \
469 if(buttons & Qt::ExtraButton##n) { \
470 buttonSet << Qt::ExtraButton##n; \
471 }
472
474 const QList<Qt::Key> &modifiers,
475 Qt::MouseButtons buttons)
476{
477 KisStrokeShortcut *strokeShortcut =
478 new KisStrokeShortcut(action, index);
479
480 QSet<Qt::MouseButton> buttonSet;
481 if(buttons & Qt::LeftButton) {
482 buttonSet << Qt::LeftButton;
483 }
484 if(buttons & Qt::RightButton) {
485 buttonSet << Qt::RightButton;
486 }
487 if(buttons & Qt::MiddleButton) {
488 buttonSet << Qt::MiddleButton;
489 }
490
491BOOST_PP_REPEAT_FROM_TO(1, 25, EXTRA_BUTTON, _)
492
493 if (!buttonSet.empty()) {
494 strokeShortcut->setButtons(QSet<Qt::Key>(modifiers.cbegin(), modifiers.cend()), buttonSet);
495 matcher.addShortcut(strokeShortcut);
496 }
497 else {
498 delete strokeShortcut;
499 }
500}
501
503 const QList<Qt::Key> &keys)
504{
505 if (keys.size() == 0) return;
506
507 KisSingleActionShortcut *keyShortcut =
508 new KisSingleActionShortcut(action, index);
509
510 //Note: Ordering is important here, Shift + V is different from V + Shift,
511 //which is the reason we use the last key here since most users will enter
512 //shortcuts as "Shift + V". Ideally this should not happen, but this is
513 //the way the shortcut matcher is currently implemented.
514 QList<Qt::Key> allKeys = keys;
515 Qt::Key key = allKeys.takeLast();
516 QSet<Qt::Key> modifiers = QSet<Qt::Key>(allKeys.begin(), allKeys.end());
517 keyShortcut->setKey(modifiers, key);
518 matcher.addShortcut(keyShortcut);
519}
520
522 const QList<Qt::Key> &modifiers,
524{
525 QScopedPointer<KisSingleActionShortcut> keyShortcut(
526 new KisSingleActionShortcut(action, index));
527
529 switch(wheelAction) {
532 break;
535 break;
538 break;
541 break;
544 break;
545 default:
546 return;
547 }
548 keyShortcut->setWheel(QSet<Qt::Key>(modifiers.begin(), modifiers.end()), a);
549 matcher.addShortcut(keyShortcut.take());
550}
551
553{
554 KisTouchShortcut *shortcut = new KisTouchShortcut(action, index, gesture);
555 dbgKrita << "TouchAction:" << action->name();
556 switch(gesture) {
557#ifndef Q_OS_MACOS
560 // Touch painting takes precedence over one-finger touch shortcuts, so
561 // disable this type of shortcut when touch painting is active.
562 shortcut->setDisableOnTouchPainting(true);
563 shortcut->setMinimumTouchPoints(1);
564 shortcut->setMaximumTouchPoints(1);
565 break;
568 shortcut->setMinimumTouchPoints(2);
569 shortcut->setMaximumTouchPoints(2);
570 break;
573 shortcut->setMinimumTouchPoints(3);
574 shortcut->setMaximumTouchPoints(3);
575 break;
578 shortcut->setMinimumTouchPoints(4);
579 shortcut->setMaximumTouchPoints(4);
580 break;
583 shortcut->setMinimumTouchPoints(5);
584 shortcut->setMaximumTouchPoints(5);
585#endif
586 default:
587 break;
588 }
589 matcher.addShortcut(shortcut);
590}
591
593{
594 // Qt5 only implements QNativeGestureEvent for macOS
595 Qt::NativeGestureType type;
596 switch (gesture) {
597#ifdef Q_OS_MACOS
598 case KisShortcutConfiguration::PinchGesture:
599 type = Qt::ZoomNativeGesture;
600 break;
601 case KisShortcutConfiguration::PanGesture:
602 type = Qt::PanNativeGesture;
603 break;
604 case KisShortcutConfiguration::RotateGesture:
605 type = Qt::RotateNativeGesture;
606 break;
607 case KisShortcutConfiguration::SmartZoomGesture:
608 type = Qt::SmartZoomNativeGesture;
609 break;
610#endif
611 default:
612 return false;
613 }
614
615 KisNativeGestureShortcut *shortcut = new KisNativeGestureShortcut(action, index, type);
616 matcher.addShortcut(shortcut);
617 return true;
618}
619
621{
623 Q_FOREACH (KisAbstractInputAction *action, actions) {
624 KisToolInvocationAction *toolAction =
625 dynamic_cast<KisToolInvocationAction*>(action);
626
627 if(toolAction) {
628 defaultInputAction = toolAction;
629 }
630 }
631
632 connect(KisInputProfileManager::instance(), SIGNAL(currentProfileChanged()), q, SLOT(profileChanged()));
633 if(KisInputProfileManager::instance()->currentProfile()) {
634 q->profileChanged();
635 }
636}
637
639{
640 bool retval = false;
641
643 event->type() == QEvent::KeyPress ||
644 event->type() == QEvent::KeyRelease) {
645
647 retval = true;
648 }
649
650 return retval && !forwardAllEventsToTool;
651}
652
653#ifdef HAVE_X11
654inline QPointF dividePoints(const QPointF &pt1, const QPointF &pt2) {
655 return QPointF(pt1.x() / pt2.x(), pt1.y() / pt2.y());
656}
657
658inline QPointF multiplyPoints(const QPointF &pt1, const QPointF &pt2) {
659 return QPointF(pt1.x() * pt2.x(), pt1.y() * pt2.y());
660}
661#endif
662
667
669{
685#ifdef Q_OS_WIN32
687 return;
688 }
689#endif
690
692}
693
698
703
708
713
715{
716 bool retval = false;
717
718 if (event->type() == QTouchEvent::TouchUpdate && touchHasBlockedPressEvents) {
719 matcher.touchUpdateEvent((QTouchEvent *)event);
720 } else if (!matcher.pointerMoved(event) && toolProxy && event->type() != QTouchEvent::TouchUpdate) {
721 toolProxy->forwardHoverEvent(event);
722 }
723 retval = true;
724 event->setAccepted(true);
725
726 return retval;
727}
728
730{
732
733 QVector<Qt::Key> newKeys;
734 Qt::KeyboardModifiers modifiers = mapper.queryStandardModifiers();
735 Q_FOREACH (Qt::Key key, mapper.queryExtendedModifiers()) {
736 QKeyEvent kevent(QEvent::ShortcutOverride, key, modifiers);
738 }
739
740 fixShortcutMatcherModifiersState(newKeys, modifiers);
741}
742
744{
746
747 matcher.handlePolledKeys(newKeys);
748
749 for (auto it = danglingKeys.begin(); it != danglingKeys.end();) {
750 if (newKeys.contains(*it)) {
751 newKeys.removeOne(*it);
752 it = danglingKeys.erase(it);
753 } else {
754 ++it;
755 }
756 }
757
758 Q_FOREACH (Qt::Key key, danglingKeys) {
759 QKeyEvent kevent(QEvent::KeyRelease, key, modifiers);
760 processUnhandledEvent(&kevent);
761 }
762
763 Q_FOREACH (Qt::Key key, newKeys) {
764 // just replay the whole sequence
765 {
766 QKeyEvent kevent(QEvent::ShortcutOverride, key, modifiers);
767 processUnhandledEvent(&kevent);
768 }
769 {
770 QKeyEvent kevent(QEvent::KeyPress, key, modifiers);
771 processUnhandledEvent(&kevent);
772 }
773 }
774}
775
777{
778 // on OS X, we need to compute the timestamp that compares correctly against the native event timestamp,
779 // which seems to be the msecs since system startup. On Linux with WinTab, we produce the timestamp that
780 // we compare against ourselves in QWindowSystemInterface.
781
782 QElapsedTimer elapsed;
783 elapsed.start();
784 return elapsed.msecsSinceReference();
785}
786
788{
789 dbgTablet << qUtf8Printable(message);
790}
float value(const T *src, size_t ch)
const Params2D p
@ AllActionGroup
KisMagneticGraph::vertex_descriptor target(typename KisMagneticGraph::edge_descriptor e, KisMagneticGraph g)
KisMagneticGraph::vertex_descriptor source(typename KisMagneticGraph::edge_descriptor e, KisMagneticGraph g)
connect(this, SIGNAL(optionsChanged()), this, SLOT(saveOptions()))
static bool debugEnabled()
Linethickness.
Abstract base class for input actions.
bool testingAcceptCompressedTabletEvents(bool defaultValue=false) const
int tabletEventsDelay(bool defaultValue=false) const
bool trackTabletEventLatency(bool defaultValue=false) const
bool useRightMiddleTabletButtonWorkaround(bool defaultValue=false) const
bool testingCompressBrushEvents(bool defaultValue=false) const
static Qt::Key workaroundShiftAltMetaHell(const QKeyEvent *keyEvent)
bool eventFilter(QObject *object, QEvent *event) override
bool eventFilter(QObject *object, QEvent *event) override
virtual void print(const QString &message) override
void debugEvent(QEvent *event)
void setMaskSyntheticEvents(bool value)
bool addNativeGestureShortcut(KisAbstractInputAction *action, int index, KisShortcutConfiguration::GestureAction gesture)
bool handleCompressedTabletEvent(QEvent *event)
void addKeyShortcut(KisAbstractInputAction *action, int index, const QList< Qt::Key > &keys)
void addWheelShortcut(KisAbstractInputAction *action, int index, const QList< Qt::Key > &modifiers, KisShortcutConfiguration::MouseWheelMovement wheelAction)
KisToolInvocationAction * defaultInputAction
QPointer< KisToolProxy > toolProxy
KisSignalCompressor moveEventCompressor
void addStrokeShortcut(KisAbstractInputAction *action, int index, const QList< Qt::Key > &modifiers, Qt::MouseButtons buttons)
QScopedPointer< QEvent > compressedMoveEvent
QPointer< KisCanvas2 > canvas
bool processUnhandledEvent(QEvent *event)
KisSharedPtr< TabletLatencyTracker > tabletLatencyTracker
void addTouchShortcut(KisAbstractInputAction *action, int index, KisShortcutConfiguration::GestureAction gesture)
KisPopupWidgetInterface * popupWidget
Central object to manage canvas input.
void setupAsEventFilter(QObject *receiver)
bool eventFilter(QObject *object, QEvent *event) override
QList< KisAbstractInputAction * > actions
static KisInputProfileManager * instance()
@ WheelTrackpad
A pan movement on a trackpad.
@ WheelDown
Downwards movement, toward the user.
@ WheelUp
Upwards movement, away from the user.
void addShortcut(KisSingleActionShortcut *shortcut)
void setInputActionGroupsMaskCallback(std::function< KisInputActionGroupsMask()> func)
QVector< Qt::Key > debugPressedKeys() const
bool touchUpdateEvent(QTouchEvent *event)
void handlePolledKeys(const QVector< Qt::Key > &keys)
bool pointerMoved(QEvent *event)
void setDelay(std::function< bool()> idleCallback, int idleDelay, int timeout)
@ WheelDown
Mouse wheel moves down.
@ WheelTrackpad
A pan movement on a trackpad.
@ WheelRight
Mouse wheel moves right.
@ WheelLeft
Mouse wheel moves left.
void setKey(const QSet< Qt::Key > &modifiers, Qt::Key key)
void setButtons(const QSet< Qt::Key > &modifiers, const QSet< Qt::MouseButton > &buttons)
QString eventToString(const QMouseEvent &ev, const QString &prefix)
static KisTabletDebugger * instance()
Tool Invocation action of KisAbstractInputAction.
The KisTouchShortcut class only handles touch gestures it does not handle tool invocation i....
void setMaximumTouchPoints(int max)
void setDisableOnTouchPainting(bool disableOnTouchPainting)
void setMinimumTouchPoints(int min)
#define KIS_SAFE_ASSERT_RECOVER_BREAK(cond)
Definition kis_assert.h:127
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:128
#define KIS_SAFE_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:130
#define dbgKrita
Definition kis_debug.h:45
#define dbgTablet
Definition kis_debug.h:59
static const int OtherWidgetsThreshold
static const int InputWidgetsThreshold
static bool isMouseEventType(QEvent::Type t)
bool isInputWidget(QWidget *w)
#define EXTRA_BUTTON(z, n, _)
QString buttons(const T &ev)
bool eventFilter(QObject *target, QEvent *event)
#define _(s)
Definition xcftools.h:32