2 * Copyright (C) 2005 Taybin Rutkin <taybin@taybin.com>
3 * Copyright (C) 2006-2014 David Robillard <d@drobilla.net>
4 * Copyright (C) 2006-2017 Paul Davis <paul@linuxaudiosystems.com>
5 * Copyright (C) 2007-2012 Carl Hetherington <carl@carlh.net>
6 * Copyright (C) 2014-2017 Nick Mainsbridge <mainsbridge@gmail.com>
7 * Copyright (C) 2014-2018 Ben Loftis <ben@harrisonconsoles.com>
8 * Copyright (C) 2014-2018 Robin Gareus <robin@gareus.org>
9 * Copyright (C) 2015-2017 Tim Mayberry <mojofunk@gmail.com>
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
21 * You should have received a copy of the GNU General Public License along
22 * with this program; if not, write to the Free Software Foundation, Inc.,
23 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
27 #include <sigc++/bind.h>
29 #include "pbd/error.h"
30 #include "pbd/stacktrace.h"
31 #include "pbd/types_convert.h"
33 #include "ardour/evoral_types_convert.h"
34 #include "ardour/playlist.h"
35 #include "ardour/rc_configuration.h"
36 #include "ardour/selection.h"
38 #include "control_protocol/control_protocol.h"
40 #include "audio_region_view.h"
42 #include "gui_thread.h"
43 #include "midi_cut_buffer.h"
44 #include "region_gain_line.h"
45 #include "region_view.h"
46 #include "selection.h"
47 #include "selection_templates.h"
48 #include "time_axis_view.h"
49 #include "automation_time_axis.h"
50 #include "public_editor.h"
51 #include "control_point.h"
52 #include "vca_time_axis.h"
57 using namespace ARDOUR;
60 struct AudioRangeComparator {
61 bool operator()(AudioRange a, AudioRange b) {
62 return a.start < b.start;
66 Selection::Selection (const PublicEditor* e, bool mls)
70 , manage_libardour_selection (mls)
74 /* we have disambiguate which remove() for the compiler */
76 void (Selection::*marker_remove)(ArdourMarker*) = &Selection::remove;
77 ArdourMarker::CatchDeletion.connect (*this, MISSING_INVALIDATOR, boost::bind (marker_remove, this, _1), gui_context());
79 void (Selection::*point_remove)(ControlPoint*) = &Selection::remove;
80 ControlPoint::CatchDeletion.connect (*this, MISSING_INVALIDATOR, boost::bind (point_remove, this, _1), gui_context());
85 Selection::operator= (const Selection& other)
88 regions = other.regions;
89 tracks = other.tracks;
92 midi_regions = other.midi_regions;
93 midi_notes = other.midi_notes;
100 operator== (const Selection& a, const Selection& b)
102 return a.regions == b.regions &&
103 a.tracks == b.tracks &&
105 a.lines == b.lines &&
106 a.playlists == b.playlists &&
107 a.midi_notes == b.midi_notes &&
108 a.midi_regions == b.midi_regions;
111 /** Clear everything from the Selection */
122 clear_midi_regions ();
124 pending_midi_note_selection.clear();
128 Selection::clear_objects (bool with_signal)
130 clear_regions (with_signal);
131 clear_points (with_signal);
132 clear_lines(with_signal);
133 clear_playlists (with_signal);
134 clear_midi_notes (with_signal);
135 clear_midi_regions (with_signal);
139 Selection::clear_time (bool with_signal)
148 Selection::dump_region_layers()
150 cerr << "region selection layer dump" << endl;
151 for (RegionSelection::iterator i = regions.begin(); i != regions.end(); ++i) {
152 cerr << "layer: " << (int)(*i)->region()->layer() << endl;
158 Selection::clear_regions (bool with_signal)
160 if (!regions.empty()) {
161 regions.clear_all ();
169 Selection::clear_midi_notes (bool with_signal)
171 if (!midi_notes.empty()) {
172 for (MidiNoteSelection::iterator x = midi_notes.begin(); x != midi_notes.end(); ++x) {
181 // clear note selections for MRV's that have note selections
182 // this will cause the MRV to be removed from the list
183 for (MidiRegionSelection::iterator i = midi_regions.begin();
184 i != midi_regions.end();) {
185 MidiRegionSelection::iterator tmp = i;
187 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*i);
189 mrv->clear_selection();
196 Selection::clear_midi_regions (bool with_signal)
198 if (!midi_regions.empty()) {
199 midi_regions.clear ();
201 MidiRegionsChanged ();
207 Selection::clear_playlists (bool with_signal)
209 /* Selections own their playlists */
211 for (PlaylistSelection::iterator i = playlists.begin(); i != playlists.end(); ++i) {
212 /* selections own their own regions, which are copies of the "originals". make them go away */
213 (*i)->drop_regions ();
217 if (!playlists.empty()) {
226 Selection::clear_lines (bool with_signal)
228 if (!lines.empty()) {
237 Selection::clear_markers (bool with_signal)
239 if (!markers.empty()) {
248 Selection::toggle (boost::shared_ptr<Playlist> pl)
250 clear_time(); // enforce object/range exclusivity
251 clear_tracks(); // enforce object/track exclusivity
253 PlaylistSelection::iterator i;
255 if ((i = find (playlists.begin(), playlists.end(), pl)) == playlists.end()) {
257 playlists.push_back(pl);
266 Selection::toggle (const MidiNoteSelection& midi_note_list)
268 clear_time(); // enforce object/range exclusivity
269 clear_tracks(); // enforce object/track exclusivity
271 for (MidiNoteSelection::const_iterator i = midi_note_list.begin(); i != midi_note_list.end(); ++i) {
277 Selection::toggle (MidiCutBuffer* midi)
279 MidiNoteSelection::iterator i;
281 if ((i = find (midi_notes.begin(), midi_notes.end(), midi)) == midi_notes.end()) {
282 midi_notes.push_back (midi);
284 /* remember that we own the MCB */
286 midi_notes.erase (i);
294 Selection::toggle (RegionView* r)
296 clear_time(); // enforce object/range exclusivity
297 clear_tracks(); // enforce object/track exclusivity
299 RegionSelection::iterator i;
301 if ((i = find (regions.begin(), regions.end(), r)) == regions.end()) {
311 Selection::toggle (MidiRegionView* mrv)
313 clear_time(); // enforce object/range exclusivity
314 clear_tracks(); // enforce object/track exclusivity
316 MidiRegionSelection::iterator i;
318 if ((i = find (midi_regions.begin(), midi_regions.end(), mrv)) == midi_regions.end()) {
321 midi_regions.erase (i);
324 MidiRegionsChanged ();
328 Selection::toggle (vector<RegionView*>& r)
330 clear_time(); // enforce object/range exclusivity
331 clear_tracks(); // enforce object/track exclusivity
333 RegionSelection::iterator i;
335 for (vector<RegionView*>::iterator x = r.begin(); x != r.end(); ++x) {
336 if ((i = find (regions.begin(), regions.end(), (*x))) == regions.end()) {
347 Selection::toggle (samplepos_t start, samplepos_t end)
349 clear_objects(); // enforce object/range exclusivity
351 AudioRangeComparator cmp;
353 /* XXX this implementation is incorrect */
355 time.push_back (AudioRange (start, end, ++next_time_id));
365 Selection::add (boost::shared_ptr<Playlist> pl)
368 if (find (playlists.begin(), playlists.end(), pl) == playlists.end()) {
369 clear_time(); // enforce object/range exclusivity
370 clear_tracks(); // enforce object/track exclusivity
372 playlists.push_back(pl);
378 Selection::add (const list<boost::shared_ptr<Playlist> >& pllist)
380 bool changed = false;
382 for (list<boost::shared_ptr<Playlist> >::const_iterator i = pllist.begin(); i != pllist.end(); ++i) {
383 if (find (playlists.begin(), playlists.end(), (*i)) == playlists.end()) {
385 playlists.push_back (*i);
391 clear_time(); // enforce object/range exclusivity
392 clear_tracks(); // enforce object/track exclusivity
398 Selection::add (const MidiNoteSelection& midi_list)
400 const MidiNoteSelection::const_iterator b = midi_list.begin();
401 const MidiNoteSelection::const_iterator e = midi_list.end();
403 if (!midi_list.empty()) {
404 clear_time(); // enforce object/range exclusivity
405 clear_tracks(); // enforce object/track exclusivity
406 midi_notes.insert (midi_notes.end(), b, e);
412 Selection::add (MidiCutBuffer* midi)
414 /* we take ownership of the MCB */
416 if (find (midi_notes.begin(), midi_notes.end(), midi) == midi_notes.end()) {
417 midi_notes.push_back (midi);
423 Selection::add (vector<RegionView*>& v)
425 /* XXX This method or the add (const RegionSelection&) needs to go
428 bool changed = false;
430 for (vector<RegionView*>::iterator i = v.begin(); i != v.end(); ++i) {
431 if (find (regions.begin(), regions.end(), (*i)) == regions.end()) {
432 changed = regions.add ((*i));
437 clear_time(); // enforce object/range exclusivity
438 clear_tracks(); // enforce object/track exclusivity
444 Selection::add (const RegionSelection& rs)
446 /* XXX This method or the add (const vector<RegionView*>&) needs to go
449 bool changed = false;
451 for (RegionSelection::const_iterator i = rs.begin(); i != rs.end(); ++i) {
452 if (find (regions.begin(), regions.end(), (*i)) == regions.end()) {
453 changed = regions.add ((*i));
458 clear_time(); // enforce object/range exclusivity
459 clear_tracks(); // enforce object/track exclusivity
465 Selection::add (RegionView* r)
467 if (find (regions.begin(), regions.end(), r) == regions.end()) {
468 bool changed = regions.add (r);
470 clear_time(); // enforce object/range exclusivity
471 clear_tracks(); // enforce object/track exclusivity
478 Selection::add (MidiRegionView* mrv)
480 DEBUG_TRACE(DEBUG::Selection, string_compose("Selection::add MRV %1\n", mrv));
482 clear_time(); // enforce object/range exclusivity
483 clear_tracks(); // enforce object/track exclusivity
485 if (find (midi_regions.begin(), midi_regions.end(), mrv) == midi_regions.end()) {
486 midi_regions.push_back (mrv);
487 /* XXX should we do this? */
488 MidiRegionsChanged ();
493 Selection::add (samplepos_t start, samplepos_t end)
495 clear_objects(); // enforce object/range exclusivity
497 AudioRangeComparator cmp;
499 /* XXX this implementation is incorrect */
501 time.push_back (AudioRange (start, end, ++next_time_id));
511 Selection::move_time (samplecnt_t distance)
517 for (list<AudioRange>::iterator i = time.begin(); i != time.end(); ++i) {
518 (*i).start += distance;
519 (*i).end += distance;
526 Selection::replace (uint32_t sid, samplepos_t start, samplepos_t end)
528 clear_objects(); // enforce object/range exclusivity
530 for (list<AudioRange>::iterator i = time.begin(); i != time.end(); ++i) {
531 if ((*i).id == sid) {
533 time.push_back (AudioRange(start,end, sid));
535 /* don't consolidate here */
538 AudioRangeComparator cmp;
548 Selection::add (boost::shared_ptr<Evoral::ControlList> cl)
550 boost::shared_ptr<ARDOUR::AutomationList> al = boost::dynamic_pointer_cast<ARDOUR::AutomationList>(cl);
553 warning << "Programming error: Selected list is not an ARDOUR::AutomationList" << endmsg;
558 clear_time(); // enforce object/range exclusivity
559 clear_tracks(); // enforce object/track exclusivity
562 /* The original may change so we must store a copy (not a pointer) here.
563 * e.g AutomationLine rewrites the list with gain mapping.
564 * the downside is that we can't perfom duplicate checks.
565 * This code was changed in response to #6842
567 lines.push_back (boost::shared_ptr<ARDOUR::AutomationList> (new ARDOUR::AutomationList(*al)));
572 Selection::remove (ControlPoint* p)
574 PointSelection::iterator i = find (points.begin(), points.end(), p);
575 if (i != points.end ()) {
581 Selection::remove (const MidiNoteSelection& midi_list)
583 bool changed = false;
585 for (MidiNoteSelection::const_iterator i = midi_list.begin(); i != midi_list.end(); ++i) {
587 MidiNoteSelection::iterator x;
589 if ((x = find (midi_notes.begin(), midi_notes.end(), (*i))) != midi_notes.end()) {
590 midi_notes.erase (x);
601 Selection::remove (MidiCutBuffer* midi)
603 MidiNoteSelection::iterator x;
605 if ((x = find (midi_notes.begin(), midi_notes.end(), midi)) != midi_notes.end()) {
606 /* remember that we own the MCB */
608 midi_notes.erase (x);
614 Selection::remove (boost::shared_ptr<Playlist> track)
616 list<boost::shared_ptr<Playlist> >::iterator i;
617 if ((i = find (playlists.begin(), playlists.end(), track)) != playlists.end()) {
624 Selection::remove (const list<boost::shared_ptr<Playlist> >& pllist)
626 bool changed = false;
628 for (list<boost::shared_ptr<Playlist> >::const_iterator i = pllist.begin(); i != pllist.end(); ++i) {
630 list<boost::shared_ptr<Playlist> >::iterator x;
632 if ((x = find (playlists.begin(), playlists.end(), (*i))) != playlists.end()) {
644 Selection::remove (RegionView* r)
646 if (regions.remove (r)) {
652 Selection::remove (MidiRegionView* mrv)
654 DEBUG_TRACE(DEBUG::Selection, string_compose("Selection::remove MRV %1\n", mrv));
656 MidiRegionSelection::iterator x;
658 if ((x = find (midi_regions.begin(), midi_regions.end(), mrv)) != midi_regions.end()) {
659 midi_regions.erase (x);
660 MidiRegionsChanged ();
665 Selection::remove (uint32_t selection_id)
671 for (list<AudioRange>::iterator i = time.begin(); i != time.end(); ++i) {
672 if ((*i).id == selection_id) {
682 Selection::remove (samplepos_t /*start*/, samplepos_t /*end*/)
687 Selection::remove (boost::shared_ptr<ARDOUR::AutomationList> ac)
689 AutomationSelection::iterator i;
690 if ((i = find (lines.begin(), lines.end(), ac)) != lines.end()) {
697 Selection::set (const MidiNoteSelection& midi_list)
699 if (!midi_list.empty()) {
700 clear_time (); // enforce region/object exclusivity
701 clear_tracks(); // enforce object/track exclusivity
708 Selection::set (boost::shared_ptr<Playlist> playlist)
711 clear_time (); // enforce region/object exclusivity
712 clear_tracks(); // enforce object/track exclusivity
719 Selection::set (const list<boost::shared_ptr<Playlist> >& pllist)
721 if (!pllist.empty()) {
722 clear_time(); // enforce region/object exclusivity
729 Selection::set (const RegionSelection& rs)
732 clear_time(); // enforce region/object exclusivity
733 clear_tracks(); // enforce object/track exclusivity
737 RegionsChanged(); /* EMIT SIGNAL */
741 Selection::set (MidiRegionView* mrv)
744 clear_time(); // enforce region/object exclusivity
745 clear_tracks(); // enforce object/track exclusivity
752 Selection::set (RegionView* r, bool /*also_clear_tracks*/)
755 clear_time(); // enforce region/object exclusivity
756 clear_tracks(); // enforce object/track exclusivity
763 Selection::set (vector<RegionView*>& v)
766 clear_time(); // enforce region/object exclusivity
767 clear_tracks(); // enforce object/track exclusivity
775 /** Set the start and end time of the time selection, without changing
776 * the list of tracks it applies to.
779 Selection::set (samplepos_t start, samplepos_t end)
781 clear_objects(); // enforce region/object exclusivity
784 if ((start == 0 && end == 0) || end < start) {
789 time.push_back (AudioRange (start, end, ++next_time_id));
791 /* reuse the first entry, and remove all the rest */
793 while (time.size() > 1) {
796 time.front().start = start;
797 time.front().end = end;
804 return time.front().id;
807 /** Set the start and end of the range selection. If more than one range
808 * is currently selected, the start of the earliest range and the end of the
809 * latest range are set. If no range is currently selected, this method
810 * selects a single range from start to end.
812 * @param start New start time.
813 * @param end New end time.
816 Selection::set_preserving_all_ranges (samplepos_t start, samplepos_t end)
818 clear_objects(); // enforce region/object exclusivity
820 if ((start == 0 && end == 0) || (end < start)) {
825 time.push_back (AudioRange (start, end, ++next_time_id));
827 time.sort (AudioRangeComparator ());
828 time.front().start = start;
829 time.back().end = end;
838 Selection::set (boost::shared_ptr<Evoral::ControlList> ac)
840 clear_time(); // enforce region/object exclusivity
841 clear_tracks(); // enforce object/track exclusivity
848 Selection::selected (ArdourMarker* m) const
850 return find (markers.begin(), markers.end(), m) != markers.end();
854 Selection::selected (RegionView* rv) const
856 return find (regions.begin(), regions.end(), rv) != regions.end();
860 Selection::selected (ControlPoint* cp) const
862 return find (points.begin(), points.end(), cp) != points.end();
866 Selection::empty (bool internal_selection)
868 bool object_level_empty = regions.empty () &&
871 playlists.empty () &&
874 playlists.empty () &&
879 if (!internal_selection) {
880 return object_level_empty;
883 /* this is intended to really only apply when using a Selection
887 return object_level_empty && midi_notes.empty() && points.empty();
891 Selection::toggle (ControlPoint* cp)
893 clear_time(); // enforce region/object exclusivity
894 clear_tracks(); // enforce object/track exclusivity
896 cp->set_selected (!cp->selected ());
897 PointSelection::iterator i = find (points.begin(), points.end(), cp);
898 if (i == points.end()) {
899 points.push_back (cp);
904 PointsChanged (); /* EMIT SIGNAL */
908 Selection::toggle (vector<ControlPoint*> const & cps)
910 clear_time(); // enforce region/object exclusivity
911 clear_tracks(); // enforce object/track exclusivity
913 for (vector<ControlPoint*>::const_iterator i = cps.begin(); i != cps.end(); ++i) {
919 Selection::toggle (list<Selectable*> const & selectables)
921 clear_time(); // enforce region/object exclusivity
922 clear_tracks(); // enforce object/track exclusivity
926 vector<RegionView*> rvs;
927 vector<ControlPoint*> cps;
929 for (std::list<Selectable*>::const_iterator i = selectables.begin(); i != selectables.end(); ++i) {
930 if ((rv = dynamic_cast<RegionView*> (*i)) != 0) {
932 } else if ((cp = dynamic_cast<ControlPoint*> (*i)) != 0) {
935 fatal << _("programming error: ")
936 << X_("unknown selectable type passed to Selection::toggle()")
938 abort(); /*NOTREACHED*/
952 Selection::set (list<Selectable*> const & selectables)
954 clear_time (); // enforce region/object exclusivity
955 clear_tracks(); // enforce object/track exclusivity
962 Selection::add (PointSelection const & s)
964 clear_time (); // enforce region/object exclusivity
965 clear_tracks(); // enforce object/track exclusivity
967 for (PointSelection::const_iterator i = s.begin(); i != s.end(); ++i) {
968 points.push_back (*i);
973 Selection::add (list<Selectable*> const & selectables)
975 clear_time (); // enforce region/object exclusivity
976 clear_tracks(); // enforce object/track exclusivity
980 vector<RegionView*> rvs;
981 vector<ControlPoint*> cps;
983 for (std::list<Selectable*>::const_iterator i = selectables.begin(); i != selectables.end(); ++i) {
984 if ((rv = dynamic_cast<RegionView*> (*i)) != 0) {
986 } else if ((cp = dynamic_cast<ControlPoint*> (*i)) != 0) {
989 fatal << _("programming error: ")
990 << X_("unknown selectable type passed to Selection::add()")
992 abort(); /*NOTREACHED*/
1006 Selection::clear_points (bool with_signal)
1008 if (!points.empty()) {
1017 Selection::add (ControlPoint* cp)
1019 clear_time (); // enforce region/object exclusivity
1020 clear_tracks(); // enforce object/track exclusivity
1022 cp->set_selected (true);
1023 points.push_back (cp);
1024 PointsChanged (); /* EMIT SIGNAL */
1028 Selection::add (vector<ControlPoint*> const & cps)
1030 clear_time (); // enforce region/object exclusivity
1031 clear_tracks(); // enforce object/track exclusivity
1033 for (vector<ControlPoint*>::const_iterator i = cps.begin(); i != cps.end(); ++i) {
1034 (*i)->set_selected (true);
1035 points.push_back (*i);
1037 PointsChanged (); /* EMIT SIGNAL */
1041 Selection::set (ControlPoint* cp)
1043 clear_time (); // enforce region/object exclusivity
1044 clear_tracks(); // enforce object/track exclusivity
1046 if (cp->selected () && points.size () == 1) {
1050 for (uint32_t i = 0; i < cp->line().npoints(); ++i) {
1051 cp->line().nth (i)->set_selected (false);
1059 Selection::set (ArdourMarker* m)
1061 clear_time (); // enforce region/object exclusivity
1062 clear_tracks(); // enforce object/track exclusivity
1069 Selection::toggle (ArdourMarker* m)
1071 MarkerSelection::iterator i;
1073 if ((i = find (markers.begin(), markers.end(), m)) == markers.end()) {
1081 Selection::remove (ArdourMarker* m)
1083 MarkerSelection::iterator i;
1085 if ((i = find (markers.begin(), markers.end(), m)) != markers.end()) {
1092 Selection::add (ArdourMarker* m)
1094 clear_time (); //enforce region/object exclusivity
1095 clear_tracks(); //enforce object/track exclusivity
1097 if (find (markers.begin(), markers.end(), m) == markers.end()) {
1098 markers.push_back (m);
1104 Selection::add (const list<ArdourMarker*>& m)
1106 clear_time (); // enforce region/object exclusivity
1107 clear_tracks(); // enforce object/track exclusivity
1109 markers.insert (markers.end(), m.begin(), m.end());
1117 MarkerSelection::range (samplepos_t& s, samplepos_t& e)
1122 for (MarkerSelection::iterator i = begin(); i != end(); ++i) {
1124 if ((*i)->position() < s) {
1125 s = (*i)->position();
1128 if ((*i)->position() > e) {
1129 e = (*i)->position();
1133 s = std::min (s, e);
1134 e = std::max (s, e);
1138 Selection::get_state () const
1140 /* XXX: not complete; just sufficient to get track selection state
1141 so that re-opening plugin windows for editor mixer strips works
1144 XMLNode* node = new XMLNode (X_("Selection"));
1146 for (TrackSelection::const_iterator i = tracks.begin(); i != tracks.end(); ++i) {
1147 StripableTimeAxisView* stv = dynamic_cast<StripableTimeAxisView*> (*i);
1148 AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (*i);
1150 XMLNode* t = node->add_child (X_("StripableView"));
1151 t->set_property (X_("id"), stv->stripable()->id ());
1153 XMLNode* t = node->add_child (X_("AutomationView"));
1154 t->set_property (X_("id"), atv->parent_stripable()->id ());
1155 t->set_property (X_("parameter"), EventTypeMap::instance().to_symbol (atv->parameter ()));
1159 if (!regions.empty()) {
1160 XMLNode* parent = node->add_child (X_("Regions"));
1161 for (RegionSelection::const_iterator i = regions.begin(); i != regions.end(); ++i) {
1162 XMLNode* r = parent->add_child (X_("Region"));
1163 r->set_property (X_("id"), (*i)->region ()->id ());
1167 /* midi region views have thir own internal selection. */
1168 list<pair<PBD::ID, std::set<boost::shared_ptr<Evoral::Note<Temporal::Beats> > > > > rid_notes;
1169 editor->get_per_region_note_selection (rid_notes);
1171 list<pair<PBD::ID, std::set<boost::shared_ptr<Evoral::Note<Temporal::Beats> > > > >::iterator rn_it;
1172 for (rn_it = rid_notes.begin(); rn_it != rid_notes.end(); ++rn_it) {
1173 XMLNode* n = node->add_child (X_("MIDINotes"));
1174 n->set_property (X_("region-id"), (*rn_it).first);
1176 for (std::set<boost::shared_ptr<Evoral::Note<Temporal::Beats> > >::iterator i = (*rn_it).second.begin(); i != (*rn_it).second.end(); ++i) {
1177 XMLNode* nc = n->add_child(X_("note"));
1178 nc->set_property(X_("note-id"), (*i)->id());
1182 for (PointSelection::const_iterator i = points.begin(); i != points.end(); ++i) {
1183 AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (&(*i)->line().trackview);
1186 XMLNode* r = node->add_child (X_("ControlPoint"));
1187 r->set_property (X_("type"), "track");
1188 r->set_property (X_("route-id"), atv->parent_stripable()->id ());
1189 r->set_property (X_("automation-list-id"), (*i)->line().the_list()->id ());
1190 r->set_property (X_("parameter"), EventTypeMap::instance().to_symbol ((*i)->line().the_list()->parameter ()));
1191 r->set_property (X_("view-index"), (*i)->view_index());
1195 AudioRegionGainLine* argl = dynamic_cast<AudioRegionGainLine*> (&(*i)->line());
1197 XMLNode* r = node->add_child (X_("ControlPoint"));
1198 r->set_property (X_("type"), "region");
1199 r->set_property (X_("region-id"), argl->region_view ().region ()->id ());
1200 r->set_property (X_("view-index"), (*i)->view_index());
1205 for (TimeSelection::const_iterator i = time.begin(); i != time.end(); ++i) {
1206 XMLNode* t = node->add_child (X_("AudioRange"));
1207 t->set_property (X_("start"), (*i).start);
1208 t->set_property (X_("end"), (*i).end);
1211 for (MarkerSelection::const_iterator i = markers.begin(); i != markers.end(); ++i) {
1212 XMLNode* t = node->add_child (X_("Marker"));
1215 Location* loc = editor->find_location_from_marker (*i, is_start);
1217 t->set_property (X_("id"), loc->id());
1218 t->set_property (X_("start"), is_start);
1225 Selection::set_state (XMLNode const & node, int)
1227 if (node.name() != X_("Selection")) {
1232 clear_midi_notes ();
1237 /* NOTE: stripable/time-axis-view selection is saved/restored by
1238 * ARDOUR::CoreSelection, not this Selection object
1242 XMLNodeList children = node.children ();
1244 for (XMLNodeList::const_iterator i = children.begin(); i != children.end(); ++i) {
1245 if ((*i)->name() == X_("Regions")) {
1246 RegionSelection selected_regions;
1247 XMLNodeList children = (*i)->children ();
1248 for (XMLNodeList::const_iterator ci = children.begin(); ci != children.end(); ++ci) {
1251 if (!(*ci)->get_property (X_("id"), id)) {
1256 editor->get_regionviews_by_id (id, rs);
1259 for (RegionSelection::const_iterator i = rs.begin(); i != rs.end(); ++i) {
1260 selected_regions.push_back (*i);
1264 regionviews haven't been constructed - stash the region IDs
1265 so we can identify them in Editor::region_view_added ()
1267 regions.pending.push_back (id);
1271 if (!selected_regions.empty()) {
1272 add (selected_regions);
1275 } else if ((*i)->name() == X_("MIDINotes")) {
1277 if (!(*i)->get_property (X_("region-id"), id)) {
1283 editor->get_regionviews_by_id (id, rs); // there could be more than one
1285 std::list<Evoral::event_id_t> notes;
1286 XMLNodeList children = (*i)->children ();
1288 for (XMLNodeList::const_iterator ci = children.begin(); ci != children.end(); ++ci) {
1289 Evoral::event_id_t id;
1290 if ((*ci)->get_property (X_ ("note-id"), id)) {
1291 notes.push_back (id);
1295 for (RegionSelection::iterator rsi = rs.begin(); rsi != rs.end(); ++rsi) {
1296 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (*rsi);
1298 mrv->select_notes(notes);
1303 /* regionviews containing these notes don't yet exist on the canvas.*/
1304 pending_midi_note_selection.push_back (make_pair (id, notes));
1307 } else if ((*i)->name() == X_("ControlPoint")) {
1308 XMLProperty const * prop_type = (*i)->property (X_("type"));
1312 if (prop_type->value () == "track") {
1317 uint32_t view_index;
1319 if (!(*i)->get_property (X_("route-id"), route_id) ||
1320 !(*i)->get_property (X_("automation-list-id"), alist_id) ||
1321 !(*i)->get_property (X_("parameter"), param) ||
1322 !(*i)->get_property (X_("view-index"), view_index)) {
1326 StripableTimeAxisView* stv = editor->get_stripable_time_axis_by_id (route_id);
1327 vector <ControlPoint *> cps;
1330 boost::shared_ptr<AutomationTimeAxisView> atv = stv->automation_child (EventTypeMap::instance().from_symbol (param));
1332 list<boost::shared_ptr<AutomationLine> > lines = atv->lines();
1333 for (list<boost::shared_ptr<AutomationLine> > ::iterator li = lines.begin(); li != lines.end(); ++li) {
1334 if ((*li)->the_list()->id() == alist_id) {
1335 ControlPoint* cp = (*li)->nth(view_index);
1347 } else if (prop_type->value () == "region") {
1350 uint32_t view_index;
1351 if (!(*i)->get_property (X_("region-id"), region_id) ||
1352 !(*i)->get_property (X_("view-index"), view_index)) {
1357 editor->get_regionviews_by_id (region_id, rs);
1360 vector <ControlPoint *> cps;
1361 for (RegionSelection::iterator rsi = rs.begin(); rsi != rs.end(); ++rsi) {
1362 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (*rsi);
1364 boost::shared_ptr<AudioRegionGainLine> gl = arv->get_gain_line ();
1365 ControlPoint* cp = gl->nth(view_index);
1378 } else if ((*i)->name() == X_("AudioRange")) {
1382 if (!(*i)->get_property (X_("start"), start) || !(*i)->get_property (X_("end"), end)) {
1388 } else if ((*i)->name() == X_("AutomationView")) {
1392 if (!(*i)->get_property (X_("id"), id) || !(*i)->get_property (X_("parameter"), param)) {
1396 StripableTimeAxisView* stv = editor->get_stripable_time_axis_by_id (id);
1399 boost::shared_ptr<AutomationTimeAxisView> atv = stv->automation_child (EventTypeMap::instance().from_symbol (param));
1401 /* the automation could be for an entity that was never saved
1402 in the session file. Don't freak out if we can't find
1411 } else if ((*i)->name() == X_("Marker")) {
1414 if (!(*i)->get_property (X_("id"), id) || !(*i)->get_property (X_("start"), is_start)) {
1418 ArdourMarker* m = editor->find_marker_from_location_id (id, is_start);
1431 Selection::remove_regions (TimeAxisView* t)
1433 RegionSelection::iterator i = regions.begin();
1434 while (i != regions.end ()) {
1435 RegionSelection::iterator tmp = i;
1438 if (&(*i)->get_time_axis_view() == t) {
1446 /* TIME AXIS VIEW ... proxy for Stripable/Controllable
1448 * public methods just modify the CoreSelection; PresentationInfo::Changed will
1449 * trigger Selection::core_selection_changed() and we will update our own data
1454 Selection::toggle (const TrackViewList& track_list)
1456 TrackViewList t = add_grouped_tracks (track_list);
1458 CoreSelection& selection (editor->session()->selection());
1459 PresentationInfo::ChangeSuspender cs;
1461 for (TrackSelection::const_iterator i = t.begin(); i != t.end(); ++i) {
1462 boost::shared_ptr<Stripable> s = (*i)->stripable ();
1463 boost::shared_ptr<AutomationControl> c = (*i)->control ();
1464 selection.toggle (s, c);
1469 Selection::toggle (TimeAxisView* track)
1472 tr.push_back (track);
1477 Selection::add (TrackViewList const & track_list)
1479 TrackViewList t = add_grouped_tracks (track_list);
1481 CoreSelection& selection (editor->session()->selection());
1482 PresentationInfo::ChangeSuspender cs;
1484 for (TrackSelection::const_iterator i = t.begin(); i != t.end(); ++i) {
1485 boost::shared_ptr<Stripable> s = (*i)->stripable ();
1486 boost::shared_ptr<AutomationControl> c = (*i)->control ();
1487 selection.add (s, c);
1492 Selection::add (TimeAxisView* track)
1495 tr.push_back (track);
1500 Selection::remove (TimeAxisView* track)
1503 tvl.push_back (track);
1508 Selection::remove (const TrackViewList& t)
1510 CoreSelection& selection (editor->session()->selection());
1511 PresentationInfo::ChangeSuspender cs;
1513 for (TrackSelection::const_iterator i = t.begin(); i != t.end(); ++i) {
1514 boost::shared_ptr<Stripable> s = (*i)->stripable ();
1515 boost::shared_ptr<AutomationControl> c = (*i)->control ();
1516 selection.remove (s, c);
1521 Selection::set (TimeAxisView* track)
1524 tvl.push_back (track);
1529 Selection::set (const TrackViewList& track_list)
1531 TrackViewList t = add_grouped_tracks (track_list);
1533 CoreSelection& selection (editor->session()->selection());
1535 #if 1 // crazy optmization hack
1536 /* check is the selection actually changed, ignore NO-OPs
1538 * There are excessive calls from EditorRoutes::selection_changed():
1539 * Every click calls selection_changed() even if it doesn't change.
1540 * Also re-ordering tracks calls into this due to gtk's odd DnD signal
1541 * messaging (row removed, re-added).
1543 * Re-ordering a row results in at least 2 calls to selection_changed()
1544 * without actual change. Calling selection.clear_stripables()
1545 * and re-adding the same tracks every time in turn emits changed signals.
1547 bool changed = false;
1548 CoreSelection::StripableAutomationControls sac;
1549 selection.get_stripables (sac);
1550 for (TrackSelection::const_iterator i = t.begin(); i != t.end(); ++i) {
1551 boost::shared_ptr<Stripable> s = (*i)->stripable ();
1552 boost::shared_ptr<AutomationControl> c = (*i)->control ();
1554 for (CoreSelection::StripableAutomationControls::iterator j = sac.begin (); j != sac.end (); ++j) {
1555 if (j->stripable == s && j->controllable == c) {
1566 if (!changed && sac.size() == 0) {
1571 PresentationInfo::ChangeSuspender cs;
1573 selection.clear_stripables ();
1575 for (TrackSelection::const_iterator i = t.begin(); i != t.end(); ++i) {
1576 boost::shared_ptr<Stripable> s = (*i)->stripable ();
1577 boost::shared_ptr<AutomationControl> c = (*i)->control ();
1578 selection.add (s, c);
1583 Selection::clear_tracks (bool)
1585 if (!manage_libardour_selection) {
1589 Session* s = editor->session();
1591 CoreSelection& selection (s->selection());
1592 selection.clear_stripables ();
1597 Selection::selected (TimeAxisView* tv) const
1599 Session* session = editor->session();
1605 CoreSelection& selection (session->selection());
1606 boost::shared_ptr<Stripable> s = tv->stripable ();
1607 boost::shared_ptr<AutomationControl> c = tv->control ();
1610 return selection.selected (c);
1613 return selection.selected (s);
1617 Selection::add_grouped_tracks (TrackViewList const & t)
1619 TrackViewList added;
1621 for (TrackSelection::const_iterator i = t.begin(); i != t.end(); ++i) {
1622 if (dynamic_cast<VCATimeAxisView*> (*i)) {
1626 /* select anything in the same select-enabled route group */
1627 ARDOUR::RouteGroup* rg = (*i)->route_group ();
1629 if (rg && rg->is_active() && rg->is_select ()) {
1631 TrackViewList tr = editor->axis_views_from_routes (rg->route_list ());
1633 for (TrackViewList::iterator j = tr.begin(); j != tr.end(); ++j) {
1635 /* Do not add the trackview passed in as an
1636 * argument, because we want that to be on the
1641 if (!added.contains (*j)) {
1642 added.push_back (*j);
1649 /* now add the the trackview's passed in as actual arguments */
1650 added.insert (added.end(), t.begin(), t.end());
1656 static void dump_tracks (Selection const & s)
1658 cerr << "--TRACKS [" << s.tracks.size() << ']' << ":\n";
1659 for (TrackViewList::const_iterator x = s.tracks.begin(); x != s.tracks.end(); ++x) {
1660 cerr << (*x)->name() << ' ' << (*x)->stripable() << " C = " << (*x)->control() << endl;
1667 Selection::core_selection_changed (PropertyChange const & what_changed)
1671 pc.add (Properties::selected);
1673 if (!what_changed.contains (pc)) {
1677 CoreSelection& selection (editor->session()->selection());
1679 if (selection.selected()) {
1680 clear_objects(); // enforce object/range exclusivity
1683 tracks.clear (); // clear stage for whatever tracks are now selected (maybe none)
1685 CoreSelection::StripableAutomationControls sac;
1686 selection.get_stripables (sac);
1688 for (CoreSelection::StripableAutomationControls::const_iterator i = sac.begin(); i != sac.end(); ++i) {
1691 if ((*i).controllable) {
1692 av = editor->axis_view_by_control ((*i).controllable);
1694 av = editor->axis_view_by_stripable ((*i).stripable);
1697 tav = dynamic_cast<TimeAxisView*>(av);
1699 tracks.push_back (tav);