Various work on bundles. We now have a Bundle Manager dialogue, and hopefully things...
authorCarl Hetherington <carl@carlh.net>
Fri, 19 Oct 2007 13:30:07 +0000 (13:30 +0000)
committerCarl Hetherington <carl@carlh.net>
Fri, 19 Oct 2007 13:30:07 +0000 (13:30 +0000)
git-svn-id: svn://localhost/ardour2/trunk@2561 d708f5d6-7413-0410-9779-e7cbd77b26cf

28 files changed:
gtk2_ardour/SConscript
gtk2_ardour/ardour.menus
gtk2_ardour/ardour_ui.cc
gtk2_ardour/ardour_ui.h
gtk2_ardour/ardour_ui_dialogs.cc
gtk2_ardour/ardour_ui_ed.cc
gtk2_ardour/bundle_manager.cc [new file with mode: 0644]
gtk2_ardour/bundle_manager.h [new file with mode: 0644]
gtk2_ardour/io_selector.cc
gtk2_ardour/io_selector.h
gtk2_ardour/mixer_strip.cc
gtk2_ardour/mixer_strip.h
gtk2_ardour/port_matrix.cc [new file with mode: 0644]
gtk2_ardour/port_matrix.h [new file with mode: 0644]
libs/ardour/SConscript
libs/ardour/ardour/audioengine.h
libs/ardour/ardour/auto_bundle.h [new file with mode: 0644]
libs/ardour/ardour/bundle.h
libs/ardour/ardour/data_type.h
libs/ardour/ardour/io.h
libs/ardour/ardour/session.h
libs/ardour/ardour/user_bundle.h [new file with mode: 0644]
libs/ardour/audioengine.cc
libs/ardour/auto_bundle.cc [new file with mode: 0644]
libs/ardour/io.cc
libs/ardour/session.cc
libs/ardour/session_state.cc
libs/ardour/user_bundle.cc [new file with mode: 0644]

index 61d653bed7796936bd164766247b8add154d4dff..f1309b8c00313e53cfc0d004c403a0d68c12c047 100644 (file)
@@ -115,6 +115,7 @@ automation_time_axis.cc
 automation_streamview.cc
 automation_controller.cc
 automation_region_view.cc
+bundle_manager.cc
 midi_port_dialog.cc
 midi_time_axis.cc
 midi_streamview.cc
@@ -166,6 +167,7 @@ ghostregion.cc
 gtk-custom-hruler.c
 gtk-custom-ruler.c
 io_selector.cc
+port_matrix.cc
 keyboard.cc
 keyeditor.cc
 ladspa_pluginui.cc
index 871aa15aee9c7171cc97ef99d2ed5dbab4fd8005..6f11572151df717767f7ac72a4312856cd160b47 100644 (file)
                <menuitem action='ToggleKeyEditor'/>
                <menuitem action='ToggleThemeManager'/>
                <menuitem action='ToggleBigClock'/>
+               <menuitem action='ToggleBundleManager'/>
               <separator/>
         </menu>
         <menu name='Options' action='Options'>
index 491939bd97663d86ac927d63c861acc565412e8b..1182f397922bfdbd000233c8ccaf78fed3e6819c 100644 (file)
@@ -85,7 +85,7 @@
 #include "utils.h"
 #include "gui_thread.h"
 #include "theme_manager.h"
-
+#include "bundle_manager.h"
 
 #include "i18n.h"
 
index 9061f789a13a5c74501e3479e8c995a2a75ef356..74400396afab59849997889cdb7a012789e2f54d 100644 (file)
@@ -78,6 +78,7 @@ class AddRouteDialog;
 class NewSessionDialog;
 class LocationUI;
 class ThemeManager;
+class BundleManager;
 
 namespace Gtkmm2ext {
        class TearOff;
@@ -158,6 +159,7 @@ class ARDOUR_UI : public Gtkmm2ext::UI
        void toggle_key_editor ();
        void toggle_location_window ();
        void toggle_theme_manager ();
+       void toggle_bundle_manager ();
        void toggle_big_clock_window ();
        void toggle_connection_editor ();
        void toggle_route_params_window ();
@@ -612,6 +614,9 @@ class ARDOUR_UI : public Gtkmm2ext::UI
        RouteParams_UI *route_params;
        int             create_route_params ();
 
+       BundleManager *bundle_manager;
+       void create_bundle_manager ();
+
        ConnectionEditor *connection_editor;
        int               create_connection_editor ();
 
index 89ab470d9b9aba9e33657d38364af504f0f53d4f..34104016d7142b8ab264ebefa9bd7e139dddfcd6 100644 (file)
@@ -35,6 +35,7 @@
 #include "route_params_ui.h"
 #include "sfdb_ui.h"
 #include "theme_manager.h"
+#include "bundle_manager.h"
 #include "keyeditor.h"
 
 #include "i18n.h"
@@ -363,6 +364,33 @@ ARDOUR_UI::toggle_theme_manager ()
        }
 }
 
+void
+ARDOUR_UI::create_bundle_manager ()
+{
+       if (bundle_manager == 0) {
+               bundle_manager = new BundleManager (*session);
+               bundle_manager->signal_unmap().connect (sigc::bind (sigc::ptr_fun (&ActionManager::uncheck_toggleaction), X_("<Actions>/Common/ToggleBundleManager")));
+       }
+}
+
+void
+ARDOUR_UI::toggle_bundle_manager ()
+{
+       create_bundle_manager ();
+       
+       RefPtr<Action> act = ActionManager::get_action (X_("Common"), X_("ToggleBundleManager"));
+       if (act) {
+               RefPtr<ToggleAction> tact = RefPtr<ToggleAction>::cast_dynamic (act);
+       
+               if (tact->get_active()) {
+                       bundle_manager->show_all ();
+                       bundle_manager->present ();
+               } else {
+                       bundle_manager->hide ();
+               } 
+       }
+}
+
 int
 ARDOUR_UI::create_route_params ()
 {
index 8ba5e3790ac996d554ec8413527280f16dd409ff..74b5a70738ce91af29c69e0b11048b3a1c847d17 100644 (file)
@@ -219,6 +219,8 @@ ARDOUR_UI::install_actions ()
 
        ActionManager::register_action (common_actions, X_("About"), _("About"),  mem_fun(*this, &ARDOUR_UI::show_splash));
        ActionManager::register_toggle_action (common_actions, X_("ToggleThemeManager"), _("Theme Manager"), mem_fun(*this, &ARDOUR_UI::toggle_theme_manager));
+       ActionManager::register_toggle_action (common_actions, X_("ToggleBundleManager"), _("Bundle Manager"), mem_fun(*this, &ARDOUR_UI::toggle_bundle_manager));
+       
        ActionManager::register_toggle_action (common_actions, X_("ToggleKeyEditor"), _("Keybindings"), mem_fun(*this, &ARDOUR_UI::toggle_key_editor));
 
        Glib::RefPtr<ActionGroup> transport_actions = ActionGroup::create (X_("Transport"));
diff --git a/gtk2_ardour/bundle_manager.cc b/gtk2_ardour/bundle_manager.cc
new file mode 100644 (file)
index 0000000..4dacce7
--- /dev/null
@@ -0,0 +1,335 @@
+/*
+    Copyright (C) 2007 Paul Davis 
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <gtkmm/stock.h>
+#include <gtkmm/button.h>
+#include <gtkmm/label.h>
+#include <gtkmm/entry.h>
+#include <gtkmm/table.h>
+#include <gtkmm/comboboxtext.h>
+#include <gtkmm/alignment.h>
+#include "ardour/session.h"
+#include "ardour/user_bundle.h"
+#include "ardour/audioengine.h"
+#include "bundle_manager.h"
+#include "i18n.h"
+
+BundleEditorMatrix::BundleEditorMatrix (
+       ARDOUR::Session& session, boost::shared_ptr<ARDOUR::Bundle> bundle
+       )
+       : PortMatrix (
+               session, bundle->type(), bundle->ports_are_inputs(),
+               PortGroupList::Mask (PortGroupList::SYSTEM | PortGroupList::OTHER)
+               )
+{
+       _bundle = boost::dynamic_pointer_cast<ARDOUR::UserBundle> (bundle);
+       assert (_bundle != 0);
+}
+
+void
+BundleEditorMatrix::set_state (int r, std::string const & p, bool s)
+{
+       if (s) {
+               _bundle->add_port_to_channel (r, p);
+       } else {
+               _bundle->remove_port_from_channel (r, p);
+       }
+}
+
+bool
+BundleEditorMatrix::get_state (int r, std::string const & p) const
+{
+       return _bundle->port_attached_to_channel (r, p);
+}
+
+uint32_t
+BundleEditorMatrix::n_rows () const
+{
+       return _bundle->nchannels ();
+}
+
+uint32_t
+BundleEditorMatrix::maximum_rows () const
+{
+       /* 65536 channels in a bundle ought to be enough for anyone (TM) */
+       return 65536;
+}
+
+uint32_t
+BundleEditorMatrix::minimum_rows () const
+{
+       return 0;
+}
+
+std::string
+BundleEditorMatrix::row_name (int r) const
+{
+       std::stringstream s;
+       s << r;
+       return s.str();
+}
+
+void
+BundleEditorMatrix::add_row ()
+{
+       _bundle->add_channel ();
+       redisplay ();
+}
+
+void
+BundleEditorMatrix::remove_row (int r)
+{
+       _bundle->remove_channel (r);
+       redisplay ();
+}
+
+std::string
+BundleEditorMatrix::row_descriptor () const
+{
+       return _("channel");
+}
+
+BundleEditor::BundleEditor (ARDOUR::Session& session, boost::shared_ptr<ARDOUR::UserBundle> bundle, bool add)
+       : ArdourDialog (_("Edit Bundle")), _matrix (session, bundle), _bundle (bundle)
+{
+       Gtk::Table* t = new Gtk::Table (3, 2);
+       t->set_spacings (4);
+
+       Gtk::Alignment* a = new Gtk::Alignment (1, 0.5, 0, 1);
+       a->add (*Gtk::manage (new Gtk::Label (_("Name:"))));
+       t->attach (*Gtk::manage (a), 0, 1, 0, 1, Gtk::FILL, Gtk::FILL);
+       t->attach (_name, 1, 2, 0, 1);
+       
+       _name.set_text (_bundle->name ());
+       _name.signal_changed().connect (sigc::mem_fun (*this, &BundleEditor::name_changed));
+
+       a = new Gtk::Alignment (1, 0.5, 0, 1);
+       a->add (*Gtk::manage (new Gtk::Label (_("Direction:"))));
+       t->attach (*Gtk::manage (a), 0, 1, 1, 2, Gtk::FILL, Gtk::FILL);
+       a = new Gtk::Alignment (0, 0.5, 0, 1);
+       a->add (_input_or_output);
+       t->attach (*Gtk::manage (a), 1, 2, 1, 2);
+       
+       _input_or_output.append_text (_("Input"));
+       _input_or_output.append_text (_("Output"));
+       
+       if (bundle->ports_are_inputs()) {
+               _input_or_output.set_active_text (_("Output"));
+       } else {
+               _input_or_output.set_active_text (_("Input"));
+       }
+
+       _input_or_output.signal_changed().connect (sigc::mem_fun (*this, &BundleEditor::input_or_output_changed));
+
+       a = new Gtk::Alignment (1, 0.5, 0, 1);
+       a->add (*Gtk::manage (new Gtk::Label (_("Type:"))));
+       t->attach (*Gtk::manage (a), 0, 1, 2, 3, Gtk::FILL, Gtk::FILL);
+       a = new Gtk::Alignment (0, 0.5, 0, 1);
+       a->add (_type);
+       t->attach (*Gtk::manage (a), 1, 2, 2, 3);
+       
+       _type.append_text (_("Audio"));
+       _type.append_text (_("MIDI"));
+       
+       switch (bundle->type ()) {
+       case ARDOUR::DataType::AUDIO:
+               _type.set_active_text (_("Audio"));
+               break;
+       case ARDOUR::DataType::MIDI:
+               _type.set_active_text (_("MIDI"));
+               break;
+       }
+
+       _type.signal_changed().connect (sigc::mem_fun (*this, &BundleEditor::type_changed));
+                                       
+       get_vbox()->pack_start (*Gtk::manage (t), false, false);
+       
+       get_vbox()->pack_start (_matrix);
+
+       get_vbox()->set_spacing (4);
+
+       if (add) {
+               add_button (Gtk::Stock::CANCEL, 1);
+               add_button (Gtk::Stock::ADD, 0);
+       } else {
+               add_button (Gtk::Stock::CLOSE, 0);
+       }
+
+       show_all ();
+}
+
+void
+BundleEditor::name_changed ()
+{
+       _bundle->set_name (_name.get_text ());
+}
+
+void
+BundleEditor::input_or_output_changed ()
+{
+       if (_input_or_output.get_active_text() == _("Output")) {
+               _bundle->set_ports_are_inputs ();
+               _matrix.set_offer_inputs (true);
+       } else {
+               _bundle->set_ports_are_outputs ();
+               _matrix.set_offer_inputs (false);
+       }
+}
+
+void
+BundleEditor::type_changed ()
+{
+       ARDOUR::DataType const t = _type.get_active_text() == _("Audio") ?
+               ARDOUR::DataType::AUDIO : ARDOUR::DataType::MIDI;
+
+       _bundle->set_type (t);
+       _matrix.set_type (t);
+}
+
+void
+BundleEditor::on_map ()
+{
+       _matrix.redisplay ();
+       Window::on_map ();
+}
+
+
+BundleManager::BundleManager (ARDOUR::Session& session)
+       : ArdourDialog (_("Bundle manager")), _session (session), edit_button (_("Edit")), delete_button (_("Delete"))
+{
+       _list_model = Gtk::ListStore::create (_list_model_columns);
+       _tree_view.set_model (_list_model);
+       _tree_view.append_column (_("Name"), _list_model_columns.name);
+       _tree_view.set_headers_visible (false);
+
+       _session.foreach_bundle (sigc::mem_fun (*this, &BundleManager::add_bundle));
+       
+       /* New / Edit / Delete buttons */
+       Gtk::VBox* buttons = new Gtk::VBox;
+       buttons->set_spacing (8);
+       Gtk::Button* b = new Gtk::Button (_("New"));
+       b->set_image (*Gtk::manage (new Gtk::Image (Gtk::Stock::NEW, Gtk::ICON_SIZE_BUTTON)));
+       b->signal_clicked().connect (sigc::mem_fun (*this, &BundleManager::new_clicked));
+       buttons->pack_start (*Gtk::manage (b), false, false);
+       edit_button.set_image (*Gtk::manage (new Gtk::Image (Gtk::Stock::EDIT, Gtk::ICON_SIZE_BUTTON)));
+       edit_button.signal_clicked().connect (sigc::mem_fun (*this, &BundleManager::edit_clicked));
+       buttons->pack_start (edit_button, false, false);
+       delete_button.set_image (*Gtk::manage (new Gtk::Image (Gtk::Stock::DELETE, Gtk::ICON_SIZE_BUTTON)));
+       delete_button.signal_clicked().connect (sigc::mem_fun (*this, &BundleManager::delete_clicked));
+       buttons->pack_start (delete_button, false, false);
+       
+       Gtk::HBox* h = new Gtk::HBox;
+       h->set_spacing (8);
+       h->set_border_width (8);
+       h->pack_start (_tree_view);
+       h->pack_start (*Gtk::manage (buttons), false, false);
+
+       get_vbox()->set_spacing (8);
+       get_vbox()->pack_start (*Gtk::manage (h));
+
+       set_default_size (480, 240);
+
+       _tree_view.get_selection()->signal_changed().connect (
+               sigc::mem_fun (*this, &BundleManager::set_button_sensitivity)
+               );
+
+       set_button_sensitivity ();
+
+       show_all ();
+}
+
+void
+BundleManager::set_button_sensitivity ()
+{
+       bool const sel = (_tree_view.get_selection()->get_selected() != 0);
+       edit_button.set_sensitive (sel);
+       delete_button.set_sensitive (sel);
+}
+
+
+void
+BundleManager::new_clicked ()
+{
+       boost::shared_ptr<ARDOUR::UserBundle> b (new ARDOUR::UserBundle (""));
+
+       /* Start off with a single channel */
+       b->add_channel ();
+
+       BundleEditor e (_session, b, true);
+       if (e.run () == 0) {
+               _session.add_bundle (b);
+               add_bundle (b);
+       }
+}
+
+void
+BundleManager::edit_clicked ()
+{
+       Gtk::TreeModel::iterator i = _tree_view.get_selection()->get_selected();
+       if (i) {
+               boost::shared_ptr<ARDOUR::UserBundle> b = (*i)[_list_model_columns.bundle];
+               BundleEditor e (_session, b, false);
+               e.run ();
+       }
+       
+}
+
+void
+BundleManager::delete_clicked ()
+{
+       Gtk::TreeModel::iterator i = _tree_view.get_selection()->get_selected();
+       if (i) {
+               boost::shared_ptr<ARDOUR::UserBundle> b = (*i)[_list_model_columns.bundle];
+               _session.remove_bundle (b);
+               _list_model->erase (i);
+       }
+}
+
+void
+BundleManager::add_bundle (boost::shared_ptr<ARDOUR::Bundle> b)
+{
+       boost::shared_ptr<ARDOUR::UserBundle> u = boost::dynamic_pointer_cast<ARDOUR::UserBundle> (b);
+       if (u == 0) {
+               return;
+       }
+
+       Gtk::TreeModel::iterator i = _list_model->append ();
+       (*i)[_list_model_columns.name] = u->name ();
+       (*i)[_list_model_columns.bundle] = u;
+
+       u->NameChanged.connect (sigc::bind (sigc::mem_fun (*this, &BundleManager::bundle_name_changed), u));
+}
+
+void
+BundleManager::bundle_name_changed (boost::shared_ptr<ARDOUR::UserBundle> b)
+{
+       Gtk::TreeModel::iterator i = _list_model->children().begin ();
+       while (i != _list_model->children().end()) {
+               boost::shared_ptr<ARDOUR::UserBundle> t = (*i)[_list_model_columns.bundle];
+               if (t == b) {
+                       break;
+               }
+               ++i;
+       }
+
+       if (i != _list_model->children().end()) {
+               (*i)[_list_model_columns.name] = b->name ();
+       }
+}
+
diff --git a/gtk2_ardour/bundle_manager.h b/gtk2_ardour/bundle_manager.h
new file mode 100644 (file)
index 0000000..4d4d440
--- /dev/null
@@ -0,0 +1,107 @@
+/*
+    Copyright (C) 2007 Paul Davis 
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef __ardour_ui_bundle_manager_h__
+#define __ardour_ui_bundle_manager_h__
+
+#include <gtkmm/treeview.h>
+#include <gtkmm/liststore.h>
+#include "ardour_dialog.h"
+#include "port_matrix.h"
+
+namespace ARDOUR {
+       class Session;
+       class Bundle;
+}
+
+class BundleEditorMatrix : public PortMatrix
+{
+  public:
+       BundleEditorMatrix (ARDOUR::Session &, boost::shared_ptr<ARDOUR::Bundle>);
+
+       void set_state (int, std::string const &, bool);
+       bool get_state (int, std::string const &) const;
+       uint32_t n_rows () const;
+       uint32_t maximum_rows () const;
+       uint32_t minimum_rows () const;
+       std::string row_name (int) const;
+       void add_row ();
+       void remove_row (int);
+       std::string row_descriptor () const;
+
+  private:
+
+       boost::shared_ptr<ARDOUR::UserBundle> _bundle;
+};
+
+class BundleEditor : public ArdourDialog
+{
+  public:
+       BundleEditor (ARDOUR::Session &, boost::shared_ptr<ARDOUR::UserBundle>, bool);
+
+  protected:
+       void on_map ();
+
+  private:
+       void name_changed ();
+       void input_or_output_changed ();
+       void type_changed ();
+       
+       BundleEditorMatrix _matrix;
+       boost::shared_ptr<ARDOUR::UserBundle> _bundle;
+       Gtk::Entry _name;
+       Gtk::ComboBoxText _input_or_output;
+       Gtk::ComboBoxText _type;
+};
+
+class BundleManager : public ArdourDialog
+{
+  public:
+       BundleManager (ARDOUR::Session &);
+
+  private:
+
+       void new_clicked ();
+       void edit_clicked ();
+       void delete_clicked ();
+       void add_bundle (boost::shared_ptr<ARDOUR::Bundle>);
+       void bundle_name_changed (boost::shared_ptr<ARDOUR::UserBundle>);
+       void set_button_sensitivity ();
+
+       class ModelColumns : public Gtk::TreeModelColumnRecord
+       {
+       public:
+               ModelColumns () {
+                       add (name);
+                       add (bundle);
+               }
+               
+               Gtk::TreeModelColumn<Glib::ustring> name;
+               Gtk::TreeModelColumn<boost::shared_ptr<ARDOUR::UserBundle> > bundle;
+       };
+       
+       Gtk::TreeView _tree_view;
+       Glib::RefPtr<Gtk::ListStore> _list_model;
+       ModelColumns _list_model_columns;
+       ARDOUR::Session& _session;
+       Gtk::Button edit_button;
+       Gtk::Button delete_button;
+};
+
+#endif
index d0f236f84ab951b5f4d3585426f3b13fdbd8f404..b8598659d7c6ba7614093c33bfdfd9336175aaa2 100644 (file)
 
 */
 
-#include <gtkmm/label.h>
-#include <gtkmm/enums.h>
-#include <gtkmm/image.h>
-#include <gtkmm/stock.h>
-#include <gtkmm/messagedialog.h>
-#include <gtkmm/menu.h>
-#include <gtkmm/menu_elems.h>
-#include <gtkmm/menuitem.h>
-#include <gtkmm/menushell.h>
 #include <glibmm/objectbase.h>
 #include <gtkmm2ext/doi.h>
 #include <ardour/port_insert.h>
 #include "gui_thread.h"
 #include "i18n.h"
 
-/** Add a port to a group.
- *  @param p Port name, with or without prefix.
- */
-
-void
-PortGroup::add (std::string const & p)
-{
-       if (prefix.empty() == false && p.substr (0, prefix.length()) == prefix) {
-               ports.push_back (p.substr (prefix.length()));
-       } else {
-               ports.push_back (p);
-       }
-}
-
-
-PortGroupTable::PortGroupTable (
-       PortGroup& g, boost::shared_ptr<ARDOUR::IO> io, bool for_input
-       )
-       : _port_group (g), _ignore_check_button_toggle (false),
-         _io (io), _for_input (for_input)
+IOSelector::IOSelector (ARDOUR::Session& session, boost::shared_ptr<ARDOUR::IO> io, bool offer_inputs)
+       : PortMatrix (
+               session, io->default_type(), offer_inputs,
+               PortGroupList::Mask (PortGroupList::BUSS | PortGroupList::SYSTEM | PortGroupList::OTHER)
+               ),
+         _io (io)
 {
-       ARDOUR::DataType const t = _io->default_type();
-
-       int rows;
-       if (_for_input) {
-               rows = _io->n_inputs().get(t);
+       /* Listen for ports changing on the IO */
+       if (!offer_inputs) {
+               _io->input_changed.connect (mem_fun(*this, &IOSelector::ports_changed));
        } else {
-               rows = _io->n_outputs().get(t);
-       }       
-       
-       int const ports = _port_group.ports.size();
-
-       if (rows == 0 || ports == 0) {
-               return;
-       }
-
-       /* Sort out the table and the checkbuttons inside it */
-       
-       _table.resize (rows, ports);
-       _check_buttons.resize (rows);
-       for (int i = 0; i < rows; ++i) {
-               _check_buttons[i].resize (ports);
-       }
-
-       for (int i = 0; i < rows; ++i) {
-               for (uint32_t j = 0; j < _port_group.ports.size(); ++j) {
-                       Gtk::CheckButton* b = new Gtk::CheckButton;
-                       b->signal_toggled().connect (sigc::bind (sigc::mem_fun (*this, &PortGroupTable::check_button_toggled), b, i, _port_group.prefix + _port_group.ports[j]));
-                       _check_buttons[i][j] = b;
-                       _table.attach (*b, j, j + 1, i, i + 1);
-               }
-       }
-
-       _box.add (_table);
-
-       _ignore_check_button_toggle = true;
-
-       /* Set the state of the check boxes according to current connections */
-       for (int i = 0; i < rows; ++i) {
-               const char **connections = _for_input ? _io->input(i)->get_connections() : _io->output(i)->get_connections();
-               for (uint32_t j = 0; j < _port_group.ports.size(); ++j) {
-
-                       std::string const t = _port_group.prefix + _port_group.ports[j];
-                       int k = 0;
-                       bool required_state = false;
-
-                       while (connections && connections[k]) {
-                               if (std::string(connections[k]) == t) {
-                                       required_state = true;
-                                       break;
-                               }
-                               ++k;
-                       }
-
-                       _check_buttons[i][j]->set_active (required_state);
-               }
-       }
-
-       _ignore_check_button_toggle = false;
-}
-
-/** @return Width and height of a single check button in a port group table */
-std::pair<int, int>
-PortGroupTable::unit_size () const
-{
-       if (_check_buttons.empty() || _check_buttons[0].empty()) {
-               return std::pair<int, int> (0, 0);
+               _io->output_changed.connect (mem_fun(*this, &IOSelector::ports_changed));
        }
-
-       return std::make_pair (
-               _check_buttons[0][0]->get_width() + _table.get_col_spacing (0),
-               _check_buttons[0][0]->get_height() + _table.get_row_spacing (0)
-               );
-}
-
-Gtk::Widget&
-PortGroupTable::get_widget ()
-{
-       return _box;
-}
-
-
-/** Handle a toggle of a check button */
-void
-PortGroupTable::check_button_toggled (Gtk::CheckButton* b, int r, std::string const & p)
-{
-       if (_ignore_check_button_toggle) {
-               return;
-       }
-       
-       bool const new_state = b->get_active ();
-
-       if (new_state) {
-               if (_for_input) {
-                       _io->connect_input (_io->input(r), p, 0);
-               } else {
-                       _io->connect_output (_io->output(r), p, 0);
-               }
-       } else {
-               if (_for_input) {
-                       _io->disconnect_input (_io->input(r), p, 0);
-               } else {
-                       _io->disconnect_output (_io->output(r), p, 0);
-               }
-       }
-}
-
-
-RotatedLabelSet::RotatedLabelSet (PortGroupList& g)
-       : Glib::ObjectBase ("RotatedLabelSet"), Gtk::Widget (), _port_group_list (g), _base_width (128)
-{
-       set_flags (Gtk::NO_WINDOW);
-       set_angle (30);
-}
-
-RotatedLabelSet::~RotatedLabelSet ()
-{
-       
-}
-
-
-/** Set the angle that the labels are drawn at.
- * @param degrees New angle in degrees.
- */
-
-void
-RotatedLabelSet::set_angle (int degrees)
-{
-       _angle_degrees = degrees;
-       _angle_radians = M_PI * _angle_degrees / 180;
-
-       queue_resize ();
 }
 
-void
-RotatedLabelSet::on_size_request (Gtk::Requisition* requisition)
-{
-       *requisition = Gtk::Requisition ();
-
-       if (_pango_layout == 0) {
-               return;
-       }
-
-       /* Our height is the highest label */
-       requisition->height = 0;
-       for (PortGroupList::const_iterator i = _port_group_list.begin(); i != _port_group_list.end(); ++i) {
-               for (std::vector<std::string>::const_iterator j = (*i)->ports.begin(); j != (*i)->ports.end(); ++j) {
-                       std::pair<int, int> const d = setup_layout (*j);
-                       if (d.second > requisition->height) {
-                               requisition->height = d.second;
-                       }
-               }
-       }
-
-       /* And our width is the base plus the width of the last label */
-       requisition->width = _base_width;
-       int const n = _port_group_list.n_visible_ports ();
-       if (n > 0) {
-               std::pair<int, int> const d = setup_layout (_port_group_list.get_port_by_index (n - 1, false));
-               requisition->width += d.first;
-       }
-}
 
 void
-RotatedLabelSet::on_size_allocate (Gtk::Allocation& allocation)
+IOSelector::ports_changed (ARDOUR::IOChange change, void *src)
 {
-       set_allocation (allocation);
+       ENSURE_GUI_THREAD (bind (mem_fun (*this, &IOSelector::ports_changed), change, src));
 
-       if (_gdk_window) {
-               _gdk_window->move_resize (
-                       allocation.get_x(), allocation.get_y(), allocation.get_width(), allocation.get_height()
-                       );
-       }
+       redisplay ();
 }
 
-void
-RotatedLabelSet::on_realize ()
-{
-       Gtk::Widget::on_realize ();
-
-       Glib::RefPtr<Gtk::Style> style = get_style ();
-
-       if (!_gdk_window) {
-               GdkWindowAttr attributes;
-               memset (&attributes, 0, sizeof (attributes));
-
-               Gtk::Allocation allocation = get_allocation ();
-               attributes.x = allocation.get_x ();
-               attributes.y = allocation.get_y ();
-               attributes.width = allocation.get_width ();
-               attributes.height = allocation.get_height ();
-
-               attributes.event_mask = get_events () | Gdk::EXPOSURE_MASK; 
-               attributes.window_type = GDK_WINDOW_CHILD;
-               attributes.wclass = GDK_INPUT_OUTPUT;
 
-               _gdk_window = Gdk::Window::create (get_window (), &attributes, GDK_WA_X | GDK_WA_Y);
-               unset_flags (Gtk::NO_WINDOW);
-               set_window (_gdk_window);
-
-               _bg_colour = style->get_bg (Gtk::STATE_NORMAL );
-               modify_bg (Gtk::STATE_NORMAL, _bg_colour);
-               _fg_colour = style->get_fg (Gtk::STATE_NORMAL);
-;
-               _gdk_window->set_user_data (gobj ());
-
-               /* Set up Pango stuff */
-               _pango_context = create_pango_context ();
-
-               Pango::Matrix matrix = PANGO_MATRIX_INIT;
-               pango_matrix_rotate (&matrix, _angle_degrees);
-               _pango_context->set_matrix (matrix);
-
-               _pango_layout = Pango::Layout::create (_pango_context);
-               _gc = Gdk::GC::create (get_window ());
-       }
-}
 
 void
-RotatedLabelSet::on_unrealize()
-{
-       _gdk_window.clear ();
-
-       Gtk::Widget::on_unrealize ();
-}
-
-
-/** Set up our Pango layout to plot a given string, and compute its dimensions once
- *  it has been rotated.
- *  @param s String to use.
- *  @return width and height of the rotated string, in pixels.
- */
-
-std::pair<int, int>
-RotatedLabelSet::setup_layout (std::string const & s)
+IOSelector::set_state (int r, std::string const & p, bool s)
 {
-       _pango_layout->set_text (s);
-
-       /* Here's the unrotated size */
-       int w;
-       int h;
-       _pango_layout->get_pixel_size (w, h);
-
-       /* Rotate the width and height as appropriate.  I thought Pango might be able
-          to do this for us, but I can't find out how... */
-       std::pair<int, int> d;
-       d.first = int (w * cos (_angle_radians) - h * sin (_angle_radians));
-       d.second = int (w * sin (_angle_radians) + h * cos (_angle_radians));
-
-       return d;
+       if (s) {
+               if (!_offer_inputs) {
+                       _io->connect_input (_io->input(r), p, 0);
+               } else {
+                       _io->connect_output (_io->output(r), p, 0);
+               }
+       } else {
+               if (!_offer_inputs) {
+                       _io->disconnect_input (_io->input(r), p, 0);
+               } else {
+                       _io->disconnect_output (_io->output(r), p, 0);
+               }
+       }
 }
 
 bool
-RotatedLabelSet::on_expose_event (GdkEventExpose* event)
+IOSelector::get_state (int r, std::string const & p) const
 {
-       if (!_gdk_window) {
-               return true;
-       }
+       const char **connections = _offer_inputs ? _io->output(r)->get_connections() : _io->input(r)->get_connections();
 
-       int const height = get_allocation().get_height ();
-       double const spacing = double (_base_width) / _port_group_list.n_visible_ports();
-
-       /* Plot all the visible labels; really we should clip for efficiency */
-       int n = 0;
-       for (PortGroupList::const_iterator i = _port_group_list.begin(); i != _port_group_list.end(); ++i) {
-               if ((*i)->visible) {
-                       for (uint32_t j = 0; j < (*i)->ports.size(); ++j) {
-                               std::pair<int, int> const d = setup_layout ((*i)->ports[j]);
-                               get_window()->draw_layout (_gc, int ((n + 0.25) * spacing), height - d.second, _pango_layout, _fg_colour, _bg_colour);
-                               ++n;
-                       }
+       int k = 0;
+       while (connections && connections[k]) {
+               if (std::string (connections[k]) == p) {
+                       return true;
                }
-       }
-
-       return true;
-}
-
-/** Set the `base width'.  This is the width of the base of the label set, ie:
- *
- *     L L L L
- *    E E E E
- *   B B B B
- *  A A A A
- * L L L L
- * <--w-->
- */
-    
-void
-RotatedLabelSet::set_base_width (int w)
-{
-       _base_width = w;
-       queue_resize ();
-}
-
-
-/** Construct an IOSelector.
- *  @param session Session to operate on.
- *  @param io IO to operate on.
- *  @param for_input true if the selector is for an input, otherwise false.
- */
-IOSelector::IOSelector (ARDOUR::Session& session, boost::shared_ptr<ARDOUR::IO> io, bool for_input)
-       : _port_group_list (session, io, for_input), _io (io), _for_input (for_input),
-         _column_labels (_port_group_list)
-{
-       _row_labels_vbox[0] = _row_labels_vbox[1] = 0;
-       _side_vbox_pad[0] = _side_vbox_pad[1] = 0;
-       
-       Gtk::HBox* c = new Gtk::HBox;
-       for (PortGroupList::iterator i = _port_group_list.begin(); i != _port_group_list.end(); ++i) {
-               Gtk::CheckButton* b = new Gtk::CheckButton ((*i)->name);
-               b->set_active (true);
-               b->signal_toggled().connect (sigc::bind (sigc::mem_fun (*this, &IOSelector::group_visible_toggled), b, (*i)->name));
-               c->pack_start (*Gtk::manage (b), false, false);
-       }
-       pack_start (*Gtk::manage (c));
-       
-       _side_vbox[0].pack_start (*Gtk::manage (new Gtk::Label ("")));
-       _overall_hbox.pack_start (_side_vbox[0], false, false);
-       _scrolled_window.set_policy (Gtk::POLICY_ALWAYS, Gtk::POLICY_NEVER);
-       _scrolled_window.set_shadow_type (Gtk::SHADOW_NONE);
-       Gtk::VBox* b = new Gtk::VBox;
-       b->pack_start (_column_labels, false, false);
-       b->pack_start (_port_group_hbox, false, false);
-       Gtk::Alignment* a = new Gtk::Alignment (0, 1, 0, 0);
-       a->add (*Gtk::manage (b));
-       _scrolled_window.add (*Gtk::manage (a));
-       _overall_hbox.pack_start (_scrolled_window);
-       _side_vbox[1].pack_start (*Gtk::manage (new Gtk::Label ("")));
-       _overall_hbox.pack_start (_side_vbox[1]);
-       pack_start (_overall_hbox);
-
-       _port_group_hbox.signal_size_allocate().connect (sigc::hide (sigc::mem_fun (*this, &IOSelector::setup_dimensions)));
 
-       /* Listen for ports changing on the IO */
-       if (_for_input) {
-               _io->input_changed.connect (mem_fun(*this, &IOSelector::ports_changed));
-       } else {
-               _io->output_changed.connect (mem_fun(*this, &IOSelector::ports_changed));
+               ++k;
        }
-       
-}
 
-IOSelector::~IOSelector ()
-{
-       clear ();
+       return false;
 }
 
-/** Clear out the things that change when the number of source or destination ports changes */
-void
-IOSelector::clear ()
+uint32_t
+IOSelector::n_rows () const
 {
-       for (int i = 0; i < 2; ++i) {
-
-               for (std::vector<Gtk::EventBox*>::iterator j = _row_labels[i].begin(); j != _row_labels[i].end(); ++j) {
-                       delete *j;
-               }
-               _row_labels[i].clear ();
-               
-               if (_row_labels_vbox[i]) {
-                       _side_vbox[i].remove (*_row_labels_vbox[i]);
-               }
-               delete _row_labels_vbox[i];
-               _row_labels_vbox[i] = 0;
-               
-               if (_side_vbox_pad[i]) {
-                       _side_vbox[i].remove (*_side_vbox_pad[i]);
-               }
-               delete _side_vbox_pad[i];
-               _side_vbox_pad[i] = 0;
-       }
-
-       for (std::vector<PortGroupTable*>::iterator i = _port_group_tables.begin(); i != _port_group_tables.end(); ++i) {
-               _port_group_hbox.remove ((*i)->get_widget());
-               delete *i;
+       if (!_offer_inputs) {
+               return _io->inputs().num_ports (_io->default_type());
+       } else {
+               return _io->outputs().num_ports (_io->default_type());
        }
-
-       _port_group_tables.clear ();
 }
 
-
-/** Set up dimensions of some of our widgets which depend on other dimensions
- *  within the dialogue.
- */
-void
-IOSelector::setup_dimensions ()
+uint32_t
+IOSelector::maximum_rows () const
 {
-       /* Get some dimensions from various places */
-       int const scrollbar_height = _scrolled_window.get_hscrollbar()->get_height();
-
-       std::pair<int, int> unit_size (0, 0);
-       int port_group_tables_height = 0;
-       for (std::vector<PortGroupTable*>::iterator i = _port_group_tables.begin(); i != _port_group_tables.end(); ++i) {
-               std::pair<int, int> const u = (*i)->unit_size ();
-               unit_size.first = std::max (unit_size.first, u.first);
-               unit_size.second = std::max (unit_size.second, u.second);
-               port_group_tables_height = std::max (
-                       port_group_tables_height, (*i)->get_widget().get_height()
-                       );
-       }
-
-       /* Column labels */
-       _column_labels.set_base_width (_port_group_list.n_visible_ports () * unit_size.first);
-
-       /* Scrolled window */
-       /* XXX: really shouldn't set a minimum horizontal size here, but if we don't
-          the window starts up very small */
-       _scrolled_window.set_size_request (
-               std::min (_column_labels.get_width(), 640),
-               _column_labels.get_height() + port_group_tables_height + scrollbar_height + 16
-               );
-       
-       /* Row labels */
-       for (int i = 0; i < 2; ++i) {
-               for (std::vector<Gtk::EventBox*>::iterator j = _row_labels[i].begin(); j != _row_labels[i].end(); ++j) {
-                       (*j)->get_child()->set_size_request (-1, unit_size.second);
-               }
-
-               if (_side_vbox_pad[i]) {
-                       _side_vbox_pad[i]->set_size_request (-1, scrollbar_height + unit_size.second / 4);
-               }
+       if (!_offer_inputs) {
+               return _io->input_maximum ().get (_io->default_type());
+       } else {
+               return _io->output_maximum ().get (_io->default_type());
        }
 }
 
 
-/** Set up the dialogue */
-void
-IOSelector::setup ()
+uint32_t
+IOSelector::minimum_rows () const
 {
-       clear ();
-
-       /* Work out how many rows we have */
-       ARDOUR::DataType const t = _io->default_type();
-
-       int rows;
-       if (_for_input) {
-               rows = _io->n_inputs().get(t);
-       } else {
-               rows = _io->n_outputs().get(t);
-       }       
-       
-       /* Row labels */
-       for (int i = 0; i < 2; ++i) {
-               _row_labels_vbox[i] = new Gtk::VBox;
-               for (int j = 0; j < rows; ++j) {
-                       Gtk::Label* label = new Gtk::Label (_for_input ? _io->input(j)->name() : _io->output(j)->name());
-                       Gtk::EventBox* b = new Gtk::EventBox;
-                       b->set_events (Gdk::BUTTON_PRESS_MASK);
-                       b->signal_button_press_event().connect (sigc::bind (sigc::mem_fun (*this, &IOSelector::row_label_button_pressed), j));
-                       b->add (*Gtk::manage (label));
-                       _row_labels[i].push_back (b);
-                       _row_labels_vbox[i]->pack_start (*b, false, false);
-               }
-
-               _side_vbox[i].pack_start (*_row_labels_vbox[i], false, false);
-               _side_vbox_pad[i] = new Gtk::Label ("");
-               _side_vbox[i].pack_start (*_side_vbox_pad[i], false, false);
+       if (!_offer_inputs) {
+               return _io->input_minimum ().get (_io->default_type());
+       } else {
+               return _io->output_minimum ().get (_io->default_type());
        }
-
-       /* Checkbutton tables */
-       int n = 0;
-       for (PortGroupList::iterator i = _port_group_list.begin(); i != _port_group_list.end(); ++i) {
-               PortGroupTable* t = new PortGroupTable (**i, _io, _for_input);
-
-               /* XXX: this is a bit of a hack; should probably use a configurable colour here */
-               Gdk::Color alt_bg = get_style()->get_bg (Gtk::STATE_NORMAL);
-               alt_bg.set_rgb (alt_bg.get_red() + 4096, alt_bg.get_green() + 4096, alt_bg.get_blue () + 4096);
-               if ((n % 2) == 0) {
-                       t->get_widget().modify_bg (Gtk::STATE_NORMAL, alt_bg);
-               }
-
-               _port_group_tables.push_back (t);
-               _port_group_hbox.pack_start (t->get_widget(), false, false);
-               ++n;
-       }
-
-       show_all ();
-       
-       set_port_group_table_visibility ();
-}
-
-void
-IOSelector::ports_changed (ARDOUR::IOChange change, void *src)
-{
-       ENSURE_GUI_THREAD (bind (mem_fun (*this, &IOSelector::ports_changed), change, src));
-
-       redisplay ();
-}
-
-
-void
-IOSelector::redisplay ()
-{
-       _port_group_list.refresh ();
-       setup ();
 }
 
-
-/** Handle a button press on a row label */
-bool
-IOSelector::row_label_button_pressed (GdkEventButton* e, int r)
+std::string
+IOSelector::row_name (int r) const
 {
-       if (e->type != GDK_BUTTON_PRESS || e->button != 3) {
-               return false;
-       }
-
-       Gtk::Menu* menu = Gtk::manage (new Gtk::Menu);
-       Gtk::Menu_Helpers::MenuList& items = menu->items ();
-       menu->set_name ("ArdourContextMenu");
-
-       bool can_add;
-       bool can_remove;
-       std::string name;
-       ARDOUR::DataType const t = _io->default_type();
-
-       if (_for_input) {
-               can_add = _io->input_maximum().get(t) > _io->n_inputs().get(t);
-               can_remove = _io->input_minimum().get(t) < _io->n_inputs().get(t);
-               name = _io->input(r)->name();
+       if (!_offer_inputs) {
+               return _io->input(r)->name();
        } else {
-               can_add = _io->output_maximum().get(t) > _io->n_outputs().get(t);
-               can_remove = _io->output_minimum().get(t) < _io->n_outputs().get(t);
-               name = _io->output(r)->name();
+               return _io->output(r)->name();
        }
-       
-       items.push_back (
-               Gtk::Menu_Helpers::MenuElem (_("Add port"), sigc::mem_fun (*this, &IOSelector::add_port))
-               );
-
-       items.back().set_sensitive (can_add);
-
-       items.push_back (
-               Gtk::Menu_Helpers::MenuElem (_("Remove port '") + name + _("'"), sigc::bind (sigc::mem_fun (*this, &IOSelector::remove_port), r))
-               );
-
-       items.back().set_sensitive (can_remove);
-
-       menu->popup (e->button, e->time);
-       
-       return true;
+               
 }
 
 void
-IOSelector::add_port ()
+IOSelector::add_row ()
 {
        // The IO selector only works for single typed IOs
        const ARDOUR::DataType t = _io->default_type ();
 
-       if (_for_input) {
+       if (!_offer_inputs) {
 
                try {
                        _io->add_input_port ("", this);
@@ -633,176 +165,33 @@ IOSelector::add_port ()
        }
 }
 
+
 void
-IOSelector::remove_port (int r)
+IOSelector::remove_row (int r)
 {
        // The IO selector only works for single typed IOs
        const ARDOUR::DataType t = _io->default_type ();
        
-       if (_for_input) {
+       if (!_offer_inputs) {
                _io->remove_input_port (_io->input (r), this);
        } else {
                _io->remove_output_port (_io->output (r), this);
        }
 }
 
-void
-IOSelector::group_visible_toggled (Gtk::CheckButton* b, std::string const & n)
-{
-       PortGroupList::iterator i = _port_group_list.begin();
-       while (i != _port_group_list.end() & (*i)->name != n) {
-               ++i;
-       }
-
-       if (i == _port_group_list.end()) {
-               return;
-       }
-
-       (*i)->visible = b->get_active ();
-
-       set_port_group_table_visibility ();
-
-       _column_labels.queue_draw ();
-}
-
-void
-IOSelector::set_port_group_table_visibility ()
-{
-       for (std::vector<PortGroupTable*>::iterator j = _port_group_tables.begin(); j != _port_group_tables.end(); ++j) {
-               if ((*j)->port_group().visible) {
-                       (*j)->get_widget().show();
-               } else {
-                       (*j)->get_widget().hide();
-               }
-       }
-}
-
-
-PortGroupList::PortGroupList (ARDOUR::Session & session, boost::shared_ptr<ARDOUR::IO> io, bool for_input)
-       : _session (session), _io (io), _for_input (for_input),
-         buss (_("Buss"), "ardour:"),
-         track (_("Track"), "ardour:"),
-         system (_("System"), "system:"),
-         other (_("Other"), "")
-{
-       refresh ();
-}
-
-void
-PortGroupList::refresh ()
-{
-       clear ();
-       
-       buss.ports.clear ();
-       track.ports.clear ();
-       system.ports.clear ();
-       other.ports.clear ();
-
-       /* Find the ports provided by ardour; we can't derive their type just from their
-          names, so we'll have to be more devious. */
-
-       boost::shared_ptr<ARDOUR::Session::RouteList> routes = _session.get_routes ();
-
-       for (ARDOUR::Session::RouteList::const_iterator i = routes->begin(); i != routes->end(); ++i) {
-
-               PortGroup* g = 0;
-               if (_io->default_type() == ARDOUR::DataType::AUDIO && dynamic_cast<ARDOUR::AudioTrack*> ((*i).get())) {
-                       /* Audio track for an audio IO */
-                       g = &track;
-               } else if (_io->default_type() == ARDOUR::DataType::MIDI && dynamic_cast<ARDOUR::MidiTrack*> ((*i).get())) {
-                       /* Midi track for a MIDI IO */
-                       g = &track;
-               } else if (_io->default_type() == ARDOUR::DataType::AUDIO && dynamic_cast<ARDOUR::MidiTrack*> ((*i).get()) == 0) {
-                       /* Non-MIDI track for an Audio IO; must be an audio buss */
-                       g = &buss;
-               }
-
-               if (g) {
-                       ARDOUR::PortSet const & p = _for_input ? ((*i)->outputs()) : ((*i)->inputs());
-                       for (uint32_t j = 0; j < p.num_ports(); ++j) {
-                               g->add (p.port(j)->name ());
-                       }
-
-                       std::sort (g->ports.begin(), g->ports.end());
-               }
-       }
-       
-
-       /* XXX: inserts, sends, plugin inserts? */
-       
-       /* Now we need to find the non-ardour ports; we do this by first
-          finding all the ports that we can connect to. */
-       const char **ports = _session.engine().get_ports (
-               "", _io->default_type().to_jack_type(), _for_input ? JackPortIsOutput : JackPortIsInput
-               );
-
-       if (ports) {
-
-               int n = 0;
-               while (ports[n]) {
-                       std::string const p = ports[n];
-
-                       if (p.substr(0, strlen ("system:")) == "system:") {
-                               /* system: prefix */
-                               system.add (p);
-                       } else {
-                               if (p.substr(0, strlen("ardour:")) != "ardour:") {
-                                       /* other (non-ardour) prefix */
-                                       other.add (p);
-                               }
-                       }
-
-                       ++n;
-               }
-       }
-
-       push_back (&buss);
-       push_back (&track);
-       push_back (&system);
-       push_back (&other);
-}
-
-int
-PortGroupList::n_visible_ports () const
-{
-       int n = 0;
-       
-       for (const_iterator i = begin(); i != end(); ++i) {
-               if ((*i)->visible) {
-                       n += (*i)->ports.size();
-               }
-       }
-
-       return n;
-}
-
 std::string
-PortGroupList::get_port_by_index (int n, bool with_prefix) const
+IOSelector::row_descriptor () const
 {
-       /* XXX: slightly inefficient algorithm */
-
-       for (const_iterator i = begin(); i != end(); ++i) {
-               for (std::vector<std::string>::const_iterator j = (*i)->ports.begin(); j != (*i)->ports.end(); ++j) {
-                       if (n == 0) {
-                               if (with_prefix) {
-                                       return (*i)->prefix + *j;
-                               } else {
-                                       return *j;
-                               }
-                       }
-                       --n;
-               }
-       }
-
-       return "";
+       return _("port");
 }
 
 
+
 IOSelectorWindow::IOSelectorWindow (
        ARDOUR::Session& session, boost::shared_ptr<ARDOUR::IO> io, bool for_input, bool can_cancel
        )
        : ArdourDialog ("I/O selector"),
-         _selector (session, io, for_input),
+         _selector (session, io, !for_input),
          ok_button (can_cancel ? _("OK"): _("Close")),
          cancel_button (_("Cancel")),
          rescan_button (_("Rescan"))
index 92c3af9b48b25562bb26363c3ee492aa93b94c4a..92a112dc1dc466f1be044294a2008565f227d1ba 100644 (file)
 #ifndef __ardour_ui_io_selector_h__
 #define __ardour_ui_io_selector_h__
 
-#include <gtkmm/box.h>
-#include <gtkmm/checkbutton.h>
-#include <gtkmm/table.h>
-#include <gtkmm/frame.h>
-
 #include "ardour_dialog.h"
+#include "port_matrix.h"
 
-namespace ARDOUR {
-       class Session;
-       class IO;
-       class PortInsert;
-}
-
-/// A group of port names
-class PortGroup
-{
-  public:
-       PortGroup (std::string const & n, std::string const & p) : name (n), prefix (p), visible (true) {}
-
-       void add (std::string const & p);
-
-       std::string name;
-       std::string prefix; ///< prefix (before colon) e.g. "ardour:"
-       std::vector<std::string> ports; ///< port names
-       bool visible;
-};
-
-/// A table of checkbuttons to provide the GUI for connecting to a PortGroup
-class PortGroupTable
-{
-  public:
-       PortGroupTable (PortGroup&, boost::shared_ptr<ARDOUR::IO>, bool);
-
-       Gtk::Widget& get_widget ();
-       std::pair<int, int> unit_size () const;
-       PortGroup& port_group () { return _port_group; }
-
-  private:
-       void check_button_toggled (Gtk::CheckButton*, int, std::string const &);
-       
-       Gtk::Table _table;
-       Gtk::EventBox _box;
-       PortGroup& _port_group;
-       std::vector<std::vector<Gtk::CheckButton* > > _check_buttons;
-       bool _ignore_check_button_toggle;
-       boost::shared_ptr<ARDOUR::IO> _io;
-       bool _for_input;
-};
-
-/// A list of PortGroups
-class PortGroupList : public std::list<PortGroup*>
-{
-  public:
-       PortGroupList (ARDOUR::Session &, boost::shared_ptr<ARDOUR::IO>, bool);
-
-       void refresh ();
-       int n_visible_ports () const;
-       std::string get_port_by_index (int, bool with_prefix = true) const;
-
-  private:
-       ARDOUR::Session& _session;
-       boost::shared_ptr<ARDOUR::IO> _io;
-       bool _for_input;
-
-       PortGroup buss;
-       PortGroup track;
-       PortGroup system;
-       PortGroup other;
-};
-
-
-/// A widget which provides a set of rotated text labels
-class RotatedLabelSet : public Gtk::Widget {
-  public:
-       RotatedLabelSet (PortGroupList&);
-       virtual ~RotatedLabelSet ();
-
-       void set_angle (int);
-       void set_base_width (int);
-       void update_visibility ();
-       
-  protected:
-       virtual void on_size_request (Gtk::Requisition*);
-       virtual void on_size_allocate (Gtk::Allocation&);
-       virtual void on_realize ();
-       virtual void on_unrealize ();
-       virtual bool on_expose_event (GdkEventExpose*);
-
-       Glib::RefPtr<Gdk::Window> _gdk_window;
-
-  private:
-       std::pair<int, int> setup_layout (std::string const &);
-
-       PortGroupList& _port_group_list; ///< list of ports to display
-       int _angle_degrees; ///< label rotation angle in degrees
-       double _angle_radians; ///< label rotation angle in radians
-       int _base_width; ///< width of labels; see set_base_width() for more details
-       Glib::RefPtr<Pango::Context> _pango_context;
-       Glib::RefPtr<Pango::Layout> _pango_layout;
-       Glib::RefPtr<Gdk::GC> _gc;
-       Gdk::Color _fg_colour;
-       Gdk::Color _bg_colour;
-};
-
-
-
-/// Widget for selecting what an IO is connected to
-class IOSelector : public Gtk::VBox {
+class IOSelector : public PortMatrix {
   public:
        IOSelector (ARDOUR::Session&, boost::shared_ptr<ARDOUR::IO>, bool);
-       ~IOSelector ();
-
-       void redisplay ();
 
-       enum Result {
-               Cancelled,
-               Accepted
-       };
-
-       sigc::signal<void, Result> Finished;
+       void set_state (int, std::string const &, bool);
+       bool get_state (int, std::string const &) const;
+       uint32_t n_rows () const;
+       uint32_t maximum_rows () const;
+       uint32_t minimum_rows () const;
+       std::string row_name (int) const;
+       void add_row ();
+       void remove_row (int);
+       std::string row_descriptor () const;
 
   private:
-       void setup ();
-       void clear ();
-       void setup_dimensions ();
+       
        void ports_changed (ARDOUR::IOChange, void*);
-       bool row_label_button_pressed (GdkEventButton*, int);
-       void add_port ();
-       void remove_port (int);
-       void group_visible_toggled (Gtk::CheckButton*, std::string const &);
-       void set_port_group_table_visibility ();
-
-       PortGroupList _port_group_list;
+       
        boost::shared_ptr<ARDOUR::IO> _io;
-       bool _for_input;
-       std::vector<PortGroupTable*> _port_group_tables;
-       std::vector<Gtk::EventBox*> _row_labels[2];
-       Gtk::VBox* _row_labels_vbox[2];
-       RotatedLabelSet _column_labels;
-       Gtk::HBox _overall_hbox;
-       Gtk::VBox _side_vbox[2];
-       Gtk::HBox _port_group_hbox;
-       Gtk::ScrolledWindow _scrolled_window;
-       Gtk::Label* _side_vbox_pad[2];
 };
 
-
 class IOSelectorWindow : public ArdourDialog
 {
   public:
index f791314d7519fa0b94288948a39d84e98681479e..62aefba27f07786c75170a42ff14193fe8d00faa 100644 (file)
@@ -42,7 +42,8 @@
 #include <ardour/send.h>
 #include <ardour/processor.h>
 #include <ardour/ladspa_plugin.h>
-#include <ardour/bundle.h>
+#include <ardour/auto_bundle.h>
+#include <ardour/user_bundle.h>
 
 #include "ardour_ui.h"
 #include "ardour_dialog.h"
@@ -540,6 +541,7 @@ MixerStrip::output_press (GdkEventButton *ev)
        switch (ev->button) {
 
        case 1:
+       {
                output_menu.set_name ("ArdourContextMenu");
                citems.clear();
                
@@ -547,13 +549,16 @@ MixerStrip::output_press (GdkEventButton *ev)
                citems.push_back (SeparatorElem());
                citems.push_back (MenuElem (_("Disconnect"), mem_fun (*(static_cast<RouteUI*>(this)), &RouteUI::disconnect_output)));
                citems.push_back (SeparatorElem());
-               
-               _session.foreach_bundle (
-                       bind (mem_fun (*this, &MixerStrip::add_bundle_to_output_menu), _route->output_bundle ())
-                       );
+
+               std::vector<boost::shared_ptr<Bundle> > current = _route->bundles_connected_to_outputs ();
+
+               _session.foreach_bundle (
+                       bind (mem_fun (*this, &MixerStrip::add_bundle_to_output_menu), current)
+                       );
 
                output_menu.popup (1, ev->time);
                break;
+       }
                
        default:
                break;
@@ -607,18 +612,21 @@ MixerStrip::input_press (GdkEventButton *ev)
        switch (ev->button) {
 
        case 1:
+       {
                citems.push_back (MenuElem (_("Edit"), mem_fun(*this, &MixerStrip::edit_input_configuration)));
                citems.push_back (SeparatorElem());
                citems.push_back (MenuElem (_("Disconnect"), mem_fun (*(static_cast<RouteUI*>(this)), &RouteUI::disconnect_input)));
                citems.push_back (SeparatorElem());
-               
+
+               std::vector<boost::shared_ptr<Bundle> > current = _route->bundles_connected_to_inputs ();
+
                _session.foreach_bundle (
-                       bind (mem_fun (*this, &MixerStrip::add_bundle_to_input_menu), _route->input_bundle ())
+                       bind (mem_fun (*this, &MixerStrip::add_bundle_to_input_menu), current)
                        );
 
                input_menu.popup (1, ev->time);
                break;
-               
+       }
        default:
                break;
        }
@@ -658,23 +666,23 @@ MixerStrip::bundle_output_chosen (boost::shared_ptr<ARDOUR::Bundle> c)
 }
 
 void
-MixerStrip::add_bundle_to_input_menu (boost::shared_ptr<Bundle> b, boost::shared_ptr<Bundle> current)
+MixerStrip::add_bundle_to_input_menu (boost::shared_ptr<Bundle> b, std::vector<boost::shared_ptr<Bundle> > const & current)
 {
        using namespace Menu_Helpers;
 
        /* the input menu needs to contain only output bundles (that we
           can connect inputs to */
-       if (boost::dynamic_pointer_cast<OutputBundle, Bundle> (b) == 0) {
-               return;
-       }
+       if (b->ports_are_outputs() == false) {
+               return;
+       }
 
        MenuList& citems = input_menu.items();
        
        if (b->nchannels() == _route->n_inputs().n_total()) {
 
                citems.push_back (CheckMenuElem (b->name(), bind (mem_fun(*this, &MixerStrip::bundle_input_chosen), b)));
-               
-               if (current == b) {
+
+               if (std::find (current.begin(), current.end(), b) != current.end()) {
                        ignore_toggle = true;
                        dynamic_cast<CheckMenuItem *> (&citems.back())->set_active (true);
                        ignore_toggle = false;
@@ -683,23 +691,22 @@ MixerStrip::add_bundle_to_input_menu (boost::shared_ptr<Bundle> b, boost::shared
 }
 
 void
-MixerStrip::add_bundle_to_output_menu (boost::shared_ptr<Bundle> b, boost::shared_ptr<Bundle> current)
+MixerStrip::add_bundle_to_output_menu (boost::shared_ptr<Bundle> b, std::vector<boost::shared_ptr<Bundle> > const & current)
 {
        using namespace Menu_Helpers;
 
        /* the output menu needs to contain only input bundles (that we
           can connect outputs to */
-       if (boost::dynamic_pointer_cast<InputBundle, Bundle> (b) == 0) {
-               return;
-       }
-
+       if (b->ports_are_inputs() == false) {
+               return;
+       }
 
        if (b->nchannels() == _route->n_outputs().n_total()) {
 
                MenuList& citems = output_menu.items();
                citems.push_back (CheckMenuElem (b->name(), bind (mem_fun(*this, &MixerStrip::bundle_output_chosen), b)));
                
-               if (current == b) {
+               if (std::find (current.begin(), current.end(), b) != current.end()) {
                        ignore_toggle = true;
                        dynamic_cast<CheckMenuItem *> (&citems.back())->set_active (true);
                        ignore_toggle = false;
@@ -752,10 +759,11 @@ MixerStrip::connect_to_pan ()
 void
 MixerStrip::update_input_display ()
 {
-       boost::shared_ptr<ARDOUR::Bundle> c;
+       std::vector<boost::shared_ptr<ARDOUR::Bundle> > c = _route->bundles_connected_to_inputs ();
 
-       if ((c = _route->input_bundle()) != 0) {
-               input_label.set_text (c->name());
+       /* XXX: how do we represent >1 connected bundle? */
+       if (c.empty() == false) {
+               input_label.set_text (c[0]->name());
        } else {
                switch (_width) {
                case Wide:
@@ -772,10 +780,11 @@ MixerStrip::update_input_display ()
 void
 MixerStrip::update_output_display ()
 {
-       boost::shared_ptr<ARDOUR::Bundle> c;
+       std::vector<boost::shared_ptr<ARDOUR::Bundle> > c = _route->bundles_connected_to_outputs ();
 
-       if ((c = _route->output_bundle()) != 0) {
-               output_label.set_text (c->name());
+       /* XXX: how do we represent >1 connected bundle? */
+       if (c.empty() == false) {
+               output_label.set_text (c[0]->name());
        } else {
                switch (_width) {
                case Wide:
index c3cf68a9ce6ee9723058c9b544ac152fbc699604..f134897fc54c6a33a1b2611268ac4d02180660fa 100644 (file)
@@ -168,10 +168,10 @@ class MixerStrip : public RouteUI, public Gtk::EventBox
        gint output_press (GdkEventButton *);
 
        Gtk::Menu  input_menu;
-       void add_bundle_to_input_menu (boost::shared_ptr<ARDOUR::Bundle>, boost::shared_ptr<ARDOUR::Bundle>);
+       void add_bundle_to_input_menu (boost::shared_ptr<ARDOUR::Bundle>, std::vector<boost::shared_ptr<ARDOUR::Bundle> > const &);
 
        Gtk::Menu output_menu;
-       void add_bundle_to_output_menu (boost::shared_ptr<ARDOUR::Bundle>, boost::shared_ptr<ARDOUR::Bundle>);
+       void add_bundle_to_output_menu (boost::shared_ptr<ARDOUR::Bundle>, std::vector<boost::shared_ptr<ARDOUR::Bundle> > const &);
        
        void bundle_input_chosen (boost::shared_ptr<ARDOUR::Bundle>);
        void bundle_output_chosen (boost::shared_ptr<ARDOUR::Bundle>);
diff --git a/gtk2_ardour/port_matrix.cc b/gtk2_ardour/port_matrix.cc
new file mode 100644 (file)
index 0000000..19c0dc9
--- /dev/null
@@ -0,0 +1,730 @@
+/*
+    Copyright (C) 2002-2007 Paul Davis 
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <gtkmm/label.h>
+#include <gtkmm/enums.h>
+#include <gtkmm/image.h>
+#include <gtkmm/stock.h>
+#include <gtkmm/messagedialog.h>
+#include <gtkmm/menu.h>
+#include <gtkmm/menu_elems.h>
+#include <gtkmm/menuitem.h>
+#include <gtkmm/menushell.h>
+#include <glibmm/objectbase.h>
+#include <gtkmm2ext/doi.h>
+#include <ardour/port_insert.h>
+#include "ardour/session.h"
+#include "ardour/io.h"
+#include "ardour/audioengine.h"
+#include "ardour/track.h"
+#include "ardour/audio_track.h"
+#include "ardour/midi_track.h"
+#include "ardour/data_type.h"
+#include "io_selector.h"
+#include "utils.h"
+#include "gui_thread.h"
+#include "i18n.h"
+
+/** Add a port to a group.
+ *  @param p Port name, with or without prefix.
+ */
+
+void
+PortGroup::add (std::string const & p)
+{
+       if (prefix.empty() == false && p.substr (0, prefix.length()) == prefix) {
+               ports.push_back (p.substr (prefix.length()));
+       } else {
+               ports.push_back (p);
+       }
+}
+
+/** PortGroupUI constructor.
+ *  @param m PortMatrix to work for.
+ *  @Param g PortGroup to represent.
+ */
+
+PortGroupUI::PortGroupUI (PortMatrix& m, PortGroup& g)
+       : _port_matrix (m), _port_group (g), _ignore_check_button_toggle (false),
+         _visibility_checkbutton (g.name)
+{
+       int const ports = _port_group.ports.size();
+       int const rows = _port_matrix.n_rows ();
+
+       if (rows == 0 || ports == 0) {
+               return;
+       }
+
+       /* Sort out the table and the checkbuttons inside it */
+       
+       _table.resize (rows, ports);
+       _port_checkbuttons.resize (rows);
+       for (int i = 0; i < rows; ++i) {
+               _port_checkbuttons[i].resize (ports);
+       }
+
+       for (int i = 0; i < rows; ++i) {
+               for (uint32_t j = 0; j < _port_group.ports.size(); ++j) {
+                       Gtk::CheckButton* b = new Gtk::CheckButton;
+                       
+                       b->signal_toggled().connect (
+                               sigc::bind (sigc::mem_fun (*this, &PortGroupUI::port_checkbutton_toggled), b, i, j)
+                               );
+                       
+                       _port_checkbuttons[i][j] = b;
+                       _table.attach (*b, j, j + 1, i, i + 1);
+               }
+       }
+
+       _table_box.add (_table);
+
+       _ignore_check_button_toggle = true;
+
+       /* Set the state of the check boxes according to current connections */
+       for (int i = 0; i < rows; ++i) {
+               for (uint32_t j = 0; j < _port_group.ports.size(); ++j) {
+                       std::string const t = _port_group.prefix + _port_group.ports[j];
+                       bool const s = _port_matrix.get_state (i, t);
+                       _port_checkbuttons[i][j]->set_active (s);
+                       if (s) {
+                               _port_group.visible = true;
+                       }
+               }
+       }
+
+       _ignore_check_button_toggle = false;
+
+       _visibility_checkbutton.signal_toggled().connect (sigc::mem_fun (*this, &PortGroupUI::visibility_checkbutton_toggled));
+}
+
+/** The visibility of a PortGroupUI has been toggled */
+void
+PortGroupUI::visibility_checkbutton_toggled ()
+{
+       _port_group.visible = _visibility_checkbutton.get_active ();
+       setup_visibility ();
+}
+
+/** @return Width and height of a single checkbutton in a port group table */
+std::pair<int, int>
+PortGroupUI::unit_size () const
+{
+       if (_port_checkbuttons.empty() || _port_checkbuttons[0].empty())
+       {
+               return std::pair<int, int> (0, 0);
+       }
+
+       int r = 0;
+       /* We can't ask for row spacing unless there >1 rows, otherwise we get a warning */
+       if (_table.property_n_rows() > 1) {
+               r = _table.get_row_spacing (0);
+       }
+
+       return std::make_pair (
+               _port_checkbuttons[0][0]->get_width() + _table.get_col_spacing (0),
+               _port_checkbuttons[0][0]->get_height() + r
+               );
+}
+
+/** @return Table widget containing the port checkbuttons */
+Gtk::Widget&
+PortGroupUI::get_table ()
+{
+       return _table_box;
+}
+
+/** @return Checkbutton used to toggle visibility */
+Gtk::Widget&
+PortGroupUI::get_visibility_checkbutton ()
+{
+       return _visibility_checkbutton;
+}
+
+
+/** Handle a toggle of a port check button */
+void
+PortGroupUI::port_checkbutton_toggled (Gtk::CheckButton* b, int r, int c)
+{
+       if (_ignore_check_button_toggle == false) {
+               _port_matrix.set_state (r, _port_group.prefix + _port_group.ports[c], b->get_active());
+       }
+}
+
+/** Set up visibility of the port group according to PortGroup::visible */
+void
+PortGroupUI::setup_visibility ()
+{
+       if (_port_group.visible) {
+               _table_box.show ();
+       } else {
+               _table_box.hide ();
+       }
+
+       if (_visibility_checkbutton.get_active () != _port_group.visible) {
+               _visibility_checkbutton.set_active (_port_group.visible);
+       }
+}
+
+
+RotatedLabelSet::RotatedLabelSet (PortGroupList& g)
+       : Glib::ObjectBase ("RotatedLabelSet"), Gtk::Widget (), _port_group_list (g), _base_width (128)
+{
+       set_flags (Gtk::NO_WINDOW);
+       set_angle (30);
+}
+
+RotatedLabelSet::~RotatedLabelSet ()
+{
+       
+}
+
+
+/** Set the angle that the labels are drawn at.
+ * @param degrees New angle in degrees.
+ */
+
+void
+RotatedLabelSet::set_angle (int degrees)
+{
+       _angle_degrees = degrees;
+       _angle_radians = M_PI * _angle_degrees / 180;
+
+       queue_resize ();
+}
+
+void
+RotatedLabelSet::on_size_request (Gtk::Requisition* requisition)
+{
+       *requisition = Gtk::Requisition ();
+
+       if (_pango_layout == 0) {
+               return;
+       }
+
+       /* Our height is the highest label */
+       requisition->height = 0;
+       for (PortGroupList::const_iterator i = _port_group_list.begin(); i != _port_group_list.end(); ++i) {
+               for (std::vector<std::string>::const_iterator j = (*i)->ports.begin(); j != (*i)->ports.end(); ++j) {
+                       std::pair<int, int> const d = setup_layout (*j);
+                       if (d.second > requisition->height) {
+                               requisition->height = d.second;
+                       }
+               }
+       }
+
+       /* And our width is the base plus the width of the last label */
+       requisition->width = _base_width;
+       int const n = _port_group_list.n_visible_ports ();
+       if (n > 0) {
+               std::pair<int, int> const d = setup_layout (_port_group_list.get_port_by_index (n - 1, false));
+               requisition->width += d.first;
+       }
+}
+
+void
+RotatedLabelSet::on_size_allocate (Gtk::Allocation& allocation)
+{
+       set_allocation (allocation);
+
+       if (_gdk_window) {
+               _gdk_window->move_resize (
+                       allocation.get_x(), allocation.get_y(), allocation.get_width(), allocation.get_height()
+                       );
+       }
+}
+
+void
+RotatedLabelSet::on_realize ()
+{
+       Gtk::Widget::on_realize ();
+
+       Glib::RefPtr<Gtk::Style> style = get_style ();
+
+       if (!_gdk_window) {
+               GdkWindowAttr attributes;
+               memset (&attributes, 0, sizeof (attributes));
+
+               Gtk::Allocation allocation = get_allocation ();
+               attributes.x = allocation.get_x ();
+               attributes.y = allocation.get_y ();
+               attributes.width = allocation.get_width ();
+               attributes.height = allocation.get_height ();
+
+               attributes.event_mask = get_events () | Gdk::EXPOSURE_MASK; 
+               attributes.window_type = GDK_WINDOW_CHILD;
+               attributes.wclass = GDK_INPUT_OUTPUT;
+
+               _gdk_window = Gdk::Window::create (get_window (), &attributes, GDK_WA_X | GDK_WA_Y);
+               unset_flags (Gtk::NO_WINDOW);
+               set_window (_gdk_window);
+
+               _bg_colour = style->get_bg (Gtk::STATE_NORMAL );
+               modify_bg (Gtk::STATE_NORMAL, _bg_colour);
+               _fg_colour = style->get_fg (Gtk::STATE_NORMAL);
+;
+               _gdk_window->set_user_data (gobj ());
+
+               /* Set up Pango stuff */
+               _pango_context = create_pango_context ();
+
+               Pango::Matrix matrix = PANGO_MATRIX_INIT;
+               pango_matrix_rotate (&matrix, _angle_degrees);
+               _pango_context->set_matrix (matrix);
+
+               _pango_layout = Pango::Layout::create (_pango_context);
+               _gc = Gdk::GC::create (get_window ());
+       }
+}
+
+void
+RotatedLabelSet::on_unrealize()
+{
+       _gdk_window.clear ();
+
+       Gtk::Widget::on_unrealize ();
+}
+
+
+/** Set up our Pango layout to plot a given string, and compute its dimensions once
+ *  it has been rotated.
+ *  @param s String to use.
+ *  @return width and height of the rotated string, in pixels.
+ */
+
+std::pair<int, int>
+RotatedLabelSet::setup_layout (std::string const & s)
+{
+       _pango_layout->set_text (s);
+
+       /* Here's the unrotated size */
+       int w;
+       int h;
+       _pango_layout->get_pixel_size (w, h);
+
+       /* Rotate the width and height as appropriate.  I thought Pango might be able
+          to do this for us, but I can't find out how... */
+       std::pair<int, int> d;
+       d.first = int (w * cos (_angle_radians) - h * sin (_angle_radians));
+       d.second = int (w * sin (_angle_radians) + h * cos (_angle_radians));
+
+       return d;
+}
+
+bool
+RotatedLabelSet::on_expose_event (GdkEventExpose* event)
+{
+       if (!_gdk_window) {
+               return true;
+       }
+
+       int const height = get_allocation().get_height ();
+       double const spacing = double (_base_width) / _port_group_list.n_visible_ports();
+
+       /* Plot all the visible labels; really we should clip for efficiency */
+       int n = 0;
+       for (PortGroupList::const_iterator i = _port_group_list.begin(); i != _port_group_list.end(); ++i) {
+               if ((*i)->visible) {
+                       for (uint32_t j = 0; j < (*i)->ports.size(); ++j) {
+                               std::pair<int, int> const d = setup_layout ((*i)->ports[j]);
+                               get_window()->draw_layout (_gc, int ((n + 0.25) * spacing), height - d.second, _pango_layout, _fg_colour, _bg_colour);
+                               ++n;
+                       }
+               }
+       }
+
+       return true;
+}
+
+/** Set the `base width'.  This is the width of the base of the label set, ie:
+ *
+ *     L L L L
+ *    E E E E
+ *   B B B B
+ *  A A A A
+ * L L L L
+ * <--w-->
+ */
+    
+void
+RotatedLabelSet::set_base_width (int w)
+{
+       _base_width = w;
+       queue_resize ();
+}
+
+
+PortMatrix::PortMatrix (ARDOUR::Session& session, ARDOUR::DataType type, bool offer_inputs, PortGroupList::Mask mask)
+       : _offer_inputs (offer_inputs), _port_group_list (session, type, offer_inputs, mask), _type (type),
+         _column_labels (_port_group_list)
+{
+       _row_labels_vbox[0] = _row_labels_vbox[1] = 0;
+       _side_vbox_pad[0] = _side_vbox_pad[1] = 0;
+
+       pack_start (_visibility_checkbutton_box, false, false);
+       
+       _side_vbox[0].pack_start (*Gtk::manage (new Gtk::Label ("")));
+       _overall_hbox.pack_start (_side_vbox[0], false, false);
+       _scrolled_window.set_policy (Gtk::POLICY_ALWAYS, Gtk::POLICY_NEVER);
+       _scrolled_window.set_shadow_type (Gtk::SHADOW_NONE);
+       Gtk::VBox* b = new Gtk::VBox;
+       b->pack_start (_column_labels, false, false);
+       b->pack_start (_port_group_hbox, false, false);
+       Gtk::Alignment* a = new Gtk::Alignment (0, 1, 0, 0);
+       a->add (*Gtk::manage (b));
+       _scrolled_window.add (*Gtk::manage (a));
+       _overall_hbox.pack_start (_scrolled_window);
+       _side_vbox[1].pack_start (*Gtk::manage (new Gtk::Label ("")));
+       _overall_hbox.pack_start (_side_vbox[1]);
+       pack_start (_overall_hbox);
+
+       _port_group_hbox.signal_size_allocate().connect (sigc::hide (sigc::mem_fun (*this, &IOSelector::setup_dimensions)));
+}
+
+PortMatrix::~PortMatrix ()
+{
+       clear ();
+}
+
+/** Clear out the things that change when the number of source or destination ports changes */
+void
+PortMatrix::clear ()
+{
+       for (int i = 0; i < 2; ++i) {
+
+               for (std::vector<Gtk::EventBox*>::iterator j = _row_labels[i].begin(); j != _row_labels[i].end(); ++j) {
+                       delete *j;
+               }
+               _row_labels[i].clear ();
+               
+               if (_row_labels_vbox[i]) {
+                       _side_vbox[i].remove (*_row_labels_vbox[i]);
+               }
+               delete _row_labels_vbox[i];
+               _row_labels_vbox[i] = 0;
+               
+               if (_side_vbox_pad[i]) {
+                       _side_vbox[i].remove (*_side_vbox_pad[i]);
+               }
+               delete _side_vbox_pad[i];
+               _side_vbox_pad[i] = 0;
+       }
+
+       for (std::vector<PortGroupUI*>::iterator i = _port_group_ui.begin(); i != _port_group_ui.end(); ++i) {
+               _port_group_hbox.remove ((*i)->get_table());
+               _visibility_checkbutton_box.remove ((*i)->get_visibility_checkbutton());
+               delete *i;
+       }
+
+       _port_group_ui.clear ();
+}
+
+
+/** Set up dimensions of some of our widgets which depend on other dimensions
+ *  within the dialogue.
+ */
+void
+PortMatrix::setup_dimensions ()
+{
+       /* Get some dimensions from various places */
+       int const scrollbar_height = _scrolled_window.get_hscrollbar()->get_height();
+
+       std::pair<int, int> unit_size (0, 0);
+       int port_group_tables_height = 0;
+       for (std::vector<PortGroupUI*>::iterator i = _port_group_ui.begin(); i != _port_group_ui.end(); ++i) {
+               std::pair<int, int> const u = (*i)->unit_size ();
+               unit_size.first = std::max (unit_size.first, u.first);
+               unit_size.second = std::max (unit_size.second, u.second);
+               port_group_tables_height = std::max (
+                       port_group_tables_height, (*i)->get_table().get_height()
+                       );
+       }
+
+       /* Column labels */
+       _column_labels.set_base_width (_port_group_list.n_visible_ports () * unit_size.first);
+
+       /* Scrolled window */
+       /* XXX: really shouldn't set a minimum horizontal size here, but if we don't
+          the window starts up very small */
+       _scrolled_window.set_size_request (
+               std::min (_column_labels.get_width(), 640),
+               _column_labels.get_height() + port_group_tables_height + scrollbar_height + 16
+               );
+       
+       /* Row labels */
+       for (int i = 0; i < 2; ++i) {
+               for (std::vector<Gtk::EventBox*>::iterator j = _row_labels[i].begin(); j != _row_labels[i].end(); ++j) {
+                       (*j)->get_child()->set_size_request (-1, unit_size.second);
+               }
+
+               if (_side_vbox_pad[i]) {
+                       _side_vbox_pad[i]->set_size_request (-1, scrollbar_height + unit_size.second / 4);
+               }
+       }
+}
+
+
+/** Set up the dialogue */
+void
+PortMatrix::setup ()
+{
+       clear ();
+
+       int const rows = n_rows ();
+       
+       /* Row labels */
+       for (int i = 0; i < 2; ++i) {
+               _row_labels_vbox[i] = new Gtk::VBox;
+               int const run_rows = std::max (1, rows);
+               for (int j = 0; j < run_rows; ++j) {
+                       Gtk::Label* label = new Gtk::Label (rows == 0 ? "Quim" : row_name (j));
+                       Gtk::EventBox* b = new Gtk::EventBox;
+                       b->set_events (Gdk::BUTTON_PRESS_MASK);
+                       b->signal_button_press_event().connect (sigc::bind (sigc::mem_fun (*this, &IOSelector::row_label_button_pressed), j));
+                       b->add (*Gtk::manage (label));
+                       _row_labels[i].push_back (b);
+                       _row_labels_vbox[i]->pack_start (*b, false, false);
+               }
+
+               _side_vbox[i].pack_start (*_row_labels_vbox[i], false, false);
+               _side_vbox_pad[i] = new Gtk::Label ("");
+               _side_vbox[i].pack_start (*_side_vbox_pad[i], false, false);
+       }
+
+       /* Checkbutton tables and visibility checkbuttons */
+       int n = 0;
+       for (PortGroupList::iterator i = _port_group_list.begin(); i != _port_group_list.end(); ++i) {
+               PortGroupUI* t = new PortGroupUI (*this, **i);
+
+               /* XXX: this is a bit of a hack; should probably use a configurable colour here */
+               Gdk::Color alt_bg = get_style()->get_bg (Gtk::STATE_NORMAL);
+               alt_bg.set_rgb (alt_bg.get_red() + 4096, alt_bg.get_green() + 4096, alt_bg.get_blue () + 4096);
+               if ((n % 2) == 0) {
+                       t->get_table().modify_bg (Gtk::STATE_NORMAL, alt_bg);
+               }
+
+               _port_group_ui.push_back (t);
+               _port_group_hbox.pack_start (t->get_table(), false, false);
+
+               _visibility_checkbutton_box.pack_start (t->get_visibility_checkbutton(), false, false);
+               ++n;
+       }
+
+       show_all ();
+
+       for (std::vector<PortGroupUI*>::iterator i = _port_group_ui.begin(); i != _port_group_ui.end(); ++i) {
+               (*i)->setup_visibility ();
+       }
+       
+}
+
+void
+PortMatrix::redisplay ()
+{
+       _port_group_list.refresh ();
+       setup ();
+}
+
+
+/** Handle a button press on a row label */
+bool
+PortMatrix::row_label_button_pressed (GdkEventButton* e, int r)
+{
+       if (e->type != GDK_BUTTON_PRESS || e->button != 3) {
+               return false;
+       }
+
+       Gtk::Menu* menu = Gtk::manage (new Gtk::Menu);
+       Gtk::Menu_Helpers::MenuList& items = menu->items ();
+       menu->set_name ("ArdourContextMenu");
+
+       bool const can_add = maximum_rows () > n_rows ();
+       bool const can_remove = minimum_rows () < n_rows ();
+       std::string const name = row_name (r);
+       
+       items.push_back (
+               Gtk::Menu_Helpers::MenuElem (string_compose(_("Add %1"), row_descriptor()), sigc::mem_fun (*this, &PortMatrix::add_row))
+               );
+
+       items.back().set_sensitive (can_add);
+
+       items.push_back (
+               Gtk::Menu_Helpers::MenuElem (string_compose(_("Remove %1 \"%2\""), row_descriptor(), name), sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_row), r))
+               );
+
+       items.back().set_sensitive (can_remove);
+
+       menu->popup (e->button, e->time);
+       
+       return true;
+}
+
+void
+PortMatrix::set_type (ARDOUR::DataType t)
+{
+       _type = t;
+       _port_group_list.set_type (t);
+       redisplay ();
+}
+
+void
+PortMatrix::set_offer_inputs (bool i)
+{
+       _offer_inputs = i;
+       _port_group_list.set_offer_inputs (i);
+       redisplay ();
+}
+
+/** PortGroupList constructor.
+ *  @param session Session to get ports from.
+ *  @param type Type of ports to offer (audio or MIDI)
+ *  @param offer_inputs true to offer output ports, otherwise false.
+ *  @param mask Mask of groups to make visible by default.
+ */
+
+PortGroupList::PortGroupList (ARDOUR::Session & session, ARDOUR::DataType type, bool offer_inputs, Mask mask)
+       : _session (session), _type (type), _offer_inputs (offer_inputs),
+         buss (_("Buss"), "ardour:", mask & BUSS),
+         track (_("Track"), "ardour:", mask & TRACK),
+         system (_("System"), "system:", mask & SYSTEM),
+         other (_("Other"), "", mask & OTHER)
+{
+       refresh ();
+}
+
+void
+PortGroupList::refresh ()
+{
+       clear ();
+       
+       buss.ports.clear ();
+       track.ports.clear ();
+       system.ports.clear ();
+       other.ports.clear ();
+
+       /* Find the ports provided by ardour; we can't derive their type just from their
+          names, so we'll have to be more devious. */
+
+       boost::shared_ptr<ARDOUR::Session::RouteList> routes = _session.get_routes ();
+
+       for (ARDOUR::Session::RouteList::const_iterator i = routes->begin(); i != routes->end(); ++i) {
+
+               PortGroup* g = 0;
+               if (_type == ARDOUR::DataType::AUDIO && dynamic_cast<ARDOUR::AudioTrack*> ((*i).get())) {
+                       /* Audio track for an audio IO */
+                       g = &track;
+               } else if (_type == ARDOUR::DataType::MIDI && dynamic_cast<ARDOUR::MidiTrack*> ((*i).get())) {
+                       /* Midi track for a MIDI IO */
+                       g = &track;
+               } else if (_type == ARDOUR::DataType::AUDIO && dynamic_cast<ARDOUR::MidiTrack*> ((*i).get()) == 0) {
+                       /* Non-MIDI track for an Audio IO; must be an audio buss */
+                       g = &buss;
+               }
+
+               if (g) {
+                       ARDOUR::PortSet const & p = _offer_inputs ? ((*i)->inputs()) : ((*i)->outputs());
+                       for (uint32_t j = 0; j < p.num_ports(); ++j) {
+                               g->add (p.port(j)->name ());
+                       }
+
+                       std::sort (g->ports.begin(), g->ports.end());
+               }
+       }
+       
+
+       /* XXX: inserts, sends, plugin inserts? */
+       
+       /* Now we need to find the non-ardour ports; we do this by first
+          finding all the ports that we can connect to. */
+       const char **ports = _session.engine().get_ports (
+               "", _type.to_jack_type(), _offer_inputs ? JackPortIsInput : JackPortIsOutput
+               );
+
+       if (ports) {
+
+               int n = 0;
+               while (ports[n]) {
+                       std::string const p = ports[n];
+
+                       if (p.substr(0, strlen ("system:")) == "system:") {
+                               /* system: prefix */
+                               system.add (p);
+                       } else {
+                               if (p.substr(0, strlen("ardour:")) != "ardour:") {
+                                       /* other (non-ardour) prefix */
+                                       other.add (p);
+                               }
+                       }
+
+                       ++n;
+               }
+       }
+
+       push_back (&buss);
+       push_back (&track);
+       push_back (&system);
+       push_back (&other);
+}
+
+int
+PortGroupList::n_visible_ports () const
+{
+       int n = 0;
+       
+       for (const_iterator i = begin(); i != end(); ++i) {
+               if ((*i)->visible) {
+                       n += (*i)->ports.size();
+               }
+       }
+
+       return n;
+}
+
+std::string
+PortGroupList::get_port_by_index (int n, bool with_prefix) const
+{
+       /* XXX: slightly inefficient algorithm */
+
+       for (const_iterator i = begin(); i != end(); ++i) {
+               for (std::vector<std::string>::const_iterator j = (*i)->ports.begin(); j != (*i)->ports.end(); ++j) {
+                       if (n == 0) {
+                               if (with_prefix) {
+                                       return (*i)->prefix + *j;
+                               } else {
+                                       return *j;
+                               }
+                       }
+                       --n;
+               }
+       }
+
+       return "";
+}
+
+void
+PortGroupList::set_type (ARDOUR::DataType t)
+{
+       _type = t;
+}
+
+void
+PortGroupList::set_offer_inputs (bool i)
+{
+       _offer_inputs = i;
+}
+
diff --git a/gtk2_ardour/port_matrix.h b/gtk2_ardour/port_matrix.h
new file mode 100644 (file)
index 0000000..a945d00
--- /dev/null
@@ -0,0 +1,200 @@
+/*
+    Copyright (C) 2002-2007 Paul Davis 
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef __ardour_ui_port_matrix_h__
+#define __ardour_ui_port_matrix_h__
+
+#include <gtkmm/box.h>
+#include <gtkmm/checkbutton.h>
+#include <gtkmm/table.h>
+#include <gtkmm/frame.h>
+#include <gtkmm/eventbox.h>
+#include <gtkmm/scrolledwindow.h>
+
+#include "ardour_dialog.h"
+
+namespace ARDOUR {
+       class Session;
+       class IO;
+       class PortInsert;
+}
+
+class PortMatrix;
+
+/// A list of port names, grouped by some aspect of their type e.g. busses, tracks, system
+class PortGroup
+{
+  public:
+       /** PortGroup constructor.
+        * @param n Name.
+        * @param p Port name prefix.
+        * @param v true if group should be visible in the UI, otherwise false.
+        */
+       PortGroup (std::string const & n, std::string const & p, bool v) : name (n), prefix (p), visible (v) {}
+
+       void add (std::string const & p);
+
+       std::string name; ///< name for the group
+       std::string prefix; ///< prefix (before colon) e.g. "ardour:"
+       std::vector<std::string> ports; ///< port names
+       bool visible; ///< true if the group is visible in the UI
+};
+
+/// The UI for a PortGroup
+class PortGroupUI
+{
+  public:
+       PortGroupUI (PortMatrix&, PortGroup&);
+
+       Gtk::Widget& get_table ();
+       Gtk::Widget& get_visibility_checkbutton ();
+       std::pair<int, int> unit_size () const;
+       PortGroup& port_group () { return _port_group; }
+       void setup_visibility ();
+
+  private:
+       void port_checkbutton_toggled (Gtk::CheckButton*, int, int);
+       void visibility_checkbutton_toggled ();
+
+       PortMatrix& _port_matrix; ///< the PortMatrix that we are working for
+       PortGroup& _port_group; ///< the PortGroup that we are representing
+       bool _ignore_check_button_toggle;
+       Gtk::Table _table;
+       Gtk::EventBox _table_box;
+       std::vector<std::vector<Gtk::CheckButton* > > _port_checkbuttons;
+       Gtk::CheckButton _visibility_checkbutton;
+};
+
+/// A list of PortGroups
+class PortGroupList : public std::list<PortGroup*>
+{
+  public:
+       enum Mask {
+               BUSS = 0x1,
+               TRACK = 0x2,
+               SYSTEM = 0x4,
+               OTHER = 0x8
+       };
+
+       PortGroupList (ARDOUR::Session &, ARDOUR::DataType, bool, Mask);
+
+       void refresh ();
+       int n_visible_ports () const;
+       std::string get_port_by_index (int, bool with_prefix = true) const;
+       void set_type (ARDOUR::DataType);
+       void set_offer_inputs (bool);
+       
+  private:
+       ARDOUR::Session& _session;
+       ARDOUR::DataType _type;
+       bool _offer_inputs;
+
+       PortGroup buss;
+       PortGroup track;
+       PortGroup system;
+       PortGroup other;
+};
+
+
+/// A widget which provides a set of rotated text labels
+class RotatedLabelSet : public Gtk::Widget {
+  public:
+       RotatedLabelSet (PortGroupList&);
+       virtual ~RotatedLabelSet ();
+
+       void set_angle (int);
+       void set_base_width (int);
+       void update_visibility ();
+       
+  protected:
+       virtual void on_size_request (Gtk::Requisition*);
+       virtual void on_size_allocate (Gtk::Allocation&);
+       virtual void on_realize ();
+       virtual void on_unrealize ();
+       virtual bool on_expose_event (GdkEventExpose*);
+
+       Glib::RefPtr<Gdk::Window> _gdk_window;
+
+  private:
+       std::pair<int, int> setup_layout (std::string const &);
+
+       PortGroupList& _port_group_list; ///< list of ports to display
+       int _angle_degrees; ///< label rotation angle in degrees
+       double _angle_radians; ///< label rotation angle in radians
+       int _base_width; ///< width of labels; see set_base_width() for more details
+       Glib::RefPtr<Pango::Context> _pango_context;
+       Glib::RefPtr<Pango::Layout> _pango_layout;
+       Glib::RefPtr<Gdk::GC> _gc;
+       Gdk::Color _fg_colour;
+       Gdk::Color _bg_colour;
+};
+
+
+class PortMatrix : public Gtk::VBox {
+  public:
+       PortMatrix (ARDOUR::Session&, ARDOUR::DataType, bool, PortGroupList::Mask);
+       ~PortMatrix ();
+
+       void redisplay ();
+
+       enum Result {
+               Cancelled,
+               Accepted
+       };
+
+       sigc::signal<void, Result> Finished;
+
+       void set_type (ARDOUR::DataType);
+       void set_offer_inputs (bool);
+
+       virtual void set_state (int, std::string const &, bool) = 0;
+       virtual bool get_state (int, std::string const &) const = 0;
+       virtual uint32_t n_rows () const = 0;
+       virtual uint32_t maximum_rows () const = 0;
+       virtual uint32_t minimum_rows () const = 0;
+       virtual std::string row_name (int) const = 0;
+       virtual void add_row () = 0;
+       virtual void remove_row (int) = 0;
+       virtual std::string row_descriptor () const = 0;
+
+  protected:
+
+       bool _offer_inputs;
+
+  private:
+       void setup ();
+       void clear ();
+       void setup_dimensions ();
+       bool row_label_button_pressed (GdkEventButton*, int);
+
+       PortGroupList _port_group_list;
+       ARDOUR::DataType _type;
+       std::vector<PortGroupUI*> _port_group_ui;
+       std::vector<Gtk::EventBox*> _row_labels[2];
+       Gtk::VBox* _row_labels_vbox[2];
+       RotatedLabelSet _column_labels;
+       Gtk::HBox _overall_hbox;
+       Gtk::VBox _side_vbox[2];
+       Gtk::HBox _port_group_hbox;
+       Gtk::ScrolledWindow _scrolled_window;
+       Gtk::Label* _side_vbox_pad[2];
+       Gtk::HBox _visibility_checkbutton_box;
+};
+
+#endif
index 4d68dc77256e157c57023ed64ff0d913b4040d3f..af08d4569c02ddae3be05bb79247b5972134ed7c 100644 (file)
@@ -12,7 +12,7 @@ ardour = env.Copy()
 # this defines the version number of libardour
 # 
 
-domain = 'libardour2'
+domain = 'libardour'
 
 ardour.Append(DOMAIN = domain, MAJOR = 2, MINOR = 0, MICRO = 0)
 ardour.Append(CXXFLAGS = "-DPACKAGE=\\\"" + domain + "\\\"")
@@ -29,6 +29,8 @@ ardour.Append(CPPPATH = '#libs/surfaces/control_protocol')
 ardour_files=Split("""
 amp.cc
 audio_buffer.cc
+auto_bundle.cc
+user_bundle.cc
 audio_diskstream.cc
 audio_library.cc
 audio_playlist.cc
@@ -45,7 +47,6 @@ automation_control.cc
 automation_event.cc
 buffer.cc
 buffer_set.cc
-bundle.cc
 chan_count.cc
 configuration.cc
 control_protocol_manager.cc
index 34f7eb8c22d083fa1d2ef3f1bb1b49e9131c87ff..503b8166f0789cda21f0ff35f9fdd439101104a2 100644 (file)
@@ -144,7 +144,7 @@ class AudioEngine : public sigc::trackable
 
        /** Caller may not delete the object pointed to by the return value
        */
-       Port *get_port_by_name (const std::string& name, bool keep = true);
+       Port *get_port_by_name (const std::string& name, bool keep = true) const;
 
        enum TransportState {
                TransportStopped = JackTransportStopped,
@@ -199,7 +199,7 @@ class AudioEngine : public sigc::trackable
        ARDOUR::Session *session;
        jack_client_t *_jack;
        std::string jack_client_name;
-       Glib::Mutex _process_lock;
+       mutable Glib::Mutex _process_lock;
        Glib::Cond session_removed;
        bool session_remove_pending;
        bool _running;
diff --git a/libs/ardour/ardour/auto_bundle.h b/libs/ardour/ardour/auto_bundle.h
new file mode 100644 (file)
index 0000000..685a083
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+    Copyright (C) 2007 Paul Davis 
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef __ardour_auto_bundle_h__
+#define __ardour_auto_bundle_h__
+
+#include <vector>
+#include <glibmm/thread.h>
+#include "ardour/bundle.h"
+
+namespace ARDOUR {
+
+class AutoBundle : public Bundle {
+
+  public:
+       AutoBundle (bool i = true);
+       AutoBundle (std::string const &, bool i = true);
+
+       uint32_t nchannels () const;
+       const PortList& channel_ports (uint32_t) const;
+
+       void set_channels (uint32_t);
+       void set_port (uint32_t, std::string const &);
+
+  private:
+       /// mutex for _ports;
+       /// XXX: is this necessary?
+       mutable Glib::Mutex _ports_mutex;
+       std::vector<PortList> _ports;
+};
+
+}      
+       
+#endif /* __ardour_auto_bundle_h__ */
index 9c5f3cb21afc8c0388d101de75af06fef0b831b3..ba92063b309dbbc00a9e656f3f1664d9b0a22353 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2002 Paul Davis 
+    Copyright (C) 2002-2007 Paul Davis 
 
     This program is free software; you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
 #ifndef __ardour_bundle_h__
 #define __ardour_bundle_h__
 
-#include <vector>
 #include <string>
 #include <sigc++/signal.h>
-#include <glibmm/thread.h>
-#include <pbd/stateful.h> 
-
-using std::vector;
-using std::string;
+#include "ardour/data_type.h"
 
 namespace ARDOUR {
 
+typedef std::vector<std::string> PortList;
+  
 /**
- *  A set of `channels', each of which is associated with 0 or more
- *  JACK ports.
+ *  A set of `channels', each of which is associated with 0 or more JACK ports.
  */
 
-class Bundle : public PBD::Stateful, public sigc::trackable {
+class Bundle {
   public:
-       /**
-        *  Bundle constructor.
-        *  @param name Name for this Bundle.
-        *  @param dy true if this Bundle is `dynamic', ie it is created on-the-fly
-        *  and should not be written to the session file.
-        */
-       Bundle (string name, bool dy = false) : _name (name), _dynamic(dy) {}
-       ~Bundle() {}
-
-       /// A vector of JACK port names
-       typedef vector<string> PortList;
-
-       void set_name (string name, void *src);
-
-       /**
-        *  @return name of this Bundle.
-        */
-       string name() const { return _name; }
-
-       /**
-        *  @return true if this Bundle is marked as `dynamic', meaning
-        *  that it won't be written to the session file.
-        */
-       bool dynamic() const { return _dynamic; }
+       Bundle () : _type (DataType::AUDIO) {}
+       Bundle (bool i) : _type (DataType::AUDIO), _ports_are_inputs (i) {}
+       Bundle (std::string const & n, bool i = true) : _name (n), _type (DataType::AUDIO), _ports_are_inputs (i) {}
+       virtual ~Bundle() {}
 
        /**
         *  @return Number of channels that this Bundle has.
         */
-       uint32_t nchannels () const { return _channels.size(); }
-       const PortList& channel_ports (int ch) const;
+       virtual uint32_t nchannels () const = 0;
+       virtual const PortList& channel_ports (uint32_t) const = 0;
 
-       void set_nchannels (int n);
+       void set_name (std::string const & n) {
+               _name = n;
+               NameChanged ();
+       }
+       
+       std::string name () const { return _name; }
 
-       void add_port_to_channel (int ch, string portname);
-       void remove_port_from_channel (int ch, string portname);
+       sigc::signal<void> NameChanged;
 
-       /// Our name changed
-       sigc::signal<void, void*> NameChanged;
-       /// The number of channels changed
-       sigc::signal<void> ConfigurationChanged;
-       /// The ports associated with one of our channels changed
-       sigc::signal<void, int> PortsChanged;
+       void set_type (DataType t) { _type = t; }
+       DataType type () const { return _type; }
 
-       bool operator==(const Bundle& other) const;
-
-       XMLNode& get_state (void);
-       int set_state (const XMLNode&);
-
-  protected:
-       Bundle (const XMLNode&);
+       void set_ports_are_inputs () { _ports_are_inputs = true; }
+       void set_ports_are_outputs () { _ports_are_inputs = false; }
+       bool ports_are_inputs () const { return _ports_are_inputs; }
+       bool ports_are_outputs () const { return !_ports_are_inputs; }
 
   private:
-       mutable Glib::Mutex channels_lock; ///< mutex for _channels
-       vector<PortList> _channels; ///< list of JACK ports associated with each of our channels
-       string _name; ///< name
-       bool _dynamic; ///< true if `dynamic', ie not to be written to the session file
-
-       int set_channels (const string& str);
-       int parse_io_string (const string& str, vector<string>& ports);
-};
-
-/**
- *  Bundle in which the JACK ports are inputs.
- */
-  
-class InputBundle : public Bundle {
-  public:
-       /**
-        *  InputBundle constructor.
-        *  \param name Name.
-        *  \param dy true if this Bundle is `dynamic'; ie it is created on-the-fly
-        *  and should not be written to the session file.
-        */
-       InputBundle (string name, bool dy = false) : Bundle (name, dy) {}
-       InputBundle (const XMLNode&);
-};
-
-/**
- *  Bundle in which the JACK ports are outputs.
- */
-  
-class OutputBundle : public Bundle {
-  public:
-       /**
-        *  OutputBundle constructor.
-        *  \param name Name.
-        *  \param dy true if this Bundle is `dynamic'; ie it is created on-the-fly
-        *  and should not be written to the session file.
-        */  
-        OutputBundle (string name, bool dy = false) : Bundle (name, dy) {}
-       OutputBundle (const XMLNode&);
+       std::string _name;
+       ARDOUR::DataType _type;
+       bool _ports_are_inputs;
 };
 
 }
 
 #endif /* __ardour_bundle_h__ */
-
index 68d9554904fbd95ac85edd89fc6f58e1a7727633..854f52acbae2a3971bd9cd7dddbe997a8d891980 100644 (file)
@@ -21,7 +21,6 @@
 #define __ardour_data_type_h__
 
 #include <string>
-#include <ardour/data_type.h>
 #include <jack/jack.h>
 
 namespace ARDOUR {
index 6e68c01d8c2039463dc92c93ef6f8138ee0bfe8b..888b770250cb72de4de29ac730f6e4199c2b60c7 100644 (file)
@@ -43,6 +43,7 @@
 #include <ardour/chan_count.h>
 #include <ardour/latent.h>
 #include <ardour/automation_control.h>
+#include <ardour/user_bundle.h>
 
 using std::string;
 using std::vector;
@@ -54,6 +55,7 @@ namespace ARDOUR {
 class Session;
 class AudioEngine;
 class Bundle;
+class AutoBundle;
 class Panner;
 class PeakMeter;
 class Port;
@@ -123,9 +125,12 @@ class IO : public Automatable, public Latent
        int connect_input_ports_to_bundle (boost::shared_ptr<Bundle>, void *src);
        int connect_output_ports_to_bundle (boost::shared_ptr<Bundle>, void *src);
 
-       boost::shared_ptr<Bundle> input_bundle();
-       boost::shared_ptr<Bundle> output_bundle();
+       std::vector<boost::shared_ptr<Bundle> > bundles_connected_to_inputs ();
+       std::vector<boost::shared_ptr<Bundle> > bundles_connected_to_outputs ();
 
+        boost::shared_ptr<AutoBundle> bundle_for_inputs () { return _bundle_for_inputs; }
+        boost::shared_ptr<AutoBundle> bundle_for_outputs () { return _bundle_for_outputs; }
+       
        int add_input_port (string source, void *src, DataType type = DataType::NIL);
        int add_output_port (string destination, void *src, DataType type = DataType::NIL);
 
@@ -179,9 +184,6 @@ class IO : public Automatable, public Latent
 
        void attach_buffers(ChanCount ignored);
 
-        boost::shared_ptr<Bundle> bundle_for_inputs () const { return _bundle_for_inputs; }
-        boost::shared_ptr<Bundle> bundle_for_outputs () const { return _bundle_for_outputs; }
-
        sigc::signal<void,IOChange,void*> input_changed;
        sigc::signal<void,IOChange,void*> output_changed;
 
@@ -272,8 +274,6 @@ class IO : public Automatable, public Latent
        PortSet             _outputs;
        PortSet             _inputs;
        PeakMeter*          _meter;
-       boost::shared_ptr<Bundle> _input_bundle; ///< bundle connected to our inputs
-       boost::shared_ptr<Bundle> _output_bundle; ///< bundle connected to our outputs
        bool                 no_panner_reset;
        bool                _phase_invert;
        bool                _denormal_protection;
@@ -310,13 +310,6 @@ class IO : public Automatable, public Latent
 
        friend class Send;
 
-       /* are these the best variable names ever, or what? */
-
-       sigc::connection input_bundle_configuration_connection;
-       sigc::connection output_bundle_configuration_connection;
-       sigc::connection input_bundle_connection_connection;
-       sigc::connection output_bundle_connection_connection;
-
        static bool panners_legal;
        
        int connecting_became_legal ();
@@ -330,8 +323,21 @@ class IO : public Automatable, public Latent
        ChanCount _output_minimum; ///< minimum number of output channels (0 for no minimum)
        ChanCount _output_maximum; ///< maximum number of output channels (ChanCount::INFINITE for no maximum)
 
-       boost::shared_ptr<Bundle> _bundle_for_inputs;
-       boost::shared_ptr<Bundle> _bundle_for_outputs;
+       boost::shared_ptr<AutoBundle> _bundle_for_inputs; ///< a bundle representing our inputs
+       boost::shared_ptr<AutoBundle> _bundle_for_outputs; ///< a bundle representing our outputs
+
+       struct UserBundleInfo {
+               UserBundleInfo (IO*, boost::shared_ptr<UserBundle> b);
+               
+               boost::shared_ptr<UserBundle> bundle;
+               sigc::connection configuration_will_change;
+               sigc::connection configuration_has_changed;
+               sigc::connection ports_will_change;
+               sigc::connection ports_have_changed;
+       };
+       
+       std::vector<UserBundleInfo> _bundles_connected_to_outputs; ///< user bundles connected to our outputs
+       std::vector<UserBundleInfo> _bundles_connected_to_inputs; ///< user bundles connected to our inputs
 
        static int parse_io_string (const string&, vector<string>& chns);
 
@@ -343,13 +349,14 @@ class IO : public Automatable, public Latent
        int ensure_inputs (ChanCount, bool clear, bool lockit, void *src);
        int ensure_outputs (ChanCount, bool clear, bool lockit, void *src);
 
-       void drop_input_bundle ();
-       void drop_output_bundle ();
+       void check_bundles_connected_to_inputs ();
+       void check_bundles_connected_to_outputs ();
+       void check_bundles (std::vector<UserBundleInfo>&, const PortSet&);
 
-       void input_bundle_configuration_changed ();
-       void input_bundle_connection_changed (int);
-       void output_bundle_configuration_changed ();
-       void output_bundle_connection_changed (int);
+       void bundle_configuration_will_change ();
+       void bundle_configuration_has_changed ();
+       void bundle_ports_will_change (int);
+       void bundle_ports_have_changed (int);
 
        int create_ports (const XMLNode&);
        int make_connections (const XMLNode&);
@@ -363,8 +370,11 @@ class IO : public Automatable, public Latent
        int32_t find_input_port_hole ();
        int32_t find_output_port_hole ();
 
-       void create_bundles ();
-       void setup_bundles ();
+       void create_bundles_for_inputs_and_outputs ();
+       void setup_bundles_for_inputs_and_outputs ();
+
+       void maybe_add_input_bundle_to_list (boost::shared_ptr<Bundle>, std::vector<boost::shared_ptr<Bundle> >*);
+       void maybe_add_output_bundle_to_list (boost::shared_ptr<Bundle>, std::vector<boost::shared_ptr<Bundle> >*);
 };
 
 } // namespace ARDOUR
index bbcae6e91d3d0ddeaec99bea8021618d50c63dab..e153914a09fd81d36df52351c7c39f43d2ef14f8 100644 (file)
@@ -489,7 +489,8 @@ class Session : public PBD::StatefulDestructible
        
        void   set_remote_control_ids();
 
-       AudioEngine &engine() { return _engine; };
+       AudioEngine & engine() { return _engine; }
+       AudioEngine const & engine () const { return _engine; }
 
        int32_t  max_level;
        int32_t  min_level;
@@ -716,7 +717,6 @@ class Session : public PBD::StatefulDestructible
        void add_bundle (boost::shared_ptr<Bundle>);
        void remove_bundle (boost::shared_ptr<Bundle>);
        boost::shared_ptr<Bundle> bundle_by_name (string) const;
-       boost::shared_ptr<Bundle> bundle_by_ports (vector<string> const &) const;
 
        sigc::signal<void,boost::shared_ptr<Bundle> > BundleAdded;
        sigc::signal<void,boost::shared_ptr<Bundle> > BundleRemoved;
@@ -1564,7 +1564,8 @@ class Session : public PBD::StatefulDestructible
        typedef list<boost::shared_ptr<Bundle> > BundleList;
        mutable Glib::Mutex bundle_lock;
        BundleList _bundles;
-       int load_bundles (const XMLNode&);
+       XMLNode* _bundle_xml_node;
+       int load_bundles (XMLNode const &);
 
        void reverse_diskstream_buffers ();
 
diff --git a/libs/ardour/ardour/user_bundle.h b/libs/ardour/ardour/user_bundle.h
new file mode 100644 (file)
index 0000000..954e93d
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+    Copyright (C) 2007 Paul Davis 
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef __ardour_user_bundle_h__
+#define __ardour_user_bundle_h__
+
+#include <vector>
+#include <glibmm/thread.h>
+#include "pbd/stateful.h"
+#include "ardour/bundle.h"
+
+namespace ARDOUR {
+
+class Session;
+
+class UserBundle : public Bundle, public PBD::Stateful {
+
+  public:
+       UserBundle (std::string const &);
+       UserBundle (XMLNode const &, bool);
+
+       uint32_t nchannels () const;
+       const ARDOUR::PortList& channel_ports (uint32_t) const;
+
+       void add_channel ();
+       void set_channels (uint32_t);
+       void remove_channel (uint32_t);
+       void add_port_to_channel (uint32_t, std::string const &);
+       void remove_port_from_channel (uint32_t, std::string const &);
+       bool port_attached_to_channel (uint32_t, std::string const &) const;
+       XMLNode& get_state ();
+
+       /// The number of channels is about to change
+       sigc::signal<void> ConfigurationWillChange;
+       /// The number of channels has changed
+       sigc::signal<void> ConfigurationHasChanged;
+       /// The port set associated with one of our channels is about to change
+       /// Parameter is the channel number
+       sigc::signal<void, int> PortsWillChange;
+       /// The port set associated with one of our channels has changed
+       /// Parameter is the channel number
+       sigc::signal<void, int> PortsHaveChanged;
+       
+  private:
+
+       int set_state (const XMLNode &);
+
+       /// mutex for _ports;
+       /// XXX: is this necessary?
+       mutable Glib::Mutex _ports_mutex; 
+       std::vector<PortList> _ports;
+};
+
+}
+       
+#endif
index 4552de11866c01ebc512928a54cea868bb2264db..2e1b790848d90fbe8fbcc250bdc8402af5f65f97 100644 (file)
@@ -763,7 +763,7 @@ AudioEngine::frames_per_cycle ()
  * Note this can return NULL, it will NOT create a port if it is not found (any more).
  */
 Port *
-AudioEngine::get_port_by_name (const string& portname, bool keep)
+AudioEngine::get_port_by_name (const string& portname, bool keep) const
 {
        Glib::Mutex::Lock lm (_process_lock);
 
diff --git a/libs/ardour/auto_bundle.cc b/libs/ardour/auto_bundle.cc
new file mode 100644 (file)
index 0000000..9da32bb
--- /dev/null
@@ -0,0 +1,47 @@
+#include <cassert>
+#include "ardour/auto_bundle.h"
+
+ARDOUR::AutoBundle::AutoBundle (bool i)
+       : Bundle (i)
+{
+
+}
+
+ARDOUR::AutoBundle::AutoBundle (std::string const & n, bool i)
+       : Bundle (n, i)
+{
+       
+}
+
+uint32_t
+ARDOUR::AutoBundle::nchannels () const
+{
+       Glib::Mutex::Lock lm (_ports_mutex);
+       return _ports.size ();
+}
+
+const ARDOUR::PortList&
+ARDOUR::AutoBundle::channel_ports (uint32_t c) const
+{
+       assert (c < nchannels());
+
+       Glib::Mutex::Lock lm (_ports_mutex);
+       return _ports[c];
+}
+
+void
+ARDOUR::AutoBundle::set_channels (uint32_t n)
+{
+       Glib::Mutex::Lock lm (_ports_mutex);
+       _ports.resize (n);
+}
+
+void
+ARDOUR::AutoBundle::set_port (uint32_t c, std::string const & p)
+{
+       assert (c < nchannels ());
+
+       Glib::Mutex::Lock lm (_ports_mutex);
+       _ports[c].resize (1);
+       _ports[c][0] = p;
+}
index 95f40f1f80ea0ec2176c1205c03fc14b8c7254e5..d0a233a3567f29e0a8cf41dec71bfd762186630e 100644 (file)
@@ -34,7 +34,7 @@
 #include <ardour/port.h>
 #include <ardour/audio_port.h>
 #include <ardour/midi_port.h>
-#include <ardour/bundle.h>
+#include <ardour/auto_bundle.h>
 #include <ardour/session.h>
 #include <ardour/cycle_timer.h>
 #include <ardour/panner.h>
@@ -153,7 +153,7 @@ IO::IO (Session& s, const string& name,
 
        _session.add_controllable (_gain_control);
 
-       create_bundles ();
+       create_bundles_for_inputs_and_outputs ();
 }
 
 IO::IO (Session& s, const XMLNode& node, DataType dt)
@@ -193,7 +193,7 @@ IO::IO (Session& s, const XMLNode& node, DataType dt)
 
        _session.add_controllable (_gain_control);
 
-       create_bundles ();
+       create_bundles_for_inputs_and_outputs ();
 }
 
 IO::~IO ()
@@ -334,24 +334,62 @@ IO::just_meter_input (nframes_t start_frame, nframes_t end_frame,
        _meter->run(bufs, start_frame, end_frame, nframes, offset);
 }
 
+
 void
-IO::drop_input_bundle ()
+IO::check_bundles_connected_to_inputs ()
 {
-       _input_bundle.reset ();
-       input_bundle_configuration_connection.disconnect();
-       input_bundle_connection_connection.disconnect();
-       _session.set_dirty ();
+       check_bundles (_bundles_connected_to_inputs, inputs());
 }
 
 void
-IO::drop_output_bundle ()
+IO::check_bundles_connected_to_outputs ()
 {
-       _output_bundle.reset ();
-       output_bundle_configuration_connection.disconnect();
-       output_bundle_connection_connection.disconnect();
-       _session.set_dirty ();
+       check_bundles (_bundles_connected_to_outputs, outputs());
 }
 
+void
+IO::check_bundles (std::vector<UserBundleInfo>& list, const PortSet& ports)
+{
+       std::vector<UserBundleInfo> new_list;
+       
+       for (std::vector<UserBundleInfo>::iterator i = list.begin(); i != list.end(); ++i) {
+
+               uint32_t const N = i->bundle->nchannels ();
+
+               if (ports.num_ports() < N) {
+                       continue;
+               }
+
+               bool ok = true;
+               for (uint32_t j = 0; j < N; ++j) {
+                       /* Every port on bundle channel j must be connected to our input j */
+                       PortList const pl = i->bundle->channel_ports (j);
+                       for (uint32_t k = 0; k < pl.size(); ++k) {
+                               if (ports.port(j)->connected_to (pl[k]) == false) {
+                                       ok = false;
+                                       break;
+                               }
+                       }
+
+                       if (ok == false) {
+                               break;
+                       }
+               }
+
+               if (ok) {
+                       new_list.push_back (*i);
+               } else {
+                       i->configuration_will_change.disconnect ();
+                       i->configuration_has_changed.disconnect ();
+                       i->ports_will_change.disconnect ();
+                       i->ports_have_changed.disconnect ();
+               }
+       }
+
+       list = new_list;
+}
+
+
 int
 IO::disconnect_input (Port* our_port, string other_port, void* src)
 {
@@ -378,7 +416,7 @@ IO::disconnect_input (Port* our_port, string other_port, void* src)
                                return -1;
                        }
 
-                       drop_input_bundle ();
+                       check_bundles_connected_to_inputs ();
                }
        }
 
@@ -412,8 +450,6 @@ IO::connect_input (Port* our_port, string other_port, void* src)
                        if (_session.engine().connect (other_port, our_port->name())) {
                                return -1;
                        }
-                       
-                       drop_input_bundle ();
                }
        }
 
@@ -448,7 +484,7 @@ IO::disconnect_output (Port* our_port, string other_port, void* src)
                                return -1;
                        }
 
-                       drop_output_bundle ();
+                       check_bundles_connected_to_outputs ();
                }
        }
 
@@ -482,8 +518,6 @@ IO::connect_output (Port* our_port, string other_port, void* src)
                        if (_session.engine().connect (our_port->name(), other_port)) {
                                return -1;
                        }
-
-                       drop_output_bundle ();
                }
        }
 
@@ -544,7 +578,7 @@ IO::remove_output_port (Port* port, void* src)
                                } 
 
                                _session.engine().unregister_port (*port);
-                               drop_output_bundle ();
+                               check_bundles_connected_to_outputs ();
                                
                                setup_peak_meters ();
                                reset_panner ();
@@ -555,7 +589,7 @@ IO::remove_output_port (Port* port, void* src)
        }
 
        if (change == ConnectionsChanged) {
-               setup_bundles ();
+               setup_bundles_for_inputs_and_outputs ();
        }
 
        if (change != NoChange) {
@@ -608,7 +642,6 @@ IO::add_output_port (string destination, void* src, DataType type)
                        }
                        
                        _outputs.add (our_port);
-                       drop_output_bundle ();
                        setup_peak_meters ();
                        reset_panner ();
                }
@@ -624,7 +657,7 @@ IO::add_output_port (string destination, void* src, DataType type)
        
        // pan_changed (src); /* EMIT SIGNAL */
        output_changed (ConfigurationChanged, src); /* EMIT SIGNAL */
-       setup_bundles ();
+       setup_bundles_for_inputs_and_outputs ();
        _session.set_dirty ();
 
        return 0;
@@ -655,7 +688,7 @@ IO::remove_input_port (Port* port, void* src)
                                } 
 
                                _session.engine().unregister_port (*port);
-                               drop_input_bundle ();
+                               check_bundles_connected_to_inputs ();
                                
                                setup_peak_meters ();
                                reset_panner ();
@@ -666,7 +699,7 @@ IO::remove_input_port (Port* port, void* src)
        }
 
        if (change == ConfigurationChanged) {
-               setup_bundles ();
+               setup_bundles_for_inputs_and_outputs ();
        }
 
        if (change != NoChange) {
@@ -719,7 +752,6 @@ IO::add_input_port (string source, void* src, DataType type)
                        }
 
                        _inputs.add (our_port);
-                       drop_input_bundle ();
                        setup_peak_meters ();
                        reset_panner ();
                }
@@ -736,7 +768,7 @@ IO::add_input_port (string source, void* src, DataType type)
 
        // pan_changed (src); /* EMIT SIGNAL */
        input_changed (ConfigurationChanged, src); /* EMIT SIGNAL */
-       setup_bundles ();
+       setup_bundles_for_inputs_and_outputs ();
        _session.set_dirty ();
        
        return 0;
@@ -755,7 +787,7 @@ IO::disconnect_inputs (void* src)
                                _session.engine().disconnect (*i);
                        }
 
-                       drop_input_bundle ();
+                       check_bundles_connected_to_inputs ();
                }
        }
        
@@ -777,7 +809,7 @@ IO::disconnect_outputs (void* src)
                                _session.engine().disconnect (*i);
                        }
 
-                       drop_output_bundle ();
+                       check_bundles_connected_to_outputs ();
                }
        }
 
@@ -841,7 +873,7 @@ IO::ensure_inputs_locked (ChanCount count, bool clear, void* src)
        }
        
        if (changed) {
-               drop_input_bundle ();
+               check_bundles_connected_to_inputs ();
                setup_peak_meters ();
                reset_panner ();
                PortCountChanged (n_inputs()); /* EMIT SIGNAL */
@@ -1008,18 +1040,18 @@ IO::ensure_io (ChanCount in, ChanCount out, bool clear, void* src)
        }
 
        if (out_changed) {
-               drop_output_bundle ();
+               check_bundles_connected_to_outputs ();
                output_changed (ConfigurationChanged, src); /* EMIT SIGNAL */
        }
        
        if (in_changed) {
-               drop_input_bundle ();
+               check_bundles_connected_to_inputs ();
                input_changed (ConfigurationChanged, src); /* EMIT SIGNAL */
        }
 
        if (in_changed || out_changed) {
                PortCountChanged (max (n_outputs(), n_inputs())); /* EMIT SIGNAL */
-               setup_bundles ();
+               setup_bundles_for_inputs_and_outputs ();
                _session.set_dirty ();
        }
 
@@ -1047,7 +1079,7 @@ IO::ensure_inputs (ChanCount count, bool clear, bool lockit, void* src)
 
        if (changed) {
                input_changed (ConfigurationChanged, src); /* EMIT SIGNAL */
-               setup_bundles ();
+               setup_bundles_for_inputs_and_outputs ();
                _session.set_dirty ();
        }
        return 0;
@@ -1106,7 +1138,7 @@ IO::ensure_outputs_locked (ChanCount count, bool clear, void* src)
        }
        
        if (changed) {
-               drop_output_bundle ();
+               check_bundles_connected_to_outputs ();
                PortCountChanged (n_outputs()); /* EMIT SIGNAL */
                _session.set_dirty ();
        }
@@ -1145,7 +1177,7 @@ IO::ensure_outputs (ChanCount count, bool clear, bool lockit, void* src)
 
        if (changed) {
                 output_changed (ConfigurationChanged, src); /* EMIT SIGNAL */
-                setup_bundles ();
+                setup_bundles_for_inputs_and_outputs ();
        }
 
        return 0;
@@ -1209,8 +1241,6 @@ IO::state (bool full_state)
        XMLNode* node = new XMLNode (state_node_name);
        char buf[64];
        string str;
-       bool need_ins = true;
-       bool need_outs = true;
        LocaleGuard lg (X_("POSIX"));
        Glib::Mutex::Lock lm (io_lock);
 
@@ -1218,83 +1248,91 @@ IO::state (bool full_state)
        id().print (buf, sizeof (buf));
        node->add_property("id", buf);
 
-       str = "";
-
-       if (_input_bundle && !_input_bundle->dynamic()) {
-               node->add_property ("input-connection", _input_bundle->name());
-               need_ins = false;
+       for (
+         std::vector<UserBundleInfo>::iterator i = _bundles_connected_to_inputs.begin();
+         i != _bundles_connected_to_inputs.end();
+         ++i
+         )
+       {
+               XMLNode* n = new XMLNode ("InputBundle");
+               n->add_property ("name", i->bundle->name ());
+               node->add_child_nocopy (*n);
        }
 
-       if (_output_bundle && !_output_bundle->dynamic()) {
-               node->add_property ("output-connection", _output_bundle->name());
-               need_outs = false;
+       for (
+         std::vector<UserBundleInfo>::iterator i = _bundles_connected_to_outputs.begin();
+         i != _bundles_connected_to_outputs.end();
+         ++i
+         )
+       {
+               XMLNode* n = new XMLNode ("OutputBundle");
+               n->add_property ("name", i->bundle->name ());
+               node->add_child_nocopy (*n);
        }
+       
+       str = "";
 
-       if (need_ins) {
-               for (PortSet::iterator i = _inputs.begin(); i != _inputs.end(); ++i) {
+       for (PortSet::iterator i = _inputs.begin(); i != _inputs.end(); ++i) {
                        
-                       const char **connections = i->get_connections();
+               const char **connections = i->get_connections();
+               
+               if (connections && connections[0]) {
+                       str += '{';
                        
-                       if (connections && connections[0]) {
-                               str += '{';
+                       for (int n = 0; connections && connections[n]; ++n) {
+                               if (n) {
+                                       str += ',';
+                               }
                                
-                               for (int n = 0; connections && connections[n]; ++n) {
-                                       if (n) {
-                                               str += ',';
-                                       }
-                                       
-                                       /* if its a connection to our own port,
-                                          return only the port name, not the
-                                          whole thing. this allows connections
-                                          to be re-established even when our
-                                          client name is different.
-                                       */
-                                       
-                                       str += _session.engine().make_port_name_relative (connections[n]);
-                               }       
-
-                               str += '}';
+                               /* if its a connection to our own port,
+                                  return only the port name, not the
+                                  whole thing. this allows connections
+                                  to be re-established even when our
+                                  client name is different.
+                               */
                                
-                               free (connections);
-                       }
-                       else {
-                               str += "{}";
-                       }
+                               str += _session.engine().make_port_name_relative (connections[n]);
+                       }       
+                       
+                       str += '}';
+                       
+                       free (connections);
+               }
+               else {
+                       str += "{}";
                }
-               
-               node->add_property ("inputs", str);
        }
+       
+       node->add_property ("inputs", str);
 
-       if (need_outs) {
-               str = "";
+       str = "";
+       
+       for (PortSet::iterator i = _outputs.begin(); i != _outputs.end(); ++i) {
                
-               for (PortSet::iterator i = _outputs.begin(); i != _outputs.end(); ++i) {
+               const char **connections = i->get_connections();
+               
+               if (connections && connections[0]) {
                        
-                       const char **connections = i->get_connections();
+                       str += '{';
                        
-                       if (connections && connections[0]) {
-                               
-                               str += '{';
-                               
-                               for (int n = 0; connections[n]; ++n) {
-                                       if (n) {
-                                               str += ',';
-                                       }
-
-                                       str += _session.engine().make_port_name_relative (connections[n]);
+                       for (int n = 0; connections[n]; ++n) {
+                               if (n) {
+                                       str += ',';
                                }
-
-                               str += '}';
                                
-                               free (connections);
-                       }
-                       else {
-                               str += "{}";
+                               str += _session.engine().make_port_name_relative (connections[n]);
                        }
+                       
+                       str += '}';
+                       
+                       free (connections);
+               }
+               else {
+                       str += "{}";
                }
-               
-               node->add_property ("outputs", str);
        }
+       
+       node->add_property ("outputs", str);
 
        node->add_child_nocopy (_panner->state (full_state));
        node->add_child_nocopy (_gain_control->get_state ());
@@ -1575,55 +1613,12 @@ IO::ports_became_legal ()
 int
 IO::create_ports (const XMLNode& node)
 {
-       const XMLProperty* prop;
+       XMLProperty const * prop;
        int num_inputs = 0;
        int num_outputs = 0;
 
-       /* XXX: we could change *-connection to *-bundle, but it seems a bit silly to
-        * break the session file format.
-        */
-       if ((prop = node.property ("input-connection")) != 0) {
-
-               boost::shared_ptr<Bundle> c = _session.bundle_by_name (prop->value());
-               
-               if (c == 0) {
-                       error << string_compose(_("Unknown bundle \"%1\" listed for input of %2"), prop->value(), _name) << endmsg;
-
-                       if ((c = _session.bundle_by_name (_("in 1"))) == 0) {
-                               error << _("No input bundles available as a replacement")
-                                     << endmsg;
-                               return -1;
-                       }  else {
-                               info << string_compose (_("Bundle %1 was not available - \"in 1\" used instead"), prop->value())
-                                    << endmsg;
-                       }
-               } 
-
-               num_inputs = c->nchannels();
-
-       } else if ((prop = node.property ("inputs")) != 0) {
-
+       if ((prop = node.property ("inputs")) != 0) {
                num_inputs = count (prop->value().begin(), prop->value().end(), '{');
-       }
-       
-       if ((prop = node.property ("output-connection")) != 0) {
-               boost::shared_ptr<Bundle> c = _session.bundle_by_name (prop->value());
-
-               if (c == 0) {
-                       error << string_compose(_("Unknown bundle \"%1\" listed for output of %2"), prop->value(), _name) << endmsg;
-
-                       if ((c = _session.bundle_by_name (_("out 1"))) == 0) {
-                               error << _("No output bundles available as a replacement")
-                                     << endmsg;
-                               return -1;
-                       }  else {
-                               info << string_compose (_("Bundle %1 was not available - \"out 1\" used instead"), prop->value())
-                                    << endmsg;
-                       }
-               } 
-
-               num_outputs = c->nchannels ();
-               
        } else if ((prop = node.property ("outputs")) != 0) {
                num_outputs = count (prop->value().begin(), prop->value().end(), '{');
        }
@@ -1648,57 +1643,48 @@ IO::create_ports (const XMLNode& node)
 int
 IO::make_connections (const XMLNode& node)
 {
-       const XMLProperty* prop;
-
-       if ((prop = node.property ("input-connection")) != 0) {
-               boost::shared_ptr<Bundle> c = _session.bundle_by_name (prop->value());
-               
-               if (c == 0) {
-                       error << string_compose(_("Unknown connection \"%1\" listed for input of %2"), prop->value(), _name) << endmsg;
-
-                       if ((c = _session.bundle_by_name (_("in 1"))) == 0) {
-                               error << _("No input connections available as a replacement")
-                                     << endmsg;
-                               return -1;
-                       } else {
-                               info << string_compose (_("Bundle %1 was not available - \"in 1\" used instead"), prop->value())
-                                    << endmsg;
-                       }
-               } 
-
-               connect_input_ports_to_bundle (c, this);
-
-       } else if ((prop = node.property ("inputs")) != 0) {
+       XMLProperty const * prop;
+       
+       if ((prop = node.property ("inputs")) != 0) {
                if (set_inputs (prop->value())) {
                        error << string_compose(_("improper input channel list in XML node (%1)"), prop->value()) << endmsg;
                        return -1;
                }
        }
-       
-       if ((prop = node.property ("output-bundle")) != 0) {
-               boost::shared_ptr<Bundle> c = _session.bundle_by_name (prop->value());
-               
-               if (c == 0) {
-                       error << string_compose(_("Unknown bundle \"%1\" listed for output of %2"), prop->value(), _name) << endmsg;
 
-                       if ((c = _session.bundle_by_name (_("out 1"))) == 0) {
-                               error << _("No output bundles available as a replacement")
-                                     << endmsg;
-                               return -1;
-                       }  else {
-                               info << string_compose (_("Bundle %1 was not available - \"out 1\" used instead"), prop->value())
-                                    << endmsg;
-                       }
-               } 
-
-               connect_output_ports_to_bundle (c, this);
-               
-       } else if ((prop = node.property ("outputs")) != 0) {
+                               
+       if ((prop = node.property ("outputs")) != 0) {
                if (set_outputs (prop->value())) {
                        error << string_compose(_("improper output channel list in XML node (%1)"), prop->value()) << endmsg;
                        return -1;
                }
        }
+
+       for (XMLNodeConstIterator i = node.children().begin(); i != node.children().end(); ++i) {
+
+               if ((*i)->name() == "InputBundle") {
+                       XMLProperty const * prop = (*i)->property ("name");
+                       if (prop) {
+                               boost::shared_ptr<Bundle> b = _session.bundle_by_name (prop->value());
+                               if (b) {
+                                       connect_input_ports_to_bundle (b, this);
+                               } else {
+                                       error << string_compose(_("Unknown bundle \"%1\" listed for input of %2"), prop->value(), _name) << endmsg;
+                               }
+                       }
+                       
+               } else if ((*i)->name() == "OutputBundle") {
+                       XMLProperty const * prop = (*i)->property ("name");
+                       if (prop) {
+                               boost::shared_ptr<Bundle> b = _session.bundle_by_name (prop->value());
+                               if (b) {
+                                       connect_output_ports_to_bundle (b, this);
+                               } else {
+                                       error << string_compose(_("Unknown bundle \"%1\" listed for output of %2"), prop->value(), _name) << endmsg;
+                               }
+                       }
+               }
+       }
        
        return 0;
 }
@@ -1880,7 +1866,7 @@ IO::set_name (const string& str)
 
        bool const r = SessionObject::set_name(name);
 
-       setup_bundles ();
+       setup_bundles_for_inputs_and_outputs ();
 
        return r;
 }
@@ -1960,61 +1946,21 @@ IO::input_latency () const
 int
 IO::connect_input_ports_to_bundle (boost::shared_ptr<Bundle> c, void* src)
 {
-       uint32_t limit;
-
        {
                BLOCK_PROCESS_CALLBACK ();
                Glib::Mutex::Lock lm2 (io_lock);
-               
-               limit = c->nchannels();
-               
-               drop_input_bundle ();
-               
-               // FIXME bundles only work for audio-only
-               if (ensure_inputs (ChanCount(DataType::AUDIO, limit), false, false, src)) {
-                       return -1;
-               }
 
-               /* first pass: check the current state to see what's correctly
-                  connected, and drop anything that we don't want.
-               */
-               
-               for (uint32_t n = 0; n < limit; ++n) {
-                       const Bundle::PortList& pl = c->channel_ports (n);
-                       
-                       for (Bundle::PortList::const_iterator i = pl.begin(); i != pl.end(); ++i) {
-                               
-                               if (!_inputs.port(n)->connected_to ((*i))) {
-                                       
-                                       /* clear any existing connections */
-                                       
-                                       _session.engine().disconnect (*_inputs.port(n));
-                                       
-                               } else if (_inputs.port(n)->connected() > 1) {
-                                       
-                                       /* OK, it is connected to the port we want,
-                                          but its also connected to other ports.
-                                          Change that situation.
-                                       */
-                                       
-                                       /* XXX could be optimized to not drop
-                                          the one we want.
-                                       */
-                                       
-                                       _session.engine().disconnect (*_inputs.port(n));
-                                       
-                               }
-                       }
-               }
-               
-               /* second pass: connect all requested ports where necessary */
+               /* Connect to the bundle, not worrying about any connections
+                  that are already made. */
+
+               uint32_t const channels = c->nchannels ();
                
-               for (uint32_t n = 0; n < limit; ++n) {
-                       const Bundle::PortList& pl = c->channel_ports (n);
-                       
-                       for (Bundle::PortList::const_iterator i = pl.begin(); i != pl.end(); ++i) {
-                               
-                               if (!_inputs.port(n)->connected_to ((*i))) {
+               for (uint32_t n = 0; n < channels; ++n) {
+                       const PortList& pl = c->channel_ports (n);
+
+                       for (PortList::const_iterator i = pl.begin(); i != pl.end(); ++i) {
+
+                         if (!_inputs.port(n)->connected_to (*i)) {
                                        
                                        if (_session.engine().connect (*i, _inputs.port(n)->name())) {
                                                return -1;
@@ -2023,13 +1969,23 @@ IO::connect_input_ports_to_bundle (boost::shared_ptr<Bundle> c, void* src)
                                
                        }
                }
-               
-               _input_bundle = c;
-               
-               input_bundle_configuration_connection = c->ConfigurationChanged.connect
-                       (mem_fun (*this, &IO::input_bundle_configuration_changed));
-               input_bundle_connection_connection = c->PortsChanged.connect
-                       (mem_fun (*this, &IO::input_bundle_connection_changed));
+
+               /* If this is a UserBundle, make a note of what we've done */
+
+               boost::shared_ptr<UserBundle> ub = boost::dynamic_pointer_cast<UserBundle> (c);
+               if (ub) {
+
+                       /* See if we already know about this one */
+                       std::vector<UserBundleInfo>::iterator i = _bundles_connected_to_inputs.begin();
+                       while (i != _bundles_connected_to_inputs.end() && i->bundle != ub) {
+                               ++i;
+                       }
+
+                       if (i == _bundles_connected_to_inputs.end()) {
+                               /* We don't, so make a note */
+                               _bundles_connected_to_inputs.push_back (UserBundleInfo (this, ub));
+                       }
+               }
        }
 
        input_changed (IOChange (ConfigurationChanged|ConnectionsChanged), src); /* EMIT SIGNAL */
@@ -2039,62 +1995,22 @@ IO::connect_input_ports_to_bundle (boost::shared_ptr<Bundle> c, void* src)
 int
 IO::connect_output_ports_to_bundle (boost::shared_ptr<Bundle> c, void* src)
 {
-       uint32_t limit; 
-
        {
                BLOCK_PROCESS_CALLBACK ();
                Glib::Mutex::Lock lm2 (io_lock);
 
-               limit = c->nchannels();
-                       
-               drop_output_bundle ();
-
-               // FIXME: audio-only
-               if (ensure_outputs (ChanCount(DataType::AUDIO, limit), false, false, src)) {
-                       return -1;
-               }
-
-               /* first pass: check the current state to see what's correctly
-                  connected, and drop anything that we don't want.
-               */
-                       
-               for (uint32_t n = 0; n < limit; ++n) {
-
-                       const Bundle::PortList& pl = c->channel_ports (n);
-                               
-                       for (Bundle::PortList::const_iterator i = pl.begin(); i != pl.end(); ++i) {
-                                       
-                               if (!_outputs.port(n)->connected_to ((*i))) {
-
-                                       /* clear any existing connections */
-
-                                       _session.engine().disconnect (*_outputs.port(n));
+               /* Connect to the bundle, not worrying about any connections
+                  that are already made. */
 
-                               } else if (_outputs.port(n)->connected() > 1) {
+               uint32_t const channels = c->nchannels ();
 
-                                       /* OK, it is connected to the port we want,
-                                          but its also connected to other ports.
-                                          Change that situation.
-                                       */
+               for (uint32_t n = 0; n < channels; ++n) {
 
-                                       /* XXX could be optimized to not drop
-                                          the one we want.
-                                       */
-                                               
-                                       _session.engine().disconnect (*_outputs.port(n));
-                               }
-                       }
-               }
+                       const PortList& pl = c->channel_ports (n);
 
-               /* second pass: connect all requested ports where necessary */
+                       for (PortList::const_iterator i = pl.begin(); i != pl.end(); ++i) {
 
-               for (uint32_t n = 0; n < limit; ++n) {
-
-                       const Bundle::PortList& pl = c->channel_ports (n);
-                               
-                       for (Bundle::PortList::const_iterator i = pl.begin(); i != pl.end(); ++i) {
-                                       
-                               if (!_outputs.port(n)->connected_to ((*i))) {
+                               if (!_outputs.port(n)->connected_to (*i)) {
                                                
                                        if (_session.engine().connect (_outputs.port(n)->name(), *i)) {
                                                return -1;
@@ -2103,12 +2019,22 @@ IO::connect_output_ports_to_bundle (boost::shared_ptr<Bundle> c, void* src)
                        }
                }
 
-               _output_bundle = c;
+               /* If this is a UserBundle, make a note of what we've done */
 
-               output_bundle_configuration_connection = c->ConfigurationChanged.connect
-                       (mem_fun (*this, &IO::output_bundle_configuration_changed));
-               output_bundle_connection_connection = c->PortsChanged.connect
-                       (mem_fun (*this, &IO::output_bundle_connection_changed));
+               boost::shared_ptr<UserBundle> ub = boost::dynamic_pointer_cast<UserBundle> (c);
+               if (ub) {
+
+                       /* See if we already know about this one */
+                       std::vector<UserBundleInfo>::iterator i = _bundles_connected_to_outputs.begin();
+                       while (i != _bundles_connected_to_outputs.end() && i->bundle != ub) {
+                               ++i;
+                       }
+
+                       if (i == _bundles_connected_to_outputs.end()) {
+                               /* We don't, so make a note */
+                               _bundles_connected_to_outputs.push_back (UserBundleInfo (this, ub));
+                       }
+               }
        }
 
        output_changed (IOChange (ConnectionsChanged|ConfigurationChanged), src); /* EMIT SIGNAL */
@@ -2159,27 +2085,31 @@ IO::reset_panners ()
 }
 
 void
-IO::input_bundle_connection_changed (int ignored)
+IO::bundle_configuration_will_change ()
 {
-       connect_input_ports_to_bundle (_input_bundle, this);
+       //XXX
+//     connect_input_ports_to_bundle (_input_bundle, this);
 }
 
 void
-IO::input_bundle_configuration_changed ()
+IO::bundle_configuration_has_changed ()
 {
-       connect_input_ports_to_bundle (_input_bundle, this);
+       //XXX
+//     connect_input_ports_to_bundle (_input_bundle, this);
 }
 
 void
-IO::output_bundle_connection_changed (int ignored)
+IO::bundle_ports_will_change (int ignored)
 {
-       connect_output_ports_to_bundle (_output_bundle, this);
+//XXX
+//     connect_output_ports_to_bundle (_output_bundle, this);
 }
 
 void
-IO::output_bundle_configuration_changed ()
+IO::bundle_ports_have_changed (int ignored)
 {
-       connect_output_ports_to_bundle (_output_bundle, this);
+       //XXX
+//     connect_output_ports_to_bundle (_output_bundle, this);
 }
 
 void
@@ -2483,27 +2413,25 @@ IO::update_port_total_latencies ()
  */
 
 void
-IO::setup_bundles ()
+IO::setup_bundles_for_inputs_and_outputs ()
 {
         char buf[32];
 
         snprintf(buf, sizeof (buf), _("%s in"), _name.c_str());
-        _bundle_for_inputs->set_name (buf, 0);
-        int const ins = n_inputs().n_total();
-        _bundle_for_inputs->set_nchannels (ins);
-        
-        for (int i = 0; i < ins; ++i) {
-                _bundle_for_inputs->add_port_to_channel (i, _inputs.port(i)->name ());
-        }
+        _bundle_for_inputs->set_name (buf);
+       uint32_t const ni = inputs().num_ports();
+       _bundle_for_inputs->set_channels (ni);
+       for (uint32_t i = 0; i < ni; ++i) {
+               _bundle_for_inputs->set_port (i, inputs().port(i)->name());
+       }
 
         snprintf(buf, sizeof (buf), _("%s out"), _name.c_str());
-        _bundle_for_outputs->set_name (buf, 0);
-        int const outs = n_outputs().n_total();
-        _bundle_for_outputs->set_nchannels (outs);
-        
-        for (int i = 0; i < outs; ++i) {
-                _bundle_for_outputs->add_port_to_channel (i, _outputs.port(i)->name ());
-        }
+        _bundle_for_outputs->set_name (buf);
+       uint32_t const no = outputs().num_ports();
+       _bundle_for_outputs->set_channels (no);
+       for (uint32_t i = 0; i < no; ++i) {
+               _bundle_for_outputs->set_port (i, outputs().port(i)->name());
+       }
 }
 
 
@@ -2512,64 +2440,131 @@ IO::setup_bundles ()
  */
 
 void
-IO::create_bundles ()
+IO::create_bundles_for_inputs_and_outputs ()
 {
-        _bundle_for_inputs = boost::shared_ptr<Bundle> (
-                new InputBundle ("", true)
-                );
-        
-        _bundle_for_outputs = boost::shared_ptr<Bundle> (
-                new OutputBundle ("", true)
-                );
-
-        setup_bundles ();
+       _bundle_for_inputs = boost::shared_ptr<AutoBundle> (new AutoBundle (true));
+        _bundle_for_outputs = boost::shared_ptr<AutoBundle> (new AutoBundle (false));
+        setup_bundles_for_inputs_and_outputs ();
 }
 
-boost::shared_ptr<Bundle>
-IO::input_bundle()
+/** Add a bundle to a list if is connected to our inputs.
+ *  @param b Bundle to check.
+ *  @param bundles List to add to.
+ */
+void
+IO::maybe_add_input_bundle_to_list (boost::shared_ptr<Bundle> b, std::vector<boost::shared_ptr<Bundle> >* bundles)
 {
-       if (_input_bundle) {
-               return _input_bundle;
+       boost::shared_ptr<AutoBundle> ab = boost::dynamic_pointer_cast<AutoBundle, Bundle> (b);
+       if (ab == 0 || ab->ports_are_outputs() == false) {
+               return;
+       }
+
+       if (ab->nchannels () != n_inputs().n_total ()) {
+               return;
        }
 
-       /* XXX: will only report the first bundle found; should really return a list, I think */
-          
-       /* check that _input_bundle is right wrt the connections that are currently made */
+       for (uint32_t i = 0; i < n_inputs().n_total (); ++i) {
 
-       /* make a vector of the first output connected to each of our inputs */
-       std::vector<std::string> connected;
-        for (uint32_t i = 0; i < _inputs.num_ports(); ++i) {
-               const char** c = _inputs.port(i)->get_connections ();
-               if (c) {
-                       connected.push_back (c[0]);
+               PortList const & pl = b->channel_ports (i);
+
+               if (pl.empty()) {
+                       return;
+               }
+
+               if (!input(i)->connected_to (pl[0])) {
+                       return;
                }
        }
 
-       _input_bundle = _session.bundle_by_ports (connected);
-       return _input_bundle;
+       bundles->push_back (b);
+}
+
+/** @return Bundles connected to our inputs */
+std::vector<boost::shared_ptr<Bundle> >
+IO::bundles_connected_to_inputs ()
+{
+       std::vector<boost::shared_ptr<Bundle> > bundles;
+       
+       /* User bundles */
+       for (std::vector<UserBundleInfo>::iterator i = _bundles_connected_to_inputs.begin(); i != _bundles_connected_to_inputs.end(); ++i) {
+               bundles.push_back (i->bundle);
+       }
+
+       /* Auto bundles */
+       _session.foreach_bundle (
+               sigc::bind (sigc::mem_fun (*this, &IO::maybe_add_input_bundle_to_list), &bundles)
+               );
+
+       return bundles;
 }
 
 
-boost::shared_ptr<Bundle>
-IO::output_bundle()
+/** Add a bundle to a list if is connected to our outputs.
+ *  @param b Bundle to check.
+ *  @param bundles List to add to.
+ */
+void
+IO::maybe_add_output_bundle_to_list (boost::shared_ptr<Bundle> b, std::vector<boost::shared_ptr<Bundle> >* bundles)
 {
-       if (_output_bundle) {
-               return _output_bundle;
+       boost::shared_ptr<AutoBundle> ab = boost::dynamic_pointer_cast<AutoBundle, Bundle> (b);
+       if (ab == 0 || ab->ports_are_inputs() == false) {
+               return;
+       }
+
+       if (ab->nchannels () != n_outputs().n_total ()) {
+               return;
        }
-       
-       /* XXX: will only report the first bundle found; should really return a list, I think */
-          
-       /* check that _output_bundle is right wrt the connections that are currently made */
 
-       /* make a vector of the first input connected to each of our outputs */
-       std::vector<std::string> connected;
-        for (uint32_t i = 0; i < _outputs.num_ports(); ++i) {
-               const char** c = _outputs.port(i)->get_connections ();
-               if (c) {
-                       connected.push_back (c[0]);
+       for (uint32_t i = 0; i < n_outputs().n_total (); ++i) {
+
+               PortList const & pl = b->channel_ports (i);
+
+               if (pl.empty()) {
+                       return;
+               }
+
+               if (!output(i)->connected_to (pl[0])) {
+                       return;
                }
        }
 
-       _output_bundle = _session.bundle_by_ports (connected);
-       return _output_bundle;
+       bundles->push_back (b);
+}
+
+
+/* @return Bundles connected to our outputs */
+std::vector<boost::shared_ptr<Bundle> >
+IO::bundles_connected_to_outputs ()
+{
+       std::vector<boost::shared_ptr<Bundle> > bundles;
+
+       /* User bundles */
+       for (std::vector<UserBundleInfo>::iterator i = _bundles_connected_to_outputs.begin(); i != _bundles_connected_to_outputs.end(); ++i) {
+               bundles.push_back (i->bundle);
+       }
+
+       /* Auto bundles */
+       _session.foreach_bundle (
+               sigc::bind (sigc::mem_fun (*this, &IO::maybe_add_output_bundle_to_list), &bundles)
+               );
+
+       return bundles; 
+}
+
+
+IO::UserBundleInfo::UserBundleInfo (IO* io, boost::shared_ptr<UserBundle> b)
+{
+       bundle = b;
+       configuration_will_change = b->ConfigurationWillChange.connect (
+               sigc::mem_fun (*io, &IO::bundle_configuration_will_change)
+               );
+       configuration_has_changed = b->ConfigurationHasChanged.connect (
+               sigc::mem_fun (*io, &IO::bundle_configuration_has_changed)
+               );
+       ports_will_change = b->PortsWillChange.connect (
+               sigc::mem_fun (*io, &IO::bundle_ports_will_change)
+               );
+       ports_have_changed = b->PortsHaveChanged.connect (
+               sigc::mem_fun (*io, &IO::bundle_ports_have_changed)
+               );
 }
index 1382fa3f0aefe748281396120a4a98b6d981ea45..e155800d23e044b7b9ff093f9c09095ed9a3efd4 100644 (file)
@@ -62,7 +62,7 @@
 #include <ardour/processor.h>
 #include <ardour/plugin_insert.h>
 #include <ardour/port_insert.h>
-#include <ardour/bundle.h>
+#include <ardour/auto_bundle.h>
 #include <ardour/slave.h>
 #include <ardour/tempo.h>
 #include <ardour/audio_track.h>
@@ -123,6 +123,7 @@ Session::Session (AudioEngine &eng,
          diskstreams (new DiskstreamList),
          routes (new RouteList),
          auditioner ((Auditioner*) 0),
+         _bundle_xml_node (0),
          _click_io ((IO*) 0),
          main_outs (0)
 {
@@ -223,6 +224,7 @@ Session::Session (AudioEngine &eng,
          _send_smpte_update (false),
          diskstreams (new DiskstreamList),
          routes (new RouteList),
+         _bundle_xml_node (0),
          main_outs (0)
 
 {
@@ -599,22 +601,22 @@ Session::when_engine_running ()
                char buf[32];
                snprintf (buf, sizeof (buf), _("out %" PRIu32), np+1);
 
-               shared_ptr<Bundle> c (new InputBundle (buf, true));
-               c->set_nchannels (1);
-               c->add_port_to_channel (0, _engine.get_nth_physical_output (DataType::AUDIO, np));
+               shared_ptr<AutoBundle> c (new AutoBundle (buf, true));
+               c->set_channels (1);
+               c->set_port (0, _engine.get_nth_physical_output (DataType::AUDIO, np));
 
-               add_bundle (c);
+               add_bundle (c);
        }
 
        for (uint32_t np = 0; np < n_physical_inputs; ++np) {
                char buf[32];
                snprintf (buf, sizeof (buf), _("in %" PRIu32), np+1);
 
-               shared_ptr<Bundle> c (new OutputBundle (buf, true));
-               c->set_nchannels (1);
-               c->add_port_to_channel (0, _engine.get_nth_physical_input (DataType::AUDIO, np));
+               shared_ptr<AutoBundle> c (new AutoBundle (buf, false));
+               c->set_channels (1);
+               c->set_port (0, _engine.get_nth_physical_input (DataType::AUDIO, np));
 
-               add_bundle (c);
+               add_bundle (c);
        }
 
        /* TWO: STEREO */
@@ -623,24 +625,24 @@ Session::when_engine_running ()
                char buf[32];
                snprintf (buf, sizeof (buf), _("out %" PRIu32 "+%" PRIu32), np+1, np+2);
 
-               shared_ptr<Bundle> c (new InputBundle (buf, true));
-               c->set_nchannels (2);
-               c->add_port_to_channel (0, _engine.get_nth_physical_output (DataType::AUDIO, np));
-               c->add_port_to_channel (1, _engine.get_nth_physical_output (DataType::AUDIO, np+1));
+               shared_ptr<AutoBundle> c (new AutoBundle (buf, true));
+               c->set_channels (2);
+               c->set_port (0, _engine.get_nth_physical_output (DataType::AUDIO, np));
+               c->set_port (1, _engine.get_nth_physical_output (DataType::AUDIO, np + 1));
 
-               add_bundle (c);
+               add_bundle (c);
        }
 
        for (uint32_t np = 0; np < n_physical_inputs; np +=2) {
                char buf[32];
                snprintf (buf, sizeof (buf), _("in %" PRIu32 "+%" PRIu32), np+1, np+2);
 
-               shared_ptr<Bundle> c (new OutputBundle (buf, true));
-               c->set_nchannels (2);
-               c->add_port_to_channel (0, _engine.get_nth_physical_input (DataType::AUDIO, np));
-               c->add_port_to_channel (1, _engine.get_nth_physical_input (DataType::AUDIO, np+1));
+               shared_ptr<AutoBundle> c (new AutoBundle (buf, false));
+               c->set_channels (2);
+               c->set_port (0, _engine.get_nth_physical_input (DataType::AUDIO, np));
+               c->set_port (1, _engine.get_nth_physical_input (DataType::AUDIO, np + 1));
 
-               add_bundle (c);
+               add_bundle (c);
        }
 
        /* THREE MASTER */
@@ -685,13 +687,13 @@ Session::when_engine_running ()
                        
                }
 
-               shared_ptr<Bundle> c (new OutputBundle (_("Master Out"), true));
+               shared_ptr<AutoBundle> c (new AutoBundle (_("Master Out"), true));
 
-               c->set_nchannels (_master_out->n_inputs().n_total());
-               for (uint32_t n = 0; n < _master_out->n_inputs ().n_total(); ++n) {
-                       c->add_port_to_channel ((int) n, _master_out->input(n)->name());
-               }
-               add_bundle (c);
+               c->set_channels (_master_out->n_inputs().n_total());
+               for (uint32_t n = 0; n < _master_out->n_inputs ().n_total(); ++n) {
+                       c->set_port (n, _master_out->input(n)->name());
+               }
+               add_bundle (c);
        } 
 
        hookup_io ();
@@ -802,7 +804,13 @@ Session::hookup_io ()
                for (RouteList::iterator x = r->begin(); x != r->end(); ++x) {
                        (*x)->set_control_outs (cports);
                }
-       } 
+       }
+
+       /* load bundles, which we may have postponed earlier on */
+       if (_bundle_xml_node) {
+               load_bundles (*_bundle_xml_node);
+               delete _bundle_xml_node;
+       }       
 
        /* Tell all IO objects to connect themselves together */
 
@@ -3739,35 +3747,6 @@ Session::bundle_by_name (string name) const
        return boost::shared_ptr<Bundle> ();
 }
 
-boost::shared_ptr<Bundle>
-Session::bundle_by_ports (std::vector<std::string> const & wanted_ports) const
-{
-       Glib::Mutex::Lock lm (bundle_lock);
-
-       for (BundleList::const_iterator i = _bundles.begin(); i != _bundles.end(); ++i) {
-               if ((*i)->nchannels() != wanted_ports.size()) {
-                       continue;
-               }
-
-               bool match = true;
-               for (uint32_t j = 0; j < (*i)->nchannels(); ++j) {
-                       Bundle::PortList const p = (*i)->channel_ports (j);
-                       if (p.empty() || p[0] != wanted_ports[j]) {
-                               /* not this bundle */
-                               match = false;
-                               break;
-                       }
-               }
-
-               if (match) {
-                       /* matched bundle */
-                       return *i;
-               }
-       }
-
-       return boost::shared_ptr<Bundle> ();
-}
-
 void
 Session::tempo_map_changed (Change ignored)
 {
index 0534da6c89dc364a631f5906ce0ec93d91924378..c711b227e75e6a811e8d3a19d44671d5ac23a294 100644 (file)
@@ -953,12 +953,13 @@ Session::state(bool full_state)
                node->add_child_nocopy (loc.get_state());
        }
        
-       child = node->add_child ("Connections");
+       child = node->add_child ("Bundles");
        {
                Glib::Mutex::Lock lm (bundle_lock);
                for (BundleList::iterator i = _bundles.begin(); i != _bundles.end(); ++i) {
-                       if (!(*i)->dynamic()) {
-                               child->add_child_nocopy ((*i)->get_state());
+                       boost::shared_ptr<UserBundle> b = boost::dynamic_pointer_cast<UserBundle> (*i);
+                       if (b) {
+                               child->add_child_nocopy (b->get_state());
                        }
                }
        }
@@ -1197,13 +1198,16 @@ Session::set_state (const XMLNode& node)
                goto out;
        }
 
-       if ((child = find_named_node (node, "Connections")) == 0) {
-               error << _("Session: XML state has no connections section") << endmsg;
-               goto out;
-       } else if (load_bundles (*child)) {
+       if ((child = find_named_node (node, "Bundles")) == 0) {
+               error << _("Session: XML state has no bundles section") << endmsg;
                goto out;
+       } else {
+               /* We can't load Bundles yet as they need to be able
+                  to convert from port names to Port objects, which can't happen until
+                  later */
+               _bundle_xml_node = new XMLNode (*child);
        }
-
+       
        if ((child = find_named_node (node, "EditGroups")) == 0) {
                error << _("Session: XML state has no edit groups section") << endmsg;
                goto out;
@@ -1237,7 +1241,7 @@ Session::set_state (const XMLNode& node)
        } else if (_click_io) {
                _click_io->set_state (*child);
        }
-       
+
        if ((child = find_named_node (node, "ControlProtocols")) != 0) {
                ControlProtocolManager::instance().set_protocol_states (*child);
        }
@@ -1876,23 +1880,23 @@ Session::automation_dir () const
 }
 
 int
-Session::load_bundles (const XMLNode& node)
-{
-       XMLNodeList nlist = node.children();
-       XMLNodeConstIterator niter;
-
-       set_dirty();
-
-       for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
-               if ((*niter)->name() == "InputConnection") {
-                       add_bundle (boost::shared_ptr<Bundle> (new InputBundle (**niter)));
-               } else if ((*niter)->name() == "OutputConnection") {
-                       add_bundle (boost::shared_ptr<Bundle> (new OutputBundle (**niter)));
-               } else {
-                       error << string_compose(_("Unknown node \"%1\" found in Connections list from state file"), (*niter)->name()) << endmsg;
-                       return -1;
-               }
-       }
+Session::load_bundles (XMLNode const & node)
+{
+       XMLNodeList nlist = node.children();
+       XMLNodeConstIterator niter;
+
+       set_dirty();
+
+       for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
+               if ((*niter)->name() == "InputBundle") {
+                       add_bundle (boost::shared_ptr<UserBundle> (new UserBundle (**niter, true)));
+               } else if ((*niter)->name() == "OutputBundle") {
+                       add_bundle (boost::shared_ptr<UserBundle> (new UserBundle (**niter, false)));
+               } else {
+                       error << string_compose(_("Unknown node \"%1\" found in Bundles list from state file"), (*niter)->name()) << endmsg;
+                       return -1;
+               }
+       }
 
        return 0;
 }                              
diff --git a/libs/ardour/user_bundle.cc b/libs/ardour/user_bundle.cc
new file mode 100644 (file)
index 0000000..471d823
--- /dev/null
@@ -0,0 +1,198 @@
+#include <cassert>
+#include <pbd/failed_constructor.h>
+#include <pbd/compose.h>
+#include <pbd/xml++.h>
+#include "ardour/user_bundle.h"
+#include "ardour/port_set.h"
+#include "ardour/io.h"
+#include "ardour/session.h"
+#include "ardour/audioengine.h"
+#include "i18n.h"
+
+ARDOUR::UserBundle::UserBundle (std::string const & n)
+       : Bundle (n)
+{
+
+}
+
+ARDOUR::UserBundle::UserBundle (XMLNode const & x, bool i)
+       : Bundle (i)
+{
+       if (set_state (x)) {
+               throw failed_constructor ();
+       }
+}
+
+uint32_t
+ARDOUR::UserBundle::nchannels () const
+{
+       Glib::Mutex::Lock lm (_ports_mutex);
+       return _ports.size ();
+}
+
+const ARDOUR::PortList&
+ARDOUR::UserBundle::channel_ports (uint32_t n) const
+{
+       assert (n < nchannels ());
+
+       Glib::Mutex::Lock lm (_ports_mutex);
+       return _ports[n];
+}
+
+void
+ARDOUR::UserBundle::add_port_to_channel (uint32_t c, std::string const & p)
+{
+       assert (c < nchannels ());
+       
+       PortsWillChange (c);
+
+       {
+               Glib::Mutex::Lock lm (_ports_mutex);
+               _ports[c].push_back (p);
+       }
+       
+       PortsHaveChanged (c);
+}
+
+void
+ARDOUR::UserBundle::remove_port_from_channel (uint32_t c, std::string const & p)
+{
+       assert (c < nchannels ());
+
+       PortsWillChange (c);
+
+       {
+               Glib::Mutex::Lock lm (_ports_mutex);
+               PortList::iterator i = std::find (_ports[c].begin(), _ports[c].end(), p);
+               if (i != _ports[c].end()) {
+                       _ports[c].erase (i);
+               }
+       }
+       
+       PortsHaveChanged (c);
+}
+
+bool
+ARDOUR::UserBundle::port_attached_to_channel (uint32_t c, std::string const & p) const
+{
+       assert (c < nchannels ());
+
+       Glib::Mutex::Lock lm (_ports_mutex);
+       return std::find (_ports[c].begin(), _ports[c].end(), p) != _ports[c].end();
+}
+
+void
+ARDOUR::UserBundle::add_channel ()
+{
+       ConfigurationWillChange ();
+
+       {
+               Glib::Mutex::Lock lm (_ports_mutex);
+               _ports.resize (_ports.size() + 1);
+       }
+       
+       ConfigurationHasChanged ();
+}
+
+void
+ARDOUR::UserBundle::set_channels (uint32_t n)
+{
+       ConfigurationWillChange ();
+
+       {
+               Glib::Mutex::Lock lm (_ports_mutex);
+               _ports.resize (n);
+       }
+
+       ConfigurationHasChanged ();
+}
+
+void
+ARDOUR::UserBundle::remove_channel (uint32_t r)
+{
+       assert (r < nchannels ());
+
+       ConfigurationWillChange ();
+
+       {
+               Glib::Mutex::Lock lm (_ports_mutex);
+               _ports.erase (_ports.begin() + r, _ports.begin() + r + 1);
+       }
+
+       ConfigurationHasChanged ();
+}
+
+int
+ARDOUR::UserBundle::set_state (XMLNode const & node)
+{
+       XMLProperty const * name;
+       
+       if ((name = node.property ("name")) == 0) {
+               PBD::error << _("Node for Bundle has no \"name\" property") << endmsg;
+               return -1;
+       }
+
+       set_name (name->value ());
+
+       XMLNodeList const channels = node.children ();
+
+       int n = 0;
+       for (XMLNodeConstIterator i = channels.begin(); i != channels.end(); ++i) {
+
+               if ((*i)->name() != "Channel") {
+                       PBD::error << string_compose (_("Unknown node \"%s\" in Bundle"), (*i)->name()) << endmsg;
+                       return -1;
+               }
+
+               add_channel ();
+
+               XMLNodeList const ports = (*i)->children ();
+
+               for (XMLNodeConstIterator j = ports.begin(); j != ports.end(); ++j) {
+                       if ((*j)->name() != "Port") {
+                               PBD::error << string_compose (_("Unknown node \"%s\" in Bundle"), (*j)->name()) << endmsg;
+                               return -1;
+                       }
+
+                       if ((name = (*j)->property ("name")) == 0) {
+                               PBD::error << _("Node for Port has no \"name\" property") << endmsg;
+                               return -1;
+                       } 
+                       
+                       add_port_to_channel (n, name->value ());
+               }
+
+               ++n;
+       }
+
+       return 0;
+}
+
+XMLNode&
+ARDOUR::UserBundle::get_state ()
+{
+       XMLNode *node;
+       
+       if (ports_are_inputs ()) {
+               node = new XMLNode ("InputBundle");
+       } else {
+               node = new XMLNode ("OutputBundle");
+       }
+
+       node->add_property ("name", name ());
+
+       for (std::vector<PortList>::iterator i = _ports.begin(); i != _ports.end(); ++i) {
+
+               XMLNode* c = new XMLNode ("Channel");
+
+               for (PortList::iterator j = i->begin(); j != i->end(); ++j) {
+                       XMLNode* p = new XMLNode ("Port");
+                       p->add_property ("name", *j);
+                       c->add_child_nocopy (*p);
+               }
+
+               node->add_child_nocopy (*c);
+       }
+
+       return *node;
+}