1 /***************************************************************************
2 amarokslider.cpp - description
3 -------------------
4 begin : Dec 15 2003
5 copyright : (C) 2003 by Mark Kretschmann
6 email : markey@web.de
7 copyright : (C) 2005 by Gábor Lehel
8 email : illissius@gmail.com
9 ***************************************************************************/
10
11 /***************************************************************************
12 * *
13 * This program is free software; you can redistribute it and/or modify *
14 * it under the terms of the GNU General Public License as published by *
15 * the Free Software Foundation; either version 2 of the License, or *
16 * (at your option) any later version. *
17 * *
18 ***************************************************************************/
19
20 #include "config.h"
21
22 #include "volumeslider.h"
23
24 #include <QApplication>
25 #include <QWidget>
26 #include <QHash>
27 #include <QString>
28 #include <QStringBuilder>
29 #include <QImage>
30 #include <QPainter>
31 #include <QPainterPath>
32 #include <QPalette>
33 #include <QFont>
34 #include <QBrush>
35 #include <QPen>
36 #include <QPoint>
37 #include <QPolygon>
38 #include <QRect>
39 #include <QVector>
40 #include <QMenu>
41 #include <QStyle>
42 #include <QStyleOption>
43 #include <QTimer>
44 #include <QAction>
45 #include <QSlider>
46 #include <QLinearGradient>
47 #include <QStyleOptionViewItem>
48 #include <QFlags>
49 #include <QtEvents>
50
SliderSlider(const Qt::Orientation orientation,QWidget * parent,const uint max)51 SliderSlider::SliderSlider(const Qt::Orientation orientation, QWidget *parent, const uint max)
52 : QSlider(orientation, parent),
53 sliding_(false),
54 outside_(false),
55 prev_value_(0) {
56
57 setRange(0, max);
58
59 }
60
wheelEvent(QWheelEvent * e)61 void SliderSlider::wheelEvent(QWheelEvent *e) {
62
63 if (orientation() == Qt::Vertical) {
64 // Will be handled by the parent widget
65 e->ignore();
66 return;
67 }
68
69 // Position Slider (horizontal)
70 int step = e->angleDelta().y() * 1500 / 18;
71 int nval = qBound(minimum(), QSlider::value() + step, maximum());
72
73 QSlider::setValue(nval);
74
75 emit sliderReleased(value());
76
77 }
78
mouseMoveEvent(QMouseEvent * e)79 void SliderSlider::mouseMoveEvent(QMouseEvent *e) {
80
81 if (sliding_) {
82 // feels better, but using set value of 20 is bad of course
83 QRect rect(-20, -20, width() + 40, height() + 40);
84
85 if (orientation() == Qt::Horizontal && !rect.contains(e->pos())) {
86 if (!outside_) QSlider::setValue(prev_value_);
87 outside_ = true;
88 }
89 else {
90 outside_ = false;
91 slideEvent(e);
92 emit sliderMoved(value());
93 }
94 }
95 else {
96 QSlider::mouseMoveEvent(e);
97 }
98
99 }
100
slideEvent(QMouseEvent * e)101 void SliderSlider::slideEvent(QMouseEvent *e) {
102
103 QStyleOptionSlider option;
104 initStyleOption(&option);
105 QRect sliderRect(style()->subControlRect(QStyle::CC_Slider, &option, QStyle::SC_SliderHandle, this));
106
107 QSlider::setValue(
108 orientation() == Qt::Horizontal
109 ? ((QApplication::layoutDirection() == Qt::RightToLeft)
110 ? QStyle::sliderValueFromPosition(
111 minimum(), maximum(),
112 width() - (e->pos().x() - sliderRect.width() / 2),
113 width() + sliderRect.width(), true)
114 : QStyle::sliderValueFromPosition(
115 minimum(), maximum(),
116 e->pos().x() - sliderRect.width() / 2,
117 width() - sliderRect.width()))
118 : QStyle::sliderValueFromPosition(
119 minimum(), maximum(), e->pos().y() - sliderRect.height() / 2,
120 height() - sliderRect.height()));
121
122 }
123
mousePressEvent(QMouseEvent * e)124 void SliderSlider::mousePressEvent(QMouseEvent *e) {
125
126 QStyleOptionSlider option;
127 initStyleOption(&option);
128 QRect sliderRect(style()->subControlRect(QStyle::CC_Slider, &option, QStyle::SC_SliderHandle, this));
129
130 sliding_ = true;
131 prev_value_ = QSlider::value();
132
133 if (!sliderRect.contains(e->pos())) mouseMoveEvent(e);
134
135 }
136
mouseReleaseEvent(QMouseEvent *)137 void SliderSlider::mouseReleaseEvent(QMouseEvent*) {
138
139 if (!outside_ && QSlider::value() != prev_value_) {
140 emit sliderReleased(value());
141 }
142
143 sliding_ = false;
144 outside_ = false;
145
146 }
147
setValue(int newValue)148 void SliderSlider::setValue(int newValue) {
149 // don't adjust the slider while the user is dragging it!
150
151 if (!sliding_ || outside_) {
152 QSlider::setValue(adjustValue(newValue));
153 }
154 else {
155 prev_value_ = newValue;
156 }
157
158 }
159
160 //////////////////////////////////////////////////////////////////////////////////////////
161 /// CLASS PrettySlider
162 //////////////////////////////////////////////////////////////////////////////////////////
163
164 #define THICKNESS 7
165 #define MARGIN 3
166
PrettySlider(const Qt::Orientation orientation,const SliderMode mode,QWidget * parent,const uint max)167 PrettySlider::PrettySlider(const Qt::Orientation orientation, const SliderMode mode, QWidget *parent, const uint max)
168 : SliderSlider(orientation, parent, max), m_mode(mode) {
169
170 if (m_mode == Pretty) {
171 setFocusPolicy(Qt::NoFocus);
172 }
173
174 }
175
mousePressEvent(QMouseEvent * e)176 void PrettySlider::mousePressEvent(QMouseEvent *e) {
177
178 SliderSlider::mousePressEvent(e);
179
180 slideEvent(e);
181
182 }
183
slideEvent(QMouseEvent * e)184 void PrettySlider::slideEvent(QMouseEvent *e) {
185
186 if (m_mode == Pretty) {
187 QSlider::setValue(orientation() == Qt::Horizontal ? QStyle::sliderValueFromPosition(minimum(), maximum(), e->pos().x(), width() - 2) : QStyle::sliderValueFromPosition(minimum(), maximum(), e->pos().y(), height() - 2)); // clazy:exclude=skipped-base-method
188 }
189 else {
190 SliderSlider::slideEvent(e);
191 }
192
193 }
194
195 namespace Amarok {
196 namespace ColorScheme {
197 extern QColor Background;
198 extern QColor Foreground;
199 } // namespace ColorScheme
200 } // namespace Amarok
201
202 #if 0
203 /** these functions aren't required in our fixed size world, but they may become useful one day **/
204
205 QSize PrettySlider::minimumSizeHint() const {
206 return sizeHint();
207 }
208
209 QSize PrettySlider::sizeHint() const {
210 constPolish();
211
212 return (orientation() == Horizontal
213 ? QSize( maxValue(), THICKNESS + MARGIN )
214 : QSize( THICKNESS + MARGIN, maxValue() )).expandedTo( QApplit ication::globalStrut() );
215 }
216 #endif
217
218 //////////////////////////////////////////////////////////////////////////////////////////
219 /// CLASS VolumeSlider
220 //////////////////////////////////////////////////////////////////////////////////////////
221
VolumeSlider(QWidget * parent,const uint max)222 VolumeSlider::VolumeSlider(QWidget *parent, const uint max)
223 : SliderSlider(Qt::Horizontal, parent, max),
224 anim_enter_(false),
225 anim_count_(0),
226 timer_anim_(new QTimer(this)),
227 pixmap_inset_(QPixmap(drawVolumePixmap())) {
228
229 setFocusPolicy(Qt::NoFocus);
230
231 // Store theme colors to check theme change at paintEvent
232 previous_theme_text_color_ = palette().color(QPalette::WindowText);
233 previous_theme_highlight_color_ = palette().color(QPalette::Highlight);
234
235 drawVolumeSliderHandle();
236 generateGradient();
237
238 setMinimumWidth(pixmap_inset_.width());
239 setMinimumHeight(pixmap_inset_.height());
240
241 QObject::connect(timer_anim_, &QTimer::timeout, this, &VolumeSlider::slotAnimTimer);
242
243 }
244
SetEnabled(const bool enabled)245 void VolumeSlider::SetEnabled(const bool enabled) {
246 QSlider::setEnabled(enabled);
247 QSlider::setVisible(enabled);
248 }
249
generateGradient()250 void VolumeSlider::generateGradient() {
251
252 const QImage mask(":/pictures/volumeslider-gradient.png");
253
254 QImage gradient_image(mask.size(), QImage::Format_ARGB32_Premultiplied);
255 QPainter p(&gradient_image);
256
257 QLinearGradient gradient(gradient_image.rect().topLeft(), gradient_image.rect().topRight());
258 gradient.setColorAt(0, palette().color(QPalette::Window));
259 gradient.setColorAt(1, palette().color(QPalette::Highlight));
260 p.fillRect(gradient_image.rect(), QBrush(gradient));
261
262 p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
263 p.drawImage(0, 0, mask);
264 p.end();
265
266 pixmap_gradient_ = QPixmap::fromImage(gradient_image);
267
268 }
269
slotAnimTimer()270 void VolumeSlider::slotAnimTimer() {
271
272 if (anim_enter_) {
273 ++anim_count_;
274 update();
275 if (anim_count_ == ANIM_MAX - 1) timer_anim_->stop();
276 }
277 else {
278 --anim_count_;
279 update();
280 if (anim_count_ == 0) timer_anim_->stop();
281 }
282
283 }
284
mousePressEvent(QMouseEvent * e)285 void VolumeSlider::mousePressEvent(QMouseEvent *e) {
286
287 if (e->button() != Qt::RightButton) {
288 SliderSlider::mousePressEvent(e);
289 slideEvent(e);
290 }
291
292 }
293
contextMenuEvent(QContextMenuEvent * e)294 void VolumeSlider::contextMenuEvent(QContextMenuEvent *e) {
295
296 QHash<QAction*, int> values;
297 QMenu menu;
298 menu.setTitle("Volume");
299 values[menu.addAction("100%")] = 100;
300 values[menu.addAction("80%")] = 80;
301 values[menu.addAction("60%")] = 60;
302 values[menu.addAction("40%")] = 40;
303 values[menu.addAction("20%")] = 20;
304 values[menu.addAction("0%")] = 0;
305
306 QAction *ret = menu.exec(mapToGlobal(e->pos()));
307 if (ret) {
308 QSlider::setValue(values[ret]); // clazy:exclude=skipped-base-method
309 emit sliderReleased(values[ret]);
310 }
311
312 }
313
slideEvent(QMouseEvent * e)314 void VolumeSlider::slideEvent(QMouseEvent *e) {
315 QSlider::setValue(QStyle::sliderValueFromPosition(minimum(), maximum(), e->pos().x(), width() - 2)); // clazy:exclude=skipped-base-method
316 }
317
wheelEvent(QWheelEvent * e)318 void VolumeSlider::wheelEvent(QWheelEvent *e) {
319
320 const uint step = e->angleDelta().y() / (e->angleDelta().x() == 0 ? 30 : -30);
321 QSlider::setValue(SliderSlider::value() + step); // clazy:exclude=skipped-base-method
322 emit sliderReleased(value());
323
324 }
325
paintEvent(QPaintEvent *)326 void VolumeSlider::paintEvent(QPaintEvent*) {
327
328 QPainter p(this);
329
330 const int padding = 7;
331 const int offset = static_cast<int>(static_cast<double>((width() - 2 * padding) * value()) / maximum());
332
333 // If theme changed since last paintEvent, redraw the volume pixmap with new theme colors
334 if (previous_theme_text_color_ != palette().color(QPalette::WindowText)) {
335 pixmap_inset_ = drawVolumePixmap();
336 previous_theme_text_color_ = palette().color(QPalette::WindowText);
337 }
338
339 if (previous_theme_highlight_color_ != palette().color(QPalette::Highlight)) {
340 drawVolumeSliderHandle();
341 previous_theme_highlight_color_ = palette().color(QPalette::Highlight);
342 }
343
344 p.drawPixmap(0, 0, pixmap_gradient_, 0, 0, offset + padding, 0);
345 p.drawPixmap(0, 0, pixmap_inset_);
346 p.drawPixmap(offset - handle_pixmaps_[0].width() / 2 + padding, 0, handle_pixmaps_[anim_count_]);
347
348 // Draw percentage number
349 QStyleOptionViewItem opt;
350 p.setPen(opt.palette.color(QPalette::Normal, QPalette::Text));
351 QFont vol_font(opt.font);
352 vol_font.setPixelSize(9);
353 p.setFont(vol_font);
354 const QRect rect(0, 0, 34, 15);
355 p.drawText(rect, Qt::AlignRight | Qt::AlignVCenter, QString::number(value()) + '%');
356
357 }
358
359 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
enterEvent(QEnterEvent *)360 void VolumeSlider::enterEvent(QEnterEvent*) {
361 #else
362 void VolumeSlider::enterEvent(QEvent*) {
363 #endif
364
365 anim_enter_ = true;
366 anim_count_ = 0;
367
368 timer_anim_->start(ANIM_INTERVAL);
369
370 }
371
372 void VolumeSlider::leaveEvent(QEvent*) {
373
374 // This can happen if you enter and leave the widget quickly
375 if (anim_count_ == 0) anim_count_ = 1;
376
377 anim_enter_ = false;
378 timer_anim_->start(ANIM_INTERVAL);
379
380 }
381
382 void VolumeSlider::paletteChange(const QPalette&) {
383 generateGradient();
384 }
385
386 QPixmap VolumeSlider::drawVolumePixmap () const {
387
388 QPixmap pixmap(112, 36);
389 pixmap.fill(Qt::transparent);
390 QPainter painter(&pixmap);
391 QPen pen(palette().color(QPalette::WindowText), 0.3, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
392 painter.setPen(pen);
393
394 painter.setRenderHint(QPainter::Antialiasing);
395 painter.setRenderHint(QPainter::SmoothPixmapTransform);
396 // Draw volume control pixmap
397 QPolygon poly;
398 poly << QPoint(6, 21) << QPoint(104, 21) << QPoint(104, 7) << QPoint(6, 16) << QPoint(6, 21);
399 QPainterPath path;
400 path.addPolygon(poly);
401 painter.drawPolygon(poly);
402 painter.drawLine(6, 29, 104, 29);
403
404 // Return QPixmap
405 return pixmap;
406
407 }
408
409 void VolumeSlider::drawVolumeSliderHandle() {
410
411 QImage pixmapHandle(":/pictures/volumeslider-handle.png");
412 QImage pixmapHandleGlow(":/pictures/volumeslider-handle_glow.png");
413
414 QImage pixmapHandleGlow_image(pixmapHandleGlow.size(), QImage::Format_ARGB32_Premultiplied);
415 QPainter painter(&pixmapHandleGlow_image);
416
417 painter.setRenderHint(QPainter::Antialiasing);
418 painter.setRenderHint(QPainter::SmoothPixmapTransform);
419
420 // Repaint volume slider handle glow image with theme highlight color
421 painter.fillRect(pixmapHandleGlow_image.rect(), QBrush(palette().color(QPalette::Highlight)));
422 painter.setCompositionMode(QPainter::CompositionMode_DestinationIn);
423 painter.drawImage(0, 0, pixmapHandleGlow);
424
425 // Overlay the volume slider handle image
426 painter.setCompositionMode(QPainter::CompositionMode_SourceAtop);
427 painter.drawImage(0, 0, pixmapHandle);
428
429 // BEGIN Calculate handle animation pixmaps for mouse-over effect
430 float opacity = 0.0;
431 const float step = 1.0 / ANIM_MAX;
432 QImage dst;
433 handle_pixmaps_.clear();
434 for (int i = 0; i < ANIM_MAX; ++i) {
435 dst = pixmapHandle.copy();
436
437 QPainter p(&dst);
438 p.setOpacity(opacity);
439 p.drawImage(0, 0, pixmapHandleGlow_image);
440 p.end();
441
442 handle_pixmaps_.append(QPixmap::fromImage(dst));
443 opacity += step;
444 }
445 // END
446
447 }
448