File indexing completed on 2024-03-24 03:58:53
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"