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