fix crash when copy'ing latent plugins
[ardour.git] / libs / ardour / location.cc
1 /*
2     Copyright (C) 2000 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 <set>
22 #include <cstdio> /* for sprintf */
23 #include <unistd.h>
24 #include <cerrno>
25 #include <ctime>
26 #include <list>
27
28 #include "pbd/convert.h"
29 #include "pbd/stl_delete.h"
30 #include "pbd/xml++.h"
31 #include "pbd/enumwriter.h"
32
33 #include "ardour/location.h"
34 #include "ardour/midi_scene_change.h"
35 #include "ardour/session.h"
36 #include "ardour/audiofilesource.h"
37 #include "ardour/tempo.h"
38
39 #include "pbd/i18n.h"
40
41 using namespace std;
42 using namespace ARDOUR;
43 using namespace PBD;
44
45 PBD::Signal0<void> Location::scene_changed;
46 PBD::Signal1<void,Location*> Location::name_changed;
47 PBD::Signal1<void,Location*> Location::end_changed;
48 PBD::Signal1<void,Location*> Location::start_changed;
49 PBD::Signal1<void,Location*> Location::flags_changed;
50 PBD::Signal1<void,Location*> Location::lock_changed;
51 PBD::Signal1<void,Location*> Location::position_lock_style_changed;
52 PBD::Signal1<void,Location*> Location::changed;
53
54 Location::Location (Session& s)
55         : SessionHandleRef (s)
56         , _start (0)
57         , _end (0)
58         , _flags (Flags (0))
59         , _locked (false)
60         , _position_lock_style (AudioTime)
61 {
62         assert (_start >= 0);
63         assert (_end >= 0);
64 }
65
66 /** Construct a new Location, giving it the position lock style determined by glue-new-markers-to-bars-and-beats */
67 Location::Location (Session& s, framepos_t sample_start, framepos_t sample_end, const std::string &name, Flags bits)
68         : SessionHandleRef (s)
69         , _name (name)
70         , _start (sample_start)
71         , _end (sample_end)
72         , _flags (bits)
73         , _locked (false)
74         , _position_lock_style (s.config.get_glue_new_markers_to_bars_and_beats() ? MusicTime : AudioTime)
75
76 {
77         recompute_bbt_from_frames ();
78
79         assert (_start >= 0);
80         assert (_end >= 0);
81 }
82
83 Location::Location (const Location& other)
84         : SessionHandleRef (other._session)
85         , StatefulDestructible()
86         , _name (other._name)
87         , _start (other._start)
88         , _bbt_start (other._bbt_start)
89         , _end (other._end)
90         , _bbt_end (other._bbt_end)
91         , _flags (other._flags)
92         , _position_lock_style (other._position_lock_style)
93
94 {
95         /* copy is not locked even if original was */
96
97         _locked = false;
98
99         assert (_start >= 0);
100         assert (_end >= 0);
101
102         /* scene change is NOT COPIED */
103 }
104
105 Location::Location (Session& s, const XMLNode& node)
106         : SessionHandleRef (s)
107         , _flags (Flags (0))
108         , _position_lock_style (AudioTime)
109 {
110         /* Note: _position_lock_style is initialised above in case set_state doesn't set it
111            (for 2.X session file compatibility).
112         */
113
114         if (set_state (node, Stateful::loading_state_version)) {
115                 throw failed_constructor ();
116         }
117
118         assert (_start >= 0);
119         assert (_end >= 0);
120 }
121
122 bool
123 Location::operator== (const Location& other)
124 {
125         if (_name != other._name ||
126             _start != other._start ||
127             _end != other._end ||
128             _bbt_start != other._bbt_start ||
129             _bbt_end != other._bbt_end ||
130             _flags != other._flags ||
131             _position_lock_style != other._position_lock_style) {
132                 return false;
133         }
134         return true;
135 }
136
137 Location*
138 Location::operator= (const Location& other)
139 {
140         if (this == &other) {
141                 return this;
142         }
143
144         _name = other._name;
145         _start = other._start;
146         _bbt_start = other._bbt_start;
147         _end = other._end;
148         _bbt_end = other._bbt_end;
149         _flags = other._flags;
150         _position_lock_style = other._position_lock_style;
151
152         /* XXX need to copy scene change */
153
154         /* copy is not locked even if original was */
155
156         _locked = false;
157
158         /* "changed" not emitted on purpose */
159
160         assert (_start >= 0);
161         assert (_end >= 0);
162
163         return this;
164 }
165
166 /** Set location name
167  */
168
169 void
170 Location::set_name (const std::string& str)
171 {
172         _name = str;
173
174         name_changed (this); /* EMIT SIGNAL */
175         NameChanged  (); /* EMIT SIGNAL */
176 }
177
178 /** Set start position.
179  *  @param s New start.
180  *  @param force true to force setting, even if the given new start is after the current end.
181  *  @param allow_bbt_recompute True to recompute BBT start time from the new given start time.
182  */
183 int
184 Location::set_start (framepos_t s, bool force, bool allow_bbt_recompute)
185 {
186         if (s < 0) {
187                 return -1;
188         }
189
190         if (_locked) {
191                 return -1;
192         }
193
194         if (!force) {
195                 if (((is_auto_punch() || is_auto_loop()) && s >= _end) || (!is_mark() && s > _end)) {
196                         return -1;
197                 }
198         }
199
200         if (is_mark()) {
201                 if (_start != s) {
202                         _start = s;
203                         _end = s;
204                         if (allow_bbt_recompute) {
205                                 recompute_bbt_from_frames ();
206                         }
207
208                         start_changed (this); /* EMIT SIGNAL */
209                         StartChanged (); /* EMIT SIGNAL */
210                         //end_changed (this); /* EMIT SIGNAL */
211                         //EndChanged (); /* EMIT SIGNAL */
212                 }
213
214                 /* moving the start (position) of a marker with a scene change
215                    requires an update in the Scene Changer.
216                 */
217
218                 if (_scene_change) {
219                         scene_changed (); /* EMIT SIGNAL */
220                 }
221
222                 assert (_start >= 0);
223                 assert (_end >= 0);
224
225                 return 0;
226         } else if (!force) {
227                 /* range locations must exceed a minimum duration */
228                 if (_end - s < Config->get_range_location_minimum()) {
229                         return -1;
230                 }
231         }
232
233         if (s != _start) {
234
235                 framepos_t const old = _start;
236
237                 _start = s;
238                 if (allow_bbt_recompute) {
239                         recompute_bbt_from_frames ();
240                 }
241                 start_changed (this); /* EMIT SIGNAL */
242                 StartChanged (); /* EMIT SIGNAL */
243
244                 if (is_session_range ()) {
245                         Session::StartTimeChanged (old); /* EMIT SIGNAL */
246                         AudioFileSource::set_header_position_offset (s);
247                 }
248         }
249
250         assert (_start >= 0);
251
252         return 0;
253 }
254
255 /** Set end position.
256  *  @param s New end.
257  *  @param force true to force setting, even if the given new end is before the current start.
258  *  @param allow_bbt_recompute True to recompute BBT end time from the new given end time.
259  */
260 int
261 Location::set_end (framepos_t e, bool force, bool allow_bbt_recompute)
262 {
263         if (e < 0) {
264                 return -1;
265         }
266
267         if (_locked) {
268                 return -1;
269         }
270
271         if (!force) {
272                 if (((is_auto_punch() || is_auto_loop()) && e <= _start) || e < _start) {
273                         return -1;
274                 }
275         }
276
277         if (is_mark()) {
278                 if (_start != e) {
279                         _start = e;
280                         _end = e;
281                         if (allow_bbt_recompute) {
282                                 recompute_bbt_from_frames ();
283                         }
284                         //start_changed (this); /* EMIT SIGNAL */
285                         //StartChanged (); /* EMIT SIGNAL */
286                         end_changed (this); /* EMIT SIGNAL */
287                         EndChanged (); /* EMIT SIGNAL */
288                 }
289
290                 assert (_start >= 0);
291                 assert (_end >= 0);
292
293                 return 0;
294         } else if (!force) {
295                 /* range locations must exceed a minimum duration */
296                 if (e - _start < Config->get_range_location_minimum()) {
297                         return -1;
298                 }
299         }
300
301         if (e != _end) {
302
303                 framepos_t const old = _end;
304
305                 _end = e;
306                 if (allow_bbt_recompute) {
307                         recompute_bbt_from_frames ();
308                 }
309
310                 end_changed(this); /* EMIT SIGNAL */
311                 EndChanged(); /* EMIT SIGNAL */
312
313                 if (is_session_range()) {
314                         Session::EndTimeChanged (old); /* EMIT SIGNAL */
315                 }
316         }
317
318         assert (_end >= 0);
319
320         return 0;
321 }
322
323 int
324 Location::set (framepos_t s, framepos_t e, bool allow_bbt_recompute)
325 {
326         if (s < 0 || e < 0) {
327                 return -1;
328         }
329
330         /* check validity */
331         if (((is_auto_punch() || is_auto_loop()) && s >= e) || (!is_mark() && s > e)) {
332                 return -1;
333         }
334
335         bool start_change = false;
336         bool end_change = false;
337
338         if (is_mark()) {
339
340                 if (_start != s) {
341                         _start = s;
342                         _end = s;
343
344                         if (allow_bbt_recompute) {
345                                 recompute_bbt_from_frames ();
346                         }
347
348                         start_change = true;
349                         end_change = true;
350                 }
351
352                 assert (_start >= 0);
353                 assert (_end >= 0);
354
355         } else {
356
357                 /* range locations must exceed a minimum duration */
358                 if (e - s < Config->get_range_location_minimum()) {
359                         return -1;
360                 }
361
362                 if (s != _start) {
363
364                         framepos_t const old = _start;
365                         _start = s;
366
367                         if (allow_bbt_recompute) {
368                                 recompute_bbt_from_frames ();
369                         }
370
371                         start_change = true;
372
373                         if (is_session_range ()) {
374                                 Session::StartTimeChanged (old); /* EMIT SIGNAL */
375                                 AudioFileSource::set_header_position_offset (s);
376                         }
377                 }
378
379
380                 if (e != _end) {
381
382                         framepos_t const old = _end;
383                         _end = e;
384
385                         if (allow_bbt_recompute) {
386                                 recompute_bbt_from_frames ();
387                         }
388
389                         end_change = true;
390
391                         if (is_session_range()) {
392                                 Session::EndTimeChanged (old); /* EMIT SIGNAL */
393                         }
394                 }
395
396                 assert (_end >= 0);
397         }
398
399         if (start_change && end_change) {
400                 changed (this);
401                 Changed ();
402         } else if (start_change) {
403                 start_changed(this); /* EMIT SIGNAL */
404                 StartChanged(); /* EMIT SIGNAL */
405         } else if (end_change) {
406                 end_changed(this); /* EMIT SIGNAL */
407                 EndChanged(); /* EMIT SIGNAL */
408         }
409
410         return 0;
411 }
412
413 int
414 Location::move_to (framepos_t pos)
415 {
416         if (pos < 0) {
417                 return -1;
418         }
419
420         if (_locked) {
421                 return -1;
422         }
423
424         if (_start != pos) {
425                 _start = pos;
426                 _end = _start + length();
427                 recompute_bbt_from_frames ();
428
429                 changed (this); /* EMIT SIGNAL */
430                 Changed (); /* EMIT SIGNAL */
431         }
432
433         assert (_start >= 0);
434         assert (_end >= 0);
435
436         return 0;
437 }
438
439 void
440 Location::set_hidden (bool yn, void*)
441 {
442         if (set_flag_internal (yn, IsHidden)) {
443                 flags_changed (this); /* EMIT SIGNAL */
444                 FlagsChanged ();
445         }
446 }
447
448 void
449 Location::set_cd (bool yn, void*)
450 {
451         // XXX this really needs to be session start
452         // but its not available here - leave to GUI
453
454         if (yn && _start == 0) {
455                 error << _("You cannot put a CD marker at this position") << endmsg;
456                 return;
457         }
458
459         if (set_flag_internal (yn, IsCDMarker)) {
460                 flags_changed (this); /* EMIT SIGNAL */
461                 FlagsChanged ();
462         }
463 }
464
465 void
466 Location::set_is_range_marker (bool yn, void*)
467 {
468         if (set_flag_internal (yn, IsRangeMarker)) {
469                 flags_changed (this);
470                 FlagsChanged (); /* EMIT SIGNAL */
471         }
472 }
473
474 void
475 Location::set_skip (bool yn)
476 {
477         if (is_range_marker() && length() > 0) {
478                 if (set_flag_internal (yn, IsSkip)) {
479                         flags_changed (this);
480                         FlagsChanged ();
481                 }
482         }
483 }
484
485 void
486 Location::set_skipping (bool yn)
487 {
488         if (is_range_marker() && is_skip() && length() > 0) {
489                 if (set_flag_internal (yn, IsSkipping)) {
490                         flags_changed (this);
491                         FlagsChanged ();
492                 }
493         }
494 }
495
496 void
497 Location::set_auto_punch (bool yn, void*)
498 {
499         if (is_mark() || _start == _end) {
500                 return;
501         }
502
503         if (set_flag_internal (yn, IsAutoPunch)) {
504                 flags_changed (this); /* EMIT SIGNAL */
505                 FlagsChanged (); /* EMIT SIGNAL */
506         }
507 }
508
509 void
510 Location::set_auto_loop (bool yn, void*)
511 {
512         if (is_mark() || _start == _end) {
513                 return;
514         }
515
516         if (set_flag_internal (yn, IsAutoLoop)) {
517                 flags_changed (this); /* EMIT SIGNAL */
518                 FlagsChanged (); /* EMIT SIGNAL */
519         }
520 }
521
522 bool
523 Location::set_flag_internal (bool yn, Flags flag)
524 {
525         if (yn) {
526                 if (!(_flags & flag)) {
527                         _flags = Flags (_flags | flag);
528                         return true;
529                 }
530         } else {
531                 if (_flags & flag) {
532                         _flags = Flags (_flags & ~flag);
533                         return true;
534                 }
535         }
536         return false;
537 }
538
539 void
540 Location::set_mark (bool yn)
541 {
542         /* This function is private, and so does not emit signals */
543
544         if (_start != _end) {
545                 return;
546         }
547
548         set_flag_internal (yn, IsMark);
549 }
550
551
552 XMLNode&
553 Location::cd_info_node(const string & name, const string & value)
554 {
555         XMLNode* root = new XMLNode("CD-Info");
556
557         root->add_property("name", name);
558         root->add_property("value", value);
559
560         return *root;
561 }
562
563
564 XMLNode&
565 Location::get_state ()
566 {
567         XMLNode *node = new XMLNode ("Location");
568         char buf[64];
569
570         typedef map<string, string>::const_iterator CI;
571
572         for(CI m = cd_info.begin(); m != cd_info.end(); ++m){
573                 node->add_child_nocopy(cd_info_node(m->first, m->second));
574         }
575
576         id().print (buf, sizeof (buf));
577         node->add_property("id", buf);
578         node->add_property ("name", name());
579         snprintf (buf, sizeof (buf), "%" PRId64, start());
580         node->add_property ("start", buf);
581         snprintf (buf, sizeof (buf), "%" PRId64, end());
582         node->add_property ("end", buf);
583         node->add_property ("flags", enum_2_string (_flags));
584         node->add_property ("locked", (_locked ? "yes" : "no"));
585         node->add_property ("position-lock-style", enum_2_string (_position_lock_style));
586
587         if (_scene_change) {
588                 node->add_child_nocopy (_scene_change->get_state());
589         }
590
591         return *node;
592 }
593
594 int
595 Location::set_state (const XMLNode& node, int version)
596 {
597         XMLProperty const * prop;
598
599         XMLNodeList cd_list = node.children();
600         XMLNodeConstIterator cd_iter;
601         XMLNode *cd_node;
602
603         string cd_name;
604         string cd_value;
605
606         if (node.name() != "Location") {
607                 error << _("incorrect XML node passed to Location::set_state") << endmsg;
608                 return -1;
609         }
610
611         if (!set_id (node)) {
612                 warning << _("XML node for Location has no ID information") << endmsg;
613         }
614
615         if ((prop = node.property ("name")) == 0) {
616                 error << _("XML node for Location has no name information") << endmsg;
617                 return -1;
618         }
619
620         set_name (prop->value());
621
622         if ((prop = node.property ("start")) == 0) {
623                 error << _("XML node for Location has no start information") << endmsg;
624                 return -1;
625         }
626
627         /* can't use set_start() here, because _end
628            may make the value of _start illegal.
629         */
630
631         sscanf (prop->value().c_str(), "%" PRId64, &_start);
632
633         if ((prop = node.property ("end")) == 0) {
634                 error << _("XML node for Location has no end information") << endmsg;
635                 return -1;
636         }
637
638         sscanf (prop->value().c_str(), "%" PRId64, &_end);
639
640         if ((prop = node.property ("flags")) == 0) {
641                 error << _("XML node for Location has no flags information") << endmsg;
642                 return -1;
643         }
644
645         Flags old_flags (_flags);
646         _flags = Flags (string_2_enum (prop->value(), _flags));
647
648         if (old_flags != _flags) {
649                 FlagsChanged ();
650         }
651
652         if ((prop = node.property ("locked")) != 0) {
653                 _locked = string_is_affirmative (prop->value());
654         } else {
655                 _locked = false;
656         }
657
658         for (cd_iter = cd_list.begin(); cd_iter != cd_list.end(); ++cd_iter) {
659
660                 cd_node = *cd_iter;
661
662                 if (cd_node->name() != "CD-Info") {
663                         continue;
664                 }
665
666                 if ((prop = cd_node->property ("name")) != 0) {
667                         cd_name = prop->value();
668                 } else {
669                         throw failed_constructor ();
670                 }
671
672                 if ((prop = cd_node->property ("value")) != 0) {
673                         cd_value = prop->value();
674                 } else {
675                         throw failed_constructor ();
676                 }
677
678
679                 cd_info[cd_name] = cd_value;
680         }
681
682         if ((prop = node.property ("position-lock-style")) != 0) {
683                 _position_lock_style = PositionLockStyle (string_2_enum (prop->value(), _position_lock_style));
684         }
685
686         XMLNode* scene_child = find_named_node (node, SceneChange::xml_node_name);
687
688         if (scene_child) {
689                 _scene_change = SceneChange::factory (*scene_child, version);
690         }
691
692         recompute_bbt_from_frames ();
693
694         changed (this); /* EMIT SIGNAL */
695         Changed (); /* EMIT SIGNAL */
696
697         assert (_start >= 0);
698         assert (_end >= 0);
699
700         return 0;
701 }
702
703 void
704 Location::set_position_lock_style (PositionLockStyle ps)
705 {
706         if (_position_lock_style == ps) {
707                 return;
708         }
709
710         _position_lock_style = ps;
711
712         recompute_bbt_from_frames ();
713
714         position_lock_style_changed (this); /* EMIT SIGNAL */
715         PositionLockStyleChanged (); /* EMIT SIGNAL */
716 }
717
718 void
719 Location::recompute_bbt_from_frames ()
720 {
721         if (_position_lock_style != MusicTime) {
722                 return;
723         }
724
725         _bbt_start = _session.tempo_map().beat_at_frame (_start);
726         _bbt_end = _session.tempo_map().beat_at_frame (_end);
727 }
728
729 void
730 Location::recompute_frames_from_bbt ()
731 {
732         if (_position_lock_style != MusicTime) {
733                 return;
734         }
735
736         TempoMap& map (_session.tempo_map());
737         set (map.frame_at_beat (_bbt_start), map.frame_at_beat (_bbt_end), false);
738 }
739
740 void
741 Location::lock ()
742 {
743         _locked = true;
744         lock_changed (this);
745         LockChanged ();
746 }
747
748 void
749 Location::unlock ()
750 {
751         _locked = false;
752         lock_changed (this);
753         LockChanged ();
754 }
755
756 void
757 Location::set_scene_change (boost::shared_ptr<SceneChange>  sc)
758 {
759         if (_scene_change != sc) {
760                 _scene_change = sc;
761                 _session.set_dirty ();
762
763                 scene_changed (); /* EMIT SIGNAL */
764                 SceneChangeChanged (); /* EMIT SIGNAL */
765         }
766 }
767
768 /*---------------------------------------------------------------------- */
769
770 Locations::Locations (Session& s)
771         : SessionHandleRef (s)
772 {
773         current_location = 0;
774 }
775
776 Locations::~Locations ()
777 {
778         for (LocationList::iterator i = locations.begin(); i != locations.end(); ) {
779                 LocationList::iterator tmp = i;
780                 ++tmp;
781                 delete *i;
782                 i = tmp;
783         }
784 }
785
786 int
787 Locations::set_current (Location *loc, bool want_lock)
788 {
789         int ret;
790
791         if (want_lock) {
792                 Glib::Threads::Mutex::Lock lm (lock);
793                 ret = set_current_unlocked (loc);
794         } else {
795                 ret = set_current_unlocked (loc);
796         }
797
798         if (ret == 0) {
799                 current_changed (current_location); /* EMIT SIGNAL */
800         }
801         return ret;
802 }
803
804 int
805 Locations::next_available_name(string& result,string base)
806 {
807         LocationList::iterator i;
808         string::size_type l;
809         int suffix;
810         char buf[32];
811         std::map<uint32_t,bool> taken;
812         uint32_t n;
813
814         result = base;
815         l = base.length();
816
817         if (!base.empty()) {
818
819                 /* find all existing names that match "base", and store
820                    the numeric part of them (if any) in the map "taken"
821                 */
822
823                 for (i = locations.begin(); i != locations.end(); ++i) {
824
825                         const string& temp ((*i)->name());
826
827                         if (!temp.find (base,0)) {
828                                 /* grab what comes after the "base" as if it was
829                                    a number, and assuming that works OK,
830                                    store it in "taken" so that we know it
831                                    has been used.
832                                 */
833                                 if ((suffix = atoi (temp.substr(l))) != 0) {
834                                         taken.insert (make_pair (suffix,true));
835                                 }
836                         }
837                 }
838         }
839
840         /* Now search for an un-used suffix to add to "base". This
841            will find "holes" in the numbering sequence when a location
842            was deleted.
843
844            This must start at 1, both for human-numbering reasons
845            and also because the call to atoi() above would return
846            zero if there is no recognizable numeric suffix, causing
847            "base 0" not to be inserted into the "taken" map.
848         */
849
850         n = 1;
851
852         while (n < UINT32_MAX) {
853                 if (taken.find (n) == taken.end()) {
854                         snprintf (buf, sizeof(buf), "%d", n);
855                         result += buf;
856                         return 1;
857                 }
858                 ++n;
859         }
860
861         return 0;
862 }
863
864 int
865 Locations::set_current_unlocked (Location *loc)
866 {
867         if (find (locations.begin(), locations.end(), loc) == locations.end()) {
868                 error << _("Locations: attempt to use unknown location as selected location") << endmsg;
869                 return -1;
870         }
871
872         current_location = loc;
873         return 0;
874 }
875
876 void
877 Locations::clear ()
878 {
879         {
880                 Glib::Threads::Mutex::Lock lm (lock);
881
882                 for (LocationList::iterator i = locations.begin(); i != locations.end(); ) {
883
884                         LocationList::iterator tmp = i;
885                         ++tmp;
886
887                         if (!(*i)->is_session_range()) {
888                                 delete *i;
889                                 locations.erase (i);
890                         }
891
892                         i = tmp;
893                 }
894
895                 current_location = 0;
896         }
897
898         changed (); /* EMIT SIGNAL */
899         current_changed (0); /* EMIT SIGNAL */
900 }
901
902 void
903 Locations::clear_markers ()
904 {
905         {
906                 Glib::Threads::Mutex::Lock lm (lock);
907                 LocationList::iterator tmp;
908
909                 for (LocationList::iterator i = locations.begin(); i != locations.end(); ) {
910                         tmp = i;
911                         ++tmp;
912
913                         if ((*i)->is_mark() && !(*i)->is_session_range()) {
914                                 delete *i;
915                                 locations.erase (i);
916                         }
917
918                         i = tmp;
919                 }
920         }
921
922         changed (); /* EMIT SIGNAL */
923 }
924
925 void
926 Locations::clear_ranges ()
927 {
928         {
929                 Glib::Threads::Mutex::Lock lm (lock);
930                 LocationList::iterator tmp;
931
932                 for (LocationList::iterator i = locations.begin(); i != locations.end(); ) {
933
934                         tmp = i;
935                         ++tmp;
936
937                         /* We do not remove these ranges as part of this
938                          * operation
939                          */
940
941                         if ((*i)->is_auto_punch() ||
942                             (*i)->is_auto_loop() ||
943                             (*i)->is_session_range()) {
944                                 i = tmp;
945                                 continue;
946                         }
947
948                         if (!(*i)->is_mark()) {
949                                 delete *i;
950                                 locations.erase (i);
951
952                         }
953
954                         i = tmp;
955                 }
956
957                 current_location = 0;
958         }
959
960         changed ();
961         current_changed (0); /* EMIT SIGNAL */
962 }
963
964 void
965 Locations::add (Location *loc, bool make_current)
966 {
967         assert (loc);
968
969         {
970                 Glib::Threads::Mutex::Lock lm (lock);
971                 locations.push_back (loc);
972
973                 if (make_current) {
974                         current_location = loc;
975                 }
976         }
977
978         added (loc); /* EMIT SIGNAL */
979
980         if (make_current) {
981                 current_changed (current_location); /* EMIT SIGNAL */
982         }
983
984         if (loc->is_session_range()) {
985                 Session::StartTimeChanged (0);
986                 Session::EndTimeChanged (1);
987         }
988 }
989
990 void
991 Locations::remove (Location *loc)
992 {
993         bool was_removed = false;
994         bool was_current = false;
995         LocationList::iterator i;
996
997         if (loc->is_session_range()) {
998                 return;
999         }
1000
1001         {
1002                 Glib::Threads::Mutex::Lock lm (lock);
1003
1004                 for (i = locations.begin(); i != locations.end(); ++i) {
1005                         if ((*i) == loc) {
1006                                 delete *i;
1007                                 locations.erase (i);
1008                                 was_removed = true;
1009                                 if (current_location == loc) {
1010                                         current_location = 0;
1011                                         was_current = true;
1012                                 }
1013                                 break;
1014                         }
1015                 }
1016         }
1017
1018         if (was_removed) {
1019
1020                 removed (loc); /* EMIT SIGNAL */
1021
1022                 if (was_current) {
1023                         current_changed (0); /* EMIT SIGNAL */
1024                 }
1025         }
1026 }
1027
1028 XMLNode&
1029 Locations::get_state ()
1030 {
1031         XMLNode *node = new XMLNode ("Locations");
1032         LocationList::iterator iter;
1033         Glib::Threads::Mutex::Lock lm (lock);
1034
1035         for (iter = locations.begin(); iter != locations.end(); ++iter) {
1036                 node->add_child_nocopy ((*iter)->get_state ());
1037         }
1038
1039         return *node;
1040 }
1041
1042 int
1043 Locations::set_state (const XMLNode& node, int version)
1044 {
1045         if (node.name() != "Locations") {
1046                 error << _("incorrect XML mode passed to Locations::set_state") << endmsg;
1047                 return -1;
1048         }
1049
1050         XMLNodeList nlist = node.children();
1051
1052         /* build up a new locations list in here */
1053         LocationList new_locations;
1054
1055         current_location = 0;
1056
1057         Location* session_range_location = 0;
1058         if (version < 3000) {
1059                 session_range_location = new Location (_session, 0, 0, _("session"), Location::IsSessionRange);
1060                 new_locations.push_back (session_range_location);
1061         }
1062
1063         {
1064                 Glib::Threads::Mutex::Lock lm (lock);
1065
1066                 XMLNodeConstIterator niter;
1067                 for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
1068
1069                         try {
1070
1071                                 XMLProperty const * prop_id = (*niter)->property ("id");
1072                                 assert (prop_id);
1073                                 PBD::ID id (prop_id->value ());
1074
1075                                 LocationList::const_iterator i = locations.begin();
1076                                 while (i != locations.end () && (*i)->id() != id) {
1077                                         ++i;
1078                                 }
1079
1080                                 Location* loc;
1081                                 if (i != locations.end()) {
1082                                         /* we can re-use an old Location object */
1083                                         loc = *i;
1084
1085                                         // changed locations will be updated by Locations::changed signal
1086                                         loc->set_state (**niter, version);
1087                                 } else {
1088                                         loc = new Location (_session, **niter);
1089                                 }
1090
1091                                 bool add = true;
1092
1093                                 if (version < 3000) {
1094                                         /* look for old-style IsStart / IsEnd properties in this location;
1095                                            if they are present, update the session_range_location accordingly
1096                                         */
1097                                         XMLProperty const * prop = (*niter)->property ("flags");
1098                                         if (prop) {
1099                                                 string v = prop->value ();
1100                                                 while (1) {
1101                                                         string::size_type const c = v.find_first_of (',');
1102                                                         string const s = v.substr (0, c);
1103                                                         if (s == X_("IsStart")) {
1104                                                                 session_range_location->set_start (loc->start(), true);
1105                                                                 add = false;
1106                                                         } else if (s == X_("IsEnd")) {
1107                                                                 session_range_location->set_end (loc->start(), true);
1108                                                                 add = false;
1109                                                         }
1110
1111                                                         if (c == string::npos) {
1112                                                                 break;
1113                                                         }
1114
1115                                                         v = v.substr (c + 1);
1116                                                 }
1117                                         }
1118                                 }
1119
1120                                 if (add) {
1121                                         new_locations.push_back (loc);
1122                                 }
1123                         }
1124
1125                         catch (failed_constructor& err) {
1126                                 error << _("could not load location from session file - ignored") << endmsg;
1127                         }
1128                 }
1129
1130                 /* We may have some unused locations in the old list. */
1131                 for (LocationList::iterator i = locations.begin(); i != locations.end(); ) {
1132                         LocationList::iterator tmp = i;
1133                         ++tmp;
1134
1135                         LocationList::iterator n = new_locations.begin();
1136                         bool found = false;
1137
1138                         while (n != new_locations.end ()) {
1139                                 if ((*i)->id() == (*n)->id()) {
1140                                         found = true;
1141                                         break;
1142                                 }
1143                                 ++n;
1144                         }
1145
1146                         if (!found) {
1147                                 delete *i;
1148                                 locations.erase (i);
1149                         }
1150
1151                         i = tmp;
1152                 }
1153
1154                 locations = new_locations;
1155
1156                 if (locations.size()) {
1157                         current_location = locations.front();
1158                 } else {
1159                         current_location = 0;
1160                 }
1161         }
1162
1163         changed (); /* EMIT SIGNAL */
1164
1165         return 0;
1166 }
1167
1168
1169 typedef std::pair<framepos_t,Location*> LocationPair;
1170
1171 struct LocationStartEarlierComparison
1172 {
1173         bool operator() (LocationPair a, LocationPair b) {
1174                 return a.first < b.first;
1175         }
1176 };
1177
1178 struct LocationStartLaterComparison
1179 {
1180         bool operator() (LocationPair a, LocationPair b) {
1181                 return a.first > b.first;
1182         }
1183 };
1184
1185 framepos_t
1186 Locations::first_mark_before (framepos_t frame, bool include_special_ranges)
1187 {
1188         Glib::Threads::Mutex::Lock lm (lock);
1189         vector<LocationPair> locs;
1190
1191         for (LocationList::iterator i = locations.begin(); i != locations.end(); ++i) {
1192                 locs.push_back (make_pair ((*i)->start(), (*i)));
1193                 if (!(*i)->is_mark()) {
1194                         locs.push_back (make_pair ((*i)->end(), (*i)));
1195                 }
1196         }
1197
1198         LocationStartLaterComparison cmp;
1199         sort (locs.begin(), locs.end(), cmp);
1200
1201         /* locs is sorted in ascending order */
1202
1203         for (vector<LocationPair>::iterator i = locs.begin(); i != locs.end(); ++i) {
1204                 if ((*i).second->is_hidden()) {
1205                         continue;
1206                 }
1207                 if (!include_special_ranges && ((*i).second->is_auto_loop() || (*i).second->is_auto_punch())) {
1208                         continue;
1209                 }
1210                 if ((*i).first < frame) {
1211                         return (*i).first;
1212                 }
1213         }
1214
1215         return -1;
1216 }
1217
1218 Location*
1219 Locations::mark_at (framepos_t pos, framecnt_t slop) const
1220 {
1221         Glib::Threads::Mutex::Lock lm (lock);
1222         Location* closest = 0;
1223         frameoffset_t mindelta = max_framepos;
1224         frameoffset_t delta;
1225
1226         /* locations are not necessarily stored in linear time order so we have
1227          * to iterate across all of them to find the one closest to a give point.
1228          */
1229
1230         for (LocationList::const_iterator i = locations.begin(); i != locations.end(); ++i) {
1231
1232                 if ((*i)->is_mark()) {
1233                         if (pos > (*i)->start()) {
1234                                 delta = pos - (*i)->start();
1235                         } else {
1236                                 delta = (*i)->start() - pos;
1237                         }
1238
1239                         if (slop == 0 && delta == 0) {
1240                                 /* special case: no slop, and direct hit for position */
1241                                 return *i;
1242                         }
1243
1244                         if (delta <= slop) {
1245                                 if (delta < mindelta) {
1246                                         closest = *i;
1247                                         mindelta = delta;
1248                                 }
1249                         }
1250                 }
1251         }
1252
1253         return closest;
1254 }
1255
1256 framepos_t
1257 Locations::first_mark_after (framepos_t frame, bool include_special_ranges)
1258 {
1259         Glib::Threads::Mutex::Lock lm (lock);
1260         vector<LocationPair> locs;
1261
1262         for (LocationList::iterator i = locations.begin(); i != locations.end(); ++i) {
1263                 locs.push_back (make_pair ((*i)->start(), (*i)));
1264                 if (!(*i)->is_mark()) {
1265                         locs.push_back (make_pair ((*i)->end(), (*i)));
1266                 }
1267         }
1268
1269         LocationStartEarlierComparison cmp;
1270         sort (locs.begin(), locs.end(), cmp);
1271
1272         /* locs is sorted in reverse order */
1273
1274         for (vector<LocationPair>::iterator i = locs.begin(); i != locs.end(); ++i) {
1275                 if ((*i).second->is_hidden()) {
1276                         continue;
1277                 }
1278                 if (!include_special_ranges && ((*i).second->is_auto_loop() || (*i).second->is_auto_punch())) {
1279                         continue;
1280                 }
1281                 if ((*i).first > frame) {
1282                         return (*i).first;
1283                 }
1284         }
1285
1286         return -1;
1287 }
1288
1289 /** Look for the `marks' (either locations which are marks, or start/end points of range markers) either
1290  *  side of a frame.  Note that if frame is exactly on a `mark', that mark will not be considered for returning
1291  *  as before/after.
1292  *  @param frame Frame to look for.
1293  *  @param before Filled in with the position of the last `mark' before `frame' (or max_framepos if none exists)
1294  *  @param after Filled in with the position of the next `mark' after `frame' (or max_framepos if none exists)
1295  */
1296 void
1297 Locations::marks_either_side (framepos_t const frame, framepos_t& before, framepos_t& after) const
1298 {
1299         before = after = max_framepos;
1300
1301         LocationList locs;
1302
1303         {
1304                 Glib::Threads::Mutex::Lock lm (lock);
1305                 locs = locations;
1306         }
1307
1308         /* Get a list of positions; don't store any that are exactly on our requested position */
1309
1310         std::list<framepos_t> positions;
1311
1312         for (LocationList::const_iterator i = locs.begin(); i != locs.end(); ++i) {
1313                 if (((*i)->is_auto_loop() || (*i)->is_auto_punch())) {
1314                         continue;
1315                 }
1316
1317                 if (!(*i)->is_hidden()) {
1318                         if ((*i)->is_mark ()) {
1319                                 if ((*i)->start() != frame) {
1320                                         positions.push_back ((*i)->start ());
1321                                 }
1322                         } else {
1323                                 if ((*i)->start() != frame) {
1324                                         positions.push_back ((*i)->start ());
1325                                 }
1326                                 if ((*i)->end() != frame) {
1327                                         positions.push_back ((*i)->end ());
1328                                 }
1329                         }
1330                 }
1331         }
1332
1333         if (positions.empty ()) {
1334                 return;
1335         }
1336
1337         positions.sort ();
1338
1339         std::list<framepos_t>::iterator i = positions.begin ();
1340         while (i != positions.end () && *i < frame) {
1341                 ++i;
1342         }
1343
1344         if (i == positions.end ()) {
1345                 /* run out of marks */
1346                 before = positions.back ();
1347                 return;
1348         }
1349
1350         after = *i;
1351
1352         if (i == positions.begin ()) {
1353                 /* none before */
1354                 return;
1355         }
1356
1357         --i;
1358         before = *i;
1359 }
1360
1361 Location*
1362 Locations::session_range_location () const
1363 {
1364         for (LocationList::const_iterator i = locations.begin(); i != locations.end(); ++i) {
1365                 if ((*i)->is_session_range()) {
1366                         return const_cast<Location*> (*i);
1367                 }
1368         }
1369         return 0;
1370 }
1371
1372 Location*
1373 Locations::auto_loop_location () const
1374 {
1375         for (LocationList::const_iterator i = locations.begin(); i != locations.end(); ++i) {
1376                 if ((*i)->is_auto_loop()) {
1377                         return const_cast<Location*> (*i);
1378                 }
1379         }
1380         return 0;
1381 }
1382
1383 Location*
1384 Locations::auto_punch_location () const
1385 {
1386         for (LocationList::const_iterator i = locations.begin(); i != locations.end(); ++i) {
1387                 if ((*i)->is_auto_punch()) {
1388                         return const_cast<Location*> (*i);
1389                 }
1390         }
1391         return 0;
1392 }
1393
1394 uint32_t
1395 Locations::num_range_markers () const
1396 {
1397         uint32_t cnt = 0;
1398         Glib::Threads::Mutex::Lock lm (lock);
1399         for (LocationList::const_iterator i = locations.begin(); i != locations.end(); ++i) {
1400                 if ((*i)->is_range_marker()) {
1401                         ++cnt;
1402                 }
1403         }
1404         return cnt;
1405 }
1406
1407 Location *
1408 Locations::get_location_by_id(PBD::ID id)
1409 {
1410         LocationList::iterator it;
1411         for (it  = locations.begin(); it != locations.end(); ++it)
1412                 if (id == (*it)->id())
1413                         return *it;
1414
1415         return 0;
1416 }
1417
1418 void
1419 Locations::find_all_between (framepos_t start, framepos_t end, LocationList& ll, Location::Flags flags)
1420 {
1421         Glib::Threads::Mutex::Lock lm (lock);
1422
1423         for (LocationList::const_iterator i = locations.begin(); i != locations.end(); ++i) {
1424                 if ((flags == 0 || (*i)->matches (flags)) &&
1425                     ((*i)->start() >= start && (*i)->end() < end)) {
1426                         ll.push_back (*i);
1427                 }
1428         }
1429 }
1430