+ ChanCount out = _out;
+ ChanCount sinks = _sinks;
+ assert (add || sinks.get (dt) > 0);
+ sinks.set (dt, sinks.get (dt) + (add ? 1 : -1));
+ if (!_route ()->customize_plugin_insert (_pi, _n_plugins, out, sinks)) {
+ error_message_dialog (_("Failed to alter plugin input configuration."));
+ }
+}
+
+void
+PluginPinWidget::add_sidechain_port (DataType dt)
+{
+ assert (_session);
+ if (_session->actively_recording ()) {
+ error_message_dialog (/* unused */ "");
+ return;
+ }
+
+ boost::shared_ptr<IO> io = _pi->sidechain_input ();
+ if (!io) {
+ return;
+ }
+
+ // this triggers a PluginIoReConfigure with process and processor write lock held
+ // from /this/ thread.
+ io->add_port ("", this, dt);
+}
+
+void
+PluginPinWidget::remove_port (boost::weak_ptr<ARDOUR::Port> wp)
+{
+ assert (_session);
+ if (_session->actively_recording ()) {
+ error_message_dialog (/* unused */ "");
+ return;
+ }
+ boost::shared_ptr<ARDOUR::Port> p = wp.lock ();
+ boost::shared_ptr<IO> io = _pi->sidechain_input ();
+ if (!io || !p) {
+ return;
+ }
+ io->remove_port (p, this);
+}
+
+void
+PluginPinWidget::disconnect_port (boost::weak_ptr<ARDOUR::Port> wp)
+{
+ assert (_session);
+ if (_session->actively_recording ()) {
+ error_message_dialog (/* unused */ "");
+ return;
+ }
+
+ boost::shared_ptr<ARDOUR::Port> p = wp.lock ();
+ boost::shared_ptr<IO> io = _pi->sidechain_input ();
+ if (!io || !p) {
+ return;
+ }
+ p->disconnect_all ();
+}
+
+void
+PluginPinWidget::connect_port (boost::weak_ptr<ARDOUR::Port> wp0, boost::weak_ptr<ARDOUR::Port> wp1)
+{
+ assert (_session);
+ if (_session->actively_recording ()) {
+ error_message_dialog (/* unused */ "");
+ return;
+ }
+
+ boost::shared_ptr<ARDOUR::Port> p0 = wp0.lock ();
+ boost::shared_ptr<ARDOUR::Port> p1 = wp1.lock ();
+ boost::shared_ptr<IO> io = _pi->sidechain_input ();
+ if (!io || !p0 || !p1) {
+ return;
+ }
+ _ignore_updates = true;
+ p0->disconnect_all ();
+ _ignore_updates = false;
+ p0->connect (p1->name ());
+}
+
+void
+PluginPinWidget::add_send_from (boost::weak_ptr<ARDOUR::Port> wp, boost::weak_ptr<ARDOUR::Route> wr)
+{
+ assert (_session);
+ if (_session->actively_recording ()) {
+ error_message_dialog (/* unused */ "");
+ return;
+ }
+
+ boost::shared_ptr<Port> p = wp.lock ();
+ boost::shared_ptr<Route> r = wr.lock ();
+ boost::shared_ptr<IO> io = _pi->sidechain_input ();
+ if (!p || !r || !io) {
+ return;
+ }
+
+ boost::shared_ptr<Pannable> sendpan (new Pannable (*_session));
+ boost::shared_ptr<Send> send (new Send (*_session, r->pannable (), r->mute_master ()));
+ const ChanCount& outs (r->amp ()->input_streams ());
+ try {
+ Glib::Threads::Mutex::Lock lm (AudioEngine::instance ()->process_lock ());
+ send->output()->ensure_io (outs, false, this);
+ } catch (AudioEngine::PortRegistrationFailure& err) {
+ error << string_compose (_("Cannot set up new send: %1"), err.what ()) << endmsg;
+ return;
+ }
+
+ std::string sendname = send->name ();
+ string::size_type last_letter = sendname.find_last_not_of ("0123456789");
+ if (last_letter != string::npos) {
+ send->output ()->set_pretty_name (string_compose (_("SC %1 (%2)"),
+ r->name (),
+ sendname.substr (last_letter + 1)));
+ }
+
+ _ignore_updates = true;
+ p->disconnect_all ();
+
+ DataType dt = p->type ();
+ PortSet& ps (send->output ()->ports ());
+ for (PortSet::iterator i = ps.begin (dt); i != ps.end (dt); ++i) {
+ p->connect (&(**i));
+ }
+
+ send->set_remove_on_disconnect (true);
+ r->add_processor (send, PreFader);
+ _ignore_updates = false;
+ queue_idle_update ();
+}
+
+bool
+PluginPinWidget::sc_input_release (GdkEventButton *ev)
+{
+ assert (_session);
+ if (_session->actively_recording ()) {
+ error_message_dialog (/* unused */ "");
+ return false;
+ }
+
+ if (ev->button == 3) {
+ connect_sidechain ();
+ }
+ return false;
+}
+
+bool
+PluginPinWidget::sc_input_press (GdkEventButton *ev, boost::weak_ptr<ARDOUR::Port> wp)
+{
+ using namespace Menu_Helpers;
+ assert (_session);
+ if (!ARDOUR_UI_UTILS::engine_is_running ()) {
+ return false;
+ }
+ if (_session->actively_recording ()) {
+ error_message_dialog (/* unused */ "");
+ return false;
+ }
+
+ if (ev->button == 1) {
+ MenuList& citems = input_menu.items ();
+ input_menu.set_name ("ArdourContextMenu");
+ citems.clear ();
+
+ boost::shared_ptr<Port> p = wp.lock ();
+ if (p && p->connected ()) {
+ citems.push_back (MenuElem (_("Disconnect"), sigc::bind (sigc::mem_fun (*this, &PluginPinWidget::disconnect_port), wp)));
+ citems.push_back (SeparatorElem ());
+ }
+
+#if 0
+ // TODO add system inputs, too ?!
+ boost::shared_ptr<ARDOUR::BundleList> b = _session->bundles ();
+ for (ARDOUR::BundleList::iterator i = b->begin(); i != b->end(); ++i) {
+ for (uint32_t j = 0; j < i->nchannels ().n_total (); ++j) {
+ }
+ //maybe_add_bundle_to_input_menu (*i, current);
+ }
+#endif
+
+ RouteList copy = _session->get_routelist ();
+ copy.sort (Stripable::Sorter(true));
+ uint32_t added = 0;
+ for (ARDOUR::RouteList::const_iterator i = copy.begin (); i != copy.end (); ++i) {
+ added += maybe_add_route_to_input_menu (*i, p->type (), wp);
+ }
+
+ if (added > 0) {
+ citems.push_back (SeparatorElem ());
+ }
+ citems.push_back (MenuElem (_("Routing Grid"), sigc::mem_fun (*this, &PluginPinWidget::connect_sidechain)));
+ input_menu.popup (1, ev->time);
+ }
+ return false;
+}
+
+uint32_t
+PluginPinWidget::maybe_add_route_to_input_menu (boost::shared_ptr<Route> r, DataType dt, boost::weak_ptr<Port> wp)
+{
+ uint32_t added = 0;
+ using namespace Menu_Helpers;
+ if (r->output () == _route ()->output ()) {
+ return added;
+ }
+
+ if (_route ()->feeds_according_to_graph (r)) {
+ return added;
+ }
+
+ MenuList& citems = input_menu.items ();
+
+ /*check if there's already a send.. */
+ bool already_present = false;
+ uint32_t nth = 0;
+ boost::shared_ptr<Processor> proc;
+ /* Note: nth_send () takes a processor read-lock */
+ while ((proc = r->nth_send (nth))) {
+ boost::shared_ptr<IOProcessor> send = boost::dynamic_pointer_cast<IOProcessor> (proc);
+ if (!send || !send->output ()) {
+ ++nth;
+ continue;
+ }
+ if (send->output ()->connected_to (_pi->sidechain_input ())) {
+ // only if (send->remove_on_disconnect ()) ??
+ already_present = true;
+ break;
+ }
+ ++nth;
+ }
+ /* we're going to create the new send pre-fader, so check the route amp's data type. */
+ const ChanCount& rc (r->amp ()->input_streams ());
+ if (!already_present && rc.get (dt) > 0) {
+ citems.push_back (MenuElemNoMnemonic (r->name (), sigc::bind (sigc::mem_fun (*this, &PluginPinWidget::add_send_from), wp, boost::weak_ptr<Route> (r))));
+ ++added;
+ }
+ return added;
+}
+
+void
+PluginPinWidget::port_connected_or_disconnected (boost::weak_ptr<ARDOUR::Port> w0, boost::weak_ptr<ARDOUR::Port> w1)
+{
+ boost::shared_ptr<Port> p0 = w0.lock ();
+ boost::shared_ptr<Port> p1 = w1.lock ();
+
+ boost::shared_ptr<IO> io = _pi->sidechain_input ();
+ if (!io) { return; }
+
+ if (p0 && io->has_port (p0)) {
+ queue_idle_update ();
+ }
+ else if (p1 && io->has_port (p1)) {
+ queue_idle_update ();
+ }
+}
+
+/* lifted from ProcessorEntry::Control */
+PluginPinWidget::Control::Control (boost::shared_ptr<AutomationControl> c, string const & n)
+ : _control (c)
+ , _adjustment (gain_to_slider_position_with_max (1.0, Config->get_max_gain ()), 0, 1, 0.01, 0.1)
+ , _slider (&_adjustment, boost::shared_ptr<PBD::Controllable> (), 0, max (13.f, rintf (13.f * UIConfiguration::instance ().get_ui_scale ())))
+ , _slider_persistant_tooltip (&_slider)
+ , _ignore_ui_adjustment (false)
+ , _name (n)
+{
+ _slider.set_controllable (c);
+ box.set_padding (0, 0, 4, 4);
+
+ _slider.set_name ("ProcessorControlSlider");
+ _slider.set_text (_name);
+
+ box.add (_slider);
+ _slider.show ();
+
+ const ARDOUR::ParameterDescriptor& desc = c->desc ();
+ double const lo = c->internal_to_interface (desc.lower);
+ double const up = c->internal_to_interface (desc.upper);
+ double const normal = c->internal_to_interface (desc.normal);
+ double const smallstep = c->internal_to_interface (desc.lower + desc.smallstep);
+ double const largestep = c->internal_to_interface (desc.lower + desc.largestep);
+
+ _adjustment.set_lower (lo);
+ _adjustment.set_upper (up);
+ _adjustment.set_step_increment (smallstep);
+ _adjustment.set_page_increment (largestep);
+ _slider.set_default_value (normal);
+
+ _adjustment.signal_value_changed ().connect (sigc::mem_fun (*this, &Control::slider_adjusted));
+ // dup. currently timers are used :(
+ //c->Changed.connect (_connection, MISSING_INVALIDATOR, boost::bind (&Control::control_changed, this), gui_context ());
+
+ // yuck, do we really need to do this?
+ // according to c404374 this is only needed for send automation
+ timer_connection = Timers::rapid_connect (sigc::mem_fun (*this, &Control::control_changed));
+
+ control_changed ();
+ set_tooltip ();
+
+ /* We're providing our own PersistentTooltip */
+ set_no_tooltip_whatsoever (_slider);
+}
+
+PluginPinWidget::Control::~Control ()
+{
+ timer_connection.disconnect ();
+}
+
+void
+PluginPinWidget::Control::set_tooltip ()
+{
+ boost::shared_ptr<AutomationControl> c = _control.lock ();
+ if (!c) {
+ return;
+ }
+ std::string tt = _name + ": " + ARDOUR::value_as_string (c->desc(), c->get_value ());
+ string sm = Gtkmm2ext::markup_escape_text (tt);
+ _slider_persistant_tooltip.set_tip (sm);
+}
+
+void
+PluginPinWidget::Control::slider_adjusted ()
+{
+ if (_ignore_ui_adjustment) {
+ return;
+ }
+ boost::shared_ptr<AutomationControl> c = _control.lock ();
+ if (!c) {
+ return;
+ }
+ c->set_value ( c->interface_to_internal (_adjustment.get_value ()) , Controllable::NoGroup);
+ set_tooltip ();
+}
+
+
+void
+PluginPinWidget::Control::control_changed ()
+{
+ boost::shared_ptr<AutomationControl> c = _control.lock ();
+ if (!c) {
+ return;
+ }
+
+ _ignore_ui_adjustment = true;
+
+ // as long as rapid timers are used, only update the tooltip
+ // if the value has changed.
+ const double nval = c->internal_to_interface (c->get_value ());
+ if (_adjustment.get_value () != nval) {
+ _adjustment.set_value (nval);
+ set_tooltip ();
+ }
+
+ _ignore_ui_adjustment = false;
+}
+
+
+
+PluginPinDialog::PluginPinDialog (boost::shared_ptr<ARDOUR::PluginInsert> pi)
+ : ArdourWindow (string_compose (_("Pin Configuration: %1"), pi->name ()))
+{
+ ppw.push_back (PluginPinWidgetPtr(new PluginPinWidget (pi)));
+ add (*ppw.back());
+}
+
+
+PluginPinDialog::PluginPinDialog (boost::shared_ptr<ARDOUR::Route> r)
+ : ArdourWindow (string_compose (_("Pin Configuration: %1"), r->name ()))
+ , _route (r)
+ , _height_mapped (false)
+{
+ vbox = manage (new VBox ());
+ vbox->signal_size_allocate().connect (sigc::mem_fun (*this, &PluginPinDialog::map_height));
+ scroller = manage (new ScrolledWindow);
+ scroller->set_policy (Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
+ scroller->set_shadow_type (Gtk::SHADOW_NONE);
+ scroller->show ();
+ vbox->show ();
+ scroller->add (*vbox);
+ add (*scroller);
+
+
+ _route->foreach_processor (sigc::mem_fun (*this, &PluginPinDialog::add_processor));
+
+ _route->processors_changed.connect (
+ _route_connections, invalidator (*this), boost::bind (&PluginPinDialog::route_processors_changed, this, _1), gui_context()
+ );
+
+ _route->DropReferences.connect (
+ _route_connections, invalidator (*this), boost::bind (&PluginPinDialog::route_going_away, this), gui_context()
+ );
+}
+void
+PluginPinDialog::set_session (ARDOUR::Session *s)
+{
+ SessionHandlePtr::set_session (s);
+ for (PluginPinWidgetList::iterator i = ppw.begin(); i != ppw.end(); ++i) {
+ (*i)->set_session (s);
+ }
+}
+
+void
+PluginPinDialog::map_height (Gtk::Allocation&)
+{
+ if (!_height_mapped) {
+ scroller->set_size_request (-1, std::min (600, 2 + vbox->get_height()));
+ _height_mapped = true;
+ }
+}
+
+void
+PluginPinDialog::route_processors_changed (ARDOUR::RouteProcessorChange)
+{
+ ppw.clear ();
+ _height_mapped = false;
+ scroller->remove ();
+ vbox = manage (new VBox ());
+ vbox->signal_size_allocate().connect (sigc::mem_fun (*this, &PluginPinDialog::map_height));
+ scroller->add (*vbox);
+ _route->foreach_processor (sigc::mem_fun (*this, &PluginPinDialog::add_processor));
+ vbox->show ();
+}
+
+void
+PluginPinDialog::route_going_away ()
+{
+ ppw.clear ();
+ _route.reset ();
+ remove ();
+}
+
+void
+PluginPinDialog::add_processor (boost::weak_ptr<Processor> p)
+{
+ boost::shared_ptr<Processor> proc = p.lock ();
+ if (!proc || !proc->display_to_user ()) {
+ return;
+ }
+ boost::shared_ptr<PluginInsert> pi = boost::dynamic_pointer_cast<PluginInsert> (proc);
+#ifdef MIXBUS
+ if (pi && pi->is_channelstrip ()) {
+ pi.reset ();
+ }
+#endif
+ if (pi) {
+ ppw.push_back (PluginPinWidgetPtr(new PluginPinWidget (pi)));
+ ppw.back()->set_session (_session);
+ vbox->pack_start (*ppw.back());
+ } else {
+ HBox* hbox = manage (new HBox ());
+ hbox->pack_start (*manage (new HSeparator ()));
+ hbox->pack_start (*manage (new Label (proc->display_name ())));
+ hbox->pack_start (*manage (new HSeparator ()));
+ vbox->pack_start (*hbox, false, false);
+ hbox->show_all ();
+ }