ardour/gtk2_ardour/route_list_base.cc
Robin Gareus 0b9b4ff6b9
Don't list foldback busses in editor and triggerbase "Tracks" list
There ought to be no timeline dependence for those and they
there is no FoldbackTimeAxis to begin with.
2025-11-10 21:18:31 +01:00

1275 lines
39 KiB
C++

/*
* Copyright (C) 2009-2011 Carl Hetherington <carl@carlh.net>
* Copyright (C) 2009-2014 David Robillard <d@drobilla.net>
* Copyright (C) 2009-2017 Paul Davis <paul@linuxaudiosystems.com>
* Copyright (C) 2013-2015 Nick Mainsbridge <mainsbridge@gmail.com>
* Copyright (C) 2014-2019 Robin Gareus <robin@gareus.org>
* Copyright (C) 2016-2018 Len Ovens <len@ovenwerks.net>
*
* 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 <algorithm>
#include <cassert>
#include <cmath>
#include <cstdlib>
#include <list>
#include <vector>
#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 "rta_manager.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_("Cues|C"), _("Visible on Cues window ?"));
setup_col (append_toggle (_columns.active, _columns.activatable, sigc::mem_fun (*this, &RouteListBase::on_tv_active_changed)), S_("Active|A"), _("Track/Bus active ?"));
setup_col (append_toggle (_columns.rta_enabled, _columns.active, sigc::mem_fun (*this, &EditorRoutes::on_tv_rta_enable_toggled)), S_("RTA|RA"), _("Realtime Analyzer 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, bool require_mod_to_edit)
{
Gtk::Label* l = manage (new Label (label));
set_tooltip (*l, tooltip);
tvc->set_widget (*l);
l->show ();
if (require_mod_to_edit) {
/* Try to prevent single mouse presses from initiating edits.
* This relies on a hack in gtktreeview.c:gtk_treeview_button_press() */
tvc->set_data ("mouse-edits-require-mod1", (gpointer)0x1);
}
}
void
RouteListBase::add_name_column ()
{
Gtk::TreeViewColumn* tvc = manage (new Gtk::TreeViewColumn ("", _columns.text));
setup_col (tvc, _("Name"), ("Track/Bus name"), true);
CellRendererText* cell = dynamic_cast<CellRendererText*> (tvc->get_first_cell ());
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|RS"), _("Record Safe"), _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<Window*> (_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), std::bind (&RouteListBase::add_masters, this, _1), gui_context ());
_session->RouteAdded.connect (_session_connections, invalidator (_scroller), std::bind (&RouteListBase::add_routes, this, _1), gui_context ());
_session->SoloChanged.connect (_session_connections, invalidator (_scroller), std::bind (&RouteListBase::queue_idle_update, this), gui_context ());
_session->RecordStateChanged.connect (_session_connections, invalidator (_scroller), std::bind (&RouteListBase::queue_idle_update, this), gui_context ());
PresentationInfo::Change.connect (_session_connections, invalidator (_scroller), std::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));
std::shared_ptr<Stripable> stripable = row[_columns.stripable];
std::shared_ptr<MidiTrack> mt = std::dynamic_pointer_cast<MidiTrack> (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));
std::shared_ptr<Stripable> stripable = row[_columns.stripable];
std::shared_ptr<AutomationControl> 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));
std::shared_ptr<Stripable> stripable = row[_columns.stripable];
std::shared_ptr<AutomationControl> 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));
std::shared_ptr<Stripable> stripable = row[_columns.stripable];
std::shared_ptr<AutomationControl> 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));
std::shared_ptr<Stripable> stripable = row[_columns.stripable];
std::shared_ptr<AutomationControl> 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));
std::shared_ptr<Stripable> stripable = row[_columns.stripable];
std::shared_ptr<AutomationControl> 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));
std::shared_ptr<Stripable> stripable = row[_columns.stripable];
std::shared_ptr<AutomationControl> ac (stripable->solo_safe_control ());
if (ac) {
ac->set_value (!ac->get_value (), Controllable::UseGroup);
}
}
void
RouteListBase::on_tv_rta_enable_toggled (std::string const& path_string)
{
Gtk::TreeModel::Row row = *_model->get_iter (Gtk::TreeModel::Path (path_string));
std::shared_ptr<Stripable> stripable = row[_columns.stripable];
std::shared_ptr<Route> route = std::dynamic_pointer_cast<Route> (stripable);
if (route) {
bool attached = RTAManager::instance ()->attached (route);
if (attached) {
RTAManager::instance ()->remove (route);
} else {
RTAManager::instance ()->attach (route);
ARDOUR_UI::instance()->show_realtime_analyzer ();
}
}
}
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
std::shared_ptr<Stripable> stripable = (*iter)[_columns.stripable];
if (hidden != stripable->presentation_info ().hidden ()) {
stripable->presentation_info ().set_hidden (hidden);
std::shared_ptr<Route> route = std::dynamic_pointer_cast<Route> (stripable);
RouteGroup* rg = route ? route->route_group () : 0;
if (rg && rg->is_active () && rg->is_hidden ()) {
std::shared_ptr<RouteList> 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]);
std::shared_ptr<Stripable> 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);
std::shared_ptr<Stripable> stripable = row[_columns.stripable];
std::shared_ptr<Route> route = std::dynamic_pointer_cast<Route> (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 (std::dynamic_pointer_cast<Stripable> (*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<bool> 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) {
std::shared_ptr<Stripable> r = (*it)[_columns.stripable];
if (r->presentation_info ().order () == (slist.front ()->presentation_info ().order () + slist.size ())) {
insert_iter = it;
break;
}
}
{
PBD::Unwinder<bool> uw (_ignore_selection_change, true);
_display.set_model (Glib::RefPtr<ListStore> ());
}
for (StripableList::iterator s = slist.begin (); s != slist.end (); ++s) {
std::shared_ptr<Stripable> stripable = *s;
std::shared_ptr<MidiTrack> midi_trk;
std::shared_ptr<Route> route;
TreeModel::Row row;
if (std::dynamic_pointer_cast<VCA> (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 = std::dynamic_pointer_cast<Route> (*s))) {
if (route->is_auditioner ()) {
continue;
}
if (route->is_monitor ()) {
continue;
}
if (route->is_surround_master ()) {
continue;
}
if (route->is_foldbackbus ()) {
continue;
}
row = *(_model->insert (insert_iter));
midi_trk = std::dynamic_pointer_cast<MidiTrack> (stripable);
row[_columns.is_track] = (std::dynamic_pointer_cast<Track> (stripable) != 0);
row[_columns.activatable] = !stripable->is_singleton ();
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;
std::weak_ptr<Stripable> 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), std::bind (&RouteListBase::route_property_changed, this, _1, ws), gui_context ());
stripable->presentation_info ().PropertyChanged.connect (_stripable_connections, invalidator (_scroller), std::bind (&RouteListBase::route_property_changed, this, _1, ws), gui_context ());
if (std::dynamic_pointer_cast<Track> (stripable)) {
std::shared_ptr<Track> t = std::dynamic_pointer_cast<Track> (stripable);
t->rec_enable_control ()->Changed.connect (_stripable_connections, invalidator (_scroller), std::bind (&RouteListBase::queue_idle_update, this), gui_context ());
t->rec_safe_control ()->Changed.connect (_stripable_connections, invalidator (_scroller), std::bind (&RouteListBase::queue_idle_update, this), gui_context ());
}
if (midi_trk) {
midi_trk->StepEditStatusChange.connect (_stripable_connections, invalidator (_scroller), std::bind (&RouteListBase::queue_idle_update, this), gui_context ());
midi_trk->InputActiveChanged.connect (_stripable_connections, invalidator (_scroller), std::bind (&RouteListBase::update_input_active_display, this), gui_context ());
}
std::shared_ptr<AutomationControl> ac;
if ((ac = stripable->mute_control ()) != 0) {
ac->Changed.connect (_stripable_connections, invalidator (_scroller), std::bind (&RouteListBase::queue_idle_update, this), gui_context ());
}
if ((ac = stripable->solo_control ()) != 0) {
ac->Changed.connect (_stripable_connections, invalidator (_scroller), std::bind (&RouteListBase::queue_idle_update, this), gui_context ());
}
if ((ac = stripable->solo_isolate_control ()) != 0) {
ac->Changed.connect (_stripable_connections, invalidator (_scroller), std::bind (&RouteListBase::queue_idle_update, this), gui_context ());
}
if ((ac = stripable->solo_safe_control ()) != 0) {
ac->Changed.connect (_stripable_connections, invalidator (_scroller), std::bind (&RouteListBase::queue_idle_update, this), gui_context ());
}
if (route) {
route->active_changed.connect (_stripable_connections, invalidator (_scroller), std::bind (&RouteListBase::queue_idle_update, this), gui_context ());
route->gui_changed.connect (_stripable_connections, invalidator (_scroller), std::bind (&RouteListBase::handle_gui_changes, this, _1), gui_context());
}
stripable->DropReferences.connect (_stripable_connections, invalidator (_scroller), std::bind (&RouteListBase::remove_strip, this, ws), gui_context ());
}
queue_idle_update ();
update_input_active_display ();
{
PBD::Unwinder<bool> 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) {
std::shared_ptr<Stripable> stripable = (*ri)[_columns.stripable];
if (stripable && stripable->is_selected ()) {
_display.get_selection ()->select (*ri);
} else {
_display.get_selection ()->unselect (*ri);
}
}
}
}
void
RouteListBase::remove_strip (std::weak_ptr<ARDOUR::Stripable> ws)
{
std::shared_ptr<Stripable> stripable (ws.lock ());
if (!stripable) {
return;
}
TreeModel::Children rows = _model->children ();
TreeModel::Children::iterator ri;
PBD::Unwinder<bool> uw (_ignore_selection_change, true);
for (ri = rows.begin (); ri != rows.end (); ++ri) {
std::shared_ptr<Stripable> s = (*ri)[_columns.stripable];
if (s == stripable) {
PBD::Unwinder<bool> uw (_route_deletion_in_progress, true);
_model->erase (ri);
break;
}
}
}
void
RouteListBase::route_property_changed (const PropertyChange& what_changed, std::weak_ptr<Stripable> 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;
}
std::shared_ptr<Stripable> stripable = s.lock ();
if (!stripable) {
return;
}
TreeModel::Children rows = _model->children ();
for (TreeModel::Children::iterator i = rows.begin (); i != rows.end (); ++i) {
std::shared_ptr<Stripable> 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) {
std::shared_ptr<Stripable> 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<int> neworder;
uint32_t old_order = 0;
TreeOrderKeys sorted;
for (TreeModel::Children::iterator ri = rows.begin (); ri != rows.end (); ++ri, ++old_order) {
std::shared_ptr<Stripable> 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<bool> 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<bool> 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<bool> uw (_ignore_selection_change, true);
/* set the treeview model selection state */
for (TreeModel::Children::iterator ri = rows.begin (); ri != rows.end (); ++ri) {
std::shared_ptr<Stripable> 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<bool> uw (_ignore_visibility_change, true);
for (i = rows.begin (); i != rows.end (); ++i) {
std::shared_ptr<Stripable> stripable = (*i)[_columns.stripable];
/*
* which = 0: any (incl. VCA)
* which = 1: audio-tracks
* which = 2: busses
* which = 3: midi-tracks
*/
bool is_audio = std::dynamic_pointer_cast<AudioTrack> (stripable) != 0;
bool is_midi = std::dynamic_pointer_cast<MidiTrack> (stripable) != 0;
bool is_bus = !is_audio && !is_midi && std::dynamic_pointer_cast<Route> (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;
std::shared_ptr<RouteList> 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) {
std::shared_ptr<Track> t = std::dynamic_pointer_cast<Track> (*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 (std::shared_ptr<RouteList> rl)
{
RefPtr<TreeSelection> selection = _display.get_selection ();
TreePath path;
TreeIter iter;
if (selection->count_selected_rows () != 0) {
/* use selection */
RefPtr<TreeModel> tm = RefPtr<TreeModel>::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) {
std::shared_ptr<Stripable> stripable = (*iter)[_columns.stripable];
std::shared_ptr<Route> route = std::dynamic_pointer_cast<Route> (stripable);
if (route) {
rl->push_back (route);
}
}
return !rl->empty ();
}
bool
RouteListBase::select_function (const Glib::RefPtr<Gtk::TreeModel>&, 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) {
std::shared_ptr<Stripable> stripable = (*i)[_columns.stripable];
if (std::dynamic_pointer_cast<Track> (stripable)) {
std::shared_ptr<MidiTrack> mt = std::dynamic_pointer_cast<MidiTrack> (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) {
std::shared_ptr<Stripable> stripable = (*i)[_columns.stripable];
std::shared_ptr<Route> route = std::dynamic_pointer_cast<Route> (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 ();
(*i)[_columns.rta_enabled] = RTAManager::instance ()->attached (route);
} else {
(*i)[_columns.active] = true;
(*i)[_columns.rta_enabled] = false;
}
std::shared_ptr<Track> trk (std::dynamic_pointer_cast<Track> (route));
if (trk) {
std::shared_ptr<MidiTrack> mt = std::dynamic_pointer_cast<MidiTrack> (route);
if (trk->rec_enable_control ()->get_value ()) {
if (_session->record_status () == 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::handle_gui_changes (std::string const& what)
{
if (what == "rta") {
queue_idle_update ();
}
}
void
RouteListBase::clear ()
{
PBD::Unwinder<bool> uw (_ignore_selection_change, true);
_stripable_connections.drop_connections ();
_display.set_model (Glib::RefPtr<Gtk::TreeStore> (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<Gtk::Entry*> (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;
}
std::shared_ptr<Stripable> stripable = (*iter)[_columns.stripable];
if (stripable && stripable->name () != new_text) {
stripable->set_name (new_text);
}
}
void
RouteListBase::show_tracks_with_regions_at_playhead ()
{
std::shared_ptr<RouteList> 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) {
std::shared_ptr<Stripable> stripable = (*i)[_columns.stripable];
std::shared_ptr<Route> route = std::dynamic_pointer_cast<Route> (stripable);
bool to_show = std::find (r->begin (), r->end (), route) != r->end ();
stripable->presentation_info ().set_hidden (!to_show);
}
}