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