13
0

Separate IO connection button into dedicated class

This commit is contained in:
Robin Gareus 2021-01-29 03:31:37 +01:00
parent 33088c728a
commit 3e7b2bb3f5
Signed by: rgareus
GPG Key ID: A090BCE02CF57F04
5 changed files with 708 additions and 695 deletions

624
gtk2_ardour/io_button.cc Normal file
View File

@ -0,0 +1,624 @@
/*
* Copyright (C) 2005-2017 Paul Davis <paul@linuxaudiosystems.com>
* Copyright (C) 2012-2021 Robin Gareus <robin@gareus.org>
*
* 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<ARDOUR::Route> 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<IO>
IOButton::io () const
{
return _input ? _route->input () : _route->output ();
}
boost::shared_ptr<Track>
IOButton::track () const
{
return boost::dynamic_pointer_cast<Track> (_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<Port> wa, boost::weak_ptr<Port> wb)
{
boost::shared_ptr<Port> a = wa.lock ();
boost::shared_ptr<Port> b = wb.lock ();
if ((a && io ()->has_port (a)) || (b && io ()->has_port (b))) {
update ();
}
}
void
IOButton::bundle_chosen (boost::shared_ptr<ARDOUR::Bundle> 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<Route> a, boost::shared_ptr<Route> 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<ARDOUR::BundleList> 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<UserBundle> (*i)) {
maybe_add_bundle_to_menu (*i, current);
}
}
for (ARDOUR::BundleList::iterator i = b->begin (); i != b->end (); ++i) {
if (boost::dynamic_pointer_cast<UserBundle> (*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<Route> master = _route->session ().master_out ();
if (master) {
maybe_add_bundle_to_menu (master->input ()->bundle (), current, intended_type);
}
}
boost::shared_ptr<ARDOUR::RouteList> 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<UserBundle> (*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<UserBundle> (*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> 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> 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 (_("<b>INPUT</b> to %1"),
Gtkmm2ext::markup_escape_text (_route->name ()));
} else {
tooltip << string_compose (_("<b>OUTPUT</b> from %1"),
Gtkmm2ext::markup_escape_text (_route->name ()));
}
string arrow = Gtkmm2ext::markup_escape_text (_input ? " <- " : " -> ");
vector<string> 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<string>::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<ARDOUR::RouteList> routes = _route->session ().get_routes ();
for (ARDOUR::RouteList::const_iterator route = routes->begin ();
route != routes->end ();
++route) {
boost::shared_ptr<IO> 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<ARDOUR::BundleList> bundles = _route->session ().bundles ();
boost::shared_ptr<ARDOUR::Port> ap = boost::dynamic_pointer_cast<ARDOUR::Port> (_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<UserBundle> (*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<string> 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<string>::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<string> 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<string>::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<Bundle> 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<Route> 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<boost::shared_ptr<Bundle>>::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)));
}

73
gtk2_ardour/io_button.h Normal file
View File

@ -0,0 +1,73 @@
/*
* Copyright (C) 2005-2021 Paul Davis <paul@linuxaudiosystems.com>
* Copyright (C) 2013-2021 Robin Gareus <robin@gareus.org>
*
* 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 <list>
#include <boost/shared_ptr.hpp>
#include <gtkmm/menu.h>
#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<ARDOUR::Route>, 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<ARDOUR::Port>, boost::weak_ptr<ARDOUR::Port>);
void maybe_add_bundle_to_menu (boost::shared_ptr<ARDOUR::Bundle>, ARDOUR::BundleList const&, ARDOUR::DataType = ARDOUR::DataType::NIL);
void disconnect ();
void add_port (ARDOUR::DataType);
void bundle_chosen (boost::shared_ptr<ARDOUR::Bundle>);
boost::shared_ptr<ARDOUR::IO> io () const;
boost::shared_ptr<ARDOUR::Track> track () const;
ARDOUR::DataType guess_main_type (bool favor_connected = true) const;
bool _input;
boost::shared_ptr<ARDOUR::Route> _route;
RouteUI* _route_ui;
Gtk::Menu _menu;
std::list<boost::shared_ptr<ARDOUR::Bundle>> _menu_bundles;
PBD::ScopedConnectionList _connections;
};
#endif

View File

@ -118,6 +118,8 @@ MixerStrip::MixerStrip (Mixer_UI& mx, Session* sess, bool in_mixer)
, mute_solo_table (1, 2) , mute_solo_table (1, 2)
, master_volume_table (2, 2) , master_volume_table (2, 2)
, bottom_button_table (1, 3) , bottom_button_table (1, 3)
, input_button (true)
, output_button (false)
, monitor_section_button (0) , monitor_section_button (0)
, midi_input_enable_button (0) , midi_input_enable_button (0)
, _plugin_insert_cnt (0) , _plugin_insert_cnt (0)
@ -154,6 +156,8 @@ MixerStrip::MixerStrip (Mixer_UI& mx, Session* sess, boost::shared_ptr<Route> rt
, mute_solo_table (1, 2) , mute_solo_table (1, 2)
, master_volume_table (1, 2) , master_volume_table (1, 2)
, bottom_button_table (1, 3) , bottom_button_table (1, 3)
, input_button (true)
, output_button (false)
, monitor_section_button (0) , monitor_section_button (0)
, midi_input_enable_button (0) , midi_input_enable_button (0)
, _plugin_insert_cnt (0) , _plugin_insert_cnt (0)
@ -196,14 +200,8 @@ MixerStrip::init ()
set_tooltip (&hide_button, _("Hide this mixer strip")); set_tooltip (&hide_button, _("Hide this mixer strip"));
input_button_box.set_spacing(2); 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); 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); 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)); hide_button.set_events (hide_button.get_events() & ~(Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK));
@ -370,17 +368,6 @@ MixerStrip::init ()
_packed = false; _packed = false;
_embedded = 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); 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); 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); 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 /* 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 must be the same as those used in RCOptionEditor so that the configuration changes
are recognised when they occur. are recognised when they occur.
@ -720,6 +699,9 @@ MixerStrip::set_route (boost::shared_ptr<Route> 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())); gpm.meter_point_button.set_text (meter_point_string (_route->meter_point()));
delete route_ops_menu; delete route_ops_menu;
@ -885,310 +867,6 @@ MixerStrip::set_packed (bool yn)
set_gui_property ("visible", _packed); set_gui_property ("visible", _packed);
} }
struct RouteCompareByName {
bool operator() (boost::shared_ptr<Route> a, boost::shared_ptr<Route> 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<RouteUI*>(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<ARDOUR::BundleList> 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<Route> 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<UserBundle> (*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<UserBundle> (*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<RouteUI*>(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<RouteUI*>(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<ARDOUR::BundleList> 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<UserBundle> (*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<UserBundle> (*i) == 0) {
maybe_add_bundle_to_input_menu (*i, current);
}
}
boost::shared_ptr<ARDOUR::RouteList> 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<RouteUI*>(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<ARDOUR::Bundle> c)
{
_route->input()->connect_ports_to_bundle (c, true, this);
}
void
MixerStrip::bundle_output_chosen (boost::shared_ptr<ARDOUR::Bundle> c)
{
_route->output()->connect_ports_to_bundle (c, true, true, this);
}
void
MixerStrip::maybe_add_bundle_to_input_menu (boost::shared_ptr<Bundle> 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<boost::shared_ptr<Bundle> >::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<Bundle> 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<Route> 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<boost::shared_ptr<Bundle> >::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 void
MixerStrip::connect_to_pan () MixerStrip::connect_to_pan ()
{ {
@ -1233,305 +911,10 @@ MixerStrip::update_panner_choices ()
panners.set_available_panners(PannerManager::instance().PannerManager::get_available_panners(in, out)); 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> 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> 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 (_("<b>INPUT</b> to %1"),
Gtkmm2ext::markup_escape_text (_route->name()));
} else {
tooltip << string_compose (_("<b>OUTPUT</b> from %1"),
Gtkmm2ext::markup_escape_text (_route->name()));
}
string arrow = Gtkmm2ext::markup_escape_text(for_input ? " <- " : " -> ");
vector<string> 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<string>::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<ARDOUR::RouteList> routes = _session->get_routes ();
for (ARDOUR::RouteList::const_iterator route = routes->begin();
route != routes->end();
++route) {
boost::shared_ptr<IO> 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<ARDOUR::BundleList> bundles = _session->bundles ();
boost::shared_ptr<ARDOUR::Port> ap = boost::dynamic_pointer_cast<ARDOUR::Port> (_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<UserBundle> (*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<string> 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<string>::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<string> 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<string>::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 void
MixerStrip::update_input_display () MixerStrip::update_input_display ()
{ {
update_io_button (true);
panners.setup_pan (); panners.setup_pan ();
if (has_audio_outputs ()) { if (has_audio_outputs ()) {
@ -1545,7 +928,6 @@ MixerStrip::update_input_display ()
void void
MixerStrip::update_output_display () MixerStrip::update_output_display ()
{ {
update_io_button (false);
gpm.setup_meters (); gpm.setup_meters ();
panners.setup_pan (); panners.setup_pan ();
@ -1569,36 +951,6 @@ MixerStrip::io_changed_proxy ()
Glib::signal_idle().connect_once (sigc::mem_fun (*this, &MixerStrip::update_trim_control)); Glib::signal_idle().connect_once (sigc::mem_fun (*this, &MixerStrip::update_trim_control));
} }
void
MixerStrip::port_connected_or_disconnected (boost::weak_ptr<Port> wa, boost::weak_ptr<Port> wb)
{
boost::shared_ptr<Port> a = wa.lock ();
boost::shared_ptr<Port> 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 void
MixerStrip::setup_comment_button () 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 void
MixerStrip::name_button_resized (Gtk::Allocation& alloc) MixerStrip::name_button_resized (Gtk::Allocation& alloc)
{ {

View File

@ -54,6 +54,7 @@
#include "axis_view.h" #include "axis_view.h"
#include "control_slave_ui.h" #include "control_slave_ui.h"
#include "io_button.h"
#include "route_ui.h" #include "route_ui.h"
#include "gain_meter.h" #include "gain_meter.h"
#include "panner_ui.h" #include "panner_ui.h"
@ -201,13 +202,11 @@ private:
void monitor_changed (); void monitor_changed ();
void monitor_section_added_or_removed (); void monitor_section_added_or_removed ();
ArdourWidgets::ArdourButton input_button; IOButton input_button;
ArdourWidgets::ArdourButton output_button; IOButton output_button;
ArdourWidgets::ArdourButton* monitor_section_button; ArdourWidgets::ArdourButton* monitor_section_button;
void input_button_resized (Gtk::Allocation&);
void output_button_resized (Gtk::Allocation&);
void comment_button_resized (Gtk::Allocation&); void comment_button_resized (Gtk::Allocation&);
ArdourWidgets::ArdourButton* midi_input_enable_button; ArdourWidgets::ArdourButton* midi_input_enable_button;
@ -244,24 +243,6 @@ private:
ArdourWidgets::ArdourButton group_button; ArdourWidgets::ArdourButton group_button;
RouteGroupMenu* group_menu; 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<boost::shared_ptr<ARDOUR::Bundle> > input_menu_bundles;
void maybe_add_bundle_to_input_menu (boost::shared_ptr<ARDOUR::Bundle>, ARDOUR::BundleList const &);
Gtk::Menu output_menu;
std::list<boost::shared_ptr<ARDOUR::Bundle> > output_menu_bundles;
void maybe_add_bundle_to_output_menu (boost::shared_ptr<ARDOUR::Bundle>, ARDOUR::BundleList const &,
ARDOUR::DataType type = ARDOUR::DataType::NIL);
void bundle_input_chosen (boost::shared_ptr<ARDOUR::Bundle>);
void bundle_output_chosen (boost::shared_ptr<ARDOUR::Bundle>);
void io_changed_proxy (); void io_changed_proxy ();
Gtk::Menu *send_action_menu; Gtk::Menu *send_action_menu;
@ -315,12 +296,6 @@ private:
void reset_strip_style (); void reset_strip_style ();
void update_sensitivity (); 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<ARDOUR::Port>, boost::weak_ptr<ARDOUR::Port>);
void port_pretty_name_changed (std::string);
bool mixer_strip_enter_event ( GdkEventCrossing * ); bool mixer_strip_enter_event ( GdkEventCrossing * );
bool mixer_strip_leave_event ( GdkEventCrossing * ); bool mixer_strip_leave_event ( GdkEventCrossing * );

View File

@ -130,6 +130,7 @@ gtk2_ardour_sources = [
'insert_remove_time_dialog.cc', 'insert_remove_time_dialog.cc',
'instrument_selector.cc', 'instrument_selector.cc',
'interthread_progress_window.cc', 'interthread_progress_window.cc',
'io_button.cc',
'io_selector.cc', 'io_selector.cc',
'hit.cc', 'hit.cc',
'keyboard.cc', 'keyboard.cc',