diff options
Diffstat (limited to 'gtk2_ardour')
-rw-r--r-- | gtk2_ardour/SConscript | 2 | ||||
-rw-r--r-- | gtk2_ardour/ardour.menus | 1 | ||||
-rw-r--r-- | gtk2_ardour/ardour_ui.cc | 2 | ||||
-rw-r--r-- | gtk2_ardour/ardour_ui.h | 5 | ||||
-rw-r--r-- | gtk2_ardour/ardour_ui_dialogs.cc | 28 | ||||
-rw-r--r-- | gtk2_ardour/ardour_ui_ed.cc | 2 | ||||
-rw-r--r-- | gtk2_ardour/bundle_manager.cc | 335 | ||||
-rw-r--r-- | gtk2_ardour/bundle_manager.h | 107 | ||||
-rw-r--r-- | gtk2_ardour/io_selector.cc | 747 | ||||
-rw-r--r-- | gtk2_ardour/io_selector.h | 149 | ||||
-rw-r--r-- | gtk2_ardour/mixer_strip.cc | 61 | ||||
-rw-r--r-- | gtk2_ardour/mixer_strip.h | 4 | ||||
-rw-r--r-- | gtk2_ardour/port_matrix.cc | 730 | ||||
-rw-r--r-- | gtk2_ardour/port_matrix.h | 200 |
14 files changed, 1529 insertions, 844 deletions
diff --git a/gtk2_ardour/SConscript b/gtk2_ardour/SConscript index 61d653bed7..f1309b8c00 100644 --- a/gtk2_ardour/SConscript +++ b/gtk2_ardour/SConscript @@ -115,6 +115,7 @@ automation_time_axis.cc automation_streamview.cc automation_controller.cc automation_region_view.cc +bundle_manager.cc midi_port_dialog.cc midi_time_axis.cc midi_streamview.cc @@ -166,6 +167,7 @@ ghostregion.cc gtk-custom-hruler.c gtk-custom-ruler.c io_selector.cc +port_matrix.cc keyboard.cc keyeditor.cc ladspa_pluginui.cc diff --git a/gtk2_ardour/ardour.menus b/gtk2_ardour/ardour.menus index 871aa15aee..6f11572151 100644 --- a/gtk2_ardour/ardour.menus +++ b/gtk2_ardour/ardour.menus @@ -237,6 +237,7 @@ <menuitem action='ToggleKeyEditor'/> <menuitem action='ToggleThemeManager'/> <menuitem action='ToggleBigClock'/> + <menuitem action='ToggleBundleManager'/> <separator/> </menu> <menu name='Options' action='Options'> diff --git a/gtk2_ardour/ardour_ui.cc b/gtk2_ardour/ardour_ui.cc index 491939bd97..1182f39792 100644 --- a/gtk2_ardour/ardour_ui.cc +++ b/gtk2_ardour/ardour_ui.cc @@ -85,7 +85,7 @@ #include "utils.h" #include "gui_thread.h" #include "theme_manager.h" - +#include "bundle_manager.h" #include "i18n.h" diff --git a/gtk2_ardour/ardour_ui.h b/gtk2_ardour/ardour_ui.h index 9061f789a1..74400396af 100644 --- a/gtk2_ardour/ardour_ui.h +++ b/gtk2_ardour/ardour_ui.h @@ -78,6 +78,7 @@ class AddRouteDialog; class NewSessionDialog; class LocationUI; class ThemeManager; +class BundleManager; namespace Gtkmm2ext { class TearOff; @@ -158,6 +159,7 @@ class ARDOUR_UI : public Gtkmm2ext::UI void toggle_key_editor (); void toggle_location_window (); void toggle_theme_manager (); + void toggle_bundle_manager (); void toggle_big_clock_window (); void toggle_connection_editor (); void toggle_route_params_window (); @@ -612,6 +614,9 @@ class ARDOUR_UI : public Gtkmm2ext::UI RouteParams_UI *route_params; int create_route_params (); + BundleManager *bundle_manager; + void create_bundle_manager (); + ConnectionEditor *connection_editor; int create_connection_editor (); diff --git a/gtk2_ardour/ardour_ui_dialogs.cc b/gtk2_ardour/ardour_ui_dialogs.cc index 89ab470d9b..34104016d7 100644 --- a/gtk2_ardour/ardour_ui_dialogs.cc +++ b/gtk2_ardour/ardour_ui_dialogs.cc @@ -35,6 +35,7 @@ #include "route_params_ui.h" #include "sfdb_ui.h" #include "theme_manager.h" +#include "bundle_manager.h" #include "keyeditor.h" #include "i18n.h" @@ -363,6 +364,33 @@ ARDOUR_UI::toggle_theme_manager () } } +void +ARDOUR_UI::create_bundle_manager () +{ + if (bundle_manager == 0) { + bundle_manager = new BundleManager (*session); + bundle_manager->signal_unmap().connect (sigc::bind (sigc::ptr_fun (&ActionManager::uncheck_toggleaction), X_("<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 () { diff --git a/gtk2_ardour/ardour_ui_ed.cc b/gtk2_ardour/ardour_ui_ed.cc index 8ba5e3790a..74b5a70738 100644 --- a/gtk2_ardour/ardour_ui_ed.cc +++ b/gtk2_ardour/ardour_ui_ed.cc @@ -219,6 +219,8 @@ ARDOUR_UI::install_actions () ActionManager::register_action (common_actions, X_("About"), _("About"), mem_fun(*this, &ARDOUR_UI::show_splash)); ActionManager::register_toggle_action (common_actions, X_("ToggleThemeManager"), _("Theme Manager"), mem_fun(*this, &ARDOUR_UI::toggle_theme_manager)); + ActionManager::register_toggle_action (common_actions, X_("ToggleBundleManager"), _("Bundle Manager"), mem_fun(*this, &ARDOUR_UI::toggle_bundle_manager)); + ActionManager::register_toggle_action (common_actions, X_("ToggleKeyEditor"), _("Keybindings"), mem_fun(*this, &ARDOUR_UI::toggle_key_editor)); Glib::RefPtr<ActionGroup> transport_actions = ActionGroup::create (X_("Transport")); diff --git a/gtk2_ardour/bundle_manager.cc b/gtk2_ardour/bundle_manager.cc new file mode 100644 index 0000000000..4dacce782f --- /dev/null +++ b/gtk2_ardour/bundle_manager.cc @@ -0,0 +1,335 @@ +/* + Copyright (C) 2007 Paul Davis + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include <gtkmm/stock.h> +#include <gtkmm/button.h> +#include <gtkmm/label.h> +#include <gtkmm/entry.h> +#include <gtkmm/table.h> +#include <gtkmm/comboboxtext.h> +#include <gtkmm/alignment.h> +#include "ardour/session.h" +#include "ardour/user_bundle.h" +#include "ardour/audioengine.h" +#include "bundle_manager.h" +#include "i18n.h" + +BundleEditorMatrix::BundleEditorMatrix ( + ARDOUR::Session& session, boost::shared_ptr<ARDOUR::Bundle> bundle + ) + : PortMatrix ( + session, bundle->type(), bundle->ports_are_inputs(), + PortGroupList::Mask (PortGroupList::SYSTEM | PortGroupList::OTHER) + ) +{ + _bundle = boost::dynamic_pointer_cast<ARDOUR::UserBundle> (bundle); + assert (_bundle != 0); +} + +void +BundleEditorMatrix::set_state (int r, std::string const & p, bool s) +{ + if (s) { + _bundle->add_port_to_channel (r, p); + } else { + _bundle->remove_port_from_channel (r, p); + } +} + +bool +BundleEditorMatrix::get_state (int r, std::string const & p) const +{ + return _bundle->port_attached_to_channel (r, p); +} + +uint32_t +BundleEditorMatrix::n_rows () const +{ + return _bundle->nchannels (); +} + +uint32_t +BundleEditorMatrix::maximum_rows () const +{ + /* 65536 channels in a bundle ought to be enough for anyone (TM) */ + return 65536; +} + +uint32_t +BundleEditorMatrix::minimum_rows () const +{ + return 0; +} + +std::string +BundleEditorMatrix::row_name (int r) const +{ + std::stringstream s; + s << r; + return s.str(); +} + +void +BundleEditorMatrix::add_row () +{ + _bundle->add_channel (); + redisplay (); +} + +void +BundleEditorMatrix::remove_row (int r) +{ + _bundle->remove_channel (r); + redisplay (); +} + +std::string +BundleEditorMatrix::row_descriptor () const +{ + return _("channel"); +} + +BundleEditor::BundleEditor (ARDOUR::Session& session, boost::shared_ptr<ARDOUR::UserBundle> bundle, bool add) + : ArdourDialog (_("Edit Bundle")), _matrix (session, bundle), _bundle (bundle) +{ + Gtk::Table* t = new Gtk::Table (3, 2); + t->set_spacings (4); + + Gtk::Alignment* a = new Gtk::Alignment (1, 0.5, 0, 1); + a->add (*Gtk::manage (new Gtk::Label (_("Name:")))); + t->attach (*Gtk::manage (a), 0, 1, 0, 1, Gtk::FILL, Gtk::FILL); + t->attach (_name, 1, 2, 0, 1); + + _name.set_text (_bundle->name ()); + _name.signal_changed().connect (sigc::mem_fun (*this, &BundleEditor::name_changed)); + + a = new Gtk::Alignment (1, 0.5, 0, 1); + a->add (*Gtk::manage (new Gtk::Label (_("Direction:")))); + t->attach (*Gtk::manage (a), 0, 1, 1, 2, Gtk::FILL, Gtk::FILL); + a = new Gtk::Alignment (0, 0.5, 0, 1); + a->add (_input_or_output); + t->attach (*Gtk::manage (a), 1, 2, 1, 2); + + _input_or_output.append_text (_("Input")); + _input_or_output.append_text (_("Output")); + + if (bundle->ports_are_inputs()) { + _input_or_output.set_active_text (_("Output")); + } else { + _input_or_output.set_active_text (_("Input")); + } + + _input_or_output.signal_changed().connect (sigc::mem_fun (*this, &BundleEditor::input_or_output_changed)); + + a = new Gtk::Alignment (1, 0.5, 0, 1); + a->add (*Gtk::manage (new Gtk::Label (_("Type:")))); + t->attach (*Gtk::manage (a), 0, 1, 2, 3, Gtk::FILL, Gtk::FILL); + a = new Gtk::Alignment (0, 0.5, 0, 1); + a->add (_type); + t->attach (*Gtk::manage (a), 1, 2, 2, 3); + + _type.append_text (_("Audio")); + _type.append_text (_("MIDI")); + + switch (bundle->type ()) { + case ARDOUR::DataType::AUDIO: + _type.set_active_text (_("Audio")); + break; + case ARDOUR::DataType::MIDI: + _type.set_active_text (_("MIDI")); + break; + } + + _type.signal_changed().connect (sigc::mem_fun (*this, &BundleEditor::type_changed)); + + get_vbox()->pack_start (*Gtk::manage (t), false, false); + + get_vbox()->pack_start (_matrix); + + get_vbox()->set_spacing (4); + + if (add) { + add_button (Gtk::Stock::CANCEL, 1); + add_button (Gtk::Stock::ADD, 0); + } else { + add_button (Gtk::Stock::CLOSE, 0); + } + + show_all (); +} + +void +BundleEditor::name_changed () +{ + _bundle->set_name (_name.get_text ()); +} + +void +BundleEditor::input_or_output_changed () +{ + if (_input_or_output.get_active_text() == _("Output")) { + _bundle->set_ports_are_inputs (); + _matrix.set_offer_inputs (true); + } else { + _bundle->set_ports_are_outputs (); + _matrix.set_offer_inputs (false); + } +} + +void +BundleEditor::type_changed () +{ + ARDOUR::DataType const t = _type.get_active_text() == _("Audio") ? + ARDOUR::DataType::AUDIO : ARDOUR::DataType::MIDI; + + _bundle->set_type (t); + _matrix.set_type (t); +} + +void +BundleEditor::on_map () +{ + _matrix.redisplay (); + Window::on_map (); +} + + +BundleManager::BundleManager (ARDOUR::Session& session) + : ArdourDialog (_("Bundle manager")), _session (session), edit_button (_("Edit")), delete_button (_("Delete")) +{ + _list_model = Gtk::ListStore::create (_list_model_columns); + _tree_view.set_model (_list_model); + _tree_view.append_column (_("Name"), _list_model_columns.name); + _tree_view.set_headers_visible (false); + + _session.foreach_bundle (sigc::mem_fun (*this, &BundleManager::add_bundle)); + + /* New / Edit / Delete buttons */ + Gtk::VBox* buttons = new Gtk::VBox; + buttons->set_spacing (8); + Gtk::Button* b = new Gtk::Button (_("New")); + b->set_image (*Gtk::manage (new Gtk::Image (Gtk::Stock::NEW, Gtk::ICON_SIZE_BUTTON))); + b->signal_clicked().connect (sigc::mem_fun (*this, &BundleManager::new_clicked)); + buttons->pack_start (*Gtk::manage (b), false, false); + edit_button.set_image (*Gtk::manage (new Gtk::Image (Gtk::Stock::EDIT, Gtk::ICON_SIZE_BUTTON))); + edit_button.signal_clicked().connect (sigc::mem_fun (*this, &BundleManager::edit_clicked)); + buttons->pack_start (edit_button, false, false); + delete_button.set_image (*Gtk::manage (new Gtk::Image (Gtk::Stock::DELETE, Gtk::ICON_SIZE_BUTTON))); + delete_button.signal_clicked().connect (sigc::mem_fun (*this, &BundleManager::delete_clicked)); + buttons->pack_start (delete_button, false, false); + + Gtk::HBox* h = new Gtk::HBox; + h->set_spacing (8); + h->set_border_width (8); + h->pack_start (_tree_view); + h->pack_start (*Gtk::manage (buttons), false, false); + + get_vbox()->set_spacing (8); + get_vbox()->pack_start (*Gtk::manage (h)); + + set_default_size (480, 240); + + _tree_view.get_selection()->signal_changed().connect ( + sigc::mem_fun (*this, &BundleManager::set_button_sensitivity) + ); + + set_button_sensitivity (); + + show_all (); +} + +void +BundleManager::set_button_sensitivity () +{ + bool const sel = (_tree_view.get_selection()->get_selected() != 0); + edit_button.set_sensitive (sel); + delete_button.set_sensitive (sel); +} + + +void +BundleManager::new_clicked () +{ + boost::shared_ptr<ARDOUR::UserBundle> b (new ARDOUR::UserBundle ("")); + + /* Start off with a single channel */ + b->add_channel (); + + BundleEditor e (_session, b, true); + if (e.run () == 0) { + _session.add_bundle (b); + add_bundle (b); + } +} + +void +BundleManager::edit_clicked () +{ + Gtk::TreeModel::iterator i = _tree_view.get_selection()->get_selected(); + if (i) { + boost::shared_ptr<ARDOUR::UserBundle> b = (*i)[_list_model_columns.bundle]; + BundleEditor e (_session, b, false); + e.run (); + } + +} + +void +BundleManager::delete_clicked () +{ + Gtk::TreeModel::iterator i = _tree_view.get_selection()->get_selected(); + if (i) { + boost::shared_ptr<ARDOUR::UserBundle> b = (*i)[_list_model_columns.bundle]; + _session.remove_bundle (b); + _list_model->erase (i); + } +} + +void +BundleManager::add_bundle (boost::shared_ptr<ARDOUR::Bundle> b) +{ + boost::shared_ptr<ARDOUR::UserBundle> u = boost::dynamic_pointer_cast<ARDOUR::UserBundle> (b); + if (u == 0) { + return; + } + + Gtk::TreeModel::iterator i = _list_model->append (); + (*i)[_list_model_columns.name] = u->name (); + (*i)[_list_model_columns.bundle] = u; + + u->NameChanged.connect (sigc::bind (sigc::mem_fun (*this, &BundleManager::bundle_name_changed), u)); +} + +void +BundleManager::bundle_name_changed (boost::shared_ptr<ARDOUR::UserBundle> b) +{ + Gtk::TreeModel::iterator i = _list_model->children().begin (); + while (i != _list_model->children().end()) { + boost::shared_ptr<ARDOUR::UserBundle> t = (*i)[_list_model_columns.bundle]; + if (t == b) { + break; + } + ++i; + } + + if (i != _list_model->children().end()) { + (*i)[_list_model_columns.name] = b->name (); + } +} + diff --git a/gtk2_ardour/bundle_manager.h b/gtk2_ardour/bundle_manager.h new file mode 100644 index 0000000000..4d4d44074f --- /dev/null +++ b/gtk2_ardour/bundle_manager.h @@ -0,0 +1,107 @@ +/* + Copyright (C) 2007 Paul Davis + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#ifndef __ardour_ui_bundle_manager_h__ +#define __ardour_ui_bundle_manager_h__ + +#include <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 diff --git a/gtk2_ardour/io_selector.cc b/gtk2_ardour/io_selector.cc index d0f236f84a..b8598659d7 100644 --- a/gtk2_ardour/io_selector.cc +++ b/gtk2_ardour/io_selector.cc @@ -17,15 +17,6 @@ */ -#include <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,575 +32,116 @@ #include "gui_thread.h" #include "i18n.h" -/** Add a port to a group. - * @param p Port name, with or without prefix. - */ - -void -PortGroup::add (std::string const & p) -{ - if (prefix.empty() == false && p.substr (0, prefix.length()) == prefix) { - ports.push_back (p.substr (prefix.length())); - } else { - ports.push_back (p); - } -} - - -PortGroupTable::PortGroupTable ( - PortGroup& g, boost::shared_ptr<ARDOUR::IO> io, bool for_input - ) - : _port_group (g), _ignore_check_button_toggle (false), - _io (io), _for_input (for_input) +IOSelector::IOSelector (ARDOUR::Session& session, boost::shared_ptr<ARDOUR::IO> io, bool offer_inputs) + : PortMatrix ( + session, io->default_type(), offer_inputs, + PortGroupList::Mask (PortGroupList::BUSS | PortGroupList::SYSTEM | PortGroupList::OTHER) + ), + _io (io) { - ARDOUR::DataType const t = _io->default_type(); - - int rows; - if (_for_input) { - rows = _io->n_inputs().get(t); + /* Listen for ports changing on the IO */ + if (!offer_inputs) { + _io->input_changed.connect (mem_fun(*this, &IOSelector::ports_changed)); } else { - rows = _io->n_outputs().get(t); - } - - int const ports = _port_group.ports.size(); - - if (rows == 0 || ports == 0) { - return; - } - - /* Sort out the table and the checkbuttons inside it */ - - _table.resize (rows, ports); - _check_buttons.resize (rows); - for (int i = 0; i < rows; ++i) { - _check_buttons[i].resize (ports); - } - - for (int i = 0; i < rows; ++i) { - for (uint32_t j = 0; j < _port_group.ports.size(); ++j) { - Gtk::CheckButton* b = new Gtk::CheckButton; - b->signal_toggled().connect (sigc::bind (sigc::mem_fun (*this, &PortGroupTable::check_button_toggled), b, i, _port_group.prefix + _port_group.ports[j])); - _check_buttons[i][j] = b; - _table.attach (*b, j, j + 1, i, i + 1); - } - } - - _box.add (_table); - - _ignore_check_button_toggle = true; - - /* Set the state of the check boxes according to current connections */ - for (int i = 0; i < rows; ++i) { - const char **connections = _for_input ? _io->input(i)->get_connections() : _io->output(i)->get_connections(); - for (uint32_t j = 0; j < _port_group.ports.size(); ++j) { - - std::string const t = _port_group.prefix + _port_group.ports[j]; - int k = 0; - bool required_state = false; - - while (connections && connections[k]) { - if (std::string(connections[k]) == t) { - required_state = true; - break; - } - ++k; - } - - _check_buttons[i][j]->set_active (required_state); - } - } - - _ignore_check_button_toggle = false; -} - -/** @return Width and height of a single check button in a port group table */ -std::pair<int, int> -PortGroupTable::unit_size () const -{ - if (_check_buttons.empty() || _check_buttons[0].empty()) { - return std::pair<int, int> (0, 0); + _io->output_changed.connect (mem_fun(*this, &IOSelector::ports_changed)); } - - return std::make_pair ( - _check_buttons[0][0]->get_width() + _table.get_col_spacing (0), - _check_buttons[0][0]->get_height() + _table.get_row_spacing (0) - ); -} - -Gtk::Widget& -PortGroupTable::get_widget () -{ - return _box; -} - - -/** Handle a toggle of a check button */ -void -PortGroupTable::check_button_toggled (Gtk::CheckButton* b, int r, std::string const & p) -{ - if (_ignore_check_button_toggle) { - return; - } - - bool const new_state = b->get_active (); - - if (new_state) { - if (_for_input) { - _io->connect_input (_io->input(r), p, 0); - } else { - _io->connect_output (_io->output(r), p, 0); - } - } else { - if (_for_input) { - _io->disconnect_input (_io->input(r), p, 0); - } else { - _io->disconnect_output (_io->output(r), p, 0); - } - } -} - - -RotatedLabelSet::RotatedLabelSet (PortGroupList& g) - : Glib::ObjectBase ("RotatedLabelSet"), Gtk::Widget (), _port_group_list (g), _base_width (128) -{ - set_flags (Gtk::NO_WINDOW); - set_angle (30); -} - -RotatedLabelSet::~RotatedLabelSet () -{ - -} - - -/** Set the angle that the labels are drawn at. - * @param degrees New angle in degrees. - */ - -void -RotatedLabelSet::set_angle (int degrees) -{ - _angle_degrees = degrees; - _angle_radians = M_PI * _angle_degrees / 180; - - queue_resize (); } -void -RotatedLabelSet::on_size_request (Gtk::Requisition* requisition) -{ - *requisition = Gtk::Requisition (); - - if (_pango_layout == 0) { - return; - } - - /* Our height is the highest label */ - requisition->height = 0; - for (PortGroupList::const_iterator i = _port_group_list.begin(); i != _port_group_list.end(); ++i) { - for (std::vector<std::string>::const_iterator j = (*i)->ports.begin(); j != (*i)->ports.end(); ++j) { - std::pair<int, int> const d = setup_layout (*j); - if (d.second > requisition->height) { - requisition->height = d.second; - } - } - } - - /* And our width is the base plus the width of the last label */ - requisition->width = _base_width; - int const n = _port_group_list.n_visible_ports (); - if (n > 0) { - std::pair<int, int> const d = setup_layout (_port_group_list.get_port_by_index (n - 1, false)); - requisition->width += d.first; - } -} void -RotatedLabelSet::on_size_allocate (Gtk::Allocation& allocation) +IOSelector::ports_changed (ARDOUR::IOChange change, void *src) { - set_allocation (allocation); + ENSURE_GUI_THREAD (bind (mem_fun (*this, &IOSelector::ports_changed), change, src)); - if (_gdk_window) { - _gdk_window->move_resize ( - allocation.get_x(), allocation.get_y(), allocation.get_width(), allocation.get_height() - ); - } + redisplay (); } -void -RotatedLabelSet::on_realize () -{ - Gtk::Widget::on_realize (); - - Glib::RefPtr<Gtk::Style> style = get_style (); - - if (!_gdk_window) { - GdkWindowAttr attributes; - memset (&attributes, 0, sizeof (attributes)); - - Gtk::Allocation allocation = get_allocation (); - attributes.x = allocation.get_x (); - attributes.y = allocation.get_y (); - attributes.width = allocation.get_width (); - attributes.height = allocation.get_height (); - - attributes.event_mask = get_events () | Gdk::EXPOSURE_MASK; - attributes.window_type = GDK_WINDOW_CHILD; - attributes.wclass = GDK_INPUT_OUTPUT; - _gdk_window = Gdk::Window::create (get_window (), &attributes, GDK_WA_X | GDK_WA_Y); - unset_flags (Gtk::NO_WINDOW); - set_window (_gdk_window); - - _bg_colour = style->get_bg (Gtk::STATE_NORMAL ); - modify_bg (Gtk::STATE_NORMAL, _bg_colour); - _fg_colour = style->get_fg (Gtk::STATE_NORMAL); -; - _gdk_window->set_user_data (gobj ()); - - /* Set up Pango stuff */ - _pango_context = create_pango_context (); - - Pango::Matrix matrix = PANGO_MATRIX_INIT; - pango_matrix_rotate (&matrix, _angle_degrees); - _pango_context->set_matrix (matrix); - - _pango_layout = Pango::Layout::create (_pango_context); - _gc = Gdk::GC::create (get_window ()); - } -} void -RotatedLabelSet::on_unrealize() -{ - _gdk_window.clear (); - - Gtk::Widget::on_unrealize (); -} - - -/** Set up our Pango layout to plot a given string, and compute its dimensions once - * it has been rotated. - * @param s String to use. - * @return width and height of the rotated string, in pixels. - */ - -std::pair<int, int> -RotatedLabelSet::setup_layout (std::string const & s) +IOSelector::set_state (int r, std::string const & p, bool s) { - _pango_layout->set_text (s); - - /* Here's the unrotated size */ - int w; - int h; - _pango_layout->get_pixel_size (w, h); - - /* Rotate the width and height as appropriate. I thought Pango might be able - to do this for us, but I can't find out how... */ - std::pair<int, int> d; - d.first = int (w * cos (_angle_radians) - h * sin (_angle_radians)); - d.second = int (w * sin (_angle_radians) + h * cos (_angle_radians)); - - return d; + if (s) { + if (!_offer_inputs) { + _io->connect_input (_io->input(r), p, 0); + } else { + _io->connect_output (_io->output(r), p, 0); + } + } else { + if (!_offer_inputs) { + _io->disconnect_input (_io->input(r), p, 0); + } else { + _io->disconnect_output (_io->output(r), p, 0); + } + } } bool -RotatedLabelSet::on_expose_event (GdkEventExpose* event) +IOSelector::get_state (int r, std::string const & p) const { - if (!_gdk_window) { - return true; - } + const char **connections = _offer_inputs ? _io->output(r)->get_connections() : _io->input(r)->get_connections(); - int const height = get_allocation().get_height (); - double const spacing = double (_base_width) / _port_group_list.n_visible_ports(); - - /* Plot all the visible labels; really we should clip for efficiency */ - int n = 0; - for (PortGroupList::const_iterator i = _port_group_list.begin(); i != _port_group_list.end(); ++i) { - if ((*i)->visible) { - for (uint32_t j = 0; j < (*i)->ports.size(); ++j) { - std::pair<int, int> const d = setup_layout ((*i)->ports[j]); - get_window()->draw_layout (_gc, int ((n + 0.25) * spacing), height - d.second, _pango_layout, _fg_colour, _bg_colour); - ++n; - } + int k = 0; + while (connections && connections[k]) { + if (std::string (connections[k]) == p) { + return true; } - } - - return true; -} - -/** Set the `base width'. This is the width of the base of the label set, ie: - * - * L L L L - * E E E E - * B B B B - * A A A A - * L L L L - * <--w--> - */ - -void -RotatedLabelSet::set_base_width (int w) -{ - _base_width = w; - queue_resize (); -} - - -/** Construct an IOSelector. - * @param session Session to operate on. - * @param io IO to operate on. - * @param for_input true if the selector is for an input, otherwise false. - */ - -IOSelector::IOSelector (ARDOUR::Session& session, boost::shared_ptr<ARDOUR::IO> io, bool for_input) - : _port_group_list (session, io, for_input), _io (io), _for_input (for_input), - _column_labels (_port_group_list) -{ - _row_labels_vbox[0] = _row_labels_vbox[1] = 0; - _side_vbox_pad[0] = _side_vbox_pad[1] = 0; - - Gtk::HBox* c = new Gtk::HBox; - for (PortGroupList::iterator i = _port_group_list.begin(); i != _port_group_list.end(); ++i) { - Gtk::CheckButton* b = new Gtk::CheckButton ((*i)->name); - b->set_active (true); - b->signal_toggled().connect (sigc::bind (sigc::mem_fun (*this, &IOSelector::group_visible_toggled), b, (*i)->name)); - c->pack_start (*Gtk::manage (b), false, false); - } - pack_start (*Gtk::manage (c)); - - _side_vbox[0].pack_start (*Gtk::manage (new Gtk::Label (""))); - _overall_hbox.pack_start (_side_vbox[0], false, false); - _scrolled_window.set_policy (Gtk::POLICY_ALWAYS, Gtk::POLICY_NEVER); - _scrolled_window.set_shadow_type (Gtk::SHADOW_NONE); - Gtk::VBox* b = new Gtk::VBox; - b->pack_start (_column_labels, false, false); - b->pack_start (_port_group_hbox, false, false); - Gtk::Alignment* a = new Gtk::Alignment (0, 1, 0, 0); - a->add (*Gtk::manage (b)); - _scrolled_window.add (*Gtk::manage (a)); - _overall_hbox.pack_start (_scrolled_window); - _side_vbox[1].pack_start (*Gtk::manage (new Gtk::Label (""))); - _overall_hbox.pack_start (_side_vbox[1]); - pack_start (_overall_hbox); - - _port_group_hbox.signal_size_allocate().connect (sigc::hide (sigc::mem_fun (*this, &IOSelector::setup_dimensions))); - /* Listen for ports changing on the IO */ - if (_for_input) { - _io->input_changed.connect (mem_fun(*this, &IOSelector::ports_changed)); - } else { - _io->output_changed.connect (mem_fun(*this, &IOSelector::ports_changed)); + ++k; } - -} -IOSelector::~IOSelector () -{ - clear (); + return false; } -/** Clear out the things that change when the number of source or destination ports changes */ -void -IOSelector::clear () +uint32_t +IOSelector::n_rows () const { - for (int i = 0; i < 2; ++i) { - - for (std::vector<Gtk::EventBox*>::iterator j = _row_labels[i].begin(); j != _row_labels[i].end(); ++j) { - delete *j; - } - _row_labels[i].clear (); - - if (_row_labels_vbox[i]) { - _side_vbox[i].remove (*_row_labels_vbox[i]); - } - delete _row_labels_vbox[i]; - _row_labels_vbox[i] = 0; - - if (_side_vbox_pad[i]) { - _side_vbox[i].remove (*_side_vbox_pad[i]); - } - delete _side_vbox_pad[i]; - _side_vbox_pad[i] = 0; - } - - for (std::vector<PortGroupTable*>::iterator i = _port_group_tables.begin(); i != _port_group_tables.end(); ++i) { - _port_group_hbox.remove ((*i)->get_widget()); - delete *i; + if (!_offer_inputs) { + return _io->inputs().num_ports (_io->default_type()); + } else { + return _io->outputs().num_ports (_io->default_type()); } - - _port_group_tables.clear (); } - -/** Set up dimensions of some of our widgets which depend on other dimensions - * within the dialogue. - */ -void -IOSelector::setup_dimensions () +uint32_t +IOSelector::maximum_rows () const { - /* Get some dimensions from various places */ - int const scrollbar_height = _scrolled_window.get_hscrollbar()->get_height(); - - std::pair<int, int> unit_size (0, 0); - int port_group_tables_height = 0; - for (std::vector<PortGroupTable*>::iterator i = _port_group_tables.begin(); i != _port_group_tables.end(); ++i) { - std::pair<int, int> const u = (*i)->unit_size (); - unit_size.first = std::max (unit_size.first, u.first); - unit_size.second = std::max (unit_size.second, u.second); - port_group_tables_height = std::max ( - port_group_tables_height, (*i)->get_widget().get_height() - ); - } - - /* Column labels */ - _column_labels.set_base_width (_port_group_list.n_visible_ports () * unit_size.first); - - /* Scrolled window */ - /* XXX: really shouldn't set a minimum horizontal size here, but if we don't - the window starts up very small */ - _scrolled_window.set_size_request ( - std::min (_column_labels.get_width(), 640), - _column_labels.get_height() + port_group_tables_height + scrollbar_height + 16 - ); - - /* Row labels */ - for (int i = 0; i < 2; ++i) { - for (std::vector<Gtk::EventBox*>::iterator j = _row_labels[i].begin(); j != _row_labels[i].end(); ++j) { - (*j)->get_child()->set_size_request (-1, unit_size.second); - } - - if (_side_vbox_pad[i]) { - _side_vbox_pad[i]->set_size_request (-1, scrollbar_height + unit_size.second / 4); - } + if (!_offer_inputs) { + return _io->input_maximum ().get (_io->default_type()); + } else { + return _io->output_maximum ().get (_io->default_type()); } } -/** Set up the dialogue */ -void -IOSelector::setup () +uint32_t +IOSelector::minimum_rows () const { - clear (); - - /* Work out how many rows we have */ - ARDOUR::DataType const t = _io->default_type(); - - int rows; - if (_for_input) { - rows = _io->n_inputs().get(t); - } else { - rows = _io->n_outputs().get(t); - } - - /* Row labels */ - for (int i = 0; i < 2; ++i) { - _row_labels_vbox[i] = new Gtk::VBox; - for (int j = 0; j < rows; ++j) { - Gtk::Label* label = new Gtk::Label (_for_input ? _io->input(j)->name() : _io->output(j)->name()); - Gtk::EventBox* b = new Gtk::EventBox; - b->set_events (Gdk::BUTTON_PRESS_MASK); - b->signal_button_press_event().connect (sigc::bind (sigc::mem_fun (*this, &IOSelector::row_label_button_pressed), j)); - b->add (*Gtk::manage (label)); - _row_labels[i].push_back (b); - _row_labels_vbox[i]->pack_start (*b, false, false); - } - - _side_vbox[i].pack_start (*_row_labels_vbox[i], false, false); - _side_vbox_pad[i] = new Gtk::Label (""); - _side_vbox[i].pack_start (*_side_vbox_pad[i], false, false); + if (!_offer_inputs) { + return _io->input_minimum ().get (_io->default_type()); + } else { + return _io->output_minimum ().get (_io->default_type()); } - - /* Checkbutton tables */ - int n = 0; - for (PortGroupList::iterator i = _port_group_list.begin(); i != _port_group_list.end(); ++i) { - PortGroupTable* t = new PortGroupTable (**i, _io, _for_input); - - /* XXX: this is a bit of a hack; should probably use a configurable colour here */ - Gdk::Color alt_bg = get_style()->get_bg (Gtk::STATE_NORMAL); - alt_bg.set_rgb (alt_bg.get_red() + 4096, alt_bg.get_green() + 4096, alt_bg.get_blue () + 4096); - if ((n % 2) == 0) { - t->get_widget().modify_bg (Gtk::STATE_NORMAL, alt_bg); - } - - _port_group_tables.push_back (t); - _port_group_hbox.pack_start (t->get_widget(), false, false); - ++n; - } - - show_all (); - - set_port_group_table_visibility (); -} - -void -IOSelector::ports_changed (ARDOUR::IOChange change, void *src) -{ - ENSURE_GUI_THREAD (bind (mem_fun (*this, &IOSelector::ports_changed), change, src)); - - redisplay (); -} - - -void -IOSelector::redisplay () -{ - _port_group_list.refresh (); - setup (); } - -/** Handle a button press on a row label */ -bool -IOSelector::row_label_button_pressed (GdkEventButton* e, int r) +std::string +IOSelector::row_name (int r) const { - if (e->type != GDK_BUTTON_PRESS || e->button != 3) { - return false; - } - - Gtk::Menu* menu = Gtk::manage (new Gtk::Menu); - Gtk::Menu_Helpers::MenuList& items = menu->items (); - menu->set_name ("ArdourContextMenu"); - - bool can_add; - bool can_remove; - std::string name; - ARDOUR::DataType const t = _io->default_type(); - - if (_for_input) { - can_add = _io->input_maximum().get(t) > _io->n_inputs().get(t); - can_remove = _io->input_minimum().get(t) < _io->n_inputs().get(t); - name = _io->input(r)->name(); + if (!_offer_inputs) { + return _io->input(r)->name(); } else { - can_add = _io->output_maximum().get(t) > _io->n_outputs().get(t); - can_remove = _io->output_minimum().get(t) < _io->n_outputs().get(t); - name = _io->output(r)->name(); + return _io->output(r)->name(); } - - items.push_back ( - Gtk::Menu_Helpers::MenuElem (_("Add port"), sigc::mem_fun (*this, &IOSelector::add_port)) - ); - - items.back().set_sensitive (can_add); - - items.push_back ( - Gtk::Menu_Helpers::MenuElem (_("Remove port '") + name + _("'"), sigc::bind (sigc::mem_fun (*this, &IOSelector::remove_port), r)) - ); - - items.back().set_sensitive (can_remove); - - menu->popup (e->button, e->time); - - return true; + } void -IOSelector::add_port () +IOSelector::add_row () { // The IO selector only works for single typed IOs const ARDOUR::DataType t = _io->default_type (); - if (_for_input) { + if (!_offer_inputs) { try { _io->add_input_port ("", this); @@ -633,176 +165,33 @@ IOSelector::add_port () } } + void -IOSelector::remove_port (int r) +IOSelector::remove_row (int r) { // The IO selector only works for single typed IOs const ARDOUR::DataType t = _io->default_type (); - if (_for_input) { + if (!_offer_inputs) { _io->remove_input_port (_io->input (r), this); } else { _io->remove_output_port (_io->output (r), this); } } -void -IOSelector::group_visible_toggled (Gtk::CheckButton* b, std::string const & n) -{ - PortGroupList::iterator i = _port_group_list.begin(); - while (i != _port_group_list.end() & (*i)->name != n) { - ++i; - } - - if (i == _port_group_list.end()) { - return; - } - - (*i)->visible = b->get_active (); - - set_port_group_table_visibility (); - - _column_labels.queue_draw (); -} - -void -IOSelector::set_port_group_table_visibility () -{ - for (std::vector<PortGroupTable*>::iterator j = _port_group_tables.begin(); j != _port_group_tables.end(); ++j) { - if ((*j)->port_group().visible) { - (*j)->get_widget().show(); - } else { - (*j)->get_widget().hide(); - } - } -} - - -PortGroupList::PortGroupList (ARDOUR::Session & session, boost::shared_ptr<ARDOUR::IO> io, bool for_input) - : _session (session), _io (io), _for_input (for_input), - buss (_("Buss"), "ardour:"), - track (_("Track"), "ardour:"), - system (_("System"), "system:"), - other (_("Other"), "") -{ - refresh (); -} - -void -PortGroupList::refresh () -{ - clear (); - - buss.ports.clear (); - track.ports.clear (); - system.ports.clear (); - other.ports.clear (); - - /* Find the ports provided by ardour; we can't derive their type just from their - names, so we'll have to be more devious. */ - - boost::shared_ptr<ARDOUR::Session::RouteList> routes = _session.get_routes (); - - for (ARDOUR::Session::RouteList::const_iterator i = routes->begin(); i != routes->end(); ++i) { - - PortGroup* g = 0; - if (_io->default_type() == ARDOUR::DataType::AUDIO && dynamic_cast<ARDOUR::AudioTrack*> ((*i).get())) { - /* Audio track for an audio IO */ - g = &track; - } else if (_io->default_type() == ARDOUR::DataType::MIDI && dynamic_cast<ARDOUR::MidiTrack*> ((*i).get())) { - /* Midi track for a MIDI IO */ - g = &track; - } else if (_io->default_type() == ARDOUR::DataType::AUDIO && dynamic_cast<ARDOUR::MidiTrack*> ((*i).get()) == 0) { - /* Non-MIDI track for an Audio IO; must be an audio buss */ - g = &buss; - } - - if (g) { - ARDOUR::PortSet const & p = _for_input ? ((*i)->outputs()) : ((*i)->inputs()); - for (uint32_t j = 0; j < p.num_ports(); ++j) { - g->add (p.port(j)->name ()); - } - - std::sort (g->ports.begin(), g->ports.end()); - } - } - - - /* XXX: inserts, sends, plugin inserts? */ - - /* Now we need to find the non-ardour ports; we do this by first - finding all the ports that we can connect to. */ - const char **ports = _session.engine().get_ports ( - "", _io->default_type().to_jack_type(), _for_input ? JackPortIsOutput : JackPortIsInput - ); - - if (ports) { - - int n = 0; - while (ports[n]) { - std::string const p = ports[n]; - - if (p.substr(0, strlen ("system:")) == "system:") { - /* system: prefix */ - system.add (p); - } else { - if (p.substr(0, strlen("ardour:")) != "ardour:") { - /* other (non-ardour) prefix */ - other.add (p); - } - } - - ++n; - } - } - - push_back (&buss); - push_back (&track); - push_back (&system); - push_back (&other); -} - -int -PortGroupList::n_visible_ports () const -{ - int n = 0; - - for (const_iterator i = begin(); i != end(); ++i) { - if ((*i)->visible) { - n += (*i)->ports.size(); - } - } - - return n; -} - std::string -PortGroupList::get_port_by_index (int n, bool with_prefix) const +IOSelector::row_descriptor () const { - /* XXX: slightly inefficient algorithm */ - - for (const_iterator i = begin(); i != end(); ++i) { - for (std::vector<std::string>::const_iterator j = (*i)->ports.begin(); j != (*i)->ports.end(); ++j) { - if (n == 0) { - if (with_prefix) { - return (*i)->prefix + *j; - } else { - return *j; - } - } - --n; - } - } - - return ""; + return _("port"); } + IOSelectorWindow::IOSelectorWindow ( ARDOUR::Session& session, boost::shared_ptr<ARDOUR::IO> io, bool for_input, bool can_cancel ) : ArdourDialog ("I/O selector"), - _selector (session, io, for_input), + _selector (session, io, !for_input), ok_button (can_cancel ? _("OK"): _("Close")), cancel_button (_("Cancel")), rescan_button (_("Rescan")) diff --git a/gtk2_ardour/io_selector.h b/gtk2_ardour/io_selector.h index 92c3af9b48..92a112dc1d 100644 --- a/gtk2_ardour/io_selector.h +++ b/gtk2_ardour/io_selector.h @@ -20,153 +20,30 @@ #ifndef __ardour_ui_io_selector_h__ #define __ardour_ui_io_selector_h__ -#include <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: diff --git a/gtk2_ardour/mixer_strip.cc b/gtk2_ardour/mixer_strip.cc index f791314d75..62aefba27f 100644 --- a/gtk2_ardour/mixer_strip.cc +++ b/gtk2_ardour/mixer_strip.cc @@ -42,7 +42,8 @@ #include <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: diff --git a/gtk2_ardour/mixer_strip.h b/gtk2_ardour/mixer_strip.h index c3cf68a9ce..f134897fc5 100644 --- a/gtk2_ardour/mixer_strip.h +++ b/gtk2_ardour/mixer_strip.h @@ -168,10 +168,10 @@ class MixerStrip : public RouteUI, public Gtk::EventBox gint output_press (GdkEventButton *); Gtk::Menu input_menu; - void add_bundle_to_input_menu (boost::shared_ptr<ARDOUR::Bundle>, boost::shared_ptr<ARDOUR::Bundle>); + void add_bundle_to_input_menu (boost::shared_ptr<ARDOUR::Bundle>, std::vector<boost::shared_ptr<ARDOUR::Bundle> > const &); Gtk::Menu output_menu; - void add_bundle_to_output_menu (boost::shared_ptr<ARDOUR::Bundle>, boost::shared_ptr<ARDOUR::Bundle>); + void add_bundle_to_output_menu (boost::shared_ptr<ARDOUR::Bundle>, std::vector<boost::shared_ptr<ARDOUR::Bundle> > const &); void bundle_input_chosen (boost::shared_ptr<ARDOUR::Bundle>); void bundle_output_chosen (boost::shared_ptr<ARDOUR::Bundle>); diff --git a/gtk2_ardour/port_matrix.cc b/gtk2_ardour/port_matrix.cc new file mode 100644 index 0000000000..19c0dc9d7f --- /dev/null +++ b/gtk2_ardour/port_matrix.cc @@ -0,0 +1,730 @@ +/* + Copyright (C) 2002-2007 Paul Davis + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include <gtkmm/label.h> +#include <gtkmm/enums.h> +#include <gtkmm/image.h> +#include <gtkmm/stock.h> +#include <gtkmm/messagedialog.h> +#include <gtkmm/menu.h> +#include <gtkmm/menu_elems.h> +#include <gtkmm/menuitem.h> +#include <gtkmm/menushell.h> +#include <glibmm/objectbase.h> +#include <gtkmm2ext/doi.h> +#include <ardour/port_insert.h> +#include "ardour/session.h" +#include "ardour/io.h" +#include "ardour/audioengine.h" +#include "ardour/track.h" +#include "ardour/audio_track.h" +#include "ardour/midi_track.h" +#include "ardour/data_type.h" +#include "io_selector.h" +#include "utils.h" +#include "gui_thread.h" +#include "i18n.h" + +/** Add a port to a group. + * @param p Port name, with or without prefix. + */ + +void +PortGroup::add (std::string const & p) +{ + if (prefix.empty() == false && p.substr (0, prefix.length()) == prefix) { + ports.push_back (p.substr (prefix.length())); + } else { + ports.push_back (p); + } +} + +/** PortGroupUI constructor. + * @param m PortMatrix to work for. + * @Param g PortGroup to represent. + */ + +PortGroupUI::PortGroupUI (PortMatrix& m, PortGroup& g) + : _port_matrix (m), _port_group (g), _ignore_check_button_toggle (false), + _visibility_checkbutton (g.name) +{ + int const ports = _port_group.ports.size(); + int const rows = _port_matrix.n_rows (); + + if (rows == 0 || ports == 0) { + return; + } + + /* Sort out the table and the checkbuttons inside it */ + + _table.resize (rows, ports); + _port_checkbuttons.resize (rows); + for (int i = 0; i < rows; ++i) { + _port_checkbuttons[i].resize (ports); + } + + for (int i = 0; i < rows; ++i) { + for (uint32_t j = 0; j < _port_group.ports.size(); ++j) { + Gtk::CheckButton* b = new Gtk::CheckButton; + + b->signal_toggled().connect ( + sigc::bind (sigc::mem_fun (*this, &PortGroupUI::port_checkbutton_toggled), b, i, j) + ); + + _port_checkbuttons[i][j] = b; + _table.attach (*b, j, j + 1, i, i + 1); + } + } + + _table_box.add (_table); + + _ignore_check_button_toggle = true; + + /* Set the state of the check boxes according to current connections */ + for (int i = 0; i < rows; ++i) { + for (uint32_t j = 0; j < _port_group.ports.size(); ++j) { + std::string const t = _port_group.prefix + _port_group.ports[j]; + bool const s = _port_matrix.get_state (i, t); + _port_checkbuttons[i][j]->set_active (s); + if (s) { + _port_group.visible = true; + } + } + } + + _ignore_check_button_toggle = false; + + _visibility_checkbutton.signal_toggled().connect (sigc::mem_fun (*this, &PortGroupUI::visibility_checkbutton_toggled)); +} + +/** The visibility of a PortGroupUI has been toggled */ +void +PortGroupUI::visibility_checkbutton_toggled () +{ + _port_group.visible = _visibility_checkbutton.get_active (); + setup_visibility (); +} + +/** @return Width and height of a single checkbutton in a port group table */ +std::pair<int, int> +PortGroupUI::unit_size () const +{ + if (_port_checkbuttons.empty() || _port_checkbuttons[0].empty()) + { + return std::pair<int, int> (0, 0); + } + + int r = 0; + /* We can't ask for row spacing unless there >1 rows, otherwise we get a warning */ + if (_table.property_n_rows() > 1) { + r = _table.get_row_spacing (0); + } + + return std::make_pair ( + _port_checkbuttons[0][0]->get_width() + _table.get_col_spacing (0), + _port_checkbuttons[0][0]->get_height() + r + ); +} + +/** @return Table widget containing the port checkbuttons */ +Gtk::Widget& +PortGroupUI::get_table () +{ + return _table_box; +} + +/** @return Checkbutton used to toggle visibility */ +Gtk::Widget& +PortGroupUI::get_visibility_checkbutton () +{ + return _visibility_checkbutton; +} + + +/** Handle a toggle of a port check button */ +void +PortGroupUI::port_checkbutton_toggled (Gtk::CheckButton* b, int r, int c) +{ + if (_ignore_check_button_toggle == false) { + _port_matrix.set_state (r, _port_group.prefix + _port_group.ports[c], b->get_active()); + } +} + +/** Set up visibility of the port group according to PortGroup::visible */ +void +PortGroupUI::setup_visibility () +{ + if (_port_group.visible) { + _table_box.show (); + } else { + _table_box.hide (); + } + + if (_visibility_checkbutton.get_active () != _port_group.visible) { + _visibility_checkbutton.set_active (_port_group.visible); + } +} + + +RotatedLabelSet::RotatedLabelSet (PortGroupList& g) + : Glib::ObjectBase ("RotatedLabelSet"), Gtk::Widget (), _port_group_list (g), _base_width (128) +{ + set_flags (Gtk::NO_WINDOW); + set_angle (30); +} + +RotatedLabelSet::~RotatedLabelSet () +{ + +} + + +/** Set the angle that the labels are drawn at. + * @param degrees New angle in degrees. + */ + +void +RotatedLabelSet::set_angle (int degrees) +{ + _angle_degrees = degrees; + _angle_radians = M_PI * _angle_degrees / 180; + + queue_resize (); +} + +void +RotatedLabelSet::on_size_request (Gtk::Requisition* requisition) +{ + *requisition = Gtk::Requisition (); + + if (_pango_layout == 0) { + return; + } + + /* Our height is the highest label */ + requisition->height = 0; + for (PortGroupList::const_iterator i = _port_group_list.begin(); i != _port_group_list.end(); ++i) { + for (std::vector<std::string>::const_iterator j = (*i)->ports.begin(); j != (*i)->ports.end(); ++j) { + std::pair<int, int> const d = setup_layout (*j); + if (d.second > requisition->height) { + requisition->height = d.second; + } + } + } + + /* And our width is the base plus the width of the last label */ + requisition->width = _base_width; + int const n = _port_group_list.n_visible_ports (); + if (n > 0) { + std::pair<int, int> const d = setup_layout (_port_group_list.get_port_by_index (n - 1, false)); + requisition->width += d.first; + } +} + +void +RotatedLabelSet::on_size_allocate (Gtk::Allocation& allocation) +{ + set_allocation (allocation); + + if (_gdk_window) { + _gdk_window->move_resize ( + allocation.get_x(), allocation.get_y(), allocation.get_width(), allocation.get_height() + ); + } +} + +void +RotatedLabelSet::on_realize () +{ + Gtk::Widget::on_realize (); + + Glib::RefPtr<Gtk::Style> style = get_style (); + + if (!_gdk_window) { + GdkWindowAttr attributes; + memset (&attributes, 0, sizeof (attributes)); + + Gtk::Allocation allocation = get_allocation (); + attributes.x = allocation.get_x (); + attributes.y = allocation.get_y (); + attributes.width = allocation.get_width (); + attributes.height = allocation.get_height (); + + attributes.event_mask = get_events () | Gdk::EXPOSURE_MASK; + attributes.window_type = GDK_WINDOW_CHILD; + attributes.wclass = GDK_INPUT_OUTPUT; + + _gdk_window = Gdk::Window::create (get_window (), &attributes, GDK_WA_X | GDK_WA_Y); + unset_flags (Gtk::NO_WINDOW); + set_window (_gdk_window); + + _bg_colour = style->get_bg (Gtk::STATE_NORMAL ); + modify_bg (Gtk::STATE_NORMAL, _bg_colour); + _fg_colour = style->get_fg (Gtk::STATE_NORMAL); +; + _gdk_window->set_user_data (gobj ()); + + /* Set up Pango stuff */ + _pango_context = create_pango_context (); + + Pango::Matrix matrix = PANGO_MATRIX_INIT; + pango_matrix_rotate (&matrix, _angle_degrees); + _pango_context->set_matrix (matrix); + + _pango_layout = Pango::Layout::create (_pango_context); + _gc = Gdk::GC::create (get_window ()); + } +} + +void +RotatedLabelSet::on_unrealize() +{ + _gdk_window.clear (); + + Gtk::Widget::on_unrealize (); +} + + +/** Set up our Pango layout to plot a given string, and compute its dimensions once + * it has been rotated. + * @param s String to use. + * @return width and height of the rotated string, in pixels. + */ + +std::pair<int, int> +RotatedLabelSet::setup_layout (std::string const & s) +{ + _pango_layout->set_text (s); + + /* Here's the unrotated size */ + int w; + int h; + _pango_layout->get_pixel_size (w, h); + + /* Rotate the width and height as appropriate. I thought Pango might be able + to do this for us, but I can't find out how... */ + std::pair<int, int> d; + d.first = int (w * cos (_angle_radians) - h * sin (_angle_radians)); + d.second = int (w * sin (_angle_radians) + h * cos (_angle_radians)); + + return d; +} + +bool +RotatedLabelSet::on_expose_event (GdkEventExpose* event) +{ + if (!_gdk_window) { + return true; + } + + int const height = get_allocation().get_height (); + double const spacing = double (_base_width) / _port_group_list.n_visible_ports(); + + /* Plot all the visible labels; really we should clip for efficiency */ + int n = 0; + for (PortGroupList::const_iterator i = _port_group_list.begin(); i != _port_group_list.end(); ++i) { + if ((*i)->visible) { + for (uint32_t j = 0; j < (*i)->ports.size(); ++j) { + std::pair<int, int> const d = setup_layout ((*i)->ports[j]); + get_window()->draw_layout (_gc, int ((n + 0.25) * spacing), height - d.second, _pango_layout, _fg_colour, _bg_colour); + ++n; + } + } + } + + return true; +} + +/** Set the `base width'. This is the width of the base of the label set, ie: + * + * L L L L + * E E E E + * B B B B + * A A A A + * L L L L + * <--w--> + */ + +void +RotatedLabelSet::set_base_width (int w) +{ + _base_width = w; + queue_resize (); +} + + +PortMatrix::PortMatrix (ARDOUR::Session& session, ARDOUR::DataType type, bool offer_inputs, PortGroupList::Mask mask) + : _offer_inputs (offer_inputs), _port_group_list (session, type, offer_inputs, mask), _type (type), + _column_labels (_port_group_list) +{ + _row_labels_vbox[0] = _row_labels_vbox[1] = 0; + _side_vbox_pad[0] = _side_vbox_pad[1] = 0; + + pack_start (_visibility_checkbutton_box, false, false); + + _side_vbox[0].pack_start (*Gtk::manage (new Gtk::Label (""))); + _overall_hbox.pack_start (_side_vbox[0], false, false); + _scrolled_window.set_policy (Gtk::POLICY_ALWAYS, Gtk::POLICY_NEVER); + _scrolled_window.set_shadow_type (Gtk::SHADOW_NONE); + Gtk::VBox* b = new Gtk::VBox; + b->pack_start (_column_labels, false, false); + b->pack_start (_port_group_hbox, false, false); + Gtk::Alignment* a = new Gtk::Alignment (0, 1, 0, 0); + a->add (*Gtk::manage (b)); + _scrolled_window.add (*Gtk::manage (a)); + _overall_hbox.pack_start (_scrolled_window); + _side_vbox[1].pack_start (*Gtk::manage (new Gtk::Label (""))); + _overall_hbox.pack_start (_side_vbox[1]); + pack_start (_overall_hbox); + + _port_group_hbox.signal_size_allocate().connect (sigc::hide (sigc::mem_fun (*this, &IOSelector::setup_dimensions))); +} + +PortMatrix::~PortMatrix () +{ + clear (); +} + +/** Clear out the things that change when the number of source or destination ports changes */ +void +PortMatrix::clear () +{ + for (int i = 0; i < 2; ++i) { + + for (std::vector<Gtk::EventBox*>::iterator j = _row_labels[i].begin(); j != _row_labels[i].end(); ++j) { + delete *j; + } + _row_labels[i].clear (); + + if (_row_labels_vbox[i]) { + _side_vbox[i].remove (*_row_labels_vbox[i]); + } + delete _row_labels_vbox[i]; + _row_labels_vbox[i] = 0; + + if (_side_vbox_pad[i]) { + _side_vbox[i].remove (*_side_vbox_pad[i]); + } + delete _side_vbox_pad[i]; + _side_vbox_pad[i] = 0; + } + + for (std::vector<PortGroupUI*>::iterator i = _port_group_ui.begin(); i != _port_group_ui.end(); ++i) { + _port_group_hbox.remove ((*i)->get_table()); + _visibility_checkbutton_box.remove ((*i)->get_visibility_checkbutton()); + delete *i; + } + + _port_group_ui.clear (); +} + + +/** Set up dimensions of some of our widgets which depend on other dimensions + * within the dialogue. + */ +void +PortMatrix::setup_dimensions () +{ + /* Get some dimensions from various places */ + int const scrollbar_height = _scrolled_window.get_hscrollbar()->get_height(); + + std::pair<int, int> unit_size (0, 0); + int port_group_tables_height = 0; + for (std::vector<PortGroupUI*>::iterator i = _port_group_ui.begin(); i != _port_group_ui.end(); ++i) { + std::pair<int, int> const u = (*i)->unit_size (); + unit_size.first = std::max (unit_size.first, u.first); + unit_size.second = std::max (unit_size.second, u.second); + port_group_tables_height = std::max ( + port_group_tables_height, (*i)->get_table().get_height() + ); + } + + /* Column labels */ + _column_labels.set_base_width (_port_group_list.n_visible_ports () * unit_size.first); + + /* Scrolled window */ + /* XXX: really shouldn't set a minimum horizontal size here, but if we don't + the window starts up very small */ + _scrolled_window.set_size_request ( + std::min (_column_labels.get_width(), 640), + _column_labels.get_height() + port_group_tables_height + scrollbar_height + 16 + ); + + /* Row labels */ + for (int i = 0; i < 2; ++i) { + for (std::vector<Gtk::EventBox*>::iterator j = _row_labels[i].begin(); j != _row_labels[i].end(); ++j) { + (*j)->get_child()->set_size_request (-1, unit_size.second); + } + + if (_side_vbox_pad[i]) { + _side_vbox_pad[i]->set_size_request (-1, scrollbar_height + unit_size.second / 4); + } + } +} + + +/** Set up the dialogue */ +void +PortMatrix::setup () +{ + clear (); + + int const rows = n_rows (); + + /* Row labels */ + for (int i = 0; i < 2; ++i) { + _row_labels_vbox[i] = new Gtk::VBox; + int const run_rows = std::max (1, rows); + for (int j = 0; j < run_rows; ++j) { + Gtk::Label* label = new Gtk::Label (rows == 0 ? "Quim" : row_name (j)); + Gtk::EventBox* b = new Gtk::EventBox; + b->set_events (Gdk::BUTTON_PRESS_MASK); + b->signal_button_press_event().connect (sigc::bind (sigc::mem_fun (*this, &IOSelector::row_label_button_pressed), j)); + b->add (*Gtk::manage (label)); + _row_labels[i].push_back (b); + _row_labels_vbox[i]->pack_start (*b, false, false); + } + + _side_vbox[i].pack_start (*_row_labels_vbox[i], false, false); + _side_vbox_pad[i] = new Gtk::Label (""); + _side_vbox[i].pack_start (*_side_vbox_pad[i], false, false); + } + + /* Checkbutton tables and visibility checkbuttons */ + int n = 0; + for (PortGroupList::iterator i = _port_group_list.begin(); i != _port_group_list.end(); ++i) { + PortGroupUI* t = new PortGroupUI (*this, **i); + + /* XXX: this is a bit of a hack; should probably use a configurable colour here */ + Gdk::Color alt_bg = get_style()->get_bg (Gtk::STATE_NORMAL); + alt_bg.set_rgb (alt_bg.get_red() + 4096, alt_bg.get_green() + 4096, alt_bg.get_blue () + 4096); + if ((n % 2) == 0) { + t->get_table().modify_bg (Gtk::STATE_NORMAL, alt_bg); + } + + _port_group_ui.push_back (t); + _port_group_hbox.pack_start (t->get_table(), false, false); + + _visibility_checkbutton_box.pack_start (t->get_visibility_checkbutton(), false, false); + ++n; + } + + show_all (); + + for (std::vector<PortGroupUI*>::iterator i = _port_group_ui.begin(); i != _port_group_ui.end(); ++i) { + (*i)->setup_visibility (); + } + +} + +void +PortMatrix::redisplay () +{ + _port_group_list.refresh (); + setup (); +} + + +/** Handle a button press on a row label */ +bool +PortMatrix::row_label_button_pressed (GdkEventButton* e, int r) +{ + if (e->type != GDK_BUTTON_PRESS || e->button != 3) { + return false; + } + + Gtk::Menu* menu = Gtk::manage (new Gtk::Menu); + Gtk::Menu_Helpers::MenuList& items = menu->items (); + menu->set_name ("ArdourContextMenu"); + + bool const can_add = maximum_rows () > n_rows (); + bool const can_remove = minimum_rows () < n_rows (); + std::string const name = row_name (r); + + items.push_back ( + Gtk::Menu_Helpers::MenuElem (string_compose(_("Add %1"), row_descriptor()), sigc::mem_fun (*this, &PortMatrix::add_row)) + ); + + items.back().set_sensitive (can_add); + + items.push_back ( + Gtk::Menu_Helpers::MenuElem (string_compose(_("Remove %1 \"%2\""), row_descriptor(), name), sigc::bind (sigc::mem_fun (*this, &PortMatrix::remove_row), r)) + ); + + items.back().set_sensitive (can_remove); + + menu->popup (e->button, e->time); + + return true; +} + +void +PortMatrix::set_type (ARDOUR::DataType t) +{ + _type = t; + _port_group_list.set_type (t); + redisplay (); +} + +void +PortMatrix::set_offer_inputs (bool i) +{ + _offer_inputs = i; + _port_group_list.set_offer_inputs (i); + redisplay (); +} + +/** PortGroupList constructor. + * @param session Session to get ports from. + * @param type Type of ports to offer (audio or MIDI) + * @param offer_inputs true to offer output ports, otherwise false. + * @param mask Mask of groups to make visible by default. + */ + +PortGroupList::PortGroupList (ARDOUR::Session & session, ARDOUR::DataType type, bool offer_inputs, Mask mask) + : _session (session), _type (type), _offer_inputs (offer_inputs), + buss (_("Buss"), "ardour:", mask & BUSS), + track (_("Track"), "ardour:", mask & TRACK), + system (_("System"), "system:", mask & SYSTEM), + other (_("Other"), "", mask & OTHER) +{ + refresh (); +} + +void +PortGroupList::refresh () +{ + clear (); + + buss.ports.clear (); + track.ports.clear (); + system.ports.clear (); + other.ports.clear (); + + /* Find the ports provided by ardour; we can't derive their type just from their + names, so we'll have to be more devious. */ + + boost::shared_ptr<ARDOUR::Session::RouteList> routes = _session.get_routes (); + + for (ARDOUR::Session::RouteList::const_iterator i = routes->begin(); i != routes->end(); ++i) { + + PortGroup* g = 0; + if (_type == ARDOUR::DataType::AUDIO && dynamic_cast<ARDOUR::AudioTrack*> ((*i).get())) { + /* Audio track for an audio IO */ + g = &track; + } else if (_type == ARDOUR::DataType::MIDI && dynamic_cast<ARDOUR::MidiTrack*> ((*i).get())) { + /* Midi track for a MIDI IO */ + g = &track; + } else if (_type == ARDOUR::DataType::AUDIO && dynamic_cast<ARDOUR::MidiTrack*> ((*i).get()) == 0) { + /* Non-MIDI track for an Audio IO; must be an audio buss */ + g = &buss; + } + + if (g) { + ARDOUR::PortSet const & p = _offer_inputs ? ((*i)->inputs()) : ((*i)->outputs()); + for (uint32_t j = 0; j < p.num_ports(); ++j) { + g->add (p.port(j)->name ()); + } + + std::sort (g->ports.begin(), g->ports.end()); + } + } + + + /* XXX: inserts, sends, plugin inserts? */ + + /* Now we need to find the non-ardour ports; we do this by first + finding all the ports that we can connect to. */ + const char **ports = _session.engine().get_ports ( + "", _type.to_jack_type(), _offer_inputs ? JackPortIsInput : JackPortIsOutput + ); + + if (ports) { + + int n = 0; + while (ports[n]) { + std::string const p = ports[n]; + + if (p.substr(0, strlen ("system:")) == "system:") { + /* system: prefix */ + system.add (p); + } else { + if (p.substr(0, strlen("ardour:")) != "ardour:") { + /* other (non-ardour) prefix */ + other.add (p); + } + } + + ++n; + } + } + + push_back (&buss); + push_back (&track); + push_back (&system); + push_back (&other); +} + +int +PortGroupList::n_visible_ports () const +{ + int n = 0; + + for (const_iterator i = begin(); i != end(); ++i) { + if ((*i)->visible) { + n += (*i)->ports.size(); + } + } + + return n; +} + +std::string +PortGroupList::get_port_by_index (int n, bool with_prefix) const +{ + /* XXX: slightly inefficient algorithm */ + + for (const_iterator i = begin(); i != end(); ++i) { + for (std::vector<std::string>::const_iterator j = (*i)->ports.begin(); j != (*i)->ports.end(); ++j) { + if (n == 0) { + if (with_prefix) { + return (*i)->prefix + *j; + } else { + return *j; + } + } + --n; + } + } + + return ""; +} + +void +PortGroupList::set_type (ARDOUR::DataType t) +{ + _type = t; +} + +void +PortGroupList::set_offer_inputs (bool i) +{ + _offer_inputs = i; +} + diff --git a/gtk2_ardour/port_matrix.h b/gtk2_ardour/port_matrix.h new file mode 100644 index 0000000000..a945d00496 --- /dev/null +++ b/gtk2_ardour/port_matrix.h @@ -0,0 +1,200 @@ +/* + Copyright (C) 2002-2007 Paul Davis + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#ifndef __ardour_ui_port_matrix_h__ +#define __ardour_ui_port_matrix_h__ + +#include <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 |