File indexing completed on 2024-05-12 16:02:29

0001 /* This file is part of the KDE project
0002  * SPDX-FileCopyrightText: 2018 Emmet O 'Neill <emmetoneill.pdx@gmail.com>
0003  * SPDX-FileCopyrightText: 2018 Eoin O 'Neill <eoinoneill1991@gmail.com>
0004  * SPDX-FileCopyrightText: 2021 Alvin Wong <alvin@alvinhc.com>
0005  *
0006  * SPDX-License-Identifier: LGPL-2.0-or-later
0007  */
0008 
0009 #include <KisKineticScroller.h>
0010 
0011 #include <QAbstractItemView>
0012 #include <QEvent>
0013 #include <QScrollBar>
0014 
0015 #include <ksharedconfig.h>
0016 #include <kconfiggroup.h>
0017 
0018 namespace {
0019     /**
0020      * Event filter to be installed on the scroll bars of QAbstractScrollArea,
0021      * used to detect cursor entering/leaving the scrollbars in order to
0022      * temporarily disable/re-enable the QScroller. This allows the scroll bars
0023      * to be dragged normally even when kinetic scrolling is enabled.
0024      */
0025     class KisKineticScrollerEventFilter : public QObject
0026     {
0027         Q_OBJECT
0028 
0029         QAbstractScrollArea *m_scrollArea;
0030         QScroller::ScrollerGestureType m_gestureType;
0031 
0032     public:
0033         KisKineticScrollerEventFilter(QScroller::ScrollerGestureType gestureType, QAbstractScrollArea *parent)
0034             : QObject(parent)
0035             , m_scrollArea(parent)
0036             , m_gestureType(gestureType)
0037         {
0038         }
0039 
0040     protected:
0041         bool eventFilter(QObject *watched, QEvent *event) override {
0042             switch (event->type()) {
0043             case QEvent::Enter:
0044                 QScroller::ungrabGesture(m_scrollArea);
0045                 break;
0046             case QEvent::Leave:
0047                 QScroller::grabGesture(m_scrollArea, m_gestureType);
0048                 break;
0049             default:
0050                 break;
0051             }
0052             return QObject::eventFilter(watched, event);
0053         }
0054     };
0055 } /* namespace */
0056 
0057 QScroller* KisKineticScroller::createPreconfiguredScroller(QAbstractScrollArea *scrollArea) {
0058     KConfigGroup config = KSharedConfig::openConfig()->group("");
0059     int sensitivity = config.readEntry("KineticScrollingSensitivity", 75);
0060     bool enabled = config.readEntry("KineticScrollingEnabled", true);
0061     bool hideScrollBars = config.readEntry("KineticScrollingHideScrollbar", false);
0062     float resistanceCoefficient = config.readEntry("KineticScrollingResistanceCoefficient", 10.0f);
0063     float dragVelocitySmoothFactor = config.readEntry("KineticScrollingDragVelocitySmoothingFactor", 1.0f);
0064     float minimumVelocity = config.readEntry("KineticScrollingMinimumVelocity", 0.0f);
0065     float axisLockThresh = config.readEntry("KineticScrollingAxisLockThreshold", 1.0f);
0066     float maximumClickThroughVelocity = config.readEntry("KineticScrollingMaxClickThroughVelocity", 0.0f);
0067     float flickAccelerationFactor = config.readEntry("KineticScrollingFlickAccelerationFactor", 1.5f);
0068     float overshootDragResistanceFactor = config.readEntry("KineticScrollingOvershotDragResistanceFactor", 0.1f);
0069     float overshootDragDistanceFactor = config.readEntry("KineticScrollingOvershootDragDistanceFactor", 0.3f);
0070     float overshootScrollDistanceFactor = config.readEntry("KineticScrollingOvershootScrollDistanceFactor", 0.1f);
0071     float overshootScrollTime = config.readEntry("KineticScrollingOvershootScrollTime", 0.4f);
0072     QScroller::ScrollerGestureType gestureType = getConfiguredGestureType();
0073 
0074     if (enabled && scrollArea) {
0075         if (hideScrollBars) {
0076             scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOff);
0077             scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOff);
0078         } else if (gestureType != QScroller::TouchGesture) {
0079             auto *filter = new KisKineticScrollerEventFilter(gestureType, scrollArea);
0080             scrollArea->horizontalScrollBar()->installEventFilter(filter);
0081             scrollArea->verticalScrollBar()->installEventFilter(filter);
0082         }
0083 
0084         QAbstractItemView *itemView = qobject_cast<QAbstractItemView *>(scrollArea);
0085         if (itemView) {
0086             itemView->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
0087         }
0088 
0089         QScroller *scroller = QScroller::scroller(scrollArea);
0090         QScroller::grabGesture(scrollArea, gestureType);
0091 
0092         QScrollerProperties properties;
0093 
0094         // DragStartDistance seems to be based on meter per second; though it's
0095         // not explicitly documented, other QScroller values are in that metric.
0096         // To start kinetic scrolling, with minimal sensitity, we expect a drag
0097         // of 10 mm, with minimum sensitity any > 0 mm.
0098         const float mm = 0.001f;
0099         const float resistance = 1.0f - (sensitivity / 100.0f);
0100         const float mousePressEventDelay = config.readEntry("KineticScrollingMousePressDelay", 1.0f - 0.75f * resistance);
0101 
0102         properties.setScrollMetric(QScrollerProperties::DragStartDistance, resistance * resistanceCoefficient * mm);
0103         properties.setScrollMetric(QScrollerProperties::DragVelocitySmoothingFactor, dragVelocitySmoothFactor);
0104         properties.setScrollMetric(QScrollerProperties::MinimumVelocity, minimumVelocity);
0105         properties.setScrollMetric(QScrollerProperties::AxisLockThreshold, axisLockThresh);
0106         properties.setScrollMetric(QScrollerProperties::MaximumClickThroughVelocity, maximumClickThroughVelocity);
0107         properties.setScrollMetric(QScrollerProperties::MousePressEventDelay, mousePressEventDelay);
0108         properties.setScrollMetric(QScrollerProperties::AcceleratingFlickSpeedupFactor, flickAccelerationFactor);
0109 
0110         properties.setScrollMetric(QScrollerProperties::VerticalOvershootPolicy, QScrollerProperties::OvershootAlwaysOn);
0111         properties.setScrollMetric(QScrollerProperties::OvershootDragResistanceFactor, overshootDragResistanceFactor);
0112         properties.setScrollMetric(QScrollerProperties::OvershootDragDistanceFactor, overshootDragDistanceFactor);
0113         properties.setScrollMetric(QScrollerProperties::OvershootScrollDistanceFactor, overshootScrollDistanceFactor);
0114         properties.setScrollMetric(QScrollerProperties::OvershootScrollTime, overshootScrollTime);
0115 
0116         scroller->setScrollerProperties(properties);
0117 
0118         return scroller;
0119     }
0120 
0121     return nullptr;
0122 }
0123 
0124 QScroller::ScrollerGestureType KisKineticScroller::getConfiguredGestureType() {
0125     KConfigGroup config = KSharedConfig::openConfig()->group("");
0126 #ifdef Q_OS_ANDROID
0127     // Use a different default. Shouldn't we use KisConfig::kineticScrollingGesture?
0128     int gesture = config.readEntry("KineticScrollingGesture", 1);
0129 #else
0130     int gesture = config.readEntry("KineticScrollingGesture", 0);
0131 #endif
0132 
0133     switch (gesture) {
0134     case 0: {
0135         return QScroller::TouchGesture;
0136     }
0137     case 1: {
0138         return QScroller::LeftMouseButtonGesture;
0139     }
0140     case 2: {
0141         return QScroller::MiddleMouseButtonGesture;
0142     }
0143     case 3: {
0144         return QScroller::RightMouseButtonGesture;
0145     }
0146     default:
0147         return QScroller::MiddleMouseButtonGesture;
0148     }
0149 }
0150 
0151 void KisKineticScroller::updateCursor(QWidget *source, QScroller::State state) {
0152     if( state == QScroller::State::Pressed ) {
0153         source->setCursor(Qt::OpenHandCursor);
0154     } else if (state == QScroller::State::Dragging) {
0155         source->setCursor(Qt::ClosedHandCursor);
0156     } else {
0157         source->setCursor(Qt::ArrowCursor);
0158     }
0159 }
0160 
0161 #include "KisKineticScroller.moc"