Update locations GUI more efficiently by avoiding a rebuild when a location is remove...
[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
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/session.h"
35 #include "ardour/audiofilesource.h"
36
37 #include "i18n.h"
38
39 #define SUFFIX_MAX 32
40
41 using namespace std;
42 using namespace ARDOUR;
43 using namespace PBD;
44
45 Location::Location (const Location& other)
46         : StatefulDestructible(),
47           _name (other._name),
48           _start (other._start),
49           _end (other._end),
50           _flags (other._flags)
51 {
52         /* copy is not locked even if original was */
53
54         _locked = false;
55 }
56
57 Location::Location (const XMLNode& node)
58 {
59         if (set_state (node, Stateful::loading_state_version)) {
60                 throw failed_constructor ();
61         }
62 }
63
64 Location*
65 Location::operator= (const Location& other)
66 {
67         if (this == &other) {
68                 return this;
69         }
70
71         _name = other._name;
72         _start = other._start;
73         _end = other._end;
74         _flags = other._flags;
75
76         /* copy is not locked even if original was */
77
78         _locked = false;
79
80         /* "changed" not emitted on purpose */
81
82         return this;
83 }
84
85 int
86 Location::set_start (nframes64_t s)
87 {
88         if (_locked) {
89                 return -1;
90         }
91
92         if (((is_auto_punch() || is_auto_loop()) && s >= _end) || (!is_mark() && s > _end)) {
93                 return -1;
94         }
95
96         if (is_mark()) {
97                 if (_start != s) {
98                         _start = s;
99                         _end = s;
100                         start_changed (this); /* EMIT SIGNAL */
101                         end_changed (this); /* EMIT SIGNAL */
102                 }
103                 return 0;
104         }
105         
106         if (s != _start) {
107                 _start = s;
108                 start_changed (this); /* EMIT SIGNAL */
109                 if (is_session_range ()) {
110                         Session::StartTimeChanged (); /* EMIT SIGNAL */
111                         AudioFileSource::set_header_position_offset (s);
112                 }
113         }
114
115         return 0;
116 }
117
118 int
119 Location::set_end (nframes64_t e)
120 {
121         if (_locked) {
122                 return -1;
123         }
124
125         if (((is_auto_punch() || is_auto_loop()) && e <= _start) || e < _start) {
126                 return -1;
127         }
128         
129         if (is_mark()) {
130                 if (_start != e) {
131                         _start = e;
132                         _end = e;
133                         start_changed (this); /* EMIT SIGNAL */
134                         end_changed (this); /* EMIT SIGNAL */
135                 }
136                 return 0;
137         }
138
139         if (e != _end) {
140                 _end = e;
141                 end_changed(this); /* EMIT SIGNAL */
142
143                 if (is_session_range()) {
144                         Session::EndTimeChanged (); /* EMIT SIGNAL */
145                 }
146         }
147
148         return 0;
149 }
150
151 int
152 Location::set (nframes64_t start, nframes64_t end)
153 {
154         int const s = set_start (start);
155         int const e = set_end (end);
156
157         return (s == 0 && e == 0) ? 0 : -1;
158 }
159
160 int
161 Location::move_to (nframes64_t pos)
162 {
163         if (_locked) {
164                 return -1;
165         }
166
167         if (_start != pos) {
168                 _start = pos;
169                 _end = _start + length();
170
171                 changed (this); /* EMIT SIGNAL */
172         }
173
174         return 0;
175 }
176
177 void
178 Location::set_hidden (bool yn, void *src)
179 {
180         if (set_flag_internal (yn, IsHidden)) {
181                  FlagsChanged (this, src); /* EMIT SIGNAL */
182         }
183 }
184
185 void
186 Location::set_cd (bool yn, void *src)
187 {
188         // XXX this really needs to be session start
189         // but its not available here - leave to GUI
190
191         if (_start == 0) {
192                 error << _("You cannot put a CD marker at this position") << endmsg;
193                 return;
194         }
195
196         if (set_flag_internal (yn, IsCDMarker)) {
197                  FlagsChanged (this, src); /* EMIT SIGNAL */
198         }
199 }
200
201 void
202 Location::set_is_range_marker (bool yn, void *src)
203 {
204        if (set_flag_internal (yn, IsRangeMarker)) {
205                 FlagsChanged (this, src); /* EMIT SIGNAL */
206        }
207 }
208
209 void
210 Location::set_auto_punch (bool yn, void *src)
211 {
212         if (is_mark() || _start == _end) {
213                 return;
214         }
215
216         if (set_flag_internal (yn, IsAutoPunch)) {
217                  FlagsChanged (this, src); /* EMIT SIGNAL */
218         }
219 }
220
221 void
222 Location::set_auto_loop (bool yn, void *src)
223 {
224         if (is_mark() || _start == _end) {
225                 return;
226         }
227
228         if (set_flag_internal (yn, IsAutoLoop)) {
229                  FlagsChanged (this, src); /* EMIT SIGNAL */
230         }
231 }
232
233 bool
234 Location::set_flag_internal (bool yn, Flags flag)
235 {
236         if (yn) {
237                 if (!(_flags & flag)) {
238                         _flags = Flags (_flags | flag);
239                         return true;
240                 }
241         } else {
242                 if (_flags & flag) {
243                         _flags = Flags (_flags & ~flag);
244                         return true;
245                 }
246         }
247         return false;
248 }
249
250 void
251 Location::set_mark (bool yn)
252 {
253         /* This function is private, and so does not emit signals */
254
255         if (_start != _end) {
256                 return;
257         }
258
259         set_flag_internal (yn, IsMark);
260 }
261
262
263 XMLNode&
264 Location::cd_info_node(const string & name, const string & value)
265 {
266         XMLNode* root = new XMLNode("CD-Info");
267
268         root->add_property("name", name);
269         root->add_property("value", value);
270
271         return *root;
272 }
273
274
275 XMLNode&
276 Location::get_state (void)
277 {
278         XMLNode *node = new XMLNode ("Location");
279         char buf[64];
280
281         typedef map<string, string>::const_iterator CI;
282
283         for(CI m = cd_info.begin(); m != cd_info.end(); ++m){
284                 node->add_child_nocopy(cd_info_node(m->first, m->second));
285         }
286
287         id().print (buf, sizeof (buf));
288         node->add_property("id", buf);
289         node->add_property ("name", name());
290         snprintf (buf, sizeof (buf), "%" PRId64, start());
291         node->add_property ("start", buf);
292         snprintf (buf, sizeof (buf), "%" PRId64, end());
293         node->add_property ("end", buf);
294         node->add_property ("flags", enum_2_string (_flags));
295         node->add_property ("locked", (_locked ? "yes" : "no"));
296
297         return *node;
298 }
299
300 int
301 Location::set_state (const XMLNode& node, int /*version*/)
302 {
303         const XMLProperty *prop;
304
305         XMLNodeList cd_list = node.children();
306         XMLNodeConstIterator cd_iter;
307         XMLNode *cd_node;
308
309         string cd_name;
310         string cd_value;
311
312         if (node.name() != "Location") {
313                 error << _("incorrect XML node passed to Location::set_state") << endmsg;
314                 return -1;
315         }
316
317         if ((prop = node.property ("id")) == 0) {
318                 warning << _("XML node for Location has no ID information") << endmsg;
319         } else {
320                 _id = prop->value ();
321         }
322
323         if ((prop = node.property ("name")) == 0) {
324                 error << _("XML node for Location has no name information") << endmsg;
325                 return -1;
326         }
327
328         set_name (prop->value());
329
330         if ((prop = node.property ("start")) == 0) {
331                 error << _("XML node for Location has no start information") << endmsg;
332                 return -1;
333         }
334
335                 /* can't use set_start() here, because _end
336                    may make the value of _start illegal.
337                 */
338
339         sscanf (prop->value().c_str(), "%" PRId64, &_start);
340
341         if ((prop = node.property ("end")) == 0) {
342                   error << _("XML node for Location has no end information") << endmsg;
343                   return -1;
344         }
345
346         sscanf (prop->value().c_str(), "%" PRId64, &_end);
347
348         if ((prop = node.property ("flags")) == 0) {
349                   error << _("XML node for Location has no flags information") << endmsg;
350                   return -1;
351         }
352
353         _flags = Flags (string_2_enum (prop->value(), _flags));
354
355         if ((prop = node.property ("locked")) != 0) {
356                 _locked = string_is_affirmative (prop->value());
357         } else {
358                 _locked = false;
359         }
360
361         for (cd_iter = cd_list.begin(); cd_iter != cd_list.end(); ++cd_iter) {
362
363                   cd_node = *cd_iter;
364
365                   if (cd_node->name() != "CD-Info") {
366                     continue;
367                   }
368
369                   if ((prop = cd_node->property ("name")) != 0) {
370                     cd_name = prop->value();
371                   } else {
372                     throw failed_constructor ();
373                   }
374
375                   if ((prop = cd_node->property ("value")) != 0) {
376                     cd_value = prop->value();
377                   } else {
378                     throw failed_constructor ();
379                   }
380
381
382                   cd_info[cd_name] = cd_value;
383
384         }
385
386         changed(this); /* EMIT SIGNAL */
387
388         return 0;
389 }
390
391 /*---------------------------------------------------------------------- */
392
393 Locations::Locations ()
394
395 {
396         current_location = 0;
397 }
398
399 Locations::~Locations ()
400 {
401         for (LocationList::iterator i = locations.begin(); i != locations.end(); ) {
402                 LocationList::iterator tmp = i;
403                 ++tmp;
404                 delete *i;
405                 i = tmp;
406         }
407 }
408
409 int
410 Locations::set_current (Location *loc, bool want_lock)
411
412 {
413         int ret;
414
415         if (want_lock) {
416                 Glib::Mutex::Lock lm (lock);
417                 ret = set_current_unlocked (loc);
418         } else {
419                 ret = set_current_unlocked (loc);
420         }
421
422         if (ret == 0) {
423                  current_changed (current_location); /* EMIT SIGNAL */
424         }
425         return ret;
426 }
427
428 int
429 Locations::next_available_name(string& result,string base)
430 {
431         LocationList::iterator i;
432         Location* location;
433         string temp;
434         string::size_type l;
435         int suffix;
436         char buf[32];
437         bool available[SUFFIX_MAX+1];
438
439         result = base;
440         for (int k=1; k<SUFFIX_MAX; k++) {
441                 available[k] = true;
442         }
443         l = base.length();
444         for (i = locations.begin(); i != locations.end(); ++i) {
445                 location =* i;
446                 temp = location->name();
447                 if (l && !temp.find(base,0)) {
448                         suffix = atoi(temp.substr(l,3).c_str());
449                         if (suffix) available[suffix] = false;
450                 }
451         }
452         for (int k=1; k<=SUFFIX_MAX; k++) {
453                 if (available[k]) {
454                         snprintf (buf, 31, "%d", k);
455                         result += buf;
456                         return 1;
457                 }
458         }
459         return 0;
460 }
461
462 int
463 Locations::set_current_unlocked (Location *loc)
464 {
465         if (find (locations.begin(), locations.end(), loc) == locations.end()) {
466                 error << _("Locations: attempt to use unknown location as selected location") << endmsg;
467                 return -1;
468         }
469
470         current_location = loc;
471         return 0;
472 }
473
474 void
475 Locations::clear ()
476 {
477         {
478                 Glib::Mutex::Lock lm (lock);
479
480                 for (LocationList::iterator i = locations.begin(); i != locations.end(); ) {
481
482                         LocationList::iterator tmp = i;
483                         ++tmp;
484
485                         if (!(*i)->is_session_range()) {
486                                 locations.erase (i);
487                         }
488
489                         i = tmp;
490                 }
491
492                 current_location = 0;
493         }
494
495         changed (OTHER); /* EMIT SIGNAL */
496         current_changed (0); /* EMIT SIGNAL */
497 }
498
499 void
500 Locations::clear_markers ()
501 {
502         {
503                 Glib::Mutex::Lock lm (lock);
504                 LocationList::iterator tmp;
505
506                 for (LocationList::iterator i = locations.begin(); i != locations.end(); ) {
507                         tmp = i;
508                         ++tmp;
509
510                         if ((*i)->is_mark() && !(*i)->is_session_range()) {
511                                 locations.erase (i);
512                         }
513
514                         i = tmp;
515                 }
516         }
517
518         changed (OTHER); /* EMIT SIGNAL */
519 }
520
521 void
522 Locations::clear_ranges ()
523 {
524         {
525                 Glib::Mutex::Lock lm (lock);
526                 LocationList::iterator tmp;
527
528                 for (LocationList::iterator i = locations.begin(); i != locations.end(); ) {
529
530                         tmp = i;
531                         ++tmp;
532
533                         if (!(*i)->is_mark()) {
534                                 locations.erase (i);
535
536                         }
537
538                         i = tmp;
539                 }
540
541                 current_location = 0;
542         }
543
544         changed (OTHER); /* EMIT SIGNAL */
545         current_changed (0); /* EMIT SIGNAL */
546 }
547
548 void
549 Locations::add (Location *loc, bool make_current)
550 {
551         assert (loc);
552         
553         {
554                 Glib::Mutex::Lock lm (lock);
555                 locations.push_back (loc);
556
557                 if (make_current) {
558                         current_location = loc;
559                 }
560         }
561
562         added (loc); /* EMIT SIGNAL */
563
564         if (make_current) {
565                  current_changed (current_location); /* EMIT SIGNAL */
566         }
567 }
568
569 void
570 Locations::remove (Location *loc)
571 {
572         bool was_removed = false;
573         bool was_current = false;
574         LocationList::iterator i;
575
576         if (loc->is_session_range()) {
577                 return;
578         }
579
580         {
581                 Glib::Mutex::Lock lm (lock);
582
583                 for (i = locations.begin(); i != locations.end(); ++i) {
584                         if ((*i) == loc) {
585                                 locations.erase (i);
586                                 was_removed = true;
587                                 if (current_location == loc) {
588                                         current_location = 0;
589                                         was_current = true;
590                                 }
591                                 break;
592                         }
593                 }
594         }
595
596         if (was_removed) {
597
598                 removed (loc); /* EMIT SIGNAL */
599
600                 if (was_current) {
601                          current_changed (0); /* EMIT SIGNAL */
602                 }
603
604                 changed (REMOVAL); /* EMIT_SIGNAL */
605         }
606 }
607
608 void
609 Locations::location_changed (Location* /*loc*/)
610 {
611         changed (OTHER); /* EMIT SIGNAL */
612 }
613
614 XMLNode&
615 Locations::get_state ()
616 {
617         XMLNode *node = new XMLNode ("Locations");
618         LocationList::iterator iter;
619         Glib::Mutex::Lock lm (lock);
620
621         for (iter  = locations.begin(); iter != locations.end(); ++iter) {
622                 node->add_child_nocopy ((*iter)->get_state ());
623         }
624
625         return *node;
626 }
627
628 int
629 Locations::set_state (const XMLNode& node, int version)
630 {
631         if (node.name() != "Locations") {
632                 error << _("incorrect XML mode passed to Locations::set_state") << endmsg;
633                 return -1;
634         }
635
636         XMLNodeList nlist = node.children();
637
638         locations.clear ();
639         current_location = 0;
640
641         Location* session_range_location = 0;
642         if (version < 3000) {
643                 session_range_location = new Location (0, 0, _("session"), Location::IsSessionRange);
644                 locations.push_back (session_range_location);
645         }
646
647         {
648                 Glib::Mutex::Lock lm (lock);
649
650                 XMLNodeConstIterator niter;
651                 for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
652
653                         try {
654
655                                 Location *loc = new Location (**niter);
656
657                                 bool add = true;
658
659                                 if (version < 3000) {
660                                         /* look for old-style IsStart / IsEnd properties in this location;
661                                            if they are present, update the session_range_location accordingly
662                                         */
663                                         XMLProperty const * prop = (*niter)->property ("flags");
664                                         if (prop) {
665                                                 string v = prop->value ();
666                                                 while (1) {
667                                                         string::size_type const c = v.find_first_of (',');
668                                                         string const s = v.substr (0, c);
669                                                         if (s == X_("IsStart")) {
670                                                                 session_range_location->set_start (loc->start());
671                                                                 add = false;
672                                                         } else if (s == X_("IsEnd")) {
673                                                                 session_range_location->set_end (loc->start());
674                                                                 add = false;
675                                                         }
676
677                                                         if (c == string::npos) {
678                                                                 break;
679                                                         }
680
681                                                         v = v.substr (c + 1);
682                                                 }
683                                         }
684                                 }
685
686                                 if (add) {
687                                         locations.push_back (loc);
688                                 }
689                         }
690
691                         catch (failed_constructor& err) {
692                                 error << _("could not load location from session file - ignored") << endmsg;
693                         }
694                 }
695
696                 if (locations.size()) {
697                         current_location = locations.front();
698                 } else {
699                         current_location = 0;
700                 }
701         }
702
703         changed (OTHER); /* EMIT SIGNAL */
704
705         return 0;
706 }
707
708 struct LocationStartEarlierComparison
709 {
710     bool operator() (Location *a, Location *b) {
711         return a->start() < b->start();
712     }
713 };
714
715 struct LocationStartLaterComparison
716 {
717     bool operator() (Location *a, Location *b) {
718         return a->start() > b->start();
719     }
720 };
721
722 Location *
723 Locations::first_location_before (nframes64_t frame, bool include_special_ranges)
724 {
725         LocationList locs;
726
727         {
728                 Glib::Mutex::Lock lm (lock);
729                 locs = locations;
730         }
731
732         LocationStartLaterComparison cmp;
733         locs.sort (cmp);
734
735         /* locs is now sorted latest..earliest */
736
737         for (LocationList::iterator i = locs.begin(); i != locs.end(); ++i) {
738                 if (!include_special_ranges && ((*i)->is_auto_loop() || (*i)->is_auto_punch())) {
739                         continue;
740                 }
741                 if (!(*i)->is_hidden() && (*i)->start() < frame) {
742                         return (*i);
743                 }
744         }
745
746         return 0;
747 }
748
749 Location *
750 Locations::first_location_after (nframes64_t frame, bool include_special_ranges)
751 {
752         LocationList locs;
753
754         {
755                 Glib::Mutex::Lock lm (lock);
756                 locs = locations;
757         }
758
759         LocationStartEarlierComparison cmp;
760         locs.sort (cmp);
761
762         /* locs is now sorted earliest..latest */
763
764         for (LocationList::iterator i = locs.begin(); i != locs.end(); ++i) {
765                 if (!include_special_ranges && ((*i)->is_auto_loop() || (*i)->is_auto_punch())) {
766                         continue;
767                 }
768                 if (!(*i)->is_hidden() && (*i)->start() > frame) {
769                         return (*i);
770                 }
771         }
772
773         return 0;
774 }
775
776 /** Look for the `marks' (either locations which are marks, or start/end points of range markers) either
777  *  side of a frame.
778  *  @param frame Frame to look for.
779  *  @param before Filled in with the position of the last `mark' before `frame' (or max_frames if none exists)
780  *  @param after Filled in with the position of the last `mark' after `frame' (or max_frames if none exists)
781  */
782 void
783 Locations::marks_either_side (nframes64_t const frame, nframes64_t& before, nframes64_t& after) const
784 {
785         before = after = max_frames;
786         
787         LocationList locs;
788
789         {
790                 Glib::Mutex::Lock lm (lock);
791                 locs = locations;
792         }
793
794         std::list<nframes64_t> positions;
795
796         for (LocationList::const_iterator i = locs.begin(); i != locs.end(); ++i) {
797                 if (((*i)->is_auto_loop() || (*i)->is_auto_punch())) {
798                         continue;
799                 }
800
801                 if (!(*i)->is_hidden()) {
802                         if ((*i)->is_mark ()) {
803                                 positions.push_back ((*i)->start ());
804                         } else {
805                                 positions.push_back ((*i)->start ());
806                                 positions.push_back ((*i)->end ());
807                         }
808                 }
809         }
810
811         if (positions.empty ()) {
812                 return;
813         }
814
815         positions.sort ();
816
817         std::list<nframes64_t>::iterator i = positions.begin ();
818         while (i != positions.end () && *i < frame) {
819                 ++i;
820         }
821
822         if (i == positions.end ()) {
823                 /* run out of marks */
824                 before = positions.back ();
825                 return;
826         }
827
828         after = *i;
829
830         if (i == positions.begin ()) {
831                 /* none before */
832                 return;
833         }
834         
835         --i;
836         before = *i;
837 }
838
839 Location*
840 Locations::session_range_location () const
841 {
842         for (LocationList::const_iterator i = locations.begin(); i != locations.end(); ++i) {
843                 if ((*i)->is_session_range()) {
844                         return const_cast<Location*> (*i);
845                 }
846         }
847         return 0;
848 }
849
850 Location*
851 Locations::auto_loop_location () const
852 {
853         for (LocationList::const_iterator i = locations.begin(); i != locations.end(); ++i) {
854                 if ((*i)->is_auto_loop()) {
855                         return const_cast<Location*> (*i);
856                 }
857         }
858         return 0;
859 }
860
861 Location*
862 Locations::auto_punch_location () const
863 {
864         for (LocationList::const_iterator i = locations.begin(); i != locations.end(); ++i) {
865                 if ((*i)->is_auto_punch()) {
866                         return const_cast<Location*> (*i);
867                 }
868         }
869        return 0;
870 }
871
872 uint32_t
873 Locations::num_range_markers () const
874 {
875         uint32_t cnt = 0;
876         Glib::Mutex::Lock lm (lock);
877         for (LocationList::const_iterator i = locations.begin(); i != locations.end(); ++i) {
878                 if ((*i)->is_range_marker()) {
879                         ++cnt;
880                 }
881         }
882         return cnt;
883 }
884
885 Location *
886 Locations::get_location_by_id(PBD::ID id)
887 {
888     LocationList::iterator it;
889     for (it  = locations.begin(); it != locations.end(); ++it)
890         if (id == (*it)->id())
891             return *it;
892
893     return 0;
894 }
895
896 void
897 Locations::find_all_between (nframes64_t start, nframes64_t end, LocationList& ll, Location::Flags flags)
898 {
899         Glib::Mutex::Lock lm (lock);
900
901         for (LocationList::const_iterator i = locations.begin(); i != locations.end(); ++i) {
902                 if ((flags == 0 || (*i)->matches (flags)) &&
903                     ((*i)->start() >= start && (*i)->end() < end)) {
904                         ll.push_back (*i);
905                 }
906         }
907 }