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 <QTimer>
13#include <QtGlobal>
14
15#include <boost/preprocessor/repeat_from_to.hpp>
16
17#include "kis_input_manager.h"
18#include "kis_config.h"
21#include "kis_stroke_shortcut.h"
22#include "kis_touch_shortcut.h"
27
28#include "config-qt-patches-present.h"
29
30#include <memory>
31
33{
34 Q_UNUSED(value)
35 // TODO: remove
36}
37
39 : q(qq)
40 , moveEventCompressor(10 /* ms */,
41 KisSignalCompressor::FIRST_ACTIVE,
42 KisSignalCompressor::ADDITIVE_INTERVAL)
43 , priorityEventFilterSeqNo(0)
44 , popupWidget(nullptr)
45 , canvasSwitcher(this, qq)
46{
47 KisConfig cfg(true);
48
52
53 if (cfg.trackTabletEventLatency()) {
55 }
56
58 [this] () {
59 return this->canvas ? this->canvas->inputActionGroupsMaskInterface()->inputActionGroupsMask() : AllActionGroup;
60 });
61
62#ifdef Q_OS_ANDROID
63 // a workaround for Xiaomi tablets
65#endif
66
72#ifdef Q_OS_MACOS
74#endif
75
80#if defined Q_OS_LINUX && !KRITA_QT_HAS_UNBALANCED_KEY_PRESS_RELEASE_PATCH
82#endif
83
84 if (qEnvironmentVariableIsSet("KRITA_FIX_UNBALANCED_KEY_EVENTS")) {
85 useUnbalancedKeyPressEventWorkaround = qEnvironmentVariableIntValue("KRITA_FIX_UNBALANCED_KEY_EVENTS");
86 }
87}
88
89static const int InputWidgetsThreshold = 2000;
90static const int OtherWidgetsThreshold = 400;
91
93 : QObject(p),
94 d(_d),
95 eatOneMouseStroke(false),
96 focusSwitchThreshold(InputWidgetsThreshold)
97{
98}
99
101{
102 QWidget *widget = qobject_cast<QWidget*>(object);
104
105 thresholdConnections.clear();
106 thresholdConnections.addConnection(&focusSwitchThreshold, SIGNAL(timeout()), widget, SLOT(setFocus()));
107}
108
110{
111 if (!canvas) return;
112
113 QObject *canvasWidget = canvas->canvasWidget();
114
115 if (!canvasResolver.contains(canvasWidget)) {
116 canvasResolver.insert(canvasWidget, canvas);
117 } else {
118 // just a sanity cheek to find out if we are
119 // trying to add two canvases concurrently.
121 }
122
123 if (canvas != d->canvas) {
124 d->q->setupAsEventFilter(canvasWidget);
125 canvasWidget->installEventFilter(this);
126
127 setupFocusThreshold(canvasWidget);
128 focusSwitchThreshold.setEnabled(false);
129
130 d->canvas = canvas;
131 d->toolProxy = qobject_cast<KisToolProxy*>(canvas->toolProxy());
132 }
133}
134
136{
137 QObject *widget = canvas->canvasWidget();
138
139 canvasResolver.remove(widget);
140
141 if (d->eventsReceiver == widget) {
143 }
144
145 widget->removeEventFilter(this);
146
147 if (d->canvas == canvas) {
148 d->canvas = 0;
149 d->toolProxy = 0;
150 }
151}
152
153bool isInputWidget(QWidget *w)
154{
155 if (!w) return false;
156
157
159 types << QLatin1String("QAbstractSlider");
160 types << QLatin1String("QAbstractSpinBox");
161 types << QLatin1String("QLineEdit");
162 types << QLatin1String("QTextEdit");
163 types << QLatin1String("QPlainTextEdit");
164 types << QLatin1String("QComboBox");
165 types << QLatin1String("QKeySequenceEdit");
166
167 Q_FOREACH (const QLatin1String &type, types) {
168 if (w->inherits(type.data())) {
169 return true;
170 }
171 }
172
173 return false;
174}
175
176bool KisInputManager::Private::CanvasSwitcher::eventFilter(QObject* object, QEvent* event )
177{
178 if (canvasResolver.contains(object)) {
179 switch (event->type()) {
180 case QEvent::FocusIn: {
181 QFocusEvent *fevent = static_cast<QFocusEvent*>(event);
182 KisCanvas2 *canvas = canvasResolver.value(object);
183
184 // only relevant canvases from the same main window should be
185 // registered in the switcher
187
188 if (canvas != d->canvas) {
189 eatOneMouseStroke = 2 * (fevent->reason() == Qt::MouseFocusReason);
190 }
191
192 d->canvas = canvas;
193 d->toolProxy = qobject_cast<KisToolProxy*>(canvas->toolProxy());
194
195 d->q->setupAsEventFilter(object);
196
197 object->removeEventFilter(this);
198 object->installEventFilter(this);
199
200 setupFocusThreshold(object);
201 focusSwitchThreshold.setEnabled(false);
202
203 const QPoint globalPos = QCursor::pos();
204 const QPoint localPos = d->canvas->canvasWidget()->mapFromGlobal(globalPos);
205 QWidget *canvasWindow = d->canvas->canvasWidget()->window();
206 const QPoint windowsPos = canvasWindow ? canvasWindow->mapFromGlobal(globalPos) : localPos;
207
208 QEnterEvent event(localPos, windowsPos, globalPos);
209 d->q->eventFilter(object, &event);
210 break;
211 }
212 case QEvent::FocusOut: {
213 focusSwitchThreshold.setEnabled(true);
214 break;
215 }
216 case QEvent::Enter: {
217 break;
218 }
219 case QEvent::Leave: {
220 focusSwitchThreshold.stop();
221 break;
222 }
223 case QEvent::Wheel: {
224 QWidget *widget = static_cast<QWidget*>(object);
225 widget->setFocus();
226 break;
227 }
228 case QEvent::MouseButtonPress:
229 case QEvent::MouseButtonRelease:
230 case QEvent::TabletPress:
231 case QEvent::TabletRelease:
232 focusSwitchThreshold.forceDone();
233
234 if (eatOneMouseStroke) {
235 eatOneMouseStroke--;
236 return true;
237 }
238 break;
239 case QEvent::MouseButtonDblClick:
240 focusSwitchThreshold.forceDone();
241 if (eatOneMouseStroke) {
242 return true;
243 }
244 break;
245 case QEvent::MouseMove:
246 case QEvent::TabletMove: {
247 QWidget *widget = static_cast<QWidget*>(object);
248
249 if (!widget->hasFocus()) {
250 const int delay =
251 isInputWidget(QApplication::focusWidget()) ?
253
254 focusSwitchThreshold.setDelayThreshold(delay);
255 focusSwitchThreshold.start();
256 }
257 }
258 break;
259 default:
260 break;
261 }
262 }
263 return QObject::eventFilter(object, event);
264}
265
269
271{
272 switch (event->type()) {
273 case QEvent::TabletEnterProximity:
274 KisInputEventsEater::debugEvent<QEvent>(event);
276 break;
277 case QEvent::TabletLeaveProximity:
278 KisInputEventsEater::debugEvent<QEvent>(event);
280 break;
281#ifdef Q_OS_WIN
282 case QEvent::KeyPress:
283 case QEvent::KeyRelease:
284 case QEvent::ShortcutOverride:
285 if (d->ignoreHighFunctionKeys) {
286 QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
287 int key = keyEvent->key();
288
289 if (key >= Qt::Key_F13 && key <= Qt::Key_F35) {
291 const QString pre = "[BLOCKED HIGH F-KEY]";
293 }
294 return true;
295 }
296 break;
297 }
298#endif /* Q_OS_WIN */
299 default:
300 break;
301 }
302 return QObject::eventFilter(object, event);
303}
304
305#define EXTRA_BUTTON(z, n, _) \
306 if(buttons & Qt::ExtraButton##n) { \
307 buttonSet << Qt::ExtraButton##n; \
308 }
309
311 const QList<Qt::Key> &modifiers,
312 Qt::MouseButtons buttons)
313{
314 KisStrokeShortcut *strokeShortcut =
315 new KisStrokeShortcut(action, index);
316
317 QSet<Qt::MouseButton> buttonSet;
318 if(buttons & Qt::LeftButton) {
319 buttonSet << Qt::LeftButton;
320 }
321 if(buttons & Qt::RightButton) {
322 buttonSet << Qt::RightButton;
323 }
324 if(buttons & Qt::MiddleButton) {
325 buttonSet << Qt::MiddleButton;
326 }
327
328BOOST_PP_REPEAT_FROM_TO(1, 25, EXTRA_BUTTON, _)
329
330 if (!buttonSet.empty()) {
331 strokeShortcut->setButtons(QSet<Qt::Key>(modifiers.cbegin(), modifiers.cend()), buttonSet);
332 matcher.addShortcut(strokeShortcut);
333 }
334 else {
335 delete strokeShortcut;
336 }
337}
338
340 const QList<Qt::Key> &keys)
341{
342 if (keys.size() == 0) return;
343
344 KisSingleActionShortcut *keyShortcut =
345 new KisSingleActionShortcut(action, index);
346
347 //Note: Ordering is important here, Shift + V is different from V + Shift,
348 //which is the reason we use the last key here since most users will enter
349 //shortcuts as "Shift + V". Ideally this should not happen, but this is
350 //the way the shortcut matcher is currently implemented.
351 QList<Qt::Key> allKeys = keys;
352 Qt::Key key = allKeys.takeLast();
353 QSet<Qt::Key> modifiers = QSet<Qt::Key>(allKeys.begin(), allKeys.end());
354 keyShortcut->setKey(modifiers, key);
355 matcher.addShortcut(keyShortcut);
356}
357
359 const QList<Qt::Key> &modifiers,
361{
362
363
364 auto addSingleActionShortcut = [&] (KisSingleActionShortcut::WheelAction a) {
365 std::unique_ptr<KisSingleActionShortcut> keyShortcut(
366 new KisSingleActionShortcut(action, index));
367 keyShortcut->setWheel(QSet<Qt::Key>(modifiers.begin(), modifiers.end()), a);
368 matcher.addShortcut(keyShortcut.release());
369 };
370
371 switch(wheelAction) {
373 addSingleActionShortcut(KisSingleActionShortcut::WheelUp);
374 break;
376 addSingleActionShortcut(KisSingleActionShortcut::WheelDown);
377 break;
379 addSingleActionShortcut(KisSingleActionShortcut::WheelLeft);
380 break;
382 addSingleActionShortcut(KisSingleActionShortcut::WheelRight);
383 break;
386 break;
387 }
388}
389
391{
392 KisTouchShortcut *shortcut = new KisTouchShortcut(action, index, gesture);
393 if (isTouchPainting) {
394 shortcut->setIsTouchPainting(true);
395 shortcut->setMinDragThreshold(1.5); // min threshold for touch painting is 1.5 px
396 }
397 dbgKrita << "TouchAction:" << action->name() << (isTouchPainting ? "touch-painting" : "");
398 switch(gesture) {
402 shortcut->setMinimumTouchPoints(1);
403 shortcut->setMaximumTouchPoints(1);
404 break;
407 shortcut->setMinimumTouchPoints(2);
408 shortcut->setMaximumTouchPoints(2);
409 break;
412 shortcut->setMinimumTouchPoints(3);
413 shortcut->setMaximumTouchPoints(3);
414 break;
417 shortcut->setMinimumTouchPoints(4);
418 shortcut->setMaximumTouchPoints(4);
419 break;
422 shortcut->setMinimumTouchPoints(5);
423 shortcut->setMaximumTouchPoints(5);
424 default:
425 break;
426 }
427 matcher.addShortcut(shortcut);
428}
429
431{
432 auto addNativeGesture = [&] (KisNativeGestureShortcut::Type type) {
433 KisNativeGestureShortcut *shortcut = new KisNativeGestureShortcut(action, index, type);
434 matcher.addShortcut(shortcut);
435 };
436
437 // Native gestures:
438 // Qt5 only implements QNativeGestureEvent for macOS
439 // Qt6 implements QNativeGestureEvent for macOS and Wayland
440 //
441 // Touchpad pixel scrolling:
442 // Qt5 only implements touchpad pixel scrolling for macOS
443 // Qt6 implements touchpad pixel scrolling for macOS and Wayland
444
445 switch (gesture) {
448 break;
451 break;
454 break;
455 default:
456 return false;
457 }
458
459 return true;
460}
461
463{
465 Q_FOREACH (KisAbstractInputAction *action, actions) {
466 KisToolInvocationAction *toolAction =
467 dynamic_cast<KisToolInvocationAction*>(action);
468
469 if(toolAction) {
470 defaultInputAction = toolAction;
471 }
472 }
473
474 connect(KisInputProfileManager::instance(), SIGNAL(currentProfileChanged()), q, SLOT(profileChanged()));
475 if(KisInputProfileManager::instance()->currentProfile()) {
476 q->profileChanged();
477 }
478}
479
481{
482 bool retval = false;
483
485 event->type() == QEvent::KeyPress ||
486 event->type() == QEvent::KeyRelease) {
487
489 retval = true;
490 }
491
492 return retval && !forwardAllEventsToTool;
493}
494
499
501{
502 bool retval = false;
503
504 if (!matcher.pointerMoved(event) && toolProxy) {
505 toolProxy->forwardHoverEvent(event);
506 }
507 retval = true;
508 event->setAccepted(true);
509
510 return retval;
511}
512
514{
516
517 QVector<Qt::Key> newKeys;
518 Qt::KeyboardModifiers modifiers = mapper.queryStandardModifiers();
519 Q_FOREACH (Qt::Key key, mapper.queryExtendedModifiers()) {
520 QKeyEvent kevent(QEvent::ShortcutOverride, key, modifiers);
522 }
523
524 fixShortcutMatcherModifiersState(newKeys, modifiers);
525}
526
528{
530
531 matcher.handlePolledKeys(newKeys);
532
533 for (auto it = danglingKeys.begin(); it != danglingKeys.end();) {
534 if (newKeys.contains(*it)) {
535 newKeys.removeOne(*it);
536 it = danglingKeys.erase(it);
537 } else {
538 ++it;
539 }
540 }
541
549
550 Q_FOREACH (Qt::Key key, danglingKeys) {
551 QKeyEvent kevent(QEvent::KeyRelease, key, modifiers);
552 processUnhandledEvent(&kevent);
553 }
554
555 Q_FOREACH (Qt::Key key, newKeys) {
556 // just replay the whole sequence
557 {
558 QKeyEvent kevent(QEvent::ShortcutOverride, key, modifiers);
559 processUnhandledEvent(&kevent);
560 }
561 {
562 QKeyEvent kevent(QEvent::KeyPress, key, modifiers);
563 processUnhandledEvent(&kevent);
564 }
565 }
566}
567
569{
570 // on OS X, we need to compute the timestamp that compares correctly against the native event timestamp,
571 // which seems to be the msecs since system startup. On Linux with WinTab, we produce the timestamp that
572 // we compare against ourselves in QWindowSystemInterface.
573
574 QElapsedTimer elapsed;
575 elapsed.start();
576 return elapsed.msecsSinceReference();
577}
578
580{
581 dbgTablet << qUtf8Printable(message);
582}
float value(const T *src, size_t ch)
const Params2D p
@ AllActionGroup
Abstract base class for input actions.
static void setInputManager(KisInputManager *manager)
bool testingAcceptCompressedTabletEvents(bool defaultValue=false) const
int tabletEventsDelay(bool defaultValue=false) const
bool trackTabletEventLatency(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 addTouchShortcut(KisAbstractInputAction *action, int index, KisShortcutConfiguration::TouchGestureAction gesture, bool isTouchPainting=false)
void setMaskSyntheticEvents(bool value)
KisInputEventsEater eventEater
bool handleCompressedTabletEvent(QEvent *event)
bool addNativeGestureShortcut(KisAbstractInputAction *action, int index, KisShortcutConfiguration::NativeGestureAction gesture)
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
Central object to manage canvas input.
void setupAsEventFilter(QObject *receiver)
bool eventFilter(QObject *object, QEvent *event) override
QList< KisAbstractInputAction * > actions
static KisInputProfileManager * instance()
@ TouchpadScroll
Two-dimensional pixel scrolling on a touchpad, supported on MacOS and Wayland only.
@ SmartZoomGesture
Smart zoom gesture, typically a double tap that is a boolean zoom/unzoom.
@ PinchGesture
Pinch gesture, a two finger gesture used for navigating the canvas.
@ WheelDown
Downwards movement, toward the user.
@ WheelUp
Upwards movement, away from the user.
@ WheelReserved_0
Do not use! Previously was used for TouchpadScroll shortcut.
void addShortcut(KisSingleActionShortcut *shortcut)
void setInputActionGroupsMaskCallback(std::function< KisInputActionGroupsMask()> func)
void setIgnoreMultiFingerCancelWorkaroundEnalbed(bool value)
QVector< Qt::Key > debugPressedKeys() const
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.
@ 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 setIsTouchPainting(bool value)
void setMinDragThreshold(qreal value)
void setMaximumTouchPoints(int max)
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:49
#define dbgTablet
Definition kis_debug.h:62
static const int OtherWidgetsThreshold
static const int InputWidgetsThreshold
bool isInputWidget(QWidget *w)
#define EXTRA_BUTTON(z, n, _)
QString buttons(const T &ev)