[krita] libs: Fix shortcuts when the cursor is outside the canvas

Dmitry Kazakov dimula73 at gmail.com
Wed Apr 27 17:39:08 UTC 2016


Git commit d64d85fb10a58b6ed3b9451abeb1927576052e4d by Dmitry Kazakov.
Committed on 27/04/2016 at 17:38.
Pushed by dkazakov into branch 'master'.

Fix shortcuts when the cursor is outside the canvas

This patch needs testing on Windows, OSX and all the variations
of DE on Linux!

Before today the focus was automatically transferred to the canvas
when the cursor was hovering over it. That caused all input boxes
in the dockers work only when you keep the cursor over them. That
was hardly convenient.

Now the focus can be transferred in a semi-automatic way.

* if you move the cursor outside the canvas without clicking anywhere
  the focus is kept on the canvas and you can still use the shortcuts

* if you click in any input box outside the canvas, the focus will be
  transferred to that input box. Therefore the shortcuts will not work.

* to return input focus to the canvas you should do one of the following:
    - just start a stroke on it
    - keep your cursor *moving* over the canvas for 2 seconds and
      the focus will be transferred automatically. "Moving" condition is
      needed for a case when you drop your mouse or a stylus in a fixed
      position on a canvas and move your hands to the keyboard to fill
      in input boxes in the dockers. If you do this "dropping" quick
      enough (< 2 sec), the focus will be kept on your input box :)

To implement such behavior I had to create a special class
KisTimedSignalThreshold which uses almost the same interface as
KisSignalsCompressor, but does what we need: emit the signal if the
flow of events is strong enough.

Fixes T2346
BUG:362217,343763
CC:kimageshop at kde.org

M  +1    -0    libs/image/CMakeLists.txt
A  +82   -0    libs/image/kis_timed_signal_threshold.cpp     [License: GPL (v2+)]
A  +71   -0    libs/image/kis_timed_signal_threshold.h     [License: GPL (v2+)]
M  +3    -15   libs/ui/input/kis_input_manager.cpp
M  +39   -5    libs/ui/input/kis_input_manager_p.cpp
M  +8    -1    libs/ui/input/kis_input_manager_p.h
M  +5    -1    libs/ui/input/kis_shortcut_matcher.cpp

http://commits.kde.org/krita/d64d85fb10a58b6ed3b9451abeb1927576052e4d

diff --git a/libs/image/CMakeLists.txt b/libs/image/CMakeLists.txt
index 1915295..b314643 100644
--- a/libs/image/CMakeLists.txt
+++ b/libs/image/CMakeLists.txt
@@ -180,6 +180,7 @@ set(kritaimage_LIB_SRCS
    kis_signal_compressor_with_param.cpp
    kis_thread_safe_signal_compressor.cpp
    kis_acyclic_signal_connector.cpp
+   kis_timed_signal_threshold.cpp
    kis_layer.cc
    kis_indirect_painting_support.cpp
    kis_abstract_projection_plane.cpp
diff --git a/libs/image/kis_timed_signal_threshold.cpp b/libs/image/kis_timed_signal_threshold.cpp
new file mode 100644
index 0000000..98a9244
--- /dev/null
+++ b/libs/image/kis_timed_signal_threshold.cpp
@@ -0,0 +1,82 @@
+/*
+ *  Copyright (c) 2016 Dmitry Kazakov <dimula73 at 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_timed_signal_threshold.h"
+#include <QElapsedTimer>
+#include "kis_debug.h"
+
+
+struct KisTimedSignalThreshold::Private
+{
+    Private(int _delay, int _cancelDelay)
+        : delay(_delay),
+          cancelDelay(_cancelDelay),
+          enabled(true)
+    {
+        if (cancelDelay < 0) {
+            cancelDelay = 2 * delay;
+        }
+    }
+
+    QElapsedTimer timer;
+    int delay;
+    int cancelDelay;
+    bool enabled;
+};
+
+
+KisTimedSignalThreshold::KisTimedSignalThreshold(int delay, int cancelDelay, QObject *parent)
+    : QObject(parent),
+      m_d(new Private(delay, cancelDelay))
+{
+}
+
+KisTimedSignalThreshold::~KisTimedSignalThreshold()
+{
+}
+
+void KisTimedSignalThreshold::forceDone()
+{
+    stop();
+    emit timeout();
+}
+
+void KisTimedSignalThreshold::start()
+{
+    if (!m_d->timer.isValid()) {
+        m_d->timer.start();
+    } else if (m_d->timer.elapsed() > 2 * m_d->delay) {
+        stop();
+    } else if (m_d->timer.elapsed() > m_d->delay) {
+        forceDone();
+    }
+}
+
+void KisTimedSignalThreshold::stop()
+{
+    m_d->timer.invalidate();
+}
+
+void KisTimedSignalThreshold::setEnabled(bool value)
+{
+    m_d->enabled = value;
+    if (!m_d->enabled) {
+        stop();
+    }
+}
+
diff --git a/libs/image/kis_timed_signal_threshold.h b/libs/image/kis_timed_signal_threshold.h
new file mode 100644
index 0000000..5505179
--- /dev/null
+++ b/libs/image/kis_timed_signal_threshold.h
@@ -0,0 +1,71 @@
+/*
+ *  Copyright (c) 2016 Dmitry Kazakov <dimula73 at 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_TIMED_SIGNAL_THRESHOLD_H
+#define __KIS_TIMED_SIGNAL_THRESHOLD_H
+
+#include "kritaimage_export.h"
+#include <QScopedPointer>
+#include <QObject>
+
+
+/**
+ * Emits the timeout() signal if and only if the flow of start()
+ * events has been coming for a consecutive \p delay of milliseconds.
+ * If the events were not coming for \p cancelDelay of milliseconds the
+ * counting is dropped and the new period is started.
+ */
+class KRITAIMAGE_EXPORT KisTimedSignalThreshold : public QObject
+{
+    Q_OBJECT
+public:
+    KisTimedSignalThreshold(int delay, int cancelDelay = -1, QObject *parent = 0);
+    ~KisTimedSignalThreshold();
+
+public Q_SLOTS:
+    /**
+     * Stops counting and emits the signal forcefully
+     */
+    void forceDone();
+
+    /**
+     * Start/continue counting and if the signal flow is stable enough
+     * (longer than \p delay and shorter than \p cancelDelay), the
+     * timeout signal in emitted.
+     */
+    void start();
+
+    /**
+     * Stops counting the signals flow
+     */
+    void stop();
+
+    /**
+     * Enable or disable emitting the signal
+     */
+    void setEnabled(bool value);
+
+Q_SIGNALS:
+    void timeout();
+
+private:
+    struct Private;
+    const QScopedPointer<Private> m_d;
+};
+
+#endif /* __KIS_TIMED_SIGNAL_THRESHOLD_H */
diff --git a/libs/ui/input/kis_input_manager.cpp b/libs/ui/input/kis_input_manager.cpp
index 0b19e63..f2c2626 100644
--- a/libs/ui/input/kis_input_manager.cpp
+++ b/libs/ui/input/kis_input_manager.cpp
@@ -163,17 +163,8 @@ void KisInputManager::stopIgnoringEvents()
 
 void KisInputManager::slotFocusOnEnter(bool value)
 {
-    if (d->focusOnEnter == value) {
-        return;
-    }
-
-    d->focusOnEnter = value;
-
-    if (d->focusOnEnter && d->containsPointer) {
-        if (d->canvas) {
-            d->canvas->canvasWidget()->setFocus();
-        }
-    }
+    Q_UNUSED(value);
+    // not used anymore
 }
 
 #if defined (__clang__)
@@ -376,10 +367,7 @@ bool KisInputManager::eventFilterImpl(QEvent * event)
         d->containsPointer = true;
         //Make sure the input actions know we are active.
         KisAbstractInputAction::setInputManager(this);
-        //Ensure we have focus so we get key events.
-        if (d->focusOnEnter) {
-            d->canvas->canvasWidget()->setFocus();
-        }
+
         stop_ignore_cursor_events();
         touch_stop_block_press_events();
 
diff --git a/libs/ui/input/kis_input_manager_p.cpp b/libs/ui/input/kis_input_manager_p.cpp
index 353cabe4..2d4c7a2 100644
--- a/libs/ui/input/kis_input_manager_p.cpp
+++ b/libs/ui/input/kis_input_manager_p.cpp
@@ -31,7 +31,6 @@
 #include "kis_touch_shortcut.h"
 #include "kis_input_profile_manager.h"
 
-
 /**
  * This hungry class EventEater encapsulates event masking logic.
  *
@@ -147,10 +146,20 @@ KisInputManager::Private::Private(KisInputManager *qq)
 KisInputManager::Private::CanvasSwitcher::CanvasSwitcher(Private *_d, QObject *p)
     : QObject(p),
       d(_d),
-      eatOneMouseStroke(false)
+      eatOneMouseStroke(false),
+      focusSwitchThreshold(2000)
 {
 }
 
+void KisInputManager::Private::CanvasSwitcher::setupFocusThreshold(QObject* object)
+{
+    QWidget *widget = qobject_cast<QWidget*>(object);
+    KIS_SAFE_ASSERT_RECOVER_RETURN(widget);
+
+    thresholdConnections.clear();
+    thresholdConnections.addConnection(&focusSwitchThreshold, SIGNAL(timeout()), widget, SLOT(setFocus()));
+}
+
 void KisInputManager::Private::CanvasSwitcher::addCanvas(KisCanvas2 *canvas)
 {
     QObject *canvasWidget = canvas->canvasWidget();
@@ -160,6 +169,9 @@ void KisInputManager::Private::CanvasSwitcher::addCanvas(KisCanvas2 *canvas)
         d->q->setupAsEventFilter(canvasWidget);
         canvasWidget->installEventFilter(this);
 
+        setupFocusThreshold(canvasWidget);
+        focusSwitchThreshold.setEnabled(false);
+
         d->canvas = canvas;
         d->toolProxy = dynamic_cast<KisToolProxy*>(canvas->toolProxy());
     } else {
@@ -186,9 +198,11 @@ bool KisInputManager::Private::CanvasSwitcher::eventFilter(QObject* object, QEve
         switch (event->type()) {
         case QEvent::FocusIn: {
             QFocusEvent *fevent = static_cast<QFocusEvent*>(event);
-            eatOneMouseStroke = 2 * (fevent->reason() == Qt::MouseFocusReason);
-
             KisCanvas2 *canvas = canvasResolver.value(object);
+            if (canvas != d->canvas) {
+                eatOneMouseStroke = 2 * (fevent->reason() == Qt::MouseFocusReason);
+            }
+
             d->canvas = canvas;
             d->toolProxy = dynamic_cast<KisToolProxy*>(canvas->toolProxy());
 
@@ -197,11 +211,24 @@ bool KisInputManager::Private::CanvasSwitcher::eventFilter(QObject* object, QEve
             object->removeEventFilter(this);
             object->installEventFilter(this);
 
+            setupFocusThreshold(object);
+            focusSwitchThreshold.setEnabled(false);
+
             QEvent event(QEvent::Enter);
             d->q->eventFilter(object, &event);
             break;
         }
-
+        case QEvent::FocusOut: {
+            focusSwitchThreshold.setEnabled(true);
+            break;
+        }
+        case QEvent::Enter: {
+            break;
+        }
+        case QEvent::Leave: {
+            focusSwitchThreshold.stop();
+            break;
+        }
         case QEvent::Wheel: {
             QWidget *widget = static_cast<QWidget*>(object);
             widget->setFocus();
@@ -211,16 +238,23 @@ bool KisInputManager::Private::CanvasSwitcher::eventFilter(QObject* object, QEve
         case QEvent::MouseButtonRelease:
         case QEvent::TabletPress:
         case QEvent::TabletRelease:
+            focusSwitchThreshold.forceDone();
+
             if (eatOneMouseStroke) {
                 eatOneMouseStroke--;
                 return true;
             }
             break;
         case QEvent::MouseButtonDblClick:
+            focusSwitchThreshold.forceDone();
             if (eatOneMouseStroke) {
                 return true;
             }
             break;
+        case QEvent::MouseMove:
+        case QEvent::TabletMove:
+            focusSwitchThreshold.start();
+            break;
         default:
             break;
         }
diff --git a/libs/ui/input/kis_input_manager_p.h b/libs/ui/input/kis_input_manager_p.h
index 6f2d115..a4a0c24 100644
--- a/libs/ui/input/kis_input_manager_p.h
+++ b/libs/ui/input/kis_input_manager_p.h
@@ -31,6 +31,9 @@
 #include "kis_tool_proxy.h"
 #include "kis_signal_compressor.h"
 #include "input/kis_tablet_debugger.h"
+#include "kis_timed_signal_threshold.h"
+#include "kis_signal_auto_connection.h"
+
 
 class KisToolInvocationAction;
 
@@ -109,9 +112,14 @@ public:
         bool eventFilter(QObject* object, QEvent* event );
 
     private:
+        void setupFocusThreshold(QObject *object);
+
+    private:
         KisInputManager::Private *d;
         QMap<QObject*, KisCanvas2*> canvasResolver;
         int eatOneMouseStroke;
+        KisTimedSignalThreshold focusSwitchThreshold;
+        KisSignalAutoConnectionsStore thresholdConnections;
     };
     CanvasSwitcher canvasSwitcher;
 
@@ -133,6 +141,5 @@ public:
     };
     EventEater eventEater;
 
-    bool focusOnEnter = true;
     bool containsPointer = true;
 };
diff --git a/libs/ui/input/kis_shortcut_matcher.cpp b/libs/ui/input/kis_shortcut_matcher.cpp
index 540d2fb..08206ee 100644
--- a/libs/ui/input/kis_shortcut_matcher.cpp
+++ b/libs/ui/input/kis_shortcut_matcher.cpp
@@ -81,6 +81,10 @@ public:
     inline bool actionsSuppressed() const {
         return suppressAllActions || !cursorEntered;
     }
+
+    inline bool actionsSuppressedIgnoreFocus() const {
+        return suppressAllActions;
+    }
 };
 
 KisShortcutMatcher::KisShortcutMatcher()
@@ -360,7 +364,7 @@ bool KisShortcutMatcher::tryRunWheelShortcut(KisSingleActionShortcut::WheelActio
 template<typename T, typename U>
 bool KisShortcutMatcher::tryRunSingleActionShortcutImpl(T param, U *event, const QSet<Qt::Key> &keysState)
 {
-    if (m_d->actionsSuppressed()) {
+    if (m_d->actionsSuppressedIgnoreFocus()) {
         DEBUG_EVENT_ACTION("Event suppressed", event)
         return false;
     }


More information about the kimageshop mailing list