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