OSC: use internal_to_interface or reverse
[ardour.git] / libs / surfaces / generic_midi / gmcp_gui.cc
1 /*
2     Copyright (C) 2009-2012 Paul Davis
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
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18 */
19
20 #include <iostream>
21 #include <list>
22 #include <string>
23
24 #include <gtkmm/comboboxtext.h>
25 #include <gtkmm/label.h>
26 #include <gtkmm/box.h>
27 #include <gtkmm/adjustment.h>
28 #include <gtkmm/spinbutton.h>
29 #include <gtkmm/table.h>
30 #include <gtkmm/liststore.h>
31
32 #include "pbd/unwind.h"
33
34 #include "ardour/audioengine.h"
35 #include "ardour/port.h"
36 #include "ardour/midi_port.h"
37
38 #include "gtkmm2ext/gtk_ui.h"
39 #include "gtkmm2ext/gui_thread.h"
40 #include "gtkmm2ext/utils.h"
41
42 #include "generic_midi_control_protocol.h"
43
44 #include "pbd/i18n.h"
45
46 class GMCPGUI : public Gtk::VBox
47 {
48 public:
49         GMCPGUI (GenericMidiControlProtocol&);
50         ~GMCPGUI ();
51
52 private:
53         GenericMidiControlProtocol& cp;
54         Gtk::ComboBoxText map_combo;
55         Gtk::Adjustment bank_adjustment;
56         Gtk::SpinButton bank_spinner;
57         Gtk::CheckButton feedback_enable;
58         Gtk::CheckButton motorised_button;
59         Gtk::Adjustment threshold_adjustment;
60         Gtk::SpinButton threshold_spinner;
61
62         Gtk::ComboBox input_combo;
63         Gtk::ComboBox output_combo;
64
65         void binding_changed ();
66         void bank_changed ();
67         void motorised_changed ();
68         void threshold_changed ();
69         void toggle_feedback_enable ();
70
71         void update_port_combos ();
72         PBD::ScopedConnection connection_change_connection;
73         void connection_handler ();
74
75         struct MidiPortColumns : public Gtk::TreeModel::ColumnRecord {
76                 MidiPortColumns() {
77                         add (short_name);
78                         add (full_name);
79                 }
80                 Gtk::TreeModelColumn<std::string> short_name;
81                 Gtk::TreeModelColumn<std::string> full_name;
82         };
83
84         MidiPortColumns midi_port_columns;
85         bool ignore_active_change;
86
87         Glib::RefPtr<Gtk::ListStore> build_midi_port_list (std::vector<std::string> const & ports, bool for_input);
88         void active_port_changed (Gtk::ComboBox*,bool for_input);
89 };
90
91 using namespace PBD;
92 using namespace ARDOUR;
93 using namespace std;
94 using namespace Gtk;
95 using namespace Gtkmm2ext;
96
97 void*
98 GenericMidiControlProtocol::get_gui () const
99 {
100         if (!gui) {
101                 const_cast<GenericMidiControlProtocol*>(this)->build_gui ();
102         }
103         static_cast<Gtk::VBox*>(gui)->show_all();
104         return gui;
105 }
106
107 void
108 GenericMidiControlProtocol::tear_down_gui ()
109 {
110         if (gui) {
111                 Gtk::Widget *w = static_cast<Gtk::VBox*>(gui)->get_parent();
112                 if (w) {
113                         w->hide();
114                         delete w;
115                 }
116         }
117         delete (GMCPGUI*) gui;
118         gui = 0;
119 }
120
121 void
122 GenericMidiControlProtocol::build_gui ()
123 {
124         gui = (void*) new GMCPGUI (*this);
125 }
126
127 /*--------------------*/
128
129 GMCPGUI::GMCPGUI (GenericMidiControlProtocol& p)
130         : cp (p)
131         , bank_adjustment (1, 1, 100, 1, 10)
132         , bank_spinner (bank_adjustment)
133         , feedback_enable (_("Enable Feedback"))
134         , motorised_button (_("Motorised"))
135         , threshold_adjustment (p.threshold(), 1, 127, 1, 10)
136         , threshold_spinner (threshold_adjustment)
137         , ignore_active_change (false)
138 {
139         vector<string> popdowns;
140         popdowns.push_back (_("Reset All"));
141
142         for (list<GenericMidiControlProtocol::MapInfo>::iterator x = cp.map_info.begin(); x != cp.map_info.end(); ++x) {
143                 popdowns.push_back (x->name);
144         }
145
146         set_popdown_strings (map_combo, popdowns);
147
148         if (cp.current_binding().empty()) {
149                 map_combo.set_active_text (popdowns[0]);
150         } else {
151                 map_combo.set_active_text (cp.current_binding());
152         }
153
154         map_combo.signal_changed().connect (sigc::mem_fun (*this, &GMCPGUI::binding_changed));
155
156         set_spacing (6);
157         set_border_width (6);
158
159         Table* table = manage (new Table);
160         table->set_row_spacings (6);
161         table->set_col_spacings (6);
162         table->show ();
163
164         int n = 0;
165
166         // MIDI input and output selectors
167         input_combo.pack_start (midi_port_columns.short_name);
168         output_combo.pack_start (midi_port_columns.short_name);
169
170         input_combo.signal_changed().connect (sigc::bind (sigc::mem_fun (*this, &GMCPGUI::active_port_changed), &input_combo, true));
171         output_combo.signal_changed().connect (sigc::bind (sigc::mem_fun (*this, &GMCPGUI::active_port_changed), &output_combo, false));
172
173         Label* label = manage (new Gtk::Label);
174         label->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Incoming MIDI on:")));
175         label->set_alignment (1.0, 0.5);
176         table->attach (*label, 0, 1, n, n+1, AttachOptions(FILL|EXPAND), AttachOptions(0));
177         table->attach (input_combo, 1, 2, n, n+1, AttachOptions(FILL|EXPAND), AttachOptions(0), 0, 0);
178         n++;
179
180         label = manage (new Gtk::Label);
181         label->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Outgoing MIDI on:")));
182         label->set_alignment (1.0, 0.5);
183         table->attach (*label, 0, 1, n, n+1, AttachOptions(FILL|EXPAND), AttachOptions(0));
184         table->attach (output_combo, 1, 2, n, n+1, AttachOptions(FILL|EXPAND), AttachOptions(0), 0, 0);
185         n++;
186
187         //MIDI binding file selector...
188         label = manage (new Label (_("MIDI Bindings:")));
189         label->set_alignment (0, 0.5);
190         table->attach (*label, 0, 1, n, n + 1);
191         table->attach (map_combo, 1, 2, n, n + 1);
192         ++n;
193
194         map_combo.show ();
195         label->show ();
196
197         bank_adjustment.signal_value_changed().connect (sigc::mem_fun (*this, &GMCPGUI::bank_changed));
198
199         label = manage (new Label (_("Current Bank:")));
200         label->set_alignment (0, 0.5);
201         table->attach (*label, 0, 1, n, n + 1);
202         table->attach (bank_spinner, 1, 2, n, n + 1);
203         ++n;
204
205         bank_spinner.show ();
206         label->show ();
207
208         feedback_enable.signal_toggled().connect (sigc::mem_fun (*this, &GMCPGUI::toggle_feedback_enable));
209         table->attach (feedback_enable, 0, 2, n, n + 1);
210         ++n;
211         feedback_enable.show ();
212         feedback_enable.set_active (p.get_feedback ());
213
214         motorised_button.signal_toggled().connect (sigc::mem_fun (*this, &GMCPGUI::motorised_changed));
215         table->attach (motorised_button, 0, 2, n, n + 1);
216         ++n;
217
218         motorised_button.show ();
219         motorised_button.set_active (p.motorised ());
220
221         threshold_adjustment.signal_value_changed().connect (sigc::mem_fun (*this, &GMCPGUI::threshold_changed));
222
223         Gtkmm2ext::UI::instance()->set_tip (threshold_spinner,
224                                             string_compose (_("Controls how %1 behaves if the MIDI controller sends discontinuous values"), PROGRAM_NAME));
225
226         label = manage (new Label (_("Smoothing:")));
227         label->set_alignment (0, 0.5);
228         table->attach (*label, 0, 1, n, n + 1);
229         table->attach (threshold_spinner, 1, 2, n, n + 1);
230         ++n;
231
232         threshold_spinner.show ();
233         label->show ();
234
235         pack_start (*table, false, false);
236
237         binding_changed ();
238
239         /* update the port connection combos */
240
241         update_port_combos ();
242
243         /* catch future changes to connection state */
244
245         cp.ConnectionChange.connect (connection_change_connection, invalidator (*this), boost::bind (&GMCPGUI::connection_handler, this), gui_context());
246 }
247
248 GMCPGUI::~GMCPGUI ()
249 {
250 }
251
252 void
253 GMCPGUI::bank_changed ()
254 {
255         int new_bank = bank_adjustment.get_value() - 1;
256         cp.set_current_bank (new_bank);
257 }
258
259 void
260 GMCPGUI::binding_changed ()
261 {
262         string str = map_combo.get_active_text ();
263
264         if (str == _("Reset All")) {
265                 cp.drop_bindings ();
266         } else {
267                 for (list<GenericMidiControlProtocol::MapInfo>::iterator x = cp.map_info.begin(); x != cp.map_info.end(); ++x) {
268                         if (str == x->name) {
269                                 cp.load_bindings (x->path);
270                                 motorised_button.set_active (cp.motorised ());
271                                 threshold_adjustment.set_value (cp.threshold ());
272                                 break;
273                         }
274                 }
275         }
276 }
277
278 void
279 GMCPGUI::toggle_feedback_enable ()
280 {
281         cp.set_feedback (feedback_enable.get_active ());
282 }
283
284 void
285 GMCPGUI::motorised_changed ()
286 {
287         cp.set_motorised (motorised_button.get_active ());
288 }
289
290 void
291 GMCPGUI::threshold_changed ()
292 {
293         cp.set_threshold (threshold_adjustment.get_value());
294 }
295
296 void
297 GMCPGUI::connection_handler ()
298 {
299         /* ignore all changes to combobox active strings here, because we're
300            updating them to match a new ("external") reality - we were called
301            because port connections have changed.
302         */
303
304         PBD::Unwinder<bool> ici (ignore_active_change, true);
305
306         update_port_combos ();
307 }
308
309 void
310 GMCPGUI::update_port_combos ()
311 {
312         vector<string> midi_inputs;
313         vector<string> midi_outputs;
314
315         ARDOUR::AudioEngine::instance()->get_ports ("", ARDOUR::DataType::MIDI, ARDOUR::PortFlags (ARDOUR::IsOutput|ARDOUR::IsTerminal), midi_inputs);
316         ARDOUR::AudioEngine::instance()->get_ports ("", ARDOUR::DataType::MIDI, ARDOUR::PortFlags (ARDOUR::IsInput|ARDOUR::IsTerminal), midi_outputs);
317
318         Glib::RefPtr<Gtk::ListStore> input = build_midi_port_list (midi_inputs, true);
319         Glib::RefPtr<Gtk::ListStore> output = build_midi_port_list (midi_outputs, false);
320         bool input_found = false;
321         bool output_found = false;
322         int n;
323
324         input_combo.set_model (input);
325         output_combo.set_model (output);
326
327         Gtk::TreeModel::Children children = input->children();
328         Gtk::TreeModel::Children::iterator i;
329         i = children.begin();
330         ++i; /* skip "Disconnected" */
331
332
333         for (n = 1;  i != children.end(); ++i, ++n) {
334                 string port_name = (*i)[midi_port_columns.full_name];
335                 if (cp.input_port()->connected_to (port_name)) {
336                         input_combo.set_active (n);
337                         input_found = true;
338                         break;
339                 }
340         }
341
342         if (!input_found) {
343                 input_combo.set_active (0); /* disconnected */
344         }
345
346         children = output->children();
347         i = children.begin();
348         ++i; /* skip "Disconnected" */
349
350         for (n = 1;  i != children.end(); ++i, ++n) {
351                 string port_name = (*i)[midi_port_columns.full_name];
352                 if (cp.output_port()->connected_to (port_name)) {
353                         output_combo.set_active (n);
354                         output_found = true;
355                         break;
356                 }
357         }
358
359         if (!output_found) {
360                 output_combo.set_active (0); /* disconnected */
361         }
362 }
363
364 Glib::RefPtr<Gtk::ListStore>
365 GMCPGUI::build_midi_port_list (vector<string> const & ports, bool for_input)
366 {
367         Glib::RefPtr<Gtk::ListStore> store = ListStore::create (midi_port_columns);
368         TreeModel::Row row;
369
370         row = *store->append ();
371         row[midi_port_columns.full_name] = string();
372         row[midi_port_columns.short_name] = _("Disconnected");
373
374         for (vector<string>::const_iterator p = ports.begin(); p != ports.end(); ++p) {
375                 row = *store->append ();
376                 row[midi_port_columns.full_name] = *p;
377                 std::string pn = ARDOUR::AudioEngine::instance()->get_pretty_name_by_name (*p);
378                 if (pn.empty ()) {
379                         pn = (*p).substr ((*p).find (':') + 1);
380                 }
381                 row[midi_port_columns.short_name] = pn;
382         }
383
384         return store;
385 }
386
387 void
388 GMCPGUI::active_port_changed (Gtk::ComboBox* combo, bool for_input)
389 {
390         if (ignore_active_change) {
391                 return;
392         }
393
394         TreeModel::iterator active = combo->get_active ();
395         string new_port = (*active)[midi_port_columns.full_name];
396
397         if (new_port.empty()) {
398                 if (for_input) {
399                         cp.input_port()->disconnect_all ();
400                 } else {
401                         cp.output_port()->disconnect_all ();
402                 }
403
404                 return;
405         }
406
407         if (for_input) {
408                 if (!cp.input_port()->connected_to (new_port)) {
409                         cp.input_port()->disconnect_all ();
410                         cp.input_port()->connect (new_port);
411                 }
412         } else {
413                 if (!cp.output_port()->connected_to (new_port)) {
414                         cp.output_port()->disconnect_all ();
415                         cp.output_port()->connect (new_port);
416                 }
417         }
418 }