From 3e7b2bb3f53a461e3b1fdc0cd91a6936638653f9 Mon Sep 17 00:00:00 2001 From: Robin Gareus Date: Fri, 29 Jan 2021 03:31:37 +0100 Subject: [PATCH] Separate IO connection button into dedicated class --- gtk2_ardour/io_button.cc | 624 ++++++++++++++++++++++++++++++++++ gtk2_ardour/io_button.h | 73 ++++ gtk2_ardour/mixer_strip.cc | 674 +------------------------------------ gtk2_ardour/mixer_strip.h | 31 +- gtk2_ardour/wscript | 1 + 5 files changed, 708 insertions(+), 695 deletions(-) create mode 100644 gtk2_ardour/io_button.cc create mode 100644 gtk2_ardour/io_button.h diff --git a/gtk2_ardour/io_button.cc b/gtk2_ardour/io_button.cc new file mode 100644 index 0000000000..33c2d8c54d --- /dev/null +++ b/gtk2_ardour/io_button.cc @@ -0,0 +1,624 @@ +/* + * Copyright (C) 2005-2017 Paul Davis + * Copyright (C) 2012-2021 Robin Gareus + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "ardour/async_midi_port.h" +#include "ardour/audioengine.h" +#include "ardour/profile.h" +#include "ardour/route.h" +#include "ardour/session.h" +#include "ardour/track.h" +#include "ardour/user_bundle.h" + +#include "gtkmm2ext/menu_elems.h" +#include "gtkmm2ext/utils.h" +#include "widgets/tooltips.h" + +#include "ardour_message.h" +#include "gui_thread.h" +#include "io_button.h" +#include "io_selector.h" +#include "route_ui.h" +#include "ui_config.h" + +#include "pbd/i18n.h" + +using namespace ARDOUR; +using namespace ArdourWidgets; +using namespace PBD; +using namespace Gtkmm2ext; +using namespace std; + +IOButton::IOButton (bool input) + : _input (input) + , _route_ui (0) +{ + set_text (input ? _("Input") : _("Output")); + set_name ("mixer strip button"); + set_text_ellipsize (Pango::ELLIPSIZE_MIDDLE); + + signal_button_press_event ().connect (sigc::mem_fun (*this, &IOButton::button_press), false); + signal_button_release_event ().connect (sigc::mem_fun (*this, &IOButton::button_release), false); + signal_size_allocate ().connect (sigc::mem_fun (*this, &IOButton::button_resized)); +} + +void +IOButton::set_route (boost::shared_ptr rt, RouteUI* routeui) +{ + _connections.drop_connections (); + + _route = rt; + _route_ui = routeui; + + if (!_route) { + _route_ui = NULL; + return; + } + + AudioEngine::instance ()->PortConnectedOrDisconnected.connect (_connections, invalidator (*this), boost::bind (&IOButton::port_connected_or_disconnected, this, _1, _3), gui_context ()); + + AudioEngine::instance ()->PortPrettyNameChanged.connect (_connections, invalidator (*this), boost::bind (&IOButton::port_pretty_name_changed, this, _1), gui_context ()); + + io ()->changed.connect (_connections, invalidator (*this), boost::bind (&IOButton::update, this), gui_context ()); + + update (); +} + +IOButton::~IOButton () +{ +} + +boost::shared_ptr +IOButton::io () const +{ + return _input ? _route->input () : _route->output (); +} + +boost::shared_ptr +IOButton::track () const +{ + return boost::dynamic_pointer_cast (_route); +} + +void +IOButton::port_pretty_name_changed (std::string pn) +{ + if (io ()->connected_to (pn)) { + update (); + } +} + +void +IOButton::port_connected_or_disconnected (boost::weak_ptr wa, boost::weak_ptr wb) +{ + boost::shared_ptr a = wa.lock (); + boost::shared_ptr b = wb.lock (); + + if ((a && io ()->has_port (a)) || (b && io ()->has_port (b))) { + update (); + } +} + +void +IOButton::bundle_chosen (boost::shared_ptr c) +{ + if (_input) { + _route->input ()->connect_ports_to_bundle (c, true, this); + } else { + _route->output ()->connect_ports_to_bundle (c, true, true, this); + } +} + +void +IOButton::disconnect () +{ + io ()->disconnect (this); +} + +void +IOButton::add_port (DataType t) +{ + if (io ()->add_port ("", this, t) != 0) { + ArdourMessageDialog msg (_("It is not possible to add a port here.")); + msg.set_title (_("Cannot add port")); + msg.run (); + } +} + +void +IOButton::button_resized (Gtk::Allocation& alloc) +{ + set_layout_ellipsize_width (alloc.get_width () * PANGO_SCALE); +} + +struct RouteCompareByName { + bool operator() (boost::shared_ptr a, boost::shared_ptr b) + { + return a->name ().compare (b->name ()) < 0; + } +}; + +bool +IOButton::button_release (GdkEventButton* ev) +{ + if (!_route || !_route_ui) { + return false; + } + if (ev->button == 3) { + if (_input) { + _route_ui->edit_input_configuration (); + } else { + _route_ui->edit_output_configuration (); + } + } + return false; +} + +bool +IOButton::button_press (GdkEventButton* ev) +{ + using namespace Gtk::Menu_Helpers; + + if (!ARDOUR_UI_UTILS::engine_is_running () || !_route || !_route_ui) { + return true; + } + + MenuList& citems = _menu.items (); + _menu.set_name ("ArdourContextMenu"); + citems.clear (); + + if (_route->session ().actively_recording () && track () && track ()->rec_enable_control ()->get_value ()) { + return true; + } + + switch (ev->button) { + case 3: + /* don't handle the mouse-down here, parent handles mouse-up if needed. */ + return false; + case 1: + break; + default: + /* do nothing */ + return true; + } + + citems.push_back (MenuElem (_("Disconnect"), sigc::mem_fun (*this, &IOButton::disconnect))); + citems.push_back (SeparatorElem ()); + uint32_t const n_with_separator = citems.size (); + + _menu_bundles.clear (); + ARDOUR::BundleList current = io ()->bundles_connected (); + boost::shared_ptr b = _route->session ().bundles (); + + if (_input) { + /* give user bundles first chance at being in the menu */ + for (ARDOUR::BundleList::iterator i = b->begin (); i != b->end (); ++i) { + if (boost::dynamic_pointer_cast (*i)) { + maybe_add_bundle_to_menu (*i, current); + } + } + + for (ARDOUR::BundleList::iterator i = b->begin (); i != b->end (); ++i) { + if (boost::dynamic_pointer_cast (*i) == 0) { + maybe_add_bundle_to_menu (*i, current); + } + } + } else { + /* guess the user-intended main type of the route output */ + DataType intended_type = guess_main_type (); + + /* try adding the master bus first */ + boost::shared_ptr master = _route->session ().master_out (); + if (master) { + maybe_add_bundle_to_menu (master->input ()->bundle (), current, intended_type); + } + } + + boost::shared_ptr routes = _route->session ().get_routes (); + RouteList copy = *routes; + copy.sort (RouteCompareByName ()); + + for (ARDOUR::RouteList::const_iterator i = copy.begin (); i != copy.end (); ++i) { + maybe_add_bundle_to_menu ((*i)->output ()->bundle (), current); + } + + if (!_input) { + DataType intended_type = guess_main_type (); + /* then try adding user output bundles, often labeled/grouped physical inputs */ + for (ARDOUR::BundleList::iterator i = b->begin (); i != b->end (); ++i) { + if (boost::dynamic_pointer_cast (*i)) { + maybe_add_bundle_to_menu (*i, current, intended_type); + } + } + + /* then all other bundles, including physical outs or other sofware */ + for (ARDOUR::BundleList::iterator i = b->begin (); i != b->end (); ++i) { + if (boost::dynamic_pointer_cast (*i) == 0) { + maybe_add_bundle_to_menu (*i, current, intended_type); + } + } + } + + if (citems.size () > n_with_separator) { + citems.push_back (SeparatorElem ()); + } + + if (_input || !ARDOUR::Profile->get_mixbus ()) { + bool need_separator = false; + for (DataType::iterator i = DataType::begin (); i != DataType::end (); ++i) { + if (!io ()->can_add_port (*i)) { + continue; + } + need_separator = true; + citems.push_back ( + MenuElem ( + string_compose (_("Add %1 port"), (*i).to_i18n_string ()), + sigc::bind (sigc::mem_fun (*this, &IOButton::add_port), *i))); + } + if (need_separator) { + citems.push_back (SeparatorElem ()); + } + } + + if (_input) { + citems.push_back (MenuElem (_("Routing Grid"), sigc::mem_fun (*_route_ui, &RouteUI::edit_input_configuration))); + } else { + citems.push_back (MenuElem (_("Routing Grid"), sigc::mem_fun (*_route_ui, &RouteUI::edit_output_configuration))); + } + + Gtkmm2ext::anchored_menu_popup (&_menu, this, "", 1, ev->time); + return true; +} + +DataType +IOButton::guess_main_type (bool favor_connected) const +{ + /* The heuristic follows these principles: + * A) If all ports that the user connected are of the same type, then he + * very probably intends to use the IO with that type. A common subcase + * is when the IO has only ports of the same type (connected or not). + * B) If several types of ports are connected, then we should guess based + * on the likeliness of the user wanting to use a given type. + * We assume that the DataTypes are ordered from the most likely to the + * least likely when iterating or comparing them with "<". + * C) If no port is connected, the same logic can be applied with all ports + * instead of connected ones. TODO: Try other ideas, for instance look at + * the last plugin output when |for_input| is false (note: when StrictIO + * the outs of the last plugin should be the same as the outs of the route + * modulo the panner which forwards non-audio anyway). + * All of these constraints are respected by the following algorithm that + * just returns the most likely datatype found in connected ports if any, or + * available ports if any (since if all ports are of the same type, the most + * likely found will be that one obviously). */ + + boost::shared_ptr io = _input ? _route->input () : _route->output (); + + /* Find most likely type among connected ports */ + if (favor_connected) { + DataType type = DataType::NIL; /* NIL is always last so least likely */ + for (PortSet::iterator p = io->ports ().begin (); p != io->ports ().end (); ++p) { + if (p->connected () && p->type () < type) + type = p->type (); + } + if (type != DataType::NIL) { + /* There has been a connected port (necessarily non-NIL) */ + return type; + } + } + + /* Find most likely type among available ports. + * The iterator stops before NIL. */ + for (DataType::iterator t = DataType::begin (); t != DataType::end (); ++t) { + if (io->n_ports ().n (*t) > 0) + return *t; + } + + /* No port at all, return the most likely datatype by default */ + return DataType::front (); +} + +/* + * Output port labelling + * + * Case 1: Each output has one connection, all connections are to system:playback_%i + * out 1 -> system:playback_1 + * out 2 -> system:playback_2 + * out 3 -> system:playback_3 + * Display as: 1/2/3 + * + * Case 2: Each output has one connection, all connections are to ardour:track_x/in 1 + * out 1 -> ardour:track_x/in 1 + * out 2 -> ardour:track_x/in 2 + * Display as: track_x + * + * Case 3: Each output has one connection, all connections are to Jack client "program x" + * out 1 -> program x:foo + * out 2 -> program x:foo + * Display as: program x + * + * Case 4: No connections (Disconnected) + * Display as: - + * + * Default case (unusual routing): + * Display as: *number of connections* + * + * + * Tooltips + * + * .-----------------------------------------------. + * | Mixdown | + * | out 1 -> ardour:master/in 1, jamin:input/in 1 | + * | out 2 -> ardour:master/in 2, jamin:input/in 2 | + * '-----------------------------------------------' + * .-----------------------------------------------. + * | Guitar SM58 | + * | Disconnected | + * '-----------------------------------------------' + */ + +void +IOButton::update () +{ + ostringstream tooltip; + ostringstream label; + bool have_label = false; + + uint32_t total_connection_count = 0; + uint32_t typed_connection_count = 0; + bool each_typed_port_has_one_connection = true; + + DataType dt = guess_main_type (); + boost::shared_ptr io = _input ? _route->input () : _route->output (); + + /* Fill in the tooltip. Also count: + * - The total number of connections. + * - The number of main-typed connections. + * - Whether each main-typed port has exactly one connection. */ + if (_input) { + tooltip << string_compose (_("INPUT to %1"), + Gtkmm2ext::markup_escape_text (_route->name ())); + } else { + tooltip << string_compose (_("OUTPUT from %1"), + Gtkmm2ext::markup_escape_text (_route->name ())); + } + + string arrow = Gtkmm2ext::markup_escape_text (_input ? " <- " : " -> "); + vector port_connections; + for (PortSet::iterator port = io->ports ().begin (); + port != io->ports ().end (); + ++port) { + port_connections.clear (); + port->get_connections (port_connections); + + uint32_t port_connection_count = 0; + + for (vector::iterator i = port_connections.begin (); + i != port_connections.end (); + ++i) { + ++port_connection_count; + + if (port_connection_count == 1) { + tooltip << endl + << Gtkmm2ext::markup_escape_text ( + port->name ().substr (port->name ().find ("/") + 1)); + tooltip << arrow; + } else { + tooltip << ", "; + } + + tooltip << Gtkmm2ext::markup_escape_text (*i); + } + + total_connection_count += port_connection_count; + if (port->type () == dt) { + typed_connection_count += port_connection_count; + each_typed_port_has_one_connection &= (port_connection_count == 1); + } + } + + if (total_connection_count == 0) { + tooltip << endl + << _("Disconnected"); + } + + if (typed_connection_count == 0) { + label << "-"; + have_label = true; + } + + /* Are all main-typed channels connected to the same route ? */ + if (!have_label) { + boost::shared_ptr routes = _route->session ().get_routes (); + for (ARDOUR::RouteList::const_iterator route = routes->begin (); + route != routes->end (); + ++route) { + boost::shared_ptr dest_io = _input ? (*route)->output () : (*route)->input (); + if (io->bundle ()->connected_to (dest_io->bundle (), _route->session ().engine (), dt, true)) { + label << Gtkmm2ext::markup_escape_text ((*route)->name ()); + have_label = true; + break; + } + } + } + + /* Are all main-typed channels connected to the same (user) bundle ? */ + if (!have_label) { + boost::shared_ptr bundles = _route->session ().bundles (); + boost::shared_ptr ap = boost::dynamic_pointer_cast (_route->session ().vkbd_output_port ()); + std::string vkbd_portname = AudioEngine::instance ()->make_port_name_non_relative (ap->name ()); + for (ARDOUR::BundleList::iterator bundle = bundles->begin (); + bundle != bundles->end (); + ++bundle) { + if (boost::dynamic_pointer_cast (*bundle) == 0) { + if (!(*bundle)->offers_port (vkbd_portname)) { + continue; + } + } + if (io->bundle ()->connected_to (*bundle, _route->session ().engine (), dt, true)) { + label << Gtkmm2ext::markup_escape_text ((*bundle)->name ()); + have_label = true; + break; + } + } + } + + /* Is each main-typed channel only connected to a physical output ? */ + if (!have_label && each_typed_port_has_one_connection) { + ostringstream temp_label; + vector phys; + string playorcapture; + if (_input) { + _route->session ().engine ().get_physical_inputs (dt, phys); + playorcapture = "capture_"; + } else { + _route->session ().engine ().get_physical_outputs (dt, phys); + playorcapture = "playback_"; + } + for (PortSet::iterator port = io->ports ().begin (dt); + port != io->ports ().end (dt); + ++port) { + string pn = ""; + for (vector::iterator s = phys.begin (); + s != phys.end (); + ++s) { + if (!port->connected_to (*s)) { + continue; + } + pn = AudioEngine::instance ()->get_pretty_name_by_name (*s); + if (pn.empty ()) { + string::size_type start = (*s).find (playorcapture); + if (start != string::npos) { + pn = (*s).substr (start + playorcapture.size ()); + } + } + break; + } + + if (pn.empty ()) { + temp_label.str (""); /* erase the failed attempt */ + break; + } + if (port != io->ports ().begin (dt)) + temp_label << "/"; + temp_label << pn; + } + + if (!temp_label.str ().empty ()) { + label << temp_label.str (); + have_label = true; + } + } + + /* Is each main-typed channel connected to a single and different port with + * the same client name (e.g. another JACK client) ? */ + if (!have_label && each_typed_port_has_one_connection) { + string maybe_client = ""; + vector connections; + for (PortSet::iterator port = io->ports ().begin (dt); + port != io->ports ().end (dt); + ++port) { + port_connections.clear (); + port->get_connections (port_connections); + string connection = port_connections.front (); + + vector::iterator i = connections.begin (); + while (i != connections.end () && *i != connection) { + ++i; + } + if (i != connections.end ()) { + break; /* duplicate connection */ + } + connections.push_back (connection); + + connection = connection.substr (0, connection.find (":")); + + if (maybe_client.empty ()) { + maybe_client = connection; + } + if (maybe_client != connection) { + break; + } + } + if (connections.size () == io->n_ports ().n (dt)) { + label << maybe_client; + have_label = true; + } + } + + /* Odd configuration */ + if (!have_label) { + label << "*" << total_connection_count << "*"; + } + + if (total_connection_count > typed_connection_count) { + label << "\u2295"; /* circled plus */ + } + + set_text (label.str ()); + set_tooltip (this, tooltip.str ()); +} + +void +IOButton::maybe_add_bundle_to_menu (boost::shared_ptr b, ARDOUR::BundleList const& /*current*/, ARDOUR::DataType type) +{ + using namespace Gtk::Menu_Helpers; + + if (_input) { + /* The bundle should be a source with matching inputs, but not ours */ + if (b->ports_are_outputs () == false || b->nchannels () != _route->n_inputs () || *b == *_route->output ()->bundle ()) { + return; + } + } else { + /* The bundle should be sink, but not ours */ + if (b->ports_are_inputs () == false || *b == *_route->input ()->bundle ()) { + return; + } + + /* Don't add the monitor input unless we are Master */ + boost::shared_ptr monitor = _route->session ().monitor_out (); + if ((!_route->is_master ()) && monitor && b->has_same_ports (monitor->input ()->bundle ())) { + return; + } + + /* It should either match exactly our outputs (if |type| is DataType::NIL) + * or have the same number of |type| channels than our outputs. */ + if (type == DataType::NIL) { + if (b->nchannels () != _route->n_outputs ()) { + return; + } + } else { + if (b->nchannels ().n (type) != _route->n_outputs ().n (type)) + return; + } + } + + /* Avoid adding duplicates */ + list>::iterator i = _menu_bundles.begin (); + while (i != _menu_bundles.end () && b->has_same_ports (*i) == false) { + ++i; + } + if (i != _menu_bundles.end ()) { + return; + } + + /* Finally add the bundle to the menu */ + _menu_bundles.push_back (b); + + MenuList& citems = _menu.items (); + citems.push_back (MenuElemNoMnemonic (b->name (), sigc::bind (sigc::mem_fun (*this, &IOButton::bundle_chosen), b))); +} diff --git a/gtk2_ardour/io_button.h b/gtk2_ardour/io_button.h new file mode 100644 index 0000000000..5485b07cd9 --- /dev/null +++ b/gtk2_ardour/io_button.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2005-2021 Paul Davis + * Copyright (C) 2013-2021 Robin Gareus + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _gtkardour_io_button_h_ +#define _gtkardour_io_button_h_ + +#include + +#include +#include + +#include "widgets/ardour_button.h" + +namespace ARDOUR +{ + class Bundle; + class IO; + class Route; + class Track; + class Port; +} + +class RouteUI; + +class IOButton : public ArdourWidgets::ArdourButton +{ +public: + IOButton (bool input); + ~IOButton (); + + void set_route (boost::shared_ptr, RouteUI*); + +private: + void update (); + bool button_press (GdkEventButton*); + bool button_release (GdkEventButton*); + void button_resized (Gtk::Allocation&); + void port_pretty_name_changed (std::string); + void port_connected_or_disconnected (boost::weak_ptr, boost::weak_ptr); + void maybe_add_bundle_to_menu (boost::shared_ptr, ARDOUR::BundleList const&, ARDOUR::DataType = ARDOUR::DataType::NIL); + void disconnect (); + void add_port (ARDOUR::DataType); + void bundle_chosen (boost::shared_ptr); + + boost::shared_ptr io () const; + boost::shared_ptr track () const; + ARDOUR::DataType guess_main_type (bool favor_connected = true) const; + + bool _input; + boost::shared_ptr _route; + RouteUI* _route_ui; + Gtk::Menu _menu; + std::list> _menu_bundles; + PBD::ScopedConnectionList _connections; +}; + +#endif diff --git a/gtk2_ardour/mixer_strip.cc b/gtk2_ardour/mixer_strip.cc index d968f08d68..3c2a7b2413 100644 --- a/gtk2_ardour/mixer_strip.cc +++ b/gtk2_ardour/mixer_strip.cc @@ -118,6 +118,8 @@ MixerStrip::MixerStrip (Mixer_UI& mx, Session* sess, bool in_mixer) , mute_solo_table (1, 2) , master_volume_table (2, 2) , bottom_button_table (1, 3) + , input_button (true) + , output_button (false) , monitor_section_button (0) , midi_input_enable_button (0) , _plugin_insert_cnt (0) @@ -154,6 +156,8 @@ MixerStrip::MixerStrip (Mixer_UI& mx, Session* sess, boost::shared_ptr rt , mute_solo_table (1, 2) , master_volume_table (1, 2) , bottom_button_table (1, 3) + , input_button (true) + , output_button (false) , monitor_section_button (0) , midi_input_enable_button (0) , _plugin_insert_cnt (0) @@ -196,14 +200,8 @@ MixerStrip::init () set_tooltip (&hide_button, _("Hide this mixer strip")); input_button_box.set_spacing(2); - - input_button.set_text (_("Input")); - input_button.set_name ("mixer strip button"); input_button_box.pack_start (input_button, true, true); - output_button.set_text (_("Output")); - output_button.set_name ("mixer strip button"); - bottom_button_table.attach (gpm.meter_point_button, 2, 3, 0, 1); hide_button.set_events (hide_button.get_events() & ~(Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK)); @@ -370,17 +368,6 @@ MixerStrip::init () _packed = false; _embedded = false; - input_button.signal_button_press_event().connect (sigc::mem_fun(*this, &MixerStrip::input_press), false); - input_button.signal_button_release_event().connect (sigc::mem_fun(*this, &MixerStrip::input_release), false); - input_button.signal_size_allocate().connect (sigc::mem_fun (*this, &MixerStrip::input_button_resized)); - - input_button.set_text_ellipsize (Pango::ELLIPSIZE_MIDDLE); - output_button.set_text_ellipsize (Pango::ELLIPSIZE_MIDDLE); - - output_button.signal_button_press_event().connect (sigc::mem_fun(*this, &MixerStrip::output_press), false); - output_button.signal_button_release_event().connect (sigc::mem_fun(*this, &MixerStrip::output_release), false); - output_button.signal_size_allocate().connect (sigc::mem_fun (*this, &MixerStrip::output_button_resized)); - number_label.signal_button_press_event().connect (sigc::mem_fun(*this, &MixerStrip::number_button_button_press), false); name_button.signal_button_press_event().connect (sigc::mem_fun(*this, &MixerStrip::name_button_button_press), false); @@ -403,14 +390,6 @@ MixerStrip::init () set_flags (get_flags() | Gtk::CAN_FOCUS); - AudioEngine::instance()->PortConnectedOrDisconnected.connect ( - *this, invalidator (*this), boost::bind (&MixerStrip::port_connected_or_disconnected, this, _1, _3), gui_context () - ); - - AudioEngine::instance()->PortPrettyNameChanged.connect ( - *this, invalidator (*this), boost::bind (&MixerStrip::port_pretty_name_changed, this, _1), gui_context () - ); - /* Add the widgets under visibility control to the VisibilityGroup; the names used here must be the same as those used in RCOptionEditor so that the configuration changes are recognised when they occur. @@ -720,6 +699,9 @@ MixerStrip::set_route (boost::shared_ptr rt) } } + input_button.set_route (route (), this); + output_button.set_route (route (), this); + gpm.meter_point_button.set_text (meter_point_string (_route->meter_point())); delete route_ops_menu; @@ -885,310 +867,6 @@ MixerStrip::set_packed (bool yn) set_gui_property ("visible", _packed); } -struct RouteCompareByName { - bool operator() (boost::shared_ptr a, boost::shared_ptr b) { - return a->name().compare (b->name()) < 0; - } -}; - -gint -MixerStrip::output_release (GdkEventButton *ev) -{ - switch (ev->button) { - case 3: - edit_output_configuration (); - break; - } - - return false; -} - -gint -MixerStrip::output_press (GdkEventButton *ev) -{ - using namespace Menu_Helpers; - if (!ARDOUR_UI_UTILS::engine_is_running ()) { - return true; - } - - MenuList& citems = output_menu.items(); - switch (ev->button) { - - case 3: - return false; //wait for the mouse-up to pop the dialog - - case 1: - { - output_menu.set_name ("ArdourContextMenu"); - citems.clear (); - output_menu_bundles.clear (); - - citems.push_back (MenuElem (_("Disconnect"), sigc::mem_fun (*(static_cast(this)), &RouteUI::disconnect_output))); - - citems.push_back (SeparatorElem()); - uint32_t const n_with_separator = citems.size (); - - ARDOUR::BundleList current = _route->output()->bundles_connected (); - - boost::shared_ptr b = _session->bundles (); - - /* guess the user-intended main type of the route output */ - DataType intended_type = guess_main_type(false); - - /* try adding the master bus first */ - boost::shared_ptr master = _session->master_out(); - if (master) { - maybe_add_bundle_to_output_menu (master->input()->bundle(), current, intended_type); - } - - /* then other routes inputs */ - RouteList copy = _session->get_routelist (); - copy.sort (RouteCompareByName ()); - for (ARDOUR::RouteList::const_iterator i = copy.begin(); i != copy.end(); ++i) { - maybe_add_bundle_to_output_menu ((*i)->input()->bundle(), current, intended_type); - } - - /* then try adding user bundles, often labeled/grouped physical inputs */ - for (ARDOUR::BundleList::iterator i = b->begin(); i != b->end(); ++i) { - if (boost::dynamic_pointer_cast (*i)) { - maybe_add_bundle_to_output_menu (*i, current, intended_type); - } - } - - /* then all other bundles, including physical outs or other sofware */ - for (ARDOUR::BundleList::iterator i = b->begin(); i != b->end(); ++i) { - if (boost::dynamic_pointer_cast (*i) == 0) { - maybe_add_bundle_to_output_menu (*i, current, intended_type); - } - } - - if (citems.size() == n_with_separator) { - /* no routes added; remove the separator */ - citems.pop_back (); - } - - citems.push_back (SeparatorElem()); - - if (!ARDOUR::Profile->get_mixbus()) { - bool need_separator = false; - for (DataType::iterator i = DataType::begin(); i != DataType::end(); ++i) { - if (!_route->output()->can_add_port (*i)) { - continue; - } - need_separator = true; - citems.push_back ( - MenuElem ( - string_compose (_("Add %1 port"), (*i).to_i18n_string()), - sigc::bind (sigc::mem_fun (*this, &MixerStrip::add_output_port), *i) - ) - ); - } - if (need_separator) { - citems.push_back (SeparatorElem()); - } - } - - citems.push_back (MenuElem (_("Routing Grid"), sigc::mem_fun (*(static_cast(this)), &RouteUI::edit_output_configuration))); - - Gtkmm2ext::anchored_menu_popup(&output_menu, &output_button, "", - 1, ev->time); - - break; - } - - default: - break; - } - return TRUE; -} - -gint -MixerStrip::input_release (GdkEventButton *ev) -{ - switch (ev->button) { - - case 3: - edit_input_configuration (); - break; - default: - break; - - } - - return false; -} - - -gint -MixerStrip::input_press (GdkEventButton *ev) -{ - using namespace Menu_Helpers; - - MenuList& citems = input_menu.items(); - input_menu.set_name ("ArdourContextMenu"); - citems.clear(); - - if (!ARDOUR_UI_UTILS::engine_is_running ()) { - return true; - } - - if (_session->actively_recording() && is_track() && track()->rec_enable_control()->get_value()) - return true; - - switch (ev->button) { - - case 3: - return false; //don't handle the mouse-down here. wait for mouse-up to pop the menu - - case 1: - { - citems.push_back (MenuElem (_("Disconnect"), sigc::mem_fun (*(static_cast(this)), &RouteUI::disconnect_input))); - - citems.push_back (SeparatorElem()); - uint32_t const n_with_separator = citems.size (); - - input_menu_bundles.clear (); - - ARDOUR::BundleList current = _route->input()->bundles_connected (); - - boost::shared_ptr b = _session->bundles (); - - /* give user bundles first chance at being in the menu */ - - for (ARDOUR::BundleList::iterator i = b->begin(); i != b->end(); ++i) { - if (boost::dynamic_pointer_cast (*i)) { - maybe_add_bundle_to_input_menu (*i, current); - } - } - - for (ARDOUR::BundleList::iterator i = b->begin(); i != b->end(); ++i) { - if (boost::dynamic_pointer_cast (*i) == 0) { - maybe_add_bundle_to_input_menu (*i, current); - } - } - - boost::shared_ptr routes = _session->get_routes (); - RouteList copy = *routes; - copy.sort (RouteCompareByName ()); - for (ARDOUR::RouteList::const_iterator i = copy.begin(); i != copy.end(); ++i) { - maybe_add_bundle_to_input_menu ((*i)->output()->bundle(), current); - } - - if (citems.size() == n_with_separator) { - /* no routes added; remove the separator */ - citems.pop_back (); - } - - citems.push_back (SeparatorElem()); - - bool need_separator = false; - for (DataType::iterator i = DataType::begin(); i != DataType::end(); ++i) { - if (!_route->input()->can_add_port (*i)) { - continue; - } - need_separator = true; - citems.push_back ( - MenuElem ( - string_compose (_("Add %1 port"), (*i).to_i18n_string()), - sigc::bind (sigc::mem_fun (*this, &MixerStrip::add_input_port), *i) - ) - ); - } - if (need_separator) { - citems.push_back (SeparatorElem()); - } - - citems.push_back (MenuElem (_("Routing Grid"), sigc::mem_fun (*(static_cast(this)), &RouteUI::edit_input_configuration))); - - Gtkmm2ext::anchored_menu_popup(&input_menu, &input_button, "", - 1, ev->time); - - break; - } - default: - break; - } - return TRUE; -} - -void -MixerStrip::bundle_input_chosen (boost::shared_ptr c) -{ - _route->input()->connect_ports_to_bundle (c, true, this); -} - -void -MixerStrip::bundle_output_chosen (boost::shared_ptr c) -{ - _route->output()->connect_ports_to_bundle (c, true, true, this); -} - -void -MixerStrip::maybe_add_bundle_to_input_menu (boost::shared_ptr b, ARDOUR::BundleList const& /*current*/) -{ - using namespace Menu_Helpers; - - if (b->ports_are_outputs() == false || b->nchannels() != _route->n_inputs() || *b == *_route->output()->bundle()) { - return; - } - - list >::iterator i = input_menu_bundles.begin (); - while (i != input_menu_bundles.end() && b->has_same_ports (*i) == false) { - ++i; - } - - if (i != input_menu_bundles.end()) { - return; - } - - input_menu_bundles.push_back (b); - - MenuList& citems = input_menu.items(); - citems.push_back (MenuElemNoMnemonic (b->name (), sigc::bind (sigc::mem_fun(*this, &MixerStrip::bundle_input_chosen), b))); -} - -void -MixerStrip::maybe_add_bundle_to_output_menu (boost::shared_ptr b, ARDOUR::BundleList const& /*current*/, - DataType type) -{ - using namespace Menu_Helpers; - - /* The bundle should be an input one, but not ours */ - if (b->ports_are_inputs() == false || *b == *_route->input()->bundle()) { - return; - } - - /* Don't add the monitor input unless we are Master */ - boost::shared_ptr monitor = _session->monitor_out(); - if ((!_route->is_master()) && monitor && b->has_same_ports (monitor->input()->bundle())) - return; - - /* It should either match exactly our outputs (if |type| is DataType::NIL) - * or have the same number of |type| channels than our outputs. */ - if (type == DataType::NIL) { - if(b->nchannels() != _route->n_outputs()) - return; - } else { - if (b->nchannels().n(type) != _route->n_outputs().n(type)) - return; - } - - /* Avoid adding duplicates */ - list >::iterator i = output_menu_bundles.begin (); - while (i != output_menu_bundles.end() && b->has_same_ports (*i) == false) { - ++i; - } - if (i != output_menu_bundles.end()) { - return; - } - - /* Now add the bundle to the menu */ - output_menu_bundles.push_back (b); - - MenuList& citems = output_menu.items(); - citems.push_back (MenuElemNoMnemonic (b->name (), sigc::bind (sigc::mem_fun(*this, &MixerStrip::bundle_output_chosen), b))); -} - void MixerStrip::connect_to_pan () { @@ -1233,305 +911,10 @@ MixerStrip::update_panner_choices () panners.set_available_panners(PannerManager::instance().PannerManager::get_available_panners(in, out)); } -DataType -MixerStrip::guess_main_type(bool for_input, bool favor_connected) const -{ - /* The heuristic follows these principles: - * A) If all ports that the user connected are of the same type, then he - * very probably intends to use the IO with that type. A common subcase - * is when the IO has only ports of the same type (connected or not). - * B) If several types of ports are connected, then we should guess based - * on the likeliness of the user wanting to use a given type. - * We assume that the DataTypes are ordered from the most likely to the - * least likely when iterating or comparing them with "<". - * C) If no port is connected, the same logic can be applied with all ports - * instead of connected ones. TODO: Try other ideas, for instance look at - * the last plugin output when |for_input| is false (note: when StrictIO - * the outs of the last plugin should be the same as the outs of the route - * modulo the panner which forwards non-audio anyway). - * All of these constraints are respected by the following algorithm that - * just returns the most likely datatype found in connected ports if any, or - * available ports if any (since if all ports are of the same type, the most - * likely found will be that one obviously). */ - - boost::shared_ptr io = for_input ? _route->input() : _route->output(); - - /* Find most likely type among connected ports */ - if (favor_connected) { - DataType type = DataType::NIL; /* NIL is always last so least likely */ - for (PortSet::iterator p = io->ports().begin(); p != io->ports().end(); ++p) { - if (p->connected() && p->type() < type) - type = p->type(); - } - if (type != DataType::NIL) { - /* There has been a connected port (necessarily non-NIL) */ - return type; - } - } - - /* Find most likely type among available ports. - * The iterator stops before NIL. */ - for (DataType::iterator t = DataType::begin(); t != DataType::end(); ++t) { - if (io->n_ports().n(*t) > 0) - return *t; - } - - /* No port at all, return the most likely datatype by default */ - return DataType::front(); -} - -/* - * Output port labelling - * - * Case 1: Each output has one connection, all connections are to system:playback_%i - * out 1 -> system:playback_1 - * out 2 -> system:playback_2 - * out 3 -> system:playback_3 - * Display as: 1/2/3 - * - * Case 2: Each output has one connection, all connections are to ardour:track_x/in 1 - * out 1 -> ardour:track_x/in 1 - * out 2 -> ardour:track_x/in 2 - * Display as: track_x - * - * Case 3: Each output has one connection, all connections are to Jack client "program x" - * out 1 -> program x:foo - * out 2 -> program x:foo - * Display as: program x - * - * Case 4: No connections (Disconnected) - * Display as: - - * - * Default case (unusual routing): - * Display as: *number of connections* - * - * - * Tooltips - * - * .-----------------------------------------------. - * | Mixdown | - * | out 1 -> ardour:master/in 1, jamin:input/in 1 | - * | out 2 -> ardour:master/in 2, jamin:input/in 2 | - * '-----------------------------------------------' - * .-----------------------------------------------. - * | Guitar SM58 | - * | Disconnected | - * '-----------------------------------------------' - */ - -void -MixerStrip::update_io_button (bool for_input) -{ - ostringstream tooltip; - ostringstream label; - bool have_label = false; - - uint32_t total_connection_count = 0; - uint32_t typed_connection_count = 0; - bool each_typed_port_has_one_connection = true; - - DataType dt = guess_main_type(for_input); - boost::shared_ptr io = for_input ? _route->input() : _route->output(); - - /* Fill in the tooltip. Also count: - * - The total number of connections. - * - The number of main-typed connections. - * - Whether each main-typed port has exactly one connection. */ - if (for_input) { - tooltip << string_compose (_("INPUT to %1"), - Gtkmm2ext::markup_escape_text (_route->name())); - } else { - tooltip << string_compose (_("OUTPUT from %1"), - Gtkmm2ext::markup_escape_text (_route->name())); - } - - string arrow = Gtkmm2ext::markup_escape_text(for_input ? " <- " : " -> "); - vector port_connections; - for (PortSet::iterator port = io->ports().begin(); - port != io->ports().end(); - ++port) { - port_connections.clear(); - port->get_connections(port_connections); - - uint32_t port_connection_count = 0; - - for (vector::iterator i = port_connections.begin(); - i != port_connections.end(); - ++i) { - ++port_connection_count; - - if (port_connection_count == 1) { - tooltip << endl << Gtkmm2ext::markup_escape_text ( - port->name().substr(port->name().find("/") + 1)); - tooltip << arrow; - } else { - tooltip << ", "; - } - - tooltip << Gtkmm2ext::markup_escape_text(*i); - } - - total_connection_count += port_connection_count; - if (port->type() == dt) { - typed_connection_count += port_connection_count; - each_typed_port_has_one_connection &= (port_connection_count == 1); - } - - } - - if (total_connection_count == 0) { - tooltip << endl << _("Disconnected"); - } - - if (typed_connection_count == 0) { - label << "-"; - have_label = true; - } - - /* Are all main-typed channels connected to the same route ? */ - if (!have_label) { - boost::shared_ptr routes = _session->get_routes (); - for (ARDOUR::RouteList::const_iterator route = routes->begin(); - route != routes->end(); - ++route) { - boost::shared_ptr dest_io = - for_input ? (*route)->output() : (*route)->input(); - if (io->bundle()->connected_to(dest_io->bundle(), - _session->engine(), - dt, true)) { - label << Gtkmm2ext::markup_escape_text ((*route)->name()); - have_label = true; - break; - } - } - } - - /* Are all main-typed channels connected to the same (user) bundle ? */ - if (!have_label) { - boost::shared_ptr bundles = _session->bundles (); - boost::shared_ptr ap = boost::dynamic_pointer_cast (_session->vkbd_output_port()); - std::string vkbd_portname = AudioEngine::instance ()->make_port_name_non_relative (ap->name ()); - for (ARDOUR::BundleList::iterator bundle = bundles->begin(); - bundle != bundles->end(); - ++bundle) { - if (boost::dynamic_pointer_cast (*bundle) == 0) { - if (!(*bundle)->offers_port (vkbd_portname)) { - continue; - } - } - if (io->bundle()->connected_to (*bundle, _session->engine(), dt, true)) { - label << Gtkmm2ext::markup_escape_text ((*bundle)->name()); - have_label = true; - break; - } - } - } - - /* Is each main-typed channel only connected to a physical output ? */ - if (!have_label && each_typed_port_has_one_connection) { - ostringstream temp_label; - vector phys; - string playorcapture; - if (for_input) { - _session->engine().get_physical_inputs(dt, phys); - playorcapture = "capture_"; - } else { - _session->engine().get_physical_outputs(dt, phys); - playorcapture = "playback_"; - } - for (PortSet::iterator port = io->ports().begin(dt); - port != io->ports().end(dt); - ++port) { - string pn = ""; - for (vector::iterator s = phys.begin(); - s != phys.end(); - ++s) { - if (!port->connected_to(*s)) - continue; - pn = AudioEngine::instance()->get_pretty_name_by_name(*s); - if (pn.empty()) { - string::size_type start = (*s).find(playorcapture); - if (start != string::npos) { - pn = (*s).substr(start + playorcapture.size()); - } - } - break; - } - if (pn.empty()) { - temp_label.str(""); /* erase the failed attempt */ - break; - } - if (port != io->ports().begin(dt)) - temp_label << "/"; - temp_label << pn; - } - - if (!temp_label.str().empty()) { - label << temp_label.str(); - have_label = true; - } - } - - /* Is each main-typed channel connected to a single and different port with - * the same client name (e.g. another JACK client) ? */ - if (!have_label && each_typed_port_has_one_connection) { - string maybe_client = ""; - vector connections; - for (PortSet::iterator port = io->ports().begin(dt); - port != io->ports().end(dt); - ++port) { - port_connections.clear(); - port->get_connections(port_connections); - string connection = port_connections.front(); - - vector::iterator i = connections.begin(); - while (i != connections.end() && *i != connection) { - ++i; - } - if (i != connections.end()) - break; /* duplicate connection */ - connections.push_back(connection); - - connection = connection.substr(0, connection.find(":")); - if (maybe_client.empty()) - maybe_client = connection; - if (maybe_client != connection) - break; - } - if (connections.size() == io->n_ports().n(dt)) { - label << maybe_client; - have_label = true; - } - } - - /* Odd configuration */ - if (!have_label) { - label << "*" << total_connection_count << "*"; - } - - if (total_connection_count > typed_connection_count) { - label << "\u2295"; /* circled plus */ - } - - /* Actually set the properties of the button */ - char * cstr = new char[tooltip.str().size() + 1]; - strcpy(cstr, tooltip.str().c_str()); - - if (for_input) { - input_button.set_text (label.str()); - set_tooltip (&input_button, cstr); - } else { - output_button.set_text (label.str()); - set_tooltip (&output_button, cstr); - } - - delete [] cstr; -} void MixerStrip::update_input_display () { - update_io_button (true); panners.setup_pan (); if (has_audio_outputs ()) { @@ -1545,7 +928,6 @@ MixerStrip::update_input_display () void MixerStrip::update_output_display () { - update_io_button (false); gpm.setup_meters (); panners.setup_pan (); @@ -1569,36 +951,6 @@ MixerStrip::io_changed_proxy () Glib::signal_idle().connect_once (sigc::mem_fun (*this, &MixerStrip::update_trim_control)); } -void -MixerStrip::port_connected_or_disconnected (boost::weak_ptr wa, boost::weak_ptr wb) -{ - boost::shared_ptr a = wa.lock (); - boost::shared_ptr b = wb.lock (); - - if ((a && _route->input()->has_port (a)) || (b && _route->input()->has_port (b))) { - update_input_display (); - set_width_enum (_width, this); - } - - if ((a && _route->output()->has_port (a)) || (b && _route->output()->has_port (b))) { - update_output_display (); - set_width_enum (_width, this); - } -} - -void -MixerStrip::port_pretty_name_changed (std::string pn) -{ - if (_route->input()->connected_to (pn)) { - update_input_display (); - set_width_enum (_width, this); - } - if (_route->output()->connected_to (pn)) { - update_output_display (); - set_width_enum (_width, this); - } -} - void MixerStrip::setup_comment_button () { @@ -1924,18 +1276,6 @@ MixerStrip::name_changed () } } -void -MixerStrip::input_button_resized (Gtk::Allocation& alloc) -{ - input_button.set_layout_ellipsize_width (alloc.get_width() * PANGO_SCALE); -} - -void -MixerStrip::output_button_resized (Gtk::Allocation& alloc) -{ - output_button.set_layout_ellipsize_width (alloc.get_width() * PANGO_SCALE); -} - void MixerStrip::name_button_resized (Gtk::Allocation& alloc) { diff --git a/gtk2_ardour/mixer_strip.h b/gtk2_ardour/mixer_strip.h index e895368b85..c3afdeb273 100644 --- a/gtk2_ardour/mixer_strip.h +++ b/gtk2_ardour/mixer_strip.h @@ -54,6 +54,7 @@ #include "axis_view.h" #include "control_slave_ui.h" +#include "io_button.h" #include "route_ui.h" #include "gain_meter.h" #include "panner_ui.h" @@ -201,13 +202,11 @@ private: void monitor_changed (); void monitor_section_added_or_removed (); - ArdourWidgets::ArdourButton input_button; - ArdourWidgets::ArdourButton output_button; + IOButton input_button; + IOButton output_button; ArdourWidgets::ArdourButton* monitor_section_button; - void input_button_resized (Gtk::Allocation&); - void output_button_resized (Gtk::Allocation&); void comment_button_resized (Gtk::Allocation&); ArdourWidgets::ArdourButton* midi_input_enable_button; @@ -244,24 +243,6 @@ private: ArdourWidgets::ArdourButton group_button; RouteGroupMenu* group_menu; - gint input_press (GdkEventButton *); - gint input_release (GdkEventButton *); - - gint output_press (GdkEventButton *); - gint output_release (GdkEventButton *); - - Gtk::Menu input_menu; - std::list > input_menu_bundles; - void maybe_add_bundle_to_input_menu (boost::shared_ptr, ARDOUR::BundleList const &); - - Gtk::Menu output_menu; - std::list > output_menu_bundles; - void maybe_add_bundle_to_output_menu (boost::shared_ptr, ARDOUR::BundleList const &, - ARDOUR::DataType type = ARDOUR::DataType::NIL); - - void bundle_input_chosen (boost::shared_ptr); - void bundle_output_chosen (boost::shared_ptr); - void io_changed_proxy (); Gtk::Menu *send_action_menu; @@ -315,12 +296,6 @@ private: void reset_strip_style (); void update_sensitivity (); - ARDOUR::DataType guess_main_type(bool for_input, bool favor_connected = true) const; - - void update_io_button (bool input_button); - void port_connected_or_disconnected (boost::weak_ptr, boost::weak_ptr); - void port_pretty_name_changed (std::string); - bool mixer_strip_enter_event ( GdkEventCrossing * ); bool mixer_strip_leave_event ( GdkEventCrossing * ); diff --git a/gtk2_ardour/wscript b/gtk2_ardour/wscript index 09a2f3b6ba..8b2a511e8b 100644 --- a/gtk2_ardour/wscript +++ b/gtk2_ardour/wscript @@ -130,6 +130,7 @@ gtk2_ardour_sources = [ 'insert_remove_time_dialog.cc', 'instrument_selector.cc', 'interthread_progress_window.cc', + 'io_button.cc', 'io_selector.cc', 'hit.cc', 'keyboard.cc',