2 Copyright (C) 2018-2019 Len Ovens
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19 #include "ardour/audioengine.h"
20 #include "ardour/pannable.h"
21 #include "ardour/panner_shell.h"
22 #include "ardour/panner_manager.h"
23 #include "ardour/profile.h"
24 #include "ardour/route.h"
25 #include "ardour/send.h"
26 #include "ardour/session.h"
27 #include "ardour/user_bundle.h"
28 #include "ardour/value_as_string.h"
30 #include "gtkmm2ext/gtk_ui.h"
31 #include "gtkmm2ext/menu_elems.h"
32 #include "gtkmm2ext/utils.h"
33 #include "gtkmm2ext/doi.h"
35 #include "widgets/tooltips.h"
37 #include "ardour_window.h"
38 #include "enums_convert.h"
39 #include "foldback_strip.h"
42 #include "public_editor.h"
45 #include "io_selector.h"
47 #include "gui_thread.h"
48 #include "ui_config.h"
52 using namespace ARDOUR;
53 using namespace ArdourWidgets;
56 using namespace Gtkmm2ext;
59 #define PX_SCALE(px) std::max((float)px, rintf((float)px * UIConfiguration::instance().get_ui_scale()))
61 FoldbackSend::FoldbackSend (boost::shared_ptr<Send> snd, \
62 boost::shared_ptr<ARDOUR::Route> sr, boost::shared_ptr<ARDOUR::Route> fr, uint32_t wd)
63 : _button (ArdourButton::led_default_elements)
66 , _foldback_route (fr)
70 , pan_control (ArdourKnob::default_elements, ArdourKnob::Flags (ArdourKnob::Detent | ArdourKnob::ArcToZero))
71 , _adjustment (gain_to_slider_position_with_max (1.0, Config->get_max_gain()), 0, 1, 0.01, 0.1)
72 , _slider (&_adjustment, boost::shared_ptr<PBD::Controllable>(), 0, max(13.f, rintf(13.f * UIConfiguration::instance().get_ui_scale())))
73 , _ignore_ui_adjustment (true)
74 , _slider_persistant_tooltip (&_slider)
78 HBox * snd_but_pan = new HBox ();
80 _button.set_distinct_led_click (true);
81 _button.set_fallthrough_to_parent(true);
82 _button.set_led_left (true);
83 _button.signal_led_clicked.connect (sigc::mem_fun (*this, &FoldbackSend::led_clicked));
84 _button.set_name ("processor prefader");
85 _button.set_layout_ellipsize_width (PX_SCALE(_width) * PANGO_SCALE);
86 _button.set_text_ellipsize (Pango::ELLIPSIZE_END);
88 snd_but_pan->pack_start (_button, true, true);
89 _button.set_active (_send_proc->enabled ());
92 if (_foldback_route->input()->n_ports().n_audio() == 2) {
93 _button.set_layout_ellipsize_width (PX_SCALE(_width - 19) * PANGO_SCALE);
94 boost::shared_ptr<Pannable> pannable = _send_del->panner()->pannable();
95 boost::shared_ptr<AutomationControl> ac;
96 ac = pannable->pan_azimuth_control;
97 pan_control.set_size_request (PX_SCALE(19), PX_SCALE(19));
98 pan_control.set_tooltip_prefix (_("Pan: "));
99 pan_control.set_name ("trim knob");
100 pan_control.set_no_show_all (true);
101 snd_but_pan->pack_start (pan_control, false, false);
103 pan_control.set_controllable (ac);
105 boost::shared_ptr<AutomationControl> lc;
106 lc = _send->gain_control();
107 _slider.set_controllable (lc);
108 _slider.set_name ("ProcessorControlSlider");
109 _slider.set_text (_("Level"));
111 pack_start (*snd_but_pan, Gtk::PACK_SHRINK);
113 pack_start (_slider, true, true);
117 _adjustment.signal_value_changed().connect (sigc::mem_fun (*this, &FoldbackSend::level_adjusted));
118 lc->Changed.connect (_connections, invalidator (*this), boost::bind (&FoldbackSend::level_changed, this), gui_context ());
119 _send_proc->ActiveChanged.connect (_connections, invalidator (*this), boost::bind (&FoldbackSend::send_state_changed, this), gui_context ());
120 _button.signal_button_press_event().connect (sigc::mem_fun (*this, &FoldbackSend::button_press));
121 _send_route->PropertyChanged.connect (_connections, invalidator (*this), boost::bind (&FoldbackSend::route_property_changed, this, _1), gui_context());
128 FoldbackSend::~FoldbackSend ()
130 _connections.drop_connections();
131 _slider.set_controllable (boost::shared_ptr<AutomationControl> ());
132 pan_control.set_controllable (boost::shared_ptr<AutomationControl> ());
133 _send = boost::shared_ptr<Send> ();
134 _send_route = boost::shared_ptr<Route> ();
135 _foldback_route = boost::shared_ptr<Route> ();
136 _send_proc = boost::shared_ptr<Processor> ();
137 _send_del = boost::shared_ptr<Delivery> ();
142 FoldbackSend::route_property_changed (const PropertyChange& what_changed)
144 if (what_changed.contains (ARDOUR::Properties::name)) {
150 FoldbackSend::name_changed ()
152 _button.set_text (_send_route->name ());
154 ArdourWidgets::set_tooltip (_button, Gtkmm2ext::markup_escape_text(_send_route->name()));
158 FoldbackSend::led_clicked(GdkEventButton *ev)
161 if (_button.get_active ()) {
162 _send_proc->enable (false);
165 _send_proc->enable (true);
171 FoldbackSend::button_press (GdkEventButton* ev)
173 if (ev->button == 1) {
174 Menu* menu = build_send_menu ();
176 Gtkmm2ext::anchored_menu_popup(menu, &_button, "", 1, ev->time);
183 FoldbackSend::send_state_changed ()
185 _button.set_active (_send_proc->enabled ());
190 FoldbackSend::level_adjusted ()
192 if (_ignore_ui_adjustment) {
195 boost::shared_ptr<AutomationControl> lc = _send->gain_control();
201 lc->set_value ( lc->interface_to_internal(_adjustment.get_value ()) , Controllable::NoGroup);
206 FoldbackSend::level_changed ()
208 boost::shared_ptr<AutomationControl> lc = _send->gain_control();
213 _ignore_ui_adjustment = true;
215 const double nval = lc->internal_to_interface (lc->get_value ());
216 if (_adjustment.get_value() != nval) {
217 _adjustment.set_value (nval);
221 _ignore_ui_adjustment = false;
225 FoldbackSend::set_tooltip ()
227 boost::shared_ptr<AutomationControl> lc = _send->gain_control();
232 std::string tt = ARDOUR::value_as_string (lc->desc(), lc->get_value ());
233 string sm = Gtkmm2ext::markup_escape_text (tt);
234 _slider_persistant_tooltip.set_tip (sm);
238 FoldbackSend::build_send_menu ()
240 using namespace Menu_Helpers;
246 Menu* menu = manage (new Menu);
247 MenuList& items = menu->items ();
248 menu->set_name ("ArdourContextMenu");
251 MenuElem(_("Copy track/bus gain to send"), sigc::bind (sigc::mem_fun (*this, &FoldbackSend::set_gain), -0.1))
254 MenuElem(_("Set send gain to -inf"), sigc::bind (sigc::mem_fun (*this, &FoldbackSend::set_gain), 0.0))
257 MenuElem(_("Set send gain to 0dB"), sigc::bind (sigc::mem_fun (*this, &FoldbackSend::set_gain), 1.0))
259 items.push_back (MenuElem(_("Remove This Send"), sigc::mem_fun (*this, &FoldbackSend::remove_me)));
266 FoldbackSend::set_gain (float new_gain)
269 // get level from sending route
270 new_gain = _send_route->gain_control ()->get_value ();
272 boost::shared_ptr<AutomationControl> lc = _send->gain_control();
277 lc->set_value (new_gain, Controllable::NoGroup);
282 FoldbackSend::remove_me ()
284 boost::shared_ptr<Processor> send_proc = boost::dynamic_pointer_cast<Processor> (_send);
285 _connections.drop_connections();
286 _send_route->remove_processor (send_proc);
291 FoldbackStrip* FoldbackStrip::_entered_foldback_strip;
292 PBD::Signal1<void,FoldbackStrip*> FoldbackStrip::CatchDeletion;
294 FoldbackStrip::FoldbackStrip (Mixer_UI& mx, Session* sess, boost::shared_ptr<Route> rt)
295 : SessionHandlePtr (sess)
298 , _mixer_owned (true)
302 , mute_solo_table (1, 2)
303 , _plugin_insert_cnt (0)
304 , _comment_button (_("Comments"))
305 , fb_level_control (0)
313 FoldbackStrip::init ()
315 _entered_foldback_strip= 0;
316 ignore_comment_edit = false;
317 ignore_toggle = false;
320 _previous_button.set_name ("mixer strip button");
321 _previous_button.set_icon (ArdourIcon::ScrollLeft);
322 _previous_button.set_tweaks (ArdourButton::Square);
323 UI::instance()->set_tip (&_previous_button, _("Previous foldback bus"), "");
324 _previous_button.set_sensitive (false);
326 _next_button.set_name ("mixer strip button");
327 _next_button.set_icon (ArdourIcon::ScrollRight);
328 _next_button.set_tweaks (ArdourButton::Square);
329 UI::instance()->set_tip (&_next_button, _("Next foldback bus"), "");
330 _next_button.set_sensitive (false);
332 _hide_button.set_name ("mixer strip button");
333 _hide_button.set_icon (ArdourIcon::HideEye);
334 _hide_button.set_tweaks (ArdourButton::Square);
335 set_tooltip (&_hide_button, _("Hide Foldback strip"));
337 prev_next_box.pack_start (_previous_button, false, true);
338 prev_next_box.pack_start (_next_button, false, true);
339 prev_next_box.pack_end (_hide_button, false, true);
341 name_button.set_name ("mixer strip button");
342 name_button.set_text_ellipsize (Pango::ELLIPSIZE_END);
343 name_button.set_layout_ellipsize_width (PX_SCALE(_width) * PANGO_SCALE);
345 // invertbuttons and box in route_ui
347 _show_sends_button.set_name ("send alert button");
348 _show_sends_button.set_text (_("Show Sends"));
349 UI::instance()->set_tip (&_show_sends_button, _("make mixer strips show sends to this bus"), "");
351 send_display.set_flags (CAN_FOCUS);
352 send_display.set_spacing (4);
354 send_scroller.set_policy (Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
355 send_scroller.add (send_display);
356 send_scroller.get_child()->set_name ("FoldbackBusStripBase");
358 // panners from route_ui
359 panners.set_width (Wide);
361 insert_box = new ProcessorBox (0, boost::bind (&FoldbackStrip::plugin_selector, this), _pr_selection, 0);
362 insert_box->set_no_show_all ();
364 insert_box->set_session (_session);
365 insert_box->set_width (Wide);
366 insert_box->set_size_request (PX_SCALE(_width + 34), PX_SCALE(100));
368 mute_solo_table.set_homogeneous (true);
369 mute_solo_table.set_spacings (2);
370 solo_button->set_text (_("Listen"));
371 mute_solo_table.attach (*solo_button, 0, 2, 0, 1);
372 mute_solo_table.set_size_request (PX_SCALE(_width + 34), PX_SCALE(20));
374 fb_level_control = new ArdourKnob (ArdourKnob::default_elements, ArdourKnob::Detent);
375 fb_level_control->set_size_request (PX_SCALE(50), PX_SCALE(50));
376 fb_level_control->set_tooltip_prefix (_("Level: "));
377 fb_level_control->set_name ("foldback knob");
378 fb_level_control->set_no_show_all (true);
380 VBox* level_box = manage (new VBox);
381 level_box->pack_start (*fb_level_control, true, false);
382 master_box.pack_start (*level_box, true, false);
383 master_box.set_size_request (PX_SCALE(_width + 34), PX_SCALE(80));
384 master_box.set_name ("FoldbackBusStripBase");
387 output_button.set_text (_("Output"));
388 output_button.set_name ("mixer strip button");
389 output_button.set_text_ellipsize (Pango::ELLIPSIZE_MIDDLE);
390 output_button.set_layout_ellipsize_width (PX_SCALE(_width) * PANGO_SCALE);
392 _comment_button.set_name (X_("mixer strip button"));
393 _comment_button.set_text_ellipsize (Pango::ELLIPSIZE_END);
394 _comment_button.set_layout_ellipsize_width (PX_SCALE(_width) * PANGO_SCALE);
396 global_vpacker.set_border_width (1);
397 global_vpacker.set_spacing (2);
399 // Packing is from top down to the send box. Thje send box
400 // needs the most room and takes all left over space
401 // Everything below the send box is packed from the bottom up
402 // the panner is the last thing to pack as it doesn't always show
403 // and packing it below the sendbox means nothing moves when it shows
405 global_vpacker.pack_start (prev_next_box, Gtk::PACK_SHRINK);
406 global_vpacker.pack_start (name_button, Gtk::PACK_SHRINK);
407 global_vpacker.pack_start (_invert_button_box, Gtk::PACK_SHRINK);
408 global_vpacker.pack_start (_show_sends_button, Gtk::PACK_SHRINK);
409 global_vpacker.pack_start (send_scroller, true, true);
411 //add a spacer underneath the foldback bus;
412 //this fills the area that is taken up by the scrollbar on the tracks;
413 //and therefore keeps the strip boxes "even" across the bottom
414 int scrollbar_height = 0;
416 Gtk::Window window (WINDOW_TOPLEVEL);
417 HScrollbar scrollbar;
418 window.add (scrollbar);
419 scrollbar.set_name ("MixerWindow");
420 scrollbar.ensure_style();
421 Gtk::Requisition requisition(scrollbar.size_request ());
422 scrollbar_height = requisition.height;
424 spacer.set_size_request (-1, scrollbar_height);
425 global_vpacker.pack_end (spacer, false, false);
427 global_vpacker.pack_end (_comment_button, Gtk::PACK_SHRINK);
428 global_vpacker.pack_end (output_button, Gtk::PACK_SHRINK);
429 global_vpacker.pack_end (master_box, Gtk::PACK_SHRINK);
430 global_vpacker.pack_end (mute_solo_table, Gtk::PACK_SHRINK);
431 global_vpacker.pack_end (*insert_box, Gtk::PACK_SHRINK);
432 global_vpacker.pack_end (panners, Gtk::PACK_SHRINK);
434 global_frame.add (global_vpacker);
435 global_frame.set_shadow_type (Gtk::SHADOW_IN);
436 global_frame.set_name ("BaseFrame");
440 /* force setting of visible selected status */
443 set_selected (false);
447 _session->engine().Stopped.connect (*this, invalidator (*this), boost::bind (&FoldbackStrip::engine_stopped, this), gui_context());
448 _session->engine().Running.connect (*this, invalidator (*this), boost::bind (&FoldbackStrip::engine_running, this), gui_context());
450 output_button.signal_button_press_event().connect (sigc::mem_fun(*this, &FoldbackStrip::output_press), false);
451 output_button.signal_button_release_event().connect (sigc::mem_fun(*this, &FoldbackStrip::output_release), false);
453 name_button.signal_button_press_event().connect (sigc::mem_fun(*this, &FoldbackStrip::name_button_button_press), false);
454 _previous_button.signal_clicked.connect (sigc::mem_fun (*this, &FoldbackStrip::previous_button_clicked));
455 _next_button.signal_clicked.connect (sigc::mem_fun (*this, &FoldbackStrip::next_button_clicked));
456 _hide_button.signal_clicked.connect (sigc::mem_fun(*this, &FoldbackStrip::hide_clicked));
457 _show_sends_button.signal_clicked.connect (sigc::mem_fun(*this, &FoldbackStrip::show_sends_clicked));
458 send_scroller.signal_button_press_event().connect (sigc::mem_fun (*this, &FoldbackStrip::send_button_press_event));
459 _comment_button.signal_clicked.connect (sigc::mem_fun (*this, &RouteUI::toggle_comment_editor));
461 add_events (Gdk::BUTTON_RELEASE_MASK|
462 Gdk::ENTER_NOTIFY_MASK|
463 Gdk::LEAVE_NOTIFY_MASK|
465 Gdk::KEY_RELEASE_MASK);
467 set_flags (get_flags() | Gtk::CAN_FOCUS);
469 AudioEngine::instance()->PortConnectedOrDisconnected.connect (
470 *this, invalidator (*this), boost::bind (&FoldbackStrip::port_connected_or_disconnected, this, _1, _3), gui_context ()
473 //watch for mouse enter/exit so we can do some stuff
474 signal_enter_notify_event().connect (sigc::mem_fun(*this, &FoldbackStrip::mixer_strip_enter_event ));
475 signal_leave_notify_event().connect (sigc::mem_fun(*this, &FoldbackStrip::mixer_strip_leave_event ));
479 FoldbackStrip::~FoldbackStrip ()
481 CatchDeletion (this);
482 delete fb_level_control;
483 fb_level_control = 0;
484 _connections.drop_connections();
486 send_blink_connection.disconnect ();
488 if (this ==_entered_foldback_strip)
489 _entered_foldback_strip = NULL;
493 FoldbackStrip::mixer_strip_enter_event (GdkEventCrossing* /*ev*/)
495 _entered_foldback_strip = this;
497 //although we are triggering on the "enter", to the user it will appear that it is happenin on the "leave"
498 //because the FoldbackStrip control is a parent that encompasses the strip
499 deselect_all_processors();
505 FoldbackStrip::mixer_strip_leave_event (GdkEventCrossing *ev)
507 //if we have moved outside our strip, but not into a child view, then deselect ourselves
508 if ( !(ev->detail == GDK_NOTIFY_INFERIOR) ) {
509 _entered_foldback_strip= 0;
517 FoldbackStrip::name() const
520 return _route->name();
526 FoldbackStrip::update_fb_level_control ()
528 fb_level_control->show ();
529 fb_level_control->set_controllable (_route->gain_control());
533 FoldbackStrip::set_route (boost::shared_ptr<Route> rt)
538 RouteUI::self_delete ();
543 _route->solo_control()->set_value (0.0, Controllable::NoGroup);
546 RouteUI::set_route (rt);
548 insert_box->set_route (_route);
549 revert_to_default_display ();
550 update_fb_level_control();
552 BusSendDisplayChanged (boost::shared_ptr<Route> ());
553 _showing_sends = false;
554 _show_sends_button.set_active (false);
555 send_blink_connection.disconnect ();
557 if (_route->panner_shell()) {
558 update_panner_choices();
559 _route->panner_shell()->Changed.connect (route_connections, invalidator (*this), boost::bind (&FoldbackStrip::connect_to_pan, this), gui_context());
562 _route->output()->changed.connect (*this, invalidator (*this), boost::bind (&FoldbackStrip::update_output_display, this), gui_context());
563 _route->io_changed.connect (route_connections, invalidator (*this), boost::bind (&FoldbackStrip::io_changed_proxy, this), gui_context ());
565 _route->comment_changed.connect (route_connections, invalidator (*this), boost::bind (&FoldbackStrip::setup_comment_button, this), gui_context());
567 /* now force an update of all the various elements */
571 _session->FBSendsChanged.connect (route_connections, invalidator (*this), boost::bind (&FoldbackStrip::update_send_box, this), gui_context());
572 update_mute_display ();
573 update_solo_display ();
576 panners.setup_pan ();
578 update_output_display ();
580 add_events (Gdk::BUTTON_RELEASE_MASK);
581 prev_next_changed ();
582 _previous_button.show();
585 prev_next_box.show ();
587 send_display.show ();
588 send_scroller.show ();
589 _show_sends_button.show();
591 solo_button->show ();
592 mute_solo_table.show();
594 output_button.show();
595 _comment_button.show();
598 global_vpacker.show();
606 // predicate for sort call in get_sorted_stripables
607 struct StripableByPresentationOrder
609 bool operator () (const boost::shared_ptr<Stripable> & a, const boost::shared_ptr<Stripable> & b) const
611 return a->presentation_info().order() < b->presentation_info().order();
616 FoldbackStrip::update_send_box ()
622 StripableList stripables;
625 Route::FedBy fed_by = _route->fed_by();
626 for (Route::FedBy::iterator i = fed_by.begin(); i != fed_by.end(); ++i) {
628 boost::shared_ptr<Route> rt (i->r.lock());
629 boost::shared_ptr<Stripable> s = boost::dynamic_pointer_cast<Stripable> (rt);
630 stripables.push_back (s);
633 stripables.sort (StripableByPresentationOrder());
634 for (StripableList::iterator it = stripables.begin(); it != stripables.end(); ++it) {
636 boost::shared_ptr<Stripable> s_sp = *it;
637 boost::shared_ptr<Route> s_rt = boost::dynamic_pointer_cast<Route> (s_sp);
638 boost::shared_ptr<Send> snd = s_rt->internal_send_for (_route);
640 FoldbackSend * fb_s = new FoldbackSend (snd, s_rt, _route, _width);
641 send_display.pack_start (*fb_s, Gtk::PACK_SHRINK);
643 s_rt->processors_changed.connect (_connections, invalidator (*this), boost::bind (&FoldbackStrip::processors_changed, this, _1), gui_context ());
649 FoldbackStrip::clear_send_box ()
651 std::vector< Widget* > snd_list = send_display.get_children ();
652 _connections.drop_connections ();
653 for (uint32_t i = 0; i < snd_list.size(); i++) {
654 send_display.remove (*(snd_list[i]));
661 FoldbackStrip::processors_changed (RouteProcessorChange)
667 FoldbackStrip::set_packed (bool yn)
673 FoldbackStrip::output_release (GdkEventButton *ev)
675 switch (ev->button) {
677 edit_output_configuration ();
685 FoldbackStrip::output_press (GdkEventButton *ev)
687 using namespace Menu_Helpers;
688 if (!ARDOUR_UI_UTILS::engine_is_running ()) {
692 MenuList& citems = output_menu.items();
693 switch (ev->button) {
696 return false; //wait for the mouse-up to pop the dialog
700 output_menu.set_name ("ArdourContextMenu");
702 output_menu_bundles.clear ();
704 citems.push_back (MenuElem (_("Disconnect"), sigc::mem_fun (*(static_cast<RouteUI*>(this)), &RouteUI::disconnect_output)));
706 citems.push_back (SeparatorElem());
707 uint32_t const n_with_separator = citems.size ();
709 ARDOUR::BundleList current = _route->output()->bundles_connected ();
711 boost::shared_ptr<ARDOUR::BundleList> b = _session->bundles ();
713 DataType intended_type = DataType::AUDIO;
715 /* then try adding user bundles, often labeled/grouped physical inputs */
716 for (ARDOUR::BundleList::iterator i = b->begin(); i != b->end(); ++i) {
717 if (boost::dynamic_pointer_cast<UserBundle> (*i)) {
718 maybe_add_bundle_to_output_menu (*i, current, intended_type);
722 /* then all other bundles, including physical outs or other sofware */
723 for (ARDOUR::BundleList::iterator i = b->begin(); i != b->end(); ++i) {
724 if (boost::dynamic_pointer_cast<UserBundle> (*i) == 0) {
725 maybe_add_bundle_to_output_menu (*i, current, intended_type);
729 if (citems.size() == n_with_separator) {
730 /* no routes added; remove the separator */
734 citems.push_back (SeparatorElem());
735 citems.push_back (MenuElem (_("Routing Grid"), sigc::mem_fun (*(static_cast<RouteUI*>(this)), &RouteUI::edit_output_configuration)));
737 Gtkmm2ext::anchored_menu_popup(&output_menu, &output_button, "",
750 FoldbackStrip::bundle_output_chosen (boost::shared_ptr<ARDOUR::Bundle> c)
756 _route->output()->connect_ports_to_bundle (c, true, true, this);
760 FoldbackStrip::maybe_add_bundle_to_output_menu (boost::shared_ptr<Bundle> b, ARDOUR::BundleList const& /*current*/,
763 using namespace Menu_Helpers;
765 /* The bundle should be an input one, but not ours */
766 if (b->ports_are_inputs() == false || *b == *_route->input()->bundle()) {
770 /* Don't add the monitor input */
771 boost::shared_ptr<Route> monitor = _session->monitor_out();
772 if (monitor && b->has_same_ports (monitor->input()->bundle()))
775 /* It should have the same number of |type| channels as our outputs. */
776 if (b->nchannels().n(type) != _route->n_outputs().n(type)) {
780 /* Avoid adding duplicates */
781 list<boost::shared_ptr<Bundle> >::iterator i = output_menu_bundles.begin ();
782 while (i != output_menu_bundles.end() && b->has_same_ports (*i) == false) {
785 if (i != output_menu_bundles.end()) {
789 /* Now add the bundle to the menu */
790 output_menu_bundles.push_back (b);
792 MenuList& citems = output_menu.items();
793 citems.push_back (MenuElemNoMnemonic (b->name (), sigc::bind (sigc::mem_fun(*this, &FoldbackStrip::bundle_output_chosen), b)));
797 FoldbackStrip::connect_to_pan ()
799 ENSURE_GUI_THREAD (*this, &FoldbackStrip::connect_to_pan)
801 panstate_connection.disconnect ();
802 panstyle_connection.disconnect ();
804 if (!_route->panner()) {
808 boost::shared_ptr<Pannable> p = _route->pannable ();
810 update_panner_choices();
814 FoldbackStrip::update_panner_choices ()
816 ENSURE_GUI_THREAD (*this, &FoldbackStrip::update_panner_choices)
817 if (!_route->panner_shell()) { return; }
819 uint32_t in = _route->output()->n_ports().n_audio();
821 if (_route->panner()) {
822 in = _route->panner()->in().n_audio();
825 panners.set_available_panners(PannerManager::instance().PannerManager::get_available_panners(in, out));
829 * Output port labelling
831 * Case 1: Each output has one connection, all connections are to system:playback_%i
832 * out 1 -> system:playback_1
833 * out 2 -> system:playback_2
834 * out 3 -> system:playback_3
837 * Case 2: Each output has one connection, all connections are to ardour:track_x/in 1
838 * out 1 -> ardour:track_x/in 1
839 * out 2 -> ardour:track_x/in 2
840 * Display as: track_x
842 * Case 3: Each output has one connection, all connections are to Jack client "program x"
843 * out 1 -> program x:foo
844 * out 2 -> program x:foo
845 * Display as: program x
847 * Case 4: No connections (Disconnected)
850 * Default case (unusual routing):
851 * Display as: *number of connections*
856 * .-----------------------------------------------.
858 * | out 1 -> ardour:master/in 1, jamin:input/in 1 |
859 * | out 2 -> ardour:master/in 2, jamin:input/in 2 |
860 * '-----------------------------------------------'
861 * .-----------------------------------------------.
864 * '-----------------------------------------------'
868 FoldbackStrip::update_io_button ()
870 ostringstream tooltip;
872 bool have_label = false;
874 uint32_t total_connection_count = 0;
875 uint32_t typed_connection_count = 0;
876 bool each_typed_port_has_one_connection = true;
878 DataType dt = DataType::AUDIO;
879 boost::shared_ptr<IO> io = _route->output();
881 /* Fill in the tooltip. Also count:
882 * - The total number of connections.
883 * - The number of main-typed connections.
884 * - Whether each main-typed port has exactly one connection. */
886 tooltip << string_compose (_("<b>OUTPUT</b> from %1"),
887 Gtkmm2ext::markup_escape_text (_route->name()));
889 string arrow = Gtkmm2ext::markup_escape_text(" -> ");
890 vector<string> port_connections;
891 for (PortSet::iterator port = io->ports().begin();
892 port != io->ports().end();
894 port_connections.clear();
895 port->get_connections(port_connections);
897 uint32_t port_connection_count = 0;
899 for (vector<string>::iterator i = port_connections.begin();
900 i != port_connections.end();
902 ++port_connection_count;
904 if (port_connection_count == 1) {
905 tooltip << endl << Gtkmm2ext::markup_escape_text (
906 port->name().substr(port->name().find("/") + 1));
912 tooltip << Gtkmm2ext::markup_escape_text(*i);
915 total_connection_count += port_connection_count;
916 if (port->type() == dt) {
917 typed_connection_count += port_connection_count;
918 each_typed_port_has_one_connection &= (port_connection_count == 1);
923 if (total_connection_count == 0) {
924 tooltip << endl << _("Disconnected");
927 if (typed_connection_count == 0) {
932 /* Are all main-typed channels connected to the same route ? */
934 boost::shared_ptr<ARDOUR::RouteList> routes = _session->get_routes ();
935 for (ARDOUR::RouteList::const_iterator route = routes->begin();
936 route != routes->end();
938 boost::shared_ptr<IO> dest_io = (*route)->output();
939 if (io->bundle()->connected_to(dest_io->bundle(),
942 label << Gtkmm2ext::markup_escape_text ((*route)->name());
949 /* Are all main-typed channels connected to the same (user) bundle ? */
951 boost::shared_ptr<ARDOUR::BundleList> bundles = _session->bundles ();
952 for (ARDOUR::BundleList::iterator bundle = bundles->begin();
953 bundle != bundles->end();
955 if (boost::dynamic_pointer_cast<UserBundle> (*bundle) == 0)
957 if (io->bundle()->connected_to(*bundle, _session->engine(),
959 label << Gtkmm2ext::markup_escape_text ((*bundle)->name());
966 /* Is each main-typed channel only connected to a physical output ? */
967 if (!have_label && each_typed_port_has_one_connection) {
968 ostringstream temp_label;
970 string playorcapture;
972 _session->engine().get_physical_outputs(dt, phys);
973 playorcapture = "playback_";
974 for (PortSet::iterator port = io->ports().begin(dt);
975 port != io->ports().end(dt);
978 for (vector<string>::iterator s = phys.begin();
981 if (!port->connected_to(*s))
983 pn = AudioEngine::instance()->get_pretty_name_by_name(*s);
985 string::size_type start = (*s).find(playorcapture);
986 if (start != string::npos) {
987 pn = (*s).substr(start + playorcapture.size());
993 temp_label.str(""); /* erase the failed attempt */
996 if (port != io->ports().begin(dt))
1001 if (!temp_label.str().empty()) {
1002 label << temp_label.str();
1007 /* Is each main-typed channel connected to a single and different port with
1008 * the same client name (e.g. another JACK client) ? */
1009 if (!have_label && each_typed_port_has_one_connection) {
1010 string maybe_client = "";
1011 vector<string> connections;
1012 for (PortSet::iterator port = io->ports().begin(dt);
1013 port != io->ports().end(dt);
1015 port_connections.clear();
1016 port->get_connections(port_connections);
1017 string connection = port_connections.front();
1019 vector<string>::iterator i = connections.begin();
1020 while (i != connections.end() && *i != connection) {
1023 if (i != connections.end())
1024 break; /* duplicate connection */
1025 connections.push_back(connection);
1027 connection = connection.substr(0, connection.find(":"));
1028 if (maybe_client.empty())
1029 maybe_client = connection;
1030 if (maybe_client != connection)
1033 if (connections.size() == io->n_ports().n(dt)) {
1034 label << maybe_client;
1039 /* Odd configuration */
1041 label << "*" << total_connection_count << "*";
1044 if (total_connection_count > typed_connection_count) {
1045 label << "\u2295"; /* circled plus */
1048 /* Actually set the properties of the button */
1049 char * cstr = new char[tooltip.str().size() + 1];
1050 strcpy(cstr, tooltip.str().c_str());
1052 output_button.set_text (label.str());
1053 set_tooltip (&output_button, cstr);
1059 FoldbackStrip::update_output_display ()
1061 update_io_button ();
1062 panners.setup_pan ();
1064 if (has_audio_outputs ()) {
1065 panners.show_all ();
1067 panners.hide_all ();
1072 FoldbackStrip::io_changed_proxy ()
1074 Glib::signal_idle().connect_once (sigc::mem_fun (*this, &FoldbackStrip::update_panner_choices));
1078 FoldbackStrip::port_connected_or_disconnected (boost::weak_ptr<Port> wa, boost::weak_ptr<Port> wb)
1080 boost::shared_ptr<Port> a = wa.lock ();
1081 boost::shared_ptr<Port> b = wb.lock ();
1083 if ((a && _route->output()->has_port (a)) || (b && _route->output()->has_port (b))) {
1084 update_output_display ();
1089 FoldbackStrip::setup_comment_button ()
1091 std::string comment = _route->comment();
1093 set_tooltip (_comment_button, comment.empty() ? _("Click to add/edit comments") : _route->comment());
1095 if (comment.empty ()) {
1096 _comment_button.set_name ("generic button");
1097 _comment_button.set_text (_("Comments"));
1101 _comment_button.set_name ("comment button");
1103 string::size_type pos = comment.find_first_of (" \t\n");
1104 if (pos != string::npos) {
1105 comment = comment.substr (0, pos);
1107 if (comment.empty()) {
1108 _comment_button.set_text (_("Comments"));
1110 _comment_button.set_text (comment);
1115 FoldbackStrip::help_count_plugins (boost::weak_ptr<Processor> p)
1117 boost::shared_ptr<Processor> processor (p.lock ());
1118 if (!processor || !processor->display_to_user()) {
1121 boost::shared_ptr<PluginInsert> pi = boost::dynamic_pointer_cast<PluginInsert> (processor);
1123 if (pi && pi->is_channelstrip ()) {
1128 ++_plugin_insert_cnt;
1133 FoldbackStrip::build_route_ops_menu ()
1135 using namespace Menu_Helpers;
1137 Menu* menu = manage (new Menu);
1138 MenuList& items = menu->items ();
1139 menu->set_name ("ArdourContextMenu");
1141 items.push_back (MenuElem (_("Comments..."), sigc::mem_fun (*this, &RouteUI::open_comment_editor)));
1143 items.push_back (MenuElem (_("Outputs..."), sigc::mem_fun (*this, &RouteUI::edit_output_configuration)));
1145 items.push_back (SeparatorElem());
1147 items.push_back (MenuElem (_("Save As Template..."), sigc::mem_fun(*this, &RouteUI::save_as_template)));
1149 items.push_back (MenuElem (_("Rename..."), sigc::mem_fun(*this, &RouteUI::route_rename)));
1151 items.push_back (SeparatorElem());
1152 items.push_back (CheckMenuElem (_("Active")));
1153 Gtk::CheckMenuItem* i = dynamic_cast<Gtk::CheckMenuItem *> (&items.back());
1154 i->set_active (_route->active());
1155 i->set_sensitive(! _session->transport_rolling());
1156 i->signal_activate().connect (sigc::bind (sigc::mem_fun (*this, &RouteUI::set_route_active), !_route->active(), false));
1158 items.push_back (SeparatorElem());
1159 items.push_back (CheckMenuElem (_("Protect Against Denormals"), sigc::mem_fun (*this, &RouteUI::toggle_denormal_protection)));
1160 denormal_menu_item = dynamic_cast<Gtk::CheckMenuItem *> (&items.back());
1161 denormal_menu_item->set_active (_route->denormal_protection());
1163 items.push_back (SeparatorElem());
1164 items.push_back (MenuElem (_("Remove"), sigc::mem_fun(*this, &FoldbackStrip::remove_current_fb)));
1169 FoldbackStrip::build_route_select_menu ()
1171 using namespace Menu_Helpers;
1173 Menu* menu = manage (new Menu);
1174 MenuList& items = menu->items ();
1175 menu->set_name ("ArdourContextMenu");
1177 StripableList fb_list;
1178 _session->get_stripables (fb_list, PresentationInfo::FoldbackBus);
1179 for (StripableList::iterator s = fb_list.begin(); s != fb_list.end(); ++s) {
1181 boost::shared_ptr<Route> route = boost::dynamic_pointer_cast<Route> ((*s));
1182 if (route == _route) {
1185 items.push_back (MenuElem (route->name (), sigc::bind (sigc::mem_fun (*this, &FoldbackStrip::set_route), route)));
1192 FoldbackStrip::name_button_button_press (GdkEventButton* ev)
1194 if (ev->button == 1) {
1195 Menu* menu = build_route_select_menu ();
1197 Gtkmm2ext::anchored_menu_popup(menu, &name_button, "",
1200 } else if (ev->button == 3) {
1201 Menu* r_menu = build_route_ops_menu ();
1202 r_menu->popup (3, ev->time);
1209 FoldbackStrip::previous_button_clicked ()
1211 bool past_current = false;
1212 StripableList slist;
1213 boost::shared_ptr<Route> previous = boost::shared_ptr<Route> ();
1214 _session->get_stripables (slist, PresentationInfo::FoldbackBus);
1215 if (slist.size () > 1) {
1216 for (StripableList::iterator s = slist.begin(); s != slist.end(); ++s) {
1217 if ((*s) == _route) {
1218 past_current = true;
1220 if (!past_current) {
1221 previous = boost::dynamic_pointer_cast<Route> (*s);
1225 // only one route do nothing
1228 //use previous to set route
1230 set_route (previous);
1235 FoldbackStrip::next_button_clicked ()
1237 bool past_current = false;
1238 StripableList slist;
1239 boost::shared_ptr<Route> next = boost::shared_ptr<Route> ();
1240 _session->get_stripables (slist, PresentationInfo::FoldbackBus);
1241 if (slist.size () > 1) {
1242 for (StripableList::iterator s = slist.begin(); s != slist.end(); ++s) {
1244 next = boost::dynamic_pointer_cast<Route> (*s);
1247 if ((*s) == _route) {
1248 past_current = true;
1252 // only one route do nothing
1255 //use next to set route
1262 FoldbackStrip::prev_next_changed ()
1264 StripableList slist;
1265 _session->get_stripables (slist, PresentationInfo::FoldbackBus);
1266 if ((slist.size() < 2) || (boost::dynamic_pointer_cast<Stripable> (_route) == *(slist.begin()))) {
1267 _previous_button.set_sensitive (false);
1269 _previous_button.set_sensitive (true);
1271 if ((slist.size () < 2) || boost::dynamic_pointer_cast<Stripable> (_route) == *(--slist.end())) {
1272 _next_button.set_sensitive (false);
1274 _next_button.set_sensitive (true);
1279 FoldbackStrip::hide_clicked()
1281 _hide_button.set_sensitive(false);
1282 ActionManager::get_toggle_action (X_("Mixer"), X_("ToggleFoldbackStrip"))->set_active (false);
1283 _hide_button.set_sensitive(true);
1287 FoldbackStrip::show_sends_clicked ()
1289 if (_showing_sends) {
1290 BusSendDisplayChanged (boost::shared_ptr<Route> ()); /* EMIT SIGNAL */
1291 _showing_sends = false;
1292 _show_sends_button.set_active (false);
1293 send_blink_connection.disconnect ();
1295 BusSendDisplayChanged (_route); /* EMIT SIGNAL */
1296 _showing_sends = true;
1297 _show_sends_button.set_active (true);
1298 send_blink_connection = Timers::blink_connect (sigc::mem_fun (*this, &FoldbackStrip::send_blink));
1303 FoldbackStrip::send_blink (bool onoff)
1305 if (!(&_show_sends_button)) {
1310 _show_sends_button.set_active_state (Gtkmm2ext::ExplicitActive);
1312 _show_sends_button.unset_active_state ();
1317 FoldbackStrip::set_selected (bool yn)
1320 global_frame.set_shadow_type (Gtk::SHADOW_IN);
1321 global_frame.set_name ("MixerStripFrame");
1323 global_frame.queue_draw ();
1328 FoldbackStrip::route_property_changed (const PropertyChange& what_changed)
1330 if (what_changed.contains (ARDOUR::Properties::name)) {
1336 FoldbackStrip::name_changed ()
1338 name_button.set_text (_route->name());
1340 set_tooltip (name_button, Gtkmm2ext::markup_escape_text(_route->name()));
1344 FoldbackStrip::set_embedded (bool yn)
1350 FoldbackStrip::map_frozen ()
1352 ENSURE_GUI_THREAD (*this, &FoldbackStrip::map_frozen)
1355 RouteUI::map_frozen ();
1359 FoldbackStrip::hide_redirect_editors ()
1361 _route->foreach_processor (sigc::mem_fun (*this, &FoldbackStrip::hide_processor_editor));
1365 FoldbackStrip::hide_processor_editor (boost::weak_ptr<Processor> p)
1367 boost::shared_ptr<Processor> processor (p.lock ());
1372 Gtk::Window* w = insert_box->get_processor_ui (processor);
1380 FoldbackStrip::reset_strip_style ()
1382 if (_route->active()) {
1383 set_name ("FoldbackBusStripBase");
1385 set_name ("AudioBusStripBaseInactive");
1391 FoldbackStrip::engine_stopped ()
1396 FoldbackStrip::engine_running ()
1401 FoldbackStrip::drop_send ()
1403 boost::shared_ptr<Send> current_send;
1405 if (_current_delivery && ((current_send = boost::dynamic_pointer_cast<Send>(_current_delivery)) != 0)) {
1406 current_send->set_metering (false);
1409 send_gone_connection.disconnect ();
1410 output_button.set_sensitive (true);
1411 set_invert_sensitive (true);
1412 solo_button->set_sensitive (true);
1413 _comment_button.set_sensitive (true);
1414 fb_level_control->set_sensitive (true);
1415 set_button_names (); // update solo button visual state
1419 FoldbackStrip::set_current_delivery (boost::shared_ptr<Delivery> d)
1421 _current_delivery = d;
1422 DeliveryChanged (_current_delivery);
1426 FoldbackStrip::revert_to_default_display ()
1430 set_current_delivery (_route->main_outs ());
1432 panner_ui().set_panner (_route->main_outs()->panner_shell(), _route->main_outs()->panner());
1433 update_panner_choices();
1434 panner_ui().setup_pan ();
1435 panner_ui().set_send_drawing_mode (false);
1437 if (has_audio_outputs ()) {
1438 panners.show_all ();
1440 panners.hide_all ();
1443 reset_strip_style ();
1447 FoldbackStrip::set_button_names ()
1450 if (!Config->get_solo_control_is_listen_control()) {
1451 solo_button->hide ();
1453 solo_button->set_sensitive (true);
1454 solo_button->show ();
1455 UI::instance()->set_tip (solo_button, _("Listen on monitor"), "");
1456 switch (Config->get_listen_position()) {
1457 case AfterFaderListen:
1458 solo_button->set_text (_("Listen"));
1460 case PreFaderListen:
1461 solo_button->set_text (_("Listen"));
1468 FoldbackStrip::plugin_selector()
1470 return _mixer.plugin_selector();
1474 FoldbackStrip::route_active_changed ()
1476 reset_strip_style ();
1480 FoldbackStrip::copy_processors ()
1482 insert_box->processor_operation (ProcessorBox::ProcessorsCopy);
1486 FoldbackStrip::cut_processors ()
1488 insert_box->processor_operation (ProcessorBox::ProcessorsCut);
1492 FoldbackStrip::paste_processors ()
1494 insert_box->processor_operation (ProcessorBox::ProcessorsPaste);
1498 FoldbackStrip::select_all_processors ()
1500 insert_box->processor_operation (ProcessorBox::ProcessorsSelectAll);
1504 FoldbackStrip::deselect_all_processors ()
1506 insert_box->processor_operation (ProcessorBox::ProcessorsSelectNone);
1510 FoldbackStrip::delete_processors ()
1512 return insert_box->processor_operation (ProcessorBox::ProcessorsDelete);
1516 FoldbackStrip::toggle_processors ()
1518 insert_box->processor_operation (ProcessorBox::ProcessorsToggleActive);
1522 FoldbackStrip::ab_plugins ()
1524 insert_box->processor_operation (ProcessorBox::ProcessorsAB);
1528 FoldbackStrip::create_selected_sends (bool include_buses)
1530 boost::shared_ptr<StripableList> slist (new StripableList);
1531 PresentationInfo::Flag fl = PresentationInfo::AudioTrack;
1532 if (include_buses) {
1533 fl = PresentationInfo::MixerRoutes;
1535 _session->get_stripables (*slist, fl);
1537 for (StripableList::iterator i = (*slist).begin(); i != (*slist).end(); ++i) {
1538 if ((*i)->is_selected() && !(*i)->is_master() && !(*i)->is_monitor()) {
1539 boost::shared_ptr<Route> rt = boost::dynamic_pointer_cast<Route>(*i);
1541 rt->add_foldback_send (_route);
1549 FoldbackStrip::send_button_press_event (GdkEventButton *ev)
1551 if (ev->button == 3) {
1552 Menu* menu = build_sends_menu ();
1553 menu->popup (3, ev->time);
1560 FoldbackStrip::build_sends_menu ()
1562 using namespace Menu_Helpers;
1564 Menu* menu = manage (new Menu);
1565 MenuList& items = menu->items ();
1566 menu->set_name ("ArdourContextMenu");
1569 MenuElem(_("Assign selected tracks (prefader)"), sigc::bind (sigc::mem_fun (*this, &FoldbackStrip::create_selected_sends), false))
1573 MenuElem(_("Assign selected tracks and buses (prefader)"), sigc::bind (sigc::mem_fun (*this, &FoldbackStrip::create_selected_sends), true)));
1575 items.push_back (MenuElem(_("Copy track/bus gains to sends"), sigc::mem_fun (*this, &RouteUI::set_sends_gain_from_track)));
1576 items.push_back (MenuElem(_("Set sends gain to -inf"), sigc::mem_fun (*this, &RouteUI::set_sends_gain_to_zero)));
1577 items.push_back (MenuElem(_("Set sends gain to 0dB"), sigc::mem_fun (*this, &RouteUI::set_sends_gain_to_unity)));
1583 FoldbackStrip::remove_current_fb ()
1586 StripableList slist;
1587 boost::shared_ptr<Route> next = boost::shared_ptr<Route> ();
1588 boost::shared_ptr<Route> old_route = _route;
1589 _session->get_stripables (slist, PresentationInfo::FoldbackBus);
1590 if (slist.size ()) {
1591 for (StripableList::iterator s = slist.begin(); s != slist.end(); ++s) {
1592 if ((*s) != _route) {
1593 next = boost::dynamic_pointer_cast<Route> (*s);
1600 _session->remove_route (old_route);
1601 prev_next_changed ();
1604 RouteUI::self_delete ();
1605 _session->remove_route (old_route);