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
19MouseEventListener::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
33MouseEventListener::~MouseEventListener()
34{
35}
36
37Qt::MouseButtons MouseEventListener::acceptedButtons() const
38{
39 return m_acceptedButtons;
40}
41
42Qt::CursorShape MouseEventListener::cursorShape() const
43{
44 return cursor().shape();
45}
46
47void 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
58void 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
68void MouseEventListener::setHoverEnabled(bool enable)
69{
70 if (enable == acceptHoverEvents()) {
71 return;
72 }
73
74 setAcceptHoverEvents(enable);
75 Q_EMIT hoverEnabledChanged(hoverEnabled: enable);
76}
77
78bool MouseEventListener::hoverEnabled() const
79{
80 return acceptHoverEvents();
81}
82
83bool MouseEventListener::isPressed() const
84{
85 return m_pressed;
86}
87
88void 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
98void 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
108void 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
132bool MouseEventListener::containsMouse() const
133{
134 return m_containsMouse || m_childContainsMouse;
135}
136
137void 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
191void 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
219void 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
249void 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
264void 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
274bool 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
469QScreen *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
480void MouseEventListener::mouseUngrabEvent()
481{
482 handleUngrab();
483
484 QQuickItem::mouseUngrabEvent();
485}
486
487void MouseEventListener::touchUngrabEvent()
488{
489 handleUngrab();
490
491 QQuickItem::touchUngrabEvent();
492}
493
494void 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

source code of kdeclarative/src/qmlcontrols/kquickcontrolsaddons/mouseeventlistener.cpp