[prev in list] [next in list] [prev in thread] [next in thread] 

List:       kde-commits
Subject:    [calligra/krita-new-move-tool-kazakov] krita/ui: Fixed the second half of bug 302758
From:       Dmitry Kazakov <dimula73 () gmail ! com>
Date:       2012-11-12 15:34:47
Message-ID: 20121112153447.36FE0A6091 () git ! kde ! org
[Download RAW message or body]

Git commit ac7eea24b10c0920fbef3e65f59db2722998d30e by Dmitry Kazakov.
Committed on 12/11/2012 at 16:33.
Pushed by dkazakov into branch 'krita-new-move-tool-kazakov'.

Fixed the second half of bug 302758

This patch almost rewrites the KisInputManager to fix the mentioned
bug. Now all the state transitions of the actions are controlled by
a special class KisShorcutMatcher. This class is easily controlled by
a separate unittest. The work of the actions is now can be represented
by a simple state machine with three states (see docs for class
KisStrokeShortcut).

CCBUG:302758

M  +4    -1    krita/ui/CMakeLists.txt
M  +39   -18   krita/ui/input/kis_abstract_input_action.cpp
M  +50   -23   krita/ui/input/kis_abstract_input_action.h
A  +68   -0    krita/ui/input/kis_abstract_shortcut.cpp     [License: GPL (v2+)]
A  +72   -0    krita/ui/input/kis_abstract_shortcut.h     [License: GPL (v2+)]
M  +16   -16   krita/ui/input/kis_alternate_invocation_action.cpp
M  +3    -5    krita/ui/input/kis_alternate_invocation_action.h
M  +16   -16   krita/ui/input/kis_change_primary_setting_action.cpp
M  +3    -5    krita/ui/input/kis_change_primary_setting_action.h
M  +218  -279  krita/ui/input/kis_input_manager.cpp
M  +4    -9    krita/ui/input/kis_input_manager.h
A  +70   -0    krita/ui/input/kis_key_shortcut.cpp     [License: GPL (v2+)]
A  +54   -0    krita/ui/input/kis_key_shortcut.h     [License: GPL (v2+)]
M  +17   -38   krita/ui/input/kis_pan_action.cpp
M  +4    -4    krita/ui/input/kis_pan_action.h
M  +25   -57   krita/ui/input/kis_rotate_canvas_action.cpp
M  +4    -9    krita/ui/input/kis_rotate_canvas_action.h
D  +0    -162  krita/ui/input/kis_shortcut.cpp
D  +0    -120  krita/ui/input/kis_shortcut.h
A  +287  -0    krita/ui/input/kis_shortcut_matcher.cpp     [License: GPL (v2+)]
A  +143  -0    krita/ui/input/kis_shortcut_matcher.h     [License: GPL (v2+)]
M  +3    -12   krita/ui/input/kis_show_palette_action.cpp
M  +1    -3    krita/ui/input/kis_show_palette_action.h
A  +72   -0    krita/ui/input/kis_stroke_shortcut.cpp     [License: GPL (v2+)]
A  +80   -0    krita/ui/input/kis_stroke_shortcut.h     [License: GPL (v2+)]
M  +33   -50   krita/ui/input/kis_tool_invocation_action.cpp
M  +3    -5    krita/ui/input/kis_tool_invocation_action.h
M  +19   -47   krita/ui/input/kis_zoom_action.cpp
M  +4    -8    krita/ui/input/kis_zoom_action.h
M  +6    -0    krita/ui/tests/CMakeLists.txt
A  +357  -0    krita/ui/tests/kis_input_manager_test.cpp     [License: GPL (v2+)]
C  +14   -19   krita/ui/tests/kis_input_manager_test.h [from: \
krita/ui/input/kis_show_palette_action.h - 053% similarity]     [License: GPL]

http://commits.kde.org/calligra/ac7eea24b10c0920fbef3e65f59db2722998d30e

diff --git a/krita/ui/CMakeLists.txt b/krita/ui/CMakeLists.txt
index 640d0a6..a0cb7d6 100644
--- a/krita/ui/CMakeLists.txt
+++ b/krita/ui/CMakeLists.txt
@@ -189,7 +189,6 @@ set(kritaui_LIB_SRCS
 #    widgets/kis_light_source.cpp
 #    widgets/kis_light_stage.cpp
     input/kis_input_manager.cpp
-    input/kis_shortcut.cpp
     input/kis_abstract_input_action.cpp
     input/kis_tool_invocation_action.cpp
     input/kis_pan_action.cpp
@@ -198,6 +197,10 @@ set(kritaui_LIB_SRCS
     input/kis_zoom_action.cpp
     input/kis_show_palette_action.cpp
     input/kis_change_primary_setting_action.cpp
+    input/kis_abstract_shortcut.cpp
+    input/kis_key_shortcut.cpp
+    input/kis_stroke_shortcut.cpp
+    input/kis_shortcut_matcher.cpp
     kis_ui_action_factory.cpp
     kis_ui_action_factory_registry.cpp
     actions/kis_selection_action_factories.cpp
diff --git a/krita/ui/input/kis_abstract_input_action.cpp \
b/krita/ui/input/kis_abstract_input_action.cpp index ace6b20..7eb4c69 100644
--- a/krita/ui/input/kis_abstract_input_action.cpp
+++ b/krita/ui/input/kis_abstract_input_action.cpp
@@ -19,6 +19,7 @@
 #include "kis_abstract_input_action.h"
 
 #include <QPointF>
+#include <QMouseEvent>
 #include <KLocalizedString>
 
 class KisAbstractInputAction::Private
@@ -30,7 +31,7 @@ public:
     QString description;
     QHash<QString, int> indexes;
 
-    QPointF mousePosition;
+    QPointF lastMousePosition;
 };
 
 KisAbstractInputAction::KisAbstractInputAction(KisInputManager* manager) : d(new \
Private) @@ -44,9 +45,44 @@ KisAbstractInputAction::~KisAbstractInputAction()
     delete d;
 }
 
-bool KisAbstractInputAction::handleTablet() const
+void KisAbstractInputAction::activate()
 {
-    return false;
+}
+
+void KisAbstractInputAction::deactivate()
+{
+}
+
+void KisAbstractInputAction::begin(int shortcut, QEvent *event)
+{
+    Q_UNUSED(shortcut);
+
+    QMouseEvent *mouseEvent;
+    if (event && (mouseEvent = dynamic_cast<QMouseEvent*>(event))) {
+        d->lastMousePosition = mouseEvent->posF();
+    }
+}
+
+void KisAbstractInputAction::inputEvent(QEvent* event)
+{
+    QMouseEvent *mouseEvent;
+    if (event && (mouseEvent = dynamic_cast<QMouseEvent*>(event))) {
+        if (mouseEvent->type() == QEvent::MouseMove) {
+            mouseMoved(d->lastMousePosition, mouseEvent->posF());
+        }
+        d->lastMousePosition = mouseEvent->posF();
+    }
+}
+
+void KisAbstractInputAction::end(QEvent *event)
+{
+    Q_UNUSED(event);
+}
+
+void KisAbstractInputAction::mouseMoved(const QPointF &lastPos, const QPointF &pos)
+{
+    Q_UNUSED(lastPos);
+    Q_UNUSED(pos);
 }
 
 KisInputManager* KisAbstractInputAction::inputManager() const
@@ -83,18 +119,3 @@ void KisAbstractInputAction::setShortcutIndexes(const QHash< \
QString, int >& ind  {
     d->indexes = indexes;
 }
-
-bool KisAbstractInputAction::isBlockingAutoRepeat() const
-{
-    return false;
-}
-
-QPointF KisAbstractInputAction::mousePosition() const
-{
-    return d->mousePosition;
-}
-
-void KisAbstractInputAction::setMousePosition(const QPointF &position)
-{
-    d->mousePosition = position;
-}
diff --git a/krita/ui/input/kis_abstract_input_action.h \
b/krita/ui/input/kis_abstract_input_action.h index 802ae27..6c78016 100644
--- a/krita/ui/input/kis_abstract_input_action.h
+++ b/krita/ui/input/kis_abstract_input_action.h
@@ -20,6 +20,7 @@
 #define KIS_ABSTRACT_INPUT_ACTION_H
 
 #include <QHash>
+#include "krita_export.h"
 
 class QPointF;
 class QEvent;
@@ -30,15 +31,27 @@ class KisInputManager;
  *
  * Input actions represent actions to be performed when interacting
  * with the canvas. They are managed by KisInputManager and activated
- * when KisShortcut detects it matches a certain set of inputs.
+ * when KisKeyShortcut or KisStrokeShortcut detects it matches a certain
+ * set of inputs.
  *
  * The begin() method uses an index for the type of behaviour to activate.
  * This index can be used to trigger behaviour when different events occur.
- * For example, in the Pan action, this is used to have a single toggle
- * behaviour and four additional options to pan a fixed amount in a certain
- * direction. Each action will always have at least one behaviour.
+ *
+ * The events can be of two types:
+ * 1) Key events. The input manager calls begin() and end() sequentially
+ *    with an \p index parameter to begin() representing the type of
+ *    action that should be performed. The \p event parameter of both
+ *    calls in null.
+ * 2) Stroke events. The input manager calls begin() and end() on the
+ *    corresponding mouse down and up events. The \p event parameter
+ *    will be of QMouseEvent type, representing the event happened.
+ *    All the mouse move events between begin() and end() will be
+ *    redirected to the inputEvent() method.
+ *
+ *    You can fetch the QTabletEvent data for the current mouse event
+ *    with inputManager()->lastTabletEvent().
  */
-class KisAbstractInputAction
+class KRITAUI_EXPORT KisAbstractInputAction
 {
 public:
     /**
@@ -53,27 +66,48 @@ public:
     virtual ~KisAbstractInputAction();
 
     /**
+     * The method is called when the action is yet to be started,
+     * that is, e.g. the user has pressed all the modifiers for the
+     * action but hasn't started painting yet. This method is a right
+     * place to show the user what he is going to do, e.g. change the
+     * cursor.
+     */
+    virtual void activate();
+
+    /**
+     * The method is called when the action is not a candidate for
+     * the starting anymore. The action should revert everything that
+     * was done in activate() method.
+     *
+     * \see activate()
+     */
+    virtual void deactivate();
+
+    /**
      * Begin the action.
      *
      * \param shortcut The index of the behaviour to trigger.
+     * \param event The mouse event that has triggered this action.
+     *              Is null for keyboard-activated actions.
      */
-    virtual void begin(int shortcut) = 0;
+    virtual void begin(int shortcut, QEvent *event);
     /**
      * End the action.
+     * \param event The mouse event that has finished this action.
+     *              Is null for keyboard-activated actions.
      */
-    virtual void end() = 0;
+    virtual void end(QEvent *event);
     /**
      * Process an input event.
      *
+     * By default handles MouseMove events and passes the data to
+     * a convenience mouseMoved() method
+     *
      * \param event An event to process.
      */
-    virtual void inputEvent(QEvent* event) = 0;
+    virtual void inputEvent(QEvent* event);
 
     /**
-     * Does this action handle tablet events in a special way?
-     */
-    virtual bool handleTablet() const;
-    /**
      * The indexes of shortcut behaviours available.
      */
     virtual QHash<QString, int> shortcutIndexes() const;
@@ -86,11 +120,6 @@ public:
      */
     virtual QString description() const;
 
-    /**
-     * Does this action block auto repeat events?
-     */
-    virtual bool isBlockingAutoRepeat() const;
-
 protected:
     /**
      * The input manager this action belongs to.
@@ -114,14 +143,12 @@ protected:
      * \param indexes The new indexes.
      */
     void setShortcutIndexes(const QHash<QString, int> &indexes);
+
     /**
-     * Return the locally cached mouse position.
-     */
-    QPointF mousePosition() const;
-    /**
-     * Set a mouse position to cache locally.
+     * Convenience method for handling the mouse moves. It is
+     * called by the default implementation of inputEvent
      */
-    void setMousePosition(const QPointF &position);
+    virtual void mouseMoved(const QPointF &lastPos, const QPointF &pos);
 
 private:
     class Private;
diff --git a/krita/ui/input/kis_abstract_shortcut.cpp \
b/krita/ui/input/kis_abstract_shortcut.cpp new file mode 100644
index 0000000..661006b
--- /dev/null
+++ b/krita/ui/input/kis_abstract_shortcut.cpp
@@ -0,0 +1,68 @@
+/*
+ *  Copyright (c) 2012 Dmitry Kazakov <dimula73@gmail.com>
+ *
+ *  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_abstract_shortcut.h"
+
+struct KisAbstractShortcut::Private
+{
+    KisAbstractInputAction *action;
+    int shortcutIndex;
+};
+
+KisAbstractShortcut::KisAbstractShortcut(KisAbstractInputAction *action, int index)
+    : m_d(new Private)
+{
+    m_d->action = action;
+    m_d->shortcutIndex = index;
+}
+
+KisAbstractShortcut::~KisAbstractShortcut()
+{
+    delete m_d;
+}
+
+KisAbstractInputAction* KisAbstractShortcut::action() const
+{
+    return m_d->action;
+}
+
+void KisAbstractShortcut::setAction(KisAbstractInputAction* action)
+{
+    m_d->action = action;
+}
+
+int KisAbstractShortcut::shortcutIndex() const
+{
+    return m_d->shortcutIndex;
+}
+
+void KisAbstractShortcut::setShortcutIndex(int index)
+{
+    m_d->shortcutIndex = index;
+}
+
+bool KisAbstractShortcut::compareKeys(const QList<Qt::Key> &keys1,
+                                      const QList<Qt::Key> &keys2)
+{
+    if (keys1.size() != keys2.size()) return false;
+
+    foreach(Qt::Key key, keys1) {
+        if (!keys2.contains(key)) return false;
+    }
+    return true;
+}
diff --git a/krita/ui/input/kis_abstract_shortcut.h \
b/krita/ui/input/kis_abstract_shortcut.h new file mode 100644
index 0000000..8ea368c
--- /dev/null
+++ b/krita/ui/input/kis_abstract_shortcut.h
@@ -0,0 +1,72 @@
+/*
+ *  Copyright (C) 2012 Arjen Hiemstra <ahiemstra@heimr.nl>
+ *  Copyright (c) 2012 Dmitry Kazakov <dimula73@gmail.com>
+ *
+ *  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_ABSTRACT_SHORTCUT_H
+#define __KIS_ABSTRACT_SHORTCUT_H
+
+#include <Qt>
+#include <QList>
+#include <krita_export.h>
+class KisAbstractInputAction;
+
+
+class KRITAUI_EXPORT KisAbstractShortcut
+{
+public:
+    KisAbstractShortcut(KisAbstractInputAction *action, int index);
+    virtual ~KisAbstractShortcut();
+
+    /**
+     * The priority of the shortcut. The shortcut with the
+     * greatest value will be chosen for executution
+     */
+    virtual int priority() const = 0;
+
+    /**
+     * The action associated with this shortcut.
+     */
+    KisAbstractInputAction* action() const;
+
+    /**
+     * Set the action associated with this shortcut.
+     */
+    void setAction(KisAbstractInputAction *action);
+
+    /**
+     * The index of the shortcut.
+     *
+     * \see KisAbstractInputAction::begin()
+     */
+    int shortcutIndex() const;
+
+    /**
+     * Set the index of the shortcut.
+     */
+    void setShortcutIndex(int index);
+
+protected:
+    bool compareKeys(const QList<Qt::Key> &keys1,
+                     const QList<Qt::Key> &keys2);
+
+private:
+    class Private;
+    Private * const m_d;
+};
+
+#endif /* __KIS_ABSTRACT_SHORTCUT_H */
diff --git a/krita/ui/input/kis_alternate_invocation_action.cpp \
b/krita/ui/input/kis_alternate_invocation_action.cpp index b7fbd77..656fc46 100644
--- a/krita/ui/input/kis_alternate_invocation_action.cpp
+++ b/krita/ui/input/kis_alternate_invocation_action.cpp
@@ -37,28 +37,28 @@ KisAlternateInvocationAction::~KisAlternateInvocationAction()
 {
 }
 
-void KisAlternateInvocationAction::begin(int /*shortcut*/)
+void KisAlternateInvocationAction::begin(int shortcut, QEvent *event)
 {
-    QMouseEvent mevent(QEvent::MouseButtonPress, \
inputManager()->mousePosition().toPoint(), Qt::LeftButton, Qt::LeftButton, \
                Qt::ControlModifier);
-    inputManager()->toolProxy()->mousePressEvent(&mevent, \
                inputManager()->mousePosition());
-}
+    KisAbstractInputAction::begin(shortcut, event);
 
-void KisAlternateInvocationAction::end()
-{
-    QMouseEvent mevent(QEvent::MouseButtonRelease, mousePosition().toPoint(), \
                Qt::LeftButton, Qt::LeftButton, Qt::ControlModifier);
-    inputManager()->toolProxy()->mouseReleaseEvent(&mevent, mousePosition());
+    QMouseEvent *mouseEvent = dynamic_cast<QMouseEvent*>(event);
+    QMouseEvent targetEvent(QEvent::MouseButtonPress, mouseEvent->pos(), \
Qt::LeftButton, Qt::LeftButton, Qt::ControlModifier); +    \
inputManager()->toolProxy()->mousePressEvent(&targetEvent, \
inputManager()->widgetToPixel(mouseEvent->posF()));  }
 
-void KisAlternateInvocationAction::inputEvent(QEvent* event)
+void KisAlternateInvocationAction::end(QEvent *event)
 {
-    if(event->type() == QEvent::MouseMove) {
-        QMouseEvent *mevent = static_cast<QMouseEvent*>(event);
-        setMousePosition(inputManager()->widgetToPixel(mevent->posF()));
-        inputManager()->toolProxy()->mouseMoveEvent(mevent, mousePosition());
-    }
+    QMouseEvent *mouseEvent = dynamic_cast<QMouseEvent*>(event);
+    QMouseEvent targetEvent(QEvent::MouseButtonRelease, mouseEvent->pos(), \
Qt::LeftButton, Qt::LeftButton, Qt::ControlModifier); +    \
inputManager()->toolProxy()->mouseReleaseEvent(&targetEvent, \
inputManager()->widgetToPixel(mouseEvent->posF())); +
+    KisAbstractInputAction::end(event);
 }
 
-bool KisAlternateInvocationAction::isBlockingAutoRepeat() const
+void KisAlternateInvocationAction::mouseMoved(const QPointF &lastPos, const QPointF \
&pos)  {
-    return true;
+    Q_UNUSED(lastPos);
+
+    QMouseEvent targetEvent(QEvent::MouseButtonRelease, pos.toPoint(), Qt::NoButton, \
Qt::LeftButton, Qt::ControlModifier); +    \
inputManager()->toolProxy()->mouseMoveEvent(&targetEvent, \
inputManager()->widgetToPixel(pos));  }
diff --git a/krita/ui/input/kis_alternate_invocation_action.h \
b/krita/ui/input/kis_alternate_invocation_action.h index 9da4dc8..bd5fa2d 100644
--- a/krita/ui/input/kis_alternate_invocation_action.h
+++ b/krita/ui/input/kis_alternate_invocation_action.h
@@ -34,11 +34,9 @@ public:
     explicit KisAlternateInvocationAction(KisInputManager *manager);
     virtual ~KisAlternateInvocationAction();
 
-    virtual void begin(int /*shortcut*/);
-    virtual void end();
-    virtual void inputEvent(QEvent* event);
-
-    virtual bool isBlockingAutoRepeat() const;
+    void begin(int shortcut, QEvent *event);
+    void end(QEvent *event);
+    void mouseMoved(const QPointF &lastPos, const QPointF &pos);
 };
 
 #endif // KIS_ALTERNATE_INVOCATION_ACTION_H
diff --git a/krita/ui/input/kis_change_primary_setting_action.cpp \
b/krita/ui/input/kis_change_primary_setting_action.cpp index bcff0c8..9f60cfc 100644
--- a/krita/ui/input/kis_change_primary_setting_action.cpp
+++ b/krita/ui/input/kis_change_primary_setting_action.cpp
@@ -35,28 +35,28 @@ KisChangePrimarySettingAction::~KisChangePrimarySettingAction()
 
 }
 
-void KisChangePrimarySettingAction::begin(int shortcut)
+void KisChangePrimarySettingAction::begin(int shortcut, QEvent *event)
 {
-    QMouseEvent mevent(QEvent::MouseButtonPress, \
inputManager()->mousePosition().toPoint(), Qt::LeftButton, Qt::LeftButton, \
                Qt::ShiftModifier);
-    inputManager()->toolProxy()->mousePressEvent(&mevent, \
                inputManager()->mousePosition());
-}
+    KisAbstractInputAction::begin(shortcut, event);
 
-void KisChangePrimarySettingAction::end()
-{
-    QMouseEvent mevent(QEvent::MouseButtonRelease, mousePosition().toPoint(), \
                Qt::LeftButton, Qt::LeftButton, Qt::ShiftModifier);
-    inputManager()->toolProxy()->mouseReleaseEvent(&mevent, mousePosition());
+    QMouseEvent *mouseEvent = dynamic_cast<QMouseEvent*>(event);
+    QMouseEvent targetEvent(QEvent::MouseButtonPress, mouseEvent->pos(), \
Qt::LeftButton, Qt::LeftButton, Qt::ShiftModifier); +    \
inputManager()->toolProxy()->mousePressEvent(&targetEvent, \
inputManager()->widgetToPixel(mouseEvent->posF()));  }
 
-void KisChangePrimarySettingAction::inputEvent(QEvent* event)
+void KisChangePrimarySettingAction::end(QEvent *event)
 {
-    if(event->type() == QEvent::MouseMove) {
-        QMouseEvent *mevent = static_cast<QMouseEvent*>(event);
-        setMousePosition(inputManager()->widgetToPixel(mevent->posF()));
-        inputManager()->toolProxy()->mouseMoveEvent(mevent, mousePosition());
-    }
+    QMouseEvent *mouseEvent = dynamic_cast<QMouseEvent*>(event);
+    QMouseEvent targetEvent(QEvent::MouseButtonRelease, mouseEvent->pos(), \
Qt::LeftButton, Qt::LeftButton, Qt::ShiftModifier); +    \
inputManager()->toolProxy()->mouseReleaseEvent(&targetEvent, \
inputManager()->widgetToPixel(mouseEvent->posF())); +
+    KisAbstractInputAction::end(event);
 }
 
-bool KisChangePrimarySettingAction::isBlockingAutoRepeat() const
+void KisChangePrimarySettingAction::mouseMoved(const QPointF &lastPos, const QPointF \
&pos)  {
-    return true;
+    Q_UNUSED(lastPos);
+
+    QMouseEvent targetEvent(QEvent::MouseButtonRelease, pos.toPoint(), Qt::NoButton, \
Qt::LeftButton, Qt::ShiftModifier); +    \
inputManager()->toolProxy()->mouseMoveEvent(&targetEvent, \
inputManager()->widgetToPixel(pos));  }
diff --git a/krita/ui/input/kis_change_primary_setting_action.h \
b/krita/ui/input/kis_change_primary_setting_action.h index 7fceeee..0efc167 100644
--- a/krita/ui/input/kis_change_primary_setting_action.h
+++ b/krita/ui/input/kis_change_primary_setting_action.h
@@ -33,11 +33,9 @@ public:
     explicit KisChangePrimarySettingAction(KisInputManager* manager);
     virtual ~KisChangePrimarySettingAction();
 
-    virtual void begin(int shortcut);
-    virtual void end();
-    virtual void inputEvent(QEvent* event);
-
-    virtual bool isBlockingAutoRepeat() const;
+    void begin(int shortcut, QEvent *event);
+    void end(QEvent *event);
+    void mouseMoved(const QPointF &lastPos, const QPointF &pos);
 };
 
 #endif // KISCHANGEPRIMARYSETTINGACTION_H
diff --git a/krita/ui/input/kis_input_manager.cpp \
b/krita/ui/input/kis_input_manager.cpp index 98475be..c77000e 100644
--- a/krita/ui/input/kis_input_manager.cpp
+++ b/krita/ui/input/kis_input_manager.cpp
@@ -34,7 +34,6 @@
 #include <kis_canvas_resource_provider.h>
 #include <ko_favorite_resource_manager.h>
 
-#include "kis_shortcut.h"
 #include "kis_abstract_input_action.h"
 #include "kis_tool_invocation_action.h"
 #include "kis_pan_action.h"
@@ -44,174 +43,136 @@
 #include "kis_show_palette_action.h"
 #include "kis_change_primary_setting_action.h"
 
+#include "kis_shortcut_matcher.h"
+#include "kis_stroke_shortcut.h"
+#include "kis_key_shortcut.h"
+
 class KisInputManager::Private
 {
 public:
     Private(KisInputManager *qq)
         : q(qq)
         , toolProxy(0)
-        , currentAction(0)
-        , currentShortcut(0)
-        , tabletPressEvent(0)
         , setMirrorMode(false)
-        , fixedAction(false)
+        , forwardAllEventsToTool(false)
+        , lastTabletEvent(0)
     { }
 
-    void match(QEvent *event);
+    bool tryHidePopupPalette();
+    bool trySetMirrorMode(const QPointF &mousePosition);
+    void saveTabletEvent(const QTabletEvent *event);
+    void resetSavedTabletEvent();
+    void addStrokeShortcut(KisAbstractInputAction* action, int index,
+                           const QList<Qt::Key> &modifiers,
+                           const QList<Qt::MouseButton> &buttons);
+    void addKeyShortcut(KisAbstractInputAction* action, int index,
+                        const QList<Qt::Key> &modifiers,
+                        Qt::Key key);
+    bool processUnhandledEvent(QEvent *event);
     void setupActions();
-    KisShortcut *createShortcut(KisAbstractInputAction* action, int index);
-    void clearState();
 
     KisInputManager *q;
 
     KisCanvas2 *canvas;
     KoToolProxy *toolProxy;
 
-    KisAbstractInputAction* currentAction;
-    KisShortcut* currentShortcut;
-
-    QList<KisShortcut*> shortcuts;
-    QList<KisAbstractInputAction*> actions;
-
-    QList<KisShortcut*> potentialShortcuts;
-
-    QPointF mousePosition;
+    bool setMirrorMode;
+    bool forwardAllEventsToTool;
 
-    QTabletEvent *tabletPressEvent;
+    KisShortcutMatcher matcher;
+    QTabletEvent *lastTabletEvent;
 
-    bool setMirrorMode;
-    bool fixedAction;
+    KisAbstractInputAction *defaultInputAction;
 };
 
-void KisInputManager::Private::match(QEvent* event)
-{
-    if (fixedAction) {
-        return;
-    }
-    //Go through all possible shortcuts and update their state.
-    foreach (KisShortcut* shortcut, potentialShortcuts) {
-        shortcut->match(event);
-        if(shortcut->matchLevel() == KisShortcut::NoMatch) {
-            //There is no chance of this shortcut matching anything with the current \
                input,
-            //so remove it from the list of shortcuts to search through.
-            potentialShortcuts.removeOne(shortcut);
-        }
-    }
-
-    if (potentialShortcuts.count() == 0) {
-        //With the current input, there is simply no shortcut that matches,
-        //so restart the matching.
-        potentialShortcuts = shortcuts;
-        return;
-    }
+static inline QList<Qt::Key> KEYS() {
+    return QList<Qt::Key>();
+}
+static inline QList<Qt::Key> KEYS(Qt::Key key) {
+    return QList<Qt::Key>() << key;
+}
+static inline QList<Qt::Key> KEYS(Qt::Key key1, Qt::Key key2) {
+    return QList<Qt::Key>() << key1 << key2;
+}
+static inline QList<Qt::MouseButton> BUTTONS(Qt::MouseButton button) {
+    return QList<Qt::MouseButton>() << button;
+}
+static inline QList<Qt::MouseButton> BUTTONS(Qt::MouseButton button1, \
Qt::MouseButton button2) { +    return QList<Qt::MouseButton>() << button1 << \
button2; +}
 
-    if (potentialShortcuts.count() == 1 || event->type() == QEvent::MouseButtonPress \
                || event->type() == QEvent::MouseButtonDblClick) {
-        //Either we have only one possible match or we reached the queue threshold.
-        KisShortcut* completedShortcut = 0;
-        foreach (KisShortcut* shortcut, potentialShortcuts) {
-            if (shortcut->matchLevel() == KisShortcut::CompleteMatch) {
-                //Set the matched shortcut to the one with the highest \
                priority.std::
-                if (!completedShortcut || completedShortcut->priority() < \
                shortcut->priority()) {
-                    completedShortcut = shortcut;
-                }
-            } else {
-                shortcut->clear();
-            }
-        }
+void KisInputManager::Private::addStrokeShortcut(KisAbstractInputAction* action, int \
index, +                                                 const QList<Qt::Key> \
&modifiers, +                                                 const \
QList<Qt::MouseButton> &buttons) +{
+    KisStrokeShortcut *strokeShortcut =
+        new KisStrokeShortcut(action, index);
+    strokeShortcut->setButtons(modifiers, buttons);
+    matcher.addShortcut(strokeShortcut);
+}
 
-        //We really do have a matched action, so lets activate it.
-        if (completedShortcut) {
-            currentShortcut = completedShortcut;
-            currentAction = completedShortcut->action();
-            currentAction->begin(completedShortcut->shortcutIndex());
-        }
-    }
+void KisInputManager::Private::addKeyShortcut(KisAbstractInputAction* action, int \
index, +                                              const QList<Qt::Key> \
&modifiers, +                                              Qt::Key key)
+{
+    KisKeyShortcut *keyShortcut =
+        new KisKeyShortcut(action, index);
+    keyShortcut->setKey(modifiers, key);
+    matcher.addShortcut(keyShortcut);
 }
 
 void KisInputManager::Private::setupActions()
 {
+#if QT_VERSION >= 0x040700
+    Qt::MouseButton middleButton = Qt::MiddleButton;
+#else
+    Qt::MouseButton middleButton = Qt::MidButton;
+#endif
+
     //Create all the actions.
     KisAbstractInputAction* action = new KisToolInvocationAction(q);
-    actions.append(action);
-
-    KisShortcut* shortcut = createShortcut(action, \
                KisToolInvocationAction::ActivateShortcut);
-    shortcut->setButtons(QList<Qt::MouseButton>() << Qt::LeftButton);
-
-    shortcut = createShortcut(action, KisToolInvocationAction::ConfirmShortcut);
-    shortcut->setKeys(QList<Qt::Key>() << Qt::Key_Return);
+    matcher.addAction(action);
+    addStrokeShortcut(action, KisToolInvocationAction::ActivateShortcut, KEYS(), \
BUTTONS(Qt::LeftButton)); +    addKeyShortcut(action, \
KisToolInvocationAction::ConfirmShortcut, KEYS(), Qt::Key_Return); +    \
defaultInputAction = action;  
     action = new KisAlternateInvocationAction(q);
-    actions.append(action);
-
-    shortcut = createShortcut(action, 0);
-    shortcut->setButtons(QList<Qt::MouseButton>() << Qt::LeftButton);
-    shortcut->setKeys(QList<Qt::Key>() << Qt::Key_Control);
+    matcher.addAction(action);
+    addStrokeShortcut(action, 0, KEYS(Qt::Key_Control), BUTTONS(Qt::LeftButton));
 
     action = new KisChangePrimarySettingAction(q);
-    actions.append(action);
+    matcher.addAction(action);
+    addStrokeShortcut(action, 0, KEYS(Qt::Key_Shift), BUTTONS(Qt::LeftButton));
 
-    shortcut = createShortcut(action, 0);
-    shortcut->setButtons(QList<Qt::MouseButton>() << Qt::LeftButton);
-    shortcut->setKeys(QList<Qt::Key>() << Qt::Key_Shift);
 
     action = new KisPanAction(q);
-    actions.append(action);
+    matcher.addAction(action);
 
-    shortcut = createShortcut(action, KisPanAction::PanToggleShortcut);
-    shortcut->setKeys(QList<Qt::Key>() << Qt::Key_Space);
+    addStrokeShortcut(action, KisPanAction::PanToggleShortcut, KEYS(Qt::Key_Space), \
BUTTONS(Qt::LeftButton)); +    addStrokeShortcut(action, \
KisPanAction::PanToggleShortcut, KEYS(), BUTTONS(middleButton));  
-    shortcut = createShortcut(action, KisPanAction::PanToggleShortcut);
-#if QT_VERSION >= 0x040700
-    shortcut->setButtons(QList<Qt::MouseButton>() << Qt::MiddleButton);
-#else
-    shortcut->setButtons(QList<Qt::MouseButton>() << Qt::MidButton);
-#endif
+    addKeyShortcut(action, KisPanAction::PanLeftShortcut, KEYS(), Qt::Key_Left);
+    addKeyShortcut(action, KisPanAction::PanRightShortcut, KEYS(), Qt::Key_Right);
+    addKeyShortcut(action, KisPanAction::PanUpShortcut, KEYS(), Qt::Key_Up);
+    addKeyShortcut(action, KisPanAction::PanDownShortcut, KEYS(), Qt::Key_Down);
 
-    shortcut = createShortcut(action, KisPanAction::PanLeftShortcut);
-    shortcut->setKeys(QList<Qt::Key>() << Qt::Key_Left);
-    shortcut = createShortcut(action, KisPanAction::PanRightShortcut);
-    shortcut->setKeys(QList<Qt::Key>() << Qt::Key_Right);
-    shortcut = createShortcut(action, KisPanAction::PanUpShortcut);
-    shortcut->setKeys(QList<Qt::Key>() << Qt::Key_Up);
-    shortcut = createShortcut(action, KisPanAction::PanDownShortcut);
-    shortcut->setKeys(QList<Qt::Key>() << Qt::Key_Down);
 
     action = new KisRotateCanvasAction(q);
-    actions.append(action);
+    matcher.addAction(action);
 
-    shortcut = createShortcut(action, KisRotateCanvasAction::RotateToggleShortcut);
-    shortcut->setKeys(QList<Qt::Key>() << Qt::Key_Shift << Qt::Key_Space);
+    addStrokeShortcut(action, KisPanAction::PanToggleShortcut, KEYS(Qt::Key_Shift, \
Qt::Key_Space), BUTTONS(Qt::LeftButton)); +    addStrokeShortcut(action, \
KisPanAction::PanToggleShortcut, KEYS(Qt::Key_Shift), BUTTONS(middleButton));  
-    shortcut = createShortcut(action, KisRotateCanvasAction::RotateToggleShortcut);
-    shortcut->setKeys(QList<Qt::Key>() << Qt::Key_Shift);
-#if QT_VERSION >= 0x040700
-    shortcut->setButtons(QList<Qt::MouseButton>() << Qt::MiddleButton);
-#else
-    shortcut->setButtons(QList<Qt::MouseButton>() << Qt::MidButton);
-#endif
+    addKeyShortcut(action, KisRotateCanvasAction::RotateLeftShortcut, KEYS(), \
Qt::Key_4); +    addKeyShortcut(action, KisRotateCanvasAction::RotateResetShortcut, \
KEYS(), Qt::Key_5); +    addKeyShortcut(action, \
KisRotateCanvasAction::RotateRightShortcut, KEYS(), Qt::Key_6);  
-    shortcut = createShortcut(action, KisRotateCanvasAction::RotateLeftShortcut);
-    shortcut->setKeys(QList<Qt::Key>() << Qt::Key_4);
-
-    shortcut = createShortcut(action, KisRotateCanvasAction::RotateRightShortcut);
-    shortcut->setKeys(QList<Qt::Key>() << Qt::Key_6);
-
-    shortcut = createShortcut(action, KisRotateCanvasAction::RotateResetShortcut);
-    shortcut->setKeys(QList<Qt::Key>() << Qt::Key_5);
 
     action = new KisZoomAction(q);
-    actions.append(action);
-
-    shortcut = createShortcut(action, KisZoomAction::ZoomToggleShortcut);
-    shortcut->setKeys(QList<Qt::Key>() << Qt::Key_Control << Qt::Key_Space);
+    matcher.addAction(action);
 
-    shortcut = createShortcut(action, KisZoomAction::ZoomToggleShortcut);
-    shortcut->setKeys(QList<Qt::Key>() << Qt::Key_Control);
-#if QT_VERSION >= 0x040700
-    shortcut->setButtons(QList<Qt::MouseButton>() << Qt::MiddleButton);
-#else
-    shortcut->setButtons(QList<Qt::MouseButton>() << Qt::MidButton);
-#endif
+    addStrokeShortcut(action, KisZoomAction::ZoomToggleShortcut, \
KEYS(Qt::Key_Control, Qt::Key_Space), BUTTONS(Qt::LeftButton)); +    \
addStrokeShortcut(action, KisZoomAction::ZoomToggleShortcut, KEYS(Qt::Key_Control), \
BUTTONS(middleButton));  
     /**
      * FIXME: Zooming with Wheel is implemented on a level of
@@ -229,57 +190,84 @@ void KisInputManager::Private::setupActions()
     // shortcut = createShortcut(action, KisZoomAction::ZoomOutShortcut);
     // shortcut->setWheel(KisShortcut::WheelDown);
 
-    shortcut = createShortcut(action, KisZoomAction::ZoomInShortcut);
-    shortcut->setKeys(QList<Qt::Key>() << Qt::Key_Plus);
-    shortcut = createShortcut(action, KisZoomAction::ZoomOutShortcut);
-    shortcut->setKeys(QList<Qt::Key>() << Qt::Key_Minus);
+    addKeyShortcut(action, KisZoomAction::ZoomInShortcut, KEYS(), Qt::Key_Plus);
+    addKeyShortcut(action, KisZoomAction::ZoomOutShortcut, KEYS(), Qt::Key_Minus);
 
-    shortcut = createShortcut(action, KisZoomAction::ZoomResetShortcut);
-    shortcut->setKeys(QList<Qt::Key>() << Qt::Key_1);
-    shortcut = createShortcut(action, KisZoomAction::ZoomToPageShortcut);
-    shortcut->setKeys(QList<Qt::Key>() << Qt::Key_2);
-    shortcut = createShortcut(action, KisZoomAction::ZoomToWidthShortcut);
-    shortcut->setKeys(QList<Qt::Key>() << Qt::Key_3);
+    addKeyShortcut(action, KisZoomAction::ZoomResetShortcut, KEYS(), Qt::Key_1);
+    addKeyShortcut(action, KisZoomAction::ZoomToPageShortcut, KEYS(), Qt::Key_2);
+    addKeyShortcut(action, KisZoomAction::ZoomToWidthShortcut, KEYS(), Qt::Key_3);
 
     action = new KisShowPaletteAction(q);
-    actions.append(action);
-
-    shortcut = createShortcut(action, 0);
-    shortcut->setButtons(QList<Qt::MouseButton>() << Qt::RightButton);
+    matcher.addAction(action);
 
-    shortcut = createShortcut(action, 0);
-    shortcut->setKeys(QList<Qt::Key>() << Qt::Key_F);
+    addStrokeShortcut(action, 0, KEYS(), BUTTONS(Qt::RightButton));
+    addKeyShortcut(action, 0, KEYS(), Qt::Key_F);
 }
 
-KisShortcut* KisInputManager::Private::createShortcut(KisAbstractInputAction* \
action, int index) +bool KisInputManager::Private::processUnhandledEvent(QEvent \
*event)  {
-    KisShortcut* shortcut = new KisShortcut;
-    shortcut->setAction(action);
-    shortcut->setShortcutIndex(index);
-    shortcuts.append(shortcut);
+    bool retval = false;
 
-    return shortcut;
-}
+    if (forwardAllEventsToTool ||
+        event->type() == QEvent::KeyPress ||
+        event->type() == QEvent::KeyRelease) {
 
-void KisInputManager::Private::clearState()
-{
-    if (fixedAction) {
-        return;
+        defaultInputAction->inputEvent(event);
+        retval = true;
     }
 
-    if (currentShortcut) {
-        currentAction->end();
-        currentAction = 0;
-        currentShortcut = 0;
-        potentialShortcuts = shortcuts;
+    return retval && !forwardAllEventsToTool;
+}
 
-        delete tabletPressEvent;
-        tabletPressEvent = 0;
+bool KisInputManager::Private::tryHidePopupPalette()
+{
+    if (canvas->favoriteResourceManager()->isPopupPaletteVisible()) {
+        canvas->favoriteResourceManager()->slotShowPopupPalette();
+        return true;
     }
+    return false;
+}
 
-    foreach (KisShortcut* shortcut, shortcuts) {
-        shortcut->clear();
+bool KisInputManager::Private::trySetMirrorMode(const QPointF &mousePosition)
+{
+    if (setMirrorMode) {
+        canvas->resourceManager()->setResource(KisCanvasResourceProvider::MirrorAxisCenter, \
canvas->image()->documentToPixel(mousePosition)); +        \
QApplication::restoreOverrideCursor(); +        setMirrorMode = false;
+        return true;
     }
+    return false;
+}
+
+void KisInputManager::Private::saveTabletEvent(const QTabletEvent *event)
+{
+    delete lastTabletEvent;
+    lastTabletEvent =
+        new QTabletEvent(event->type(),
+                         event->pos(),
+                         event->globalPos(),
+                         event->hiResGlobalPos(),
+                         event->device(),
+                         event->pointerType(),
+                         event->pressure(),
+                         event->xTilt(),
+                         event->yTilt(),
+                         event->tangentialPressure(),
+                         event->rotation(),
+                         event->z(),
+                         event->modifiers(),
+                         event->uniqueId());
+}
+
+void KisInputManager::Private::resetSavedTabletEvent()
+{
+    delete lastTabletEvent;
+    lastTabletEvent = 0;
+}
+
+QTabletEvent* KisInputManager::lastTabletEvent() const
+{
+    return d->lastTabletEvent;
 }
 
 KisInputManager::KisInputManager(KisCanvas2 *canvas, KoToolProxy *proxy)
@@ -290,8 +278,6 @@ KisInputManager::KisInputManager(KisCanvas2 *canvas, KoToolProxy \
*proxy)  
     d->setupActions();
 
-    d->potentialShortcuts = d->shortcuts;
-
     /*
      * Temporary solution so we can still set the mirror axis.
      *
@@ -310,135 +296,101 @@ KisInputManager::KisInputManager(KisCanvas2 *canvas, \
KoToolProxy *proxy)  
 KisInputManager::~KisInputManager()
 {
-    qDeleteAll(d->shortcuts);
-    qDeleteAll(d->actions);
     delete d;
 }
 
 bool KisInputManager::eventFilter(QObject* object, QEvent* event)
 {
-    Q_UNUSED(object)
+    Q_UNUSED(object);
+    bool retval = false;
+
     switch (event->type()) {
     case QEvent::MouseButtonPress:
     case QEvent::MouseButtonDblClick: {
-        d->mousePosition = widgetToPixel(static_cast<QMouseEvent*>(event)->posF());
-
-        //If the palette is visible, then hide it and eat the event.
-        if (canvas()->favoriteResourceManager()->isPopupPaletteVisible()) {
-            canvas()->favoriteResourceManager()->slotShowPopupPalette();
-            return true;
-        }
+        QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
 
-        if (d->setMirrorMode) {
-            d->canvas->resourceManager()->setResource(KisCanvasResourceProvider::MirrorAxisCenter, \
                d->canvas->image()->documentToPixel(d->mousePosition));
-            QApplication::restoreOverrideCursor();
-            d->setMirrorMode = false;
-            return true;
+        if (d->tryHidePopupPalette() || \
d->trySetMirrorMode(widgetToPixel(mouseEvent->posF()))) { +            retval = true;
+        } else {
+            retval = d->matcher.buttonPressed(mouseEvent->button(), mouseEvent);
         }
-    } //Intentional fall through
-    case QEvent::KeyPress:
-    case QEvent::KeyRelease:
-        if (event->type() == QEvent::KeyPress || event->type() == \
                QEvent::KeyRelease) {
-            QKeyEvent *kevent = static_cast<QKeyEvent*>(event);
-            if (kevent->isAutoRepeat()) {
-                if (d->currentAction) {
-                    if (d->currentAction->isBlockingAutoRepeat()) {
-                        return true; //Ignore auto repeat key events if the action \
                is asking for it.
-                    }
-                } else {
-                    return true; //Always ignore auto repeat key events when we do \
                not have a current action.
-                }
-            }
+        d->resetSavedTabletEvent();
+        break;
+    }
+    case QEvent::MouseButtonRelease: {
+        QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
+        retval = d->matcher.buttonReleased(mouseEvent->button(), mouseEvent);
+        d->resetSavedTabletEvent();
+        break;
+    }
+    case QEvent::KeyPress: {
+        QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
+        if (!keyEvent->isAutoRepeat()) {
+            retval = d->matcher.keyPressed((Qt::Key)keyEvent->key());
         }
-        //Intentional fall through
-    case QEvent::MouseButtonRelease:
-        if (d->currentAction) { //If we are currently performing an action, we only \
                update the state of that action and shortcut.
-            d->currentShortcut->match(event);
-
-            if ((d->currentShortcut->matchLevel() == KisShortcut::PartialMatch || \
                d->currentShortcut->matchLevel() == KisShortcut::NoMatch)
-                && !d->fixedAction) {
-                d->clearState();
-                break;
-            }
-
-            d->currentAction->inputEvent(event);
-        } else { //Try to find a matching shortcut.
-            d->match(event);
+
+        /**
+         * Workaround for temporary switching of tools by
+         * KoCanvasControllerWidget. We don't need this switch because
+         * we handle it ourselves.
+         */
+        retval |= !d->forwardAllEventsToTool &&
+            (keyEvent->key() == Qt::Key_Space ||
+             keyEvent->key() == Qt::Key_Escape);
+
+        break;
+    }
+    case QEvent::KeyRelease: {
+        QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
+        if (!keyEvent->isAutoRepeat()) {
+            retval = d->matcher.keyReleased((Qt::Key)keyEvent->key());
         }
-        return true;
-    case QEvent::MouseMove:
-        if (!d->currentAction) {
-            QMouseEvent *mevent = static_cast<QMouseEvent*>(event);
+        break;
+    }
+    case QEvent::MouseMove: {
+        QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
+        if (!d->matcher.mouseMoved(mouseEvent)) {
             //Update the current tool so things like the brush outline gets updated.
-            d->toolProxy->mouseMoveEvent(mevent, widgetToPixel(mevent->posF()));
-        } else {
-            d->currentAction->inputEvent(event);
-        }
-        return true;
-    case QEvent::Wheel:
-        if (d->currentAction) {
-            d->currentAction->inputEvent(event);
-        } else {
-            d->match(event);
-            if (d->currentAction) {
-                d->clearState();
-            }
+            d->toolProxy->mouseMoveEvent(mouseEvent, \
widgetToPixel(mouseEvent->posF()));  }
+        retval = true;
+        d->resetSavedTabletEvent();
+        break;
+    }
+    case QEvent::Wheel: {
+        QWheelEvent *wheelEvent = static_cast<QWheelEvent*>(event);
+        KisKeyShortcut::WheelAction action =
+            wheelEvent->delta() > 0 ?
+            KisKeyShortcut::WheelUp : KisKeyShortcut::WheelDown;
+
+        retval = d->matcher.wheelEvent(action);
         break;
+    }
     case QEvent::Enter:
         //Ensure we have focus so we get key events.
         d->canvas->canvasWidget()->setFocus();
-        return true;
+        break;
     case QEvent::Leave:
         //Clear all state so we don't have half-matched shortcuts dangling around.
-        d->clearState();
-        return true;
-    case QEvent::TabletPress: {
+        d->matcher.reset();
+        break;
+    case QEvent::TabletPress:
+    case QEvent::TabletMove:
+    case QEvent::TabletRelease: {
         //We want both the tablet information and the mouse button state.
-        //Since QTabletEvent only provides the tablet information, we save that
-        //and then ignore the event so it will generate a mouse event.
-        QTabletEvent* tevent = static_cast<QTabletEvent*>(event);
-
-        //Since events get deleted once they are processed we need to clone the \
                event
-        //to save it.
-        QTabletEvent* newEvent = new QTabletEvent(QEvent::TabletPress,
-                                                  tevent->pos(),
-                                                  tevent->globalPos(),
-                                                  tevent->hiResGlobalPos(),
-                                                  tevent->device(),
-                                                  tevent->pointerType(),
-                                                  tevent->pressure(),
-                                                  tevent->xTilt(),
-                                                  tevent->yTilt(),
-                                                  tevent->tangentialPressure(),
-                                                  tevent->rotation(),
-                                                  tevent->z(),
-                                                  tevent->modifiers(),
-                                                  tevent->uniqueId()
-                                                  );
-        d->tabletPressEvent = newEvent;
+        //Since QTabletEvent only provides the tablet information, we
+        //save that and then ignore the event so it will generate a mouse
+        //event.
+        QTabletEvent* tabletEvent = static_cast<QTabletEvent*>(event);
+        d->saveTabletEvent(tabletEvent);
         event->ignore();
         break;
     }
-    case QEvent::TabletMove:
-        //Only process tablet move events if the current action has special code for \
                it.
-        //In all other cases, we simply ignore it so it will generate a mouse event
-        //instead.
-        if (d->currentAction && d->currentAction->handleTablet()) {
-            d->currentAction->inputEvent(event);
-            return true;
-        } else {
-            event->ignore();
-        }
-        break;
-    case QEvent::TabletRelease:
-        //Always ignore tablet release events and have them generate mouse events \
                instead.
-        event->ignore();
     default:
         break;
     }
 
-    return false;
+    return !retval ? d->processUnhandledEvent(event) : true;
 }
 
 KisCanvas2* KisInputManager::canvas() const
@@ -451,16 +403,6 @@ KoToolProxy* KisInputManager::toolProxy() const
     return d->toolProxy;
 }
 
-QPointF KisInputManager::mousePosition() const
-{
-    return d->mousePosition;
-}
-
-QTabletEvent* KisInputManager::tabletPressEvent() const
-{
-    return d->tabletPressEvent;
-}
-
 void KisInputManager::setMirrorAxis()
 {
     d->setMirrorMode = true;
@@ -471,14 +413,11 @@ void KisInputManager::slotToolChanged()
 {
     QString toolId = KoToolManager::instance()->activeToolId();
     if (toolId == "ArtisticTextToolFactoryID" || toolId == "TextToolFactory_ID") {
-        d->fixedAction = true;
-        if (!d->currentAction) {
-            d->currentShortcut = d->shortcuts.at(0);
-            d->currentAction = d->currentShortcut->action();
-            d->currentAction->begin(d->currentShortcut->shortcutIndex());
-        }
+        d->forwardAllEventsToTool = true;
+        d->matcher.suppressAllActions(true);
     } else {
-        d->fixedAction = false;
+        d->forwardAllEventsToTool = false;
+        d->matcher.suppressAllActions(false);
     }
 }
 
diff --git a/krita/ui/input/kis_input_manager.h b/krita/ui/input/kis_input_manager.h
index 8689684..5a269d3 100644
--- a/krita/ui/input/kis_input_manager.h
+++ b/krita/ui/input/kis_input_manager.h
@@ -75,17 +75,12 @@ public:
      * The tool proxy of the current application.
      */
     KoToolProxy *toolProxy() const;
+
     /**
-     * The mouse position of the last mouse press event.
-     */
-    QPointF mousePosition() const;
-    /**
-     * This method can be used by actions to check whether we are
-     * dealing with tablet events.
-     *
-     * \return A tablet press event if there was one, otherwise 0.
+     * Returns the event object for the last tablet event
+     * happened. Returns null if there was no tablet event recently
      */
-    QTabletEvent *tabletPressEvent() const;
+    QTabletEvent *lastTabletEvent() const;
 
     /**
      * Convert a widget position to a pixel position.
diff --git a/krita/ui/input/kis_key_shortcut.cpp \
b/krita/ui/input/kis_key_shortcut.cpp new file mode 100644
index 0000000..54c3957
--- /dev/null
+++ b/krita/ui/input/kis_key_shortcut.cpp
@@ -0,0 +1,70 @@
+/*
+ *  Copyright (c) 2012 Dmitry Kazakov <dimula73@gmail.com>
+ *
+ *  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_key_shortcut.h"
+
+struct KisKeyShortcut::Private
+{
+    QList<Qt::Key> modifiers;
+    Qt::Key key;
+    bool useWheel;
+    WheelAction wheelAction;
+};
+
+
+KisKeyShortcut::KisKeyShortcut(KisAbstractInputAction *action, int index)
+    : KisAbstractShortcut(action, index),
+      m_d(new Private)
+{
+}
+
+KisKeyShortcut::~KisKeyShortcut()
+{
+    delete m_d;
+}
+
+int KisKeyShortcut::priority() const
+{
+    return m_d->modifiers.size() * 2 + 1;
+}
+
+void KisKeyShortcut::setKey(const QList<Qt::Key> &modifiers, Qt::Key key)
+{
+    m_d->modifiers = modifiers;
+    m_d->key = key;
+    m_d->useWheel = false;
+}
+
+void KisKeyShortcut::setWheel(const QList<Qt::Key> &modifiers, WheelAction \
wheelAction) +{
+    m_d->modifiers = modifiers;
+    m_d->wheelAction = wheelAction;
+    m_d->useWheel = true;
+}
+
+bool KisKeyShortcut::matchKey(const QList<Qt::Key> &modifiers, Qt::Key key)
+{
+    return !m_d->useWheel && key == m_d->key &&
+        compareKeys(modifiers, m_d->modifiers);
+}
+
+bool KisKeyShortcut::matchKey(const QList<Qt::Key> &modifiers, WheelAction \
wheelAction) +{
+    return m_d->useWheel && wheelAction == m_d->wheelAction &&
+        compareKeys(modifiers, m_d->modifiers);
+}
diff --git a/krita/ui/input/kis_key_shortcut.h b/krita/ui/input/kis_key_shortcut.h
new file mode 100644
index 0000000..fb683c2
--- /dev/null
+++ b/krita/ui/input/kis_key_shortcut.h
@@ -0,0 +1,54 @@
+/*
+ *  Copyright (c) 2012 Dmitry Kazakov <dimula73@gmail.com>
+ *
+ *  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_KEY_SHORTCUT_H
+#define __KIS_KEY_SHORTCUT_H
+
+#include "kis_abstract_shortcut.h"
+
+/**
+ * This class represents a shortcut that is started with simple
+ * key presses only, that is a simple keyboard hotkey or a mouse
+ * wheel rotation by one delta.
+ */
+
+class KRITAUI_EXPORT KisKeyShortcut : public KisAbstractShortcut
+{
+public:
+    enum WheelAction {
+        WheelUp, ///< Mouse wheel moves up.
+        WheelDown ///< Mouse wheel moves down.
+    };
+
+    KisKeyShortcut(KisAbstractInputAction *action, int index);
+    ~KisKeyShortcut();
+
+    int priority() const;
+
+    void setKey(const QList<Qt::Key> &modifiers, Qt::Key key);
+    void setWheel(const QList<Qt::Key> &modifiers, WheelAction wheelAction);
+
+    bool matchKey(const QList<Qt::Key> &modifiers, Qt::Key key);
+    bool matchKey(const QList<Qt::Key> &modifiers, WheelAction wheelAction);
+
+private:
+    class Private;
+    Private * const m_d;
+};
+
+#endif /* __KIS_KEY_SHORTCUT_H */
diff --git a/krita/ui/input/kis_pan_action.cpp b/krita/ui/input/kis_pan_action.cpp
index fbffb1a..cfb7334 100644
--- a/krita/ui/input/kis_pan_action.cpp
+++ b/krita/ui/input/kis_pan_action.cpp
@@ -33,9 +33,8 @@
 class KisPanAction::Private
 {
 public:
-    Private() : active(false), panDistance(10) { }
+    Private() : panDistance(10) { }
 
-    bool active;
     const int panDistance;
 };
 
@@ -58,13 +57,22 @@ KisPanAction::~KisPanAction()
     delete d;
 }
 
-void KisPanAction::begin(int shortcut)
+void KisPanAction::activate()
 {
+    QApplication::setOverrideCursor(Qt::OpenHandCursor);
+}
+
+void KisPanAction::deactivate()
+{
+    QApplication::restoreOverrideCursor();
+}
+
+void KisPanAction::begin(int shortcut, QEvent *event)
+{
+    KisAbstractInputAction::begin(shortcut, event);
+
     switch (shortcut) {
         case PanToggleShortcut:
-            setMousePosition(inputManager()->canvas()->coordinatesConverter()->documentToWidget(inputManager()->mousePosition()));
                
-            QApplication::setOverrideCursor(Qt::OpenHandCursor);
-            d->active = true;
             break;
         case PanLeftShortcut:
             inputManager()->canvas()->canvasController()->pan(QPoint(d->panDistance, \
0)); @@ -81,37 +89,8 @@ void KisPanAction::begin(int shortcut)
     }
 }
 
-void KisPanAction::end()
-{
-    d->active = false;
-    QApplication::restoreOverrideCursor();
-}
-
-void KisPanAction::inputEvent(QEvent *event)
-{
-    switch (event->type()) {
-        case QEvent::MouseButtonPress: {
-            setMousePosition(static_cast<QMouseEvent*>(event)->posF());
-            break;
-        }
-        case QEvent::MouseMove: {
-            QMouseEvent *mevent = static_cast<QMouseEvent*>(event);
-            if (mevent->buttons()) {
-                QPointF relMovement = -(mevent->posF() - mousePosition());
-                inputManager()->canvas()->canvasController()->pan(relMovement.toPoint());
                
-                setMousePosition(mevent->posF());
-                QApplication::changeOverrideCursor(Qt::ClosedHandCursor);
-            } else {
-                QApplication::changeOverrideCursor(Qt::OpenHandCursor);
-            }
-            break;
-        }
-        default:
-            break;
-    }
-}
-
-bool KisPanAction::isBlockingAutoRepeat() const
+void KisPanAction::mouseMoved(const QPointF &lastPos, const QPointF &pos)
 {
-    return d->active;
+    QPointF relMovement = -(pos - lastPos);
+    inputManager()->canvas()->canvasController()->pan(relMovement.toPoint());
 }
diff --git a/krita/ui/input/kis_pan_action.h b/krita/ui/input/kis_pan_action.h
index e2ed64b..9944271 100644
--- a/krita/ui/input/kis_pan_action.h
+++ b/krita/ui/input/kis_pan_action.h
@@ -43,11 +43,11 @@ public:
     explicit KisPanAction(KisInputManager *manager);
     virtual ~KisPanAction();
 
-    virtual void begin(int shortcut);
-    virtual void end();
-    virtual void inputEvent(QEvent* event);
+    void activate();
+    void deactivate();
 
-    virtual bool isBlockingAutoRepeat() const;
+    void begin(int shortcut, QEvent *event);
+    void mouseMoved(const QPointF &lastPos, const QPointF &pos);
 
 private:
     class Private;
diff --git a/krita/ui/input/kis_rotate_canvas_action.cpp \
b/krita/ui/input/kis_rotate_canvas_action.cpp index 5c4eaf5..be10feb 100644
--- a/krita/ui/input/kis_rotate_canvas_action.cpp
+++ b/krita/ui/input/kis_rotate_canvas_action.cpp
@@ -19,26 +19,15 @@
 #include "kis_rotate_canvas_action.h"
 
 #include <QApplication>
-
 #include <KLocalizedString>
 
 #include "kis_canvas_controller.h"
-
 #include <kis_canvas2.h>
-#include <kis_image.h>
-
 #include "kis_input_manager.h"
 
-class KisRotateCanvasAction::Private
-{
-public:
-    Private() : active(false) { }
-
-    bool active;
-};
 
 KisRotateCanvasAction::KisRotateCanvasAction(KisInputManager* manager)
-    : KisAbstractInputAction(manager), d(new Private)
+    : KisAbstractInputAction(manager)
 {
     setName(i18n("Rotate Canvas"));
     QHash<QString, int> shortcuts;
@@ -51,19 +40,27 @@ KisRotateCanvasAction::KisRotateCanvasAction(KisInputManager* \
manager)  
 KisRotateCanvasAction::~KisRotateCanvasAction()
 {
-    delete d;
 }
 
-void KisRotateCanvasAction::begin(int shortcut)
+void KisRotateCanvasAction::activate()
 {
+    QApplication::setOverrideCursor(Qt::OpenHandCursor);
+}
+
+void KisRotateCanvasAction::deactivate()
+{
+    QApplication::restoreOverrideCursor();
+}
+
+void KisRotateCanvasAction::begin(int shortcut, QEvent *event)
+{
+    KisAbstractInputAction::begin(shortcut, event);
+
     KisCanvasController *canvasController =
         dynamic_cast<KisCanvasController*>(inputManager()->canvas()->canvasController());
  
     switch(shortcut) {
         case RotateToggleShortcut:
-            setMousePosition(inputManager()->canvas()->coordinatesConverter()->documentToWidget(inputManager()->mousePosition()));
                
-            QApplication::setOverrideCursor(Qt::OpenHandCursor);
-            d->active = true;
             break;
         case RotateLeftShortcut:
             canvasController->rotateCanvasLeft15();
@@ -77,48 +74,19 @@ void KisRotateCanvasAction::begin(int shortcut)
     }
 }
 
-void KisRotateCanvasAction::end()
+void KisRotateCanvasAction::mouseMoved(const QPointF &lastPos, const QPointF &pos)
 {
-    d->active = false;
-    QApplication::restoreOverrideCursor();
-}
+    const KisCoordinatesConverter *converter = \
inputManager()->canvas()->coordinatesConverter(); +    QPointF centerPoint = \
converter->flakeToWidget(converter->flakeCenterPoint()); +    QPointF oldPoint = \
lastPos - centerPoint; +    QPointF newPoint = pos - centerPoint;
 
-void KisRotateCanvasAction::inputEvent(QEvent* event)
-{
-    switch (event->type()) {
-        case QEvent::MouseButtonPress: {
-            setMousePosition(static_cast<QMouseEvent*>(event)->posF());
-            break;
-        }
-        case QEvent::MouseMove: {
-            QMouseEvent *mevent = static_cast<QMouseEvent*>(event);
-            if (mevent->buttons()) {
-                const KisCoordinatesConverter *converter = \
                inputManager()->canvas()->coordinatesConverter();
-                QPointF centerPoint = \
                converter->flakeToWidget(converter->flakeCenterPoint());
-                QPointF oldPoint = mousePosition() - centerPoint;
-                QPointF newPoint = mevent->posF() - centerPoint;
+    qreal oldAngle = atan2(oldPoint.y(), oldPoint.x());
+    qreal newAngle = atan2(newPoint.y(), newPoint.x());
 
-                qreal oldAngle = atan2(oldPoint.y(), oldPoint.x());
-                qreal newAngle = atan2(newPoint.y(), newPoint.x());
+    float angle = (180 / M_PI) * (newAngle - oldAngle);
 
-                float angle = (180 / M_PI) * (newAngle - oldAngle);
-
-                KisCanvasController *canvasController =
-                    \
                dynamic_cast<KisCanvasController*>(inputManager()->canvas()->canvasController());
                
-                canvasController->rotateCanvas(angle);
-
-                setMousePosition(mevent->posF());
-                QApplication::changeOverrideCursor(Qt::ClosedHandCursor);
-            } else {
-                QApplication::changeOverrideCursor(Qt::OpenHandCursor);
-            }
-        }
-        default:
-            break;
-    }
-}
-
-bool KisRotateCanvasAction::isBlockingAutoRepeat() const
-{
-    return d->active;
+    KisCanvasController *canvasController =
+        dynamic_cast<KisCanvasController*>(inputManager()->canvas()->canvasController());
 +    canvasController->rotateCanvas(angle);
 }
diff --git a/krita/ui/input/kis_rotate_canvas_action.h \
b/krita/ui/input/kis_rotate_canvas_action.h index 0ae2a6b..fdaaa60 100644
--- a/krita/ui/input/kis_rotate_canvas_action.h
+++ b/krita/ui/input/kis_rotate_canvas_action.h
@@ -43,15 +43,10 @@ public:
     explicit KisRotateCanvasAction(KisInputManager* manager);
     virtual ~KisRotateCanvasAction();
 
-    virtual void begin(int shortcut);
-    virtual void end();
-    virtual void inputEvent(QEvent* event);
-
-    virtual bool isBlockingAutoRepeat() const;
-
-private:
-    class Private;
-    Private * const d;
+    void activate();
+    void deactivate();
+    void begin(int shortcut, QEvent *event);
+    void mouseMoved(const QPointF &lastPos, const QPointF &pos);
 };
 
 #endif // KIS_ROTATE_CANVAS_ACTION_H
diff --git a/krita/ui/input/kis_shortcut.cpp b/krita/ui/input/kis_shortcut.cpp
deleted file mode 100644
index 61a40c1..0000000
--- a/krita/ui/input/kis_shortcut.cpp
+++ /dev/null
@@ -1,162 +0,0 @@
-/* This file is part of the KDE project
- * Copyright (C) 2012 Arjen Hiemstra <ahiemstra@heimr.nl>
- *
- *  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.h"
-
-#include "kis_abstract_input_action.h"
-#include <QEvent>
-#include <QDebug>
-#include <QKeyEvent>
-
-class KisShortcut::Private
-{
-public:
-    Private() : wheelState(WheelUndefined), currentWheelState(WheelUndefined), \
                action(0), shortcutIndex(0) { }
-    QList<Qt::Key> keys;
-    QList<Qt::Key> keyState;
-    QList<Qt::MouseButton> buttons;
-    QList<Qt::MouseButton> buttonState;
-    WheelState wheelState;
-    WheelState currentWheelState;
-
-    KisAbstractInputAction *action;
-    int shortcutIndex;
-};
-
-KisShortcut::KisShortcut() : d(new Private)
-{
-}
-
-KisShortcut::~KisShortcut()
-{
-    delete d;
-}
-
-int KisShortcut::priority() const
-{
-    return d->keys.count() * 2 + d->buttons.count();
-}
-
-KisAbstractInputAction* KisShortcut::action() const
-{
-    return d->action;
-}
-
-void KisShortcut::setAction(KisAbstractInputAction* action)
-{
-    d->action = action;
-}
-
-int KisShortcut::shortcutIndex() const
-{
-    return d->shortcutIndex;
-}
-
-void KisShortcut::setShortcutIndex(int index)
-{
-    d->shortcutIndex = index;
-}
-
-void KisShortcut::setButtons(const QList<Qt::MouseButton> &buttons)
-{
-    d->buttons = buttons;
-    d->buttonState.clear();
-}
-
-void KisShortcut::setKeys(const QList< Qt::Key >& keys)
-{
-    d->keys = keys;
-    d->keyState.clear();
-}
-
-void KisShortcut::setWheel(KisShortcut::WheelState state)
-{
-    d->wheelState = state;
-}
-
-KisShortcut::MatchLevel KisShortcut::matchLevel()
-{
-    if (d->keys.count() == d->keyState.count() && d->buttons.count() == \
d->buttonState.count() && (d->wheelState == WheelUndefined || d->currentWheelState == \
                d->wheelState)) {
-        return CompleteMatch;
-    } else if (d->keyState.count() > 0 || d->buttonState.count() > 0) {
-        return PartialMatch;
-    }
-
-    return NoMatch;
-}
-
-void KisShortcut::match(QEvent* event)
-{
-    switch (event->type()) {
-        case QEvent::KeyPress: {
-            QKeyEvent *kevent = static_cast<QKeyEvent*>(event);
-            Qt::Key key = static_cast<Qt::Key>(kevent->key());
-            if (d->keys.contains(key) && !d->keyState.contains(key)) {
-                d->keyState.append(key);
-            }
-            break;
-        }
-        case QEvent::KeyRelease: {
-            QKeyEvent *kevent = static_cast<QKeyEvent*>(event);
-            Qt::Key key = static_cast<Qt::Key>(kevent->key());
-            if (d->keyState.contains(key)) {
-                d->keyState.removeOne(key);
-            }
-            break;
-        }
-        case QEvent::MouseButtonPress: {
-            Qt::MouseButton button = static_cast<QMouseEvent*>(event)->button();
-            if (d->buttons.contains(button) && !d->buttonState.contains(button)) {
-                d->buttonState.append(button);
-            }
-            break;
-        }
-        case QEvent::MouseButtonRelease: {
-            Qt::MouseButton button = static_cast<QMouseEvent*>(event)->button();
-            if (d->buttonState.contains(button)) {
-                d->buttonState.removeOne(button);
-            }
-            break;
-        }
-        case QEvent::MouseButtonDblClick: {
-            Qt::MouseButton button = static_cast<QMouseEvent*>(event)->button();
-            if (d->buttons.contains(button) && !d->buttonState.contains(button)) {
-                d->buttonState.append(button);
-            }
-            break;
-        }
-        case QEvent::Wheel: {
-            QWheelEvent *wevent = static_cast<QWheelEvent*>(event);
-            if (wevent->delta() > 0) {
-                d->currentWheelState = WheelUp;
-            } else {
-                d->currentWheelState = WheelDown;
-            }
-            break;
-        }
-        default:
-            break;
-    }
-}
-
-void KisShortcut::clear()
-{
-    d->buttonState.clear();
-    d->keyState.clear();
-    d->currentWheelState = WheelUndefined;
-}
diff --git a/krita/ui/input/kis_shortcut.h b/krita/ui/input/kis_shortcut.h
deleted file mode 100644
index fafc428..0000000
--- a/krita/ui/input/kis_shortcut.h
+++ /dev/null
@@ -1,120 +0,0 @@
-/* This file is part of the KDE project
- * Copyright (C) 2012 Arjen Hiemstra <ahiemstra@heimr.nl>
- *
- *  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 KISSHORTCUT_H
-#define KISSHORTCUT_H
-
-#include <Qt>
-#include <QList>
-
-class QEvent;
-class KisAbstractInputAction;
-
-/**
- * \brief A combination of keys and buttons used for matching input.
- *
- * The Shortcut class manages a combination of keys and buttons and
- * the state of those buttons. It can be used to detect whether a certain
- * combination of inputs has been activated.
- */
-class KisShortcut
-{
-
-public:
-    /**
-     * Describes how well recent input matches this shortcut.
-     */
-    enum MatchLevel {
-        NoMatch, ///< No match at all.
-        PartialMatch, ///< It may match, with additional input.
-        CompleteMatch ///< Completely matches the input sent.
-    };
-
-    /**
-     * States the mouse wheel can be in.
-     */
-    enum WheelState {
-        WheelUndefined, ///< The state is unknown.
-        WheelUp, ///< Mouse wheel moves up.
-        WheelDown ///< Mouse wheel moves down.
-    };
-
-    /**
-     * Constructor.
-     */
-    KisShortcut();
-    /**
-     * Destructor.
-     */
-    virtual ~KisShortcut();
-
-    /**
-     */
-    int priority() const;
-    /**
-     * The action associated with this shortcut.
-     */
-    KisAbstractInputAction* action() const;
-    /**
-     * Set the action associated with this shortcut.
-     */
-    void setAction(KisAbstractInputAction *action);
-    /**
-     * The index of the shortcut.
-     *
-     * \see KisAbstractInputAction::begin()
-     */
-    int shortcutIndex() const;
-    /**
-     * Set the index of the shortcut.
-     */
-    void setShortcutIndex(int index);
-    /**
-     * Set the list of keys used by this shortcut.
-     */
-    void setKeys(const QList<Qt::Key> &keys);
-    /**
-     * Set the list of buttons used by this shortcut.
-     */
-    void setButtons(const QList<Qt::MouseButton> &buttons);
-    /**
-     * Set the wheel state to use for this shortcut.
-     */
-    void setWheel(WheelState state);
-    /**
-     * Returns how well this shortcut matches recent input.
-     */
-    MatchLevel matchLevel();
-    /**
-     * Try to match input to the keys and buttons used by
-     * this shortcut.
-     *
-     * \param event An event to match.
-     */
-    void match(QEvent* event);
-    /**
-     * Clear all state of this shortcut.
-     */
-    void clear();
-
-private:
-    class Private;
-    Private * const d;
-};
-
-#endif // KISSHORTCUT_H
diff --git a/krita/ui/input/kis_shortcut_matcher.cpp \
b/krita/ui/input/kis_shortcut_matcher.cpp new file mode 100644
index 0000000..e0e99c2
--- /dev/null
+++ b/krita/ui/input/kis_shortcut_matcher.cpp
@@ -0,0 +1,287 @@
+/*
+ *  Copyright (c) 2012 Dmitry Kazakov <dimula73@gmail.com>
+ *
+ *  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 <QMouseEvent>
+#include <QTabletEvent>
+
+#include "kis_abstract_input_action.h"
+#include "kis_stroke_shortcut.h"
+
+
+struct KisShortcutMatcher::Private
+{
+    Private() : suppressAllActions(false) {}
+
+    QList<KisKeyShortcut*> keyShortcuts;
+    QList<KisStrokeShortcut*> strokeShortcuts;
+    QList<KisAbstractInputAction*> actions;
+
+    QList<Qt::Key> keys;
+    QList<Qt::MouseButton> buttons;
+
+    KisStrokeShortcut *runningShortcut;
+    KisStrokeShortcut *readyShortcut;
+    QList<KisStrokeShortcut*> readyShortcuts;
+
+    bool suppressAllActions;
+};
+
+KisShortcutMatcher::KisShortcutMatcher()
+    : m_d(new Private)
+{
+    m_d->runningShortcut = 0;
+    m_d->readyShortcut = 0;
+}
+
+KisShortcutMatcher::~KisShortcutMatcher()
+{
+    qDeleteAll(m_d->keyShortcuts);
+    qDeleteAll(m_d->strokeShortcuts);
+    qDeleteAll(m_d->actions);
+    delete m_d;
+}
+
+void KisShortcutMatcher::addShortcut(KisKeyShortcut *shortcut)
+{
+    m_d->keyShortcuts.append(shortcut);
+}
+
+void KisShortcutMatcher::addShortcut(KisStrokeShortcut *shortcut)
+{
+    m_d->strokeShortcuts.append(shortcut);
+}
+
+void KisShortcutMatcher::addAction(KisAbstractInputAction *action)
+{
+    m_d->actions.append(action);
+}
+
+bool KisShortcutMatcher::keyPressed(Qt::Key key)
+{
+    bool retval = false;
+
+    if (m_d->keys.contains(key)) reset();
+
+    if (!m_d->runningShortcut) {
+        retval = tryRunKeyShortcut(key);
+    }
+
+    m_d->keys.append(key);
+
+    if (!m_d->runningShortcut) {
+        prepareReadyShortcuts();
+        tryActivateReadyShortcut();
+    }
+
+    return retval;
+}
+
+bool KisShortcutMatcher::keyReleased(Qt::Key key)
+{
+    if (!m_d->keys.contains(key)) reset();
+    else m_d->keys.removeOne(key);
+
+    if (!m_d->runningShortcut) {
+        prepareReadyShortcuts();
+        tryActivateReadyShortcut();
+    }
+
+    return false;
+}
+
+bool KisShortcutMatcher::buttonPressed(Qt::MouseButton button, QMouseEvent *event)
+{
+    bool retval = false;
+
+    if (m_d->buttons.contains(button)) reset();
+
+    if (!m_d->runningShortcut) {
+        retval = tryRunReadyShortcut(button, event);
+    }
+
+    m_d->buttons.append(button);
+
+    if (!m_d->runningShortcut) {
+        prepareReadyShortcuts();
+        tryActivateReadyShortcut();
+    }
+
+    return retval;
+}
+
+bool KisShortcutMatcher::buttonReleased(Qt::MouseButton button, QMouseEvent *event)
+{
+    bool retval = false;
+
+    if (m_d->runningShortcut) {
+        retval = tryEndRunningShortcut(button, event);
+    }
+
+    if (!m_d->buttons.contains(button)) reset();
+    else m_d->buttons.removeOne(button);
+
+    if (!m_d->runningShortcut) {
+        prepareReadyShortcuts();
+        tryActivateReadyShortcut();
+    }
+
+    return retval;
+}
+
+bool KisShortcutMatcher::wheelEvent(KisKeyShortcut::WheelAction wheelAction)
+{
+    if (m_d->runningShortcut) return false;
+
+    return tryRunWheelShortcut(wheelAction);
+}
+
+bool KisShortcutMatcher::mouseMoved(QMouseEvent *event)
+{
+    if (!m_d->runningShortcut) return false;
+
+    m_d->runningShortcut->action()->inputEvent(event);
+    return true;
+}
+
+void KisShortcutMatcher::reset()
+{
+    m_d->keys.clear();
+    m_d->buttons.clear();
+}
+
+void KisShortcutMatcher::suppressAllActions(bool value)
+{
+    m_d->suppressAllActions = value;
+}
+
+bool KisShortcutMatcher::tryRunWheelShortcut(KisKeyShortcut::WheelAction \
wheelAction) +{
+    return tryRunKeyShortcutImpl(wheelAction);
+}
+
+bool KisShortcutMatcher::tryRunKeyShortcut(Qt::Key key)
+{
+    return tryRunKeyShortcutImpl(key);
+}
+
+template<typename T>
+bool KisShortcutMatcher::tryRunKeyShortcutImpl(T param)
+{
+    if (m_d->suppressAllActions) return false;
+
+    KisKeyShortcut *goodCandidate = 0;
+
+    foreach(KisKeyShortcut *s, m_d->keyShortcuts) {
+        if(s->matchKey(m_d->keys, param) &&
+           (!goodCandidate || s->priority() > goodCandidate->priority())) {
+
+            goodCandidate = s;
+        }
+    }
+
+    if (goodCandidate) {
+        goodCandidate->action()->begin(goodCandidate->shortcutIndex(), 0);
+        goodCandidate->action()->end(0);
+    }
+
+    return goodCandidate;
+}
+
+void KisShortcutMatcher::prepareReadyShortcuts()
+{
+    m_d->readyShortcuts.clear();
+    if (m_d->suppressAllActions) return;
+
+    foreach(KisStrokeShortcut *s, m_d->strokeShortcuts) {
+        if (s->matchReady(m_d->keys, m_d->buttons)) {
+            m_d->readyShortcuts.append(s);
+        }
+    }
+}
+
+bool KisShortcutMatcher::tryRunReadyShortcut(Qt::MouseButton button, QMouseEvent \
*event) +{
+    KisStrokeShortcut *goodCandidate = 0;
+
+    foreach(KisStrokeShortcut *s, m_d->readyShortcuts) {
+        if (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();
+                goodCandidate->action()->activate();
+            }
+            m_d->readyShortcut = 0;
+        } else {
+            goodCandidate->action()->activate();
+        }
+
+        goodCandidate->action()->begin(goodCandidate->shortcutIndex(), event);
+        m_d->runningShortcut = goodCandidate;
+    }
+
+    return goodCandidate;
+}
+
+void KisShortcutMatcher::tryActivateReadyShortcut()
+{
+    KisStrokeShortcut *goodCandidate = 0;
+
+    foreach(KisStrokeShortcut *s, m_d->readyShortcuts) {
+        if (!goodCandidate || s->priority() > goodCandidate->priority()) {
+            goodCandidate = s;
+        }
+    }
+
+    if (goodCandidate) {
+        if (m_d->readyShortcut && m_d->readyShortcut != goodCandidate) {
+            m_d->readyShortcut->action()->deactivate();
+            m_d->readyShortcut = 0;
+        }
+
+        if (!m_d->readyShortcut) {
+            goodCandidate->action()->activate();
+            m_d->readyShortcut = goodCandidate;
+        }
+    } else if (m_d->readyShortcut) {
+        m_d->readyShortcut->action()->deactivate();
+        m_d->readyShortcut = 0;
+    }
+}
+
+bool KisShortcutMatcher::tryEndRunningShortcut(Qt::MouseButton button, QMouseEvent \
*event) +{
+    Q_ASSERT(m_d->runningShortcut);
+    Q_ASSERT(!m_d->readyShortcut);
+
+    if (m_d->runningShortcut->matchBegin(button)) {
+        m_d->runningShortcut->action()->end(event);
+        m_d->runningShortcut->action()->deactivate();
+        m_d->runningShortcut = 0;
+    }
+
+    return !m_d->runningShortcut;
+}
diff --git a/krita/ui/input/kis_shortcut_matcher.h \
b/krita/ui/input/kis_shortcut_matcher.h new file mode 100644
index 0000000..0449a6e
--- /dev/null
+++ b/krita/ui/input/kis_shortcut_matcher.h
@@ -0,0 +1,143 @@
+/*
+ *  Copyright (c) 2012 Dmitry Kazakov <dimula73@gmail.com>
+ *
+ *  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 "kis_abstract_shortcut.h"
+
+#include <QList>
+#include "kis_key_shortcut.h"
+
+class QMouseEvent;
+class QTabletEvent;
+
+class KisStrokeShortcut;
+class KisAbstractInputAction;
+
+/**
+ * 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.
+ *
+ * \see KisStrokeShortcut
+ * \see KisKeyShortcut
+ */
+class KRITAUI_EXPORT KisShortcutMatcher
+{
+public:
+    KisShortcutMatcher();
+    ~KisShortcutMatcher();
+
+    void addShortcut(KisKeyShortcut *shortcut);
+    void addShortcut(KisStrokeShortcut *shortcut);
+    void addAction(KisAbstractInputAction *action);
+
+    /**
+     * Handles a key press event.
+     *
+     * \return whether the event has been handled successfully and
+     * should be eaten by the events filter
+     */
+    bool keyPressed(Qt::Key key);
+
+    /**
+     * Handles a key release event.
+     *
+     * \return whether the event has been handled successfully and
+     * should be eaten by the events filter
+     */
+    bool keyReleased(Qt::Key key);
+
+    /**
+     * Handles the mouse button press event
+     *
+     * \param button the button that has been pressed
+     * \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 buttonPressed(Qt::MouseButton button, QMouseEvent *event);
+
+    /**
+     * Handles the mouse button release event
+     *
+     * \param button the button that has been pressed
+     * \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 buttonReleased(Qt::MouseButton button, QMouseEvent *event);
+
+    /**
+     * Handles the mouse wheel event
+     *
+     * \return whether the event has been handled successfully and
+     * should be eaten by the events filter
+     */
+    bool wheelEvent(KisKeyShortcut::WheelAction wheelAction);
+
+    /**
+     * Handles the mouse move event
+     *
+     * \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 mouseMoved(QMouseEvent *event);
+
+    /**
+     * Resets the internal state of the matcher
+     *
+     * This should be done when the window has lost the focus for
+     * some time, so that several events could be lost
+     */
+    void reset();
+
+    /**
+     * 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);
+
+private:
+    friend class KisInputManagerTest;
+
+    bool tryRunKeyShortcut(Qt::Key key);
+    bool tryRunWheelShortcut(KisKeyShortcut::WheelAction wheelAction);
+    template<typename T> bool tryRunKeyShortcutImpl(T param);
+
+    void prepareReadyShortcuts();
+
+    bool tryRunReadyShortcut(Qt::MouseButton button, QMouseEvent *event);
+    void tryActivateReadyShortcut();
+    bool tryEndRunningShortcut(Qt::MouseButton button, QMouseEvent *event);
+
+private:
+    class Private;
+    Private * const m_d;
+};
+
+#endif /* __KIS_SHORTCUT_MATCHER_H */
diff --git a/krita/ui/input/kis_show_palette_action.cpp \
b/krita/ui/input/kis_show_palette_action.cpp index 11913a5..1af939c 100644
--- a/krita/ui/input/kis_show_palette_action.cpp
+++ b/krita/ui/input/kis_show_palette_action.cpp
@@ -36,17 +36,8 @@ KisShowPaletteAction::~KisShowPaletteAction()
 
 }
 
-void KisShowPaletteAction::begin(int /*shortcut*/)
+void KisShowPaletteAction::begin(int, QEvent *event)
 {
-    inputManager()->canvas()->favoriteResourceManager()->slotShowPopupPalette(inputMa \
nager()->canvas()->coordinatesConverter()->documentToWidget(inputManager()->mousePosition()).toPoint());
                
-}
-
-void KisShowPaletteAction::end()
-{
-
-}
-
-void KisShowPaletteAction::inputEvent(QEvent* event)
-{
-    Q_UNUSED(event);
+    QMouseEvent *mouseEvent = dynamic_cast<QMouseEvent*>(event);
+    inputManager()->canvas()->favoriteResourceManager()->slotShowPopupPalette(mouseEvent->pos());
  }
diff --git a/krita/ui/input/kis_show_palette_action.h \
b/krita/ui/input/kis_show_palette_action.h index 2485883..ebafab3 100644
--- a/krita/ui/input/kis_show_palette_action.h
+++ b/krita/ui/input/kis_show_palette_action.h
@@ -32,9 +32,7 @@ public:
     explicit KisShowPaletteAction(KisInputManager* manager);
     virtual ~KisShowPaletteAction();
 
-    virtual void begin(int /*shortcut*/);
-    virtual void end();
-    virtual void inputEvent(QEvent* event);
+    virtual void begin(int, QEvent *);
 };
 
 #endif // KIS_SHOW_PALETTE_ACTION_H
diff --git a/krita/ui/input/kis_stroke_shortcut.cpp \
b/krita/ui/input/kis_stroke_shortcut.cpp new file mode 100644
index 0000000..091ecaa
--- /dev/null
+++ b/krita/ui/input/kis_stroke_shortcut.cpp
@@ -0,0 +1,72 @@
+/*
+ *  Copyright (c) 2012 Dmitry Kazakov <dimula73@gmail.com>
+ *
+ *  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_stroke_shortcut.h"
+
+
+struct KisStrokeShortcut::Private
+{
+    QList<Qt::Key> modifiers;
+    QList<Qt::MouseButton> buttons;
+};
+
+
+KisStrokeShortcut::KisStrokeShortcut(KisAbstractInputAction *action, int index)
+    : KisAbstractShortcut(action, index),
+      m_d(new Private)
+{
+}
+
+KisStrokeShortcut::~KisStrokeShortcut()
+{
+    delete m_d;
+}
+
+int KisStrokeShortcut::priority() const
+{
+    return m_d->modifiers.size() * 2 + m_d->buttons.size();
+}
+
+void KisStrokeShortcut::setButtons(const QList<Qt::Key> &modifiers,
+                                   const QList<Qt::MouseButton> &buttons)
+{
+    Q_ASSERT(buttons.size() > 0);
+
+    m_d->modifiers = modifiers;
+    m_d->buttons = buttons;
+}
+
+bool KisStrokeShortcut::matchReady(const QList<Qt::Key> &modifiers,
+                                   const QList<Qt::MouseButton> &buttons)
+{
+    if (!compareKeys(m_d->modifiers, modifiers) ||
+        buttons.size() < m_d->buttons.size() - 1) {
+
+        return false;
+    }
+
+    foreach(Qt::MouseButton button, buttons) {
+        if (!m_d->buttons.contains(button)) return false;
+    }
+    return true;
+}
+
+bool KisStrokeShortcut::matchBegin(Qt::MouseButton button)
+{
+    return m_d->buttons.contains(button);
+}
diff --git a/krita/ui/input/kis_stroke_shortcut.h \
b/krita/ui/input/kis_stroke_shortcut.h new file mode 100644
index 0000000..4743148
--- /dev/null
+++ b/krita/ui/input/kis_stroke_shortcut.h
@@ -0,0 +1,80 @@
+/*
+ *  Copyright (c) 2012 Dmitry Kazakov <dimula73@gmail.com>
+ *
+ *  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_STROKE_SHORTCUT_H
+#define __KIS_STROKE_SHORTCUT_H
+
+#include "kis_abstract_shortcut.h"
+
+/**
+ * This class represents a shortcut that starts an action that can
+ * involve pressing the mouse button and, probably, moving the cursor.
+ *
+ * The stroke shortcut may be represented as a simple state machine:
+ * It transits between 3 states:
+ *
+ * Idle <-> Ready <-> Running
+ *
+ * The possibility of trasition between Idle <-> Ready is defined
+ * with a matchReady() method. The transition Ready <-> Running is
+ * defined by matchBegin(). The Ready state is used for showing the
+ * user the cursor of the upcoming action and the Running state shows
+ * that the action linked to the shortcut should be activated.
+ */
+
+class KRITAUI_EXPORT KisStrokeShortcut : public KisAbstractShortcut
+{
+public:
+    KisStrokeShortcut(KisAbstractInputAction *action, int index);
+    ~KisStrokeShortcut();
+
+    int priority() const;
+
+    /**
+     * Sets the configuration for this shortcut
+     *
+     * \param modifiers keyboard keys that should be holded
+     *                  for the shortcut to trigger
+     * \param buttons mouse buttons that should be pressed (simultaneously)
+     *                for the shortcut to trigger
+     */
+    void setButtons(const QList<Qt::Key> &modifiers,
+                    const QList<Qt::MouseButton> &buttons);
+
+    /**
+     * Reports whether all but one buttons and modifiers are pressed
+     * for the shortcut. Such configuration means that the input manager
+     * can show the user that pressing the mouse button will start some
+     * action. This can be done with, e.g. changing the cursor.
+     */
+
+    bool matchReady(const QList<Qt::Key> &modifiers,
+                    const QList<Qt::MouseButton> &buttons);
+    /**
+     * Reports whether the shortcut can transit form the "Ready"
+     * to "Running" state. It means that the last button of the shortcut
+     * is pressed.
+     */
+    bool matchBegin(Qt::MouseButton button);
+
+private:
+    class Private;
+    Private * const m_d;
+};
+
+#endif /* __KIS_STROKE_SHORTCUT_H */
diff --git a/krita/ui/input/kis_tool_invocation_action.cpp \
b/krita/ui/input/kis_tool_invocation_action.cpp index a9b6a41..91cb866 100644
--- a/krita/ui/input/kis_tool_invocation_action.cpp
+++ b/krita/ui/input/kis_tool_invocation_action.cpp
@@ -31,18 +31,11 @@
 class KisToolInvocationAction::Private
 {
 public:
-    Private(KisToolInvocationAction *qq) : q(qq), useTablet(false) { }
+    Private(KisToolInvocationAction *qq) : q(qq), active(false) { }
     QPointF tabletToPixel(const QPointF& globalPos);
 
     KisToolInvocationAction *q;
-
-    bool useTablet;
-    QTabletEvent::TabletDevice tabletDevice;
-    QTabletEvent::PointerType pointerType;
-    int tabletZ;
-    qint64 tabletID;
-
-    Qt::KeyboardModifiers modifiers;
+    bool active;
 };
 
 KisToolInvocationAction::KisToolInvocationAction(KisInputManager *manager)
@@ -57,24 +50,19 @@ KisToolInvocationAction::~KisToolInvocationAction()
     delete d;
 }
 
-void KisToolInvocationAction::begin(int shortcut)
+void KisToolInvocationAction::begin(int shortcut, QEvent *event)
 {
     if (shortcut == ActivateShortcut) {
-        if (inputManager()->tabletPressEvent()) {
-            QTabletEvent *pressEvent = inputManager()->tabletPressEvent();
-            inputManager()->toolProxy()->tabletEvent(pressEvent, \
                d->tabletToPixel(pressEvent->hiResGlobalPos()));
-            d->useTablet = true;
-            d->pointerType = pressEvent->pointerType();
-            d->tabletDevice = pressEvent->device();
-            d->tabletZ = pressEvent->z();
-            d->tabletID = pressEvent->uniqueId();
-            setMousePosition(d->tabletToPixel(pressEvent->hiResGlobalPos()));
+        QTabletEvent *tabletEvent = inputManager()->lastTabletEvent();
+        QMouseEvent *mouseEvent = dynamic_cast<QMouseEvent*>(event);
 
-        } else {
-            QMouseEvent pressEvent(QEvent::MouseButtonPress, \
                inputManager()->mousePosition().toPoint(), Qt::LeftButton, \
                Qt::LeftButton, 0);
-            inputManager()->toolProxy()->mousePressEvent(&pressEvent, \
                inputManager()->mousePosition());
-            setMousePosition(inputManager()->mousePosition());
+        if (tabletEvent) {
+            inputManager()->toolProxy()->tabletEvent(tabletEvent, \
d->tabletToPixel(tabletEvent->hiResGlobalPos())); +        } else if (mouseEvent) {
+            inputManager()->toolProxy()->mousePressEvent(mouseEvent, \
inputManager()->widgetToPixel(mouseEvent->posF()));  }
+
+        d->active = true;
     } else {
         QKeyEvent pressEvent(QEvent::KeyPress, Qt::Key_Return, 0);
         inputManager()->toolProxy()->keyPressEvent(&pressEvent);
@@ -83,40 +71,40 @@ void KisToolInvocationAction::begin(int shortcut)
     }
 }
 
-void KisToolInvocationAction::end()
+void KisToolInvocationAction::end(QEvent *event)
 {
-    if(d->useTablet) {
-        QTabletEvent releaseEvent(QEvent::TabletRelease, mousePosition().toPoint(), \
mousePosition().toPoint(), mousePosition(), d->tabletDevice, d->pointerType, 0.f, 0, \
                0, 0.f, 0.f, d->tabletZ, d->modifiers, d->tabletID);
-        inputManager()->toolProxy()->tabletEvent(&releaseEvent, mousePosition());
-        d->useTablet = false;
-    } else {
-        QMouseEvent releaseEvent(QEvent::MouseButtonRelease, \
                mousePosition().toPoint(), Qt::LeftButton, Qt::LeftButton, \
                d->modifiers);
-        inputManager()->toolProxy()->mouseReleaseEvent(&releaseEvent, \
mousePosition()); +    if (d->active) {
+        QTabletEvent *tabletEvent = inputManager()->lastTabletEvent();
+        QMouseEvent *mouseEvent = dynamic_cast<QMouseEvent*>(event);
+
+        if (tabletEvent) {
+            inputManager()->toolProxy()->tabletEvent(tabletEvent, \
d->tabletToPixel(tabletEvent->hiResGlobalPos())); +        } else {
+            inputManager()->toolProxy()->mouseReleaseEvent(mouseEvent, \
inputManager()->widgetToPixel(mouseEvent->posF())); +        }
+
+        d->active = false;
     }
+
+    KisAbstractInputAction::end(event);
 }
 
 void KisToolInvocationAction::inputEvent(QEvent* event)
 {
     if(event->type() == QEvent::MouseButtonPress) {
         QMouseEvent* mevent = static_cast<QMouseEvent*>(event);
-        setMousePosition(inputManager()->widgetToPixel(mevent->posF()));
-        d->modifiers = mevent->modifiers();
-        inputManager()->toolProxy()->mousePressEvent(mevent, mousePosition());
+        inputManager()->toolProxy()->mousePressEvent(mevent, \
inputManager()->widgetToPixel(mevent->posF()));  } else if(event->type() == \
QEvent::MouseButtonRelease) {  QMouseEvent* mevent = \
                static_cast<QMouseEvent*>(event);
-        setMousePosition(inputManager()->widgetToPixel(mevent->posF()));
-        d->modifiers = mevent->modifiers();
-        inputManager()->toolProxy()->mouseReleaseEvent(mevent, mousePosition());
+        inputManager()->toolProxy()->mouseReleaseEvent(mevent, \
inputManager()->widgetToPixel(mevent->posF()));  } else if(event->type() == \
QEvent::MouseMove) { +        QTabletEvent* tevent = \
inputManager()->lastTabletEvent();  QMouseEvent* mevent = \
                static_cast<QMouseEvent*>(event);
-        setMousePosition(inputManager()->widgetToPixel(mevent->posF()));
-        d->modifiers = mevent->modifiers();
-        inputManager()->toolProxy()->mouseMoveEvent(mevent, mousePosition());
-    } else if(event->type() == QEvent::TabletMove) {
-        QTabletEvent* tevent = static_cast<QTabletEvent*>(event);
-        setMousePosition(d->tabletToPixel(tevent->hiResGlobalPos()));
-        d->modifiers = tevent->modifiers();
-        inputManager()->toolProxy()->tabletEvent(tevent, mousePosition());
+        if (tevent && tevent->type() == QEvent::TabletMove) {
+            inputManager()->toolProxy()->tabletEvent(tevent, \
d->tabletToPixel(tevent->hiResGlobalPos())); +        } else {
+            inputManager()->toolProxy()->mouseMoveEvent(mevent, \
inputManager()->widgetToPixel(mevent->posF())); +        }
     } else if(event->type() == QEvent::KeyPress) {
         QKeyEvent* kevent = static_cast<QKeyEvent*>(event);
         inputManager()->toolProxy()->keyPressEvent(kevent);
@@ -126,11 +114,6 @@ void KisToolInvocationAction::inputEvent(QEvent* event)
     }
 }
 
-bool KisToolInvocationAction::handleTablet() const
-{
-    return true;
-}
-
 QPointF KisToolInvocationAction::Private::tabletToPixel(const QPointF &globalPos)
 {
     const QPointF pos = globalPos - \
                q->inputManager()->canvas()->canvasWidget()->mapToGlobal(QPoint(0, \
                0));
diff --git a/krita/ui/input/kis_tool_invocation_action.h \
b/krita/ui/input/kis_tool_invocation_action.h index e44a114..a06a003 100644
--- a/krita/ui/input/kis_tool_invocation_action.h
+++ b/krita/ui/input/kis_tool_invocation_action.h
@@ -37,11 +37,9 @@ public:
     explicit KisToolInvocationAction(KisInputManager *manager);
     virtual ~KisToolInvocationAction();
 
-    virtual void begin(int shortcut);
-    virtual void end();
-    virtual void inputEvent(QEvent* event);
-
-    virtual bool handleTablet() const;
+    void begin(int shortcut, QEvent *event);
+    void end(QEvent *event);
+    void inputEvent(QEvent* event);
 
 private:
     class Private;
diff --git a/krita/ui/input/kis_zoom_action.cpp b/krita/ui/input/kis_zoom_action.cpp
index 394c079..82c42a3 100644
--- a/krita/ui/input/kis_zoom_action.cpp
+++ b/krita/ui/input/kis_zoom_action.cpp
@@ -28,16 +28,9 @@
 #include <KoCanvasController.h>
 #include <KoZoomController.h>
 
-class KisZoomAction::Private
-{
-public:
-    Private() : active(false) { }
-
-    bool active;
-};
 
 KisZoomAction::KisZoomAction(KisInputManager* manager)
-    : KisAbstractInputAction(manager), d(new Private)
+    : KisAbstractInputAction(manager)
 {
     setName(i18n("Zoom Canvas"));
 
@@ -53,17 +46,24 @@ KisZoomAction::KisZoomAction(KisInputManager* manager)
 
 KisZoomAction::~KisZoomAction()
 {
-    delete d;
 }
 
-void KisZoomAction::begin(int shortcut)
+void KisZoomAction::activate()
+{
+    QApplication::setOverrideCursor(Qt::OpenHandCursor);
+}
+
+void KisZoomAction::deactivate()
+{
+    QApplication::restoreOverrideCursor();
+}
+
+void KisZoomAction::begin(int shortcut, QEvent *event)
 {
-    switch(shortcut)
-    {
+    KisAbstractInputAction::begin(shortcut, event);
+
+    switch(shortcut) {
         case ZoomToggleShortcut:
-            setMousePosition(inputManager()->canvas()->coordinatesConverter()->documentToWidget(inputManager()->mousePosition()));
                
-            QApplication::setOverrideCursor(Qt::OpenHandCursor);
-            d->active = true;
             break;
         case ZoomInShortcut: {
             float zoom = \
inputManager()->canvas()->view()->zoomController()->zoomAction()->effectiveZoom(); @@ \
-105,38 +105,10 @@ void KisZoomAction::begin(int shortcut)  }
 }
 
-void KisZoomAction::end()
-{
-    d->active = false;
-    QApplication::restoreOverrideCursor();
-}
-
-void KisZoomAction::inputEvent(QEvent* event)
+void KisZoomAction::mouseMoved(const QPointF &lastPos, const QPointF &pos)
 {
-    switch (event->type()) {
-        case QEvent::MouseButtonPress:
-            setMousePosition(static_cast<QMouseEvent*>(event)->posF());
-            break;
-        case QEvent::MouseMove: {
-            QMouseEvent *mevent = static_cast<QMouseEvent*>(event);
-            if(mevent->buttons()) {
-                QPointF relMovement = -(mevent->posF() - mousePosition());
-
-                float zoom = \
inputManager()->canvas()->view()->zoomController()->zoomAction()->effectiveZoom() + \
                relMovement.y() / 100;
-                inputManager()->canvas()->view()->zoomController()->setZoom(KoZoomMode::ZOOM_CONSTANT, \
                zoom);
-
-                setMousePosition(mevent->posF());
-                QApplication::changeOverrideCursor(Qt::ClosedHandCursor);
-            } else {
-                QApplication::changeOverrideCursor(Qt::OpenHandCursor);
-            }
-        }
-        default:
-            break;
-    }
-}
+    QPointF relMovement = -(pos - lastPos);
 
-bool KisZoomAction::isBlockingAutoRepeat() const
-{
-    return d->active;
+    float zoom = inputManager()->canvas()->view()->zoomController()->zoomAction()->effectiveZoom() \
+ relMovement.y() / 100; +    \
inputManager()->canvas()->view()->zoomController()->setZoom(KoZoomMode::ZOOM_CONSTANT, \
zoom);  }
diff --git a/krita/ui/input/kis_zoom_action.h b/krita/ui/input/kis_zoom_action.h
index 8c37fcc..be09310 100644
--- a/krita/ui/input/kis_zoom_action.h
+++ b/krita/ui/input/kis_zoom_action.h
@@ -43,15 +43,11 @@ public:
     explicit KisZoomAction(KisInputManager* manager);
     virtual ~KisZoomAction();
 
-    virtual void begin(int shortcut);
-    virtual void end();
-    virtual void inputEvent(QEvent* event);
+    void activate();
+    void deactivate();
 
-    virtual bool isBlockingAutoRepeat() const;
-
-private:
-    class Private;
-    Private * const d;
+    void begin(int shortcut, QEvent *event = 0);
+    void mouseMoved(const QPointF &lastPos, const QPointF &pos);
 };
 
 #endif // KIS_ZOOM_ACTION_H
diff --git a/krita/ui/tests/CMakeLists.txt b/krita/ui/tests/CMakeLists.txt
index e8f1c21..a7499fe 100644
--- a/krita/ui/tests/CMakeLists.txt
+++ b/krita/ui/tests/CMakeLists.txt
@@ -150,3 +150,9 @@ target_link_libraries(KisModelIndexConverterTest  \
${KDE4_KDEUI_LIBS}  kritaui kr  set(kis_zoom_and_pan_test_SRCS \
kis_zoom_and_pan_test.cpp ../../sdk/tests/testutil.cpp)  \
kde4_add_unit_test(KisZoomAndPanTest TESTNAME krita-ui-KisZoomAndPanTest  \
${kis_zoom_and_pan_test_SRCS})  target_link_libraries(KisZoomAndPanTest  \
${KDE4_KDEUI_LIBS}  kritaui kritaimage ${QT_QTTEST_LIBRARY}) +
+########### next target ###############
+
+set(kis_input_manager_test_SRCS kis_input_manager_test.cpp \
../../sdk/tests/testutil.cpp) +kde4_add_unit_test(KisInputManagerTest TESTNAME \
krita-ui-KisInputManagerTest  ${kis_input_manager_test_SRCS}) \
+target_link_libraries(KisInputManagerTest  ${KDE4_KDEUI_LIBS}  kritaui kritaimage \
                ${QT_QTTEST_LIBRARY})
diff --git a/krita/ui/tests/kis_input_manager_test.cpp \
b/krita/ui/tests/kis_input_manager_test.cpp new file mode 100644
index 0000000..aab8f61
--- /dev/null
+++ b/krita/ui/tests/kis_input_manager_test.cpp
@@ -0,0 +1,357 @@
+/*
+ *  Copyright (c) 2012 Dmitry Kazakov <dimula73@gmail.com>
+ *
+ *  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_input_manager_test.h"
+
+#include <qtest_kde.h>
+
+#include <QMouseEvent>
+
+#include "input/kis_key_shortcut.h"
+#include "input/kis_stroke_shortcut.h"
+#include "input/kis_abstract_input_action.h"
+#include "input/kis_shortcut_matcher.h"
+
+
+void KisInputManagerTest::testKeyShortcut()
+{
+    KisKeyShortcut s(0,0);
+    s.setKey(QList<Qt::Key>() << Qt::Key_Shift, Qt::Key_Space);
+
+    QVERIFY(s.matchKey(QList<Qt::Key>() << Qt::Key_Shift, Qt::Key_Space));
+    QVERIFY(!s.matchKey(QList<Qt::Key>() << Qt::Key_Control, Qt::Key_Space));
+    QVERIFY(!s.matchKey(QList<Qt::Key>(), Qt::Key_Space));
+    QVERIFY(!s.matchKey(QList<Qt::Key>() << Qt::Key_Shift, Qt::Key_Escape));
+    QVERIFY(!s.matchKey(QList<Qt::Key>() << Qt::Key_Shift, \
KisKeyShortcut::WheelUp)); +
+    s.setWheel(QList<Qt::Key>() << Qt::Key_Shift, KisKeyShortcut::WheelUp);
+
+    QVERIFY(!s.matchKey(QList<Qt::Key>() << Qt::Key_Shift, Qt::Key_Space));
+    QVERIFY(!s.matchKey(QList<Qt::Key>() << Qt::Key_Control, Qt::Key_Space));
+    QVERIFY(!s.matchKey(QList<Qt::Key>(), Qt::Key_Space));
+    QVERIFY(!s.matchKey(QList<Qt::Key>() << Qt::Key_Shift, Qt::Key_Escape));
+    QVERIFY(s.matchKey(QList<Qt::Key>() << Qt::Key_Shift, KisKeyShortcut::WheelUp));
+}
+
+void KisInputManagerTest::testStrokeShortcut()
+{
+    KisStrokeShortcut s(0,0);
+    s.setButtons(QList<Qt::Key>() << Qt::Key_Shift << Qt::Key_Control,
+                 QList<Qt::MouseButton>() << Qt::LeftButton);
+
+    QVERIFY(s.matchReady(QList<Qt::Key>() << Qt::Key_Shift << Qt::Key_Control,
+                         QList<Qt::MouseButton>() << Qt::LeftButton));
+
+    QVERIFY(s.matchReady(QList<Qt::Key>() << Qt::Key_Shift << Qt::Key_Control,
+                         QList<Qt::MouseButton>()));
+
+    QVERIFY(!s.matchReady(QList<Qt::Key>() << Qt::Key_Control << Qt::Key_Alt,
+                         QList<Qt::MouseButton>()));
+
+    QVERIFY(!s.matchReady(QList<Qt::Key>() << Qt::Key_Shift << Qt::Key_Control,
+                         QList<Qt::MouseButton>() << Qt::RightButton));
+
+    QVERIFY(s.matchBegin(Qt::LeftButton));
+    QVERIFY(!s.matchBegin(Qt::RightButton));
+}
+
+struct TestingAction : public KisAbstractInputAction
+{
+    TestingAction() : KisAbstractInputAction(0) { reset(); }
+    ~TestingAction() {}
+
+    void begin(int shortcut, QEvent *event) { m_beginIndex = shortcut; \
m_beginNonNull = event;} +    void end(QEvent *event) { m_ended = true; m_endNonNull \
= event; } +    void inputEvent(QEvent* event) { Q_UNUSED(event); m_gotInput = true; \
} +
+    void reset() {
+        m_beginIndex = -1;
+        m_ended = false;
+        m_gotInput = false;
+        m_beginNonNull = false;
+        m_endNonNull = false;
+    }
+
+
+    int m_beginIndex;
+    bool m_ended;
+    bool m_gotInput;
+    bool m_beginNonNull;
+    bool m_endNonNull;
+};
+
+KisKeyShortcut* createKeyShortcut(KisAbstractInputAction *action,
+                                  int shortcutIndex,
+                                  const QList<Qt::Key> &modifiers,
+                                  Qt::Key key)
+{
+    KisKeyShortcut *s = new KisKeyShortcut(action, shortcutIndex);
+    s->setKey(modifiers, key);
+    return s;
+}
+
+KisStrokeShortcut* createStrokeShortcut(KisAbstractInputAction *action,
+                                     int shortcutIndex,
+                                     const QList<Qt::Key> &modifiers,
+                                     Qt::MouseButton button)
+{
+    KisStrokeShortcut *s = new KisStrokeShortcut(action, shortcutIndex);
+    s->setButtons(modifiers, QList<Qt::MouseButton>() << button);
+    return s;
+}
+
+void KisInputManagerTest::testKeyEvents()
+{
+    KisShortcutMatcher m;
+
+    TestingAction *a = new TestingAction();
+    m.addAction(a);
+
+
+    m.addShortcut(
+        createKeyShortcut(a, 10,
+                          QList<Qt::Key>() << Qt::Key_Shift,
+                          Qt::Key_Enter));
+
+    m.addShortcut(
+        createKeyShortcut(a, 11,
+                          QList<Qt::Key>() << Qt::Key_Shift << Qt::Key_Control,
+                          Qt::Key_Enter));
+
+    m.addShortcut(
+        createStrokeShortcut(a, 12,
+                             QList<Qt::Key>() << Qt::Key_Shift,
+                             Qt::RightButton));
+
+    m.addShortcut(
+        createStrokeShortcut(a, 13,
+                             QList<Qt::Key>() << Qt::Key_Shift << Qt::Key_Control,
+                             Qt::LeftButton));
+
+    QCOMPARE(a->m_beginIndex, -1);
+
+    // Test event with random values
+    QMouseEvent mouseEvent(QEvent::MouseMove, QPoint(),
+                           Qt::LeftButton, Qt::NoButton, Qt::NoModifier);
+
+    // Press Ctrl+Shift
+    QVERIFY(!m.keyPressed(Qt::Key_Shift));
+    QCOMPARE(a->m_beginIndex, -1);
+
+    QVERIFY(!m.keyPressed(Qt::Key_Control));
+    QCOMPARE(a->m_beginIndex, -1);
+
+
+    // Complete Ctrl+Shift+Enter shortcut
+    QVERIFY(m.keyPressed(Qt::Key_Enter));
+    QCOMPARE(a->m_beginIndex, 11);
+    QCOMPARE(a->m_ended, true);
+    QCOMPARE(a->m_beginNonNull, false);
+    QCOMPARE(a->m_endNonNull, false);
+    a->reset();
+
+
+    // Pressing mouse buttons is disabled since Enter is pressed
+    QVERIFY(!m.buttonPressed(Qt::LeftButton, &mouseEvent));
+    QCOMPARE(a->m_beginIndex, -1);
+    QVERIFY(!m.buttonReleased(Qt::LeftButton, &mouseEvent));
+    QCOMPARE(a->m_beginIndex, -1);
+
+
+    // Release Enter, so the system should be ready for new shortcuts
+    QVERIFY(!m.keyReleased(Qt::Key_Enter));
+    QCOMPARE(a->m_beginIndex, -1);
+
+
+    // Complete Ctrl+Shift+LB shortcut
+    QVERIFY(m.buttonPressed(Qt::LeftButton, &mouseEvent));
+    QCOMPARE(a->m_beginIndex, 13);
+    QCOMPARE(a->m_ended, false);
+    QCOMPARE(a->m_beginNonNull, true);
+    QCOMPARE(a->m_endNonNull, false);
+    a->reset();
+
+    QVERIFY(m.buttonReleased(Qt::LeftButton, &mouseEvent));
+    QCOMPARE(a->m_beginIndex, -1);
+    QCOMPARE(a->m_ended, true);
+    QCOMPARE(a->m_beginNonNull, false);
+    QCOMPARE(a->m_endNonNull, true);
+    a->reset();
+
+
+    // There is no Ctrl+Shift+RB shortcut
+    QVERIFY(!m.buttonPressed(Qt::RightButton, &mouseEvent));
+    QCOMPARE(a->m_beginIndex, -1);
+
+    QVERIFY(!m.buttonReleased(Qt::RightButton, &mouseEvent));
+    QCOMPARE(a->m_beginIndex, -1);
+
+
+    // Check that Ctrl+Shift+Enter is still enabled
+    QVERIFY(m.keyPressed(Qt::Key_Enter));
+    QCOMPARE(a->m_beginIndex, 11);
+    QCOMPARE(a->m_ended, true);
+    QCOMPARE(a->m_beginNonNull, false);
+    QCOMPARE(a->m_endNonNull, false);
+    a->reset();
+
+    QVERIFY(!m.keyReleased(Qt::Key_Enter));
+    QCOMPARE(a->m_beginIndex, -1);
+
+
+    // Release Ctrl
+    QVERIFY(!m.keyReleased(Qt::Key_Control));
+    QCOMPARE(a->m_beginIndex, -1);
+
+
+    // There is no Shift+LB shortcut
+    QVERIFY(!m.buttonPressed(Qt::LeftButton, &mouseEvent));
+    QCOMPARE(a->m_beginIndex, -1);
+
+    QVERIFY(!m.buttonReleased(Qt::LeftButton, &mouseEvent));
+    QCOMPARE(a->m_beginIndex, -1);
+
+
+    // But there *is* Shift+RB shortcut
+    QVERIFY(m.buttonPressed(Qt::RightButton, &mouseEvent));
+    QCOMPARE(a->m_beginIndex, 12);
+    QCOMPARE(a->m_ended, false);
+    QCOMPARE(a->m_beginNonNull, true);
+    QCOMPARE(a->m_endNonNull, false);
+    a->reset();
+
+    QVERIFY(m.buttonReleased(Qt::RightButton, &mouseEvent));
+    QCOMPARE(a->m_beginIndex, -1);
+    QCOMPARE(a->m_ended, true);
+    QCOMPARE(a->m_beginNonNull, false);
+    QCOMPARE(a->m_endNonNull, true);
+    a->reset();
+
+
+    // Check that Shift+Enter still works
+    QVERIFY(m.keyPressed(Qt::Key_Enter));
+    QCOMPARE(a->m_beginIndex, 10);
+    QCOMPARE(a->m_ended, true);
+    QCOMPARE(a->m_beginNonNull, false);
+    QCOMPARE(a->m_endNonNull, false);
+    a->reset();
+}
+
+void KisInputManagerTest::testReleaseUnnecessaryModifiers()
+{
+    KisShortcutMatcher m;
+
+    TestingAction *a = new TestingAction();
+    m.addAction(a);
+
+    m.addShortcut(
+        createStrokeShortcut(a, 13,
+                             QList<Qt::Key>() << Qt::Key_Shift << Qt::Key_Control,
+                             Qt::LeftButton));
+
+    // Test event with random values
+    QMouseEvent mouseEvent(QEvent::MouseMove, QPoint(),
+                           Qt::LeftButton, Qt::NoButton, Qt::NoModifier);
+
+    // Press Ctrl+Shift
+    QVERIFY(!m.keyPressed(Qt::Key_Shift));
+    QCOMPARE(a->m_beginIndex, -1);
+
+    QVERIFY(!m.keyPressed(Qt::Key_Control));
+    QCOMPARE(a->m_beginIndex, -1);
+
+    // Complete Ctrl+Shift+LB shortcut
+    QVERIFY(m.buttonPressed(Qt::LeftButton, &mouseEvent));
+    QCOMPARE(a->m_beginIndex, 13);
+    QCOMPARE(a->m_ended, false);
+    a->reset();
+
+    // Release Ctrl
+    QVERIFY(!m.keyReleased(Qt::Key_Control));
+    QCOMPARE(a->m_beginIndex, -1);
+    QCOMPARE(a->m_ended, false);
+
+    // Release Shift
+    QVERIFY(!m.keyReleased(Qt::Key_Shift));
+    QCOMPARE(a->m_beginIndex, -1);
+    QCOMPARE(a->m_ended, false);
+
+    // Release LB, now it should end
+    QVERIFY(m.buttonReleased(Qt::LeftButton, &mouseEvent));
+    QCOMPARE(a->m_beginIndex, -1);
+    QCOMPARE(a->m_ended, true);
+    a->reset();
+}
+
+void KisInputManagerTest::testMouseMoves()
+{
+    KisShortcutMatcher m;
+
+    TestingAction *a = new TestingAction();
+    m.addAction(a);
+
+    m.addShortcut(
+        createStrokeShortcut(a, 13,
+                             QList<Qt::Key>() << Qt::Key_Shift << Qt::Key_Control,
+                             Qt::LeftButton));
+
+    // Test event with random values
+    QMouseEvent mouseEvent(QEvent::MouseMove, QPoint(),
+                           Qt::LeftButton, Qt::NoButton, Qt::NoModifier);
+
+
+    // Press Ctrl+Shift
+    QVERIFY(!m.keyPressed(Qt::Key_Shift));
+    QCOMPARE(a->m_beginIndex, -1);
+
+    QVERIFY(!m.keyPressed(Qt::Key_Control));
+    QCOMPARE(a->m_beginIndex, -1);
+
+    QVERIFY(!m.mouseMoved(&mouseEvent));
+    QCOMPARE(a->m_gotInput, false);
+
+    // Complete Ctrl+Shift+LB shortcut
+    QVERIFY(m.buttonPressed(Qt::LeftButton, &mouseEvent));
+    QCOMPARE(a->m_beginIndex, 13);
+    QCOMPARE(a->m_ended, false);
+    QCOMPARE(a->m_gotInput, false);
+    a->reset();
+
+    QVERIFY(m.mouseMoved(&mouseEvent));
+    QCOMPARE(a->m_gotInput, true);
+    a->reset();
+
+    // Release Ctrl
+    QVERIFY(!m.keyReleased(Qt::Key_Control));
+    QCOMPARE(a->m_beginIndex, -1);
+    QCOMPARE(a->m_ended, false);
+    QCOMPARE(a->m_gotInput, false);
+
+    // Release Shift
+    QVERIFY(!m.keyReleased(Qt::Key_Shift));
+    QCOMPARE(a->m_beginIndex, -1);
+    QCOMPARE(a->m_ended, false);
+
+    // Release LB, now it should end
+    QVERIFY(m.buttonReleased(Qt::LeftButton, &mouseEvent));
+    QCOMPARE(a->m_beginIndex, -1);
+    QCOMPARE(a->m_ended, true);
+    a->reset();
+}
+
+QTEST_KDEMAIN(KisInputManagerTest, GUI)
diff --git a/krita/ui/input/kis_show_palette_action.h \
b/krita/ui/tests/kis_input_manager_test.h similarity index 53%
copy from krita/ui/input/kis_show_palette_action.h
copy to krita/ui/tests/kis_input_manager_test.h
index 2485883..1fb8eab 100644
--- a/krita/ui/input/kis_show_palette_action.h
+++ b/krita/ui/tests/kis_input_manager_test.h
@@ -1,5 +1,5 @@
-/* This file is part of the KDE project
- * Copyright (C) 2012 Arjen Hiemstra <ahiemstra@heimr.nl>
+/*
+ *  Copyright (c) 2012 Dmitry Kazakov <dimula73@gmail.com>
  *
  *  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
@@ -16,25 +16,20 @@
  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  */
 
-#ifndef KIS_SHOW_PALETTE_ACTION_H
-#define KIS_SHOW_PALETTE_ACTION_H
+#ifndef __KIS_INPUT_MANAGER_TEST_H
+#define __KIS_INPUT_MANAGER_TEST_H
 
-#include "kis_abstract_input_action.h"
+#include <QtTest/QtTest>
 
-/**
- * \brief Show Palette implementation of KisAbstractInputAction.
- *
- * The Show Palette action shows the popup palette.
- */
-class KisShowPaletteAction : public KisAbstractInputAction
+class KisInputManagerTest : public QObject
 {
-public:
-    explicit KisShowPaletteAction(KisInputManager* manager);
-    virtual ~KisShowPaletteAction();
-
-    virtual void begin(int /*shortcut*/);
-    virtual void end();
-    virtual void inputEvent(QEvent* event);
+    Q_OBJECT
+private slots:
+    void testKeyShortcut();
+    void testStrokeShortcut();
+    void testKeyEvents();
+    void testReleaseUnnecessaryModifiers();
+    void testMouseMoves();
 };
 
-#endif // KIS_SHOW_PALETTE_ACTION_H
+#endif /* __KIS_INPUT_MANAGER_TEST_H */


[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic