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