From e2757229a74a17a76682b6c72868d8e4822b7678 Mon Sep 17 00:00:00 2001 From: Paul Davis Date: Tue, 15 Nov 2011 19:33:09 +0000 Subject: provide link-editor-and-mixer-selection option. gui implementation is slightly hacky because of the implicit endless loop that the link creates git-svn-id: svn://localhost/ardour2/branches/3.0@10624 d708f5d6-7413-0410-9779-e7cbd77b26cf --- gtk2_ardour/ardour_ui_dependents.cc | 3 ++ gtk2_ardour/ardour_ui_mixer.cc | 2 +- gtk2_ardour/editor.cc | 5 ++- gtk2_ardour/editor.h | 6 +++- gtk2_ardour/editor_mixer.cc | 35 +++++++++++++++++++ gtk2_ardour/mixer_actor.h | 4 +-- gtk2_ardour/mixer_ui.cc | 50 +++++++++++++++++++++++++++ gtk2_ardour/mixer_ui.h | 11 ++++-- gtk2_ardour/processor_box.cc | 2 +- gtk2_ardour/processor_box.h | 6 ++-- gtk2_ardour/public_editor.h | 3 +- gtk2_ardour/rc_option_editor.cc | 8 +++++ gtk2_ardour/route_params_ui.h | 2 +- gtk2_ardour/route_processor_selection.cc | 54 +++++++++++++++++++----------- gtk2_ardour/route_processor_selection.h | 11 +++--- gtk2_ardour/selection.cc | 27 ++++++++++++--- gtk2_ardour/selection.h | 3 ++ libs/ardour/ardour/rc_configuration_vars.h | 1 + 18 files changed, 191 insertions(+), 42 deletions(-) diff --git a/gtk2_ardour/ardour_ui_dependents.cc b/gtk2_ardour/ardour_ui_dependents.cc index 937ba5ab81..c55942b4bd 100644 --- a/gtk2_ardour/ardour_ui_dependents.cc +++ b/gtk2_ardour/ardour_ui_dependents.cc @@ -66,6 +66,9 @@ ARDOUR_UI::we_have_dependents () keyboard->setup_keybindings (); editor->setup_tooltips (); editor->UpdateAllTransportClocks.connect (sigc::mem_fun (*this, &ARDOUR_UI::update_transport_clocks)); + + editor->track_mixer_selection (); + mixer->track_editor_selection (); } void diff --git a/gtk2_ardour/ardour_ui_mixer.cc b/gtk2_ardour/ardour_ui_mixer.cc index b6983cd8ac..e3250d2a1f 100644 --- a/gtk2_ardour/ardour_ui_mixer.cc +++ b/gtk2_ardour/ardour_ui_mixer.cc @@ -35,7 +35,7 @@ ARDOUR_UI::create_mixer () { try { - mixer = new Mixer_UI (); + mixer = Mixer_UI::instance (); } catch (failed_constructor& err) { diff --git a/gtk2_ardour/editor.cc b/gtk2_ardour/editor.cc index e022ad67b5..80f7feed72 100644 --- a/gtk2_ardour/editor.cc +++ b/gtk2_ardour/editor.cc @@ -108,6 +108,7 @@ #include "marker.h" #include "midi_time_axis.h" #include "mixer_strip.h" +#include "mixer_ui.h" #include "mouse_cursors.h" #include "playlist_selector.h" #include "public_editor.h" @@ -282,6 +283,7 @@ Editor::Editor () , _last_cut_copy_source_track (0) , _region_selection_change_updates_region_list (true) + , _following_mixer_selection (false) { constructed = false; @@ -5005,7 +5007,7 @@ Editor::foreach_time_axis_view (sigc::slot theslot) /** Find a RouteTimeAxisView by the ID of its route */ RouteTimeAxisView* -Editor::get_route_view_by_route_id (PBD::ID& id) const +Editor::get_route_view_by_route_id (const PBD::ID& id) const { RouteTimeAxisView* v; @@ -5499,3 +5501,4 @@ Editor::popup_control_point_context_menu (ArdourCanvas::Item* item, GdkEvent* ev _control_point_context_menu.popup (event->button.button, event->button.time); } + diff --git a/gtk2_ardour/editor.h b/gtk2_ardour/editor.h index 683cdc037a..e799e89664 100644 --- a/gtk2_ardour/editor.h +++ b/gtk2_ardour/editor.h @@ -201,7 +201,7 @@ class Editor : public PublicEditor, public PBD::ScopedConnectionList, public ARD void foreach_time_axis_view (sigc::slot); void add_to_idle_resize (TimeAxisView*, int32_t); - RouteTimeAxisView* get_route_view_by_route_id (PBD::ID& id) const; + RouteTimeAxisView* get_route_view_by_route_id (const PBD::ID& id) const; void consider_auditioning (boost::shared_ptr); void hide_a_region (boost::shared_ptr); @@ -269,6 +269,7 @@ class Editor : public PublicEditor, public PBD::ScopedConnectionList, public ARD Selection& get_selection() const { return *selection; } Selection& get_cut_buffer() const { return *cut_buffer; } + void track_mixer_selection (); bool extend_selection_to_track (TimeAxisView&); @@ -2065,6 +2066,9 @@ class Editor : public PublicEditor, public PBD::ScopedConnectionList, public ARD void resize_text_widgets (); + void follow_mixer_selection (); + bool _following_mixer_selection; + friend class Drag; friend class RegionDrag; friend class RegionMoveDrag; diff --git a/gtk2_ardour/editor_mixer.cc b/gtk2_ardour/editor_mixer.cc index 09e3764483..0817a31128 100644 --- a/gtk2_ardour/editor_mixer.cc +++ b/gtk2_ardour/editor_mixer.cc @@ -27,6 +27,8 @@ #include "pbd/enumwriter.h" +#include "ardour/rc_configuration.h" + #include "actions.h" #include "ardour_ui.h" #include "audio_time_axis.h" @@ -38,6 +40,7 @@ #include "gui_thread.h" #include "midi_time_axis.h" #include "mixer_strip.h" +#include "mixer_ui.h" #include "selection.h" #include "i18n.h" @@ -251,3 +254,35 @@ Editor::mixer_strip_width_changed () editor_mixer_strip_width = current_mixer_strip->get_width_enum (); } + +void +Editor::track_mixer_selection () +{ + Mixer_UI::instance()->selection().RoutesChanged.connect (sigc::mem_fun (*this, &Editor::follow_mixer_selection)); +} + +void +Editor::follow_mixer_selection () +{ + if (!ARDOUR::Config->get_link_editor_and_mixer_selection() || _following_mixer_selection) { + return; + } + + _following_mixer_selection = true; + selection->block_tracks_changed (true); + + RouteUISelection& s (Mixer_UI::instance()->selection().routes); + + selection->clear_tracks (); + + for (RouteUISelection::iterator i = s.begin(); i != s.end(); ++i) { + TimeAxisView* tav = get_route_view_by_route_id ((*i)->route()->id()); + if (tav) { + selection->add (tav); + } + } + + _following_mixer_selection = false; + selection->block_tracks_changed (false); + selection->TracksChanged (); /* EMIT SIGNAL */ +} diff --git a/gtk2_ardour/mixer_actor.h b/gtk2_ardour/mixer_actor.h index 980938f8a2..85c7bc1cf8 100644 --- a/gtk2_ardour/mixer_actor.h +++ b/gtk2_ardour/mixer_actor.h @@ -35,14 +35,14 @@ class MixerActor : virtual public sigc::trackable MixerActor (); virtual ~MixerActor (); - RouteRedirectSelection& selection() { return _selection; } + RouteProcessorSelection& selection() { return _selection; } void register_actions (); void load_bindings (); Gtkmm2ext::Bindings bindings; protected: - RouteRedirectSelection _selection; + RouteProcessorSelection _selection; RouteUISelection _route_targets; Gtkmm2ext::ActionMap myactions; diff --git a/gtk2_ardour/mixer_ui.cc b/gtk2_ardour/mixer_ui.cc index c3aa14d6e2..216c005178 100644 --- a/gtk2_ardour/mixer_ui.cc +++ b/gtk2_ardour/mixer_ui.cc @@ -47,6 +47,7 @@ #include "mixer_strip.h" #include "monitor_section.h" #include "plugin_selector.h" +#include "public_editor.h" #include "ardour_ui.h" #include "prompter.h" #include "utils.h" @@ -65,8 +66,21 @@ using namespace std; using PBD::atoi; +Mixer_UI* Mixer_UI::_instance = 0; + +Mixer_UI* +Mixer_UI::instance () +{ + if (!_instance) { + _instance = new Mixer_UI; + } + + return _instance; +} + Mixer_UI::Mixer_UI () : Window (Gtk::WINDOW_TOPLEVEL) + , _following_editor_selection (false) { /* allow this window to become the key focus window */ set_flags (CAN_FOCUS); @@ -238,6 +252,13 @@ Mixer_UI::~Mixer_UI () { } +void +Mixer_UI::track_editor_selection () +{ + PublicEditor::instance().get_selection().TracksChanged.connect (sigc::mem_fun (*this, &Mixer_UI::follow_editor_selection)); +} + + void Mixer_UI::ensure_float (Window& win) { @@ -431,6 +452,35 @@ Mixer_UI::sync_order_keys (string const & src) } } +void +Mixer_UI::follow_editor_selection () +{ + if (!Config->get_link_editor_and_mixer_selection() || _following_editor_selection) { + return; + } + + _following_editor_selection = true; + _selection.block_routes_changed (true); + + TrackSelection& s (PublicEditor::instance().get_selection().tracks); + + _selection.clear_routes (); + + for (TrackViewList::iterator i = s.begin(); i != s.end(); ++i) { + RouteTimeAxisView* rtav = dynamic_cast (*i); + if (rtav) { + MixerStrip* ms = strip_by_route (rtav->route()); + if (ms) { + _selection.add (ms); + } + } + } + + _following_editor_selection = false; + _selection.block_routes_changed (false); +} + + MixerStrip* Mixer_UI::strip_by_route (boost::shared_ptr r) { diff --git a/gtk2_ardour/mixer_ui.h b/gtk2_ardour/mixer_ui.h index 6388ed5180..5a3b746457 100644 --- a/gtk2_ardour/mixer_ui.h +++ b/gtk2_ardour/mixer_ui.h @@ -55,10 +55,11 @@ class MonitorSection; class Mixer_UI : public Gtk::Window, public PBD::ScopedConnectionList, public ARDOUR::SessionHandlePtr, public MixerActor { public: - Mixer_UI (); + static Mixer_UI* instance(); ~Mixer_UI(); void set_session (ARDOUR::Session *); + void track_editor_selection (); PluginSelector* plugin_selector(); @@ -86,6 +87,9 @@ class Mixer_UI : public Gtk::Window, public PBD::ScopedConnectionList, public AR void set_route_targets_for_operation (); private: + Mixer_UI (); + static Mixer_UI* _instance; + bool _visible; Gtk::HBox global_hpacker; @@ -146,8 +150,6 @@ class Mixer_UI : public Gtk::Window, public PBD::ScopedConnectionList, public AR void strip_select_op (bool audiotrack, bool select); void select_strip_op (MixerStrip*, bool select); - void follow_strip_selection (); - gint start_updating (); gint stop_updating (); @@ -270,6 +272,9 @@ class Mixer_UI : public Gtk::Window, public PBD::ScopedConnectionList, public AR MixerStrip* strip_by_x (int x); friend class MixerGroupTabs; + + void follow_editor_selection (); + bool _following_editor_selection; }; #endif /* __ardour_mixer_ui_h__ */ diff --git a/gtk2_ardour/processor_box.cc b/gtk2_ardour/processor_box.cc index 9d6bc10682..47d8a56016 100644 --- a/gtk2_ardour/processor_box.cc +++ b/gtk2_ardour/processor_box.cc @@ -460,7 +460,7 @@ PluginInsertProcessorEntry::SplittingIcon::on_expose_event (GdkEventExpose* ev) } ProcessorBox::ProcessorBox (ARDOUR::Session* sess, boost::function get_plugin_selector, - RouteRedirectSelection& rsel, MixerStrip* parent, bool owner_is_mixer) + RouteProcessorSelection& rsel, MixerStrip* parent, bool owner_is_mixer) : _parent_strip (parent) , _owner_is_mixer (owner_is_mixer) , ab_direction (true) diff --git a/gtk2_ardour/processor_box.h b/gtk2_ardour/processor_box.h index a17903c323..9bc45d716d 100644 --- a/gtk2_ardour/processor_box.h +++ b/gtk2_ardour/processor_box.h @@ -58,7 +58,7 @@ class MotionController; class PluginSelector; class PluginUIWindow; -class RouteRedirectSelection; +class RouteProcessorSelection; class MixerStrip; namespace ARDOUR { @@ -205,7 +205,7 @@ class ProcessorBox : public Gtk::HBox, public PluginInterestedObject, public ARD }; ProcessorBox (ARDOUR::Session*, boost::function get_plugin_selector, - RouteRedirectSelection&, MixerStrip* parent, bool owner_is_mixer = false); + RouteProcessorSelection&, MixerStrip* parent, bool owner_is_mixer = false); ~ProcessorBox (); void set_route (boost::shared_ptr); @@ -253,7 +253,7 @@ class ProcessorBox : public Gtk::HBox, public PluginInterestedObject, public ARD */ int _placement; - RouteRedirectSelection& _rr_selection; + RouteProcessorSelection& _rr_selection; void route_going_away (); diff --git a/gtk2_ardour/public_editor.h b/gtk2_ardour/public_editor.h index d42605eb1b..c8352150fc 100644 --- a/gtk2_ardour/public_editor.h +++ b/gtk2_ardour/public_editor.h @@ -203,6 +203,7 @@ class PublicEditor : public Gtk::Window, public PBD::StatefulDestructible { virtual gulong frame_to_pixel (framepos_t frame) const = 0; virtual Selection& get_selection () const = 0; virtual Selection& get_cut_buffer () const = 0; + virtual void track_mixer_selection () = 0; virtual bool extend_selection_to_track (TimeAxisView&) = 0; virtual void play_selection () = 0; virtual void set_show_measures (bool yn) = 0; @@ -286,7 +287,7 @@ class PublicEditor : public Gtk::Window, public PBD::StatefulDestructible { virtual TimeAxisView* get_named_time_axis(const std::string & name) = 0; #endif - virtual RouteTimeAxisView* get_route_view_by_route_id (PBD::ID& id) const = 0; + virtual RouteTimeAxisView* get_route_view_by_route_id (const PBD::ID& id) const = 0; virtual void get_equivalent_regions (RegionView* rv, std::vector&, PBD::PropertyID) const = 0; diff --git a/gtk2_ardour/rc_option_editor.cc b/gtk2_ardour/rc_option_editor.cc index 5e7df484ea..249846a296 100644 --- a/gtk2_ardour/rc_option_editor.cc +++ b/gtk2_ardour/rc_option_editor.cc @@ -1107,6 +1107,14 @@ RCOptionEditor::RCOptionEditor () sigc::mem_fun (*_rc_config, &RCConfiguration::set_sync_all_route_ordering) )); + add_option (_("Editor"), + new BoolOption ( + "link-editor-and-mixer-selection", + _("Synchronise editor and mixer selection"), + sigc::mem_fun (*_rc_config, &RCConfiguration::get_link_editor_and_mixer_selection), + sigc::mem_fun (*_rc_config, &RCConfiguration::set_link_editor_and_mixer_selection) + )); + add_option (_("Editor"), new BoolOption ( "name-new-markers", diff --git a/gtk2_ardour/route_params_ui.h b/gtk2_ardour/route_params_ui.h index d520e2988a..0da47bd4d4 100644 --- a/gtk2_ardour/route_params_ui.h +++ b/gtk2_ardour/route_params_ui.h @@ -123,7 +123,7 @@ class RouteParams_UI : public ArdourDialog, public PBD::ScopedConnectionList IOSelector * _output_iosel; PluginSelector *_plugin_selector; - RouteRedirectSelection _rr_selection; + RouteProcessorSelection _rr_selection; boost::shared_ptr _route; PBD::ScopedConnection _route_processors_connection; diff --git a/gtk2_ardour/route_processor_selection.cc b/gtk2_ardour/route_processor_selection.cc index 9022119729..1b4c0c538b 100644 --- a/gtk2_ardour/route_processor_selection.cc +++ b/gtk2_ardour/route_processor_selection.cc @@ -36,8 +36,13 @@ using namespace std; using namespace ARDOUR; using namespace PBD; -RouteRedirectSelection& -RouteRedirectSelection::operator= (const RouteRedirectSelection& other) +RouteProcessorSelection::RouteProcessorSelection() + : _no_route_change_signal (false) +{ +} + +RouteProcessorSelection& +RouteProcessorSelection::operator= (const RouteProcessorSelection& other) { if (&other != this) { processors = other.processors; @@ -47,39 +52,41 @@ RouteRedirectSelection::operator= (const RouteRedirectSelection& other) } bool -operator== (const RouteRedirectSelection& a, const RouteRedirectSelection& b) +operator== (const RouteProcessorSelection& a, const RouteProcessorSelection& b) { // XXX MUST TEST PROCESSORS SOMEHOW return a.routes == b.routes; } void -RouteRedirectSelection::clear () +RouteProcessorSelection::clear () { clear_processors (); clear_routes (); } void -RouteRedirectSelection::clear_processors () +RouteProcessorSelection::clear_processors () { processors.clear (); ProcessorsChanged (); } void -RouteRedirectSelection::clear_routes () +RouteProcessorSelection::clear_routes () { for (RouteUISelection::iterator i = routes.begin(); i != routes.end(); ++i) { (*i)->set_selected (false); } routes.clear (); drop_connections (); - RoutesChanged (); + if (!_no_route_change_signal) { + RoutesChanged (); + } } void -RouteRedirectSelection::add (XMLNode* node) +RouteProcessorSelection::add (XMLNode* node) { // XXX check for duplicate processors.add (node); @@ -87,7 +94,7 @@ RouteRedirectSelection::add (XMLNode* node) } void -RouteRedirectSelection::set (XMLNode* node) +RouteProcessorSelection::set (XMLNode* node) { clear_processors (); processors.set (node); @@ -95,7 +102,7 @@ RouteRedirectSelection::set (XMLNode* node) } void -RouteRedirectSelection::add (RouteUI* r) +RouteProcessorSelection::add (RouteUI* r) { if (find (routes.begin(), routes.end(), r) == routes.end()) { if (routes.insert (r).second) { @@ -104,43 +111,52 @@ RouteRedirectSelection::add (RouteUI* r) MixerStrip* ms = dynamic_cast (r); if (ms) { - ms->CatchDeletion.connect (*this, invalidator (*this), ui_bind (&RouteRedirectSelection::remove, this, _1), gui_context()); + ms->CatchDeletion.connect (*this, invalidator (*this), ui_bind (&RouteProcessorSelection::remove, this, _1), gui_context()); + } + + if (!_no_route_change_signal) { + RoutesChanged(); } - - RoutesChanged(); } } } void -RouteRedirectSelection::remove (RouteUI* r) +RouteProcessorSelection::remove (RouteUI* r) { - ENSURE_GUI_THREAD (*this, &RouteRedirectSelection::remove, r); + ENSURE_GUI_THREAD (*this, &RouteProcessorSelection::remove, r); RouteUISelection::iterator i; if ((i = find (routes.begin(), routes.end(), r)) != routes.end()) { routes.erase (i); (*i)->set_selected (false); - RoutesChanged (); + if (!_no_route_change_signal) { + RoutesChanged (); + } } } void -RouteRedirectSelection::set (RouteUI* r) +RouteProcessorSelection::set (RouteUI* r) { clear_routes (); add (r); } bool -RouteRedirectSelection::selected (RouteUI* r) +RouteProcessorSelection::selected (RouteUI* r) { return find (routes.begin(), routes.end(), r) != routes.end(); } bool -RouteRedirectSelection::empty () +RouteProcessorSelection::empty () { return processors.empty () && routes.empty (); } +void +RouteProcessorSelection::block_routes_changed (bool yn) +{ + _no_route_change_signal = yn; +} diff --git a/gtk2_ardour/route_processor_selection.h b/gtk2_ardour/route_processor_selection.h index 08616f0d50..3f8bb1a08c 100644 --- a/gtk2_ardour/route_processor_selection.h +++ b/gtk2_ardour/route_processor_selection.h @@ -26,19 +26,21 @@ #include "processor_selection.h" #include "route_ui_selection.h" -class RouteRedirectSelection : public PBD::ScopedConnectionList, public sigc::trackable +class RouteProcessorSelection : public PBD::ScopedConnectionList, public sigc::trackable { public: ProcessorSelection processors; RouteUISelection routes; - RouteRedirectSelection() {} + RouteProcessorSelection(); - RouteRedirectSelection& operator= (const RouteRedirectSelection& other); + RouteProcessorSelection& operator= (const RouteProcessorSelection& other); sigc::signal ProcessorsChanged; sigc::signal RoutesChanged; + void block_routes_changed (bool); + void clear (); bool empty(); @@ -56,9 +58,10 @@ class RouteRedirectSelection : public PBD::ScopedConnectionList, public sigc::tr private: void removed (RouteUI*); + bool _no_route_change_signal; }; -bool operator==(const RouteRedirectSelection& a, const RouteRedirectSelection& b); +bool operator==(const RouteProcessorSelection& a, const RouteProcessorSelection& b); #endif /* __ardour_gtk_route_processor_selection_h__ */ diff --git a/gtk2_ardour/selection.cc b/gtk2_ardour/selection.cc index 5b7a88c81e..0b15bbec08 100644 --- a/gtk2_ardour/selection.cc +++ b/gtk2_ardour/selection.cc @@ -51,6 +51,7 @@ Selection::Selection (const PublicEditor* e) : tracks (e) , editor (e) , next_time_id (0) + , _no_tracks_changed (false) { clear (); @@ -129,7 +130,9 @@ Selection::clear_tracks () { if (!tracks.empty()) { tracks.clear (); - TracksChanged(); + if (!_no_tracks_changed) { + TracksChanged(); + } } } @@ -231,7 +234,9 @@ Selection::toggle (TimeAxisView* track) tracks.erase (i); } - TracksChanged(); + if (!_no_tracks_changed) { + TracksChanged(); + } } void @@ -353,7 +358,9 @@ Selection::add (const TrackViewList& track_list) TrackViewList added = tracks.add (track_list); if (!added.empty()) { - TracksChanged (); + if (!_no_tracks_changed) { + TracksChanged (); + } } } @@ -519,7 +526,9 @@ Selection::remove (TimeAxisView* track) list::iterator i; if ((i = find (tracks.begin(), tracks.end(), track)) != tracks.end()) { tracks.erase (i); - TracksChanged(); + if (!_no_tracks_changed) { + TracksChanged(); + } } } @@ -538,7 +547,9 @@ Selection::remove (const TrackViewList& track_list) } if (changed) { - TracksChanged(); + if (!_no_tracks_changed) { + TracksChanged(); + } } } @@ -1232,3 +1243,9 @@ Selection::remove_regions (TimeAxisView* t) i = tmp; } } + +void +Selection::block_tracks_changed (bool yn) +{ + _no_tracks_changed = yn; +} diff --git a/gtk2_ardour/selection.h b/gtk2_ardour/selection.h index ded71c65e5..c474faa5b2 100644 --- a/gtk2_ardour/selection.h +++ b/gtk2_ardour/selection.h @@ -104,6 +104,8 @@ class Selection : public sigc::trackable, public PBD::ScopedConnectionList sigc::signal MidiNotesChanged; sigc::signal MidiRegionsChanged; + void block_tracks_changed (bool); + void clear (); bool empty (bool internal_selection = false); @@ -204,6 +206,7 @@ class Selection : public sigc::trackable, public PBD::ScopedConnectionList PublicEditor const * editor; uint32_t next_time_id; + bool _no_tracks_changed; }; bool operator==(const Selection& a, const Selection& b); diff --git a/libs/ardour/ardour/rc_configuration_vars.h b/libs/ardour/ardour/rc_configuration_vars.h index 035bc34553..35f5ce9838 100644 --- a/libs/ardour/ardour/rc_configuration_vars.h +++ b/libs/ardour/ardour/rc_configuration_vars.h @@ -73,6 +73,7 @@ CONFIG_VARIABLE (bool, use_osc, "use-osc", false) CONFIG_VARIABLE (EditMode, edit_mode, "edit-mode", Slide) CONFIG_VARIABLE (bool, link_region_and_track_selection, "link-region-and-track-selection", false) +CONFIG_VARIABLE (bool, link_editor_and_mixer_selection, "link-editor-and-mixer-selection", false) CONFIG_VARIABLE (std::string, keyboard_layout_name, "keyboard-layout-name", "ansi") CONFIG_VARIABLE (bool, automation_follows_regions, "automation-follows-regions", true) CONFIG_VARIABLE (bool, region_boundaries_from_selected_tracks, "region-boundaries-from-selected-tracks", true) -- cgit v1.2.3