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