From: Carl Hetherington Date: Fri, 19 Oct 2007 13:30:07 +0000 (+0000) Subject: Various work on bundles. We now have a Bundle Manager dialogue, and hopefully things... X-Git-Tag: 3.0-alpha5~4466 X-Git-Url: https://main.carlh.net/gitweb/?p=ardour.git;a=commitdiff_plain;h=77f16522e0b396262bc272c1637753faa9da0ba7 Various work on bundles. We now have a Bundle Manager dialogue, and hopefully things are a bit cleaner internally. This commit changes the session file format with respect to bundles (or Connections as they used to be called). git-svn-id: svn://localhost/ardour2/trunk@2561 d708f5d6-7413-0410-9779-e7cbd77b26cf --- diff --git a/gtk2_ardour/SConscript b/gtk2_ardour/SConscript index 61d653bed7..f1309b8c00 100644 --- a/gtk2_ardour/SConscript +++ b/gtk2_ardour/SConscript @@ -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 diff --git a/gtk2_ardour/ardour.menus b/gtk2_ardour/ardour.menus index 871aa15aee..6f11572151 100644 --- a/gtk2_ardour/ardour.menus +++ b/gtk2_ardour/ardour.menus @@ -237,6 +237,7 @@ + diff --git a/gtk2_ardour/ardour_ui.cc b/gtk2_ardour/ardour_ui.cc index 491939bd97..1182f39792 100644 --- a/gtk2_ardour/ardour_ui.cc +++ b/gtk2_ardour/ardour_ui.cc @@ -85,7 +85,7 @@ #include "utils.h" #include "gui_thread.h" #include "theme_manager.h" - +#include "bundle_manager.h" #include "i18n.h" diff --git a/gtk2_ardour/ardour_ui.h b/gtk2_ardour/ardour_ui.h index 9061f789a1..74400396af 100644 --- a/gtk2_ardour/ardour_ui.h +++ b/gtk2_ardour/ardour_ui.h @@ -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 (); diff --git a/gtk2_ardour/ardour_ui_dialogs.cc b/gtk2_ardour/ardour_ui_dialogs.cc index 89ab470d9b..34104016d7 100644 --- a/gtk2_ardour/ardour_ui_dialogs.cc +++ b/gtk2_ardour/ardour_ui_dialogs.cc @@ -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_("/Common/ToggleBundleManager"))); + } +} + +void +ARDOUR_UI::toggle_bundle_manager () +{ + create_bundle_manager (); + + RefPtr act = ActionManager::get_action (X_("Common"), X_("ToggleBundleManager")); + if (act) { + RefPtr tact = RefPtr::cast_dynamic (act); + + if (tact->get_active()) { + bundle_manager->show_all (); + bundle_manager->present (); + } else { + bundle_manager->hide (); + } + } +} + int ARDOUR_UI::create_route_params () { diff --git a/gtk2_ardour/ardour_ui_ed.cc b/gtk2_ardour/ardour_ui_ed.cc index 8ba5e3790a..74b5a70738 100644 --- a/gtk2_ardour/ardour_ui_ed.cc +++ b/gtk2_ardour/ardour_ui_ed.cc @@ -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 transport_actions = ActionGroup::create (X_("Transport")); diff --git a/gtk2_ardour/bundle_manager.cc b/gtk2_ardour/bundle_manager.cc new file mode 100644 index 0000000000..4dacce782f --- /dev/null +++ b/gtk2_ardour/bundle_manager.cc @@ -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 +#include +#include +#include +#include +#include +#include +#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 bundle + ) + : PortMatrix ( + session, bundle->type(), bundle->ports_are_inputs(), + PortGroupList::Mask (PortGroupList::SYSTEM | PortGroupList::OTHER) + ) +{ + _bundle = boost::dynamic_pointer_cast (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 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 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 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 b = (*i)[_list_model_columns.bundle]; + _session.remove_bundle (b); + _list_model->erase (i); + } +} + +void +BundleManager::add_bundle (boost::shared_ptr b) +{ + boost::shared_ptr u = boost::dynamic_pointer_cast (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 b) +{ + Gtk::TreeModel::iterator i = _list_model->children().begin (); + while (i != _list_model->children().end()) { + boost::shared_ptr 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 index 0000000000..4d4d44074f --- /dev/null +++ b/gtk2_ardour/bundle_manager.h @@ -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 +#include +#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); + + 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 _bundle; +}; + +class BundleEditor : public ArdourDialog +{ + public: + BundleEditor (ARDOUR::Session &, boost::shared_ptr, bool); + + protected: + void on_map (); + + private: + void name_changed (); + void input_or_output_changed (); + void type_changed (); + + BundleEditorMatrix _matrix; + boost::shared_ptr _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); + void bundle_name_changed (boost::shared_ptr); + void set_button_sensitivity (); + + class ModelColumns : public Gtk::TreeModelColumnRecord + { + public: + ModelColumns () { + add (name); + add (bundle); + } + + Gtk::TreeModelColumn name; + Gtk::TreeModelColumn > bundle; + }; + + Gtk::TreeView _tree_view; + Glib::RefPtr _list_model; + ModelColumns _list_model_columns; + ARDOUR::Session& _session; + Gtk::Button edit_button; + Gtk::Button delete_button; +}; + +#endif diff --git a/gtk2_ardour/io_selector.cc b/gtk2_ardour/io_selector.cc index d0f236f84a..b8598659d7 100644 --- a/gtk2_ardour/io_selector.cc +++ b/gtk2_ardour/io_selector.cc @@ -17,15 +17,6 @@ */ -#include -#include -#include -#include -#include -#include -#include -#include -#include #include #include #include @@ -41,575 +32,116 @@ #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 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 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 -PortGroupTable::unit_size () const -{ - if (_check_buttons.empty() || _check_buttons[0].empty()) { - return std::pair (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::const_iterator j = (*i)->ports.begin(); j != (*i)->ports.end(); ++j) { - std::pair 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 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 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 -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 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 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 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::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::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 unit_size (0, 0); - int port_group_tables_height = 0; - for (std::vector::iterator i = _port_group_tables.begin(); i != _port_group_tables.end(); ++i) { - std::pair 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::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::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 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 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 ((*i).get())) { - /* Audio track for an audio IO */ - g = &track; - } else if (_io->default_type() == ARDOUR::DataType::MIDI && dynamic_cast ((*i).get())) { - /* Midi track for a MIDI IO */ - g = &track; - } else if (_io->default_type() == ARDOUR::DataType::AUDIO && dynamic_cast ((*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::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 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")) diff --git a/gtk2_ardour/io_selector.h b/gtk2_ardour/io_selector.h index 92c3af9b48..92a112dc1d 100644 --- a/gtk2_ardour/io_selector.h +++ b/gtk2_ardour/io_selector.h @@ -20,153 +20,30 @@ #ifndef __ardour_ui_io_selector_h__ #define __ardour_ui_io_selector_h__ -#include -#include -#include -#include - #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 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, bool); - - Gtk::Widget& get_widget (); - std::pair 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 > _check_buttons; - bool _ignore_check_button_toggle; - boost::shared_ptr _io; - bool _for_input; -}; - -/// A list of PortGroups -class PortGroupList : public std::list -{ - public: - PortGroupList (ARDOUR::Session &, boost::shared_ptr, 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 _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; - - private: - std::pair 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; - Glib::RefPtr _pango_layout; - Glib::RefPtr _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, bool); - ~IOSelector (); - - void redisplay (); - enum Result { - Cancelled, - Accepted - }; - - sigc::signal 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 _io; - bool _for_input; - std::vector _port_group_tables; - std::vector _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: diff --git a/gtk2_ardour/mixer_strip.cc b/gtk2_ardour/mixer_strip.cc index f791314d75..62aefba27f 100644 --- a/gtk2_ardour/mixer_strip.cc +++ b/gtk2_ardour/mixer_strip.cc @@ -42,7 +42,8 @@ #include #include #include -#include +#include +#include #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(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 > 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(this)), &RouteUI::disconnect_input))); citems.push_back (SeparatorElem()); - + + std::vector > 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 c) } void -MixerStrip::add_bundle_to_input_menu (boost::shared_ptr b, boost::shared_ptr current) +MixerStrip::add_bundle_to_input_menu (boost::shared_ptr b, std::vector > 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 (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 (&citems.back())->set_active (true); ignore_toggle = false; @@ -683,23 +691,22 @@ MixerStrip::add_bundle_to_input_menu (boost::shared_ptr b, boost::shared } void -MixerStrip::add_bundle_to_output_menu (boost::shared_ptr b, boost::shared_ptr current) +MixerStrip::add_bundle_to_output_menu (boost::shared_ptr b, std::vector > 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 (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 (&citems.back())->set_active (true); ignore_toggle = false; @@ -752,10 +759,11 @@ MixerStrip::connect_to_pan () void MixerStrip::update_input_display () { - boost::shared_ptr c; + std::vector > 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 c; + std::vector > 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: diff --git a/gtk2_ardour/mixer_strip.h b/gtk2_ardour/mixer_strip.h index c3cf68a9ce..f134897fc5 100644 --- a/gtk2_ardour/mixer_strip.h +++ b/gtk2_ardour/mixer_strip.h @@ -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, boost::shared_ptr); + void add_bundle_to_input_menu (boost::shared_ptr, std::vector > const &); Gtk::Menu output_menu; - void add_bundle_to_output_menu (boost::shared_ptr, boost::shared_ptr); + void add_bundle_to_output_menu (boost::shared_ptr, std::vector > const &); void bundle_input_chosen (boost::shared_ptr); void bundle_output_chosen (boost::shared_ptr); diff --git a/gtk2_ardour/port_matrix.cc b/gtk2_ardour/port_matrix.cc new file mode 100644 index 0000000000..19c0dc9d7f --- /dev/null +++ b/gtk2_ardour/port_matrix.cc @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 +PortGroupUI::unit_size () const +{ + if (_port_checkbuttons.empty() || _port_checkbuttons[0].empty()) + { + return std::pair (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::const_iterator j = (*i)->ports.begin(); j != (*i)->ports.end(); ++j) { + std::pair 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 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 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 +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 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 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::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::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 unit_size (0, 0); + int port_group_tables_height = 0; + for (std::vector::iterator i = _port_group_ui.begin(); i != _port_group_ui.end(); ++i) { + std::pair 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::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::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 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 ((*i).get())) { + /* Audio track for an audio IO */ + g = &track; + } else if (_type == ARDOUR::DataType::MIDI && dynamic_cast ((*i).get())) { + /* Midi track for a MIDI IO */ + g = &track; + } else if (_type == ARDOUR::DataType::AUDIO && dynamic_cast ((*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::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 index 0000000000..a945d00496 --- /dev/null +++ b/gtk2_ardour/port_matrix.h @@ -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 +#include +#include +#include +#include +#include + +#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 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 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 > _port_checkbuttons; + Gtk::CheckButton _visibility_checkbutton; +}; + +/// A list of PortGroups +class PortGroupList : public std::list +{ + 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; + + private: + std::pair 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; + Glib::RefPtr _pango_layout; + Glib::RefPtr _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 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 _port_group_ui; + std::vector _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 diff --git a/libs/ardour/SConscript b/libs/ardour/SConscript index 4d68dc7725..af08d4569c 100644 --- a/libs/ardour/SConscript +++ b/libs/ardour/SConscript @@ -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 diff --git a/libs/ardour/ardour/audioengine.h b/libs/ardour/ardour/audioengine.h index 34f7eb8c22..503b8166f0 100644 --- a/libs/ardour/ardour/audioengine.h +++ b/libs/ardour/ardour/audioengine.h @@ -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 index 0000000000..685a083e8d --- /dev/null +++ b/libs/ardour/ardour/auto_bundle.h @@ -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 +#include +#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 _ports; +}; + +} + +#endif /* __ardour_auto_bundle_h__ */ diff --git a/libs/ardour/ardour/bundle.h b/libs/ardour/ardour/bundle.h index 9c5f3cb21a..ba92063b30 100644 --- a/libs/ardour/ardour/bundle.h +++ b/libs/ardour/ardour/bundle.h @@ -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 @@ -20,118 +20,54 @@ #ifndef __ardour_bundle_h__ #define __ardour_bundle_h__ -#include #include #include -#include -#include - -using std::vector; -using std::string; +#include "ardour/data_type.h" namespace ARDOUR { +typedef std::vector 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 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 NameChanged; - /// Our name changed - sigc::signal NameChanged; - /// The number of channels changed - sigc::signal ConfigurationChanged; - /// The ports associated with one of our channels changed - sigc::signal 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 _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& 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__ */ - diff --git a/libs/ardour/ardour/data_type.h b/libs/ardour/ardour/data_type.h index 68d9554904..854f52acba 100644 --- a/libs/ardour/ardour/data_type.h +++ b/libs/ardour/ardour/data_type.h @@ -21,7 +21,6 @@ #define __ardour_data_type_h__ #include -#include #include namespace ARDOUR { diff --git a/libs/ardour/ardour/io.h b/libs/ardour/ardour/io.h index 6e68c01d8c..888b770250 100644 --- a/libs/ardour/ardour/io.h +++ b/libs/ardour/ardour/io.h @@ -43,6 +43,7 @@ #include #include #include +#include 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, void *src); int connect_output_ports_to_bundle (boost::shared_ptr, void *src); - boost::shared_ptr input_bundle(); - boost::shared_ptr output_bundle(); + std::vector > bundles_connected_to_inputs (); + std::vector > bundles_connected_to_outputs (); + boost::shared_ptr bundle_for_inputs () { return _bundle_for_inputs; } + boost::shared_ptr 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_for_inputs () const { return _bundle_for_inputs; } - boost::shared_ptr bundle_for_outputs () const { return _bundle_for_outputs; } - sigc::signal input_changed; sigc::signal output_changed; @@ -272,8 +274,6 @@ class IO : public Automatable, public Latent PortSet _outputs; PortSet _inputs; PeakMeter* _meter; - boost::shared_ptr _input_bundle; ///< bundle connected to our inputs - boost::shared_ptr _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_for_inputs; - boost::shared_ptr _bundle_for_outputs; + boost::shared_ptr _bundle_for_inputs; ///< a bundle representing our inputs + boost::shared_ptr _bundle_for_outputs; ///< a bundle representing our outputs + + struct UserBundleInfo { + UserBundleInfo (IO*, boost::shared_ptr b); + + boost::shared_ptr bundle; + sigc::connection configuration_will_change; + sigc::connection configuration_has_changed; + sigc::connection ports_will_change; + sigc::connection ports_have_changed; + }; + + std::vector _bundles_connected_to_outputs; ///< user bundles connected to our outputs + std::vector _bundles_connected_to_inputs; ///< user bundles connected to our inputs static int parse_io_string (const string&, vector& 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&, 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, std::vector >*); + void maybe_add_output_bundle_to_list (boost::shared_ptr, std::vector >*); }; } // namespace ARDOUR diff --git a/libs/ardour/ardour/session.h b/libs/ardour/ardour/session.h index bbcae6e91d..e153914a09 100644 --- a/libs/ardour/ardour/session.h +++ b/libs/ardour/ardour/session.h @@ -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); void remove_bundle (boost::shared_ptr); boost::shared_ptr bundle_by_name (string) const; - boost::shared_ptr bundle_by_ports (vector const &) const; sigc::signal > BundleAdded; sigc::signal > BundleRemoved; @@ -1564,7 +1564,8 @@ class Session : public PBD::StatefulDestructible typedef list > 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 index 0000000000..954e93d5d1 --- /dev/null +++ b/libs/ardour/ardour/user_bundle.h @@ -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 +#include +#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 ConfigurationWillChange; + /// The number of channels has changed + sigc::signal ConfigurationHasChanged; + /// The port set associated with one of our channels is about to change + /// Parameter is the channel number + sigc::signal PortsWillChange; + /// The port set associated with one of our channels has changed + /// Parameter is the channel number + sigc::signal PortsHaveChanged; + + private: + + int set_state (const XMLNode &); + + /// mutex for _ports; + /// XXX: is this necessary? + mutable Glib::Mutex _ports_mutex; + std::vector _ports; +}; + +} + +#endif diff --git a/libs/ardour/audioengine.cc b/libs/ardour/audioengine.cc index 4552de1186..2e1b790848 100644 --- a/libs/ardour/audioengine.cc +++ b/libs/ardour/audioengine.cc @@ -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 index 0000000000..9da32bbb7a --- /dev/null +++ b/libs/ardour/auto_bundle.cc @@ -0,0 +1,47 @@ +#include +#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; +} diff --git a/libs/ardour/io.cc b/libs/ardour/io.cc index 95f40f1f80..d0a233a356 100644 --- a/libs/ardour/io.cc +++ b/libs/ardour/io.cc @@ -34,7 +34,7 @@ #include #include #include -#include +#include #include #include #include @@ -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& list, const PortSet& ports) +{ + std::vector new_list; + + for (std::vector::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::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::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 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 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 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 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 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 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 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 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 ub = boost::dynamic_pointer_cast (c); + if (ub) { + + /* See if we already know about this one */ + std::vector::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 c, void* src) int IO::connect_output_ports_to_bundle (boost::shared_ptr 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 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 ub = boost::dynamic_pointer_cast (c); + if (ub) { + + /* See if we already know about this one */ + std::vector::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 ( - new InputBundle ("", true) - ); - - _bundle_for_outputs = boost::shared_ptr ( - new OutputBundle ("", true) - ); - - setup_bundles (); + _bundle_for_inputs = boost::shared_ptr (new AutoBundle (true)); + _bundle_for_outputs = boost::shared_ptr (new AutoBundle (false)); + setup_bundles_for_inputs_and_outputs (); } -boost::shared_ptr -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 b, std::vector >* bundles) { - if (_input_bundle) { - return _input_bundle; + boost::shared_ptr ab = boost::dynamic_pointer_cast (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 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 > +IO::bundles_connected_to_inputs () +{ + std::vector > bundles; + + /* User bundles */ + for (std::vector::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 -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 b, std::vector >* bundles) { - if (_output_bundle) { - return _output_bundle; + boost::shared_ptr ab = boost::dynamic_pointer_cast (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 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 > +IO::bundles_connected_to_outputs () +{ + std::vector > bundles; + + /* User bundles */ + for (std::vector::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 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) + ); } diff --git a/libs/ardour/session.cc b/libs/ardour/session.cc index 1382fa3f0a..e155800d23 100644 --- a/libs/ardour/session.cc +++ b/libs/ardour/session.cc @@ -62,7 +62,7 @@ #include #include #include -#include +#include #include #include #include @@ -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 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 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 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 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 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 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 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 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 c (new OutputBundle (_("Master Out"), true)); + shared_ptr 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 (); } -boost::shared_ptr -Session::bundle_by_ports (std::vector 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 (); -} - void Session::tempo_map_changed (Change ignored) { diff --git a/libs/ardour/session_state.cc b/libs/ardour/session_state.cc index 0534da6c89..c711b227e7 100644 --- a/libs/ardour/session_state.cc +++ b/libs/ardour/session_state.cc @@ -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 b = boost::dynamic_pointer_cast (*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 (new InputBundle (**niter))); - } else if ((*niter)->name() == "OutputConnection") { - add_bundle (boost::shared_ptr (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 (new UserBundle (**niter, true))); + } else if ((*niter)->name() == "OutputBundle") { + add_bundle (boost::shared_ptr (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 index 0000000000..471d823496 --- /dev/null +++ b/libs/ardour/user_bundle.cc @@ -0,0 +1,198 @@ +#include +#include +#include +#include +#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::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; +}