2 Copyright (C) 2015 Paul Davis
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.
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.
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.
20 #include <gtkmm/alignment.h>
21 #include <gtkmm/label.h>
22 #include <gtkmm/liststore.h>
24 #include "pbd/unwind.h"
25 #include "pbd/strsplit.h"
26 #include "pbd/file_utils.h"
28 #include "gtkmm2ext/bindings.h"
29 #include "gtkmm2ext/gui_thread.h"
30 #include "gtkmm2ext/utils.h"
32 #include "ardour/audioengine.h"
33 #include "ardour/filesystem_paths.h"
34 #include "ardour/parameter_descriptor.h"
42 using namespace ARDOUR;
43 using namespace ArdourSurface;
46 using namespace Gtkmm2ext;
49 Push2::get_gui () const
52 const_cast<Push2*>(this)->build_gui ();
54 static_cast<Gtk::VBox*>(gui)->show_all();
59 Push2::tear_down_gui ()
62 Gtk::Widget *w = static_cast<Gtk::VBox*>(gui)->get_parent();
75 gui = new P2GUI (*this);
78 /*--------------------*/
80 P2GUI::P2GUI (Push2& p)
84 , ignore_active_change (false)
86 , root_note_octave_adjustment (p2.root_octave(), 0, 10, 1, 1)
87 , root_note_octave (root_note_octave_adjustment)
88 , root_note_octave_label (_("Octave"))
89 , root_note_label (_("Root"))
90 , mode_label (_("Mode (Scale)"))
91 , inkey_button (_("In-Key Mode"))
94 set_border_width (12);
96 table.set_row_spacings (4);
97 table.set_col_spacings (6);
98 table.set_border_width (12);
99 table.set_homogeneous (false);
101 std::string data_file_path;
102 string name = "push2-small.png";
103 Searchpath spath(ARDOUR::ardour_data_search_path());
104 spath.add_subdirectory_to_paths ("icons");
105 find_file (spath, name, data_file_path);
106 if (!data_file_path.empty()) {
107 image.set (data_file_path);
108 hpacker.pack_start (image, false, false);
114 input_combo.pack_start (midi_port_columns.short_name);
115 output_combo.pack_start (midi_port_columns.short_name);
117 input_combo.signal_changed().connect (sigc::bind (sigc::mem_fun (*this, &P2GUI::active_port_changed), &input_combo, true));
118 output_combo.signal_changed().connect (sigc::bind (sigc::mem_fun (*this, &P2GUI::active_port_changed), &output_combo, false));
120 l = manage (new Gtk::Label);
121 l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Incoming MIDI on:")));
122 l->set_alignment (1.0, 0.5);
123 table.attach (*l, 0, 1, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions(0));
124 table.attach (input_combo, 1, 2, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions(0), 0, 0);
127 l = manage (new Gtk::Label);
128 l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Outgoing MIDI on:")));
129 l->set_alignment (1.0, 0.5);
130 table.attach (*l, 0, 1, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions(0));
131 table.attach (output_combo, 1, 2, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions(0), 0, 0);
134 hpacker.pack_start (table, true, true);
136 pad_table.set_spacings (3);
139 root_note_selector.set_model (build_note_columns());
140 root_note_selector.pack_start (note_columns.name);
141 root_note_selector.set_active (p2.scale_root());
143 mode_selector.set_model (build_mode_columns());
144 mode_selector.pack_start (mode_columns.name);
145 mode_selector.set_active ((int) p2.mode());
147 mode_packer.set_border_width (12);
148 mode_packer.set_spacings (12);
150 mode_packer.attach (root_note_label, 0, 1, 0, 1, AttachOptions (FILL|EXPAND), SHRINK);
151 mode_packer.attach (root_note_selector, 1, 2, 0, 1, AttachOptions (FILL|EXPAND), SHRINK);
153 mode_packer.attach (root_note_octave_label, 0, 1, 1, 2, AttachOptions (FILL|EXPAND), SHRINK);
154 mode_packer.attach (root_note_octave, 1, 2, 1, 2, AttachOptions (FILL|EXPAND), SHRINK);
156 mode_packer.attach (mode_label, 0, 1, 2, 3, AttachOptions (FILL|EXPAND), SHRINK);
157 mode_packer.attach (mode_selector, 1, 2, 2, 3, AttachOptions (FILL|EXPAND), SHRINK);
159 inkey_button.set_active (p2.in_key());
160 mode_packer.attach (inkey_button, 1, 2, 3, 4, AttachOptions (FILL|EXPAND), SHRINK);
162 pad_notebook.append_page (pad_table, _("Pad Layout"));
163 pad_notebook.append_page (mode_packer, _("Modes/Scales"));
164 pad_notebook.append_page (custom_packer, _("Custom"));
166 root_note_octave_adjustment.signal_value_changed().connect (sigc::mem_fun (*this, &P2GUI::reprogram_pad_scale));
167 root_note_selector.signal_changed().connect (sigc::mem_fun (*this, &P2GUI::reprogram_pad_scale));
168 mode_selector.signal_changed().connect (sigc::mem_fun (*this, &P2GUI::reprogram_pad_scale));
169 inkey_button.signal_clicked().connect (sigc::mem_fun (*this, &P2GUI::reprogram_pad_scale));
173 pack_start (hpacker, false, false);
174 pack_start (pad_notebook);
176 /* update the port connection combos */
178 update_port_combos ();
180 /* catch future changes to connection state */
182 // p2.ConnectionChange.connect (connection_change_connection, invalidator (*this), boost::bind (&P2GUI::connection_handler, this), gui_context());
183 p2.PadChange.connect (p2_connections, invalidator (*this), boost::bind (&P2GUI::build_pad_table, this), gui_context());
191 P2GUI::connection_handler ()
193 /* ignore all changes to combobox active strings here, because we're
194 updating them to match a new ("external") reality - we were called
195 because port connections have changed.
198 PBD::Unwinder<bool> ici (ignore_active_change, true);
200 update_port_combos ();
204 P2GUI::update_port_combos ()
206 vector<string> midi_inputs;
207 vector<string> midi_outputs;
209 ARDOUR::AudioEngine::instance()->get_ports ("", ARDOUR::DataType::MIDI, ARDOUR::PortFlags (ARDOUR::IsOutput|ARDOUR::IsTerminal), midi_inputs);
210 ARDOUR::AudioEngine::instance()->get_ports ("", ARDOUR::DataType::MIDI, ARDOUR::PortFlags (ARDOUR::IsInput|ARDOUR::IsTerminal), midi_outputs);
212 Glib::RefPtr<Gtk::ListStore> input = build_midi_port_list (midi_inputs, true);
213 Glib::RefPtr<Gtk::ListStore> output = build_midi_port_list (midi_outputs, false);
214 bool input_found = false;
215 bool output_found = false;
218 input_combo.set_model (input);
219 output_combo.set_model (output);
221 Gtk::TreeModel::Children children = input->children();
222 Gtk::TreeModel::Children::iterator i;
223 i = children.begin();
224 ++i; /* skip "Disconnected" */
227 for (n = 1; i != children.end(); ++i, ++n) {
228 string port_name = (*i)[midi_port_columns.full_name];
229 if (p2.input_port()->connected_to (port_name)) {
230 input_combo.set_active (n);
237 input_combo.set_active (0); /* disconnected */
240 children = output->children();
241 i = children.begin();
242 ++i; /* skip "Disconnected" */
244 for (n = 1; i != children.end(); ++i, ++n) {
245 string port_name = (*i)[midi_port_columns.full_name];
246 if (p2.output_port()->connected_to (port_name)) {
247 output_combo.set_active (n);
254 output_combo.set_active (0); /* disconnected */
259 P2GUI::build_available_action_menu ()
261 /* build a model of all available actions (needs to be tree structured
265 available_action_model = TreeStore::create (action_columns);
267 vector<string> paths;
268 vector<string> labels;
269 vector<string> tooltips;
271 vector<Glib::RefPtr<Gtk::Action> > actions;
273 Gtkmm2ext::ActionMap::get_all_actions (paths, labels, tooltips, keys, actions);
275 typedef std::map<string,TreeIter> NodeMap;
280 vector<string>::iterator k;
281 vector<string>::iterator p;
282 vector<string>::iterator t;
283 vector<string>::iterator l;
285 available_action_model->clear ();
288 TreeModel::Row parent;
290 /* Disabled item (row 0) */
292 rowp = available_action_model->append();
294 parent[action_columns.name] = _("Disabled");
298 rowp = available_action_model->append();
300 parent[action_columns.name] = _("Shift");
301 rowp = available_action_model->append();
303 parent[action_columns.name] = _("Control");
304 rowp = available_action_model->append();
306 parent[action_columns.name] = _("Option");
307 rowp = available_action_model->append();
309 parent[action_columns.name] = _("CmdAlt");
312 for (l = labels.begin(), k = keys.begin(), p = paths.begin(), t = tooltips.begin(); l != labels.end(); ++k, ++p, ++t, ++l) {
315 vector<string> parts;
319 split (*p, parts, '/');
325 //kinda kludgy way to avoid displaying menu items as mappable
326 if ( parts[1] == _("Main_menu") )
328 if ( parts[1] == _("JACK") )
330 if ( parts[1] == _("redirectmenu") )
332 if ( parts[1] == _("Editor_menus") )
334 if ( parts[1] == _("RegionList") )
336 if ( parts[1] == _("ProcessorMenu") )
339 if ((r = nodes.find (parts[1])) == nodes.end()) {
341 /* top level is missing */
344 TreeModel::Row parent;
345 rowp = available_action_model->append();
346 nodes[parts[1]] = rowp;
348 parent[action_columns.name] = parts[1];
350 row = *(available_action_model->append (parent.children()));
354 row = *(available_action_model->append ((*r->second)->children()));
358 /* add this action */
361 row[action_columns.name] = *t;
364 row[action_columns.name] = *l;
369 /* ControlProtocol::access_action() is not interested in the
370 legacy "<Actions>/" prefix part of a path.
372 path = path.substr (strlen ("<Actions>/"));
374 row[action_columns.path] = path;
380 P2GUI::find_action_in_model (const TreeModel::iterator& iter, std::string const & action_path, TreeModel::iterator* found)
382 TreeModel::Row row = *iter;
383 string path = row[action_columns.path];
385 if (path == action_path) {
393 Glib::RefPtr<Gtk::ListStore>
394 P2GUI::build_midi_port_list (vector<string> const & ports, bool for_input)
396 Glib::RefPtr<Gtk::ListStore> store = ListStore::create (midi_port_columns);
399 row = *store->append ();
400 row[midi_port_columns.full_name] = string();
401 row[midi_port_columns.short_name] = _("Disconnected");
403 for (vector<string>::const_iterator p = ports.begin(); p != ports.end(); ++p) {
404 row = *store->append ();
405 row[midi_port_columns.full_name] = *p;
406 std::string pn = ARDOUR::AudioEngine::instance()->get_pretty_name_by_name (*p);
408 pn = (*p).substr ((*p).find (':') + 1);
410 row[midi_port_columns.short_name] = pn;
417 P2GUI::active_port_changed (Gtk::ComboBox* combo, bool for_input)
419 if (ignore_active_change) {
423 TreeModel::iterator active = combo->get_active ();
424 string new_port = (*active)[midi_port_columns.full_name];
426 if (new_port.empty()) {
428 p2.input_port()->disconnect_all ();
430 p2.output_port()->disconnect_all ();
437 if (!p2.input_port()->connected_to (new_port)) {
438 p2.input_port()->disconnect_all ();
439 p2.input_port()->connect (new_port);
442 if (!p2.output_port()->connected_to (new_port)) {
443 p2.output_port()->disconnect_all ();
444 p2.output_port()->connect (new_port);
450 P2GUI::build_pad_table ()
452 container_clear (pad_table);
454 for (int row = 7; row >= 0; --row) {
455 for (int col = 0; col < 8; ++col) {
457 int n = p2.pad_note (row, col);
459 Gtk::Button* b = manage (new Button (string_compose ("%1 (%2)", ParameterDescriptor::midi_note_name (n), n)));
462 pad_table.attach (*b, col, col+1, (7-row), (8-row));
467 Glib::RefPtr<Gtk::ListStore>
468 P2GUI::build_mode_columns ()
470 Glib::RefPtr<Gtk::ListStore> store = ListStore::create (mode_columns);
473 row = *store->append();
474 row[mode_columns.name] = _("Dorian");
475 row[mode_columns.mode] = MusicalMode::Dorian;
477 row = *store->append();
478 row[mode_columns.name] = _("Ionian (\"Major\")");
479 row[mode_columns.mode] = MusicalMode::IonianMajor;
481 row = *store->append();
482 row[mode_columns.name] = _("Minor");
483 row[mode_columns.mode] = MusicalMode::Minor;
485 row = *store->append();
486 row[mode_columns.name] = _("Harmonic Minor");
487 row[mode_columns.mode] = MusicalMode::HarmonicMinor;
489 row = *store->append();
490 row[mode_columns.name] = _("Melodic Minor Ascending");
491 row[mode_columns.mode] = MusicalMode::MelodicMinorAscending;
493 row = *store->append();
494 row[mode_columns.name] = _("Melodic Minor Descending");
495 row[mode_columns.mode] = MusicalMode::MelodicMinorDescending;
497 row = *store->append();
498 row[mode_columns.name] = _("Phrygian");
499 row[mode_columns.mode] = MusicalMode::Phrygian;
501 row = *store->append();
502 row[mode_columns.name] = _("Lydian");
503 row[mode_columns.mode] = MusicalMode::Lydian;
505 row = *store->append();
506 row[mode_columns.name] = _("Mixolydian");
507 row[mode_columns.mode] = MusicalMode::Mixolydian;
509 row = *store->append();
510 row[mode_columns.name] = _("Aeolian (\"Major\")");
511 row[mode_columns.mode] = MusicalMode::Aeolian;
513 row = *store->append();
514 row[mode_columns.name] = _("Locrian");
515 row[mode_columns.mode] = MusicalMode::Locrian;
517 row = *store->append();
518 row[mode_columns.name] = _("Pentatonic Major");
519 row[mode_columns.mode] = MusicalMode::PentatonicMajor;
521 row = *store->append();
522 row[mode_columns.name] = _("Pentatonic Minor");
523 row[mode_columns.mode] = MusicalMode::PentatonicMinor;
525 row = *store->append();
526 row[mode_columns.name] = _("Chromatic");
527 row[mode_columns.mode] = MusicalMode::Chromatic;
529 row = *store->append();
530 row[mode_columns.name] = _("Blues Scale");
531 row[mode_columns.mode] = MusicalMode::BluesScale;
533 row = *store->append();
534 row[mode_columns.name] = _("Neapolitan Minor");
535 row[mode_columns.mode] = MusicalMode::NeapolitanMinor;
537 row = *store->append();
538 row[mode_columns.name] = _("Neapolitan Major");
539 row[mode_columns.mode] = MusicalMode::NeapolitanMajor;
541 row = *store->append();
542 row[mode_columns.name] = _("Oriental");
543 row[mode_columns.mode] = MusicalMode::Oriental;
545 row = *store->append();
546 row[mode_columns.name] = _("Double Harmonic");
547 row[mode_columns.mode] = MusicalMode::DoubleHarmonic;
549 row = *store->append();
550 row[mode_columns.name] = _("Enigmatic");
551 row[mode_columns.mode] = MusicalMode::Enigmatic;
553 row = *store->append();
554 row[mode_columns.name] = _("Hirajoshi");
555 row[mode_columns.mode] = MusicalMode::Hirajoshi;
557 row = *store->append();
558 row[mode_columns.name] = _("Hungarian Minor");
559 row[mode_columns.mode] = MusicalMode::HungarianMinor;
561 row = *store->append();
562 row[mode_columns.name] = _("Hungarian Major");
563 row[mode_columns.mode] = MusicalMode::HungarianMajor;
565 row = *store->append();
566 row[mode_columns.name] = _("Kumoi");
567 row[mode_columns.mode] = MusicalMode::Kumoi;
569 row = *store->append();
570 row[mode_columns.name] = _("Iwato");
571 row[mode_columns.mode] = MusicalMode::Iwato;
573 row = *store->append();
574 row[mode_columns.name] = _("Hindu");
575 row[mode_columns.mode] = MusicalMode::Hindu;
577 row = *store->append();
578 row[mode_columns.name] = _("Spanish 8 Tone");
579 row[mode_columns.mode] = MusicalMode::Spanish8Tone;
581 row = *store->append();
582 row[mode_columns.name] = _("Pelog");
583 row[mode_columns.mode] = MusicalMode::Pelog;
585 row = *store->append();
586 row[mode_columns.name] = _("Hungarian Gypsy");
587 row[mode_columns.mode] = MusicalMode::HungarianGypsy;
589 row = *store->append();
590 row[mode_columns.name] = _("Overtone");
591 row[mode_columns.mode] = MusicalMode::Overtone;
593 row = *store->append();
594 row[mode_columns.name] = _("Leading Whole Tone");
595 row[mode_columns.mode] = MusicalMode::LeadingWholeTone;
597 row = *store->append();
598 row[mode_columns.name] = _("Arabian");
599 row[mode_columns.mode] = MusicalMode::Arabian;
601 row = *store->append();
602 row[mode_columns.name] = _("Balinese");
603 row[mode_columns.mode] = MusicalMode::Balinese;
605 row = *store->append();
606 row[mode_columns.name] = _("Gypsy");
607 row[mode_columns.mode] = MusicalMode::Gypsy;
609 row = *store->append();
610 row[mode_columns.name] = _("Mohammedan");
611 row[mode_columns.mode] = MusicalMode::Mohammedan;
613 row = *store->append();
614 row[mode_columns.name] = _("Javanese");
615 row[mode_columns.mode] = MusicalMode::Javanese;
617 row = *store->append();
618 row[mode_columns.name] = _("Persian");
619 row[mode_columns.mode] = MusicalMode::Persian;
621 row = *store->append();
622 row[mode_columns.name] = _("Algerian");
623 row[mode_columns.mode] = MusicalMode::Algerian;
628 Glib::RefPtr<Gtk::ListStore>
629 P2GUI::build_note_columns ()
631 Glib::RefPtr<Gtk::ListStore> store = ListStore::create (note_columns);
634 row = *store->append ();
635 row[note_columns.number] = 0;
636 row[note_columns.name] = "C";
638 row = *store->append ();
639 row[note_columns.number] = 1;
640 row[note_columns.name] = "C#";
642 row = *store->append ();
643 row[note_columns.number] = 2;
644 row[note_columns.name] = "D";
646 row = *store->append ();
647 row[note_columns.number] = 3;
648 row[note_columns.name] = "D#";
650 row = *store->append ();
651 row[note_columns.number] = 4;
652 row[note_columns.name] = "E";
654 row = *store->append ();
655 row[note_columns.number] = 5;
656 row[note_columns.name] = "F";
658 row = *store->append ();
659 row[note_columns.number] = 6;
660 row[note_columns.name] = "F#";
662 row = *store->append ();
663 row[note_columns.number] = 7;
664 row[note_columns.name] = "G";
666 row = *store->append ();
667 row[note_columns.number] = 8;
668 row[note_columns.name] = "G#";
670 row = *store->append ();
671 row[note_columns.number] = 9;
672 row[note_columns.name] = "A";
674 row = *store->append ();
675 row[note_columns.number] = 10;
676 row[note_columns.name] = "A#";
678 row = *store->append ();
679 row[note_columns.number] = 11;
680 row[note_columns.name] = "B";
686 P2GUI::reprogram_pad_scale ()
690 MusicalMode::Type mode;
693 Gtk::TreeModel::iterator iter = root_note_selector.get_active();
695 Gtk::TreeModel::Row row = *iter;
697 root = row[note_columns.number];
705 octave = (int) floor (root_note_octave_adjustment.get_value ());
707 iter = mode_selector.get_active();
709 Gtk::TreeModel::Row row = *iter;
711 mode = row[mode_columns.mode];
713 mode = MusicalMode::IonianMajor;
716 mode = MusicalMode::IonianMajor;
719 inkey = inkey_button.get_active ();
721 p2.set_pad_scale (root, octave, mode, inkey);