Kirigami2

wheelhandler.cpp
1/*
2 * SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
3 *
4 * SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6
7#include "wheelhandler.h"
8#include "settings.h"
9
10#include <QQmlEngine>
11#include <QQuickItem>
12#include <QQuickWindow>
13#include <QWheelEvent>
14
15#include "platform/units.h"
16
17KirigamiWheelEvent::KirigamiWheelEvent(QObject *parent)
18 : QObject(parent)
19{
20}
21
22KirigamiWheelEvent::~KirigamiWheelEvent()
23{
24}
25
26void KirigamiWheelEvent::initializeFromEvent(QWheelEvent *event)
27{
28 m_x = event->position().x();
29 m_y = event->position().y();
30 m_angleDelta = event->angleDelta();
31 m_pixelDelta = event->pixelDelta();
32 m_buttons = event->buttons();
33 m_modifiers = event->modifiers();
34 m_accepted = false;
35 m_inverted = event->inverted();
36}
37
38qreal KirigamiWheelEvent::x() const
39{
40 return m_x;
41}
42
43qreal KirigamiWheelEvent::y() const
44{
45 return m_y;
46}
47
49{
50 return m_angleDelta;
51}
52
54{
55 return m_pixelDelta;
56}
57
59{
60 return m_buttons;
61}
62
64{
65 return m_modifiers;
66}
67
69{
70 return m_inverted;
71}
72
73bool KirigamiWheelEvent::isAccepted()
74{
75 return m_accepted;
76}
77
78void KirigamiWheelEvent::setAccepted(bool accepted)
79{
80 m_accepted = accepted;
81}
82
83///////////////////////////////
84
85WheelFilterItem::WheelFilterItem(QQuickItem *parent)
86 : QQuickItem(parent)
87{
88 setEnabled(false);
89}
90
91///////////////////////////////
92
93WheelHandler::WheelHandler(QObject *parent)
94 : QObject(parent)
95 , m_filterItem(new WheelFilterItem(nullptr))
96{
97 m_filterItem->installEventFilter(this);
98
99 m_wheelScrollingTimer.setSingleShot(true);
100 m_wheelScrollingTimer.setInterval(m_wheelScrollingDuration);
101 m_wheelScrollingTimer.callOnTimeout([this]() {
102 setScrolling(false);
103 });
104
105 m_yScrollAnimation.setEasingCurve(QEasingCurve::OutCubic);
106
107 connect(QGuiApplication::styleHints(), &QStyleHints::wheelScrollLinesChanged, this, [this](int scrollLines) {
108 m_defaultPixelStepSize = 20 * scrollLines;
109 if (!m_explicitVStepSize && m_verticalStepSize != m_defaultPixelStepSize) {
110 m_verticalStepSize = m_defaultPixelStepSize;
111 Q_EMIT verticalStepSizeChanged();
112 }
113 if (!m_explicitHStepSize && m_horizontalStepSize != m_defaultPixelStepSize) {
114 m_horizontalStepSize = m_defaultPixelStepSize;
115 Q_EMIT horizontalStepSizeChanged();
116 }
117 });
118}
119
120WheelHandler::~WheelHandler()
121{
122 delete m_filterItem;
123}
124
126{
127 return m_flickable;
128}
129
130void WheelHandler::setTarget(QQuickItem *target)
131{
132 if (m_flickable == target) {
133 return;
134 }
135
136 if (target && !target->inherits("QQuickFlickable")) {
137 qmlWarning(this) << "target must be a QQuickFlickable";
138 return;
139 }
140
141 if (m_flickable) {
142 m_flickable->removeEventFilter(this);
143 disconnect(m_flickable, nullptr, m_filterItem, nullptr);
144 disconnect(m_flickable, &QQuickItem::parentChanged, this, &WheelHandler::_k_rebindScrollBars);
145 }
146
147 m_flickable = target;
148 m_filterItem->setParentItem(target);
149 if (m_yScrollAnimation.targetObject()) {
150 m_yScrollAnimation.stop();
151 }
152 m_yScrollAnimation.setTargetObject(target);
153
154 if (target) {
155 target->installEventFilter(this);
156
157 // Stack WheelFilterItem over the Flickable's scrollable content
158 m_filterItem->stackAfter(target->property("contentItem").value<QQuickItem *>());
159 // Make it fill the Flickable
160 m_filterItem->setWidth(target->width());
161 m_filterItem->setHeight(target->height());
162 connect(target, &QQuickItem::widthChanged, m_filterItem, [this, target]() {
163 m_filterItem->setWidth(target->width());
164 });
165 connect(target, &QQuickItem::heightChanged, m_filterItem, [this, target]() {
166 m_filterItem->setHeight(target->height());
167 });
168 }
169
170 _k_rebindScrollBars();
171
172 Q_EMIT targetChanged();
173}
174
175void WheelHandler::_k_rebindScrollBars()
176{
177 struct ScrollBarAttached {
178 QObject *attached = nullptr;
179 QQuickItem *vertical = nullptr;
180 QQuickItem *horizontal = nullptr;
181 };
182
183 ScrollBarAttached attachedToFlickable;
184 ScrollBarAttached attachedToScrollView;
185
186 if (m_flickable) {
187 // Get ScrollBars so that we can filter them too, even if they're not
188 // in the bounds of the Flickable
189 const auto flickableChildren = m_flickable->children();
190 for (const auto child : flickableChildren) {
191 if (child->inherits("QQuickScrollBarAttached")) {
192 attachedToFlickable.attached = child;
193 attachedToFlickable.vertical = child->property("vertical").value<QQuickItem *>();
194 attachedToFlickable.horizontal = child->property("horizontal").value<QQuickItem *>();
195 break;
196 }
197 }
198
199 // Check ScrollView if there are no scrollbars attached to the Flickable.
200 // We need to check if the parent inherits QQuickScrollView in case the
201 // parent is another Flickable that already has a Kirigami WheelHandler.
202 auto flickableParent = m_flickable->parentItem();
203 if (flickableParent && flickableParent->inherits("QQuickScrollView")) {
204 const auto siblings = flickableParent->children();
205 for (const auto child : siblings) {
206 if (child->inherits("QQuickScrollBarAttached")) {
207 attachedToScrollView.attached = child;
208 attachedToScrollView.vertical = child->property("vertical").value<QQuickItem *>();
209 attachedToScrollView.horizontal = child->property("horizontal").value<QQuickItem *>();
210 break;
211 }
212 }
213 }
214 }
215
216 // Dilemma: ScrollBars can be attached to both ScrollView and Flickable,
217 // but only one of them should be shown anyway. Let's prefer Flickable.
218
219 struct ChosenScrollBar {
220 QObject *attached = nullptr;
221 QQuickItem *scrollBar = nullptr;
222 };
223
224 ChosenScrollBar vertical;
225 if (attachedToFlickable.vertical) {
226 vertical.attached = attachedToFlickable.attached;
227 vertical.scrollBar = attachedToFlickable.vertical;
228 } else if (attachedToScrollView.vertical) {
229 vertical.attached = attachedToScrollView.attached;
230 vertical.scrollBar = attachedToScrollView.vertical;
231 }
232
233 ChosenScrollBar horizontal;
234 if (attachedToFlickable.horizontal) {
235 horizontal.attached = attachedToFlickable.attached;
236 horizontal.scrollBar = attachedToFlickable.horizontal;
237 } else if (attachedToScrollView.horizontal) {
238 horizontal.attached = attachedToScrollView.attached;
239 horizontal.scrollBar = attachedToScrollView.horizontal;
240 }
241
242 // Flickable may get re-parented to or out of a ScrollView, so we need to
243 // redo the discovery process. This is especially important for
244 // Kirigami.ScrollablePage component.
245 if (m_flickable) {
246 if (attachedToFlickable.horizontal && attachedToFlickable.vertical) {
247 // But if both scrollbars are already those from the preferred
248 // Flickable, there's no need for rediscovery.
249 disconnect(m_flickable, &QQuickItem::parentChanged, this, &WheelHandler::_k_rebindScrollBars);
250 } else {
251 connect(m_flickable, &QQuickItem::parentChanged, this, &WheelHandler::_k_rebindScrollBars, Qt::UniqueConnection);
252 }
253 }
254
255 if (m_verticalScrollBar != vertical.scrollBar) {
256 if (m_verticalScrollBar) {
257 m_verticalScrollBar->removeEventFilter(this);
258 disconnect(m_verticalChangedConnection);
259 }
260 m_verticalScrollBar = vertical.scrollBar;
261 if (vertical.scrollBar) {
262 vertical.scrollBar->installEventFilter(this);
263 m_verticalChangedConnection = connect(vertical.attached, SIGNAL(verticalChanged()), this, SLOT(_k_rebindScrollBars()));
264 }
265 }
266
267 if (m_horizontalScrollBar != horizontal.scrollBar) {
268 if (m_horizontalScrollBar) {
269 m_horizontalScrollBar->removeEventFilter(this);
270 disconnect(m_horizontalChangedConnection);
271 }
272 m_horizontalScrollBar = horizontal.scrollBar;
273 if (horizontal.scrollBar) {
274 horizontal.scrollBar->installEventFilter(this);
275 m_horizontalChangedConnection = connect(horizontal.attached, SIGNAL(horizontalChanged()), this, SLOT(_k_rebindScrollBars()));
276 }
277 }
278}
279
281{
282 return m_verticalStepSize;
283}
284
285void WheelHandler::setVerticalStepSize(qreal stepSize)
286{
287 m_explicitVStepSize = true;
288 if (qFuzzyCompare(m_verticalStepSize, stepSize)) {
289 return;
290 }
291 // Mimic the behavior of QQuickScrollBar when stepSize is 0
292 if (qFuzzyIsNull(stepSize)) {
293 resetVerticalStepSize();
294 return;
295 }
296 m_verticalStepSize = stepSize;
297 Q_EMIT verticalStepSizeChanged();
298}
299
300void WheelHandler::resetVerticalStepSize()
301{
302 m_explicitVStepSize = false;
303 if (qFuzzyCompare(m_verticalStepSize, m_defaultPixelStepSize)) {
304 return;
305 }
306 m_verticalStepSize = m_defaultPixelStepSize;
307 Q_EMIT verticalStepSizeChanged();
308}
309
311{
312 return m_horizontalStepSize;
313}
314
315void WheelHandler::setHorizontalStepSize(qreal stepSize)
316{
317 m_explicitHStepSize = true;
318 if (qFuzzyCompare(m_horizontalStepSize, stepSize)) {
319 return;
320 }
321 // Mimic the behavior of QQuickScrollBar when stepSize is 0
322 if (qFuzzyIsNull(stepSize)) {
323 resetHorizontalStepSize();
324 return;
325 }
326 m_horizontalStepSize = stepSize;
327 Q_EMIT horizontalStepSizeChanged();
328}
329
330void WheelHandler::resetHorizontalStepSize()
331{
332 m_explicitHStepSize = false;
333 if (qFuzzyCompare(m_horizontalStepSize, m_defaultPixelStepSize)) {
334 return;
335 }
336 m_horizontalStepSize = m_defaultPixelStepSize;
337 Q_EMIT horizontalStepSizeChanged();
338}
339
341{
342 return m_pageScrollModifiers;
343}
344
345void WheelHandler::setPageScrollModifiers(Qt::KeyboardModifiers modifiers)
346{
347 if (m_pageScrollModifiers == modifiers) {
348 return;
349 }
350 m_pageScrollModifiers = modifiers;
351 Q_EMIT pageScrollModifiersChanged();
352}
353
354void WheelHandler::resetPageScrollModifiers()
355{
356 setPageScrollModifiers(m_defaultPageScrollModifiers);
357}
358
360{
361 return m_filterMouseEvents;
362}
363
364void WheelHandler::setFilterMouseEvents(bool enabled)
365{
366 if (m_filterMouseEvents == enabled) {
367 return;
368 }
369 m_filterMouseEvents = enabled;
370 Q_EMIT filterMouseEventsChanged();
371}
372
374{
375 return m_keyNavigationEnabled;
376}
377
378void WheelHandler::setKeyNavigationEnabled(bool enabled)
379{
380 if (m_keyNavigationEnabled == enabled) {
381 return;
382 }
383 m_keyNavigationEnabled = enabled;
384 Q_EMIT keyNavigationEnabledChanged();
385}
386
387void WheelHandler::classBegin()
388{
389 // Initializes smooth scrolling
390 m_engine = qmlEngine(this);
391 auto units = m_engine->singletonInstance<Kirigami::Platform::Units *>("org.kde.kirigami.platform", "Units");
392 m_yScrollAnimation.setDuration(units->longDuration());
393 connect(units, &Kirigami::Platform::Units::longDurationChanged, this, [this] {
394 m_yScrollAnimation.setDuration(static_cast<Kirigami::Platform::Units *>(sender())->longDuration());
395 });
396}
397
398void WheelHandler::componentComplete()
399{
400}
401
402void WheelHandler::setScrolling(bool scrolling)
403{
404 if (m_wheelScrolling == scrolling) {
405 if (m_wheelScrolling) {
406 m_wheelScrollingTimer.start();
407 }
408 return;
409 }
410 m_wheelScrolling = scrolling;
411 m_filterItem->setEnabled(m_wheelScrolling);
412}
413
414bool WheelHandler::scrollFlickable(QPointF pixelDelta, QPointF angleDelta, Qt::KeyboardModifiers modifiers)
415{
416 if (!m_flickable || (pixelDelta.isNull() && angleDelta.isNull())) {
417 return false;
418 }
419
420 const qreal width = m_flickable->width();
421 const qreal height = m_flickable->height();
422 const qreal contentWidth = m_flickable->property("contentWidth").toReal();
423 const qreal contentHeight = m_flickable->property("contentHeight").toReal();
424 const qreal contentX = m_flickable->property("contentX").toReal();
425 const qreal contentY = m_flickable->property("contentY").toReal();
426 const qreal topMargin = m_flickable->property("topMargin").toReal();
427 const qreal bottomMargin = m_flickable->property("bottomMargin").toReal();
428 const qreal leftMargin = m_flickable->property("leftMargin").toReal();
429 const qreal rightMargin = m_flickable->property("rightMargin").toReal();
430 const qreal originX = m_flickable->property("originX").toReal();
431 const qreal originY = m_flickable->property("originY").toReal();
432 const qreal pageWidth = width - leftMargin - rightMargin;
433 const qreal pageHeight = height - topMargin - bottomMargin;
434 const auto window = m_flickable->window();
435 const qreal devicePixelRatio = window != nullptr ? window->devicePixelRatio() : qGuiApp->devicePixelRatio();
436
437 // HACK: Only transpose deltas when not using xcb in order to not conflict with xcb's own delta transposing
438 if (modifiers & m_defaultHorizontalScrollModifiers && qGuiApp->platformName() != QLatin1String("xcb")) {
439 angleDelta = angleDelta.transposed();
440 pixelDelta = pixelDelta.transposed();
441 }
442
443 const qreal xTicks = angleDelta.x() / 120;
444 const qreal yTicks = angleDelta.y() / 120;
445 qreal xChange;
446 qreal yChange;
447 bool scrolled = false;
448
449 // Scroll X
450 if (contentWidth > pageWidth) {
451 // Use page size with pageScrollModifiers. Matches QScrollBar, which uses QAbstractSlider behavior.
452 if (modifiers & m_pageScrollModifiers) {
453 xChange = qBound(-pageWidth, xTicks * pageWidth, pageWidth);
454 } else if (pixelDelta.x() != 0) {
455 xChange = pixelDelta.x();
456 } else {
457 xChange = xTicks * m_horizontalStepSize;
458 }
459
460 // contentX and contentY use reversed signs from what x and y would normally use, so flip the signs
461
462 qreal minXExtent = leftMargin - originX;
463 qreal maxXExtent = width - (contentWidth + rightMargin + originX);
464
465 qreal newContentX = qBound(-minXExtent, contentX - xChange, -maxXExtent);
466 // Flickable::pixelAligned rounds the position, so round to mimic that behavior.
467 // Rounding prevents fractional positioning from causing text to be
468 // clipped off on the top and bottom.
469 // Multiply by devicePixelRatio before rounding and divide by devicePixelRatio
470 // after to make position match pixels on the screen more closely.
471 newContentX = std::round(newContentX * devicePixelRatio) / devicePixelRatio;
472 if (contentX != newContentX) {
473 scrolled = true;
474 m_flickable->setProperty("contentX", newContentX);
475 }
476 }
477
478 // Scroll Y
479 if (contentHeight > pageHeight) {
480 if (modifiers & m_pageScrollModifiers) {
481 yChange = qBound(-pageHeight, yTicks * pageHeight, pageHeight);
482 } else if (pixelDelta.y() != 0) {
483 yChange = pixelDelta.y();
484 } else {
485 yChange = yTicks * m_verticalStepSize;
486 }
487
488 // contentX and contentY use reversed signs from what x and y would normally use, so flip the signs
489
490 qreal minYExtent = topMargin - originY;
491 qreal maxYExtent = height - (contentHeight + bottomMargin + originY);
492
493 qreal newContentY;
494 if (m_yScrollAnimation.state() == QPropertyAnimation::Running) {
495 m_yScrollAnimation.stop();
496 newContentY = std::clamp(m_yScrollAnimation.endValue().toReal() + -yChange, -minYExtent, -maxYExtent);
497 } else {
498 newContentY = std::clamp(contentY - yChange, -minYExtent, -maxYExtent);
499 }
500
501 // Flickable::pixelAligned rounds the position, so round to mimic that behavior.
502 // Rounding prevents fractional positioning from causing text to be
503 // clipped off on the top and bottom.
504 // Multiply by devicePixelRatio before rounding and divide by devicePixelRatio
505 // after to make position match pixels on the screen more closely.
506 newContentY = std::round(newContentY * devicePixelRatio) / devicePixelRatio;
507 if (contentY != newContentY) {
508 scrolled = true;
509 if (m_wasTouched || !m_engine) {
510 m_flickable->setProperty("contentY", newContentY);
511 } else {
512 m_yScrollAnimation.setEndValue(newContentY);
513 m_yScrollAnimation.start(QAbstractAnimation::KeepWhenStopped);
514 }
515 }
516 }
517
518 return scrolled;
519}
520
521bool WheelHandler::scrollUp(qreal stepSize)
522{
523 if (qFuzzyIsNull(stepSize)) {
524 return false;
525 } else if (stepSize < 0) {
526 stepSize = m_verticalStepSize;
527 }
528 // contentY uses reversed sign
529 return scrollFlickable(QPointF(0, stepSize));
530}
531
532bool WheelHandler::scrollDown(qreal stepSize)
533{
534 if (qFuzzyIsNull(stepSize)) {
535 return false;
536 } else if (stepSize < 0) {
537 stepSize = m_verticalStepSize;
538 }
539 // contentY uses reversed sign
540 return scrollFlickable(QPointF(0, -stepSize));
541}
542
543bool WheelHandler::scrollLeft(qreal stepSize)
544{
545 if (qFuzzyIsNull(stepSize)) {
546 return false;
547 } else if (stepSize < 0) {
548 stepSize = m_horizontalStepSize;
549 }
550 // contentX uses reversed sign
551 return scrollFlickable(QPoint(stepSize, 0));
552}
553
554bool WheelHandler::scrollRight(qreal stepSize)
555{
556 if (qFuzzyIsNull(stepSize)) {
557 return false;
558 } else if (stepSize < 0) {
559 stepSize = m_horizontalStepSize;
560 }
561 // contentX uses reversed sign
562 return scrollFlickable(QPoint(-stepSize, 0));
563}
564
565bool WheelHandler::eventFilter(QObject *watched, QEvent *event)
566{
567 auto item = qobject_cast<QQuickItem *>(watched);
568 if (!item || !item->isEnabled()) {
569 return false;
570 }
571
572 qreal contentWidth = 0;
573 qreal contentHeight = 0;
574 qreal pageWidth = 0;
575 qreal pageHeight = 0;
576 if (m_flickable) {
577 contentWidth = m_flickable->property("contentWidth").toReal();
578 contentHeight = m_flickable->property("contentHeight").toReal();
579 pageWidth = m_flickable->width() - m_flickable->property("leftMargin").toReal() - m_flickable->property("rightMargin").toReal();
580 pageHeight = m_flickable->height() - m_flickable->property("topMargin").toReal() - m_flickable->property("bottomMargin").toReal();
581 }
582
583 // The code handling touch, mouse and hover events is mostly copied/adapted from QQuickScrollView::childMouseEventFilter()
584 switch (event->type()) {
585 case QEvent::Wheel: {
586 // QQuickScrollBar::interactive handling Matches behavior in QQuickScrollView::eventFilter()
587 if (m_filterMouseEvents) {
588 if (m_verticalScrollBar) {
589 m_verticalScrollBar->setProperty("interactive", true);
590 }
591 if (m_horizontalScrollBar) {
592 m_horizontalScrollBar->setProperty("interactive", true);
593 }
594 }
595 QWheelEvent *wheelEvent = static_cast<QWheelEvent *>(event);
596
597 // Can't use wheelEvent->deviceType() to determine device type since on Wayland mouse is always regarded as touchpad
598 // https://invent.kde.org/qt/qt/qtwayland/-/blob/e695a39519a7629c1549275a148cfb9ab99a07a9/src/client/qwaylandinputdevice.cpp#L445
599 // and we can only expect a touchpad never generates the same angle delta as a mouse
600
601 // mouse wheel can also generate angle delta like 240, 360 and so on when scrolling very fast
602 // only checking wheelEvent->angleDelta().y() because we only animate for contentY
603 m_wasTouched = (std::abs(wheelEvent->angleDelta().y()) != 0 && std::abs(wheelEvent->angleDelta().y()) % 120 != 0);
604 // NOTE: On X11 with libinput, pixelDelta is identical to angleDelta when using a mouse that shouldn't use pixelDelta.
605 // If faulty pixelDelta, reset pixelDelta to (0,0).
606 if (wheelEvent->pixelDelta() == wheelEvent->angleDelta()) {
607 // In order to change any of the data, we have to create a whole new QWheelEvent from its constructor.
608 QWheelEvent newWheelEvent(wheelEvent->position(),
609 wheelEvent->globalPosition(),
610 QPoint(0, 0), // pixelDelta
611 wheelEvent->angleDelta(),
612 wheelEvent->buttons(),
613 wheelEvent->modifiers(),
614 wheelEvent->phase(),
615 wheelEvent->inverted(),
616 wheelEvent->source());
617 m_kirigamiWheelEvent.initializeFromEvent(&newWheelEvent);
618 } else {
619 m_kirigamiWheelEvent.initializeFromEvent(wheelEvent);
620 }
621
622 Q_EMIT wheel(&m_kirigamiWheelEvent);
623
624 if (m_kirigamiWheelEvent.isAccepted()) {
625 return true;
626 }
627
628 bool scrolled = false;
629 if (m_scrollFlickableTarget || (contentHeight <= pageHeight && contentWidth <= pageWidth)) {
630 // Don't use pixelDelta from the event unless angleDelta is not available
631 // because scrolling by pixelDelta is too slow on Wayland with libinput.
632 QPointF pixelDelta = m_kirigamiWheelEvent.angleDelta().isNull() ? m_kirigamiWheelEvent.pixelDelta() : QPoint(0, 0);
633 scrolled = scrollFlickable(pixelDelta, m_kirigamiWheelEvent.angleDelta(), Qt::KeyboardModifiers(m_kirigamiWheelEvent.modifiers()));
634 }
635 setScrolling(scrolled);
636
637 // NOTE: Wheel events created by touchpad gestures with pixel deltas will cause scrolling to jump back
638 // to where scrolling started unless the event is always accepted before it reaches the Flickable.
639 bool flickableWillUseGestureScrolling = !(wheelEvent->source() == Qt::MouseEventNotSynthesized || wheelEvent->pixelDelta().isNull());
640 return scrolled || m_blockTargetWheel || flickableWillUseGestureScrolling;
641 }
642
643 case QEvent::TouchBegin: {
644 m_wasTouched = true;
645 if (!m_filterMouseEvents) {
646 break;
647 }
648 if (m_verticalScrollBar) {
649 m_verticalScrollBar->setProperty("interactive", false);
650 }
651 if (m_horizontalScrollBar) {
652 m_horizontalScrollBar->setProperty("interactive", false);
653 }
654 break;
655 }
656
657 case QEvent::TouchEnd: {
658 m_wasTouched = false;
659 break;
660 }
661
663 // NOTE: Flickable does not handle touch events, only synthesized mouse events
664 m_wasTouched = static_cast<QMouseEvent *>(event)->source() != Qt::MouseEventNotSynthesized;
665 if (!m_filterMouseEvents) {
666 break;
667 }
668 if (!m_wasTouched) {
669 if (m_verticalScrollBar) {
670 m_verticalScrollBar->setProperty("interactive", true);
671 }
672 if (m_horizontalScrollBar) {
673 m_horizontalScrollBar->setProperty("interactive", true);
674 }
675 break;
676 }
677 return !m_wasTouched && item == m_flickable;
678 }
679
682 setScrolling(false);
683 if (!m_filterMouseEvents) {
684 break;
685 }
686 if (static_cast<QMouseEvent *>(event)->source() == Qt::MouseEventNotSynthesized && item == m_flickable) {
687 return true;
688 }
689 break;
690 }
691
693 case QEvent::HoverMove: {
694 if (!m_filterMouseEvents) {
695 break;
696 }
697 if (m_wasTouched && (item == m_verticalScrollBar || item == m_horizontalScrollBar)) {
698 if (m_verticalScrollBar) {
699 m_verticalScrollBar->setProperty("interactive", true);
700 }
701 if (m_horizontalScrollBar) {
702 m_horizontalScrollBar->setProperty("interactive", true);
703 }
704 }
705 break;
706 }
707
708 case QEvent::KeyPress: {
709 if (!m_keyNavigationEnabled) {
710 break;
711 }
712 QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
713 bool horizontalScroll = keyEvent->modifiers() & m_defaultHorizontalScrollModifiers;
714 switch (keyEvent->key()) {
715 case Qt::Key_Up:
716 return scrollUp();
717 case Qt::Key_Down:
718 return scrollDown();
719 case Qt::Key_Left:
720 return scrollLeft();
721 case Qt::Key_Right:
722 return scrollRight();
723 case Qt::Key_PageUp:
724 return horizontalScroll ? scrollLeft(pageWidth) : scrollUp(pageHeight);
725 case Qt::Key_PageDown:
726 return horizontalScroll ? scrollRight(pageWidth) : scrollDown(pageHeight);
727 case Qt::Key_Home:
728 return horizontalScroll ? scrollLeft(contentWidth) : scrollUp(contentHeight);
729 case Qt::Key_End:
730 return horizontalScroll ? scrollRight(contentWidth) : scrollDown(contentHeight);
731 default:
732 break;
733 }
734 break;
735 }
736
737 default:
738 break;
739 }
740
741 return false;
742}
743
744#include "moc_wheelhandler.cpp"
QML_ELEMENTqreal x
A set of values to define semantically sizes and durations.
Definition units.h:80
bool filterMouseEvents
qreal horizontalStepSize
Qt::KeyboardModifiers pageScrollModifiers
bool keyNavigationEnabled
Q_INVOKABLE bool scrollDown(qreal stepSize=-1)
QML_ELEMENTQQuickItem * target
Q_INVOKABLE bool scrollRight(qreal stepSize=-1)
void wheel(KirigamiWheelEvent *wheel)
qreal verticalStepSize
Q_INVOKABLE bool scrollUp(qreal stepSize=-1)
Q_INVOKABLE bool scrollLeft(qreal stepSize=-1)
QWidget * window(QObject *job)
void start(QAbstractAnimation::DeletionPolicy policy)
QStyleHints * styleHints()
Qt::KeyboardModifiers modifiers() const const
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *method)
virtual bool event(QEvent *e)
void installEventFilter(QObject *filterObj)
QObject * sender() const const
int devicePixelRatio() const const
bool isNull() const const
int y() const const
bool isNull() const const
QPointF transposed() const const
qreal x() const const
qreal y() const const
T singletonInstance(int qmlTypeId)
void heightChanged()
void parentChanged(QQuickItem *)
void widthChanged()
void wheelScrollLinesChanged(int scrollLines)
UniqueConnection
typedef KeyboardModifiers
MouseEventNotSynthesized
void keyEvent(QTest::KeyAction action, QWindow *window, char ascii, Qt::KeyboardModifiers modifier, int delay)
void start(int msec)
QQmlInfo qmlWarning(const QObject *object)
void setDuration(int msecs)
QPoint angleDelta() const const
Qt::MouseButtons buttons() const const
QPointF globalPosition() const const
bool inverted() const const
Qt::ScrollPhase phase() const const
QPoint pixelDelta() const const
QPointF position() const const
Qt::MouseEventSource source() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Sun Feb 25 2024 18:45:17 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.