+
+void
+PluginPinWidget::darea_size_request (Gtk::Requisition* req)
+{
+ req->width = _min_width;
+ req->height = _min_height;
+}
+
+void
+PluginPinWidget::darea_size_allocate (Gtk::Allocation&)
+{
+ _position_valid = false;
+}
+
+bool
+PluginPinWidget::drag_type_matches (const CtrlElem& e)
+{
+ if (!_dragging || !_selection) {
+ return true;
+ }
+ if (_selection->dt != e->dt) {
+ return false;
+ }
+ if (_selection->ct == Input && e->ct == Sink) { return true; }
+ if (_selection->ct == Sink && e->ct == Input) { return true; }
+ if (_selection->ct == Output && e->ct == Source) { return true; }
+ if (_selection->ct == Source && e->ct == Output) { return true; }
+ if (_selection->ct == Input && e->ct == Output) { return true; }
+ if (_selection->ct == Output && e->ct == Input) { return true; }
+ return false;
+}
+
+void
+PluginPinWidget::start_drag (const CtrlElem& e, double x, double y)
+{
+ assert (_selection == e);
+ _drag_dst.reset ();
+ if (e->ct == Sink) {
+ bool valid;
+ const ChanMapping& map (_in_map[e->ip]);
+ uint32_t idx = map.get (e->dt, e->id, &valid);
+ if (valid) {
+ const CtrlWidget& cw = get_io_ctrl (Input, e->dt, idx, 0);
+ _drag_dst = e;
+ _selection = cw.e;
+ }
+ }
+ else if (e->ct == Output) {
+ for (uint32_t i = 0; i < _n_plugins; ++i) {
+ bool valid;
+ const ChanMapping& map (_out_map[i]);
+ uint32_t idx = map.get_src (e->dt, e->id, &valid);
+ if (valid) {
+ const CtrlWidget& cw = get_io_ctrl (Source, e->dt, idx, i);
+ _drag_dst = e;
+ _selection = cw.e;
+ break;
+ }
+ }
+ if (!_drag_dst) {
+ bool valid;
+ const ChanMapping& map (_thru_map);
+ uint32_t idx = map.get (e->dt, e->id, &valid);
+ if (valid) {
+ const CtrlWidget& cw = get_io_ctrl (Input, e->dt, idx, 0);
+ _drag_dst = e;
+ _selection = cw.e;
+ }
+ }
+ }
+ _dragging = true;
+ _drag_x = x;
+ _drag_y = y;
+}
+
+bool
+PluginPinWidget::darea_motion_notify_event (GdkEventMotion* ev)
+{
+ bool changed = false;
+ _hover.reset ();
+ for (CtrlElemList::iterator i = _elements.begin (); i != _elements.end (); ++i) {
+ if (ev->x >= i->x && ev->x <= i->x + i->w
+ && ev->y >= i->y && ev->y <= i->y + i->h
+ && drag_type_matches (i->e))
+ {
+ if (!i->prelight) changed = true;
+ i->prelight = true;
+ _hover = i->e;
+ } else {
+ if (i->prelight) changed = true;
+ i->prelight = false;
+ }
+ }
+ if (_dragging) {
+ _drag_x = ev->x;
+ _drag_y = ev->y;
+ }
+ if (changed || _dragging) {
+ darea.queue_draw ();
+ }
+ return true;
+}
+
+bool
+PluginPinWidget::darea_button_press_event (GdkEventButton* ev)
+{
+ if (ev->type != GDK_BUTTON_PRESS) {
+ return false;
+ }
+
+ switch (ev->button) {
+ case 1:
+ _drag_dst.reset ();
+ if (!_selection || (_selection && !_hover)) {
+ _selection = _hover;
+ _actor.reset ();
+ if (_selection) {
+ start_drag (_selection, ev->x, ev->y);
+ } else {
+ darea.queue_draw ();
+ }
+ } else if (_selection && _hover && _selection != _hover) {
+ if (_selection->dt != _hover->dt) { _actor.reset (); }
+ else if (_selection->ct == Input && _hover->ct == Sink) { _actor = _hover; }
+ else if (_selection->ct == Sink && _hover->ct == Input) { _actor = _hover; }
+ else if (_selection->ct == Output && _hover->ct == Source) { _actor = _hover; }
+ else if (_selection->ct == Source && _hover->ct == Output) { _actor = _hover; }
+ else if (_selection->ct == Input && _hover->ct == Output) { _actor = _hover; }
+ else if (_selection->ct == Output && _hover->ct == Input) { _actor = _hover; }
+ if (!_actor) {
+ _selection = _hover;
+ start_drag (_selection, ev->x, ev->y);
+ } else {
+ darea.queue_draw ();
+ }
+ } else if (_hover) {
+ _selection = _hover;
+ _actor.reset ();
+ start_drag (_selection, ev->x, ev->y);
+ }
+ break;
+ case 3:
+ _drag_dst.reset ();
+ if (_selection != _hover) {
+ _selection = _hover;
+ darea.queue_draw ();
+ }
+ _actor.reset ();
+ break;
+ default:
+ break;
+ }
+
+ return true;
+}
+
+bool
+PluginPinWidget::darea_button_release_event (GdkEventButton* ev)
+{
+ if (_dragging && _selection && _drag_dst && _drag_dst == _hover) {
+ // select click. (or re-connect same)
+ assert (_selection != _hover);
+ _actor.reset ();
+ _dragging = false;
+ _drag_dst.reset ();
+ _selection =_hover;
+ darea.queue_draw ();
+ return true;
+ }
+
+ if (_dragging && _hover && _hover != _selection) {
+ _actor = _hover;
+ }
+
+ if (_hover == _actor && _actor && ev->button == 1) {
+ assert (_selection);
+ assert (_selection->dt == _actor->dt);
+ if (_drag_dst) {
+ assert (_dragging && _selection != _drag_dst);
+ handle_disconnect (_drag_dst, true);
+ }
+ if (_selection->ct == Input && _actor->ct == Sink) {
+ handle_input_action (_actor, _selection);
+ }
+ else if (_selection->ct == Sink && _actor->ct == Input) {
+ handle_input_action (_selection, _actor);
+ }
+ else if (_selection->ct == Output && _actor->ct == Source) {
+ handle_output_action (_actor, _selection);
+ }
+ else if (_selection->ct == Source && _actor->ct == Output) {
+ handle_output_action (_selection, _actor);
+ }
+ else if (_selection->ct == Input && _actor->ct == Output) {
+ handle_thru_action (_actor, _selection);
+ }
+ else if (_selection->ct == Output && _actor->ct == Input) {
+ handle_thru_action (_selection, _actor);
+ }
+ _selection.reset ();
+ } else if (_hover == _selection && _selection && ev->button == 3) {
+ handle_disconnect (_selection);
+ } else if (!_hover && ev->button == 3) {
+ reset_menu.popup (1, ev->time);
+ }
+
+ if (_dragging && _hover != _selection) {
+ _selection.reset ();
+ }
+ _actor.reset ();
+ _dragging = false;
+ _drag_dst.reset ();
+ darea.queue_draw ();
+ return true;
+}
+
+void
+PluginPinWidget::handle_input_action (const CtrlElem &s, const CtrlElem &i)
+{
+ const int pc = s->ip;
+ bool valid;
+ ChanMapping in_map (_pi->input_map (pc));
+ uint32_t idx = in_map.get (s->dt, s->id, &valid);
+
+ if (valid && idx == i->id) {
+ // disconnect
+ if (!_dragging) {
+ in_map.unset (s->dt, s->id);
+ _pi->set_input_map (pc, in_map);
+ } else {
+ plugin_reconfigured ();
+ }
+ }
+ else if (!valid) {
+ // connect
+ in_map.set (s->dt, s->id, i->id);
+ _pi->set_input_map (pc, in_map);
+ }
+ else {
+ // reconnect
+ in_map.unset (s->dt, s->id);
+ in_map.set (s->dt, s->id, i->id);
+ _pi->set_input_map (pc, in_map);
+ }
+}
+
+void
+PluginPinWidget::disconnect_other_outputs (uint32_t skip_pc, DataType dt, uint32_t id)
+{
+ _ignore_updates = true;
+ for (uint32_t n = 0; n < _n_plugins; ++n) {
+ if (n == skip_pc) {
+ continue;
+ }
+ bool valid;
+ ChanMapping n_out_map (_pi->output_map (n));
+ uint32_t idx = n_out_map.get_src (dt, id, &valid);
+ if (valid) {
+ n_out_map.unset (dt, idx);
+ _pi->set_output_map (n, n_out_map);
+ }
+ }
+ _ignore_updates = false;
+}
+
+void
+PluginPinWidget::disconnect_other_thru (DataType dt, uint32_t id)
+{
+ _ignore_updates = true;
+ bool valid;
+ ChanMapping n_thru_map (_pi->thru_map ());
+ n_thru_map.get (dt, id, &valid);
+ if (valid) {
+ n_thru_map.unset (dt, id);
+ _pi->set_thru_map (n_thru_map);
+ }
+ _ignore_updates = false;
+}
+
+void
+PluginPinWidget::handle_output_action (const CtrlElem &s, const CtrlElem &o)
+{
+ const uint32_t pc = s->ip;
+ bool valid;
+ ChanMapping out_map (_pi->output_map (pc));
+ uint32_t idx = out_map.get (s->dt, s->id, &valid);
+
+ if (valid && idx == o->id) {
+ // disconnect
+ if (!_dragging) {
+ out_map.unset (s->dt, s->id);
+ _pi->set_output_map (pc, out_map);
+ } else {
+ plugin_reconfigured ();
+ }
+ }
+ else {
+ // disconnect source
+ disconnect_other_outputs (pc, s->dt, o->id);
+ disconnect_other_thru (s->dt, o->id);
+ out_map = _pi->output_map (pc); // re-read map
+ if (valid) {
+ out_map.unset (s->dt, s->id);
+ }
+ idx = out_map.get_src (s->dt, o->id, &valid);
+ if (valid) {
+ out_map.unset (s->dt, idx);
+ }
+ // connect
+ out_map.set (s->dt, s->id, o->id);
+ _pi->set_output_map (pc, out_map);
+ }
+}
+
+void
+PluginPinWidget::handle_thru_action (const CtrlElem &o, const CtrlElem &i)
+{
+ bool valid;
+ ChanMapping thru_map (_pi->thru_map ());
+ uint32_t idx = thru_map.get (o->dt, o->id, &valid);
+
+ if (valid && idx == i->id) {
+ if (!_dragging) {
+ thru_map.unset (o->dt, o->id);
+ }
+ } else {
+ // disconnect other outputs first
+ disconnect_other_outputs (UINT32_MAX, o->dt, o->id);
+ disconnect_other_thru (o->dt, o->id);
+ thru_map = _pi->thru_map (); // re-read map
+
+ thru_map.set (o->dt, o->id, i->id);
+ }
+ _pi->set_thru_map (thru_map);
+}
+
+bool
+PluginPinWidget::handle_disconnect (const CtrlElem &e, bool no_signal)
+{
+ _ignore_updates = true;
+ bool changed = false;
+ bool valid;
+
+ switch (e->ct) {
+ case Input:
+ {
+ ChanMapping n_thru_map (_pi->thru_map ());
+ for (uint32_t i = 0; i < _sources.n_total (); ++i) {
+ uint32_t idx = n_thru_map.get (e->dt, i, &valid);
+ if (valid && idx == e->id) {
+ n_thru_map.unset (e->dt, i);
+ changed = true;
+ }
+ }
+ if (changed) {
+ _pi->set_thru_map (n_thru_map);
+ }
+ }
+ for (uint32_t n = 0; n < _n_plugins; ++n) {
+ ChanMapping map (_pi->input_map (n));
+ for (uint32_t i = 0; i < _sinks.n_total (); ++i) {
+ uint32_t idx = map.get (e->dt, i, &valid);
+ if (valid && idx == e->id) {
+ map.unset (e->dt, i);
+ changed = true;
+ }
+ }
+ _pi->set_input_map (n, map);
+ }
+ break;
+ case Sink:
+ {
+ ChanMapping map (_pi->input_map (e->ip));
+ map.get (e->dt, e->id, &valid);
+ if (valid) {
+ map.unset (e->dt, e->id);
+ _pi->set_input_map (e->ip, map);
+ changed = true;
+ }
+ }
+ break;
+ case Source:
+ {
+ ChanMapping map (_pi->output_map (e->ip));
+ map.get (e->dt, e->id, &valid);
+ if (valid) {
+ map.unset (e->dt, e->id);
+ _pi->set_output_map (e->ip, map);
+ changed = true;
+ }
+ }
+ break;
+ case Output:
+ for (uint32_t n = 0; n < _n_plugins; ++n) {
+ ChanMapping map (_pi->output_map (n));
+ for (uint32_t i = 0; i < _sources.n_total (); ++i) {
+ uint32_t idx = map.get (e->dt, i, &valid);
+ if (valid && idx == e->id) {
+ map.unset (e->dt, i);
+ changed = true;
+ }
+ }
+ if (changed) {
+ _pi->set_output_map (n, map);
+ }
+ }
+ {
+ ChanMapping n_thru_map (_pi->thru_map ());
+ n_thru_map.get (e->dt, e->id, &valid);
+ if (valid) {
+ n_thru_map.unset (e->dt, e->id);
+ changed = true;
+ _pi->set_thru_map (n_thru_map);
+ }
+ }
+ break;
+ }
+ _ignore_updates = false;
+ if (changed && !no_signal) {
+ plugin_reconfigured ();
+ }
+ return changed;
+}
+
+void
+PluginPinWidget::toggle_sidechain ()
+{
+ if (!_route ()->add_remove_sidechain (_pi, !_pi->has_sidechain ())) {
+ error_message_dialog (_("Failed to toggle sidechain."));
+ }
+}
+
+void
+PluginPinWidget::connect_sidechain ()
+{
+ assert (_session);
+
+ if (_sidechain_selector == 0) {
+ _sidechain_selector = new IOSelectorWindow (_session, _pi->sidechain_input ());
+ }
+
+ if (_sidechain_selector->is_visible ()) {
+ _sidechain_selector->get_toplevel ()->get_window ()->raise ();
+ } else {
+ _sidechain_selector->present ();
+ }
+}
+
+void
+PluginPinWidget::reset_configuration ()
+{
+ bool rv;
+ if (_set_config.get_active ()) {
+ rv = _route ()->reset_plugin_insert (_pi);
+ } else {
+ rv = _route ()->customize_plugin_insert (_pi, _n_plugins, _out, _sinks);
+ }
+ if (!rv) {
+ error_message_dialog (_("Failed to reset plugin configuration."));
+ }
+}
+
+void
+PluginPinWidget::reset_mapping ()
+{
+ _pi->reset_map ();
+}
+
+void
+PluginPinWidget::select_output_preset (uint32_t n_audio)
+{
+ ChanCount out (DataType::AUDIO, n_audio);
+ if (!_route ()->plugin_preset_output (_pi, out)) {
+ error_message_dialog (_("Failed to change channel preset."));
+ }
+}
+
+void
+PluginPinWidget::add_remove_plugin_clicked (bool add)
+{
+ ChanCount out = _out;
+ ChanCount sinks = _sinks;
+ assert (add || _n_plugins > 0);
+ if (!_route ()->customize_plugin_insert (_pi, _n_plugins + (add ? 1 : -1), out, sinks)) {
+ error_message_dialog (_("Failed to change instance count"));
+ }
+}
+
+void
+PluginPinWidget::add_remove_port_clicked (bool add, ARDOUR::DataType dt)
+{
+ ChanCount out = _out;
+ ChanCount sinks = _sinks;
+ assert (add || out.get (dt) > 0);
+ out.set (dt, out.get (dt) + (add ? 1 : -1));
+ if (!_route ()->customize_plugin_insert (_pi, _n_plugins, out, sinks)) {
+ error_message_dialog (_("Failed to alter plugin output configuration."));
+ }
+}
+
+void
+PluginPinWidget::add_remove_inpin_clicked (bool add, ARDOUR::DataType dt)
+{
+ 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 ();
+ }
+}