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