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