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
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -237,6 +237,7 @@
|
||||
<menuitem action='ToggleKeyEditor'/>
|
||||
<menuitem action='ToggleThemeManager'/>
|
||||
<menuitem action='ToggleBigClock'/>
|
||||
<menuitem action='ToggleBundleManager'/>
|
||||
<separator/>
|
||||
</menu>
|
||||
<menu name='Options' action='Options'>
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
#include "utils.h"
|
||||
#include "gui_thread.h"
|
||||
#include "theme_manager.h"
|
||||
|
||||
#include "bundle_manager.h"
|
||||
|
||||
#include "i18n.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 ();
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
#include "route_params_ui.h"
|
||||
#include "sfdb_ui.h"
|
||||
#include "theme_manager.h"
|
||||
#include "bundle_manager.h"
|
||||
#include "keyeditor.h"
|
||||
|
||||
#include "i18n.h"
|
||||
@@ -363,6 +364,33 @@ ARDOUR_UI::toggle_theme_manager ()
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ARDOUR_UI::create_bundle_manager ()
|
||||
{
|
||||
if (bundle_manager == 0) {
|
||||
bundle_manager = new BundleManager (*session);
|
||||
bundle_manager->signal_unmap().connect (sigc::bind (sigc::ptr_fun (&ActionManager::uncheck_toggleaction), X_("<Actions>/Common/ToggleBundleManager")));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ARDOUR_UI::toggle_bundle_manager ()
|
||||
{
|
||||
create_bundle_manager ();
|
||||
|
||||
RefPtr<Action> act = ActionManager::get_action (X_("Common"), X_("ToggleBundleManager"));
|
||||
if (act) {
|
||||
RefPtr<ToggleAction> tact = RefPtr<ToggleAction>::cast_dynamic (act);
|
||||
|
||||
if (tact->get_active()) {
|
||||
bundle_manager->show_all ();
|
||||
bundle_manager->present ();
|
||||
} else {
|
||||
bundle_manager->hide ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
ARDOUR_UI::create_route_params ()
|
||||
{
|
||||
|
||||
@@ -219,6 +219,8 @@ ARDOUR_UI::install_actions ()
|
||||
|
||||
ActionManager::register_action (common_actions, X_("About"), _("About"), mem_fun(*this, &ARDOUR_UI::show_splash));
|
||||
ActionManager::register_toggle_action (common_actions, X_("ToggleThemeManager"), _("Theme Manager"), mem_fun(*this, &ARDOUR_UI::toggle_theme_manager));
|
||||
ActionManager::register_toggle_action (common_actions, X_("ToggleBundleManager"), _("Bundle Manager"), mem_fun(*this, &ARDOUR_UI::toggle_bundle_manager));
|
||||
|
||||
ActionManager::register_toggle_action (common_actions, X_("ToggleKeyEditor"), _("Keybindings"), mem_fun(*this, &ARDOUR_UI::toggle_key_editor));
|
||||
|
||||
Glib::RefPtr<ActionGroup> transport_actions = ActionGroup::create (X_("Transport"));
|
||||
|
||||
335
gtk2_ardour/bundle_manager.cc
Normal file
335
gtk2_ardour/bundle_manager.cc
Normal file
@@ -0,0 +1,335 @@
|
||||
/*
|
||||
Copyright (C) 2007 Paul Davis
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
|
||||
*/
|
||||
|
||||
#include <gtkmm/stock.h>
|
||||
#include <gtkmm/button.h>
|
||||
#include <gtkmm/label.h>
|
||||
#include <gtkmm/entry.h>
|
||||
#include <gtkmm/table.h>
|
||||
#include <gtkmm/comboboxtext.h>
|
||||
#include <gtkmm/alignment.h>
|
||||
#include "ardour/session.h"
|
||||
#include "ardour/user_bundle.h"
|
||||
#include "ardour/audioengine.h"
|
||||
#include "bundle_manager.h"
|
||||
#include "i18n.h"
|
||||
|
||||
BundleEditorMatrix::BundleEditorMatrix (
|
||||
ARDOUR::Session& session, boost::shared_ptr<ARDOUR::Bundle> bundle
|
||||
)
|
||||
: PortMatrix (
|
||||
session, bundle->type(), bundle->ports_are_inputs(),
|
||||
PortGroupList::Mask (PortGroupList::SYSTEM | PortGroupList::OTHER)
|
||||
)
|
||||
{
|
||||
_bundle = boost::dynamic_pointer_cast<ARDOUR::UserBundle> (bundle);
|
||||
assert (_bundle != 0);
|
||||
}
|
||||
|
||||
void
|
||||
BundleEditorMatrix::set_state (int r, std::string const & p, bool s)
|
||||
{
|
||||
if (s) {
|
||||
_bundle->add_port_to_channel (r, p);
|
||||
} else {
|
||||
_bundle->remove_port_from_channel (r, p);
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
BundleEditorMatrix::get_state (int r, std::string const & p) const
|
||||
{
|
||||
return _bundle->port_attached_to_channel (r, p);
|
||||
}
|
||||
|
||||
uint32_t
|
||||
BundleEditorMatrix::n_rows () const
|
||||
{
|
||||
return _bundle->nchannels ();
|
||||
}
|
||||
|
||||
uint32_t
|
||||
BundleEditorMatrix::maximum_rows () const
|
||||
{
|
||||
/* 65536 channels in a bundle ought to be enough for anyone (TM) */
|
||||
return 65536;
|
||||
}
|
||||
|
||||
uint32_t
|
||||
BundleEditorMatrix::minimum_rows () const
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string
|
||||
BundleEditorMatrix::row_name (int r) const
|
||||
{
|
||||
std::stringstream s;
|
||||
s << r;
|
||||
return s.str();
|
||||
}
|
||||
|
||||
void
|
||||
BundleEditorMatrix::add_row ()
|
||||
{
|
||||
_bundle->add_channel ();
|
||||
redisplay ();
|
||||
}
|
||||
|
||||
void
|
||||
BundleEditorMatrix::remove_row (int r)
|
||||
{
|
||||
_bundle->remove_channel (r);
|
||||
redisplay ();
|
||||
}
|
||||
|
||||
std::string
|
||||
BundleEditorMatrix::row_descriptor () const
|
||||
{
|
||||
return _("channel");
|
||||
}
|
||||
|
||||
BundleEditor::BundleEditor (ARDOUR::Session& session, boost::shared_ptr<ARDOUR::UserBundle> bundle, bool add)
|
||||
: ArdourDialog (_("Edit Bundle")), _matrix (session, bundle), _bundle (bundle)
|
||||
{
|
||||
Gtk::Table* t = new Gtk::Table (3, 2);
|
||||
t->set_spacings (4);
|
||||
|
||||
Gtk::Alignment* a = new Gtk::Alignment (1, 0.5, 0, 1);
|
||||
a->add (*Gtk::manage (new Gtk::Label (_("Name:"))));
|
||||
t->attach (*Gtk::manage (a), 0, 1, 0, 1, Gtk::FILL, Gtk::FILL);
|
||||
t->attach (_name, 1, 2, 0, 1);
|
||||
|
||||
_name.set_text (_bundle->name ());
|
||||
_name.signal_changed().connect (sigc::mem_fun (*this, &BundleEditor::name_changed));
|
||||
|
||||
a = new Gtk::Alignment (1, 0.5, 0, 1);
|
||||
a->add (*Gtk::manage (new Gtk::Label (_("Direction:"))));
|
||||
t->attach (*Gtk::manage (a), 0, 1, 1, 2, Gtk::FILL, Gtk::FILL);
|
||||
a = new Gtk::Alignment (0, 0.5, 0, 1);
|
||||
a->add (_input_or_output);
|
||||
t->attach (*Gtk::manage (a), 1, 2, 1, 2);
|
||||
|
||||
_input_or_output.append_text (_("Input"));
|
||||
_input_or_output.append_text (_("Output"));
|
||||
|
||||
if (bundle->ports_are_inputs()) {
|
||||
_input_or_output.set_active_text (_("Output"));
|
||||
} else {
|
||||
_input_or_output.set_active_text (_("Input"));
|
||||
}
|
||||
|
||||
_input_or_output.signal_changed().connect (sigc::mem_fun (*this, &BundleEditor::input_or_output_changed));
|
||||
|
||||
a = new Gtk::Alignment (1, 0.5, 0, 1);
|
||||
a->add (*Gtk::manage (new Gtk::Label (_("Type:"))));
|
||||
t->attach (*Gtk::manage (a), 0, 1, 2, 3, Gtk::FILL, Gtk::FILL);
|
||||
a = new Gtk::Alignment (0, 0.5, 0, 1);
|
||||
a->add (_type);
|
||||
t->attach (*Gtk::manage (a), 1, 2, 2, 3);
|
||||
|
||||
_type.append_text (_("Audio"));
|
||||
_type.append_text (_("MIDI"));
|
||||
|
||||
switch (bundle->type ()) {
|
||||
case ARDOUR::DataType::AUDIO:
|
||||
_type.set_active_text (_("Audio"));
|
||||
break;
|
||||
case ARDOUR::DataType::MIDI:
|
||||
_type.set_active_text (_("MIDI"));
|
||||
break;
|
||||
}
|
||||
|
||||
_type.signal_changed().connect (sigc::mem_fun (*this, &BundleEditor::type_changed));
|
||||
|
||||
get_vbox()->pack_start (*Gtk::manage (t), false, false);
|
||||
|
||||
get_vbox()->pack_start (_matrix);
|
||||
|
||||
get_vbox()->set_spacing (4);
|
||||
|
||||
if (add) {
|
||||
add_button (Gtk::Stock::CANCEL, 1);
|
||||
add_button (Gtk::Stock::ADD, 0);
|
||||
} else {
|
||||
add_button (Gtk::Stock::CLOSE, 0);
|
||||
}
|
||||
|
||||
show_all ();
|
||||
}
|
||||
|
||||
void
|
||||
BundleEditor::name_changed ()
|
||||
{
|
||||
_bundle->set_name (_name.get_text ());
|
||||
}
|
||||
|
||||
void
|
||||
BundleEditor::input_or_output_changed ()
|
||||
{
|
||||
if (_input_or_output.get_active_text() == _("Output")) {
|
||||
_bundle->set_ports_are_inputs ();
|
||||
_matrix.set_offer_inputs (true);
|
||||
} else {
|
||||
_bundle->set_ports_are_outputs ();
|
||||
_matrix.set_offer_inputs (false);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
BundleEditor::type_changed ()
|
||||
{
|
||||
ARDOUR::DataType const t = _type.get_active_text() == _("Audio") ?
|
||||
ARDOUR::DataType::AUDIO : ARDOUR::DataType::MIDI;
|
||||
|
||||
_bundle->set_type (t);
|
||||
_matrix.set_type (t);
|
||||
}
|
||||
|
||||
void
|
||||
BundleEditor::on_map ()
|
||||
{
|
||||
_matrix.redisplay ();
|
||||
Window::on_map ();
|
||||
}
|
||||
|
||||
|
||||
BundleManager::BundleManager (ARDOUR::Session& session)
|
||||
: ArdourDialog (_("Bundle manager")), _session (session), edit_button (_("Edit")), delete_button (_("Delete"))
|
||||
{
|
||||
_list_model = Gtk::ListStore::create (_list_model_columns);
|
||||
_tree_view.set_model (_list_model);
|
||||
_tree_view.append_column (_("Name"), _list_model_columns.name);
|
||||
_tree_view.set_headers_visible (false);
|
||||
|
||||
_session.foreach_bundle (sigc::mem_fun (*this, &BundleManager::add_bundle));
|
||||
|
||||
/* New / Edit / Delete buttons */
|
||||
Gtk::VBox* buttons = new Gtk::VBox;
|
||||
buttons->set_spacing (8);
|
||||
Gtk::Button* b = new Gtk::Button (_("New"));
|
||||
b->set_image (*Gtk::manage (new Gtk::Image (Gtk::Stock::NEW, Gtk::ICON_SIZE_BUTTON)));
|
||||
b->signal_clicked().connect (sigc::mem_fun (*this, &BundleManager::new_clicked));
|
||||
buttons->pack_start (*Gtk::manage (b), false, false);
|
||||
edit_button.set_image (*Gtk::manage (new Gtk::Image (Gtk::Stock::EDIT, Gtk::ICON_SIZE_BUTTON)));
|
||||
edit_button.signal_clicked().connect (sigc::mem_fun (*this, &BundleManager::edit_clicked));
|
||||
buttons->pack_start (edit_button, false, false);
|
||||
delete_button.set_image (*Gtk::manage (new Gtk::Image (Gtk::Stock::DELETE, Gtk::ICON_SIZE_BUTTON)));
|
||||
delete_button.signal_clicked().connect (sigc::mem_fun (*this, &BundleManager::delete_clicked));
|
||||
buttons->pack_start (delete_button, false, false);
|
||||
|
||||
Gtk::HBox* h = new Gtk::HBox;
|
||||
h->set_spacing (8);
|
||||
h->set_border_width (8);
|
||||
h->pack_start (_tree_view);
|
||||
h->pack_start (*Gtk::manage (buttons), false, false);
|
||||
|
||||
get_vbox()->set_spacing (8);
|
||||
get_vbox()->pack_start (*Gtk::manage (h));
|
||||
|
||||
set_default_size (480, 240);
|
||||
|
||||
_tree_view.get_selection()->signal_changed().connect (
|
||||
sigc::mem_fun (*this, &BundleManager::set_button_sensitivity)
|
||||
);
|
||||
|
||||
set_button_sensitivity ();
|
||||
|
||||
show_all ();
|
||||
}
|
||||
|
||||
void
|
||||
BundleManager::set_button_sensitivity ()
|
||||
{
|
||||
bool const sel = (_tree_view.get_selection()->get_selected() != 0);
|
||||
edit_button.set_sensitive (sel);
|
||||
delete_button.set_sensitive (sel);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
BundleManager::new_clicked ()
|
||||
{
|
||||
boost::shared_ptr<ARDOUR::UserBundle> b (new ARDOUR::UserBundle (""));
|
||||
|
||||
/* Start off with a single channel */
|
||||
b->add_channel ();
|
||||
|
||||
BundleEditor e (_session, b, true);
|
||||
if (e.run () == 0) {
|
||||
_session.add_bundle (b);
|
||||
add_bundle (b);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
BundleManager::edit_clicked ()
|
||||
{
|
||||
Gtk::TreeModel::iterator i = _tree_view.get_selection()->get_selected();
|
||||
if (i) {
|
||||
boost::shared_ptr<ARDOUR::UserBundle> b = (*i)[_list_model_columns.bundle];
|
||||
BundleEditor e (_session, b, false);
|
||||
e.run ();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
BundleManager::delete_clicked ()
|
||||
{
|
||||
Gtk::TreeModel::iterator i = _tree_view.get_selection()->get_selected();
|
||||
if (i) {
|
||||
boost::shared_ptr<ARDOUR::UserBundle> b = (*i)[_list_model_columns.bundle];
|
||||
_session.remove_bundle (b);
|
||||
_list_model->erase (i);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
BundleManager::add_bundle (boost::shared_ptr<ARDOUR::Bundle> b)
|
||||
{
|
||||
boost::shared_ptr<ARDOUR::UserBundle> u = boost::dynamic_pointer_cast<ARDOUR::UserBundle> (b);
|
||||
if (u == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
Gtk::TreeModel::iterator i = _list_model->append ();
|
||||
(*i)[_list_model_columns.name] = u->name ();
|
||||
(*i)[_list_model_columns.bundle] = u;
|
||||
|
||||
u->NameChanged.connect (sigc::bind (sigc::mem_fun (*this, &BundleManager::bundle_name_changed), u));
|
||||
}
|
||||
|
||||
void
|
||||
BundleManager::bundle_name_changed (boost::shared_ptr<ARDOUR::UserBundle> b)
|
||||
{
|
||||
Gtk::TreeModel::iterator i = _list_model->children().begin ();
|
||||
while (i != _list_model->children().end()) {
|
||||
boost::shared_ptr<ARDOUR::UserBundle> t = (*i)[_list_model_columns.bundle];
|
||||
if (t == b) {
|
||||
break;
|
||||
}
|
||||
++i;
|
||||
}
|
||||
|
||||
if (i != _list_model->children().end()) {
|
||||
(*i)[_list_model_columns.name] = b->name ();
|
||||
}
|
||||
}
|
||||
|
||||
107
gtk2_ardour/bundle_manager.h
Normal file
107
gtk2_ardour/bundle_manager.h
Normal file
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
Copyright (C) 2007 Paul Davis
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
|
||||
*/
|
||||
|
||||
#ifndef __ardour_ui_bundle_manager_h__
|
||||
#define __ardour_ui_bundle_manager_h__
|
||||
|
||||
#include <gtkmm/treeview.h>
|
||||
#include <gtkmm/liststore.h>
|
||||
#include "ardour_dialog.h"
|
||||
#include "port_matrix.h"
|
||||
|
||||
namespace ARDOUR {
|
||||
class Session;
|
||||
class Bundle;
|
||||
}
|
||||
|
||||
class BundleEditorMatrix : public PortMatrix
|
||||
{
|
||||
public:
|
||||
BundleEditorMatrix (ARDOUR::Session &, boost::shared_ptr<ARDOUR::Bundle>);
|
||||
|
||||
void set_state (int, std::string const &, bool);
|
||||
bool get_state (int, std::string const &) const;
|
||||
uint32_t n_rows () const;
|
||||
uint32_t maximum_rows () const;
|
||||
uint32_t minimum_rows () const;
|
||||
std::string row_name (int) const;
|
||||
void add_row ();
|
||||
void remove_row (int);
|
||||
std::string row_descriptor () const;
|
||||
|
||||
private:
|
||||
|
||||
boost::shared_ptr<ARDOUR::UserBundle> _bundle;
|
||||
};
|
||||
|
||||
class BundleEditor : public ArdourDialog
|
||||
{
|
||||
public:
|
||||
BundleEditor (ARDOUR::Session &, boost::shared_ptr<ARDOUR::UserBundle>, bool);
|
||||
|
||||
protected:
|
||||
void on_map ();
|
||||
|
||||
private:
|
||||
void name_changed ();
|
||||
void input_or_output_changed ();
|
||||
void type_changed ();
|
||||
|
||||
BundleEditorMatrix _matrix;
|
||||
boost::shared_ptr<ARDOUR::UserBundle> _bundle;
|
||||
Gtk::Entry _name;
|
||||
Gtk::ComboBoxText _input_or_output;
|
||||
Gtk::ComboBoxText _type;
|
||||
};
|
||||
|
||||
class BundleManager : public ArdourDialog
|
||||
{
|
||||
public:
|
||||
BundleManager (ARDOUR::Session &);
|
||||
|
||||
private:
|
||||
|
||||
void new_clicked ();
|
||||
void edit_clicked ();
|
||||
void delete_clicked ();
|
||||
void add_bundle (boost::shared_ptr<ARDOUR::Bundle>);
|
||||
void bundle_name_changed (boost::shared_ptr<ARDOUR::UserBundle>);
|
||||
void set_button_sensitivity ();
|
||||
|
||||
class ModelColumns : public Gtk::TreeModelColumnRecord
|
||||
{
|
||||
public:
|
||||
ModelColumns () {
|
||||
add (name);
|
||||
add (bundle);
|
||||
}
|
||||
|
||||
Gtk::TreeModelColumn<Glib::ustring> name;
|
||||
Gtk::TreeModelColumn<boost::shared_ptr<ARDOUR::UserBundle> > bundle;
|
||||
};
|
||||
|
||||
Gtk::TreeView _tree_view;
|
||||
Glib::RefPtr<Gtk::ListStore> _list_model;
|
||||
ModelColumns _list_model_columns;
|
||||
ARDOUR::Session& _session;
|
||||
Gtk::Button edit_button;
|
||||
Gtk::Button delete_button;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -17,15 +17,6 @@
|
||||
|
||||
*/
|
||||
|
||||
#include <gtkmm/label.h>
|
||||
#include <gtkmm/enums.h>
|
||||
#include <gtkmm/image.h>
|
||||
#include <gtkmm/stock.h>
|
||||
#include <gtkmm/messagedialog.h>
|
||||
#include <gtkmm/menu.h>
|
||||
#include <gtkmm/menu_elems.h>
|
||||
#include <gtkmm/menuitem.h>
|
||||
#include <gtkmm/menushell.h>
|
||||
#include <glibmm/objectbase.h>
|
||||
#include <gtkmm2ext/doi.h>
|
||||
#include <ardour/port_insert.h>
|
||||
@@ -41,506 +32,21 @@
|
||||
#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)
|
||||
IOSelector::IOSelector (ARDOUR::Session& session, boost::shared_ptr<ARDOUR::IO> io, bool offer_inputs)
|
||||
: PortMatrix (
|
||||
session, io->default_type(), offer_inputs,
|
||||
PortGroupList::Mask (PortGroupList::BUSS | PortGroupList::SYSTEM | PortGroupList::OTHER)
|
||||
),
|
||||
_io (io)
|
||||
{
|
||||
if (prefix.empty() == false && p.substr (0, prefix.length()) == prefix) {
|
||||
ports.push_back (p.substr (prefix.length()));
|
||||
} else {
|
||||
ports.push_back (p);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
PortGroupTable::PortGroupTable (
|
||||
PortGroup& g, boost::shared_ptr<ARDOUR::IO> io, bool for_input
|
||||
)
|
||||
: _port_group (g), _ignore_check_button_toggle (false),
|
||||
_io (io), _for_input (for_input)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
int const ports = _port_group.ports.size();
|
||||
|
||||
if (rows == 0 || ports == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Sort out the table and the checkbuttons inside it */
|
||||
|
||||
_table.resize (rows, ports);
|
||||
_check_buttons.resize (rows);
|
||||
for (int i = 0; i < rows; ++i) {
|
||||
_check_buttons[i].resize (ports);
|
||||
}
|
||||
|
||||
for (int i = 0; i < rows; ++i) {
|
||||
for (uint32_t j = 0; j < _port_group.ports.size(); ++j) {
|
||||
Gtk::CheckButton* b = new Gtk::CheckButton;
|
||||
b->signal_toggled().connect (sigc::bind (sigc::mem_fun (*this, &PortGroupTable::check_button_toggled), b, i, _port_group.prefix + _port_group.ports[j]));
|
||||
_check_buttons[i][j] = b;
|
||||
_table.attach (*b, j, j + 1, i, i + 1);
|
||||
}
|
||||
}
|
||||
|
||||
_box.add (_table);
|
||||
|
||||
_ignore_check_button_toggle = true;
|
||||
|
||||
/* Set the state of the check boxes according to current connections */
|
||||
for (int i = 0; i < rows; ++i) {
|
||||
const char **connections = _for_input ? _io->input(i)->get_connections() : _io->output(i)->get_connections();
|
||||
for (uint32_t j = 0; j < _port_group.ports.size(); ++j) {
|
||||
|
||||
std::string const t = _port_group.prefix + _port_group.ports[j];
|
||||
int k = 0;
|
||||
bool required_state = false;
|
||||
|
||||
while (connections && connections[k]) {
|
||||
if (std::string(connections[k]) == t) {
|
||||
required_state = true;
|
||||
break;
|
||||
}
|
||||
++k;
|
||||
}
|
||||
|
||||
_check_buttons[i][j]->set_active (required_state);
|
||||
}
|
||||
}
|
||||
|
||||
_ignore_check_button_toggle = false;
|
||||
}
|
||||
|
||||
/** @return Width and height of a single check button in a port group table */
|
||||
std::pair<int, int>
|
||||
PortGroupTable::unit_size () const
|
||||
{
|
||||
if (_check_buttons.empty() || _check_buttons[0].empty()) {
|
||||
return std::pair<int, int> (0, 0);
|
||||
}
|
||||
|
||||
return std::make_pair (
|
||||
_check_buttons[0][0]->get_width() + _table.get_col_spacing (0),
|
||||
_check_buttons[0][0]->get_height() + _table.get_row_spacing (0)
|
||||
);
|
||||
}
|
||||
|
||||
Gtk::Widget&
|
||||
PortGroupTable::get_widget ()
|
||||
{
|
||||
return _box;
|
||||
}
|
||||
|
||||
|
||||
/** Handle a toggle of a check button */
|
||||
void
|
||||
PortGroupTable::check_button_toggled (Gtk::CheckButton* b, int r, std::string const & p)
|
||||
{
|
||||
if (_ignore_check_button_toggle) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool const new_state = b->get_active ();
|
||||
|
||||
if (new_state) {
|
||||
if (_for_input) {
|
||||
_io->connect_input (_io->input(r), p, 0);
|
||||
} else {
|
||||
_io->connect_output (_io->output(r), p, 0);
|
||||
}
|
||||
} else {
|
||||
if (_for_input) {
|
||||
_io->disconnect_input (_io->input(r), p, 0);
|
||||
} else {
|
||||
_io->disconnect_output (_io->output(r), p, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
RotatedLabelSet::RotatedLabelSet (PortGroupList& g)
|
||||
: Glib::ObjectBase ("RotatedLabelSet"), Gtk::Widget (), _port_group_list (g), _base_width (128)
|
||||
{
|
||||
set_flags (Gtk::NO_WINDOW);
|
||||
set_angle (30);
|
||||
}
|
||||
|
||||
RotatedLabelSet::~RotatedLabelSet ()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
/** Set the angle that the labels are drawn at.
|
||||
* @param degrees New angle in degrees.
|
||||
*/
|
||||
|
||||
void
|
||||
RotatedLabelSet::set_angle (int degrees)
|
||||
{
|
||||
_angle_degrees = degrees;
|
||||
_angle_radians = M_PI * _angle_degrees / 180;
|
||||
|
||||
queue_resize ();
|
||||
}
|
||||
|
||||
void
|
||||
RotatedLabelSet::on_size_request (Gtk::Requisition* requisition)
|
||||
{
|
||||
*requisition = Gtk::Requisition ();
|
||||
|
||||
if (_pango_layout == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Our height is the highest label */
|
||||
requisition->height = 0;
|
||||
for (PortGroupList::const_iterator i = _port_group_list.begin(); i != _port_group_list.end(); ++i) {
|
||||
for (std::vector<std::string>::const_iterator j = (*i)->ports.begin(); j != (*i)->ports.end(); ++j) {
|
||||
std::pair<int, int> const d = setup_layout (*j);
|
||||
if (d.second > requisition->height) {
|
||||
requisition->height = d.second;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* And our width is the base plus the width of the last label */
|
||||
requisition->width = _base_width;
|
||||
int const n = _port_group_list.n_visible_ports ();
|
||||
if (n > 0) {
|
||||
std::pair<int, int> const d = setup_layout (_port_group_list.get_port_by_index (n - 1, false));
|
||||
requisition->width += d.first;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
RotatedLabelSet::on_size_allocate (Gtk::Allocation& allocation)
|
||||
{
|
||||
set_allocation (allocation);
|
||||
|
||||
if (_gdk_window) {
|
||||
_gdk_window->move_resize (
|
||||
allocation.get_x(), allocation.get_y(), allocation.get_width(), allocation.get_height()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
RotatedLabelSet::on_realize ()
|
||||
{
|
||||
Gtk::Widget::on_realize ();
|
||||
|
||||
Glib::RefPtr<Gtk::Style> style = get_style ();
|
||||
|
||||
if (!_gdk_window) {
|
||||
GdkWindowAttr attributes;
|
||||
memset (&attributes, 0, sizeof (attributes));
|
||||
|
||||
Gtk::Allocation allocation = get_allocation ();
|
||||
attributes.x = allocation.get_x ();
|
||||
attributes.y = allocation.get_y ();
|
||||
attributes.width = allocation.get_width ();
|
||||
attributes.height = allocation.get_height ();
|
||||
|
||||
attributes.event_mask = get_events () | Gdk::EXPOSURE_MASK;
|
||||
attributes.window_type = GDK_WINDOW_CHILD;
|
||||
attributes.wclass = GDK_INPUT_OUTPUT;
|
||||
|
||||
_gdk_window = Gdk::Window::create (get_window (), &attributes, GDK_WA_X | GDK_WA_Y);
|
||||
unset_flags (Gtk::NO_WINDOW);
|
||||
set_window (_gdk_window);
|
||||
|
||||
_bg_colour = style->get_bg (Gtk::STATE_NORMAL );
|
||||
modify_bg (Gtk::STATE_NORMAL, _bg_colour);
|
||||
_fg_colour = style->get_fg (Gtk::STATE_NORMAL);
|
||||
;
|
||||
_gdk_window->set_user_data (gobj ());
|
||||
|
||||
/* Set up Pango stuff */
|
||||
_pango_context = create_pango_context ();
|
||||
|
||||
Pango::Matrix matrix = PANGO_MATRIX_INIT;
|
||||
pango_matrix_rotate (&matrix, _angle_degrees);
|
||||
_pango_context->set_matrix (matrix);
|
||||
|
||||
_pango_layout = Pango::Layout::create (_pango_context);
|
||||
_gc = Gdk::GC::create (get_window ());
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
RotatedLabelSet::on_unrealize()
|
||||
{
|
||||
_gdk_window.clear ();
|
||||
|
||||
Gtk::Widget::on_unrealize ();
|
||||
}
|
||||
|
||||
|
||||
/** Set up our Pango layout to plot a given string, and compute its dimensions once
|
||||
* it has been rotated.
|
||||
* @param s String to use.
|
||||
* @return width and height of the rotated string, in pixels.
|
||||
*/
|
||||
|
||||
std::pair<int, int>
|
||||
RotatedLabelSet::setup_layout (std::string const & s)
|
||||
{
|
||||
_pango_layout->set_text (s);
|
||||
|
||||
/* Here's the unrotated size */
|
||||
int w;
|
||||
int h;
|
||||
_pango_layout->get_pixel_size (w, h);
|
||||
|
||||
/* Rotate the width and height as appropriate. I thought Pango might be able
|
||||
to do this for us, but I can't find out how... */
|
||||
std::pair<int, int> d;
|
||||
d.first = int (w * cos (_angle_radians) - h * sin (_angle_radians));
|
||||
d.second = int (w * sin (_angle_radians) + h * cos (_angle_radians));
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
bool
|
||||
RotatedLabelSet::on_expose_event (GdkEventExpose* event)
|
||||
{
|
||||
if (!_gdk_window) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int const height = get_allocation().get_height ();
|
||||
double const spacing = double (_base_width) / _port_group_list.n_visible_ports();
|
||||
|
||||
/* Plot all the visible labels; really we should clip for efficiency */
|
||||
int n = 0;
|
||||
for (PortGroupList::const_iterator i = _port_group_list.begin(); i != _port_group_list.end(); ++i) {
|
||||
if ((*i)->visible) {
|
||||
for (uint32_t j = 0; j < (*i)->ports.size(); ++j) {
|
||||
std::pair<int, int> const d = setup_layout ((*i)->ports[j]);
|
||||
get_window()->draw_layout (_gc, int ((n + 0.25) * spacing), height - d.second, _pango_layout, _fg_colour, _bg_colour);
|
||||
++n;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Set the `base width'. This is the width of the base of the label set, ie:
|
||||
*
|
||||
* L L L L
|
||||
* E E E E
|
||||
* B B B B
|
||||
* A A A A
|
||||
* L L L L
|
||||
* <--w-->
|
||||
*/
|
||||
|
||||
void
|
||||
RotatedLabelSet::set_base_width (int w)
|
||||
{
|
||||
_base_width = w;
|
||||
queue_resize ();
|
||||
}
|
||||
|
||||
|
||||
/** Construct an IOSelector.
|
||||
* @param session Session to operate on.
|
||||
* @param io IO to operate on.
|
||||
* @param for_input true if the selector is for an input, otherwise false.
|
||||
*/
|
||||
|
||||
IOSelector::IOSelector (ARDOUR::Session& session, boost::shared_ptr<ARDOUR::IO> io, bool for_input)
|
||||
: _port_group_list (session, io, for_input), _io (io), _for_input (for_input),
|
||||
_column_labels (_port_group_list)
|
||||
{
|
||||
_row_labels_vbox[0] = _row_labels_vbox[1] = 0;
|
||||
_side_vbox_pad[0] = _side_vbox_pad[1] = 0;
|
||||
|
||||
Gtk::HBox* c = new Gtk::HBox;
|
||||
for (PortGroupList::iterator i = _port_group_list.begin(); i != _port_group_list.end(); ++i) {
|
||||
Gtk::CheckButton* b = new Gtk::CheckButton ((*i)->name);
|
||||
b->set_active (true);
|
||||
b->signal_toggled().connect (sigc::bind (sigc::mem_fun (*this, &IOSelector::group_visible_toggled), b, (*i)->name));
|
||||
c->pack_start (*Gtk::manage (b), false, false);
|
||||
}
|
||||
pack_start (*Gtk::manage (c));
|
||||
|
||||
_side_vbox[0].pack_start (*Gtk::manage (new Gtk::Label ("")));
|
||||
_overall_hbox.pack_start (_side_vbox[0], false, false);
|
||||
_scrolled_window.set_policy (Gtk::POLICY_ALWAYS, Gtk::POLICY_NEVER);
|
||||
_scrolled_window.set_shadow_type (Gtk::SHADOW_NONE);
|
||||
Gtk::VBox* b = new Gtk::VBox;
|
||||
b->pack_start (_column_labels, false, false);
|
||||
b->pack_start (_port_group_hbox, false, false);
|
||||
Gtk::Alignment* a = new Gtk::Alignment (0, 1, 0, 0);
|
||||
a->add (*Gtk::manage (b));
|
||||
_scrolled_window.add (*Gtk::manage (a));
|
||||
_overall_hbox.pack_start (_scrolled_window);
|
||||
_side_vbox[1].pack_start (*Gtk::manage (new Gtk::Label ("")));
|
||||
_overall_hbox.pack_start (_side_vbox[1]);
|
||||
pack_start (_overall_hbox);
|
||||
|
||||
_port_group_hbox.signal_size_allocate().connect (sigc::hide (sigc::mem_fun (*this, &IOSelector::setup_dimensions)));
|
||||
|
||||
/* Listen for ports changing on the IO */
|
||||
if (_for_input) {
|
||||
if (!offer_inputs) {
|
||||
_io->input_changed.connect (mem_fun(*this, &IOSelector::ports_changed));
|
||||
} else {
|
||||
_io->output_changed.connect (mem_fun(*this, &IOSelector::ports_changed));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
IOSelector::~IOSelector ()
|
||||
{
|
||||
clear ();
|
||||
}
|
||||
|
||||
/** Clear out the things that change when the number of source or destination ports changes */
|
||||
void
|
||||
IOSelector::clear ()
|
||||
{
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
|
||||
for (std::vector<Gtk::EventBox*>::iterator j = _row_labels[i].begin(); j != _row_labels[i].end(); ++j) {
|
||||
delete *j;
|
||||
}
|
||||
_row_labels[i].clear ();
|
||||
|
||||
if (_row_labels_vbox[i]) {
|
||||
_side_vbox[i].remove (*_row_labels_vbox[i]);
|
||||
}
|
||||
delete _row_labels_vbox[i];
|
||||
_row_labels_vbox[i] = 0;
|
||||
|
||||
if (_side_vbox_pad[i]) {
|
||||
_side_vbox[i].remove (*_side_vbox_pad[i]);
|
||||
}
|
||||
delete _side_vbox_pad[i];
|
||||
_side_vbox_pad[i] = 0;
|
||||
}
|
||||
|
||||
for (std::vector<PortGroupTable*>::iterator i = _port_group_tables.begin(); i != _port_group_tables.end(); ++i) {
|
||||
_port_group_hbox.remove ((*i)->get_widget());
|
||||
delete *i;
|
||||
}
|
||||
|
||||
_port_group_tables.clear ();
|
||||
}
|
||||
|
||||
|
||||
/** Set up dimensions of some of our widgets which depend on other dimensions
|
||||
* within the dialogue.
|
||||
*/
|
||||
void
|
||||
IOSelector::setup_dimensions ()
|
||||
{
|
||||
/* Get some dimensions from various places */
|
||||
int const scrollbar_height = _scrolled_window.get_hscrollbar()->get_height();
|
||||
|
||||
std::pair<int, int> unit_size (0, 0);
|
||||
int port_group_tables_height = 0;
|
||||
for (std::vector<PortGroupTable*>::iterator i = _port_group_tables.begin(); i != _port_group_tables.end(); ++i) {
|
||||
std::pair<int, int> const u = (*i)->unit_size ();
|
||||
unit_size.first = std::max (unit_size.first, u.first);
|
||||
unit_size.second = std::max (unit_size.second, u.second);
|
||||
port_group_tables_height = std::max (
|
||||
port_group_tables_height, (*i)->get_widget().get_height()
|
||||
);
|
||||
}
|
||||
|
||||
/* Column labels */
|
||||
_column_labels.set_base_width (_port_group_list.n_visible_ports () * unit_size.first);
|
||||
|
||||
/* Scrolled window */
|
||||
/* XXX: really shouldn't set a minimum horizontal size here, but if we don't
|
||||
the window starts up very small */
|
||||
_scrolled_window.set_size_request (
|
||||
std::min (_column_labels.get_width(), 640),
|
||||
_column_labels.get_height() + port_group_tables_height + scrollbar_height + 16
|
||||
);
|
||||
|
||||
/* Row labels */
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
for (std::vector<Gtk::EventBox*>::iterator j = _row_labels[i].begin(); j != _row_labels[i].end(); ++j) {
|
||||
(*j)->get_child()->set_size_request (-1, unit_size.second);
|
||||
}
|
||||
|
||||
if (_side_vbox_pad[i]) {
|
||||
_side_vbox_pad[i]->set_size_request (-1, scrollbar_height + unit_size.second / 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Set up the dialogue */
|
||||
void
|
||||
IOSelector::setup ()
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
/* 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)
|
||||
@@ -551,65 +57,91 @@ IOSelector::ports_changed (ARDOUR::IOChange change, void *src)
|
||||
}
|
||||
|
||||
|
||||
|
||||
void
|
||||
IOSelector::redisplay ()
|
||||
IOSelector::set_state (int r, std::string const & p, bool s)
|
||||
{
|
||||
_port_group_list.refresh ();
|
||||
setup ();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Handle a button press on a row label */
|
||||
bool
|
||||
IOSelector::row_label_button_pressed (GdkEventButton* e, int r)
|
||||
IOSelector::get_state (int r, std::string const & p) const
|
||||
{
|
||||
if (e->type != GDK_BUTTON_PRESS || e->button != 3) {
|
||||
return false;
|
||||
const char **connections = _offer_inputs ? _io->output(r)->get_connections() : _io->input(r)->get_connections();
|
||||
|
||||
int k = 0;
|
||||
while (connections && connections[k]) {
|
||||
if (std::string (connections[k]) == p) {
|
||||
return true;
|
||||
}
|
||||
|
||||
++k;
|
||||
}
|
||||
|
||||
Gtk::Menu* menu = Gtk::manage (new Gtk::Menu);
|
||||
Gtk::Menu_Helpers::MenuList& items = menu->items ();
|
||||
menu->set_name ("ArdourContextMenu");
|
||||
return false;
|
||||
}
|
||||
|
||||
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();
|
||||
uint32_t
|
||||
IOSelector::n_rows () const
|
||||
{
|
||||
if (!_offer_inputs) {
|
||||
return _io->inputs().num_ports (_io->default_type());
|
||||
} 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->outputs().num_ports (_io->default_type());
|
||||
}
|
||||
|
||||
items.push_back (
|
||||
Gtk::Menu_Helpers::MenuElem (_("Add port"), sigc::mem_fun (*this, &IOSelector::add_port))
|
||||
);
|
||||
}
|
||||
|
||||
items.back().set_sensitive (can_add);
|
||||
uint32_t
|
||||
IOSelector::maximum_rows () const
|
||||
{
|
||||
if (!_offer_inputs) {
|
||||
return _io->input_maximum ().get (_io->default_type());
|
||||
} else {
|
||||
return _io->output_maximum ().get (_io->default_type());
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
uint32_t
|
||||
IOSelector::minimum_rows () const
|
||||
{
|
||||
if (!_offer_inputs) {
|
||||
return _io->input_minimum ().get (_io->default_type());
|
||||
} else {
|
||||
return _io->output_minimum ().get (_io->default_type());
|
||||
}
|
||||
}
|
||||
|
||||
menu->popup (e->button, e->time);
|
||||
|
||||
return true;
|
||||
std::string
|
||||
IOSelector::row_name (int r) const
|
||||
{
|
||||
if (!_offer_inputs) {
|
||||
return _io->input(r)->name();
|
||||
} else {
|
||||
return _io->output(r)->name();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
IOSelector::add_port ()
|
||||
IOSelector::add_row ()
|
||||
{
|
||||
// The IO selector only works for single typed IOs
|
||||
const ARDOUR::DataType t = _io->default_type ();
|
||||
|
||||
if (_for_input) {
|
||||
if (!_offer_inputs) {
|
||||
|
||||
try {
|
||||
_io->add_input_port ("", this);
|
||||
@@ -633,176 +165,33 @@ IOSelector::add_port ()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
IOSelector::remove_port (int r)
|
||||
IOSelector::remove_row (int r)
|
||||
{
|
||||
// The IO selector only works for single typed IOs
|
||||
const ARDOUR::DataType t = _io->default_type ();
|
||||
|
||||
if (_for_input) {
|
||||
if (!_offer_inputs) {
|
||||
_io->remove_input_port (_io->input (r), this);
|
||||
} else {
|
||||
_io->remove_output_port (_io->output (r), this);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
IOSelector::group_visible_toggled (Gtk::CheckButton* b, std::string const & n)
|
||||
{
|
||||
PortGroupList::iterator i = _port_group_list.begin();
|
||||
while (i != _port_group_list.end() & (*i)->name != n) {
|
||||
++i;
|
||||
}
|
||||
|
||||
if (i == _port_group_list.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
(*i)->visible = b->get_active ();
|
||||
|
||||
set_port_group_table_visibility ();
|
||||
|
||||
_column_labels.queue_draw ();
|
||||
}
|
||||
|
||||
void
|
||||
IOSelector::set_port_group_table_visibility ()
|
||||
{
|
||||
for (std::vector<PortGroupTable*>::iterator j = _port_group_tables.begin(); j != _port_group_tables.end(); ++j) {
|
||||
if ((*j)->port_group().visible) {
|
||||
(*j)->get_widget().show();
|
||||
} else {
|
||||
(*j)->get_widget().hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
PortGroupList::PortGroupList (ARDOUR::Session & session, boost::shared_ptr<ARDOUR::IO> io, bool for_input)
|
||||
: _session (session), _io (io), _for_input (for_input),
|
||||
buss (_("Buss"), "ardour:"),
|
||||
track (_("Track"), "ardour:"),
|
||||
system (_("System"), "system:"),
|
||||
other (_("Other"), "")
|
||||
{
|
||||
refresh ();
|
||||
}
|
||||
|
||||
void
|
||||
PortGroupList::refresh ()
|
||||
{
|
||||
clear ();
|
||||
|
||||
buss.ports.clear ();
|
||||
track.ports.clear ();
|
||||
system.ports.clear ();
|
||||
other.ports.clear ();
|
||||
|
||||
/* Find the ports provided by ardour; we can't derive their type just from their
|
||||
names, so we'll have to be more devious. */
|
||||
|
||||
boost::shared_ptr<ARDOUR::Session::RouteList> routes = _session.get_routes ();
|
||||
|
||||
for (ARDOUR::Session::RouteList::const_iterator i = routes->begin(); i != routes->end(); ++i) {
|
||||
|
||||
PortGroup* g = 0;
|
||||
if (_io->default_type() == ARDOUR::DataType::AUDIO && dynamic_cast<ARDOUR::AudioTrack*> ((*i).get())) {
|
||||
/* Audio track for an audio IO */
|
||||
g = &track;
|
||||
} else if (_io->default_type() == ARDOUR::DataType::MIDI && dynamic_cast<ARDOUR::MidiTrack*> ((*i).get())) {
|
||||
/* Midi track for a MIDI IO */
|
||||
g = &track;
|
||||
} else if (_io->default_type() == ARDOUR::DataType::AUDIO && dynamic_cast<ARDOUR::MidiTrack*> ((*i).get()) == 0) {
|
||||
/* Non-MIDI track for an Audio IO; must be an audio buss */
|
||||
g = &buss;
|
||||
}
|
||||
|
||||
if (g) {
|
||||
ARDOUR::PortSet const & p = _for_input ? ((*i)->outputs()) : ((*i)->inputs());
|
||||
for (uint32_t j = 0; j < p.num_ports(); ++j) {
|
||||
g->add (p.port(j)->name ());
|
||||
}
|
||||
|
||||
std::sort (g->ports.begin(), g->ports.end());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* XXX: inserts, sends, plugin inserts? */
|
||||
|
||||
/* Now we need to find the non-ardour ports; we do this by first
|
||||
finding all the ports that we can connect to. */
|
||||
const char **ports = _session.engine().get_ports (
|
||||
"", _io->default_type().to_jack_type(), _for_input ? JackPortIsOutput : JackPortIsInput
|
||||
);
|
||||
|
||||
if (ports) {
|
||||
|
||||
int n = 0;
|
||||
while (ports[n]) {
|
||||
std::string const p = ports[n];
|
||||
|
||||
if (p.substr(0, strlen ("system:")) == "system:") {
|
||||
/* system: prefix */
|
||||
system.add (p);
|
||||
} else {
|
||||
if (p.substr(0, strlen("ardour:")) != "ardour:") {
|
||||
/* other (non-ardour) prefix */
|
||||
other.add (p);
|
||||
}
|
||||
}
|
||||
|
||||
++n;
|
||||
}
|
||||
}
|
||||
|
||||
push_back (&buss);
|
||||
push_back (&track);
|
||||
push_back (&system);
|
||||
push_back (&other);
|
||||
}
|
||||
|
||||
int
|
||||
PortGroupList::n_visible_ports () const
|
||||
{
|
||||
int n = 0;
|
||||
|
||||
for (const_iterator i = begin(); i != end(); ++i) {
|
||||
if ((*i)->visible) {
|
||||
n += (*i)->ports.size();
|
||||
}
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
std::string
|
||||
PortGroupList::get_port_by_index (int n, bool with_prefix) const
|
||||
IOSelector::row_descriptor () const
|
||||
{
|
||||
/* XXX: slightly inefficient algorithm */
|
||||
|
||||
for (const_iterator i = begin(); i != end(); ++i) {
|
||||
for (std::vector<std::string>::const_iterator j = (*i)->ports.begin(); j != (*i)->ports.end(); ++j) {
|
||||
if (n == 0) {
|
||||
if (with_prefix) {
|
||||
return (*i)->prefix + *j;
|
||||
} else {
|
||||
return *j;
|
||||
}
|
||||
}
|
||||
--n;
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
return _("port");
|
||||
}
|
||||
|
||||
|
||||
|
||||
IOSelectorWindow::IOSelectorWindow (
|
||||
ARDOUR::Session& session, boost::shared_ptr<ARDOUR::IO> io, bool for_input, bool can_cancel
|
||||
)
|
||||
: ArdourDialog ("I/O selector"),
|
||||
_selector (session, io, for_input),
|
||||
_selector (session, io, !for_input),
|
||||
ok_button (can_cancel ? _("OK"): _("Close")),
|
||||
cancel_button (_("Cancel")),
|
||||
rescan_button (_("Rescan"))
|
||||
|
||||
@@ -20,153 +20,30 @@
|
||||
#ifndef __ardour_ui_io_selector_h__
|
||||
#define __ardour_ui_io_selector_h__
|
||||
|
||||
#include <gtkmm/box.h>
|
||||
#include <gtkmm/checkbutton.h>
|
||||
#include <gtkmm/table.h>
|
||||
#include <gtkmm/frame.h>
|
||||
|
||||
#include "ardour_dialog.h"
|
||||
#include "port_matrix.h"
|
||||
|
||||
namespace ARDOUR {
|
||||
class Session;
|
||||
class IO;
|
||||
class PortInsert;
|
||||
}
|
||||
|
||||
/// A group of port names
|
||||
class PortGroup
|
||||
{
|
||||
public:
|
||||
PortGroup (std::string const & n, std::string const & p) : name (n), prefix (p), visible (true) {}
|
||||
|
||||
void add (std::string const & p);
|
||||
|
||||
std::string name;
|
||||
std::string prefix; ///< prefix (before colon) e.g. "ardour:"
|
||||
std::vector<std::string> ports; ///< port names
|
||||
bool visible;
|
||||
};
|
||||
|
||||
/// A table of checkbuttons to provide the GUI for connecting to a PortGroup
|
||||
class PortGroupTable
|
||||
{
|
||||
public:
|
||||
PortGroupTable (PortGroup&, boost::shared_ptr<ARDOUR::IO>, bool);
|
||||
|
||||
Gtk::Widget& get_widget ();
|
||||
std::pair<int, int> unit_size () const;
|
||||
PortGroup& port_group () { return _port_group; }
|
||||
|
||||
private:
|
||||
void check_button_toggled (Gtk::CheckButton*, int, std::string const &);
|
||||
|
||||
Gtk::Table _table;
|
||||
Gtk::EventBox _box;
|
||||
PortGroup& _port_group;
|
||||
std::vector<std::vector<Gtk::CheckButton* > > _check_buttons;
|
||||
bool _ignore_check_button_toggle;
|
||||
boost::shared_ptr<ARDOUR::IO> _io;
|
||||
bool _for_input;
|
||||
};
|
||||
|
||||
/// A list of PortGroups
|
||||
class PortGroupList : public std::list<PortGroup*>
|
||||
{
|
||||
public:
|
||||
PortGroupList (ARDOUR::Session &, boost::shared_ptr<ARDOUR::IO>, bool);
|
||||
|
||||
void refresh ();
|
||||
int n_visible_ports () const;
|
||||
std::string get_port_by_index (int, bool with_prefix = true) const;
|
||||
|
||||
private:
|
||||
ARDOUR::Session& _session;
|
||||
boost::shared_ptr<ARDOUR::IO> _io;
|
||||
bool _for_input;
|
||||
|
||||
PortGroup buss;
|
||||
PortGroup track;
|
||||
PortGroup system;
|
||||
PortGroup other;
|
||||
};
|
||||
|
||||
|
||||
/// A widget which provides a set of rotated text labels
|
||||
class RotatedLabelSet : public Gtk::Widget {
|
||||
public:
|
||||
RotatedLabelSet (PortGroupList&);
|
||||
virtual ~RotatedLabelSet ();
|
||||
|
||||
void set_angle (int);
|
||||
void set_base_width (int);
|
||||
void update_visibility ();
|
||||
|
||||
protected:
|
||||
virtual void on_size_request (Gtk::Requisition*);
|
||||
virtual void on_size_allocate (Gtk::Allocation&);
|
||||
virtual void on_realize ();
|
||||
virtual void on_unrealize ();
|
||||
virtual bool on_expose_event (GdkEventExpose*);
|
||||
|
||||
Glib::RefPtr<Gdk::Window> _gdk_window;
|
||||
|
||||
private:
|
||||
std::pair<int, int> setup_layout (std::string const &);
|
||||
|
||||
PortGroupList& _port_group_list; ///< list of ports to display
|
||||
int _angle_degrees; ///< label rotation angle in degrees
|
||||
double _angle_radians; ///< label rotation angle in radians
|
||||
int _base_width; ///< width of labels; see set_base_width() for more details
|
||||
Glib::RefPtr<Pango::Context> _pango_context;
|
||||
Glib::RefPtr<Pango::Layout> _pango_layout;
|
||||
Glib::RefPtr<Gdk::GC> _gc;
|
||||
Gdk::Color _fg_colour;
|
||||
Gdk::Color _bg_colour;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/// Widget for selecting what an IO is connected to
|
||||
class IOSelector : public Gtk::VBox {
|
||||
class IOSelector : public PortMatrix {
|
||||
public:
|
||||
IOSelector (ARDOUR::Session&, boost::shared_ptr<ARDOUR::IO>, bool);
|
||||
~IOSelector ();
|
||||
|
||||
void redisplay ();
|
||||
|
||||
enum Result {
|
||||
Cancelled,
|
||||
Accepted
|
||||
};
|
||||
|
||||
sigc::signal<void, Result> Finished;
|
||||
void set_state (int, std::string const &, bool);
|
||||
bool get_state (int, std::string const &) const;
|
||||
uint32_t n_rows () const;
|
||||
uint32_t maximum_rows () const;
|
||||
uint32_t minimum_rows () const;
|
||||
std::string row_name (int) const;
|
||||
void add_row ();
|
||||
void remove_row (int);
|
||||
std::string row_descriptor () const;
|
||||
|
||||
private:
|
||||
void setup ();
|
||||
void clear ();
|
||||
void setup_dimensions ();
|
||||
|
||||
void ports_changed (ARDOUR::IOChange, void*);
|
||||
bool row_label_button_pressed (GdkEventButton*, int);
|
||||
void add_port ();
|
||||
void remove_port (int);
|
||||
void group_visible_toggled (Gtk::CheckButton*, std::string const &);
|
||||
void set_port_group_table_visibility ();
|
||||
|
||||
PortGroupList _port_group_list;
|
||||
|
||||
boost::shared_ptr<ARDOUR::IO> _io;
|
||||
bool _for_input;
|
||||
std::vector<PortGroupTable*> _port_group_tables;
|
||||
std::vector<Gtk::EventBox*> _row_labels[2];
|
||||
Gtk::VBox* _row_labels_vbox[2];
|
||||
RotatedLabelSet _column_labels;
|
||||
Gtk::HBox _overall_hbox;
|
||||
Gtk::VBox _side_vbox[2];
|
||||
Gtk::HBox _port_group_hbox;
|
||||
Gtk::ScrolledWindow _scrolled_window;
|
||||
Gtk::Label* _side_vbox_pad[2];
|
||||
};
|
||||
|
||||
|
||||
class IOSelectorWindow : public ArdourDialog
|
||||
{
|
||||
public:
|
||||
|
||||
@@ -42,7 +42,8 @@
|
||||
#include <ardour/send.h>
|
||||
#include <ardour/processor.h>
|
||||
#include <ardour/ladspa_plugin.h>
|
||||
#include <ardour/bundle.h>
|
||||
#include <ardour/auto_bundle.h>
|
||||
#include <ardour/user_bundle.h>
|
||||
|
||||
#include "ardour_ui.h"
|
||||
#include "ardour_dialog.h"
|
||||
@@ -540,6 +541,7 @@ MixerStrip::output_press (GdkEventButton *ev)
|
||||
switch (ev->button) {
|
||||
|
||||
case 1:
|
||||
{
|
||||
output_menu.set_name ("ArdourContextMenu");
|
||||
citems.clear();
|
||||
|
||||
@@ -547,13 +549,16 @@ MixerStrip::output_press (GdkEventButton *ev)
|
||||
citems.push_back (SeparatorElem());
|
||||
citems.push_back (MenuElem (_("Disconnect"), mem_fun (*(static_cast<RouteUI*>(this)), &RouteUI::disconnect_output)));
|
||||
citems.push_back (SeparatorElem());
|
||||
|
||||
_session.foreach_bundle (
|
||||
bind (mem_fun (*this, &MixerStrip::add_bundle_to_output_menu), _route->output_bundle ())
|
||||
);
|
||||
|
||||
std::vector<boost::shared_ptr<Bundle> > current = _route->bundles_connected_to_outputs ();
|
||||
|
||||
_session.foreach_bundle (
|
||||
bind (mem_fun (*this, &MixerStrip::add_bundle_to_output_menu), current)
|
||||
);
|
||||
|
||||
output_menu.popup (1, ev->time);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
@@ -607,18 +612,21 @@ MixerStrip::input_press (GdkEventButton *ev)
|
||||
switch (ev->button) {
|
||||
|
||||
case 1:
|
||||
{
|
||||
citems.push_back (MenuElem (_("Edit"), mem_fun(*this, &MixerStrip::edit_input_configuration)));
|
||||
citems.push_back (SeparatorElem());
|
||||
citems.push_back (MenuElem (_("Disconnect"), mem_fun (*(static_cast<RouteUI*>(this)), &RouteUI::disconnect_input)));
|
||||
citems.push_back (SeparatorElem());
|
||||
|
||||
|
||||
std::vector<boost::shared_ptr<Bundle> > current = _route->bundles_connected_to_inputs ();
|
||||
|
||||
_session.foreach_bundle (
|
||||
bind (mem_fun (*this, &MixerStrip::add_bundle_to_input_menu), _route->input_bundle ())
|
||||
bind (mem_fun (*this, &MixerStrip::add_bundle_to_input_menu), current)
|
||||
);
|
||||
|
||||
input_menu.popup (1, ev->time);
|
||||
break;
|
||||
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -658,23 +666,23 @@ MixerStrip::bundle_output_chosen (boost::shared_ptr<ARDOUR::Bundle> c)
|
||||
}
|
||||
|
||||
void
|
||||
MixerStrip::add_bundle_to_input_menu (boost::shared_ptr<Bundle> b, boost::shared_ptr<Bundle> current)
|
||||
MixerStrip::add_bundle_to_input_menu (boost::shared_ptr<Bundle> b, std::vector<boost::shared_ptr<Bundle> > const & current)
|
||||
{
|
||||
using namespace Menu_Helpers;
|
||||
|
||||
/* the input menu needs to contain only output bundles (that we
|
||||
can connect inputs to */
|
||||
if (boost::dynamic_pointer_cast<OutputBundle, Bundle> (b) == 0) {
|
||||
return;
|
||||
}
|
||||
if (b->ports_are_outputs() == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
MenuList& citems = input_menu.items();
|
||||
|
||||
if (b->nchannels() == _route->n_inputs().n_total()) {
|
||||
|
||||
citems.push_back (CheckMenuElem (b->name(), bind (mem_fun(*this, &MixerStrip::bundle_input_chosen), b)));
|
||||
|
||||
if (current == b) {
|
||||
|
||||
if (std::find (current.begin(), current.end(), b) != current.end()) {
|
||||
ignore_toggle = true;
|
||||
dynamic_cast<CheckMenuItem *> (&citems.back())->set_active (true);
|
||||
ignore_toggle = false;
|
||||
@@ -683,23 +691,22 @@ MixerStrip::add_bundle_to_input_menu (boost::shared_ptr<Bundle> b, boost::shared
|
||||
}
|
||||
|
||||
void
|
||||
MixerStrip::add_bundle_to_output_menu (boost::shared_ptr<Bundle> b, boost::shared_ptr<Bundle> current)
|
||||
MixerStrip::add_bundle_to_output_menu (boost::shared_ptr<Bundle> b, std::vector<boost::shared_ptr<Bundle> > const & current)
|
||||
{
|
||||
using namespace Menu_Helpers;
|
||||
|
||||
/* the output menu needs to contain only input bundles (that we
|
||||
can connect outputs to */
|
||||
if (boost::dynamic_pointer_cast<InputBundle, Bundle> (b) == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (b->ports_are_inputs() == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (b->nchannels() == _route->n_outputs().n_total()) {
|
||||
|
||||
MenuList& citems = output_menu.items();
|
||||
citems.push_back (CheckMenuElem (b->name(), bind (mem_fun(*this, &MixerStrip::bundle_output_chosen), b)));
|
||||
|
||||
if (current == b) {
|
||||
if (std::find (current.begin(), current.end(), b) != current.end()) {
|
||||
ignore_toggle = true;
|
||||
dynamic_cast<CheckMenuItem *> (&citems.back())->set_active (true);
|
||||
ignore_toggle = false;
|
||||
@@ -752,10 +759,11 @@ MixerStrip::connect_to_pan ()
|
||||
void
|
||||
MixerStrip::update_input_display ()
|
||||
{
|
||||
boost::shared_ptr<ARDOUR::Bundle> c;
|
||||
std::vector<boost::shared_ptr<ARDOUR::Bundle> > c = _route->bundles_connected_to_inputs ();
|
||||
|
||||
if ((c = _route->input_bundle()) != 0) {
|
||||
input_label.set_text (c->name());
|
||||
/* XXX: how do we represent >1 connected bundle? */
|
||||
if (c.empty() == false) {
|
||||
input_label.set_text (c[0]->name());
|
||||
} else {
|
||||
switch (_width) {
|
||||
case Wide:
|
||||
@@ -772,10 +780,11 @@ MixerStrip::update_input_display ()
|
||||
void
|
||||
MixerStrip::update_output_display ()
|
||||
{
|
||||
boost::shared_ptr<ARDOUR::Bundle> c;
|
||||
std::vector<boost::shared_ptr<ARDOUR::Bundle> > c = _route->bundles_connected_to_outputs ();
|
||||
|
||||
if ((c = _route->output_bundle()) != 0) {
|
||||
output_label.set_text (c->name());
|
||||
/* XXX: how do we represent >1 connected bundle? */
|
||||
if (c.empty() == false) {
|
||||
output_label.set_text (c[0]->name());
|
||||
} else {
|
||||
switch (_width) {
|
||||
case Wide:
|
||||
|
||||
@@ -168,10 +168,10 @@ class MixerStrip : public RouteUI, public Gtk::EventBox
|
||||
gint output_press (GdkEventButton *);
|
||||
|
||||
Gtk::Menu input_menu;
|
||||
void add_bundle_to_input_menu (boost::shared_ptr<ARDOUR::Bundle>, boost::shared_ptr<ARDOUR::Bundle>);
|
||||
void add_bundle_to_input_menu (boost::shared_ptr<ARDOUR::Bundle>, std::vector<boost::shared_ptr<ARDOUR::Bundle> > const &);
|
||||
|
||||
Gtk::Menu output_menu;
|
||||
void add_bundle_to_output_menu (boost::shared_ptr<ARDOUR::Bundle>, boost::shared_ptr<ARDOUR::Bundle>);
|
||||
void add_bundle_to_output_menu (boost::shared_ptr<ARDOUR::Bundle>, std::vector<boost::shared_ptr<ARDOUR::Bundle> > const &);
|
||||
|
||||
void bundle_input_chosen (boost::shared_ptr<ARDOUR::Bundle>);
|
||||
void bundle_output_chosen (boost::shared_ptr<ARDOUR::Bundle>);
|
||||
|
||||
730
gtk2_ardour/port_matrix.cc
Normal file
730
gtk2_ardour/port_matrix.cc
Normal file
@@ -0,0 +1,730 @@
|
||||
/*
|
||||
Copyright (C) 2002-2007 Paul Davis
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
|
||||
*/
|
||||
|
||||
#include <gtkmm/label.h>
|
||||
#include <gtkmm/enums.h>
|
||||
#include <gtkmm/image.h>
|
||||
#include <gtkmm/stock.h>
|
||||
#include <gtkmm/messagedialog.h>
|
||||
#include <gtkmm/menu.h>
|
||||
#include <gtkmm/menu_elems.h>
|
||||
#include <gtkmm/menuitem.h>
|
||||
#include <gtkmm/menushell.h>
|
||||
#include <glibmm/objectbase.h>
|
||||
#include <gtkmm2ext/doi.h>
|
||||
#include <ardour/port_insert.h>
|
||||
#include "ardour/session.h"
|
||||
#include "ardour/io.h"
|
||||
#include "ardour/audioengine.h"
|
||||
#include "ardour/track.h"
|
||||
#include "ardour/audio_track.h"
|
||||
#include "ardour/midi_track.h"
|
||||
#include "ardour/data_type.h"
|
||||
#include "io_selector.h"
|
||||
#include "utils.h"
|
||||
#include "gui_thread.h"
|
||||
#include "i18n.h"
|
||||
|
||||
/** Add a port to a group.
|
||||
* @param p Port name, with or without prefix.
|
||||
*/
|
||||
|
||||
void
|
||||
PortGroup::add (std::string const & p)
|
||||
{
|
||||
if (prefix.empty() == false && p.substr (0, prefix.length()) == prefix) {
|
||||
ports.push_back (p.substr (prefix.length()));
|
||||
} else {
|
||||
ports.push_back (p);
|
||||
}
|
||||
}
|
||||
|
||||
/** PortGroupUI constructor.
|
||||
* @param m PortMatrix to work for.
|
||||
* @Param g PortGroup to represent.
|
||||
*/
|
||||
|
||||
PortGroupUI::PortGroupUI (PortMatrix& m, PortGroup& g)
|
||||
: _port_matrix (m), _port_group (g), _ignore_check_button_toggle (false),
|
||||
_visibility_checkbutton (g.name)
|
||||
{
|
||||
int const ports = _port_group.ports.size();
|
||||
int const rows = _port_matrix.n_rows ();
|
||||
|
||||
if (rows == 0 || ports == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Sort out the table and the checkbuttons inside it */
|
||||
|
||||
_table.resize (rows, ports);
|
||||
_port_checkbuttons.resize (rows);
|
||||
for (int i = 0; i < rows; ++i) {
|
||||
_port_checkbuttons[i].resize (ports);
|
||||
}
|
||||
|
||||
for (int i = 0; i < rows; ++i) {
|
||||
for (uint32_t j = 0; j < _port_group.ports.size(); ++j) {
|
||||
Gtk::CheckButton* b = new Gtk::CheckButton;
|
||||
|
||||
b->signal_toggled().connect (
|
||||
sigc::bind (sigc::mem_fun (*this, &PortGroupUI::port_checkbutton_toggled), b, i, j)
|
||||
);
|
||||
|
||||
_port_checkbuttons[i][j] = b;
|
||||
_table.attach (*b, j, j + 1, i, i + 1);
|
||||
}
|
||||
}
|
||||
|
||||
_table_box.add (_table);
|
||||
|
||||
_ignore_check_button_toggle = true;
|
||||
|
||||
/* Set the state of the check boxes according to current connections */
|
||||
for (int i = 0; i < rows; ++i) {
|
||||
for (uint32_t j = 0; j < _port_group.ports.size(); ++j) {
|
||||
std::string const t = _port_group.prefix + _port_group.ports[j];
|
||||
bool const s = _port_matrix.get_state (i, t);
|
||||
_port_checkbuttons[i][j]->set_active (s);
|
||||
if (s) {
|
||||
_port_group.visible = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_ignore_check_button_toggle = false;
|
||||
|
||||
_visibility_checkbutton.signal_toggled().connect (sigc::mem_fun (*this, &PortGroupUI::visibility_checkbutton_toggled));
|
||||
}
|
||||
|
||||
/** The visibility of a PortGroupUI has been toggled */
|
||||
void
|
||||
PortGroupUI::visibility_checkbutton_toggled ()
|
||||
{
|
||||
_port_group.visible = _visibility_checkbutton.get_active ();
|
||||
setup_visibility ();
|
||||
}
|
||||
|
||||
/** @return Width and height of a single checkbutton in a port group table */
|
||||
std::pair<int, int>
|
||||
PortGroupUI::unit_size () const
|
||||
{
|
||||
if (_port_checkbuttons.empty() || _port_checkbuttons[0].empty())
|
||||
{
|
||||
return std::pair<int, int> (0, 0);
|
||||
}
|
||||
|
||||
int r = 0;
|
||||
/* We can't ask for row spacing unless there >1 rows, otherwise we get a warning */
|
||||
if (_table.property_n_rows() > 1) {
|
||||
r = _table.get_row_spacing (0);
|
||||
}
|
||||
|
||||
return std::make_pair (
|
||||
_port_checkbuttons[0][0]->get_width() + _table.get_col_spacing (0),
|
||||
_port_checkbuttons[0][0]->get_height() + r
|
||||
);
|
||||
}
|
||||
|
||||
/** @return Table widget containing the port checkbuttons */
|
||||
Gtk::Widget&
|
||||
PortGroupUI::get_table ()
|
||||
{
|
||||
return _table_box;
|
||||
}
|
||||
|
||||
/** @return Checkbutton used to toggle visibility */
|
||||
Gtk::Widget&
|
||||
PortGroupUI::get_visibility_checkbutton ()
|
||||
{
|
||||
return _visibility_checkbutton;
|
||||
}
|
||||
|
||||
|
||||
/** Handle a toggle of a port check button */
|
||||
void
|
||||
PortGroupUI::port_checkbutton_toggled (Gtk::CheckButton* b, int r, int c)
|
||||
{
|
||||
if (_ignore_check_button_toggle == false) {
|
||||
_port_matrix.set_state (r, _port_group.prefix + _port_group.ports[c], b->get_active());
|
||||
}
|
||||
}
|
||||
|
||||
/** Set up visibility of the port group according to PortGroup::visible */
|
||||
void
|
||||
PortGroupUI::setup_visibility ()
|
||||
{
|
||||
if (_port_group.visible) {
|
||||
_table_box.show ();
|
||||
} else {
|
||||
_table_box.hide ();
|
||||
}
|
||||
|
||||
if (_visibility_checkbutton.get_active () != _port_group.visible) {
|
||||
_visibility_checkbutton.set_active (_port_group.visible);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
RotatedLabelSet::RotatedLabelSet (PortGroupList& g)
|
||||
: Glib::ObjectBase ("RotatedLabelSet"), Gtk::Widget (), _port_group_list (g), _base_width (128)
|
||||
{
|
||||
set_flags (Gtk::NO_WINDOW);
|
||||
set_angle (30);
|
||||
}
|
||||
|
||||
RotatedLabelSet::~RotatedLabelSet ()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
/** Set the angle that the labels are drawn at.
|
||||
* @param degrees New angle in degrees.
|
||||
*/
|
||||
|
||||
void
|
||||
RotatedLabelSet::set_angle (int degrees)
|
||||
{
|
||||
_angle_degrees = degrees;
|
||||
_angle_radians = M_PI * _angle_degrees / 180;
|
||||
|
||||
queue_resize ();
|
||||
}
|
||||
|
||||
void
|
||||
RotatedLabelSet::on_size_request (Gtk::Requisition* requisition)
|
||||
{
|
||||
*requisition = Gtk::Requisition ();
|
||||
|
||||
if (_pango_layout == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Our height is the highest label */
|
||||
requisition->height = 0;
|
||||
for (PortGroupList::const_iterator i = _port_group_list.begin(); i != _port_group_list.end(); ++i) {
|
||||
for (std::vector<std::string>::const_iterator j = (*i)->ports.begin(); j != (*i)->ports.end(); ++j) {
|
||||
std::pair<int, int> const d = setup_layout (*j);
|
||||
if (d.second > requisition->height) {
|
||||
requisition->height = d.second;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* And our width is the base plus the width of the last label */
|
||||
requisition->width = _base_width;
|
||||
int const n = _port_group_list.n_visible_ports ();
|
||||
if (n > 0) {
|
||||
std::pair<int, int> const d = setup_layout (_port_group_list.get_port_by_index (n - 1, false));
|
||||
requisition->width += d.first;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
RotatedLabelSet::on_size_allocate (Gtk::Allocation& allocation)
|
||||
{
|
||||
set_allocation (allocation);
|
||||
|
||||
if (_gdk_window) {
|
||||
_gdk_window->move_resize (
|
||||
allocation.get_x(), allocation.get_y(), allocation.get_width(), allocation.get_height()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
RotatedLabelSet::on_realize ()
|
||||
{
|
||||
Gtk::Widget::on_realize ();
|
||||
|
||||
Glib::RefPtr<Gtk::Style> style = get_style ();
|
||||
|
||||
if (!_gdk_window) {
|
||||
GdkWindowAttr attributes;
|
||||
memset (&attributes, 0, sizeof (attributes));
|
||||
|
||||
Gtk::Allocation allocation = get_allocation ();
|
||||
attributes.x = allocation.get_x ();
|
||||
attributes.y = allocation.get_y ();
|
||||
attributes.width = allocation.get_width ();
|
||||
attributes.height = allocation.get_height ();
|
||||
|
||||
attributes.event_mask = get_events () | Gdk::EXPOSURE_MASK;
|
||||
attributes.window_type = GDK_WINDOW_CHILD;
|
||||
attributes.wclass = GDK_INPUT_OUTPUT;
|
||||
|
||||
_gdk_window = Gdk::Window::create (get_window (), &attributes, GDK_WA_X | GDK_WA_Y);
|
||||
unset_flags (Gtk::NO_WINDOW);
|
||||
set_window (_gdk_window);
|
||||
|
||||
_bg_colour = style->get_bg (Gtk::STATE_NORMAL );
|
||||
modify_bg (Gtk::STATE_NORMAL, _bg_colour);
|
||||
_fg_colour = style->get_fg (Gtk::STATE_NORMAL);
|
||||
;
|
||||
_gdk_window->set_user_data (gobj ());
|
||||
|
||||
/* Set up Pango stuff */
|
||||
_pango_context = create_pango_context ();
|
||||
|
||||
Pango::Matrix matrix = PANGO_MATRIX_INIT;
|
||||
pango_matrix_rotate (&matrix, _angle_degrees);
|
||||
_pango_context->set_matrix (matrix);
|
||||
|
||||
_pango_layout = Pango::Layout::create (_pango_context);
|
||||
_gc = Gdk::GC::create (get_window ());
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
RotatedLabelSet::on_unrealize()
|
||||
{
|
||||
_gdk_window.clear ();
|
||||
|
||||
Gtk::Widget::on_unrealize ();
|
||||
}
|
||||
|
||||
|
||||
/** Set up our Pango layout to plot a given string, and compute its dimensions once
|
||||
* it has been rotated.
|
||||
* @param s String to use.
|
||||
* @return width and height of the rotated string, in pixels.
|
||||
*/
|
||||
|
||||
std::pair<int, int>
|
||||
RotatedLabelSet::setup_layout (std::string const & s)
|
||||
{
|
||||
_pango_layout->set_text (s);
|
||||
|
||||
/* Here's the unrotated size */
|
||||
int w;
|
||||
int h;
|
||||
_pango_layout->get_pixel_size (w, h);
|
||||
|
||||
/* Rotate the width and height as appropriate. I thought Pango might be able
|
||||
to do this for us, but I can't find out how... */
|
||||
std::pair<int, int> d;
|
||||
d.first = int (w * cos (_angle_radians) - h * sin (_angle_radians));
|
||||
d.second = int (w * sin (_angle_radians) + h * cos (_angle_radians));
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
bool
|
||||
RotatedLabelSet::on_expose_event (GdkEventExpose* event)
|
||||
{
|
||||
if (!_gdk_window) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int const height = get_allocation().get_height ();
|
||||
double const spacing = double (_base_width) / _port_group_list.n_visible_ports();
|
||||
|
||||
/* Plot all the visible labels; really we should clip for efficiency */
|
||||
int n = 0;
|
||||
for (PortGroupList::const_iterator i = _port_group_list.begin(); i != _port_group_list.end(); ++i) {
|
||||
if ((*i)->visible) {
|
||||
for (uint32_t j = 0; j < (*i)->ports.size(); ++j) {
|
||||
std::pair<int, int> const d = setup_layout ((*i)->ports[j]);
|
||||
get_window()->draw_layout (_gc, int ((n + 0.25) * spacing), height - d.second, _pango_layout, _fg_colour, _bg_colour);
|
||||
++n;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Set the `base width'. This is the width of the base of the label set, ie:
|
||||
*
|
||||
* L L L L
|
||||
* E E E E
|
||||
* B B B B
|
||||
* A A A A
|
||||
* L L L L
|
||||
* <--w-->
|
||||
*/
|
||||
|
||||
void
|
||||
RotatedLabelSet::set_base_width (int w)
|
||||
{
|
||||
_base_width = w;
|
||||
queue_resize ();
|
||||
}
|
||||
|
||||
|
||||
PortMatrix::PortMatrix (ARDOUR::Session& session, ARDOUR::DataType type, bool offer_inputs, PortGroupList::Mask mask)
|
||||
: _offer_inputs (offer_inputs), _port_group_list (session, type, offer_inputs, mask), _type (type),
|
||||
_column_labels (_port_group_list)
|
||||
{
|
||||
_row_labels_vbox[0] = _row_labels_vbox[1] = 0;
|
||||
_side_vbox_pad[0] = _side_vbox_pad[1] = 0;
|
||||
|
||||
pack_start (_visibility_checkbutton_box, false, false);
|
||||
|
||||
_side_vbox[0].pack_start (*Gtk::manage (new Gtk::Label ("")));
|
||||
_overall_hbox.pack_start (_side_vbox[0], false, false);
|
||||
_scrolled_window.set_policy (Gtk::POLICY_ALWAYS, Gtk::POLICY_NEVER);
|
||||
_scrolled_window.set_shadow_type (Gtk::SHADOW_NONE);
|
||||
Gtk::VBox* b = new Gtk::VBox;
|
||||
b->pack_start (_column_labels, false, false);
|
||||
b->pack_start (_port_group_hbox, false, false);
|
||||
Gtk::Alignment* a = new Gtk::Alignment (0, 1, 0, 0);
|
||||
a->add (*Gtk::manage (b));
|
||||
_scrolled_window.add (*Gtk::manage (a));
|
||||
_overall_hbox.pack_start (_scrolled_window);
|
||||
_side_vbox[1].pack_start (*Gtk::manage (new Gtk::Label ("")));
|
||||
_overall_hbox.pack_start (_side_vbox[1]);
|
||||
pack_start (_overall_hbox);
|
||||
|
||||
_port_group_hbox.signal_size_allocate().connect (sigc::hide (sigc::mem_fun (*this, &IOSelector::setup_dimensions)));
|
||||
}
|
||||
|
||||
PortMatrix::~PortMatrix ()
|
||||
{
|
||||
clear ();
|
||||
}
|
||||
|
||||
/** Clear out the things that change when the number of source or destination ports changes */
|
||||
void
|
||||
PortMatrix::clear ()
|
||||
{
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
|
||||
for (std::vector<Gtk::EventBox*>::iterator j = _row_labels[i].begin(); j != _row_labels[i].end(); ++j) {
|
||||
delete *j;
|
||||
}
|
||||
_row_labels[i].clear ();
|
||||
|
||||
if (_row_labels_vbox[i]) {
|
||||
_side_vbox[i].remove (*_row_labels_vbox[i]);
|
||||
}
|
||||
delete _row_labels_vbox[i];
|
||||
_row_labels_vbox[i] = 0;
|
||||
|
||||
if (_side_vbox_pad[i]) {
|
||||
_side_vbox[i].remove (*_side_vbox_pad[i]);
|
||||
}
|
||||
delete _side_vbox_pad[i];
|
||||
_side_vbox_pad[i] = 0;
|
||||
}
|
||||
|
||||
for (std::vector<PortGroupUI*>::iterator i = _port_group_ui.begin(); i != _port_group_ui.end(); ++i) {
|
||||
_port_group_hbox.remove ((*i)->get_table());
|
||||
_visibility_checkbutton_box.remove ((*i)->get_visibility_checkbutton());
|
||||
delete *i;
|
||||
}
|
||||
|
||||
_port_group_ui.clear ();
|
||||
}
|
||||
|
||||
|
||||
/** Set up dimensions of some of our widgets which depend on other dimensions
|
||||
* within the dialogue.
|
||||
*/
|
||||
void
|
||||
PortMatrix::setup_dimensions ()
|
||||
{
|
||||
/* Get some dimensions from various places */
|
||||
int const scrollbar_height = _scrolled_window.get_hscrollbar()->get_height();
|
||||
|
||||
std::pair<int, int> unit_size (0, 0);
|
||||
int port_group_tables_height = 0;
|
||||
for (std::vector<PortGroupUI*>::iterator i = _port_group_ui.begin(); i != _port_group_ui.end(); ++i) {
|
||||
std::pair<int, int> const u = (*i)->unit_size ();
|
||||
unit_size.first = std::max (unit_size.first, u.first);
|
||||
unit_size.second = std::max (unit_size.second, u.second);
|
||||
port_group_tables_height = std::max (
|
||||
port_group_tables_height, (*i)->get_table().get_height()
|
||||
);
|
||||
}
|
||||
|
||||
/* Column labels */
|
||||
_column_labels.set_base_width (_port_group_list.n_visible_ports () * unit_size.first);
|
||||
|
||||
/* Scrolled window */
|
||||
/* XXX: really shouldn't set a minimum horizontal size here, but if we don't
|
||||
the window starts up very small */
|
||||
_scrolled_window.set_size_request (
|
||||
std::min (_column_labels.get_width(), 640),
|
||||
_column_labels.get_height() + port_group_tables_height + scrollbar_height + 16
|
||||
);
|
||||
|
||||
/* Row labels */
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
for (std::vector<Gtk::EventBox*>::iterator j = _row_labels[i].begin(); j != _row_labels[i].end(); ++j) {
|
||||
(*j)->get_child()->set_size_request (-1, unit_size.second);
|
||||
}
|
||||
|
||||
if (_side_vbox_pad[i]) {
|
||||
_side_vbox_pad[i]->set_size_request (-1, scrollbar_height + unit_size.second / 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Set up the dialogue */
|
||||
void
|
||||
PortMatrix::setup ()
|
||||
{
|
||||
clear ();
|
||||
|
||||
int const rows = n_rows ();
|
||||
|
||||
/* Row labels */
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
_row_labels_vbox[i] = new Gtk::VBox;
|
||||
int const run_rows = std::max (1, rows);
|
||||
for (int j = 0; j < run_rows; ++j) {
|
||||
Gtk::Label* label = new Gtk::Label (rows == 0 ? "Quim" : row_name (j));
|
||||
Gtk::EventBox* b = new Gtk::EventBox;
|
||||
b->set_events (Gdk::BUTTON_PRESS_MASK);
|
||||
b->signal_button_press_event().connect (sigc::bind (sigc::mem_fun (*this, &IOSelector::row_label_button_pressed), j));
|
||||
b->add (*Gtk::manage (label));
|
||||
_row_labels[i].push_back (b);
|
||||
_row_labels_vbox[i]->pack_start (*b, false, false);
|
||||
}
|
||||
|
||||
_side_vbox[i].pack_start (*_row_labels_vbox[i], false, false);
|
||||
_side_vbox_pad[i] = new Gtk::Label ("");
|
||||
_side_vbox[i].pack_start (*_side_vbox_pad[i], false, false);
|
||||
}
|
||||
|
||||
/* Checkbutton tables and visibility checkbuttons */
|
||||
int n = 0;
|
||||
for (PortGroupList::iterator i = _port_group_list.begin(); i != _port_group_list.end(); ++i) {
|
||||
PortGroupUI* t = new PortGroupUI (*this, **i);
|
||||
|
||||
/* XXX: this is a bit of a hack; should probably use a configurable colour here */
|
||||
Gdk::Color alt_bg = get_style()->get_bg (Gtk::STATE_NORMAL);
|
||||
alt_bg.set_rgb (alt_bg.get_red() + 4096, alt_bg.get_green() + 4096, alt_bg.get_blue () + 4096);
|
||||
if ((n % 2) == 0) {
|
||||
t->get_table().modify_bg (Gtk::STATE_NORMAL, alt_bg);
|
||||
}
|
||||
|
||||
_port_group_ui.push_back (t);
|
||||
_port_group_hbox.pack_start (t->get_table(), false, false);
|
||||
|
||||
_visibility_checkbutton_box.pack_start (t->get_visibility_checkbutton(), false, false);
|
||||
++n;
|
||||
}
|
||||
|
||||
show_all ();
|
||||
|
||||
for (std::vector<PortGroupUI*>::iterator i = _port_group_ui.begin(); i != _port_group_ui.end(); ++i) {
|
||||
(*i)->setup_visibility ();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
PortMatrix::redisplay ()
|
||||
{
|
||||
_port_group_list.refresh ();
|
||||
setup ();
|
||||
}
|
||||
|
||||
|
||||
/** Handle a button press on a row label */
|
||||
bool
|
||||
PortMatrix::row_label_button_pressed (GdkEventButton* e, int r)
|
||||
{
|
||||
if (e->type != GDK_BUTTON_PRESS || e->button != 3) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Gtk::Menu* menu = Gtk::manage (new Gtk::Menu);
|
||||
Gtk::Menu_Helpers::MenuList& items = menu->items ();
|
||||
menu->set_name ("ArdourContextMenu");
|
||||
|
||||
bool const can_add = maximum_rows () > n_rows ();
|
||||
bool const can_remove = minimum_rows () < n_rows ();
|
||||
std::string const name = row_name (r);
|
||||
|
||||
items.push_back (
|
||||
Gtk::Menu_Helpers::MenuElem (string_compose(_("Add %1"), row_descriptor()), sigc::mem_fun (*this, &PortMatrix::add_row))
|
||||
);
|
||||
|
||||
items.back().set_sensitive (can_add);
|
||||
|
||||
items.push_back (
|
||||
Gtk::Menu_Helpers::MenuElem (string_compose(_("Remove %1 \"%2\""), row_descriptor(), name), sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_row), r))
|
||||
);
|
||||
|
||||
items.back().set_sensitive (can_remove);
|
||||
|
||||
menu->popup (e->button, e->time);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
PortMatrix::set_type (ARDOUR::DataType t)
|
||||
{
|
||||
_type = t;
|
||||
_port_group_list.set_type (t);
|
||||
redisplay ();
|
||||
}
|
||||
|
||||
void
|
||||
PortMatrix::set_offer_inputs (bool i)
|
||||
{
|
||||
_offer_inputs = i;
|
||||
_port_group_list.set_offer_inputs (i);
|
||||
redisplay ();
|
||||
}
|
||||
|
||||
/** PortGroupList constructor.
|
||||
* @param session Session to get ports from.
|
||||
* @param type Type of ports to offer (audio or MIDI)
|
||||
* @param offer_inputs true to offer output ports, otherwise false.
|
||||
* @param mask Mask of groups to make visible by default.
|
||||
*/
|
||||
|
||||
PortGroupList::PortGroupList (ARDOUR::Session & session, ARDOUR::DataType type, bool offer_inputs, Mask mask)
|
||||
: _session (session), _type (type), _offer_inputs (offer_inputs),
|
||||
buss (_("Buss"), "ardour:", mask & BUSS),
|
||||
track (_("Track"), "ardour:", mask & TRACK),
|
||||
system (_("System"), "system:", mask & SYSTEM),
|
||||
other (_("Other"), "", mask & OTHER)
|
||||
{
|
||||
refresh ();
|
||||
}
|
||||
|
||||
void
|
||||
PortGroupList::refresh ()
|
||||
{
|
||||
clear ();
|
||||
|
||||
buss.ports.clear ();
|
||||
track.ports.clear ();
|
||||
system.ports.clear ();
|
||||
other.ports.clear ();
|
||||
|
||||
/* Find the ports provided by ardour; we can't derive their type just from their
|
||||
names, so we'll have to be more devious. */
|
||||
|
||||
boost::shared_ptr<ARDOUR::Session::RouteList> routes = _session.get_routes ();
|
||||
|
||||
for (ARDOUR::Session::RouteList::const_iterator i = routes->begin(); i != routes->end(); ++i) {
|
||||
|
||||
PortGroup* g = 0;
|
||||
if (_type == ARDOUR::DataType::AUDIO && dynamic_cast<ARDOUR::AudioTrack*> ((*i).get())) {
|
||||
/* Audio track for an audio IO */
|
||||
g = &track;
|
||||
} else if (_type == ARDOUR::DataType::MIDI && dynamic_cast<ARDOUR::MidiTrack*> ((*i).get())) {
|
||||
/* Midi track for a MIDI IO */
|
||||
g = &track;
|
||||
} else if (_type == ARDOUR::DataType::AUDIO && dynamic_cast<ARDOUR::MidiTrack*> ((*i).get()) == 0) {
|
||||
/* Non-MIDI track for an Audio IO; must be an audio buss */
|
||||
g = &buss;
|
||||
}
|
||||
|
||||
if (g) {
|
||||
ARDOUR::PortSet const & p = _offer_inputs ? ((*i)->inputs()) : ((*i)->outputs());
|
||||
for (uint32_t j = 0; j < p.num_ports(); ++j) {
|
||||
g->add (p.port(j)->name ());
|
||||
}
|
||||
|
||||
std::sort (g->ports.begin(), g->ports.end());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* XXX: inserts, sends, plugin inserts? */
|
||||
|
||||
/* Now we need to find the non-ardour ports; we do this by first
|
||||
finding all the ports that we can connect to. */
|
||||
const char **ports = _session.engine().get_ports (
|
||||
"", _type.to_jack_type(), _offer_inputs ? JackPortIsInput : JackPortIsOutput
|
||||
);
|
||||
|
||||
if (ports) {
|
||||
|
||||
int n = 0;
|
||||
while (ports[n]) {
|
||||
std::string const p = ports[n];
|
||||
|
||||
if (p.substr(0, strlen ("system:")) == "system:") {
|
||||
/* system: prefix */
|
||||
system.add (p);
|
||||
} else {
|
||||
if (p.substr(0, strlen("ardour:")) != "ardour:") {
|
||||
/* other (non-ardour) prefix */
|
||||
other.add (p);
|
||||
}
|
||||
}
|
||||
|
||||
++n;
|
||||
}
|
||||
}
|
||||
|
||||
push_back (&buss);
|
||||
push_back (&track);
|
||||
push_back (&system);
|
||||
push_back (&other);
|
||||
}
|
||||
|
||||
int
|
||||
PortGroupList::n_visible_ports () const
|
||||
{
|
||||
int n = 0;
|
||||
|
||||
for (const_iterator i = begin(); i != end(); ++i) {
|
||||
if ((*i)->visible) {
|
||||
n += (*i)->ports.size();
|
||||
}
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
std::string
|
||||
PortGroupList::get_port_by_index (int n, bool with_prefix) const
|
||||
{
|
||||
/* XXX: slightly inefficient algorithm */
|
||||
|
||||
for (const_iterator i = begin(); i != end(); ++i) {
|
||||
for (std::vector<std::string>::const_iterator j = (*i)->ports.begin(); j != (*i)->ports.end(); ++j) {
|
||||
if (n == 0) {
|
||||
if (with_prefix) {
|
||||
return (*i)->prefix + *j;
|
||||
} else {
|
||||
return *j;
|
||||
}
|
||||
}
|
||||
--n;
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
void
|
||||
PortGroupList::set_type (ARDOUR::DataType t)
|
||||
{
|
||||
_type = t;
|
||||
}
|
||||
|
||||
void
|
||||
PortGroupList::set_offer_inputs (bool i)
|
||||
{
|
||||
_offer_inputs = i;
|
||||
}
|
||||
|
||||
200
gtk2_ardour/port_matrix.h
Normal file
200
gtk2_ardour/port_matrix.h
Normal file
@@ -0,0 +1,200 @@
|
||||
/*
|
||||
Copyright (C) 2002-2007 Paul Davis
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
|
||||
*/
|
||||
|
||||
#ifndef __ardour_ui_port_matrix_h__
|
||||
#define __ardour_ui_port_matrix_h__
|
||||
|
||||
#include <gtkmm/box.h>
|
||||
#include <gtkmm/checkbutton.h>
|
||||
#include <gtkmm/table.h>
|
||||
#include <gtkmm/frame.h>
|
||||
#include <gtkmm/eventbox.h>
|
||||
#include <gtkmm/scrolledwindow.h>
|
||||
|
||||
#include "ardour_dialog.h"
|
||||
|
||||
namespace ARDOUR {
|
||||
class Session;
|
||||
class IO;
|
||||
class PortInsert;
|
||||
}
|
||||
|
||||
class PortMatrix;
|
||||
|
||||
/// A list of port names, grouped by some aspect of their type e.g. busses, tracks, system
|
||||
class PortGroup
|
||||
{
|
||||
public:
|
||||
/** PortGroup constructor.
|
||||
* @param n Name.
|
||||
* @param p Port name prefix.
|
||||
* @param v true if group should be visible in the UI, otherwise false.
|
||||
*/
|
||||
PortGroup (std::string const & n, std::string const & p, bool v) : name (n), prefix (p), visible (v) {}
|
||||
|
||||
void add (std::string const & p);
|
||||
|
||||
std::string name; ///< name for the group
|
||||
std::string prefix; ///< prefix (before colon) e.g. "ardour:"
|
||||
std::vector<std::string> ports; ///< port names
|
||||
bool visible; ///< true if the group is visible in the UI
|
||||
};
|
||||
|
||||
/// The UI for a PortGroup
|
||||
class PortGroupUI
|
||||
{
|
||||
public:
|
||||
PortGroupUI (PortMatrix&, PortGroup&);
|
||||
|
||||
Gtk::Widget& get_table ();
|
||||
Gtk::Widget& get_visibility_checkbutton ();
|
||||
std::pair<int, int> unit_size () const;
|
||||
PortGroup& port_group () { return _port_group; }
|
||||
void setup_visibility ();
|
||||
|
||||
private:
|
||||
void port_checkbutton_toggled (Gtk::CheckButton*, int, int);
|
||||
void visibility_checkbutton_toggled ();
|
||||
|
||||
PortMatrix& _port_matrix; ///< the PortMatrix that we are working for
|
||||
PortGroup& _port_group; ///< the PortGroup that we are representing
|
||||
bool _ignore_check_button_toggle;
|
||||
Gtk::Table _table;
|
||||
Gtk::EventBox _table_box;
|
||||
std::vector<std::vector<Gtk::CheckButton* > > _port_checkbuttons;
|
||||
Gtk::CheckButton _visibility_checkbutton;
|
||||
};
|
||||
|
||||
/// A list of PortGroups
|
||||
class PortGroupList : public std::list<PortGroup*>
|
||||
{
|
||||
public:
|
||||
enum Mask {
|
||||
BUSS = 0x1,
|
||||
TRACK = 0x2,
|
||||
SYSTEM = 0x4,
|
||||
OTHER = 0x8
|
||||
};
|
||||
|
||||
PortGroupList (ARDOUR::Session &, ARDOUR::DataType, bool, Mask);
|
||||
|
||||
void refresh ();
|
||||
int n_visible_ports () const;
|
||||
std::string get_port_by_index (int, bool with_prefix = true) const;
|
||||
void set_type (ARDOUR::DataType);
|
||||
void set_offer_inputs (bool);
|
||||
|
||||
private:
|
||||
ARDOUR::Session& _session;
|
||||
ARDOUR::DataType _type;
|
||||
bool _offer_inputs;
|
||||
|
||||
PortGroup buss;
|
||||
PortGroup track;
|
||||
PortGroup system;
|
||||
PortGroup other;
|
||||
};
|
||||
|
||||
|
||||
/// A widget which provides a set of rotated text labels
|
||||
class RotatedLabelSet : public Gtk::Widget {
|
||||
public:
|
||||
RotatedLabelSet (PortGroupList&);
|
||||
virtual ~RotatedLabelSet ();
|
||||
|
||||
void set_angle (int);
|
||||
void set_base_width (int);
|
||||
void update_visibility ();
|
||||
|
||||
protected:
|
||||
virtual void on_size_request (Gtk::Requisition*);
|
||||
virtual void on_size_allocate (Gtk::Allocation&);
|
||||
virtual void on_realize ();
|
||||
virtual void on_unrealize ();
|
||||
virtual bool on_expose_event (GdkEventExpose*);
|
||||
|
||||
Glib::RefPtr<Gdk::Window> _gdk_window;
|
||||
|
||||
private:
|
||||
std::pair<int, int> setup_layout (std::string const &);
|
||||
|
||||
PortGroupList& _port_group_list; ///< list of ports to display
|
||||
int _angle_degrees; ///< label rotation angle in degrees
|
||||
double _angle_radians; ///< label rotation angle in radians
|
||||
int _base_width; ///< width of labels; see set_base_width() for more details
|
||||
Glib::RefPtr<Pango::Context> _pango_context;
|
||||
Glib::RefPtr<Pango::Layout> _pango_layout;
|
||||
Glib::RefPtr<Gdk::GC> _gc;
|
||||
Gdk::Color _fg_colour;
|
||||
Gdk::Color _bg_colour;
|
||||
};
|
||||
|
||||
|
||||
class PortMatrix : public Gtk::VBox {
|
||||
public:
|
||||
PortMatrix (ARDOUR::Session&, ARDOUR::DataType, bool, PortGroupList::Mask);
|
||||
~PortMatrix ();
|
||||
|
||||
void redisplay ();
|
||||
|
||||
enum Result {
|
||||
Cancelled,
|
||||
Accepted
|
||||
};
|
||||
|
||||
sigc::signal<void, Result> Finished;
|
||||
|
||||
void set_type (ARDOUR::DataType);
|
||||
void set_offer_inputs (bool);
|
||||
|
||||
virtual void set_state (int, std::string const &, bool) = 0;
|
||||
virtual bool get_state (int, std::string const &) const = 0;
|
||||
virtual uint32_t n_rows () const = 0;
|
||||
virtual uint32_t maximum_rows () const = 0;
|
||||
virtual uint32_t minimum_rows () const = 0;
|
||||
virtual std::string row_name (int) const = 0;
|
||||
virtual void add_row () = 0;
|
||||
virtual void remove_row (int) = 0;
|
||||
virtual std::string row_descriptor () const = 0;
|
||||
|
||||
protected:
|
||||
|
||||
bool _offer_inputs;
|
||||
|
||||
private:
|
||||
void setup ();
|
||||
void clear ();
|
||||
void setup_dimensions ();
|
||||
bool row_label_button_pressed (GdkEventButton*, int);
|
||||
|
||||
PortGroupList _port_group_list;
|
||||
ARDOUR::DataType _type;
|
||||
std::vector<PortGroupUI*> _port_group_ui;
|
||||
std::vector<Gtk::EventBox*> _row_labels[2];
|
||||
Gtk::VBox* _row_labels_vbox[2];
|
||||
RotatedLabelSet _column_labels;
|
||||
Gtk::HBox _overall_hbox;
|
||||
Gtk::VBox _side_vbox[2];
|
||||
Gtk::HBox _port_group_hbox;
|
||||
Gtk::ScrolledWindow _scrolled_window;
|
||||
Gtk::Label* _side_vbox_pad[2];
|
||||
Gtk::HBox _visibility_checkbutton_box;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
50
libs/ardour/ardour/auto_bundle.h
Normal file
50
libs/ardour/ardour/auto_bundle.h
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
Copyright (C) 2007 Paul Davis
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
|
||||
*/
|
||||
|
||||
#ifndef __ardour_auto_bundle_h__
|
||||
#define __ardour_auto_bundle_h__
|
||||
|
||||
#include <vector>
|
||||
#include <glibmm/thread.h>
|
||||
#include "ardour/bundle.h"
|
||||
|
||||
namespace ARDOUR {
|
||||
|
||||
class AutoBundle : public Bundle {
|
||||
|
||||
public:
|
||||
AutoBundle (bool i = true);
|
||||
AutoBundle (std::string const &, bool i = true);
|
||||
|
||||
uint32_t nchannels () const;
|
||||
const PortList& channel_ports (uint32_t) const;
|
||||
|
||||
void set_channels (uint32_t);
|
||||
void set_port (uint32_t, std::string const &);
|
||||
|
||||
private:
|
||||
/// mutex for _ports;
|
||||
/// XXX: is this necessary?
|
||||
mutable Glib::Mutex _ports_mutex;
|
||||
std::vector<PortList> _ports;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* __ardour_auto_bundle_h__ */
|
||||
@@ -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 <vector>
|
||||
#include <string>
|
||||
#include <sigc++/signal.h>
|
||||
#include <glibmm/thread.h>
|
||||
#include <pbd/stateful.h>
|
||||
|
||||
using std::vector;
|
||||
using std::string;
|
||||
#include "ardour/data_type.h"
|
||||
|
||||
namespace ARDOUR {
|
||||
|
||||
typedef std::vector<std::string> PortList;
|
||||
|
||||
/**
|
||||
* A set of `channels', each of which is associated with 0 or more
|
||||
* JACK ports.
|
||||
* A set of `channels', each of which is associated with 0 or more JACK ports.
|
||||
*/
|
||||
|
||||
class Bundle : public PBD::Stateful, public sigc::trackable {
|
||||
class Bundle {
|
||||
public:
|
||||
/**
|
||||
* Bundle constructor.
|
||||
* @param name Name for this Bundle.
|
||||
* @param dy true if this Bundle is `dynamic', ie it is created on-the-fly
|
||||
* and should not be written to the session file.
|
||||
*/
|
||||
Bundle (string name, bool dy = false) : _name (name), _dynamic(dy) {}
|
||||
~Bundle() {}
|
||||
|
||||
/// A vector of JACK port names
|
||||
typedef vector<string> PortList;
|
||||
|
||||
void set_name (string name, void *src);
|
||||
|
||||
/**
|
||||
* @return name of this Bundle.
|
||||
*/
|
||||
string name() const { return _name; }
|
||||
|
||||
/**
|
||||
* @return true if this Bundle is marked as `dynamic', meaning
|
||||
* that it won't be written to the session file.
|
||||
*/
|
||||
bool dynamic() const { return _dynamic; }
|
||||
Bundle () : _type (DataType::AUDIO) {}
|
||||
Bundle (bool i) : _type (DataType::AUDIO), _ports_are_inputs (i) {}
|
||||
Bundle (std::string const & n, bool i = true) : _name (n), _type (DataType::AUDIO), _ports_are_inputs (i) {}
|
||||
virtual ~Bundle() {}
|
||||
|
||||
/**
|
||||
* @return Number of channels that this Bundle has.
|
||||
*/
|
||||
uint32_t nchannels () const { return _channels.size(); }
|
||||
const PortList& channel_ports (int ch) const;
|
||||
virtual uint32_t nchannels () const = 0;
|
||||
virtual const PortList& channel_ports (uint32_t) const = 0;
|
||||
|
||||
void set_nchannels (int n);
|
||||
void set_name (std::string const & n) {
|
||||
_name = n;
|
||||
NameChanged ();
|
||||
}
|
||||
|
||||
std::string name () const { return _name; }
|
||||
|
||||
void add_port_to_channel (int ch, string portname);
|
||||
void remove_port_from_channel (int ch, string portname);
|
||||
sigc::signal<void> NameChanged;
|
||||
|
||||
/// Our name changed
|
||||
sigc::signal<void, void*> NameChanged;
|
||||
/// The number of channels changed
|
||||
sigc::signal<void> ConfigurationChanged;
|
||||
/// The ports associated with one of our channels changed
|
||||
sigc::signal<void, int> PortsChanged;
|
||||
void set_type (DataType t) { _type = t; }
|
||||
DataType type () const { return _type; }
|
||||
|
||||
bool operator==(const Bundle& other) const;
|
||||
|
||||
XMLNode& get_state (void);
|
||||
int set_state (const XMLNode&);
|
||||
|
||||
protected:
|
||||
Bundle (const XMLNode&);
|
||||
void set_ports_are_inputs () { _ports_are_inputs = true; }
|
||||
void set_ports_are_outputs () { _ports_are_inputs = false; }
|
||||
bool ports_are_inputs () const { return _ports_are_inputs; }
|
||||
bool ports_are_outputs () const { return !_ports_are_inputs; }
|
||||
|
||||
private:
|
||||
mutable Glib::Mutex channels_lock; ///< mutex for _channels
|
||||
vector<PortList> _channels; ///< list of JACK ports associated with each of our channels
|
||||
string _name; ///< name
|
||||
bool _dynamic; ///< true if `dynamic', ie not to be written to the session file
|
||||
|
||||
int set_channels (const string& str);
|
||||
int parse_io_string (const string& str, vector<string>& ports);
|
||||
};
|
||||
|
||||
/**
|
||||
* Bundle in which the JACK ports are inputs.
|
||||
*/
|
||||
|
||||
class InputBundle : public Bundle {
|
||||
public:
|
||||
/**
|
||||
* InputBundle constructor.
|
||||
* \param name Name.
|
||||
* \param dy true if this Bundle is `dynamic'; ie it is created on-the-fly
|
||||
* and should not be written to the session file.
|
||||
*/
|
||||
InputBundle (string name, bool dy = false) : Bundle (name, dy) {}
|
||||
InputBundle (const XMLNode&);
|
||||
};
|
||||
|
||||
/**
|
||||
* Bundle in which the JACK ports are outputs.
|
||||
*/
|
||||
|
||||
class OutputBundle : public Bundle {
|
||||
public:
|
||||
/**
|
||||
* OutputBundle constructor.
|
||||
* \param name Name.
|
||||
* \param dy true if this Bundle is `dynamic'; ie it is created on-the-fly
|
||||
* and should not be written to the session file.
|
||||
*/
|
||||
OutputBundle (string name, bool dy = false) : Bundle (name, dy) {}
|
||||
OutputBundle (const XMLNode&);
|
||||
std::string _name;
|
||||
ARDOUR::DataType _type;
|
||||
bool _ports_are_inputs;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* __ardour_bundle_h__ */
|
||||
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
#define __ardour_data_type_h__
|
||||
|
||||
#include <string>
|
||||
#include <ardour/data_type.h>
|
||||
#include <jack/jack.h>
|
||||
|
||||
namespace ARDOUR {
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
#include <ardour/chan_count.h>
|
||||
#include <ardour/latent.h>
|
||||
#include <ardour/automation_control.h>
|
||||
#include <ardour/user_bundle.h>
|
||||
|
||||
using std::string;
|
||||
using std::vector;
|
||||
@@ -54,6 +55,7 @@ namespace ARDOUR {
|
||||
class Session;
|
||||
class AudioEngine;
|
||||
class Bundle;
|
||||
class AutoBundle;
|
||||
class Panner;
|
||||
class PeakMeter;
|
||||
class Port;
|
||||
@@ -123,9 +125,12 @@ class IO : public Automatable, public Latent
|
||||
int connect_input_ports_to_bundle (boost::shared_ptr<Bundle>, void *src);
|
||||
int connect_output_ports_to_bundle (boost::shared_ptr<Bundle>, void *src);
|
||||
|
||||
boost::shared_ptr<Bundle> input_bundle();
|
||||
boost::shared_ptr<Bundle> output_bundle();
|
||||
std::vector<boost::shared_ptr<Bundle> > bundles_connected_to_inputs ();
|
||||
std::vector<boost::shared_ptr<Bundle> > bundles_connected_to_outputs ();
|
||||
|
||||
boost::shared_ptr<AutoBundle> bundle_for_inputs () { return _bundle_for_inputs; }
|
||||
boost::shared_ptr<AutoBundle> bundle_for_outputs () { return _bundle_for_outputs; }
|
||||
|
||||
int add_input_port (string source, void *src, DataType type = DataType::NIL);
|
||||
int add_output_port (string destination, void *src, DataType type = DataType::NIL);
|
||||
|
||||
@@ -179,9 +184,6 @@ class IO : public Automatable, public Latent
|
||||
|
||||
void attach_buffers(ChanCount ignored);
|
||||
|
||||
boost::shared_ptr<Bundle> bundle_for_inputs () const { return _bundle_for_inputs; }
|
||||
boost::shared_ptr<Bundle> bundle_for_outputs () const { return _bundle_for_outputs; }
|
||||
|
||||
sigc::signal<void,IOChange,void*> input_changed;
|
||||
sigc::signal<void,IOChange,void*> output_changed;
|
||||
|
||||
@@ -272,8 +274,6 @@ class IO : public Automatable, public Latent
|
||||
PortSet _outputs;
|
||||
PortSet _inputs;
|
||||
PeakMeter* _meter;
|
||||
boost::shared_ptr<Bundle> _input_bundle; ///< bundle connected to our inputs
|
||||
boost::shared_ptr<Bundle> _output_bundle; ///< bundle connected to our outputs
|
||||
bool no_panner_reset;
|
||||
bool _phase_invert;
|
||||
bool _denormal_protection;
|
||||
@@ -310,13 +310,6 @@ class IO : public Automatable, public Latent
|
||||
|
||||
friend class Send;
|
||||
|
||||
/* are these the best variable names ever, or what? */
|
||||
|
||||
sigc::connection input_bundle_configuration_connection;
|
||||
sigc::connection output_bundle_configuration_connection;
|
||||
sigc::connection input_bundle_connection_connection;
|
||||
sigc::connection output_bundle_connection_connection;
|
||||
|
||||
static bool panners_legal;
|
||||
|
||||
int connecting_became_legal ();
|
||||
@@ -330,8 +323,21 @@ class IO : public Automatable, public Latent
|
||||
ChanCount _output_minimum; ///< minimum number of output channels (0 for no minimum)
|
||||
ChanCount _output_maximum; ///< maximum number of output channels (ChanCount::INFINITE for no maximum)
|
||||
|
||||
boost::shared_ptr<Bundle> _bundle_for_inputs;
|
||||
boost::shared_ptr<Bundle> _bundle_for_outputs;
|
||||
boost::shared_ptr<AutoBundle> _bundle_for_inputs; ///< a bundle representing our inputs
|
||||
boost::shared_ptr<AutoBundle> _bundle_for_outputs; ///< a bundle representing our outputs
|
||||
|
||||
struct UserBundleInfo {
|
||||
UserBundleInfo (IO*, boost::shared_ptr<UserBundle> b);
|
||||
|
||||
boost::shared_ptr<UserBundle> bundle;
|
||||
sigc::connection configuration_will_change;
|
||||
sigc::connection configuration_has_changed;
|
||||
sigc::connection ports_will_change;
|
||||
sigc::connection ports_have_changed;
|
||||
};
|
||||
|
||||
std::vector<UserBundleInfo> _bundles_connected_to_outputs; ///< user bundles connected to our outputs
|
||||
std::vector<UserBundleInfo> _bundles_connected_to_inputs; ///< user bundles connected to our inputs
|
||||
|
||||
static int parse_io_string (const string&, vector<string>& chns);
|
||||
|
||||
@@ -343,13 +349,14 @@ class IO : public Automatable, public Latent
|
||||
int ensure_inputs (ChanCount, bool clear, bool lockit, void *src);
|
||||
int ensure_outputs (ChanCount, bool clear, bool lockit, void *src);
|
||||
|
||||
void drop_input_bundle ();
|
||||
void drop_output_bundle ();
|
||||
void check_bundles_connected_to_inputs ();
|
||||
void check_bundles_connected_to_outputs ();
|
||||
void check_bundles (std::vector<UserBundleInfo>&, const PortSet&);
|
||||
|
||||
void input_bundle_configuration_changed ();
|
||||
void input_bundle_connection_changed (int);
|
||||
void output_bundle_configuration_changed ();
|
||||
void output_bundle_connection_changed (int);
|
||||
void bundle_configuration_will_change ();
|
||||
void bundle_configuration_has_changed ();
|
||||
void bundle_ports_will_change (int);
|
||||
void bundle_ports_have_changed (int);
|
||||
|
||||
int create_ports (const XMLNode&);
|
||||
int make_connections (const XMLNode&);
|
||||
@@ -363,8 +370,11 @@ class IO : public Automatable, public Latent
|
||||
int32_t find_input_port_hole ();
|
||||
int32_t find_output_port_hole ();
|
||||
|
||||
void create_bundles ();
|
||||
void setup_bundles ();
|
||||
void create_bundles_for_inputs_and_outputs ();
|
||||
void setup_bundles_for_inputs_and_outputs ();
|
||||
|
||||
void maybe_add_input_bundle_to_list (boost::shared_ptr<Bundle>, std::vector<boost::shared_ptr<Bundle> >*);
|
||||
void maybe_add_output_bundle_to_list (boost::shared_ptr<Bundle>, std::vector<boost::shared_ptr<Bundle> >*);
|
||||
};
|
||||
|
||||
} // namespace ARDOUR
|
||||
|
||||
@@ -489,7 +489,8 @@ class Session : public PBD::StatefulDestructible
|
||||
|
||||
void set_remote_control_ids();
|
||||
|
||||
AudioEngine &engine() { return _engine; };
|
||||
AudioEngine & engine() { return _engine; }
|
||||
AudioEngine const & engine () const { return _engine; }
|
||||
|
||||
int32_t max_level;
|
||||
int32_t min_level;
|
||||
@@ -716,7 +717,6 @@ class Session : public PBD::StatefulDestructible
|
||||
void add_bundle (boost::shared_ptr<Bundle>);
|
||||
void remove_bundle (boost::shared_ptr<Bundle>);
|
||||
boost::shared_ptr<Bundle> bundle_by_name (string) const;
|
||||
boost::shared_ptr<Bundle> bundle_by_ports (vector<string> const &) const;
|
||||
|
||||
sigc::signal<void,boost::shared_ptr<Bundle> > BundleAdded;
|
||||
sigc::signal<void,boost::shared_ptr<Bundle> > BundleRemoved;
|
||||
@@ -1564,7 +1564,8 @@ class Session : public PBD::StatefulDestructible
|
||||
typedef list<boost::shared_ptr<Bundle> > BundleList;
|
||||
mutable Glib::Mutex bundle_lock;
|
||||
BundleList _bundles;
|
||||
int load_bundles (const XMLNode&);
|
||||
XMLNode* _bundle_xml_node;
|
||||
int load_bundles (XMLNode const &);
|
||||
|
||||
void reverse_diskstream_buffers ();
|
||||
|
||||
|
||||
72
libs/ardour/ardour/user_bundle.h
Normal file
72
libs/ardour/ardour/user_bundle.h
Normal file
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
Copyright (C) 2007 Paul Davis
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
|
||||
*/
|
||||
|
||||
#ifndef __ardour_user_bundle_h__
|
||||
#define __ardour_user_bundle_h__
|
||||
|
||||
#include <vector>
|
||||
#include <glibmm/thread.h>
|
||||
#include "pbd/stateful.h"
|
||||
#include "ardour/bundle.h"
|
||||
|
||||
namespace ARDOUR {
|
||||
|
||||
class Session;
|
||||
|
||||
class UserBundle : public Bundle, public PBD::Stateful {
|
||||
|
||||
public:
|
||||
UserBundle (std::string const &);
|
||||
UserBundle (XMLNode const &, bool);
|
||||
|
||||
uint32_t nchannels () const;
|
||||
const ARDOUR::PortList& channel_ports (uint32_t) const;
|
||||
|
||||
void add_channel ();
|
||||
void set_channels (uint32_t);
|
||||
void remove_channel (uint32_t);
|
||||
void add_port_to_channel (uint32_t, std::string const &);
|
||||
void remove_port_from_channel (uint32_t, std::string const &);
|
||||
bool port_attached_to_channel (uint32_t, std::string const &) const;
|
||||
XMLNode& get_state ();
|
||||
|
||||
/// The number of channels is about to change
|
||||
sigc::signal<void> ConfigurationWillChange;
|
||||
/// The number of channels has changed
|
||||
sigc::signal<void> ConfigurationHasChanged;
|
||||
/// The port set associated with one of our channels is about to change
|
||||
/// Parameter is the channel number
|
||||
sigc::signal<void, int> PortsWillChange;
|
||||
/// The port set associated with one of our channels has changed
|
||||
/// Parameter is the channel number
|
||||
sigc::signal<void, int> PortsHaveChanged;
|
||||
|
||||
private:
|
||||
|
||||
int set_state (const XMLNode &);
|
||||
|
||||
/// mutex for _ports;
|
||||
/// XXX: is this necessary?
|
||||
mutable Glib::Mutex _ports_mutex;
|
||||
std::vector<PortList> _ports;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -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);
|
||||
|
||||
|
||||
47
libs/ardour/auto_bundle.cc
Normal file
47
libs/ardour/auto_bundle.cc
Normal file
@@ -0,0 +1,47 @@
|
||||
#include <cassert>
|
||||
#include "ardour/auto_bundle.h"
|
||||
|
||||
ARDOUR::AutoBundle::AutoBundle (bool i)
|
||||
: Bundle (i)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
ARDOUR::AutoBundle::AutoBundle (std::string const & n, bool i)
|
||||
: Bundle (n, i)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
uint32_t
|
||||
ARDOUR::AutoBundle::nchannels () const
|
||||
{
|
||||
Glib::Mutex::Lock lm (_ports_mutex);
|
||||
return _ports.size ();
|
||||
}
|
||||
|
||||
const ARDOUR::PortList&
|
||||
ARDOUR::AutoBundle::channel_ports (uint32_t c) const
|
||||
{
|
||||
assert (c < nchannels());
|
||||
|
||||
Glib::Mutex::Lock lm (_ports_mutex);
|
||||
return _ports[c];
|
||||
}
|
||||
|
||||
void
|
||||
ARDOUR::AutoBundle::set_channels (uint32_t n)
|
||||
{
|
||||
Glib::Mutex::Lock lm (_ports_mutex);
|
||||
_ports.resize (n);
|
||||
}
|
||||
|
||||
void
|
||||
ARDOUR::AutoBundle::set_port (uint32_t c, std::string const & p)
|
||||
{
|
||||
assert (c < nchannels ());
|
||||
|
||||
Glib::Mutex::Lock lm (_ports_mutex);
|
||||
_ports[c].resize (1);
|
||||
_ports[c][0] = p;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -62,7 +62,7 @@
|
||||
#include <ardour/processor.h>
|
||||
#include <ardour/plugin_insert.h>
|
||||
#include <ardour/port_insert.h>
|
||||
#include <ardour/bundle.h>
|
||||
#include <ardour/auto_bundle.h>
|
||||
#include <ardour/slave.h>
|
||||
#include <ardour/tempo.h>
|
||||
#include <ardour/audio_track.h>
|
||||
@@ -123,6 +123,7 @@ Session::Session (AudioEngine &eng,
|
||||
diskstreams (new DiskstreamList),
|
||||
routes (new RouteList),
|
||||
auditioner ((Auditioner*) 0),
|
||||
_bundle_xml_node (0),
|
||||
_click_io ((IO*) 0),
|
||||
main_outs (0)
|
||||
{
|
||||
@@ -223,6 +224,7 @@ Session::Session (AudioEngine &eng,
|
||||
_send_smpte_update (false),
|
||||
diskstreams (new DiskstreamList),
|
||||
routes (new RouteList),
|
||||
_bundle_xml_node (0),
|
||||
main_outs (0)
|
||||
|
||||
{
|
||||
@@ -599,22 +601,22 @@ Session::when_engine_running ()
|
||||
char buf[32];
|
||||
snprintf (buf, sizeof (buf), _("out %" PRIu32), np+1);
|
||||
|
||||
shared_ptr<Bundle> c (new InputBundle (buf, true));
|
||||
c->set_nchannels (1);
|
||||
c->add_port_to_channel (0, _engine.get_nth_physical_output (DataType::AUDIO, np));
|
||||
shared_ptr<AutoBundle> c (new AutoBundle (buf, true));
|
||||
c->set_channels (1);
|
||||
c->set_port (0, _engine.get_nth_physical_output (DataType::AUDIO, np));
|
||||
|
||||
add_bundle (c);
|
||||
add_bundle (c);
|
||||
}
|
||||
|
||||
for (uint32_t np = 0; np < n_physical_inputs; ++np) {
|
||||
char buf[32];
|
||||
snprintf (buf, sizeof (buf), _("in %" PRIu32), np+1);
|
||||
|
||||
shared_ptr<Bundle> c (new OutputBundle (buf, true));
|
||||
c->set_nchannels (1);
|
||||
c->add_port_to_channel (0, _engine.get_nth_physical_input (DataType::AUDIO, np));
|
||||
shared_ptr<AutoBundle> c (new AutoBundle (buf, false));
|
||||
c->set_channels (1);
|
||||
c->set_port (0, _engine.get_nth_physical_input (DataType::AUDIO, np));
|
||||
|
||||
add_bundle (c);
|
||||
add_bundle (c);
|
||||
}
|
||||
|
||||
/* TWO: STEREO */
|
||||
@@ -623,24 +625,24 @@ Session::when_engine_running ()
|
||||
char buf[32];
|
||||
snprintf (buf, sizeof (buf), _("out %" PRIu32 "+%" PRIu32), np+1, np+2);
|
||||
|
||||
shared_ptr<Bundle> c (new InputBundle (buf, true));
|
||||
c->set_nchannels (2);
|
||||
c->add_port_to_channel (0, _engine.get_nth_physical_output (DataType::AUDIO, np));
|
||||
c->add_port_to_channel (1, _engine.get_nth_physical_output (DataType::AUDIO, np+1));
|
||||
shared_ptr<AutoBundle> c (new AutoBundle (buf, true));
|
||||
c->set_channels (2);
|
||||
c->set_port (0, _engine.get_nth_physical_output (DataType::AUDIO, np));
|
||||
c->set_port (1, _engine.get_nth_physical_output (DataType::AUDIO, np + 1));
|
||||
|
||||
add_bundle (c);
|
||||
add_bundle (c);
|
||||
}
|
||||
|
||||
for (uint32_t np = 0; np < n_physical_inputs; np +=2) {
|
||||
char buf[32];
|
||||
snprintf (buf, sizeof (buf), _("in %" PRIu32 "+%" PRIu32), np+1, np+2);
|
||||
|
||||
shared_ptr<Bundle> c (new OutputBundle (buf, true));
|
||||
c->set_nchannels (2);
|
||||
c->add_port_to_channel (0, _engine.get_nth_physical_input (DataType::AUDIO, np));
|
||||
c->add_port_to_channel (1, _engine.get_nth_physical_input (DataType::AUDIO, np+1));
|
||||
shared_ptr<AutoBundle> c (new AutoBundle (buf, false));
|
||||
c->set_channels (2);
|
||||
c->set_port (0, _engine.get_nth_physical_input (DataType::AUDIO, np));
|
||||
c->set_port (1, _engine.get_nth_physical_input (DataType::AUDIO, np + 1));
|
||||
|
||||
add_bundle (c);
|
||||
add_bundle (c);
|
||||
}
|
||||
|
||||
/* THREE MASTER */
|
||||
@@ -685,13 +687,13 @@ Session::when_engine_running ()
|
||||
|
||||
}
|
||||
|
||||
shared_ptr<Bundle> c (new OutputBundle (_("Master Out"), true));
|
||||
shared_ptr<AutoBundle> c (new AutoBundle (_("Master Out"), true));
|
||||
|
||||
c->set_nchannels (_master_out->n_inputs().n_total());
|
||||
for (uint32_t n = 0; n < _master_out->n_inputs ().n_total(); ++n) {
|
||||
c->add_port_to_channel ((int) n, _master_out->input(n)->name());
|
||||
}
|
||||
add_bundle (c);
|
||||
c->set_channels (_master_out->n_inputs().n_total());
|
||||
for (uint32_t n = 0; n < _master_out->n_inputs ().n_total(); ++n) {
|
||||
c->set_port (n, _master_out->input(n)->name());
|
||||
}
|
||||
add_bundle (c);
|
||||
}
|
||||
|
||||
hookup_io ();
|
||||
@@ -802,7 +804,13 @@ Session::hookup_io ()
|
||||
for (RouteList::iterator x = r->begin(); x != r->end(); ++x) {
|
||||
(*x)->set_control_outs (cports);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* load bundles, which we may have postponed earlier on */
|
||||
if (_bundle_xml_node) {
|
||||
load_bundles (*_bundle_xml_node);
|
||||
delete _bundle_xml_node;
|
||||
}
|
||||
|
||||
/* Tell all IO objects to connect themselves together */
|
||||
|
||||
@@ -3739,35 +3747,6 @@ Session::bundle_by_name (string name) const
|
||||
return boost::shared_ptr<Bundle> ();
|
||||
}
|
||||
|
||||
boost::shared_ptr<Bundle>
|
||||
Session::bundle_by_ports (std::vector<std::string> const & wanted_ports) const
|
||||
{
|
||||
Glib::Mutex::Lock lm (bundle_lock);
|
||||
|
||||
for (BundleList::const_iterator i = _bundles.begin(); i != _bundles.end(); ++i) {
|
||||
if ((*i)->nchannels() != wanted_ports.size()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bool match = true;
|
||||
for (uint32_t j = 0; j < (*i)->nchannels(); ++j) {
|
||||
Bundle::PortList const p = (*i)->channel_ports (j);
|
||||
if (p.empty() || p[0] != wanted_ports[j]) {
|
||||
/* not this bundle */
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (match) {
|
||||
/* matched bundle */
|
||||
return *i;
|
||||
}
|
||||
}
|
||||
|
||||
return boost::shared_ptr<Bundle> ();
|
||||
}
|
||||
|
||||
void
|
||||
Session::tempo_map_changed (Change ignored)
|
||||
{
|
||||
|
||||
@@ -953,12 +953,13 @@ Session::state(bool full_state)
|
||||
node->add_child_nocopy (loc.get_state());
|
||||
}
|
||||
|
||||
child = node->add_child ("Connections");
|
||||
child = node->add_child ("Bundles");
|
||||
{
|
||||
Glib::Mutex::Lock lm (bundle_lock);
|
||||
for (BundleList::iterator i = _bundles.begin(); i != _bundles.end(); ++i) {
|
||||
if (!(*i)->dynamic()) {
|
||||
child->add_child_nocopy ((*i)->get_state());
|
||||
boost::shared_ptr<UserBundle> b = boost::dynamic_pointer_cast<UserBundle> (*i);
|
||||
if (b) {
|
||||
child->add_child_nocopy (b->get_state());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1197,13 +1198,16 @@ Session::set_state (const XMLNode& node)
|
||||
goto out;
|
||||
}
|
||||
|
||||
if ((child = find_named_node (node, "Connections")) == 0) {
|
||||
error << _("Session: XML state has no connections section") << endmsg;
|
||||
goto out;
|
||||
} else if (load_bundles (*child)) {
|
||||
if ((child = find_named_node (node, "Bundles")) == 0) {
|
||||
error << _("Session: XML state has no bundles section") << endmsg;
|
||||
goto out;
|
||||
} else {
|
||||
/* We can't load Bundles yet as they need to be able
|
||||
to convert from port names to Port objects, which can't happen until
|
||||
later */
|
||||
_bundle_xml_node = new XMLNode (*child);
|
||||
}
|
||||
|
||||
|
||||
if ((child = find_named_node (node, "EditGroups")) == 0) {
|
||||
error << _("Session: XML state has no edit groups section") << endmsg;
|
||||
goto out;
|
||||
@@ -1237,7 +1241,7 @@ Session::set_state (const XMLNode& node)
|
||||
} else if (_click_io) {
|
||||
_click_io->set_state (*child);
|
||||
}
|
||||
|
||||
|
||||
if ((child = find_named_node (node, "ControlProtocols")) != 0) {
|
||||
ControlProtocolManager::instance().set_protocol_states (*child);
|
||||
}
|
||||
@@ -1876,23 +1880,23 @@ Session::automation_dir () const
|
||||
}
|
||||
|
||||
int
|
||||
Session::load_bundles (const XMLNode& node)
|
||||
Session::load_bundles (XMLNode const & node)
|
||||
{
|
||||
XMLNodeList nlist = node.children();
|
||||
XMLNodeConstIterator niter;
|
||||
XMLNodeList nlist = node.children();
|
||||
XMLNodeConstIterator niter;
|
||||
|
||||
set_dirty();
|
||||
set_dirty();
|
||||
|
||||
for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
|
||||
if ((*niter)->name() == "InputConnection") {
|
||||
add_bundle (boost::shared_ptr<Bundle> (new InputBundle (**niter)));
|
||||
} else if ((*niter)->name() == "OutputConnection") {
|
||||
add_bundle (boost::shared_ptr<Bundle> (new OutputBundle (**niter)));
|
||||
} else {
|
||||
error << string_compose(_("Unknown node \"%1\" found in Connections list from state file"), (*niter)->name()) << endmsg;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
|
||||
if ((*niter)->name() == "InputBundle") {
|
||||
add_bundle (boost::shared_ptr<UserBundle> (new UserBundle (**niter, true)));
|
||||
} else if ((*niter)->name() == "OutputBundle") {
|
||||
add_bundle (boost::shared_ptr<UserBundle> (new UserBundle (**niter, false)));
|
||||
} else {
|
||||
error << string_compose(_("Unknown node \"%1\" found in Bundles list from state file"), (*niter)->name()) << endmsg;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
198
libs/ardour/user_bundle.cc
Normal file
198
libs/ardour/user_bundle.cc
Normal file
@@ -0,0 +1,198 @@
|
||||
#include <cassert>
|
||||
#include <pbd/failed_constructor.h>
|
||||
#include <pbd/compose.h>
|
||||
#include <pbd/xml++.h>
|
||||
#include "ardour/user_bundle.h"
|
||||
#include "ardour/port_set.h"
|
||||
#include "ardour/io.h"
|
||||
#include "ardour/session.h"
|
||||
#include "ardour/audioengine.h"
|
||||
#include "i18n.h"
|
||||
|
||||
ARDOUR::UserBundle::UserBundle (std::string const & n)
|
||||
: Bundle (n)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
ARDOUR::UserBundle::UserBundle (XMLNode const & x, bool i)
|
||||
: Bundle (i)
|
||||
{
|
||||
if (set_state (x)) {
|
||||
throw failed_constructor ();
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t
|
||||
ARDOUR::UserBundle::nchannels () const
|
||||
{
|
||||
Glib::Mutex::Lock lm (_ports_mutex);
|
||||
return _ports.size ();
|
||||
}
|
||||
|
||||
const ARDOUR::PortList&
|
||||
ARDOUR::UserBundle::channel_ports (uint32_t n) const
|
||||
{
|
||||
assert (n < nchannels ());
|
||||
|
||||
Glib::Mutex::Lock lm (_ports_mutex);
|
||||
return _ports[n];
|
||||
}
|
||||
|
||||
void
|
||||
ARDOUR::UserBundle::add_port_to_channel (uint32_t c, std::string const & p)
|
||||
{
|
||||
assert (c < nchannels ());
|
||||
|
||||
PortsWillChange (c);
|
||||
|
||||
{
|
||||
Glib::Mutex::Lock lm (_ports_mutex);
|
||||
_ports[c].push_back (p);
|
||||
}
|
||||
|
||||
PortsHaveChanged (c);
|
||||
}
|
||||
|
||||
void
|
||||
ARDOUR::UserBundle::remove_port_from_channel (uint32_t c, std::string const & p)
|
||||
{
|
||||
assert (c < nchannels ());
|
||||
|
||||
PortsWillChange (c);
|
||||
|
||||
{
|
||||
Glib::Mutex::Lock lm (_ports_mutex);
|
||||
PortList::iterator i = std::find (_ports[c].begin(), _ports[c].end(), p);
|
||||
if (i != _ports[c].end()) {
|
||||
_ports[c].erase (i);
|
||||
}
|
||||
}
|
||||
|
||||
PortsHaveChanged (c);
|
||||
}
|
||||
|
||||
bool
|
||||
ARDOUR::UserBundle::port_attached_to_channel (uint32_t c, std::string const & p) const
|
||||
{
|
||||
assert (c < nchannels ());
|
||||
|
||||
Glib::Mutex::Lock lm (_ports_mutex);
|
||||
return std::find (_ports[c].begin(), _ports[c].end(), p) != _ports[c].end();
|
||||
}
|
||||
|
||||
void
|
||||
ARDOUR::UserBundle::add_channel ()
|
||||
{
|
||||
ConfigurationWillChange ();
|
||||
|
||||
{
|
||||
Glib::Mutex::Lock lm (_ports_mutex);
|
||||
_ports.resize (_ports.size() + 1);
|
||||
}
|
||||
|
||||
ConfigurationHasChanged ();
|
||||
}
|
||||
|
||||
void
|
||||
ARDOUR::UserBundle::set_channels (uint32_t n)
|
||||
{
|
||||
ConfigurationWillChange ();
|
||||
|
||||
{
|
||||
Glib::Mutex::Lock lm (_ports_mutex);
|
||||
_ports.resize (n);
|
||||
}
|
||||
|
||||
ConfigurationHasChanged ();
|
||||
}
|
||||
|
||||
void
|
||||
ARDOUR::UserBundle::remove_channel (uint32_t r)
|
||||
{
|
||||
assert (r < nchannels ());
|
||||
|
||||
ConfigurationWillChange ();
|
||||
|
||||
{
|
||||
Glib::Mutex::Lock lm (_ports_mutex);
|
||||
_ports.erase (_ports.begin() + r, _ports.begin() + r + 1);
|
||||
}
|
||||
|
||||
ConfigurationHasChanged ();
|
||||
}
|
||||
|
||||
int
|
||||
ARDOUR::UserBundle::set_state (XMLNode const & node)
|
||||
{
|
||||
XMLProperty const * name;
|
||||
|
||||
if ((name = node.property ("name")) == 0) {
|
||||
PBD::error << _("Node for Bundle has no \"name\" property") << endmsg;
|
||||
return -1;
|
||||
}
|
||||
|
||||
set_name (name->value ());
|
||||
|
||||
XMLNodeList const channels = node.children ();
|
||||
|
||||
int n = 0;
|
||||
for (XMLNodeConstIterator i = channels.begin(); i != channels.end(); ++i) {
|
||||
|
||||
if ((*i)->name() != "Channel") {
|
||||
PBD::error << string_compose (_("Unknown node \"%s\" in Bundle"), (*i)->name()) << endmsg;
|
||||
return -1;
|
||||
}
|
||||
|
||||
add_channel ();
|
||||
|
||||
XMLNodeList const ports = (*i)->children ();
|
||||
|
||||
for (XMLNodeConstIterator j = ports.begin(); j != ports.end(); ++j) {
|
||||
if ((*j)->name() != "Port") {
|
||||
PBD::error << string_compose (_("Unknown node \"%s\" in Bundle"), (*j)->name()) << endmsg;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ((name = (*j)->property ("name")) == 0) {
|
||||
PBD::error << _("Node for Port has no \"name\" property") << endmsg;
|
||||
return -1;
|
||||
}
|
||||
|
||||
add_port_to_channel (n, name->value ());
|
||||
}
|
||||
|
||||
++n;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
XMLNode&
|
||||
ARDOUR::UserBundle::get_state ()
|
||||
{
|
||||
XMLNode *node;
|
||||
|
||||
if (ports_are_inputs ()) {
|
||||
node = new XMLNode ("InputBundle");
|
||||
} else {
|
||||
node = new XMLNode ("OutputBundle");
|
||||
}
|
||||
|
||||
node->add_property ("name", name ());
|
||||
|
||||
for (std::vector<PortList>::iterator i = _ports.begin(); i != _ports.end(); ++i) {
|
||||
|
||||
XMLNode* c = new XMLNode ("Channel");
|
||||
|
||||
for (PortList::iterator j = i->begin(); j != i->end(); ++j) {
|
||||
XMLNode* p = new XMLNode ("Port");
|
||||
p->add_property ("name", *j);
|
||||
c->add_child_nocopy (*p);
|
||||
}
|
||||
|
||||
node->add_child_nocopy (*c);
|
||||
}
|
||||
|
||||
return *node;
|
||||
}
|
||||
Reference in New Issue
Block a user