1 /***************************************************************************
2 * Copyright (C) 2011 by Peter Penz <peter.penz19@gmail.com> *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
9 * This program is distributed in the hope that it will be useful, *
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12 * GNU General Public License for more details. *
14 * You should have received a copy of the GNU General Public License *
15 * along with this program; if not, write to the *
16 * Free Software Foundation, Inc., *
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
18 ***************************************************************************/
20 #include "kitemlistsmoothscroller.h"
22 #include <QApplication>
24 #include <QPropertyAnimation>
26 #include <QWheelEvent>
29 KItemListSmoothScroller::KItemListSmoothScroller(QScrollBar
* scrollBar
,
32 m_scrollBarPressed(false),
33 m_smoothScrolling(true),
34 m_scrollBar(scrollBar
),
37 m_animation
= new QPropertyAnimation(this);
38 const int duration
= m_scrollBar
->style()->styleHint(QStyle::SH_Widget_Animate
, nullptr, m_scrollBar
) ? 100 : 1;
39 m_animation
->setDuration(duration
);
40 connect(m_animation
, &QPropertyAnimation::stateChanged
,
41 this, &KItemListSmoothScroller::slotAnimationStateChanged
);
43 m_scrollBar
->installEventFilter(this);
46 KItemListSmoothScroller::~KItemListSmoothScroller()
50 void KItemListSmoothScroller::setScrollBar(QScrollBar
*scrollBar
)
52 m_scrollBar
= scrollBar
;
55 QScrollBar
* KItemListSmoothScroller::scrollBar() const
60 void KItemListSmoothScroller::setTargetObject(QObject
* target
)
62 m_animation
->setTargetObject(target
);
65 QObject
* KItemListSmoothScroller::targetObject() const
67 return m_animation
->targetObject();
70 void KItemListSmoothScroller::setPropertyName(const QByteArray
& propertyName
)
72 m_animation
->setPropertyName(propertyName
);
75 QByteArray
KItemListSmoothScroller::propertyName() const
77 return m_animation
->propertyName();
80 void KItemListSmoothScroller::scrollContentsBy(qreal distance
)
82 QObject
* target
= targetObject();
87 const QByteArray name
= propertyName();
88 const qreal currentOffset
= target
->property(name
).toReal();
89 if (static_cast<int>(currentOffset
) == m_scrollBar
->value()) {
90 // The current offset is already synchronous to the scrollbar
94 const bool animRunning
= (m_animation
->state() == QAbstractAnimation::Running
);
96 // Stopping a running animation means skipping the range from the current offset
97 // until the target offset. To prevent skipping of the range the difference
98 // is added to the new target offset.
99 const qreal oldEndOffset
= m_animation
->endValue().toReal();
100 distance
+= (currentOffset
- oldEndOffset
);
103 const qreal endOffset
= currentOffset
- distance
;
104 if (m_smoothScrolling
|| animRunning
) {
105 qreal startOffset
= currentOffset
;
107 // If the animation was running and has been interrupted by assigning a new end-offset
108 // one frame must be added to the start-offset to keep the animation smooth. This also
109 // assures that animation proceeds even in cases where new end-offset are triggered
110 // within a very short timeslots.
111 startOffset
+= (endOffset
- currentOffset
) * 1000 / (m_animation
->duration() * 60);
112 if (currentOffset
< endOffset
) {
113 startOffset
= qMin(startOffset
, endOffset
);
115 startOffset
= qMax(startOffset
, endOffset
);
120 m_animation
->setStartValue(startOffset
);
121 m_animation
->setEndValue(endOffset
);
122 m_animation
->setEasingCurve(animRunning
? QEasingCurve::OutQuad
: QEasingCurve::InOutQuad
);
123 m_animation
->start();
124 target
->setProperty(name
, startOffset
);
126 target
->setProperty(name
, endOffset
);
130 void KItemListSmoothScroller::scrollTo(qreal position
)
132 int newValue
= position
;
133 newValue
= qBound(0, newValue
, m_scrollBar
->maximum());
135 if (newValue
!= m_scrollBar
->value()) {
136 m_smoothScrolling
= true;
137 m_scrollBar
->setValue(newValue
);
141 bool KItemListSmoothScroller::requestScrollBarUpdate(int newMaximum
)
143 if (m_animation
->state() == QAbstractAnimation::Running
) {
144 if (newMaximum
== m_scrollBar
->maximum()) {
145 // The value has been changed by the animation, no update
146 // of the scrollbars is required as their target state will be
147 // reached with the end of the animation.
151 // The maximum has been changed which indicates that the content
152 // of the view has been changed. Stop the animation in any case and
153 // update the scrollbars immediately.
159 bool KItemListSmoothScroller::eventFilter(QObject
* obj
, QEvent
* event
)
161 Q_ASSERT(obj
== m_scrollBar
);
163 switch (event
->type()) {
164 case QEvent::MouseButtonPress
:
165 m_scrollBarPressed
= true;
166 m_smoothScrolling
= true;
169 case QEvent::MouseButtonRelease
:
170 m_scrollBarPressed
= false;
171 m_smoothScrolling
= false;
175 handleWheelEvent(static_cast<QWheelEvent
*>(event
));
176 return true; // eat event so that QScrollBar does not scroll one step more by itself
182 return QObject::eventFilter(obj
, event
);
185 void KItemListSmoothScroller::slotAnimationStateChanged(QAbstractAnimation::State newState
,
186 QAbstractAnimation::State oldState
)
189 if (newState
== QAbstractAnimation::Stopped
&& m_smoothScrolling
&& !m_scrollBarPressed
) {
190 m_smoothScrolling
= false;
194 void KItemListSmoothScroller::handleWheelEvent(QWheelEvent
* event
)
196 const bool previous
= m_smoothScrolling
;
198 m_smoothScrolling
= true;
200 if (!event
->pixelDelta().isNull()) {
201 numPixels
= event
->pixelDelta().y();
203 const int numDegrees
= event
->angleDelta().y() / 8;
204 const int numSteps
= qApp
->wheelScrollLines() * numDegrees
/ 15;
205 numPixels
= numSteps
* m_scrollBar
->pageStep() / 12;
207 int value
= m_scrollBar
->value();
208 if (event
->modifiers().testFlag(Qt::ShiftModifier
)) {
209 const int scrollingDirection
= numPixels
> 0 ? 1 : -1;
210 value
-= m_scrollBar
->pageStep() * scrollingDirection
;
214 m_scrollBar
->setValue(value
);
216 m_smoothScrolling
= previous
;