diff --git a/libs/ui/input/kis_shortcut_matcher.cpp b/libs/ui/input/kis_shortcut_matcher.cpp index 183149d8e7..84767b0276 100644 --- a/libs/ui/input/kis_shortcut_matcher.cpp +++ b/libs/ui/input/kis_shortcut_matcher.cpp @@ -1,690 +1,828 @@ /* * Copyright (c) 2012 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_shortcut_matcher.h" #include #include #include #include "kis_assert.h" #include "kis_abstract_input_action.h" #include "kis_stroke_shortcut.h" #include "kis_touch_shortcut.h" #include "kis_native_gesture_shortcut.h" #include "kis_config.h" #ifdef DEBUG_MATCHER #include #define DEBUG_ACTION(text) dbgInput << __FUNCTION__ << "-" << text; #define DEBUG_SHORTCUT(text, shortcut) dbgInput << __FUNCTION__ << "-" << text << "act:" << shortcut->action()->name(); #define DEBUG_KEY(text) dbgInput << __FUNCTION__ << "-" << text << "keys:" << m_d->keys; #define DEBUG_BUTTON_ACTION(text, button) dbgInput << __FUNCTION__ << "-" << text << "button:" << button << "btns:" << m_d->buttons << "keys:" << m_d->keys; #define DEBUG_EVENT_ACTION(text, event) if (event) {dbgInput << __FUNCTION__ << "-" << text << "type:" << event->type();} #else #define DEBUG_ACTION(text) #define DEBUG_KEY(text) #define DEBUG_SHORTCUT(text, shortcut) #define DEBUG_BUTTON_ACTION(text, button) #define DEBUG_EVENT_ACTION(text, event) #endif class Q_DECL_HIDDEN KisShortcutMatcher::Private { public: Private() : runningShortcut(0) , readyShortcut(0) , touchShortcut(0) , nativeGestureShortcut(0) , actionGroupMask([] () { return AllActionGroup; }) , suppressAllActions(false) , cursorEntered(false) , usingTouch(false) , usingNativeGesture(false) {} ~Private() { qDeleteAll(singleActionShortcuts); qDeleteAll(strokeShortcuts); qDeleteAll(touchShortcuts); } QList singleActionShortcuts; QList strokeShortcuts; QList touchShortcuts; QList nativeGestureShortcuts; QSet keys; // Model of currently pressed keys QSet buttons; // Model of currently pressed buttons KisStrokeShortcut *runningShortcut; KisStrokeShortcut *readyShortcut; QList candidateShortcuts; KisTouchShortcut *touchShortcut; KisNativeGestureShortcut *nativeGestureShortcut; std::function actionGroupMask; bool suppressAllActions; bool cursorEntered; bool usingTouch; bool usingNativeGesture; + int recursiveCounter = 0; + int brokenByRecursion = 0; + + + struct RecursionNotifier { + RecursionNotifier(KisShortcutMatcher *_q) + : q(_q) + { + q->m_d->recursiveCounter++; + q->m_d->brokenByRecursion++; + } + + ~RecursionNotifier() { + q->m_d->recursiveCounter--; + } + + bool isInRecursion() const { + return q->m_d->recursiveCounter > 1; + } + + KisShortcutMatcher *q; + }; + + struct RecursionGuard { + RecursionGuard(KisShortcutMatcher *_q) + : q(_q) + { + q->m_d->brokenByRecursion = 0; + } + + ~RecursionGuard() { + } + + bool brokenByRecursion() const { + return q->m_d->brokenByRecursion > 0; + } + + KisShortcutMatcher *q; + }; + inline bool actionsSuppressed() const { return (suppressAllActions || !cursorEntered) && KisConfig(true).disableTouchOnCanvas(); } inline bool actionsSuppressedIgnoreFocus() const { return suppressAllActions; } // only for touch events with touchPoints count >= 2 inline bool isUsingTouch() const { return usingTouch || usingNativeGesture; } }; KisShortcutMatcher::KisShortcutMatcher() : m_d(new Private) {} KisShortcutMatcher::~KisShortcutMatcher() { delete m_d; } bool KisShortcutMatcher::hasRunningShortcut() const { return m_d->runningShortcut; } void KisShortcutMatcher::addShortcut(KisSingleActionShortcut *shortcut) { m_d->singleActionShortcuts.append(shortcut); } void KisShortcutMatcher::addShortcut(KisStrokeShortcut *shortcut) { m_d->strokeShortcuts.append(shortcut); } void KisShortcutMatcher::addShortcut( KisTouchShortcut* shortcut ) { m_d->touchShortcuts.append(shortcut); } void KisShortcutMatcher::addShortcut(KisNativeGestureShortcut *shortcut) { m_d->nativeGestureShortcuts.append(shortcut); } bool KisShortcutMatcher::supportsHiResInputEvents() { return m_d->runningShortcut && m_d->runningShortcut->action() && m_d->runningShortcut->action()->supportsHiResInputEvents(); } bool KisShortcutMatcher::keyPressed(Qt::Key key) { + Private::RecursionNotifier notifier(this); + bool retval = false; if (m_d->keys.contains(key)) { DEBUG_ACTION("Peculiar, records show key was already pressed"); } - if (!m_d->runningShortcut) { + if (!m_d->runningShortcut && !notifier.isInRecursion()) { retval = tryRunSingleActionShortcutImpl(key, (QEvent*)0, m_d->keys); } m_d->keys.insert(key); DEBUG_KEY("Pressed"); - if (!m_d->runningShortcut) { + if (notifier.isInRecursion()) { + forceDeactivateAllActions(); + } else if (!m_d->runningShortcut) { prepareReadyShortcuts(); tryActivateReadyShortcut(); } return retval; } bool KisShortcutMatcher::autoRepeatedKeyPressed(Qt::Key key) { + Private::RecursionNotifier notifier(this); + + bool retval = false; if (!m_d->keys.contains(key)) { DEBUG_ACTION("Peculiar, autorepeated key but can't remember it was pressed"); } - if (!m_d->runningShortcut) { + if (notifier.isInRecursion()) { + forceDeactivateAllActions(); + } else if (!m_d->runningShortcut) { // Autorepeated key should not be included in the shortcut QSet filteredKeys = m_d->keys; filteredKeys.remove(key); retval = tryRunSingleActionShortcutImpl(key, (QEvent*)0, filteredKeys); } return retval; } bool KisShortcutMatcher::keyReleased(Qt::Key key) { + Private::RecursionNotifier notifier(this); + if (!m_d->keys.contains(key)) reset("Peculiar, key released but can't remember it was pressed"); else m_d->keys.remove(key); - if (!m_d->runningShortcut) { + if (notifier.isInRecursion()) { + forceDeactivateAllActions(); + } else if (!m_d->runningShortcut) { prepareReadyShortcuts(); tryActivateReadyShortcut(); } return false; } bool KisShortcutMatcher::buttonPressed(Qt::MouseButton button, QEvent *event) { + Private::RecursionNotifier notifier(this); DEBUG_BUTTON_ACTION("entered", button); bool retval = false; if (m_d->isUsingTouch()) { return retval; } if (m_d->buttons.contains(button)) { DEBUG_ACTION("Peculiar, button was already pressed."); } - if (!hasRunningShortcut()) { + if (!m_d->runningShortcut && !notifier.isInRecursion()) { prepareReadyShortcuts(); retval = tryRunReadyShortcut(button, event); } m_d->buttons.insert(button); - if (!hasRunningShortcut()) { + if (notifier.isInRecursion()) { + forceDeactivateAllActions(); + } else if (!m_d->runningShortcut) { prepareReadyShortcuts(); tryActivateReadyShortcut(); } return retval; } bool KisShortcutMatcher::buttonReleased(Qt::MouseButton button, QEvent *event) { + Private::RecursionNotifier notifier(this); DEBUG_BUTTON_ACTION("entered", button); bool retval = false; if (m_d->isUsingTouch()) { return retval; } - if (m_d->runningShortcut && !m_d->readyShortcut) { + if (m_d->runningShortcut) { + KIS_SAFE_ASSERT_RECOVER_NOOP(!notifier.isInRecursion()); + retval = tryEndRunningShortcut(button, event); DEBUG_BUTTON_ACTION("ended", button); } if (!m_d->buttons.contains(button)) reset("Peculiar, button released but we can't remember it was pressed"); else m_d->buttons.remove(button); - if (!m_d->runningShortcut) { + if (notifier.isInRecursion()) { + forceDeactivateAllActions(); + } else if (!m_d->runningShortcut) { prepareReadyShortcuts(); tryActivateReadyShortcut(); } return retval; } bool KisShortcutMatcher::wheelEvent(KisSingleActionShortcut::WheelAction wheelAction, QWheelEvent *event) { - if (m_d->runningShortcut || m_d->isUsingTouch()) { + Private::RecursionNotifier notifier(this); + + + if (m_d->runningShortcut || m_d->isUsingTouch() || notifier.isInRecursion()) { DEBUG_ACTION("Wheel event canceled."); return false; } return tryRunWheelShortcut(wheelAction, event); } bool KisShortcutMatcher::pointerMoved(QEvent *event) { - if (m_d->isUsingTouch() || !m_d->runningShortcut) { + Private::RecursionNotifier notifier(this); + + + if (m_d->isUsingTouch() || !m_d->runningShortcut || notifier.isInRecursion()) { return false; } m_d->runningShortcut->action()->inputEvent(event); return true; } void KisShortcutMatcher::enterEvent() { + Private::RecursionNotifier notifier(this); + m_d->cursorEntered = true; - if (!m_d->runningShortcut) { + if (notifier.isInRecursion()) { + forceDeactivateAllActions(); + } else if (!m_d->runningShortcut) { prepareReadyShortcuts(); tryActivateReadyShortcut(); } } void KisShortcutMatcher::leaveEvent() { + Private::RecursionNotifier notifier(this); + m_d->cursorEntered = false; - if (!m_d->runningShortcut) { + if (notifier.isInRecursion()) { + forceDeactivateAllActions(); + } else if (!m_d->runningShortcut) { prepareReadyShortcuts(); tryActivateReadyShortcut(); } } bool KisShortcutMatcher::touchBeginEvent( QTouchEvent* event ) { Q_UNUSED(event) - return true; + + Private::RecursionNotifier notifier(this); + + return !notifier.isInRecursion(); } bool KisShortcutMatcher::touchUpdateEvent( QTouchEvent* event ) { bool retval = false; if (m_d->touchShortcut && !m_d->touchShortcut->match( event ) ) { retval = tryEndTouchShortcut( event ); } if (!m_d->touchShortcut ) { retval = tryRunTouchShortcut( event ); } else { m_d->touchShortcut->action()->inputEvent( event ); retval = true; } return retval; } bool KisShortcutMatcher::touchEndEvent( QTouchEvent* event ) { m_d->usingTouch = false; // we need to say we are done because qt will not send further event // we should try and end the shortcut too (it might be that there is none? (sketch)) if (tryEndTouchShortcut(event)) { return true; } return false; } bool KisShortcutMatcher::nativeGestureBeginEvent(QNativeGestureEvent *event) { Q_UNUSED(event) - return true; + + Private::RecursionNotifier notifier(this); + + return !notifier.isInRecursion(); } bool KisShortcutMatcher::nativeGestureEvent(QNativeGestureEvent *event) { bool retval = false; if ( !m_d->nativeGestureShortcut ) { retval = tryRunNativeGestureShortcut( event ); } else { m_d->nativeGestureShortcut->action()->inputEvent( event ); retval = true; } return retval; } bool KisShortcutMatcher::nativeGestureEndEvent(QNativeGestureEvent *event) { if ( m_d->nativeGestureShortcut && !m_d->nativeGestureShortcut->match( event ) ) { tryEndNativeGestureShortcut( event ); } m_d->usingNativeGesture = false; return true; } Qt::MouseButtons listToFlags(const QList &list) { Qt::MouseButtons flags; Q_FOREACH (Qt::MouseButton b, list) { flags |= b; } return flags; } void KisShortcutMatcher::reinitialize() { + Private::RecursionNotifier notifier(this); + + reset("reinitialize"); - if (!m_d->runningShortcut) { + + if (notifier.isInRecursion()) { + forceDeactivateAllActions(); + } else if (!m_d->runningShortcut) { prepareReadyShortcuts(); tryActivateReadyShortcut(); } } void KisShortcutMatcher::recoveryModifiersWithoutFocus(const QVector &keys) { + Private::RecursionNotifier notifier(this); + + Q_FOREACH (Qt::Key key, m_d->keys) { if (!keys.contains(key)) { keyReleased(key); } } Q_FOREACH (Qt::Key key, keys) { if (!m_d->keys.contains(key)) { keyPressed(key); } } - if (!m_d->runningShortcut) { + if (notifier.isInRecursion()) { + forceDeactivateAllActions(); + } else if (!m_d->runningShortcut) { prepareReadyShortcuts(); tryActivateReadyShortcut(); } DEBUG_ACTION("recoverySyncModifiers"); } void KisShortcutMatcher::lostFocusEvent(const QPointF &localPos) { if (m_d->runningShortcut) { forceEndRunningShortcut(localPos); } } void KisShortcutMatcher::reset() { m_d->keys.clear(); m_d->buttons.clear(); DEBUG_ACTION("reset!"); } void KisShortcutMatcher::reset(QString msg) { m_d->keys.clear(); m_d->buttons.clear(); Q_UNUSED(msg); DEBUG_ACTION(msg); } void KisShortcutMatcher::suppressAllActions(bool value) { m_d->suppressAllActions = value; } void KisShortcutMatcher::clearShortcuts() { reset("Clearing shortcuts"); qDeleteAll(m_d->singleActionShortcuts); m_d->singleActionShortcuts.clear(); qDeleteAll(m_d->strokeShortcuts); qDeleteAll(m_d->touchShortcuts); m_d->strokeShortcuts.clear(); m_d->candidateShortcuts.clear(); m_d->touchShortcuts.clear(); m_d->runningShortcut = 0; m_d->readyShortcut = 0; } void KisShortcutMatcher::setInputActionGroupsMaskCallback(std::function func) { m_d->actionGroupMask = func; } bool KisShortcutMatcher::tryRunWheelShortcut(KisSingleActionShortcut::WheelAction wheelAction, QWheelEvent *event) { return tryRunSingleActionShortcutImpl(wheelAction, event, m_d->keys); } // Note: sometimes event can be zero!! template bool KisShortcutMatcher::tryRunSingleActionShortcutImpl(T param, U *event, const QSet &keysState) { if (m_d->actionsSuppressedIgnoreFocus()) { DEBUG_EVENT_ACTION("Event suppressed", event) return false; } KisSingleActionShortcut *goodCandidate = 0; Q_FOREACH (KisSingleActionShortcut *s, m_d->singleActionShortcuts) { if(s->isAvailable(m_d->actionGroupMask()) && s->match(keysState, param) && (!goodCandidate || s->priority() > goodCandidate->priority())) { goodCandidate = s; } } if (goodCandidate) { - DEBUG_EVENT_ACTION("Beginning action for event", event) + DEBUG_EVENT_ACTION("Beginning action for event", event); goodCandidate->action()->begin(goodCandidate->shortcutIndex(), event); goodCandidate->action()->end(0); } else { DEBUG_EVENT_ACTION("Could not match a candidate for event", event) } return goodCandidate; } void KisShortcutMatcher::prepareReadyShortcuts() { m_d->candidateShortcuts.clear(); if (m_d->actionsSuppressed()) return; Q_FOREACH (KisStrokeShortcut *s, m_d->strokeShortcuts) { if (s->matchReady(m_d->keys, m_d->buttons)) { m_d->candidateShortcuts.append(s); } } } bool KisShortcutMatcher::tryRunReadyShortcut( Qt::MouseButton button, QEvent* event ) { KisStrokeShortcut *goodCandidate = 0; Q_FOREACH (KisStrokeShortcut *s, m_d->candidateShortcuts) { if (s->isAvailable(m_d->actionGroupMask()) && s->matchBegin(button) && (!goodCandidate || s->priority() > goodCandidate->priority())) { goodCandidate = s; } } if (goodCandidate) { if (m_d->readyShortcut) { if (m_d->readyShortcut != goodCandidate) { m_d->readyShortcut->action()->deactivate(m_d->readyShortcut->shortcutIndex()); goodCandidate->action()->activate(goodCandidate->shortcutIndex()); } m_d->readyShortcut = 0; } else { DEBUG_EVENT_ACTION("Matched *new* shortcut for event", event); goodCandidate->action()->activate(goodCandidate->shortcutIndex()); } DEBUG_SHORTCUT("Starting new action", goodCandidate); - goodCandidate->action()->begin(goodCandidate->shortcutIndex(), event); - m_d->runningShortcut = goodCandidate; + + { + m_d->runningShortcut = goodCandidate; + Private::RecursionGuard guard(this); + goodCandidate->action()->begin(goodCandidate->shortcutIndex(), event); + + // the tool migh have opened some dialog, which could break our event loop + if (guard.brokenByRecursion()) { + goodCandidate->action()->end(event); + m_d->runningShortcut = 0; + + forceDeactivateAllActions(); + } + } } - return goodCandidate; + return m_d->runningShortcut; } void KisShortcutMatcher::tryActivateReadyShortcut() { KisStrokeShortcut *goodCandidate = 0; Q_FOREACH (KisStrokeShortcut *s, m_d->candidateShortcuts) { if (!goodCandidate || s->priority() > goodCandidate->priority()) { goodCandidate = s; } } if (goodCandidate) { if (m_d->readyShortcut && m_d->readyShortcut != goodCandidate) { DEBUG_SHORTCUT("Deactivated previous shortcut action", m_d->readyShortcut); m_d->readyShortcut->action()->deactivate(m_d->readyShortcut->shortcutIndex()); m_d->readyShortcut = 0; } if (!m_d->readyShortcut) { DEBUG_SHORTCUT("Preparing new ready action", goodCandidate); goodCandidate->action()->activate(goodCandidate->shortcutIndex()); m_d->readyShortcut = goodCandidate; } } else if (m_d->readyShortcut) { DEBUG_SHORTCUT("Deactivating action", m_d->readyShortcut); m_d->readyShortcut->action()->deactivate(m_d->readyShortcut->shortcutIndex()); m_d->readyShortcut = 0; } } bool KisShortcutMatcher::tryEndRunningShortcut( Qt::MouseButton button, QEvent* event ) { - Q_ASSERT(m_d->runningShortcut); - Q_ASSERT(!m_d->readyShortcut); + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(m_d->runningShortcut, true); + KIS_SAFE_ASSERT_RECOVER(!m_d->readyShortcut) { + // it shouldn't have happened, running and ready shortcuts + // at the same time should not be possible + forceDeactivateAllActions(); + } if (m_d->runningShortcut->matchBegin(button)) { // first reset running shortcut to avoid infinite recursion via end() KisStrokeShortcut *runningShortcut = m_d->runningShortcut; m_d->runningShortcut = 0; if (runningShortcut->action()) { DEBUG_EVENT_ACTION("Ending running shortcut at event", event); KisAbstractInputAction* action = runningShortcut->action(); int shortcutIndex = runningShortcut->shortcutIndex(); action->end(event); action->deactivate(shortcutIndex); } } return !m_d->runningShortcut; } void KisShortcutMatcher::forceEndRunningShortcut(const QPointF &localPos) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->runningShortcut); - KIS_SAFE_ASSERT_RECOVER_RETURN(!m_d->readyShortcut); + KIS_SAFE_ASSERT_RECOVER(!m_d->readyShortcut) { + // it shouldn't have happened, running and ready shortcuts + // at the same time should not be possible + forceDeactivateAllActions(); + } // first reset running shortcut to avoid infinite recursion via end() KisStrokeShortcut *runningShortcut = m_d->runningShortcut; m_d->runningShortcut = 0; if (runningShortcut->action()) { DEBUG_ACTION("Forced ending running shortcut at event"); KisAbstractInputAction* action = runningShortcut->action(); int shortcutIndex = runningShortcut->shortcutIndex(); QMouseEvent event = runningShortcut->fakeEndEvent(localPos); action->end(&event); action->deactivate(shortcutIndex); } } +void KisShortcutMatcher::forceDeactivateAllActions() +{ + if (m_d->readyShortcut) { + DEBUG_SHORTCUT("Forcefully deactivating action", m_d->readyShortcut); + m_d->readyShortcut->action()->deactivate(m_d->readyShortcut->shortcutIndex()); + m_d->readyShortcut = 0; + } +} + bool KisShortcutMatcher::tryRunTouchShortcut( QTouchEvent* event ) { KisTouchShortcut *goodCandidate = 0; if (m_d->actionsSuppressed()) return false; Q_FOREACH (KisTouchShortcut* shortcut, m_d->touchShortcuts) { if (shortcut->isAvailable(m_d->actionGroupMask()) && shortcut->match( event ) && (!goodCandidate || shortcut->priority() > goodCandidate->priority()) ) { goodCandidate = shortcut; } } if( goodCandidate ) { if( m_d->runningShortcut ) { QTouchEvent touchEvent(QEvent::TouchEnd, event->device(), event->modifiers(), Qt::TouchPointReleased, event->touchPoints()); tryEndRunningShortcut(Qt::LeftButton, &touchEvent); } - goodCandidate->action()->activate(goodCandidate->shortcutIndex()); - goodCandidate->action()->begin(goodCandidate->shortcutIndex(), event); m_d->touchShortcut = goodCandidate; m_d->usingTouch = true; + + Private::RecursionGuard guard(this); + goodCandidate->action()->activate(goodCandidate->shortcutIndex()); + goodCandidate->action()->begin(goodCandidate->shortcutIndex(), event); + + // the tool migh have opened some dialog, which could break our event loop + if (guard.brokenByRecursion()) { + goodCandidate->action()->end(event); + m_d->touchShortcut = 0; + + forceDeactivateAllActions(); + } } - return goodCandidate; + return m_d->touchShortcut; } bool KisShortcutMatcher::tryEndTouchShortcut( QTouchEvent* event ) { if(m_d->touchShortcut) { // first reset running shortcut to avoid infinite recursion via end() KisTouchShortcut *touchShortcut = m_d->touchShortcut; touchShortcut->action()->end(event); touchShortcut->action()->deactivate(m_d->touchShortcut->shortcutIndex()); m_d->touchShortcut = 0; // empty it out now that we are done with it return true; } return false; } bool KisShortcutMatcher::tryRunNativeGestureShortcut(QNativeGestureEvent* event) { KisNativeGestureShortcut *goodCandidate = 0; if (m_d->actionsSuppressed()) return false; Q_FOREACH (KisNativeGestureShortcut* shortcut, m_d->nativeGestureShortcuts) { if (shortcut->match(event) && (!goodCandidate || shortcut->priority() > goodCandidate->priority())) { goodCandidate = shortcut; } } if (goodCandidate) { + m_d->nativeGestureShortcut = goodCandidate; + m_d->usingNativeGesture = true; + + Private::RecursionGuard guard(this); goodCandidate->action()->activate(goodCandidate->shortcutIndex()); goodCandidate->action()->begin(goodCandidate->shortcutIndex(), event); - m_d->nativeGestureShortcut = goodCandidate; - m_d->usingNativeGesture = true; + // the tool migh have opened some dialog, which could break our event loop + if (guard.brokenByRecursion()) { + goodCandidate->action()->end(event); + m_d->nativeGestureShortcut = 0; - return true; + forceDeactivateAllActions(); + } } - return false; + return m_d->nativeGestureShortcut; } bool KisShortcutMatcher::tryEndNativeGestureShortcut(QNativeGestureEvent* event) { if (m_d->nativeGestureShortcut) { // first reset running shortcut to avoid infinite recursion via end() KisNativeGestureShortcut *nativeGestureShortcut = m_d->nativeGestureShortcut; nativeGestureShortcut->action()->end(event); nativeGestureShortcut->action()->deactivate(m_d->nativeGestureShortcut->shortcutIndex()); m_d->nativeGestureShortcut = 0; // empty it out now that we are done with it return true; } return false; } diff --git a/libs/ui/input/kis_shortcut_matcher.h b/libs/ui/input/kis_shortcut_matcher.h index 58611cb093..2890165019 100644 --- a/libs/ui/input/kis_shortcut_matcher.h +++ b/libs/ui/input/kis_shortcut_matcher.h @@ -1,264 +1,265 @@ /* * Copyright (c) 2012 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_SHORTCUT_MATCHER_H #define __KIS_SHORTCUT_MATCHER_H #include #include "kis_single_action_shortcut.h" #include "KisInputActionGroup.h" #include class QEvent; class QWheelEvent; class QTouchEvent; class QNativeGestureEvent; class QString; class QPointF; class KisStrokeShortcut; class KisTouchShortcut; class KisNativeGestureShortcut; /** * The class that manages connections between shortcuts and actions. * * It processes input events and generates state transitions for the * actions basing on the data, represented by the shortcuts. * * The class works with two types of actions: long running * (represented by KisStrokeShortcuts) and "atomic" * (KisSingleActionShortcut). The former one invole some long * interaction with the user by means of a mouse cursor or a tablet, * the latter one simple action like "Zoom 100%" or "Reset Rotation". * * The single action shortcuts are handled quite easily. The matcher * listens to the events coming, manages two lists of the pressed keys * and buttons and when their content corresponds to some single * action shortcut it just runs this shortcut once. * * The strategy for handling the stroke shortcuts is a bit more * complex. Each such action may be in one of the three states: * * Idle <-> Ready <-> Running * * In "Idle" state the action is completely inactive and has no access * to the user * * When the action is in "Ready" state, it means that all the * modifiers for the action are already pressed and we are only * waiting for a user to press the mouse button and start a stroke. In * this state the action can show the user its Cursor to notify the user * what is going to happen next. * * In the "Running" state, the action has full access to the user * input and is considered to perform all the work it was created for. * * To implement such state transitions for the actions, * KisShortcutMatcher first forms a list of the actions which can be * moved to a ready state (m_d->readyShortcuts), then chooses the one * with the highest priority to be the only shortcut in the "Ready" * state and activates it (m_d->readyShortcut). Then when the user * presses the mouse button, the matcher looks through the list of * ready shortcuts, chooses which will be running now, deactivates (if * needed) currently activated action and starts the chosen one. * * \see KisSingleActionShortcut * \see KisStrokeShortcut */ class KRITAUI_EXPORT KisShortcutMatcher { public: KisShortcutMatcher(); ~KisShortcutMatcher(); bool hasRunningShortcut() const; void addShortcut(KisSingleActionShortcut *shortcut); void addShortcut(KisStrokeShortcut *shortcut); void addShortcut(KisTouchShortcut *shortcut); void addShortcut(KisNativeGestureShortcut *shortcut); /** * Returns true if the currently running shortcut supports * processing hi resolution flow of events from the tablet * device. In most of the cases (except of the painting itself) * too many events make the execution of the action too slow, so * the action can decide whether it needs it. */ bool supportsHiResInputEvents(); /** * Handles a key press event. * No autorepeat events should be passed to this method. * * \return whether the event has been handled successfully and * should be eaten by the events filter */ bool keyPressed(Qt::Key key); /** * Handles a key press event that has been generated by the * autorepeat. * * \return whether the event has been handled successfully and * should be eaten by the events filter */ bool autoRepeatedKeyPressed(Qt::Key key); /** * Handles a key release event. * No autorepeat events should be passed to this method. * * \return whether the event has been handled successfully and * should be eaten by the events filter */ bool keyReleased(Qt::Key key); /** * Handles button presses from a tablet or mouse. * * \param event the event that caused this call. * Must be of type QTabletEvent or QMouseEvent. * * \return whether the event has been handled successfully and * should be eaten by the events filter */ bool buttonPressed(Qt::MouseButton button, QEvent *event); /** * Handles the mouse button release event * * \param event the event that caused this call. * Must be of type QTabletEvent or QMouseEvent. * * \return whether the event has been handled successfully and * should be eaten by the events filter */ bool buttonReleased(Qt::MouseButton button, QEvent *event); /** * Handles the mouse wheel event * * \return whether the event has been handled successfully and * should be eaten by the events filter */ bool wheelEvent(KisSingleActionShortcut::WheelAction wheelAction, QWheelEvent *event); /** * Handles tablet and mouse move events. * * \param event the event that caused this call * * \return whether the event has been handled successfully and * should be eaten by the events filter */ bool pointerMoved(QEvent *event); /** * Handle cursor's Enter event. * We never eat it because it might be used by someone else */ void enterEvent(); /** * Handle cursor's Leave event. * We never eat it because it might be used by someone else */ void leaveEvent(); bool touchBeginEvent(QTouchEvent *event); bool touchUpdateEvent(QTouchEvent *event); bool touchEndEvent(QTouchEvent *event); bool nativeGestureBeginEvent(QNativeGestureEvent *event); bool nativeGestureEvent(QNativeGestureEvent *event); bool nativeGestureEndEvent(QNativeGestureEvent *event); /** * Resets the internal state of the matcher and activates the * prepared action if possible. * * This should be done when the window has lost the focus for * some time, so that several events could be lost */ void reinitialize(); /** * Resets the internal state of the matcher, tries to resync it to the state * passed via argument and activates the prepared action if possible. * * This synchronization should happen when the user hovers Krita windows, * **without** having keyboard focus set to it (therefore matcher cannot * get key press and release events). */ void recoveryModifiersWithoutFocus(const QVector &keys); /** * Kirta lost focus, it means that all the running actions should be ended * forcefully. */ void lostFocusEvent(const QPointF &localPos); /** * Disables the start of any actions. * * WARNING: the actions that has been started before this call * will *not* be ended. They will be ended in their usual way, * when the mouse button will be released. */ void suppressAllActions(bool value); /** * Remove all shortcuts that have been registered. */ void clearShortcuts(); void setInputActionGroupsMaskCallback(std::function func); private: friend class KisInputManagerTest; void reset(); void reset(QString msg); bool tryRunWheelShortcut(KisSingleActionShortcut::WheelAction wheelAction, QWheelEvent *event); template bool tryRunSingleActionShortcutImpl(T param, U *event, const QSet &keysState); void prepareReadyShortcuts(); bool tryRunReadyShortcut( Qt::MouseButton button, QEvent* event ); void tryActivateReadyShortcut(); bool tryEndRunningShortcut( Qt::MouseButton button, QEvent* event ); void forceEndRunningShortcut(const QPointF &localPos); + void forceDeactivateAllActions(); bool tryRunTouchShortcut(QTouchEvent *event); bool tryEndTouchShortcut(QTouchEvent *event); bool tryRunNativeGestureShortcut(QNativeGestureEvent *event); bool tryEndNativeGestureShortcut(QNativeGestureEvent *event); private: class Private; Private * const m_d; }; #endif /* __KIS_SHORTCUT_MATCHER_H */