2 Copyright (C) 2015 Paul Davis
3 Copyright (C) 2016 W.P. van Paassen
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21 #include <gtkmm/alignment.h>
22 #include <gtkmm/label.h>
23 #include <gtkmm/liststore.h>
25 #include "pbd/unwind.h"
26 #include "pbd/strsplit.h"
27 #include "pbd/file_utils.h"
29 #include "gtkmm2ext/bindings.h"
30 #include "gtkmm2ext/gtk_ui.h"
31 #include "gtkmm2ext/gui_thread.h"
32 #include "gtkmm2ext/utils.h"
34 #include "ardour/audioengine.h"
35 #include "ardour/filesystem_paths.h"
43 using namespace ARDOUR;
44 using namespace ArdourSurface;
47 using namespace Gtkmm2ext;
50 CC121::get_gui () const
53 const_cast<CC121*>(this)->build_gui ();
55 static_cast<Gtk::VBox*>(gui)->show_all();
60 CC121::tear_down_gui ()
63 Gtk::Widget *w = static_cast<Gtk::VBox*>(gui)->get_parent();
69 delete static_cast<CC121GUI*> (gui);
76 gui = (void*) new CC121GUI (*this);
79 /*--------------------*/
81 CC121GUI::CC121GUI (CC121& p)
85 , ignore_active_change (false)
87 set_border_width (12);
89 table.set_row_spacings (4);
90 table.set_col_spacings (6);
91 table.set_border_width (12);
92 table.set_homogeneous (false);
94 std::string data_file_path;
95 string name = "cc121.png";
96 Searchpath spath(ARDOUR::ardour_data_search_path());
97 spath.add_subdirectory_to_paths ("icons");
98 find_file (spath, name, data_file_path);
99 if (!data_file_path.empty()) {
100 image.set (data_file_path);
101 hpacker.pack_start (image, false, false);
105 Gtk::Alignment* align;
109 input_combo.pack_start (midi_port_columns.short_name);
110 output_combo.pack_start (midi_port_columns.short_name);
112 input_combo.signal_changed().connect (sigc::bind (sigc::mem_fun (*this, &CC121GUI::active_port_changed), &input_combo, true));
113 output_combo.signal_changed().connect (sigc::bind (sigc::mem_fun (*this, &CC121GUI::active_port_changed), &output_combo, false));
115 l = manage (new Gtk::Label);
116 l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Incoming MIDI on:")));
117 l->set_alignment (1.0, 0.5);
118 table.attach (*l, 0, 1, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions(0));
119 table.attach (input_combo, 1, 2, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions(0), 0, 0);
122 l = manage (new Gtk::Label);
123 l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Outgoing MIDI on:")));
124 l->set_alignment (1.0, 0.5);
125 table.attach (*l, 0, 1, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions(0));
126 table.attach (output_combo, 1, 2, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions(0), 0, 0);
129 build_available_action_menu ();
131 build_user_action_combo (function1_combo, CC121::ButtonState(0), CC121::Function1);
132 build_user_action_combo (function2_combo, CC121::ButtonState(0), CC121::Function2);
133 build_user_action_combo (function3_combo, CC121::ButtonState(0), CC121::Function3);
134 build_user_action_combo (function4_combo, CC121::ButtonState(0), CC121::Function4);
135 build_user_action_combo (value_combo, CC121::ButtonState(0), CC121::Value);
136 build_user_action_combo (lock_combo, CC121::ButtonState(0), CC121::Lock);
137 build_user_action_combo (eq1_combo, CC121::ButtonState(0), CC121::EQ1Enable);
138 build_user_action_combo (eq2_combo, CC121::ButtonState(0), CC121::EQ2Enable);
139 build_user_action_combo (eq3_combo, CC121::ButtonState(0), CC121::EQ3Enable);
140 build_user_action_combo (eq4_combo, CC121::ButtonState(0), CC121::EQ4Enable);
141 build_user_action_combo (eqtype_combo, CC121::ButtonState(0), CC121::EQType);
142 build_user_action_combo (allbypass_combo, CC121::ButtonState(0), CC121::AllBypass);
143 build_foot_action_combo (foot_combo, CC121::ButtonState(0));
144 action_table.set_row_spacings (4);
145 action_table.set_col_spacings (6);
146 action_table.set_border_width (12);
147 action_table.set_homogeneous (false);
149 l = manage (new Gtk::Label);
150 l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Function 1")));
151 l->set_alignment (1.0, 0.5);
152 action_table.attach (*l, 0, 1, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
153 align = manage (new Alignment);
154 align->set (0.0, 0.5);
155 align->add (function1_combo);
156 action_table.attach (*align, 1, 2, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
159 l = manage (new Gtk::Label);
160 l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Function 2")));
161 l->set_alignment (1.0, 0.5);
162 action_table.attach (*l, 0, 1, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
163 align = manage (new Alignment);
164 align->set (0.0, 0.5);
165 align->add (function2_combo);
166 action_table.attach (*align, 1, 2, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
169 l = manage (new Gtk::Label);
170 l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Function 3")));
171 l->set_alignment (1.0, 0.5);
172 action_table.attach (*l, 0, 1, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
173 align = manage (new Alignment);
174 align->set (0.0, 0.5);
175 align->add (function3_combo);
176 action_table.attach (*align, 1, 2, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
179 l = manage (new Gtk::Label);
180 l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Function 4")));
181 l->set_alignment (1.0, 0.5);
182 action_table.attach (*l, 0, 1, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
183 align = manage (new Alignment);
184 align->set (0.0, 0.5);
185 align->add (function4_combo);
186 action_table.attach (*align, 1, 2, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
189 l = manage (new Gtk::Label);
190 l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Value")));
191 l->set_alignment (1.0, 0.5);
192 action_table.attach (*l, 0, 1, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
193 align = manage (new Alignment);
194 align->set (0.0, 0.5);
195 align->add (value_combo);
196 action_table.attach (*align, 1, 2, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
199 l = manage (new Gtk::Label);
200 l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Lock")));
201 l->set_alignment (1.0, 0.5);
202 action_table.attach (*l, 0, 1, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
203 align = manage (new Alignment);
204 align->set (0.0, 0.5);
205 align->add (lock_combo);
206 action_table.attach (*align, 1, 2, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
209 l = manage (new Gtk::Label);
210 l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("EQ1")));
211 l->set_alignment (1.0, 0.5);
212 action_table.attach (*l, 0, 1, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
213 align = manage (new Alignment);
214 align->set (0.0, 0.5);
215 align->add (eq1_combo);
216 action_table.attach (*align, 1, 2, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
219 l = manage (new Gtk::Label);
220 l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("EQ2")));
221 l->set_alignment (1.0, 0.5);
222 action_table.attach (*l, 0, 1, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
223 align = manage (new Alignment);
224 align->set (0.0, 0.5);
225 align->add (eq2_combo);
226 action_table.attach (*align, 1, 2, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
229 l = manage (new Gtk::Label);
230 l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("EQ3")));
231 l->set_alignment (1.0, 0.5);
232 action_table.attach (*l, 0, 1, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
233 align = manage (new Alignment);
234 align->set (0.0, 0.5);
235 align->add (eq3_combo);
236 action_table.attach (*align, 1, 2, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
239 l = manage (new Gtk::Label);
240 l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("EQ4")));
241 l->set_alignment (1.0, 0.5);
242 action_table.attach (*l, 0, 1, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
243 align = manage (new Alignment);
244 align->set (0.0, 0.5);
245 align->add (eq4_combo);
246 action_table.attach (*align, 1, 2, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
249 l = manage (new Gtk::Label);
250 l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("EQType")));
251 l->set_alignment (1.0, 0.5);
252 action_table.attach (*l, 0, 1, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
253 align = manage (new Alignment);
254 align->set (0.0, 0.5);
255 align->add (eqtype_combo);
256 action_table.attach (*align, 1, 2, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
259 l = manage (new Gtk::Label);
260 l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("AllBypass")));
261 l->set_alignment (1.0, 0.5);
262 action_table.attach (*l, 0, 1, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
263 align = manage (new Alignment);
264 align->set (0.0, 0.5);
265 align->add (allbypass_combo);
266 action_table.attach (*align, 1, 2, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
269 l = manage (new Gtk::Label);
270 l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Footswitch")));
271 l->set_alignment (1.0, 0.5);
272 action_table.attach (*l, 0, 1, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
273 align = manage (new Alignment);
274 align->set (0.0, 0.5);
275 align->add (foot_combo);
276 action_table.attach (*align, 1, 2, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
279 table.attach (action_table, 0, 5, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
282 hpacker.pack_start (table, true, true);
283 pack_start (hpacker, false, false);
285 /* update the port connection combos */
287 update_port_combos ();
289 /* catch future changes to connection state */
291 fp.ConnectionChange.connect (connection_change_connection, invalidator (*this), boost::bind (&CC121GUI::connection_handler, this), gui_context());
294 CC121GUI::~CC121GUI ()
299 CC121GUI::connection_handler ()
301 /* ignore all changes to combobox active strings here, because we're
302 updating them to match a new ("external") reality - we were called
303 because port connections have changed.
306 PBD::Unwinder<bool> ici (ignore_active_change, true);
308 update_port_combos ();
312 CC121GUI::update_port_combos ()
314 vector<string> midi_inputs;
315 vector<string> midi_outputs;
317 ARDOUR::AudioEngine::instance()->get_ports ("", ARDOUR::DataType::MIDI, ARDOUR::PortFlags (ARDOUR::IsOutput|ARDOUR::IsTerminal), midi_inputs);
318 ARDOUR::AudioEngine::instance()->get_ports ("", ARDOUR::DataType::MIDI, ARDOUR::PortFlags (ARDOUR::IsInput|ARDOUR::IsTerminal), midi_outputs);
320 Glib::RefPtr<Gtk::ListStore> input = build_midi_port_list (midi_inputs, true);
321 Glib::RefPtr<Gtk::ListStore> output = build_midi_port_list (midi_outputs, false);
322 bool input_found = false;
323 bool output_found = false;
326 input_combo.set_model (input);
327 output_combo.set_model (output);
329 Gtk::TreeModel::Children children = input->children();
330 Gtk::TreeModel::Children::iterator i;
331 i = children.begin();
332 ++i; /* skip "Disconnected" */
335 for (n = 1; i != children.end(); ++i, ++n) {
336 string port_name = (*i)[midi_port_columns.full_name];
337 if (fp.input_port()->connected_to (port_name)) {
338 input_combo.set_active (n);
345 input_combo.set_active (0); /* disconnected */
348 children = output->children();
349 i = children.begin();
350 ++i; /* skip "Disconnected" */
352 for (n = 1; i != children.end(); ++i, ++n) {
353 string port_name = (*i)[midi_port_columns.full_name];
354 if (fp.output_port()->connected_to (port_name)) {
355 output_combo.set_active (n);
362 output_combo.set_active (0); /* disconnected */
367 CC121GUI::build_available_action_menu ()
369 /* build a model of all available actions (needs to be tree structured
373 available_action_model = TreeStore::create (action_columns);
375 vector<string> paths;
376 vector<string> labels;
377 vector<string> tooltips;
379 vector<Glib::RefPtr<Gtk::Action> > actions;
381 Gtkmm2ext::ActionMap::get_all_actions (paths, labels, tooltips, keys, actions);
383 typedef std::map<string,TreeIter> NodeMap;
388 vector<string>::iterator k;
389 vector<string>::iterator p;
390 vector<string>::iterator t;
391 vector<string>::iterator l;
393 available_action_model->clear ();
396 TreeModel::Row parent;
398 /* Disabled item (row 0) */
400 rowp = available_action_model->append();
402 parent[action_columns.name] = _("Disabled");
404 for (l = labels.begin(), k = keys.begin(), p = paths.begin(), t = tooltips.begin(); l != labels.end(); ++k, ++p, ++t, ++l) {
407 vector<string> parts;
411 split (*p, parts, '/');
417 //kinda kludgy way to avoid displaying menu items as mappable
418 if ( parts[1] == _("Main_menu") )
420 if ( parts[1] == _("JACK") )
422 if ( parts[1] == _("redirectmenu") )
424 if ( parts[1] == _("Editor_menus") )
426 if ( parts[1] == _("RegionList") )
428 if ( parts[1] == _("ProcessorMenu") )
431 if ((r = nodes.find (parts[1])) == nodes.end()) {
433 /* top level is missing */
436 TreeModel::Row parent;
437 rowp = available_action_model->append();
438 nodes[parts[1]] = rowp;
440 parent[action_columns.name] = parts[1];
442 row = *(available_action_model->append (parent.children()));
446 row = *(available_action_model->append ((*r->second)->children()));
450 /* add this action */
453 row[action_columns.name] = *t;
456 row[action_columns.name] = *l;
461 /* ControlProtocol::access_action() is not interested in the
462 legacy "<Actions>/" prefix part of a path.
464 path = path.substr (strlen ("<Actions>/"));
466 row[action_columns.path] = path;
471 CC121GUI::action_changed (Gtk::ComboBox* cb, CC121::ButtonID id, CC121::ButtonState bs)
473 TreeModel::const_iterator row = cb->get_active ();
474 string action_path = (*row)[action_columns.path];
476 /* release binding */
477 fp.set_action (id, action_path, false, bs);
481 CC121GUI::build_action_combo (Gtk::ComboBox& cb, vector<pair<string,string> > const & actions, CC121::ButtonID id, CC121::ButtonState bs)
483 Glib::RefPtr<Gtk::ListStore> model (Gtk::ListStore::create (action_columns));
486 string current_action = fp.get_action (id, false, bs); /* lookup release action */
489 vector<pair<string,string> >::const_iterator i;
491 rowp = model->append();
493 row[action_columns.name] = _("Disabled");
494 row[action_columns.path] = string();
496 if (current_action.empty()) {
500 for (i = actions.begin(), n = 0; i != actions.end(); ++i, ++n) {
501 rowp = model->append();
503 row[action_columns.name] = i->first;
504 row[action_columns.path] = i->second;
505 if (current_action == i->second) {
510 cb.set_model (model);
511 cb.pack_start (action_columns.name);
513 if (active_row >= 0) {
514 cb.set_active (active_row);
517 cb.signal_changed().connect (sigc::bind (sigc::mem_fun (*this, &CC121GUI::action_changed), &cb, id, bs));
521 CC121GUI::build_foot_action_combo (Gtk::ComboBox& cb, CC121::ButtonState bs)
523 vector<pair<string,string> > actions;
525 actions.push_back (make_pair (string("Toggle Roll"), string(X_("Transport/ToggleRoll"))));
526 actions.push_back (make_pair (string("Toggle Rec-Enable"), string(X_("Transport/Record"))));
527 actions.push_back (make_pair (string("Toggle Roll+Rec"), string(X_("Transport/record-roll"))));
528 actions.push_back (make_pair (string("Toggle Loop"), string(X_("Transport/Loop"))));
529 actions.push_back (make_pair (string("Toggle Click"), string(X_("Transport/ToggleClick"))));
531 build_action_combo (cb, actions, CC121::Footswitch, bs);
535 CC121GUI::find_action_in_model (const TreeModel::iterator& iter, std::string const & action_path, TreeModel::iterator* found)
537 TreeModel::Row row = *iter;
538 string path = row[action_columns.path];
540 if (path == action_path) {
549 CC121GUI::build_user_action_combo (Gtk::ComboBox& cb, CC121::ButtonState bs, CC121::ButtonID id)
551 cb.set_model (available_action_model);
552 cb.pack_start (action_columns.name);
553 cb.signal_changed().connect (sigc::bind (sigc::mem_fun (*this, &CC121GUI::action_changed), &cb, id, bs));
555 /* set the active "row" to the right value for the current button binding */
557 string current_action = fp.get_action (id, false, bs); /* lookup release action */
559 if (current_action.empty()) {
560 cb.set_active (0); /* "disabled" */
564 TreeModel::iterator iter = available_action_model->children().end();
566 available_action_model->foreach_iter (sigc::bind (sigc::mem_fun (*this, &CC121GUI::find_action_in_model), current_action, &iter));
568 if (iter != available_action_model->children().end()) {
569 cb.set_active (iter);
575 Glib::RefPtr<Gtk::ListStore>
576 CC121GUI::build_midi_port_list (vector<string> const & ports, bool for_input)
578 Glib::RefPtr<Gtk::ListStore> store = ListStore::create (midi_port_columns);
581 row = *store->append ();
582 row[midi_port_columns.full_name] = string();
583 row[midi_port_columns.short_name] = _("Disconnected");
585 for (vector<string>::const_iterator p = ports.begin(); p != ports.end(); ++p) {
586 row = *store->append ();
587 row[midi_port_columns.full_name] = *p;
588 std::string pn = ARDOUR::AudioEngine::instance()->get_pretty_name_by_name (*p);
590 pn = (*p).substr ((*p).find (':') + 1);
592 row[midi_port_columns.short_name] = pn;
599 CC121GUI::active_port_changed (Gtk::ComboBox* combo, bool for_input)
601 if (ignore_active_change) {
605 TreeModel::iterator active = combo->get_active ();
606 string new_port = (*active)[midi_port_columns.full_name];
608 if (new_port.empty()) {
610 fp.input_port()->disconnect_all ();
612 fp.output_port()->disconnect_all ();
619 if (!fp.input_port()->connected_to (new_port)) {
620 fp.input_port()->disconnect_all ();
621 fp.input_port()->connect (new_port);
624 if (!fp.output_port()->connected_to (new_port)) {
625 fp.output_port()->disconnect_all ();
626 fp.output_port()->connect (new_port);