1 // vim: set tabstop=4 shiftwidth=4 expandtab:
2 /*
3 Gwenview: an image viewer
4 Copyright 2011 Aurélien Gâteau <agateau@kde.org>
5
6 This program is free software; you can redistribute it and/or
7 modify it under the terms of the GNU General Public License
8 as published by the Free Software Foundation; either version 2
9 of the License, or (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA.
19
20 */
21 // Self
22 #include "hud/hudslider.h"
23
24 // Local
25 #include "gwenview_lib_debug.h"
26 #include <hud/hudtheme.h>
27
28 // KF
29
30 // Qt
31 #include <QApplication>
32 #include <QGraphicsSceneEvent>
33 #include <QPainter>
34 #include <QStyle>
35 #include <QStyleOptionGraphicsItem>
36 #include <QTimer>
37
38 namespace Gwenview
39 {
40 static const int FIRST_REPEAT_DELAY = 500;
41
42 struct HudSliderPrivate {
43 HudSlider *q;
44 int mMin, mMax, mPageStep, mSingleStep;
45 int mSliderPosition;
46 int mRepeatX;
47 QAbstractSlider::SliderAction mRepeatAction;
48 int mValue;
49 bool mIsDown;
50
51 QRectF mHandleRect;
52
hasValidRangeGwenview::HudSliderPrivate53 bool hasValidRange() const
54 {
55 return mMax > mMin;
56 }
57
updateHandleRectGwenview::HudSliderPrivate58 void updateHandleRect()
59 {
60 static const HudTheme::RenderInfo renderInfo = HudTheme::renderInfo(HudTheme::SliderWidgetHandle);
61 static const int radius = renderInfo.borderRadius;
62
63 const QRectF sliderRect = q->boundingRect();
64 const qreal posX = xForPosition(mSliderPosition) - radius;
65 const qreal posY = sliderRect.height() / 2 - radius;
66 mHandleRect = QRectF(posX, posY, radius * 2, radius * 2);
67 }
68
positionForXGwenview::HudSliderPrivate69 int positionForX(qreal x) const
70 {
71 static const HudTheme::RenderInfo renderInfo = HudTheme::renderInfo(HudTheme::SliderWidgetHandle);
72 static const int radius = renderInfo.borderRadius;
73
74 const qreal sliderWidth = q->boundingRect().width();
75
76 x -= radius;
77 if (QApplication::isRightToLeft()) {
78 x = sliderWidth - 2 * radius - x;
79 }
80 return mMin + int(x / (sliderWidth - 2 * radius) * (mMax - mMin));
81 }
82
xForPositionGwenview::HudSliderPrivate83 qreal xForPosition(int pos) const
84 {
85 static const HudTheme::RenderInfo renderInfo = HudTheme::renderInfo(HudTheme::SliderWidgetHandle);
86 static const int radius = renderInfo.borderRadius;
87
88 const qreal sliderWidth = q->boundingRect().width();
89 qreal x = (qreal(pos - mMin) / (mMax - mMin)) * (sliderWidth - 2 * radius);
90 if (QApplication::isRightToLeft()) {
91 x = sliderWidth - 2 * radius - x;
92 }
93 return x + radius;
94 }
95 };
96
HudSlider(QGraphicsItem * parent)97 HudSlider::HudSlider(QGraphicsItem *parent)
98 : QGraphicsWidget(parent)
99 , d(new HudSliderPrivate)
100 {
101 d->q = this;
102 d->mMin = 0;
103 d->mMax = 100;
104 d->mPageStep = 10;
105 d->mSingleStep = 1;
106 d->mSliderPosition = d->mValue = 0;
107 d->mIsDown = false;
108 d->mRepeatAction = QAbstractSlider::SliderNoAction;
109 setCursor(Qt::ArrowCursor);
110 setAcceptHoverEvents(true);
111 setFocusPolicy(Qt::WheelFocus);
112
113 QTimer::singleShot(0, this, [this]() {
114 d->updateHandleRect();
115 });
116 }
117
~HudSlider()118 HudSlider::~HudSlider()
119 {
120 delete d;
121 }
122
paint(QPainter * painter,const QStyleOptionGraphicsItem * option,QWidget *)123 void HudSlider::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *)
124 {
125 bool drawHandle = d->hasValidRange();
126 HudTheme::State state;
127 if (drawHandle && option->state.testFlag(QStyle::State_MouseOver)) {
128 state = d->mIsDown ? HudTheme::DownState : HudTheme::MouseOverState;
129 } else {
130 state = HudTheme::NormalState;
131 }
132 painter->setRenderHint(QPainter::Antialiasing);
133
134 const QRectF sliderRect = boundingRect();
135
136 // Groove
137 HudTheme::RenderInfo renderInfo = HudTheme::renderInfo(HudTheme::SliderWidgetGroove, state);
138 painter->setPen(renderInfo.borderPen);
139 painter->setBrush(renderInfo.bgBrush);
140 qreal centerY = d->mHandleRect.center().y();
141 QRectF grooveRect = QRectF(0, centerY - renderInfo.borderRadius, sliderRect.width(), 2 * renderInfo.borderRadius);
142
143 if (drawHandle) {
144 // Clip out handle
145 QPainterPath clipPath;
146 clipPath.addRect(QRectF(QPointF(0, 0), d->mHandleRect.bottomLeft()).adjusted(0, 0, 1, 0));
147 clipPath.addRect(QRectF(d->mHandleRect.topRight(), sliderRect.bottomRight()).adjusted(-1, 0, 0, 0));
148 painter->setClipPath(clipPath);
149 }
150 painter->drawRoundedRect(grooveRect.adjusted(.5, .5, -.5, -.5), renderInfo.borderRadius, renderInfo.borderRadius);
151 if (!drawHandle) {
152 return;
153 }
154 painter->setClipping(false);
155
156 // Handle
157 renderInfo = HudTheme::renderInfo(HudTheme::SliderWidgetHandle, state);
158 painter->setPen(renderInfo.borderPen);
159 painter->setBrush(renderInfo.bgBrush);
160 painter->drawRoundedRect(d->mHandleRect.adjusted(.5, .5, -.5, -.5), renderInfo.borderRadius, renderInfo.borderRadius);
161 }
162
mousePressEvent(QGraphicsSceneMouseEvent * event)163 void HudSlider::mousePressEvent(QGraphicsSceneMouseEvent *event)
164 {
165 if (!d->hasValidRange()) {
166 return;
167 }
168 const int pos = d->positionForX(event->pos().x());
169 if (d->mHandleRect.contains(event->pos())) {
170 switch (event->button()) {
171 case Qt::LeftButton:
172 d->mIsDown = true;
173 break;
174 case Qt::MiddleButton:
175 setSliderPosition(pos);
176 triggerAction(QAbstractSlider::SliderMove);
177 break;
178 default:
179 break;
180 }
181 } else {
182 d->mRepeatX = event->pos().x();
183 d->mRepeatAction = pos < d->mSliderPosition ? QAbstractSlider::SliderPageStepSub : QAbstractSlider::SliderPageStepAdd;
184 doRepeatAction(FIRST_REPEAT_DELAY);
185 }
186 update();
187 }
188
mouseMoveEvent(QGraphicsSceneMouseEvent * event)189 void HudSlider::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
190 {
191 if (!d->hasValidRange()) {
192 return;
193 }
194 if (d->mIsDown) {
195 setSliderPosition(d->positionForX(event->pos().x()));
196 triggerAction(QAbstractSlider::SliderMove);
197 update();
198 }
199 }
200
mouseReleaseEvent(QGraphicsSceneMouseEvent *)201 void HudSlider::mouseReleaseEvent(QGraphicsSceneMouseEvent * /*event*/)
202 {
203 if (!d->hasValidRange()) {
204 return;
205 }
206 d->mIsDown = false;
207 d->mRepeatAction = QAbstractSlider::SliderNoAction;
208 update();
209 }
210
wheelEvent(QGraphicsSceneWheelEvent * event)211 void HudSlider::wheelEvent(QGraphicsSceneWheelEvent *event)
212 {
213 if (!d->hasValidRange()) {
214 return;
215 }
216 int step = qMin(QApplication::wheelScrollLines() * d->mSingleStep, d->mPageStep);
217 if ((event->modifiers() & Qt::ControlModifier) || (event->modifiers() & Qt::ShiftModifier)) {
218 step = d->mPageStep;
219 }
220 setSliderPosition(d->mSliderPosition + event->delta() * step / 120);
221 triggerAction(QAbstractSlider::SliderMove);
222 }
223
keyPressEvent(QKeyEvent * event)224 void HudSlider::keyPressEvent(QKeyEvent *event)
225 {
226 if (!d->hasValidRange()) {
227 return;
228 }
229 bool rtl = QApplication::isRightToLeft();
230 switch (event->key()) {
231 case Qt::Key_Left:
232 triggerAction(rtl ? QAbstractSlider::SliderSingleStepAdd : QAbstractSlider::SliderSingleStepSub);
233 break;
234 case Qt::Key_Right:
235 triggerAction(rtl ? QAbstractSlider::SliderSingleStepSub : QAbstractSlider::SliderSingleStepAdd);
236 break;
237 case Qt::Key_PageUp:
238 triggerAction(QAbstractSlider::SliderPageStepSub);
239 break;
240 case Qt::Key_PageDown:
241 triggerAction(QAbstractSlider::SliderPageStepAdd);
242 break;
243 case Qt::Key_Home:
244 triggerAction(QAbstractSlider::SliderToMinimum);
245 break;
246 case Qt::Key_End:
247 triggerAction(QAbstractSlider::SliderToMaximum);
248 break;
249 default:
250 event->ignore();
251 break;
252 }
253 }
254
keyReleaseEvent(QKeyEvent *)255 void HudSlider::keyReleaseEvent(QKeyEvent * /*event*/)
256 {
257 if (!d->hasValidRange()) {
258 return;
259 }
260 d->mRepeatAction = QAbstractSlider::SliderNoAction;
261 }
262
setRange(int min,int max)263 void HudSlider::setRange(int min, int max)
264 {
265 if (min == d->mMin && max == d->mMax) {
266 return;
267 }
268 d->mMin = min;
269 d->mMax = max;
270 setValue(d->mValue); // ensure value is within min and max
271 d->updateHandleRect();
272 update();
273 }
274
setPageStep(int step)275 void HudSlider::setPageStep(int step)
276 {
277 d->mPageStep = step;
278 }
279
setSingleStep(int step)280 void HudSlider::setSingleStep(int step)
281 {
282 d->mSingleStep = step;
283 }
284
setValue(int value)285 void HudSlider::setValue(int value)
286 {
287 value = qBound(d->mMin, value, d->mMax);
288 if (value != d->mValue) {
289 d->mValue = value;
290 setSliderPosition(value);
291 update();
292 Q_EMIT valueChanged(d->mValue);
293 }
294 }
295
sliderPosition() const296 int HudSlider::sliderPosition() const
297 {
298 return d->mSliderPosition;
299 }
300
setSliderPosition(int pos)301 void HudSlider::setSliderPosition(int pos)
302 {
303 pos = qBound(d->mMin, pos, d->mMax);
304 if (pos != d->mSliderPosition) {
305 d->mSliderPosition = pos;
306 d->updateHandleRect();
307 update();
308 }
309 }
310
isSliderDown() const311 bool HudSlider::isSliderDown() const
312 {
313 return d->mIsDown;
314 }
315
triggerAction(QAbstractSlider::SliderAction action)316 void HudSlider::triggerAction(QAbstractSlider::SliderAction action)
317 {
318 switch (action) {
319 case QAbstractSlider::SliderSingleStepAdd:
320 setSliderPosition(d->mValue + d->mSingleStep);
321 break;
322 case QAbstractSlider::SliderSingleStepSub:
323 setSliderPosition(d->mValue - d->mSingleStep);
324 break;
325 case QAbstractSlider::SliderPageStepAdd:
326 setSliderPosition(d->mValue + d->mPageStep);
327 break;
328 case QAbstractSlider::SliderPageStepSub:
329 setSliderPosition(d->mValue - d->mPageStep);
330 break;
331 case QAbstractSlider::SliderToMinimum:
332 setSliderPosition(d->mMin);
333 break;
334 case QAbstractSlider::SliderToMaximum:
335 setSliderPosition(d->mMax);
336 break;
337 case QAbstractSlider::SliderMove:
338 case QAbstractSlider::SliderNoAction:
339 break;
340 };
341 Q_EMIT actionTriggered(action);
342 setValue(d->mSliderPosition);
343 }
344
doRepeatAction(int time)345 void HudSlider::doRepeatAction(int time)
346 {
347 int step = 0;
348 switch (d->mRepeatAction) {
349 case QAbstractSlider::SliderSingleStepAdd:
350 case QAbstractSlider::SliderSingleStepSub:
351 step = d->mSingleStep;
352 break;
353 case QAbstractSlider::SliderPageStepAdd:
354 case QAbstractSlider::SliderPageStepSub:
355 step = d->mPageStep;
356 break;
357 case QAbstractSlider::SliderToMinimum:
358 case QAbstractSlider::SliderToMaximum:
359 case QAbstractSlider::SliderMove:
360 qCWarning(GWENVIEW_LIB_LOG) << "Not much point in repeating action of type" << d->mRepeatAction;
361 return;
362 case QAbstractSlider::SliderNoAction:
363 return;
364 }
365
366 int pos = d->positionForX(d->mRepeatX);
367 if (qAbs(pos - d->mSliderPosition) >= step) {
368 // We are far enough from the position where the mouse button was held
369 // down to be able to repeat the action one more time
370 triggerAction(d->mRepeatAction);
371 QTimer::singleShot(time, this, SLOT(doRepeatAction()));
372 } else {
373 // We are too close to the held down position, reach the position and
374 // don't repeat
375 d->mRepeatAction = QAbstractSlider::SliderNoAction;
376 setSliderPosition(pos);
377 triggerAction(QAbstractSlider::SliderMove);
378 return;
379 }
380 }
381
382 } // namespace
383