1 /* This file is part of the KDE project
2 *
3 * Copyright (C) 2012 Arjen Hiemstra <ahiemstra@heimr.nl>
4 * Copyright (C) 2015 Michael Abrahams <miabraha@gmail.com>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 */
20
21 #include "kis_input_manager.h"
22
23 #include <kis_debug.h>
24 #include <QQueue>
25 #include <klocalizedstring.h>
26 #include <QApplication>
27 #include <QTouchEvent>
28 #include <QElapsedTimer>
29
30 #include <KoToolManager.h>
31
32 #include "kis_tool_proxy.h"
33
34 #include <kis_config.h>
35 #include <kis_canvas2.h>
36 #include <KisViewManager.h>
37 #include <kis_image.h>
38 #include <kis_canvas_resource_provider.h>
39 #include <kis_favorite_resource_manager.h>
40
41 #include "kis_abstract_input_action.h"
42 #include "kis_tool_invocation_action.h"
43 #include "kis_pan_action.h"
44 #include "kis_alternate_invocation_action.h"
45 #include "kis_rotate_canvas_action.h"
46 #include "kis_zoom_action.h"
47 #include "kis_show_palette_action.h"
48 #include "kis_change_primary_setting_action.h"
49
50 #include "kis_shortcut_matcher.h"
51 #include "kis_stroke_shortcut.h"
52 #include "kis_single_action_shortcut.h"
53 #include "kis_touch_shortcut.h"
54
55 #include "kis_input_profile.h"
56 #include "kis_input_profile_manager.h"
57 #include "kis_shortcut_configuration.h"
58
59 #include <input/kis_tablet_debugger.h>
60 #include <kis_signal_compressor.h>
61
62 #include "kis_extended_modifiers_mapper.h"
63 #include "kis_input_manager_p.h"
64 #include "kis_algebra_2d.h"
65
66 template <typename T>
qHash(QPointer<T> value)67 uint qHash(QPointer<T> value) {
68 return reinterpret_cast<quintptr>(value.data());
69 }
70
KisInputManager(QObject * parent)71 KisInputManager::KisInputManager(QObject *parent)
72 : QObject(parent), d(new Private(this))
73 {
74 d->setupActions();
75
76 connect(KoToolManager::instance(), SIGNAL(aboutToChangeTool(KoCanvasController*)), SLOT(slotAboutToChangeTool()));
77 connect(KoToolManager::instance(), SIGNAL(changedTool(KoCanvasController*,int)), SLOT(slotToolChanged()));
78 connect(&d->moveEventCompressor, SIGNAL(timeout()), SLOT(slotCompressedMoveEvent()));
79
80
81 QApplication::instance()->
82 installEventFilter(new Private::ProximityNotifier(d, this));
83
84 // on macos global Monitor listen to keypresses when krita is not in focus
85 // and local monitor listen presses when krita is in focus.
86 #ifdef Q_OS_MACOS
87 KisExtendedModifiersMapper::setLocalMonitor(true, &d->matcher);
88 #endif
89 }
90
~KisInputManager()91 KisInputManager::~KisInputManager()
92 {
93 #ifdef Q_OS_MACOS
94 KisExtendedModifiersMapper::setLocalMonitor(false);
95 #endif
96 delete d;
97 }
98
addTrackedCanvas(KisCanvas2 * canvas)99 void KisInputManager::addTrackedCanvas(KisCanvas2 *canvas)
100 {
101 d->canvasSwitcher.addCanvas(canvas);
102 }
103
removeTrackedCanvas(KisCanvas2 * canvas)104 void KisInputManager::removeTrackedCanvas(KisCanvas2 *canvas)
105 {
106 d->canvasSwitcher.removeCanvas(canvas);
107 }
108
toggleTabletLogger()109 void KisInputManager::toggleTabletLogger()
110 {
111 KisTabletDebugger::instance()->toggleDebugging();
112 }
113
attachPriorityEventFilter(QObject * filter,int priority)114 void KisInputManager::attachPriorityEventFilter(QObject *filter, int priority)
115 {
116 Private::PriorityList::iterator begin = d->priorityEventFilter.begin();
117 Private::PriorityList::iterator it = begin;
118 Private::PriorityList::iterator end = d->priorityEventFilter.end();
119
120 it = std::find_if(begin, end,
121 [filter] (const Private::PriorityPair &a) { return a.second == filter; });
122
123 if (it != end) return;
124
125 it = std::find_if(begin, end,
126 [priority] (const Private::PriorityPair &a) { return a.first > priority; });
127
128 d->priorityEventFilter.insert(it, qMakePair(priority, filter));
129 d->priorityEventFilterSeqNo++;
130 }
131
detachPriorityEventFilter(QObject * filter)132 void KisInputManager::detachPriorityEventFilter(QObject *filter)
133 {
134 Private::PriorityList::iterator it = d->priorityEventFilter.begin();
135 Private::PriorityList::iterator end = d->priorityEventFilter.end();
136
137 it = std::find_if(it, end,
138 [filter] (const Private::PriorityPair &a) { return a.second == filter; });
139
140 if (it != end) {
141 d->priorityEventFilter.erase(it);
142 }
143 }
144
setupAsEventFilter(QObject * receiver)145 void KisInputManager::setupAsEventFilter(QObject *receiver)
146 {
147 if (d->eventsReceiver) {
148 d->eventsReceiver->removeEventFilter(this);
149 }
150
151 d->eventsReceiver = receiver;
152
153 if (d->eventsReceiver) {
154 d->eventsReceiver->installEventFilter(this);
155 }
156 }
157
158 #if defined (__clang__)
159 #pragma GCC diagnostic ignored "-Wswitch"
160 #endif
161
eventFilter(QObject * object,QEvent * event)162 bool KisInputManager::eventFilter(QObject* object, QEvent* event)
163 {
164 if (object != d->eventsReceiver) return false;
165
166 if (d->eventEater.eventFilter(object, event)) return false;
167
168 if (!d->matcher.hasRunningShortcut()) {
169
170 int savedPriorityEventFilterSeqNo = d->priorityEventFilterSeqNo;
171
172 for (auto it = d->priorityEventFilter.begin(); it != d->priorityEventFilter.end(); /*noop*/) {
173 const QPointer<QObject> &filter = it->second;
174
175 if (filter.isNull()) {
176 it = d->priorityEventFilter.erase(it);
177
178 d->priorityEventFilterSeqNo++;
179 savedPriorityEventFilterSeqNo++;
180 continue;
181 }
182
183 if (filter->eventFilter(object, event)) return true;
184
185 /**
186 * If the filter removed itself from the filters list or
187 * added something there, just exit the loop
188 */
189 if (d->priorityEventFilterSeqNo != savedPriorityEventFilterSeqNo) {
190 return true;
191 }
192
193 ++it;
194 }
195
196 // KoToolProxy needs to pre-process some events to ensure the
197 // global shortcuts (not the input manager's ones) are not
198 // executed, in particular, this line will accept events when the
199 // tool is in text editing, preventing shortcut triggering
200 if (d->toolProxy) {
201 d->toolProxy->processEvent(event);
202 }
203 }
204
205 // Continue with the actual switch statement...
206 return eventFilterImpl(event);
207 }
208
209 // Qt's events do not have copy-ctors yet, so we should emulate them
210 // See https://bugreports.qt.io/browse/QTBUG-72488
211
212 template <class Event> void copyEventHack(Event *src, QScopedPointer<QEvent> &dst);
213
copyEventHack(QMouseEvent * src,QScopedPointer<QEvent> & dst)214 template<> void copyEventHack(QMouseEvent *src, QScopedPointer<QEvent> &dst) {
215 QMouseEvent *tmp = new QMouseEvent(src->type(),
216 src->localPos(), src->windowPos(), src->screenPos(),
217 src->button(), src->buttons(), src->modifiers(),
218 src->source());
219 tmp->setTimestamp(src->timestamp());
220 dst.reset(tmp);
221 }
222
copyEventHack(QTabletEvent * src,QScopedPointer<QEvent> & dst)223 template<> void copyEventHack(QTabletEvent *src, QScopedPointer<QEvent> &dst) {
224 QTabletEvent *tmp = new QTabletEvent(src->type(),
225 src->posF(), src->globalPosF(),
226 src->device(), src->pointerType(),
227 src->pressure(),
228 src->xTilt(), src->yTilt(),
229 src->tangentialPressure(),
230 src->rotation(),
231 src->z(),
232 src->modifiers(),
233 src->uniqueId(),
234 src->button(), src->buttons());
235 tmp->setTimestamp(src->timestamp());
236 dst.reset(tmp);
237 }
238
copyEventHack(QTouchEvent * src,QScopedPointer<QEvent> & dst)239 template<> void copyEventHack(QTouchEvent *src, QScopedPointer<QEvent> &dst) {
240 QTouchEvent *tmp = new QTouchEvent(src->type(),
241 src->device(),
242 src->modifiers(),
243 src->touchPointStates(),
244 src->touchPoints());
245 tmp->setTimestamp(src->timestamp());
246 dst.reset(tmp);
247 }
248
249
250 template <class Event>
compressMoveEventCommon(Event * event)251 bool KisInputManager::compressMoveEventCommon(Event *event)
252 {
253 /**
254 * We construct a copy of this event object, so we must ensure it
255 * has a correct type.
256 */
257 static_assert(std::is_same<Event, QMouseEvent>::value ||
258 std::is_same<Event, QTabletEvent>::value ||
259 std::is_same<Event, QTouchEvent>::value,
260 "event should be a mouse or a tablet event");
261
262 #ifdef Q_OS_WIN32
263 /**
264 * On Windows, when the user presses some global window manager shortcuts,
265 * e.g. Alt+Space (to show window title menu), events for these key presses
266 * and releases are not delivered (see bug 424319). This code is a workaround
267 * for this problem. It checks consistency of standard modifiers and resets
268 * shortcut's matcher state in case of a trouble.
269 */
270 if (event->type() == QEvent::MouseButtonPress ||
271 event->type() == QEvent::MouseButtonRelease ||
272 event->type() == QEvent::MouseMove ||
273 event->type() == QEvent::TabletMove ||
274 event->type() == QEvent::TabletPress ||
275 event->type() == QEvent::TabletRelease) {
276
277 QInputEvent *inputEvent = static_cast<QInputEvent*>(event);
278 if (!d->matcher.sanityCheckModifiersCorrectness(inputEvent->modifiers())) {
279 qWarning() << "WARNING: modifiers state became inconsistent! Trying to fix that...";
280 qWarning() << " " << ppVar(inputEvent->modifiers());
281 qWarning() << " " << ppVar(d->matcher.debugPressedKeys());
282
283 d->fixShortcutMatcherModifiersState();
284 }
285 }
286 #endif
287
288 bool retval = false;
289
290 /**
291 * Compress the events if the tool doesn't need high resolution input
292 */
293 if ((event->type() == QEvent::MouseMove ||
294 event->type() == QEvent::TabletMove ||
295 event->type() == QEvent::TouchUpdate) &&
296 (!d->matcher.supportsHiResInputEvents() ||
297 d->testingCompressBrushEvents)) {
298
299 copyEventHack(event, d->compressedMoveEvent);
300 d->moveEventCompressor.start();
301
302 /**
303 * On Linux Qt eats the rest of unneeded events if we
304 * ignore the first of the chunk of tablet events. So
305 * generally we should never activate this feature. Only
306 * for testing purposes!
307 */
308 if (d->testingAcceptCompressedTabletEvents) {
309 event->setAccepted(true);
310 }
311
312 retval = true;
313 } else {
314 slotCompressedMoveEvent();
315 retval = d->handleCompressedTabletEvent(event);
316 }
317
318 return retval;
319 }
320
shouldResetWheelDelta(QEvent * event)321 bool shouldResetWheelDelta(QEvent * event)
322 {
323 return
324 event->type() == QEvent::FocusIn ||
325 event->type() == QEvent::FocusOut ||
326 event->type() == QEvent::MouseButtonPress ||
327 event->type() == QEvent::MouseButtonRelease ||
328 event->type() == QEvent::MouseButtonDblClick ||
329 event->type() == QEvent::TabletPress ||
330 event->type() == QEvent::TabletRelease ||
331 event->type() == QEvent::Enter ||
332 event->type() == QEvent::Leave ||
333 event->type() == QEvent::TouchBegin ||
334 event->type() == QEvent::TouchEnd ||
335 event->type() == QEvent::TouchCancel ||
336 event->type() == QEvent::NativeGesture;
337
338 }
339
eventFilterImpl(QEvent * event)340 bool KisInputManager::eventFilterImpl(QEvent * event)
341 {
342 bool retval = false;
343
344 if (shouldResetWheelDelta(event)) {
345 d->accumulatedScrollDelta = 0;
346 }
347
348 switch (event->type()) {
349 case QEvent::MouseButtonPress:
350 case QEvent::MouseButtonDblClick: {
351 d->debugEvent<QMouseEvent, true>(event);
352 if (d->touchHasBlockedPressEvents) break;
353
354 QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
355
356 if (d->tryHidePopupPalette()) {
357 retval = true;
358 } else {
359 //Make sure the input actions know we are active.
360 KisAbstractInputAction::setInputManager(this);
361 retval = d->matcher.buttonPressed(mouseEvent->button(), mouseEvent);
362 }
363 //Reset signal compressor to prevent processing events before press late
364 d->resetCompressor();
365 event->setAccepted(retval);
366 break;
367 }
368 case QEvent::MouseButtonRelease: {
369 d->debugEvent<QMouseEvent, true>(event);
370 if (d->touchHasBlockedPressEvents) break;
371
372 QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
373 retval = d->matcher.buttonReleased(mouseEvent->button(), mouseEvent);
374 event->setAccepted(retval);
375 break;
376 }
377 case QEvent::ShortcutOverride: {
378 d->debugEvent<QKeyEvent, false>(event);
379 QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
380
381 Qt::Key key = KisExtendedModifiersMapper::workaroundShiftAltMetaHell(keyEvent);
382
383 if (!keyEvent->isAutoRepeat()) {
384 retval = d->matcher.keyPressed(key);
385 } else {
386 retval = d->matcher.autoRepeatedKeyPressed(key);
387 }
388
389 // In case we matched ashortcut we should accept the event to
390 // notify Qt that it shouldn't try to trigger its partially matched
391 // shortcuts.
392 if (retval) {
393 keyEvent->setAccepted(true);
394 }
395
396 break;
397 }
398 case QEvent::KeyRelease: {
399 d->debugEvent<QKeyEvent, false>(event);
400 QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
401
402 if (!keyEvent->isAutoRepeat()) {
403 Qt::Key key = KisExtendedModifiersMapper::workaroundShiftAltMetaHell(keyEvent);
404 retval = d->matcher.keyReleased(key);
405 }
406 break;
407 }
408 case QEvent::MouseMove: {
409 d->debugEvent<QMouseEvent, true>(event);
410
411 QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
412 retval = compressMoveEventCommon(mouseEvent);
413
414 break;
415 }
416 case QEvent::Wheel: {
417 d->debugEvent<QWheelEvent, false>(event);
418 QWheelEvent *wheelEvent = static_cast<QWheelEvent*>(event);
419
420 #ifdef Q_OS_MACOS
421 // Some QT wheel events are actually touch pad pan events. From the QT docs:
422 // "Wheel events are generated for both mouse wheels and trackpad scroll gestures."
423
424 // We differentiate between touchpad events and real mouse wheels by inspecting the
425 // event source.
426
427 if (wheelEvent->source() == Qt::MouseEventSource::MouseEventSynthesizedBySystem) {
428 KisAbstractInputAction::setInputManager(this);
429 retval = d->matcher.wheelEvent(KisSingleActionShortcut::WheelTrackpad, wheelEvent);
430 break;
431 }
432 #endif
433
434 d->accumulatedScrollDelta += wheelEvent->delta();
435 KisSingleActionShortcut::WheelAction action;
436
437 /**
438 * Ignore delta 0 events on OSX, since they are triggered by tablet
439 * proximity when using Wacom devices.
440 */
441 #ifdef Q_OS_MACOS
442 if(wheelEvent->delta() == 0) {
443 retval = true;
444 break;
445 }
446 #endif
447
448 if (wheelEvent->orientation() == Qt::Horizontal) {
449 if(wheelEvent->delta() < 0) {
450 action = KisSingleActionShortcut::WheelRight;
451 }
452 else {
453 action = KisSingleActionShortcut::WheelLeft;
454 }
455 }
456 else {
457 if(wheelEvent->delta() > 0) {
458 action = KisSingleActionShortcut::WheelUp;
459 }
460 else {
461 action = KisSingleActionShortcut::WheelDown;
462 }
463 }
464
465 bool wasScrolled = false;
466
467 while (qAbs(d->accumulatedScrollDelta) >= QWheelEvent::DefaultDeltasPerStep) {
468 //Make sure the input actions know we are active.
469 KisAbstractInputAction::setInputManager(this);
470 retval = d->matcher.wheelEvent(action, wheelEvent);
471 d->accumulatedScrollDelta -=
472 KisAlgebra2D::signPZ(d->accumulatedScrollDelta) *
473 QWheelEvent::DefaultDeltasPerStep;
474 wasScrolled = true;
475 }
476
477 if (wasScrolled) {
478 d->accumulatedScrollDelta = 0;
479 }
480
481 retval = !wasScrolled;
482 break;
483 }
484 #ifndef Q_OS_ANDROID
485 case QEvent::Enter:
486 d->debugEvent<QEvent, false>(event);
487 //Make sure the input actions know we are active.
488 KisAbstractInputAction::setInputManager(this);
489 if (!d->containsPointer) {
490 d->containsPointer = true;
491
492 d->allowMouseEvents();
493 d->touchHasBlockedPressEvents = false;
494 }
495 d->matcher.enterEvent();
496 break;
497 case QEvent::Leave:
498 d->debugEvent<QEvent, false>(event);
499 d->containsPointer = false;
500 /**
501 * We won't get a TabletProximityLeave event when the tablet
502 * is hovering above some other widget, so restore cursor
503 * events processing right now.
504 */
505 d->allowMouseEvents();
506 d->touchHasBlockedPressEvents = false;
507
508 d->matcher.leaveEvent();
509 break;
510 #endif
511 case QEvent::FocusIn:
512 d->debugEvent<QEvent, false>(event);
513 KisAbstractInputAction::setInputManager(this);
514
515 //Clear all state so we don't have half-matched shortcuts dangling around.
516 d->matcher.reinitialize();
517
518 { // Emulate pressing of the key that are already pressed
519 KisExtendedModifiersMapper mapper;
520
521 Qt::KeyboardModifiers modifiers = mapper.queryStandardModifiers();
522 Q_FOREACH (Qt::Key key, mapper.queryExtendedModifiers()) {
523 QKeyEvent kevent(QEvent::ShortcutOverride, key, modifiers);
524 eventFilterImpl(&kevent);
525 }
526 }
527
528 d->allowMouseEvents();
529 break;
530
531 case QEvent::FocusOut: {
532 d->debugEvent<QEvent, false>(event);
533 KisAbstractInputAction::setInputManager(this);
534
535 QPointF currentLocalPos =
536 canvas()->canvasWidget()->mapFromGlobal(QCursor::pos());
537
538 d->matcher.lostFocusEvent(currentLocalPos);
539
540 break;
541 }
542 case QEvent::TabletPress: {
543 d->debugEvent<QTabletEvent, false>(event);
544 QTabletEvent *tabletEvent = static_cast<QTabletEvent*>(event);
545 if (d->tryHidePopupPalette()) {
546 retval = true;
547 } else {
548 //Make sure the input actions know we are active.
549 KisAbstractInputAction::setInputManager(this);
550 retval = d->matcher.buttonPressed(tabletEvent->button(), tabletEvent);
551 if (!d->containsPointer) {
552 d->containsPointer = true;
553 d->touchHasBlockedPressEvents = false;
554 }
555 }
556 event->setAccepted(true);
557 retval = true;
558 d->blockMouseEvents();
559 //Reset signal compressor to prevent processing events before press late
560 d->resetCompressor();
561
562
563 #if defined Q_OS_LINUX && !defined QT_HAS_ENTER_LEAVE_PATCH
564 // remove this hack when this patch is integrated:
565 // https://codereview.qt-project.org/#/c/255384/
566 event->setAccepted(false);
567 d->eatOneMousePress();
568 #elif defined Q_OS_WIN32
569 /**
570 * Windows is the only platform that synthesizes mouse events for
571 * the tablet on OS-level, that is, even when we accept the event
572 */
573 d->eatOneMousePress();
574 #endif
575
576 break;
577 }
578 case QEvent::TabletMove: {
579 d->debugEvent<QTabletEvent, false>(event);
580
581 QTabletEvent *tabletEvent = static_cast<QTabletEvent*>(event);
582 retval = compressMoveEventCommon(tabletEvent);
583
584 if (d->tabletLatencyTracker) {
585 d->tabletLatencyTracker->push(tabletEvent->timestamp());
586 }
587
588 /**
589 * The flow of tablet events means the tablet is in the
590 * proximity area, so activate it even when the
591 * TabletEnterProximity event was missed (may happen when
592 * changing focus of the window with tablet in the proximity
593 * area)
594 */
595 d->blockMouseEvents();
596
597 #if defined Q_OS_LINUX && !defined QT_HAS_ENTER_LEAVE_PATCH
598 // remove this hack when this patch is integrated:
599 // https://codereview.qt-project.org/#/c/255384/
600 event->setAccepted(false);
601 #endif
602
603 break;
604 }
605 case QEvent::TabletRelease: {
606 #if defined(Q_OS_MAC) || defined(Q_OS_ANDROID)
607 d->allowMouseEvents();
608 #endif
609 d->debugEvent<QTabletEvent, false>(event);
610
611 QTabletEvent *tabletEvent = static_cast<QTabletEvent*>(event);
612 retval = d->matcher.buttonReleased(tabletEvent->button(), tabletEvent);
613 retval = true;
614 event->setAccepted(true);
615
616 #if defined Q_OS_LINUX && !defined QT_HAS_ENTER_LEAVE_PATCH
617 // remove this hack when this patch is integrated:
618 // https://codereview.qt-project.org/#/c/255384/
619 event->setAccepted(false);
620 #endif
621
622 break;
623 }
624
625 case QEvent::TouchBegin:
626 {
627 d->debugEvent<QTouchEvent, false>(event);
628 if (startTouch(retval)) {
629 QTouchEvent *touchEvent = static_cast<QTouchEvent *> (event);
630 KisAbstractInputAction::setInputManager(this);
631
632 if (!KisConfig(true).disableTouchOnCanvas()
633 && touchEvent->touchPoints().count() == 1)
634 {
635 d->previousPos = touchEvent->touchPoints().at(0).pos();
636 d->buttonPressed = false;
637 d->resetCompressor();
638 }
639 else {
640 retval = d->matcher.touchBeginEvent(touchEvent);
641 }
642 event->accept();
643 }
644
645 // if the event isn't handled, Qt starts to send MouseEvents
646 if (!KisConfig(true).disableTouchOnCanvas())
647 retval = true;
648 break;
649 }
650
651 case QEvent::TouchUpdate:
652 {
653 QTouchEvent *touchEvent = static_cast<QTouchEvent*>(event);
654 d->debugEvent<QTouchEvent, false>(event);
655
656 #ifdef Q_OS_MAC
657 int count = 0;
658 Q_FOREACH (const QTouchEvent::TouchPoint &point, touchEvent->touchPoints()) {
659 if (point.state() != Qt::TouchPointReleased) {
660 count++;
661 }
662 }
663
664 if (count < 2 && touchEvent->touchPoints().length() > count) {
665 d->touchHasBlockedPressEvents = false;
666 retval = d->matcher.touchEndEvent(touchEvent);
667 } else {
668 #endif
669 QPointF currentPos = touchEvent->touchPoints().at(0).pos();
670 if (d->touchStrokeStarted || (!KisConfig(true).disableTouchOnCanvas()
671 && !d->touchHasBlockedPressEvents
672 && touchEvent->touchPoints().count() == 1
673 && touchEvent->touchPointStates() != Qt::TouchPointStationary
674 && (qAbs(currentPos.x() - d->previousPos.x()) > 1 // stop wobbiliness which Qt sends us
675 || qAbs(currentPos.y() - d->previousPos.y()) > 1)))
676 {
677 d->previousPos = currentPos;
678 if (!d->buttonPressed)
679 {
680 // we start it here not in TouchBegin, because Qt::TouchPointStationary doesn't work with hpdi devices.
681 retval = d->matcher.buttonPressed(Qt::LeftButton, touchEvent);
682 d->buttonPressed = true;
683 break;
684 }
685
686 // if it is a full-fledged stroke, then ignore (currentPos.x - previousPos.x)
687 d->touchStrokeStarted = true;
688 retval = compressMoveEventCommon(touchEvent);
689 d->blockMouseEvents();
690 }
691 else if (!d->touchStrokeStarted){
692 KisAbstractInputAction::setInputManager(this);
693
694 retval = d->matcher.touchUpdateEvent(touchEvent);
695 d->touchHasBlockedPressEvents = retval;
696 }
697 #ifdef Q_OS_MACOS
698 }
699 #endif
700 // if the event isn't handled, Qt starts to send MouseEvents
701 if (!KisConfig(true).disableTouchOnCanvas())
702 retval = true;
703
704 event->accept();
705 break;
706 }
707
708 case QEvent::TouchEnd:
709 {
710 d->debugEvent<QTouchEvent, false>(event);
711 endTouch();
712 QTouchEvent *touchEvent = static_cast<QTouchEvent*>(event);
713 retval = d->matcher.touchEndEvent(touchEvent);
714 if (d->touchStrokeStarted)
715 {
716 retval = d->matcher.buttonReleased(Qt::LeftButton, touchEvent);
717
718 d->previousPos = {0, 0};
719 d->touchStrokeStarted = false; // stroke ended
720 }
721
722 // if the event isn't handled, Qt starts to send MouseEvents
723 if (!KisConfig(true).disableTouchOnCanvas())
724 retval = true;
725
726 event->accept();
727 break;
728 }
729 case QEvent::TouchCancel:
730 {
731 d->debugEvent<QTouchEvent, false>(event);
732 endTouch();
733 d->matcher.touchCancelEvent(d->previousPos);
734 // reset state
735 d->previousPos = {0, 0};
736 d->touchStrokeStarted = false;
737 retval = true;
738 event->accept();
739 break;
740 }
741
742 case QEvent::NativeGesture:
743 {
744 QNativeGestureEvent *gevent = static_cast<QNativeGestureEvent*>(event);
745 switch (gevent->gestureType()) {
746 case Qt::BeginNativeGesture:
747 {
748 if (startTouch(retval)) {
749 KisAbstractInputAction::setInputManager(this);
750 retval = d->matcher.nativeGestureBeginEvent(gevent);
751 event->accept();
752 }
753 break;
754 }
755 case Qt::EndNativeGesture:
756 {
757 endTouch();
758 retval = d->matcher.nativeGestureEndEvent(gevent);
759 event->accept();
760 break;
761 }
762 default:
763 {
764 KisAbstractInputAction::setInputManager(this);
765 retval = d->matcher.nativeGestureEvent(gevent);
766 event->accept();
767 break;
768 }
769 }
770 break;
771 }
772
773 default:
774 break;
775 }
776
777 return !retval ? d->processUnhandledEvent(event) : true;
778 }
779
startTouch(bool & retval)780 bool KisInputManager::startTouch(bool &retval)
781 {
782 // Touch rejection: if touch is disabled on canvas, no need to block mouse press events
783 if (KisConfig(true).disableTouchOnCanvas()) {
784 d->eatOneMousePress();
785 }
786 if (d->tryHidePopupPalette()) {
787 retval = true;
788 return false;
789 } else {
790 return true;
791 }
792 }
793
endTouch()794 void KisInputManager::endTouch()
795 {
796 d->touchHasBlockedPressEvents = false;
797 }
798
slotCompressedMoveEvent()799 void KisInputManager::slotCompressedMoveEvent()
800 {
801 if (d->compressedMoveEvent) {
802 // d->touchHasBlockedPressEvents = false;
803
804 (void) d->handleCompressedTabletEvent(d->compressedMoveEvent.data());
805 d->compressedMoveEvent.reset();
806 //dbgInput << "Compressed move event received.";
807 } else {
808 //dbgInput << "Unexpected empty move event";
809 }
810 }
811
canvas() const812 KisCanvas2* KisInputManager::canvas() const
813 {
814 return d->canvas;
815 }
816
toolProxy() const817 QPointer<KisToolProxy> KisInputManager::toolProxy() const
818 {
819 return d->toolProxy;
820 }
821
slotAboutToChangeTool()822 void KisInputManager::slotAboutToChangeTool()
823 {
824 QPointF currentLocalPos;
825 if (canvas() && canvas()->canvasWidget()) {
826 currentLocalPos = canvas()->canvasWidget()->mapFromGlobal(QCursor::pos());
827 }
828 d->matcher.lostFocusEvent(currentLocalPos);
829 }
830
slotToolChanged()831 void KisInputManager::slotToolChanged()
832 {
833 if (!d->canvas) return;
834 KoToolManager *toolManager = KoToolManager::instance();
835 KoToolBase *tool = toolManager->toolById(canvas(), toolManager->activeToolId());
836 if (tool) {
837 d->setMaskSyntheticEvents(tool->maskSyntheticEvents());
838 if (tool->isInTextMode()) {
839 d->forwardAllEventsToTool = true;
840 d->matcher.suppressAllActions(true);
841 } else {
842 d->forwardAllEventsToTool = false;
843 d->matcher.suppressAllActions(false);
844 }
845 }
846 }
847
848
profileChanged()849 void KisInputManager::profileChanged()
850 {
851 d->matcher.clearShortcuts();
852
853 KisInputProfile *profile = KisInputProfileManager::instance()->currentProfile();
854 if (profile) {
855 const QList<KisShortcutConfiguration*> shortcuts = profile->allShortcuts();
856
857 for (KisShortcutConfiguration * const shortcut : shortcuts) {
858 dbgUI << "Adding shortcut" << shortcut->keys() << "for action" << shortcut->action()->name();
859 switch(shortcut->type()) {
860 case KisShortcutConfiguration::KeyCombinationType:
861 d->addKeyShortcut(shortcut->action(), shortcut->mode(), shortcut->keys());
862 break;
863 case KisShortcutConfiguration::MouseButtonType:
864 d->addStrokeShortcut(shortcut->action(), shortcut->mode(), shortcut->keys(), shortcut->buttons());
865 break;
866 case KisShortcutConfiguration::MouseWheelType:
867 d->addWheelShortcut(shortcut->action(), shortcut->mode(), shortcut->keys(), shortcut->wheel());
868 break;
869 case KisShortcutConfiguration::GestureType:
870 if (!d->addNativeGestureShortcut(shortcut->action(), shortcut->mode(), shortcut->gesture())) {
871 d->addTouchShortcut(shortcut->action(), shortcut->mode(), shortcut->gesture());
872 }
873 break;
874 default:
875 break;
876 }
877 }
878 }
879 else {
880 dbgInput << "No Input Profile Found: canvas interaction will be impossible";
881 }
882 }
883