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 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
8
9#include <QEvent>
10#include <QMouseEvent>
11#include <QTabletEvent>
12
13#include "kis_assert.h"
15#include "kis_stroke_shortcut.h"
16#include "kis_touch_shortcut.h"
18#include "kis_config.h"
20#include <KoPointerEvent.h>
21
22//#define DEBUG_MATCHER
23
24#ifdef DEBUG_MATCHER
25#include <kis_debug.h>
26#define DEBUG_ACTION(text) qDebug() << __FUNCTION__ << "-" << text;
27#define DEBUG_SHORTCUT(text, shortcut) qDebug() << __FUNCTION__ << "-" << text << "act:" << shortcut->action()->name();
28#define DEBUG_KEY(text) qDebug() << __FUNCTION__ << "-" << text << "keys:" << m_d->keys;
29#define DEBUG_BUTTON_ACTION(text, button) qDebug() << __FUNCTION__ << "-" << text << "button:" << button << "btns:" << m_d->buttons << "keys:" << m_d->keys;
30#define DEBUG_EVENT_ACTION(text, event) if (event) {qDebug() << __FUNCTION__ << "-" << text << "type:" << event->type();}
31#define DEBUG_TOUCH_ACTION(text, event) \
32 if (event) { \
33 qDebug() << __FUNCTION__ << "-" << text << "type:" << event->type() << "tps:" << event->touchPoints().size() \
34 << "maxTps:" << m_d->maxTouchPoints << "drag:" << m_d->isTouchDragDetected; \
35 }
36#else
37#define DEBUG_ACTION(text)
38#define DEBUG_KEY(text)
39#define DEBUG_SHORTCUT(text, shortcut)
40#define DEBUG_BUTTON_ACTION(text, button)
41#define DEBUG_EVENT_ACTION(text, event)
42#define DEBUG_TOUCH_ACTION(text, event)
43#endif
44
45
46class Q_DECL_HIDDEN KisShortcutMatcher::Private
47{
48public:
50 : runningShortcut(0)
51 , readyShortcut(0)
52 , touchShortcut(0)
53 , nativeGestureShortcut(0)
54 , actionGroupMask([] () { return AllActionGroup; })
55 , suppressAllActions(false)
56 , suppressAllKeyboardActions(false)
57 , cursorEntered(false)
58 {}
59
61 {
62 qDeleteAll(singleActionShortcuts);
63 qDeleteAll(strokeShortcuts);
64 qDeleteAll(touchShortcuts);
65 }
66
68 QSet<KisSingleActionShortcut*> suppressedSingleActionShortcuts;
72
73 QSet<Qt::Key> keys; // Model of currently pressed keys
74 QSet<Qt::MouseButton> buttons; // Model of currently pressed buttons
75
76 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.
77
81
85
86 int maxTouchPoints{0};
87 int matchingIteration{0};
88 bool isTouchDragDetected {false};
89 bool isTouchHeld {false};
90 QScopedPointer<QEvent> bestCandidateTouchEvent;
91
92 std::function<KisInputActionGroupsMask()> actionGroupMask;
96
97 int recursiveCounter = 0;
98 int brokenByRecursion = 0;
99
100
103 : q(_q)
104 {
105 q->m_d->recursiveCounter++;
106 q->m_d->brokenByRecursion++;
107 }
108
110 q->m_d->recursiveCounter--;
111 }
112
113 bool isInRecursion() const {
114 return q->m_d->recursiveCounter > 1;
115 }
116
118 };
119
122 : q(_q)
123 {
124 q->m_d->brokenByRecursion = 0;
125 }
126
129
130 bool brokenByRecursion() const {
131 return q->m_d->brokenByRecursion > 0;
132 }
133
135 };
136
137 inline bool actionsSuppressed() const {
138#ifndef Q_OS_ANDROID
139 return suppressAllActions || !cursorEntered;
140#else
141 // when S-pen is not pointing the canvas, actions on canvas are disabled, till it points back to canvas.
142 return false;
143#endif
144 }
145
146 inline bool actionsSuppressedIgnoreFocus() const {
147 return suppressAllActions;
148 }
149
150 inline bool KeyboardActionsSuppressed() const {
151 return suppressAllKeyboardActions;
152 }
153};
154
158
163
165{
166 return m_d->runningShortcut || m_d->touchShortcut || m_d->nativeGestureShortcut;
167}
168
170{
171 for (const KisTouchShortcut *shortcut : m_d->touchShortcuts) {
172 if (shortcut->isHoldType() && shortcut->isAvailable(m_d->actionGroupMask())) {
173 return true;
174 }
175 }
176 return false;
177}
178
180{
181 m_d->singleActionShortcuts.append(shortcut);
182}
183
185{
186 m_d->strokeShortcuts.append(shortcut);
187}
188
190{
191 m_d->touchShortcuts.append(shortcut);
192}
193
195 m_d->nativeGestureShortcuts.append(shortcut);
196}
197
199{
200 return (m_d->runningShortcut && m_d->runningShortcut->action()
201 && m_d->runningShortcut->action()->supportsHiResInputEvents(m_d->runningShortcut->shortcutIndex()))
202 || (m_d->touchShortcut && m_d->touchShortcut->action()
203 && m_d->touchShortcut->action()->supportsHiResInputEvents(m_d->touchShortcut->shortcutIndex()))
204 || (m_d->nativeGestureShortcut && m_d->nativeGestureShortcut->action()
205 && m_d->nativeGestureShortcut->action()->supportsHiResInputEvents(m_d->nativeGestureShortcut->shortcutIndex()));
206}
207
209{
210 Private::RecursionNotifier notifier(this);
211
212 bool retval = false;
213
214 if (m_d->keys.contains(key)) { DEBUG_ACTION("Peculiar, records show key was already pressed"); }
215
216 if (!hasRunningShortcut() && !notifier.isInRecursion()) {
217 retval = tryRunSingleActionShortcutImpl(key, (QEvent*)0, m_d->keys);
218 }
219
220 m_d->keys.insert(key);
221 DEBUG_KEY("Pressed");
222
223 if (notifier.isInRecursion()) {
225 } else if (!hasRunningShortcut()) {
228 }
229
230 return retval;
231}
232
234{
235 Private::RecursionNotifier notifier(this);
236
237
238 bool retval = false;
239
240 if (!m_d->keys.contains(key)) { DEBUG_ACTION("Peculiar, autorepeated key but can't remember it was pressed"); }
241
242 if (m_d->polledKeys.contains(key)) {
243 m_d->polledKeys.remove(key);
244 }
245
246 if (notifier.isInRecursion()) {
248 } else if (!hasRunningShortcut()) {
249 // Autorepeated key should not be included in the shortcut
250 QSet<Qt::Key> filteredKeys = m_d->keys;
251 filteredKeys.remove(key);
252 retval = tryRunSingleActionShortcutImpl(key, (QEvent*)0, filteredKeys);
253 }
254
255 return retval;
256}
257
259{
260 Private::RecursionNotifier notifier(this);
261
262 if (!m_d->keys.contains(key)) { DEBUG_ACTION("Peculiar, key released but can't remember it was pressed"); }
263 else m_d->keys.remove(key);
264
265 m_d->polledKeys.remove(key);
266
267 DEBUG_KEY("Released");
268
269 if (notifier.isInRecursion()) {
271 } else if (!hasRunningShortcut()) {
274 }
275
276 return false;
277}
278
279bool KisShortcutMatcher::buttonPressed(Qt::MouseButton button, QEvent *event)
280{
281 Private::RecursionNotifier notifier(this);
282 DEBUG_BUTTON_ACTION("entered", button);
283
284 bool retval = false;
285
286 if (m_d->buttons.contains(button)) { DEBUG_ACTION("Peculiar, button was already pressed."); }
287
288 if (!hasRunningShortcut() && !notifier.isInRecursion()) {
290 retval = tryRunReadyShortcut(button, event);
291 }
292
293 m_d->buttons.insert(button);
294
295 if (notifier.isInRecursion()) {
297 } else if (!hasRunningShortcut()) {
300 }
301
302 return retval;
303}
304
305bool KisShortcutMatcher::buttonReleased(Qt::MouseButton button, QEvent *event)
306{
307 Private::RecursionNotifier notifier(this);
308 DEBUG_BUTTON_ACTION("entered", button);
309
310 bool retval = false;
311
312 // here we check for the presence of the **stroke** shortcut only
313 if (m_d->runningShortcut) {
314 KIS_SAFE_ASSERT_RECOVER_NOOP(!notifier.isInRecursion());
315
316 retval = tryEndRunningShortcut(button, event);
317 DEBUG_BUTTON_ACTION("ended", button);
318 }
319
320 if (!m_d->buttons.contains(button)) reset("Peculiar, button released but we can't remember it was pressed");
321 else m_d->buttons.remove(button);
322
323 if (notifier.isInRecursion()) {
325 } else if (!hasRunningShortcut()) {
328 }
329
330 return retval;
331}
332
334{
335 Private::RecursionNotifier notifier(this);
336
337
338 if (hasRunningShortcut() || notifier.isInRecursion()) {
339 DEBUG_ACTION("Wheel event canceled.");
340 return false;
341 }
342
343 return tryRunWheelShortcut(wheelAction, event);
344}
345
347{
348 Private::RecursionNotifier notifier(this);
349
350 if (notifier.isInRecursion()) {
351 return false;
352 }
353
354 bool retval = false;
355
356 if (m_d->runningShortcut) {
357 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!m_d->touchShortcut && !m_d->nativeGestureShortcut, false);
358 m_d->runningShortcut->action()->inputEvent(event);
359 retval = true;
360 }
361
362 return retval;
363}
364
366{
367 Private::RecursionNotifier notifier(this);
368
369 m_d->cursorEntered = true;
370
371 if (notifier.isInRecursion()) {
373 } else if (!hasRunningShortcut()) {
376 }
377}
378
380{
381 Private::RecursionNotifier notifier(this);
382
383 m_d->cursorEntered = false;
384
385 if (notifier.isInRecursion()) {
387 } else if (!hasRunningShortcut()) {
390 }
391}
392
393bool KisShortcutMatcher::touchBeginEvent( QTouchEvent* event )
394{
395 DEBUG_TOUCH_ACTION("entered", event)
396
397 Private::RecursionNotifier notifier(this);
398
399 m_d->lastTouchPoints = event->touchPoints();
400
401 // reset state
402 m_d->maxTouchPoints = event->touchPoints().size();
403 m_d->matchingIteration = 1;
404 m_d->isTouchDragDetected = false;
405 m_d->isTouchHeld = false;
406#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
407 KoPointerEvent::copyQtPointerEvent(event, m_d->bestCandidateTouchEvent);
408#else
409 m_d->bestCandidateTouchEvent.reset(event->clone());
410#endif
411
412 return !notifier.isInRecursion();
413}
414
416{
417 DEBUG_TOUCH_ACTION("entered", event)
418
419 if (m_d->isTouchHeld) {
420 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(m_d->touchShortcut, false);
421 m_d->touchShortcut->action()->inputEvent(event);
422 return true;
423 }
424
425 bool retval = false;
426
427 const int touchPointCount = event->touchPoints().size();
428 // check whether the touchpoints are relatively stationary or have been moved for dragging.
429 for (int i = 0; i < event->touchPoints().size() && !m_d->isTouchDragDetected; ++i) {
430 const QTouchEvent::TouchPoint &touchPoint = event->touchPoints().at(i);
431 const QPointF delta = touchPoint.pos() - touchPoint.startPos();
432 const qreal deltaSquared = delta.x() * delta.x() + delta.y() * delta.y();
433 // if the drag is detected, until the next TouchBegin even, we'll be assuming the gesture to be of dragging
434 // type.
435 m_d->isTouchDragDetected = deltaSquared > TOUCH_SLOP_SQUARED;
436 }
437
438 // for a first few events we don't process the events right away. But analyze and keep track of the event with most
439 // touchpoints. This is done to prevent conditions where in three-finger-tap, two-finger-tap be preceded due to
440 // latency
441 const int numIterations = 10;
442 if (m_d->matchingIteration <= numIterations && !m_d->isTouchDragDetected) {
443 m_d->matchingIteration++;
445 DEBUG_TOUCH_ACTION("return best", event)
446 return matchTouchShortcut((QTouchEvent *)m_d->bestCandidateTouchEvent.data());
447 }
448
449 if (m_d->isTouchDragDetected) {
450 if (m_d->touchShortcut && !m_d->touchShortcut->matchDragType(event)) {
451 DEBUG_TOUCH_ACTION("ending", event)
452 // we should end the event as an event with more touchpoints was received
453 retval = tryEndTouchShortcut(event);
454 }
455 if (!hasRunningShortcut() && touchPointCount >= m_d->maxTouchPoints) {
456 m_d->maxTouchPoints = touchPointCount;
457 DEBUG_TOUCH_ACTION("starting", event);
458 retval = tryRunTouchShortcut(event);
459 } else if (m_d->touchShortcut) {
460 // The typical assumption when we get here is that the shortcut has been matched, for which we use
461 // the events with TouchPointPressed state. But there may be instances where shortcut is never
462 // un-matched (meaning: never being tryEndTouchShortcut called on it) even when the finger is
463 // released, and when the next contact is made, the shortcut proceeds assuming continuity -- which
464 // is a false assumption.
465 // So, if we see a TouchPointPressed, we should know that somewhere previously finger was lifted
466 // and we should let the action know this.
467 if (event->touchPointStates() & Qt::TouchPointPressed) {
468 m_d->touchShortcut->action()->begin(m_d->touchShortcut->shortcutIndex(), event);
469 } else if (event->touchPointStates() & Qt::TouchPointReleased) {
470 m_d->touchShortcut->action()->end(event);
471 } else {
472 m_d->touchShortcut->action()->inputEvent(event);
473 }
474 retval = true;
475 }
476 } else {
477 // triggered if a new finger was added, which might result in shortcut not matching the action
478 if ((event->touchPointStates() & Qt::TouchPointReleased) == Qt::TouchPointReleased && !hasRunningShortcut()) {
479 // we should end the event as an event with more touchpoints was received
480 if (m_d->maxTouchPoints <= touchPointCount) {
481 m_d->maxTouchPoints = touchPointCount;
482 DEBUG_TOUCH_ACTION("firing", event);
484 m_d->bestCandidateTouchEvent.reset();
485 }
486 }
487 }
488
489 return retval;
490}
491
492bool KisShortcutMatcher::touchEndEvent(QTouchEvent *event)
493{
494 Private::RecursionNotifier notifier(this);
495
496 m_d->maxTouchPoints = 0;
497 m_d->isTouchHeld = false;
498
499 if (!m_d->isTouchDragDetected && m_d->bestCandidateTouchEvent && !hasRunningShortcut()) {
500 fireReadyTouchShortcut(static_cast<QTouchEvent *>(m_d->bestCandidateTouchEvent.data()));
501 }
502
503 DEBUG_TOUCH_ACTION("ending", event)
504 // we should try and end the shortcut too (it might be that there is none? (sketch))
505 const bool retval = tryEndTouchShortcut(event);
506
507 if (notifier.isInRecursion()) {
509 } else if (!hasRunningShortcut()) {
512 }
513
514 return retval;
515}
516
517void KisShortcutMatcher::touchCancelEvent(QTouchEvent *event, const QPointF &localPos)
518{
519 Private::RecursionNotifier notifier(this);
520
521 m_d->maxTouchPoints = 0;
522 m_d->isTouchHeld = false;
523
524 KIS_SAFE_ASSERT_RECOVER_NOOP(!m_d->runningShortcut || !m_d->touchShortcut);
525
526 // end the stroke types
527 if (m_d->touchShortcut) {
528 KisTouchShortcut *touchShortcut = m_d->touchShortcut;
529 m_d->touchShortcut = 0;
530 QScopedPointer<QEvent> dstEvent;
531#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
532 KoPointerEvent::copyQtPointerEvent(event, dstEvent);
533#else
534 dstEvent.reset(event->clone());
535#endif
536
537 // HACK: Because TouchEvents in KoPointerEvent need to contain at least one touchpoint
538 QTouchEvent* touchEvent = dynamic_cast<QTouchEvent *>(dstEvent.data());
539 KIS_ASSERT(touchEvent);
540#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
541 touchEvent->setTouchPoints(m_d->lastTouchPoints);
542#else
543 dstEvent.reset(new QTouchEvent(event->type(),
544 event->pointingDevice(),
545 event->modifiers(),
546 m_d->lastTouchPoints));
547#endif
548 touchShortcut->action()->end(dstEvent.data());
550 }
551
552 if (notifier.isInRecursion()) {
554 } else if (!hasRunningShortcut()) {
557 }
558}
559
561{
562 DEBUG_TOUCH_ACTION("hold", event)
563
564 // Can happen when multiple shortcut sources interact.
565 if (m_d->runningShortcut) {
566 return false;
567 }
568
569 m_d->isTouchHeld = true; // Must be set first, used in tryRunTouchShortcut.
570 if (tryRunTouchShortcut(event)) {
571 DEBUG_ACTION("touch shortcut found");
572 return true;
573 } else {
574 // Shouldn't really happen, since KisInputManager checks whether a touch
575 // hold shortcut exists beforehand. We'll just handle this though.
576 DEBUG_ACTION("touch shortcut not found");
577 m_d->isTouchHeld = false;
578 return touchBeginEvent(event);
579 }
580}
581
582bool KisShortcutMatcher::nativeGestureBeginEvent(QNativeGestureEvent *event)
583{
584 Q_UNUSED(event);
585
586 Private::RecursionNotifier notifier(this);
587
588 return !notifier.isInRecursion();
589}
590
591bool KisShortcutMatcher::nativeGestureEvent(QNativeGestureEvent *event)
592{
593 bool retval = false;
594 if (!hasRunningShortcut()) {
595 retval = tryRunNativeGestureShortcut( event );
596 }
597 else if (m_d->nativeGestureShortcut) {
598 m_d->nativeGestureShortcut->action()->inputEvent( event );
599 retval = true;
600 }
601
602 return retval;
603}
604
605bool KisShortcutMatcher::nativeGestureEndEvent(QNativeGestureEvent *event)
606{
607 Private::RecursionNotifier notifier(this);
608
609 if ( m_d->nativeGestureShortcut && !m_d->nativeGestureShortcut->match( event ) ) {
611 }
612
613 if (notifier.isInRecursion()) {
615 } else if (!hasRunningShortcut()) {
618 }
619
620 return true;
621}
622
623Qt::MouseButtons listToFlags(const QList<Qt::MouseButton> &list) {
624 Qt::MouseButtons flags;
625 Q_FOREACH (Qt::MouseButton b, list) {
626 flags |= b;
627 }
628 return flags;
629}
630
632{
633 Private::RecursionNotifier notifier(this);
634
635
636 reset("reinitialize");
637
638 if (notifier.isInRecursion()) {
640 } else if (!hasRunningShortcut()) {
643 }
644}
645
647{
648 Private::RecursionNotifier notifier(this);
649
650 m_d->buttons.clear();
651 DEBUG_ACTION("reinitializing buttons");
652
653 if (notifier.isInRecursion()) {
655 } else if (!hasRunningShortcut()) {
658 }
659}
660
662{
663 Q_FOREACH (Qt::Key key, m_d->keys) {
664 if (!keys.contains(key)) {
665 keyReleased(key);
666 }
667 }
668
669 Q_FOREACH (Qt::Key key, keys) {
670 if (!m_d->keys.contains(key)) {
671 keyPressed(key);
672 m_d->polledKeys << key;
673 }
674 }
675
676 Private::RecursionNotifier notifier(this);
677
678 if (notifier.isInRecursion()) {
680 } else if (!hasRunningShortcut()) {
683 }
684
685 DEBUG_ACTION("recoverySyncModifiers");
686}
687
688bool KisShortcutMatcher::sanityCheckModifiersCorrectness(Qt::KeyboardModifiers modifiers) const
689{
690 auto checkKey = [this, modifiers] (Qt::Key key, Qt::KeyboardModifier modifier) {
691 return m_d->keys.contains(key) == bool(modifiers & modifier);
692 };
693
694 return checkKey(Qt::Key_Shift, Qt::ShiftModifier) &&
695 checkKey(Qt::Key_Control, Qt::ControlModifier) &&
696 checkKey(Qt::Key_Alt, Qt::AltModifier) &&
697 checkKey(Qt::Key_Meta, Qt::MetaModifier);
698
699}
700
702{
704 std::copy(m_d->keys.begin(), m_d->keys.end(), std::back_inserter(keys));
705 return keys;
706}
707
709{
710 return !m_d->polledKeys.empty();
711}
712
713void KisShortcutMatcher::lostFocusEvent(const QPointF &localPos)
714{
715 Private::RecursionNotifier notifier(this);
716
717 DEBUG_ACTION("lostFocusEvent");
718
719 if (m_d->runningShortcut) {
720 forceEndRunningShortcut(localPos);
721 }
722
724
732}
733
735{
736 Private::RecursionNotifier notifier(this);
737
738 DEBUG_ACTION("toolHasBeenActivated");
739
740 if (notifier.isInRecursion()) {
742 } else if (!hasRunningShortcut()) {
745 }
746}
747
749{
750 m_d->keys.clear();
751 m_d->buttons.clear();
752 DEBUG_ACTION("reset!");
753}
754
755
757{
758 m_d->keys.clear();
759 m_d->buttons.clear();
760 Q_UNUSED(msg);
761 DEBUG_ACTION(msg);
762}
763
765{
766 m_d->suppressAllActions = value;
767}
768
770{
771 m_d->suppressedSingleActionShortcuts.clear();
772
773 Q_FOREACH (KisSingleActionShortcut *s, m_d->singleActionShortcuts) {
774 Q_FOREACH (const QKeySequence &seq, shortcuts) {
775 if (s->conflictsWith(seq)) {
776 m_d->suppressedSingleActionShortcuts.insert(s);
777 }
778 }
779 }
780}
781
783{
784 m_d->suppressAllKeyboardActions = value;
785}
786
788{
789 reset("Clearing shortcuts");
790 qDeleteAll(m_d->singleActionShortcuts);
791 m_d->singleActionShortcuts.clear();
792 qDeleteAll(m_d->strokeShortcuts);
793 qDeleteAll(m_d->touchShortcuts);
794 m_d->strokeShortcuts.clear();
795 m_d->candidateShortcuts.clear();
796 m_d->touchShortcuts.clear();
797 m_d->runningShortcut = 0;
798 m_d->readyShortcut = 0;
799}
800
801void KisShortcutMatcher::setInputActionGroupsMaskCallback(std::function<KisInputActionGroupsMask ()> func)
802{
803 m_d->actionGroupMask = func;
804}
805
807{
808 return tryRunSingleActionShortcutImpl(wheelAction, event, m_d->keys, false);
809}
810
811// Note: sometimes event can be zero!!
812template<typename T, typename U>
813bool KisShortcutMatcher::tryRunSingleActionShortcutImpl(T param, U *event, const QSet<Qt::Key> &keysState, bool keyboard)
814{
815 if (m_d->actionsSuppressedIgnoreFocus() || (keyboard && m_d->KeyboardActionsSuppressed())) {
816 DEBUG_EVENT_ACTION("Event suppressed", event)
817 return false;
818 }
819
820 KisSingleActionShortcut *goodCandidate = 0;
821
822 Q_FOREACH (KisSingleActionShortcut *s, m_d->singleActionShortcuts) {
823 if (!m_d->suppressedSingleActionShortcuts.contains(s) &&
824 s->isAvailable(m_d->actionGroupMask()) &&
825 s->match(keysState, param) &&
826 (!goodCandidate || s->priority() > goodCandidate->priority())) {
827
828 goodCandidate = s;
829 }
830 }
831
832 if (goodCandidate) {
833 DEBUG_EVENT_ACTION("Beginning action for event", event);
834 goodCandidate->action()->begin(goodCandidate->shortcutIndex(), event);
835 goodCandidate->action()->end(0);
836 } else {
837 DEBUG_EVENT_ACTION("Could not match a candidate for event", event)
838 }
839
840 return goodCandidate;
841}
842
844{
845 m_d->candidateShortcuts.clear();
846 if (m_d->actionsSuppressed()) return;
847
848 // Allow letting the modifiers to be matched so key_shift + middle mouse move can be matched, but key_v + mouse drag can not.
849 bool containsOnlyModifiers = !m_d->keys.isEmpty();
850 Q_FOREACH(const Qt::Key k, m_d->keys) {
851 if (k != Qt::Key_Shift && k != Qt::Key_Control && k != Qt::Key_Alt && k != Qt::Key_Meta) {
852 containsOnlyModifiers = false;
853 break;
854 }
855 }
856 if (m_d->KeyboardActionsSuppressed()
857 && !containsOnlyModifiers && !m_d->keys.isEmpty()
858 && m_d->buttons.isEmpty()) {
859 return;
860 }
861
862 Q_FOREACH (KisStrokeShortcut *s, m_d->strokeShortcuts) {
863 if (s->matchReady(m_d->keys, m_d->buttons)) {
864 m_d->candidateShortcuts.append(s);
865 }
866 }
867}
868
869bool KisShortcutMatcher::tryRunReadyShortcut( Qt::MouseButton button, QEvent* event )
870{
871 KisStrokeShortcut *goodCandidate = 0;
872
873 Q_FOREACH (KisStrokeShortcut *s, m_d->candidateShortcuts) {
874 if (s->isAvailable(m_d->actionGroupMask()) &&
875 s->matchBegin(button) &&
876 (!goodCandidate || s->priority() > goodCandidate->priority())) {
877
878 goodCandidate = s;
879 }
880 }
881
882 if (goodCandidate) {
883 if (m_d->readyShortcut) {
884 if (m_d->readyShortcut != goodCandidate) {
885 m_d->readyShortcut->action()->deactivate(m_d->readyShortcut->shortcutIndex());
886 goodCandidate->action()->activate(goodCandidate->shortcutIndex());
887 }
888 m_d->readyShortcut = 0;
889 } else {
890 DEBUG_EVENT_ACTION("Matched *new* shortcut for event", event);
891 goodCandidate->action()->activate(goodCandidate->shortcutIndex());
892 }
893
894 DEBUG_SHORTCUT("Starting new action", goodCandidate);
895
896 {
897 m_d->runningShortcut = goodCandidate;
898 Private::RecursionGuard guard(this);
899 goodCandidate->action()->begin(goodCandidate->shortcutIndex(), event);
900
901 // the tool might have opened some dialog, which could break our event loop
902 if (guard.brokenByRecursion()) {
903 goodCandidate->action()->end(event);
904 m_d->runningShortcut = 0;
905
907 }
908 }
909 }
910
911 return m_d->runningShortcut;
912}
913
915{
916 KisStrokeShortcut *goodCandidate = 0;
917
918 Q_FOREACH (KisStrokeShortcut *s, m_d->candidateShortcuts) {
919 if (!goodCandidate || s->priority() > goodCandidate->priority()) {
920 goodCandidate = s;
921 }
922 }
923
924 if (goodCandidate) {
925 if (m_d->readyShortcut && m_d->readyShortcut != goodCandidate) {
926 DEBUG_SHORTCUT("Deactivated previous shortcut action", m_d->readyShortcut);
927 m_d->readyShortcut->action()->deactivate(m_d->readyShortcut->shortcutIndex());
928 m_d->readyShortcut = 0;
929 }
930
931 if (!m_d->readyShortcut) {
932 DEBUG_SHORTCUT("Preparing new ready action", goodCandidate);
933
941 goodCandidate->action()->activate(goodCandidate->shortcutIndex());
942 m_d->readyShortcut = goodCandidate;
943 }
944 } else if (m_d->readyShortcut) {
945 DEBUG_SHORTCUT("Deactivating action", m_d->readyShortcut);
946 m_d->readyShortcut->action()->deactivate(m_d->readyShortcut->shortcutIndex());
947 m_d->readyShortcut = 0;
948 }
949}
950
951bool KisShortcutMatcher::tryEndRunningShortcut( Qt::MouseButton button, QEvent* event )
952{
953 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(m_d->runningShortcut, true);
954 KIS_SAFE_ASSERT_RECOVER(!m_d->readyShortcut) {
955 // it shouldn't have happened, running and ready shortcuts
956 // at the same time should not be possible
958 }
959
960 if (m_d->runningShortcut && m_d->runningShortcut->matchBegin(button)) {
961
962 // first reset running shortcut to avoid infinite recursion via end()
963 KisStrokeShortcut *runningShortcut = m_d->runningShortcut;
964 m_d->runningShortcut = 0;
965
966 if (runningShortcut->action()) {
967 DEBUG_EVENT_ACTION("Ending running shortcut at event", event);
969 int shortcutIndex = runningShortcut->shortcutIndex();
970 action->end(event);
971 action->deactivate(shortcutIndex);
972 }
973 }
974
975 return !m_d->runningShortcut;
976}
977
979{
980 KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->runningShortcut);
981 KIS_SAFE_ASSERT_RECOVER(!m_d->readyShortcut) {
982 // it shouldn't have happened, running and ready shortcuts
983 // at the same time should not be possible
985 }
986
987 // first reset running shortcut to avoid infinite recursion via end()
988 KisStrokeShortcut *runningShortcut = m_d->runningShortcut;
989 m_d->runningShortcut = 0;
990
991 if (runningShortcut->action()) {
992 DEBUG_ACTION("Forced ending running shortcut at event");
994 int shortcutIndex = runningShortcut->shortcutIndex();
995
996 QMouseEvent event = runningShortcut->fakeEndEvent(localPos);
997
998 action->end(&event);
999 action->deactivate(shortcutIndex);
1000 }
1001}
1002
1004{
1005 if (m_d->readyShortcut) {
1006 DEBUG_SHORTCUT("Forcefully deactivating action", m_d->readyShortcut);
1007 m_d->readyShortcut->action()->deactivate(m_d->readyShortcut->shortcutIndex());
1008 m_d->readyShortcut = 0;
1009 }
1010}
1011
1013{
1014 int touchPointCount = event->touchPoints().size();
1015 if (touchPointCount > m_d->maxTouchPoints) {
1016 m_d->maxTouchPoints = touchPointCount;
1017#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
1018 KoPointerEvent::copyQtPointerEvent(event, m_d->bestCandidateTouchEvent);
1019#else
1020 m_d->bestCandidateTouchEvent.reset(event->clone());
1021#endif
1022
1023 }
1024}
1025
1027{
1028 KisTouchShortcut *goodCandidate = matchTouchShortcut(event);
1029 if (goodCandidate) {
1030 DEBUG_TOUCH_ACTION("starting", event)
1031 goodCandidate->action()->activate(goodCandidate->shortcutIndex());
1032 goodCandidate->action()->begin(goodCandidate->shortcutIndex(), event);
1033
1034 goodCandidate->action()->end(event);
1035 goodCandidate->action()->deactivate(goodCandidate->shortcutIndex());
1036 }
1037}
1038
1040{
1041 KisTouchShortcut *goodCandidate = nullptr;
1042 Q_FOREACH (KisTouchShortcut *shortcut, m_d->touchShortcuts) {
1043 if (shortcut->isAvailable(m_d->actionGroupMask())
1044 && matchTouchShortcutBasedOnState(event, shortcut)
1045 && (!goodCandidate || shortcut->priority() > goodCandidate->priority())) {
1046
1047 goodCandidate = shortcut;
1048 }
1049 }
1050 return goodCandidate;
1051}
1052
1054{
1055 if (m_d->isTouchHeld) {
1056 return shortcut->matchHoldType(event);
1057 } else if (m_d->isTouchDragDetected) {
1058 return shortcut->matchDragType(event);
1059 } else {
1060 return shortcut->matchTapType(event);
1061 }
1062}
1063
1065{
1066 KisTouchShortcut *goodCandidate = matchTouchShortcut(event);
1067
1068 if (m_d->actionsSuppressed())
1069 return false;
1070
1071 if (goodCandidate) {
1072 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!m_d->runningShortcut, false);
1073
1074 // Because we don't match keyboard or button based actions with touch system, we have to ensure that we first
1075 // deactivate an activated readyShortcut, to not throw other statemachines out of place.
1077
1078 m_d->touchShortcut = goodCandidate;
1079
1080 Private::RecursionGuard guard(this);
1081 DEBUG_SHORTCUT("Running a touch shortcut", goodCandidate)
1082
1083 goodCandidate->action()->activate(goodCandidate->shortcutIndex());
1084 goodCandidate->action()->begin(goodCandidate->shortcutIndex(), event);
1085
1086 // the tool might have opened some dialog, which could break our event loop
1087 if (guard.brokenByRecursion()) {
1088 goodCandidate->action()->end(event);
1089 m_d->touchShortcut = 0;
1090
1092 }
1093 }
1094
1095 return m_d->touchShortcut;
1096}
1097
1099{
1100 if (m_d->touchShortcut) {
1101 // first reset running shortcut to avoid infinite recursion via end()
1102 KisTouchShortcut *touchShortcut = m_d->touchShortcut;
1103
1104 DEBUG_SHORTCUT("ending", touchShortcut)
1105 touchShortcut->action()->end(event);
1106 touchShortcut->action()->deactivate(m_d->touchShortcut->shortcutIndex());
1107
1108 m_d->touchShortcut = 0; // empty it out now that we are done with it
1109
1110 return true;
1111 }
1112
1113 return false;
1114}
1115
1117{
1118 KisNativeGestureShortcut *goodCandidate = 0;
1119
1120 if (m_d->actionsSuppressed())
1121 return false;
1122
1123 Q_FOREACH (KisNativeGestureShortcut* shortcut, m_d->nativeGestureShortcuts) {
1124 if (shortcut->match(event) && (!goodCandidate || shortcut->priority() > goodCandidate->priority())) {
1125 goodCandidate = shortcut;
1126 }
1127 }
1128
1129 if (goodCandidate) {
1130 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!m_d->runningShortcut, false);
1131
1132 // Because we don't match keyboard or button based actions with touch system, we have to ensure that we first
1133 // deactivate an activated readyShortcut, to not throw other statemachines out of place.
1135
1136 m_d->nativeGestureShortcut = goodCandidate;
1137
1138 Private::RecursionGuard guard(this);
1139 goodCandidate->action()->activate(goodCandidate->shortcutIndex());
1140 goodCandidate->action()->begin(goodCandidate->shortcutIndex(), event);
1141
1142 // the tool might have opened some dialog, which could break our event loop
1143 if (guard.brokenByRecursion()) {
1144 goodCandidate->action()->end(event);
1145 m_d->nativeGestureShortcut = 0;
1146
1148 }
1149 }
1150
1151 return m_d->nativeGestureShortcut;
1152}
1153
1155{
1156 Private::RecursionNotifier notifier(this);
1157
1158 if (m_d->nativeGestureShortcut) {
1159 // first reset running shortcut to avoid infinite recursion via end()
1160 KisNativeGestureShortcut *nativeGestureShortcut = m_d->nativeGestureShortcut;
1161
1162 nativeGestureShortcut->action()->end(event);
1163 nativeGestureShortcut->action()->deactivate(m_d->nativeGestureShortcut->shortcutIndex());
1164
1165 m_d->nativeGestureShortcut = 0; // empty it out now that we are done with it
1166
1167 return true;
1168 }
1169
1170 if (notifier.isInRecursion()) {
1172 } else if (!hasRunningShortcut()) {
1175 }
1176
1177 return false;
1178}
float value(const T *src, size_t ch)
@ AllActionGroup
Abstract base class for input actions.
virtual void activate(int shortcut)
virtual void deactivate(int shortcut)
virtual void end(QEvent *event)
virtual void begin(int shortcut, QEvent *event)
KisAbstractInputAction * action
bool isAvailable(KisInputActionGroupsMask mask) const
bool match(QNativeGestureEvent *event)
bool KeyboardActionsSuppressed() const
void lostFocusEvent(const QPointF &localPos)
QSet< Qt::MouseButton > buttons
QList< QTouchEvent::TouchPoint > lastTouchPoints
bool tryRunTouchShortcut(QTouchEvent *event)
void forceEndRunningShortcut(const QPointF &localPos)
bool actionsSuppressedIgnoreFocus() const
QList< KisNativeGestureShortcut * > nativeGestureShortcuts
bool autoRepeatedKeyPressed(Qt::Key key)
bool buttonReleased(Qt::MouseButton button, QEvent *event)
QList< KisStrokeShortcut * > strokeShortcuts
void addShortcut(KisSingleActionShortcut *shortcut)
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 touchBeginEvent(QTouchEvent *event)
bool tryRunReadyShortcut(Qt::MouseButton button, QEvent *event)
KisTouchShortcut * touchShortcut
void setInputActionGroupsMaskCallback(std::function< KisInputActionGroupsMask()> func)
KisStrokeShortcut * readyShortcut
QVector< Qt::Key > debugPressedKeys() const
KisTouchShortcut * matchTouchShortcut(QTouchEvent *event)
bool touchEndEvent(QTouchEvent *event)
bool touchUpdateEvent(QTouchEvent *event)
std::function< KisInputActionGroupsMask()> actionGroupMask
bool nativeGestureEvent(QNativeGestureEvent *event)
KisNativeGestureShortcut * nativeGestureShortcut
bool buttonPressed(Qt::MouseButton button, QEvent *event)
static constexpr int TOUCH_SLOP_SQUARED
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 matchTouchShortcutBasedOnState(QTouchEvent *event, KisTouchShortcut *shortcut)
bool pointerMoved(QEvent *event)
bool nativeGestureBeginEvent(QNativeGestureEvent *event)
void setMaxTouchPointEvent(QTouchEvent *event)
void suppressConflictingKeyActions(const QVector< QKeySequence > &shortcuts)
QScopedPointer< QEvent > bestCandidateTouchEvent
KisStrokeShortcut * runningShortcut
bool keyReleased(Qt::Key key)
void touchCancelEvent(QTouchEvent *event, const QPointF &localPos)
void fireReadyTouchShortcut(QTouchEvent *event)
QSet< KisSingleActionShortcut * > suppressedSingleActionShortcuts
bool wheelEvent(KisSingleActionShortcut::WheelAction wheelAction, QWheelEvent *event)
bool tryEndNativeGestureShortcut(QNativeGestureEvent *event)
bool touchHoldBeginEvent(QTouchEvent *event)
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)
The KisTouchShortcut class only handles touch gestures it does not handle tool invocation i....
bool matchDragType(QTouchEvent *event)
bool matchTapType(QTouchEvent *event)
bool matchHoldType(QTouchEvent *event)
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
#define KIS_ASSERT(cond)
Definition kis_assert.h:33
#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)
#define DEBUG_KEY(text)
#define DEBUG_TOUCH_ACTION(text, event)
QString button(const QWheelEvent &ev)
#define DEBUG_ACTION(action)