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