1 /*
2  * volumebutton.cc
3  * Copyright 2014 Ariadne Conill
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  *
8  * 1. Redistributions of source code must retain the above copyright notice,
9  *    this list of conditions, and the following disclaimer.
10  *
11  * 2. Redistributions in binary form must reproduce the above copyright notice,
12  *    this list of conditions, and the following disclaimer in the documentation
13  *    provided with the distribution.
14  *
15  * This software is provided "as is" and without any warranty, express or
16  * implied. In no event shall the authors be liable for any damages arising from
17  * the use of this software.
18  */
19 
20 #include "libaudqt.h"
21 
22 #include <QIcon>
23 #include <QMenu>
24 #include <QSlider>
25 #include <QToolButton>
26 #include <QVBoxLayout>
27 #include <QWheelEvent>
28 #include <QWidgetAction>
29 
30 #include <libaudcore/drct.h>
31 #include <libaudcore/hook.h>
32 #include <libaudcore/runtime.h>
33 
34 namespace audqt
35 {
36 
37 class VolumeButton : public QToolButton
38 {
39 public:
40     VolumeButton(QWidget * parent = nullptr);
41 
42 private:
43     void updateDelta();
44     void updateIcon(int val);
45     void updateVolume();
46     void setVolume(int val);
47     void setUpButton(QToolButton * button, int dir);
48 
49     void wheelEvent(QWheelEvent * e);
50 
51     QMenu m_menu;
52     QWidgetAction m_action;
53     QWidget m_container;
54     QToolButton m_buttons[2];
55     QSlider m_slider;
56     int m_scroll_delta = 0;
57 
58     HookReceiver<VolumeButton> update_hook{"set volume_delta", this,
59                                            &VolumeButton::updateDelta};
60 
61     Timer<VolumeButton> m_timer{TimerRate::Hz4, this,
62                                 &VolumeButton::updateVolume};
63 };
64 
VolumeButton(QWidget * parent)65 VolumeButton::VolumeButton(QWidget * parent)
66     : QToolButton(parent), m_action(this), m_slider(Qt::Vertical)
67 {
68     m_slider.setMinimumHeight(audqt::sizes.OneInch);
69     m_slider.setRange(0, 100);
70 
71     setUpButton(&m_buttons[0], 1);
72     setUpButton(&m_buttons[1], -1);
73 
74     auto layout = make_vbox(&m_container, sizes.TwoPt);
75     layout->setContentsMargins(margins.TwoPt);
76     layout->addWidget(&m_buttons[0]);
77     layout->addWidget(&m_slider);
78     layout->addWidget(&m_buttons[1]);
79     layout->setAlignment(&m_slider, Qt::AlignHCenter);
80 
81     m_action.setDefaultWidget(&m_container);
82     m_menu.addAction(&m_action);
83 
84     setAutoRaise(true);
85     setFocusPolicy(Qt::NoFocus);
86     setMenu(&m_menu);
87     setPopupMode(InstantPopup);
88     setStyleSheet("QToolButton::menu-indicator { image: none; }");
89 
90     int val = aud_drct_get_volume_main();
91     m_slider.setValue(val);
92     m_slider.setFocus();
93 
94     updateIcon(val);
95     updateDelta();
96 
97     connect(&m_slider, &QAbstractSlider::valueChanged, this,
98             &VolumeButton::setVolume);
99 
100     m_timer.start();
101 }
102 
updateDelta()103 void VolumeButton::updateDelta()
104 {
105     int delta = aud_get_int("volume_delta");
106     m_slider.setSingleStep(delta);
107     m_slider.setPageStep(delta);
108 }
109 
updateIcon(int val)110 void VolumeButton::updateIcon(int val)
111 {
112     if (val == 0)
113         setIcon(audqt::get_icon("audio-volume-muted"));
114     else if (val < 34)
115         setIcon(audqt::get_icon("audio-volume-low"));
116     else if (val < 67)
117         setIcon(audqt::get_icon("audio-volume-medium"));
118     else
119         setIcon(audqt::get_icon("audio-volume-high"));
120 
121     setToolTip(QString("%1 %").arg(val));
122 }
123 
updateVolume()124 void VolumeButton::updateVolume()
125 {
126     if (m_slider.isSliderDown())
127         return;
128 
129     int val = aud_drct_get_volume_main();
130     if (val != m_slider.value())
131     {
132         disconnect(&m_slider, nullptr, this, nullptr);
133         m_slider.setValue(val);
134         updateIcon(val);
135         connect(&m_slider, &QAbstractSlider::valueChanged, this,
136                 &VolumeButton::setVolume);
137     }
138 }
139 
setVolume(int val)140 void VolumeButton::setVolume(int val)
141 {
142     aud_drct_set_volume_main(val);
143     updateIcon(val);
144 }
145 
setUpButton(QToolButton * button,int dir)146 void VolumeButton::setUpButton(QToolButton * button, int dir)
147 {
148     button->setText(dir < 0 ? "-" : "+");
149     button->setAutoRaise(true);
150     button->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
151 
152     connect(button, &QAbstractButton::clicked, [this, dir]() {
153         m_slider.setValue(m_slider.value() + dir * aud_get_int("volume_delta"));
154     });
155 }
156 
wheelEvent(QWheelEvent * e)157 void VolumeButton::wheelEvent(QWheelEvent * e)
158 {
159     m_scroll_delta += e->angleDelta().y();
160 
161     /* we want discrete steps here */
162     int steps = m_scroll_delta / 120;
163     if (steps != 0)
164     {
165         m_scroll_delta -= 120 * steps;
166         m_slider.setValue(m_slider.value() +
167                           steps * aud_get_int("volume_delta"));
168     }
169 }
170 
volume_button_new(QWidget * parent)171 EXPORT QToolButton * volume_button_new(QWidget * parent)
172 {
173     return new VolumeButton(parent);
174 }
175 
176 } // namespace audqt
177