2606418eabf12eb1a0605fc4b7ed9c5f3ae41e57
[ardour.git] / libs / surfaces / push2 / gui.cc
1 /*
2     Copyright (C) 2015 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 <gtkmm/alignment.h>
21 #include <gtkmm/label.h>
22 #include <gtkmm/liststore.h>
23
24 #include "pbd/unwind.h"
25 #include "pbd/strsplit.h"
26 #include "pbd/file_utils.h"
27
28 #include "gtkmm2ext/bindings.h"
29 #include "gtkmm2ext/gui_thread.h"
30 #include "gtkmm2ext/utils.h"
31
32 #include "ardour/audioengine.h"
33 #include "ardour/filesystem_paths.h"
34
35 #include "push2.h"
36 #include "gui.h"
37
38 #include "i18n.h"
39
40 using namespace PBD;
41 using namespace ARDOUR;
42 using namespace ArdourSurface;
43 using namespace std;
44 using namespace Gtk;
45 using namespace Gtkmm2ext;
46
47 void*
48 Push2::get_gui () const
49 {
50         if (!gui) {
51                 const_cast<Push2*>(this)->build_gui ();
52         }
53         static_cast<Gtk::VBox*>(gui)->show_all();
54         return gui;
55 }
56
57 void
58 Push2::tear_down_gui ()
59 {
60         if (gui) {
61                 Gtk::Widget *w = static_cast<Gtk::VBox*>(gui)->get_parent();
62                 if (w) {
63                         w->hide();
64                         delete w;
65                 }
66         }
67         delete gui;
68         gui = 0;
69 }
70
71 void
72 Push2::build_gui ()
73 {
74         gui = new P2GUI (*this);
75 }
76
77 /*--------------------*/
78
79 P2GUI::P2GUI (Push2& p)
80         : p2 (p)
81         , table (2, 5)
82         , action_table (5, 4)
83         , ignore_active_change (false)
84         , pad_table (8, 8)
85 {
86         set_border_width (12);
87
88         table.set_row_spacings (4);
89         table.set_col_spacings (6);
90         table.set_border_width (12);
91         table.set_homogeneous (false);
92
93         std::string data_file_path;
94         string name = "push2-small.png";
95         Searchpath spath(ARDOUR::ardour_data_search_path());
96         spath.add_subdirectory_to_paths ("icons");
97         find_file (spath, name, data_file_path);
98         if (!data_file_path.empty()) {
99                 image.set (data_file_path);
100                 hpacker.pack_start (image, false, false);
101         }
102
103         Gtk::Label* l;
104         int row = 0;
105
106         input_combo.pack_start (midi_port_columns.short_name);
107         output_combo.pack_start (midi_port_columns.short_name);
108
109         input_combo.signal_changed().connect (sigc::bind (sigc::mem_fun (*this, &P2GUI::active_port_changed), &input_combo, true));
110         output_combo.signal_changed().connect (sigc::bind (sigc::mem_fun (*this, &P2GUI::active_port_changed), &output_combo, false));
111
112         l = manage (new Gtk::Label);
113         l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Incoming MIDI on:")));
114         l->set_alignment (1.0, 0.5);
115         table.attach (*l, 0, 1, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions(0));
116         table.attach (input_combo, 1, 2, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions(0), 0, 0);
117         row++;
118
119         l = manage (new Gtk::Label);
120         l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Outgoing MIDI on:")));
121         l->set_alignment (1.0, 0.5);
122         table.attach (*l, 0, 1, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions(0));
123         table.attach (output_combo, 1, 2, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions(0), 0, 0);
124         row++;
125
126         hpacker.pack_start (table, true, true);
127
128         build_pad_table ();
129
130         set_spacing (12);
131
132         pack_start (hpacker, false, false);
133         pack_start (pad_table, true, true);
134
135         /* update the port connection combos */
136
137         update_port_combos ();
138
139         /* catch future changes to connection state */
140
141         // p2.ConnectionChange.connect (connection_change_connection, invalidator (*this), boost::bind (&P2GUI::connection_handler, this), gui_context());
142         p2.PadChange.connect (p2_connections, invalidator (*this), boost::bind (&P2GUI::build_pad_table, this), gui_context());
143 }
144
145 P2GUI::~P2GUI ()
146 {
147 }
148
149 void
150 P2GUI::connection_handler ()
151 {
152         /* ignore all changes to combobox active strings here, because we're
153            updating them to match a new ("external") reality - we were called
154            because port connections have changed.
155         */
156
157         PBD::Unwinder<bool> ici (ignore_active_change, true);
158
159         update_port_combos ();
160 }
161
162 void
163 P2GUI::update_port_combos ()
164 {
165         vector<string> midi_inputs;
166         vector<string> midi_outputs;
167
168         ARDOUR::AudioEngine::instance()->get_ports ("", ARDOUR::DataType::MIDI, ARDOUR::PortFlags (ARDOUR::IsOutput|ARDOUR::IsTerminal), midi_inputs);
169         ARDOUR::AudioEngine::instance()->get_ports ("", ARDOUR::DataType::MIDI, ARDOUR::PortFlags (ARDOUR::IsInput|ARDOUR::IsTerminal), midi_outputs);
170
171         Glib::RefPtr<Gtk::ListStore> input = build_midi_port_list (midi_inputs, true);
172         Glib::RefPtr<Gtk::ListStore> output = build_midi_port_list (midi_outputs, false);
173         bool input_found = false;
174         bool output_found = false;
175         int n;
176
177         input_combo.set_model (input);
178         output_combo.set_model (output);
179
180         Gtk::TreeModel::Children children = input->children();
181         Gtk::TreeModel::Children::iterator i;
182         i = children.begin();
183         ++i; /* skip "Disconnected" */
184
185
186         for (n = 1;  i != children.end(); ++i, ++n) {
187                 string port_name = (*i)[midi_port_columns.full_name];
188                 if (p2.input_port()->connected_to (port_name)) {
189                         input_combo.set_active (n);
190                         input_found = true;
191                         break;
192                 }
193         }
194
195         if (!input_found) {
196                 input_combo.set_active (0); /* disconnected */
197         }
198
199         children = output->children();
200         i = children.begin();
201         ++i; /* skip "Disconnected" */
202
203         for (n = 1;  i != children.end(); ++i, ++n) {
204                 string port_name = (*i)[midi_port_columns.full_name];
205                 if (p2.output_port()->connected_to (port_name)) {
206                         output_combo.set_active (n);
207                         output_found = true;
208                         break;
209                 }
210         }
211
212         if (!output_found) {
213                 output_combo.set_active (0); /* disconnected */
214         }
215 }
216
217 void
218 P2GUI::build_available_action_menu ()
219 {
220         /* build a model of all available actions (needs to be tree structured
221          * more)
222          */
223
224         available_action_model = TreeStore::create (action_columns);
225
226         vector<string> paths;
227         vector<string> labels;
228         vector<string> tooltips;
229         vector<string> keys;
230         vector<Glib::RefPtr<Gtk::Action> > actions;
231
232         Gtkmm2ext::ActionMap::get_all_actions (paths, labels, tooltips, keys, actions);
233
234         typedef std::map<string,TreeIter> NodeMap;
235         NodeMap nodes;
236         NodeMap::iterator r;
237
238
239         vector<string>::iterator k;
240         vector<string>::iterator p;
241         vector<string>::iterator t;
242         vector<string>::iterator l;
243
244         available_action_model->clear ();
245
246         TreeIter rowp;
247         TreeModel::Row parent;
248
249         /* Disabled item (row 0) */
250
251         rowp = available_action_model->append();
252         parent = *(rowp);
253         parent[action_columns.name] = _("Disabled");
254
255         /* Key aliasing */
256
257         rowp = available_action_model->append();
258         parent = *(rowp);
259         parent[action_columns.name] = _("Shift");
260         rowp = available_action_model->append();
261         parent = *(rowp);
262         parent[action_columns.name] = _("Control");
263         rowp = available_action_model->append();
264         parent = *(rowp);
265         parent[action_columns.name] = _("Option");
266         rowp = available_action_model->append();
267         parent = *(rowp);
268         parent[action_columns.name] = _("CmdAlt");
269
270
271         for (l = labels.begin(), k = keys.begin(), p = paths.begin(), t = tooltips.begin(); l != labels.end(); ++k, ++p, ++t, ++l) {
272
273                 TreeModel::Row row;
274                 vector<string> parts;
275
276                 parts.clear ();
277
278                 split (*p, parts, '/');
279
280                 if (parts.empty()) {
281                         continue;
282                 }
283
284                 //kinda kludgy way to avoid displaying menu items as mappable
285                 if ( parts[1] == _("Main_menu") )
286                         continue;
287                 if ( parts[1] == _("JACK") )
288                         continue;
289                 if ( parts[1] == _("redirectmenu") )
290                         continue;
291                 if ( parts[1] == _("Editor_menus") )
292                         continue;
293                 if ( parts[1] == _("RegionList") )
294                         continue;
295                 if ( parts[1] == _("ProcessorMenu") )
296                         continue;
297
298                 if ((r = nodes.find (parts[1])) == nodes.end()) {
299
300                         /* top level is missing */
301
302                         TreeIter rowp;
303                         TreeModel::Row parent;
304                         rowp = available_action_model->append();
305                         nodes[parts[1]] = rowp;
306                         parent = *(rowp);
307                         parent[action_columns.name] = parts[1];
308
309                         row = *(available_action_model->append (parent.children()));
310
311                 } else {
312
313                         row = *(available_action_model->append ((*r->second)->children()));
314
315                 }
316
317                 /* add this action */
318
319                 if (l->empty ()) {
320                         row[action_columns.name] = *t;
321                         action_map[*t] = *p;
322                 } else {
323                         row[action_columns.name] = *l;
324                         action_map[*l] = *p;
325                 }
326
327                 string path = (*p);
328                 /* ControlProtocol::access_action() is not interested in the
329                    legacy "<Actions>/" prefix part of a path.
330                 */
331                 path = path.substr (strlen ("<Actions>/"));
332
333                 row[action_columns.path] = path;
334         }
335 }
336
337
338 bool
339 P2GUI::find_action_in_model (const TreeModel::iterator& iter, std::string const & action_path, TreeModel::iterator* found)
340 {
341         TreeModel::Row row = *iter;
342         string path = row[action_columns.path];
343
344         if (path == action_path) {
345                 *found = iter;
346                 return true;
347         }
348
349         return false;
350 }
351
352 Glib::RefPtr<Gtk::ListStore>
353 P2GUI::build_midi_port_list (vector<string> const & ports, bool for_input)
354 {
355         Glib::RefPtr<Gtk::ListStore> store = ListStore::create (midi_port_columns);
356         TreeModel::Row row;
357
358         row = *store->append ();
359         row[midi_port_columns.full_name] = string();
360         row[midi_port_columns.short_name] = _("Disconnected");
361
362         for (vector<string>::const_iterator p = ports.begin(); p != ports.end(); ++p) {
363                 row = *store->append ();
364                 row[midi_port_columns.full_name] = *p;
365                 std::string pn = ARDOUR::AudioEngine::instance()->get_pretty_name_by_name (*p);
366                 if (pn.empty ()) {
367                         pn = (*p).substr ((*p).find (':') + 1);
368                 }
369                 row[midi_port_columns.short_name] = pn;
370         }
371
372         return store;
373 }
374
375 void
376 P2GUI::active_port_changed (Gtk::ComboBox* combo, bool for_input)
377 {
378         if (ignore_active_change) {
379                 return;
380         }
381
382         TreeModel::iterator active = combo->get_active ();
383         string new_port = (*active)[midi_port_columns.full_name];
384
385         if (new_port.empty()) {
386                 if (for_input) {
387                         p2.input_port()->disconnect_all ();
388                 } else {
389                         p2.output_port()->disconnect_all ();
390                 }
391
392                 return;
393         }
394
395         if (for_input) {
396                 if (!p2.input_port()->connected_to (new_port)) {
397                         p2.input_port()->disconnect_all ();
398                         p2.input_port()->connect (new_port);
399                 }
400         } else {
401                 if (!p2.output_port()->connected_to (new_port)) {
402                         p2.output_port()->disconnect_all ();
403                         p2.output_port()->connect (new_port);
404                 }
405         }
406 }
407
408 void
409 P2GUI::build_pad_table ()
410 {
411         container_clear (pad_table);
412
413         for (int row = 0; row < 8; ++row) {
414                 for (int col = 0; col < 8; ++col) {
415
416                         Gtk::Button* b = manage (new Button (string_compose ("%1", (int) p2.pad_note (row, col))));
417                         b->show ();
418
419                         pad_table.attach (*b, col, col+1, row, row + 1);
420                 }
421         }
422 }