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