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