File indexing completed on 2024-04-21 11:33:54

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