enough with umpteen "i18n.h" files. Consolidate on pbd/i18n.h
[ardour.git] / libs / surfaces / generic_midi / gmcp_gui.cc
index 807e4bba2232b6eda520d12b6f7250cc2af9307d..3c2479b62270af2d8ef7dfa56b2c683933917a36 100644 (file)
 #include <gtkmm/adjustment.h>
 #include <gtkmm/spinbutton.h>
 #include <gtkmm/table.h>
+#include <gtkmm/liststore.h>
 
+#include "pbd/unwind.h"
+
+#include "ardour/audioengine.h"
+#include "ardour/port.h"
+#include "ardour/midi_port.h"
+
+#include "gtkmm2ext/gtk_ui.h"
+#include "gtkmm2ext/gui_thread.h"
 #include "gtkmm2ext/utils.h"
 
 #include "generic_midi_control_protocol.h"
 
-#include "i18n.h"
+#include "pbd/i18n.h"
 
-class GMCPGUI : public Gtk::VBox 
+class GMCPGUI : public Gtk::VBox
 {
 public:
        GMCPGUI (GenericMidiControlProtocol&);
        ~GMCPGUI ();
-       
+
 private:
        GenericMidiControlProtocol& cp;
        Gtk::ComboBoxText map_combo;
        Gtk::Adjustment bank_adjustment;
        Gtk::SpinButton bank_spinner;
        Gtk::CheckButton motorised_button;
+       Gtk::Adjustment threshold_adjustment;
+       Gtk::SpinButton threshold_spinner;
+
+       Gtk::ComboBox input_combo;
+       Gtk::ComboBox output_combo;
 
        void binding_changed ();
        void bank_changed ();
        void motorised_changed ();
+       void threshold_changed ();
+
+       void update_port_combos ();
+       PBD::ScopedConnection connection_change_connection;
+       void connection_handler ();
+
+       struct MidiPortColumns : public Gtk::TreeModel::ColumnRecord {
+               MidiPortColumns() {
+                       add (short_name);
+                       add (full_name);
+               }
+               Gtk::TreeModelColumn<std::string> short_name;
+               Gtk::TreeModelColumn<std::string> full_name;
+       };
+
+       MidiPortColumns midi_port_columns;
+       bool ignore_active_change;
+
+       Glib::RefPtr<Gtk::ListStore> build_midi_port_list (std::vector<std::string> const & ports, bool for_input);
+       void active_port_changed (Gtk::ComboBox*,bool for_input);
 };
 
 using namespace PBD;
@@ -64,13 +98,22 @@ GenericMidiControlProtocol::get_gui () const
        if (!gui) {
                const_cast<GenericMidiControlProtocol*>(this)->build_gui ();
        }
+       static_cast<Gtk::VBox*>(gui)->show_all();
        return gui;
 }
 
 void
 GenericMidiControlProtocol::tear_down_gui ()
 {
+       if (gui) {
+               Gtk::Widget *w = static_cast<Gtk::VBox*>(gui)->get_parent();
+               if (w) {
+                       w->hide();
+                       delete w;
+               }
+       }
        delete (GMCPGUI*) gui;
+       gui = 0;
 }
 
 void
@@ -86,6 +129,9 @@ GMCPGUI::GMCPGUI (GenericMidiControlProtocol& p)
        , bank_adjustment (1, 1, 100, 1, 10)
        , bank_spinner (bank_adjustment)
        , motorised_button ("Motorised")
+       , threshold_adjustment (p.threshold(), 1, 127, 1, 10)
+       , threshold_spinner (threshold_adjustment)
+       , ignore_active_change (false)
 {
        vector<string> popdowns;
        popdowns.push_back (_("Reset All"));
@@ -95,7 +141,7 @@ GMCPGUI::GMCPGUI (GenericMidiControlProtocol& p)
        }
 
        set_popdown_strings (map_combo, popdowns);
-       
+
        if (cp.current_binding().empty()) {
                map_combo.set_active_text (popdowns[0]);
        } else {
@@ -111,18 +157,40 @@ GMCPGUI::GMCPGUI (GenericMidiControlProtocol& p)
        table->set_row_spacings (6);
        table->set_col_spacings (6);
        table->show ();
-       
+
        int n = 0;
 
-       Label* label = manage (new Label (_("MIDI Bindings:")));
+       // MIDI input and output selectors
+       input_combo.pack_start (midi_port_columns.short_name);
+       output_combo.pack_start (midi_port_columns.short_name);
+
+       input_combo.signal_changed().connect (sigc::bind (sigc::mem_fun (*this, &GMCPGUI::active_port_changed), &input_combo, true));
+       output_combo.signal_changed().connect (sigc::bind (sigc::mem_fun (*this, &GMCPGUI::active_port_changed), &output_combo, false));
+
+       Label* label = manage (new Gtk::Label);
+       label->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Incoming MIDI on:")));
+       label->set_alignment (1.0, 0.5);
+       table->attach (*label, 0, 1, n, n+1, AttachOptions(FILL|EXPAND), AttachOptions(0));
+       table->attach (input_combo, 1, 2, n, n+1, AttachOptions(FILL|EXPAND), AttachOptions(0), 0, 0);
+       n++;
+
+       label = manage (new Gtk::Label);
+       label->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Outgoing MIDI on:")));
+       label->set_alignment (1.0, 0.5);
+       table->attach (*label, 0, 1, n, n+1, AttachOptions(FILL|EXPAND), AttachOptions(0));
+       table->attach (output_combo, 1, 2, n, n+1, AttachOptions(FILL|EXPAND), AttachOptions(0), 0, 0);
+       n++;
+
+       //MIDI binding file selector...
+       label = manage (new Label (_("MIDI Bindings:")));
        label->set_alignment (0, 0.5);
        table->attach (*label, 0, 1, n, n + 1);
        table->attach (map_combo, 1, 2, n, n + 1);
        ++n;
-       
+
        map_combo.show ();
        label->show ();
-       
+
        bank_adjustment.signal_value_changed().connect (sigc::mem_fun (*this, &GMCPGUI::bank_changed));
 
        label = manage (new Label (_("Current Bank:")));
@@ -130,7 +198,7 @@ GMCPGUI::GMCPGUI (GenericMidiControlProtocol& p)
        table->attach (*label, 0, 1, n, n + 1);
        table->attach (bank_spinner, 1, 2, n, n + 1);
        ++n;
-       
+
        bank_spinner.show ();
        label->show ();
 
@@ -139,8 +207,33 @@ GMCPGUI::GMCPGUI (GenericMidiControlProtocol& p)
        ++n;
 
        motorised_button.show ();
+       motorised_button.set_active (p.motorised ());
+
+       threshold_adjustment.signal_value_changed().connect (sigc::mem_fun (*this, &GMCPGUI::threshold_changed));
+
+       Gtkmm2ext::UI::instance()->set_tip (threshold_spinner,
+                                           string_compose (_("Controls how %1 behaves if the MIDI controller sends discontinuous values"), PROGRAM_NAME));
+
+       label = manage (new Label (_("Smoothing:")));
+       label->set_alignment (0, 0.5);
+       table->attach (*label, 0, 1, n, n + 1);
+       table->attach (threshold_spinner, 1, 2, n, n + 1);
+       ++n;
+
+       threshold_spinner.show ();
+       label->show ();
 
        pack_start (*table, false, false);
+
+       binding_changed ();
+
+       /* update the port connection combos */
+
+       update_port_combos ();
+
+       /* catch future changes to connection state */
+
+       cp.ConnectionChange.connect (connection_change_connection, invalidator (*this), boost::bind (&GMCPGUI::connection_handler, this), gui_context());
 }
 
 GMCPGUI::~GMCPGUI ()
@@ -166,6 +259,7 @@ GMCPGUI::binding_changed ()
                        if (str == x->name) {
                                cp.load_bindings (x->path);
                                motorised_button.set_active (cp.motorised ());
+                               threshold_adjustment.set_value (cp.threshold ());
                                break;
                        }
                }
@@ -177,3 +271,133 @@ GMCPGUI::motorised_changed ()
 {
        cp.set_motorised (motorised_button.get_active ());
 }
+
+void
+GMCPGUI::threshold_changed ()
+{
+       cp.set_threshold (threshold_adjustment.get_value());
+}
+
+void
+GMCPGUI::connection_handler ()
+{
+       /* ignore all changes to combobox active strings here, because we're
+          updating them to match a new ("external") reality - we were called
+          because port connections have changed.
+       */
+
+       PBD::Unwinder<bool> ici (ignore_active_change, true);
+
+       update_port_combos ();
+}
+
+void
+GMCPGUI::update_port_combos ()
+{
+       vector<string> midi_inputs;
+       vector<string> midi_outputs;
+
+       ARDOUR::AudioEngine::instance()->get_ports ("", ARDOUR::DataType::MIDI, ARDOUR::PortFlags (ARDOUR::IsOutput|ARDOUR::IsTerminal), midi_inputs);
+       ARDOUR::AudioEngine::instance()->get_ports ("", ARDOUR::DataType::MIDI, ARDOUR::PortFlags (ARDOUR::IsInput|ARDOUR::IsTerminal), midi_outputs);
+
+       Glib::RefPtr<Gtk::ListStore> input = build_midi_port_list (midi_inputs, true);
+       Glib::RefPtr<Gtk::ListStore> output = build_midi_port_list (midi_outputs, false);
+       bool input_found = false;
+       bool output_found = false;
+       int n;
+
+       input_combo.set_model (input);
+       output_combo.set_model (output);
+
+       Gtk::TreeModel::Children children = input->children();
+       Gtk::TreeModel::Children::iterator i;
+       i = children.begin();
+       ++i; /* skip "Disconnected" */
+
+
+       for (n = 1;  i != children.end(); ++i, ++n) {
+               string port_name = (*i)[midi_port_columns.full_name];
+               if (cp.input_port()->connected_to (port_name)) {
+                       input_combo.set_active (n);
+                       input_found = true;
+                       break;
+               }
+       }
+
+       if (!input_found) {
+               input_combo.set_active (0); /* disconnected */
+       }
+
+       children = output->children();
+       i = children.begin();
+       ++i; /* skip "Disconnected" */
+
+       for (n = 1;  i != children.end(); ++i, ++n) {
+               string port_name = (*i)[midi_port_columns.full_name];
+               if (cp.output_port()->connected_to (port_name)) {
+                       output_combo.set_active (n);
+                       output_found = true;
+                       break;
+               }
+       }
+
+       if (!output_found) {
+               output_combo.set_active (0); /* disconnected */
+       }
+}
+
+Glib::RefPtr<Gtk::ListStore>
+GMCPGUI::build_midi_port_list (vector<string> const & ports, bool for_input)
+{
+       Glib::RefPtr<Gtk::ListStore> store = ListStore::create (midi_port_columns);
+       TreeModel::Row row;
+
+       row = *store->append ();
+       row[midi_port_columns.full_name] = string();
+       row[midi_port_columns.short_name] = _("Disconnected");
+
+       for (vector<string>::const_iterator p = ports.begin(); p != ports.end(); ++p) {
+               row = *store->append ();
+               row[midi_port_columns.full_name] = *p;
+               std::string pn = ARDOUR::AudioEngine::instance()->get_pretty_name_by_name (*p);
+               if (pn.empty ()) {
+                       pn = (*p).substr ((*p).find (':') + 1);
+               }
+               row[midi_port_columns.short_name] = pn;
+       }
+
+       return store;
+}
+
+void
+GMCPGUI::active_port_changed (Gtk::ComboBox* combo, bool for_input)
+{
+       if (ignore_active_change) {
+               return;
+       }
+
+       TreeModel::iterator active = combo->get_active ();
+       string new_port = (*active)[midi_port_columns.full_name];
+
+       if (new_port.empty()) {
+               if (for_input) {
+                       cp.input_port()->disconnect_all ();
+               } else {
+                       cp.output_port()->disconnect_all ();
+               }
+
+               return;
+       }
+
+       if (for_input) {
+               if (!cp.input_port()->connected_to (new_port)) {
+                       cp.input_port()->disconnect_all ();
+                       cp.input_port()->connect (new_port);
+               }
+       } else {
+               if (!cp.output_port()->connected_to (new_port)) {
+                       cp.output_port()->disconnect_all ();
+                       cp.output_port()->connect (new_port);
+               }
+       }
+}