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"