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