ardour/gtk2_ardour/route_list_base.cc

1242 lines
38 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 "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 ?"));
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), 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));
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::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;
}
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), 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 (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), 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 ());
}
std::shared_ptr<AutomationControl> 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<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 ();
} else {
(*i)[_columns.active] = true;
}
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 () == 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<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);
}
}