1 /*
2 * Copyright (C) 2015-2019 Ben Loftis <ben@harrisonconsoles.com>
3 * Copyright (C) 2015-2019 Paul Davis <paul@linuxaudiosystems.com>
4 * Copyright (C) 2017-2019 Robin Gareus <robin@gareus.org>
5 * Copyright (C) 2019 Johannes Mueller <github@johannes-mueller.org>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 */
21
22 #include <gtkmm/alignment.h>
23 #include <gtkmm/label.h>
24 #include <gtkmm/liststore.h>
25
26 #include "pbd/file_utils.h"
27 #include "pbd/strsplit.h"
28 #include "pbd/unwind.h"
29
30 #include "gtkmm2ext/actions.h"
31 #include "gtkmm2ext/action_model.h"
32 #include "gtkmm2ext/bindings.h"
33 #include "gtkmm2ext/gtk_ui.h"
34 #include "gtkmm2ext/gui_thread.h"
35 #include "gtkmm2ext/utils.h"
36
37 #include "ardour/audioengine.h"
38 #include "ardour/filesystem_paths.h"
39
40 #include "faderport.h"
41 #include "gui.h"
42
43 #include "pbd/i18n.h"
44
45 using namespace PBD;
46 using namespace ARDOUR;
47 using namespace ArdourSurface;
48 using namespace std;
49 using namespace Gtk;
50 using namespace Gtkmm2ext;
51
52 void*
get_gui() const53 FaderPort::get_gui () const
54 {
55 if (!gui) {
56 const_cast<FaderPort*>(this)->build_gui ();
57 }
58 static_cast<Gtk::VBox*>(gui)->show_all();
59 return gui;
60 }
61
62 void
tear_down_gui()63 FaderPort::tear_down_gui ()
64 {
65 if (gui) {
66 Gtk::Widget *w = static_cast<Gtk::VBox*>(gui)->get_parent();
67 if (w) {
68 w->hide();
69 delete w;
70 }
71 }
72 delete static_cast<FPGUI*> (gui);
73 gui = 0;
74 }
75
76 void
build_gui()77 FaderPort::build_gui ()
78 {
79 gui = (void*) new FPGUI (*this);
80 }
81
82 /*--------------------*/
83
FPGUI(FaderPort & p)84 FPGUI::FPGUI (FaderPort& p)
85 : fp (p)
86 , table (2, 5)
87 , action_table (5, 4)
88 , ignore_active_change (false)
89 , action_model (ActionManager::ActionModel::instance ())
90 {
91 set_border_width (12);
92
93 table.set_row_spacings (4);
94 table.set_col_spacings (6);
95 table.set_border_width (12);
96 table.set_homogeneous (false);
97
98 std::string data_file_path;
99 string name = "faderport-small.png";
100 Searchpath spath(ARDOUR::ardour_data_search_path());
101 spath.add_subdirectory_to_paths ("icons");
102 find_file (spath, name, data_file_path);
103 if (!data_file_path.empty()) {
104 image.set (data_file_path);
105 hpacker.pack_start (image, false, false);
106 }
107
108 Gtk::Label* l;
109 Gtk::Alignment* align;
110 int row = 0;
111
112 input_combo.pack_start (midi_port_columns.short_name);
113 output_combo.pack_start (midi_port_columns.short_name);
114
115 input_combo.signal_changed().connect (sigc::bind (sigc::mem_fun (*this, &FPGUI::active_port_changed), &input_combo, true));
116 output_combo.signal_changed().connect (sigc::bind (sigc::mem_fun (*this, &FPGUI::active_port_changed), &output_combo, false));
117
118 l = manage (new Gtk::Label);
119 l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Incoming MIDI on:")));
120 l->set_alignment (1.0, 0.5);
121 table.attach (*l, 0, 1, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions(0));
122 table.attach (input_combo, 1, 2, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions(0), 0, 0);
123 row++;
124
125 l = manage (new Gtk::Label);
126 l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Outgoing MIDI on:")));
127 l->set_alignment (1.0, 0.5);
128 table.attach (*l, 0, 1, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions(0));
129 table.attach (output_combo, 1, 2, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions(0), 0, 0);
130 row++;
131
132 build_mix_action_combo (mix_combo[0], FaderPort::ButtonState(0));
133 build_mix_action_combo (mix_combo[1], FaderPort::ShiftDown);
134 build_mix_action_combo (mix_combo[2], FaderPort::LongPress);
135
136 build_proj_action_combo (proj_combo[0], FaderPort::ButtonState(0));
137 build_proj_action_combo (proj_combo[1], FaderPort::ShiftDown);
138 build_proj_action_combo (proj_combo[2], FaderPort::LongPress);
139
140 build_trns_action_combo (trns_combo[0], FaderPort::ButtonState(0));
141 build_trns_action_combo (trns_combo[1], FaderPort::ShiftDown);
142 build_trns_action_combo (trns_combo[2], FaderPort::LongPress);
143
144 build_foot_action_combo (foot_combo[0], FaderPort::ButtonState(0));
145 build_foot_action_combo (foot_combo[1], FaderPort::ShiftDown);
146 build_foot_action_combo (foot_combo[2], FaderPort::LongPress);
147
148 /* No shift-press combo for User because that is labelled as "next"
149 * (marker)
150 */
151
152 build_user_action_combo (user_combo[0], FaderPort::ButtonState(0));
153 build_user_action_combo (user_combo[1], FaderPort::LongPress);
154
155 action_table.set_row_spacings (4);
156 action_table.set_col_spacings (6);
157 action_table.set_border_width (12);
158 action_table.set_homogeneous (false);
159
160 int action_row = 0;
161
162 l = manage (new Gtk::Label);
163 l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Press Action")));
164 l->set_alignment (0.5, 0.5);
165 action_table.attach (*l, 1, 2, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
166 l = manage (new Gtk::Label);
167 l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Shift-Press Action")));
168 l->set_alignment (0.5, 0.5);
169 action_table.attach (*l, 2, 3, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
170 l = manage (new Gtk::Label);
171 l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Long Press Action")));
172 l->set_alignment (0.5, 0.5);
173 action_table.attach (*l, 3, 4, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
174 action_row++;
175
176 l = manage (new Gtk::Label);
177 l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Mix")));
178 l->set_alignment (1.0, 0.5);
179 action_table.attach (*l, 0, 1, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
180 align = manage (new Alignment);
181 align->set (0.0, 0.5);
182 align->add (mix_combo[0]);
183 action_table.attach (*align, 1, 2, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
184 align = manage (new Alignment);
185 align->set (0.0, 0.5);
186 align->add (mix_combo[1]);
187 action_table.attach (*align, 2, 3, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
188 align = manage (new Alignment);
189 align->set (0.0, 0.5);
190 align->add (mix_combo[2]);
191 action_table.attach (*align, 3, 4, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
192 action_row++;
193
194 l = manage (new Gtk::Label);
195 l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Proj")));
196 l->set_alignment (1.0, 0.5);
197 action_table.attach (*l, 0, 1, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
198 align = manage (new Alignment);
199 align->set (0.0, 0.5);
200 align->add (proj_combo[0]);
201 action_table.attach (*align, 1, 2, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
202 align = manage (new Alignment);
203 align->set (0.0, 0.5);
204 align->add (proj_combo[1]);
205 action_table.attach (*align, 2, 3, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
206 align = manage (new Alignment);
207 align->set (0.0, 0.5);
208 align->add (proj_combo[2]);
209 action_table.attach (*align, 3, 4, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
210 action_row++;
211
212 l = manage (new Gtk::Label);
213 l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Trns")));
214 l->set_alignment (1.0, 0.5);
215 action_table.attach (*l, 0, 1, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
216 align = manage (new Alignment);
217 align->set (0.0, 0.5);
218 align->add (trns_combo[0]);
219 action_table.attach (*align, 1, 2, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
220 align = manage (new Alignment);
221 align->set (0.0, 0.5);
222 align->add (trns_combo[1]);
223 action_table.attach (*align, 2, 3, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
224 align = manage (new Alignment);
225 align->set (0.0, 0.5);
226 align->add (trns_combo[2]);
227 action_table.attach (*align, 3, 4, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
228 action_row++;
229
230 l = manage (new Gtk::Label);
231 l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("User")));
232 l->set_alignment (1.0, 0.5);
233 action_table.attach (*l, 0, 1, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
234 align = manage (new Alignment);
235 align->set (0.0, 0.5);
236 align->add (user_combo[0]);
237 action_table.attach (*align, 1, 2, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
238 /* skip shift press combo */
239 align = manage (new Alignment);
240 align->set (0.0, 0.5);
241 align->add (user_combo[1]);
242 action_table.attach (*align, 3, 4, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
243 action_row++;
244
245 l = manage (new Gtk::Label);
246 l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Footswitch")));
247 l->set_alignment (1.0, 0.5);
248 action_table.attach (*l, 0, 1, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
249 align = manage (new Alignment);
250 align->set (0.0, 0.5);
251 align->add (foot_combo[0]);
252 action_table.attach (*align, 1, 2, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
253 align = manage (new Alignment);
254 align->set (0.0, 0.5);
255 align->add (foot_combo[1]);
256 action_table.attach (*align, 2, 3, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
257 align = manage (new Alignment);
258 align->set (0.0, 0.5);
259 align->add (foot_combo[2]);
260 action_table.attach (*align, 3, 4, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
261 action_row++;
262
263 table.attach (action_table, 0, 5, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
264 row++;
265
266 hpacker.pack_start (table, true, true);
267 pack_start (hpacker, false, false);
268
269 /* update the port connection combos */
270
271 update_port_combos ();
272
273 /* catch future changes to connection state */
274
275 ARDOUR::AudioEngine::instance()->PortRegisteredOrUnregistered.connect (_port_connections, invalidator (*this), boost::bind (&FPGUI::connection_handler, this), gui_context());
276 ARDOUR::AudioEngine::instance()->PortPrettyNameChanged.connect (_port_connections, invalidator (*this), boost::bind (&FPGUI::connection_handler, this), gui_context());
277 fp.ConnectionChange.connect (_port_connections, invalidator (*this), boost::bind (&FPGUI::connection_handler, this), gui_context());
278 }
279
~FPGUI()280 FPGUI::~FPGUI ()
281 {
282 }
283
284 void
connection_handler()285 FPGUI::connection_handler ()
286 {
287 /* ignore all changes to combobox active strings here, because we're
288 updating them to match a new ("external") reality - we were called
289 because port connections have changed.
290 */
291
292 PBD::Unwinder<bool> ici (ignore_active_change, true);
293
294 update_port_combos ();
295 }
296
297 void
update_port_combos()298 FPGUI::update_port_combos ()
299 {
300 vector<string> midi_inputs;
301 vector<string> midi_outputs;
302
303 ARDOUR::AudioEngine::instance()->get_ports ("", ARDOUR::DataType::MIDI, ARDOUR::PortFlags (ARDOUR::IsOutput|ARDOUR::IsTerminal), midi_inputs);
304 ARDOUR::AudioEngine::instance()->get_ports ("", ARDOUR::DataType::MIDI, ARDOUR::PortFlags (ARDOUR::IsInput|ARDOUR::IsTerminal), midi_outputs);
305
306 Glib::RefPtr<Gtk::ListStore> input = build_midi_port_list (midi_inputs, true);
307 Glib::RefPtr<Gtk::ListStore> output = build_midi_port_list (midi_outputs, false);
308 bool input_found = false;
309 bool output_found = false;
310 int n;
311
312 input_combo.set_model (input);
313 output_combo.set_model (output);
314
315 Gtk::TreeModel::Children children = input->children();
316 Gtk::TreeModel::Children::iterator i;
317 i = children.begin();
318 ++i; /* skip "Disconnected" */
319
320
321 for (n = 1; i != children.end(); ++i, ++n) {
322 string port_name = (*i)[midi_port_columns.full_name];
323 if (fp.input_port()->connected_to (port_name)) {
324 input_combo.set_active (n);
325 input_found = true;
326 break;
327 }
328 }
329
330 if (!input_found) {
331 input_combo.set_active (0); /* disconnected */
332 }
333
334 children = output->children();
335 i = children.begin();
336 ++i; /* skip "Disconnected" */
337
338 for (n = 1; i != children.end(); ++i, ++n) {
339 string port_name = (*i)[midi_port_columns.full_name];
340 if (fp.output_port()->connected_to (port_name)) {
341 output_combo.set_active (n);
342 output_found = true;
343 break;
344 }
345 }
346
347 if (!output_found) {
348 output_combo.set_active (0); /* disconnected */
349 }
350 }
351
352 void
action_changed(Gtk::ComboBox * cb,FaderPort::ButtonID id,FaderPort::ButtonState bs)353 FPGUI::action_changed (Gtk::ComboBox* cb, FaderPort::ButtonID id, FaderPort::ButtonState bs)
354 {
355 TreeModel::const_iterator row = cb->get_active ();
356 string action_path = (*row)[action_model.path()];
357
358 /* release binding */
359 fp.set_action (id, action_path, false, bs);
360 }
361
362 void
build_action_combo(Gtk::ComboBox & cb,vector<pair<string,string>> const & actions,FaderPort::ButtonID id,FaderPort::ButtonState bs)363 FPGUI::build_action_combo (Gtk::ComboBox& cb, vector<pair<string,string> > const & actions, FaderPort::ButtonID id, FaderPort::ButtonState bs)
364 {
365 const string current_action = fp.get_action (id, false, bs); /* lookup release action */
366 action_model.build_custom_action_combo (cb, actions, current_action);
367 cb.signal_changed().connect (sigc::bind (sigc::mem_fun (*this, &FPGUI::action_changed), &cb, id, bs));
368 }
369
370 void
build_mix_action_combo(Gtk::ComboBox & cb,FaderPort::ButtonState bs)371 FPGUI::build_mix_action_combo (Gtk::ComboBox& cb, FaderPort::ButtonState bs)
372 {
373 vector<pair<string,string> > actions;
374
375 actions.push_back (make_pair (string (_("Show Mixer Window")), string (X_("Common/show-mixer"))));
376 actions.push_back (make_pair (string (_("Show/Hide Mixer list")), string (X_("Mixer/ToggleMixerList"))));
377 actions.push_back (make_pair (string("Toggle Meterbridge"), string(X_("Common/toggle-meterbridge"))));
378 actions.push_back (make_pair (string (_("Show/Hide Editor mixer strip")), string (X_("Editor/show-editor-mixer"))));
379
380 build_action_combo (cb, actions, FaderPort::Mix, bs);
381 }
382
383 void
build_proj_action_combo(Gtk::ComboBox & cb,FaderPort::ButtonState bs)384 FPGUI::build_proj_action_combo (Gtk::ComboBox& cb, FaderPort::ButtonState bs)
385 {
386 vector<pair<string,string> > actions;
387
388 actions.push_back (make_pair (string (_("Show Editor Window")), string (X_("Common/show-editor"))));
389 actions.push_back (make_pair (string("Toggle Editor Lists"), string(X_("Editor/show-editor-list"))));
390 actions.push_back (make_pair (string("Toggle Summary"), string(X_("Editor/ToggleSummary"))));
391 actions.push_back (make_pair (string("Toggle Meterbridge"), string(X_("Common/toggle-meterbridge"))));
392 actions.push_back (make_pair (string (_("Zoom to Session")), string (X_("Editor/zoom-to-session"))));
393
394 #if 0
395 actions.push_back (make_pair (string (_("Zoom In")), string (X_("Editor/temporal-zoom-in"))));
396 actions.push_back (make_pair (string (_("Zoom Out")), string (X_("Editor/temporal-zoom-out"))));
397 #endif
398
399 build_action_combo (cb, actions, FaderPort::Proj, bs);
400 }
401
402 void
build_trns_action_combo(Gtk::ComboBox & cb,FaderPort::ButtonState bs)403 FPGUI::build_trns_action_combo (Gtk::ComboBox& cb, FaderPort::ButtonState bs)
404 {
405 vector<pair<string,string> > actions;
406
407 actions.push_back (make_pair (string("Toggle Big Clock"), string(X_("Window/toggle-big-clock")))); //note: this would really make sense if the Big Clock had transport buttons on it
408 actions.push_back (make_pair (string("Toggle Locations window"), string(X_("Window/toggle-locations"))));
409 actions.push_back (make_pair (string("Toggle Metronome"), string(X_("Transport/ToggleClick"))));
410 actions.push_back (make_pair (string("Toggle External Sync"), string(X_("Transport/ToggleExternalSync"))));
411 actions.push_back (make_pair (string("Toggle Follow Playhead"), string(X_("Editor/toggle-follow-playhead"))));
412
413 // actions.push_back (make_pair (string("Set Playhead @pointer"), string(X_("Editor/set-playhead"))));
414
415
416 build_action_combo (cb, actions, FaderPort::Trns, bs);
417 }
418
419 void
build_foot_action_combo(Gtk::ComboBox & cb,FaderPort::ButtonState bs)420 FPGUI::build_foot_action_combo (Gtk::ComboBox& cb, FaderPort::ButtonState bs)
421 {
422 vector<pair<string,string> > actions;
423
424 actions.push_back (make_pair (string("Toggle Roll"), string(X_("Transport/ToggleRoll"))));
425 actions.push_back (make_pair (string("Toggle Rec-Enable"), string(X_("Transport/Record"))));
426 actions.push_back (make_pair (string("Toggle Roll+Rec"), string(X_("Transport/record-roll"))));
427 actions.push_back (make_pair (string("Toggle Loop"), string(X_("Transport/Loop"))));
428 actions.push_back (make_pair (string("Toggle Click"), string(X_("Transport/ToggleClick"))));
429 actions.push_back (make_pair (string("Record with Pre-Roll"), string(X_("Transport/RecordPreroll"))));
430 actions.push_back (make_pair (string("Record with Count-In"), string(X_("Transport/RecordCountIn"))));
431
432 build_action_combo (cb, actions, FaderPort::Footswitch, bs);
433 }
434
435
436 void
build_user_action_combo(Gtk::ComboBox & cb,FaderPort::ButtonState bs)437 FPGUI::build_user_action_combo (Gtk::ComboBox& cb, FaderPort::ButtonState bs)
438 {
439 #ifndef MIXBUS
440 bs = FaderPort::ButtonState (bs|FaderPort::UserDown);
441 #endif
442
443 /* set the active "row" to the right value for the current button binding */
444
445 string current_action = fp.get_action (FaderPort::User, false, bs); /* lookup release action */
446 action_model.build_action_combo (cb, current_action);
447 cb.signal_changed().connect (sigc::bind (sigc::mem_fun (*this, &FPGUI::action_changed), &cb, FaderPort::User, bs));
448
449 }
450
451 Glib::RefPtr<Gtk::ListStore>
build_midi_port_list(vector<string> const & ports,bool for_input)452 FPGUI::build_midi_port_list (vector<string> const & ports, bool for_input)
453 {
454 Glib::RefPtr<Gtk::ListStore> store = ListStore::create (midi_port_columns);
455 TreeModel::Row row;
456
457 row = *store->append ();
458 row[midi_port_columns.full_name] = string();
459 row[midi_port_columns.short_name] = _("Disconnected");
460
461 for (vector<string>::const_iterator p = ports.begin(); p != ports.end(); ++p) {
462 row = *store->append ();
463 row[midi_port_columns.full_name] = *p;
464 std::string pn = ARDOUR::AudioEngine::instance()->get_pretty_name_by_name (*p);
465 if (pn.empty ()) {
466 pn = (*p).substr ((*p).find (':') + 1);
467 }
468 row[midi_port_columns.short_name] = pn;
469 }
470
471 return store;
472 }
473
474 void
active_port_changed(Gtk::ComboBox * combo,bool for_input)475 FPGUI::active_port_changed (Gtk::ComboBox* combo, bool for_input)
476 {
477 if (ignore_active_change) {
478 return;
479 }
480
481 TreeModel::iterator active = combo->get_active ();
482 string new_port = (*active)[midi_port_columns.full_name];
483
484 if (new_port.empty()) {
485 if (for_input) {
486 fp.input_port()->disconnect_all ();
487 } else {
488 fp.output_port()->disconnect_all ();
489 }
490
491 return;
492 }
493
494 if (for_input) {
495 if (!fp.input_port()->connected_to (new_port)) {
496 fp.input_port()->disconnect_all ();
497 fp.input_port()->connect (new_port);
498 }
499 } else {
500 if (!fp.output_port()->connected_to (new_port)) {
501 fp.output_port()->disconnect_all ();
502 fp.output_port()->connect (new_port);
503 }
504 }
505 }
506