Warning, file /frameworks/kirigami/src/wheelhandler.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002  *  SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
0003  *
0004  *  SPDX-License-Identifier: LGPL-2.0-or-later
0005  */
0006 
0007 #include "wheelhandler.h"
0008 #include "settings.h"
0009 
0010 #include <QQmlEngine>
0011 #include <QQuickItem>
0012 #include <QQuickWindow>
0013 #include <QWheelEvent>
0014 
0015 #include "platform/units.h"
0016 
0017 KirigamiWheelEvent::KirigamiWheelEvent(QObject *parent)
0018     : QObject(parent)
0019 {
0020 }
0021 
0022 KirigamiWheelEvent::~KirigamiWheelEvent()
0023 {
0024 }
0025 
0026 void KirigamiWheelEvent::initializeFromEvent(QWheelEvent *event)
0027 {
0028     m_x = event->position().x();
0029     m_y = event->position().y();
0030     m_angleDelta = event->angleDelta();
0031     m_pixelDelta = event->pixelDelta();
0032     m_buttons = event->buttons();
0033     m_modifiers = event->modifiers();
0034     m_accepted = false;
0035     m_inverted = event->inverted();
0036 }
0037 
0038 qreal KirigamiWheelEvent::x() const
0039 {
0040     return m_x;
0041 }
0042 
0043 qreal KirigamiWheelEvent::y() const
0044 {
0045     return m_y;
0046 }
0047 
0048 QPointF KirigamiWheelEvent::angleDelta() const
0049 {
0050     return m_angleDelta;
0051 }
0052 
0053 QPointF KirigamiWheelEvent::pixelDelta() const
0054 {
0055     return m_pixelDelta;
0056 }
0057 
0058 int KirigamiWheelEvent::buttons() const
0059 {
0060     return m_buttons;
0061 }
0062 
0063 int KirigamiWheelEvent::modifiers() const
0064 {
0065     return m_modifiers;
0066 }
0067 
0068 bool KirigamiWheelEvent::inverted() const
0069 {
0070     return m_inverted;
0071 }
0072 
0073 bool KirigamiWheelEvent::isAccepted()
0074 {
0075     return m_accepted;
0076 }
0077 
0078 void KirigamiWheelEvent::setAccepted(bool accepted)
0079 {
0080     m_accepted = accepted;
0081 }
0082 
0083 ///////////////////////////////
0084 
0085 WheelFilterItem::WheelFilterItem(QQuickItem *parent)
0086     : QQuickItem(parent)
0087 {
0088     setEnabled(false);
0089 }
0090 
0091 ///////////////////////////////
0092 
0093 WheelHandler::WheelHandler(QObject *parent)
0094     : QObject(parent)
0095     , m_filterItem(new WheelFilterItem(nullptr))
0096 {
0097     m_filterItem->installEventFilter(this);
0098 
0099     m_wheelScrollingTimer.setSingleShot(true);
0100     m_wheelScrollingTimer.setInterval(m_wheelScrollingDuration);
0101     m_wheelScrollingTimer.callOnTimeout([this]() {
0102         setScrolling(false);
0103     });
0104 
0105     m_yScrollAnimation.setEasingCurve(QEasingCurve::OutCubic);
0106 
0107     connect(QGuiApplication::styleHints(), &QStyleHints::wheelScrollLinesChanged, this, [this](int scrollLines) {
0108         m_defaultPixelStepSize = 20 * scrollLines;
0109         if (!m_explicitVStepSize && m_verticalStepSize != m_defaultPixelStepSize) {
0110             m_verticalStepSize = m_defaultPixelStepSize;
0111             Q_EMIT verticalStepSizeChanged();
0112         }
0113         if (!m_explicitHStepSize && m_horizontalStepSize != m_defaultPixelStepSize) {
0114             m_horizontalStepSize = m_defaultPixelStepSize;
0115             Q_EMIT horizontalStepSizeChanged();
0116         }
0117     });
0118 }
0119 
0120 WheelHandler::~WheelHandler()
0121 {
0122     delete m_filterItem;
0123 }
0124 
0125 QQuickItem *WheelHandler::target() const
0126 {
0127     return m_flickable;
0128 }
0129 
0130 void WheelHandler::setTarget(QQuickItem *target)
0131 {
0132     if (m_flickable == target) {
0133         return;
0134     }
0135 
0136     if (target && !target->inherits("QQuickFlickable")) {
0137         qmlWarning(this) << "target must be a QQuickFlickable";
0138         return;
0139     }
0140 
0141     if (m_flickable) {
0142         m_flickable->removeEventFilter(this);
0143         disconnect(m_flickable, nullptr, m_filterItem, nullptr);
0144         disconnect(m_flickable, &QQuickItem::parentChanged, this, &WheelHandler::_k_rebindScrollBars);
0145     }
0146 
0147     m_flickable = target;
0148     m_filterItem->setParentItem(target);
0149     if (m_yScrollAnimation.targetObject()) {
0150         m_yScrollAnimation.stop();
0151     }
0152     m_yScrollAnimation.setTargetObject(target);
0153 
0154     if (target) {
0155         target->installEventFilter(this);
0156 
0157         // Stack WheelFilterItem over the Flickable's scrollable content
0158         m_filterItem->stackAfter(target->property("contentItem").value<QQuickItem *>());
0159         // Make it fill the Flickable
0160         m_filterItem->setWidth(target->width());
0161         m_filterItem->setHeight(target->height());
0162         connect(target, &QQuickItem::widthChanged, m_filterItem, [this, target]() {
0163             m_filterItem->setWidth(target->width());
0164         });
0165         connect(target, &QQuickItem::heightChanged, m_filterItem, [this, target]() {
0166             m_filterItem->setHeight(target->height());
0167         });
0168     }
0169 
0170     _k_rebindScrollBars();
0171 
0172     Q_EMIT targetChanged();
0173 }
0174 
0175 void WheelHandler::_k_rebindScrollBars()
0176 {
0177     struct ScrollBarAttached {
0178         QObject *attached = nullptr;
0179         QQuickItem *vertical = nullptr;
0180         QQuickItem *horizontal = nullptr;
0181     };
0182 
0183     ScrollBarAttached attachedToFlickable;
0184     ScrollBarAttached attachedToScrollView;
0185 
0186     if (m_flickable) {
0187         // Get ScrollBars so that we can filter them too, even if they're not
0188         // in the bounds of the Flickable
0189         const auto flickableChildren = m_flickable->children();
0190         for (const auto child : flickableChildren) {
0191             if (child->inherits("QQuickScrollBarAttached")) {
0192                 attachedToFlickable.attached = child;
0193                 attachedToFlickable.vertical = child->property("vertical").value<QQuickItem *>();
0194                 attachedToFlickable.horizontal = child->property("horizontal").value<QQuickItem *>();
0195                 break;
0196             }
0197         }
0198 
0199         // Check ScrollView if there are no scrollbars attached to the Flickable.
0200         // We need to check if the parent inherits QQuickScrollView in case the
0201         // parent is another Flickable that already has a Kirigami WheelHandler.
0202         auto flickableParent = m_flickable->parentItem();
0203         if (flickableParent && flickableParent->inherits("QQuickScrollView")) {
0204             const auto siblings = flickableParent->children();
0205             for (const auto child : siblings) {
0206                 if (child->inherits("QQuickScrollBarAttached")) {
0207                     attachedToScrollView.attached = child;
0208                     attachedToScrollView.vertical = child->property("vertical").value<QQuickItem *>();
0209                     attachedToScrollView.horizontal = child->property("horizontal").value<QQuickItem *>();
0210                     break;
0211                 }
0212             }
0213         }
0214     }
0215 
0216     // Dilemma: ScrollBars can be attached to both ScrollView and Flickable,
0217     // but only one of them should be shown anyway. Let's prefer Flickable.
0218 
0219     struct ChosenScrollBar {
0220         QObject *attached = nullptr;
0221         QQuickItem *scrollBar = nullptr;
0222     };
0223 
0224     ChosenScrollBar vertical;
0225     if (attachedToFlickable.vertical) {
0226         vertical.attached = attachedToFlickable.attached;
0227         vertical.scrollBar = attachedToFlickable.vertical;
0228     } else if (attachedToScrollView.vertical) {
0229         vertical.attached = attachedToScrollView.attached;
0230         vertical.scrollBar = attachedToScrollView.vertical;
0231     }
0232 
0233     ChosenScrollBar horizontal;
0234     if (attachedToFlickable.horizontal) {
0235         horizontal.attached = attachedToFlickable.attached;
0236         horizontal.scrollBar = attachedToFlickable.horizontal;
0237     } else if (attachedToScrollView.horizontal) {
0238         horizontal.attached = attachedToScrollView.attached;
0239         horizontal.scrollBar = attachedToScrollView.horizontal;
0240     }
0241 
0242     // Flickable may get re-parented to or out of a ScrollView, so we need to
0243     // redo the discovery process. This is especially important for
0244     // Kirigami.ScrollablePage component.
0245     if (m_flickable) {
0246         if (attachedToFlickable.horizontal && attachedToFlickable.vertical) {
0247             // But if both scrollbars are already those from the preferred
0248             // Flickable, there's no need for rediscovery.
0249             disconnect(m_flickable, &QQuickItem::parentChanged, this, &WheelHandler::_k_rebindScrollBars);
0250         } else {
0251             connect(m_flickable, &QQuickItem::parentChanged, this, &WheelHandler::_k_rebindScrollBars, Qt::UniqueConnection);
0252         }
0253     }
0254 
0255     if (m_verticalScrollBar != vertical.scrollBar) {
0256         if (m_verticalScrollBar) {
0257             m_verticalScrollBar->removeEventFilter(this);
0258             disconnect(m_verticalChangedConnection);
0259         }
0260         m_verticalScrollBar = vertical.scrollBar;
0261         if (vertical.scrollBar) {
0262             vertical.scrollBar->installEventFilter(this);
0263             m_verticalChangedConnection = connect(vertical.attached, SIGNAL(verticalChanged()), this, SLOT(_k_rebindScrollBars()));
0264         }
0265     }
0266 
0267     if (m_horizontalScrollBar != horizontal.scrollBar) {
0268         if (m_horizontalScrollBar) {
0269             m_horizontalScrollBar->removeEventFilter(this);
0270             disconnect(m_horizontalChangedConnection);
0271         }
0272         m_horizontalScrollBar = horizontal.scrollBar;
0273         if (horizontal.scrollBar) {
0274             horizontal.scrollBar->installEventFilter(this);
0275             m_horizontalChangedConnection = connect(horizontal.attached, SIGNAL(horizontalChanged()), this, SLOT(_k_rebindScrollBars()));
0276         }
0277     }
0278 }
0279 
0280 qreal WheelHandler::verticalStepSize() const
0281 {
0282     return m_verticalStepSize;
0283 }
0284 
0285 void WheelHandler::setVerticalStepSize(qreal stepSize)
0286 {
0287     m_explicitVStepSize = true;
0288     if (qFuzzyCompare(m_verticalStepSize, stepSize)) {
0289         return;
0290     }
0291     // Mimic the behavior of QQuickScrollBar when stepSize is 0
0292     if (qFuzzyIsNull(stepSize)) {
0293         resetVerticalStepSize();
0294         return;
0295     }
0296     m_verticalStepSize = stepSize;
0297     Q_EMIT verticalStepSizeChanged();
0298 }
0299 
0300 void WheelHandler::resetVerticalStepSize()
0301 {
0302     m_explicitVStepSize = false;
0303     if (qFuzzyCompare(m_verticalStepSize, m_defaultPixelStepSize)) {
0304         return;
0305     }
0306     m_verticalStepSize = m_defaultPixelStepSize;
0307     Q_EMIT verticalStepSizeChanged();
0308 }
0309 
0310 qreal WheelHandler::horizontalStepSize() const
0311 {
0312     return m_horizontalStepSize;
0313 }
0314 
0315 void WheelHandler::setHorizontalStepSize(qreal stepSize)
0316 {
0317     m_explicitHStepSize = true;
0318     if (qFuzzyCompare(m_horizontalStepSize, stepSize)) {
0319         return;
0320     }
0321     // Mimic the behavior of QQuickScrollBar when stepSize is 0
0322     if (qFuzzyIsNull(stepSize)) {
0323         resetHorizontalStepSize();
0324         return;
0325     }
0326     m_horizontalStepSize = stepSize;
0327     Q_EMIT horizontalStepSizeChanged();
0328 }
0329 
0330 void WheelHandler::resetHorizontalStepSize()
0331 {
0332     m_explicitHStepSize = false;
0333     if (qFuzzyCompare(m_horizontalStepSize, m_defaultPixelStepSize)) {
0334         return;
0335     }
0336     m_horizontalStepSize = m_defaultPixelStepSize;
0337     Q_EMIT horizontalStepSizeChanged();
0338 }
0339 
0340 Qt::KeyboardModifiers WheelHandler::pageScrollModifiers() const
0341 {
0342     return m_pageScrollModifiers;
0343 }
0344 
0345 void WheelHandler::setPageScrollModifiers(Qt::KeyboardModifiers modifiers)
0346 {
0347     if (m_pageScrollModifiers == modifiers) {
0348         return;
0349     }
0350     m_pageScrollModifiers = modifiers;
0351     Q_EMIT pageScrollModifiersChanged();
0352 }
0353 
0354 void WheelHandler::resetPageScrollModifiers()
0355 {
0356     setPageScrollModifiers(m_defaultPageScrollModifiers);
0357 }
0358 
0359 bool WheelHandler::filterMouseEvents() const
0360 {
0361     return m_filterMouseEvents;
0362 }
0363 
0364 void WheelHandler::setFilterMouseEvents(bool enabled)
0365 {
0366     if (m_filterMouseEvents == enabled) {
0367         return;
0368     }
0369     m_filterMouseEvents = enabled;
0370     Q_EMIT filterMouseEventsChanged();
0371 }
0372 
0373 bool WheelHandler::keyNavigationEnabled() const
0374 {
0375     return m_keyNavigationEnabled;
0376 }
0377 
0378 void WheelHandler::setKeyNavigationEnabled(bool enabled)
0379 {
0380     if (m_keyNavigationEnabled == enabled) {
0381         return;
0382     }
0383     m_keyNavigationEnabled = enabled;
0384     Q_EMIT keyNavigationEnabledChanged();
0385 }
0386 
0387 void WheelHandler::classBegin()
0388 {
0389     // Initializes smooth scrolling
0390     m_engine = qmlEngine(this);
0391     auto units = m_engine->singletonInstance<Kirigami::Platform::Units *>("org.kde.kirigami.platform", "Units");
0392     m_yScrollAnimation.setDuration(units->longDuration());
0393     connect(units, &Kirigami::Platform::Units::longDurationChanged, this, [this] {
0394         m_yScrollAnimation.setDuration(static_cast<Kirigami::Platform::Units *>(sender())->longDuration());
0395     });
0396 }
0397 
0398 void WheelHandler::componentComplete()
0399 {
0400 }
0401 
0402 void WheelHandler::setScrolling(bool scrolling)
0403 {
0404     if (m_wheelScrolling == scrolling) {
0405         if (m_wheelScrolling) {
0406             m_wheelScrollingTimer.start();
0407         }
0408         return;
0409     }
0410     m_wheelScrolling = scrolling;
0411     m_filterItem->setEnabled(m_wheelScrolling);
0412 }
0413 
0414 bool WheelHandler::scrollFlickable(QPointF pixelDelta, QPointF angleDelta, Qt::KeyboardModifiers modifiers)
0415 {
0416     if (!m_flickable || (pixelDelta.isNull() && angleDelta.isNull())) {
0417         return false;
0418     }
0419 
0420     const qreal width = m_flickable->width();
0421     const qreal height = m_flickable->height();
0422     const qreal contentWidth = m_flickable->property("contentWidth").toReal();
0423     const qreal contentHeight = m_flickable->property("contentHeight").toReal();
0424     const qreal contentX = m_flickable->property("contentX").toReal();
0425     const qreal contentY = m_flickable->property("contentY").toReal();
0426     const qreal topMargin = m_flickable->property("topMargin").toReal();
0427     const qreal bottomMargin = m_flickable->property("bottomMargin").toReal();
0428     const qreal leftMargin = m_flickable->property("leftMargin").toReal();
0429     const qreal rightMargin = m_flickable->property("rightMargin").toReal();
0430     const qreal originX = m_flickable->property("originX").toReal();
0431     const qreal originY = m_flickable->property("originY").toReal();
0432     const qreal pageWidth = width - leftMargin - rightMargin;
0433     const qreal pageHeight = height - topMargin - bottomMargin;
0434     const auto window = m_flickable->window();
0435     const qreal devicePixelRatio = window != nullptr ? window->devicePixelRatio() : qGuiApp->devicePixelRatio();
0436 
0437     // HACK: Only transpose deltas when not using xcb in order to not conflict with xcb's own delta transposing
0438     if (modifiers & m_defaultHorizontalScrollModifiers && qGuiApp->platformName() != QLatin1String("xcb")) {
0439         angleDelta = angleDelta.transposed();
0440         pixelDelta = pixelDelta.transposed();
0441     }
0442 
0443     const qreal xTicks = angleDelta.x() / 120;
0444     const qreal yTicks = angleDelta.y() / 120;
0445     qreal xChange;
0446     qreal yChange;
0447     bool scrolled = false;
0448 
0449     // Scroll X
0450     if (contentWidth > pageWidth) {
0451         // Use page size with pageScrollModifiers. Matches QScrollBar, which uses QAbstractSlider behavior.
0452         if (modifiers & m_pageScrollModifiers) {
0453             xChange = qBound(-pageWidth, xTicks * pageWidth, pageWidth);
0454         } else if (pixelDelta.x() != 0) {
0455             xChange = pixelDelta.x();
0456         } else {
0457             xChange = xTicks * m_horizontalStepSize;
0458         }
0459 
0460         // contentX and contentY use reversed signs from what x and y would normally use, so flip the signs
0461 
0462         qreal minXExtent = leftMargin - originX;
0463         qreal maxXExtent = width - (contentWidth + rightMargin + originX);
0464 
0465         qreal newContentX = qBound(-minXExtent, contentX - xChange, -maxXExtent);
0466         // Flickable::pixelAligned rounds the position, so round to mimic that behavior.
0467         // Rounding prevents fractional positioning from causing text to be
0468         // clipped off on the top and bottom.
0469         // Multiply by devicePixelRatio before rounding and divide by devicePixelRatio
0470         // after to make position match pixels on the screen more closely.
0471         newContentX = std::round(newContentX * devicePixelRatio) / devicePixelRatio;
0472         if (contentX != newContentX) {
0473             scrolled = true;
0474             m_flickable->setProperty("contentX", newContentX);
0475         }
0476     }
0477 
0478     // Scroll Y
0479     if (contentHeight > pageHeight) {
0480         if (modifiers & m_pageScrollModifiers) {
0481             yChange = qBound(-pageHeight, yTicks * pageHeight, pageHeight);
0482         } else if (pixelDelta.y() != 0) {
0483             yChange = pixelDelta.y();
0484         } else {
0485             yChange = yTicks * m_verticalStepSize;
0486         }
0487 
0488         // contentX and contentY use reversed signs from what x and y would normally use, so flip the signs
0489 
0490         qreal minYExtent = topMargin - originY;
0491         qreal maxYExtent = height - (contentHeight + bottomMargin + originY);
0492 
0493         qreal newContentY;
0494         if (m_yScrollAnimation.state() == QPropertyAnimation::Running) {
0495             m_yScrollAnimation.stop();
0496             newContentY = std::clamp(m_yScrollAnimation.endValue().toReal() + -yChange, -minYExtent, -maxYExtent);
0497         } else {
0498             newContentY = std::clamp(contentY - yChange, -minYExtent, -maxYExtent);
0499         }
0500 
0501         // Flickable::pixelAligned rounds the position, so round to mimic that behavior.
0502         // Rounding prevents fractional positioning from causing text to be
0503         // clipped off on the top and bottom.
0504         // Multiply by devicePixelRatio before rounding and divide by devicePixelRatio
0505         // after to make position match pixels on the screen more closely.
0506         newContentY = std::round(newContentY * devicePixelRatio) / devicePixelRatio;
0507         if (contentY != newContentY) {
0508             scrolled = true;
0509             if (m_wasTouched || !m_engine) {
0510                 m_flickable->setProperty("contentY", newContentY);
0511             } else {
0512                 m_yScrollAnimation.setEndValue(newContentY);
0513                 m_yScrollAnimation.start(QAbstractAnimation::KeepWhenStopped);
0514             }
0515         }
0516     }
0517 
0518     return scrolled;
0519 }
0520 
0521 bool WheelHandler::scrollUp(qreal stepSize)
0522 {
0523     if (qFuzzyIsNull(stepSize)) {
0524         return false;
0525     } else if (stepSize < 0) {
0526         stepSize = m_verticalStepSize;
0527     }
0528     // contentY uses reversed sign
0529     return scrollFlickable(QPointF(0, stepSize));
0530 }
0531 
0532 bool WheelHandler::scrollDown(qreal stepSize)
0533 {
0534     if (qFuzzyIsNull(stepSize)) {
0535         return false;
0536     } else if (stepSize < 0) {
0537         stepSize = m_verticalStepSize;
0538     }
0539     // contentY uses reversed sign
0540     return scrollFlickable(QPointF(0, -stepSize));
0541 }
0542 
0543 bool WheelHandler::scrollLeft(qreal stepSize)
0544 {
0545     if (qFuzzyIsNull(stepSize)) {
0546         return false;
0547     } else if (stepSize < 0) {
0548         stepSize = m_horizontalStepSize;
0549     }
0550     // contentX uses reversed sign
0551     return scrollFlickable(QPoint(stepSize, 0));
0552 }
0553 
0554 bool WheelHandler::scrollRight(qreal stepSize)
0555 {
0556     if (qFuzzyIsNull(stepSize)) {
0557         return false;
0558     } else if (stepSize < 0) {
0559         stepSize = m_horizontalStepSize;
0560     }
0561     // contentX uses reversed sign
0562     return scrollFlickable(QPoint(-stepSize, 0));
0563 }
0564 
0565 bool WheelHandler::eventFilter(QObject *watched, QEvent *event)
0566 {
0567     auto item = qobject_cast<QQuickItem *>(watched);
0568     if (!item || !item->isEnabled()) {
0569         return false;
0570     }
0571 
0572     qreal contentWidth = 0;
0573     qreal contentHeight = 0;
0574     qreal pageWidth = 0;
0575     qreal pageHeight = 0;
0576     if (m_flickable) {
0577         contentWidth = m_flickable->property("contentWidth").toReal();
0578         contentHeight = m_flickable->property("contentHeight").toReal();
0579         pageWidth = m_flickable->width() - m_flickable->property("leftMargin").toReal() - m_flickable->property("rightMargin").toReal();
0580         pageHeight = m_flickable->height() - m_flickable->property("topMargin").toReal() - m_flickable->property("bottomMargin").toReal();
0581     }
0582 
0583     // The code handling touch, mouse and hover events is mostly copied/adapted from QQuickScrollView::childMouseEventFilter()
0584     switch (event->type()) {
0585     case QEvent::Wheel: {
0586         // QQuickScrollBar::interactive handling Matches behavior in QQuickScrollView::eventFilter()
0587         if (m_filterMouseEvents) {
0588             if (m_verticalScrollBar) {
0589                 m_verticalScrollBar->setProperty("interactive", true);
0590             }
0591             if (m_horizontalScrollBar) {
0592                 m_horizontalScrollBar->setProperty("interactive", true);
0593             }
0594         }
0595         QWheelEvent *wheelEvent = static_cast<QWheelEvent *>(event);
0596 
0597         // Can't use wheelEvent->deviceType() to determine device type since on Wayland mouse is always regarded as touchpad
0598         // https://invent.kde.org/qt/qt/qtwayland/-/blob/e695a39519a7629c1549275a148cfb9ab99a07a9/src/client/qwaylandinputdevice.cpp#L445
0599         // and we can only expect a touchpad never generates the same angle delta as a mouse
0600 
0601         // mouse wheel can also generate angle delta like 240, 360 and so on when scrolling very fast
0602         // only checking wheelEvent->angleDelta().y() because we only animate for contentY
0603         m_wasTouched = (std::abs(wheelEvent->angleDelta().y()) != 0 && std::abs(wheelEvent->angleDelta().y()) % 120 != 0);
0604         // NOTE: On X11 with libinput, pixelDelta is identical to angleDelta when using a mouse that shouldn't use pixelDelta.
0605         // If faulty pixelDelta, reset pixelDelta to (0,0).
0606         if (wheelEvent->pixelDelta() == wheelEvent->angleDelta()) {
0607             // In order to change any of the data, we have to create a whole new QWheelEvent from its constructor.
0608             QWheelEvent newWheelEvent(wheelEvent->position(),
0609                                       wheelEvent->globalPosition(),
0610                                       QPoint(0, 0), // pixelDelta
0611                                       wheelEvent->angleDelta(),
0612                                       wheelEvent->buttons(),
0613                                       wheelEvent->modifiers(),
0614                                       wheelEvent->phase(),
0615                                       wheelEvent->inverted(),
0616                                       wheelEvent->source());
0617             m_kirigamiWheelEvent.initializeFromEvent(&newWheelEvent);
0618         } else {
0619             m_kirigamiWheelEvent.initializeFromEvent(wheelEvent);
0620         }
0621 
0622         Q_EMIT wheel(&m_kirigamiWheelEvent);
0623 
0624         if (m_kirigamiWheelEvent.isAccepted()) {
0625             return true;
0626         }
0627 
0628         bool scrolled = false;
0629         if (m_scrollFlickableTarget || (contentHeight <= pageHeight && contentWidth <= pageWidth)) {
0630             // Don't use pixelDelta from the event unless angleDelta is not available
0631             // because scrolling by pixelDelta is too slow on Wayland with libinput.
0632             QPointF pixelDelta = m_kirigamiWheelEvent.angleDelta().isNull() ? m_kirigamiWheelEvent.pixelDelta() : QPoint(0, 0);
0633             scrolled = scrollFlickable(pixelDelta, m_kirigamiWheelEvent.angleDelta(), Qt::KeyboardModifiers(m_kirigamiWheelEvent.modifiers()));
0634         }
0635         setScrolling(scrolled);
0636 
0637         // NOTE: Wheel events created by touchpad gestures with pixel deltas will cause scrolling to jump back
0638         // to where scrolling started unless the event is always accepted before it reaches the Flickable.
0639         bool flickableWillUseGestureScrolling = !(wheelEvent->source() == Qt::MouseEventNotSynthesized || wheelEvent->pixelDelta().isNull());
0640         return scrolled || m_blockTargetWheel || flickableWillUseGestureScrolling;
0641     }
0642 
0643     case QEvent::TouchBegin: {
0644         m_wasTouched = true;
0645         if (!m_filterMouseEvents) {
0646             break;
0647         }
0648         if (m_verticalScrollBar) {
0649             m_verticalScrollBar->setProperty("interactive", false);
0650         }
0651         if (m_horizontalScrollBar) {
0652             m_horizontalScrollBar->setProperty("interactive", false);
0653         }
0654         break;
0655     }
0656 
0657     case QEvent::TouchEnd: {
0658         m_wasTouched = false;
0659         break;
0660     }
0661 
0662     case QEvent::MouseButtonPress: {
0663         // NOTE: Flickable does not handle touch events, only synthesized mouse events
0664         m_wasTouched = static_cast<QMouseEvent *>(event)->source() != Qt::MouseEventNotSynthesized;
0665         if (!m_filterMouseEvents) {
0666             break;
0667         }
0668         if (!m_wasTouched) {
0669             if (m_verticalScrollBar) {
0670                 m_verticalScrollBar->setProperty("interactive", true);
0671             }
0672             if (m_horizontalScrollBar) {
0673                 m_horizontalScrollBar->setProperty("interactive", true);
0674             }
0675             break;
0676         }
0677         return !m_wasTouched && item == m_flickable;
0678     }
0679 
0680     case QEvent::MouseMove:
0681     case QEvent::MouseButtonRelease: {
0682         setScrolling(false);
0683         if (!m_filterMouseEvents) {
0684             break;
0685         }
0686         if (static_cast<QMouseEvent *>(event)->source() == Qt::MouseEventNotSynthesized && item == m_flickable) {
0687             return true;
0688         }
0689         break;
0690     }
0691 
0692     case QEvent::HoverEnter:
0693     case QEvent::HoverMove: {
0694         if (!m_filterMouseEvents) {
0695             break;
0696         }
0697         if (m_wasTouched && (item == m_verticalScrollBar || item == m_horizontalScrollBar)) {
0698             if (m_verticalScrollBar) {
0699                 m_verticalScrollBar->setProperty("interactive", true);
0700             }
0701             if (m_horizontalScrollBar) {
0702                 m_horizontalScrollBar->setProperty("interactive", true);
0703             }
0704         }
0705         break;
0706     }
0707 
0708     case QEvent::KeyPress: {
0709         if (!m_keyNavigationEnabled) {
0710             break;
0711         }
0712         QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
0713         bool horizontalScroll = keyEvent->modifiers() & m_defaultHorizontalScrollModifiers;
0714         switch (keyEvent->key()) {
0715         case Qt::Key_Up:
0716             return scrollUp();
0717         case Qt::Key_Down:
0718             return scrollDown();
0719         case Qt::Key_Left:
0720             return scrollLeft();
0721         case Qt::Key_Right:
0722             return scrollRight();
0723         case Qt::Key_PageUp:
0724             return horizontalScroll ? scrollLeft(pageWidth) : scrollUp(pageHeight);
0725         case Qt::Key_PageDown:
0726             return horizontalScroll ? scrollRight(pageWidth) : scrollDown(pageHeight);
0727         case Qt::Key_Home:
0728             return horizontalScroll ? scrollLeft(contentWidth) : scrollUp(contentHeight);
0729         case Qt::Key_End:
0730             return horizontalScroll ? scrollRight(contentWidth) : scrollDown(contentHeight);
0731         default:
0732             break;
0733         }
0734         break;
0735     }
0736 
0737     default:
0738         break;
0739     }
0740 
0741     return false;
0742 }
0743 
0744 #include "moc_wheelhandler.cpp"