David Robillard
2fa6caad95
The idea here is to do the reasonable thing, and copy objects of some type (e.g. MIDI region, gain line) to tracks with a matching type. The user can override this with a track selection, which will be used straight-up. Lost: ability to copy/paste lines across types, e.g. gain to pan. This is often questionable, but sometimes useful, so we will need to implement some sort of "greedy mode" to make it possible. Implementation simple, but not sure what to do. Perhaps this should only be possible if one automation track is explicitly (i.e. via track selection) involved, and the types are at least compatible-ish?
1321 lines
27 KiB
C++
1321 lines
27 KiB
C++
/*
|
|
Copyright (C) 2002 Paul Davis
|
|
|
|
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., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
|
|
*/
|
|
|
|
#include <algorithm>
|
|
#include <sigc++/bind.h>
|
|
|
|
#include "pbd/error.h"
|
|
#include "pbd/stacktrace.h"
|
|
|
|
#include "ardour/playlist.h"
|
|
#include "ardour/rc_configuration.h"
|
|
|
|
#include "gui_thread.h"
|
|
#include "midi_cut_buffer.h"
|
|
#include "region_view.h"
|
|
#include "selection.h"
|
|
#include "selection_templates.h"
|
|
#include "time_axis_view.h"
|
|
#include "automation_time_axis.h"
|
|
#include "public_editor.h"
|
|
#include "control_point.h"
|
|
|
|
#include "i18n.h"
|
|
|
|
using namespace std;
|
|
using namespace ARDOUR;
|
|
using namespace PBD;
|
|
|
|
struct AudioRangeComparator {
|
|
bool operator()(AudioRange a, AudioRange b) {
|
|
return a.start < b.start;
|
|
}
|
|
};
|
|
|
|
Selection::Selection (const PublicEditor* e)
|
|
: tracks (e)
|
|
, editor (e)
|
|
, next_time_id (0)
|
|
, _no_tracks_changed (false)
|
|
{
|
|
clear ();
|
|
|
|
/* we have disambiguate which remove() for the compiler */
|
|
|
|
void (Selection::*track_remove)(TimeAxisView*) = &Selection::remove;
|
|
TimeAxisView::CatchDeletion.connect (*this, MISSING_INVALIDATOR, boost::bind (track_remove, this, _1), gui_context());
|
|
|
|
void (Selection::*marker_remove)(Marker*) = &Selection::remove;
|
|
Marker::CatchDeletion.connect (*this, MISSING_INVALIDATOR, boost::bind (marker_remove, this, _1), gui_context());
|
|
|
|
void (Selection::*point_remove)(ControlPoint*) = &Selection::remove;
|
|
ControlPoint::CatchDeletion.connect (*this, MISSING_INVALIDATOR, boost::bind (point_remove, this, _1), gui_context());
|
|
}
|
|
|
|
#if 0
|
|
Selection&
|
|
Selection::operator= (const Selection& other)
|
|
{
|
|
if (&other != this) {
|
|
regions = other.regions;
|
|
tracks = other.tracks;
|
|
time = other.time;
|
|
lines = other.lines;
|
|
midi_regions = other.midi_regions;
|
|
midi_notes = other.midi_notes;
|
|
}
|
|
return *this;
|
|
}
|
|
#endif
|
|
|
|
bool
|
|
operator== (const Selection& a, const Selection& b)
|
|
{
|
|
return a.regions == b.regions &&
|
|
a.tracks == b.tracks &&
|
|
a.time == b.time &&
|
|
a.lines == b.lines &&
|
|
a.playlists == b.playlists &&
|
|
a.midi_notes == b.midi_notes &&
|
|
a.midi_regions == b.midi_regions;
|
|
}
|
|
|
|
/** Clear everything from the Selection */
|
|
void
|
|
Selection::clear ()
|
|
{
|
|
clear_tracks ();
|
|
clear_regions ();
|
|
clear_points ();
|
|
clear_lines();
|
|
clear_time ();
|
|
clear_playlists ();
|
|
clear_midi_notes ();
|
|
clear_midi_regions ();
|
|
clear_markers ();
|
|
}
|
|
|
|
void
|
|
Selection::clear_objects ()
|
|
{
|
|
clear_regions ();
|
|
clear_points ();
|
|
clear_lines();
|
|
clear_playlists ();
|
|
clear_midi_notes ();
|
|
clear_midi_regions ();
|
|
}
|
|
|
|
void
|
|
Selection::clear_tracks ()
|
|
{
|
|
if (!tracks.empty()) {
|
|
for (TrackViewList::iterator x = tracks.begin(); x != tracks.end(); ++x) {
|
|
(*x)->set_selected (false);
|
|
}
|
|
tracks.clear ();
|
|
if (!_no_tracks_changed) {
|
|
TracksChanged();
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
Selection::clear_time ()
|
|
{
|
|
time.clear();
|
|
|
|
TimeChanged ();
|
|
}
|
|
|
|
void
|
|
Selection::dump_region_layers()
|
|
{
|
|
cerr << "region selection layer dump" << endl;
|
|
for (RegionSelection::iterator i = regions.begin(); i != regions.end(); ++i) {
|
|
cerr << "layer: " << (int)(*i)->region()->layer() << endl;
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
Selection::clear_regions ()
|
|
{
|
|
if (!regions.empty()) {
|
|
regions.clear_all ();
|
|
RegionsChanged();
|
|
}
|
|
}
|
|
|
|
void
|
|
Selection::clear_midi_notes ()
|
|
{
|
|
if (!midi_notes.empty()) {
|
|
for (MidiNoteSelection::iterator x = midi_notes.begin(); x != midi_notes.end(); ++x) {
|
|
delete *x;
|
|
}
|
|
midi_notes.clear ();
|
|
MidiNotesChanged ();
|
|
}
|
|
}
|
|
|
|
void
|
|
Selection::clear_midi_regions ()
|
|
{
|
|
if (!midi_regions.empty()) {
|
|
midi_regions.clear ();
|
|
MidiRegionsChanged ();
|
|
}
|
|
}
|
|
|
|
void
|
|
Selection::clear_playlists ()
|
|
{
|
|
/* Selections own their playlists */
|
|
|
|
for (PlaylistSelection::iterator i = playlists.begin(); i != playlists.end(); ++i) {
|
|
/* selections own their own regions, which are copies of the "originals". make them go away */
|
|
(*i)->drop_regions ();
|
|
(*i)->release ();
|
|
}
|
|
|
|
if (!playlists.empty()) {
|
|
playlists.clear ();
|
|
PlaylistsChanged();
|
|
}
|
|
}
|
|
|
|
void
|
|
Selection::clear_lines ()
|
|
{
|
|
if (!lines.empty()) {
|
|
lines.clear ();
|
|
LinesChanged();
|
|
}
|
|
}
|
|
|
|
void
|
|
Selection::clear_markers ()
|
|
{
|
|
if (!markers.empty()) {
|
|
markers.clear ();
|
|
MarkersChanged();
|
|
}
|
|
}
|
|
|
|
void
|
|
Selection::toggle (boost::shared_ptr<Playlist> pl)
|
|
{
|
|
clear_time(); //enforce object/range exclusivity
|
|
clear_tracks(); //enforce object/track exclusivity
|
|
|
|
PlaylistSelection::iterator i;
|
|
|
|
if ((i = find (playlists.begin(), playlists.end(), pl)) == playlists.end()) {
|
|
pl->use ();
|
|
playlists.push_back(pl);
|
|
} else {
|
|
playlists.erase (i);
|
|
}
|
|
|
|
PlaylistsChanged ();
|
|
}
|
|
|
|
void
|
|
Selection::toggle (const TrackViewList& track_list)
|
|
{
|
|
for (TrackViewList::const_iterator i = track_list.begin(); i != track_list.end(); ++i) {
|
|
toggle ((*i));
|
|
}
|
|
}
|
|
|
|
void
|
|
Selection::toggle (TimeAxisView* track)
|
|
{
|
|
TrackSelection::iterator i;
|
|
|
|
if ((i = find (tracks.begin(), tracks.end(), track)) == tracks.end()) {
|
|
track->set_selected (true);
|
|
tracks.push_back (track);
|
|
} else {
|
|
track->set_selected (false);
|
|
tracks.erase (i);
|
|
}
|
|
|
|
if (!_no_tracks_changed) {
|
|
TracksChanged();
|
|
}
|
|
}
|
|
|
|
void
|
|
Selection::toggle (const MidiNoteSelection& midi_note_list)
|
|
{
|
|
clear_time(); //enforce object/range exclusivity
|
|
clear_tracks(); //enforce object/track exclusivity
|
|
|
|
for (MidiNoteSelection::const_iterator i = midi_note_list.begin(); i != midi_note_list.end(); ++i) {
|
|
toggle ((*i));
|
|
}
|
|
}
|
|
|
|
void
|
|
Selection::toggle (MidiCutBuffer* midi)
|
|
{
|
|
MidiNoteSelection::iterator i;
|
|
|
|
if ((i = find (midi_notes.begin(), midi_notes.end(), midi)) == midi_notes.end()) {
|
|
midi_notes.push_back (midi);
|
|
} else {
|
|
/* remember that we own the MCB */
|
|
delete *i;
|
|
midi_notes.erase (i);
|
|
}
|
|
|
|
MidiNotesChanged();
|
|
}
|
|
|
|
|
|
void
|
|
Selection::toggle (RegionView* r)
|
|
{
|
|
clear_time(); //enforce object/range exclusivity
|
|
clear_tracks(); //enforce object/track exclusivity
|
|
|
|
RegionSelection::iterator i;
|
|
|
|
if ((i = find (regions.begin(), regions.end(), r)) == regions.end()) {
|
|
add (r);
|
|
} else {
|
|
remove (*i);
|
|
}
|
|
|
|
RegionsChanged ();
|
|
}
|
|
|
|
void
|
|
Selection::toggle (MidiRegionView* mrv)
|
|
{
|
|
clear_time(); //enforce object/range exclusivity
|
|
clear_tracks(); //enforce object/track exclusivity
|
|
|
|
MidiRegionSelection::iterator i;
|
|
|
|
if ((i = find (midi_regions.begin(), midi_regions.end(), mrv)) == midi_regions.end()) {
|
|
add (mrv);
|
|
} else {
|
|
midi_regions.erase (i);
|
|
}
|
|
|
|
MidiRegionsChanged ();
|
|
}
|
|
|
|
void
|
|
Selection::toggle (vector<RegionView*>& r)
|
|
{
|
|
clear_time(); //enforce object/range exclusivity
|
|
clear_tracks(); //enforce object/track exclusivity
|
|
|
|
RegionSelection::iterator i;
|
|
|
|
for (vector<RegionView*>::iterator x = r.begin(); x != r.end(); ++x) {
|
|
if ((i = find (regions.begin(), regions.end(), (*x))) == regions.end()) {
|
|
add ((*x));
|
|
} else {
|
|
remove (*x);
|
|
}
|
|
}
|
|
|
|
RegionsChanged ();
|
|
}
|
|
|
|
long
|
|
Selection::toggle (framepos_t start, framepos_t end)
|
|
{
|
|
clear_objects(); //enforce object/range exclusivity
|
|
|
|
AudioRangeComparator cmp;
|
|
|
|
/* XXX this implementation is incorrect */
|
|
|
|
time.push_back (AudioRange (start, end, ++next_time_id));
|
|
time.consolidate ();
|
|
time.sort (cmp);
|
|
|
|
TimeChanged ();
|
|
|
|
return next_time_id;
|
|
}
|
|
|
|
void
|
|
Selection::add (boost::shared_ptr<Playlist> pl)
|
|
{
|
|
clear_time(); //enforce object/range exclusivity
|
|
clear_tracks(); //enforce object/track exclusivity
|
|
|
|
if (find (playlists.begin(), playlists.end(), pl) == playlists.end()) {
|
|
pl->use ();
|
|
playlists.push_back(pl);
|
|
PlaylistsChanged ();
|
|
}
|
|
}
|
|
|
|
void
|
|
Selection::add (const list<boost::shared_ptr<Playlist> >& pllist)
|
|
{
|
|
clear_time(); //enforce object/range exclusivity
|
|
clear_tracks(); //enforce object/track exclusivity
|
|
|
|
bool changed = false;
|
|
|
|
for (list<boost::shared_ptr<Playlist> >::const_iterator i = pllist.begin(); i != pllist.end(); ++i) {
|
|
if (find (playlists.begin(), playlists.end(), (*i)) == playlists.end()) {
|
|
(*i)->use ();
|
|
playlists.push_back (*i);
|
|
changed = true;
|
|
}
|
|
}
|
|
|
|
if (changed) {
|
|
PlaylistsChanged ();
|
|
}
|
|
}
|
|
|
|
void
|
|
Selection::add (const TrackViewList& track_list)
|
|
{
|
|
clear_objects(); //enforce object/range exclusivity
|
|
|
|
TrackViewList added = tracks.add (track_list);
|
|
|
|
if (!added.empty()) {
|
|
for (TrackViewList::iterator x = added.begin(); x != added.end(); ++x) {
|
|
(*x)->set_selected (true);
|
|
}
|
|
if (!_no_tracks_changed) {
|
|
TracksChanged ();
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
Selection::add (TimeAxisView* track)
|
|
{
|
|
clear_objects(); //enforce object/range exclusivity
|
|
|
|
TrackViewList tr;
|
|
track->set_selected (true);
|
|
tr.push_back (track);
|
|
add (tr);
|
|
}
|
|
|
|
void
|
|
Selection::add (const MidiNoteSelection& midi_list)
|
|
{
|
|
clear_time(); //enforce object/range exclusivity
|
|
clear_tracks(); //enforce object/track exclusivity
|
|
|
|
const MidiNoteSelection::const_iterator b = midi_list.begin();
|
|
const MidiNoteSelection::const_iterator e = midi_list.end();
|
|
|
|
if (!midi_list.empty()) {
|
|
midi_notes.insert (midi_notes.end(), b, e);
|
|
MidiNotesChanged ();
|
|
}
|
|
}
|
|
|
|
void
|
|
Selection::add (MidiCutBuffer* midi)
|
|
{
|
|
/* we take ownership of the MCB */
|
|
|
|
if (find (midi_notes.begin(), midi_notes.end(), midi) == midi_notes.end()) {
|
|
midi_notes.push_back (midi);
|
|
MidiNotesChanged ();
|
|
}
|
|
}
|
|
|
|
void
|
|
Selection::add (vector<RegionView*>& v)
|
|
{
|
|
clear_time(); //enforce object/range exclusivity
|
|
clear_tracks(); //enforce object/track exclusivity
|
|
|
|
/* XXX This method or the add (const RegionSelection&) needs to go
|
|
*/
|
|
|
|
bool changed = false;
|
|
|
|
for (vector<RegionView*>::iterator i = v.begin(); i != v.end(); ++i) {
|
|
if (find (regions.begin(), regions.end(), (*i)) == regions.end()) {
|
|
changed = regions.add ((*i));
|
|
}
|
|
}
|
|
|
|
if (changed) {
|
|
RegionsChanged ();
|
|
}
|
|
}
|
|
|
|
void
|
|
Selection::add (const RegionSelection& rs)
|
|
{
|
|
clear_time(); //enforce object/range exclusivity
|
|
clear_tracks(); //enforce object/track exclusivity
|
|
|
|
/* XXX This method or the add (const vector<RegionView*>&) needs to go
|
|
*/
|
|
|
|
bool changed = false;
|
|
|
|
for (RegionSelection::const_iterator i = rs.begin(); i != rs.end(); ++i) {
|
|
if (find (regions.begin(), regions.end(), (*i)) == regions.end()) {
|
|
changed = regions.add ((*i));
|
|
}
|
|
}
|
|
|
|
if (changed) {
|
|
RegionsChanged ();
|
|
}
|
|
}
|
|
|
|
void
|
|
Selection::add (RegionView* r)
|
|
{
|
|
clear_time(); //enforce object/range exclusivity
|
|
clear_tracks(); //enforce object/track exclusivity
|
|
|
|
if (find (regions.begin(), regions.end(), r) == regions.end()) {
|
|
bool changed = regions.add (r);
|
|
if (changed) {
|
|
RegionsChanged ();
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
Selection::add (MidiRegionView* mrv)
|
|
{
|
|
clear_time(); //enforce object/range exclusivity
|
|
clear_tracks(); //enforce object/track exclusivity
|
|
|
|
if (find (midi_regions.begin(), midi_regions.end(), mrv) == midi_regions.end()) {
|
|
midi_regions.push_back (mrv);
|
|
/* XXX should we do this? */
|
|
MidiRegionsChanged ();
|
|
}
|
|
}
|
|
|
|
long
|
|
Selection::add (framepos_t start, framepos_t end)
|
|
{
|
|
clear_objects(); //enforce object/range exclusivity
|
|
|
|
AudioRangeComparator cmp;
|
|
|
|
/* XXX this implementation is incorrect */
|
|
|
|
time.push_back (AudioRange (start, end, ++next_time_id));
|
|
time.consolidate ();
|
|
time.sort (cmp);
|
|
|
|
TimeChanged ();
|
|
|
|
return next_time_id;
|
|
}
|
|
|
|
void
|
|
Selection::move_time (framecnt_t distance)
|
|
{
|
|
if (distance == 0) {
|
|
return;
|
|
}
|
|
|
|
for (list<AudioRange>::iterator i = time.begin(); i != time.end(); ++i) {
|
|
(*i).start += distance;
|
|
(*i).end += distance;
|
|
}
|
|
|
|
TimeChanged ();
|
|
}
|
|
|
|
void
|
|
Selection::replace (uint32_t sid, framepos_t start, framepos_t end)
|
|
{
|
|
clear_objects(); //enforce object/range exclusivity
|
|
|
|
for (list<AudioRange>::iterator i = time.begin(); i != time.end(); ++i) {
|
|
if ((*i).id == sid) {
|
|
time.erase (i);
|
|
time.push_back (AudioRange(start,end, sid));
|
|
|
|
/* don't consolidate here */
|
|
|
|
|
|
AudioRangeComparator cmp;
|
|
time.sort (cmp);
|
|
|
|
TimeChanged ();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
Selection::add (boost::shared_ptr<Evoral::ControlList> cl)
|
|
{
|
|
clear_time(); //enforce object/range exclusivity
|
|
clear_tracks(); //enforce object/track exclusivity
|
|
|
|
boost::shared_ptr<ARDOUR::AutomationList> al
|
|
= boost::dynamic_pointer_cast<ARDOUR::AutomationList>(cl);
|
|
if (!al) {
|
|
warning << "Programming error: Selected list is not an ARDOUR::AutomationList" << endmsg;
|
|
return;
|
|
}
|
|
if (find (lines.begin(), lines.end(), al) == lines.end()) {
|
|
lines.push_back (al);
|
|
LinesChanged();
|
|
}
|
|
}
|
|
|
|
void
|
|
Selection::remove (TimeAxisView* track)
|
|
{
|
|
list<TimeAxisView*>::iterator i;
|
|
if ((i = find (tracks.begin(), tracks.end(), track)) != tracks.end()) {
|
|
track->set_selected (false);
|
|
tracks.erase (i);
|
|
if (!_no_tracks_changed) {
|
|
TracksChanged();
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
Selection::remove (const TrackViewList& track_list)
|
|
{
|
|
bool changed = false;
|
|
|
|
for (TrackViewList::const_iterator i = track_list.begin(); i != track_list.end(); ++i) {
|
|
|
|
TrackViewList::iterator x = find (tracks.begin(), tracks.end(), *i);
|
|
if (x != tracks.end()) {
|
|
(*i)->set_selected (false);
|
|
tracks.erase (x);
|
|
changed = true;
|
|
}
|
|
}
|
|
|
|
if (changed) {
|
|
if (!_no_tracks_changed) {
|
|
TracksChanged();
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
Selection::remove (ControlPoint* p)
|
|
{
|
|
PointSelection::iterator i = find (points.begin(), points.end(), p);
|
|
if (i != points.end ()) {
|
|
points.erase (i);
|
|
}
|
|
}
|
|
|
|
void
|
|
Selection::remove (const MidiNoteSelection& midi_list)
|
|
{
|
|
bool changed = false;
|
|
|
|
for (MidiNoteSelection::const_iterator i = midi_list.begin(); i != midi_list.end(); ++i) {
|
|
|
|
MidiNoteSelection::iterator x;
|
|
|
|
if ((x = find (midi_notes.begin(), midi_notes.end(), (*i))) != midi_notes.end()) {
|
|
midi_notes.erase (x);
|
|
changed = true;
|
|
}
|
|
}
|
|
|
|
if (changed) {
|
|
MidiNotesChanged();
|
|
}
|
|
}
|
|
|
|
void
|
|
Selection::remove (MidiCutBuffer* midi)
|
|
{
|
|
MidiNoteSelection::iterator x;
|
|
|
|
if ((x = find (midi_notes.begin(), midi_notes.end(), midi)) != midi_notes.end()) {
|
|
/* remember that we own the MCB */
|
|
delete *x;
|
|
midi_notes.erase (x);
|
|
MidiNotesChanged ();
|
|
}
|
|
}
|
|
|
|
void
|
|
Selection::remove (boost::shared_ptr<Playlist> track)
|
|
{
|
|
list<boost::shared_ptr<Playlist> >::iterator i;
|
|
if ((i = find (playlists.begin(), playlists.end(), track)) != playlists.end()) {
|
|
playlists.erase (i);
|
|
PlaylistsChanged();
|
|
}
|
|
}
|
|
|
|
void
|
|
Selection::remove (const list<boost::shared_ptr<Playlist> >& pllist)
|
|
{
|
|
bool changed = false;
|
|
|
|
for (list<boost::shared_ptr<Playlist> >::const_iterator i = pllist.begin(); i != pllist.end(); ++i) {
|
|
|
|
list<boost::shared_ptr<Playlist> >::iterator x;
|
|
|
|
if ((x = find (playlists.begin(), playlists.end(), (*i))) != playlists.end()) {
|
|
playlists.erase (x);
|
|
changed = true;
|
|
}
|
|
}
|
|
|
|
if (changed) {
|
|
PlaylistsChanged();
|
|
}
|
|
}
|
|
|
|
void
|
|
Selection::remove (RegionView* r)
|
|
{
|
|
if (regions.remove (r)) {
|
|
RegionsChanged ();
|
|
}
|
|
}
|
|
|
|
void
|
|
Selection::remove (MidiRegionView* mrv)
|
|
{
|
|
MidiRegionSelection::iterator x;
|
|
|
|
if ((x = find (midi_regions.begin(), midi_regions.end(), mrv)) != midi_regions.end()) {
|
|
midi_regions.erase (x);
|
|
MidiRegionsChanged ();
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
Selection::remove (uint32_t selection_id)
|
|
{
|
|
if (time.empty()) {
|
|
return;
|
|
}
|
|
|
|
for (list<AudioRange>::iterator i = time.begin(); i != time.end(); ++i) {
|
|
if ((*i).id == selection_id) {
|
|
time.erase (i);
|
|
|
|
TimeChanged ();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
Selection::remove (framepos_t /*start*/, framepos_t /*end*/)
|
|
{
|
|
}
|
|
|
|
void
|
|
Selection::remove (boost::shared_ptr<ARDOUR::AutomationList> ac)
|
|
{
|
|
AutomationSelection::iterator i;
|
|
if ((i = find (lines.begin(), lines.end(), ac)) != lines.end()) {
|
|
lines.erase (i);
|
|
LinesChanged();
|
|
}
|
|
}
|
|
|
|
void
|
|
Selection::set (TimeAxisView* track)
|
|
{
|
|
clear_objects(); //enforce object/range exclusivity
|
|
clear_tracks ();
|
|
add (track);
|
|
}
|
|
|
|
void
|
|
Selection::set (const TrackViewList& track_list)
|
|
{
|
|
clear_objects(); //enforce object/range exclusivity
|
|
clear_tracks ();
|
|
add (track_list);
|
|
}
|
|
|
|
void
|
|
Selection::set (const MidiNoteSelection& midi_list)
|
|
{
|
|
clear_time (); //enforce region/object exclusivity
|
|
clear_tracks(); //enforce object/track exclusivity
|
|
clear_objects ();
|
|
add (midi_list);
|
|
}
|
|
|
|
void
|
|
Selection::set (boost::shared_ptr<Playlist> playlist)
|
|
{
|
|
clear_time (); //enforce region/object exclusivity
|
|
clear_tracks(); //enforce object/track exclusivity
|
|
clear_objects ();
|
|
add (playlist);
|
|
}
|
|
|
|
void
|
|
Selection::set (const list<boost::shared_ptr<Playlist> >& pllist)
|
|
{
|
|
clear_time(); //enforce region/object exclusivity
|
|
clear_objects ();
|
|
add (pllist);
|
|
}
|
|
|
|
void
|
|
Selection::set (const RegionSelection& rs)
|
|
{
|
|
clear_time(); //enforce region/object exclusivity
|
|
clear_tracks(); //enforce object/track exclusivity
|
|
clear_objects();
|
|
regions = rs;
|
|
RegionsChanged(); /* EMIT SIGNAL */
|
|
}
|
|
|
|
void
|
|
Selection::set (MidiRegionView* mrv)
|
|
{
|
|
clear_time(); //enforce region/object exclusivity
|
|
clear_tracks(); //enforce object/track exclusivity
|
|
clear_objects ();
|
|
add (mrv);
|
|
}
|
|
|
|
void
|
|
Selection::set (RegionView* r, bool /*also_clear_tracks*/)
|
|
{
|
|
clear_time(); //enforce region/object exclusivity
|
|
clear_tracks(); //enforce object/track exclusivity
|
|
clear_objects ();
|
|
add (r);
|
|
}
|
|
|
|
void
|
|
Selection::set (vector<RegionView*>& v)
|
|
{
|
|
clear_time(); //enforce region/object exclusivity
|
|
clear_tracks(); //enforce object/track exclusivity
|
|
clear_objects();
|
|
|
|
add (v);
|
|
}
|
|
|
|
/** Set the start and end time of the time selection, without changing
|
|
* the list of tracks it applies to.
|
|
*/
|
|
long
|
|
Selection::set (framepos_t start, framepos_t end)
|
|
{
|
|
clear_objects(); //enforce region/object exclusivity
|
|
clear_time();
|
|
|
|
if ((start == 0 && end == 0) || end < start) {
|
|
return 0;
|
|
}
|
|
|
|
if (time.empty()) {
|
|
time.push_back (AudioRange (start, end, ++next_time_id));
|
|
} else {
|
|
/* reuse the first entry, and remove all the rest */
|
|
|
|
while (time.size() > 1) {
|
|
time.pop_front();
|
|
}
|
|
time.front().start = start;
|
|
time.front().end = end;
|
|
}
|
|
|
|
time.consolidate ();
|
|
|
|
TimeChanged ();
|
|
|
|
return time.front().id;
|
|
}
|
|
|
|
/** Set the start and end of the range selection. If more than one range
|
|
* is currently selected, the start of the earliest range and the end of the
|
|
* latest range are set. If no range is currently selected, this method
|
|
* selects a single range from start to end.
|
|
*
|
|
* @param start New start time.
|
|
* @param end New end time.
|
|
*/
|
|
void
|
|
Selection::set_preserving_all_ranges (framepos_t start, framepos_t end)
|
|
{
|
|
clear_objects(); //enforce region/object exclusivity
|
|
|
|
if ((start == 0 && end == 0) || (end < start)) {
|
|
return;
|
|
}
|
|
|
|
if (time.empty ()) {
|
|
time.push_back (AudioRange (start, end, ++next_time_id));
|
|
} else {
|
|
time.sort (AudioRangeComparator ());
|
|
time.front().start = start;
|
|
time.back().end = end;
|
|
}
|
|
|
|
time.consolidate ();
|
|
|
|
TimeChanged ();
|
|
}
|
|
|
|
void
|
|
Selection::set (boost::shared_ptr<Evoral::ControlList> ac)
|
|
{
|
|
clear_time(); //enforce region/object exclusivity
|
|
clear_tracks(); //enforce object/track exclusivity
|
|
clear_objects();
|
|
|
|
add (ac);
|
|
}
|
|
|
|
bool
|
|
Selection::selected (Marker* m)
|
|
{
|
|
return find (markers.begin(), markers.end(), m) != markers.end();
|
|
}
|
|
|
|
bool
|
|
Selection::selected (TimeAxisView* tv)
|
|
{
|
|
return tv->get_selected ();
|
|
}
|
|
|
|
bool
|
|
Selection::selected (RegionView* rv)
|
|
{
|
|
return find (regions.begin(), regions.end(), rv) != regions.end();
|
|
}
|
|
|
|
bool
|
|
Selection::selected (ControlPoint* cp)
|
|
{
|
|
return find (points.begin(), points.end(), cp) != points.end();
|
|
}
|
|
|
|
bool
|
|
Selection::empty (bool internal_selection)
|
|
{
|
|
bool object_level_empty = regions.empty () &&
|
|
tracks.empty () &&
|
|
points.empty () &&
|
|
playlists.empty () &&
|
|
lines.empty () &&
|
|
time.empty () &&
|
|
playlists.empty () &&
|
|
markers.empty() &&
|
|
midi_regions.empty()
|
|
;
|
|
|
|
if (!internal_selection) {
|
|
return object_level_empty;
|
|
}
|
|
|
|
/* this is intended to really only apply when using a Selection
|
|
as a cut buffer.
|
|
*/
|
|
|
|
return object_level_empty && midi_notes.empty() && points.empty();
|
|
}
|
|
|
|
void
|
|
Selection::toggle (ControlPoint* cp)
|
|
{
|
|
clear_time(); //enforce region/object exclusivity
|
|
clear_tracks(); //enforce object/track exclusivity
|
|
|
|
cp->set_selected (!cp->get_selected ());
|
|
PointSelection::iterator i = find (points.begin(), points.end(), cp);
|
|
if (i == points.end()) {
|
|
points.push_back (cp);
|
|
} else {
|
|
points.erase (i);
|
|
}
|
|
|
|
PointsChanged (); /* EMIT SIGNAL */
|
|
}
|
|
|
|
void
|
|
Selection::toggle (vector<ControlPoint*> const & cps)
|
|
{
|
|
clear_time(); //enforce region/object exclusivity
|
|
clear_tracks(); //enforce object/track exclusivity
|
|
|
|
for (vector<ControlPoint*>::const_iterator i = cps.begin(); i != cps.end(); ++i) {
|
|
toggle (*i);
|
|
}
|
|
}
|
|
|
|
void
|
|
Selection::toggle (list<Selectable*> const & selectables)
|
|
{
|
|
clear_time(); //enforce region/object exclusivity
|
|
clear_tracks(); //enforce object/track exclusivity
|
|
|
|
RegionView* rv;
|
|
ControlPoint* cp;
|
|
vector<RegionView*> rvs;
|
|
vector<ControlPoint*> cps;
|
|
|
|
for (std::list<Selectable*>::const_iterator i = selectables.begin(); i != selectables.end(); ++i) {
|
|
if ((rv = dynamic_cast<RegionView*> (*i)) != 0) {
|
|
rvs.push_back (rv);
|
|
} else if ((cp = dynamic_cast<ControlPoint*> (*i)) != 0) {
|
|
cps.push_back (cp);
|
|
} else {
|
|
fatal << _("programming error: ")
|
|
<< X_("unknown selectable type passed to Selection::toggle()")
|
|
<< endmsg;
|
|
abort(); /*NOTREACHED*/
|
|
}
|
|
}
|
|
|
|
if (!rvs.empty()) {
|
|
toggle (rvs);
|
|
}
|
|
|
|
if (!cps.empty()) {
|
|
toggle (cps);
|
|
}
|
|
}
|
|
|
|
void
|
|
Selection::set (list<Selectable*> const & selectables)
|
|
{
|
|
clear_time (); //enforce region/object exclusivity
|
|
clear_tracks(); //enforce object/track exclusivity
|
|
clear_objects ();
|
|
|
|
add (selectables);
|
|
}
|
|
|
|
void
|
|
Selection::add (PointSelection const & s)
|
|
{
|
|
clear_time (); //enforce region/object exclusivity
|
|
clear_tracks(); //enforce object/track exclusivity
|
|
|
|
for (PointSelection::const_iterator i = s.begin(); i != s.end(); ++i) {
|
|
points.push_back (*i);
|
|
}
|
|
}
|
|
|
|
void
|
|
Selection::add (list<Selectable*> const & selectables)
|
|
{
|
|
clear_time (); //enforce region/object exclusivity
|
|
clear_tracks(); //enforce object/track exclusivity
|
|
|
|
RegionView* rv;
|
|
ControlPoint* cp;
|
|
vector<RegionView*> rvs;
|
|
vector<ControlPoint*> cps;
|
|
|
|
for (std::list<Selectable*>::const_iterator i = selectables.begin(); i != selectables.end(); ++i) {
|
|
if ((rv = dynamic_cast<RegionView*> (*i)) != 0) {
|
|
rvs.push_back (rv);
|
|
} else if ((cp = dynamic_cast<ControlPoint*> (*i)) != 0) {
|
|
cps.push_back (cp);
|
|
} else {
|
|
fatal << _("programming error: ")
|
|
<< X_("unknown selectable type passed to Selection::add()")
|
|
<< endmsg;
|
|
abort(); /*NOTREACHED*/
|
|
}
|
|
}
|
|
|
|
if (!rvs.empty()) {
|
|
add (rvs);
|
|
}
|
|
|
|
if (!cps.empty()) {
|
|
add (cps);
|
|
}
|
|
}
|
|
|
|
void
|
|
Selection::clear_points ()
|
|
{
|
|
if (!points.empty()) {
|
|
points.clear ();
|
|
PointsChanged ();
|
|
}
|
|
}
|
|
|
|
void
|
|
Selection::add (ControlPoint* cp)
|
|
{
|
|
clear_time (); //enforce region/object exclusivity
|
|
clear_tracks(); //enforce object/track exclusivity
|
|
|
|
cp->set_selected (true);
|
|
points.push_back (cp);
|
|
PointsChanged (); /* EMIT SIGNAL */
|
|
}
|
|
|
|
void
|
|
Selection::add (vector<ControlPoint*> const & cps)
|
|
{
|
|
clear_time (); //enforce region/object exclusivity
|
|
clear_tracks(); //enforce object/track exclusivity
|
|
|
|
for (vector<ControlPoint*>::const_iterator i = cps.begin(); i != cps.end(); ++i) {
|
|
(*i)->set_selected (true);
|
|
points.push_back (*i);
|
|
}
|
|
PointsChanged (); /* EMIT SIGNAL */
|
|
}
|
|
|
|
void
|
|
Selection::set (ControlPoint* cp)
|
|
{
|
|
clear_time (); //enforce region/object exclusivity
|
|
clear_tracks(); //enforce object/track exclusivity
|
|
|
|
if (cp->get_selected()) {
|
|
return;
|
|
}
|
|
|
|
for (uint32_t i = 0; i < cp->line().npoints(); ++i) {
|
|
cp->line().nth (i)->set_selected (false);
|
|
}
|
|
|
|
clear_objects ();
|
|
add (cp);
|
|
}
|
|
|
|
void
|
|
Selection::set (Marker* m)
|
|
{
|
|
clear_time (); //enforce region/object exclusivity
|
|
clear_tracks(); //enforce object/track exclusivity
|
|
markers.clear ();
|
|
|
|
add (m);
|
|
}
|
|
|
|
void
|
|
Selection::toggle (Marker* m)
|
|
{
|
|
MarkerSelection::iterator i;
|
|
|
|
if ((i = find (markers.begin(), markers.end(), m)) == markers.end()) {
|
|
add (m);
|
|
} else {
|
|
remove (m);
|
|
}
|
|
}
|
|
|
|
void
|
|
Selection::remove (Marker* m)
|
|
{
|
|
MarkerSelection::iterator i;
|
|
|
|
if ((i = find (markers.begin(), markers.end(), m)) != markers.end()) {
|
|
markers.erase (i);
|
|
MarkersChanged();
|
|
}
|
|
}
|
|
|
|
void
|
|
Selection::add (Marker* m)
|
|
{
|
|
clear_time (); //enforce region/object exclusivity
|
|
clear_tracks(); //enforce object/track exclusivity
|
|
|
|
if (find (markers.begin(), markers.end(), m) == markers.end()) {
|
|
markers.push_back (m);
|
|
MarkersChanged();
|
|
}
|
|
}
|
|
|
|
void
|
|
Selection::add (const list<Marker*>& m)
|
|
{
|
|
clear_time (); //enforce region/object exclusivity
|
|
clear_tracks(); //enforce object/track exclusivity
|
|
|
|
markers.insert (markers.end(), m.begin(), m.end());
|
|
markers.sort ();
|
|
markers.unique ();
|
|
|
|
MarkersChanged ();
|
|
}
|
|
|
|
void
|
|
MarkerSelection::range (framepos_t& s, framepos_t& e)
|
|
{
|
|
s = max_framepos;
|
|
e = 0;
|
|
|
|
for (MarkerSelection::iterator i = begin(); i != end(); ++i) {
|
|
|
|
if ((*i)->position() < s) {
|
|
s = (*i)->position();
|
|
}
|
|
|
|
if ((*i)->position() > e) {
|
|
e = (*i)->position();
|
|
}
|
|
}
|
|
|
|
s = std::min (s, e);
|
|
e = std::max (s, e);
|
|
}
|
|
|
|
XMLNode&
|
|
Selection::get_state () const
|
|
{
|
|
/* XXX: not complete; just sufficient to get track selection state
|
|
so that re-opening plugin windows for editor mixer strips works
|
|
*/
|
|
|
|
XMLNode* node = new XMLNode (X_("Selection"));
|
|
|
|
for (TrackSelection::const_iterator i = tracks.begin(); i != tracks.end(); ++i) {
|
|
RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (*i);
|
|
AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (*i);
|
|
if (rtv) {
|
|
XMLNode* t = node->add_child (X_("RouteView"));
|
|
t->add_property (X_("id"), atoi (rtv->route()->id().to_s().c_str()));
|
|
} else if (atv) {
|
|
XMLNode* t = node->add_child (X_("AutomationView"));
|
|
t->add_property (X_("id"), atoi (atv->parent_route()->id().to_s().c_str()));
|
|
t->add_property (X_("parameter"), EventTypeMap::instance().to_symbol (atv->parameter ()));
|
|
}
|
|
}
|
|
|
|
for (MarkerSelection::const_iterator i = markers.begin(); i != markers.end(); ++i) {
|
|
XMLNode* t = node->add_child (X_("Marker"));
|
|
|
|
bool is_start;
|
|
Location* loc = editor->find_location_from_marker (*i, is_start);
|
|
|
|
t->add_property (X_("id"), atoi (loc->id().to_s().c_str()));
|
|
t->add_property (X_("start"), is_start ? X_("yes") : X_("no"));
|
|
}
|
|
|
|
return *node;
|
|
}
|
|
|
|
int
|
|
Selection::set_state (XMLNode const & node, int)
|
|
{
|
|
if (node.name() != X_("Selection")) {
|
|
return -1;
|
|
}
|
|
|
|
XMLNodeList children = node.children ();
|
|
for (XMLNodeList::const_iterator i = children.begin(); i != children.end(); ++i) {
|
|
if ((*i)->name() == X_("RouteView")) {
|
|
|
|
XMLProperty* prop_id = (*i)->property (X_("id"));
|
|
assert (prop_id);
|
|
PBD::ID id (prop_id->value ());
|
|
RouteTimeAxisView* rtv = editor->get_route_view_by_route_id (id);
|
|
if (rtv) {
|
|
add (rtv);
|
|
}
|
|
|
|
} else if ((*i)->name() == X_("AutomationView")) {
|
|
|
|
XMLProperty* prop_id = (*i)->property (X_("id"));
|
|
XMLProperty* prop_parameter = (*i)->property (X_("parameter"));
|
|
|
|
assert (prop_id);
|
|
assert (prop_parameter);
|
|
|
|
PBD::ID id (prop_id->value ());
|
|
RouteTimeAxisView* rtv = editor->get_route_view_by_route_id (id);
|
|
|
|
if (rtv) {
|
|
boost::shared_ptr<AutomationTimeAxisView> atv = rtv->automation_child (EventTypeMap::instance().new_parameter (prop_parameter->value ()));
|
|
|
|
/* the automation could be for an entity that was never saved
|
|
in the session file. Don't freak out if we can't find
|
|
it.
|
|
*/
|
|
|
|
if (atv) {
|
|
add (atv.get());
|
|
}
|
|
}
|
|
|
|
} else if ((*i)->name() == X_("Marker")) {
|
|
|
|
XMLProperty* prop_id = (*i)->property (X_("id"));
|
|
XMLProperty* prop_start = (*i)->property (X_("start"));
|
|
assert (prop_id);
|
|
assert (prop_start);
|
|
|
|
PBD::ID id (prop_id->value ());
|
|
Marker* m = editor->find_marker_from_location_id (id, string_is_affirmative (prop_start->value ()));
|
|
if (m) {
|
|
add (m);
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
Selection::remove_regions (TimeAxisView* t)
|
|
{
|
|
RegionSelection::iterator i = regions.begin();
|
|
while (i != regions.end ()) {
|
|
RegionSelection::iterator tmp = i;
|
|
++tmp;
|
|
|
|
if (&(*i)->get_time_axis_view() == t) {
|
|
remove (*i);
|
|
}
|
|
|
|
i = tmp;
|
|
}
|
|
}
|
|
|
|
void
|
|
Selection::block_tracks_changed (bool yn)
|
|
{
|
|
_no_tracks_changed = yn;
|
|
}
|