File indexing completed on 2025-01-19 03:57:35
0001 /* ============================================================ 0002 * 0003 * This file is a part of digiKam project 0004 * https://www.digikam.org 0005 * 0006 * Date : 2014-02-01 0007 * Description : Kinetic Scroller for Thumbnail Bar 0008 * based on Razvan Petru implementation. 0009 * 0010 * SPDX-FileCopyrightText: 2014 by Mohamed_Anwer <m_dot_anwer at gmx dot com> 0011 * 0012 * SPDX-License-Identifier: GPL-2.0-or-later 0013 * 0014 * ============================================================ */ 0015 0016 #include "showfotokineticscroller.h" 0017 0018 // Qt includes 0019 0020 #include <QApplication> 0021 #include <QScrollBar> 0022 #include <QMouseEvent> 0023 #include <QEvent> 0024 #include <QTimer> 0025 0026 namespace ShowFoto 0027 { 0028 0029 /** 0030 *A number of mouse moves are ignored after a press to differentiate 0031 *it from a press & drag. 0032 */ 0033 static const int gMaxIgnoredMouseMoves = 4; 0034 0035 /** 0036 * The timer measures the drag speed & handles kinetic scrolling. Adjusting 0037 * the timer interval will change the scrolling speed and smoothness. 0038 */ 0039 static const int gTimerInterval = 30; 0040 0041 /** 0042 * The speed measurement is imprecise, limit it so that the scrolling is not 0043 * too fast. 0044 */ 0045 static const int gMaxDecelerationSpeed = 30; 0046 0047 /** 0048 * influences how fast the scroller decelerates 0049 */ 0050 static const int gFriction = 1; 0051 0052 class Q_DECL_HIDDEN ShowfotoKineticScroller::Private 0053 { 0054 public: 0055 0056 explicit Private() 0057 : isPressed (false), 0058 isMoving (false), 0059 lastMouseYPos (0), 0060 lastMouseXPos (0), 0061 lastScrollBarPosition(0), 0062 velocity (0), 0063 ignoredMouseMoves (0), 0064 ignoredMouseActions (0), 0065 scrollArea (nullptr), 0066 scrollFlow (QListView::TopToBottom) 0067 { 0068 } 0069 0070 void stopMotion() 0071 { 0072 isMoving = false; 0073 velocity = 0; 0074 kineticTimer.stop(); 0075 } 0076 0077 bool isPressed; 0078 bool isMoving; 0079 0080 int lastMouseYPos; 0081 int lastMouseXPos; 0082 int lastScrollBarPosition; 0083 int velocity; 0084 int ignoredMouseMoves; 0085 int ignoredMouseActions; 0086 0087 QAbstractScrollArea* scrollArea; 0088 QPoint lastPressPoint; 0089 QTimer kineticTimer; 0090 QListView::Flow scrollFlow; 0091 }; 0092 0093 ShowfotoKineticScroller::ShowfotoKineticScroller(QObject* const parent) 0094 : QObject(parent), 0095 d (new Private()) 0096 { 0097 connect(&d->kineticTimer, &QTimer::timeout, 0098 this, &ShowfotoKineticScroller::onKineticTimerElapsed); 0099 } 0100 0101 ShowfotoKineticScroller::~ShowfotoKineticScroller() 0102 { 0103 delete d; 0104 } 0105 0106 void ShowfotoKineticScroller::enableKineticScrollFor(QAbstractScrollArea* const scrollArea) 0107 { 0108 if (!scrollArea) 0109 { 0110 Q_ASSERT_X(0, "kinetic scroller", "missing scroll area"); 0111 return; 0112 } 0113 0114 // remove existing association 0115 0116 if (d->scrollArea) 0117 { 0118 d->scrollArea->viewport()->removeEventFilter(this); 0119 d->scrollArea->removeEventFilter(this); 0120 d->scrollArea = nullptr; 0121 } 0122 0123 // associate 0124 0125 scrollArea->installEventFilter(this); 0126 scrollArea->viewport()->installEventFilter(this); 0127 d->scrollArea = scrollArea; 0128 } 0129 0130 /// intercepts mouse events to make the scrolling work 0131 bool ShowfotoKineticScroller::eventFilter(QObject* object, QEvent* event) 0132 { 0133 const QEvent::Type eventType = event->type(); 0134 const bool isMouseAction = QEvent::MouseButtonPress == eventType || QEvent::MouseButtonRelease == eventType; 0135 const bool isMouseEvent = isMouseAction || QEvent::MouseMove == eventType; 0136 0137 if (!isMouseEvent || !d->scrollArea) 0138 { 0139 return false; 0140 } 0141 0142 if (isMouseAction && (d->ignoredMouseActions-- > 0)) // don't filter simulated click 0143 { 0144 return false; 0145 } 0146 0147 QMouseEvent* const mouseEvent = static_cast<QMouseEvent*>(event); 0148 0149 switch (eventType) 0150 { 0151 case QEvent::MouseButtonPress: 0152 { 0153 d->isPressed = true; 0154 d->lastPressPoint = mouseEvent->pos(); 0155 0156 if (d->scrollFlow == QListView::TopToBottom) 0157 { 0158 d->lastScrollBarPosition = d->scrollArea->verticalScrollBar()->value(); 0159 } 0160 else 0161 { 0162 d->lastScrollBarPosition = d->scrollArea->horizontalScrollBar()->value(); 0163 } 0164 0165 if (d->isMoving) 0166 { 0167 // press while kinetic scrolling, so stop 0168 0169 d->stopMotion(); 0170 } 0171 0172 break; 0173 } 0174 0175 case QEvent::MouseMove: 0176 { 0177 if (!d->isMoving && d->isPressed) 0178 { 0179 // A few move events are ignored as "click jitter", but after that we 0180 // assume that the user is doing a click & drag 0181 0182 if (d->ignoredMouseMoves < gMaxIgnoredMouseMoves) 0183 { 0184 ++d->ignoredMouseMoves; 0185 } 0186 else if (d->isPressed) 0187 { 0188 d->ignoredMouseMoves = 0; 0189 d->isMoving = true; 0190 0191 if (d->scrollFlow == QListView::TopToBottom) 0192 { 0193 d->lastMouseYPos = mouseEvent->pos().y(); 0194 } 0195 else 0196 { 0197 d->lastMouseXPos = mouseEvent->pos().x(); 0198 } 0199 0200 if (!d->kineticTimer.isActive()) 0201 { 0202 d->kineticTimer.start(gTimerInterval); 0203 } 0204 } 0205 } 0206 else if (d->isPressed) 0207 { 0208 // manual scroll 0209 0210 if (d->scrollFlow == QListView::TopToBottom) 0211 { 0212 const int dragDistance = mouseEvent->pos().y() - d->lastPressPoint.y(); 0213 d->scrollArea->verticalScrollBar()->setValue(d->lastScrollBarPosition - dragDistance); 0214 } 0215 else 0216 { 0217 const int dragDistance = mouseEvent->pos().x() - d->lastPressPoint.x(); 0218 d->scrollArea->horizontalScrollBar()->setValue(d->lastScrollBarPosition - dragDistance); 0219 } 0220 } 0221 0222 break; 0223 } 0224 0225 case QEvent::MouseButtonRelease: 0226 { 0227 d->isPressed = false; 0228 0229 // Looks like the user wanted a single click. Simulate the click, 0230 // as the events were already consumed 0231 0232 if (!d->isMoving) 0233 { 0234 QMouseEvent* const mousePress = new QMouseEvent(QEvent::MouseButtonPress, 0235 d->lastPressPoint, Qt::LeftButton, 0236 Qt::LeftButton, Qt::NoModifier); 0237 QMouseEvent* const mouseRelease = new QMouseEvent(QEvent::MouseButtonRelease, 0238 d->lastPressPoint, Qt::LeftButton, 0239 Qt::LeftButton, Qt::NoModifier); 0240 d->ignoredMouseActions = 2; 0241 0242 QApplication::postEvent(object, mousePress); 0243 QApplication::postEvent(object, mouseRelease); 0244 } 0245 0246 break; 0247 } 0248 0249 default: 0250 { 0251 // Nothing to do here. 0252 break; 0253 } 0254 } 0255 0256 return true; // filter event 0257 } 0258 0259 void ShowfotoKineticScroller::onKineticTimerElapsed() 0260 { 0261 if (d->isPressed && d->isMoving) 0262 { 0263 if (d->scrollFlow == QListView::TopToBottom) 0264 { 0265 // the speed is measured between two timer ticks 0266 0267 const int cursorYPos = d->scrollArea->mapFromGlobal(QCursor::pos()).y(); 0268 d->velocity = cursorYPos - d->lastMouseYPos; 0269 d->lastMouseYPos = cursorYPos; 0270 } 0271 else 0272 { 0273 const int cursorXPos = d->scrollArea->mapFromGlobal(QCursor::pos()).x(); 0274 d->velocity = cursorXPos - d->lastMouseXPos; 0275 d->lastMouseXPos = cursorXPos; 0276 } 0277 } 0278 else if (!d->isPressed && d->isMoving) 0279 { 0280 // use the previously recorded speed and gradually decelerate 0281 0282 d->velocity = qBound(-gMaxDecelerationSpeed, d->velocity, gMaxDecelerationSpeed); 0283 0284 if (d->velocity > 0) 0285 { 0286 d->velocity -= gFriction; 0287 } 0288 else if (d->velocity < 0) 0289 { 0290 d->velocity += gFriction; 0291 } 0292 0293 if (qAbs(d->velocity) < qAbs(gFriction)) 0294 { 0295 d->stopMotion(); 0296 } 0297 0298 if (d->scrollFlow == QListView::TopToBottom) 0299 { 0300 const int scrollBarYPos = d->scrollArea->verticalScrollBar()->value(); 0301 d->scrollArea->verticalScrollBar()->setValue(scrollBarYPos - d->velocity); 0302 } 0303 else 0304 { 0305 const int scrollBarXPos = d->scrollArea->horizontalScrollBar()->value(); 0306 d->scrollArea->horizontalScrollBar()->setValue(scrollBarXPos - d->velocity); 0307 } 0308 } 0309 else 0310 { 0311 d->stopMotion(); 0312 } 0313 } 0314 0315 void ShowfotoKineticScroller::setScrollFlow(QListView::Flow flow) 0316 { 0317 d->scrollFlow = flow; 0318 } 0319 0320 } // namespace ShowFoto 0321 0322 #include "moc_showfotokineticscroller.cpp"