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 QScopedPointer<QEvent> bestCandidateTouchEvent;
90
91 std::function<KisInputActionGroupsMask()> actionGroupMask;
95
96 int recursiveCounter = 0;
97 int brokenByRecursion = 0;
98
99
102 : q(_q)
103 {
104 q->m_d->recursiveCounter++;
105 q->m_d->brokenByRecursion++;
106 }
107
109 q->m_d->recursiveCounter--;
110 }
111
112 bool isInRecursion() const {
113 return q->m_d->recursiveCounter > 1;
114 }
115
117 };
118
121 : q(_q)
122 {
123 q->m_d->brokenByRecursion = 0;
124 }
125
128
129 bool brokenByRecursion() const {
130 return q->m_d->brokenByRecursion > 0;
131 }
132
134 };
135
136 inline bool actionsSuppressed() const {
137#ifndef Q_OS_ANDROID
138 return suppressAllActions || !cursorEntered;
139#else
140 // when S-pen is not pointing the canvas, actions on canvas are disabled, till it points back to canvas.
141 return false;
142#endif
143 }
144
145 inline bool actionsSuppressedIgnoreFocus() const {
146 return suppressAllActions;
147 }
148
149 inline bool KeyboardActionsSuppressed() const {
150 return suppressAllKeyboardActions;
151 }
152};
153
157
162
164{
165 return m_d->runningShortcut || m_d->touchShortcut || m_d->nativeGestureShortcut;
166}
167
169{
170 m_d->singleActionShortcuts.append(shortcut);
171}
172
174{
175 m_d->strokeShortcuts.append(shortcut);
176}
177
179{
180 m_d->touchShortcuts.append(shortcut);
181}
182
184 m_d->nativeGestureShortcuts.append(shortcut);
185}
186
188{
189 return (m_d->runningShortcut && m_d->runningShortcut->action()
190 && m_d->runningShortcut->action()->supportsHiResInputEvents(m_d->runningShortcut->shortcutIndex()))
191 || (m_d->touchShortcut && m_d->touchShortcut->action()
192 && m_d->touchShortcut->action()->supportsHiResInputEvents(m_d->touchShortcut->shortcutIndex()))
193 || (m_d->nativeGestureShortcut && m_d->nativeGestureShortcut->action()
194 && m_d->nativeGestureShortcut->action()->supportsHiResInputEvents(m_d->nativeGestureShortcut->shortcutIndex()));
195}
196
198{
199 Private::RecursionNotifier notifier(this);
200
201 bool retval = false;
202
203 if (m_d->keys.contains(key)) { DEBUG_ACTION("Peculiar, records show key was already pressed"); }
204
205 if (!hasRunningShortcut() && !notifier.isInRecursion()) {
206 retval = tryRunSingleActionShortcutImpl(key, (QEvent*)0, m_d->keys);
207 }
208
209 m_d->keys.insert(key);
210 DEBUG_KEY("Pressed");
211
212 if (notifier.isInRecursion()) {
214 } else if (!hasRunningShortcut()) {
217 }
218
219 return retval;
220}
221
223{
224 Private::RecursionNotifier notifier(this);
225
226
227 bool retval = false;
228
229 if (!m_d->keys.contains(key)) { DEBUG_ACTION("Peculiar, autorepeated key but can't remember it was pressed"); }
230
231 if (m_d->polledKeys.contains(key)) {
232 m_d->polledKeys.remove(key);
233 }
234
235 if (notifier.isInRecursion()) {
237 } else if (!hasRunningShortcut()) {
238 // Autorepeated key should not be included in the shortcut
239 QSet<Qt::Key> filteredKeys = m_d->keys;
240 filteredKeys.remove(key);
241 retval = tryRunSingleActionShortcutImpl(key, (QEvent*)0, filteredKeys);
242 }
243
244 return retval;
245}
246
248{
249 Private::RecursionNotifier notifier(this);
250
251 if (!m_d->keys.contains(key)) { DEBUG_ACTION("Peculiar, key released but can't remember it was pressed"); }
252 else m_d->keys.remove(key);
253
254 m_d->polledKeys.remove(key);
255
256 DEBUG_KEY("Released");
257
258 if (notifier.isInRecursion()) {
260 } else if (!hasRunningShortcut()) {
263 }
264
265 return false;
266}
267
268bool KisShortcutMatcher::buttonPressed(Qt::MouseButton button, QEvent *event)
269{
270 Private::RecursionNotifier notifier(this);
271 DEBUG_BUTTON_ACTION("entered", button);
272
273 bool retval = false;
274
275 if (m_d->buttons.contains(button)) { DEBUG_ACTION("Peculiar, button was already pressed."); }
276
277 if (!hasRunningShortcut() && !notifier.isInRecursion()) {
279 retval = tryRunReadyShortcut(button, event);
280 }
281
282 m_d->buttons.insert(button);
283
284 if (notifier.isInRecursion()) {
286 } else if (!hasRunningShortcut()) {
289 }
290
291 return retval;
292}
293
294bool KisShortcutMatcher::buttonReleased(Qt::MouseButton button, QEvent *event)
295{
296 Private::RecursionNotifier notifier(this);
297 DEBUG_BUTTON_ACTION("entered", button);
298
299 bool retval = false;
300
301 // here we check for the presence of the **stroke** shortcut only
302 if (m_d->runningShortcut) {
303 KIS_SAFE_ASSERT_RECOVER_NOOP(!notifier.isInRecursion());
304
305 retval = tryEndRunningShortcut(button, event);
306 DEBUG_BUTTON_ACTION("ended", button);
307 }
308
309 if (!m_d->buttons.contains(button)) reset("Peculiar, button released but we can't remember it was pressed");
310 else m_d->buttons.remove(button);
311
312 if (notifier.isInRecursion()) {
314 } else if (!hasRunningShortcut()) {
317 }
318
319 return retval;
320}
321
323{
324 Private::RecursionNotifier notifier(this);
325
326
327 if (hasRunningShortcut() || notifier.isInRecursion()) {
328 DEBUG_ACTION("Wheel event canceled.");
329 return false;
330 }
331
332 return tryRunWheelShortcut(wheelAction, event);
333}
334
336{
337 Private::RecursionNotifier notifier(this);
338
339 if (notifier.isInRecursion()) {
340 return false;
341 }
342
343 bool retval = false;
344
345 if (m_d->runningShortcut) {
346 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!m_d->touchShortcut && !m_d->nativeGestureShortcut, false);
347 m_d->runningShortcut->action()->inputEvent(event);
348 retval = true;
349 }
350
351 return retval;
352}
353
355{
356 Private::RecursionNotifier notifier(this);
357
358 m_d->cursorEntered = true;
359
360 if (notifier.isInRecursion()) {
362 } else if (!hasRunningShortcut()) {
365 }
366}
367
369{
370 Private::RecursionNotifier notifier(this);
371
372 m_d->cursorEntered = false;
373
374 if (notifier.isInRecursion()) {
376 } else if (!hasRunningShortcut()) {
379 }
380}
381
382bool KisShortcutMatcher::touchBeginEvent( QTouchEvent* event )
383{
384 DEBUG_TOUCH_ACTION("entered", event)
385
386 Private::RecursionNotifier notifier(this);
387
388 m_d->lastTouchPoints = event->touchPoints();
389
390 // reset state
391 m_d->maxTouchPoints = event->touchPoints().size();
392 m_d->matchingIteration = 1;
393 m_d->isTouchDragDetected = false;
394#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
395 KoPointerEvent::copyQtPointerEvent(event, m_d->bestCandidateTouchEvent);
396#else
397 m_d->bestCandidateTouchEvent.reset(event->clone());
398#endif
399
400 return !notifier.isInRecursion();
401}
402
404{
405 DEBUG_TOUCH_ACTION("entered", event)
406
407 bool retval = false;
408
409 const int touchPointCount = event->touchPoints().size();
410 const int touchSlopSquared = 16 * 16;
411 // check whether the touchpoints are relatively stationary or have been moved for dragging.
412 for (int i = 0; i < event->touchPoints().size() && !m_d->isTouchDragDetected; ++i) {
413 const QTouchEvent::TouchPoint &touchPoint = event->touchPoints().at(i);
414 const QPointF delta = touchPoint.pos() - touchPoint.startPos();
415 const qreal deltaSquared = delta.x() * delta.x() + delta.y() * delta.y();
416 // if the drag is detected, until the next TouchBegin even, we'll be assuming the gesture to be of dragging
417 // type.
418 m_d->isTouchDragDetected = deltaSquared > touchSlopSquared;
419 }
420
421 // for a first few events we don't process the events right away. But analyze and keep track of the event with most
422 // touchpoints. This is done to prevent conditions where in three-finger-tap, two-finger-tap be preceded due to
423 // latency
424 const int numIterations = 10;
425 if (m_d->matchingIteration <= numIterations && !m_d->isTouchDragDetected) {
426 m_d->matchingIteration++;
428 DEBUG_TOUCH_ACTION("return best", event)
429 return matchTouchShortcut((QTouchEvent *)m_d->bestCandidateTouchEvent.data());
430 }
431
432 if (m_d->isTouchDragDetected) {
433 if (m_d->touchShortcut && !m_d->touchShortcut->matchDragType(event)) {
434 DEBUG_TOUCH_ACTION("ending", event)
435 // we should end the event as an event with more touchpoints was received
436 retval = tryEndTouchShortcut(event);
437 }
438 if (!hasRunningShortcut() && touchPointCount >= m_d->maxTouchPoints) {
439 m_d->maxTouchPoints = touchPointCount;
440 DEBUG_TOUCH_ACTION("starting", event);
441 retval = tryRunTouchShortcut(event);
442 } else if (m_d->touchShortcut) {
443 // The typical assumption when we get here is that the shortcut has been matched, for which we use
444 // the events with TouchPointPressed state. But there may be instances where shortcut is never
445 // un-matched (meaning: never being tryEndTouchShortcut called on it) even when the finger is
446 // released, and when the next contact is made, the shortcut proceeds assuming continuity -- which
447 // is a false assumption.
448 // So, if we see a TouchPointPressed, we should know that somewhere previously finger was lifted
449 // and we should let the action know this.
450 if (event->touchPointStates() & Qt::TouchPointPressed) {
451 m_d->touchShortcut->action()->begin(m_d->touchShortcut->shortcutIndex(), event);
452 } else if (event->touchPointStates() & Qt::TouchPointReleased) {
453 m_d->touchShortcut->action()->end(event);
454 } else {
455 m_d->touchShortcut->action()->inputEvent(event);
456 }
457 retval = true;
458 }
459 } else {
460 // triggered if a new finger was added, which might result in shortcut not matching the action
461 if ((event->touchPointStates() & Qt::TouchPointReleased) == Qt::TouchPointReleased && !hasRunningShortcut()) {
462 // we should end the event as an event with more touchpoints was received
463 if (m_d->maxTouchPoints <= touchPointCount) {
464 m_d->maxTouchPoints = touchPointCount;
465 DEBUG_TOUCH_ACTION("firing", event);
467 m_d->bestCandidateTouchEvent.reset();
468 }
469 }
470 }
471
472 return retval;
473}
474
475bool KisShortcutMatcher::touchEndEvent(QTouchEvent *event)
476{
477 Private::RecursionNotifier notifier(this);
478
479 m_d->maxTouchPoints = 0;
480
481 if (!m_d->isTouchDragDetected && m_d->bestCandidateTouchEvent && !hasRunningShortcut()) {
482 fireReadyTouchShortcut(static_cast<QTouchEvent *>(m_d->bestCandidateTouchEvent.data()));
483 }
484
485 DEBUG_TOUCH_ACTION("ending", event)
486 // we should try and end the shortcut too (it might be that there is none? (sketch))
487 const bool retval = tryEndTouchShortcut(event);
488
489 if (notifier.isInRecursion()) {
491 } else if (!hasRunningShortcut()) {
494 }
495
496 return retval;
497}
498
499void KisShortcutMatcher::touchCancelEvent(QTouchEvent *event, const QPointF &localPos)
500{
501 Private::RecursionNotifier notifier(this);
502
503 m_d->maxTouchPoints = 0;
504
505 KIS_SAFE_ASSERT_RECOVER_NOOP(!m_d->runningShortcut || !m_d->touchShortcut);
506
507 // end the stroke types
508 if (m_d->touchShortcut) {
509 KisTouchShortcut *touchShortcut = m_d->touchShortcut;
510 m_d->touchShortcut = 0;
511 QScopedPointer<QEvent> dstEvent;
512#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
513 KoPointerEvent::copyQtPointerEvent(event, dstEvent);
514#else
515 dstEvent.reset(event->clone());
516#endif
517
518 // HACK: Because TouchEvents in KoPointerEvent need to contain at least one touchpoint
519 QTouchEvent* touchEvent = dynamic_cast<QTouchEvent *>(dstEvent.data());
520 KIS_ASSERT(touchEvent);
521#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
522 touchEvent->setTouchPoints(m_d->lastTouchPoints);
523#else
524 dstEvent.reset(new QTouchEvent(event->type(),
525 event->pointingDevice(),
526 event->modifiers(),
527 m_d->lastTouchPoints));
528#endif
529 touchShortcut->action()->end(dstEvent.data());
531 }
532
533 if (notifier.isInRecursion()) {
535 } else if (!hasRunningShortcut()) {
538 }
539}
540
541bool KisShortcutMatcher::nativeGestureBeginEvent(QNativeGestureEvent *event)
542{
543 Q_UNUSED(event);
544
545 Private::RecursionNotifier notifier(this);
546
547 return !notifier.isInRecursion();
548}
549
550bool KisShortcutMatcher::nativeGestureEvent(QNativeGestureEvent *event)
551{
552 bool retval = false;
553 if (!hasRunningShortcut()) {
554 retval = tryRunNativeGestureShortcut( event );
555 }
556 else if (m_d->nativeGestureShortcut) {
557 m_d->nativeGestureShortcut->action()->inputEvent( event );
558 retval = true;
559 }
560
561 return retval;
562}
563
564bool KisShortcutMatcher::nativeGestureEndEvent(QNativeGestureEvent *event)
565{
566 Private::RecursionNotifier notifier(this);
567
568 if ( m_d->nativeGestureShortcut && !m_d->nativeGestureShortcut->match( event ) ) {
570 }
571
572 if (notifier.isInRecursion()) {
574 } else if (!hasRunningShortcut()) {
577 }
578
579 return true;
580}
581
582Qt::MouseButtons listToFlags(const QList<Qt::MouseButton> &list) {
583 Qt::MouseButtons flags;
584 Q_FOREACH (Qt::MouseButton b, list) {
585 flags |= b;
586 }
587 return flags;
588}
589
591{
592 Private::RecursionNotifier notifier(this);
593
594
595 reset("reinitialize");
596
597 if (notifier.isInRecursion()) {
599 } else if (!hasRunningShortcut()) {
602 }
603}
604
606{
607 Private::RecursionNotifier notifier(this);
608
609 m_d->buttons.clear();
610 DEBUG_ACTION("reinitializing buttons");
611
612 if (notifier.isInRecursion()) {
614 } else if (!hasRunningShortcut()) {
617 }
618}
619
621{
622 Q_FOREACH (Qt::Key key, m_d->keys) {
623 if (!keys.contains(key)) {
624 keyReleased(key);
625 }
626 }
627
628 Q_FOREACH (Qt::Key key, keys) {
629 if (!m_d->keys.contains(key)) {
630 keyPressed(key);
631 m_d->polledKeys << key;
632 }
633 }
634
635 Private::RecursionNotifier notifier(this);
636
637 if (notifier.isInRecursion()) {
639 } else if (!hasRunningShortcut()) {
642 }
643
644 DEBUG_ACTION("recoverySyncModifiers");
645}
646
647bool KisShortcutMatcher::sanityCheckModifiersCorrectness(Qt::KeyboardModifiers modifiers) const
648{
649 auto checkKey = [this, modifiers] (Qt::Key key, Qt::KeyboardModifier modifier) {
650 return m_d->keys.contains(key) == bool(modifiers & modifier);
651 };
652
653 return checkKey(Qt::Key_Shift, Qt::ShiftModifier) &&
654 checkKey(Qt::Key_Control, Qt::ControlModifier) &&
655 checkKey(Qt::Key_Alt, Qt::AltModifier) &&
656 checkKey(Qt::Key_Meta, Qt::MetaModifier);
657
658}
659
661{
663 std::copy(m_d->keys.begin(), m_d->keys.end(), std::back_inserter(keys));
664 return keys;
665}
666
668{
669 return !m_d->polledKeys.empty();
670}
671
672void KisShortcutMatcher::lostFocusEvent(const QPointF &localPos)
673{
674 Private::RecursionNotifier notifier(this);
675
676 DEBUG_ACTION("lostFocusEvent");
677
678 if (m_d->runningShortcut) {
679 forceEndRunningShortcut(localPos);
680 }
681
683
691}
692
694{
695 Private::RecursionNotifier notifier(this);
696
697 DEBUG_ACTION("toolHasBeenActivated");
698
699 if (notifier.isInRecursion()) {
701 } else if (!hasRunningShortcut()) {
704 }
705}
706
708{
709 m_d->keys.clear();
710 m_d->buttons.clear();
711 DEBUG_ACTION("reset!");
712}
713
714
716{
717 m_d->keys.clear();
718 m_d->buttons.clear();
719 Q_UNUSED(msg);
720 DEBUG_ACTION(msg);
721}
722
724{
725 m_d->suppressAllActions = value;
726}
727
729{
730 m_d->suppressedSingleActionShortcuts.clear();
731
732 Q_FOREACH (KisSingleActionShortcut *s, m_d->singleActionShortcuts) {
733 Q_FOREACH (const QKeySequence &seq, shortcuts) {
734 if (s->conflictsWith(seq)) {
735 m_d->suppressedSingleActionShortcuts.insert(s);
736 }
737 }
738 }
739}
740
742{
743 m_d->suppressAllKeyboardActions = value;
744}
745
747{
748 reset("Clearing shortcuts");
749 qDeleteAll(m_d->singleActionShortcuts);
750 m_d->singleActionShortcuts.clear();
751 qDeleteAll(m_d->strokeShortcuts);
752 qDeleteAll(m_d->touchShortcuts);
753 m_d->strokeShortcuts.clear();
754 m_d->candidateShortcuts.clear();
755 m_d->touchShortcuts.clear();
756 m_d->runningShortcut = 0;
757 m_d->readyShortcut = 0;
758}
759
760void KisShortcutMatcher::setInputActionGroupsMaskCallback(std::function<KisInputActionGroupsMask ()> func)
761{
762 m_d->actionGroupMask = func;
763}
764
766{
767 return tryRunSingleActionShortcutImpl(wheelAction, event, m_d->keys, false);
768}
769
770// Note: sometimes event can be zero!!
771template<typename T, typename U>
772bool KisShortcutMatcher::tryRunSingleActionShortcutImpl(T param, U *event, const QSet<Qt::Key> &keysState, bool keyboard)
773{
774 if (m_d->actionsSuppressedIgnoreFocus() || (keyboard && m_d->KeyboardActionsSuppressed())) {
775 DEBUG_EVENT_ACTION("Event suppressed", event)
776 return false;
777 }
778
779 KisSingleActionShortcut *goodCandidate = 0;
780
781 Q_FOREACH (KisSingleActionShortcut *s, m_d->singleActionShortcuts) {
782 if (!m_d->suppressedSingleActionShortcuts.contains(s) &&
783 s->isAvailable(m_d->actionGroupMask()) &&
784 s->match(keysState, param) &&
785 (!goodCandidate || s->priority() > goodCandidate->priority())) {
786
787 goodCandidate = s;
788 }
789 }
790
791 if (goodCandidate) {
792 DEBUG_EVENT_ACTION("Beginning action for event", event);
793 goodCandidate->action()->begin(goodCandidate->shortcutIndex(), event);
794 goodCandidate->action()->end(0);
795 } else {
796 DEBUG_EVENT_ACTION("Could not match a candidate for event", event)
797 }
798
799 return goodCandidate;
800}
801
803{
804 m_d->candidateShortcuts.clear();
805 if (m_d->actionsSuppressed()) return;
806
807 // Allow letting the modifiers to be matched so key_shift + middle mouse move can be matched, but key_v + mouse drag can not.
808 bool containsOnlyModifiers = !m_d->keys.isEmpty();
809 Q_FOREACH(const Qt::Key k, m_d->keys) {
810 if (k != Qt::Key_Shift && k != Qt::Key_Control && k != Qt::Key_Alt && k != Qt::Key_Meta) {
811 containsOnlyModifiers = false;
812 break;
813 }
814 }
815 if (m_d->KeyboardActionsSuppressed()
816 && !containsOnlyModifiers && !m_d->keys.isEmpty()
817 && m_d->buttons.isEmpty()) {
818 return;
819 }
820
821 Q_FOREACH (KisStrokeShortcut *s, m_d->strokeShortcuts) {
822 if (s->matchReady(m_d->keys, m_d->buttons)) {
823 m_d->candidateShortcuts.append(s);
824 }
825 }
826}
827
828bool KisShortcutMatcher::tryRunReadyShortcut( Qt::MouseButton button, QEvent* event )
829{
830 KisStrokeShortcut *goodCandidate = 0;
831
832 Q_FOREACH (KisStrokeShortcut *s, m_d->candidateShortcuts) {
833 if (s->isAvailable(m_d->actionGroupMask()) &&
834 s->matchBegin(button) &&
835 (!goodCandidate || s->priority() > goodCandidate->priority())) {
836
837 goodCandidate = s;
838 }
839 }
840
841 if (goodCandidate) {
842 if (m_d->readyShortcut) {
843 if (m_d->readyShortcut != goodCandidate) {
844 m_d->readyShortcut->action()->deactivate(m_d->readyShortcut->shortcutIndex());
845 goodCandidate->action()->activate(goodCandidate->shortcutIndex());
846 }
847 m_d->readyShortcut = 0;
848 } else {
849 DEBUG_EVENT_ACTION("Matched *new* shortcut for event", event);
850 goodCandidate->action()->activate(goodCandidate->shortcutIndex());
851 }
852
853 DEBUG_SHORTCUT("Starting new action", goodCandidate);
854
855 {
856 m_d->runningShortcut = goodCandidate;
857 Private::RecursionGuard guard(this);
858 goodCandidate->action()->begin(goodCandidate->shortcutIndex(), event);
859
860 // the tool might have opened some dialog, which could break our event loop
861 if (guard.brokenByRecursion()) {
862 goodCandidate->action()->end(event);
863 m_d->runningShortcut = 0;
864
866 }
867 }
868 }
869
870 return m_d->runningShortcut;
871}
872
874{
875 KisStrokeShortcut *goodCandidate = 0;
876
877 Q_FOREACH (KisStrokeShortcut *s, m_d->candidateShortcuts) {
878 if (!goodCandidate || s->priority() > goodCandidate->priority()) {
879 goodCandidate = s;
880 }
881 }
882
883 if (goodCandidate) {
884 if (m_d->readyShortcut && m_d->readyShortcut != goodCandidate) {
885 DEBUG_SHORTCUT("Deactivated previous shortcut action", m_d->readyShortcut);
886 m_d->readyShortcut->action()->deactivate(m_d->readyShortcut->shortcutIndex());
887 m_d->readyShortcut = 0;
888 }
889
890 if (!m_d->readyShortcut) {
891 DEBUG_SHORTCUT("Preparing new ready action", goodCandidate);
892
900 goodCandidate->action()->activate(goodCandidate->shortcutIndex());
901 m_d->readyShortcut = goodCandidate;
902 }
903 } else if (m_d->readyShortcut) {
904 DEBUG_SHORTCUT("Deactivating action", m_d->readyShortcut);
905 m_d->readyShortcut->action()->deactivate(m_d->readyShortcut->shortcutIndex());
906 m_d->readyShortcut = 0;
907 }
908}
909
910bool KisShortcutMatcher::tryEndRunningShortcut( Qt::MouseButton button, QEvent* event )
911{
912 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(m_d->runningShortcut, true);
913 KIS_SAFE_ASSERT_RECOVER(!m_d->readyShortcut) {
914 // it shouldn't have happened, running and ready shortcuts
915 // at the same time should not be possible
917 }
918
919 if (m_d->runningShortcut && m_d->runningShortcut->matchBegin(button)) {
920
921 // first reset running shortcut to avoid infinite recursion via end()
922 KisStrokeShortcut *runningShortcut = m_d->runningShortcut;
923 m_d->runningShortcut = 0;
924
925 if (runningShortcut->action()) {
926 DEBUG_EVENT_ACTION("Ending running shortcut at event", event);
928 int shortcutIndex = runningShortcut->shortcutIndex();
929 action->end(event);
930 action->deactivate(shortcutIndex);
931 }
932 }
933
934 return !m_d->runningShortcut;
935}
936
938{
939 KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->runningShortcut);
940 KIS_SAFE_ASSERT_RECOVER(!m_d->readyShortcut) {
941 // it shouldn't have happened, running and ready shortcuts
942 // at the same time should not be possible
944 }
945
946 // first reset running shortcut to avoid infinite recursion via end()
947 KisStrokeShortcut *runningShortcut = m_d->runningShortcut;
948 m_d->runningShortcut = 0;
949
950 if (runningShortcut->action()) {
951 DEBUG_ACTION("Forced ending running shortcut at event");
953 int shortcutIndex = runningShortcut->shortcutIndex();
954
955 QMouseEvent event = runningShortcut->fakeEndEvent(localPos);
956
957 action->end(&event);
958 action->deactivate(shortcutIndex);
959 }
960}
961
963{
964 if (m_d->readyShortcut) {
965 DEBUG_SHORTCUT("Forcefully deactivating action", m_d->readyShortcut);
966 m_d->readyShortcut->action()->deactivate(m_d->readyShortcut->shortcutIndex());
967 m_d->readyShortcut = 0;
968 }
969}
970
972{
973 int touchPointCount = event->touchPoints().size();
974 if (touchPointCount > m_d->maxTouchPoints) {
975 m_d->maxTouchPoints = touchPointCount;
976#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
977 KoPointerEvent::copyQtPointerEvent(event, m_d->bestCandidateTouchEvent);
978#else
979 m_d->bestCandidateTouchEvent.reset(event->clone());
980#endif
981
982 }
983}
984
986{
987 KisTouchShortcut *goodCandidate = matchTouchShortcut(event);
988 if (goodCandidate) {
989 DEBUG_TOUCH_ACTION("starting", event)
990 goodCandidate->action()->activate(goodCandidate->shortcutIndex());
991 goodCandidate->action()->begin(goodCandidate->shortcutIndex(), event);
992
993 goodCandidate->action()->end(event);
994 goodCandidate->action()->deactivate(goodCandidate->shortcutIndex());
995 }
996}
997
999{
1000 KisTouchShortcut *goodCandidate = nullptr;
1001 Q_FOREACH (KisTouchShortcut *shortcut, m_d->touchShortcuts) {
1002 // if the type of the action is drag, check if we match with drag type and if the type is tap, check if we match
1003 // with tap type.
1004 if (shortcut->isAvailable(m_d->actionGroupMask())
1005 && ((shortcut->matchDragType(event) && m_d->isTouchDragDetected)
1006 || (shortcut->matchTapType(event) && !m_d->isTouchDragDetected))
1007 && (!goodCandidate || shortcut->priority() > goodCandidate->priority())) {
1008
1009 goodCandidate = shortcut;
1010 }
1011 }
1012 return goodCandidate;
1013}
1014
1016{
1017 KisTouchShortcut *goodCandidate = matchTouchShortcut(event);
1018
1019 if (m_d->actionsSuppressed())
1020 return false;
1021
1022 if (goodCandidate) {
1023 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!m_d->runningShortcut, false);
1024
1025 // Because we don't match keyboard or button based actions with touch system, we have to ensure that we first
1026 // deactivate an activated readyShortcut, to not throw other statemachines out of place.
1028
1029 m_d->touchShortcut = goodCandidate;
1030
1031 Private::RecursionGuard guard(this);
1032 DEBUG_SHORTCUT("Running a touch shortcut", goodCandidate)
1033
1034 goodCandidate->action()->activate(goodCandidate->shortcutIndex());
1035 goodCandidate->action()->begin(goodCandidate->shortcutIndex(), event);
1036
1037 // the tool might have opened some dialog, which could break our event loop
1038 if (guard.brokenByRecursion()) {
1039 goodCandidate->action()->end(event);
1040 m_d->touchShortcut = 0;
1041
1043 }
1044 }
1045
1046 return m_d->touchShortcut;
1047}
1048
1050{
1051 if (m_d->touchShortcut) {
1052 // first reset running shortcut to avoid infinite recursion via end()
1053 KisTouchShortcut *touchShortcut = m_d->touchShortcut;
1054
1055 DEBUG_SHORTCUT("ending", touchShortcut)
1056 touchShortcut->action()->end(event);
1057 touchShortcut->action()->deactivate(m_d->touchShortcut->shortcutIndex());
1058
1059 m_d->touchShortcut = 0; // empty it out now that we are done with it
1060
1061 return true;
1062 }
1063
1064 return false;
1065}
1066
1068{
1069 KisNativeGestureShortcut *goodCandidate = 0;
1070
1071 if (m_d->actionsSuppressed())
1072 return false;
1073
1074 Q_FOREACH (KisNativeGestureShortcut* shortcut, m_d->nativeGestureShortcuts) {
1075 if (shortcut->match(event) && (!goodCandidate || shortcut->priority() > goodCandidate->priority())) {
1076 goodCandidate = shortcut;
1077 }
1078 }
1079
1080 if (goodCandidate) {
1081 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!m_d->runningShortcut, false);
1082
1083 // Because we don't match keyboard or button based actions with touch system, we have to ensure that we first
1084 // deactivate an activated readyShortcut, to not throw other statemachines out of place.
1086
1087 m_d->nativeGestureShortcut = goodCandidate;
1088
1089 Private::RecursionGuard guard(this);
1090 goodCandidate->action()->activate(goodCandidate->shortcutIndex());
1091 goodCandidate->action()->begin(goodCandidate->shortcutIndex(), event);
1092
1093 // the tool might have opened some dialog, which could break our event loop
1094 if (guard.brokenByRecursion()) {
1095 goodCandidate->action()->end(event);
1096 m_d->nativeGestureShortcut = 0;
1097
1099 }
1100 }
1101
1102 return m_d->nativeGestureShortcut;
1103}
1104
1106{
1107 Private::RecursionNotifier notifier(this);
1108
1109 if (m_d->nativeGestureShortcut) {
1110 // first reset running shortcut to avoid infinite recursion via end()
1111 KisNativeGestureShortcut *nativeGestureShortcut = m_d->nativeGestureShortcut;
1112
1113 nativeGestureShortcut->action()->end(event);
1114 nativeGestureShortcut->action()->deactivate(m_d->nativeGestureShortcut->shortcutIndex());
1115
1116 m_d->nativeGestureShortcut = 0; // empty it out now that we are done with it
1117
1118 return true;
1119 }
1120
1121 if (notifier.isInRecursion()) {
1123 } else if (!hasRunningShortcut()) {
1126 }
1127
1128 return false;
1129}
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)
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)
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 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)
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)