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