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