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