File indexing completed on 2024-05-19 15:09:24

0001 /*
0002     SPDX-FileCopyrightText: 2011 Marco Martin <notmart@gmail.com>
0003     SPDX-FileCopyrightText: 2013 Sebastian Kügler <sebas@kde.org>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "mouseeventlistener.h"
0009 
0010 #include <QDebug>
0011 #include <QEvent>
0012 #include <QGuiApplication>
0013 #include <QMouseEvent>
0014 #include <QQuickWindow>
0015 #include <QScreen>
0016 #include <QStyleHints>
0017 #include <QTimer>
0018 
0019 MouseEventListener::MouseEventListener(QQuickItem *parent)
0020     : QQuickItem(parent)
0021     , m_pressed(false)
0022     , m_pressAndHoldEvent(nullptr)
0023     , m_lastEvent(nullptr)
0024     , m_containsMouse(false)
0025     , m_acceptedButtons(Qt::LeftButton)
0026 {
0027     m_pressAndHoldTimer = new QTimer(this);
0028     m_pressAndHoldTimer->setSingleShot(true);
0029     connect(m_pressAndHoldTimer, &QTimer::timeout, this, &MouseEventListener::handlePressAndHold);
0030     setFiltersChildMouseEvents(true);
0031     setAcceptedMouseButtons(Qt::LeftButton | Qt::RightButton | Qt::MiddleButton | Qt::XButton1 | Qt::XButton2);
0032 }
0033 
0034 MouseEventListener::~MouseEventListener()
0035 {
0036 }
0037 
0038 Qt::MouseButtons MouseEventListener::acceptedButtons() const
0039 {
0040     return m_acceptedButtons;
0041 }
0042 
0043 Qt::CursorShape MouseEventListener::cursorShape() const
0044 {
0045     return cursor().shape();
0046 }
0047 
0048 void MouseEventListener::setCursorShape(Qt::CursorShape shape)
0049 {
0050     if (cursor().shape() == shape) {
0051         return;
0052     }
0053 
0054     setCursor(shape);
0055 
0056     Q_EMIT cursorShapeChanged();
0057 }
0058 
0059 void MouseEventListener::setAcceptedButtons(Qt::MouseButtons buttons)
0060 {
0061     if (buttons == m_acceptedButtons) {
0062         return;
0063     }
0064 
0065     m_acceptedButtons = buttons;
0066     Q_EMIT acceptedButtonsChanged();
0067 }
0068 
0069 void MouseEventListener::setHoverEnabled(bool enable)
0070 {
0071     if (enable == acceptHoverEvents()) {
0072         return;
0073     }
0074 
0075     setAcceptHoverEvents(enable);
0076     Q_EMIT hoverEnabledChanged(enable);
0077 }
0078 
0079 bool MouseEventListener::hoverEnabled() const
0080 {
0081     return acceptHoverEvents();
0082 }
0083 
0084 bool MouseEventListener::isPressed() const
0085 {
0086     return m_pressed;
0087 }
0088 
0089 void MouseEventListener::hoverEnterEvent(QHoverEvent *event)
0090 {
0091     Q_UNUSED(event);
0092 
0093     m_containsMouse = true;
0094     Q_EMIT containsMouseChanged(true);
0095 }
0096 
0097 void MouseEventListener::hoverLeaveEvent(QHoverEvent *event)
0098 {
0099     Q_UNUSED(event);
0100 
0101     m_containsMouse = false;
0102     Q_EMIT containsMouseChanged(false);
0103 }
0104 
0105 void MouseEventListener::hoverMoveEvent(QHoverEvent *event)
0106 {
0107     if (m_lastEvent == event) {
0108         return;
0109     }
0110 
0111     QQuickWindow *w = window();
0112     QPoint screenPos;
0113     if (w) {
0114         screenPos = w->mapToGlobal(event->pos());
0115     }
0116 
0117     KDeclarativeMouseEvent dme(event->pos().x(),
0118                                event->pos().y(),
0119                                screenPos.x(),
0120                                screenPos.y(),
0121                                Qt::NoButton,
0122                                Qt::NoButton,
0123                                event->modifiers(),
0124                                nullptr,
0125                                Qt::MouseEventNotSynthesized);
0126     Q_EMIT positionChanged(&dme);
0127 }
0128 
0129 bool MouseEventListener::containsMouse() const
0130 {
0131     return m_containsMouse;
0132 }
0133 
0134 void MouseEventListener::mousePressEvent(QMouseEvent *me)
0135 {
0136     if (m_lastEvent == me || !(me->buttons() & m_acceptedButtons)) {
0137         me->setAccepted(false);
0138         return;
0139     }
0140 
0141     // 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
0142     // doesn't seem to be any good way to properly reset this.
0143     // this msolution will still caused a missed click after the popup is gone, but gets the situation unblocked.
0144     QPoint viewPosition;
0145     if (window()) {
0146         viewPosition = window()->position();
0147     }
0148 
0149     if (!QRectF(mapToScene(QPoint(0, 0)) + viewPosition, QSizeF(width(), height())).contains(me->screenPos())) {
0150         me->ignore();
0151         return;
0152     }
0153     m_buttonDownPos = me->screenPos();
0154 
0155     KDeclarativeMouseEvent dme(me->pos().x(),
0156                                me->pos().y(),
0157                                me->screenPos().x(),
0158                                me->screenPos().y(),
0159                                me->button(),
0160                                me->buttons(),
0161                                me->modifiers(),
0162                                screenForGlobalPos(me->globalPos()),
0163                                me->source());
0164     if (!m_pressAndHoldEvent) {
0165         m_pressAndHoldEvent = new KDeclarativeMouseEvent(me->pos().x(),
0166                                                          me->pos().y(),
0167                                                          me->screenPos().x(),
0168                                                          me->screenPos().y(),
0169                                                          me->button(),
0170                                                          me->buttons(),
0171                                                          me->modifiers(),
0172                                                          screenForGlobalPos(me->globalPos()),
0173                                                          me->source());
0174     }
0175 
0176     m_pressed = true;
0177     Q_EMIT pressed(&dme);
0178     Q_EMIT pressedChanged();
0179 
0180     if (dme.isAccepted()) {
0181         me->setAccepted(true);
0182         return;
0183     }
0184 
0185     m_pressAndHoldTimer->start(QGuiApplication::styleHints()->mousePressAndHoldInterval());
0186 }
0187 
0188 void MouseEventListener::mouseMoveEvent(QMouseEvent *me)
0189 {
0190     if (m_lastEvent == me || !(me->buttons() & m_acceptedButtons)) {
0191         me->setAccepted(false);
0192         return;
0193     }
0194 
0195     if (QPointF(me->screenPos() - m_buttonDownPos).manhattanLength() > QGuiApplication::styleHints()->startDragDistance() && m_pressAndHoldTimer->isActive()) {
0196         m_pressAndHoldTimer->stop();
0197     }
0198 
0199     KDeclarativeMouseEvent dme(me->pos().x(),
0200                                me->pos().y(),
0201                                me->screenPos().x(),
0202                                me->screenPos().y(),
0203                                me->button(),
0204                                me->buttons(),
0205                                me->modifiers(),
0206                                screenForGlobalPos(me->globalPos()),
0207                                me->source());
0208     Q_EMIT positionChanged(&dme);
0209 
0210     if (dme.isAccepted()) {
0211         me->setAccepted(true);
0212     }
0213 }
0214 
0215 void MouseEventListener::mouseReleaseEvent(QMouseEvent *me)
0216 {
0217     if (m_lastEvent == me) {
0218         me->setAccepted(false);
0219         return;
0220     }
0221 
0222     KDeclarativeMouseEvent dme(me->pos().x(),
0223                                me->pos().y(),
0224                                me->screenPos().x(),
0225                                me->screenPos().y(),
0226                                me->button(),
0227                                me->buttons(),
0228                                me->modifiers(),
0229                                screenForGlobalPos(me->globalPos()),
0230                                me->source());
0231     m_pressed = false;
0232     Q_EMIT released(&dme);
0233     Q_EMIT pressedChanged();
0234 
0235     if (boundingRect().contains(me->pos()) && m_pressAndHoldTimer->isActive()) {
0236         Q_EMIT clicked(&dme);
0237         m_pressAndHoldTimer->stop();
0238     }
0239 
0240     if (dme.isAccepted()) {
0241         me->setAccepted(true);
0242     }
0243 }
0244 
0245 void MouseEventListener::wheelEvent(QWheelEvent *we)
0246 {
0247     if (m_lastEvent == we) {
0248         return;
0249     }
0250 
0251     KDeclarativeWheelEvent dwe(we->position().toPoint(),
0252                                we->globalPosition().toPoint(),
0253                                we->angleDelta(),
0254                                we->buttons(),
0255                                we->modifiers(),
0256                                Qt::Vertical /* HACK, deprecated, remove */);
0257     Q_EMIT wheelMoved(&dwe);
0258 }
0259 
0260 void MouseEventListener::handlePressAndHold()
0261 {
0262     if (m_pressed) {
0263         Q_EMIT pressAndHold(m_pressAndHoldEvent);
0264 
0265         delete m_pressAndHoldEvent;
0266         m_pressAndHoldEvent = nullptr;
0267     }
0268 }
0269 
0270 bool MouseEventListener::childMouseEventFilter(QQuickItem *item, QEvent *event)
0271 {
0272     if (!isEnabled()) {
0273         return false;
0274     }
0275 
0276     // don't filter other mouseeventlisteners
0277     if (qobject_cast<MouseEventListener *>(item)) {
0278         return false;
0279     }
0280 
0281     switch (event->type()) {
0282     case QEvent::MouseButtonPress: {
0283         m_lastEvent = event;
0284         QMouseEvent *me = static_cast<QMouseEvent *>(event);
0285 
0286         if (!(me->buttons() & m_acceptedButtons)) {
0287             break;
0288         }
0289 
0290         // the parent will receive events in its own coordinates
0291         const QPointF myPos = mapFromScene(me->windowPos());
0292 
0293         KDeclarativeMouseEvent dme(myPos.x(),
0294                                    myPos.y(),
0295                                    me->screenPos().x(),
0296                                    me->screenPos().y(),
0297                                    me->button(),
0298                                    me->buttons(),
0299                                    me->modifiers(),
0300                                    screenForGlobalPos(me->globalPos()),
0301                                    me->source());
0302         delete m_pressAndHoldEvent;
0303         m_pressAndHoldEvent = new KDeclarativeMouseEvent(myPos.x(),
0304                                                          myPos.y(),
0305                                                          me->screenPos().x(),
0306                                                          me->screenPos().y(),
0307                                                          me->button(),
0308                                                          me->buttons(),
0309                                                          me->modifiers(),
0310                                                          screenForGlobalPos(me->globalPos()),
0311                                                          me->source());
0312 
0313         // qDebug() << "pressed in sceneEventFilter";
0314         m_buttonDownPos = me->screenPos();
0315         m_pressed = true;
0316         Q_EMIT pressed(&dme);
0317         Q_EMIT pressedChanged();
0318 
0319         if (dme.isAccepted()) {
0320             return true;
0321         }
0322 
0323         m_pressAndHoldTimer->start(QGuiApplication::styleHints()->mousePressAndHoldInterval());
0324 
0325         break;
0326     }
0327     case QEvent::HoverMove: {
0328         if (!acceptHoverEvents()) {
0329             break;
0330         }
0331         m_lastEvent = event;
0332         QHoverEvent *he = static_cast<QHoverEvent *>(event);
0333         const QPointF myPos = item->mapToItem(this, he->pos());
0334 
0335         QQuickWindow *w = window();
0336         QPoint screenPos;
0337         if (w) {
0338             screenPos = w->mapToGlobal(myPos.toPoint());
0339         }
0340 
0341         KDeclarativeMouseEvent
0342             dme(myPos.x(), myPos.y(), screenPos.x(), screenPos.y(), Qt::NoButton, Qt::NoButton, he->modifiers(), nullptr, Qt::MouseEventNotSynthesized);
0343         // qDebug() << "positionChanged..." << dme.x() << dme.y();
0344         Q_EMIT positionChanged(&dme);
0345 
0346         if (dme.isAccepted()) {
0347             return true;
0348         }
0349         break;
0350     }
0351     case QEvent::MouseMove: {
0352         m_lastEvent = event;
0353         QMouseEvent *me = static_cast<QMouseEvent *>(event);
0354         if (!(me->buttons() & m_acceptedButtons)) {
0355             break;
0356         }
0357 
0358         const QPointF myPos = mapFromScene(me->windowPos());
0359         KDeclarativeMouseEvent dme(myPos.x(),
0360                                    myPos.y(),
0361                                    me->screenPos().x(),
0362                                    me->screenPos().y(),
0363                                    me->button(),
0364                                    me->buttons(),
0365                                    me->modifiers(),
0366                                    screenForGlobalPos(me->globalPos()),
0367                                    me->source());
0368         // qDebug() << "positionChanged..." << dme.x() << dme.y();
0369 
0370         // stop the pressandhold if mouse moved enough
0371         if (QPointF(me->screenPos() - m_buttonDownPos).manhattanLength() > QGuiApplication::styleHints()->startDragDistance()
0372             && m_pressAndHoldTimer->isActive()) {
0373             m_pressAndHoldTimer->stop();
0374 
0375             // if the mouse moves and we are waiting to emit a press and hold event, update the coordinates
0376             // as there is no update function, delete the old event and create a new one
0377         } else if (m_pressAndHoldEvent) {
0378             delete m_pressAndHoldEvent;
0379             m_pressAndHoldEvent = new KDeclarativeMouseEvent(myPos.x(),
0380                                                              myPos.y(),
0381                                                              me->screenPos().x(),
0382                                                              me->screenPos().y(),
0383                                                              me->button(),
0384                                                              me->buttons(),
0385                                                              me->modifiers(),
0386                                                              screenForGlobalPos(me->globalPos()),
0387                                                              me->source());
0388         }
0389         Q_EMIT positionChanged(&dme);
0390 
0391         if (dme.isAccepted()) {
0392             return true;
0393         }
0394         break;
0395     }
0396     case QEvent::MouseButtonRelease: {
0397         m_lastEvent = event;
0398         QMouseEvent *me = static_cast<QMouseEvent *>(event);
0399 
0400         const QPointF myPos = mapFromScene(me->windowPos());
0401         KDeclarativeMouseEvent dme(myPos.x(),
0402                                    myPos.y(),
0403                                    me->screenPos().x(),
0404                                    me->screenPos().y(),
0405                                    me->button(),
0406                                    me->buttons(),
0407                                    me->modifiers(),
0408                                    screenForGlobalPos(me->globalPos()),
0409                                    me->source());
0410         m_pressed = false;
0411 
0412         Q_EMIT released(&dme);
0413         Q_EMIT pressedChanged();
0414 
0415         if (QPointF(me->screenPos() - m_buttonDownPos).manhattanLength() <= QGuiApplication::styleHints()->startDragDistance()
0416             && m_pressAndHoldTimer->isActive()) {
0417             Q_EMIT clicked(&dme);
0418             m_pressAndHoldTimer->stop();
0419         }
0420 
0421         if (dme.isAccepted()) {
0422             return true;
0423         }
0424         break;
0425     }
0426     case QEvent::UngrabMouse: {
0427         m_lastEvent = event;
0428         handleUngrab();
0429         break;
0430     }
0431     case QEvent::Wheel: {
0432         m_lastEvent = event;
0433         QWheelEvent *we = static_cast<QWheelEvent *>(event);
0434         KDeclarativeWheelEvent dwe(we->position().toPoint(),
0435                                    we->globalPosition().toPoint(),
0436                                    we->angleDelta(),
0437                                    we->buttons(),
0438                                    we->modifiers(),
0439                                    Qt::Vertical /* HACK, deprecated, remove */);
0440         Q_EMIT wheelMoved(&dwe);
0441         break;
0442     }
0443     default:
0444         break;
0445     }
0446 
0447     return QQuickItem::childMouseEventFilter(item, event);
0448     //    return false;
0449 }
0450 
0451 QScreen *MouseEventListener::screenForGlobalPos(const QPoint &globalPos)
0452 {
0453     const auto screens = QGuiApplication::screens();
0454     for (QScreen *screen : screens) {
0455         if (screen->geometry().contains(globalPos)) {
0456             return screen;
0457         }
0458     }
0459     return nullptr;
0460 }
0461 
0462 void MouseEventListener::mouseUngrabEvent()
0463 {
0464     handleUngrab();
0465 
0466     QQuickItem::mouseUngrabEvent();
0467 }
0468 
0469 void MouseEventListener::touchUngrabEvent()
0470 {
0471     handleUngrab();
0472 
0473     QQuickItem::touchUngrabEvent();
0474 }
0475 
0476 void MouseEventListener::handleUngrab()
0477 {
0478     if (m_pressed) {
0479         m_pressAndHoldTimer->stop();
0480 
0481         m_pressed = false;
0482         Q_EMIT pressedChanged();
0483 
0484         Q_EMIT canceled();
0485     }
0486 }
0487 
0488 #include "moc_mouseeventlistener.cpp"