Commit 80cb31e7 authored by Dmitry Kazakov's avatar Dmitry Kazakov
Browse files

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
parent 92e9c165
......@@ -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
......
......@@ -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;
}
......@@ -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:
/**
......@@ -52,27 +65,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.
*/
......@@ -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;
......
/*
* 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;
}
/* This file is part of the KDE project
* Copyright (C) 2012 Arjen Hiemstra <ahiemstra@heimr.nl>
/*
* 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,105 +17,56 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KISSHORTCUT_H
#define KISSHORTCUT_H
#ifndef __KIS_ABSTRACT_SHORTCUT_H
#define __KIS_ABSTRACT_SHORTCUT_H
#include <Qt>
#include <QList>
class QEvent;
#include <krita_export.h>
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
{
class KRITAUI_EXPORT KisAbstractShortcut
{
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.
};
KisAbstractShortcut(KisAbstractInputAction *action, int index);
virtual ~KisAbstractShortcut();
/**
* Constructor.
*/
KisShortcut();
/**
* Destructor.
* The priority of the shortcut. The shortcut with the
* greatest value will be chosen for executution
*/
virtual ~KisShortcut();
virtual int priority() const = 0;
/**
*/
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();
protected:
bool compareKeys(const QList<Qt::Key> &keys1,
const QList<Qt::Key> &keys2);
private:
class Private;
Private * const d;
Private * const m_d;
};
#endif // KISSHORTCUT_H
#endif /* __KIS_ABSTRACT_SHORTCUT_H */
......@@ -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));
}
......@@ -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
......@@ -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));
}
......@@ -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
This diff is collapsed.
......@@ -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.
......
/*
* 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 u