Krita Source Code Documentation
Loading...
Searching...
No Matches
KisInputEventsEater.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2015 Michael Abrahams <miabraha@gmail.com>
3 * SPDX-FileCopyrightText: 2026 Dmitry Kazakov <dimula73@gmail.com>
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 */
7
9
10#include <kis_config.h>
11
12#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
13// for namespace Qt::StringLiterals
14#include <KisPortingUtils.h>
15#endif
16
17
47static bool isMouseEventType(QEvent::Type t)
48{
49 return (t == QEvent::MouseMove ||
50 t == QEvent::MouseButtonPress ||
51 t == QEvent::MouseButtonRelease ||
52 t == QEvent::MouseButtonDblClick);
53}
54
55static bool isTabletEventType(QEvent::Type t)
56{
57 return (t == QEvent::TabletMove ||
58 t == QEvent::TabletPress ||
59 t == QEvent::TabletRelease);
60}
61
62static bool isTouchEventType(QEvent::Type t)
63{
64 return (t == QEvent::TouchBegin ||
65 t == QEvent::TouchEnd ||
66 t == QEvent::TouchUpdate ||
67 t == QEvent::TouchCancel);
68}
69
70static bool isNativeGestureEventType(QEvent::Type t)
71{
72 return t == QEvent::NativeGesture;
73}
74
80
81void KisInputEventsEater::debugEaterStateTransition(const QLatin1String &stateName, bool newValue, QEvent::Type eventType, const QLatin1String &comment)
82{
83 if (comment.isEmpty()) {
84 dbgInputEater.noquote().nospace() << stateName << " <- " << newValue << " by " << eventType;
85 } else {
86 dbgInputEater.noquote().nospace() << stateName << " <- " << newValue << " by " << eventType << " (" << comment << ")";
87 }
88}
89bool KisInputEventsEater::eventFilter(QObject* target, QEvent* event )
90{
91 using namespace Qt::StringLiterals;
92
93 Q_UNUSED(target);
94
95 {
96 // handle `newTabletIsHovering` and `tabletIsPressed` state transitions
97
98 if (isTabletEventType(event->type())) {
99
100 QTabletEvent *ev = static_cast<QTabletEvent*>(event);
101 const bool newTabletIsHovering = ev->buttons() == Qt::NoButton;
102 const bool newTabletIsPressed = !newTabletIsHovering;
103
104 if (newTabletIsHovering != tabletIsHovering) {
105 tabletIsHovering = newTabletIsHovering;
106 debugEaterStateTransition("tabletIsHovering"_L1, newTabletIsHovering, event->type());
107 }
108
109 if (newTabletIsPressed != tabletIsPressed) {
110 tabletIsPressed = newTabletIsPressed;
111 debugEaterStateTransition("tabletIsPressed"_L1, newTabletIsPressed, event->type());
112 }
113
114#ifdef KRITA_ENABLE_MISSING_PROXIMITY_ENTER_WORKAROUND
115 if (!tabletIsInProximity && !missingTabletProximityEventsWorkaround) {
116 warnInputEater << "WARNING: received a tablet event while the tablet is not in proximity! Activating a workaround...";
117 missingTabletProximityEventsWorkaround = MissingTabletProximityEnterWorkaround();
118 } else if (tabletIsInProximity && missingTabletProximityEventsWorkaround) {
119 warnInputEater << "WARNING: received a delayed tablet proximity event, disabling the workaround...";
120 missingTabletProximityEventsWorkaround = std::nullopt;
121 } else if (missingTabletProximityEventsWorkaround) {
122 missingTabletProximityEventsWorkaround->notifyTabletEventArrived();
123 }
124#endif /* KRITA_ENABLE_MISSING_PROXIMITY_ENTER_WORKAROUND */
125 }
126
127#ifdef KRITA_ENABLE_MISSING_PROXIMITY_ENTER_WORKAROUND
128 if (missingTabletProximityEventsWorkaround && !tabletIsPressed && isMouseEventType(event->type())) {
129 missingTabletProximityEventsWorkaround->notifyMouseEventArrived();
130 if (missingTabletProximityEventsWorkaround->shouldForgetAboutTabletInProximity()) {
131 if (tabletIsHovering) {
132 tabletIsHovering = false;
133 debugEaterStateTransition("tabletIsHovering"_L1, false, event->type(), "missing proximity workaround"_L1);
134 }
135 missingTabletProximityEventsWorkaround = std::nullopt;
136 }
137 }
138#endif /* KRITA_ENABLE_MISSING_PROXIMITY_ENTER_WORKAROUND */
139
140 if (event->type() == QEvent::FocusOut ||
141 event->type() == QEvent::Leave ||
153 (event->type() == QEvent::TouchBegin && !tabletIsInProximity)) {
154
155 if (tabletIsHovering) {
156 tabletIsHovering = false;
157 debugEaterStateTransition("tabletIsHovering"_L1, false, event->type());
158 }
159 if (tabletIsPressed) {
160 tabletIsPressed = false;
161 debugEaterStateTransition("tabletIsPressed"_L1, false, event->type());
162 }
163
164#ifdef KRITA_ENABLE_MISSING_PROXIMITY_ENTER_WORKAROUND
165 missingTabletProximityEventsWorkaround = std::nullopt;
166#endif /* KRITA_ENABLE_MISSING_PROXIMITY_ENTER_WORKAROUND */
167 }
168
169 // also proximity-leave in a corresponding function...
170 }
171
172 {
173 // handle `touchIsActive` state transitions
174
175 bool assumeTouchStarted = event->type() == QEvent::TouchBegin;
176
177 if (!assumeTouchStarted && event->type() == QEvent::TouchUpdate) {
178 QTouchEvent *touchEvent = static_cast<QTouchEvent*>(event);
179
180#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
181 using DeviceType = QInputDevice::DeviceType;
182#else
183 using DeviceType = QTouchDevice::DeviceType;
184#endif
185
186 if (touchEvent->device()->type() == DeviceType::TouchPad) {
187#ifdef Q_OS_MACOS
202 // noop, keep assumeTouchStarted false
203#else
204 dbgInputEater << "Received a TouchUpdate for a touchpad device on a non-MacOS system!";
205#endif
206 } else {
207 assumeTouchStarted = true;
208 }
209 }
210
211 if (assumeTouchStarted) {
212 if (!touchIsActive) {
213 touchIsActive = true;
214 debugEaterStateTransition("touchIsActive"_L1, true, event->type());
215 }
216 }
217
218 if (event->type() == QEvent::TouchEnd ||
219 event->type() == QEvent::TouchCancel) {
220 if (touchIsActive) {
221 touchIsActive = false;
222 debugEaterStateTransition("touchIsActive"_L1, false, event->type());
223 }
224 }
225 }
226
227 if (eatOneMousePressEvent && event->type() == QEvent::MouseButtonPress
228 // Drop one mouse press following tabletPress or touchBegin
229 && (static_cast<QMouseEvent*>(event)->button() == Qt::LeftButton)) {
230 eatOneMousePressEvent = false;
231 debugEvent<QMouseEvent>(event, BlockedByNextPressSuppression);
232 return true;
233 }
234
236 if (event->type() == QEvent::TabletPress ||
237 event->type() == QEvent::TabletRelease) {
238
239 QTabletEvent *te = static_cast<QTabletEvent*>(event);
240 if (te->button() != Qt::LeftButton) {
241 debugEvent<QTabletEvent>(te, BlockedByButtonsWorkaround);
242 return true;
243 }
244 } else if (event->type() == QEvent::MouseButtonPress ||
245 event->type() == QEvent::MouseButtonRelease ||
246 event->type() == QEvent::MouseButtonDblClick) {
247
248 QMouseEvent *me = static_cast<QMouseEvent*>(event);
249 if (me->button() != Qt::LeftButton) {
250 return false;
251 }
252 }
253 }
254
255 const bool shouldEatMouseEvents =
256 // On MacOS Qt sends fake mouse events instead of hover events,
257 // so we should not block mouse events when the tablet just hovers
258 // the canvas. The real TabletMove events will arrive only when
259 // the user touches the surface of the tablet, i.e. after a TabletPress.
260#ifndef Q_OS_MACOS
263#endif
266
267 if (isMouseEventType(event->type()) &&
268 (shouldEatMouseEvents
269 // On Mac, we need mouse events when the tablet is in proximity, but not pressed down
270 // since tablet move events are not generated until after tablet press.
271 #ifndef Q_OS_MAC
272 || (static_cast<QMouseEvent*>(event)->source() != Qt::MouseEventNotSynthesized)
273 #endif
274 )) {
275
276 EventBlockingReasons reasons = NotBlocked;
277#ifndef Q_OS_MACOS
279 reasons.setFlag(BlockedByTabletHover, tabletIsHovering);
280 reasons.setFlag(BlockedBySynthetic, static_cast<QMouseEvent*>(event)->source() != Qt::MouseEventNotSynthesized);
281#endif
282 reasons.setFlag(BlockedByTabletPress, tabletIsPressed);
283 reasons.setFlag(BlockedByTouchPress, touchIsActive);
284
285 debugEvent<QMouseEvent>(event, reasons);
286
287 // Drop mouse events if enabled or event was synthetic & synthetic events are disabled
288 return true;
289 }
290
291 if (event->type() == QEvent::TouchBegin &&
293
294 EventBlockingReasons reasons = NotBlocked;
296 reasons.setFlag(BlockedByTabletHover, tabletIsHovering);
297 reasons.setFlag(BlockedByTabletPress, tabletIsPressed);
298
299 debugEvent<QTouchEvent>(event, reasons);
300
301 if (touchIsActive) {
302 // don't forget to reset the touch active state, since we are
303 // not going to receive the res of the events
304 touchIsActive = false;
305 debugEaterStateTransition("touchIsActive"_L1, false, event->type(), "cancelled by tablet"_L1);
306 }
307
308 // Drop touch-begin event. It will prevent KisShortcutMatcher to
309 // start any touch action, while letting it complete any existing
310 // actions. We explicitly accept the event to make sure OS will
311 // not trigger any global gestures (works on WinInk only).
312 event->accept();
313 return true;
314 }
315
316 if (isMouseEventType(event->type())) {
317 debugEvent<QMouseEvent>(event, NotBlocked);
318 } else if (isTabletEventType(event->type())) {
319 debugEvent<QTabletEvent>(event, NotBlocked);
320 } else if (isTouchEventType(event->type())) {
321 debugEvent<QTouchEvent>(event, NotBlocked);
322 } else if (isNativeGestureEventType(event->type())) {
323 debugEvent<QNativeGestureEvent>(event, NotBlocked);
324 }
325
326 return false; // All clear - let this one through!
327}
328
330{
331 using namespace Qt::StringLiterals;
332
333 tabletIsInProximity = true;
334 debugEaterStateTransition("tabletIsInProximity"_L1, true, QEvent::TabletEnterProximity);
335}
336
338{
339 using namespace Qt::StringLiterals;
340
341 tabletIsInProximity = false;
342 debugEaterStateTransition("tabletIsInProximity"_L1, false, QEvent::TabletLeaveProximity);
343
344 if (tabletIsHovering) {
345 tabletIsHovering = false;
346 debugEaterStateTransition("tabletIsHovering"_L1, false, QEvent::TabletLeaveProximity);
347 }
348}
349
351{
352 // Enable on other platforms if getting full-pressure splotches
354}
static bool isTouchEventType(QEvent::Type t)
static bool isMouseEventType(QEvent::Type t)
static bool isNativeGestureEventType(QEvent::Type t)
static bool isTabletEventType(QEvent::Type t)
KisMagneticGraph::vertex_descriptor target(typename KisMagneticGraph::edge_descriptor e, KisMagneticGraph g)
KisMagneticGraph::vertex_descriptor source(typename KisMagneticGraph::edge_descriptor e, KisMagneticGraph g)
bool useRightMiddleTabletButtonWorkaround(bool defaultValue=false) const
bool eventFilter(QObject *target, QEvent *event)
void debugEaterStateTransition(const QLatin1String &stateName, bool newValue, QEvent::Type eventType, const QLatin1String &comment=QLatin1String())
#define warnInputEater
Definition kis_debug.h:110
#define dbgInputEater
Definition kis_debug.h:67