From b68097422aa5b7104ed35043810009535122bacb Mon Sep 17 00:00:00 2001 From: Robin Gareus Date: Tue, 25 Jan 2022 23:19:20 +0100 Subject: [PATCH] Reduce and abstract EditorRoutes to a dedicated RouteList --- gtk2_ardour/editor.cc | 4 +- gtk2_ardour/editor_routes.cc | 1857 +------------------------------- gtk2_ardour/editor_routes.h | 205 +--- gtk2_ardour/route_list_base.cc | 1233 +++++++++++++++++++++ gtk2_ardour/route_list_base.h | 234 ++++ gtk2_ardour/wscript | 1 + 6 files changed, 1490 insertions(+), 2044 deletions(-) create mode 100644 gtk2_ardour/route_list_base.cc create mode 100644 gtk2_ardour/route_list_base.h diff --git a/gtk2_ardour/editor.cc b/gtk2_ardour/editor.cc index dd5b5b2a96..2f555041be 100644 --- a/gtk2_ardour/editor.cc +++ b/gtk2_ardour/editor.cc @@ -667,7 +667,7 @@ Editor::Editor () PresentationInfo::Change.connect (*this, MISSING_INVALIDATOR, boost::bind (&Editor::presentation_info_changed, this, _1), gui_context()); _route_groups = new EditorRouteGroups (this); - _routes = new EditorRoutes (this); + _routes = new EditorRoutes (); _regions = new EditorRegions (this); _sources = new EditorSources (this); _snapshots = new EditorSnapshots (this); @@ -1339,7 +1339,7 @@ Editor::set_session (Session *t) _regions->set_session (_session); _sources->set_session (_session); _snapshots->set_session (_session); - //_routes->set_session (_session); // temp disabled for EditorRoutes update + _routes->set_session (_session); _locations->set_session (_session); _properties_box->set_session (_session); diff --git a/gtk2_ardour/editor_routes.cc b/gtk2_ardour/editor_routes.cc index 157188e966..83a0be821a 100644 --- a/gtk2_ardour/editor_routes.cc +++ b/gtk2_ardour/editor_routes.cc @@ -1,10 +1,5 @@ /* - * Copyright (C) 2009-2011 Carl Hetherington - * Copyright (C) 2009-2014 David Robillard - * Copyright (C) 2009-2017 Paul Davis - * Copyright (C) 2013-2015 Nick Mainsbridge - * Copyright (C) 2014-2019 Robin Gareus - * Copyright (C) 2016-2018 Len Ovens + * Copyright (C) 2022 Robin Gareus * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,1853 +16,25 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include -#include -#include -#include -#include -#include - -#include "pbd/unknown_type.h" -#include "pbd/unwind.h" - -#include "ardour/debug.h" -#include "ardour/audio_track.h" -#include "ardour/midi_track.h" -#include "ardour/route.h" -#include "ardour/selection.h" -#include "ardour/session.h" -#include "ardour/solo_isolate_control.h" -#include "ardour/utils.h" -#include "ardour/vca.h" -#include "ardour/vca_manager.h" - -#include "gtkmm2ext/cell_renderer_pixbuf_multi.h" -#include "gtkmm2ext/cell_renderer_pixbuf_toggle.h" -#include "gtkmm2ext/treeutils.h" - -#include "widgets/tooltips.h" - -#include "actions.h" -#include "ardour_ui.h" -#include "audio_time_axis.h" -#include "editor.h" -#include "editor_group_tabs.h" #include "editor_routes.h" -#include "gui_thread.h" -#include "keyboard.h" -#include "midi_time_axis.h" -#include "mixer_strip.h" -#include "route_sorter.h" -#include "vca_time_axis.h" -#include "utils.h" #include "pbd/i18n.h" -using namespace std; -using namespace ARDOUR; -using namespace ArdourWidgets; -using namespace ARDOUR_UI_UTILS; -using namespace PBD; -using namespace Gtk; -using namespace Gtkmm2ext; -using namespace Glib; -using Gtkmm2ext::Keyboard; - -struct ColumnInfo { - int index; - const char* label; - const char* tooltip; -}; - -EditorRoutes::EditorRoutes (Editor* e) - : EditorComponent (e) - , _ignore_reorder (false) - , _ignore_selection_change (false) - , column_does_not_select (false) - , _no_redisplay (false) - , _adding_routes (false) - , _route_deletion_in_progress (false) - , _redisplay_on_resume (false) - , _idle_update_queued (false) - , _redisplay_active (0) - , _menu (0) - , old_focus (0) - , name_editable (0) +EditorRoutes::EditorRoutes () { - static const int column_width = 22; - - _scroller.add (_display); - _scroller.set_policy (POLICY_NEVER, POLICY_AUTOMATIC); - - _model = ListStore::create (_columns); - _display.set_model (_model); - - _display.get_selection()->set_select_function (sigc::mem_fun (*this, &EditorRoutes::select_function)); - - // Record enable toggle - CellRendererPixbufMulti* rec_col_renderer = manage (new CellRendererPixbufMulti()); - - rec_col_renderer->set_pixbuf (0, ::get_icon("record-normal-disabled")); - rec_col_renderer->set_pixbuf (1, ::get_icon("record-normal-in-progress")); - rec_col_renderer->set_pixbuf (2, ::get_icon("record-normal-enabled")); - rec_col_renderer->set_pixbuf (3, ::get_icon("record-step")); - rec_col_renderer->signal_changed().connect (sigc::mem_fun (*this, &EditorRoutes::on_tv_rec_enable_changed)); - - rec_state_column = manage (new TreeViewColumn("R", *rec_col_renderer)); - - rec_state_column->add_attribute(rec_col_renderer->property_state(), _columns.rec_state); - rec_state_column->add_attribute(rec_col_renderer->property_visible(), _columns.is_track); - - rec_state_column->set_sizing(TREE_VIEW_COLUMN_FIXED); - rec_state_column->set_alignment(ALIGN_CENTER); - rec_state_column->set_expand(false); - rec_state_column->set_fixed_width(column_width); - - - // Record safe toggle - CellRendererPixbufMulti* rec_safe_renderer = manage (new CellRendererPixbufMulti ()); - - rec_safe_renderer->set_pixbuf (0, ::get_icon("rec-safe-disabled")); - rec_safe_renderer->set_pixbuf (1, ::get_icon("rec-safe-enabled")); - rec_safe_renderer->signal_changed().connect (sigc::mem_fun (*this, &EditorRoutes::on_tv_rec_safe_toggled)); - - rec_safe_column = manage (new TreeViewColumn(_("RS"), *rec_safe_renderer)); - rec_safe_column->add_attribute(rec_safe_renderer->property_state(), _columns.rec_safe); - rec_safe_column->add_attribute(rec_safe_renderer->property_visible(), _columns.is_track); - rec_safe_column->set_sizing(TREE_VIEW_COLUMN_FIXED); - rec_safe_column->set_alignment(ALIGN_CENTER); - rec_safe_column->set_expand(false); - rec_safe_column->set_fixed_width(column_width); - - - // MIDI Input Active - - CellRendererPixbufMulti* input_active_col_renderer = manage (new CellRendererPixbufMulti()); - input_active_col_renderer->set_pixbuf (0, ::get_icon("midi-input-inactive")); - input_active_col_renderer->set_pixbuf (1, ::get_icon("midi-input-active")); - input_active_col_renderer->signal_changed().connect (sigc::mem_fun (*this, &EditorRoutes::on_input_active_changed)); - - input_active_column = manage (new TreeViewColumn ("I", *input_active_col_renderer)); - - input_active_column->add_attribute(input_active_col_renderer->property_state(), _columns.is_input_active); - input_active_column->add_attribute (input_active_col_renderer->property_visible(), _columns.is_midi); - - input_active_column->set_sizing(TREE_VIEW_COLUMN_FIXED); - input_active_column->set_alignment(ALIGN_CENTER); - input_active_column->set_expand(false); - input_active_column->set_fixed_width(column_width); - - // Mute enable toggle - CellRendererPixbufMulti* mute_col_renderer = manage (new CellRendererPixbufMulti()); - - mute_col_renderer->set_pixbuf (Gtkmm2ext::Off, ::get_icon("mute-disabled")); - mute_col_renderer->set_pixbuf (Gtkmm2ext::ImplicitActive, ::get_icon("muted-by-others")); - mute_col_renderer->set_pixbuf (Gtkmm2ext::ExplicitActive, ::get_icon("mute-enabled")); - mute_col_renderer->signal_changed().connect (sigc::mem_fun (*this, &EditorRoutes::on_tv_mute_enable_toggled)); - - mute_state_column = manage (new TreeViewColumn("M", *mute_col_renderer)); - - mute_state_column->add_attribute(mute_col_renderer->property_state(), _columns.mute_state); - mute_state_column->set_sizing(TREE_VIEW_COLUMN_FIXED); - mute_state_column->set_alignment(ALIGN_CENTER); - mute_state_column->set_expand(false); - mute_state_column->set_fixed_width(15); - - // Solo enable toggle - CellRendererPixbufMulti* solo_col_renderer = manage (new CellRendererPixbufMulti()); - - solo_col_renderer->set_pixbuf (Gtkmm2ext::Off, ::get_icon("solo-disabled")); - solo_col_renderer->set_pixbuf (Gtkmm2ext::ExplicitActive, ::get_icon("solo-enabled")); - solo_col_renderer->set_pixbuf (Gtkmm2ext::ImplicitActive, ::get_icon("soloed-by-others")); - solo_col_renderer->signal_changed().connect (sigc::mem_fun (*this, &EditorRoutes::on_tv_solo_enable_toggled)); - - solo_state_column = manage (new TreeViewColumn("S", *solo_col_renderer)); - - solo_state_column->add_attribute(solo_col_renderer->property_state(), _columns.solo_state); - solo_state_column->add_attribute(solo_col_renderer->property_visible(), _columns.solo_visible); - solo_state_column->set_sizing(TREE_VIEW_COLUMN_FIXED); - solo_state_column->set_alignment(ALIGN_CENTER); - solo_state_column->set_expand(false); - solo_state_column->set_fixed_width(column_width); - - // Solo isolate toggle - CellRendererPixbufMulti* solo_iso_renderer = manage (new CellRendererPixbufMulti()); - - solo_iso_renderer->set_pixbuf (0, ::get_icon("solo-isolate-disabled")); - solo_iso_renderer->set_pixbuf (1, ::get_icon("solo-isolate-enabled")); - solo_iso_renderer->signal_changed().connect (sigc::mem_fun (*this, &EditorRoutes::on_tv_solo_isolate_toggled)); - - solo_isolate_state_column = manage (new TreeViewColumn("SI", *solo_iso_renderer)); - - solo_isolate_state_column->add_attribute(solo_iso_renderer->property_state(), _columns.solo_isolate_state); - solo_isolate_state_column->add_attribute(solo_iso_renderer->property_visible(), _columns.solo_lock_iso_visible); - solo_isolate_state_column->set_sizing(TREE_VIEW_COLUMN_FIXED); - solo_isolate_state_column->set_alignment(ALIGN_CENTER); - solo_isolate_state_column->set_expand(false); - solo_isolate_state_column->set_fixed_width(column_width); - - // Solo safe toggle - CellRendererPixbufMulti* solo_safe_renderer = manage (new CellRendererPixbufMulti ()); - - solo_safe_renderer->set_pixbuf (0, ::get_icon("solo-safe-disabled")); - solo_safe_renderer->set_pixbuf (1, ::get_icon("solo-safe-enabled")); - solo_safe_renderer->signal_changed().connect (sigc::mem_fun (*this, &EditorRoutes::on_tv_solo_safe_toggled)); - - solo_safe_state_column = manage (new TreeViewColumn(_("SS"), *solo_safe_renderer)); - solo_safe_state_column->add_attribute(solo_safe_renderer->property_state(), _columns.solo_safe_state); - solo_safe_state_column->add_attribute(solo_safe_renderer->property_visible(), _columns.solo_lock_iso_visible); - solo_safe_state_column->set_sizing(TREE_VIEW_COLUMN_FIXED); - solo_safe_state_column->set_alignment(ALIGN_CENTER); - solo_safe_state_column->set_expand(false); - solo_safe_state_column->set_fixed_width(column_width); - - _name_column = _display.append_column ("", _columns.text) - 1; - _visible_column = _display.append_column ("", _columns.visible) - 1; - _trigger_column = _display.append_column ("", _columns.trigger) - 1; - _active_column = _display.append_column ("", _columns.active) - 1; - - name_column = _display.get_column (_name_column); - visible_column = _display.get_column (_visible_column); - trigger_column = _display.get_column (_trigger_column); - active_column = _display.get_column (_active_column); - - _display.append_column (*input_active_column); - _display.append_column (*rec_state_column); - _display.append_column (*rec_safe_column); - _display.append_column (*mute_state_column); - _display.append_column (*solo_state_column); - _display.append_column (*solo_isolate_state_column); - _display.append_column (*solo_safe_state_column); - - - TreeViewColumn* col; - Gtk::Label* l; - - ColumnInfo ci[] = { - { 0, _("Name"), _("Track/Bus Name") }, - { 1, S_("Visible|V"), _("Track/Bus visible ?") }, - { 2, S_("Trigger|T"), _("Visible on TriggerPage ?") }, - { 3, S_("Active|A"), _("Track/Bus active ?") }, - { 4, S_("MidiInput|I"), _("MIDI input enabled") }, - { 5, S_("Rec|R"), _("Record enabled") }, - { 6, S_("Rec|RS"), _("Record Safe") }, - { 7, S_("Mute|M"), _("Muted") }, - { 8, S_("Solo|S"), _("Soloed") }, - { 9, S_("SoloIso|SI"), _("Solo Isolated") }, - {10, S_("SoloLock|SS"), _("Solo Safe (Locked)") }, - { -1, 0, 0 } - }; - - for (int i = 0; ci[i].index >= 0; ++i) { - col = _display.get_column (ci[i].index); - l = manage (new Label (ci[i].label)); - set_tooltip (*l, ci[i].tooltip); - col->set_widget (*l); - l->show (); - } - - _display.set_headers_visible (true); - _display.get_selection()->set_mode (SELECTION_MULTIPLE); - _display.get_selection()->signal_changed().connect (sigc::mem_fun (*this, &EditorRoutes::selection_changed)); - _display.set_reorderable (true); - _display.set_name (X_("EditGroupList")); - _display.set_rules_hint (true); - _display.set_size_request (100, -1); - - CellRendererText* name_cell = dynamic_cast (_display.get_column_cell_renderer (_name_column)); - - assert (name_cell); - name_cell->signal_editing_started().connect (sigc::mem_fun (*this, &EditorRoutes::name_edit_started)); - - TreeViewColumn* name_column = _display.get_column (_name_column); - - assert (name_column); - - name_column->add_attribute (name_cell->property_editable(), _columns.name_editable); - name_column->set_sizing(TREE_VIEW_COLUMN_FIXED); - name_column->set_expand(true); - name_column->set_min_width(50); - - name_cell->property_editable() = true; - name_cell->signal_edited().connect (sigc::mem_fun (*this, &EditorRoutes::name_edit)); - - // Set the visible column cell renderer to radio toggle - CellRendererToggle* visible_cell = dynamic_cast (_display.get_column_cell_renderer (_visible_column)); - - visible_cell->property_activatable() = true; - visible_cell->property_radio() = false; - visible_cell->signal_toggled().connect (sigc::mem_fun (*this, &EditorRoutes::visible_changed)); - - TreeViewColumn* visible_col = dynamic_cast (_display.get_column (_visible_column)); - visible_col->set_expand(false); - visible_col->set_sizing(TREE_VIEW_COLUMN_FIXED); - visible_col->set_fixed_width(30); - visible_col->set_alignment(ALIGN_CENTER); - - CellRendererToggle* trigger_cell = dynamic_cast (_display.get_column_cell_renderer (_trigger_column)); - - trigger_cell->property_activatable() = true; - trigger_cell->property_radio() = false; - trigger_cell->signal_toggled().connect (sigc::mem_fun (*this, &EditorRoutes::trigger_changed)); - - TreeViewColumn* trigger_col = dynamic_cast (_display.get_column (_trigger_column)); - trigger_col->set_expand(false); - trigger_col->set_sizing(TREE_VIEW_COLUMN_FIXED); - trigger_col->set_fixed_width(30); - trigger_col->set_alignment(ALIGN_CENTER); - trigger_col->add_attribute (trigger_cell->property_visible(), _columns.is_track); - - CellRendererToggle* active_cell = dynamic_cast (_display.get_column_cell_renderer (_active_column)); - - active_cell->property_activatable() = true; - active_cell->property_radio() = false; - active_cell->signal_toggled().connect (sigc::mem_fun (*this, &EditorRoutes::active_changed)); - - TreeViewColumn* active_col = dynamic_cast (_display.get_column (_active_column)); - active_col->set_expand (false); - active_col->set_sizing (TREE_VIEW_COLUMN_FIXED); - active_col->set_fixed_width (30); - active_col->set_alignment (ALIGN_CENTER); - active_col->add_attribute (active_cell->property_visible(), _columns.no_vca); - - - _model->signal_row_deleted().connect (sigc::mem_fun (*this, &EditorRoutes::row_deleted)); - _model->signal_rows_reordered().connect (sigc::mem_fun (*this, &EditorRoutes::reordered)); - - _display.signal_button_press_event().connect (sigc::mem_fun (*this, &EditorRoutes::button_press), false); - _display.signal_button_release_event().connect (sigc::mem_fun (*this, &EditorRoutes::button_release), false); - _scroller.signal_key_press_event().connect (sigc::mem_fun(*this, &EditorRoutes::key_press), false); - - _scroller.signal_focus_in_event().connect (sigc::mem_fun (*this, &EditorRoutes::focus_in), false); - _scroller.signal_focus_out_event().connect (sigc::mem_fun (*this, &EditorRoutes::focus_out)); - - _display.signal_enter_notify_event().connect (sigc::mem_fun (*this, &EditorRoutes::enter_notify), false); - _display.signal_leave_notify_event().connect (sigc::mem_fun (*this, &EditorRoutes::leave_notify), false); - - _display.set_enable_search (false); -} - -EditorRoutes::~EditorRoutes () -{ - delete _menu; -} - -bool -EditorRoutes::focus_in (GdkEventFocus*) -{ - Window* win = dynamic_cast (_scroller.get_toplevel ()); - - if (win) { - old_focus = win->get_focus (); - } else { - old_focus = 0; - } - - name_editable = 0; - - /* try to do nothing on focus in (doesn't work, hence selection_count nonsense) */ - return true; -} - -bool -EditorRoutes::focus_out (GdkEventFocus*) -{ - if (old_focus) { - old_focus->grab_focus (); - old_focus = 0; - } - - return false; -} - -bool -EditorRoutes::enter_notify (GdkEventCrossing*) -{ - if (name_editable) { - return true; - } - - Keyboard::magic_widget_grab_focus (); - return false; -} - -bool -EditorRoutes::leave_notify (GdkEventCrossing*) -{ - if (old_focus) { - old_focus->grab_focus (); - old_focus = 0; - } - - Keyboard::magic_widget_drop_focus (); - return false; + init (); } void -EditorRoutes::set_session (Session* s) +EditorRoutes::init () { - SessionHandlePtr::set_session (s); + setup_col (append_toggle (_columns.visible, _columns.noop_true, sigc::mem_fun (*this, &EditorRoutes::on_tv_visible_changed)), S_("Visible|V"), _("Track/Bus visible ?")); + setup_col (append_toggle (_columns.trigger, _columns.is_track, sigc::mem_fun (*this, &EditorRoutes::on_tv_trigger_changed)), S_("Trigger|T"), _("Visible on TriggerPage ?")); + setup_col (append_toggle (_columns.active, _columns.activatable, sigc::mem_fun (*this, &EditorRoutes::on_tv_active_changed)), S_("Active|A"), _("Track/Bus active ?")); - initial_display (); - - if (_session) { - _session->SoloChanged.connect (*this, MISSING_INVALIDATOR, boost::bind (&EditorRoutes::solo_changed_so_update_mute, this), gui_context()); - _session->RecordStateChanged.connect (*this, MISSING_INVALIDATOR, boost::bind (&EditorRoutes::update_rec_display, this), gui_context()); - - /* TODO: check if these needs to be tied in with DisplaySuspender - * Given that the UI is single-threaded and DisplaySuspender is only used - * in loops in the UI thread all should be fine. - */ - _session->BatchUpdateStart.connect (*this, MISSING_INVALIDATOR, boost::bind (&EditorRoutes::suspend_redisplay, this), gui_context()); - _session->BatchUpdateEnd.connect (*this, MISSING_INVALIDATOR, boost::bind (&EditorRoutes::resume_redisplay, this), gui_context()); - } -} - -void -EditorRoutes::on_input_active_changed (std::string const & path_string) -{ - // Get the model row that has been toggled. - Gtk::TreeModel::Row row = *_model->get_iter (Gtk::TreeModel::Path (path_string)); - - TimeAxisView* tv = row[_columns.tv]; - RouteTimeAxisView *rtv = dynamic_cast (tv); - - if (rtv) { - boost::shared_ptr mt; - mt = rtv->midi_track(); - if (mt) { - mt->set_input_active (!mt->input_active()); - } - } -} - -void -EditorRoutes::on_tv_rec_enable_changed (std::string const & path_string) -{ - // Get the model row that has been toggled. - Gtk::TreeModel::Row row = *_model->get_iter (Gtk::TreeModel::Path (path_string)); - - TimeAxisView* tv = row[_columns.tv]; - StripableTimeAxisView* stv = dynamic_cast (tv); - - if (!stv || !stv->stripable()) { - return; - } - - boost::shared_ptr ac = stv->stripable()->rec_enable_control(); - - if (ac) { - ac->set_value (!ac->get_value(), Controllable::UseGroup); - } -} - -void -EditorRoutes::on_tv_rec_safe_toggled (std::string const & path_string) -{ - Gtk::TreeModel::Row row = *_model->get_iter (Gtk::TreeModel::Path (path_string)); - TimeAxisView* tv = row[_columns.tv]; - StripableTimeAxisView* stv = dynamic_cast (tv); - - if (!stv || !stv->stripable()) { - return; - } - - boost::shared_ptr ac (stv->stripable()->rec_safe_control()); - - if (ac) { - ac->set_value (!ac->get_value(), Controllable::UseGroup); - } -} - -void -EditorRoutes::on_tv_mute_enable_toggled (std::string const & path_string) -{ - // Get the model row that has been toggled. - Gtk::TreeModel::Row row = *_model->get_iter (Gtk::TreeModel::Path (path_string)); - - TimeAxisView *tv = row[_columns.tv]; - StripableTimeAxisView* stv = dynamic_cast (tv); - - if (!stv || !stv->stripable()) { - return; - } - - boost::shared_ptr ac (stv->stripable()->mute_control()); - - if (ac) { - ac->set_value (!ac->get_value(), Controllable::UseGroup); - } -} - -void -EditorRoutes::on_tv_solo_enable_toggled (std::string const & path_string) -{ - // Get the model row that has been toggled. - Gtk::TreeModel::Row row = *_model->get_iter (Gtk::TreeModel::Path (path_string)); - - TimeAxisView *tv = row[_columns.tv]; - StripableTimeAxisView* stv = dynamic_cast (tv); - - if (!stv || !stv->stripable()) { - return; - } - - boost::shared_ptr ac (stv->stripable()->solo_control()); - - if (ac) { - ac->set_value (!ac->get_value(), Controllable::UseGroup); - } -} - -void -EditorRoutes::on_tv_solo_isolate_toggled (std::string const & path_string) -{ - // Get the model row that has been toggled. - Gtk::TreeModel::Row row = *_model->get_iter (Gtk::TreeModel::Path (path_string)); - - TimeAxisView *tv = row[_columns.tv]; - StripableTimeAxisView* stv = dynamic_cast (tv); - - if (!stv || !stv->stripable()) { - return; - } - - boost::shared_ptr ac (stv->stripable()->solo_isolate_control()); - - if (ac) { - ac->set_value (!ac->get_value(), Controllable::UseGroup); - } -} - -void -EditorRoutes::on_tv_solo_safe_toggled (std::string const & path_string) -{ - // Get the model row that has been toggled. - Gtk::TreeModel::Row row = *_model->get_iter (Gtk::TreeModel::Path (path_string)); - - TimeAxisView *tv = row[_columns.tv]; - StripableTimeAxisView* stv = dynamic_cast (tv); - - if (!stv || !stv->stripable()) { - return; - } - - boost::shared_ptr ac (stv->stripable()->solo_safe_control()); - - if (ac) { - ac->set_value (!ac->get_value(), Controllable::UseGroup); - } -} - -void -EditorRoutes::build_menu () -{ - using namespace Menu_Helpers; - using namespace Gtk; - - _menu = new Menu; - - MenuList& items = _menu->items(); - _menu->set_name ("ArdourContextMenu"); - - items.push_back (MenuElem (_("Show All"), sigc::mem_fun (*this, &EditorRoutes::show_all_routes))); - items.push_back (MenuElem (_("Hide All"), sigc::mem_fun (*this, &EditorRoutes::hide_all_routes))); - items.push_back (MenuElem (_("Show All Audio Tracks"), sigc::mem_fun (*this, &EditorRoutes::show_all_audiotracks))); - items.push_back (MenuElem (_("Hide All Audio Tracks"), sigc::mem_fun (*this, &EditorRoutes::hide_all_audiotracks))); - items.push_back (MenuElem (_("Show All Midi Tracks"), sigc::mem_fun (*this, &EditorRoutes::show_all_miditracks))); - items.push_back (MenuElem (_("Hide All Midi Tracks"), sigc::mem_fun (*this, &EditorRoutes::hide_all_miditracks))); - items.push_back (MenuElem (_("Show All Busses"), sigc::mem_fun (*this, &EditorRoutes::show_all_audiobus))); - items.push_back (MenuElem (_("Hide All Busses"), sigc::mem_fun (*this, &EditorRoutes::hide_all_audiobus))); - items.push_back (MenuElem (_("Only Show Tracks with Regions Under Playhead"), sigc::mem_fun (*this, &EditorRoutes::show_tracks_with_regions_at_playhead))); -} - -void -EditorRoutes::redisplay_real () -{ - TreeModel::Children rows = _model->children(); - TreeModel::Children::iterator i; - uint32_t position; - - /* n will be the count of tracks plus children (updated by TimeAxisView::show_at), - * so we will use that to know where to put things. - */ - int n; - - for (n = 0, position = 0, i = rows.begin(); i != rows.end(); ++i) { - TimeAxisView *tv = (*i)[_columns.tv]; - - if (tv == 0) { - // just a "title" row - continue; - } - - bool visible = tv->marked_for_display (); - - /* show or hide the TimeAxisView */ - if (visible) { - position += tv->show_at (position, n, &_editor->edit_controls_vbox); - } else { - tv->hide (); - } - - n++; - } - - /* whenever we go idle, update the track view list to reflect the new order. - * we can't do this here, because we could mess up something that is traversing - * the track order and has caused a redisplay of the list. - */ - //Glib::signal_idle().connect (sigc::mem_fun (*_editor, &Editor::sync_track_view_list_and_routes)); - - _editor->reset_controls_layout_height (position); - _editor->reset_controls_layout_width (); - _editor->_full_canvas_height = position; - - if ((_editor->vertical_adjustment.get_value() + _editor->_visible_canvas_height) > _editor->vertical_adjustment.get_upper()) { - /* - * We're increasing the size of the canvas while the bottom is visible. - * We scroll down to keep in step with the controls layout. - */ - _editor->vertical_adjustment.set_value (_editor->_full_canvas_height - _editor->_visible_canvas_height); - } -} - -void -EditorRoutes::redisplay () -{ - if (!_session || _session->deletion_in_progress()) { - return; - } - - if (_no_redisplay) { - _redisplay_on_resume = true; - return; - } - - ++_redisplay_active; - if (_redisplay_active != 1) { - /* recursive re-display can happen if redisplay shows/hides a TrackView - * which has children and their display status changes as result. - */ - return; - } - - redisplay_real (); - - while (_redisplay_active != 1) { - _redisplay_active = 1; - redisplay_real (); - } - _redisplay_active = 0; -} - -void -EditorRoutes::row_deleted (Gtk::TreeModel::Path const &) -{ - if (!_session || _session->deletion_in_progress()) { - return; - } - /* this happens as the second step of a DnD within the treeview, and - * when a route is actually removed. we don't differentiate between - * the two cases. - * - * note that the sync_presentation_info_from_treeview() step may not - * actually change any presentation info (e.g. the last track may be - * removed, so all other tracks keep the same presentation info), which - * means that no redisplay would happen. so we have to force a - * redisplay. - */ - - DEBUG_TRACE (DEBUG::OrderKeys, "editor routes treeview row deleted\n"); - - DisplaySuspender ds; - sync_presentation_info_from_treeview (); -} - -void -EditorRoutes::reordered (TreeModel::Path const &, TreeModel::iterator const &, int* /*what*/) -{ - /* reordering implies that RID's will change, so - sync_presentation_info_from_treeview() will cause a redisplay. - */ - - DEBUG_TRACE (DEBUG::OrderKeys, "editor routes treeview reordered\n"); - sync_presentation_info_from_treeview (); -} - -void -EditorRoutes::visible_changed (std::string const & path) -{ - if (_session && _session->deletion_in_progress()) { - return; - } - - DisplaySuspender ds; - TreeIter iter; - - if ((iter = _model->get_iter (path))) { - TimeAxisView* tv = (*iter)[_columns.tv]; - if (tv) { - bool visible = (*iter)[_columns.visible]; - - if (tv->set_marked_for_display (!visible)) { - update_visibility (); - } - } - } -} - -void -EditorRoutes::trigger_changed (std::string const & path) -{ - if (_session && _session->deletion_in_progress()) { - return; - } - - Gtk::TreeModel::Row row = *_model->get_iter (path); - assert (row[_columns.is_track]); - boost::shared_ptr stripable = row[_columns.stripable]; - if (stripable) { - bool const tt = row[_columns.trigger]; - stripable->presentation_info ().set_trigger_track (!tt); - } -} - -void -EditorRoutes::active_changed (std::string const & path) -{ - if (_session && _session->deletion_in_progress ()) { - return; - } - - Gtk::TreeModel::Row row = *_model->get_iter (path); - boost::shared_ptr stripable = row[_columns.stripable]; - boost::shared_ptr route = boost::dynamic_pointer_cast (stripable); - if (route) { - bool const active = row[_columns.active]; - route->set_active (!active, this); - } -} - -void -EditorRoutes::time_axis_views_added (list tavs) -{ - PBD::Unwinder at (_adding_routes, true); - bool from_scratch = (_model->children().size() == 0); - Gtk::TreeModel::Children::iterator insert_iter = _model->children().end(); - - for (Gtk::TreeModel::Children::iterator it = _model->children().begin(); it != _model->children().end(); ++it) { - - boost::shared_ptr r = (*it)[_columns.stripable]; - - if (r->presentation_info().order() == (tavs.front()->stripable()->presentation_info().order() + tavs.size())) { - insert_iter = it; - break; - } - } - - { - PBD::Unwinder uw (_ignore_selection_change, true); - _display.set_model (Glib::RefPtr()); - } - - for (list::iterator x = tavs.begin(); x != tavs.end(); ++x) { - - VCATimeAxisView* vtav = dynamic_cast (*x); - RouteTimeAxisView* rtav = dynamic_cast (*x); - - TreeModel::Row row = *(_model->insert (insert_iter)); - - boost::shared_ptr stripable; - boost::shared_ptr midi_trk; - - if (vtav) { - - stripable = vtav->vca(); - - row[_columns.is_track] = false; - row[_columns.is_input_active] = false; - row[_columns.is_midi] = false; - row[_columns.no_vca] = false; - - } else if (rtav) { - - stripable = rtav->route (); - midi_trk= boost::dynamic_pointer_cast (stripable); - - row[_columns.is_track] = (boost::dynamic_pointer_cast (stripable) != 0); - row[_columns.no_vca] = true; - - if (midi_trk) { - row[_columns.is_input_active] = midi_trk->input_active (); - row[_columns.is_midi] = true; - } else { - row[_columns.is_input_active] = false; - row[_columns.is_midi] = false; - } - } - - if (!stripable) { - continue; - } - - row[_columns.text] = stripable->name(); - row[_columns.visible] = (*x)->marked_for_display(); - row[_columns.trigger] = stripable->presentation_info ().trigger_track () && row[_columns.is_track]; - row[_columns.active] = true; - row[_columns.tv] = *x; - row[_columns.stripable] = stripable; - row[_columns.mute_state] = RouteUI::mute_active_state (_session, stripable); - row[_columns.solo_state] = RouteUI::solo_active_state (stripable); - row[_columns.solo_visible] = !stripable->is_master (); - row[_columns.solo_lock_iso_visible] = row[_columns.solo_visible] && row[_columns.no_vca]; - row[_columns.solo_isolate_state] = RouteUI::solo_isolate_active_state (stripable); - row[_columns.solo_safe_state] = RouteUI::solo_safe_active_state (stripable); - row[_columns.name_editable] = true; - - boost::weak_ptr ws (stripable); - - /* for now, we need both of these. PropertyChanged covers on - * pre-defined, "global" things of interest to a - * UI. gui_changed covers arbitrary, un-enumerated, un-typed - * changes that may only be of interest to a particular - * UI (e.g. track-height is not of any relevant to OSC) - */ - - stripable->gui_changed.connect (*this, MISSING_INVALIDATOR, boost::bind (&EditorRoutes::handle_gui_changes, this, _1, _2), gui_context()); - stripable->PropertyChanged.connect (*this, MISSING_INVALIDATOR, boost::bind (&EditorRoutes::route_property_changed, this, _1, ws), gui_context()); - stripable->presentation_info().PropertyChanged.connect (*this, MISSING_INVALIDATOR, boost::bind (&EditorRoutes::route_property_changed, this, _1, ws), gui_context()); - - if (boost::dynamic_pointer_cast (stripable)) { - boost::shared_ptr t = boost::dynamic_pointer_cast (stripable); - t->rec_enable_control()->Changed.connect (*this, MISSING_INVALIDATOR, boost::bind (&EditorRoutes::update_rec_display, this), gui_context()); - t->rec_safe_control()->Changed.connect (*this, MISSING_INVALIDATOR, boost::bind (&EditorRoutes::update_rec_display, this), gui_context()); - } - - if (midi_trk) { - midi_trk->StepEditStatusChange.connect (*this, MISSING_INVALIDATOR, boost::bind (&EditorRoutes::update_rec_display, this), gui_context()); - midi_trk->InputActiveChanged.connect (*this, MISSING_INVALIDATOR, boost::bind (&EditorRoutes::update_input_active_display, this), gui_context()); - } - - boost::shared_ptr ac; - - if ((ac = stripable->mute_control()) != 0) { - ac->Changed.connect (*this, MISSING_INVALIDATOR, boost::bind (&EditorRoutes::update_mute_display, this), gui_context()); - } - if ((ac = stripable->solo_control()) != 0) { - ac->Changed.connect (*this, MISSING_INVALIDATOR, boost::bind (&EditorRoutes::update_solo_display, this), gui_context()); - } - if ((ac = stripable->solo_isolate_control()) != 0) { - ac->Changed.connect (*this, MISSING_INVALIDATOR, boost::bind (&EditorRoutes::update_solo_isolate_display, this), gui_context()); - } - if ((ac = stripable->solo_safe_control()) != 0) { - ac->Changed.connect (*this, MISSING_INVALIDATOR, boost::bind (&EditorRoutes::update_solo_safe_display, this), gui_context()); - } - - if (rtav) { - rtav->route()->active_changed.connect (*this, MISSING_INVALIDATOR, boost::bind (&EditorRoutes::update_active_display, this), gui_context ()); - } - } - - update_rec_display (); - update_mute_display (); - update_solo_display (); - update_solo_isolate_display (); - update_solo_safe_display (); - update_input_active_display (); - update_active_display (); - - { - PBD::Unwinder uw (_ignore_selection_change, true); - _display.set_model (_model); - } - - /* now update route order keys from the treeview/track display order */ - - if (!from_scratch) { - sync_presentation_info_from_treeview (); - } - - redisplay (); -} - -void -EditorRoutes::handle_gui_changes (string const & what, void*) -{ - if (_adding_routes) { - return; - } - - if (what == "track_height") { - /* Optional :make tracks change height while it happens, instead - of on first-idle - */ - redisplay (); - } - - if (what == "visible_tracks") { - redisplay (); - } -} - -void -EditorRoutes::route_removed (TimeAxisView *tv) -{ - ENSURE_GUI_THREAD (*this, &EditorRoutes::route_removed, tv) - - TreeModel::Children rows = _model->children(); - TreeModel::Children::iterator ri; - - PBD::Unwinder uw (_ignore_selection_change, true); - - for (ri = rows.begin(); ri != rows.end(); ++ri) { - if ((*ri)[_columns.tv] == tv) { - PBD::Unwinder uw (_route_deletion_in_progress, true); - _model->erase (ri); - break; - } - } -} - -void -EditorRoutes::route_property_changed (const PropertyChange& what_changed, boost::weak_ptr s) -{ - if (_adding_routes) { - return; - } - - PropertyChange interests; - interests.add (ARDOUR::Properties::name); - interests.add (ARDOUR::Properties::hidden); - interests.add (ARDOUR::Properties::trigger_track); - - if (!what_changed.contains (interests)) { - return; - } - - boost::shared_ptr stripable = s.lock (); - - if (!stripable) { - return; - } - - TreeModel::Children rows = _model->children(); - TreeModel::Children::iterator i; - - for (i = rows.begin(); i != rows.end(); ++i) { - - boost::shared_ptr ss = (*i)[_columns.stripable]; - - if (ss == stripable) { - - if (what_changed.contains (ARDOUR::Properties::name)) { - (*i)[_columns.text] = stripable->name(); - break; - } - - if (what_changed.contains (ARDOUR::Properties::hidden)) { - (*i)[_columns.visible] = !stripable->presentation_info().hidden(); - redisplay (); - } - - if (what_changed.contains (ARDOUR::Properties::trigger_track)) { - (*i)[_columns.trigger] = stripable->presentation_info ().trigger_track () && (*i)[_columns.is_track]; - } - - break; - } - } -} - -void -EditorRoutes::update_active_display () -{ - if (!_idle_update_queued) { - _idle_update_queued = true; - Glib::signal_idle().connect (sigc::mem_fun (*this, &EditorRoutes::idle_update_mute_rec_solo_etc)); - } -} - -void -EditorRoutes::update_visibility () -{ - TreeModel::Children rows = _model->children(); - TreeModel::Children::iterator i; - - DisplaySuspender ds; - - for (i = rows.begin(); i != rows.end(); ++i) { - TimeAxisView *tv = (*i)[_columns.tv]; - (*i)[_columns.visible] = tv->marked_for_display (); - } - - /* force route order keys catch up with visibility changes */ - - sync_presentation_info_from_treeview (); -} - -void -EditorRoutes::hide_track_in_display (TimeAxisView& tv) -{ - TreeModel::Children rows = _model->children(); - TreeModel::Children::iterator i; - - for (i = rows.begin(); i != rows.end(); ++i) { - if ((*i)[_columns.tv] == &tv) { - tv.set_marked_for_display (false); - (*i)[_columns.visible] = false; - redisplay (); - break; - } - } -} - -void -EditorRoutes::show_track_in_display (TimeAxisView& tv) -{ - TreeModel::Children rows = _model->children(); - TreeModel::Children::iterator i; - - - for (i = rows.begin(); i != rows.end(); ++i) { - if ((*i)[_columns.tv] == &tv) { - tv.set_marked_for_display (true); - (*i)[_columns.visible] = true; - redisplay (); - break; - } - } -} - -void -EditorRoutes::sync_presentation_info_from_treeview () -{ - if (_ignore_reorder || !_session || _session->deletion_in_progress()) { - return; - } - - TreeModel::Children rows = _model->children(); - - if (rows.empty()) { - return; - } - - DEBUG_TRACE (DEBUG::OrderKeys, "editor sync presentation info from treeview\n"); - - bool change = false; - PresentationInfo::order_t order = 0; - - PresentationInfo::ChangeSuspender cs; - - for (TreeModel::Children::iterator ri = rows.begin(); ri != rows.end(); ++ri) { - boost::shared_ptr stripable = (*ri)[_columns.stripable]; - bool visible = (*ri)[_columns.visible]; - -#ifndef NDEBUG // these should not exist in the treeview - assert (stripable); - if (stripable->is_monitor() || stripable->is_auditioner()) { - assert (0); - continue; - } -#endif - - stripable->presentation_info().set_hidden (!visible); - - if (order != stripable->presentation_info().order()) { - stripable->set_presentation_order (order); - change = true; - } - ++order; - } - - change |= _session->ensure_stripable_sort_order (); - - if (change) { - _session->set_dirty(); - } -} - -void -EditorRoutes::sync_treeview_from_presentation_info (PropertyChange const & what_changed) -{ - /* Some route order key(s) have been changed, make sure that - we update out tree/list model and GUI to reflect the change. - */ - - if (_ignore_reorder || !_session || _session->deletion_in_progress()) { - return; - } - - DEBUG_TRACE (DEBUG::OrderKeys, "editor sync model from presentation info.\n"); - - PropertyChange hidden_or_order; - hidden_or_order.add (Properties::hidden); - hidden_or_order.add (Properties::order); - - TreeModel::Children rows = _model->children(); - - bool changed = false; - - if (what_changed.contains (hidden_or_order)) { - vector neworder; - uint32_t old_order = 0; - - if (rows.empty()) { - return; - } - - TreeOrderKeys sorted; - for (TreeModel::Children::iterator ri = rows.begin(); ri != rows.end(); ++ri, ++old_order) { - boost::shared_ptr stripable = (*ri)[_columns.stripable]; - /* use global order */ - sorted.push_back (TreeOrderKey (old_order, stripable)); - } - - TreeOrderKeySorter cmp; - - sort (sorted.begin(), sorted.end(), cmp); - neworder.assign (sorted.size(), 0); - - uint32_t n = 0; - - for (TreeOrderKeys::iterator sr = sorted.begin(); sr != sorted.end(); ++sr, ++n) { - - neworder[n] = sr->old_display_order; - - if (sr->old_display_order != n) { - changed = true; - } - } - - if (changed) { - Unwinder uw (_ignore_reorder, true); - /* prevent traverse_cells: assertion 'row_path != NULL' - * in case of DnD re-order: row-removed + row-inserted. - * - * The rows (stripables) are not actually removed from the model, - * but only from the display in the DnDTreeView. - * ->reorder() will fail to find the row_path. - * (re-order drag -> remove row -> sync PI from TV -> notify -> sync TV from PI -> crash) - */ - Unwinder uw2 (_ignore_selection_change, true); - - _display.unset_model(); - _model->reorder (neworder); - _display.set_model (_model); - } - } - - if (changed || what_changed.contains (Properties::selected)) { - /* by the time this is invoked, the GUI Selection model has - * already updated itself. - */ - PBD::Unwinder uw (_ignore_selection_change, true); - - /* set the treeview model selection state */ - for (TreeModel::Children::iterator ri = rows.begin(); ri != rows.end(); ++ri) { - boost::shared_ptr stripable = (*ri)[_columns.stripable]; - if (stripable && stripable->is_selected()) { - _display.get_selection()->select (*ri); - } else { - _display.get_selection()->unselect (*ri); - } - } - } - - redisplay (); -} - -void -EditorRoutes::hide_all_tracks (bool /*with_select*/) -{ - TreeModel::Children rows = _model->children(); - TreeModel::Children::iterator i; - - DisplaySuspender ds; - - for (i = rows.begin(); i != rows.end(); ++i) { - - TreeModel::Row row = (*i); - TimeAxisView *tv = row[_columns.tv]; - - if (tv == 0) { - continue; - } - - row[_columns.visible] = false; - } -} - -void -EditorRoutes::set_all_tracks_visibility (bool yn) -{ - TreeModel::Children rows = _model->children(); - TreeModel::Children::iterator i; - - DisplaySuspender ds; - - for (i = rows.begin(); i != rows.end(); ++i) { - - TreeModel::Row row = (*i); - TimeAxisView* tv = row[_columns.tv]; - - if (tv == 0) { - continue; - } - - tv->set_marked_for_display (yn); - (*i)[_columns.visible] = yn; - } - - /* force route order keys catch up with visibility changes - */ - - sync_presentation_info_from_treeview (); -} - -void -EditorRoutes::set_all_audio_midi_visibility (int tracks, bool yn) -{ - TreeModel::Children rows = _model->children(); - TreeModel::Children::iterator i; - - DisplaySuspender ds; - - for (i = rows.begin(); i != rows.end(); ++i) { - - TreeModel::Row row = (*i); - TimeAxisView* tv = row[_columns.tv]; - - AudioTimeAxisView* atv; - MidiTimeAxisView* mtv; - - if (tv == 0) { - continue; - } - - if ((atv = dynamic_cast(tv)) != 0) { - switch (tracks) { - case 0: - atv->set_marked_for_display (yn); - (*i)[_columns.visible] = yn; - break; - - case 1: - if (atv->is_audio_track()) { - atv->set_marked_for_display (yn); - (*i)[_columns.visible] = yn; - } - break; - - case 2: - if (!atv->is_audio_track()) { - atv->set_marked_for_display (yn); - (*i)[_columns.visible] = yn; - } - break; - } - } - else if ((mtv = dynamic_cast(tv)) != 0) { - switch (tracks) { - case 0: - mtv->set_marked_for_display (yn); - (*i)[_columns.visible] = yn; - break; - - case 3: - if (mtv->is_midi_track()) { - mtv->set_marked_for_display (yn); - (*i)[_columns.visible] = yn; - } - break; - } - } - } - - /* force route order keys catch up with visibility changes - */ - - sync_presentation_info_from_treeview (); -} - -void -EditorRoutes::hide_all_routes () -{ - set_all_tracks_visibility (false); -} - -void -EditorRoutes::show_all_routes () -{ - set_all_tracks_visibility (true); -} - -void -EditorRoutes::show_all_audiotracks() -{ - set_all_audio_midi_visibility (1, true); -} -void -EditorRoutes::hide_all_audiotracks () -{ - set_all_audio_midi_visibility (1, false); -} - -void -EditorRoutes::show_all_audiobus () -{ - set_all_audio_midi_visibility (2, true); -} -void -EditorRoutes::hide_all_audiobus () -{ - set_all_audio_midi_visibility (2, false); -} - -void -EditorRoutes::show_all_miditracks() -{ - set_all_audio_midi_visibility (3, true); -} -void -EditorRoutes::hide_all_miditracks () -{ - set_all_audio_midi_visibility (3, false); -} - -bool -EditorRoutes::key_press (GdkEventKey* ev) -{ - TreeViewColumn *col; - boost::shared_ptr rl (new RouteList); - TreePath path; - - switch (ev->keyval) { - case GDK_Tab: - case GDK_ISO_Left_Tab: - - /* If we appear to be editing something, leave that cleanly and appropriately. */ - if (name_editable) { - name_editable->editing_done (); - name_editable = 0; - } - - col = _display.get_column (_name_column); // select&focus on name column - - if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) { - treeview_select_previous (_display, _model, col); - } else { - treeview_select_next (_display, _model, col); - } - - return true; - break; - - case 'm': - if (get_relevant_routes (rl)) { - _session->set_controls (route_list_to_control_list (rl, &Stripable::mute_control), rl->front()->muted() ? 0.0 : 1.0, Controllable::NoGroup); - } - return true; - break; - - case 's': - if (get_relevant_routes (rl)) { - _session->set_controls (route_list_to_control_list (rl, &Stripable::solo_control), rl->front()->self_soloed() ? 0.0 : 1.0, Controllable::NoGroup); - } - return true; - break; - - case 'r': - if (get_relevant_routes (rl)) { - for (RouteList::const_iterator r = rl->begin(); r != rl->end(); ++r) { - boost::shared_ptr t = boost::dynamic_pointer_cast (*r); - if (t) { - _session->set_controls (route_list_to_control_list (rl, &Stripable::rec_enable_control), !t->rec_enable_control()->get_value(), Controllable::NoGroup); - break; - } - } - } - break; - - default: - break; - } - - return false; -} - -bool -EditorRoutes::get_relevant_routes (boost::shared_ptr rl) -{ - TimeAxisView* tv; - RouteTimeAxisView* rtv; - RefPtr selection = _display.get_selection(); - TreePath path; - TreeIter iter; - - if (selection->count_selected_rows() != 0) { - - /* use selection */ - - RefPtr tm = RefPtr::cast_dynamic (_model); - iter = selection->get_selected (tm); - - } else { - /* use mouse pointer */ - - int x, y; - int bx, by; - - _display.get_pointer (x, y); - _display.convert_widget_to_bin_window_coords (x, y, bx, by); - - if (_display.get_path_at_pos (bx, by, path)) { - iter = _model->get_iter (path); - } - } - - if (iter) { - tv = (*iter)[_columns.tv]; - if (tv) { - rtv = dynamic_cast(tv); - if (rtv) { - rl->push_back (rtv->route()); - } - } - } - - return !rl->empty(); -} - -bool -EditorRoutes::select_function(const Glib::RefPtr&, const Gtk::TreeModel::Path&, bool) -{ - return !column_does_not_select; -} - -bool -EditorRoutes::button_release (GdkEventButton*) -{ - column_does_not_select = false; - return false; -} - -bool -EditorRoutes::button_press (GdkEventButton* ev) -{ - if (Keyboard::is_context_menu_event (ev)) { - if (_menu == 0) { - build_menu (); - } - _menu->popup (ev->button, ev->time); - return true; - } - - TreeModel::Path path; - TreeViewColumn *tvc; - int cell_x; - int cell_y; - - if (!_display.get_path_at_pos ((int) ev->x, (int) ev->y, path, tvc, cell_x, cell_y)) { - /* cancel selection */ - _display.get_selection()->unselect_all (); - /* end any editing by grabbing focus */ - _display.grab_focus (); - return true; - } - - if ((tvc == rec_state_column) || - (tvc == rec_safe_column) || - (tvc == input_active_column) || - (tvc == mute_state_column) || - (tvc == solo_state_column) || - (tvc == solo_safe_state_column) || - (tvc == solo_isolate_state_column) || - (tvc == visible_column) || - (tvc == trigger_column) || - (tvc == active_column)) { - column_does_not_select = true; - } - - //Scroll editor canvas to selected track - if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) { - - Gtk::TreeModel::Row row = *_model->get_iter (path); - TimeAxisView *tv = row[_columns.tv]; - - if (tv) { - _editor->ensure_time_axis_view_is_visible (*tv, true); - } - } - - return false; -} - -void -EditorRoutes::selection_changed () -{ - if (_ignore_selection_change || column_does_not_select) { - return; - } - - _editor->begin_reversible_selection_op (X_("Select Track from Route List")); - - if (_display.get_selection()->count_selected_rows() > 0) { - - TreeIter iter; - TreeView::Selection::ListHandle_Path rows = _display.get_selection()->get_selected_rows (); - TrackViewList selected; - - for (TreeView::Selection::ListHandle_Path::iterator i = rows.begin(); i != rows.end(); ++i) { - - if ((iter = _model->get_iter (*i))) { - - TimeAxisView* tv = (*iter)[_columns.tv]; - selected.push_back (tv); - } - - } - - _editor->get_selection().set (selected); - _editor->ensure_time_axis_view_is_visible (*(selected.front()), true); - - } else { - _editor->get_selection().clear_tracks (); - } - - _editor->commit_reversible_selection_op (); -} - -void -EditorRoutes::initial_display () -{ - - if (!_session) { - clear (); - return; - } - - DisplaySuspender ds; - _model->clear (); - - StripableList s; - - _session->get_stripables (s); - _editor->add_stripables (s); - - sync_treeview_from_presentation_info (Properties::order); -} - -struct ViewStripable { - TimeAxisView* tav; - boost::shared_ptr stripable; - - ViewStripable (TimeAxisView* t, boost::shared_ptr s) - : tav (t), stripable (s) {} -}; - -void -EditorRoutes::move_selected_tracks (bool up) -{ - TimeAxisView* scroll_to = 0; - StripableList sl; - _session->get_stripables (sl); - - if (sl.size() < 2) { - /* nope */ - return; - } - - sl.sort (Stripable::Sorter()); - - std::list view_stripables; - - /* build a list that includes time axis view information */ - - for (StripableList::const_iterator sli = sl.begin(); sli != sl.end(); ++sli) { - TimeAxisView* tv = _editor->time_axis_view_from_stripable (*sli); - view_stripables.push_back (ViewStripable (tv, *sli)); - } - - /* for each selected stripable, move it above or below the adjacent - * stripable that has a time-axis view representation here. If there's - * no such representation, then - */ - - list::iterator unselected_neighbour; - list::iterator vsi; - - { - PresentationInfo::ChangeSuspender cs; - - if (up) { - unselected_neighbour = view_stripables.end (); - vsi = view_stripables.begin(); - - while (vsi != view_stripables.end()) { - - if (vsi->stripable->is_selected()) { - - if (unselected_neighbour != view_stripables.end()) { - - PresentationInfo::order_t unselected_neighbour_order = unselected_neighbour->stripable->presentation_info().order(); - PresentationInfo::order_t my_order = vsi->stripable->presentation_info().order(); - - unselected_neighbour->stripable->set_presentation_order (my_order); - vsi->stripable->set_presentation_order (unselected_neighbour_order); - - if (!scroll_to) { - scroll_to = vsi->tav; - } - } - - } else { - - if (vsi->tav) { - unselected_neighbour = vsi; - } - - } - - ++vsi; - } - - } else { - - unselected_neighbour = view_stripables.end(); - vsi = unselected_neighbour; - - do { - - --vsi; - - if (vsi->stripable->is_selected()) { - - if (unselected_neighbour != view_stripables.end()) { - - PresentationInfo::order_t unselected_neighbour_order = unselected_neighbour->stripable->presentation_info().order(); - PresentationInfo::order_t my_order = vsi->stripable->presentation_info().order(); - - unselected_neighbour->stripable->set_presentation_order (my_order); - vsi->stripable->set_presentation_order (unselected_neighbour_order); - - if (!scroll_to) { - scroll_to = vsi->tav; - } - } - - } else { - - if (vsi->tav) { - unselected_neighbour = vsi; - } - - } - - } while (vsi != view_stripables.begin()); - } - } - - if (scroll_to) { - _editor->ensure_time_axis_view_is_visible (*scroll_to, false); - } -} - -void -EditorRoutes::update_input_active_display () -{ - TreeModel::Children rows = _model->children(); - TreeModel::Children::iterator i; - - for (i = rows.begin(); i != rows.end(); ++i) { - boost::shared_ptr stripable = (*i)[_columns.stripable]; - - if (boost::dynamic_pointer_cast (stripable)) { - boost::shared_ptr mt = boost::dynamic_pointer_cast (stripable); - - if (mt) { - (*i)[_columns.is_input_active] = mt->input_active(); - } - } - } -} - -void -EditorRoutes::update_rec_display () -{ - if (!_idle_update_queued) { - _idle_update_queued = true; - Glib::signal_idle().connect (sigc::mem_fun (*this, &EditorRoutes::idle_update_mute_rec_solo_etc)); - } -} - -bool -EditorRoutes::idle_update_mute_rec_solo_etc() -{ - _idle_update_queued = false; - TreeModel::Children rows = _model->children(); - TreeModel::Children::iterator i; - - for (i = rows.begin(); i != rows.end(); ++i) { - boost::shared_ptr stripable = (*i)[_columns.stripable]; - boost::shared_ptr route = boost::dynamic_pointer_cast (stripable); - (*i)[_columns.mute_state] = RouteUI::mute_active_state (_session, stripable); - (*i)[_columns.solo_state] = RouteUI::solo_active_state (stripable); - (*i)[_columns.solo_isolate_state] = RouteUI::solo_isolate_active_state (stripable) ? 1 : 0; - (*i)[_columns.solo_safe_state] = RouteUI::solo_safe_active_state (stripable) ? 1 : 0; - if (route) { - (*i)[_columns.active] = route->active (); - } else { - (*i)[_columns.active] = true; - } - - boost::shared_ptr trk (boost::dynamic_pointer_cast(route)); - - if (trk) { - boost::shared_ptr mt = boost::dynamic_pointer_cast (route); - - if (trk->rec_enable_control()->get_value()) { - if (_session->record_status() == Session::Recording) { - (*i)[_columns.rec_state] = 1; - } else { - (*i)[_columns.rec_state] = 2; - } - } else if (mt && mt->step_editing()) { - (*i)[_columns.rec_state] = 3; - } else { - (*i)[_columns.rec_state] = 0; - } - - (*i)[_columns.rec_safe] = trk->rec_safe_control()->get_value(); - (*i)[_columns.name_editable] = !trk->rec_enable_control()->get_value(); - } - } - - return false; // do not call again (until needed) -} - - -void -EditorRoutes::update_mute_display () -{ - if (!_idle_update_queued) { - _idle_update_queued = true; - Glib::signal_idle().connect (sigc::mem_fun (*this, &EditorRoutes::idle_update_mute_rec_solo_etc)); - } -} - -void -EditorRoutes::update_solo_display () -{ - if (!_idle_update_queued) { - _idle_update_queued = true; - Glib::signal_idle().connect (sigc::mem_fun (*this, &EditorRoutes::idle_update_mute_rec_solo_etc)); - } -} - -void -EditorRoutes::update_solo_isolate_display () -{ - if (!_idle_update_queued) { - _idle_update_queued = true; - Glib::signal_idle().connect (sigc::mem_fun (*this, &EditorRoutes::idle_update_mute_rec_solo_etc)); - } -} - -void -EditorRoutes::update_solo_safe_display () -{ - if (!_idle_update_queued) { - _idle_update_queued = true; - Glib::signal_idle().connect (sigc::mem_fun (*this, &EditorRoutes::idle_update_mute_rec_solo_etc)); - } -} - -list -EditorRoutes::views () const -{ - list v; - for (TreeModel::Children::iterator i = _model->children().begin(); i != _model->children().end(); ++i) { - v.push_back ((*i)[_columns.tv]); - } - - return v; -} - -void -EditorRoutes::clear () -{ - PBD::Unwinder uw (_ignore_selection_change, true); - _display.set_model (Glib::RefPtr (0)); - _model->clear (); - _display.set_model (_model); -} - -void -EditorRoutes::name_edit_started (CellEditable* ce, const Glib::ustring&) -{ - name_editable = ce; - - /* give it a special name */ - - Gtk::Entry *e = dynamic_cast (ce); - - if (e) { - e->set_name (X_("RouteNameEditorEntry")); - } -} - -void -EditorRoutes::name_edit (std::string const & path, std::string const & new_text) -{ - name_editable = 0; - - TreeIter iter = _model->get_iter (path); - - if (!iter) { - return; - } - - boost::shared_ptr stripable = (*iter)[_columns.stripable]; - - if (stripable && stripable->name() != new_text) { - stripable->set_name (new_text); - } -} - -void -EditorRoutes::solo_changed_so_update_mute () -{ - update_mute_display (); -} - -void -EditorRoutes::show_tracks_with_regions_at_playhead () -{ - boost::shared_ptr const r = _session->get_routes_with_regions_at (timepos_t (_session->transport_sample ())); - - set show; - for (RouteList::const_iterator i = r->begin(); i != r->end(); ++i) { - TimeAxisView* tav = _editor->time_axis_view_from_stripable (*i); - if (tav) { - show.insert (tav); - } - } - - DisplaySuspender ds; - - TreeModel::Children rows = _model->children (); - for (TreeModel::Children::iterator i = rows.begin(); i != rows.end(); ++i) { - TimeAxisView* tv = (*i)[_columns.tv]; - bool to_show = (show.find (tv) != show.end()); - - tv->set_marked_for_display (to_show); - (*i)[_columns.visible] = to_show; - } - - /* force route order keys catch up with visibility changes - */ - - sync_presentation_info_from_treeview (); + append_col_input_active (); + append_col_rec_enable (); + append_col_rec_safe (); + append_col_mute (); + append_col_solo (); } diff --git a/gtk2_ardour/editor_routes.h b/gtk2_ardour/editor_routes.h index adc2001784..49d4968b0d 100644 --- a/gtk2_ardour/editor_routes.h +++ b/gtk2_ardour/editor_routes.h @@ -1,8 +1,5 @@ /* - * Copyright (C) 2009-2011 Carl Hetherington - * Copyright (C) 2009-2011 David Robillard - * Copyright (C) 2009-2017 Paul Davis - * Copyright (C) 2014-2019 Robin Gareus + * Copyright (C) 2022 Robin Gareus * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,204 +16,18 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef __ardour_gtk_editor_route_h__ -#define __ardour_gtk_editor_route_h__ +#ifndef _ardour_gtk_editor_routes_h_ +#define _ardour_gtk_editor_routes_h_ -#include -#include -#include -#include -#include +#include "route_list_base.h" -#include "pbd/properties.h" -#include "pbd/signals.h" - -#include "ardour/route.h" -#include "ardour/session_handle.h" -#include "ardour/types.h" - -#include "gtkmm2ext/widget_state.h" - -#include "editor_component.h" - -class TimeAxisView; - -class EditorRoutes : public EditorComponent, public PBD::ScopedConnectionList, public ARDOUR::SessionHandlePtr +class EditorRoutes : public RouteListBase { public: - EditorRoutes (Editor *); - ~EditorRoutes (); - - void set_session (ARDOUR::Session *); - - Gtk::Widget& widget () { - return _scroller; - } - - void move_selected_tracks (bool); - void show_track_in_display (TimeAxisView &); - - - void suspend_redisplay () { - if (!_no_redisplay) { - _no_redisplay = true; - _redisplay_on_resume = false; - } - } - - void resume_redisplay () { - _no_redisplay = false; - if (_redisplay_on_resume) { - redisplay (); - } - } - - void redisplay (); - void update_visibility (); - void time_axis_views_added (std::list); - void route_removed (TimeAxisView *); - void hide_track_in_display (TimeAxisView &); - std::list views () const; - void hide_all_tracks (bool); - void clear (); - void sync_presentation_info_from_treeview (); - void sync_treeview_from_presentation_info (PBD::PropertyChange const &); + EditorRoutes (); private: - void initial_display (); - void redisplay_real (); - void on_input_active_changed (std::string const &); - void on_tv_rec_enable_changed (std::string const &); - void on_tv_rec_safe_toggled (std::string const &); - void on_tv_mute_enable_toggled (std::string const &); - void on_tv_solo_enable_toggled (std::string const &); - void on_tv_solo_isolate_toggled (std::string const &); - void on_tv_solo_safe_toggled (std::string const &); - void build_menu (); - void row_deleted (Gtk::TreeModel::Path const &); - void visible_changed (std::string const &); - void trigger_changed (std::string const &); - void active_changed (std::string const &); - void reordered (Gtk::TreeModel::Path const &, Gtk::TreeModel::iterator const &, int *); - bool button_press (GdkEventButton *); - bool button_release (GdkEventButton *); - void route_property_changed (const PBD::PropertyChange&, boost::weak_ptr); - void handle_gui_changes (std::string const &, void *); - bool idle_update_mute_rec_solo_etc (); - void update_rec_display (); - void update_mute_display (); - void update_solo_display (); - void update_solo_isolate_display (); - void update_solo_safe_display (); - void update_input_active_display (); - void update_active_display (); - void set_all_tracks_visibility (bool); - void set_all_audio_midi_visibility (int, bool); - void show_all_routes (); - void hide_all_routes (); - void show_all_audiotracks (); - void hide_all_audiotracks (); - void show_all_audiobus (); - void hide_all_audiobus (); - void show_all_miditracks (); - void hide_all_miditracks (); - void show_tracks_with_regions_at_playhead (); - void selection_changed (); - - void name_edit (std::string const &, std::string const &); - void solo_changed_so_update_mute (); - - struct ModelColumns : public Gtk::TreeModel::ColumnRecord { - ModelColumns() { - add (text); - add (visible); - add (trigger); - add (rec_state); - add (rec_safe); - add (mute_state); - add (solo_state); - add (solo_visible); - add (solo_lock_iso_visible); - add (solo_isolate_state); - add (solo_safe_state); - add (is_track); - add (tv); - add (stripable); - add (name_editable); - add (is_input_active); - add (is_midi); - add (no_vca); - add (active); - } - - Gtk::TreeModelColumn text; - Gtk::TreeModelColumn visible; - Gtk::TreeModelColumn trigger; - Gtk::TreeModelColumn rec_state; - Gtk::TreeModelColumn rec_safe; - Gtk::TreeModelColumn mute_state; - Gtk::TreeModelColumn solo_state; - /** true if the solo buttons are visible for this route, otherwise false */ - Gtk::TreeModelColumn solo_visible; - Gtk::TreeModelColumn solo_lock_iso_visible; - Gtk::TreeModelColumn solo_isolate_state; - Gtk::TreeModelColumn solo_safe_state; - Gtk::TreeModelColumn is_track; - Gtk::TreeModelColumn tv; - Gtk::TreeModelColumn > stripable; - Gtk::TreeModelColumn name_editable; - Gtk::TreeModelColumn is_input_active; - Gtk::TreeModelColumn is_midi; - Gtk::TreeModelColumn no_vca; // activatable - Gtk::TreeModelColumn active; - }; - - Gtk::TreeViewColumn* rec_state_column; - Gtk::TreeViewColumn* rec_safe_column; - Gtk::TreeViewColumn* input_active_column; - Gtk::TreeViewColumn* mute_state_column; - Gtk::TreeViewColumn* solo_state_column; - Gtk::TreeViewColumn* solo_safe_state_column; - Gtk::TreeViewColumn* solo_isolate_state_column; - Gtk::TreeViewColumn* name_column; - Gtk::TreeViewColumn* visible_column; - Gtk::TreeViewColumn* trigger_column; - Gtk::TreeViewColumn* active_column; - - Gtk::ScrolledWindow _scroller; - Gtk::TreeView _display; - Glib::RefPtr _model; - ModelColumns _columns; - int _name_column; - int _visible_column; - int _trigger_column; - int _active_column; - - bool _ignore_reorder; - bool _ignore_selection_change; - bool column_does_not_select; - bool _no_redisplay; - bool _adding_routes; - bool _route_deletion_in_progress; - bool _redisplay_on_resume; - bool _idle_update_queued; - - int _redisplay_active; - - Gtk::Menu* _menu; - Gtk::Widget* old_focus; - Gtk::CellEditable* name_editable; - - bool select_function (const Glib::RefPtr& model, const Gtk::TreeModel::Path& path, bool); - - bool key_press (GdkEventKey* ev); - bool focus_in (GdkEventFocus*); - bool focus_out (GdkEventFocus*); - bool enter_notify (GdkEventCrossing*); - bool leave_notify (GdkEventCrossing*); - void name_edit_started (Gtk::CellEditable*, const Glib::ustring&); - - bool get_relevant_routes (boost::shared_ptr rl); + void init (); }; -#endif /* __ardour_gtk_editor_route_h__ */ +#endif /* _ardour_gtk_editor_routes_h_ */ diff --git a/gtk2_ardour/route_list_base.cc b/gtk2_ardour/route_list_base.cc new file mode 100644 index 0000000000..475a6fb08a --- /dev/null +++ b/gtk2_ardour/route_list_base.cc @@ -0,0 +1,1233 @@ +/* + * Copyright (C) 2009-2011 Carl Hetherington + * Copyright (C) 2009-2014 David Robillard + * Copyright (C) 2009-2017 Paul Davis + * Copyright (C) 2013-2015 Nick Mainsbridge + * Copyright (C) 2014-2019 Robin Gareus + * Copyright (C) 2016-2018 Len Ovens + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include +#include +#include +#include + +#include "pbd/unwind.h" + +#include "ardour/audio_track.h" +#include "ardour/debug.h" +#include "ardour/midi_track.h" +#include "ardour/route.h" +#include "ardour/selection.h" +#include "ardour/session.h" +#include "ardour/solo_isolate_control.h" +#include "ardour/utils.h" +#include "ardour/vca.h" +#include "ardour/vca_manager.h" + +#include "gtkmm2ext/treeutils.h" + +#include "widgets/tooltips.h" + +#include "actions.h" +#include "ardour_ui.h" +#include "route_list_base.h" +#include "gui_thread.h" +#include "keyboard.h" +#include "public_editor.h" +#include "route_sorter.h" +#include "utils.h" + +#include "pbd/i18n.h" + +using namespace std; +using namespace ARDOUR; +using namespace ArdourWidgets; +using namespace ARDOUR_UI_UTILS; +using namespace PBD; +using namespace Gtk; +using namespace Gtkmm2ext; +using namespace Glib; +using Gtkmm2ext::Keyboard; + +RouteListBase::RouteListBase () + : _menu (0) + , old_focus (0) + , name_editable (0) + , _ignore_reorder (false) + , _ignore_visibility_change (false) + , _ignore_selection_change (false) + , _column_does_not_select (false) + , _adding_routes (false) + , _route_deletion_in_progress (false) +{ + add_name_column (); + +#if 0 + setup_col (append_toggle (_columns.visible, _columns.noop_true, sigc::mem_fun (*this, &RouteListBase::on_tv_visible_changed)), S_("Visible|V"), _("Track/Bus visible ?")); + setup_col (append_toggle (_columns.trigger, _columns.is_track, sigc::mem_fun (*this, &RouteListBase::on_tv_trigger_changed)), S_("Trigger|T"), _("Visible on TriggerPage ?")); + setup_col (append_toggle (_columns.active, _columns.activatable, sigc::mem_fun (*this, &RouteListBase::on_tv_active_changed)), S_("Active|A"), _("Track/Bus active ?")); + + append_col_input_active (); + append_col_rec_enable (); + append_col_rec_safe (); + append_col_mute (); + append_col_solo (); +#endif + + _display.set_headers_visible (true); + _display.get_selection ()->set_mode (SELECTION_MULTIPLE); + _display.get_selection ()->signal_changed ().connect (sigc::mem_fun (*this, &RouteListBase::selection_changed)); + _display.set_reorderable (true); + _display.set_name (X_("EditGroupList")); + _display.set_rules_hint (true); + _display.set_size_request (100, -1); + + _scroller.add (_display); + _scroller.set_policy (POLICY_NEVER, POLICY_AUTOMATIC); + + _model = ListStore::create (_columns); + _display.set_model (_model); + + _display.get_selection ()->set_select_function (sigc::mem_fun (*this, &RouteListBase::select_function)); + + _model->signal_row_deleted ().connect (sigc::mem_fun (*this, &RouteListBase::row_deleted)); + _model->signal_rows_reordered ().connect (sigc::mem_fun (*this, &RouteListBase::reordered)); + + _display.signal_button_press_event ().connect (sigc::mem_fun (*this, &RouteListBase::button_press), false); + _display.signal_button_release_event ().connect (sigc::mem_fun (*this, &RouteListBase::button_release), false); + _scroller.signal_key_press_event ().connect (sigc::mem_fun (*this, &RouteListBase::key_press), false); + + _scroller.signal_focus_in_event ().connect (sigc::mem_fun (*this, &RouteListBase::focus_in), false); + _scroller.signal_focus_out_event ().connect (sigc::mem_fun (*this, &RouteListBase::focus_out)); + + _display.signal_enter_notify_event ().connect (sigc::mem_fun (*this, &RouteListBase::enter_notify), false); + _display.signal_leave_notify_event ().connect (sigc::mem_fun (*this, &RouteListBase::leave_notify), false); + + _display.set_enable_search (false); +} + +RouteListBase::~RouteListBase () +{ + delete _menu; +} + +void +RouteListBase::setup_col (Gtk::TreeViewColumn* tvc, const char* label, const char* tooltip) +{ + Gtk::Label* l = manage (new Label (label)); + set_tooltip (*l, tooltip); + tvc->set_widget (*l); + l->show (); +} + +void +RouteListBase::add_name_column () +{ + Gtk::TreeViewColumn* tvc = manage (new Gtk::TreeViewColumn ("", _columns.text)); + + setup_col (tvc, _("Name"), ("Track/Bus name")); + + CellRendererText* cell = dynamic_cast (tvc->get_first_cell_renderer ()); + cell->signal_editing_started ().connect (sigc::mem_fun (*this, &RouteListBase::name_edit_started)); + tvc->set_sizing (TREE_VIEW_COLUMN_FIXED); + tvc->set_expand (true); + tvc->set_min_width (50); + cell->property_editable () = true; + cell->signal_editing_started ().connect (sigc::mem_fun (*this, &RouteListBase::name_edit_started)); + cell->signal_edited ().connect (sigc::mem_fun (*this, &RouteListBase::name_edit)); + + _display.append_column (*tvc); +} + +void +RouteListBase::append_col_rec_enable () +{ + CellRendererPixbufMulti* cell; + cell = append_cell (S_("Rec|R"), _("Record enabled"), _columns.rec_state, _columns.is_track, sigc::mem_fun (*this, &RouteListBase::on_tv_rec_enable_changed)); + cell->set_pixbuf (0, ::get_icon ("record-normal-disabled")); + cell->set_pixbuf (1, ::get_icon ("record-normal-in-progress")); + cell->set_pixbuf (2, ::get_icon ("record-normal-enabled")); + cell->set_pixbuf (3, ::get_icon ("record-step")); +} + +void +RouteListBase::append_col_rec_safe () +{ + CellRendererPixbufMulti* cell; + cell = append_cell (S_("Rec|R"), _("Record enabled"), _columns.rec_safe, _columns.is_track, sigc::mem_fun (*this, &RouteListBase::on_tv_rec_safe_toggled)); + cell->set_pixbuf (0, ::get_icon ("rec-safe-disabled")); + cell->set_pixbuf (1, ::get_icon ("rec-safe-enabled")); +} + +void +RouteListBase::append_col_input_active () +{ + CellRendererPixbufMulti* cell; + cell = append_cell (S_("MidiInput|I"), _("MIDI input enabled"), _columns.is_input_active, _columns.is_midi, sigc::mem_fun (*this, &RouteListBase::on_tv_input_active_changed)); + cell->set_pixbuf (0, ::get_icon ("midi-input-inactive")); + cell->set_pixbuf (1, ::get_icon ("midi-input-active")); +} + +void +RouteListBase::append_col_mute () +{ + CellRendererPixbufMulti* cell; + cell = append_cell (S_("Mute|M"), _("Muted"), _columns.mute_state, _columns.noop_true, sigc::mem_fun (*this, &RouteListBase::on_tv_mute_enable_toggled)); + cell->set_pixbuf (Gtkmm2ext::Off, ::get_icon ("mute-disabled")); + cell->set_pixbuf (Gtkmm2ext::ImplicitActive, ::get_icon ("muted-by-others")); + cell->set_pixbuf (Gtkmm2ext::ExplicitActive, ::get_icon ("mute-enabled")); +} + +void +RouteListBase::append_col_solo () +{ + CellRendererPixbufMulti* cell; + cell = append_cell (S_("Solo|S"), _("Soloed"), _columns.solo_state, _columns.solo_visible, sigc::mem_fun (*this, &RouteListBase::on_tv_solo_enable_toggled)); + cell->set_pixbuf (Gtkmm2ext::Off, ::get_icon ("solo-disabled")); + cell->set_pixbuf (Gtkmm2ext::ExplicitActive, ::get_icon ("solo-enabled")); + cell->set_pixbuf (Gtkmm2ext::ImplicitActive, ::get_icon ("soloed-by-others")); + + cell = append_cell (S_("SoloIso|SI"), _("Solo Isolated"), _columns.solo_isolate_state, _columns.solo_lock_iso_visible, sigc::mem_fun (*this, &RouteListBase::on_tv_solo_isolate_toggled)); + cell->set_pixbuf (0, ::get_icon ("solo-isolate-disabled")); + cell->set_pixbuf (1, ::get_icon ("solo-isolate-enabled")); + + cell = append_cell (S_("SoloLock|SS"), _("Solo Safe (Locked)"), _columns.solo_safe_state, _columns.solo_lock_iso_visible, sigc::mem_fun (*this, &RouteListBase::on_tv_solo_safe_toggled)); + cell->set_pixbuf (0, ::get_icon ("solo-safe-disabled")); + cell->set_pixbuf (1, ::get_icon ("solo-safe-enabled")); +} + +bool +RouteListBase::focus_in (GdkEventFocus*) +{ + Window* win = dynamic_cast (_scroller.get_toplevel ()); + + if (win) { + old_focus = win->get_focus (); + } else { + old_focus = 0; + } + + name_editable = 0; + + /* try to do nothing on focus in (doesn't work, hence selection_count nonsense) */ + return true; +} + +bool +RouteListBase::focus_out (GdkEventFocus*) +{ + if (old_focus) { + old_focus->grab_focus (); + old_focus = 0; + } + + return false; +} + +bool +RouteListBase::enter_notify (GdkEventCrossing*) +{ + if (name_editable) { + return true; + } + + Keyboard::magic_widget_grab_focus (); + return false; +} + +bool +RouteListBase::leave_notify (GdkEventCrossing*) +{ + if (old_focus) { + old_focus->grab_focus (); + old_focus = 0; + } + + Keyboard::magic_widget_drop_focus (); + return false; +} + +void +RouteListBase::set_session (Session* s) +{ + SessionHandlePtr::set_session (s); + + initial_display (); + + if (_session) { + _session->vca_manager ().VCAAdded.connect (_session_connections, invalidator (_scroller), boost::bind (&RouteListBase::add_masters, this, _1), gui_context ()); + _session->RouteAdded.connect (_session_connections, invalidator (_scroller), boost::bind (&RouteListBase::add_routes, this, _1), gui_context ()); + _session->SoloChanged.connect (_session_connections, invalidator (_scroller), boost::bind (&RouteListBase::queue_idle_update, this), gui_context ()); + _session->RecordStateChanged.connect (_session_connections, invalidator (_scroller), boost::bind (&RouteListBase::queue_idle_update, this), gui_context ()); + PresentationInfo::Change.connect (_session_connections, invalidator (_scroller), boost::bind (&RouteListBase::presentation_info_changed, this, _1), gui_context ()); + } +} + +void +RouteListBase::on_tv_input_active_changed (std::string const& path_string) +{ + Gtk::TreeModel::Row row = *_model->get_iter (Gtk::TreeModel::Path (path_string)); + boost::shared_ptr stripable = row[_columns.stripable]; + boost::shared_ptr mt = boost::dynamic_pointer_cast (stripable); + + if (mt) { + mt->set_input_active (!mt->input_active ()); + } +} + +void +RouteListBase::on_tv_rec_enable_changed (std::string const& path_string) +{ + Gtk::TreeModel::Row row = *_model->get_iter (Gtk::TreeModel::Path (path_string)); + boost::shared_ptr stripable = row[_columns.stripable]; + boost::shared_ptr ac = stripable->rec_enable_control (); + + if (ac) { + ac->set_value (!ac->get_value (), Controllable::UseGroup); + } +} + +void +RouteListBase::on_tv_rec_safe_toggled (std::string const& path_string) +{ + Gtk::TreeModel::Row row = *_model->get_iter (Gtk::TreeModel::Path (path_string)); + boost::shared_ptr stripable = row[_columns.stripable]; + boost::shared_ptr ac (stripable->rec_safe_control ()); + + if (ac) { + ac->set_value (!ac->get_value (), Controllable::UseGroup); + } +} + +void +RouteListBase::on_tv_mute_enable_toggled (std::string const& path_string) +{ + // Get the model row that has been toggled. + Gtk::TreeModel::Row row = *_model->get_iter (Gtk::TreeModel::Path (path_string)); + boost::shared_ptr stripable = row[_columns.stripable]; + boost::shared_ptr ac (stripable->mute_control ()); + + if (ac) { + ac->set_value (!ac->get_value (), Controllable::UseGroup); + } +} + +void +RouteListBase::on_tv_solo_enable_toggled (std::string const& path_string) +{ + // Get the model row that has been toggled. + Gtk::TreeModel::Row row = *_model->get_iter (Gtk::TreeModel::Path (path_string)); + boost::shared_ptr stripable = row[_columns.stripable]; + boost::shared_ptr ac (stripable->solo_control ()); + + if (ac) { + ac->set_value (!ac->get_value (), Controllable::UseGroup); + } +} + +void +RouteListBase::on_tv_solo_isolate_toggled (std::string const& path_string) +{ + // Get the model row that has been toggled. + Gtk::TreeModel::Row row = *_model->get_iter (Gtk::TreeModel::Path (path_string)); + boost::shared_ptr stripable = row[_columns.stripable]; + boost::shared_ptr ac (stripable->solo_isolate_control ()); + + if (ac) { + ac->set_value (!ac->get_value (), Controllable::UseGroup); + } +} + +void +RouteListBase::on_tv_solo_safe_toggled (std::string const& path_string) +{ + // Get the model row that has been toggled. + Gtk::TreeModel::Row row = *_model->get_iter (Gtk::TreeModel::Path (path_string)); + boost::shared_ptr stripable = row[_columns.stripable]; + boost::shared_ptr ac (stripable->solo_safe_control ()); + + if (ac) { + ac->set_value (!ac->get_value (), Controllable::UseGroup); + } +} + +void +RouteListBase::build_menu () +{ + using namespace Menu_Helpers; + using namespace Gtk; + + _menu = new Menu; + + MenuList& items = _menu->items (); + _menu->set_name ("ArdourContextMenu"); + + items.push_back (MenuElem (_("Show All"), sigc::bind (sigc::mem_fun (*this, &RouteListBase::set_all_audio_midi_visibility), 0, true))); + items.push_back (MenuElem (_("Hide All"), sigc::bind (sigc::mem_fun (*this, &RouteListBase::set_all_audio_midi_visibility), 0, false))); + items.push_back (MenuElem (_("Show All Audio Tracks"), sigc::bind (sigc::mem_fun (*this, &RouteListBase::set_all_audio_midi_visibility), 1, true))); + items.push_back (MenuElem (_("Hide All Audio Tracks"), sigc::bind (sigc::mem_fun (*this, &RouteListBase::set_all_audio_midi_visibility), 1, false))); + items.push_back (MenuElem (_("Show All Midi Tracks"), sigc::bind (sigc::mem_fun (*this, &RouteListBase::set_all_audio_midi_visibility), 3, true))); + items.push_back (MenuElem (_("Hide All Midi Tracks"), sigc::bind (sigc::mem_fun (*this, &RouteListBase::set_all_audio_midi_visibility), 3, false))); + items.push_back (MenuElem (_("Show All Busses"), sigc::bind (sigc::mem_fun (*this, &RouteListBase::set_all_audio_midi_visibility), 2, true))); + items.push_back (MenuElem (_("Hide All Busses"), sigc::bind (sigc::mem_fun (*this, &RouteListBase::set_all_audio_midi_visibility), 2, false))); + items.push_back (MenuElem (_("Only Show Tracks with Regions Under Playhead"), sigc::mem_fun (*this, &RouteListBase::show_tracks_with_regions_at_playhead))); +} + +void +RouteListBase::row_deleted (Gtk::TreeModel::Path const&) +{ + if (!_session || _session->deletion_in_progress ()) { + return; + } + /* this happens as the second step of a DnD within the treeview, and + * when a route is actually removed. we don't differentiate between + * the two cases. + * + * note that the sync_presentation_info_from_treeview() step may not + * actually change any presentation info (e.g. the last track may be + * removed, so all other tracks keep the same presentation info), which + * means that no redisplay would happen. so we have to force a + * redisplay. + */ + + DEBUG_TRACE (DEBUG::OrderKeys, "editor routes treeview row deleted\n"); + + if (!_route_deletion_in_progress) { + sync_presentation_info_from_treeview (); + } +} + +void +RouteListBase::reordered (TreeModel::Path const&, TreeModel::iterator const&, int* /*what*/) +{ + /* reordering implies that RID's will change, so + * sync_presentation_info_from_treeview() will cause a redisplay. + */ + + DEBUG_TRACE (DEBUG::OrderKeys, "editor routes treeview reordered\n"); + sync_presentation_info_from_treeview (); +} + +void +RouteListBase::on_tv_visible_changed (std::string const& path) +{ + if (!_session || _session->deletion_in_progress ()) { + return; + } + if (_ignore_visibility_change) { + return; + } + + DisplaySuspender ds; + TreeIter iter; + + if ((iter = _model->get_iter (path))) { + bool hidden = (*iter)[_columns.visible]; // toggle -> invert flag + + boost::shared_ptr stripable = (*iter)[_columns.stripable]; + + if (hidden != stripable->presentation_info ().hidden ()) { + stripable->presentation_info ().set_hidden (hidden); + + boost::shared_ptr route = boost::dynamic_pointer_cast (stripable); + RouteGroup* rg = route ? route->route_group () : 0; + if (rg && rg->is_active () && rg->is_hidden ()) { + boost::shared_ptr rl (rg->route_list ()); + for (RouteList::const_iterator i = rl->begin (); i != rl->end (); ++i) { + (*i)->presentation_info ().set_hidden (hidden); + } + } + } + } +} + +void +RouteListBase::on_tv_trigger_changed (std::string const& path) +{ + if (!_session || _session->deletion_in_progress ()) { + return; + } + + Gtk::TreeModel::Row row = *_model->get_iter (path); + assert (row[_columns.is_track]); + boost::shared_ptr stripable = row[_columns.stripable]; + bool const tt = row[_columns.trigger]; + stripable->presentation_info ().set_trigger_track (!tt); +} + +void +RouteListBase::on_tv_active_changed (std::string const& path) +{ + if (!_session || _session->deletion_in_progress ()) { + return; + } + + Gtk::TreeModel::Row row = *_model->get_iter (path); + boost::shared_ptr stripable = row[_columns.stripable]; + boost::shared_ptr route = boost::dynamic_pointer_cast (stripable); + if (route) { + bool const active = row[_columns.active]; + route->set_active (!active, this); + } +} + +void +RouteListBase::initial_display () +{ + if (!_session) { + clear (); + return; + } + + _model->clear (); + + StripableList sl; + _session->get_stripables (sl); + add_stripables (sl); + + sync_treeview_from_presentation_info (Properties::order); +} + +void +RouteListBase::add_masters (VCAList& vlist) +{ + StripableList sl; + + for (VCAList::iterator v = vlist.begin (); v != vlist.end (); ++v) { + sl.push_back (boost::dynamic_pointer_cast (*v)); + } + + add_stripables (sl); +} + +void +RouteListBase::add_routes (RouteList& rlist) +{ + StripableList sl; + + for (RouteList::iterator r = rlist.begin (); r != rlist.end (); ++r) { + sl.push_back (*r); + } + + add_stripables (sl); +} + +void +RouteListBase::add_stripables (StripableList& slist) +{ + PBD::Unwinder at (_adding_routes, true); + + Gtk::TreeModel::Children::iterator insert_iter = _model->children ().end (); + + slist.sort (Stripable::Sorter ()); + + for (Gtk::TreeModel::Children::iterator it = _model->children ().begin (); it != _model->children ().end (); ++it) { + boost::shared_ptr r = (*it)[_columns.stripable]; + + if (r->presentation_info ().order () == (slist.front ()->presentation_info ().order () + slist.size ())) { + insert_iter = it; + break; + } + } + + { + PBD::Unwinder uw (_ignore_selection_change, true); + _display.set_model (Glib::RefPtr ()); + } + + for (StripableList::iterator s = slist.begin (); s != slist.end (); ++s) { + boost::shared_ptr stripable = *s; + boost::shared_ptr midi_trk; + boost::shared_ptr route; + + TreeModel::Row row; + + if (boost::dynamic_pointer_cast (stripable)) { + row = *(_model->insert (insert_iter)); + + row[_columns.is_track] = false; + row[_columns.is_input_active] = false; + row[_columns.is_midi] = false; + row[_columns.activatable] = true; + + } else if ((route = boost::dynamic_pointer_cast (*s))) { + if (route->is_auditioner ()) { + continue; + } + if (route->is_monitor ()) { + continue; + } + + row = *(_model->insert (insert_iter)); + + midi_trk = boost::dynamic_pointer_cast (stripable); + + row[_columns.is_track] = (boost::dynamic_pointer_cast (stripable) != 0); + row[_columns.activatable] = !stripable->is_master (); + + if (midi_trk) { + row[_columns.is_input_active] = midi_trk->input_active (); + row[_columns.is_midi] = true; + } else { + row[_columns.is_input_active] = false; + row[_columns.is_midi] = false; + } + } + + row[_columns.noop_true] = true; + row[_columns.text] = stripable->name (); + row[_columns.visible] = !stripable->presentation_info ().hidden (); + row[_columns.trigger] = stripable->presentation_info ().trigger_track () && row[_columns.is_track]; + row[_columns.active] = true; + row[_columns.stripable] = stripable; + row[_columns.mute_state] = RouteUI::mute_active_state (_session, stripable); + row[_columns.solo_state] = RouteUI::solo_active_state (stripable); + row[_columns.solo_visible] = !stripable->is_master (); + row[_columns.solo_lock_iso_visible] = row[_columns.solo_visible] && row[_columns.activatable]; + row[_columns.solo_isolate_state] = RouteUI::solo_isolate_active_state (stripable); + row[_columns.solo_safe_state] = RouteUI::solo_safe_active_state (stripable); + row[_columns.name_editable] = true; + + boost::weak_ptr ws (stripable); + + /* for now, we need both of these. PropertyChanged covers on + * pre-defined, "global" things of interest to a + * UI. gui_changed covers arbitrary, un-enumerated, un-typed + * changes that may only be of interest to a particular + * UI (e.g. track-height is not of any relevant to OSC) + */ + + stripable->PropertyChanged.connect (_stripable_connections, invalidator (_scroller), boost::bind (&RouteListBase::route_property_changed, this, _1, ws), gui_context ()); + stripable->presentation_info ().PropertyChanged.connect (_stripable_connections, invalidator (_scroller), boost::bind (&RouteListBase::route_property_changed, this, _1, ws), gui_context ()); + + if (boost::dynamic_pointer_cast (stripable)) { + boost::shared_ptr t = boost::dynamic_pointer_cast (stripable); + t->rec_enable_control ()->Changed.connect (_stripable_connections, invalidator (_scroller), boost::bind (&RouteListBase::queue_idle_update, this), gui_context ()); + t->rec_safe_control ()->Changed.connect (_stripable_connections, invalidator (_scroller), boost::bind (&RouteListBase::queue_idle_update, this), gui_context ()); + } + + if (midi_trk) { + midi_trk->StepEditStatusChange.connect (_stripable_connections, invalidator (_scroller), boost::bind (&RouteListBase::queue_idle_update, this), gui_context ()); + midi_trk->InputActiveChanged.connect (_stripable_connections, invalidator (_scroller), boost::bind (&RouteListBase::update_input_active_display, this), gui_context ()); + } + + boost::shared_ptr ac; + + if ((ac = stripable->mute_control ()) != 0) { + ac->Changed.connect (_stripable_connections, invalidator (_scroller), boost::bind (&RouteListBase::queue_idle_update, this), gui_context ()); + } + if ((ac = stripable->solo_control ()) != 0) { + ac->Changed.connect (_stripable_connections, invalidator (_scroller), boost::bind (&RouteListBase::queue_idle_update, this), gui_context ()); + } + if ((ac = stripable->solo_isolate_control ()) != 0) { + ac->Changed.connect (_stripable_connections, invalidator (_scroller), boost::bind (&RouteListBase::queue_idle_update, this), gui_context ()); + } + if ((ac = stripable->solo_safe_control ()) != 0) { + ac->Changed.connect (_stripable_connections, invalidator (_scroller), boost::bind (&RouteListBase::queue_idle_update, this), gui_context ()); + } + + if (route) { + route->active_changed.connect (_stripable_connections, invalidator (_scroller), boost::bind (&RouteListBase::queue_idle_update, this), gui_context ()); + } + stripable->DropReferences.connect (_stripable_connections, invalidator (_scroller), boost::bind (&RouteListBase::remove_strip, this, ws), gui_context ()); + } + + queue_idle_update (); + update_input_active_display (); + + { + PBD::Unwinder uw (_ignore_selection_change, true); + _display.set_model (_model); + + /* set the treeview model selection state */ + TreeModel::Children rows = _model->children (); + for (TreeModel::Children::iterator ri = rows.begin (); ri != rows.end (); ++ri) { + boost::shared_ptr stripable = (*ri)[_columns.stripable]; + if (stripable && stripable->is_selected ()) { + _display.get_selection ()->select (*ri); + } else { + _display.get_selection ()->unselect (*ri); + } + } + } +} + +void +RouteListBase::remove_strip (boost::weak_ptr ws) +{ + boost::shared_ptr stripable (ws.lock ()); + + if (!stripable) { + return; + } + + TreeModel::Children rows = _model->children (); + TreeModel::Children::iterator ri; + + PBD::Unwinder uw (_ignore_selection_change, true); + + for (ri = rows.begin (); ri != rows.end (); ++ri) { + boost::shared_ptr s = (*ri)[_columns.stripable]; + if (s == stripable) { + PBD::Unwinder uw (_route_deletion_in_progress, true); + _model->erase (ri); + break; + } + } +} + +void +RouteListBase::route_property_changed (const PropertyChange& what_changed, boost::weak_ptr s) +{ + if (_adding_routes) { + return; + } + + PropertyChange interests; + interests.add (ARDOUR::Properties::name); + interests.add (ARDOUR::Properties::hidden); + interests.add (ARDOUR::Properties::trigger_track); + + if (!what_changed.contains (interests)) { + return; + } + + boost::shared_ptr stripable = s.lock (); + + if (!stripable) { + return; + } + + TreeModel::Children rows = _model->children (); + for (TreeModel::Children::iterator i = rows.begin (); i != rows.end (); ++i) { + boost::shared_ptr ss = (*i)[_columns.stripable]; + + if (ss != stripable) { + continue; + } + + if (what_changed.contains (ARDOUR::Properties::name)) { + (*i)[_columns.text] = stripable->name (); + } + + if (what_changed.contains (ARDOUR::Properties::hidden)) { + (*i)[_columns.visible] = !stripable->presentation_info ().hidden (); + } + + if (what_changed.contains (ARDOUR::Properties::trigger_track)) { + (*i)[_columns.trigger] = stripable->presentation_info ().trigger_track () && (*i)[_columns.is_track]; + } + + break; + } +} + +void +RouteListBase::presentation_info_changed (PropertyChange const& what_changed) +{ + PropertyChange soh; + + soh.add (Properties::order); + soh.add (Properties::selected); + if (what_changed.contains (soh)) { + sync_treeview_from_presentation_info (what_changed); + } +} + +void +RouteListBase::sync_presentation_info_from_treeview () +{ + if (_ignore_reorder || !_session || _session->deletion_in_progress ()) { + return; + } + + TreeModel::Children rows = _model->children (); + + if (rows.empty ()) { + return; + } + + DEBUG_TRACE (DEBUG::OrderKeys, "editor sync presentation info from treeview\n"); + + PresentationInfo::order_t order = 0; + + PresentationInfo::ChangeSuspender cs; + + for (TreeModel::Children::iterator ri = rows.begin (); ri != rows.end (); ++ri) { + boost::shared_ptr stripable = (*ri)[_columns.stripable]; + bool visible = (*ri)[_columns.visible]; + + stripable->presentation_info ().set_hidden (!visible); + stripable->set_presentation_order (order); + ++order; + } +} + +void +RouteListBase::sync_treeview_from_presentation_info (PropertyChange const& what_changed) +{ + /* Some route order key(s) have been changed, make sure that + * we update out tree/list model and GUI to reflect the change. + */ + + if (_ignore_reorder || !_session || _session->deletion_in_progress ()) { + return; + } + + TreeModel::Children rows = _model->children (); + if (rows.empty ()) { + return; + } + + DEBUG_TRACE (DEBUG::OrderKeys, "editor sync model from presentation info.\n"); + + bool changed = false; + + if (what_changed.contains (Properties::order)) { + vector neworder; + uint32_t old_order = 0; + + TreeOrderKeys sorted; + for (TreeModel::Children::iterator ri = rows.begin (); ri != rows.end (); ++ri, ++old_order) { + boost::shared_ptr stripable = (*ri)[_columns.stripable]; + /* use global order */ + sorted.push_back (TreeOrderKey (old_order, stripable)); + } + + TreeOrderKeySorter cmp; + + sort (sorted.begin (), sorted.end (), cmp); + neworder.assign (sorted.size (), 0); + + uint32_t n = 0; + + for (TreeOrderKeys::iterator sr = sorted.begin (); sr != sorted.end (); ++sr, ++n) { + neworder[n] = sr->old_display_order; + + if (sr->old_display_order != n) { + changed = true; + } + } + + if (changed) { + Unwinder uw (_ignore_reorder, true); + /* prevent traverse_cells: assertion 'row_path != NULL' + * in case of DnD re-order: row-removed + row-inserted. + * + * The rows (stripables) are not actually removed from the model, + * but only from the display in the DnDTreeView. + * ->reorder() will fail to find the row_path. + * (re-order drag -> remove row -> sync PI from TV -> notify -> sync TV from PI -> crash) + */ + Unwinder uw2 (_ignore_selection_change, true); + + _display.unset_model (); + _model->reorder (neworder); + _display.set_model (_model); + } + } + + if (changed || what_changed.contains (Properties::selected)) { + /* by the time this is invoked, the GUI Selection model has + * already updated itself. + */ + PBD::Unwinder uw (_ignore_selection_change, true); + + /* set the treeview model selection state */ + for (TreeModel::Children::iterator ri = rows.begin (); ri != rows.end (); ++ri) { + boost::shared_ptr stripable = (*ri)[_columns.stripable]; + if (stripable && stripable->is_selected ()) { + _display.get_selection ()->select (*ri); + } else { + _display.get_selection ()->unselect (*ri); + } + } + } +} + +void +RouteListBase::set_all_audio_midi_visibility (int which, bool yn) +{ + TreeModel::Children rows = _model->children (); + TreeModel::Children::iterator i; + + DisplaySuspender ds; + PBD::Unwinder uw (_ignore_visibility_change, true); + + for (i = rows.begin (); i != rows.end (); ++i) { + boost::shared_ptr stripable = (*i)[_columns.stripable]; + + /* + * which = 0: any (incl. VCA) + * which = 1: audio-tracks + * which = 2: busses + * which = 3: midi-tracks + */ + bool is_audio = boost::dynamic_pointer_cast (stripable) != 0; + bool is_midi = boost::dynamic_pointer_cast (stripable) != 0; + bool is_bus = !is_audio && !is_midi && boost::dynamic_pointer_cast (stripable) != 0; + + switch (which) { + case 0: + (*i)[_columns.visible] = yn; + break; + case 1: + if (is_audio) { + (*i)[_columns.visible] = yn; + } + break; + case 2: + if (is_bus) { + (*i)[_columns.visible] = yn; + } + break; + case 3: + if (is_midi) { + (*i)[_columns.visible] = yn; + } + break; + default: + assert (0); + } + } + + sync_presentation_info_from_treeview (); +} + +bool +RouteListBase::key_press (GdkEventKey* ev) +{ + TreeViewColumn* col; + TreePath path; + + boost::shared_ptr rl (new RouteList); + + switch (ev->keyval) { + case GDK_Tab: + case GDK_ISO_Left_Tab: + + /* If we appear to be editing something, leave that cleanly and appropriately. */ + if (name_editable) { + name_editable->editing_done (); + name_editable = 0; + } + + col = _display.get_column (0); /* track-name col */ + + if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) { + treeview_select_previous (_display, _model, col); + } else { + treeview_select_next (_display, _model, col); + } + + return true; + break; + + case 'm': + if (get_relevant_routes (rl)) { + _session->set_controls (route_list_to_control_list (rl, &Stripable::mute_control), rl->front ()->muted () ? 0.0 : 1.0, Controllable::NoGroup); + } + return true; + break; + + case 's': + if (get_relevant_routes (rl)) { + _session->set_controls (route_list_to_control_list (rl, &Stripable::solo_control), rl->front ()->self_soloed () ? 0.0 : 1.0, Controllable::NoGroup); + } + return true; + break; + + case 'r': + if (get_relevant_routes (rl)) { + for (RouteList::const_iterator r = rl->begin (); r != rl->end (); ++r) { + boost::shared_ptr t = boost::dynamic_pointer_cast (*r); + if (t) { + _session->set_controls (route_list_to_control_list (rl, &Stripable::rec_enable_control), !t->rec_enable_control ()->get_value (), Controllable::NoGroup); + break; + } + } + } + break; + + default: + break; + } + + return false; +} + +bool +RouteListBase::get_relevant_routes (boost::shared_ptr rl) +{ + RefPtr selection = _display.get_selection (); + TreePath path; + TreeIter iter; + + if (selection->count_selected_rows () != 0) { + /* use selection */ + + RefPtr tm = RefPtr::cast_dynamic (_model); + iter = selection->get_selected (tm); + + } else { + /* use mouse pointer */ + + int x, y; + int bx, by; + + _display.get_pointer (x, y); + _display.convert_widget_to_bin_window_coords (x, y, bx, by); + + if (_display.get_path_at_pos (bx, by, path)) { + iter = _model->get_iter (path); + } + } + + if (iter) { + boost::shared_ptr stripable = (*iter)[_columns.stripable]; + boost::shared_ptr route = boost::dynamic_pointer_cast (stripable); + if (route) { + rl->push_back (route); + } + } + + return !rl->empty (); +} + +bool +RouteListBase::select_function (const Glib::RefPtr&, const Gtk::TreeModel::Path&, bool) +{ + return !_column_does_not_select; +} + +bool +RouteListBase::button_release (GdkEventButton*) +{ + _column_does_not_select = false; + return false; +} + +bool +RouteListBase::button_press (GdkEventButton* ev) +{ + if (Keyboard::is_context_menu_event (ev)) { + if (_menu == 0) { + build_menu (); + } + _menu->popup (ev->button, ev->time); + return true; + } + + TreeModel::Path path; + TreeViewColumn* tvc; + int cell_x; + int cell_y; + + if (!_display.get_path_at_pos ((int)ev->x, (int)ev->y, path, tvc, cell_x, cell_y)) { + /* cancel selection */ + _display.get_selection ()->unselect_all (); + /* end any editing by grabbing focus */ + _display.grab_focus (); + return true; + } + + if (no_select_columns.find (tvc) != no_select_columns.end ()) { + _column_does_not_select = true; + } + + //Scroll editor canvas to selected track + if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) { + PublicEditor& e (PublicEditor::instance ()); + Gtk::TreeModel::Row row = *_model->get_iter (path); + TimeAxisView* tv = e.time_axis_view_from_stripable (row[_columns.stripable]); + if (tv) { + e.ensure_time_axis_view_is_visible (*tv, true); + } + } + + return false; +} + +void +RouteListBase::selection_changed () +{ + if (_ignore_selection_change || _column_does_not_select) { + return; + } + + PublicEditor& e (PublicEditor::instance ()); + TrackViewList selected; + + if (_display.get_selection ()->count_selected_rows () > 0) { + TreeView::Selection::ListHandle_Path rows = _display.get_selection ()->get_selected_rows (); + + for (TreeView::Selection::ListHandle_Path::iterator i = rows.begin (); i != rows.end (); ++i) { + TreeIter iter; + if ((iter = _model->get_iter (*i))) { + TimeAxisView* tv = e.time_axis_view_from_stripable ((*iter)[_columns.stripable]); + if (tv) { + selected.push_back (tv); + } + } + } + } + + e.begin_reversible_selection_op (X_("Select Track from Route List")); + Selection& s (e.get_selection ()); + + if (selected.empty ()) { + s.clear_tracks (); + } else { + s.set (selected); + e.ensure_time_axis_view_is_visible (*(selected.front ()), true); + } + + e.commit_reversible_selection_op (); +} + +void +RouteListBase::update_input_active_display () +{ + TreeModel::Children rows = _model->children (); + TreeModel::Children::iterator i; + + for (i = rows.begin (); i != rows.end (); ++i) { + boost::shared_ptr stripable = (*i)[_columns.stripable]; + + if (boost::dynamic_pointer_cast (stripable)) { + boost::shared_ptr mt = boost::dynamic_pointer_cast (stripable); + + if (mt) { + (*i)[_columns.is_input_active] = mt->input_active (); + } + } + } +} + +void +RouteListBase::queue_idle_update () +{ + if (!_idle_update_connection.connected ()) { + _idle_update_connection = Glib::signal_idle ().connect (sigc::mem_fun (*this, &RouteListBase::idle_update_mute_rec_solo_etc)); + } +} + +bool +RouteListBase::idle_update_mute_rec_solo_etc () +{ + TreeModel::Children rows = _model->children (); + TreeModel::Children::iterator i; + + for (i = rows.begin (); i != rows.end (); ++i) { + boost::shared_ptr stripable = (*i)[_columns.stripable]; + boost::shared_ptr route = boost::dynamic_pointer_cast (stripable); + (*i)[_columns.mute_state] = RouteUI::mute_active_state (_session, stripable); + (*i)[_columns.solo_state] = RouteUI::solo_active_state (stripable); + (*i)[_columns.solo_isolate_state] = RouteUI::solo_isolate_active_state (stripable) ? 1 : 0; + (*i)[_columns.solo_safe_state] = RouteUI::solo_safe_active_state (stripable) ? 1 : 0; + if (route) { + (*i)[_columns.active] = route->active (); + } else { + (*i)[_columns.active] = true; + } + + boost::shared_ptr trk (boost::dynamic_pointer_cast (route)); + + if (trk) { + boost::shared_ptr mt = boost::dynamic_pointer_cast (route); + + if (trk->rec_enable_control ()->get_value ()) { + if (_session->record_status () == Session::Recording) { + (*i)[_columns.rec_state] = 1; + } else { + (*i)[_columns.rec_state] = 2; + } + } else if (mt && mt->step_editing ()) { + (*i)[_columns.rec_state] = 3; + } else { + (*i)[_columns.rec_state] = 0; + } + + (*i)[_columns.rec_safe] = trk->rec_safe_control ()->get_value (); + (*i)[_columns.name_editable] = !trk->rec_enable_control ()->get_value (); + } + } + + return false; // do not call again (until needed) +} + +void +RouteListBase::clear () +{ + PBD::Unwinder uw (_ignore_selection_change, true); + _stripable_connections.drop_connections (); + _display.set_model (Glib::RefPtr (0)); + _model->clear (); + _display.set_model (_model); +} + +void +RouteListBase::name_edit_started (CellEditable* ce, const Glib::ustring&) +{ + name_editable = ce; + + /* give it a special name */ + + Gtk::Entry* e = dynamic_cast (ce); + + if (e) { + e->set_name (X_("RouteNameEditorEntry")); + } +} + +void +RouteListBase::name_edit (std::string const& path, std::string const& new_text) +{ + name_editable = 0; + + TreeIter iter = _model->get_iter (path); + + if (!iter) { + return; + } + + boost::shared_ptr stripable = (*iter)[_columns.stripable]; + + if (stripable && stripable->name () != new_text) { + stripable->set_name (new_text); + } +} + +void +RouteListBase::show_tracks_with_regions_at_playhead () +{ + boost::shared_ptr const r = _session->get_routes_with_regions_at (timepos_t (_session->transport_sample ())); + + DisplaySuspender ds; + + TreeModel::Children rows = _model->children (); + for (TreeModel::Children::iterator i = rows.begin (); i != rows.end (); ++i) { + boost::shared_ptr stripable = (*i)[_columns.stripable]; + boost::shared_ptr route = boost::dynamic_pointer_cast (stripable); + + bool to_show = std::find (r->begin (), r->end (), route) != r->end (); + stripable->presentation_info ().set_hidden (!to_show); + } +} diff --git a/gtk2_ardour/route_list_base.h b/gtk2_ardour/route_list_base.h new file mode 100644 index 0000000000..d6b60dca29 --- /dev/null +++ b/gtk2_ardour/route_list_base.h @@ -0,0 +1,234 @@ +/* + * Copyright (C) 2009-2011 Carl Hetherington + * Copyright (C) 2009-2011 David Robillard + * Copyright (C) 2009-2017 Paul Davis + * Copyright (C) 2014-2019 Robin Gareus + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _ardour_gtk_route_list_base_h_ +#define _ardour_gtk_route_list_base_h_ + +#include + +#include +#include +#include +#include +#include + +#include "pbd/properties.h" +#include "pbd/signals.h" + +#include "ardour/route.h" +#include "ardour/session_handle.h" +#include "ardour/types.h" + +#include "gtkmm2ext/cell_renderer_pixbuf_multi.h" + +class TimeAxisView; + +class RouteListBase : public ARDOUR::SessionHandlePtr +{ +public: + RouteListBase (); + ~RouteListBase (); + + void set_session (ARDOUR::Session*); + + Gtk::Widget& widget () + { + return _scroller; + } + + void clear (); + +protected: + void add_name_column (); + void append_col_rec_enable (); + void append_col_rec_safe (); + void append_col_input_active (); + void append_col_mute (); + void append_col_solo (); + + void setup_col (Gtk::TreeViewColumn*, const char*, const char*); + + template + Gtk::TreeViewColumn* append_toggle (Gtk::TreeModelColumn const& col_state, Gtk::TreeModelColumn const& col_viz, sigc::slot cb) + { + Gtk::TreeViewColumn* tvc = manage (new Gtk::TreeViewColumn ("", col_state)); + tvc->set_fixed_width (30); + tvc->set_sizing (Gtk::TREE_VIEW_COLUMN_FIXED); + tvc->set_expand (false); + tvc->set_alignment (Gtk::ALIGN_CENTER); + + Gtk::CellRendererToggle* tc = dynamic_cast (tvc->get_first_cell_renderer ()); + tc->property_activatable () = true; + tc->property_radio () = false; + tc->signal_toggled ().connect (cb); + + tvc->add_attribute (tc->property_visible (), col_viz); + + _display.append_column (*tvc); + no_select_columns.insert (tvc); + return tvc; + } + + template + Gtkmm2ext::CellRendererPixbufMulti* append_cell (const char* lbl, const char* tip, Gtk::TreeModelColumn const& col_state, Gtk::TreeModelColumn const& col_viz, sigc::slot cb) + { + Gtkmm2ext::CellRendererPixbufMulti* cell; + Gtk::TreeViewColumn* tvc; + + cell = manage (new Gtkmm2ext::CellRendererPixbufMulti ()); + cell->signal_changed ().connect (cb); + + tvc = manage (new Gtk::TreeViewColumn (lbl, *cell)); + tvc->add_attribute (cell->property_state (), col_state); + tvc->add_attribute (cell->property_visible (), col_viz); + tvc->set_sizing (Gtk::TREE_VIEW_COLUMN_FIXED); + tvc->set_alignment (Gtk::ALIGN_CENTER); + tvc->set_expand (false); + tvc->set_fixed_width (24); + + setup_col (tvc, lbl, tip); + _display.append_column (*tvc); + no_select_columns.insert (tvc); + return cell; + } + + void on_tv_input_active_changed (std::string const&); + void on_tv_rec_enable_changed (std::string const&); + void on_tv_rec_safe_toggled (std::string const&); + void on_tv_mute_enable_toggled (std::string const&); + void on_tv_solo_enable_toggled (std::string const&); + void on_tv_solo_isolate_toggled (std::string const&); + void on_tv_solo_safe_toggled (std::string const&); + void on_tv_visible_changed (std::string const&); + void on_tv_trigger_changed (std::string const&); + void on_tv_active_changed (std::string const&); + + struct ModelColumns : public Gtk::TreeModel::ColumnRecord { + ModelColumns () + { + add (text); + add (visible); + add (trigger); + add (rec_state); + add (rec_safe); + add (mute_state); + add (solo_state); + add (solo_visible); + add (solo_lock_iso_visible); + add (solo_isolate_state); + add (solo_safe_state); + add (is_track); + add (stripable); + add (name_editable); + add (is_input_active); + add (is_midi); + add (activatable); + add (active); + add (noop_true); + } + + Gtk::TreeModelColumn text; + Gtk::TreeModelColumn visible; + Gtk::TreeModelColumn trigger; + Gtk::TreeModelColumn rec_state; + Gtk::TreeModelColumn rec_safe; + Gtk::TreeModelColumn mute_state; + Gtk::TreeModelColumn solo_state; + Gtk::TreeModelColumn solo_visible; // true if the solo buttons are visible for this route, otherwise false + Gtk::TreeModelColumn solo_lock_iso_visible; + Gtk::TreeModelColumn solo_isolate_state; + Gtk::TreeModelColumn solo_safe_state; + Gtk::TreeModelColumn is_track; + Gtk::TreeModelColumn> stripable; + Gtk::TreeModelColumn name_editable; + Gtk::TreeModelColumn is_input_active; + Gtk::TreeModelColumn is_midi; + Gtk::TreeModelColumn activatable; + Gtk::TreeModelColumn active; + Gtk::TreeModelColumn noop_true; // always true + }; + + Gtk::TreeView _display; + ModelColumns _columns; + +private: + + void redisplay (); + void initial_display (); + void sync_presentation_info_from_treeview (); + void sync_treeview_from_presentation_info (PBD::PropertyChange const&); + + void add_routes (ARDOUR::RouteList&); + void add_masters (ARDOUR::VCAList&); + void add_stripables (ARDOUR::StripableList&); + void remove_strip (boost::weak_ptr); + + void selection_changed (); + void row_deleted (Gtk::TreeModel::Path const&); + void reordered (Gtk::TreeModel::Path const&, Gtk::TreeModel::iterator const&, int*); + + bool button_press (GdkEventButton*); + bool button_release (GdkEventButton*); + void build_menu (); + void set_all_audio_midi_visibility (int, bool); + void show_tracks_with_regions_at_playhead (); + + void queue_idle_update (); + bool idle_update_mute_rec_solo_etc (); + void update_input_active_display (); + + void route_property_changed (const PBD::PropertyChange&, boost::weak_ptr); + void presentation_info_changed (PBD::PropertyChange const&); + + void name_edit (std::string const&, std::string const&); + + bool select_function (const Glib::RefPtr& model, const Gtk::TreeModel::Path& path, bool); + + bool key_press (GdkEventKey* ev); + bool focus_in (GdkEventFocus*); + bool focus_out (GdkEventFocus*); + bool enter_notify (GdkEventCrossing*); + bool leave_notify (GdkEventCrossing*); + void name_edit_started (Gtk::CellEditable*, const Glib::ustring&); + + bool get_relevant_routes (boost::shared_ptr rl); + + Gtk::ScrolledWindow _scroller; + Glib::RefPtr _model; + + Gtk::Menu* _menu; + Gtk::Widget* old_focus; + Gtk::CellEditable* name_editable; + + std::set no_select_columns; + + bool _ignore_reorder; + bool _ignore_visibility_change; + bool _ignore_selection_change; + bool _column_does_not_select; + bool _adding_routes; + bool _route_deletion_in_progress; + + sigc::connection _idle_update_connection; + PBD::ScopedConnectionList _stripable_connections; +}; + +#endif /* _ardour_gtk_route_list_base_h_ */ diff --git a/gtk2_ardour/wscript b/gtk2_ardour/wscript index e82f828145..429e5bf4fb 100644 --- a/gtk2_ardour/wscript +++ b/gtk2_ardour/wscript @@ -258,6 +258,7 @@ gtk2_ardour_sources = [ 'rhythm_ferret.cc', 'route_group_dialog.cc', 'route_group_menu.cc', + 'route_list_base.cc', 'route_params_ui.cc', 'route_processor_selection.cc', 'route_time_axis.cc',