Vkeybd: add a mod-wheel
[ardour.git] / gtk2_ardour / transport_control_ui.cc
1 /*
2  * Copyright (C) 2017 Robin Gareus <robin@gareus.org>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17  */
18
19 #include <gtkmm/sizegroup.h>
20
21 #include "ardour/dB.h"
22 #include "ardour/profile.h"
23 #include "widgets/tooltips.h"
24 #include "gtkmm2ext/gui_thread.h"
25
26 #include "actions.h"
27 #include "ardour_ui.h"
28 #include "timers.h"
29 #include "transport_control_ui.h"
30
31 #include "pbd/i18n.h"
32
33 using namespace Glib;
34 using namespace Gtk;
35 using namespace ARDOUR;
36 using namespace ArdourWidgets;
37
38 TransportControlUI::TransportControlUI ()
39 {
40         Config->ParameterChanged.connect (config_connection, MISSING_INVALIDATOR, boost::bind (&TransportControlUI::parameter_changed, this, _1), gui_context());
41 }
42
43 void
44 TransportControlUI::map_actions ()
45 {
46         /* setup actions */
47         RefPtr<Action> act;
48
49         act = ActionManager::get_action (X_("Transport"), X_("ToggleClick"));
50         click_button.set_related_action (act);
51         act = ActionManager::get_action (X_("Transport"), X_("Stop"));
52         stop_button.set_related_action (act);
53         act = ActionManager::get_action (X_("Transport"), X_("Roll"));
54         roll_button.set_related_action (act);
55         act = ActionManager::get_action (X_("Transport"), X_("Record"));
56         rec_button.set_related_action (act);
57         act = ActionManager::get_action (X_("Transport"), X_("GotoStart"));
58         goto_start_button.set_related_action (act);
59         act = ActionManager::get_action (X_("Transport"), X_("GotoEnd"));
60         goto_end_button.set_related_action (act);
61         act = ActionManager::get_action (X_("Transport"), X_("Loop"));
62         auto_loop_button.set_related_action (act);
63         act = ActionManager::get_action (X_("Transport"), X_("PlaySelection"));
64         play_selection_button.set_related_action (act);
65
66         act = ActionManager::get_action (X_("MIDI"), X_("panic"));
67         midi_panic_button.set_related_action (act);
68
69         /* tooltips depend on actions */
70         set_tooltip (roll_button, _("Play from playhead"));
71         set_tooltip (stop_button, _("Stop playback"));
72         set_tooltip (rec_button, _("Toggle record"));
73         set_tooltip (play_selection_button, _("Play range/selection"));
74         set_tooltip (goto_start_button, _("Go to start of session"));
75         set_tooltip (goto_end_button, _("Go to end of session"));
76         set_tooltip (auto_loop_button, _("Play loop range"));
77         set_tooltip (midi_panic_button, _("MIDI Panic\nSend note off and reset controller messages on all MIDI channels"));
78
79         /* set click_button tooltip */
80         parameter_changed ("click-gain");
81 }
82
83 void
84 TransportControlUI::setup (TransportControlProvider* ui)
85 {
86         click_button.signal_button_press_event().connect (sigc::mem_fun (*ui, &TransportControlProvider::click_button_clicked), false);
87         click_button.signal_scroll_event().connect (sigc::mem_fun (*this, &TransportControlUI::click_button_scroll), false);
88
89         /* setup icons */
90
91         click_button.set_icon (ArdourIcon::TransportMetronom);
92         goto_start_button.set_icon (ArdourIcon::TransportStart);
93         goto_end_button.set_icon (ArdourIcon::TransportEnd);
94         roll_button.set_icon (ArdourIcon::TransportPlay);
95         stop_button.set_icon (ArdourIcon::TransportStop);
96         play_selection_button.set_icon (ArdourIcon::TransportRange);
97         auto_loop_button.set_icon (ArdourIcon::TransportLoop);
98         rec_button.set_icon (ArdourIcon::RecButton);
99         midi_panic_button.set_icon (ArdourIcon::TransportPanic);
100
101         /* transport control size-group */
102
103         Glib::RefPtr<SizeGroup> transport_button_size_group = SizeGroup::create (SIZE_GROUP_BOTH);
104         transport_button_size_group->add_widget (goto_start_button);
105         transport_button_size_group->add_widget (goto_end_button);
106         transport_button_size_group->add_widget (auto_loop_button);
107         transport_button_size_group->add_widget (rec_button);
108         if (!ARDOUR::Profile->get_mixbus()) {
109                 /*note: since we aren't showing this button, it doesn't get allocated
110                  * and therefore blows-up the size-group.  so remove it.
111                  */
112                 transport_button_size_group->add_widget (play_selection_button);
113         }
114         transport_button_size_group->add_widget (roll_button);
115         transport_button_size_group->add_widget (stop_button);
116
117         transport_button_size_group->add_widget (midi_panic_button);
118         transport_button_size_group->add_widget (click_button);
119
120 #define PX_SCALE(px) std::max((float)px, rintf((float)px * UIConfiguration::instance().get_ui_scale()))
121
122         click_button.set_size_request (PX_SCALE(20), PX_SCALE(20));
123         set_spacing (PX_SCALE(2));
124
125 #undef PX_SCALE
126
127         if (!ARDOUR::Profile->get_mixbus()) {
128                 pack_start (midi_panic_button, true, true, 0);
129         } else {
130                 pack_start (midi_panic_button, true, true, 3);
131         }
132         pack_start (click_button, true, true, 0);
133         pack_start (goto_start_button, true, true);
134         pack_start (goto_end_button, true, true);
135         pack_start (auto_loop_button, true, true);
136         if (!ARDOUR::Profile->get_mixbus()) {
137                 pack_start (play_selection_button, true, true);
138         }
139         pack_start (roll_button, true, true);
140         pack_start (stop_button, true, true);
141         pack_start (rec_button, true, true, 3);
142
143         roll_button.set_name ("transport button");
144         stop_button.set_name ("transport button");
145         goto_start_button.set_name ("transport button");
146         goto_end_button.set_name ("transport button");
147         auto_loop_button.set_name ("transport button");
148         play_selection_button.set_name ("transport button");
149         rec_button.set_name ("transport recenable button");
150         midi_panic_button.set_name ("transport button"); // XXX ???
151         click_button.set_name ("transport button");
152
153         roll_button.set_controllable (ui->roll_controllable);
154         stop_button.set_controllable (ui->stop_controllable);
155         goto_start_button.set_controllable (ui->goto_start_controllable);
156         goto_end_button.set_controllable (ui->goto_end_controllable);
157         auto_loop_button.set_controllable (ui->auto_loop_controllable);
158         play_selection_button.set_controllable (ui->play_selection_controllable);
159         rec_button.set_controllable (ui->rec_controllable);
160
161         stop_button.set_active (true);
162
163         Timers::blink_connect (sigc::mem_fun (*this, &TransportControlUI::blink_rec_enable));
164 }
165
166 void
167 TransportControlUI::set_session (ARDOUR::Session *s)
168 {
169         SessionHandlePtr::set_session (s);
170         set_loop_sensitivity ();
171         map_transport_state ();
172
173         if (!_session) {
174                 rec_button.set_sensitive (false);
175                 return;
176         }
177
178         _session->config.ParameterChanged.connect (_session_connections, MISSING_INVALIDATOR, boost::bind (&TransportControlUI::parameter_changed, this, _1), gui_context());
179         _session->StepEditStatusChange.connect (_session_connections, MISSING_INVALIDATOR, boost::bind (&TransportControlUI::step_edit_status_change, this, _1), gui_context());
180         _session->TransportStateChange.connect (_session_connections, MISSING_INVALIDATOR, boost::bind (&TransportControlUI::map_transport_state, this), gui_context());
181         _session->auto_loop_location_changed.connect (_session_connections, MISSING_INVALIDATOR, boost::bind (&TransportControlUI::set_loop_sensitivity, this), gui_context ());
182
183         rec_button.set_sensitive (true);
184 }
185
186 void
187 TransportControlUI::parameter_changed (std::string p)
188 {
189         if (p == "external-sync") {
190                 set_loop_sensitivity ();
191         } else if (p == "click-record-only") {
192                 // TODO set a flag, blink or gray-out metronome button while rolling, only
193                 if (Config->get_click_record_only()) {
194                         click_button.set_name ("generic button"); // XXX
195                 } else {
196                         click_button.set_name ("transport button");
197                 }
198         } else if (p == "click-gain") {
199                 float gain_db = accurate_coefficient_to_dB (Config->get_click_gain());
200                 char tmp[32];
201                 snprintf(tmp, 31, "%+.1f", gain_db);
202                 set_tooltip (click_button, string_compose (_("Enable/Disable metronome\n\nRight-click to access preferences\nMouse-wheel to modify level\nSignal Level: %1 dBFS"), tmp));
203         }
204 }
205
206 void
207 TransportControlUI::map_transport_state ()
208 {
209         if (!_session) {
210                 auto_loop_button.unset_active_state ();
211                 play_selection_button.unset_active_state ();
212                 roll_button.unset_active_state ();
213                 stop_button.set_active_state (Gtkmm2ext::ExplicitActive);
214                 return;
215         }
216
217         float sp = _session->transport_speed();
218
219         if (sp != 0.0f) {
220
221                 /* we're rolling */
222
223                 if (_session->get_play_range()) {
224
225                         play_selection_button.set_active_state (Gtkmm2ext::ExplicitActive);
226                         roll_button.unset_active_state ();
227                         auto_loop_button.unset_active_state ();
228
229                 } else if (_session->get_play_loop ()) {
230
231                         auto_loop_button.set_active (true);
232                         play_selection_button.set_active (false);
233
234                         if (Config->get_loop_is_mode()) {
235                                 roll_button.set_active (true);
236                         } else {
237                                 roll_button.set_active (false);
238                         }
239
240                 } else {
241
242                         roll_button.set_active (true);
243                         play_selection_button.set_active (false);
244                         auto_loop_button.set_active (false);
245
246                 }
247
248                 if (UIConfiguration::instance().get_follow_edits() && !_session->config.get_external_sync()) {
249                         /* light up both roll and play-selection if they are joined */
250                         roll_button.set_active (true);
251                         play_selection_button.set_active (true);
252                 }
253
254                 stop_button.set_active (false);
255
256         } else {
257
258                 stop_button.set_active (true);
259                 roll_button.set_active (false);
260                 play_selection_button.set_active (false);
261                 if (Config->get_loop_is_mode ()) {
262                         auto_loop_button.set_active (_session->get_play_loop());
263                 } else {
264                         auto_loop_button.set_active (false);
265                 }
266         }
267 }
268
269 void
270 TransportControlUI::step_edit_status_change (bool yn)
271 {
272         // XXX should really store pre-step edit status of things
273         // we make insensitive
274
275         if (yn) {
276                 rec_button.set_active_state (Gtkmm2ext::ImplicitActive);
277                 rec_button.set_sensitive (false);
278         } else {
279                 rec_button.unset_active_state ();;
280                 rec_button.set_sensitive (true);
281         }
282 }
283
284 void
285 TransportControlUI::set_loop_sensitivity ()
286 {
287         if (!_session || _session->config.get_external_sync()) {
288                 auto_loop_button.set_sensitive (false);
289         } else {
290                 auto_loop_button.set_sensitive (_session && _session->locations()->auto_loop_location());
291         }
292 }
293
294 void
295 TransportControlUI::blink_rec_enable (bool onoff)
296 {
297         if (_session == 0) {
298                 return;
299         }
300
301         if (_session->step_editing()) {
302                 return;
303         }
304
305         Session::RecordState const r = _session->record_status ();
306         bool const h = _session->have_rec_enabled_track ();
307
308         if (r == Session::Enabled || (r == Session::Recording && !h)) {
309                 if (onoff) {
310                         rec_button.set_active_state (Gtkmm2ext::ExplicitActive);
311                 } else {
312                         rec_button.set_active_state (Gtkmm2ext::Off);
313                 }
314         } else if (r == Session::Recording && h) {
315                 rec_button.set_active_state (Gtkmm2ext::ExplicitActive);
316         } else {
317                 rec_button.unset_active_state ();
318         }
319 }
320
321 bool
322 TransportControlUI::click_button_scroll (GdkEventScroll* ev)
323 {
324         gain_t gain = Config->get_click_gain();
325         float gain_db = accurate_coefficient_to_dB (gain);
326
327         switch (ev->direction) {
328                 case GDK_SCROLL_UP:
329                 case GDK_SCROLL_LEFT:
330                         gain_db += 1;
331                         break;
332                 case GDK_SCROLL_DOWN:
333                 case GDK_SCROLL_RIGHT:
334                         gain_db -= 1;
335                         break;
336         }
337         gain_db = std::max (-60.f, gain_db);
338         gain = dB_to_coefficient (gain_db);
339         gain = std::min (gain, Config->get_max_gain());
340         Config->set_click_gain (gain);
341         return true;
342 }