mackie: reverse previous profile editor changes; allow profile to define anything...
[ardour.git] / libs / surfaces / mackie / gui.cc
1 /*
2         Copyright (C) 2010 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 #include <gtkmm/comboboxtext.h>
20 #include <gtkmm/box.h>
21 #include <gtkmm/spinbutton.h>
22 #include <gtkmm/table.h>
23 #include <gtkmm/treeview.h>
24 #include <gtkmm/liststore.h>
25 #include <gtkmm/treestore.h>
26 #include <gtkmm/notebook.h>
27 #include <gtkmm/cellrenderercombo.h>
28 #include <gtkmm/scale.h>
29 #include <gtkmm/alignment.h>
30
31 #include "pbd/error.h"
32 #include "pbd/unwind.h"
33 #include "pbd/strsplit.h"
34 #include "pbd/stacktrace.h"
35
36 #include "gtkmm2ext/actions.h"
37 #include "gtkmm2ext/gui_thread.h"
38 #include "gtkmm2ext/utils.h"
39
40 #include "ardour/audioengine.h"
41 #include "ardour/port.h"
42 #include "ardour/rc_configuration.h"
43
44 #include "mackie_control_protocol.h"
45 #include "device_info.h"
46 #include "gui.h"
47 #include "surface.h"
48 #include "surface_port.h"
49
50 #include "i18n.h"
51
52 using namespace std;
53 using namespace Gtk;
54 using namespace ArdourSurface;
55 using namespace Mackie;
56
57 void*
58 MackieControlProtocol::get_gui () const
59 {
60         if (!_gui) {
61                 const_cast<MackieControlProtocol*>(this)->build_gui ();
62         }
63         static_cast<Gtk::Notebook*>(_gui)->show_all();
64         return _gui;
65 }
66
67 void
68 MackieControlProtocol::tear_down_gui ()
69 {
70         if (_gui) {
71                 Gtk::Widget *w = static_cast<Gtk::Widget*>(_gui)->get_parent();
72                 if (w) {
73                         w->hide();
74                         delete w;
75                 }
76         }
77         delete (MackieControlProtocolGUI*) _gui;
78         _gui = 0;
79 }
80
81 void
82 MackieControlProtocol::build_gui ()
83 {
84         _gui = (void *) new MackieControlProtocolGUI (*this);
85 }
86
87 MackieControlProtocolGUI::MackieControlProtocolGUI (MackieControlProtocol& p)
88         : _cp (p)
89         , table (2, 9)
90         , touch_sensitivity_adjustment (0, 0, 9, 1, 4)
91         , touch_sensitivity_scale (touch_sensitivity_adjustment)
92         , recalibrate_fader_button (_("Recalibrate Faders"))
93         , ipmidi_base_port_adjustment (_cp.ipmidi_base(), 0, 32767, 1, 1000)
94         , discover_button (_("Discover Mackie Devices"))
95         , _device_dependent_widget (0)
96         , ignore_active_change (false)
97 {
98         Gtk::Label* l;
99         Gtk::Alignment* align;
100         int row = 0;
101
102         set_border_width (12);
103
104         table.set_row_spacings (4);
105         table.set_col_spacings (6);
106         table.set_border_width (12);
107         table.set_homogeneous (false);
108
109         l = manage (new Gtk::Label (_("Device Type:")));
110         l->set_alignment (1.0, 0.5);
111         table.attach (*l, 0, 1, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions(0));
112         table.attach (_surface_combo, 1, 2, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions(0), 0, 0);
113         row++;
114
115         vector<string> surfaces;
116
117         for (std::map<std::string,DeviceInfo>::iterator i = DeviceInfo::device_info.begin(); i != DeviceInfo::device_info.end(); ++i) {
118                 surfaces.push_back (i->first);
119         }
120         Gtkmm2ext::set_popdown_strings (_surface_combo, surfaces);
121         _surface_combo.signal_changed().connect (sigc::mem_fun (*this, &MackieControlProtocolGUI::surface_combo_changed));
122
123         _cp.DeviceChanged.connect (device_change_connection, invalidator (*this), boost::bind (&MackieControlProtocolGUI::device_changed, this), gui_context());
124         _cp.ConnectionChange.connect (connection_change_connection, invalidator (*this), boost::bind (&MackieControlProtocolGUI::connection_handler, this), gui_context());
125
126         ipmidi_base_port_adjustment.signal_value_changed().connect (sigc::mem_fun (*this, &MackieControlProtocolGUI::ipmidi_spinner_changed));
127
128         /* device-dependent part */
129
130         device_dependent_row = row;
131
132         if (_device_dependent_widget) {
133                 table.remove (*_device_dependent_widget);
134                 _device_dependent_widget = 0;
135         }
136
137         _device_dependent_widget = device_dependent_widget ();
138         table.attach (*_device_dependent_widget, 0, 12, row, row+1, AttachOptions(0), AttachOptions(0), 0, 0);
139         row++;
140
141         /* back to the boilerplate */
142
143         RadioButtonGroup rb_group = absolute_touch_mode_button.get_group();
144         touch_move_mode_button.set_group (rb_group);
145
146         recalibrate_fader_button.signal_clicked().connect (sigc::mem_fun (*this, &MackieControlProtocolGUI::recalibrate_faders));
147         backlight_button.signal_clicked().connect (sigc::mem_fun (*this, &MackieControlProtocolGUI::toggle_backlight));
148
149         touch_sensitivity_adjustment.signal_value_changed().connect (sigc::mem_fun (*this, &MackieControlProtocolGUI::touch_sensitive_change));
150         touch_sensitivity_scale.set_update_policy (Gtk::UPDATE_DISCONTINUOUS);
151
152         l = manage (new Gtk::Label (_("Button click")));
153         l->set_alignment (1.0, 0.5);
154         table.attach (*l, 0, 1, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
155         align = manage (new Alignment);
156         align->set (0.0, 0.5);
157         align->add (relay_click_button);
158         table.attach (*align, 1, 2, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
159         row++;
160
161         l = manage (new Gtk::Label (_("Backlight")));
162         l->set_alignment (1.0, 0.5);
163         table.attach (*l, 0, 1, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
164         align = manage (new Alignment);
165         align->set (0.0, 0.5);
166         align->add (backlight_button);
167         table.attach (*align, 1, 2, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
168         row++;
169
170         l = manage (new Gtk::Label (_("Send Fader Position Only When Touched")));
171         l->set_alignment (1.0, 0.5);
172         table.attach (*l, 0, 1, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
173         align = manage (new Alignment);
174         align->set (0.0, 0.5);
175         align->add (absolute_touch_mode_button);
176         table.attach (*align, 1, 2, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
177         row++;
178
179         l = manage (new Gtk::Label (_("Send Fader Position When Moved")));
180         l->set_alignment (1.0, 0.5);
181         table.attach (*l, 0, 1, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
182         align = manage (new Alignment);
183         align->set (0.0, 0.5);
184         align->add (touch_move_mode_button);
185         table.attach (*align, 1, 2, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
186         row++;
187
188         l = manage (new Gtk::Label (_("Fader Touch Sense Sensitivity")));
189         l->set_alignment (1.0, 0.5);
190         table.attach (*l, 0, 1, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
191         touch_sensitivity_scale.property_digits() = 0;
192         touch_sensitivity_scale.property_draw_value() = false;
193         table.attach (touch_sensitivity_scale, 1, 2, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
194         row++;
195         table.attach (recalibrate_fader_button, 1, 2, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
196         row++;
197
198
199         table.attach (discover_button, 1, 2, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
200         discover_button.signal_clicked().connect (sigc::mem_fun (*this, &MackieControlProtocolGUI::discover_clicked));
201         row++;
202
203         vector<string> profiles;
204
205         for (std::map<std::string,DeviceProfile>::iterator i = DeviceProfile::device_profiles.begin(); i != DeviceProfile::device_profiles.end(); ++i) {
206                 cerr << "add discovered profile " << i->first << endl;
207                 profiles.push_back (i->first);
208         }
209         Gtkmm2ext::set_popdown_strings (_profile_combo, profiles);
210         cerr << "set active profile from " << p.device_profile().name() << endl;
211         _profile_combo.set_active_text (p.device_profile().name());
212         _profile_combo.signal_changed().connect (sigc::mem_fun (*this, &MackieControlProtocolGUI::profile_combo_changed));
213
214         append_page (table, _("Device Setup"));
215         table.show_all();
216
217         /* function key editor */
218
219         VBox* fkey_packer = manage (new VBox);
220         HBox* profile_packer = manage (new HBox);
221         HBox* observation_packer = manage (new HBox);
222
223         l = manage (new Gtk::Label (_("Profile/Settings:")));
224         profile_packer->pack_start (*l, false, false);
225         profile_packer->pack_start (_profile_combo, true, true);
226         profile_packer->set_spacing (12);
227         profile_packer->set_border_width (12);
228
229         l = manage (new Gtk::Label (_("* Button available at the original Mackie MCU PRO or current device if enabled (NOT implemented yet). Device specific name presented.")));
230         observation_packer->pack_start (*l, false, false);
231
232         fkey_packer->pack_start (*profile_packer, false, false);
233         fkey_packer->pack_start (function_key_scroller, true, true);
234         fkey_packer->pack_start (*observation_packer, false, false);
235         fkey_packer->set_spacing (12);
236         function_key_scroller.property_shadow_type() = Gtk::SHADOW_NONE;
237         function_key_scroller.add (function_key_editor);
238         append_page (*fkey_packer, _("Function Keys"));
239
240         build_available_action_menu ();
241         build_function_key_editor ();
242         refresh_function_key_editor ();
243         fkey_packer->show_all();
244 }
245
246 void
247 MackieControlProtocolGUI::connection_handler ()
248 {
249         /* ignore all changes to combobox active strings here, because we're
250            updating them to match a new ("external") reality - we were called
251            because port connections have changed.
252         */
253
254         PBD::Unwinder<bool> ici (ignore_active_change, true);
255
256         vector<Gtk::ComboBox*>::iterator ic;
257         vector<Gtk::ComboBox*>::iterator oc;
258
259         vector<string> midi_inputs;
260         vector<string> midi_outputs;
261
262         ARDOUR::AudioEngine::instance()->get_ports ("", ARDOUR::DataType::MIDI, ARDOUR::PortFlags (ARDOUR::IsOutput|ARDOUR::IsTerminal), midi_inputs);
263         ARDOUR::AudioEngine::instance()->get_ports ("", ARDOUR::DataType::MIDI, ARDOUR::PortFlags (ARDOUR::IsInput|ARDOUR::IsTerminal), midi_outputs);
264
265         for (ic = input_combos.begin(), oc = output_combos.begin(); ic != input_combos.end() && oc != output_combos.end(); ++ic, ++oc) {
266
267                 boost::shared_ptr<Surface> surface = _cp.get_surface_by_raw_pointer ((*ic)->get_data ("surface"));
268
269                 if (surface) {
270                         update_port_combos (midi_inputs, midi_outputs, *ic, *oc, surface);
271                 }
272         }
273 }
274
275 void
276 MackieControlProtocolGUI::update_port_combos (vector<string> const& midi_inputs, vector<string> const& midi_outputs,
277                                               Gtk::ComboBox* input_combo,
278                                               Gtk::ComboBox* output_combo,
279                                               boost::shared_ptr<Surface> surface)
280 {
281         Glib::RefPtr<Gtk::ListStore> input = build_midi_port_list (midi_inputs, true);
282         Glib::RefPtr<Gtk::ListStore> output = build_midi_port_list (midi_outputs, false);
283         bool input_found = false;
284         bool output_found = false;
285         int n;
286
287         input_combo->set_model (input);
288         output_combo->set_model (output);
289
290         Gtk::TreeModel::Children children = input->children();
291         Gtk::TreeModel::Children::iterator i;
292         i = children.begin();
293         ++i; /* skip "Disconnected" */
294
295
296         for (n = 1;  i != children.end(); ++i, ++n) {
297                 string port_name = (*i)[midi_port_columns.full_name];
298                 if (surface->port().input().connected_to (port_name)) {
299                         input_combo->set_active (n);
300                         input_found = true;
301                         break;
302                 }
303         }
304
305         if (!input_found) {
306                 input_combo->set_active (0); /* disconnected */
307         }
308
309         children = output->children();
310         i = children.begin();
311         ++i; /* skip "Disconnected" */
312
313         for (n = 1;  i != children.end(); ++i, ++n) {
314                 string port_name = (*i)[midi_port_columns.full_name];
315                 if (surface->port().output().connected_to (port_name)) {
316                         output_combo->set_active (n);
317                         output_found = true;
318                         break;
319                 }
320         }
321
322         if (!output_found) {
323                 output_combo->set_active (0); /* disconnected */
324         }
325 }
326
327 Gtk::Widget*
328 MackieControlProtocolGUI::device_dependent_widget ()
329 {
330         Gtk::Table* dd_table;
331         Gtk::Label* l;
332         int row = 0;
333
334         uint32_t n_surfaces = 1 + _cp.device_info().extenders();
335
336         if (!_cp.device_info().uses_ipmidi()) {
337                 dd_table = Gtk::manage (new Gtk::Table (n_surfaces, 2));
338         } else {
339                 dd_table = Gtk::manage (new Gtk::Table (1, 2));
340         }
341
342         dd_table = Gtk::manage (new Gtk::Table (2, n_surfaces));
343         dd_table->set_row_spacings (4);
344         dd_table->set_col_spacings (6);
345         dd_table->set_border_width (12);
346
347         _surface_combo.set_active_text (_cp.device_info().name());
348
349         vector<string> midi_inputs;
350         vector<string> midi_outputs;
351
352         ARDOUR::AudioEngine::instance()->get_ports ("", ARDOUR::DataType::MIDI, ARDOUR::PortFlags (ARDOUR::IsOutput|ARDOUR::IsPhysical), midi_inputs);
353         ARDOUR::AudioEngine::instance()->get_ports ("", ARDOUR::DataType::MIDI, ARDOUR::PortFlags (ARDOUR::IsInput|ARDOUR::IsPhysical), midi_outputs);
354
355         input_combos.clear ();
356         output_combos.clear ();
357
358         if (!_cp.device_info().uses_ipmidi()) {
359
360                 for (uint32_t n = 0; n < n_surfaces; ++n) {
361
362                         boost::shared_ptr<Surface> surface = _cp.nth_surface (n);
363
364                         if (!surface) {
365                                 PBD::fatal << string_compose (_("programming error: %1\n"), string_compose ("n=%1 surface not found!", n)) << endmsg;
366                                 /*NOTREACHED*/
367                         }
368
369                         Gtk::ComboBox* input_combo = manage (new Gtk::ComboBox);
370                         Gtk::ComboBox* output_combo = manage (new Gtk::ComboBox);
371
372                         update_port_combos (midi_inputs, midi_outputs, input_combo, output_combo, surface);
373
374                         input_combo->pack_start (midi_port_columns.short_name);
375                         input_combo->set_data ("surface", surface.get());
376                         input_combos.push_back (input_combo);
377                         output_combo->pack_start (midi_port_columns.short_name);
378                         output_combo->set_data ("surface", surface.get());
379                         output_combos.push_back (output_combo);
380
381                         boost::weak_ptr<Surface> ws (surface);
382                         input_combo->signal_changed().connect (sigc::bind (sigc::mem_fun (*this, &MackieControlProtocolGUI::active_port_changed), input_combo, ws, true));
383                         output_combo->signal_changed().connect (sigc::bind (sigc::mem_fun (*this, &MackieControlProtocolGUI::active_port_changed), output_combo, ws, false));
384
385                         string send_string;
386                         string receive_string;
387
388                         if (n_surfaces > 1) {
389                                 if (n == 0) {
390                                         send_string = _("Main surface sends via:");
391                                         receive_string = _("Main surface receives via:");
392                                 } else {
393                                         send_string = string_compose (_("Extender %1 sends via:"), n);
394                                         receive_string = string_compose (_("Extender %1 receives via:"), n);
395                                 }
396                         } else {
397                                 send_string = _("Surface sends via:");
398                                 receive_string = _("Surface receives via:");
399                         }
400
401                         l = manage (new Gtk::Label (send_string));
402                         l->set_alignment (1.0, 0.5);
403                         dd_table->attach (*l, 0, 1, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions(0));
404                         dd_table->attach (*input_combo, 1, 2, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions(0), 0, 0);
405                         row++;
406
407                         l = manage (new Gtk::Label (receive_string));
408                         l->set_alignment (1.0, 0.5);
409                         dd_table->attach (*l, 0, 1, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions(0));
410                         dd_table->attach (*output_combo, 1, 2, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions(0), 0, 0);
411                         row++;
412                 }
413
414         } else {
415
416                 l = manage (new Gtk::Label (_("ipMIDI Port (lowest)")));
417                 l->set_alignment (1.0, 0.5);
418
419                 Gtk::SpinButton*  ipmidi_base_port_spinner = manage (new Gtk::SpinButton (ipmidi_base_port_adjustment));
420                 dd_table->attach (*l, 0, 1, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
421                 dd_table->attach (*ipmidi_base_port_spinner, 1, 2, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
422                 row++;
423         }
424
425         return dd_table;
426 }
427
428 CellRendererCombo*
429 MackieControlProtocolGUI::make_action_renderer (Glib::RefPtr<TreeStore> model, Gtk::TreeModelColumnBase column)
430 {
431         CellRendererCombo* renderer = manage (new CellRendererCombo);
432         renderer->property_model() = model;
433         renderer->property_editable() = true;
434         renderer->property_text_column() = 0;
435         renderer->property_has_entry() = false;
436         renderer->signal_edited().connect (sigc::bind (sigc::mem_fun(*this, &MackieControlProtocolGUI::action_changed), column));
437
438         return renderer;
439 }
440
441 void
442 MackieControlProtocolGUI::build_available_action_menu ()
443 {
444         /* build a model of all available actions (needs to be tree structured
445          * more)
446          */
447
448         available_action_model = TreeStore::create (available_action_columns);
449
450         vector<string> paths;
451         vector<string> labels;
452         vector<string> tooltips;
453         vector<string> keys;
454         vector<AccelKey> bindings;
455         typedef std::map<string,TreeIter> NodeMap;
456         NodeMap nodes;
457         NodeMap::iterator r;
458
459         ActionManager::get_all_actions (labels, paths, tooltips, keys, bindings);
460
461         vector<string>::iterator k;
462         vector<string>::iterator p;
463         vector<string>::iterator t;
464         vector<string>::iterator l;
465
466         available_action_model->clear ();
467
468         /* Because there are button bindings built in that are not
469         in the key binding map, there needs to be a way to undo
470         a profile edit. */
471         TreeIter rowp;
472         TreeModel::Row parent;
473         rowp = available_action_model->append();
474         parent = *(rowp);
475         parent[available_action_columns.name] = _("Remove Binding");
476
477         /* Key aliasing */
478
479         rowp = available_action_model->append();
480         parent = *(rowp);
481         parent[available_action_columns.name] = _("Shift");
482         rowp = available_action_model->append();
483         parent = *(rowp);
484         parent[available_action_columns.name] = _("Control");
485         rowp = available_action_model->append();
486         parent = *(rowp);
487         parent[available_action_columns.name] = _("Option");
488         rowp = available_action_model->append();
489         parent = *(rowp);
490         parent[available_action_columns.name] = _("CmdAlt");
491
492         for (l = labels.begin(), k = keys.begin(), p = paths.begin(), t = tooltips.begin(); l != labels.end(); ++k, ++p, ++t, ++l) {
493
494                 TreeModel::Row row;
495                 vector<string> parts;
496
497                 parts.clear ();
498
499                 split (*p, parts, '/');
500
501                 if (parts.empty()) {
502                         continue;
503                 }
504
505                 //kinda kludgy way to avoid displaying menu items as mappable
506                 if ( parts[1] == _("Main_menu") )
507                         continue;
508                 if ( parts[1] == _("JACK") )
509                         continue;
510                 if ( parts[1] == _("redirectmenu") )
511                         continue;
512                 if ( parts[1] == _("Editor_menus") )
513                         continue;
514                 if ( parts[1] == _("RegionList") )
515                         continue;
516                 if ( parts[1] == _("ProcessorMenu") )
517                         continue;
518
519                 if ((r = nodes.find (parts[1])) == nodes.end()) {
520
521                         /* top level is missing */
522
523                         TreeIter rowp;
524                         TreeModel::Row parent;
525                         rowp = available_action_model->append();
526                         nodes[parts[1]] = rowp;
527                         parent = *(rowp);
528                         parent[available_action_columns.name] = parts[1];
529
530                         row = *(available_action_model->append (parent.children()));
531
532                 } else {
533
534                         row = *(available_action_model->append ((*r->second)->children()));
535
536                 }
537
538                 /* add this action */
539
540                 if (l->empty ()) {
541                         row[available_action_columns.name] = *t;
542                         action_map[*t] = *p;
543                 } else {
544                         row[available_action_columns.name] = *l;
545                         action_map[*l] = *p;
546                 }
547
548                 row[available_action_columns.path] = (*p);
549         }
550 }
551
552 void
553 MackieControlProtocolGUI::build_function_key_editor ()
554 {
555         function_key_editor.append_column (_("Key"), function_key_columns.name);
556
557         TreeViewColumn* col;
558         CellRendererCombo* renderer;
559
560         renderer = make_action_renderer (available_action_model, function_key_columns.plain);
561         col = manage (new TreeViewColumn (_("Plain"), *renderer));
562         col->add_attribute (renderer->property_text(), function_key_columns.plain);
563         function_key_editor.append_column (*col);
564
565         renderer = make_action_renderer (available_action_model, function_key_columns.shift);
566         col = manage (new TreeViewColumn (_("Shift"), *renderer));
567         col->add_attribute (renderer->property_text(), function_key_columns.shift);
568         function_key_editor.append_column (*col);
569
570         renderer = make_action_renderer (available_action_model, function_key_columns.control);
571         col = manage (new TreeViewColumn (_("Control"), *renderer));
572         col->add_attribute (renderer->property_text(), function_key_columns.control);
573         function_key_editor.append_column (*col);
574
575         renderer = make_action_renderer (available_action_model, function_key_columns.option);
576         col = manage (new TreeViewColumn (_("Option"), *renderer));
577         col->add_attribute (renderer->property_text(), function_key_columns.option);
578         function_key_editor.append_column (*col);
579
580         renderer = make_action_renderer (available_action_model, function_key_columns.cmdalt);
581         col = manage (new TreeViewColumn (_("Cmd/Alt"), *renderer));
582         col->add_attribute (renderer->property_text(), function_key_columns.cmdalt);
583         function_key_editor.append_column (*col);
584
585         renderer = make_action_renderer (available_action_model, function_key_columns.shiftcontrol);
586         col = manage (new TreeViewColumn (_("Shift+Control"), *renderer));
587         col->add_attribute (renderer->property_text(), function_key_columns.shiftcontrol);
588         function_key_editor.append_column (*col);
589
590         function_key_model = ListStore::create (function_key_columns);
591         function_key_editor.set_model (function_key_model);
592 }
593
594 void
595 MackieControlProtocolGUI::refresh_function_key_editor ()
596 {
597         function_key_editor.set_model (Glib::RefPtr<TreeModel>());
598         function_key_model->clear ();
599
600         /* now fill with data */
601
602         TreeModel::Row row;
603         DeviceProfile dp (_cp.device_profile());
604         DeviceInfo di;
605
606         for (int n = 0; n < Mackie::Button::FinalGlobalButton; ++n) {
607
608                 Mackie::Button::ID bid = (Mackie::Button::ID) n;
609
610                 row = *(function_key_model->append());
611                 if (di.global_buttons().find (bid) == di.global_buttons().end()) {
612                         row[function_key_columns.name] = Mackie::Button::id_to_name (bid);
613                 } else {
614                         row[function_key_columns.name] = di.get_global_button_name (bid) + "*";
615                 }
616                 row[function_key_columns.id] = bid;
617
618                 Glib::RefPtr<Gtk::Action> act;
619                 string action;
620                 const string defstring = "\u2022";
621
622                 /* We only allow plain bindings for Fn keys. All others are
623                  * reserved for hard-coded actions.
624                  */
625
626                 if (bid >= Mackie::Button::F1 && bid <= Mackie::Button::F8) {
627
628                         action = dp.get_button_action (bid, 0);
629                         if (action.empty()) {
630                                 row[function_key_columns.plain] = defstring;
631                         } else {
632                                 if (action.find ('/') == string::npos) {
633                                         /* Probably a key alias */
634                                         row[function_key_columns.plain] = action;
635                                 } else {
636
637                                         act = ActionManager::get_action (action.c_str());
638                                         if (act) {
639                                                 row[function_key_columns.plain] = act->get_label();
640                                         } else {
641                                                 row[function_key_columns.plain] = defstring;
642                                         }
643                                 }
644                         }
645                 }
646
647                 /* We only allow plain bindings for Fn keys. All others are
648                  * reserved for hard-coded actions.
649                  */
650
651                 if (bid >= Mackie::Button::F1 && bid <= Mackie::Button::F8) {
652
653                         action = dp.get_button_action (bid, MackieControlProtocol::MODIFIER_SHIFT);
654                         if (action.empty()) {
655                                 row[function_key_columns.shift] = defstring;
656                         } else {
657                                 if (action.find ('/') == string::npos) {
658                                         /* Probably a key alias */
659                                         row[function_key_columns.shift] = action;
660                                 } else {
661                                         act = ActionManager::get_action (action.c_str());
662                                         if (act) {
663                                                 row[function_key_columns.shift] = act->get_label();
664                                         } else {
665                                                 row[function_key_columns.shift] = defstring;
666                                         }
667                                 }
668                         }
669                 }
670
671                 action = dp.get_button_action (bid, MackieControlProtocol::MODIFIER_CONTROL);
672                 if (action.empty()) {
673                         row[function_key_columns.control] = defstring;
674                 } else {
675                         if (action.find ('/') == string::npos) {
676                                 /* Probably a key alias */
677                                 row[function_key_columns.control] = action;
678                         } else {
679                                 act = ActionManager::get_action (action.c_str());
680                                 if (act) {
681                                         row[function_key_columns.control] = act->get_label();
682                                 } else {
683                                         row[function_key_columns.control] = defstring;
684                                 }
685                         }
686                 }
687
688                 action = dp.get_button_action (bid, MackieControlProtocol::MODIFIER_OPTION);
689                 if (action.empty()) {
690                         row[function_key_columns.option] = defstring;
691                 } else {
692                         if (action.find ('/') == string::npos) {
693                                 /* Probably a key alias */
694                                 row[function_key_columns.option] = action;
695                         } else {
696                                 act = ActionManager::get_action (action.c_str());
697                                 if (act) {
698                                         row[function_key_columns.option] = act->get_label();
699                                 } else {
700                                         row[function_key_columns.option] = defstring;
701                                 }
702                         }
703                 }
704
705                 action = dp.get_button_action (bid, MackieControlProtocol::MODIFIER_CMDALT);
706                 if (action.empty()) {
707                         row[function_key_columns.cmdalt] = defstring;
708                 } else {
709                         if (action.find ('/') == string::npos) {
710                                 /* Probably a key alias */
711                                 row[function_key_columns.cmdalt] = action;
712                         } else {
713                                 act = ActionManager::get_action (action.c_str());
714                                 if (act) {
715                                         row[function_key_columns.cmdalt] = act->get_label();
716                                 } else {
717                                         row[function_key_columns.cmdalt] = defstring;
718                                 }
719                         }
720                 }
721
722                 action = dp.get_button_action (bid, (MackieControlProtocol::MODIFIER_SHIFT|MackieControlProtocol::MODIFIER_CONTROL));
723                 if (action.empty()) {
724                         row[function_key_columns.shiftcontrol] = defstring;
725                 } else {
726                         act = ActionManager::get_action (action.c_str());
727                         if (act) {
728                                 row[function_key_columns.shiftcontrol] = act->get_label();
729                         } else {
730                                 row[function_key_columns.shiftcontrol] = defstring;
731                         }
732                 }
733         }
734
735         function_key_editor.set_model (function_key_model);
736 }
737
738 void
739 MackieControlProtocolGUI::action_changed (const Glib::ustring &sPath, const Glib::ustring &text, TreeModelColumnBase col)
740 {
741         // Remove Binding is not in the action map but still valid
742         bool remove (false);
743         if ( text == "Remove Binding") {
744                 remove = true;
745         }
746         Gtk::TreePath path(sPath);
747         Gtk::TreeModel::iterator row = function_key_model->get_iter(path);
748
749         if (row) {
750
751                 std::map<std::string,std::string>::iterator i = action_map.find (text);
752
753                 if (i == action_map.end()) {
754                         if (!remove) {
755                                 return;
756                         }
757                 }
758                 Glib::RefPtr<Gtk::Action> act = ActionManager::get_action (i->second.c_str());
759
760                 if (act || remove) {
761                         /* update visible text, using string supplied by
762                            available action model so that it matches and is found
763                            within the model.
764                         */
765                         if (remove) {
766                                 Glib::ustring dot = "\u2022";
767                                 (*row).set_value (col.index(), dot);
768                         } else {
769                                 (*row).set_value (col.index(), text);
770                         }
771
772                         /* update the current DeviceProfile, using the full
773                          * path
774                          */
775
776                         int modifier;
777
778                         switch (col.index()) {
779                         case 3:
780                                 modifier = MackieControlProtocol::MODIFIER_SHIFT;
781                                 break;
782                         case 4:
783                                 modifier = MackieControlProtocol::MODIFIER_CONTROL;
784                                 break;
785                         case 5:
786                                 modifier = MackieControlProtocol::MODIFIER_OPTION;
787                                 break;
788                         case 6:
789                                 modifier = MackieControlProtocol::MODIFIER_CMDALT;
790                                 break;
791                         case 7:
792                                 modifier = (MackieControlProtocol::MODIFIER_SHIFT|MackieControlProtocol::MODIFIER_CONTROL);
793                                 break;
794                         default:
795                                 modifier = 0;
796                         }
797
798                         if (remove) {
799                                 _cp.device_profile().set_button_action ((*row)[function_key_columns.id], modifier, "");
800                         } else {
801                                 _cp.device_profile().set_button_action ((*row)[function_key_columns.id], modifier, i->second);
802                         }
803
804                 } else {
805                         std::cerr << "no such action\n";
806                 }
807         }
808 }
809
810 void
811 MackieControlProtocolGUI::surface_combo_changed ()
812 {
813         _cp.set_device (_surface_combo.get_active_text(), false);
814 }
815
816 void
817 MackieControlProtocolGUI::device_changed ()
818 {
819         if (_device_dependent_widget) {
820                 table.remove (*_device_dependent_widget);
821                 _device_dependent_widget = 0;
822         }
823
824         _device_dependent_widget = device_dependent_widget ();
825         _device_dependent_widget->show_all ();
826
827         table.attach (*_device_dependent_widget, 0, 12, device_dependent_row, device_dependent_row+1, AttachOptions(0), AttachOptions(0), 0, 0);
828 }
829
830 void
831 MackieControlProtocolGUI::profile_combo_changed ()
832 {
833         string profile = _profile_combo.get_active_text();
834
835         _cp.set_profile (profile);
836
837         refresh_function_key_editor ();
838 }
839
840 void
841 MackieControlProtocolGUI::ipmidi_spinner_changed ()
842 {
843         _cp.set_ipmidi_base ((int16_t) lrintf (ipmidi_base_port_adjustment.get_value()));
844 }
845
846 void
847 MackieControlProtocolGUI::discover_clicked ()
848 {
849         /* this should help to get things started */
850         _cp.ping_devices ();
851 }
852
853 void
854 MackieControlProtocolGUI::recalibrate_faders ()
855 {
856         _cp.recalibrate_faders ();
857 }
858
859 void
860 MackieControlProtocolGUI::toggle_backlight ()
861 {
862         _cp.toggle_backlight ();
863 }
864
865 void
866 MackieControlProtocolGUI::touch_sensitive_change ()
867 {
868         int sensitivity = (int) touch_sensitivity_adjustment.get_value ();
869         _cp.set_touch_sensitivity (sensitivity);
870 }
871
872 Glib::RefPtr<Gtk::ListStore>
873 MackieControlProtocolGUI::build_midi_port_list (vector<string> const & ports, bool for_input)
874 {
875         Glib::RefPtr<Gtk::ListStore> store = ListStore::create (midi_port_columns);
876         TreeModel::Row row;
877
878         row = *store->append ();
879         row[midi_port_columns.full_name] = string();
880         row[midi_port_columns.short_name] = _("Disconnected");
881
882         for (vector<string>::const_iterator p = ports.begin(); p != ports.end(); ++p) {
883                 row = *store->append ();
884                 row[midi_port_columns.full_name] = *p;
885                 std::string pn = ARDOUR::AudioEngine::instance()->get_pretty_name_by_name (*p);
886                 if (pn.empty ()) {
887                         pn = (*p).substr ((*p).find (':') + 1);
888                 }
889                 row[midi_port_columns.short_name] = pn;
890         }
891
892         return store;
893 }
894
895 void
896 MackieControlProtocolGUI::active_port_changed (Gtk::ComboBox* combo, boost::weak_ptr<Surface> ws, bool for_input)
897 {
898         if (ignore_active_change) {
899                 return;
900         }
901
902         boost::shared_ptr<Surface> surface = ws.lock();
903
904         if (!surface) {
905                 return;
906         }
907
908         TreeModel::iterator active = combo->get_active ();
909         string new_port = (*active)[midi_port_columns.full_name];
910
911         if (new_port.empty()) {
912                 if (for_input) {
913                         surface->port().input().disconnect_all ();
914                 } else {
915                         surface->port().output().disconnect_all ();
916                 }
917
918                 return;
919         }
920
921         if (for_input) {
922                 if (!surface->port().input().connected_to (new_port)) {
923                         surface->port().input().disconnect_all ();
924                         surface->port().input().connect (new_port);
925                 }
926         } else {
927                 if (!surface->port().output().connected_to (new_port)) {
928                         surface->port().output().disconnect_all ();
929                         surface->port().output().connect (new_port);
930                 }
931         }
932 }