Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_shortcut_matcher.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2012 Dmitry Kazakov <dimula73@gmail.com>
3 * SPDX-FileCopyrightText: 2026 Ayanami Kaine <personal@ayanamikaine.com>
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 */
7
9
10#include <QEvent>
11#include <QMouseEvent>
12#include <QTabletEvent>
13
14#include "kis_assert.h"
16#include "kis_stroke_shortcut.h"
17#include "kis_touch_shortcut.h"
20#include "kis_config.h"
23#include <KoPointerEvent.h>
24
25#if QT_VERSION > QT_VERSION_CHECK(6, 0, 0)
26// for QMutableEventPoint
27#include <QWindow>
28#include <QtGui/private/qeventpoint_p.h>
29#endif
30
31inline QString debugShortcutName(const KisAbstractShortcut *shortcut)
32{
33 auto action = shortcut->action();
34 return QString("%1/%2").arg(action->name()).arg(action->shortcutIndexes().key(shortcut->shortcutIndex(), "<unknown>"));
35}
36
37#define DEBUG_ACTION(text) dbgInputMatcher << __FUNCTION__ << "-" << text;
38#define DEBUG_SHORTCUT(text, shortcut) dbgInputMatcher << __FUNCTION__ << "-" << text << "act:" << debugShortcutName(shortcut);
39#define DEBUG_KEY(text) dbgInputMatcher << __FUNCTION__ << "-" << text << "keys:" << m_d->keys;
40#define DEBUG_BUTTON_ACTION(text, button) dbgInputMatcher << __FUNCTION__ << "-" << text << "button:" << button << "btns:" << m_d->buttons << "keys:" << m_d->keys;
41#define DEBUG_EVENT_ACTION(text, event) if (event) {dbgInputMatcher << __FUNCTION__ << "-" << text << "type:" << event->type();}
42#define DEBUG_TOUCH_ACTION(text, event) \
43 if (event) { \
44 dbgInputMatcher << __FUNCTION__ << "-" << text << "type:" << event->type() \
45 << "tps:" << KisTouchShortcut::countTouchPoints(event, KisTouchShortcut::allTouchStates()) \
46 << "pressedTps:" << KisTouchShortcut::countTouchPoints(event, KisTouchShortcut::pressedOnlyTouchStates()) \
47 << "maxTps:" << m_d->maxTouchPoints << "drag:" << m_d->isTouchDragDetected; \
48 }
49
50
51namespace
52{
53QTouchEvent generateFakeTouchEndEvent(const QTouchEvent *event)
54{
55#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
56 auto points = event->touchPoints();
57#else
58 auto points = event->points();
59#endif
60
61 for (auto it = points.begin(); it != points.end();) {
62#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
63 if (it->state() == Qt::TouchPointReleased) {
64 it = points.erase(it);
65 } else {
66 it->setState(Qt::TouchPointReleased);
67 ++it;
68 }
69#else
70 if (it->state() == QEventPoint::Released) {
71 it = points.erase(it);
72 } else {
73 QMutableEventPoint::setState(*it, QEventPoint::Released);
74 ++it;
75 }
76#endif
77 }
78
79#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
80 return QTouchEvent(QEvent::TouchEnd, event->device(), event->modifiers(), Qt::TouchPointReleased, points);
81#else
82 return QTouchEvent(QEvent::TouchEnd, event->pointingDevice(), event->modifiers(), points);
83#endif
84
85}
86
87} // namespace
88
89class Q_DECL_HIDDEN KisShortcutMatcher::Private
90{
91public:
93 : runningShortcut(0)
94 , readyShortcut(0)
95 , touchShortcut(0)
96 , nativeGestureShortcut(0)
97 , touchpadScrollShortcut(0)
98 , actionGroupMask([] () { return AllActionGroup; })
99 , suppressAllActions(false)
100 , suppressAllKeyboardActions(false)
101 , cursorEntered(false)
102 {}
103
105 {
106 qDeleteAll(singleActionShortcuts);
107 qDeleteAll(strokeShortcuts);
108 qDeleteAll(touchShortcuts);
109 }
110
112 QSet<KisSingleActionShortcut*> suppressedSingleActionShortcuts;
117
118 QSet<Qt::Key> keys; // Model of currently pressed keys
119 QSet<Qt::MouseButton> buttons; // Model of currently pressed buttons
120
121 QSet<Qt::Key> polledKeys; // Keys that were polled using native platform APIs and thus need to be treated carefully, as they may not generate QT key events.
122
126
130 std::optional<KisTouchHoldEventsPostponer> touchHoldEventPostponer;
131
132
133 int maxTouchPoints{0};
134 int matchingIteration{0};
135 bool isTouchDragDetected {false};
136 bool isTouchHeld {false};
137 QScopedPointer<QEvent> bestCandidateForTapTouchEvent;
138 QScopedPointer<QTouchEvent> lastProcessedTouchEvent;
139 bool touchActionTracked {false};
140 int touchHoldDelayMs {400};
141
142
143 // A workaround for some Android systems, which force-cancel all the
144 // actions with 3+ fingers. This workaround just converts the "cancel"
145 // into "end" event, letting Krita handle these events as "tap" events.
147 // NOTE: we cannot reuse maxTouchPoints for this workaround, because
148 // maxTouchPoints is used at the lower stages of the touch processing
149 // pipeline, **after** the touch events left the hold-postponer. And
150 // we need this workaround to cancel the postponer itself.
151 int lastRawTouchPointsCount = 0;
152 };
153 std::optional<IgnoreMultiFingerCancelWorkaround> ignoreMultiFingerCancelWorkaround;
154
155 std::function<KisInputActionGroupsMask()> actionGroupMask;
159
160 int recursiveCounter = 0;
161 int brokenByRecursion = 0;
162
163
166 : q(_q)
167 {
168 q->m_d->recursiveCounter++;
169 q->m_d->brokenByRecursion++;
170 }
171
173 q->m_d->recursiveCounter--;
174 }
175
176 bool isInRecursion() const {
177 return q->m_d->recursiveCounter > 1;
178 }
179
181 };
182
185 : q(_q)
186 {
187 q->m_d->brokenByRecursion = 0;
188 }
189
192
193 bool brokenByRecursion() const {
194 return q->m_d->brokenByRecursion > 0;
195 }
196
198 };
199
200 inline bool actionsSuppressed() const {
201#ifndef Q_OS_ANDROID
202 return suppressAllActions || !cursorEntered;
203#else
204 // when S-pen is not pointing the canvas, actions on canvas are disabled, till it points back to canvas.
205 return suppressAllActions;
206#endif
207 }
208
209 inline bool actionsSuppressedIgnoreFocus() const {
210 return suppressAllActions;
211 }
212
213 inline bool KeyboardActionsSuppressed() const {
214 return suppressAllKeyboardActions;
215 }
216};
217
221
226
228{
229 return m_d->runningShortcut || m_d->touchShortcut || m_d->nativeGestureShortcut || m_d->touchpadScrollShortcut;
230}
231
233{
234 for (const KisTouchShortcut *shortcut : m_d->touchShortcuts) {
235 if (shortcut->isHoldType() && shortcut->isAvailable(m_d->actionGroupMask())) {
236 return true;
237 }
238 }
239 return false;
240}
241
243{
244 m_d->singleActionShortcuts.append(shortcut);
245}
246
248{
249 m_d->strokeShortcuts.append(shortcut);
250}
251
253{
254 m_d->touchShortcuts.append(shortcut);
255}
256
258{
259 m_d->nativeGestureShortcuts.append(shortcut);
260}
261
263{
264 m_d->touchpadScrollShortcuts.append(shortcut);
265}
266
268{
269 return (m_d->runningShortcut && m_d->runningShortcut->action()
270 && m_d->runningShortcut->action()->supportsHiResInputEvents(m_d->runningShortcut->shortcutIndex()))
271 || (m_d->touchShortcut && m_d->touchShortcut->action()
272 && m_d->touchShortcut->action()->supportsHiResInputEvents(m_d->touchShortcut->shortcutIndex()))
273 || (m_d->nativeGestureShortcut && m_d->nativeGestureShortcut->action()
274 && m_d->nativeGestureShortcut->action()->supportsHiResInputEvents(m_d->nativeGestureShortcut->shortcutIndex()))
275 || (m_d->touchpadScrollShortcut && m_d->touchpadScrollShortcut->action()
276 && m_d->touchpadScrollShortcut->action()->supportsHiResInputEvents(m_d->touchpadScrollShortcut->shortcutIndex()));
277}
278
280{
281 Private::RecursionNotifier notifier(this);
282
283 bool retval = false;
284
285 if (m_d->keys.contains(key)) { DEBUG_ACTION("Peculiar, records show key was already pressed"); }
286
287 if (!hasRunningShortcut() && !notifier.isInRecursion()) {
288 retval = tryRunSingleActionShortcutImpl(key, (QEvent*)0, m_d->keys);
289 }
290
291 m_d->keys.insert(key);
292 DEBUG_KEY("Pressed");
293
294 if (notifier.isInRecursion()) {
296 } else if (!hasRunningShortcut()) {
299 }
300
301 return retval;
302}
303
305{
306 Private::RecursionNotifier notifier(this);
307
308
309 bool retval = false;
310
311 if (!m_d->keys.contains(key)) { DEBUG_ACTION("Peculiar, autorepeated key but can't remember it was pressed"); }
312
313 if (m_d->polledKeys.contains(key)) {
314 m_d->polledKeys.remove(key);
315 }
316
317 if (notifier.isInRecursion()) {
319 } else if (!hasRunningShortcut()) {
320 // Autorepeated key should not be included in the shortcut
321 QSet<Qt::Key> filteredKeys = m_d->keys;
322 filteredKeys.remove(key);
323 retval = tryRunSingleActionShortcutImpl(key, (QEvent*)0, filteredKeys);
324 }
325
326 return retval;
327}
328
330{
331 Private::RecursionNotifier notifier(this);
332
333 if (!m_d->keys.contains(key)) { DEBUG_ACTION("Peculiar, key released but can't remember it was pressed"); }
334 else m_d->keys.remove(key);
335
336 m_d->polledKeys.remove(key);
337
338 DEBUG_KEY("Released");
339
340 if (notifier.isInRecursion()) {
342 } else if (!hasRunningShortcut()) {
345 }
346
347 return false;
348}
349
350bool KisShortcutMatcher::buttonPressed(Qt::MouseButton button, QEvent *event)
351{
352 Private::RecursionNotifier notifier(this);
353 DEBUG_BUTTON_ACTION("entered", button);
354
355 // the tablet actions have the priority over any existing
356 // touch action, so we should cancel them first
358
359 bool retval = false;
360
361 if (m_d->buttons.contains(button)) { DEBUG_ACTION("Peculiar, button was already pressed."); }
362
363 if (!hasRunningShortcut() && !notifier.isInRecursion()) {
365 retval = tryRunReadyShortcut(button, event);
366 }
367
368 m_d->buttons.insert(button);
369
370 if (notifier.isInRecursion()) {
372 } else if (!hasRunningShortcut()) {
375 }
376
377 return retval;
378}
379
380bool KisShortcutMatcher::buttonReleased(Qt::MouseButton button, QEvent *event)
381{
382 Private::RecursionNotifier notifier(this);
383 DEBUG_BUTTON_ACTION("entered", button);
384
385 bool retval = false;
386
387 // here we check for the presence of the **stroke** shortcut only
388 if (m_d->runningShortcut) {
389 KIS_SAFE_ASSERT_RECOVER_NOOP(!notifier.isInRecursion());
390
391 retval = tryEndRunningShortcut(button, event);
392 DEBUG_BUTTON_ACTION("ended", button);
393 }
394
395 if (!m_d->buttons.contains(button)) reset("Peculiar, button released but we can't remember it was pressed");
396 else m_d->buttons.remove(button);
397
398 if (notifier.isInRecursion()) {
400 } else if (!hasRunningShortcut()) {
403 }
404
405 return retval;
406}
407
409{
410 Private::RecursionNotifier notifier(this);
411
412
413 if (hasRunningShortcut() || notifier.isInRecursion()) {
414 DEBUG_ACTION("Wheel event canceled.");
415 return false;
416 }
417
418 return tryRunWheelShortcut(wheelAction, event);
419}
420
422{
423 Private::RecursionNotifier notifier(this);
424
425 if (notifier.isInRecursion()) {
426 return false;
427 }
428
429 bool retval = false;
430
431 if (m_d->runningShortcut) {
432 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!m_d->touchShortcut && !m_d->nativeGestureShortcut, false);
433 m_d->runningShortcut->action()->inputEvent(event);
434 retval = true;
435 }
436
437 return retval;
438}
439
441{
442 Private::RecursionNotifier notifier(this);
443
444 m_d->cursorEntered = true;
445
446 if (notifier.isInRecursion()) {
448 } else if (!hasRunningShortcut()) {
451 }
452}
453
455{
456 Private::RecursionNotifier notifier(this);
457
458 m_d->cursorEntered = false;
459
460 if (notifier.isInRecursion()) {
462 } else if (!hasRunningShortcut()) {
465 }
466}
467
468bool KisShortcutMatcher::touchBeginEvent( QTouchEvent* event )
469{
470 DEBUG_TOUCH_ACTION("entered", event)
471
472 Private::RecursionNotifier notifier(this);
473
474 if (hasRunningShortcut()) {
475 DEBUG_ACTION("touch action rejected by a running shortcut");
476
477 // touch has been triggered while some tablet action is in progress,
478 // just consume and ignore it
479 return !notifier.isInRecursion();
480 }
481
482#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
483 QScopedPointer<QEvent> tmp;
484 KoPointerEvent::copyQtPointerEvent(event, tmp);
485 m_d->lastProcessedTouchEvent.reset(static_cast<QTouchEvent*>(tmp.take()));
486#else
487 m_d->lastProcessedTouchEvent.reset(event->clone());
488#endif
489
490 // reset state
492 if (m_d->ignoreMultiFingerCancelWorkaround) {
493 m_d->ignoreMultiFingerCancelWorkaround->lastRawTouchPointsCount = m_d->maxTouchPoints;
494 }
495 m_d->matchingIteration = 1;
496 m_d->isTouchDragDetected = false;
497 m_d->isTouchHeld = false;
498 m_d->touchActionTracked = true;
499#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
500 KoPointerEvent::copyQtPointerEvent(event, m_d->bestCandidateForTapTouchEvent);
501#else
502 m_d->bestCandidateForTapTouchEvent.reset(event->clone());
503#endif
504
505 // TODO: also check if the first point actually qualifies
506 if (hasTouchHoldShortcut()) {
507 m_d->touchHoldEventPostponer.emplace(TOUCH_SLOP, m_d->touchHoldDelayMs);
508 QObject::connect(&m_d->touchHoldEventPostponer.value(),
510 [this]() {
511 slotTouchHoldCompleted();
512 });
513 m_d->touchHoldEventPostponer->pushThrough(event);
514 if (m_d->touchHoldEventPostponer->state() == KisTouchHoldEventsPostponer::HoldCancelled) {
515 m_d->touchHoldEventPostponer.reset();
516 } else {
517 DEBUG_ACTION("thouch-hold timer is set up");
518 }
519 }
520
521 return !notifier.isInRecursion();
522}
523
525{
526 // the touch action has been overridden by some tablet action,
527 // consume and ignore it.
528 if (!m_d->touchActionTracked) return false;
529
530 if (m_d->ignoreMultiFingerCancelWorkaround) {
531 m_d->ignoreMultiFingerCancelWorkaround->lastRawTouchPointsCount =
533 }
534
535 if (m_d->touchHoldEventPostponer) {
536 m_d->touchHoldEventPostponer->pushThrough(event);
537 if (m_d->touchHoldEventPostponer->state() == KisTouchHoldEventsPostponer::HoldCancelled) {
538 // process all the postponed **update** events,
539 // the very first begin event is only used in
540 // the hold-completion operation
541 Q_FOREACH (QTouchEvent *event, m_d->touchHoldEventPostponer->postponedEvents()) {
542 if (event->type() == QEvent::TouchUpdate) {
543 (void) touchUpdateEventImpl(event);
544 }
545 }
546 m_d->touchHoldEventPostponer.reset();
547 } else {
548 // noop, we are waiting for hold
549 }
550 } else {
551 // TODO: don't enter for hold-drag events
552 return touchUpdateEventImpl(event);
553 }
554
555 return true;
556}
557
559{
560 KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->touchHoldEventPostponer);
562
563 // Can happen when multiple shortcut sources interact
564 KIS_SAFE_ASSERT_RECOVER_RETURN(!m_d->runningShortcut);
565
566 // take the very first begin event to start the action
567 QTouchEvent *event = m_d->touchHoldEventPostponer->postponedEvents().front();
568 KIS_SAFE_ASSERT_RECOVER_RETURN(event->type() == QEvent::TouchBegin);
569
570 m_d->isTouchHeld = true; // Must be set first, used in tryRunTouchShortcut.
571
572 DEBUG_TOUCH_ACTION("entered", event);
573
575 // noop, all is fine!
576 } else {
577 KIS_SAFE_ASSERT_RECOVER(0 && "should not happen")
578 {
579 // Shouldn't really happen, since KisInputManager checks whether a touch
580 // hold shortcut exists beforehand. We'll just handle this though.
581 m_d->isTouchHeld = false;
582 }
583 }
584
585 // remove the postponer to continue processing events in-place
586 m_d->touchHoldEventPostponer.reset();
587}
588
590{
591 if (_41020().isDebugEnabled()) {
592 const int oldNumPressedPoints = KisTouchShortcut::countTouchPoints(m_d->lastProcessedTouchEvent.data(), KisTouchShortcut::pressedOnlyTouchStates());
594 if (oldNumPressedPoints != newNumPressedPoints) {
595 DEBUG_TOUCH_ACTION("touch point state changed", event)
596 }
597 }
598
599#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
600 QScopedPointer<QEvent> tmp;
601 KoPointerEvent::copyQtPointerEvent(event, tmp);
602 m_d->lastProcessedTouchEvent.reset(static_cast<QTouchEvent*>(tmp.take()));
603#else
604 m_d->lastProcessedTouchEvent.reset(event->clone());
605#endif
606
607 if (m_d->isTouchHeld) {
608 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(m_d->touchShortcut, false);
609 m_d->touchShortcut->action()->inputEvent(event);
610 return true;
611 }
612
613 bool retval = false;
614
615 // Check whether the touchpoints are relatively stationary or have
616 // been moved for dragging. If the drag is detected, until the next
617 // TouchBegin event, we'll be assuming the gesture to be of
618 // dragging type.
619 if (!m_d->isTouchDragDetected) {
621 m_d->isTouchDragDetected = true;
622 }
623 }
624
625 // Different drag shortcuts can have different drag thresholds. E.g.
626 // touch-painting shortcut will have much lower threshold value. The
627 // shortcuts will calculate the distance themselves in
628 // KisTouchShortcut::matchDragType, so we should just ask them to do that.
629 const int numStillDraggedPoints =
631 if (!hasRunningShortcut() && numStillDraggedPoints >= m_d->maxTouchPoints) {
632 m_d->maxTouchPoints = numStillDraggedPoints;
634 m_d->isTouchDragDetected = true;
635 return true;
636 }
637 }
638
639 // for a first few events we don't process the events right away. But analyze and keep track of the event with most
640 // touchpoints. This is done to prevent conditions where in three-finger-tap, two-finger-tap be preceded due to
641 // latency
642 const int numIterations = 10;
643 if (m_d->matchingIteration <= numIterations && !m_d->isTouchDragDetected) {
644 m_d->matchingIteration++;
646 return matchTouchShortcut((QTouchEvent *)m_d->bestCandidateForTapTouchEvent.data(),
649 }
650
651 if (m_d->isTouchDragDetected) {
652 if (m_d->touchShortcut
653 // TODO: use a different check without threshold! split into match() and matchBegin()
654 && !m_d->touchShortcut->matchDragType(event, KisTouchShortcut::pressedOnlyTouchStates())) {
655 // we should end the event as an event with more touchpoints was received
656 retval = tryEndTouchShortcut(event);
657 } else if (m_d->touchShortcut) {
658 m_d->touchShortcut->action()->inputEvent(event);
659 retval = true;
660 }
661 } else { // !m_d->isTouchDragDetected
662 // triggered if a new finger was added, which might result in shortcut not matching the action
663#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
664 const auto FlagReleased = Qt::TouchPointReleased;
665#else
666 const auto FlagReleased = QEventPoint::Released;
667#endif
668 if (event->touchPointStates().testFlag(FlagReleased) && !hasRunningShortcut()) {
669 const int previousNumPoints = KisTouchShortcut::countTouchPoints(event, KisTouchShortcut::allTouchStates());
670
671 // we should end the event as an event with more touchpoints was received
672 if (previousNumPoints >= m_d->maxTouchPoints) {
673 m_d->maxTouchPoints = previousNumPoints;
675 m_d->bestCandidateForTapTouchEvent.reset();
676 retval = true;
677 }
678 }
679 }
680 }
681 // TODO: update maxTouchPoints
682
683 return retval;
684}
685
686bool KisShortcutMatcher::touchEndEvent(QTouchEvent *event)
687{
688 Private::RecursionNotifier notifier(this);
689
690 DEBUG_TOUCH_ACTION("enter", event);
691
692 // the touch action has been overridden by some tablet action,
693 // consume and ignore it.
694 if (!m_d->touchActionTracked) {
695 DEBUG_ACTION("touch was not tracked");
696 KIS_SAFE_ASSERT_RECOVER(!m_d->touchHoldEventPostponer.has_value()) {
697 m_d->touchHoldEventPostponer->cancelHoldWait();
698 m_d->touchHoldEventPostponer.reset();
699 }
700 return true;
701 }
702
703 // flush all the touch-hold postiponed events if they were present
704 if (m_d->touchHoldEventPostponer) {
705 DEBUG_ACTION("flushing all postponed hold events");
706
708 m_d->touchHoldEventPostponer->cancelHoldWait();
709 // process all the postponed **update** events,
710 // the very first begin event is only used in
711 // the hold-completion operation
712 Q_FOREACH (QTouchEvent *event, m_d->touchHoldEventPostponer->postponedEvents()) {
713 if (event->type() == QEvent::TouchUpdate) {
714 (void) touchUpdateEventImpl(event);
715 }
716 }
717 m_d->touchHoldEventPostponer.reset();
718 }
719
720 m_d->touchActionTracked = false;
721
722 bool retval = false;
723
724 if (!m_d->isTouchDragDetected && m_d->bestCandidateForTapTouchEvent && !hasRunningShortcut()) {
725 retval = tryFireTapTouchShortcut(static_cast<QTouchEvent *>(m_d->bestCandidateForTapTouchEvent.data()),
727 }
728
729 // we should try and end the shortcut too (it might be that there is none? (sketch))
730 retval |= tryEndTouchShortcut(event);
731
732 if (notifier.isInRecursion()) {
734 } else if (!hasRunningShortcut()) {
737 }
738
739 return retval;
740}
741
743{
744 // discard all the postponed touch-hold events
745 if (m_d->touchHoldEventPostponer) {
747 m_d->touchHoldEventPostponer->cancelHoldWait();
748 m_d->touchHoldEventPostponer.reset();
749 }
750
751 // end the touch action if present
752 if (m_d->touchShortcut) {
753 DEBUG_ACTION("force-cancel touch action")
754
755 KisTouchShortcut *touchShortcut = m_d->touchShortcut;
756 m_d->touchShortcut = 0;
757
758 KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->lastProcessedTouchEvent);
759
760 // on some platforms touch-cancel event may have **no**
761 // touch points, which would greatly confuse our KoPointerEvent
762 // class, so we should just generate a normal touch-end from the
763 // last known event
764 QTouchEvent touchEvent = generateFakeTouchEndEvent(m_d->lastProcessedTouchEvent.data());
765 touchShortcut->action()->end(&touchEvent);
767 }
768
769 m_d->touchActionTracked = false;
770}
771
773{
774 Q_UNUSED(event)
775
776 // The design requirement of touch-cancel handling is the following:
777 //
778 // 1) If drag-action has already been started, it is finished normally
779 // 2) If tap-action is pending, it is cancelled
780 // 3) If hold-action is pending, it is cancelled
781 // 4) If hold-action has already been started, it is ended normally
782
783 // On some Android devices, such as Xiaomi Pads, the system always eats
784 // multitouch inputs with more than two fingers, even if the user
785 // disables all gestures related to them in their system settings or
786 // uses the game boost mode that is supposed to disable gestures. So we
787 // handle those inputs even when they are cancelled, if the user wants
788 // to use it for a system gesture, they can disable the Krita shortcut.
789 if (m_d->ignoreMultiFingerCancelWorkaround) {
790 // NOTE: we cannot use the number of touch points from the actual
791 // cancel event, since Qt sets touch points as empty for the cancel
792 // event
793 if (m_d->ignoreMultiFingerCancelWorkaround->lastRawTouchPointsCount >= 3) {
794 DEBUG_ACTION("IgnoreMultiFingerCancelWorkaround: converting a cancelled action into a normally finished action")
795 touchEndEvent(event);
796 return;
797 }
798 }
799
800 Private::RecursionNotifier notifier(this);
801
802 DEBUG_TOUCH_ACTION("enter", event);
803
804 // the touch action has been overridden by some tablet action,
805 // consume and ignore it.
806 if (!m_d->touchActionTracked) {
807 KIS_SAFE_ASSERT_RECOVER(!m_d->touchHoldEventPostponer.has_value()) {
808 m_d->touchHoldEventPostponer->cancelHoldWait();
809 m_d->touchHoldEventPostponer.reset();
810 }
811 return;
812 }
813
814 KIS_SAFE_ASSERT_RECOVER_NOOP(!m_d->runningShortcut || !m_d->touchShortcut);
815
817
818 if (notifier.isInRecursion()) {
820 } else if (!hasRunningShortcut()) {
823 }
824}
825
826bool KisShortcutMatcher::nativeGestureBeginEvent(QNativeGestureEvent *event)
827{
828 Q_UNUSED(event);
829
830 Private::RecursionNotifier notifier(this);
831
832 DEBUG_EVENT_ACTION("entered", event);
833
834 return !notifier.isInRecursion();
835}
836
837bool KisShortcutMatcher::nativeGestureEvent(QNativeGestureEvent *event)
838{
839 bool retval = false;
840
841 DEBUG_EVENT_ACTION("entered", event);
842
843 if (!hasRunningShortcut()) {
844 retval = tryRunNativeGestureShortcut( event );
845 }
846 else if (m_d->nativeGestureShortcut) {
847 m_d->nativeGestureShortcut->action()->inputEvent( event );
848 retval = true;
849 }
850
851 return retval;
852}
853
854bool KisShortcutMatcher::nativeGestureEndEvent(QNativeGestureEvent *event)
855{
856 Private::RecursionNotifier notifier(this);
857
858 DEBUG_EVENT_ACTION("entered", event);
859
860 if (m_d->nativeGestureShortcut) {
862 }
863
864 if (notifier.isInRecursion()) {
866 } else if (!hasRunningShortcut()) {
869 }
870
871 return true;
872}
873
875{
876 return !m_d->touchpadScrollShortcuts.isEmpty();
877}
878
880{
881 Private::RecursionNotifier notifier(this);
882
883 if (notifier.isInRecursion()) return false;
884
885 DEBUG_EVENT_ACTION("entered", event);
886
887 if (!hasRunningShortcut()) {
889 }
890
891 if (m_d->touchpadScrollShortcut) {
892 m_d->touchpadScrollShortcut->action()->inputEvent(event);
893 }
894
895 return bool(m_d->touchpadScrollShortcut);
896}
897
899{
900 Private::RecursionNotifier notifier(this);
901
902 if (notifier.isInRecursion()) return false;
903
904 DEBUG_EVENT_ACTION("entered", event);
905
906 if (m_d->touchpadScrollShortcut) {
907 m_d->touchpadScrollShortcut->action()->inputEvent(event);
908 }
909
910 return bool(m_d->touchpadScrollShortcut);
911}
912
914{
915 Private::RecursionNotifier notifier(this);
916
917 if (notifier.isInRecursion()) return false;
918
919 DEBUG_EVENT_ACTION("entered", event);
920
921 bool retval = false;
922
923 if (m_d->touchpadScrollShortcut) {
924 retval = tryEndTouchpadScrollShortcut(event);
925 }
926
927 if (notifier.isInRecursion()) {
929 } else if (!hasRunningShortcut()) {
932 }
933
934 return retval;
935}
936
937Qt::MouseButtons listToFlags(const QList<Qt::MouseButton> &list) {
938 Qt::MouseButtons flags;
939 Q_FOREACH (Qt::MouseButton b, list) {
940 flags |= b;
941 }
942 return flags;
943}
944
946{
947 Private::RecursionNotifier notifier(this);
948
949
950 reset("reinitialize");
951
952 if (notifier.isInRecursion()) {
954 } else if (!hasRunningShortcut()) {
957 }
958}
959
961{
962 Private::RecursionNotifier notifier(this);
963
964 m_d->buttons.clear();
965 DEBUG_ACTION("reinitializing buttons");
966
967 if (notifier.isInRecursion()) {
969 } else if (!hasRunningShortcut()) {
972 }
973}
974
976{
977 Q_FOREACH (Qt::Key key, m_d->keys) {
978 if (!keys.contains(key)) {
979 keyReleased(key);
980 }
981 }
982
983 Q_FOREACH (Qt::Key key, keys) {
984 if (!m_d->keys.contains(key)) {
985 keyPressed(key);
986 m_d->polledKeys << key;
987 }
988 }
989
990 Private::RecursionNotifier notifier(this);
991
992 if (notifier.isInRecursion()) {
994 } else if (!hasRunningShortcut()) {
997 }
998
999 DEBUG_ACTION("recoverySyncModifiers");
1000}
1001
1002bool KisShortcutMatcher::sanityCheckModifiersCorrectness(Qt::KeyboardModifiers modifiers) const
1003{
1004 auto checkKey = [this, modifiers] (Qt::Key key, Qt::KeyboardModifier modifier) {
1005 return m_d->keys.contains(key) == bool(modifiers & modifier);
1006 };
1007
1008 return checkKey(Qt::Key_Shift, Qt::ShiftModifier) &&
1009 checkKey(Qt::Key_Control, Qt::ControlModifier) &&
1010 checkKey(Qt::Key_Alt, Qt::AltModifier) &&
1011 checkKey(Qt::Key_Meta, Qt::MetaModifier);
1012
1013}
1014
1016{
1018 std::copy(m_d->keys.begin(), m_d->keys.end(), std::back_inserter(keys));
1019 return keys;
1020}
1021
1023{
1024 return !m_d->polledKeys.empty();
1025}
1026
1027void KisShortcutMatcher::lostFocusEvent(const QPointF &localPos)
1028{
1029 Private::RecursionNotifier notifier(this);
1030
1031 DEBUG_ACTION("lostFocusEvent");
1032
1033 if (m_d->runningShortcut) {
1034 forceEndRunningShortcut(localPos);
1035 }
1036
1038
1046}
1047
1049{
1050 Private::RecursionNotifier notifier(this);
1051
1052 DEBUG_ACTION("toolHasBeenActivated");
1053
1054 if (notifier.isInRecursion()) {
1056 } else if (!hasRunningShortcut()) {
1059 }
1060}
1061
1063{
1064 m_d->keys.clear();
1065 m_d->buttons.clear();
1066 DEBUG_ACTION("reset!");
1067}
1068
1069
1071{
1072 m_d->keys.clear();
1073 m_d->buttons.clear();
1074 Q_UNUSED(msg);
1075 DEBUG_ACTION(msg);
1076}
1077
1079{
1080 m_d->suppressAllActions = value;
1081}
1082
1084{
1085 m_d->suppressedSingleActionShortcuts.clear();
1086
1087 Q_FOREACH (KisSingleActionShortcut *s, m_d->singleActionShortcuts) {
1088 Q_FOREACH (const QKeySequence &seq, shortcuts) {
1089 if (s->conflictsWith(seq)) {
1090 m_d->suppressedSingleActionShortcuts.insert(s);
1091 }
1092 }
1093 }
1094}
1095
1097{
1098 m_d->suppressAllKeyboardActions = value;
1099}
1100
1102{
1103 reset("Clearing shortcuts");
1104
1105 qDeleteAll(m_d->singleActionShortcuts);
1106 m_d->singleActionShortcuts.clear();
1107 m_d->suppressedSingleActionShortcuts.clear();
1108
1109 qDeleteAll(m_d->strokeShortcuts);
1110 m_d->strokeShortcuts.clear();
1111
1112 qDeleteAll(m_d->touchShortcuts);
1113 m_d->touchShortcuts.clear();
1114
1115 qDeleteAll(m_d->nativeGestureShortcuts);
1116 m_d->nativeGestureShortcuts.clear();
1117
1118 qDeleteAll(m_d->touchpadScrollShortcuts);
1119 m_d->touchpadScrollShortcuts.clear();
1120
1121 m_d->candidateShortcuts.clear();
1122 m_d->runningShortcut = 0;
1123 m_d->readyShortcut = 0;
1124 m_d->touchShortcut = 0;
1125 m_d->nativeGestureShortcut = 0;
1126 m_d->touchpadScrollShortcut = 0;
1127}
1128
1129void KisShortcutMatcher::setInputActionGroupsMaskCallback(std::function<KisInputActionGroupsMask ()> func)
1130{
1131 m_d->actionGroupMask = func;
1132}
1133
1135{
1136 return tryRunSingleActionShortcutImpl(wheelAction, event, m_d->keys, false);
1137}
1138
1139// Note: sometimes event can be zero!!
1140template<typename T, typename U>
1141bool KisShortcutMatcher::tryRunSingleActionShortcutImpl(T param, U *event, const QSet<Qt::Key> &keysState, bool keyboard)
1142{
1143 if (m_d->actionsSuppressedIgnoreFocus() || (keyboard && m_d->KeyboardActionsSuppressed())) {
1144 DEBUG_EVENT_ACTION("Event suppressed", event)
1145 return false;
1146 }
1147
1148 KisSingleActionShortcut *goodCandidate = 0;
1149
1150 Q_FOREACH (KisSingleActionShortcut *s, m_d->singleActionShortcuts) {
1151 if (!m_d->suppressedSingleActionShortcuts.contains(s) &&
1152 s->isAvailable(m_d->actionGroupMask()) &&
1153 s->match(keysState, param) &&
1154 (!goodCandidate || s->priority() > goodCandidate->priority())) {
1155
1156 goodCandidate = s;
1157 }
1158 }
1159
1160 if (goodCandidate) {
1161 DEBUG_EVENT_ACTION("Beginning action for event", event);
1162 goodCandidate->action()->begin(goodCandidate->shortcutIndex(), event);
1163 goodCandidate->action()->end(0);
1164 } else {
1165 DEBUG_EVENT_ACTION("Could not match a candidate for event", event)
1166 }
1167
1168 return goodCandidate;
1169}
1170
1172{
1173 m_d->candidateShortcuts.clear();
1174 if (m_d->actionsSuppressed()) return;
1175
1176 // Allow letting the modifiers to be matched so key_shift + middle mouse move can be matched, but key_v + mouse drag can not.
1177 bool containsOnlyModifiers = !m_d->keys.isEmpty();
1178 Q_FOREACH(const Qt::Key k, m_d->keys) {
1179 if (k != Qt::Key_Shift && k != Qt::Key_Control && k != Qt::Key_Alt && k != Qt::Key_Meta) {
1180 containsOnlyModifiers = false;
1181 break;
1182 }
1183 }
1184 if (m_d->KeyboardActionsSuppressed()
1185 && !containsOnlyModifiers && !m_d->keys.isEmpty()
1186 && m_d->buttons.isEmpty()) {
1187 return;
1188 }
1189
1190 Q_FOREACH (KisStrokeShortcut *s, m_d->strokeShortcuts) {
1191 if (s->matchReady(m_d->keys, m_d->buttons)) {
1192 m_d->candidateShortcuts.append(s);
1193 }
1194 }
1195}
1196
1197bool KisShortcutMatcher::tryRunReadyShortcut( Qt::MouseButton button, QEvent* event )
1198{
1199 KisStrokeShortcut *goodCandidate = 0;
1200
1201 Q_FOREACH (KisStrokeShortcut *s, m_d->candidateShortcuts) {
1202 if (s->isAvailable(m_d->actionGroupMask()) &&
1203 s->matchBegin(button) &&
1204 (!goodCandidate || s->priority() > goodCandidate->priority())) {
1205
1206 goodCandidate = s;
1207 }
1208 }
1209
1210 if (goodCandidate) {
1211 if (m_d->readyShortcut) {
1212 if (m_d->readyShortcut != goodCandidate) {
1213 if (goodCandidate->action() == m_d->readyShortcut->action() &&
1214 goodCandidate->action()->trySwitchShortcut(
1215 m_d->readyShortcut->shortcutIndex(), goodCandidate->shortcutIndex())) {
1216 m_d->readyShortcut = goodCandidate;
1217 } else {
1218 m_d->readyShortcut->action()->deactivate(m_d->readyShortcut->shortcutIndex());
1219 goodCandidate->action()->activate(goodCandidate->shortcutIndex());
1220 }
1221 }
1222 m_d->readyShortcut = 0;
1223 } else {
1224 DEBUG_EVENT_ACTION("Matched *new* shortcut for event", event);
1225 goodCandidate->action()->activate(goodCandidate->shortcutIndex());
1226 }
1227
1228 DEBUG_SHORTCUT("Starting new action", goodCandidate);
1229
1230 {
1231 m_d->runningShortcut = goodCandidate;
1232 Private::RecursionGuard guard(this);
1233 goodCandidate->action()->begin(goodCandidate->shortcutIndex(), event);
1234
1235 // the tool might have opened some dialog, which could break our event loop
1236 if (guard.brokenByRecursion()) {
1237 goodCandidate->action()->end(event);
1238 m_d->runningShortcut = 0;
1239
1241 }
1242 }
1243 }
1244
1245 return m_d->runningShortcut;
1246}
1247
1249{
1250 KisStrokeShortcut *goodCandidate = 0;
1251
1252 Q_FOREACH (KisStrokeShortcut *s, m_d->candidateShortcuts) {
1253 if (!goodCandidate || s->priority() > goodCandidate->priority()) {
1254 goodCandidate = s;
1255 }
1256 }
1257
1258 if (goodCandidate) {
1259 if (m_d->readyShortcut && m_d->readyShortcut != goodCandidate) {
1260 if (goodCandidate->action() == m_d->readyShortcut->action() &&
1261 goodCandidate->action()->trySwitchShortcut(
1262 m_d->readyShortcut->shortcutIndex(), goodCandidate->shortcutIndex())) {
1263 DEBUG_SHORTCUT("Switched previous shortcut action", m_d->readyShortcut);
1264 m_d->readyShortcut = goodCandidate;
1265 } else {
1266 DEBUG_SHORTCUT("Deactivated previous shortcut action", m_d->readyShortcut);
1267 m_d->readyShortcut->action()->deactivate(m_d->readyShortcut->shortcutIndex());
1268 m_d->readyShortcut = 0;
1269 }
1270 }
1271
1272 if (!m_d->readyShortcut) {
1273 DEBUG_SHORTCUT("Preparing new ready action", goodCandidate);
1274
1282 goodCandidate->action()->activate(goodCandidate->shortcutIndex());
1283 m_d->readyShortcut = goodCandidate;
1284 }
1285 } else if (m_d->readyShortcut) {
1286 DEBUG_SHORTCUT("Deactivating action", m_d->readyShortcut);
1287 m_d->readyShortcut->action()->deactivate(m_d->readyShortcut->shortcutIndex());
1288 m_d->readyShortcut = 0;
1289 }
1290}
1291
1292bool KisShortcutMatcher::tryEndRunningShortcut( Qt::MouseButton button, QEvent* event )
1293{
1294 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(m_d->runningShortcut, true);
1295 KIS_SAFE_ASSERT_RECOVER(!m_d->readyShortcut) {
1296 // it shouldn't have happened, running and ready shortcuts
1297 // at the same time should not be possible
1299 }
1300
1301 if (m_d->runningShortcut && m_d->runningShortcut->matchBegin(button)) {
1302
1303 // first reset running shortcut to avoid infinite recursion via end()
1304 KisStrokeShortcut *runningShortcut = m_d->runningShortcut;
1305 m_d->runningShortcut = 0;
1306
1307 if (runningShortcut->action()) {
1308 DEBUG_EVENT_ACTION("Ending running shortcut at event", event);
1310 int shortcutIndex = runningShortcut->shortcutIndex();
1311
1312 // Keep keyed stroke shortcuts in ready state between repeated
1313 // button strokes while their activation keys are still held.
1314 QSet<Qt::MouseButton> buttonsAfterRelease = m_d->buttons;
1315 buttonsAfterRelease.remove(button);
1316 const bool keepReadyShortcut =
1317 !m_d->keys.isEmpty() &&
1318 runningShortcut->isAvailable(m_d->actionGroupMask()) &&
1319 runningShortcut->matchReady(m_d->keys, buttonsAfterRelease);
1320
1321 action->end(event);
1322
1323 if (keepReadyShortcut) {
1324 m_d->readyShortcut = runningShortcut;
1325 } else {
1326 action->deactivate(shortcutIndex);
1327 }
1328 }
1329 }
1330
1331 return !m_d->runningShortcut;
1332}
1333
1335{
1336 KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->runningShortcut);
1337 KIS_SAFE_ASSERT_RECOVER(!m_d->readyShortcut) {
1338 // it shouldn't have happened, running and ready shortcuts
1339 // at the same time should not be possible
1341 }
1342
1343 // first reset running shortcut to avoid infinite recursion via end()
1344 KisStrokeShortcut *runningShortcut = m_d->runningShortcut;
1345 m_d->runningShortcut = 0;
1346
1347 if (runningShortcut->action()) {
1348 DEBUG_ACTION("Forced ending running shortcut at event");
1350 int shortcutIndex = runningShortcut->shortcutIndex();
1351
1352 QMouseEvent event = runningShortcut->fakeEndEvent(localPos);
1353
1354 action->end(&event);
1355 action->deactivate(shortcutIndex);
1356 }
1357}
1358
1360{
1361 if (m_d->readyShortcut) {
1362 DEBUG_SHORTCUT("Forcefully deactivating action", m_d->readyShortcut);
1363 m_d->readyShortcut->action()->deactivate(m_d->readyShortcut->shortcutIndex());
1364 m_d->readyShortcut = 0;
1365 }
1366}
1367
1369{
1370 int previousNumTouchPoints = -1;
1371
1372 if (m_d->bestCandidateForTapTouchEvent) {
1373 QTouchEvent *bestTouchEvent = static_cast<QTouchEvent*>(m_d->bestCandidateForTapTouchEvent.data());
1374 previousNumTouchPoints = KisTouchShortcut::countTouchPoints(bestTouchEvent, KisTouchShortcut::pressedOnlyTouchStates());
1375 }
1376
1378
1379 if (newNumTouchPoints >= previousNumTouchPoints) {
1380#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
1381 KoPointerEvent::copyQtPointerEvent(event, m_d->bestCandidateForTapTouchEvent);
1382#else
1383 m_d->bestCandidateForTapTouchEvent.reset(event->clone());
1384#endif
1385 }
1386}
1387
1388bool KisShortcutMatcher::tryFireTapTouchShortcut(QTouchEvent *event, Qt::TouchPointStates allowedStates)
1389{
1395 if (m_d->actionsSuppressedIgnoreFocus())
1396 return false;
1397
1398 KisTouchShortcut *goodCandidate = matchTouchShortcut(event, allowedStates, TouchShortcutMode::Tap);
1399 if (goodCandidate) {
1400 DEBUG_SHORTCUT("Starting new touch-tap action", goodCandidate);
1401 goodCandidate->action()->activate(goodCandidate->shortcutIndex());
1402 goodCandidate->action()->begin(goodCandidate->shortcutIndex(), event);
1403
1404 goodCandidate->action()->end(event);
1405 goodCandidate->action()->deactivate(goodCandidate->shortcutIndex());
1406 }
1407
1408 return goodCandidate;
1409}
1410
1411KisTouchShortcut *KisShortcutMatcher::matchTouchShortcut(QTouchEvent *event, Qt::TouchPointStates allowedStates, TouchShortcutMode mode)
1412{
1413 KisTouchShortcut *goodCandidate = nullptr;
1414 Q_FOREACH (KisTouchShortcut *shortcut, m_d->touchShortcuts) {
1415 if (shortcut->isAvailable(m_d->actionGroupMask())
1416 && matchTouchShortcutBasedOnState(event, shortcut, allowedStates, mode)
1417 && (!goodCandidate || shortcut->priority() > goodCandidate->priority())) {
1418
1419 goodCandidate = shortcut;
1420 }
1421 }
1422 return goodCandidate;
1423}
1424
1425bool KisShortcutMatcher::matchTouchShortcutBasedOnState(QTouchEvent *event, KisTouchShortcut *shortcut, Qt::TouchPointStates allowedStates, TouchShortcutMode mode)
1426{
1427 switch (mode) {
1429 return shortcut->matchHoldType(event, allowedStates);
1430 break;
1432 return shortcut->matchDragType(event, allowedStates);
1433 break;
1435 return shortcut->matchTapType(event, allowedStates);
1436 break;
1437 }
1438
1439#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
1440 return false;
1441#else
1442 Q_UNREACHABLE_RETURN(false);
1443#endif
1444}
1445
1446bool KisShortcutMatcher::tryRunTouchShortcut(QTouchEvent* event, Qt::TouchPointStates allowedStates, TouchShortcutMode mode)
1447{
1448 KisTouchShortcut *goodCandidate = matchTouchShortcut(event, allowedStates, mode);
1449
1455 if (m_d->actionsSuppressedIgnoreFocus())
1456 return false;
1457
1458 if (goodCandidate) {
1459 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!m_d->runningShortcut, false);
1460
1461 // Because we don't match keyboard or button based actions with touch system, we have to ensure that we first
1462 // deactivate an activated readyShortcut, to not throw other statemachines out of place.
1464
1465 m_d->touchShortcut = goodCandidate;
1466
1467 Private::RecursionGuard guard(this);
1468 DEBUG_SHORTCUT("Running a new touch action", goodCandidate)
1469
1470 goodCandidate->action()->activate(goodCandidate->shortcutIndex());
1471 goodCandidate->action()->begin(goodCandidate->shortcutIndex(), event);
1472
1473 // the tool might have opened some dialog, which could break our event loop
1474 if (guard.brokenByRecursion()) {
1475 goodCandidate->action()->end(event);
1476 m_d->touchShortcut = 0;
1477
1479 }
1480 }
1481
1482 return m_d->touchShortcut;
1483}
1484
1486{
1487 if (m_d->touchShortcut) {
1488 // first reset running shortcut to avoid infinite recursion via end()
1489 KisTouchShortcut *touchShortcut = m_d->touchShortcut;
1490
1491 DEBUG_SHORTCUT("ending", touchShortcut)
1492 touchShortcut->action()->end(event);
1493 touchShortcut->action()->deactivate(m_d->touchShortcut->shortcutIndex());
1494
1495 m_d->touchShortcut = 0; // empty it out now that we are done with it
1496
1497 return true;
1498 }
1499
1500 return false;
1501}
1502
1503template <typename EventType, typename ShortcutType>
1504bool KisShortcutMatcher::tryRunGestureBasedShortcutImpl(EventType *event, ShortcutType **resultShortcutPtr, const QList<ShortcutType*> allTypedShortcuts)
1505{
1506 ShortcutType *goodCandidate = 0;
1507
1508 if (m_d->actionsSuppressed())
1509 return false;
1510
1511 Q_FOREACH (ShortcutType* shortcut, allTypedShortcuts) {
1512 if (shortcut->match(event) && (!goodCandidate || shortcut->priority() > goodCandidate->priority())) {
1513 goodCandidate = shortcut;
1514 }
1515 }
1516
1517 if (goodCandidate) {
1518 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!m_d->runningShortcut, false);
1519
1520 // Because we don't match keyboard or button based actions with touch system, we have to ensure that we first
1521 // deactivate an activated readyShortcut, to not throw other statemachines out of place.
1523
1524 *resultShortcutPtr = goodCandidate;
1525
1526 Private::RecursionGuard guard(this);
1527
1528 DEBUG_SHORTCUT("starting", goodCandidate)
1529
1530 goodCandidate->action()->activate(goodCandidate->shortcutIndex());
1531 goodCandidate->action()->begin(goodCandidate->shortcutIndex(), event);
1532
1533 // the tool might have opened some dialog, which could break our event loop
1534 if (guard.brokenByRecursion()) {
1535 goodCandidate->action()->end(event);
1536 *resultShortcutPtr = 0;
1537
1539 }
1540 }
1541
1542 return bool(*resultShortcutPtr);
1543}
1544
1545template <typename EventType, typename ShortcutType>
1546bool KisShortcutMatcher::tryEndGestureBasedShortcutImpl(EventType *event, ShortcutType **resultShortcutPtr)
1547{
1548 Private::RecursionNotifier notifier(this);
1549
1550 if (*resultShortcutPtr) {
1551 // first reset running shortcut to avoid infinite recursion via end()
1552 ShortcutType *runningShortcut = *resultShortcutPtr;
1553 *resultShortcutPtr = nullptr;
1554
1556
1557 runningShortcut->action()->end(event);
1559
1560 return true;
1561 }
1562
1563 if (notifier.isInRecursion()) {
1565 } else if (!hasRunningShortcut()) {
1568 }
1569
1570 return false;
1571}
1572
1574{
1575 return tryRunGestureBasedShortcutImpl(event, &m_d->nativeGestureShortcut, m_d->nativeGestureShortcuts);
1576}
1577
1579{
1580 return tryEndGestureBasedShortcutImpl(event, &m_d->nativeGestureShortcut);
1581}
1582
1584{
1585 return tryRunGestureBasedShortcutImpl(event, &m_d->touchpadScrollShortcut, m_d->touchpadScrollShortcuts);
1586}
1587
1589{
1590 return tryEndGestureBasedShortcutImpl(event, &m_d->touchpadScrollShortcut);
1591}
1592
1594{
1595 return m_d->touchHoldDelayMs;
1596}
1597
1599{
1600 m_d->touchHoldDelayMs = value;
1601}
1602
1604{
1605 if (value) {
1606 m_d->ignoreMultiFingerCancelWorkaround = Private::IgnoreMultiFingerCancelWorkaround();
1607 } else {
1608 m_d->ignoreMultiFingerCancelWorkaround = std::nullopt;
1609 }
1610}
1611
1613{
1614 return m_d->ignoreMultiFingerCancelWorkaround.has_value();
1615}
float value(const T *src, size_t ch)
@ AllActionGroup
Abstract base class for input actions.
virtual void activate(int shortcut)
virtual bool trySwitchShortcut(int oldShortcut, int newShortcut)
virtual void deactivate(int shortcut)
virtual void end(QEvent *event)
virtual void begin(int shortcut, QEvent *event)
KisAbstractInputAction * action
virtual bool isAvailable(KisInputActionGroupsMask mask) const
bool matchTouchShortcutBasedOnState(QTouchEvent *event, KisTouchShortcut *shortcut, Qt::TouchPointStates allowedStates, TouchShortcutMode mode)
bool KeyboardActionsSuppressed() const
void lostFocusEvent(const QPointF &localPos)
bool tryEndTouchpadScrollShortcut(QWheelEvent *event)
QSet< Qt::MouseButton > buttons
QList< KisTouchpadScrollShortcut * > touchpadScrollShortcuts
KisTouchpadScrollShortcut * touchpadScrollShortcut
void forceEndRunningShortcut(const QPointF &localPos)
bool actionsSuppressedIgnoreFocus() const
QList< KisNativeGestureShortcut * > nativeGestureShortcuts
bool tryRunTouchpadScrollShortcut(QWheelEvent *event)
bool autoRepeatedKeyPressed(Qt::Key key)
bool buttonReleased(Qt::MouseButton button, QEvent *event)
QList< KisStrokeShortcut * > strokeShortcuts
bool touchpadScrollEndEvent(QWheelEvent *event)
bool hasTouchpadScrollShortcuts() const
std::optional< KisTouchHoldEventsPostponer > touchHoldEventPostponer
bool tryFireTapTouchShortcut(QTouchEvent *event, Qt::TouchPointStates allowedStates)
bool tryEndGestureBasedShortcutImpl(EventType *event, ShortcutType **resultShortcutPtr)
void addShortcut(KisSingleActionShortcut *shortcut)
bool touchUpdateEventImpl(QTouchEvent *event)
void setTouchHoldDelay(int value)
QList< KisSingleActionShortcut * > singleActionShortcuts
bool tryRunNativeGestureShortcut(QNativeGestureEvent *event)
QList< KisStrokeShortcut * > candidateShortcuts
bool tryRunSingleActionShortcutImpl(T param, U *event, const QSet< Qt::Key > &keysState, bool keyboard=true)
bool nativeGestureEndEvent(QNativeGestureEvent *event)
bool tryEndTouchShortcut(QTouchEvent *event)
bool keyPressed(Qt::Key key)
bool tryRunTouchShortcut(QTouchEvent *event, Qt::TouchPointStates allowedStates, TouchShortcutMode mode)
bool touchBeginEvent(QTouchEvent *event)
bool tryRunReadyShortcut(Qt::MouseButton button, QEvent *event)
bool touchpadScrollBeginEvent(QWheelEvent *event)
bool touchpadScrollEvent(QWheelEvent *event)
KisTouchShortcut * touchShortcut
bool tryRunGestureBasedShortcutImpl(EventType *event, ShortcutType **resultShortcutPtr, const QList< ShortcutType * > allTypedShortcuts)
void setInputActionGroupsMaskCallback(std::function< KisInputActionGroupsMask()> func)
void setIgnoreMultiFingerCancelWorkaroundEnalbed(bool value)
QScopedPointer< QEvent > bestCandidateForTapTouchEvent
static constexpr qreal TOUCH_SLOP
KisStrokeShortcut * readyShortcut
void touchCancelEvent(QTouchEvent *event)
QVector< Qt::Key > debugPressedKeys() const
std::optional< IgnoreMultiFingerCancelWorkaround > ignoreMultiFingerCancelWorkaround
bool touchEndEvent(QTouchEvent *event)
bool touchUpdateEvent(QTouchEvent *event)
KisTouchShortcut * matchTouchShortcut(QTouchEvent *event, Qt::TouchPointStates allowedStates, TouchShortcutMode mode)
std::function< KisInputActionGroupsMask()> actionGroupMask
bool nativeGestureEvent(QNativeGestureEvent *event)
KisNativeGestureShortcut * nativeGestureShortcut
bool buttonPressed(Qt::MouseButton button, QEvent *event)
QList< KisTouchShortcut * > touchShortcuts
void handlePolledKeys(const QVector< Qt::Key > &keys)
bool tryEndRunningShortcut(Qt::MouseButton button, QEvent *event)
bool tryRunWheelShortcut(KisSingleActionShortcut::WheelAction wheelAction, QWheelEvent *event)
bool sanityCheckModifiersCorrectness(Qt::KeyboardModifiers modifiers) const
bool pointerMoved(QEvent *event)
bool nativeGestureBeginEvent(QNativeGestureEvent *event)
void setMaxTouchPointEvent(QTouchEvent *event)
void suppressConflictingKeyActions(const QVector< QKeySequence > &shortcuts)
KisStrokeShortcut * runningShortcut
bool keyReleased(Qt::Key key)
bool ignoreMultiFingerCancelWorkaroundEnalbed() const
QSet< KisSingleActionShortcut * > suppressedSingleActionShortcuts
bool wheelEvent(KisSingleActionShortcut::WheelAction wheelAction, QWheelEvent *event)
bool tryEndNativeGestureShortcut(QNativeGestureEvent *event)
QScopedPointer< QTouchEvent > lastProcessedTouchEvent
bool conflictsWith(const QKeySequence &seq)
bool match(const QSet< Qt::Key > &modifiers, Qt::Key key)
int priority() const override
QMouseEvent fakeEndEvent(const QPointF &localPos) const
bool matchReady(const QSet< Qt::Key > &modifiers, const QSet< Qt::MouseButton > &buttons)
bool matchBegin(Qt::MouseButton button)
@ HoldCompleted
the postponer is still accumulating the events
@ HoldCancelled
the hold action has been detected, the accumulated events should be dropped and the hold action shoul...
The KisTouchShortcut class only handles touch gestures it does not handle tool invocation i....
bool isAvailable(KisInputActionGroupsMask mask) const override
static Qt::TouchPointStates pressedOnlyTouchStates()
bool matchTapType(const QTouchEvent *event, Qt::TouchPointStates allowedStates)
bool matchDragType(const QTouchEvent *event, Qt::TouchPointStates allowedStates)
static qreal touchDragDistance(const QTouchEvent *event, Qt::TouchPointStates allowedStates)
bool matchHoldType(const QTouchEvent *event, Qt::TouchPointStates allowedStates)
static int countTouchPoints(const QTouchEvent *event, Qt::TouchPointStates allowedStates)
static Qt::TouchPointStates allTouchStates()
int priority() const override
#define KIS_SAFE_ASSERT_RECOVER(cond)
Definition kis_assert.h:126
#define KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(cond, val)
Definition kis_assert.h:129
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:128
#define KIS_SAFE_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:130
const KRITAGLOBAL_EXPORT QLoggingCategory & _41020()
typedef void(QOPENGLF_APIENTRYP PFNGLINVALIDATEBUFFERDATAPROC)(GLuint buffer)
#define DEBUG_BUTTON_ACTION(text, button)
#define DEBUG_SHORTCUT(text, shortcut)
Qt::MouseButtons listToFlags(const QList< Qt::MouseButton > &list)
#define DEBUG_EVENT_ACTION(text, event)
QString debugShortcutName(const KisAbstractShortcut *shortcut)
#define DEBUG_KEY(text)
#define DEBUG_TOUCH_ACTION(text, event)
QString button(const QWheelEvent &ev)
#define DEBUG_ACTION(action)