make all use of bind/mem_fun be explicitly sigc::
[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 "midi_cut_buffer.h"
29 #include "region_view.h"
30 #include "selection.h"
31 #include "selection_templates.h"
32 #include "time_axis_view.h"
33 #include "automation_time_axis.h"
34 #include "public_editor.h"
35
36 #include "i18n.h"
37
38 using namespace std;
39 using namespace ARDOUR;
40 using namespace PBD;
41 using namespace sigc;
42
43 struct AudioRangeComparator {
44     bool operator()(AudioRange a, AudioRange b) {
45             return a.start < b.start;
46     }
47 };
48
49 Selection&
50 Selection::operator= (const Selection& other)
51 {
52         if (&other != this) {
53                 regions = other.regions;
54                 tracks = other.tracks;
55                 time = other.time;
56                 lines = other.lines;
57                 midi_regions = other.midi_regions;
58                 midi_notes = other.midi_notes;
59         }
60         return *this;
61 }
62
63 bool
64 operator== (const Selection& a, const Selection& b)
65 {
66         return a.regions == b.regions &&
67                 a.tracks == b.tracks &&
68                 a.time.track == b.time.track &&
69                 a.time.group == b.time.group &&
70                 a.time == b.time &&
71                 a.lines == b.lines &&
72                 a.playlists == b.playlists &&
73                 a.midi_notes == b.midi_notes &&
74                 a.midi_regions == b.midi_regions;
75 }
76
77 /** Clear everything from the Selection */
78 void
79 Selection::clear ()
80 {
81         clear_tracks ();
82         clear_regions ();
83         clear_points ();
84         clear_lines();
85         clear_time ();
86         clear_playlists ();
87         clear_midi_notes ();
88         clear_midi_regions ();
89 }
90
91 void
92 Selection::dump_region_layers()
93 {
94         cerr << "region selection layer dump" << endl;
95         for (RegionSelection::iterator i = regions.begin(); i != regions.end(); ++i) {
96                 cerr << "layer: " << (int)(*i)->region()->layer() << endl;
97         }
98 }
99
100
101 void
102 Selection::clear_regions ()
103 {
104         if (!regions.empty()) {
105                 regions.clear_all ();
106                 RegionsChanged();
107         }
108 }
109
110 void
111 Selection::clear_tracks ()
112 {
113         if (!tracks.empty()) {
114                 tracks.clear ();
115                 TracksChanged();
116         }
117 }
118
119 void
120 Selection::clear_midi_notes ()
121 {
122         if (!midi_notes.empty()) {
123                 for (MidiNoteSelection::iterator x = midi_notes.begin(); x != midi_notes.end(); ++x) {
124                         delete *x;
125                 }
126                 midi_notes.clear ();
127                 MidiNotesChanged ();
128         }
129 }
130
131 void
132 Selection::clear_midi_regions ()
133 {
134         if (!midi_regions.empty()) {
135                 midi_regions.clear ();
136                 MidiRegionsChanged ();
137         }
138 }
139
140 void
141 Selection::clear_time ()
142 {
143         time.track = 0;
144         time.group = 0;
145         time.clear();
146
147         TimeChanged ();
148 }
149
150 void
151 Selection::clear_playlists ()
152 {
153         /* Selections own their playlists */
154
155         for (PlaylistSelection::iterator i = playlists.begin(); i != playlists.end(); ++i) {
156                 /* selections own their own regions, which are copies of the "originals". make them go away */
157                 (*i)->drop_regions ();
158                 (*i)->release ();
159         }
160
161         if (!playlists.empty()) {
162                 playlists.clear ();
163                 PlaylistsChanged();
164         }
165 }
166
167 void
168 Selection::clear_lines ()
169 {
170         if (!lines.empty()) {
171                 lines.clear ();
172                 LinesChanged();
173         }
174 }
175
176 void
177 Selection::clear_markers ()
178 {
179         if (!markers.empty()) {
180                 markers.clear ();
181                 MarkersChanged();
182         }
183 }
184
185 void
186 Selection::toggle (boost::shared_ptr<Playlist> pl)
187 {
188         PlaylistSelection::iterator i;
189
190         if ((i = find (playlists.begin(), playlists.end(), pl)) == playlists.end()) {
191                 pl->use ();
192                 playlists.push_back(pl);
193         } else {
194                 playlists.erase (i);
195         }
196
197         PlaylistsChanged ();
198 }
199
200 void
201 Selection::toggle (const list<TimeAxisView*>& track_list)
202 {
203         for (list<TimeAxisView*>::const_iterator i = track_list.begin(); i != track_list.end(); ++i) {
204                 toggle ((*i));
205         }
206 }
207
208 void
209 Selection::toggle (TimeAxisView* track)
210 {
211         TrackSelection::iterator i;
212
213         if ((i = find (tracks.begin(), tracks.end(), track)) == tracks.end()) {
214                 void (Selection::*pmf)(TimeAxisView*) = &Selection::remove;
215                 track->GoingAway.connect (sigc::bind (sigc::mem_fun (*this, pmf), track));
216                 tracks.push_back (track);
217         } else {
218                 tracks.erase (i);
219         }
220
221         TracksChanged();
222 }
223
224 void
225 Selection::toggle (const MidiNoteSelection& midi_note_list)
226 {
227         for (MidiNoteSelection::const_iterator i = midi_note_list.begin(); i != midi_note_list.end(); ++i) {
228                 toggle ((*i));
229         }
230 }
231
232 void
233 Selection::toggle (MidiCutBuffer* midi)
234 {
235         MidiNoteSelection::iterator i;
236
237         if ((i = find (midi_notes.begin(), midi_notes.end(), midi)) == midi_notes.end()) {
238                 midi_notes.push_back (midi);
239         } else {
240                 /* remember that we own the MCB */
241                 delete *i;
242                 midi_notes.erase (i);
243         }
244
245         MidiNotesChanged();
246 }
247
248
249 void
250 Selection::toggle (RegionView* r)
251 {
252         RegionSelection::iterator i;
253
254         if ((i = find (regions.begin(), regions.end(), r)) == regions.end()) {
255                 add (r);
256         } else {
257                 remove (*i);
258         }
259
260         RegionsChanged ();
261 }
262
263 void
264 Selection::toggle (MidiRegionView* mrv)
265 {
266         MidiRegionSelection::iterator i;
267
268         if ((i = find (midi_regions.begin(), midi_regions.end(), mrv)) == midi_regions.end()) {
269                 add (mrv);
270         } else {
271                 midi_regions.erase (i);
272         }
273
274         MidiRegionsChanged ();
275 }
276
277 void
278 Selection::toggle (vector<RegionView*>& r)
279 {
280         RegionSelection::iterator i;
281
282         for (vector<RegionView*>::iterator x = r.begin(); x != r.end(); ++x) {
283                 if ((i = find (regions.begin(), regions.end(), (*x))) == regions.end()) {
284                         add ((*x));
285                 } else {
286                         remove (*x);
287                 }
288         }
289
290         RegionsChanged ();
291 }
292
293 long
294 Selection::toggle (nframes_t start, nframes_t end)
295 {
296         AudioRangeComparator cmp;
297
298         /* XXX this implementation is incorrect */
299
300         time.push_back (AudioRange (start, end, next_time_id++));
301         time.consolidate ();
302         time.sort (cmp);
303
304         TimeChanged ();
305
306         return next_time_id - 1;
307 }
308
309 void
310 Selection::add (boost::shared_ptr<Playlist> pl)
311 {
312         if (find (playlists.begin(), playlists.end(), pl) == playlists.end()) {
313                 pl->use ();
314                 playlists.push_back(pl);
315                 PlaylistsChanged ();
316         }
317 }
318
319 void
320 Selection::add (const list<boost::shared_ptr<Playlist> >& pllist)
321 {
322         bool changed = false;
323
324         for (list<boost::shared_ptr<Playlist> >::const_iterator i = pllist.begin(); i != pllist.end(); ++i) {
325                 if (find (playlists.begin(), playlists.end(), (*i)) == playlists.end()) {
326                         (*i)->use ();
327                         playlists.push_back (*i);
328                         changed = true;
329                 }
330         }
331
332         if (changed) {
333                 PlaylistsChanged ();
334         }
335 }
336
337 void
338 Selection::add (const list<TimeAxisView*>& track_list)
339 {
340         list<TimeAxisView*> added = tracks.add (track_list);
341
342         for (list<TimeAxisView*>::const_iterator i = added.begin(); i != added.end(); ++i) {
343                 void (Selection::*pmf)(TimeAxisView*) = &Selection::remove;
344                 (*i)->GoingAway.connect (sigc::bind (sigc::mem_fun (*this, pmf), (*i)));
345         }
346
347         if (!added.empty()) {
348                 TracksChanged ();
349         }
350 }
351
352 void
353 Selection::add (TimeAxisView* track)
354 {
355         if (find (tracks.begin(), tracks.end(), track) == tracks.end()) {
356                 void (Selection::*pmf)(TimeAxisView*) = &Selection::remove;
357                 track->GoingAway.connect (sigc::bind (sigc::mem_fun (*this, pmf), track));
358                 tracks.push_back (track);
359                 TracksChanged();
360         }
361 }
362
363 void
364 Selection::add (const MidiNoteSelection& midi_list)
365 {
366         const MidiNoteSelection::const_iterator b = midi_list.begin();
367         const MidiNoteSelection::const_iterator e = midi_list.end();
368
369         if (!midi_list.empty()) {
370                 midi_notes.insert (midi_notes.end(), b, e);
371                 MidiNotesChanged ();
372         }
373 }
374
375 void
376 Selection::add (MidiCutBuffer* midi)
377 {
378         /* we take ownership of the MCB */
379
380         if (find (midi_notes.begin(), midi_notes.end(), midi) == midi_notes.end()) {
381                 midi_notes.push_back (midi);
382                 MidiNotesChanged ();
383         }
384 }
385
386 void
387 Selection::add (vector<RegionView*>& v)
388 {
389         /* XXX This method or the add (const RegionSelection&) needs to go
390          */
391
392         bool changed = false;
393
394         for (vector<RegionView*>::iterator i = v.begin(); i != v.end(); ++i) {
395                 if (find (regions.begin(), regions.end(), (*i)) == regions.end()) {
396                         changed = regions.add ((*i));
397                         if (Config->get_link_region_and_track_selection() && changed) {
398                                 add (&(*i)->get_trackview());
399                         }
400                 }
401         }
402
403         if (changed) {
404                 RegionsChanged ();
405         }
406 }
407
408 void
409 Selection::add (const RegionSelection& rs)
410 {
411         /* XXX This method or the add (const vector<RegionView*>&) needs to go
412          */
413
414         bool changed = false;
415
416         for (RegionSelection::const_iterator i = rs.begin(); i != rs.end(); ++i) {
417                 if (find (regions.begin(), regions.end(), (*i)) == regions.end()) {
418                         changed = regions.add ((*i));
419                         if (Config->get_link_region_and_track_selection() && changed) {
420                                 add (&(*i)->get_trackview());
421                         }
422                 }
423         }
424
425         if (changed) {
426                 RegionsChanged ();
427         }
428 }
429
430 void
431 Selection::add (RegionView* r)
432 {
433         if (find (regions.begin(), regions.end(), r) == regions.end()) {
434                 regions.add (r);
435                 if (Config->get_link_region_and_track_selection()) {
436                         add (&r->get_trackview());
437                 }
438                 RegionsChanged ();
439         }
440 }
441
442 void
443 Selection::add (MidiRegionView* mrv)
444 {
445         if (find (midi_regions.begin(), midi_regions.end(), mrv) == midi_regions.end()) {
446                 midi_regions.push_back (mrv);
447                 /* XXX should we do this? */
448 #if 0
449                 if (Config->get_link_region_and_track_selection()) {
450                         add (&mrv->get_trackview());
451                 }
452 #endif
453                 MidiRegionsChanged ();
454         }
455 }
456
457 long
458 Selection::add (nframes_t start, nframes_t end)
459 {
460         AudioRangeComparator cmp;
461
462         /* XXX this implementation is incorrect */
463
464         time.push_back (AudioRange (start, end, next_time_id++));
465         time.consolidate ();
466         time.sort (cmp);
467
468         TimeChanged ();
469
470         return next_time_id - 1;
471 }
472
473 void
474 Selection::replace (uint32_t sid, nframes_t start, nframes_t end)
475 {
476         for (list<AudioRange>::iterator i = time.begin(); i != time.end(); ++i) {
477                 if ((*i).id == sid) {
478                         time.erase (i);
479                         time.push_back (AudioRange(start,end, sid));
480
481                         /* don't consolidate here */
482
483
484                         AudioRangeComparator cmp;
485                         time.sort (cmp);
486
487                         TimeChanged ();
488                         break;
489                 }
490         }
491 }
492
493 void
494 Selection::add (boost::shared_ptr<Evoral::ControlList> cl)
495 {
496         boost::shared_ptr<ARDOUR::AutomationList> al
497                 = boost::dynamic_pointer_cast<ARDOUR::AutomationList>(cl);
498         if (!al) {
499                 warning << "Programming error: Selected list is not an ARDOUR::AutomationList" << endmsg;
500                 return;
501                 return;
502         }
503         if (find (lines.begin(), lines.end(), al) == lines.end()) {
504                 lines.push_back (al);
505                 LinesChanged();
506         }
507 }
508
509 void
510 Selection::remove (TimeAxisView* track)
511 {
512         list<TimeAxisView*>::iterator i;
513         if ((i = find (tracks.begin(), tracks.end(), track)) != tracks.end()) {
514                 tracks.erase (i);
515                 TracksChanged();
516         }
517 }
518
519 void
520 Selection::remove (const list<TimeAxisView*>& track_list)
521 {
522         bool changed = false;
523
524         for (list<TimeAxisView*>::const_iterator i = track_list.begin(); i != track_list.end(); ++i) {
525
526                 list<TimeAxisView*>::iterator x;
527
528                 if ((x = find (tracks.begin(), tracks.end(), (*i))) != tracks.end()) {
529                         tracks.erase (x);
530                         changed = true;
531                 }
532         }
533
534         if (changed) {
535                 TracksChanged();
536         }
537 }
538
539 void
540 Selection::remove (const MidiNoteSelection& midi_list)
541 {
542         bool changed = false;
543
544         for (MidiNoteSelection::const_iterator i = midi_list.begin(); i != midi_list.end(); ++i) {
545
546                 MidiNoteSelection::iterator x;
547
548                 if ((x = find (midi_notes.begin(), midi_notes.end(), (*i))) != midi_notes.end()) {
549                         midi_notes.erase (x);
550                         changed = true;
551                 }
552         }
553
554         if (changed) {
555                 MidiNotesChanged();
556         }
557 }
558
559 void
560 Selection::remove (MidiCutBuffer* midi)
561 {
562         MidiNoteSelection::iterator x;
563
564         if ((x = find (midi_notes.begin(), midi_notes.end(), midi)) != midi_notes.end()) {
565                 /* remember that we own the MCB */
566                 delete *x;
567                 midi_notes.erase (x);
568                 MidiNotesChanged ();
569         }
570 }
571
572 void
573 Selection::remove (boost::shared_ptr<Playlist> track)
574 {
575         list<boost::shared_ptr<Playlist> >::iterator i;
576         if ((i = find (playlists.begin(), playlists.end(), track)) != playlists.end()) {
577                 playlists.erase (i);
578                 PlaylistsChanged();
579         }
580 }
581
582 void
583 Selection::remove (const list<boost::shared_ptr<Playlist> >& pllist)
584 {
585         bool changed = false;
586
587         for (list<boost::shared_ptr<Playlist> >::const_iterator i = pllist.begin(); i != pllist.end(); ++i) {
588
589                 list<boost::shared_ptr<Playlist> >::iterator x;
590
591                 if ((x = find (playlists.begin(), playlists.end(), (*i))) != playlists.end()) {
592                         playlists.erase (x);
593                         changed = true;
594                 }
595         }
596
597         if (changed) {
598                 PlaylistsChanged();
599         }
600 }
601
602 void
603 Selection::remove (RegionView* r)
604 {
605         if (regions.remove (r)) {
606                 RegionsChanged ();
607         }
608
609         if (Config->get_link_region_and_track_selection() && !regions.involves (r->get_trackview())) {
610                 remove (&r->get_trackview());
611         }
612 }
613
614 void
615 Selection::remove (MidiRegionView* mrv)
616 {
617         MidiRegionSelection::iterator x;
618
619         if ((x = find (midi_regions.begin(), midi_regions.end(), mrv)) != midi_regions.end()) {
620                 midi_regions.erase (x);
621                 MidiRegionsChanged ();
622         }
623
624 #if 0
625         /* XXX fix this up ? */
626         if (Config->get_link_region_and_track_selection() && !regions.involves (r->get_trackview())) {
627                 remove (&r->get_trackview());
628         }
629 #endif
630 }
631
632
633 void
634 Selection::remove (uint32_t selection_id)
635 {
636         if (time.empty()) {
637                 return;
638         }
639
640         for (list<AudioRange>::iterator i = time.begin(); i != time.end(); ++i) {
641                 if ((*i).id == selection_id) {
642                         time.erase (i);
643
644                         TimeChanged ();
645                         break;
646                 }
647         }
648 }
649
650 void
651 Selection::remove (nframes_t /*start*/, nframes_t /*end*/)
652 {
653 }
654
655 void
656 Selection::remove (boost::shared_ptr<ARDOUR::AutomationList> ac)
657 {
658         AutomationSelection::iterator i;
659         if ((i = find (lines.begin(), lines.end(), ac)) != lines.end()) {
660                 lines.erase (i);
661                 LinesChanged();
662         }
663 }
664
665 void
666 Selection::set (TimeAxisView* track)
667 {
668         clear_tracks ();
669         add (track);
670 }
671
672 void
673 Selection::set (const list<TimeAxisView*>& track_list)
674 {
675         clear_tracks ();
676         add (track_list);
677 }
678
679 void
680 Selection::set (const MidiNoteSelection& midi_list)
681 {
682         clear_midi_notes ();
683         add (midi_list);
684 }
685
686 void
687 Selection::set (boost::shared_ptr<Playlist> playlist)
688 {
689         clear_playlists ();
690         add (playlist);
691 }
692
693 void
694 Selection::set (const list<boost::shared_ptr<Playlist> >& pllist)
695 {
696         clear_playlists ();
697         add (pllist);
698 }
699
700 void
701 Selection::set (const RegionSelection& rs)
702 {
703         clear_regions();
704         regions = rs;
705         RegionsChanged(); /* EMIT SIGNAL */
706 }
707
708 void
709 Selection::set (MidiRegionView* mrv)
710 {
711         clear_midi_regions ();
712         add (mrv);
713 }
714
715 void
716 Selection::set (RegionView* r, bool also_clear_tracks)
717 {
718         clear_regions ();
719         if (also_clear_tracks) {
720                 clear_tracks ();
721         }
722         add (r);
723 }
724
725 void
726 Selection::set (vector<RegionView*>& v)
727 {
728         clear_regions ();
729         if (Config->get_link_region_and_track_selection()) {
730                 clear_tracks ();
731                 // make sure to deselect any automation selections
732                 clear_points();
733         }
734         add (v);
735 }
736
737 long
738 Selection::set (TimeAxisView* track, nframes_t start, nframes_t end)
739 {
740         if ((start == 0 && end == 0) || end < start) {
741                 return 0;
742         }
743
744         if (time.empty()) {
745                 time.push_back (AudioRange (start, end, next_time_id++));
746         } else {
747                 /* reuse the first entry, and remove all the rest */
748
749                 while (time.size() > 1) {
750                         time.pop_front();
751                 }
752                 time.front().start = start;
753                 time.front().end = end;
754         }
755
756         if (track) {
757                 time.track = track;
758                 time.group = track->route_group();
759         } else {
760                 time.track = 0;
761                 time.group = 0;
762         }
763
764         time.consolidate ();
765
766         TimeChanged ();
767
768         return time.front().id;
769 }
770
771 void
772 Selection::set (boost::shared_ptr<Evoral::ControlList> ac)
773 {
774         lines.clear();
775         add (ac);
776 }
777
778 bool
779 Selection::selected (Marker* m)
780 {
781         return find (markers.begin(), markers.end(), m) != markers.end();
782 }
783
784 bool
785 Selection::selected (TimeAxisView* tv)
786 {
787         return find (tracks.begin(), tracks.end(), tv) != tracks.end();
788 }
789
790 bool
791 Selection::selected (RegionView* rv)
792 {
793         return find (regions.begin(), regions.end(), rv) != regions.end();
794 }
795
796 bool
797 Selection::empty (bool internal_selection)
798 {
799         bool object_level_empty =  regions.empty () &&
800                 tracks.empty () &&
801                 points.empty () &&
802                 playlists.empty () &&
803                 lines.empty () &&
804                 time.empty () &&
805                 playlists.empty () &&
806                 markers.empty() &&
807                 midi_regions.empty()
808                 ;
809
810         if (!internal_selection) {
811                 return object_level_empty;
812         }
813
814         /* this is intended to really only apply when using a Selection
815            as a cut buffer.
816         */
817
818         return object_level_empty && midi_notes.empty();
819 }
820
821 void
822 Selection::toggle (const vector<AutomationSelectable*>& autos)
823 {
824         for (vector<AutomationSelectable*>::const_iterator x = autos.begin(); x != autos.end(); ++x) {
825                 if ((*x)->get_selected()) {
826                         points.remove (**x);
827                 } else {
828                         points.push_back (**x);
829                 }
830
831                 delete *x;
832         }
833
834         PointsChanged (); /* EMIT SIGNAL */
835 }
836
837 void
838 Selection::toggle (list<Selectable*>& selectables)
839 {
840         RegionView* rv;
841         AutomationSelectable* as;
842         vector<RegionView*> rvs;
843         vector<AutomationSelectable*> autos;
844
845         for (std::list<Selectable*>::iterator i = selectables.begin(); i != selectables.end(); ++i) {
846                 if ((rv = dynamic_cast<RegionView*> (*i)) != 0) {
847                         rvs.push_back (rv);
848                 } else if ((as = dynamic_cast<AutomationSelectable*> (*i)) != 0) {
849                         autos.push_back (as);
850                 } else {
851                         fatal << _("programming error: ")
852                               << X_("unknown selectable type passed to Selection::toggle()")
853                               << endmsg;
854                         /*NOTREACHED*/
855                 }
856         }
857
858         if (!rvs.empty()) {
859                 toggle (rvs);
860         }
861
862         if (!autos.empty()) {
863                 toggle (autos);
864         }
865 }
866
867 void
868 Selection::set (list<Selectable*>& selectables)
869 {
870         clear_regions();
871         clear_points ();
872         add (selectables);
873 }
874
875
876 void
877 Selection::add (list<Selectable*>& selectables)
878 {
879         RegionView* rv;
880         AutomationSelectable* as;
881         vector<RegionView*> rvs;
882         vector<AutomationSelectable*> autos;
883
884         for (std::list<Selectable*>::iterator i = selectables.begin(); i != selectables.end(); ++i) {
885                 if ((rv = dynamic_cast<RegionView*> (*i)) != 0) {
886                         rvs.push_back (rv);
887                 } else if ((as = dynamic_cast<AutomationSelectable*> (*i)) != 0) {
888                         autos.push_back (as);
889                 } else {
890                         fatal << _("programming error: ")
891                               << X_("unknown selectable type passed to Selection::add()")
892                               << endmsg;
893                         /*NOTREACHED*/
894                 }
895         }
896
897         if (!rvs.empty()) {
898                 add (rvs);
899         }
900
901         if (!autos.empty()) {
902                 add (autos);
903         }
904 }
905
906 void
907 Selection::clear_points ()
908 {
909         if (!points.empty()) {
910                 points.clear ();
911                 PointsChanged ();
912         }
913 }
914
915 void
916 Selection::add (vector<AutomationSelectable*>& autos)
917 {
918         for (vector<AutomationSelectable*>::iterator i = autos.begin(); i != autos.end(); ++i) {
919                 points.push_back (**i);
920         }
921
922         PointsChanged ();
923 }
924
925 void
926 Selection::set (Marker* m)
927 {
928         clear_markers ();
929         add (m);
930 }
931
932 void
933 Selection::toggle (Marker* m)
934 {
935         MarkerSelection::iterator i;
936
937         if ((i = find (markers.begin(), markers.end(), m)) == markers.end()) {
938                 add (m);
939         } else {
940                 remove (m);
941         }
942 }
943
944 void
945 Selection::remove (Marker* m)
946 {
947         MarkerSelection::iterator i;
948
949         if ((i = find (markers.begin(), markers.end(), m)) != markers.end()) {
950                 markers.erase (i);
951                 MarkersChanged();
952         }
953 }
954
955 void
956 Selection::add (Marker* m)
957 {
958         if (find (markers.begin(), markers.end(), m) == markers.end()) {
959
960                 /* disambiguate which remove() for the compiler */
961
962                 void (Selection::*pmf)(Marker*) = &Selection::remove;
963
964                 m->GoingAway.connect (sigc::bind (sigc::mem_fun (*this, pmf), m));
965
966                 markers.push_back (m);
967                 MarkersChanged();
968         }
969 }
970
971 void
972 Selection::add (const list<Marker*>& m)
973 {
974         markers.insert (markers.end(), m.begin(), m.end());
975         MarkersChanged ();
976 }
977
978 void
979 MarkerSelection::range (nframes64_t& s, nframes64_t& e)
980 {
981         s = max_frames;
982         e = 0;
983
984         for (MarkerSelection::iterator i = begin(); i != end(); ++i) {
985
986                 if ((*i)->position() < s) {
987                         s = (*i)->position();
988                 }
989
990                 if ((*i)->position() > e) {
991                         e = (*i)->position();
992                 }
993         }
994
995         s = std::min (s, e);
996         e = std::max (s, e);
997 }