Separate and consolidate Transport-Control-UI code
[ardour.git] / gtk2_ardour / transport_control_ui.cc
1 /*
2  * Copyright (C) 2017 Robin Gareus <robin@gareus.org>
3  * Copyright (C) 2013 Paul Davis
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18  */
19
20 #include <gtkmm/sizegroup.h>
21
22 #include "ardour/dB.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         transport_button_size_group->add_widget (play_selection_button);
109         transport_button_size_group->add_widget (roll_button);
110         transport_button_size_group->add_widget (stop_button);
111
112         transport_button_size_group->add_widget (midi_panic_button);
113         transport_button_size_group->add_widget (click_button);
114
115 #define PX_SCALE(px) std::max((float)px, rintf((float)px * UIConfiguration::instance().get_ui_scale()))
116
117         click_button.set_size_request (PX_SCALE(20), PX_SCALE(20));
118         set_spacing (PX_SCALE(2));
119
120 #undef PX_SCALE
121
122         pack_start (midi_panic_button, true, true, 0);
123         pack_start (click_button, true, true, 0);
124         pack_start (goto_start_button, true, true);
125         pack_start (goto_end_button, true, true);
126         pack_start (auto_loop_button, true, true);
127         pack_start (play_selection_button, true, true);
128         pack_start (roll_button, true, true);
129         pack_start (stop_button, true, true);
130         pack_start (rec_button, true, true, 3);
131
132         roll_button.set_name ("transport button");
133         stop_button.set_name ("transport button");
134         goto_start_button.set_name ("transport button");
135         goto_end_button.set_name ("transport button");
136         auto_loop_button.set_name ("transport button");
137         play_selection_button.set_name ("transport button");
138         rec_button.set_name ("transport recenable button");
139         midi_panic_button.set_name ("transport button"); // XXX ???
140         click_button.set_name ("transport button");
141
142         roll_button.set_controllable (ui->roll_controllable);
143         stop_button.set_controllable (ui->stop_controllable);
144         goto_start_button.set_controllable (ui->goto_start_controllable);
145         goto_end_button.set_controllable (ui->goto_end_controllable);
146         auto_loop_button.set_controllable (ui->auto_loop_controllable);
147         play_selection_button.set_controllable (ui->play_selection_controllable);
148         rec_button.set_controllable (ui->rec_controllable);
149
150         stop_button.set_active (true);
151
152         Timers::blink_connect (sigc::mem_fun (*this, &TransportControlUI::blink_rec_enable));
153 }
154
155 void
156 TransportControlUI::set_session (ARDOUR::Session *s)
157 {
158         SessionHandlePtr::set_session (s);
159         set_loop_sensitivity ();
160
161         if (!_session) {
162                 rec_button.set_sensitive (false);
163                 return;
164         }
165
166         _session->config.ParameterChanged.connect (_session_connections, MISSING_INVALIDATOR, boost::bind (&TransportControlUI::parameter_changed, this, _1), gui_context());
167         _session->StepEditStatusChange.connect (_session_connections, MISSING_INVALIDATOR, boost::bind (&TransportControlUI::step_edit_status_change, this, _1), gui_context());
168         _session->TransportStateChange.connect (_session_connections, MISSING_INVALIDATOR, boost::bind (&TransportControlUI::map_transport_state, this), gui_context());
169         _session->auto_loop_location_changed.connect (_session_connections, MISSING_INVALIDATOR, boost::bind (&TransportControlUI::set_loop_sensitivity, this), gui_context ());
170
171         rec_button.set_sensitive (true);
172 }
173
174 void
175 TransportControlUI::parameter_changed (std::string p)
176 {
177         if (p == "external-sync") {
178                 set_loop_sensitivity ();
179         } else if (p == "click-record-only") {
180                 // TODO set a flag, blink or gray-out metronome button while rolling, only
181                 if (Config->get_click_record_only()) {
182                         click_button.set_name ("generic button"); // XXX
183                 } else {
184                         click_button.set_name ("transport button");
185                 }
186         } else if (p == "click-gain") {
187                 float gain_db = accurate_coefficient_to_dB (Config->get_click_gain());
188                 char tmp[32];
189                 snprintf(tmp, 31, "%+.1f", gain_db);
190                 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));
191         }
192 }
193
194 void
195 TransportControlUI::map_transport_state ()
196 {
197         if (!_session) {
198                 auto_loop_button.unset_active_state ();
199                 play_selection_button.unset_active_state ();
200                 roll_button.unset_active_state ();
201                 stop_button.set_active_state (Gtkmm2ext::ExplicitActive);
202                 return;
203         }
204
205         float sp = _session->transport_speed();
206
207         if (sp != 0.0f) {
208
209                 /* we're rolling */
210
211                 if (_session->get_play_range()) {
212
213                         play_selection_button.set_active_state (Gtkmm2ext::ExplicitActive);
214                         roll_button.unset_active_state ();
215                         auto_loop_button.unset_active_state ();
216
217                 } else if (_session->get_play_loop ()) {
218
219                         auto_loop_button.set_active (true);
220                         play_selection_button.set_active (false);
221
222                         if (Config->get_loop_is_mode()) {
223                                 roll_button.set_active (true);
224                         } else {
225                                 roll_button.set_active (false);
226                         }
227
228                 } else {
229
230                         roll_button.set_active (true);
231                         play_selection_button.set_active (false);
232                         auto_loop_button.set_active (false);
233
234                 }
235
236                 if (UIConfiguration::instance().get_follow_edits() && !_session->config.get_external_sync()) {
237                         /* light up both roll and play-selection if they are joined */
238                         roll_button.set_active (true);
239                         play_selection_button.set_active (true);
240                 }
241
242                 stop_button.set_active (false);
243
244         } else {
245
246                 stop_button.set_active (true);
247                 roll_button.set_active (false);
248                 play_selection_button.set_active (false);
249                 if (Config->get_loop_is_mode ()) {
250                         auto_loop_button.set_active (_session->get_play_loop());
251                 } else {
252                         auto_loop_button.set_active (false);
253                 }
254         }
255 }
256
257 void
258 TransportControlUI::step_edit_status_change (bool yn)
259 {
260         // XXX should really store pre-step edit status of things
261         // we make insensitive
262
263         if (yn) {
264                 rec_button.set_active_state (Gtkmm2ext::ImplicitActive);
265                 rec_button.set_sensitive (false);
266         } else {
267                 rec_button.unset_active_state ();;
268                 rec_button.set_sensitive (true);
269         }
270 }
271
272 void
273 TransportControlUI::set_loop_sensitivity ()
274 {
275         if (!_session || _session->config.get_external_sync()) {
276                 auto_loop_button.set_sensitive (false);
277         } else {
278                 auto_loop_button.set_sensitive (_session && _session->locations()->auto_loop_location());
279         }
280 }
281
282 void
283 TransportControlUI::blink_rec_enable (bool onoff)
284 {
285         if (_session == 0) {
286                 return;
287         }
288
289         if (_session->step_editing()) {
290                 return;
291         }
292
293         Session::RecordState const r = _session->record_status ();
294         bool const h = _session->have_rec_enabled_track ();
295
296         if (r == Session::Enabled || (r == Session::Recording && !h)) {
297                 if (onoff) {
298                         rec_button.set_active_state (Gtkmm2ext::ExplicitActive);
299                 } else {
300                         rec_button.set_active_state (Gtkmm2ext::Off);
301                 }
302         } else if (r == Session::Recording && h) {
303                 rec_button.set_active_state (Gtkmm2ext::ExplicitActive);
304         } else {
305                 rec_button.unset_active_state ();
306         }
307 }
308
309 bool
310 TransportControlUI::click_button_scroll (GdkEventScroll* ev)
311 {
312         gain_t gain = Config->get_click_gain();
313         float gain_db = accurate_coefficient_to_dB (gain);
314
315         switch (ev->direction) {
316                 case GDK_SCROLL_UP:
317                 case GDK_SCROLL_LEFT:
318                         gain_db += 1;
319                         break;
320                 case GDK_SCROLL_DOWN:
321                 case GDK_SCROLL_RIGHT:
322                         gain_db -= 1;
323                         break;
324         }
325         gain_db = std::max (-60.f, gain_db);
326         gain = dB_to_coefficient (gain_db);
327         gain = std::min (gain, Config->get_max_gain());
328         Config->set_click_gain (gain);
329         return true;
330 }