make MementoCommand inherit from sigc::trackable so that we don't double-call its...
[ardour.git] / libs / ardour / region.cc
1 /*
2     Copyright (C) 2000-2003 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     $Id$
19 */
20
21 #include <iostream>
22 #include <cmath>
23 #include <climits>
24 #include <algorithm>
25
26 #include <sigc++/bind.h>
27 #include <sigc++/class_slot.h>
28
29 #include <glibmm/thread.h>
30 #include <pbd/xml++.h>
31
32 #include <ardour/region.h>
33 #include <ardour/playlist.h>
34 #include <ardour/session.h>
35 #include <ardour/region_factory.h>
36
37 #include "i18n.h"
38
39 using namespace std;
40 using namespace ARDOUR;
41 using namespace PBD;
42
43 Change Region::FadeChanged       = ARDOUR::new_change ();
44 Change Region::SyncOffsetChanged = ARDOUR::new_change ();
45 Change Region::MuteChanged       = ARDOUR::new_change ();
46 Change Region::OpacityChanged    = ARDOUR::new_change ();
47 Change Region::LockChanged       = ARDOUR::new_change ();
48 Change Region::LayerChanged      = ARDOUR::new_change ();
49 Change Region::HiddenChanged     = ARDOUR::new_change ();
50
51 Region::Region (nframes_t start, nframes_t length, const string& name, layer_t layer, Region::Flag flags)
52 {
53         /* basic Region constructor */
54
55         _flags = flags;
56         _playlist = 0;
57         _read_data_count = 0;
58         _frozen = 0;
59         pending_changed = Change (0);
60
61         _name = name;
62         _start = start; 
63         _sync_position = _start;
64         _length = length; 
65         _position = 0; 
66         _layer = layer;
67         _current_state_id = 0;
68         _read_data_count = 0;
69         _first_edit = EditChangesNothing;
70         _last_layer_op = 0;
71 }
72
73 Region::Region (boost::shared_ptr<const Region> other, nframes_t offset, nframes_t length, const string& name, layer_t layer, Flag flags)
74 {
75         /* create a new Region from part of an existing one */
76
77         _frozen = 0;
78         pending_changed = Change (0);
79         _playlist = 0;
80         _read_data_count = 0;
81
82         _start = other->_start + offset; 
83         if (other->_sync_position < offset) {
84                 _sync_position = other->_sync_position;
85         } else {
86                 _sync_position = _start;
87         }
88         _length = length; 
89         _name = name;
90         _position = 0; 
91         _layer = layer; 
92         _flags = Flag (flags & ~(Locked|WholeFile|Hidden));
93         _current_state_id = 0;
94         _first_edit = EditChangesNothing;
95         _last_layer_op = 0;
96 }
97
98 Region::Region (boost::shared_ptr<const Region> other)
99 {
100         /* Pure copy constructor */
101
102         _frozen = 0;
103         pending_changed = Change (0);
104         _playlist = 0;
105         _read_data_count = 0;
106
107         _first_edit = EditChangesID;
108         other->_first_edit = EditChangesName;
109
110         if (other->_extra_xml) {
111                 _extra_xml = new XMLNode (*other->_extra_xml);
112         } else {
113                 _extra_xml = 0;
114         }
115
116         _start = other->_start;
117         _sync_position = other->_sync_position;
118         _length = other->_length; 
119         _name = other->_name;
120         _position = other->_position; 
121         _layer = other->_layer; 
122         _flags = Flag (other->_flags & ~Locked);
123         _current_state_id = 0;
124         _last_layer_op = other->_last_layer_op;
125 }
126
127 Region::Region (const XMLNode& node)
128 {
129         _frozen = 0;
130         pending_changed = Change (0);
131         _playlist = 0;
132         _read_data_count = 0;
133         _start = 0; 
134         _sync_position = _start;
135         _length = 0;
136         _name = X_("error: XML did not reset this");
137         _position = 0; 
138         _layer = 0;
139         _flags = Flag (0);
140         _current_state_id = 0;
141         _first_edit = EditChangesNothing;
142
143         if (set_state (node)) {
144                 throw failed_constructor();
145         }
146 }
147
148 Region::~Region ()
149 {
150         /* derived classes must call notify_callbacks() and then emit GoingAway */
151 }
152
153 void
154 Region::set_playlist (Playlist* pl)
155 {
156         _playlist = pl;
157 }
158
159 void
160 Region::store_state (RegionState& state) const
161 {
162         state._start = _start;
163         state._length = _length;
164         state._position = _position;
165         state._flags = _flags;
166         state._sync_position = _sync_position;
167         state._layer = _layer;
168         state._name = _name;
169         state._first_edit = _first_edit;
170 }       
171
172 Change
173 Region::restore_and_return_flags (RegionState& state)
174 {
175         Change what_changed = Change (0);
176
177         {
178                 Glib::Mutex::Lock lm (lock);
179                 
180                 if (_start != state._start) {
181                         what_changed = Change (what_changed|StartChanged);      
182                         _start = state._start;
183                 }
184                 if (_length != state._length) {
185                         what_changed = Change (what_changed|LengthChanged);
186                         _length = state._length;
187                 }
188                 if (_position != state._position) {
189                         what_changed = Change (what_changed|PositionChanged);
190                         _position = state._position;
191                 } 
192                 if (_sync_position != state._sync_position) {
193                         _sync_position = state._sync_position;
194                         what_changed = Change (what_changed|SyncOffsetChanged);
195                 }
196                 if (_layer != state._layer) {
197                         what_changed = Change (what_changed|LayerChanged);
198                         _layer = state._layer;
199                 }
200
201                 uint32_t old_flags = _flags;
202                 _flags = Flag (state._flags);
203                 
204                 if ((old_flags ^ state._flags) & Muted) {
205                         what_changed = Change (what_changed|MuteChanged);
206                 }
207                 if ((old_flags ^ state._flags) & Opaque) {
208                         what_changed = Change (what_changed|OpacityChanged);
209                 }
210                 if ((old_flags ^ state._flags) & Locked) {
211                         what_changed = Change (what_changed|LockChanged);
212                 }
213
214                 _first_edit = state._first_edit;
215         }
216
217         return what_changed;
218 }
219
220 void
221 Region::set_name (string str)
222
223 {
224         if (_name != str) {
225                 _name = str; 
226                 send_change (NameChanged);
227         }
228 }
229
230 void
231 Region::set_length (nframes_t len, void *src)
232 {
233         if (_flags & Locked) {
234                 return;
235         }
236
237         if (_length != len && len != 0) {
238
239                 /* check that the current _position wouldn't make the new 
240                    length impossible.
241                 */
242
243                 if (max_frames - len < _position) {
244                         return;
245                 }
246
247                 if (!verify_length (len)) {
248                         return;
249                 }
250                 
251                 _length = len;
252
253                 _flags = Region::Flag (_flags & ~WholeFile);
254
255                 first_edit ();
256                 maybe_uncopy ();
257
258                 if (!_frozen) {
259                         recompute_at_end ();
260
261                         char buf[64];
262                         snprintf (buf, sizeof (buf), "length set to %u", len);
263                         save_state (buf);
264                 }
265
266                 send_change (LengthChanged);
267         }
268 }
269
270 void
271 Region::maybe_uncopy ()
272 {
273 }
274
275 void
276 Region::first_edit ()
277 {
278         if (_first_edit != EditChangesNothing && _playlist) {
279
280                 _name = _playlist->session().new_region_name (_name);
281                 _first_edit = EditChangesNothing;
282
283                 send_change (NameChanged);
284                 RegionFactory::CheckNewRegion (shared_from_this());
285         }
286 }
287
288 void
289 Region::move_to_natural_position (void *src)
290 {
291         if (!_playlist) {
292                 return;
293         }
294         
295         boost::shared_ptr<Region> whole_file_region = get_parent();
296
297         if (whole_file_region) {
298                 set_position (whole_file_region->position() + _start, src);
299         }
300 }
301         
302 void
303 Region::special_set_position (nframes_t pos)
304 {
305         /* this is used when creating a whole file region as 
306            a way to store its "natural" or "captured" position.
307         */
308
309         _position = pos;
310 }
311
312 void
313 Region::set_position (nframes_t pos, void *src)
314 {
315         if (_flags & Locked) {
316                 return;
317         }
318
319         if (_position != pos) {
320                 _position = pos;
321
322                 /* check that the new _position wouldn't make the current
323                    length impossible - if so, change the length. 
324
325                    XXX is this the right thing to do?
326                 */
327
328                 if (max_frames - _length < _position) {
329                         _length = max_frames - _position;
330                 }
331
332                 if (!_frozen) {
333                         char buf[64];
334                         snprintf (buf, sizeof (buf), "position set to %u", pos);
335                         save_state (buf);
336                 }
337         }
338
339         /* do this even if the position is the same. this helps out
340            a GUI that has moved its representation already.
341         */
342
343         send_change (PositionChanged);
344 }
345
346 void
347 Region::set_position_on_top (nframes_t pos, void *src)
348 {
349         if (_flags & Locked) {
350                 return;
351         }
352
353         if (_position != pos) {
354                 _position = pos;
355
356                 if (!_frozen) {
357                         char buf[64];
358                         snprintf (buf, sizeof (buf), "position set to %u", pos);
359                         save_state (buf);
360                 }
361         }
362
363         _playlist->raise_region_to_top (boost::shared_ptr<Region>(this));
364
365         /* do this even if the position is the same. this helps out
366            a GUI that has moved its representation already.
367         */
368         
369         send_change (PositionChanged);
370 }
371
372 void
373 Region::nudge_position (long n, void *src)
374 {
375         if (_flags & Locked) {
376                 return;
377         }
378
379         if (n == 0) {
380                 return;
381         }
382         
383         if (n > 0) {
384                 if (_position > max_frames - n) {
385                         _position = max_frames;
386                 } else {
387                         _position += n;
388                 }
389         } else {
390                 if (_position < (nframes_t) -n) {
391                         _position = 0;
392                 } else {
393                         _position += n;
394                 }
395         }
396
397         if (!_frozen) {
398                 char buf[64];
399                 snprintf (buf, sizeof (buf), "position set to %u", _position);
400                 save_state (buf);
401         }
402
403         send_change (PositionChanged);
404 }
405
406 void
407 Region::set_start (nframes_t pos, void *src)
408 {
409         if (_flags & Locked) {
410                 return;
411         }
412         /* This just sets the start, nothing else. It effectively shifts
413            the contents of the Region within the overall extent of the Source,
414            without changing the Region's position or length
415         */
416
417         if (_start != pos) {
418
419                 if (!verify_start (pos)) {
420                         return;
421                 }
422
423                 _start = pos;
424                 _flags = Region::Flag (_flags & ~WholeFile);
425                 first_edit ();
426
427                 if (!_frozen) {
428                         char buf[64];
429                         snprintf (buf, sizeof (buf), "start set to %u", pos);
430                         save_state (buf);
431                 }
432
433                 send_change (StartChanged);
434         }
435 }
436
437 void
438 Region::trim_start (nframes_t new_position, void *src)
439 {
440         if (_flags & Locked) {
441                 return;
442         }
443         nframes_t new_start;
444         int32_t start_shift;
445         
446         if (new_position > _position) {
447                 start_shift = new_position - _position;
448         } else {
449                 start_shift = -(_position - new_position);
450         }
451
452         if (start_shift > 0) {
453
454                 if (_start > max_frames - start_shift) {
455                         new_start = max_frames;
456                 } else {
457                         new_start = _start + start_shift;
458                 }
459
460                 if (!verify_start (new_start)) {
461                         return;
462                 }
463
464         } else if (start_shift < 0) {
465
466                 if (_start < (nframes_t) -start_shift) {
467                         new_start = 0;
468                 } else {
469                         new_start = _start + start_shift;
470                 }
471         } else {
472                 return;
473         }
474
475         if (new_start == _start) {
476                 return;
477         }
478         
479         _start = new_start;
480         _flags = Region::Flag (_flags & ~WholeFile);
481         first_edit ();
482
483         if (!_frozen) {
484                 char buf[64];
485                 snprintf (buf, sizeof (buf), "slipped start to %u", _start);
486                 save_state (buf);
487         }
488
489         send_change (StartChanged);
490 }
491
492 void
493 Region::trim_front (nframes_t new_position, void *src)
494 {
495         if (_flags & Locked) {
496                 return;
497         }
498
499         nframes_t end = last_frame();
500         nframes_t source_zero;
501
502         if (_position > _start) {
503                 source_zero = _position - _start;
504         } else {
505                 source_zero = 0; // its actually negative, but this will work for us
506         }
507
508         if (new_position < end) { /* can't trim it zero or negative length */
509                 
510                 nframes_t newlen;
511
512                 /* can't trim it back passed where source position zero is located */
513                 
514                 new_position = max (new_position, source_zero);
515                 
516                 
517                 if (new_position > _position) {
518                         newlen = _length - (new_position - _position);
519                 } else {
520                         newlen = _length + (_position - new_position);
521                 }
522                 
523                 trim_to_internal (new_position, newlen, src);
524                 if (!_frozen) {
525                         recompute_at_start ();
526                 }
527         }
528 }
529
530 void
531 Region::trim_end (nframes_t new_endpoint, void *src)
532 {
533         if (_flags & Locked) {
534                 return;
535         }
536
537         if (new_endpoint > _position) {
538                 trim_to_internal (_position, new_endpoint - _position, this);
539                 if (!_frozen) {
540                         recompute_at_end ();
541                 }
542         }
543 }
544
545 void
546 Region::trim_to (nframes_t position, nframes_t length, void *src)
547 {
548         if (_flags & Locked) {
549                 return;
550         }
551
552         trim_to_internal (position, length, src);
553
554         if (!_frozen) {
555                 recompute_at_start ();
556                 recompute_at_end ();
557         }
558 }
559
560 void
561 Region::trim_to_internal (nframes_t position, nframes_t length, void *src)
562 {
563         int32_t start_shift;
564         nframes_t new_start;
565
566         if (_flags & Locked) {
567                 return;
568         }
569
570         if (position > _position) {
571                 start_shift = position - _position;
572         } else {
573                 start_shift = -(_position - position);
574         }
575
576         if (start_shift > 0) {
577
578                 if (_start > max_frames - start_shift) {
579                         new_start = max_frames;
580                 } else {
581                         new_start = _start + start_shift;
582                 }
583
584
585         } else if (start_shift < 0) {
586
587                 if (_start < (nframes_t) -start_shift) {
588                         new_start = 0;
589                 } else {
590                         new_start = _start + start_shift;
591                 }
592         } else {
593                 new_start = _start;
594         }
595
596         if (!verify_start_and_length (new_start, length)) {
597                 return;
598         }
599
600         Change what_changed = Change (0);
601
602         if (_start != new_start) {
603                 _start = new_start;
604                 what_changed = Change (what_changed|StartChanged);
605         }
606         if (_length != length) {
607                 _length = length;
608                 what_changed = Change (what_changed|LengthChanged);
609         }
610         if (_position != position) {
611                 _position = position;
612                 what_changed = Change (what_changed|PositionChanged);
613         }
614         
615         _flags = Region::Flag (_flags & ~WholeFile);
616
617         if (what_changed & (StartChanged|LengthChanged)) {
618                 first_edit ();
619         } 
620
621         if (what_changed) {
622                 
623                 if (!_frozen) {
624                         char buf[64];
625                         snprintf (buf, sizeof (buf), "trimmed to %u-%u", _position, _position+_length-1);
626                         save_state (buf);
627                 }
628
629                 send_change (what_changed);
630         }
631 }       
632
633 void
634 Region::set_hidden (bool yn)
635 {
636         if (hidden() != yn) {
637
638                 if (yn) {
639                         _flags = Flag (_flags|Hidden);
640                 } else {
641                         _flags = Flag (_flags & ~Hidden);
642                 }
643
644                 send_change (HiddenChanged);
645         }
646 }
647
648 void
649 Region::set_muted (bool yn)
650 {
651         if (muted() != yn) {
652
653                 if (yn) {
654                         _flags = Flag (_flags|Muted);
655                 } else {
656                         _flags = Flag (_flags & ~Muted);
657                 }
658
659                 if (!_frozen) {
660                         char buf[64];
661                         if (yn) {
662                                 snprintf (buf, sizeof (buf), "muted");
663                         } else {
664                                 snprintf (buf, sizeof (buf), "unmuted");
665                         }
666                         save_state (buf);
667                 }
668
669                 send_change (MuteChanged);
670         }
671 }
672
673 void
674 Region::set_opaque (bool yn)
675 {
676         if (opaque() != yn) {
677                 if (!_frozen) {
678                         char buf[64];
679                         if (yn) {
680                                 snprintf (buf, sizeof (buf), "opaque");
681                                 _flags = Flag (_flags|Opaque);
682                         } else {
683                                 snprintf (buf, sizeof (buf), "translucent");
684                                 _flags = Flag (_flags & ~Opaque);
685                         }
686                         save_state (buf);
687                 }
688                 send_change (OpacityChanged);
689         }
690 }
691
692 void
693 Region::set_locked (bool yn)
694 {
695         if (locked() != yn) {
696                 if (!_frozen) {
697                         char buf[64];
698                         if (yn) {
699                                 snprintf (buf, sizeof (buf), "locked");
700                                 _flags = Flag (_flags|Locked);
701                         } else {
702                                 snprintf (buf, sizeof (buf), "unlocked");
703                                 _flags = Flag (_flags & ~Locked);
704                         }
705                         save_state (buf);
706                 }
707                 send_change (LockChanged);
708         }
709 }
710
711 void
712 Region::set_sync_position (nframes_t absolute_pos)
713 {
714         nframes_t file_pos;
715
716         file_pos = _start + (absolute_pos - _position);
717
718         if (file_pos != _sync_position) {
719                 
720                 _sync_position = file_pos;
721                 _flags = Flag (_flags|SyncMarked);
722
723                 if (!_frozen) {
724                         char buf[64];
725                         maybe_uncopy ();
726                         snprintf (buf, sizeof (buf), "sync point set to %u", _sync_position);
727                         save_state (buf);
728                 }
729                 send_change (SyncOffsetChanged);
730         }
731 }
732
733 void
734 Region::clear_sync_position ()
735 {
736         if (_flags & SyncMarked) {
737                 _flags = Flag (_flags & ~SyncMarked);
738
739                 if (!_frozen) {
740                         maybe_uncopy ();
741                         save_state ("sync point removed");
742                 }
743                 send_change (SyncOffsetChanged);
744         }
745 }
746
747 nframes_t
748 Region::sync_offset (int& dir) const
749 {
750         /* returns the sync point relative the first frame of the region */
751
752         if (_flags & SyncMarked) {
753                 if (_sync_position > _start) {
754                         dir = 1;
755                         return _sync_position - _start; 
756                 } else {
757                         dir = -1;
758                         return _start - _sync_position;
759                 }
760         } else {
761                 dir = 0;
762                 return 0;
763         }
764 }
765
766 nframes_t 
767 Region::adjust_to_sync (nframes_t pos)
768 {
769         int sync_dir;
770         nframes_t offset = sync_offset (sync_dir);
771         
772         if (sync_dir > 0) {
773                 if (max_frames - pos > offset) {
774                         pos += offset;
775                 }
776         } else {
777                 if (pos > offset) {
778                         pos -= offset;
779                 } else {
780                         pos = 0;
781                 }
782         }
783
784         return pos;
785 }
786
787 nframes_t
788 Region::sync_position() const
789 {
790         if (_flags & SyncMarked) {
791                 return _sync_position; 
792         } else {
793                 return _start;
794         }
795 }
796
797
798 void
799 Region::raise ()
800 {
801         if (_playlist == 0) {
802                 return;
803         }
804
805         _playlist->raise_region (boost::shared_ptr<Region>(this));
806 }
807
808 void
809 Region::lower ()
810 {
811         if (_playlist == 0) {
812                 return;
813         }
814
815         _playlist->lower_region (boost::shared_ptr<Region>(this));
816 }
817
818 void
819 Region::raise_to_top ()
820 {
821
822         if (_playlist == 0) {
823                 return;
824         }
825
826         _playlist->raise_region_to_top (boost::shared_ptr<Region>(this));
827 }
828
829 void
830 Region::lower_to_bottom ()
831 {
832         if (_playlist == 0) {
833                 return;
834         }
835
836         _playlist->lower_region_to_bottom (boost::shared_ptr<Region>(this));
837 }
838
839 void
840 Region::set_layer (layer_t l)
841 {
842         if (_layer != l) {
843                 _layer = l;
844                 
845                 if (!_frozen) {
846                         char buf[64];
847                         snprintf (buf, sizeof (buf), "layer set to %" PRIu32, _layer);
848                         save_state (buf);
849                 }
850                 
851                 send_change (LayerChanged);
852         }
853 }
854
855 XMLNode&
856 Region::state (bool full_state)
857 {
858         XMLNode *node = new XMLNode ("Region");
859         char buf[64];
860         
861         _id.print (buf, sizeof (buf));
862         node->add_property ("id", buf);
863         node->add_property ("name", _name);
864         snprintf (buf, sizeof (buf), "%u", _start);
865         node->add_property ("start", buf);
866         snprintf (buf, sizeof (buf), "%u", _length);
867         node->add_property ("length", buf);
868         snprintf (buf, sizeof (buf), "%u", _position);
869         node->add_property ("position", buf);
870
871         /* note: flags are stored by derived classes */
872
873         snprintf (buf, sizeof (buf), "%d", (int) _layer);
874         node->add_property ("layer", buf);
875         snprintf (buf, sizeof (buf), "%u", _sync_position);
876         node->add_property ("sync-position", buf);
877
878         return *node;
879 }
880
881 XMLNode&
882 Region::get_state ()
883 {
884         return state (true);
885 }
886
887 int
888 Region::set_state (const XMLNode& node)
889 {
890         const XMLNodeList& nlist = node.children();
891         const XMLProperty *prop;
892
893         if (_extra_xml) {
894                 delete _extra_xml;
895                 _extra_xml = 0;
896         }
897
898         if ((prop = node.property ("id")) == 0) {
899                 error << _("Session: XMLNode describing a Region is incomplete (no id)") << endmsg;
900                 return -1;
901         }
902
903         _id = prop->value();
904
905         if ((prop = node.property ("name")) == 0) {
906                 error << _("Session: XMLNode describing a Region is incomplete (no name)") << endmsg;
907                 return -1;
908         }
909
910         _name = prop->value();
911
912         if ((prop = node.property ("start")) != 0) {
913                 sscanf (prop->value().c_str(), "%" PRIu32, &_start);
914         }
915
916         if ((prop = node.property ("length")) != 0) {
917                 sscanf (prop->value().c_str(), "%" PRIu32, &_length);
918         }
919
920         if ((prop = node.property ("position")) != 0) {
921                 sscanf (prop->value().c_str(), "%" PRIu32, &_position);
922         }
923
924         if ((prop = node.property ("layer")) != 0) {
925                 _layer = (layer_t) atoi (prop->value().c_str());
926         }
927
928         /* note: derived classes set flags */
929
930         if ((prop = node.property ("sync-position")) != 0) {
931                 sscanf (prop->value().c_str(), "%" PRIu32, &_sync_position);
932         } else {
933                 _sync_position = _start;
934         }
935         
936         for (XMLNodeConstIterator niter = nlist.begin(); niter != nlist.end(); ++niter) {
937                 
938                 XMLNode *child;
939                 
940                 child = (*niter);
941                 
942                 if (child->name () == "extra") {
943                         _extra_xml = new XMLNode (*child);
944                         break;
945                 }
946         }
947
948         _first_edit = EditChangesNothing;
949
950         return 0;
951 }
952
953 void
954 Region::freeze ()
955 {
956         _frozen++;
957 }
958
959 void
960 Region::thaw (const string& why)
961 {
962         Change what_changed = Change (0);
963
964         {
965                 Glib::Mutex::Lock lm (lock);
966
967                 if (_frozen && --_frozen > 0) {
968                         return;
969                 }
970
971                 if (pending_changed) {
972                         what_changed = pending_changed;
973                         pending_changed = Change (0);
974                 }
975         }
976
977         if (what_changed == Change (0)) {
978                 return;
979         }
980
981         if (what_changed & LengthChanged) {
982                 if (what_changed & PositionChanged) {
983                         recompute_at_start ();
984                 } 
985                 recompute_at_end ();
986         }
987                 
988         save_state (why);
989         StateChanged (what_changed);
990 }
991
992 void
993 Region::send_change (Change what_changed)
994 {
995         {
996                 Glib::Mutex::Lock lm (lock);
997                 if (_frozen) {
998                         pending_changed = Change (pending_changed|what_changed);
999                         return;
1000                 } 
1001         }
1002
1003         StateManager::send_state_changed (what_changed);
1004 }
1005
1006 void
1007 Region::set_last_layer_op (uint64_t when)
1008 {
1009         _last_layer_op = when;
1010 }
1011
1012 bool
1013 Region::overlap_equivalent (boost::shared_ptr<const Region> other) const
1014 {
1015         return coverage (other->first_frame(), other->last_frame()) != OverlapNone;
1016 }
1017
1018 bool
1019 Region::equivalent (boost::shared_ptr<const Region> other) const
1020 {
1021         return _start == other->_start &&
1022                 _position == other->_position &&
1023                 _length == other->_length;
1024 }
1025
1026 bool
1027 Region::size_equivalent (boost::shared_ptr<const Region> other) const
1028 {
1029         return _start == other->_start &&
1030                 _length == other->_length;
1031 }
1032
1033 bool
1034 Region::region_list_equivalent (boost::shared_ptr<const Region> other) const
1035 {
1036         return size_equivalent (other) && source_equivalent (other) && _name == other->_name;
1037 }