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
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
791 qDeleteAll(m_d->singleActionShortcuts);
792 m_d->singleActionShortcuts.clear();
793 m_d->suppressedSingleActionShortcuts.clear();
794
795 qDeleteAll(m_d->strokeShortcuts);
796 m_d->strokeShortcuts.clear();
797
798 qDeleteAll(m_d->touchShortcuts);
799 m_d->touchShortcuts.clear();
800
801 qDeleteAll(m_d->nativeGestureShortcuts);
802 m_d->nativeGestureShortcuts.clear();
803
804 m_d->candidateShortcuts.clear();
805 m_d->runningShortcut = 0;
806 m_d->readyShortcut = 0;
807 m_d->touchShortcut = 0;
808 m_d->nativeGestureShortcut = 0;
809}
810
811void KisShortcutMatcher::setInputActionGroupsMaskCallback(std::function<KisInputActionGroupsMask ()> func)
812{
813 m_d->actionGroupMask = func;
814}
815
817{
818 return tryRunSingleActionShortcutImpl(wheelAction, event, m_d->keys, false);
819}
820
821// Note: sometimes event can be zero!!
822template<typename T, typename U>
823bool KisShortcutMatcher::tryRunSingleActionShortcutImpl(T param, U *event, const QSet<Qt::Key> &keysState, bool keyboard)
824{
825 if (m_d->actionsSuppressedIgnoreFocus() || (keyboard && m_d->KeyboardActionsSuppressed())) {
826 DEBUG_EVENT_ACTION("Event suppressed", event)
827 return false;
828 }
829
830 KisSingleActionShortcut *goodCandidate = 0;
831
832 Q_FOREACH (KisSingleActionShortcut *s, m_d->singleActionShortcuts) {
833 if (!m_d->suppressedSingleActionShortcuts.contains(s) &&
834 s->isAvailable(m_d->actionGroupMask()) &&
835 s->match(keysState, param) &&
836 (!goodCandidate || s->priority() > goodCandidate->priority())) {
837
838 goodCandidate = s;
839 }
840 }
841
842 if (goodCandidate) {
843 DEBUG_EVENT_ACTION("Beginning action for event", event);
844 goodCandidate->action()->begin(goodCandidate->shortcutIndex(), event);
845 goodCandidate->action()->end(0);
846 } else {
847 DEBUG_EVENT_ACTION("Could not match a candidate for event", event)
848 }
849
850 return goodCandidate;
851}
852
854{
855 m_d->candidateShortcuts.clear();
856 if (m_d->actionsSuppressed()) return;
857
858 // Allow letting the modifiers to be matched so key_shift + middle mouse move can be matched, but key_v + mouse drag can not.
859 bool containsOnlyModifiers = !m_d->keys.isEmpty();
860 Q_FOREACH(const Qt::Key k, m_d->keys) {
861 if (k != Qt::Key_Shift && k != Qt::Key_Control && k != Qt::Key_Alt && k != Qt::Key_Meta) {
862 containsOnlyModifiers = false;
863 break;
864 }
865 }
866 if (m_d->KeyboardActionsSuppressed()
867 && !containsOnlyModifiers && !m_d->keys.isEmpty()
868 && m_d->buttons.isEmpty()) {
869 return;
870 }
871
872 Q_FOREACH (KisStrokeShortcut *s, m_d->strokeShortcuts) {
873 if (s->matchReady(m_d->keys, m_d->buttons)) {
874 m_d->candidateShortcuts.append(s);
875 }
876 }
877}
878
879bool KisShortcutMatcher::tryRunReadyShortcut( Qt::MouseButton button, QEvent* event )
880{
881 KisStrokeShortcut *goodCandidate = 0;
882
883 Q_FOREACH (KisStrokeShortcut *s, m_d->candidateShortcuts) {
884 if (s->isAvailable(m_d->actionGroupMask()) &&
885 s->matchBegin(button) &&
886 (!goodCandidate || s->priority() > goodCandidate->priority())) {
887
888 goodCandidate = s;
889 }
890 }
891
892 if (goodCandidate) {
893 if (m_d->readyShortcut) {
894 if (m_d->readyShortcut != goodCandidate) {
895 m_d->readyShortcut->action()->deactivate(m_d->readyShortcut->shortcutIndex());
896 goodCandidate->action()->activate(goodCandidate->shortcutIndex());
897 }
898 m_d->readyShortcut = 0;
899 } else {
900 DEBUG_EVENT_ACTION("Matched *new* shortcut for event", event);
901 goodCandidate->action()->activate(goodCandidate->shortcutIndex());
902 }
903
904 DEBUG_SHORTCUT("Starting new action", goodCandidate);
905
906 {
907 m_d->runningShortcut = goodCandidate;
908 Private::RecursionGuard guard(this);
909 goodCandidate->action()->begin(goodCandidate->shortcutIndex(), event);
910
911 // the tool might have opened some dialog, which could break our event loop
912 if (guard.brokenByRecursion()) {
913 goodCandidate->action()->end(event);
914 m_d->runningShortcut = 0;
915
917 }
918 }
919 }
920
921 return m_d->runningShortcut;
922}
923
925{
926 KisStrokeShortcut *goodCandidate = 0;
927
928 Q_FOREACH (KisStrokeShortcut *s, m_d->candidateShortcuts) {
929 if (!goodCandidate || s->priority() > goodCandidate->priority()) {
930 goodCandidate = s;
931 }
932 }
933
934 if (goodCandidate) {
935 if (m_d->readyShortcut && m_d->readyShortcut != goodCandidate) {
936 DEBUG_SHORTCUT("Deactivated previous shortcut action", m_d->readyShortcut);
937 m_d->readyShortcut->action()->deactivate(m_d->readyShortcut->shortcutIndex());
938 m_d->readyShortcut = 0;
939 }
940
941 if (!m_d->readyShortcut) {
942 DEBUG_SHORTCUT("Preparing new ready action", goodCandidate);
943
951 goodCandidate->action()->activate(goodCandidate->shortcutIndex());
952 m_d->readyShortcut = goodCandidate;
953 }
954 } else if (m_d->readyShortcut) {
955 DEBUG_SHORTCUT("Deactivating action", m_d->readyShortcut);
956 m_d->readyShortcut->action()->deactivate(m_d->readyShortcut->shortcutIndex());
957 m_d->readyShortcut = 0;
958 }
959}
960
961bool KisShortcutMatcher::tryEndRunningShortcut( Qt::MouseButton button, QEvent* event )
962{
963 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(m_d->runningShortcut, true);
964 KIS_SAFE_ASSERT_RECOVER(!m_d->readyShortcut) {
965 // it shouldn't have happened, running and ready shortcuts
966 // at the same time should not be possible
968 }
969
970 if (m_d->runningShortcut && m_d->runningShortcut->matchBegin(button)) {
971
972 // first reset running shortcut to avoid infinite recursion via end()
973 KisStrokeShortcut *runningShortcut = m_d->runningShortcut;
974 m_d->runningShortcut = 0;
975
976 if (runningShortcut->action()) {
977 DEBUG_EVENT_ACTION("Ending running shortcut at event", event);
979 int shortcutIndex = runningShortcut->shortcutIndex();
980 action->end(event);
981 action->deactivate(shortcutIndex);
982 }
983 }
984
985 return !m_d->runningShortcut;
986}
987
989{
990 KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->runningShortcut);
991 KIS_SAFE_ASSERT_RECOVER(!m_d->readyShortcut) {
992 // it shouldn't have happened, running and ready shortcuts
993 // at the same time should not be possible
995 }
996
997 // first reset running shortcut to avoid infinite recursion via end()
998 KisStrokeShortcut *runningShortcut = m_d->runningShortcut;
999 m_d->runningShortcut = 0;
1000
1001 if (runningShortcut->action()) {
1002 DEBUG_ACTION("Forced ending running shortcut at event");
1004 int shortcutIndex = runningShortcut->shortcutIndex();
1005
1006 QMouseEvent event = runningShortcut->fakeEndEvent(localPos);
1007
1008 action->end(&event);
1009 action->deactivate(shortcutIndex);
1010 }
1011}
1012
1014{
1015 if (m_d->readyShortcut) {
1016 DEBUG_SHORTCUT("Forcefully deactivating action", m_d->readyShortcut);
1017 m_d->readyShortcut->action()->deactivate(m_d->readyShortcut->shortcutIndex());
1018 m_d->readyShortcut = 0;
1019 }
1020}
1021
1023{
1024 int touchPointCount = event->touchPoints().size();
1025 if (touchPointCount > m_d->maxTouchPoints) {
1026 m_d->maxTouchPoints = touchPointCount;
1027#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
1028 KoPointerEvent::copyQtPointerEvent(event, m_d->bestCandidateTouchEvent);
1029#else
1030 m_d->bestCandidateTouchEvent.reset(event->clone());
1031#endif
1032
1033 }
1034}
1035
1037{
1038 KisTouchShortcut *goodCandidate = matchTouchShortcut(event);
1039 if (goodCandidate) {
1040 DEBUG_TOUCH_ACTION("starting", event)
1041 goodCandidate->action()->activate(goodCandidate->shortcutIndex());
1042 goodCandidate->action()->begin(goodCandidate->shortcutIndex(), event);
1043
1044 goodCandidate->action()->end(event);
1045 goodCandidate->action()->deactivate(goodCandidate->shortcutIndex());
1046 }
1047}
1048
1050{
1051 KisTouchShortcut *goodCandidate = nullptr;
1052 Q_FOREACH (KisTouchShortcut *shortcut, m_d->touchShortcuts) {
1053 if (shortcut->isAvailable(m_d->actionGroupMask())
1054 && matchTouchShortcutBasedOnState(event, shortcut)
1055 && (!goodCandidate || shortcut->priority() > goodCandidate->priority())) {
1056
1057 goodCandidate = shortcut;
1058 }
1059 }
1060 return goodCandidate;
1061}
1062
1064{
1065 if (m_d->isTouchHeld) {
1066 return shortcut->matchHoldType(event);
1067 } else if (m_d->isTouchDragDetected) {
1068 return shortcut->matchDragType(event);
1069 } else {
1070 return shortcut->matchTapType(event);
1071 }
1072}
1073
1075{
1076 KisTouchShortcut *goodCandidate = matchTouchShortcut(event);
1077
1078 if (m_d->actionsSuppressed())
1079 return false;
1080
1081 if (goodCandidate) {
1082 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!m_d->runningShortcut, false);
1083
1084 // Because we don't match keyboard or button based actions with touch system, we have to ensure that we first
1085 // deactivate an activated readyShortcut, to not throw other statemachines out of place.
1087
1088 m_d->touchShortcut = goodCandidate;
1089
1090 Private::RecursionGuard guard(this);
1091 DEBUG_SHORTCUT("Running a touch shortcut", goodCandidate)
1092
1093 goodCandidate->action()->activate(goodCandidate->shortcutIndex());
1094 goodCandidate->action()->begin(goodCandidate->shortcutIndex(), event);
1095
1096 // the tool might have opened some dialog, which could break our event loop
1097 if (guard.brokenByRecursion()) {
1098 goodCandidate->action()->end(event);
1099 m_d->touchShortcut = 0;
1100
1102 }
1103 }
1104
1105 return m_d->touchShortcut;
1106}
1107
1109{
1110 if (m_d->touchShortcut) {
1111 // first reset running shortcut to avoid infinite recursion via end()
1112 KisTouchShortcut *touchShortcut = m_d->touchShortcut;
1113
1114 DEBUG_SHORTCUT("ending", touchShortcut)
1115 touchShortcut->action()->end(event);
1116 touchShortcut->action()->deactivate(m_d->touchShortcut->shortcutIndex());
1117
1118 m_d->touchShortcut = 0; // empty it out now that we are done with it
1119
1120 return true;
1121 }
1122
1123 return false;
1124}
1125
1127{
1128 KisNativeGestureShortcut *goodCandidate = 0;
1129
1130 if (m_d->actionsSuppressed())
1131 return false;
1132
1133 Q_FOREACH (KisNativeGestureShortcut* shortcut, m_d->nativeGestureShortcuts) {
1134 if (shortcut->match(event) && (!goodCandidate || shortcut->priority() > goodCandidate->priority())) {
1135 goodCandidate = shortcut;
1136 }
1137 }
1138
1139 if (goodCandidate) {
1140 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!m_d->runningShortcut, false);
1141
1142 // Because we don't match keyboard or button based actions with touch system, we have to ensure that we first
1143 // deactivate an activated readyShortcut, to not throw other statemachines out of place.
1145
1146 m_d->nativeGestureShortcut = goodCandidate;
1147
1148 Private::RecursionGuard guard(this);
1149 goodCandidate->action()->activate(goodCandidate->shortcutIndex());
1150 goodCandidate->action()->begin(goodCandidate->shortcutIndex(), event);
1151
1152 // the tool might have opened some dialog, which could break our event loop
1153 if (guard.brokenByRecursion()) {
1154 goodCandidate->action()->end(event);
1155 m_d->nativeGestureShortcut = 0;
1156
1158 }
1159 }
1160
1161 return m_d->nativeGestureShortcut;
1162}
1163
1165{
1166 Private::RecursionNotifier notifier(this);
1167
1168 if (m_d->nativeGestureShortcut) {
1169 // first reset running shortcut to avoid infinite recursion via end()
1170 KisNativeGestureShortcut *nativeGestureShortcut = m_d->nativeGestureShortcut;
1171
1172 nativeGestureShortcut->action()->end(event);
1173 nativeGestureShortcut->action()->deactivate(m_d->nativeGestureShortcut->shortcutIndex());
1174
1175 m_d->nativeGestureShortcut = 0; // empty it out now that we are done with it
1176
1177 return true;
1178 }
1179
1180 if (notifier.isInRecursion()) {
1182 } else if (!hasRunningShortcut()) {
1185 }
1186
1187 return false;
1188}
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
void touchCancelEvent(QTouchEvent *event)
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 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)