// Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause #include "hoverwatcher.h" #include #include #include HoverWatcher::HoverWatcher(QWidget *watched) : QObject(watched), m_watched(watched) { Q_ASSERT(watched); m_cursorShapes[Entered].emplace(Qt::OpenHandCursor); m_cursorShapes[MousePress].emplace(Qt::ClosedHandCursor); m_cursorShapes[MouseRelease].emplace(Qt::OpenHandCursor); // no default for Left => restore override cursor m_watched->installEventFilter(this); } HoverWatcher::~HoverWatcher() { m_watched->removeEventFilter(this); } typedef QHash WatchMap; Q_GLOBAL_STATIC(WatchMap, qt_allHoverWatchers) HoverWatcher *HoverWatcher::watcher(QWidget *watched) { if (qt_allHoverWatchers()->contains(watched)) return qt_allHoverWatchers()->value(watched); HoverWatcher *watcher = new HoverWatcher(watched); qt_allHoverWatchers()->insert(watched, watcher); return watcher; } /*! \overload Const version of watcher */ const HoverWatcher *HoverWatcher::watcher(const QWidget *watched) { return watcher(const_cast(watched)); } void HoverWatcher::dismiss(QWidget *watched) { if (!hasWatcher(watched)) return; delete qt_allHoverWatchers()->take(watched); } bool HoverWatcher::hasWatcher(QWidget *widget) { return qt_allHoverWatchers()->contains(widget); } static constexpr HoverWatcher::HoverAction toHoverAction(QEvent::Type et) { switch (et) { case QEvent::Type::Enter: return HoverWatcher::HoverAction::Entered; case QEvent::Type::Leave: return HoverWatcher::HoverAction::Left; case QEvent::Type::MouseButtonPress: return HoverWatcher::HoverAction::MousePress; case QEvent::Type::MouseButtonRelease: return HoverWatcher::HoverAction::MouseRelease; default: return HoverWatcher::HoverAction::Ignore; } } void HoverWatcher::handleAction (HoverWatcher::HoverAction action) { const Qt::CursorShape newShape = cursorShape(action); if (QGuiApplication::overrideCursor() && (QGuiApplication::overrideCursor()->shape() == newShape || action == HoverAction::Ignore)) { return; } QGuiApplication::setOverrideCursor(cursorShape(action)); emit hoverAction(action); switch (action) { case HoverAction::Entered: emit entered(); break; case HoverAction::Left: emit left(); break; case HoverAction::MousePress: emit mousePressed(); break; case HoverAction::MouseRelease: { emit mouseReleased(); } break; case HoverAction::Ignore: break; } } bool HoverWatcher::hasShape(HoverAction action) const { return action != HoverAction::Ignore && m_cursorShapes[action].has_value(); } void HoverWatcher::setApplicationCursor(HoverAction action) const { if (!hasShape(action)) { QGuiApplication::restoreOverrideCursor(); return; } QGuiApplication::setOverrideCursor(cursorShape(action)); } bool HoverWatcher::eventFilter(QObject *obj, QEvent *event) { Q_ASSERT(obj == m_watched); // don't install event filters elsewhere // Ignore irrelevant events const auto action = toHoverAction(event->type()); if (action == HoverAction::Ignore) return false; // React to a QScroller having been installed or removed // A Scroller sends a fake mouse release to QPoint (-1, -1) // => needs to be ignored and end of scrolling processed instead static bool hasScroller = false; if (QScroller::hasScroller(m_watched) != hasScroller) { hasScroller = QScroller::hasScroller(m_watched); static QMetaObject::Connection con; if (hasScroller) { con = connect(QScroller::scroller(m_watched), &QScroller::stateChanged, this, &HoverWatcher::handleScrollerStateChange); } else { disconnect(con); } } // Ignore fake mouse release event sent by scroller if (action == HoverAction::MouseRelease && hasScroller) { QMouseEvent *me = static_cast(event); if (me->pos().x() < -9000000 ) return false; } // Ignore unpermitted mouse buttons if (action == HoverAction::MousePress) { QMouseEvent *me = static_cast(event); if (!m_mouseButtons.testFlag(me->button())) return false; } handleAction(action); return false; } Qt::CursorShape HoverWatcher::cursorShape(HoverAction type) const { const Qt::CursorShape fallback = Qt::ArrowCursor; if (type == HoverAction::Ignore) return fallback; return m_cursorShapes[type].value_or(fallback); } void HoverWatcher::setCursorShape(HoverAction type, Qt::CursorShape shape) { if (type == HoverAction::Ignore) return; m_cursorShapes[type].emplace(shape); } void HoverWatcher::unSetCursorShape(HoverAction type) { if (type == HoverAction::Ignore) return; m_cursorShapes[type].reset(); } void HoverWatcher::setMouseButtons(Qt::MouseButtons buttons) { m_mouseButtons = buttons; } void HoverWatcher::setMouseButton(Qt::MouseButton button, bool enable) { m_mouseButtons.setFlag(button, enable);; } /*! \brief This slot handles a QScroller state change, in case the watched widget uses a scroller. It translates \param state into the appropriate action. */ void HoverWatcher::handleScrollerStateChange(QScroller::State state) { switch (state) { case QScroller::State::Pressed: case QScroller::State::Dragging: case QScroller::State::Scrolling: handleAction(HoverAction::MousePress); break; case QScroller::State::Inactive: handleAction(HoverAction::MouseRelease); break; } }