maintain CoreSelection order in GUI track selection
[ardour.git] / gtk2_ardour / selection.cc
1 /*
2     Copyright (C) 2002 Paul Davis
3
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.
8
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.
13
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.
17
18 */
19
20 #include <algorithm>
21 #include <sigc++/bind.h>
22
23 #include "pbd/error.h"
24 #include "pbd/stacktrace.h"
25 #include "pbd/types_convert.h"
26
27 #include "ardour/evoral_types_convert.h"
28 #include "ardour/playlist.h"
29 #include "ardour/rc_configuration.h"
30 #include "ardour/selection.h"
31
32 #include "control_protocol/control_protocol.h"
33
34 #include "audio_region_view.h"
35 #include "debug.h"
36 #include "gui_thread.h"
37 #include "midi_cut_buffer.h"
38 #include "region_gain_line.h"
39 #include "region_view.h"
40 #include "selection.h"
41 #include "selection_templates.h"
42 #include "time_axis_view.h"
43 #include "automation_time_axis.h"
44 #include "public_editor.h"
45 #include "control_point.h"
46 #include "vca_time_axis.h"
47
48 #include "pbd/i18n.h"
49
50 using namespace std;
51 using namespace ARDOUR;
52 using namespace PBD;
53
54 struct AudioRangeComparator {
55         bool operator()(AudioRange a, AudioRange b) {
56                 return a.start < b.start;
57         }
58 };
59
60 Selection::Selection (const PublicEditor* e)
61         : tracks (e)
62         , editor (e)
63         , next_time_id (0)
64 {
65         clear ();
66
67         /* we have disambiguate which remove() for the compiler */
68
69         void (Selection::*marker_remove)(ArdourMarker*) = &Selection::remove;
70         ArdourMarker::CatchDeletion.connect (*this, MISSING_INVALIDATOR, boost::bind (marker_remove, this, _1), gui_context());
71
72         void (Selection::*point_remove)(ControlPoint*) = &Selection::remove;
73         ControlPoint::CatchDeletion.connect (*this, MISSING_INVALIDATOR, boost::bind (point_remove, this, _1), gui_context());
74 }
75
76 #if 0
77 Selection&
78 Selection::operator= (const Selection& other)
79 {
80         if (&other != this) {
81                 regions = other.regions;
82                 tracks = other.tracks;
83                 time = other.time;
84                 lines = other.lines;
85                 midi_regions = other.midi_regions;
86                 midi_notes = other.midi_notes;
87         }
88         return *this;
89 }
90 #endif
91
92 bool
93 operator== (const Selection& a, const Selection& b)
94 {
95         return a.regions == b.regions &&
96                 a.tracks == b.tracks &&
97                 a.time == b.time &&
98                 a.lines == b.lines &&
99                 a.playlists == b.playlists &&
100                 a.midi_notes == b.midi_notes &&
101                 a.midi_regions == b.midi_regions;
102 }
103
104 /** Clear everything from the Selection */
105 void
106 Selection::clear ()
107 {
108         clear_tracks ();
109         clear_regions ();
110         clear_points ();
111         clear_lines();
112         clear_time ();
113         clear_playlists ();
114         clear_midi_notes ();
115         clear_midi_regions ();
116         clear_markers ();
117         pending_midi_note_selection.clear();
118 }
119
120 void
121 Selection::clear_objects (bool with_signal)
122 {
123         clear_regions (with_signal);
124         clear_points (with_signal);
125         clear_lines(with_signal);
126         clear_playlists (with_signal);
127         clear_midi_notes (with_signal);
128         clear_midi_regions (with_signal);
129 }
130
131 void
132 Selection::clear_time (bool with_signal)
133 {
134         time.clear();
135         if (with_signal) {
136                 TimeChanged ();
137         }
138 }
139
140 void
141 Selection::dump_region_layers()
142 {
143         cerr << "region selection layer dump" << endl;
144         for (RegionSelection::iterator i = regions.begin(); i != regions.end(); ++i) {
145                 cerr << "layer: " << (int)(*i)->region()->layer() << endl;
146         }
147 }
148
149
150 void
151 Selection::clear_regions (bool with_signal)
152 {
153         if (!regions.empty()) {
154                 regions.clear_all ();
155                 if (with_signal) {
156                         RegionsChanged();
157                 }
158         }
159 }
160
161 void
162 Selection::clear_midi_notes (bool with_signal)
163 {
164         if (!midi_notes.empty()) {
165                 for (MidiNoteSelection::iterator x = midi_notes.begin(); x != midi_notes.end(); ++x) {
166                         delete *x;
167                 }
168                 midi_notes.clear ();
169                 if (with_signal) {
170                         MidiNotesChanged ();
171                 }
172         }
173
174         // clear note selections for MRV's that have note selections
175         // this will cause the MRV to be removed from the list
176         for (MidiRegionSelection::iterator i = midi_regions.begin();
177              i != midi_regions.end();) {
178                 MidiRegionSelection::iterator tmp = i;
179                 ++tmp;
180                 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*i);
181                 if (mrv) {
182                         mrv->clear_selection();
183                 }
184                 i = tmp;
185         }
186 }
187
188 void
189 Selection::clear_midi_regions (bool with_signal)
190 {
191         if (!midi_regions.empty()) {
192                 midi_regions.clear ();
193                 if (with_signal) {
194                         MidiRegionsChanged ();
195                 }
196         }
197 }
198
199 void
200 Selection::clear_playlists (bool with_signal)
201 {
202         /* Selections own their playlists */
203
204         for (PlaylistSelection::iterator i = playlists.begin(); i != playlists.end(); ++i) {
205                 /* selections own their own regions, which are copies of the "originals". make them go away */
206                 (*i)->drop_regions ();
207                 (*i)->release ();
208         }
209
210         if (!playlists.empty()) {
211                 playlists.clear ();
212                 if (with_signal) {
213                         PlaylistsChanged();
214                 }
215         }
216 }
217
218 void
219 Selection::clear_lines (bool with_signal)
220 {
221         if (!lines.empty()) {
222                 lines.clear ();
223                 if (with_signal) {
224                         LinesChanged();
225                 }
226         }
227 }
228
229 void
230 Selection::clear_markers (bool with_signal)
231 {
232         if (!markers.empty()) {
233                 markers.clear ();
234                 if (with_signal) {
235                         MarkersChanged();
236                 }
237         }
238 }
239
240 void
241 Selection::toggle (boost::shared_ptr<Playlist> pl)
242 {
243         clear_time();  //enforce object/range exclusivity
244         clear_tracks();  //enforce object/track exclusivity
245
246         PlaylistSelection::iterator i;
247
248         if ((i = find (playlists.begin(), playlists.end(), pl)) == playlists.end()) {
249                 pl->use ();
250                 playlists.push_back(pl);
251         } else {
252                 playlists.erase (i);
253         }
254
255         PlaylistsChanged ();
256 }
257
258 void
259 Selection::toggle (const MidiNoteSelection& midi_note_list)
260 {
261         clear_time();  //enforce object/range exclusivity
262         clear_tracks();  //enforce object/track exclusivity
263
264         for (MidiNoteSelection::const_iterator i = midi_note_list.begin(); i != midi_note_list.end(); ++i) {
265                 toggle ((*i));
266         }
267 }
268
269 void
270 Selection::toggle (MidiCutBuffer* midi)
271 {
272         MidiNoteSelection::iterator i;
273
274         if ((i = find (midi_notes.begin(), midi_notes.end(), midi)) == midi_notes.end()) {
275                 midi_notes.push_back (midi);
276         } else {
277                 /* remember that we own the MCB */
278                 delete *i;
279                 midi_notes.erase (i);
280         }
281
282         MidiNotesChanged();
283 }
284
285
286 void
287 Selection::toggle (RegionView* r)
288 {
289         clear_time();  //enforce object/range exclusivity
290         clear_tracks();  //enforce object/track exclusivity
291
292         RegionSelection::iterator i;
293
294         if ((i = find (regions.begin(), regions.end(), r)) == regions.end()) {
295                 add (r);
296         } else {
297                 remove (*i);
298         }
299
300         RegionsChanged ();
301 }
302
303 void
304 Selection::toggle (MidiRegionView* mrv)
305 {
306         clear_time();   //enforce object/range exclusivity
307         clear_tracks();  //enforce object/track exclusivity
308
309         MidiRegionSelection::iterator i;
310
311         if ((i = find (midi_regions.begin(), midi_regions.end(), mrv)) == midi_regions.end()) {
312                 add (mrv);
313         } else {
314                 midi_regions.erase (i);
315         }
316
317         MidiRegionsChanged ();
318 }
319
320 void
321 Selection::toggle (vector<RegionView*>& r)
322 {
323         clear_time();  //enforce object/range exclusivity
324         clear_tracks();  //enforce object/track exclusivity
325
326         RegionSelection::iterator i;
327
328         for (vector<RegionView*>::iterator x = r.begin(); x != r.end(); ++x) {
329                 if ((i = find (regions.begin(), regions.end(), (*x))) == regions.end()) {
330                         add ((*x));
331                 } else {
332                         remove (*x);
333                 }
334         }
335
336         RegionsChanged ();
337 }
338
339 long
340 Selection::toggle (framepos_t start, framepos_t end)
341 {
342         clear_objects();  //enforce object/range exclusivity
343
344         AudioRangeComparator cmp;
345
346         /* XXX this implementation is incorrect */
347
348         time.push_back (AudioRange (start, end, ++next_time_id));
349         time.consolidate ();
350         time.sort (cmp);
351
352         TimeChanged ();
353
354         return next_time_id;
355 }
356
357 void
358 Selection::add (boost::shared_ptr<Playlist> pl)
359 {
360
361         if (find (playlists.begin(), playlists.end(), pl) == playlists.end()) {
362                 clear_time();  //enforce object/range exclusivity
363                 clear_tracks();  //enforce object/track exclusivity
364                 pl->use ();
365                 playlists.push_back(pl);
366                 PlaylistsChanged ();
367         }
368 }
369
370 void
371 Selection::add (const list<boost::shared_ptr<Playlist> >& pllist)
372 {
373         bool changed = false;
374
375         for (list<boost::shared_ptr<Playlist> >::const_iterator i = pllist.begin(); i != pllist.end(); ++i) {
376                 if (find (playlists.begin(), playlists.end(), (*i)) == playlists.end()) {
377                         (*i)->use ();
378                         playlists.push_back (*i);
379                         changed = true;
380                 }
381         }
382
383         if (changed) {
384                 clear_time();  //enforce object/range exclusivity
385                 clear_tracks();  //enforce object/track exclusivity
386                 PlaylistsChanged ();
387         }
388 }
389
390 void
391 Selection::add (const MidiNoteSelection& midi_list)
392 {
393         const MidiNoteSelection::const_iterator b = midi_list.begin();
394         const MidiNoteSelection::const_iterator e = midi_list.end();
395
396         if (!midi_list.empty()) {
397                 clear_time();  //enforce object/range exclusivity
398                 clear_tracks();  //enforce object/track exclusivity
399                 midi_notes.insert (midi_notes.end(), b, e);
400                 MidiNotesChanged ();
401         }
402 }
403
404 void
405 Selection::add (MidiCutBuffer* midi)
406 {
407         /* we take ownership of the MCB */
408
409         if (find (midi_notes.begin(), midi_notes.end(), midi) == midi_notes.end()) {
410                 midi_notes.push_back (midi);
411                 MidiNotesChanged ();
412         }
413 }
414
415 void
416 Selection::add (vector<RegionView*>& v)
417 {
418         /* XXX This method or the add (const RegionSelection&) needs to go
419          */
420
421         bool changed = false;
422
423         for (vector<RegionView*>::iterator i = v.begin(); i != v.end(); ++i) {
424                 if (find (regions.begin(), regions.end(), (*i)) == regions.end()) {
425                         changed = regions.add ((*i));
426                 }
427         }
428
429         if (changed) {
430                 clear_time();  //enforce object/range exclusivity
431                 clear_tracks();  //enforce object/track exclusivity
432                 RegionsChanged ();
433         }
434 }
435
436 void
437 Selection::add (const RegionSelection& rs)
438 {
439         /* XXX This method or the add (const vector<RegionView*>&) needs to go
440          */
441
442         bool changed = false;
443
444         for (RegionSelection::const_iterator i = rs.begin(); i != rs.end(); ++i) {
445                 if (find (regions.begin(), regions.end(), (*i)) == regions.end()) {
446                         changed = regions.add ((*i));
447                 }
448         }
449
450         if (changed) {
451                 clear_time();  //enforce object/range exclusivity
452                 clear_tracks();  //enforce object/track exclusivity
453                 RegionsChanged ();
454         }
455 }
456
457 void
458 Selection::add (RegionView* r)
459 {
460         if (find (regions.begin(), regions.end(), r) == regions.end()) {
461                 bool changed = regions.add (r);
462                 if (changed) {
463                         clear_time();  //enforce object/range exclusivity
464                         clear_tracks();  //enforce object/track exclusivity
465                         RegionsChanged ();
466                 }
467         }
468 }
469
470 void
471 Selection::add (MidiRegionView* mrv)
472 {
473         DEBUG_TRACE(DEBUG::Selection, string_compose("Selection::add MRV %1\n", mrv));
474
475         clear_time();  //enforce object/range exclusivity
476         clear_tracks();  //enforce object/track exclusivity
477
478         if (find (midi_regions.begin(), midi_regions.end(), mrv) == midi_regions.end()) {
479                 midi_regions.push_back (mrv);
480                 /* XXX should we do this? */
481                 MidiRegionsChanged ();
482         }
483 }
484
485 long
486 Selection::add (framepos_t start, framepos_t end)
487 {
488         clear_objects();  //enforce object/range exclusivity
489
490         AudioRangeComparator cmp;
491
492         /* XXX this implementation is incorrect */
493
494         time.push_back (AudioRange (start, end, ++next_time_id));
495         time.consolidate ();
496         time.sort (cmp);
497
498         TimeChanged ();
499
500         return next_time_id;
501 }
502
503 void
504 Selection::move_time (framecnt_t distance)
505 {
506         if (distance == 0) {
507                 return;
508         }
509
510         for (list<AudioRange>::iterator i = time.begin(); i != time.end(); ++i) {
511                 (*i).start += distance;
512                 (*i).end += distance;
513         }
514
515         TimeChanged ();
516 }
517
518 void
519 Selection::replace (uint32_t sid, framepos_t start, framepos_t end)
520 {
521         clear_objects();  //enforce object/range exclusivity
522
523         for (list<AudioRange>::iterator i = time.begin(); i != time.end(); ++i) {
524                 if ((*i).id == sid) {
525                         time.erase (i);
526                         time.push_back (AudioRange(start,end, sid));
527
528                         /* don't consolidate here */
529
530
531                         AudioRangeComparator cmp;
532                         time.sort (cmp);
533
534                         TimeChanged ();
535                         break;
536                 }
537         }
538 }
539
540 void
541 Selection::add (boost::shared_ptr<Evoral::ControlList> cl)
542 {
543         boost::shared_ptr<ARDOUR::AutomationList> al = boost::dynamic_pointer_cast<ARDOUR::AutomationList>(cl);
544
545         if (!al) {
546                 warning << "Programming error: Selected list is not an ARDOUR::AutomationList" << endmsg;
547                 return;
548         }
549
550         if (!cl->empty()) {
551                 clear_time();  //enforce object/range exclusivity
552                 clear_tracks();  //enforce object/track exclusivity
553         }
554
555         /* The original may change so we must store a copy (not a pointer) here.
556          * e.g AutomationLine rewrites the list with gain mapping.
557          * the downside is that we can't perfom duplicate checks.
558          * This code was changed in response to #6842
559          */
560         lines.push_back (boost::shared_ptr<ARDOUR::AutomationList> (new ARDOUR::AutomationList(*al)));
561         LinesChanged();
562 }
563
564 void
565 Selection::remove (ControlPoint* p)
566 {
567         PointSelection::iterator i = find (points.begin(), points.end(), p);
568         if (i != points.end ()) {
569                 points.erase (i);
570         }
571 }
572
573 void
574 Selection::remove (const MidiNoteSelection& midi_list)
575 {
576         bool changed = false;
577
578         for (MidiNoteSelection::const_iterator i = midi_list.begin(); i != midi_list.end(); ++i) {
579
580                 MidiNoteSelection::iterator x;
581
582                 if ((x = find (midi_notes.begin(), midi_notes.end(), (*i))) != midi_notes.end()) {
583                         midi_notes.erase (x);
584                         changed = true;
585                 }
586         }
587
588         if (changed) {
589                 MidiNotesChanged();
590         }
591 }
592
593 void
594 Selection::remove (MidiCutBuffer* midi)
595 {
596         MidiNoteSelection::iterator x;
597
598         if ((x = find (midi_notes.begin(), midi_notes.end(), midi)) != midi_notes.end()) {
599                 /* remember that we own the MCB */
600                 delete *x;
601                 midi_notes.erase (x);
602                 MidiNotesChanged ();
603         }
604 }
605
606 void
607 Selection::remove (boost::shared_ptr<Playlist> track)
608 {
609         list<boost::shared_ptr<Playlist> >::iterator i;
610         if ((i = find (playlists.begin(), playlists.end(), track)) != playlists.end()) {
611                 playlists.erase (i);
612                 PlaylistsChanged();
613         }
614 }
615
616 void
617 Selection::remove (const list<boost::shared_ptr<Playlist> >& pllist)
618 {
619         bool changed = false;
620
621         for (list<boost::shared_ptr<Playlist> >::const_iterator i = pllist.begin(); i != pllist.end(); ++i) {
622
623                 list<boost::shared_ptr<Playlist> >::iterator x;
624
625                 if ((x = find (playlists.begin(), playlists.end(), (*i))) != playlists.end()) {
626                         playlists.erase (x);
627                         changed = true;
628                 }
629         }
630
631         if (changed) {
632                 PlaylistsChanged();
633         }
634 }
635
636 void
637 Selection::remove (RegionView* r)
638 {
639         if (regions.remove (r)) {
640                 RegionsChanged ();
641         }
642 }
643
644 void
645 Selection::remove (MidiRegionView* mrv)
646 {
647         DEBUG_TRACE(DEBUG::Selection, string_compose("Selection::remove MRV %1\n", mrv));
648
649         MidiRegionSelection::iterator x;
650
651         if ((x = find (midi_regions.begin(), midi_regions.end(), mrv)) != midi_regions.end()) {
652                 midi_regions.erase (x);
653                 MidiRegionsChanged ();
654         }
655 }
656
657 void
658 Selection::remove (uint32_t selection_id)
659 {
660         if (time.empty()) {
661                 return;
662         }
663
664         for (list<AudioRange>::iterator i = time.begin(); i != time.end(); ++i) {
665                 if ((*i).id == selection_id) {
666                         time.erase (i);
667
668                         TimeChanged ();
669                         break;
670                 }
671         }
672 }
673
674 void
675 Selection::remove (framepos_t /*start*/, framepos_t /*end*/)
676 {
677 }
678
679 void
680 Selection::remove (boost::shared_ptr<ARDOUR::AutomationList> ac)
681 {
682         AutomationSelection::iterator i;
683         if ((i = find (lines.begin(), lines.end(), ac)) != lines.end()) {
684                 lines.erase (i);
685                 LinesChanged();
686         }
687 }
688
689 void
690 Selection::set (const MidiNoteSelection& midi_list)
691 {
692         if (!midi_list.empty()) {
693                 clear_time ();  //enforce region/object exclusivity
694                 clear_tracks();  //enforce object/track exclusivity
695         }
696         clear_objects ();
697         add (midi_list);
698 }
699
700 void
701 Selection::set (boost::shared_ptr<Playlist> playlist)
702 {
703         if (playlist) {
704                 clear_time ();  //enforce region/object exclusivity
705                 clear_tracks();  //enforce object/track exclusivity
706         }
707         clear_objects ();
708         add (playlist);
709 }
710
711 void
712 Selection::set (const list<boost::shared_ptr<Playlist> >& pllist)
713 {
714         if (!pllist.empty()) {
715                 clear_time();  //enforce region/object exclusivity
716         }
717         clear_objects ();
718         add (pllist);
719 }
720
721 void
722 Selection::set (const RegionSelection& rs)
723 {
724         if (!rs.empty()) {
725                 clear_time();  //enforce region/object exclusivity
726                 clear_tracks();  //enforce object/track exclusivity
727         }
728         clear_objects();
729         regions = rs;
730         RegionsChanged(); /* EMIT SIGNAL */
731 }
732
733 void
734 Selection::set (MidiRegionView* mrv)
735 {
736         if (mrv) {
737                 clear_time();  //enforce region/object exclusivity
738                 clear_tracks();  //enforce object/track exclusivity
739         }
740         clear_objects ();
741         add (mrv);
742 }
743
744 void
745 Selection::set (RegionView* r, bool /*also_clear_tracks*/)
746 {
747         if (r) {
748                 clear_time();  //enforce region/object exclusivity
749                 clear_tracks();  //enforce object/track exclusivity
750         }
751         clear_objects ();
752         add (r);
753 }
754
755 void
756 Selection::set (vector<RegionView*>& v)
757 {
758         if (!v.empty()) {
759                 clear_time();  //enforce region/object exclusivity
760                 clear_tracks();  //enforce object/track exclusivity
761         }
762
763         clear_objects();
764
765         add (v);
766 }
767
768 /** Set the start and end time of the time selection, without changing
769  *  the list of tracks it applies to.
770  */
771 long
772 Selection::set (framepos_t start, framepos_t end)
773 {
774         clear_objects();  //enforce region/object exclusivity
775         clear_time();
776
777         if ((start == 0 && end == 0) || end < start) {
778                 return 0;
779         }
780
781         if (time.empty()) {
782                 time.push_back (AudioRange (start, end, ++next_time_id));
783         } else {
784                 /* reuse the first entry, and remove all the rest */
785
786                 while (time.size() > 1) {
787                         time.pop_front();
788                 }
789                 time.front().start = start;
790                 time.front().end = end;
791         }
792
793         time.consolidate ();
794
795         TimeChanged ();
796
797         return time.front().id;
798 }
799
800 /** Set the start and end of the range selection.  If more than one range
801  *  is currently selected, the start of the earliest range and the end of the
802  *  latest range are set.  If no range is currently selected, this method
803  *  selects a single range from start to end.
804  *
805  *  @param start New start time.
806  *  @param end New end time.
807  */
808 void
809 Selection::set_preserving_all_ranges (framepos_t start, framepos_t end)
810 {
811         clear_objects();  //enforce region/object exclusivity
812
813         if ((start == 0 && end == 0) || (end < start)) {
814                 return;
815         }
816
817         if (time.empty ()) {
818                 time.push_back (AudioRange (start, end, ++next_time_id));
819         } else {
820                 time.sort (AudioRangeComparator ());
821                 time.front().start = start;
822                 time.back().end = end;
823         }
824
825         time.consolidate ();
826
827         TimeChanged ();
828 }
829
830 void
831 Selection::set (boost::shared_ptr<Evoral::ControlList> ac)
832 {
833         clear_time();  //enforce region/object exclusivity
834         clear_tracks();  //enforce object/track exclusivity
835         clear_objects();
836
837         add (ac);
838 }
839
840 bool
841 Selection::selected (ArdourMarker* m) const
842 {
843         return find (markers.begin(), markers.end(), m) != markers.end();
844 }
845
846 bool
847 Selection::selected (RegionView* rv) const
848 {
849         return find (regions.begin(), regions.end(), rv) != regions.end();
850 }
851
852 bool
853 Selection::selected (ControlPoint* cp) const
854 {
855         return find (points.begin(), points.end(), cp) != points.end();
856 }
857
858 bool
859 Selection::empty (bool internal_selection)
860 {
861         bool object_level_empty =  regions.empty () &&
862                 tracks.empty () &&
863                 points.empty () &&
864                 playlists.empty () &&
865                 lines.empty () &&
866                 time.empty () &&
867                 playlists.empty () &&
868                 markers.empty() &&
869                 midi_regions.empty()
870                 ;
871
872         if (!internal_selection) {
873                 return object_level_empty;
874         }
875
876         /* this is intended to really only apply when using a Selection
877            as a cut buffer.
878         */
879
880         return object_level_empty && midi_notes.empty() && points.empty();
881 }
882
883 void
884 Selection::toggle (ControlPoint* cp)
885 {
886         clear_time();  //enforce region/object exclusivity
887         clear_tracks();  //enforce object/track exclusivity
888
889         cp->set_selected (!cp->selected ());
890         PointSelection::iterator i = find (points.begin(), points.end(), cp);
891         if (i == points.end()) {
892                 points.push_back (cp);
893         } else {
894                 points.erase (i);
895         }
896
897         PointsChanged (); /* EMIT SIGNAL */
898 }
899
900 void
901 Selection::toggle (vector<ControlPoint*> const & cps)
902 {
903         clear_time();  //enforce region/object exclusivity
904         clear_tracks();  //enforce object/track exclusivity
905
906         for (vector<ControlPoint*>::const_iterator i = cps.begin(); i != cps.end(); ++i) {
907                 toggle (*i);
908         }
909 }
910
911 void
912 Selection::toggle (list<Selectable*> const & selectables)
913 {
914         clear_time();  //enforce region/object exclusivity
915         clear_tracks();  //enforce object/track exclusivity
916
917         RegionView* rv;
918         ControlPoint* cp;
919         vector<RegionView*> rvs;
920         vector<ControlPoint*> cps;
921
922         for (std::list<Selectable*>::const_iterator i = selectables.begin(); i != selectables.end(); ++i) {
923                 if ((rv = dynamic_cast<RegionView*> (*i)) != 0) {
924                         rvs.push_back (rv);
925                 } else if ((cp = dynamic_cast<ControlPoint*> (*i)) != 0) {
926                         cps.push_back (cp);
927                 } else {
928                         fatal << _("programming error: ")
929                               << X_("unknown selectable type passed to Selection::toggle()")
930                               << endmsg;
931                         abort(); /*NOTREACHED*/
932                 }
933         }
934
935         if (!rvs.empty()) {
936                 toggle (rvs);
937         }
938
939         if (!cps.empty()) {
940                 toggle (cps);
941         }
942 }
943
944 void
945 Selection::set (list<Selectable*> const & selectables)
946 {
947         clear_time ();  //enforce region/object exclusivity
948         clear_tracks();  //enforce object/track exclusivity
949         clear_objects ();
950
951         add (selectables);
952 }
953
954 void
955 Selection::add (PointSelection const & s)
956 {
957         clear_time ();  //enforce region/object exclusivity
958         clear_tracks();  //enforce object/track exclusivity
959
960         for (PointSelection::const_iterator i = s.begin(); i != s.end(); ++i) {
961                 points.push_back (*i);
962         }
963 }
964
965 void
966 Selection::add (list<Selectable*> const & selectables)
967 {
968         clear_time ();  //enforce region/object exclusivity
969         clear_tracks();  //enforce object/track exclusivity
970
971         RegionView* rv;
972         ControlPoint* cp;
973         vector<RegionView*> rvs;
974         vector<ControlPoint*> cps;
975
976         for (std::list<Selectable*>::const_iterator i = selectables.begin(); i != selectables.end(); ++i) {
977                 if ((rv = dynamic_cast<RegionView*> (*i)) != 0) {
978                         rvs.push_back (rv);
979                 } else if ((cp = dynamic_cast<ControlPoint*> (*i)) != 0) {
980                         cps.push_back (cp);
981                 } else {
982                         fatal << _("programming error: ")
983                               << X_("unknown selectable type passed to Selection::add()")
984                               << endmsg;
985                         abort(); /*NOTREACHED*/
986                 }
987         }
988
989         if (!rvs.empty()) {
990                 add (rvs);
991         }
992
993         if (!cps.empty()) {
994                 add (cps);
995         }
996 }
997
998 void
999 Selection::clear_points (bool with_signal)
1000 {
1001         if (!points.empty()) {
1002                 points.clear ();
1003                 if (with_signal) {
1004                         PointsChanged ();
1005                 }
1006         }
1007 }
1008
1009 void
1010 Selection::add (ControlPoint* cp)
1011 {
1012         clear_time ();  //enforce region/object exclusivity
1013         clear_tracks();  //enforce object/track exclusivity
1014
1015         cp->set_selected (true);
1016         points.push_back (cp);
1017         PointsChanged (); /* EMIT SIGNAL */
1018 }
1019
1020 void
1021 Selection::add (vector<ControlPoint*> const & cps)
1022 {
1023         clear_time ();  //enforce region/object exclusivity
1024         clear_tracks();  //enforce object/track exclusivity
1025
1026         for (vector<ControlPoint*>::const_iterator i = cps.begin(); i != cps.end(); ++i) {
1027                 (*i)->set_selected (true);
1028                 points.push_back (*i);
1029         }
1030         PointsChanged (); /* EMIT SIGNAL */
1031 }
1032
1033 void
1034 Selection::set (ControlPoint* cp)
1035 {
1036         clear_time ();  //enforce region/object exclusivity
1037         clear_tracks();  //enforce object/track exclusivity
1038
1039         if (cp->selected () && points.size () == 1) {
1040                 return;
1041         }
1042
1043         for (uint32_t i = 0; i < cp->line().npoints(); ++i) {
1044                 cp->line().nth (i)->set_selected (false);
1045         }
1046
1047         clear_objects ();
1048         add (cp);
1049 }
1050
1051 void
1052 Selection::set (ArdourMarker* m)
1053 {
1054         clear_time ();  //enforce region/object exclusivity
1055         clear_tracks();  //enforce object/track exclusivity
1056         markers.clear ();
1057
1058         add (m);
1059 }
1060
1061 void
1062 Selection::toggle (ArdourMarker* m)
1063 {
1064         MarkerSelection::iterator i;
1065
1066         if ((i = find (markers.begin(), markers.end(), m)) == markers.end()) {
1067                 add (m);
1068         } else {
1069                 remove (m);
1070         }
1071 }
1072
1073 void
1074 Selection::remove (ArdourMarker* m)
1075 {
1076         MarkerSelection::iterator i;
1077
1078         if ((i = find (markers.begin(), markers.end(), m)) != markers.end()) {
1079                 markers.erase (i);
1080                 MarkersChanged();
1081         }
1082 }
1083
1084 void
1085 Selection::add (ArdourMarker* m)
1086 {
1087         clear_time ();  //enforce region/object exclusivity
1088         clear_tracks();  //enforce object/track exclusivity
1089
1090         if (find (markers.begin(), markers.end(), m) == markers.end()) {
1091                 markers.push_back (m);
1092                 MarkersChanged();
1093         }
1094 }
1095
1096 void
1097 Selection::add (const list<ArdourMarker*>& m)
1098 {
1099         clear_time ();  //enforce region/object exclusivity
1100         clear_tracks();  //enforce object/track exclusivity
1101
1102         markers.insert (markers.end(), m.begin(), m.end());
1103         markers.sort ();
1104         markers.unique ();
1105
1106         MarkersChanged ();
1107 }
1108
1109 void
1110 MarkerSelection::range (framepos_t& s, framepos_t& e)
1111 {
1112         s = max_framepos;
1113         e = 0;
1114
1115         for (MarkerSelection::iterator i = begin(); i != end(); ++i) {
1116
1117                 if ((*i)->position() < s) {
1118                         s = (*i)->position();
1119                 }
1120
1121                 if ((*i)->position() > e) {
1122                         e = (*i)->position();
1123                 }
1124         }
1125
1126         s = std::min (s, e);
1127         e = std::max (s, e);
1128 }
1129
1130 XMLNode&
1131 Selection::get_state () const
1132 {
1133         /* XXX: not complete; just sufficient to get track selection state
1134            so that re-opening plugin windows for editor mixer strips works
1135         */
1136
1137         XMLNode* node = new XMLNode (X_("Selection"));
1138
1139         for (TrackSelection::const_iterator i = tracks.begin(); i != tracks.end(); ++i) {
1140                 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (*i);
1141                 AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (*i);
1142                 if (rtv) {
1143                         XMLNode* t = node->add_child (X_("RouteView"));
1144                         t->set_property (X_("id"), rtv->route()->id ());
1145                 } else if (atv) {
1146                         XMLNode* t = node->add_child (X_("AutomationView"));
1147                         t->set_property (X_("id"), atv->parent_route()->id ());
1148                         t->set_property (X_("parameter"), EventTypeMap::instance().to_symbol (atv->parameter ()));
1149                 }
1150         }
1151
1152         for (RegionSelection::const_iterator i = regions.begin(); i != regions.end(); ++i) {
1153                 XMLNode* r = node->add_child (X_("Region"));
1154                 r->set_property (X_("id"), (*i)->region ()->id ());
1155         }
1156
1157         /* midi region views have thir own internal selection. */
1158         list<pair<PBD::ID, std::set<boost::shared_ptr<Evoral::Note<Evoral::Beats> > > > > rid_notes;
1159         editor->get_per_region_note_selection (rid_notes);
1160
1161         list<pair<PBD::ID, std::set<boost::shared_ptr<Evoral::Note<Evoral::Beats> > > > >::iterator rn_it;
1162         for (rn_it = rid_notes.begin(); rn_it != rid_notes.end(); ++rn_it) {
1163                 XMLNode* n = node->add_child (X_("MIDINotes"));
1164                 n->set_property (X_("region-id"), (*rn_it).first);
1165
1166                 for (std::set<boost::shared_ptr<Evoral::Note<Evoral::Beats> > >::iterator i = (*rn_it).second.begin(); i != (*rn_it).second.end(); ++i) {
1167                         XMLNode* nc = n->add_child(X_("note"));
1168                         nc->set_property(X_("note-id"), (*i)->id());
1169                 }
1170         }
1171
1172         for (PointSelection::const_iterator i = points.begin(); i != points.end(); ++i) {
1173                 AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (&(*i)->line().trackview);
1174                 if (atv) {
1175
1176                         XMLNode* r = node->add_child (X_("ControlPoint"));
1177                         r->set_property (X_("type"), "track");
1178                         r->set_property (X_("route-id"), atv->parent_route()->id ());
1179                         r->set_property (X_("automation-list-id"), (*i)->line().the_list()->id ());
1180                         r->set_property (X_("parameter"), EventTypeMap::instance().to_symbol ((*i)->line().the_list()->parameter ()));
1181                         r->set_property (X_("view-index"), (*i)->view_index());
1182                         continue;
1183                 }
1184
1185                 AudioRegionGainLine* argl = dynamic_cast<AudioRegionGainLine*> (&(*i)->line());
1186                 if (argl) {
1187                         XMLNode* r = node->add_child (X_("ControlPoint"));
1188                         r->set_property (X_("type"), "region");
1189                         r->set_property (X_("region-id"), argl->region_view ().region ()->id ());
1190                         r->set_property (X_("view-index"), (*i)->view_index());
1191                 }
1192
1193         }
1194
1195         for (TimeSelection::const_iterator i = time.begin(); i != time.end(); ++i) {
1196                 XMLNode* t = node->add_child (X_("AudioRange"));
1197                 t->set_property (X_("start"), (*i).start);
1198                 t->set_property (X_("end"), (*i).end);
1199         }
1200
1201         for (MarkerSelection::const_iterator i = markers.begin(); i != markers.end(); ++i) {
1202                 XMLNode* t = node->add_child (X_("Marker"));
1203
1204                 bool is_start;
1205                 Location* loc = editor->find_location_from_marker (*i, is_start);
1206
1207                 t->set_property (X_("id"), loc->id());
1208                 t->set_property (X_("start"), is_start);
1209         }
1210
1211         return *node;
1212 }
1213
1214 int
1215 Selection::set_state (XMLNode const & node, int)
1216 {
1217         if (node.name() != X_("Selection")) {
1218                 return -1;
1219         }
1220
1221         clear_regions ();
1222         clear_midi_notes ();
1223         clear_points ();
1224         clear_time ();
1225         clear_markers ();
1226
1227         RegionSelection selected_regions;
1228
1229         /* NOTE: stripable/time-axis-view selection is saved/restored by
1230          * ARDOUR::CoreSelection, not this Selection object
1231          */
1232
1233         PBD::ID id;
1234         XMLNodeList children = node.children ();
1235
1236         for (XMLNodeList::const_iterator i = children.begin(); i != children.end(); ++i) {
1237                 if ((*i)->name() == X_("Region")) {
1238
1239                         if (!(*i)->get_property (X_("id"), id)) {
1240                                 assert(false);
1241                         }
1242
1243                         RegionSelection rs;
1244                         editor->get_regionviews_by_id (id, rs);
1245
1246                         if (!rs.empty ()) {
1247                                 selected_regions.insert (selected_regions.end(), rs.begin(), rs.end());
1248                         } else {
1249                                 /*
1250                                   regionviews haven't been constructed - stash the region IDs
1251                                   so we can identify them in Editor::region_view_added ()
1252                                 */
1253                                 regions.pending.push_back (id);
1254                         }
1255
1256                 } else if ((*i)->name() == X_("MIDINotes")) {
1257
1258                         if (!(*i)->get_property (X_("region-id"), id)) {
1259                                 assert (false);
1260                         }
1261
1262                         RegionSelection rs;
1263
1264                         editor->get_regionviews_by_id (id, rs); // there could be more than one
1265
1266                         std::list<Evoral::event_id_t> notes;
1267                         XMLNodeList children = (*i)->children ();
1268
1269                         for (XMLNodeList::const_iterator ci = children.begin(); ci != children.end(); ++ci) {
1270                                 Evoral::event_id_t id;
1271                                 if ((*ci)->get_property (X_ ("note-id"), id)) {
1272                                         notes.push_back (id);
1273                                 }
1274                         }
1275
1276                         for (RegionSelection::iterator rsi = rs.begin(); rsi != rs.end(); ++rsi) {
1277                                 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (*rsi);
1278                                 if (mrv) {
1279                                         mrv->select_notes(notes);
1280                                 }
1281                         }
1282
1283                         if (rs.empty()) {
1284                                 /* regionviews containing these notes don't yet exist on the canvas.*/
1285                                 pending_midi_note_selection.push_back (make_pair (id, notes));
1286                         }
1287
1288                 } else if  ((*i)->name() == X_("ControlPoint")) {
1289                         XMLProperty const * prop_type = (*i)->property (X_("type"));
1290
1291                         assert(prop_type);
1292
1293                         if (prop_type->value () == "track") {
1294
1295                                 PBD::ID route_id;
1296                                 PBD::ID alist_id;
1297                                 std::string param;
1298                                 uint32_t view_index;
1299
1300                                 if (!(*i)->get_property (X_("route-id"), route_id) ||
1301                                     !(*i)->get_property (X_("automation-list-id"), alist_id) ||
1302                                     !(*i)->get_property (X_("parameter"), param) ||
1303                                     !(*i)->get_property (X_("view-index"), view_index)) {
1304                                         assert(false);
1305                                 }
1306
1307                                 RouteTimeAxisView* rtv = editor->get_route_view_by_route_id (route_id);
1308                                 vector <ControlPoint *> cps;
1309
1310                                 if (rtv) {
1311                                         boost::shared_ptr<AutomationTimeAxisView> atv = rtv->automation_child (EventTypeMap::instance().from_symbol (param));
1312                                         if (atv) {
1313                                                 list<boost::shared_ptr<AutomationLine> > lines = atv->lines();
1314                                                 for (list<boost::shared_ptr<AutomationLine> > ::iterator li = lines.begin(); li != lines.end(); ++li) {
1315                                                         if ((*li)->the_list()->id() == alist_id) {
1316                                                                 ControlPoint* cp = (*li)->nth(view_index);
1317                                                                 if (cp) {
1318                                                                         cps.push_back (cp);
1319                                                                         cp->show();
1320                                                                 }
1321                                                         }
1322                                                 }
1323                                         }
1324                                 }
1325                                 if (!cps.empty()) {
1326                                         add (cps);
1327                                 }
1328                         } else if (prop_type->value () == "region") {
1329
1330                                 PBD::ID region_id;
1331                                 uint32_t view_index;
1332                                 if (!(*i)->get_property (X_("region-id"), region_id) ||
1333                                     !(*i)->get_property (X_("view-index"), view_index)) {
1334                                         continue;
1335                                 }
1336
1337                                 RegionSelection rs;
1338                                 editor->get_regionviews_by_id (region_id, rs);
1339
1340                                 if (!rs.empty ()) {
1341                                         vector <ControlPoint *> cps;
1342                                         for (RegionSelection::iterator rsi = rs.begin(); rsi != rs.end(); ++rsi) {
1343                                                 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (*rsi);
1344                                                 if (arv) {
1345                                                         boost::shared_ptr<AudioRegionGainLine> gl = arv->get_gain_line ();
1346                                                         ControlPoint* cp = gl->nth(view_index);
1347                                                         if (cp) {
1348                                                                 cps.push_back (cp);
1349                                                                 cp->show();
1350                                                         }
1351                                                 }
1352                                         }
1353                                         if (!cps.empty()) {
1354                                                 add (cps);
1355                                         }
1356                                 }
1357                         }
1358
1359                 } else if  ((*i)->name() == X_("AudioRange")) {
1360                         framepos_t start;
1361                         framepos_t end;
1362
1363                         if (!(*i)->get_property (X_("start"), start) || !(*i)->get_property (X_("end"), end)) {
1364                                 assert(false);
1365                         }
1366                         set_preserving_all_ranges (start, end);
1367
1368                 } else if ((*i)->name() == X_("AutomationView")) {
1369
1370                         std::string param;
1371
1372                         if (!(*i)->get_property (X_("id"), id) || !(*i)->get_property (X_("parameter"), param)) {
1373                                 assert (false);
1374                         }
1375
1376                         RouteTimeAxisView* rtv = editor->get_route_view_by_route_id (id);
1377
1378                         if (rtv) {
1379                                 boost::shared_ptr<AutomationTimeAxisView> atv = rtv->automation_child (EventTypeMap::instance().from_symbol (param));
1380
1381                                 /* the automation could be for an entity that was never saved
1382                                    in the session file. Don't freak out if we can't find
1383                                    it.
1384                                 */
1385
1386                                 if (atv) {
1387                                         add (atv.get());
1388                                 }
1389                         }
1390
1391                 } else if ((*i)->name() == X_("Marker")) {
1392
1393                         bool is_start;
1394                         if (!(*i)->get_property (X_("id"), id) || !(*i)->get_property (X_("start"), is_start)) {
1395                                 assert(false);
1396                         }
1397
1398                         ArdourMarker* m = editor->find_marker_from_location_id (id, is_start);
1399                         if (m) {
1400                                 add (m);
1401                         }
1402
1403                 }
1404
1405         }
1406
1407         // now add regions to selection at once
1408         add (selected_regions);
1409
1410         return 0;
1411 }
1412
1413 void
1414 Selection::remove_regions (TimeAxisView* t)
1415 {
1416         RegionSelection::iterator i = regions.begin();
1417         while (i != regions.end ()) {
1418                 RegionSelection::iterator tmp = i;
1419                 ++tmp;
1420
1421                 if (&(*i)->get_time_axis_view() == t) {
1422                         remove (*i);
1423                 }
1424
1425                 i = tmp;
1426         }
1427 }
1428
1429 /* TIME AXIS VIEW ... proxy for Stripable/Controllable
1430  *
1431  * public methods just modify the CoreSelection; PresentationInfo::Changed will
1432  * trigger Selection::core_selection_changed() and we will update our own data
1433  * structures there.
1434  */
1435
1436 void
1437 Selection::toggle (const TrackViewList& track_list)
1438 {
1439         TrackViewList t = add_grouped_tracks (track_list);
1440
1441         CoreSelection& selection (editor->session()->selection());
1442         PresentationInfo::ChangeSuspender cs;
1443
1444         for (TrackSelection::const_iterator i = t.begin(); i != t.end(); ++i) {
1445                 boost::shared_ptr<Stripable> s = (*i)->stripable ();
1446                 boost::shared_ptr<AutomationControl> c = (*i)->control ();
1447                 selection.toggle (s, c);
1448         }
1449 }
1450
1451 void
1452 Selection::toggle (TimeAxisView* track)
1453 {
1454         if (dynamic_cast<VCATimeAxisView*> (track)) {
1455                 return;
1456         }
1457
1458         TrackViewList tr;
1459         tr.push_back (track);
1460         toggle (tr);
1461 }
1462
1463 void
1464 Selection::add (TrackViewList const & track_list)
1465 {
1466         TrackViewList t = add_grouped_tracks (track_list);
1467
1468         CoreSelection& selection (editor->session()->selection());
1469         PresentationInfo::ChangeSuspender cs;
1470
1471         for (TrackSelection::const_iterator i = t.begin(); i != t.end(); ++i) {
1472                 boost::shared_ptr<Stripable> s = (*i)->stripable ();
1473                 boost::shared_ptr<AutomationControl> c = (*i)->control ();
1474                 selection.add (s, c);
1475         }
1476 }
1477
1478 void
1479 Selection::add (TimeAxisView* track)
1480 {
1481         if (dynamic_cast<VCATimeAxisView*> (track)) {
1482                 return;
1483         }
1484
1485         TrackViewList tr;
1486         tr.push_back (track);
1487         add (tr);
1488 }
1489
1490 void
1491 Selection::remove (TimeAxisView* track)
1492 {
1493         if (dynamic_cast<VCATimeAxisView*> (track)) {
1494                 return;
1495         }
1496
1497         TrackViewList tvl;
1498         tvl.push_back (track);
1499         remove (tvl);
1500 }
1501
1502 void
1503 Selection::remove (const TrackViewList& t)
1504 {
1505         CoreSelection& selection (editor->session()->selection());
1506         PresentationInfo::ChangeSuspender cs;
1507
1508         for (TrackSelection::const_iterator i = t.begin(); i != t.end(); ++i) {
1509                 boost::shared_ptr<Stripable> s = (*i)->stripable ();
1510                 boost::shared_ptr<AutomationControl> c = (*i)->control ();
1511                 selection.remove (s, c);
1512         }
1513 }
1514
1515 void
1516 Selection::set (TimeAxisView* track)
1517 {
1518         if (dynamic_cast<VCATimeAxisView*> (track)) {
1519                 return;
1520         }
1521
1522         TrackViewList tvl;
1523         tvl.push_back (track);
1524         set (tvl);
1525 }
1526
1527 void
1528 Selection::set (const TrackViewList& track_list)
1529 {
1530         TrackViewList t = add_grouped_tracks (track_list);
1531
1532         CoreSelection& selection (editor->session()->selection());
1533         PresentationInfo::ChangeSuspender cs;
1534
1535         selection.clear_stripables ();
1536
1537         for (TrackSelection::const_iterator i = t.begin(); i != t.end(); ++i) {
1538                 boost::shared_ptr<Stripable> s = (*i)->stripable ();
1539                 boost::shared_ptr<AutomationControl> c = (*i)->control ();
1540                 selection.add (s, c);
1541         }
1542 }
1543
1544 void
1545 Selection::clear_tracks (bool)
1546 {
1547         Session* s = editor->session();
1548         if (s) {
1549                 CoreSelection& selection (s->selection());
1550                 selection.clear_stripables ();
1551         }
1552 }
1553
1554 bool
1555 Selection::selected (TimeAxisView* tv) const
1556 {
1557         Session* session = editor->session();
1558
1559         if (!session) {
1560                 return false;
1561         }
1562
1563         CoreSelection& selection (session->selection());
1564         boost::shared_ptr<Stripable> s = tv->stripable ();
1565         boost::shared_ptr<AutomationControl> c = tv->control ();
1566
1567         if (c) {
1568                 return selection.selected (c);
1569         }
1570
1571         return selection.selected (s);
1572 }
1573
1574 TrackViewList
1575 Selection::add_grouped_tracks (TrackViewList const & t)
1576 {
1577         TrackViewList added;
1578
1579         for (TrackSelection::const_iterator i = t.begin(); i != t.end(); ++i) {
1580                 if (dynamic_cast<VCATimeAxisView*> (*i)) {
1581                         continue;
1582                 }
1583
1584                 /* select anything in the same select-enabled route group */
1585                 ARDOUR::RouteGroup* rg = (*i)->route_group ();
1586
1587                 if (rg && rg->is_active() && rg->is_select ()) {
1588
1589                         TrackViewList tr = editor->axis_views_from_routes (rg->route_list ());
1590
1591                         for (TrackViewList::iterator j = tr.begin(); j != tr.end(); ++j) {
1592
1593                                 /* Do not add the trackview passed in as an
1594                                  * argument, because we want that to be on the
1595                                  * end of the list.
1596                                  */
1597
1598                                 if (*j != *i) {
1599                                         if (!added.contains (*j)) {
1600                                                 added.push_back (*j);
1601                                         }
1602                                 }
1603                         }
1604                 }
1605         }
1606
1607         /* now add the the trackview's passed in as actual arguments */
1608         added.insert (added.end(), t.begin(), t.end());
1609
1610         return added;
1611 }
1612
1613 #if 0
1614 static void dump_tracks (Selection const & s)
1615 {
1616         cerr << "--TRACKS [" << s.tracks.size() << ']' << ":\n";
1617         for (TrackViewList::const_iterator x = s.tracks.begin(); x != s.tracks.end(); ++x) {
1618                 cerr << (*x)->name() << ' ' << (*x)->stripable() << " C = " << (*x)->control() << endl;
1619         }
1620         cerr << "///\n";
1621 }
1622 #endif
1623
1624 void
1625 Selection::core_selection_changed (PropertyChange const & what_changed)
1626 {
1627         PropertyChange pc;
1628
1629         pc.add (Properties::selected);
1630
1631         if (!what_changed.contains (pc)) {
1632                 return;
1633         }
1634
1635         CoreSelection& selection (editor->session()->selection());
1636
1637         if (selection.selected()) {
1638                 clear_objects();  // enforce object/range exclusivity
1639         }
1640
1641         tracks.clear (); // clear stage for whatever tracks are now selected (maybe none)
1642
1643         CoreSelection::StripableAutomationControls sac;
1644         selection.get_stripables (sac);
1645
1646         for (CoreSelection::StripableAutomationControls::const_iterator i = sac.begin(); i != sac.end(); ++i) {
1647                 AxisView* av;
1648                 TimeAxisView* tav;
1649                 if ((*i).controllable) {
1650                         av = editor->axis_view_by_control ((*i).controllable);
1651                 } else {
1652                         av = editor->axis_view_by_stripable ((*i).stripable);
1653                 }
1654
1655                 tav = dynamic_cast<TimeAxisView*>(av);
1656                 if (tav) {
1657                         tracks.push_back (tav);
1658                 }
1659         }
1660 }