2 Copyright (C) 2002 Paul Davis
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21 #include <sigc++/bind.h>
22 #include "pbd/error.h"
23 #include "pbd/stacktrace.h"
25 #include "ardour/playlist.h"
26 #include "ardour/rc_configuration.h"
28 #include "gui_thread.h"
29 #include "midi_cut_buffer.h"
30 #include "region_view.h"
31 #include "selection.h"
32 #include "selection_templates.h"
33 #include "time_axis_view.h"
34 #include "automation_time_axis.h"
35 #include "public_editor.h"
36 #include "control_point.h"
41 using namespace ARDOUR;
44 struct AudioRangeComparator {
45 bool operator()(AudioRange a, AudioRange b) {
46 return a.start < b.start;
50 Selection::Selection (const PublicEditor* e)
54 , _no_tracks_changed (false)
58 /* we have disambiguate which remove() for the compiler */
60 void (Selection::*track_remove)(TimeAxisView*) = &Selection::remove;
61 TimeAxisView::CatchDeletion.connect (*this, MISSING_INVALIDATOR, boost::bind (track_remove, this, _1), gui_context());
63 void (Selection::*marker_remove)(Marker*) = &Selection::remove;
64 Marker::CatchDeletion.connect (*this, MISSING_INVALIDATOR, boost::bind (marker_remove, this, _1), gui_context());
66 void (Selection::*point_remove)(ControlPoint*) = &Selection::remove;
67 ControlPoint::CatchDeletion.connect (*this, MISSING_INVALIDATOR, boost::bind (point_remove, this, _1), gui_context());
72 Selection::operator= (const Selection& other)
75 regions = other.regions;
76 tracks = other.tracks;
79 midi_regions = other.midi_regions;
80 midi_notes = other.midi_notes;
87 operator== (const Selection& a, const Selection& b)
89 return a.regions == b.regions &&
90 a.tracks == b.tracks &&
93 a.playlists == b.playlists &&
94 a.midi_notes == b.midi_notes &&
95 a.midi_regions == b.midi_regions;
98 /** Clear everything from the Selection */
109 clear_midi_regions ();
114 Selection::dump_region_layers()
116 cerr << "region selection layer dump" << endl;
117 for (RegionSelection::iterator i = regions.begin(); i != regions.end(); ++i) {
118 cerr << "layer: " << (int)(*i)->region()->layer() << endl;
124 Selection::clear_regions ()
126 if (!regions.empty()) {
127 regions.clear_all ();
129 if (Config->get_link_region_and_track_selection()) {
136 Selection::clear_tracks ()
138 if (!tracks.empty()) {
140 if (!_no_tracks_changed) {
147 Selection::clear_midi_notes ()
149 if (!midi_notes.empty()) {
150 for (MidiNoteSelection::iterator x = midi_notes.begin(); x != midi_notes.end(); ++x) {
159 Selection::clear_midi_regions ()
161 if (!midi_regions.empty()) {
162 midi_regions.clear ();
163 MidiRegionsChanged ();
168 Selection::clear_time ()
176 Selection::clear_playlists ()
178 /* Selections own their playlists */
180 for (PlaylistSelection::iterator i = playlists.begin(); i != playlists.end(); ++i) {
181 /* selections own their own regions, which are copies of the "originals". make them go away */
182 (*i)->drop_regions ();
186 if (!playlists.empty()) {
193 Selection::clear_lines ()
195 if (!lines.empty()) {
202 Selection::clear_markers ()
204 if (!markers.empty()) {
211 Selection::toggle (boost::shared_ptr<Playlist> pl)
213 PlaylistSelection::iterator i;
215 if ((i = find (playlists.begin(), playlists.end(), pl)) == playlists.end()) {
217 playlists.push_back(pl);
226 Selection::toggle (const TrackViewList& track_list)
228 for (TrackViewList::const_iterator i = track_list.begin(); i != track_list.end(); ++i) {
234 Selection::toggle (TimeAxisView* track)
236 TrackSelection::iterator i;
238 if ((i = find (tracks.begin(), tracks.end(), track)) == tracks.end()) {
239 tracks.push_back (track);
244 if (!_no_tracks_changed) {
250 Selection::toggle (const MidiNoteSelection& midi_note_list)
252 for (MidiNoteSelection::const_iterator i = midi_note_list.begin(); i != midi_note_list.end(); ++i) {
258 Selection::toggle (MidiCutBuffer* midi)
260 MidiNoteSelection::iterator i;
262 if ((i = find (midi_notes.begin(), midi_notes.end(), midi)) == midi_notes.end()) {
263 midi_notes.push_back (midi);
265 /* remember that we own the MCB */
267 midi_notes.erase (i);
275 Selection::toggle (RegionView* r)
277 RegionSelection::iterator i;
279 if ((i = find (regions.begin(), regions.end(), r)) == regions.end()) {
289 Selection::toggle (MidiRegionView* mrv)
291 MidiRegionSelection::iterator i;
293 if ((i = find (midi_regions.begin(), midi_regions.end(), mrv)) == midi_regions.end()) {
296 midi_regions.erase (i);
299 MidiRegionsChanged ();
303 Selection::toggle (vector<RegionView*>& r)
305 RegionSelection::iterator i;
307 for (vector<RegionView*>::iterator x = r.begin(); x != r.end(); ++x) {
308 if ((i = find (regions.begin(), regions.end(), (*x))) == regions.end()) {
319 Selection::toggle (framepos_t start, framepos_t end)
321 AudioRangeComparator cmp;
323 /* XXX this implementation is incorrect */
325 time.push_back (AudioRange (start, end, next_time_id++));
331 return next_time_id - 1;
335 Selection::add (boost::shared_ptr<Playlist> pl)
337 if (find (playlists.begin(), playlists.end(), pl) == playlists.end()) {
339 playlists.push_back(pl);
345 Selection::add (const list<boost::shared_ptr<Playlist> >& pllist)
347 bool changed = false;
349 for (list<boost::shared_ptr<Playlist> >::const_iterator i = pllist.begin(); i != pllist.end(); ++i) {
350 if (find (playlists.begin(), playlists.end(), (*i)) == playlists.end()) {
352 playlists.push_back (*i);
363 Selection::add (const TrackViewList& track_list)
365 TrackViewList added = tracks.add (track_list);
367 if (!added.empty()) {
368 if (!_no_tracks_changed) {
375 Selection::add (TimeAxisView* track)
378 tr.push_back (track);
383 Selection::add (const MidiNoteSelection& midi_list)
385 const MidiNoteSelection::const_iterator b = midi_list.begin();
386 const MidiNoteSelection::const_iterator e = midi_list.end();
388 if (!midi_list.empty()) {
389 midi_notes.insert (midi_notes.end(), b, e);
395 Selection::add (MidiCutBuffer* midi)
397 /* we take ownership of the MCB */
399 if (find (midi_notes.begin(), midi_notes.end(), midi) == midi_notes.end()) {
400 midi_notes.push_back (midi);
406 Selection::add (vector<RegionView*>& v)
408 /* XXX This method or the add (const RegionSelection&) needs to go
411 bool changed = false;
413 for (vector<RegionView*>::iterator i = v.begin(); i != v.end(); ++i) {
414 if (find (regions.begin(), regions.end(), (*i)) == regions.end()) {
415 changed = regions.add ((*i));
416 if (Config->get_link_region_and_track_selection() && changed) {
417 add (&(*i)->get_time_axis_view());
428 Selection::add (const RegionSelection& rs)
430 /* XXX This method or the add (const vector<RegionView*>&) needs to go
433 bool changed = false;
435 for (RegionSelection::const_iterator i = rs.begin(); i != rs.end(); ++i) {
436 if (find (regions.begin(), regions.end(), (*i)) == regions.end()) {
437 changed = regions.add ((*i));
438 if (Config->get_link_region_and_track_selection() && changed) {
439 add (&(*i)->get_time_axis_view());
450 Selection::add (RegionView* r)
452 if (find (regions.begin(), regions.end(), r) == regions.end()) {
453 bool changed = regions.add (r);
454 if (Config->get_link_region_and_track_selection() && changed) {
455 add (&r->get_time_axis_view());
464 Selection::add (MidiRegionView* mrv)
466 if (find (midi_regions.begin(), midi_regions.end(), mrv) == midi_regions.end()) {
467 midi_regions.push_back (mrv);
468 /* XXX should we do this? */
470 if (Config->get_link_region_and_track_selection()) {
471 add (&mrv->get_time_axis_view());
474 MidiRegionsChanged ();
479 Selection::add (framepos_t start, framepos_t end)
481 AudioRangeComparator cmp;
483 /* XXX this implementation is incorrect */
485 time.push_back (AudioRange (start, end, next_time_id++));
491 return next_time_id - 1;
495 Selection::replace (uint32_t sid, framepos_t start, framepos_t end)
497 for (list<AudioRange>::iterator i = time.begin(); i != time.end(); ++i) {
498 if ((*i).id == sid) {
500 time.push_back (AudioRange(start,end, sid));
502 /* don't consolidate here */
505 AudioRangeComparator cmp;
515 Selection::add (boost::shared_ptr<Evoral::ControlList> cl)
517 boost::shared_ptr<ARDOUR::AutomationList> al
518 = boost::dynamic_pointer_cast<ARDOUR::AutomationList>(cl);
520 warning << "Programming error: Selected list is not an ARDOUR::AutomationList" << endmsg;
523 if (find (lines.begin(), lines.end(), al) == lines.end()) {
524 lines.push_back (al);
530 Selection::remove (TimeAxisView* track)
532 list<TimeAxisView*>::iterator i;
533 if ((i = find (tracks.begin(), tracks.end(), track)) != tracks.end()) {
535 if (!_no_tracks_changed) {
542 Selection::remove (ControlPoint* p)
544 PointSelection::iterator i = find (points.begin(), points.end(), p);
545 if (i != points.end ()) {
551 Selection::remove (const TrackViewList& track_list)
553 bool changed = false;
555 for (TrackViewList::const_iterator i = track_list.begin(); i != track_list.end(); ++i) {
557 TrackViewList::iterator x = find (tracks.begin(), tracks.end(), *i);
558 if (x != tracks.end()) {
565 if (!_no_tracks_changed) {
572 Selection::remove (const MidiNoteSelection& midi_list)
574 bool changed = false;
576 for (MidiNoteSelection::const_iterator i = midi_list.begin(); i != midi_list.end(); ++i) {
578 MidiNoteSelection::iterator x;
580 if ((x = find (midi_notes.begin(), midi_notes.end(), (*i))) != midi_notes.end()) {
581 midi_notes.erase (x);
592 Selection::remove (MidiCutBuffer* midi)
594 MidiNoteSelection::iterator x;
596 if ((x = find (midi_notes.begin(), midi_notes.end(), midi)) != midi_notes.end()) {
597 /* remember that we own the MCB */
599 midi_notes.erase (x);
605 Selection::remove (boost::shared_ptr<Playlist> track)
607 list<boost::shared_ptr<Playlist> >::iterator i;
608 if ((i = find (playlists.begin(), playlists.end(), track)) != playlists.end()) {
615 Selection::remove (const list<boost::shared_ptr<Playlist> >& pllist)
617 bool changed = false;
619 for (list<boost::shared_ptr<Playlist> >::const_iterator i = pllist.begin(); i != pllist.end(); ++i) {
621 list<boost::shared_ptr<Playlist> >::iterator x;
623 if ((x = find (playlists.begin(), playlists.end(), (*i))) != playlists.end()) {
635 Selection::remove (RegionView* r)
637 if (regions.remove (r)) {
641 if (Config->get_link_region_and_track_selection() && !regions.involves (r->get_time_axis_view())) {
642 remove (&r->get_time_axis_view());
647 Selection::remove (MidiRegionView* mrv)
649 MidiRegionSelection::iterator x;
651 if ((x = find (midi_regions.begin(), midi_regions.end(), mrv)) != midi_regions.end()) {
652 midi_regions.erase (x);
653 MidiRegionsChanged ();
657 /* XXX fix this up ? */
658 if (Config->get_link_region_and_track_selection() && !regions.involves (r->get_time_axis_view())) {
659 remove (&r->get_time_axis_view());
666 Selection::remove (uint32_t selection_id)
672 for (list<AudioRange>::iterator i = time.begin(); i != time.end(); ++i) {
673 if ((*i).id == selection_id) {
683 Selection::remove (framepos_t /*start*/, framepos_t /*end*/)
688 Selection::remove (boost::shared_ptr<ARDOUR::AutomationList> ac)
690 AutomationSelection::iterator i;
691 if ((i = find (lines.begin(), lines.end(), ac)) != lines.end()) {
698 Selection::set (TimeAxisView* track)
705 Selection::set (const TrackViewList& track_list)
712 Selection::set (const MidiNoteSelection& midi_list)
719 Selection::set (boost::shared_ptr<Playlist> playlist)
726 Selection::set (const list<boost::shared_ptr<Playlist> >& pllist)
733 Selection::set (const RegionSelection& rs)
737 RegionsChanged(); /* EMIT SIGNAL */
741 Selection::set (MidiRegionView* mrv)
743 clear_midi_regions ();
748 Selection::set (RegionView* r, bool also_clear_tracks)
751 if (also_clear_tracks && !Config->get_link_region_and_track_selection()) {
752 /* clear_regions() will have done this if the link preference
761 Selection::set (vector<RegionView*>& v)
763 bool had_regions = !regions.empty();
767 if (Config->get_link_region_and_track_selection()) {
769 /* there were regions before, so we're changing the
770 * region selection (likely), thus link region/track
771 * selection. relevant tracks will get selected
772 * as we ::add() below.
775 // make sure to deselect any automation selections
782 /** Set the start and end time of the time selection, without changing
783 * the list of tracks it applies to.
786 Selection::set (framepos_t start, framepos_t end)
788 if ((start == 0 && end == 0) || end < start) {
793 time.push_back (AudioRange (start, end, next_time_id++));
795 /* reuse the first entry, and remove all the rest */
797 while (time.size() > 1) {
800 time.front().start = start;
801 time.front().end = end;
808 return time.front().id;
811 /** Set the start and end of the range selection. If more than one range
812 * is currently selected, the start of the earliest range and the end of the
813 * latest range are set. If no range is currently selected, this method
814 * selects a single range from start to end.
816 * @param start New start time.
817 * @param end New end time.
820 Selection::set_preserving_all_ranges (framepos_t start, framepos_t end)
822 if ((start == 0 && end == 0) || (end < start)) {
827 time.push_back (AudioRange (start, end, next_time_id++));
829 time.sort (AudioRangeComparator ());
830 time.front().start = start;
831 time.back().end = end;
840 Selection::set (boost::shared_ptr<Evoral::ControlList> ac)
847 Selection::selected (Marker* m)
849 return find (markers.begin(), markers.end(), m) != markers.end();
853 Selection::selected (TimeAxisView* tv)
855 return find (tracks.begin(), tracks.end(), tv) != tracks.end();
859 Selection::selected (RegionView* rv)
861 return find (regions.begin(), regions.end(), rv) != regions.end();
865 Selection::selected (ControlPoint* cp)
867 return find (points.begin(), points.end(), cp) != points.end();
871 Selection::empty (bool internal_selection)
873 bool object_level_empty = regions.empty () &&
876 playlists.empty () &&
879 playlists.empty () &&
884 if (!internal_selection) {
885 return object_level_empty;
888 /* this is intended to really only apply when using a Selection
892 return object_level_empty && midi_notes.empty();
896 Selection::toggle (ControlPoint* cp)
898 cp->set_selected (!cp->get_selected ());
899 PointSelection::iterator i = find (points.begin(), points.end(), cp);
900 if (i == points.end()) {
901 points.push_back (cp);
906 PointsChanged (); /* EMIT SIGNAL */
910 Selection::toggle (vector<ControlPoint*> const & cps)
912 for (vector<ControlPoint*>::const_iterator i = cps.begin(); i != cps.end(); ++i) {
918 Selection::toggle (list<Selectable*> const & selectables)
922 vector<RegionView*> rvs;
923 vector<ControlPoint*> cps;
925 for (std::list<Selectable*>::const_iterator i = selectables.begin(); i != selectables.end(); ++i) {
926 if ((rv = dynamic_cast<RegionView*> (*i)) != 0) {
928 } else if ((cp = dynamic_cast<ControlPoint*> (*i)) != 0) {
931 fatal << _("programming error: ")
932 << X_("unknown selectable type passed to Selection::toggle()")
948 Selection::set (list<Selectable*> const & selectables)
953 if (Config->get_link_region_and_track_selection ()) {
961 Selection::add (PointSelection const & s)
963 for (PointSelection::const_iterator i = s.begin(); i != s.end(); ++i) {
964 points.push_back (*i);
969 Selection::add (list<Selectable*> const & selectables)
973 vector<RegionView*> rvs;
974 vector<ControlPoint*> cps;
976 for (std::list<Selectable*>::const_iterator i = selectables.begin(); i != selectables.end(); ++i) {
977 if ((rv = dynamic_cast<RegionView*> (*i)) != 0) {
979 } else if ((cp = dynamic_cast<ControlPoint*> (*i)) != 0) {
982 fatal << _("programming error: ")
983 << X_("unknown selectable type passed to Selection::add()")
999 Selection::clear_points ()
1001 if (!points.empty()) {
1008 Selection::add (ControlPoint* cp)
1010 cp->set_selected (true);
1011 points.push_back (cp);
1012 PointsChanged (); /* EMIT SIGNAL */
1016 Selection::add (vector<ControlPoint*> const & cps)
1018 for (vector<ControlPoint*>::const_iterator i = cps.begin(); i != cps.end(); ++i) {
1024 Selection::set (ControlPoint* cp)
1026 if (cp->get_selected()) {
1030 for (uint32_t i = 0; i < cp->line().npoints(); ++i) {
1031 cp->line().nth (i)->set_selected (false);
1039 Selection::set (Marker* m)
1046 Selection::toggle (Marker* m)
1048 MarkerSelection::iterator i;
1050 if ((i = find (markers.begin(), markers.end(), m)) == markers.end()) {
1058 Selection::remove (Marker* m)
1060 MarkerSelection::iterator i;
1062 if ((i = find (markers.begin(), markers.end(), m)) != markers.end()) {
1069 Selection::add (Marker* m)
1071 if (find (markers.begin(), markers.end(), m) == markers.end()) {
1072 markers.push_back (m);
1078 Selection::add (const list<Marker*>& m)
1080 markers.insert (markers.end(), m.begin(), m.end());
1088 MarkerSelection::range (framepos_t& s, framepos_t& e)
1093 for (MarkerSelection::iterator i = begin(); i != end(); ++i) {
1095 if ((*i)->position() < s) {
1096 s = (*i)->position();
1099 if ((*i)->position() > e) {
1100 e = (*i)->position();
1104 s = std::min (s, e);
1105 e = std::max (s, e);
1109 Selection::get_state () const
1111 /* XXX: not complete; just sufficient to get track selection state
1112 so that re-opening plugin windows for editor mixer strips works
1115 XMLNode* node = new XMLNode (X_("Selection"));
1117 for (TrackSelection::const_iterator i = tracks.begin(); i != tracks.end(); ++i) {
1118 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (*i);
1119 AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (*i);
1121 XMLNode* t = node->add_child (X_("RouteView"));
1122 t->add_property (X_("id"), atoi (rtv->route()->id().to_s().c_str()));
1124 XMLNode* t = node->add_child (X_("AutomationView"));
1125 t->add_property (X_("id"), atoi (atv->parent_route()->id().to_s().c_str()));
1126 t->add_property (X_("parameter"), EventTypeMap::instance().to_symbol (atv->parameter ()));
1130 for (MarkerSelection::const_iterator i = markers.begin(); i != markers.end(); ++i) {
1131 XMLNode* t = node->add_child (X_("Marker"));
1134 Location* loc = editor->find_location_from_marker (*i, is_start);
1136 t->add_property (X_("id"), atoi (loc->id().to_s().c_str()));
1137 t->add_property (X_("start"), is_start ? X_("yes") : X_("no"));
1144 Selection::set_state (XMLNode const & node, int)
1146 if (node.name() != X_("Selection")) {
1150 XMLNodeList children = node.children ();
1151 for (XMLNodeList::const_iterator i = children.begin(); i != children.end(); ++i) {
1152 if ((*i)->name() == X_("RouteView")) {
1154 XMLProperty* prop_id = (*i)->property (X_("id"));
1156 PBD::ID id (prop_id->value ());
1157 RouteTimeAxisView* rtv = editor->get_route_view_by_route_id (id);
1162 } else if ((*i)->name() == X_("AutomationView")) {
1164 XMLProperty* prop_id = (*i)->property (X_("id"));
1165 XMLProperty* prop_parameter = (*i)->property (X_("parameter"));
1168 assert (prop_parameter);
1170 PBD::ID id (prop_id->value ());
1171 RouteTimeAxisView* rtv = editor->get_route_view_by_route_id (id);
1174 boost::shared_ptr<AutomationTimeAxisView> atv = rtv->automation_child (EventTypeMap::instance().new_parameter (prop_parameter->value ()));
1176 /* the automation could be for an entity that was never saved
1177 in the session file. Don't freak out if we can't find
1186 } else if ((*i)->name() == X_("Marker")) {
1188 XMLProperty* prop_id = (*i)->property (X_("id"));
1189 XMLProperty* prop_start = (*i)->property (X_("start"));
1191 assert (prop_start);
1193 PBD::ID id (prop_id->value ());
1194 Marker* m = editor->find_marker_from_location_id (id, string_is_affirmative (prop_start->value ()));
1207 Selection::remove_regions (TimeAxisView* t)
1209 RegionSelection::iterator i = regions.begin();
1210 while (i != regions.end ()) {
1211 RegionSelection::iterator tmp = i;
1214 if (&(*i)->get_time_axis_view() == t) {
1223 Selection::block_tracks_changed (bool yn)
1225 _no_tracks_changed = yn;