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