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