1 | /* |
2 | SPDX-FileCopyrightText: 2011 Marco Martin <notmart@gmail.com> |
3 | SPDX-FileCopyrightText: 2013 Sebastian Kügler <sebas@kde.org> |
4 | |
5 | SPDX-License-Identifier: LGPL-2.0-or-later |
6 | */ |
7 | |
8 | #include "mouseeventlistener.h" |
9 | |
10 | #include <QDebug> |
11 | #include <QEvent> |
12 | #include <QGuiApplication> |
13 | #include <QMouseEvent> |
14 | #include <QQuickWindow> |
15 | #include <QScreen> |
16 | #include <QStyleHints> |
17 | #include <QTimer> |
18 | |
19 | MouseEventListener::MouseEventListener(QQuickItem *parent) |
20 | : QQuickItem(parent) |
21 | , m_pressed(false) |
22 | , m_pressAndHoldEvent(nullptr) |
23 | , m_lastEvent(nullptr) |
24 | , m_acceptedButtons(Qt::LeftButton) |
25 | { |
26 | m_pressAndHoldTimer = new QTimer(this); |
27 | m_pressAndHoldTimer->setSingleShot(true); |
28 | connect(sender: m_pressAndHoldTimer, signal: &QTimer::timeout, context: this, slot: &MouseEventListener::handlePressAndHold); |
29 | setFiltersChildMouseEvents(true); |
30 | setAcceptedMouseButtons(Qt::LeftButton | Qt::RightButton | Qt::MiddleButton | Qt::XButton1 | Qt::XButton2); |
31 | } |
32 | |
33 | MouseEventListener::~MouseEventListener() |
34 | { |
35 | } |
36 | |
37 | Qt::MouseButtons MouseEventListener::acceptedButtons() const |
38 | { |
39 | return m_acceptedButtons; |
40 | } |
41 | |
42 | Qt::CursorShape MouseEventListener::cursorShape() const |
43 | { |
44 | return cursor().shape(); |
45 | } |
46 | |
47 | void MouseEventListener::setCursorShape(Qt::CursorShape shape) |
48 | { |
49 | if (cursor().shape() == shape) { |
50 | return; |
51 | } |
52 | |
53 | setCursor(shape); |
54 | |
55 | Q_EMIT cursorShapeChanged(); |
56 | } |
57 | |
58 | void MouseEventListener::setAcceptedButtons(Qt::MouseButtons buttons) |
59 | { |
60 | if (buttons == m_acceptedButtons) { |
61 | return; |
62 | } |
63 | |
64 | m_acceptedButtons = buttons; |
65 | Q_EMIT acceptedButtonsChanged(); |
66 | } |
67 | |
68 | void MouseEventListener::setHoverEnabled(bool enable) |
69 | { |
70 | if (enable == acceptHoverEvents()) { |
71 | return; |
72 | } |
73 | |
74 | setAcceptHoverEvents(enable); |
75 | Q_EMIT hoverEnabledChanged(hoverEnabled: enable); |
76 | } |
77 | |
78 | bool MouseEventListener::hoverEnabled() const |
79 | { |
80 | return acceptHoverEvents(); |
81 | } |
82 | |
83 | bool MouseEventListener::isPressed() const |
84 | { |
85 | return m_pressed; |
86 | } |
87 | |
88 | void MouseEventListener::hoverEnterEvent(QHoverEvent *event) |
89 | { |
90 | Q_UNUSED(event); |
91 | |
92 | m_containsMouse = true; |
93 | if (!m_childContainsMouse) { |
94 | Q_EMIT containsMouseChanged(containsMouseChanged: true); |
95 | } |
96 | } |
97 | |
98 | void MouseEventListener::hoverLeaveEvent(QHoverEvent *event) |
99 | { |
100 | Q_UNUSED(event); |
101 | |
102 | m_containsMouse = false; |
103 | if (!m_childContainsMouse) { |
104 | Q_EMIT containsMouseChanged(containsMouseChanged: false); |
105 | } |
106 | } |
107 | |
108 | void MouseEventListener::hoverMoveEvent(QHoverEvent *event) |
109 | { |
110 | if (m_lastEvent == event) { |
111 | return; |
112 | } |
113 | |
114 | QQuickWindow *w = window(); |
115 | QPoint screenPos; |
116 | if (w) { |
117 | screenPos = w->mapToGlobal(pos: event->position()).toPoint(); |
118 | } |
119 | |
120 | KDeclarativeMouseEvent dme(event->position().x(), |
121 | event->position().y(), |
122 | screenPos.x(), |
123 | screenPos.y(), |
124 | Qt::NoButton, |
125 | Qt::NoButton, |
126 | event->modifiers(), |
127 | nullptr, |
128 | Qt::MouseEventNotSynthesized); |
129 | Q_EMIT positionChanged(mouse: &dme); |
130 | } |
131 | |
132 | bool MouseEventListener::containsMouse() const |
133 | { |
134 | return m_containsMouse || m_childContainsMouse; |
135 | } |
136 | |
137 | void MouseEventListener::mousePressEvent(QMouseEvent *me) |
138 | { |
139 | if (m_lastEvent == me || !(me->buttons() & m_acceptedButtons)) { |
140 | me->setAccepted(false); |
141 | return; |
142 | } |
143 | |
144 | // FIXME: when a popup window is visible: a click anywhere hides it: but the old qquickitem will continue to think it's under the mouse |
145 | // doesn't seem to be any good way to properly reset this. |
146 | // this msolution will still caused a missed click after the popup is gone, but gets the situation unblocked. |
147 | QPoint viewPosition; |
148 | if (window()) { |
149 | viewPosition = window()->position(); |
150 | } |
151 | |
152 | if (!QRectF(mapToScene(point: QPoint(0, 0)) + viewPosition, QSizeF(width(), height())).contains(p: me->globalPosition())) { |
153 | me->ignore(); |
154 | return; |
155 | } |
156 | m_buttonDownPos = me->globalPosition(); |
157 | |
158 | KDeclarativeMouseEvent dme(me->pos().x(), |
159 | me->pos().y(), |
160 | me->globalPosition().x(), |
161 | me->globalPosition().y(), |
162 | me->button(), |
163 | me->buttons(), |
164 | me->modifiers(), |
165 | screenForGlobalPos(globalPos: me->globalPosition()), |
166 | me->source()); |
167 | if (!m_pressAndHoldEvent) { |
168 | m_pressAndHoldEvent = new KDeclarativeMouseEvent(me->pos().x(), |
169 | me->pos().y(), |
170 | me->globalPosition().x(), |
171 | me->globalPosition().y(), |
172 | me->button(), |
173 | me->buttons(), |
174 | me->modifiers(), |
175 | screenForGlobalPos(globalPos: me->globalPosition()), |
176 | me->source()); |
177 | } |
178 | |
179 | m_pressed = true; |
180 | Q_EMIT pressed(mouse: &dme); |
181 | Q_EMIT pressedChanged(); |
182 | |
183 | if (dme.isAccepted()) { |
184 | me->setAccepted(true); |
185 | return; |
186 | } |
187 | |
188 | m_pressAndHoldTimer->start(msec: QGuiApplication::styleHints()->mousePressAndHoldInterval()); |
189 | } |
190 | |
191 | void MouseEventListener::mouseMoveEvent(QMouseEvent *me) |
192 | { |
193 | if (m_lastEvent == me || !(me->buttons() & m_acceptedButtons)) { |
194 | me->setAccepted(false); |
195 | return; |
196 | } |
197 | |
198 | if (QPointF(me->globalPosition() - m_buttonDownPos).manhattanLength() > QGuiApplication::styleHints()->startDragDistance() |
199 | && m_pressAndHoldTimer->isActive()) { |
200 | m_pressAndHoldTimer->stop(); |
201 | } |
202 | |
203 | KDeclarativeMouseEvent dme(me->pos().x(), |
204 | me->pos().y(), |
205 | me->globalPosition().x(), |
206 | me->globalPosition().y(), |
207 | me->button(), |
208 | me->buttons(), |
209 | me->modifiers(), |
210 | screenForGlobalPos(globalPos: me->globalPosition()), |
211 | me->source()); |
212 | Q_EMIT positionChanged(mouse: &dme); |
213 | |
214 | if (dme.isAccepted()) { |
215 | me->setAccepted(true); |
216 | } |
217 | } |
218 | |
219 | void MouseEventListener::mouseReleaseEvent(QMouseEvent *me) |
220 | { |
221 | if (m_lastEvent == me) { |
222 | me->setAccepted(false); |
223 | return; |
224 | } |
225 | |
226 | KDeclarativeMouseEvent dme(me->pos().x(), |
227 | me->pos().y(), |
228 | me->globalPosition().x(), |
229 | me->globalPosition().y(), |
230 | me->button(), |
231 | me->buttons(), |
232 | me->modifiers(), |
233 | screenForGlobalPos(globalPos: me->globalPosition()), |
234 | me->source()); |
235 | m_pressed = false; |
236 | Q_EMIT released(mouse: &dme); |
237 | Q_EMIT pressedChanged(); |
238 | |
239 | if (boundingRect().contains(p: me->pos()) && m_pressAndHoldTimer->isActive()) { |
240 | Q_EMIT clicked(mouse: &dme); |
241 | m_pressAndHoldTimer->stop(); |
242 | } |
243 | |
244 | if (dme.isAccepted()) { |
245 | me->setAccepted(true); |
246 | } |
247 | } |
248 | |
249 | void MouseEventListener::wheelEvent(QWheelEvent *we) |
250 | { |
251 | if (m_lastEvent == we) { |
252 | return; |
253 | } |
254 | |
255 | KDeclarativeWheelEvent dwe(we->position().toPoint(), |
256 | we->globalPosition().toPoint(), |
257 | we->angleDelta(), |
258 | we->buttons(), |
259 | we->modifiers(), |
260 | Qt::Vertical /* HACK, deprecated, remove */); |
261 | Q_EMIT wheelMoved(wheel: &dwe); |
262 | } |
263 | |
264 | void MouseEventListener::handlePressAndHold() |
265 | { |
266 | if (m_pressed) { |
267 | Q_EMIT pressAndHold(mouse: m_pressAndHoldEvent); |
268 | |
269 | delete m_pressAndHoldEvent; |
270 | m_pressAndHoldEvent = nullptr; |
271 | } |
272 | } |
273 | |
274 | bool MouseEventListener::childMouseEventFilter(QQuickItem *item, QEvent *event) |
275 | { |
276 | if (!isEnabled()) { |
277 | return false; |
278 | } |
279 | |
280 | // don't filter other mouseeventlisteners |
281 | if (qobject_cast<MouseEventListener *>(object: item)) { |
282 | return false; |
283 | } |
284 | |
285 | switch (event->type()) { |
286 | case QEvent::MouseButtonPress: { |
287 | m_lastEvent = event; |
288 | QMouseEvent *me = static_cast<QMouseEvent *>(event); |
289 | |
290 | if (!(me->buttons() & m_acceptedButtons)) { |
291 | break; |
292 | } |
293 | |
294 | // the parent will receive events in its own coordinates |
295 | const QPointF myPos = mapFromScene(point: me->scenePosition()); |
296 | |
297 | KDeclarativeMouseEvent dme(myPos.x(), |
298 | myPos.y(), |
299 | me->globalPosition().x(), |
300 | me->globalPosition().y(), |
301 | me->button(), |
302 | me->buttons(), |
303 | me->modifiers(), |
304 | screenForGlobalPos(globalPos: me->globalPosition()), |
305 | me->source()); |
306 | delete m_pressAndHoldEvent; |
307 | m_pressAndHoldEvent = new KDeclarativeMouseEvent(myPos.x(), |
308 | myPos.y(), |
309 | me->globalPosition().x(), |
310 | me->globalPosition().y(), |
311 | me->button(), |
312 | me->buttons(), |
313 | me->modifiers(), |
314 | screenForGlobalPos(globalPos: me->globalPosition()), |
315 | me->source()); |
316 | |
317 | // qDebug() << "pressed in sceneEventFilter"; |
318 | m_buttonDownPos = me->globalPosition(); |
319 | m_pressed = true; |
320 | Q_EMIT pressed(mouse: &dme); |
321 | Q_EMIT pressedChanged(); |
322 | |
323 | if (dme.isAccepted()) { |
324 | return true; |
325 | } |
326 | |
327 | m_pressAndHoldTimer->start(msec: QGuiApplication::styleHints()->mousePressAndHoldInterval()); |
328 | |
329 | break; |
330 | } |
331 | case QEvent::HoverMove: { |
332 | if (!acceptHoverEvents()) { |
333 | break; |
334 | } |
335 | m_lastEvent = event; |
336 | QHoverEvent *he = static_cast<QHoverEvent *>(event); |
337 | const QPointF myPos = item->mapToItem(item: this, point: he->position()); |
338 | |
339 | QQuickWindow *w = window(); |
340 | QPoint screenPos; |
341 | if (w) { |
342 | screenPos = w->mapToGlobal(pos: myPos.toPoint()); |
343 | } |
344 | |
345 | KDeclarativeMouseEvent |
346 | dme(myPos.x(), myPos.y(), screenPos.x(), screenPos.y(), Qt::NoButton, Qt::NoButton, he->modifiers(), nullptr, Qt::MouseEventNotSynthesized); |
347 | // qDebug() << "positionChanged..." << dme.x() << dme.y(); |
348 | Q_EMIT positionChanged(mouse: &dme); |
349 | |
350 | if (dme.isAccepted()) { |
351 | return true; |
352 | } |
353 | break; |
354 | } |
355 | case QEvent::MouseMove: { |
356 | m_lastEvent = event; |
357 | QMouseEvent *me = static_cast<QMouseEvent *>(event); |
358 | if (!(me->buttons() & m_acceptedButtons)) { |
359 | break; |
360 | } |
361 | |
362 | const QPointF myPos = mapFromScene(point: me->scenePosition()); |
363 | KDeclarativeMouseEvent dme(myPos.x(), |
364 | myPos.y(), |
365 | me->globalPosition().x(), |
366 | me->globalPosition().y(), |
367 | me->button(), |
368 | me->buttons(), |
369 | me->modifiers(), |
370 | screenForGlobalPos(globalPos: me->globalPosition()), |
371 | me->source()); |
372 | // qDebug() << "positionChanged..." << dme.x() << dme.y(); |
373 | |
374 | // stop the pressandhold if mouse moved enough |
375 | if (QPointF(me->globalPosition() - m_buttonDownPos).manhattanLength() > QGuiApplication::styleHints()->startDragDistance() |
376 | && m_pressAndHoldTimer->isActive()) { |
377 | m_pressAndHoldTimer->stop(); |
378 | |
379 | // if the mouse moves and we are waiting to emit a press and hold event, update the coordinates |
380 | // as there is no update function, delete the old event and create a new one |
381 | } else if (m_pressAndHoldEvent) { |
382 | delete m_pressAndHoldEvent; |
383 | m_pressAndHoldEvent = new KDeclarativeMouseEvent(myPos.x(), |
384 | myPos.y(), |
385 | me->globalPosition().x(), |
386 | me->globalPosition().y(), |
387 | me->button(), |
388 | me->buttons(), |
389 | me->modifiers(), |
390 | screenForGlobalPos(globalPos: me->globalPosition()), |
391 | me->source()); |
392 | } |
393 | Q_EMIT positionChanged(mouse: &dme); |
394 | |
395 | if (dme.isAccepted()) { |
396 | return true; |
397 | } |
398 | break; |
399 | } |
400 | case QEvent::MouseButtonRelease: { |
401 | m_lastEvent = event; |
402 | QMouseEvent *me = static_cast<QMouseEvent *>(event); |
403 | |
404 | const QPointF myPos = mapFromScene(point: me->scenePosition()); |
405 | KDeclarativeMouseEvent dme(myPos.x(), |
406 | myPos.y(), |
407 | me->globalPosition().x(), |
408 | me->globalPosition().y(), |
409 | me->button(), |
410 | me->buttons(), |
411 | me->modifiers(), |
412 | screenForGlobalPos(globalPos: me->globalPosition()), |
413 | me->source()); |
414 | m_pressed = false; |
415 | |
416 | Q_EMIT released(mouse: &dme); |
417 | Q_EMIT pressedChanged(); |
418 | |
419 | if (QPointF(me->globalPosition() - m_buttonDownPos).manhattanLength() <= QGuiApplication::styleHints()->startDragDistance() |
420 | && m_pressAndHoldTimer->isActive()) { |
421 | Q_EMIT clicked(mouse: &dme); |
422 | m_pressAndHoldTimer->stop(); |
423 | } |
424 | |
425 | if (dme.isAccepted()) { |
426 | return true; |
427 | } |
428 | break; |
429 | } |
430 | case QEvent::UngrabMouse: { |
431 | m_lastEvent = event; |
432 | handleUngrab(); |
433 | break; |
434 | } |
435 | case QEvent::Wheel: { |
436 | m_lastEvent = event; |
437 | QWheelEvent *we = static_cast<QWheelEvent *>(event); |
438 | KDeclarativeWheelEvent dwe(we->position().toPoint(), |
439 | we->globalPosition().toPoint(), |
440 | we->angleDelta(), |
441 | we->buttons(), |
442 | we->modifiers(), |
443 | Qt::Vertical /* HACK, deprecated, remove */); |
444 | Q_EMIT wheelMoved(wheel: &dwe); |
445 | break; |
446 | } |
447 | case QEvent::HoverEnter: { |
448 | m_childContainsMouse = true; |
449 | if (!m_containsMouse) { |
450 | Q_EMIT containsMouseChanged(containsMouseChanged: true); |
451 | } |
452 | break; |
453 | } |
454 | case QEvent::HoverLeave: { |
455 | m_childContainsMouse = false; |
456 | if (!m_containsMouse) { |
457 | Q_EMIT containsMouseChanged(containsMouseChanged: false); |
458 | } |
459 | break; |
460 | } |
461 | default: |
462 | break; |
463 | } |
464 | |
465 | return QQuickItem::childMouseEventFilter(item, event); |
466 | // return false; |
467 | } |
468 | |
469 | QScreen *MouseEventListener::screenForGlobalPos(const QPointF &globalPos) |
470 | { |
471 | const auto screens = QGuiApplication::screens(); |
472 | for (QScreen *screen : screens) { |
473 | if (screen->geometry().contains(p: globalPos.toPoint())) { |
474 | return screen; |
475 | } |
476 | } |
477 | return nullptr; |
478 | } |
479 | |
480 | void MouseEventListener::mouseUngrabEvent() |
481 | { |
482 | handleUngrab(); |
483 | |
484 | QQuickItem::mouseUngrabEvent(); |
485 | } |
486 | |
487 | void MouseEventListener::touchUngrabEvent() |
488 | { |
489 | handleUngrab(); |
490 | |
491 | QQuickItem::touchUngrabEvent(); |
492 | } |
493 | |
494 | void MouseEventListener::handleUngrab() |
495 | { |
496 | if (m_pressed) { |
497 | m_pressAndHoldTimer->stop(); |
498 | |
499 | m_pressed = false; |
500 | Q_EMIT pressedChanged(); |
501 | |
502 | Q_EMIT canceled(); |
503 | } |
504 | } |
505 | |
506 | #include "moc_mouseeventlistener.cpp" |
507 | |