Slightly unpleasant hack to stop control points being
[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 #include "pbd/error.h"
23 #include "pbd/stacktrace.h"
24
25 #include "ardour/playlist.h"
26 #include "ardour/rc_configuration.h"
27
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"
37
38 #include "i18n.h"
39
40 using namespace std;
41 using namespace ARDOUR;
42 using namespace PBD;
43
44 struct AudioRangeComparator {
45     bool operator()(AudioRange a, AudioRange b) {
46             return a.start < b.start;
47     }
48 };
49
50 Selection::Selection (const PublicEditor* e)
51         : tracks (e)
52         , editor (e)
53         , next_time_id (0)
54         , _no_tracks_changed (false)
55 {
56         clear ();
57
58         /* we have disambiguate which remove() for the compiler */
59
60         void (Selection::*track_remove)(TimeAxisView*) = &Selection::remove;
61         TimeAxisView::CatchDeletion.connect (*this, MISSING_INVALIDATOR, boost::bind (track_remove, this, _1), gui_context());
62
63         void (Selection::*marker_remove)(Marker*) = &Selection::remove;
64         Marker::CatchDeletion.connect (*this, MISSING_INVALIDATOR, boost::bind (marker_remove, this, _1), gui_context());
65
66         void (Selection::*point_remove)(ControlPoint*) = &Selection::remove;
67         ControlPoint::CatchDeletion.connect (*this, MISSING_INVALIDATOR, boost::bind (point_remove, this, _1), gui_context());
68 }
69
70 #if 0
71 Selection&
72 Selection::operator= (const Selection& other)
73 {
74         if (&other != this) {
75                 regions = other.regions;
76                 tracks = other.tracks;
77                 time = other.time;
78                 lines = other.lines;
79                 midi_regions = other.midi_regions;
80                 midi_notes = other.midi_notes;
81         }
82         return *this;
83 }
84 #endif
85
86 bool
87 operator== (const Selection& a, const Selection& b)
88 {
89         return a.regions == b.regions &&
90                 a.tracks == b.tracks &&
91                 a.time == b.time &&
92                 a.lines == b.lines &&
93                 a.playlists == b.playlists &&
94                 a.midi_notes == b.midi_notes &&
95                 a.midi_regions == b.midi_regions;
96 }
97
98 /** Clear everything from the Selection */
99 void
100 Selection::clear ()
101 {
102         clear_tracks ();
103         clear_regions ();
104         clear_points ();
105         clear_lines();
106         clear_time ();
107         clear_playlists ();
108         clear_midi_notes ();
109         clear_midi_regions ();
110         clear_markers ();
111 }
112
113 void
114 Selection::dump_region_layers()
115 {
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;
119         }
120 }
121
122
123 void
124 Selection::clear_regions ()
125 {
126         if (!regions.empty()) {
127                 regions.clear_all ();
128                 RegionsChanged();
129                 if (Config->get_link_region_and_track_selection()) {
130                         clear_tracks ();
131                 }
132         }
133 }
134
135 void
136 Selection::clear_tracks ()
137 {
138         if (!tracks.empty()) {
139                 tracks.clear ();
140                 if (!_no_tracks_changed) {
141                         TracksChanged();
142                 }
143         }
144 }
145
146 void
147 Selection::clear_midi_notes ()
148 {
149         if (!midi_notes.empty()) {
150                 for (MidiNoteSelection::iterator x = midi_notes.begin(); x != midi_notes.end(); ++x) {
151                         delete *x;
152                 }
153                 midi_notes.clear ();
154                 MidiNotesChanged ();
155         }
156 }
157
158 void
159 Selection::clear_midi_regions ()
160 {
161         if (!midi_regions.empty()) {
162                 midi_regions.clear ();
163                 MidiRegionsChanged ();
164         }
165 }
166
167 void
168 Selection::clear_time ()
169 {
170         time.clear();
171
172         TimeChanged ();
173 }
174
175 void
176 Selection::clear_playlists ()
177 {
178         /* Selections own their playlists */
179
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 ();
183                 (*i)->release ();
184         }
185
186         if (!playlists.empty()) {
187                 playlists.clear ();
188                 PlaylistsChanged();
189         }
190 }
191
192 void
193 Selection::clear_lines ()
194 {
195         if (!lines.empty()) {
196                 lines.clear ();
197                 LinesChanged();
198         }
199 }
200
201 void
202 Selection::clear_markers ()
203 {
204         if (!markers.empty()) {
205                 markers.clear ();
206                 MarkersChanged();
207         }
208 }
209
210 void
211 Selection::toggle (boost::shared_ptr<Playlist> pl)
212 {
213         PlaylistSelection::iterator i;
214
215         if ((i = find (playlists.begin(), playlists.end(), pl)) == playlists.end()) {
216                 pl->use ();
217                 playlists.push_back(pl);
218         } else {
219                 playlists.erase (i);
220         }
221
222         PlaylistsChanged ();
223 }
224
225 void
226 Selection::toggle (const TrackViewList& track_list)
227 {
228         for (TrackViewList::const_iterator i = track_list.begin(); i != track_list.end(); ++i) {
229                 toggle ((*i));
230         }
231 }
232
233 void
234 Selection::toggle (TimeAxisView* track)
235 {
236         TrackSelection::iterator i;
237
238         if ((i = find (tracks.begin(), tracks.end(), track)) == tracks.end()) {
239                 tracks.push_back (track);
240         } else {
241                 tracks.erase (i);
242         }
243
244         if (!_no_tracks_changed) {
245                 TracksChanged();
246         }
247 }
248
249 void
250 Selection::toggle (const MidiNoteSelection& midi_note_list)
251 {
252         for (MidiNoteSelection::const_iterator i = midi_note_list.begin(); i != midi_note_list.end(); ++i) {
253                 toggle ((*i));
254         }
255 }
256
257 void
258 Selection::toggle (MidiCutBuffer* midi)
259 {
260         MidiNoteSelection::iterator i;
261
262         if ((i = find (midi_notes.begin(), midi_notes.end(), midi)) == midi_notes.end()) {
263                 midi_notes.push_back (midi);
264         } else {
265                 /* remember that we own the MCB */
266                 delete *i;
267                 midi_notes.erase (i);
268         }
269
270         MidiNotesChanged();
271 }
272
273
274 void
275 Selection::toggle (RegionView* r)
276 {
277         RegionSelection::iterator i;
278
279         if ((i = find (regions.begin(), regions.end(), r)) == regions.end()) {
280                 add (r);
281         } else {
282                 remove (*i);
283         }
284
285         RegionsChanged ();
286 }
287
288 void
289 Selection::toggle (MidiRegionView* mrv)
290 {
291         MidiRegionSelection::iterator i;
292
293         if ((i = find (midi_regions.begin(), midi_regions.end(), mrv)) == midi_regions.end()) {
294                 add (mrv);
295         } else {
296                 midi_regions.erase (i);
297         }
298
299         MidiRegionsChanged ();
300 }
301
302 void
303 Selection::toggle (vector<RegionView*>& r)
304 {
305         RegionSelection::iterator i;
306
307         for (vector<RegionView*>::iterator x = r.begin(); x != r.end(); ++x) {
308                 if ((i = find (regions.begin(), regions.end(), (*x))) == regions.end()) {
309                         add ((*x));
310                 } else {
311                         remove (*x);
312                 }
313         }
314
315         RegionsChanged ();
316 }
317
318 long
319 Selection::toggle (framepos_t start, framepos_t end)
320 {
321         AudioRangeComparator cmp;
322
323         /* XXX this implementation is incorrect */
324
325         time.push_back (AudioRange (start, end, next_time_id++));
326         time.consolidate ();
327         time.sort (cmp);
328
329         TimeChanged ();
330
331         return next_time_id - 1;
332 }
333
334 void
335 Selection::add (boost::shared_ptr<Playlist> pl)
336 {
337         if (find (playlists.begin(), playlists.end(), pl) == playlists.end()) {
338                 pl->use ();
339                 playlists.push_back(pl);
340                 PlaylistsChanged ();
341         }
342 }
343
344 void
345 Selection::add (const list<boost::shared_ptr<Playlist> >& pllist)
346 {
347         bool changed = false;
348
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()) {
351                         (*i)->use ();
352                         playlists.push_back (*i);
353                         changed = true;
354                 }
355         }
356
357         if (changed) {
358                 PlaylistsChanged ();
359         }
360 }
361
362 void
363 Selection::add (const TrackViewList& track_list)
364 {
365         TrackViewList added = tracks.add (track_list);
366
367         if (!added.empty()) {
368                 if (!_no_tracks_changed) {
369                         TracksChanged ();
370                 }
371         }
372 }
373
374 void
375 Selection::add (TimeAxisView* track)
376 {
377         TrackViewList tr;
378         tr.push_back (track);
379         add (tr);
380 }
381
382 void
383 Selection::add (const MidiNoteSelection& midi_list)
384 {
385         const MidiNoteSelection::const_iterator b = midi_list.begin();
386         const MidiNoteSelection::const_iterator e = midi_list.end();
387
388         if (!midi_list.empty()) {
389                 midi_notes.insert (midi_notes.end(), b, e);
390                 MidiNotesChanged ();
391         }
392 }
393
394 void
395 Selection::add (MidiCutBuffer* midi)
396 {
397         /* we take ownership of the MCB */
398
399         if (find (midi_notes.begin(), midi_notes.end(), midi) == midi_notes.end()) {
400                 midi_notes.push_back (midi);
401                 MidiNotesChanged ();
402         }
403 }
404
405 void
406 Selection::add (vector<RegionView*>& v)
407 {
408         /* XXX This method or the add (const RegionSelection&) needs to go
409          */
410
411         bool changed = false;
412
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());
418                         }
419                 }
420         }
421
422         if (changed) {
423                 RegionsChanged ();
424         }
425 }
426
427 void
428 Selection::add (const RegionSelection& rs)
429 {
430         /* XXX This method or the add (const vector<RegionView*>&) needs to go
431          */
432
433         bool changed = false;
434
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());
440                         }
441                 }
442         }
443
444         if (changed) {
445                 RegionsChanged ();
446         }
447 }
448
449 void
450 Selection::add (RegionView* r)
451 {
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());
456                 }
457                 if (changed) {
458                         RegionsChanged ();
459                 }
460         }
461 }
462
463 void
464 Selection::add (MidiRegionView* mrv)
465 {
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? */
469 #if 0
470                 if (Config->get_link_region_and_track_selection()) {
471                         add (&mrv->get_time_axis_view());
472                 }
473 #endif
474                 MidiRegionsChanged ();
475         }
476 }
477
478 long
479 Selection::add (framepos_t start, framepos_t end)
480 {
481         AudioRangeComparator cmp;
482
483         /* XXX this implementation is incorrect */
484
485         time.push_back (AudioRange (start, end, next_time_id++));
486         time.consolidate ();
487         time.sort (cmp);
488
489         TimeChanged ();
490
491         return next_time_id - 1;
492 }
493
494 void
495 Selection::replace (uint32_t sid, framepos_t start, framepos_t end)
496 {
497         for (list<AudioRange>::iterator i = time.begin(); i != time.end(); ++i) {
498                 if ((*i).id == sid) {
499                         time.erase (i);
500                         time.push_back (AudioRange(start,end, sid));
501
502                         /* don't consolidate here */
503
504
505                         AudioRangeComparator cmp;
506                         time.sort (cmp);
507
508                         TimeChanged ();
509                         break;
510                 }
511         }
512 }
513
514 void
515 Selection::add (boost::shared_ptr<Evoral::ControlList> cl)
516 {
517         boost::shared_ptr<ARDOUR::AutomationList> al
518                 = boost::dynamic_pointer_cast<ARDOUR::AutomationList>(cl);
519         if (!al) {
520                 warning << "Programming error: Selected list is not an ARDOUR::AutomationList" << endmsg;
521                 return;
522         }
523         if (find (lines.begin(), lines.end(), al) == lines.end()) {
524                 lines.push_back (al);
525                 LinesChanged();
526         }
527 }
528
529 void
530 Selection::remove (TimeAxisView* track)
531 {
532         list<TimeAxisView*>::iterator i;
533         if ((i = find (tracks.begin(), tracks.end(), track)) != tracks.end()) {
534                 tracks.erase (i);
535                 if (!_no_tracks_changed) {
536                         TracksChanged();
537                 }
538         }
539 }
540
541 void
542 Selection::remove (ControlPoint* p)
543 {
544         PointSelection::iterator i = find (points.begin(), points.end(), p);
545         if (i != points.end ()) {
546                 points.erase (i);
547         }
548 }
549
550 void
551 Selection::remove (const TrackViewList& track_list)
552 {
553         bool changed = false;
554
555         for (TrackViewList::const_iterator i = track_list.begin(); i != track_list.end(); ++i) {
556
557                 TrackViewList::iterator x = find (tracks.begin(), tracks.end(), *i);
558                 if (x != tracks.end()) {
559                         tracks.erase (x);
560                         changed = true;
561                 }
562         }
563
564         if (changed) {
565                 if (!_no_tracks_changed) {
566                         TracksChanged();
567                 }
568         }
569 }
570
571 void
572 Selection::remove (const MidiNoteSelection& midi_list)
573 {
574         bool changed = false;
575
576         for (MidiNoteSelection::const_iterator i = midi_list.begin(); i != midi_list.end(); ++i) {
577
578                 MidiNoteSelection::iterator x;
579
580                 if ((x = find (midi_notes.begin(), midi_notes.end(), (*i))) != midi_notes.end()) {
581                         midi_notes.erase (x);
582                         changed = true;
583                 }
584         }
585
586         if (changed) {
587                 MidiNotesChanged();
588         }
589 }
590
591 void
592 Selection::remove (MidiCutBuffer* midi)
593 {
594         MidiNoteSelection::iterator x;
595
596         if ((x = find (midi_notes.begin(), midi_notes.end(), midi)) != midi_notes.end()) {
597                 /* remember that we own the MCB */
598                 delete *x;
599                 midi_notes.erase (x);
600                 MidiNotesChanged ();
601         }
602 }
603
604 void
605 Selection::remove (boost::shared_ptr<Playlist> track)
606 {
607         list<boost::shared_ptr<Playlist> >::iterator i;
608         if ((i = find (playlists.begin(), playlists.end(), track)) != playlists.end()) {
609                 playlists.erase (i);
610                 PlaylistsChanged();
611         }
612 }
613
614 void
615 Selection::remove (const list<boost::shared_ptr<Playlist> >& pllist)
616 {
617         bool changed = false;
618
619         for (list<boost::shared_ptr<Playlist> >::const_iterator i = pllist.begin(); i != pllist.end(); ++i) {
620
621                 list<boost::shared_ptr<Playlist> >::iterator x;
622
623                 if ((x = find (playlists.begin(), playlists.end(), (*i))) != playlists.end()) {
624                         playlists.erase (x);
625                         changed = true;
626                 }
627         }
628
629         if (changed) {
630                 PlaylistsChanged();
631         }
632 }
633
634 void
635 Selection::remove (RegionView* r)
636 {
637         if (regions.remove (r)) {
638                 RegionsChanged ();
639         }
640
641         if (Config->get_link_region_and_track_selection() && !regions.involves (r->get_time_axis_view())) {
642                 remove (&r->get_time_axis_view());
643         }
644 }
645
646 void
647 Selection::remove (MidiRegionView* 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 #if 0
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());
660         }
661 #endif
662 }
663
664
665 void
666 Selection::remove (uint32_t selection_id)
667 {
668         if (time.empty()) {
669                 return;
670         }
671
672         for (list<AudioRange>::iterator i = time.begin(); i != time.end(); ++i) {
673                 if ((*i).id == selection_id) {
674                         time.erase (i);
675
676                         TimeChanged ();
677                         break;
678                 }
679         }
680 }
681
682 void
683 Selection::remove (framepos_t /*start*/, framepos_t /*end*/)
684 {
685 }
686
687 void
688 Selection::remove (boost::shared_ptr<ARDOUR::AutomationList> ac)
689 {
690         AutomationSelection::iterator i;
691         if ((i = find (lines.begin(), lines.end(), ac)) != lines.end()) {
692                 lines.erase (i);
693                 LinesChanged();
694         }
695 }
696
697 void
698 Selection::set (TimeAxisView* track)
699 {
700         clear_tracks ();
701         add (track);
702 }
703
704 void
705 Selection::set (const TrackViewList& track_list)
706 {
707         clear_tracks ();
708         add (track_list);
709 }
710
711 void
712 Selection::set (const MidiNoteSelection& midi_list)
713 {
714         clear_midi_notes ();
715         add (midi_list);
716 }
717
718 void
719 Selection::set (boost::shared_ptr<Playlist> playlist)
720 {
721         clear_playlists ();
722         add (playlist);
723 }
724
725 void
726 Selection::set (const list<boost::shared_ptr<Playlist> >& pllist)
727 {
728         clear_playlists ();
729         add (pllist);
730 }
731
732 void
733 Selection::set (const RegionSelection& rs)
734 {
735         clear_regions();
736         regions = rs;
737         RegionsChanged(); /* EMIT SIGNAL */
738 }
739
740 void
741 Selection::set (MidiRegionView* mrv)
742 {
743         clear_midi_regions ();
744         add (mrv);
745 }
746
747 void
748 Selection::set (RegionView* r, bool also_clear_tracks)
749 {
750         clear_regions ();
751         if (also_clear_tracks && !Config->get_link_region_and_track_selection()) {
752                 /* clear_regions() will have done this if the link preference
753                  * is enabled
754                  */
755                 clear_tracks ();
756         }
757         add (r);
758 }
759
760 void
761 Selection::set (vector<RegionView*>& v)
762 {
763         bool had_regions = !regions.empty();
764
765         clear_regions ();
766
767         if (Config->get_link_region_and_track_selection()) {
768                 if (had_regions) {
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.
773                          */
774                         clear_tracks ();
775                         // make sure to deselect any automation selections
776                         clear_points();
777                 }
778         }
779         add (v);
780 }
781
782 /** Set the start and end time of the time selection, without changing
783  *  the list of tracks it applies to.
784  */
785 long
786 Selection::set (framepos_t start, framepos_t end)
787 {
788         if ((start == 0 && end == 0) || end < start) {
789                 return 0;
790         }
791
792         if (time.empty()) {
793                 time.push_back (AudioRange (start, end, next_time_id++));
794         } else {
795                 /* reuse the first entry, and remove all the rest */
796
797                 while (time.size() > 1) {
798                         time.pop_front();
799                 }
800                 time.front().start = start;
801                 time.front().end = end;
802         }
803
804         time.consolidate ();
805
806         TimeChanged ();
807
808         return time.front().id;
809 }
810
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.
815  *
816  *  @param start New start time.
817  *  @param end New end time.
818  */
819 void
820 Selection::set_preserving_all_ranges (framepos_t start, framepos_t end)
821 {
822         if ((start == 0 && end == 0) || (end < start)) {
823                 return;
824         }
825
826         if (time.empty ()) {
827                 time.push_back (AudioRange (start, end, next_time_id++));
828         } else {
829                 time.sort (AudioRangeComparator ());
830                 time.front().start = start;
831                 time.back().end = end;
832         }
833
834         time.consolidate ();
835
836         TimeChanged ();
837 }
838
839 void
840 Selection::set (boost::shared_ptr<Evoral::ControlList> ac)
841 {
842         lines.clear();
843         add (ac);
844 }
845
846 bool
847 Selection::selected (Marker* m)
848 {
849         return find (markers.begin(), markers.end(), m) != markers.end();
850 }
851
852 bool
853 Selection::selected (TimeAxisView* tv)
854 {
855         return find (tracks.begin(), tracks.end(), tv) != tracks.end();
856 }
857
858 bool
859 Selection::selected (RegionView* rv)
860 {
861         return find (regions.begin(), regions.end(), rv) != regions.end();
862 }
863
864 bool
865 Selection::selected (ControlPoint* cp)
866 {
867         return find (points.begin(), points.end(), cp) != points.end();
868 }
869
870 bool
871 Selection::empty (bool internal_selection)
872 {
873         bool object_level_empty =  regions.empty () &&
874                 tracks.empty () &&
875                 points.empty () &&
876                 playlists.empty () &&
877                 lines.empty () &&
878                 time.empty () &&
879                 playlists.empty () &&
880                 markers.empty() &&
881                 midi_regions.empty()
882                 ;
883
884         if (!internal_selection) {
885                 return object_level_empty;
886         }
887
888         /* this is intended to really only apply when using a Selection
889            as a cut buffer.
890         */
891
892         return object_level_empty && midi_notes.empty();
893 }
894
895 void
896 Selection::toggle (ControlPoint* cp)
897 {
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);
902         } else {
903                 points.erase (i);
904         }
905
906         PointsChanged (); /* EMIT SIGNAL */
907 }
908
909 void
910 Selection::toggle (vector<ControlPoint*> const & cps)
911 {
912         for (vector<ControlPoint*>::const_iterator i = cps.begin(); i != cps.end(); ++i) {
913                 toggle (*i);
914         }
915 }
916
917 void
918 Selection::toggle (list<Selectable*> const & selectables)
919 {
920         RegionView* rv;
921         ControlPoint* cp;
922         vector<RegionView*> rvs;
923         vector<ControlPoint*> cps;
924
925         for (std::list<Selectable*>::const_iterator i = selectables.begin(); i != selectables.end(); ++i) {
926                 if ((rv = dynamic_cast<RegionView*> (*i)) != 0) {
927                         rvs.push_back (rv);
928                 } else if ((cp = dynamic_cast<ControlPoint*> (*i)) != 0) {
929                         cps.push_back (cp);
930                 } else {
931                         fatal << _("programming error: ")
932                               << X_("unknown selectable type passed to Selection::toggle()")
933                               << endmsg;
934                         /*NOTREACHED*/
935                 }
936         }
937
938         if (!rvs.empty()) {
939                 toggle (rvs);
940         }
941
942         if (!cps.empty()) {
943                 toggle (cps);
944         }
945 }
946
947 void
948 Selection::set (list<Selectable*> const & selectables)
949 {
950         clear_regions();
951         clear_points ();
952
953         if (Config->get_link_region_and_track_selection ()) {
954                 clear_tracks ();
955         }
956
957         add (selectables);
958 }
959
960 void
961 Selection::add (PointSelection const & s)
962 {
963         for (PointSelection::const_iterator i = s.begin(); i != s.end(); ++i) {
964                 points.push_back (*i);
965         }
966 }
967
968 void
969 Selection::add (list<Selectable*> const & selectables)
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                         /*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 ()
1000 {
1001         if (!points.empty()) {
1002                 points.clear ();
1003                 PointsChanged ();
1004         }
1005 }
1006
1007 void
1008 Selection::add (ControlPoint* cp)
1009 {
1010         cp->set_selected (true);
1011         points.push_back (cp);
1012         PointsChanged (); /* EMIT SIGNAL */
1013 }
1014
1015 void
1016 Selection::add (vector<ControlPoint*> const & cps)
1017 {
1018         for (vector<ControlPoint*>::const_iterator i = cps.begin(); i != cps.end(); ++i) {
1019                 add (*i);
1020         }
1021 }
1022
1023 void
1024 Selection::set (ControlPoint* cp)
1025 {
1026         if (cp->get_selected()) {
1027                 return;
1028         }
1029
1030         for (uint32_t i = 0; i < cp->line().npoints(); ++i) {
1031                 cp->line().nth (i)->set_selected (false);
1032         }
1033
1034         clear_points ();
1035         add (cp);
1036 }
1037
1038 void
1039 Selection::set (Marker* m)
1040 {
1041         clear_markers ();
1042         add (m);
1043 }
1044
1045 void
1046 Selection::toggle (Marker* m)
1047 {
1048         MarkerSelection::iterator i;
1049
1050         if ((i = find (markers.begin(), markers.end(), m)) == markers.end()) {
1051                 add (m);
1052         } else {
1053                 remove (m);
1054         }
1055 }
1056
1057 void
1058 Selection::remove (Marker* m)
1059 {
1060         MarkerSelection::iterator i;
1061
1062         if ((i = find (markers.begin(), markers.end(), m)) != markers.end()) {
1063                 markers.erase (i);
1064                 MarkersChanged();
1065         }
1066 }
1067
1068 void
1069 Selection::add (Marker* m)
1070 {
1071         if (find (markers.begin(), markers.end(), m) == markers.end()) {
1072                 markers.push_back (m);
1073                 MarkersChanged();
1074         }
1075 }
1076
1077 void
1078 Selection::add (const list<Marker*>& m)
1079 {
1080         markers.insert (markers.end(), m.begin(), m.end());
1081         markers.sort ();
1082         markers.unique ();
1083         
1084         MarkersChanged ();
1085 }
1086
1087 void
1088 MarkerSelection::range (framepos_t& s, framepos_t& e)
1089 {
1090         s = max_framepos;
1091         e = 0;
1092
1093         for (MarkerSelection::iterator i = begin(); i != end(); ++i) {
1094
1095                 if ((*i)->position() < s) {
1096                         s = (*i)->position();
1097                 }
1098
1099                 if ((*i)->position() > e) {
1100                         e = (*i)->position();
1101                 }
1102         }
1103
1104         s = std::min (s, e);
1105         e = std::max (s, e);
1106 }
1107
1108 XMLNode&
1109 Selection::get_state () const
1110 {
1111         /* XXX: not complete; just sufficient to get track selection state
1112            so that re-opening plugin windows for editor mixer strips works
1113         */
1114
1115         XMLNode* node = new XMLNode (X_("Selection"));
1116
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);
1120                 if (rtv) {
1121                         XMLNode* t = node->add_child (X_("RouteView"));
1122                         t->add_property (X_("id"), atoi (rtv->route()->id().to_s().c_str()));
1123                 } else if (atv) {
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 ()));
1127                 }
1128         }
1129
1130         for (MarkerSelection::const_iterator i = markers.begin(); i != markers.end(); ++i) {
1131                 XMLNode* t = node->add_child (X_("Marker"));
1132
1133                 bool is_start;
1134                 Location* loc = editor->find_location_from_marker (*i, is_start);
1135
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"));
1138         }
1139
1140         return *node;
1141 }
1142
1143 int
1144 Selection::set_state (XMLNode const & node, int)
1145 {
1146         if (node.name() != X_("Selection")) {
1147                 return -1;
1148         }
1149
1150         XMLNodeList children = node.children ();
1151         for (XMLNodeList::const_iterator i = children.begin(); i != children.end(); ++i) {
1152                 if ((*i)->name() == X_("RouteView")) {
1153
1154                         XMLProperty* prop_id = (*i)->property (X_("id"));
1155                         assert (prop_id);
1156                         PBD::ID id (prop_id->value ());
1157                         RouteTimeAxisView* rtv = editor->get_route_view_by_route_id (id);
1158                         if (rtv) {
1159                                 add (rtv);
1160                         }
1161
1162                 } else if ((*i)->name() == X_("AutomationView")) {
1163
1164                         XMLProperty* prop_id = (*i)->property (X_("id"));
1165                         XMLProperty* prop_parameter = (*i)->property (X_("parameter"));
1166
1167                         assert (prop_id);
1168                         assert (prop_parameter);
1169
1170                         PBD::ID id (prop_id->value ());
1171                         RouteTimeAxisView* rtv = editor->get_route_view_by_route_id (id);
1172
1173                         if (rtv) {
1174                                 boost::shared_ptr<AutomationTimeAxisView> atv = rtv->automation_child (EventTypeMap::instance().new_parameter (prop_parameter->value ()));
1175
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
1178                                    it.
1179                                 */
1180
1181                                 if (atv) {
1182                                         add (atv.get());
1183                                 }
1184                         }
1185
1186                 } else if ((*i)->name() == X_("Marker")) {
1187
1188                         XMLProperty* prop_id = (*i)->property (X_("id"));
1189                         XMLProperty* prop_start = (*i)->property (X_("start"));
1190                         assert (prop_id);
1191                         assert (prop_start);
1192
1193                         PBD::ID id (prop_id->value ());
1194                         Marker* m = editor->find_marker_from_location_id (id, string_is_affirmative (prop_start->value ()));
1195                         if (m) {
1196                                 add (m);
1197                         }
1198                         
1199                 }
1200                 
1201         }
1202
1203         return 0;
1204 }
1205
1206 void
1207 Selection::remove_regions (TimeAxisView* t)
1208 {
1209         RegionSelection::iterator i = regions.begin();
1210         while (i != regions.end ()) {
1211                 RegionSelection::iterator tmp = i;
1212                 ++tmp;
1213
1214                 if (&(*i)->get_time_axis_view() == t) {
1215                         remove (*i);
1216                 }
1217
1218                 i = tmp;
1219         }
1220 }
1221
1222 void
1223 Selection::block_tracks_changed (bool yn)
1224 {
1225         _no_tracks_changed = yn;
1226 }