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