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