1 /*
2  * Cantata
3  *
4  * Copyright (c) 2011-2020 Craig Drummond <craig.p.drummond@gmail.com>
5  *
6  * ----
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; see the file COPYING.  If not, write to
20  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21  * Boston, MA 02110-1301, USA.
22  */
23 
24 #include "config.h"
25 #include "volumeslider.h"
26 #include "mpd-interface/mpdconnection.h"
27 #include "mpd-interface/mpdstatus.h"
28 #ifdef ENABLE_HTTP_STREAM_PLAYBACK
29 #include "mpd-interface/httpstream.h"
30 #endif
31 #include "support/action.h"
32 #include "support/actioncollection.h"
33 #include "gui/stdactions.h"
34 #include "support/utils.h"
35 #include "gui/settings.h"
36 #include <QStyle>
37 #include <QPainter>
38 #include <QPainterPath>
39 #include <QProxyStyle>
40 #include <QApplication>
41 #include <QLabel>
42 #include <QMouseEvent>
43 #include <QWheelEvent>
44 #include <QMenu>
45 
46 class VolumeSliderProxyStyle : public QProxyStyle
47 {
48 public:
VolumeSliderProxyStyle()49     VolumeSliderProxyStyle()
50         : QProxyStyle()
51     {
52         setBaseStyle(qApp->style());
53     }
54 
styleHint(StyleHint stylehint,const QStyleOption * opt,const QWidget * widget,QStyleHintReturn * returnData) const55     int styleHint(StyleHint stylehint, const QStyleOption *opt, const QWidget *widget, QStyleHintReturn *returnData) const override
56     {
57         if (SH_Slider_AbsoluteSetButtons==stylehint) {
58             return Qt::LeftButton|QProxyStyle::styleHint(stylehint, opt, widget, returnData);
59         } else {
60             return QProxyStyle::styleHint(stylehint, opt, widget, returnData);
61         }
62     }
63 };
64 
65 static int widthStep=4;
66 static int constHeightStep=2;
67 
VolumeSlider(bool isMpd,QWidget * p)68 VolumeSlider::VolumeSlider(bool isMpd, QWidget *p)
69     : QSlider(p)
70     , isMpdVol(isMpd)
71     , isActive(isMpd)
72     , lineWidth(0)
73     , down(false)
74     , muteAction(nullptr)
75     , menu(nullptr)
76 {
77     widthStep=4;
78     setRange(0, 100);
79     setPageStep(Settings::self()->volumeStep());
80     lineWidth=Utils::scaleForDpi(1);
81 
82     int w=lineWidth*widthStep*19;
83     int h=lineWidth*constHeightStep*10;
84     setFixedHeight(h+1);
85     setFixedWidth(w);
86     setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
87     setOrientation(Qt::Horizontal);
88     setFocusPolicy(Qt::NoFocus);
89     setStyle(new VolumeSliderProxyStyle());
90     setStyleSheet(QString("QSlider::groove:horizontal {border: 0px;} "
91                           "QSlider::sub-page:horizontal {border: 0px;} "
92                           "QSlider::handle:horizontal {width: 0px; height:0px; margin:0;}"));
93     textCol=Utils::clampColor(palette().color(QPalette::Active, QPalette::Text));
94     generatePixmaps();
95 }
96 
initActions()97 void VolumeSlider::initActions()
98 {
99     if (muteAction) {
100         return;
101     }
102     muteAction = ActionCollection::get()->createAction("mute", tr("Mute"));
103     addAction(muteAction);
104     connect(muteAction, SIGNAL(triggered()), this, SLOT(muteToggled()));
105     connect(StdActions::self()->increaseVolumeAction, SIGNAL(triggered()), this, SLOT(increaseVolume()));
106     connect(StdActions::self()->decreaseVolumeAction, SIGNAL(triggered()), this, SLOT(decreaseVolume()));
107     if (isMpdVol) {
108         connect(MPDStatus::self(), SIGNAL(updated()), this, SLOT(updateStatus()));
109         connect(this, SIGNAL(valueChanged(int)), MPDConnection::self(), SLOT(setVolume(int)));
110         connect(this, SIGNAL(toggleMute()), MPDConnection::self(), SLOT(toggleMute()));
111     }
112     #ifdef ENABLE_HTTP_STREAM_PLAYBACK
113     else {
114         connect(this, SIGNAL(valueChanged(int)), HttpStream::self(), SLOT(setVolume(int)));
115         connect(this, SIGNAL(toggleMute()), HttpStream::self(), SLOT(toggleMute()));
116         connect(HttpStream::self(), SIGNAL(update()), this, SLOT(updateStatus()));
117         connect(HttpStream::self(), SIGNAL(isEnabled(bool)), this, SLOT(setEnabled(bool)));
118         connect(HttpStream::self(), SIGNAL(isEnabled(bool)), this, SIGNAL(stateChanged()));
119     }
120     #endif
121     addAction(StdActions::self()->increaseVolumeAction);
122     addAction(StdActions::self()->decreaseVolumeAction);
123 }
124 
setColor(const QColor & col)125 void VolumeSlider::setColor(const QColor &col)
126 {
127     if (col!=textCol) {
128         textCol=col;
129         generatePixmaps();
130     }
131 }
132 
paintEvent(QPaintEvent *)133 void VolumeSlider::paintEvent(QPaintEvent *)
134 {
135     bool reverse=isRightToLeft();
136     QPainter p(this);
137     #ifdef ENABLE_HTTP_STREAM_PLAYBACK
138     bool muted = isMpdVol ? MPDConnection::self()->isMuted() : HttpStream::self()->isMuted();
139     #else
140     bool muted = MPDConnection::self()->isMuted();
141     #endif
142     if (muted || !isEnabled()) {
143         p.setOpacity(0.25);
144     }
145 
146     p.drawPixmap(0, 0, pixmaps[0]);
147     #if 1
148     int steps=(value()/10.0)+0.5;
149     if (steps>0) {
150         if (steps<10) {
151             int wStep=widthStep*lineWidth;
152             p.setClipRect(reverse
153                             ? QRect(width()-((steps*wStep*2)-wStep), 0, width(), height())
154                             : QRect(0, 0, (steps*wStep*2)-wStep, height()));
155             p.setClipping(true);
156         }
157         p.drawPixmap(0, 0, pixmaps[1]);
158         if (steps<10) {
159             p.setClipping(false);
160         }
161     }
162     #else // Partial filling of each block?
163     if (value()>0) {
164         if (value()<100) {
165             int fillWidth=(width()*(0.01*value()))+0.5;
166             p.setClipRect(reverse
167                             ? QRect(width()-fillWidth, 0, width(), height())
168                             : QRect(0, 0, fillWidth, height()));
169             p.setClipping(true);
170         }
171         p.drawPixmap(0, 0, *(pixmaps[1]));
172         if (value()<100) {
173             p.setClipping(false);
174         }
175     }
176     #endif
177 
178     if (!muted) {
179         p.setOpacity(p.opacity()*0.75);
180         p.setPen(textCol);
181         QFont f(font());
182         f.setPixelSize(qMax(height()/2.5, 8.0));
183         p.setFont(f);
184         QRect r=rect();
185         bool rtl=isRightToLeft();
186         if (rtl) {
187             r.setX(widthStep*lineWidth*12);
188         } else {
189             r.setWidth(widthStep*lineWidth*7);
190         }
191         p.drawText(r, Qt::AlignRight, QString("%1%").arg(value()));
192     }
193 }
194 
mousePressEvent(QMouseEvent * ev)195 void VolumeSlider::mousePressEvent(QMouseEvent *ev)
196 {
197     if (Qt::MiddleButton==ev->buttons()) {
198         down=true;
199     } else {
200         QSlider::mousePressEvent(ev);
201     }
202 }
203 
mouseReleaseEvent(QMouseEvent * ev)204 void VolumeSlider::mouseReleaseEvent(QMouseEvent *ev)
205 {
206     if (down) {
207         down=false;
208         muteAction->trigger();
209         update();
210     } else {
211         QSlider::mouseReleaseEvent(ev);
212     }
213 }
214 
contextMenuEvent(QContextMenuEvent * ev)215 void VolumeSlider::contextMenuEvent(QContextMenuEvent *ev)
216 {
217     static const char *constValProp="val";
218     if (!menu) {
219         menu=new QMenu(this);
220         muteMenuAction=menu->addAction(tr("Mute"));
221         muteMenuAction->setProperty(constValProp, -1);
222         for (int i=0; i<11; ++i) {
223             menu->addAction(QString("%1%").arg(i*10))->setProperty(constValProp, i*10);
224         }
225     }
226 
227     #ifdef ENABLE_HTTP_STREAM_PLAYBACK
228     bool muted = isMpdVol ? MPDConnection::self()->isMuted() : HttpStream::self()->isMuted();
229     #else
230     bool muted = MPDConnection::self()->isMuted();
231     #endif
232 
233     muteMenuAction->setText(muted ? tr("Unmute") : tr("Mute"));
234     QAction *ret = menu->exec(mapToGlobal(ev->pos()));
235     if (ret) {
236         int val=ret->property(constValProp).toInt();
237         if (-1==val) {
238             muteAction->trigger();
239         } else {
240             setValue(val);
241         }
242     }
243 }
244 
wheelEvent(QWheelEvent * ev)245 void VolumeSlider::wheelEvent(QWheelEvent *ev)
246 {
247     int numDegrees = ev->delta() / 8;
248     int numSteps = numDegrees / 15;
249     if (numSteps > 0) {
250         for (int i = 0; i < numSteps; ++i) {
251             increaseVolume();
252         }
253     } else {
254         for (int i = 0; i > numSteps; --i) {
255             decreaseVolume();
256         }
257     }
258 }
259 
muteToggled()260 void VolumeSlider::muteToggled()
261 {
262     if (isActive) {
263         emit toggleMute();
264     }
265 }
266 
updateStatus()267 void VolumeSlider::updateStatus()
268 {
269     #ifdef ENABLE_HTTP_STREAM_PLAYBACK
270     int volume=isMpdVol ? MPDStatus::self()->volume() : HttpStream::self()->volume();
271     #else
272     int volume=MPDStatus::self()->volume();
273     #endif
274 
275     blockSignals(true);
276     if (volume<0) {
277         setValue(0);
278     } else {
279         int unmuteVolume=-1;
280         if (0==volume) {
281             #ifdef ENABLE_HTTP_STREAM_PLAYBACK
282             unmuteVolume=isMpdVol ? MPDConnection::self()->unmuteVolume() : HttpStream::self()->unmuteVolume();
283             #else
284             unmuteVolume=MPDConnection::self()->unmuteVolume();
285             #endif
286             if (unmuteVolume>0) {
287                 volume=unmuteVolume;
288             }
289         }
290         setToolTip(unmuteVolume>0 ? tr("Volume %1% (Muted)").arg(volume) : tr("Volume %1%").arg(volume));
291         setValue(volume);
292     }
293     bool wasEnabled=isEnabled();
294     setEnabled(volume>=0);
295     update();
296     muteAction->setEnabled(isEnabled());
297     StdActions::self()->increaseVolumeAction->setEnabled(isEnabled());
298     StdActions::self()->decreaseVolumeAction->setEnabled(isEnabled());
299     blockSignals(false);
300     if (isEnabled()!=wasEnabled) {
301         emit stateChanged();
302     }
303 }
304 
increaseVolume()305 void VolumeSlider::increaseVolume()
306 {
307     if (isActive) {
308         triggerAction(QAbstractSlider::SliderPageStepAdd);
309     }
310 }
311 
decreaseVolume()312 void VolumeSlider::decreaseVolume()
313 {
314     if (isActive) {
315         triggerAction(QAbstractSlider::SliderPageStepSub);
316     }
317 }
318 
generatePixmaps()319 void VolumeSlider::generatePixmaps()
320 {
321     pixmaps[0]=generatePixmap(false);
322     pixmaps[1]=generatePixmap(true);
323 }
324 
generatePixmap(bool filled)325 QPixmap VolumeSlider::generatePixmap(bool filled)
326 {
327     bool reverse=isRightToLeft();
328     QPixmap pix(size());
329     pix.fill(Qt::transparent);
330     QPainter p(&pix);
331     p.setPen(textCol);
332     for (int i=0; i<10; ++i) {
333         int barHeight=(lineWidth*constHeightStep)*(i+1);
334         QRect r(reverse ? pix.width()-(widthStep+(i*lineWidth*widthStep*2))
335                         : i*lineWidth*widthStep*2,
336                 pix.height()-(barHeight+1), (lineWidth*widthStep)-1, barHeight);
337         if (filled) {
338             p.fillRect(r.adjusted(1, 1, 0, 0), textCol);
339         } else if (lineWidth>1) {
340             p.drawRect(r);
341             p.drawRect(r.adjusted(1, 1, -1, -1));
342         } else {
343             p.drawRect(r);
344         }
345     }
346     return pix;
347 }
348 
349 #include "moc_volumeslider.cpp"
350