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