1 /* This file is part of the KDE project
2    Copyright (C) 2017 Nikita Vertikov   <kitmouse.nikita@gmail.com>
3 
4    This library is free software; you can redistribute it and/or
5    modify it under the terms of the GNU Library General Public
6    License as published by the Free Software Foundation; either
7    version 2 of the License, or (at your option) any later version.
8 
9    This library is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12    Library General Public License for more details.
13 
14    You should have received a copy of the GNU Library General Public License
15    along with this library; see the file COPYING.LIB.  If not, write to
16    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18 */
19 
20 #include "KisQtWidgetsTweaker.h"
21 
22 #include <QBitArray>
23 #include <QComboBox>
24 #include <QDockWidget>
25 #include <QDoubleSpinBox>
26 #include <QEvent>
27 #include <QKeyEvent>
28 #include <QKeySequence>
29 #include <QLineEdit>
30 #include <QSpinBox>
31 
32 #include "opengl/kis_opengl_canvas2.h"
33 #include "canvas/kis_qpainter_canvas.h"
34 #include "KisMainWindow.h"
35 
36 Q_GLOBAL_STATIC(KisQtWidgetsTweaker, kqwt_instance)
37 
38 
39 namespace {
40 
41 class ShortcutOverriderBase
42 {
43 public:
44     enum class DecisionOnShortcutOverride {
45         overrideShortcut,
46         askNext,
47         dontOverrideShortcut
48     };
49 
50     constexpr ShortcutOverriderBase() = default;
~ShortcutOverriderBase()51     virtual ~ShortcutOverriderBase()
52     {}
53     virtual bool interestedInEvent(QKeyEvent *event) = 0;
54     virtual DecisionOnShortcutOverride handleEvent(QObject *receiver, QKeyEvent *event) = 0;
finishedPhysicalKeyPressHandling()55     virtual DecisionOnShortcutOverride finishedPhysicalKeyPressHandling()
56     {
57         return DecisionOnShortcutOverride::askNext;
58     }
59 };
60 
61 class LineTextEditingShortcutOverrider : public ShortcutOverriderBase
62 {
63 public:
64     constexpr LineTextEditingShortcutOverrider() = default;
65 
interestedInEvent(QKeyEvent * event)66     virtual bool interestedInEvent(QKeyEvent *event) override
67     {
68         static constexpr QKeySequence::StandardKey actionsForQLineEdit[]{
69              QKeySequence::MoveToNextChar
70             ,QKeySequence::MoveToPreviousChar
71             ,QKeySequence::MoveToStartOfLine
72             ,QKeySequence::MoveToEndOfLine
73             ,QKeySequence::MoveToPreviousWord
74             ,QKeySequence::MoveToNextWord
75             ,QKeySequence::SelectPreviousChar
76             ,QKeySequence::SelectNextChar
77             ,QKeySequence::SelectNextWord
78             ,QKeySequence::SelectPreviousWord
79             ,QKeySequence::SelectStartOfLine
80             ,QKeySequence::SelectEndOfLine
81             ,QKeySequence::SelectAll
82             ,QKeySequence::Deselect
83             ,QKeySequence::Backspace
84             ,QKeySequence::DeleteStartOfWord
85             ,QKeySequence::Delete
86             ,QKeySequence::DeleteEndOfWord
87             ,QKeySequence::DeleteEndOfLine
88             ,QKeySequence::Copy
89             ,QKeySequence::Paste
90             ,QKeySequence::Cut
91             ,QKeySequence::Undo
92             ,QKeySequence::Redo
93         };
94         for (QKeySequence::StandardKey sk : actionsForQLineEdit) {
95             if (event->matches(sk)) {
96                 event->accept();
97                 return true;
98             }
99         }
100         return false;
101     }
102 
handleEvent(QObject * receiver,QKeyEvent * event)103     virtual DecisionOnShortcutOverride handleEvent(QObject *receiver, QKeyEvent *event)  override
104     {
105         Q_UNUSED(event);
106 
107         if ((qobject_cast<QLineEdit*>     (receiver) != nullptr)||
108             (qobject_cast<QSpinBox*>      (receiver) != nullptr)||
109             (qobject_cast<QDoubleSpinBox*>(receiver) != nullptr)) {
110 
111             return DecisionOnShortcutOverride::overrideShortcut;
112         } else {
113             return DecisionOnShortcutOverride::askNext;
114         }
115     }
116 };
117 
118 class SpinboxShortcutOverrider : public ShortcutOverriderBase
119 {
120 public:
121     constexpr SpinboxShortcutOverrider() = default;
122 
interestedInEvent(QKeyEvent * event)123     virtual bool interestedInEvent(QKeyEvent *event) override
124     {
125         if (event->modifiers() != Qt::NoModifier) {
126             return false;
127         }
128         switch (event->key()) {
129         case Qt::Key_Down:
130         case Qt::Key_Up:
131         case Qt::Key_PageDown:
132         case Qt::Key_PageUp:
133             event->accept();
134             return true;
135         default:
136             return false;
137         }
138     }
handleEvent(QObject * receiver,QKeyEvent * event)139     virtual DecisionOnShortcutOverride handleEvent(QObject *receiver, QKeyEvent *event)  override
140     {
141         Q_UNUSED(event);
142 
143         if (qobject_cast<QSpinBox*>      (receiver) != nullptr||
144             qobject_cast<QDoubleSpinBox*>(receiver) != nullptr) {
145 
146             return DecisionOnShortcutOverride::overrideShortcut;
147         } else {
148             return DecisionOnShortcutOverride::askNext;
149         }
150 
151     }
152 };
153 
154 class TabShortcutOverrider : public ShortcutOverriderBase
155 {
156 public:
157     constexpr TabShortcutOverrider() = default;
158 
interestedInEvent(QKeyEvent * event)159     virtual bool interestedInEvent(QKeyEvent *event) override
160     {
161         bool tab = event->modifiers() == Qt::NoModifier &&
162                     ( event->key() == Qt::Key_Tab ||
163                       event->key() == Qt::Key_Backtab);
164         bool shiftTab = event->modifiers() == Qt::ShiftModifier &&
165                          event->key() == Qt::Key_Backtab;
166         if (tab || shiftTab) {
167             return true;
168         }else{
169             return false;
170         }
171     }
handleEvent(QObject * receiver,QKeyEvent * event)172     virtual DecisionOnShortcutOverride handleEvent(QObject *receiver, QKeyEvent *event)  override
173     {
174         Q_UNUSED(event);
175 
176         if (qobject_cast<KisQPainterCanvas*>(receiver) != nullptr||
177             qobject_cast<KisOpenGLCanvas2*> (receiver) != nullptr) {
178 
179             return DecisionOnShortcutOverride::dontOverrideShortcut;
180         } else {
181             m_nooverride = true;
182             return DecisionOnShortcutOverride::askNext;
183         }
184     }
finishedPhysicalKeyPressHandling()185     virtual DecisionOnShortcutOverride finishedPhysicalKeyPressHandling() override
186     {
187         if (m_nooverride){
188             m_nooverride = false;
189             return DecisionOnShortcutOverride::overrideShortcut;
190         }
191         return DecisionOnShortcutOverride::askNext;
192     }
193 private:
194     bool m_nooverride = false;
195 
196 };
197 
198 
199 //for some reason I can't just populate constexpr
200 //pointer array using "new"
201 LineTextEditingShortcutOverrider overrider0;
202 SpinboxShortcutOverrider overrider1;
203 TabShortcutOverrider overrider2;
204 
205 constexpr ShortcutOverriderBase *allShortcutOverriders[] = {
206     &overrider0, &overrider1, &overrider2
207 };
208 
209 constexpr int numOfShortcutOverriders =
210                     sizeof(allShortcutOverriders)/
211                     sizeof(allShortcutOverriders[0]);
212 
213 
214 
215 } //namespace
216 
217 struct KisQtWidgetsTweaker::Private
218 {
219 public:
PrivateKisQtWidgetsTweaker::Private220     Private(KisQtWidgetsTweaker *parent)
221         : q(parent)
222     {
223     }
224 
225     const KisQtWidgetsTweaker *q;
226 
227     QBitArray interestedHandlers = QBitArray(numOfShortcutOverriders);
228     ShortcutOverriderBase::DecisionOnShortcutOverride decision = ShortcutOverriderBase::DecisionOnShortcutOverride::askNext;
229     bool lastKeyPressProcessingComplete = true;
230 
newPhysicalKeyPressedKisQtWidgetsTweaker::Private231     void newPhysicalKeyPressed(QKeyEvent *event)
232     {
233         for (int i=0; i < numOfShortcutOverriders; ++i) {
234             if (allShortcutOverriders[i]->interestedInEvent(event)) {
235                 interestedHandlers.setBit(i);
236             }else{
237                 interestedHandlers.clearBit(i);
238             }
239         }
240         decision = ShortcutOverriderBase::DecisionOnShortcutOverride::askNext;
241         lastKeyPressProcessingComplete = false;
242     }
243 };
244 
KisQtWidgetsTweaker(QObject * parent)245 KisQtWidgetsTweaker::KisQtWidgetsTweaker(QObject *parent)
246     :QObject(parent)
247     , d(new KisQtWidgetsTweaker::Private(this))
248 {
249 
250 }
251 
~KisQtWidgetsTweaker()252 KisQtWidgetsTweaker::~KisQtWidgetsTweaker()
253 {
254     delete d;
255 }
eventFilter(QObject * receiver,QEvent * event)256 bool KisQtWidgetsTweaker::eventFilter(QObject *receiver, QEvent *event)
257 {
258     switch(event->type()) {
259     case QEvent::ShortcutOverride:{
260         //QLineEdit and other widgets lets qt's shortcut system take away it's keyboard events
261         //even is it knows them, such as ctrl+backspace
262         //if there is application-wide shortcut, assigned to it.
263         //The following code fixes it
264         //by accepting ShortcutOverride events.
265 
266         //if you press key 'a' and then 'b', qt at first call
267         //all handlers for 'a' key press event, and only after that
268         //for 'b'
269         QKeyEvent *key = static_cast<QKeyEvent*>(event);
270         if (d->lastKeyPressProcessingComplete) {
271             d->newPhysicalKeyPressed(key);
272         }
273         for(int i = 0; i < numOfShortcutOverriders; ++i) {
274             if (d->decision != ShortcutOverriderBase::DecisionOnShortcutOverride::askNext) {
275                 break;
276             }
277             if (d->interestedHandlers.at(i)) {
278                 d->decision = allShortcutOverriders[i]->handleEvent(receiver, key);
279             }
280         }
281         //if nothing said whether shortcutoverride to be accepted
282         //last widget that qt will ask will be kismainwindow or docker
283         if (qobject_cast<KisMainWindow*>(receiver)!=nullptr||
284             receiver->inherits(QDockWidget::staticMetaObject.className())) {
285             for (int i = 0; i < numOfShortcutOverriders; ++i) {
286                 if (d->decision != ShortcutOverriderBase::DecisionOnShortcutOverride::askNext) {
287                     break;
288                 }
289                 if (d->interestedHandlers.at(i)) {
290                     d->decision = allShortcutOverriders[i]->finishedPhysicalKeyPressHandling();
291                 }
292             }
293 
294             d->lastKeyPressProcessingComplete = true;
295         }
296         bool retval = false;
297         switch (d->decision) {
298         case ShortcutOverriderBase::DecisionOnShortcutOverride::askNext:
299             event->ignore();
300             retval = false;
301             break;
302         case ShortcutOverriderBase::DecisionOnShortcutOverride::dontOverrideShortcut:
303             event->ignore();
304             retval = true;
305             break;
306         case ShortcutOverriderBase::DecisionOnShortcutOverride::overrideShortcut:
307             event->accept();
308             //once shortcutoverride accepted, qt stop asking everyone
309             //about it and proceed to handling next event
310             d->lastKeyPressProcessingComplete = true;
311             retval = true;
312             break;
313         }
314 
315         return retval || QObject::eventFilter(receiver, event);
316 
317     }break;
318 
319         //other event types
320     default:
321         break;
322     }
323 
324 
325     //code for tweaking the behavior of other qt elements will go here
326 
327 
328     return QObject::eventFilter(receiver, event);
329 }
330 
331 
instance()332 KisQtWidgetsTweaker *KisQtWidgetsTweaker::instance()
333 {
334     return kqwt_instance;
335 }
336