b5454c4a123ca6afcad970f79657b2eef1f9476b
[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 "region_view.h"
29 #include "selection.h"
30 #include "selection_templates.h"
31 #include "time_axis_view.h"
32 #include "automation_time_axis.h"
33 #include "public_editor.h"
34
35 #include "i18n.h"
36
37 using namespace std;
38 using namespace ARDOUR;
39 using namespace PBD;
40 using namespace sigc;
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                 midi_notes.clear ();
123                 MidiNotesChanged ();
124         }
125 }
126
127 void
128 Selection::clear_midi_regions ()
129 {
130         if (!midi_regions.empty()) {
131                 midi_regions.clear ();
132                 MidiRegionsChanged ();
133         }
134 }
135
136 void
137 Selection::clear_time ()
138 {
139         time.track = 0;
140         time.group = 0;
141         time.clear();
142
143         TimeChanged ();
144 }
145
146 void
147 Selection::clear_playlists ()
148 {
149         /* Selections own their playlists */
150
151         for (PlaylistSelection::iterator i = playlists.begin(); i != playlists.end(); ++i) {
152                 /* selections own their own regions, which are copies of the "originals". make them go away */
153                 (*i)->drop_regions ();
154                 (*i)->release ();
155         }
156
157         if (!playlists.empty()) {
158                 playlists.clear ();
159                 PlaylistsChanged();
160         }
161 }
162
163 void
164 Selection::clear_lines ()
165 {
166         if (!lines.empty()) {
167                 lines.clear ();
168                 LinesChanged();
169         }
170 }
171
172 void
173 Selection::clear_markers ()
174 {
175         if (!markers.empty()) {
176                 markers.clear ();
177                 MarkersChanged();
178         }
179 }
180
181 void
182 Selection::toggle (boost::shared_ptr<Playlist> pl)
183 {
184         PlaylistSelection::iterator i;
185
186         if ((i = find (playlists.begin(), playlists.end(), pl)) == playlists.end()) {
187                 pl->use ();
188                 playlists.push_back(pl);
189         } else {
190                 playlists.erase (i);
191         }
192
193         PlaylistsChanged ();
194 }
195
196 void
197 Selection::toggle (const list<TimeAxisView*>& track_list)
198 {
199         for (list<TimeAxisView*>::const_iterator i = track_list.begin(); i != track_list.end(); ++i) {
200                 toggle ((*i));
201         }
202 }
203
204 void
205 Selection::toggle (TimeAxisView* track)
206 {
207         TrackSelection::iterator i;
208         
209         if ((i = find (tracks.begin(), tracks.end(), track)) == tracks.end()) {
210                 void (Selection::*pmf)(TimeAxisView*) = &Selection::remove;
211                 track->GoingAway.connect (sigc::bind (mem_fun (*this, pmf), track));
212                 tracks.push_back (track);
213         } else {
214                 tracks.erase (i);
215         }
216
217         TracksChanged();
218 }
219
220 void
221 Selection::toggle (const MidiNoteSelection& midi_note_list)
222 {
223         for (MidiNoteSelection::const_iterator i = midi_note_list.begin(); i != midi_note_list.end(); ++i) {
224                 toggle ((*i));
225         }
226 }
227
228 void
229 Selection::toggle (MidiCutBuffer* midi)
230 {
231         MidiNoteSelection::iterator i;
232         
233         if ((i = find (midi_notes.begin(), midi_notes.end(), midi)) == midi_notes.end()) {
234                 midi_notes.push_back (midi);
235         } else {
236                 midi_notes.erase (i);
237         }
238         
239         MidiNotesChanged();
240 }
241
242
243 void
244 Selection::toggle (RegionView* r)
245 {
246         RegionSelection::iterator i;
247
248         if ((i = find (regions.begin(), regions.end(), r)) == regions.end()) {
249                 add (r);
250         } else {
251                 remove (*i);
252         }
253
254         RegionsChanged ();
255 }
256
257 void
258 Selection::toggle (MidiRegionView* mrv)
259 {
260         MidiRegionSelection::iterator i;
261
262         if ((i = find (midi_regions.begin(), midi_regions.end(), mrv)) == midi_regions.end()) {
263                 add (mrv);
264         } else {
265                 midi_regions.erase (i);
266         }
267
268         MidiRegionsChanged ();
269 }
270
271 void
272 Selection::toggle (vector<RegionView*>& r)
273 {
274         RegionSelection::iterator i;
275
276         for (vector<RegionView*>::iterator x = r.begin(); x != r.end(); ++x) {
277                 if ((i = find (regions.begin(), regions.end(), (*x))) == regions.end()) {
278                         add ((*x));
279                 } else {
280                         remove (*x);
281                 }
282         }
283
284         RegionsChanged ();
285 }
286
287 long
288 Selection::toggle (nframes_t start, nframes_t end)
289 {
290         AudioRangeComparator cmp;
291
292         /* XXX this implementation is incorrect */
293
294         time.push_back (AudioRange (start, end, next_time_id++));
295         time.consolidate ();
296         time.sort (cmp);
297         
298         TimeChanged ();
299
300         return next_time_id - 1;
301 }
302
303 void
304 Selection::add (boost::shared_ptr<Playlist> pl)
305 {
306         if (find (playlists.begin(), playlists.end(), pl) == playlists.end()) {
307                 pl->use ();
308                 playlists.push_back(pl);
309                 PlaylistsChanged ();
310         }
311 }
312
313 void
314 Selection::add (const list<boost::shared_ptr<Playlist> >& pllist)
315 {
316         bool changed = false;
317
318         for (list<boost::shared_ptr<Playlist> >::const_iterator i = pllist.begin(); i != pllist.end(); ++i) {
319                 if (find (playlists.begin(), playlists.end(), (*i)) == playlists.end()) {
320                         (*i)->use ();
321                         playlists.push_back (*i);
322                         changed = true;
323                 }
324         }
325         
326         if (changed) {
327                 PlaylistsChanged ();
328         }
329 }
330
331 void
332 Selection::add (const list<TimeAxisView*>& track_list)
333 {
334         list<TimeAxisView*> added = tracks.add (track_list);
335
336         for (list<TimeAxisView*>::const_iterator i = added.begin(); i != added.end(); ++i) {
337                 void (Selection::*pmf)(TimeAxisView*) = &Selection::remove;
338                 (*i)->GoingAway.connect (sigc::bind (mem_fun (*this, pmf), (*i)));
339         }
340         
341         if (!added.empty()) {
342                 TracksChanged ();
343         }
344 }
345
346 void
347 Selection::add (TimeAxisView* track)
348 {
349         if (find (tracks.begin(), tracks.end(), track) == tracks.end()) {
350                 void (Selection::*pmf)(TimeAxisView*) = &Selection::remove;
351                 track->GoingAway.connect (sigc::bind (mem_fun (*this, pmf), track));
352                 tracks.push_back (track);
353                 TracksChanged();
354         }
355 }
356
357 void
358 Selection::add (const MidiNoteSelection& midi_list)
359 {
360         const MidiNoteSelection::const_iterator b = midi_list.begin();
361         const MidiNoteSelection::const_iterator e = midi_list.end();
362
363         if (!midi_list.empty()) {
364                 midi_notes.insert (midi_notes.end(), b, e);
365                 MidiNotesChanged ();
366         }
367 }
368
369 void
370 Selection::add (MidiCutBuffer* midi)
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 list<TimeAxisView*>& track_list)
513 {
514         bool changed = false;
515
516         for (list<TimeAxisView*>::const_iterator i = track_list.begin(); i != track_list.end(); ++i) {
517
518                 list<TimeAxisView*>::iterator x;
519
520                 if ((x = find (tracks.begin(), tracks.end(), (*i))) != tracks.end()) {
521                         tracks.erase (x);
522                         changed = true;
523                 }
524         }
525
526         if (changed) {
527                 TracksChanged();
528         }
529 }
530
531 void
532 Selection::remove (const MidiNoteSelection& midi_list)
533 {
534         bool changed = false;
535
536         for (MidiNoteSelection::const_iterator i = midi_list.begin(); i != midi_list.end(); ++i) {
537
538                 MidiNoteSelection::iterator x;
539
540                 if ((x = find (midi_notes.begin(), midi_notes.end(), (*i))) != midi_notes.end()) {
541                         midi_notes.erase (x);
542                         changed = true;
543                 }
544         }
545
546         if (changed) {
547                 MidiNotesChanged();
548         }
549 }
550
551 void
552 Selection::remove (MidiCutBuffer* midi)
553 {
554         MidiNoteSelection::iterator x;
555         
556         if ((x = find (midi_notes.begin(), midi_notes.end(), midi)) != midi_notes.end()) {
557                 midi_notes.erase (x);
558                 MidiNotesChanged ();
559         }
560 }
561
562 void
563 Selection::remove (boost::shared_ptr<Playlist> track)
564 {
565         list<boost::shared_ptr<Playlist> >::iterator i;
566         if ((i = find (playlists.begin(), playlists.end(), track)) != playlists.end()) {
567                 playlists.erase (i);
568                 PlaylistsChanged();
569         }
570 }
571
572 void
573 Selection::remove (const list<boost::shared_ptr<Playlist> >& pllist)
574 {
575         bool changed = false;
576
577         for (list<boost::shared_ptr<Playlist> >::const_iterator i = pllist.begin(); i != pllist.end(); ++i) {
578
579                 list<boost::shared_ptr<Playlist> >::iterator x;
580
581                 if ((x = find (playlists.begin(), playlists.end(), (*i))) != playlists.end()) {
582                         playlists.erase (x);
583                         changed = true;
584                 }
585         }
586
587         if (changed) {
588                 PlaylistsChanged();
589         }
590 }
591
592 void
593 Selection::remove (RegionView* r)
594 {
595         if (regions.remove (r)) {
596                 RegionsChanged ();
597         }
598
599         if (Config->get_link_region_and_track_selection() && !regions.involves (r->get_trackview())) {
600                 remove (&r->get_trackview());
601         }
602 }
603
604 void
605 Selection::remove (MidiRegionView* mrv)
606 {
607         MidiRegionSelection::iterator x;
608
609         if ((x = find (midi_regions.begin(), midi_regions.end(), mrv)) != midi_regions.end()) {
610                 midi_regions.erase (x);
611                 MidiRegionsChanged ();
612         }
613
614 #if 0
615         /* XXX fix this up ? */
616         if (Config->get_link_region_and_track_selection() && !regions.involves (r->get_trackview())) {
617                 remove (&r->get_trackview());
618         }
619 #endif
620 }
621
622
623 void
624 Selection::remove (uint32_t selection_id)
625 {
626         if (time.empty()) {
627                 return;
628         }
629
630         for (list<AudioRange>::iterator i = time.begin(); i != time.end(); ++i) {
631                 if ((*i).id == selection_id) {
632                         time.erase (i);
633                                                 
634                         TimeChanged ();
635                         break;
636                 }
637         }
638 }
639
640 void
641 Selection::remove (nframes_t /*start*/, nframes_t /*end*/)
642 {
643 }
644
645 void
646 Selection::remove (boost::shared_ptr<ARDOUR::AutomationList> ac)
647 {
648         AutomationSelection::iterator i;
649         if ((i = find (lines.begin(), lines.end(), ac)) != lines.end()) {
650                 lines.erase (i);
651                 LinesChanged();
652         }
653 }
654
655 void
656 Selection::set (TimeAxisView* track)
657 {
658         clear_tracks ();
659         add (track);
660 }
661
662 void
663 Selection::set (const list<TimeAxisView*>& track_list)
664 {
665         clear_tracks ();
666         add (track_list);
667 }
668
669 void
670 Selection::set (const MidiNoteSelection& midi_list)
671 {
672         clear_midi_notes ();
673         add (midi_list);
674 }
675
676 void
677 Selection::set (boost::shared_ptr<Playlist> playlist)
678 {
679         clear_playlists ();
680         add (playlist);
681 }
682
683 void
684 Selection::set (const list<boost::shared_ptr<Playlist> >& pllist)
685 {
686         clear_playlists ();
687         add (pllist);
688 }
689
690 void
691 Selection::set (const RegionSelection& rs)
692 {
693         clear_regions();
694         regions = rs;
695         RegionsChanged(); /* EMIT SIGNAL */
696 }
697
698 void
699 Selection::set (MidiRegionView* mrv) 
700 {
701         clear_midi_regions ();
702         add (mrv);
703 }
704
705 void
706 Selection::set (RegionView* r, bool also_clear_tracks)
707 {
708         clear_regions ();
709         if (also_clear_tracks) {
710                 clear_tracks ();
711         }
712         add (r);
713 }
714
715 void
716 Selection::set (vector<RegionView*>& v)
717 {
718         clear_regions ();
719         if (Config->get_link_region_and_track_selection()) {
720                 clear_tracks ();
721                 // make sure to deselect any automation selections
722                 clear_points();
723         }
724         add (v);
725 }
726
727 long
728 Selection::set (TimeAxisView* track, nframes_t start, nframes_t end)
729 {
730         if ((start == 0 && end == 0) || end < start) {
731                 return 0;
732         }
733
734         if (time.empty()) {
735                 time.push_back (AudioRange (start, end, next_time_id++));
736         } else {
737                 /* reuse the first entry, and remove all the rest */
738
739                 while (time.size() > 1) {
740                         time.pop_front();
741                 }
742                 time.front().start = start;
743                 time.front().end = end;
744         }
745
746         if (track) {
747                 time.track = track;
748                 time.group = track->route_group();
749         } else {
750                 time.track = 0;
751                 time.group = 0;
752         }
753
754         time.consolidate ();
755
756         TimeChanged ();
757
758         return time.front().id;
759 }
760
761 void
762 Selection::set (boost::shared_ptr<Evoral::ControlList> ac)
763 {
764         lines.clear();
765         add (ac);
766 }
767
768 bool
769 Selection::selected (Marker* m)
770 {
771         return find (markers.begin(), markers.end(), m) != markers.end();
772 }
773
774 bool
775 Selection::selected (TimeAxisView* tv)
776 {
777         return find (tracks.begin(), tracks.end(), tv) != tracks.end();
778 }
779
780 bool
781 Selection::selected (RegionView* rv)
782 {
783         return find (regions.begin(), regions.end(), rv) != regions.end();
784 }
785
786 bool
787 Selection::empty (bool internal_selection)
788 {
789         bool object_level_empty =  regions.empty () &&
790                 tracks.empty () &&
791                 points.empty () && 
792                 playlists.empty () && 
793                 lines.empty () &&
794                 time.empty () &&
795                 playlists.empty () &&
796                 markers.empty() &&
797                 midi_regions.empty()
798                 ;
799
800         if (!internal_selection) {
801                 return object_level_empty;
802         }
803
804         /* this is intended to really only apply when using a Selection
805            as a cut buffer.
806         */
807
808         return object_level_empty && midi_notes.empty();
809 }
810
811 void
812 Selection::toggle (const vector<AutomationSelectable*>& autos)
813 {
814         for (vector<AutomationSelectable*>::const_iterator x = autos.begin(); x != autos.end(); ++x) {
815                 if ((*x)->get_selected()) {
816                         points.remove (**x);
817                 } else {
818                         points.push_back (**x);
819                 }
820
821                 delete *x;
822         }
823
824         PointsChanged (); /* EMIT SIGNAL */
825 }
826
827 void
828 Selection::toggle (list<Selectable*>& selectables)
829 {
830         RegionView* rv;
831         AutomationSelectable* as;
832         vector<RegionView*> rvs;
833         vector<AutomationSelectable*> autos;
834
835         for (std::list<Selectable*>::iterator i = selectables.begin(); i != selectables.end(); ++i) {
836                 if ((rv = dynamic_cast<RegionView*> (*i)) != 0) {
837                         rvs.push_back (rv);
838                 } else if ((as = dynamic_cast<AutomationSelectable*> (*i)) != 0) {
839                         autos.push_back (as);
840                 } else {
841                         fatal << _("programming error: ")
842                               << X_("unknown selectable type passed to Selection::toggle()")
843                               << endmsg;
844                         /*NOTREACHED*/
845                 }
846         }
847
848         if (!rvs.empty()) {
849                 toggle (rvs);
850         } 
851
852         if (!autos.empty()) {
853                 toggle (autos);
854         } 
855 }
856
857 void
858 Selection::set (list<Selectable*>& selectables)
859 {
860         clear_regions();
861         clear_points ();
862         add (selectables);
863 }
864
865
866 void
867 Selection::add (list<Selectable*>& selectables)
868 {
869         RegionView* rv;
870         AutomationSelectable* as;
871         vector<RegionView*> rvs;
872         vector<AutomationSelectable*> autos;
873
874         for (std::list<Selectable*>::iterator i = selectables.begin(); i != selectables.end(); ++i) {
875                 if ((rv = dynamic_cast<RegionView*> (*i)) != 0) {
876                         rvs.push_back (rv);
877                 } else if ((as = dynamic_cast<AutomationSelectable*> (*i)) != 0) {
878                         autos.push_back (as);
879                 } else {
880                         fatal << _("programming error: ")
881                               << X_("unknown selectable type passed to Selection::add()")
882                               << endmsg;
883                         /*NOTREACHED*/
884                 }
885         }
886
887         if (!rvs.empty()) {
888                 add (rvs);
889         } 
890
891         if (!autos.empty()) {
892                 add (autos);
893         } 
894 }
895
896 void
897 Selection::clear_points ()
898 {
899         if (!points.empty()) {
900                 points.clear ();
901                 PointsChanged ();
902         }
903 }
904
905 void
906 Selection::add (vector<AutomationSelectable*>& autos)
907 {
908         for (vector<AutomationSelectable*>::iterator i = autos.begin(); i != autos.end(); ++i) {
909                 points.push_back (**i);
910         }
911
912         PointsChanged ();
913 }
914
915 void
916 Selection::set (Marker* m)
917 {
918         clear_markers ();
919         add (m);
920 }
921
922 void
923 Selection::toggle (Marker* m)
924 {
925         MarkerSelection::iterator i;
926         
927         if ((i = find (markers.begin(), markers.end(), m)) == markers.end()) {
928                 add (m);
929         } else {
930                 remove (m);
931         }
932 }
933
934 void
935 Selection::remove (Marker* m)
936 {
937         MarkerSelection::iterator i;
938
939         if ((i = find (markers.begin(), markers.end(), m)) != markers.end()) {
940                 markers.erase (i);
941                 MarkersChanged();
942         }
943 }
944
945 void
946 Selection::add (Marker* m)
947 {
948         if (find (markers.begin(), markers.end(), m) == markers.end()) {
949
950                 /* disambiguate which remove() for the compiler */
951                 
952                 void (Selection::*pmf)(Marker*) = &Selection::remove;
953
954                 m->GoingAway.connect (bind (mem_fun (*this, pmf), m));
955
956                 markers.push_back (m);
957                 MarkersChanged();
958         }
959 }
960
961 void
962 Selection::add (const list<Marker*>& m)
963 {
964         markers.insert (markers.end(), m.begin(), m.end());
965         MarkersChanged ();
966 }
967
968 void
969 MarkerSelection::range (nframes64_t& s, nframes64_t& e)
970 {
971         s = max_frames;
972         e = 0;
973
974         for (MarkerSelection::iterator i = begin(); i != end(); ++i) {
975
976                 if ((*i)->position() < s) {
977                         s = (*i)->position();
978                 } 
979
980                 if ((*i)->position() > e) {
981                         e = (*i)->position();
982                 }
983         }
984
985         s = std::min (s, e);
986         e = std::max (s, e);
987 }