Set up layering_index immediately on an explicit layer, so that undo
[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 */
19
20 #include <iostream>
21 #include <cmath>
22 #include <climits>
23 #include <algorithm>
24 #include <sstream>
25
26 #include <glibmm/thread.h>
27 #include "pbd/xml++.h"
28 #include "pbd/stacktrace.h"
29 #include "pbd/enumwriter.h"
30
31 #include "ardour/debug.h"
32 #include "ardour/file_source.h"
33 #include "ardour/filter.h"
34 #include "ardour/playlist.h"
35 #include "ardour/playlist_source.h"
36 #include "ardour/profile.h"
37 #include "ardour/region.h"
38 #include "ardour/region_factory.h"
39 #include "ardour/session.h"
40 #include "ardour/source.h"
41 #include "ardour/source_factory.h"
42 #include "ardour/tempo.h"
43 #include "ardour/utils.h"
44
45 #include "i18n.h"
46
47 using namespace std;
48 using namespace ARDOUR;
49 using namespace PBD;
50
51 namespace ARDOUR {
52         namespace Properties {
53                 PBD::PropertyDescriptor<bool> muted;
54                 PBD::PropertyDescriptor<bool> opaque;
55                 PBD::PropertyDescriptor<bool> locked;
56                 PBD::PropertyDescriptor<bool> automatic;
57                 PBD::PropertyDescriptor<bool> whole_file;
58                 PBD::PropertyDescriptor<bool> import;
59                 PBD::PropertyDescriptor<bool> external;
60                 PBD::PropertyDescriptor<bool> sync_marked;
61                 PBD::PropertyDescriptor<bool> left_of_split;
62                 PBD::PropertyDescriptor<bool> right_of_split;
63                 PBD::PropertyDescriptor<bool> hidden;
64                 PBD::PropertyDescriptor<bool> position_locked;
65                 PBD::PropertyDescriptor<bool> valid_transients;
66                 PBD::PropertyDescriptor<framepos_t> start;
67                 PBD::PropertyDescriptor<framecnt_t> length;
68                 PBD::PropertyDescriptor<framepos_t> position;
69                 PBD::PropertyDescriptor<framecnt_t> sync_position;
70                 PBD::PropertyDescriptor<layer_t> layer;
71                 PBD::PropertyDescriptor<framepos_t> ancestral_start;
72                 PBD::PropertyDescriptor<framecnt_t> ancestral_length;
73                 PBD::PropertyDescriptor<float> stretch;
74                 PBD::PropertyDescriptor<float> shift;
75                 PBD::PropertyDescriptor<PositionLockStyle> position_lock_style;
76                 PBD::PropertyDescriptor<uint64_t> layering_index;
77         }
78 }
79
80 PBD::Signal2<void,boost::shared_ptr<ARDOUR::Region>,const PropertyChange&> Region::RegionPropertyChanged;
81
82 void
83 Region::make_property_quarks ()
84 {
85         Properties::muted.property_id = g_quark_from_static_string (X_("muted"));
86         DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for muted = %1\n",       Properties::muted.property_id));
87         Properties::opaque.property_id = g_quark_from_static_string (X_("opaque"));
88         DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for opaque = %1\n",      Properties::opaque.property_id));
89         Properties::locked.property_id = g_quark_from_static_string (X_("locked"));
90         DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for locked = %1\n",      Properties::locked.property_id));
91         Properties::automatic.property_id = g_quark_from_static_string (X_("automatic"));
92         DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for automatic = %1\n",   Properties::automatic.property_id));
93         Properties::whole_file.property_id = g_quark_from_static_string (X_("whole-file"));
94         DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for whole-file = %1\n",  Properties::whole_file.property_id));
95         Properties::import.property_id = g_quark_from_static_string (X_("import"));
96         DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for import = %1\n",      Properties::import.property_id));
97         Properties::external.property_id = g_quark_from_static_string (X_("external"));
98         DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for external = %1\n",    Properties::external.property_id));
99         Properties::sync_marked.property_id = g_quark_from_static_string (X_("sync-marked"));
100         DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for sync-marked = %1\n",         Properties::sync_marked.property_id));
101         Properties::left_of_split.property_id = g_quark_from_static_string (X_("left-of-split"));
102         DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for left-of-split = %1\n",       Properties::left_of_split.property_id));
103         Properties::right_of_split.property_id = g_quark_from_static_string (X_("right-of-split"));
104         DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for right-of-split = %1\n",      Properties::right_of_split.property_id));
105         Properties::hidden.property_id = g_quark_from_static_string (X_("hidden"));
106         DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for hidden = %1\n",      Properties::hidden.property_id));
107         Properties::position_locked.property_id = g_quark_from_static_string (X_("position-locked"));
108         DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for position-locked = %1\n",     Properties::position_locked.property_id));
109         Properties::valid_transients.property_id = g_quark_from_static_string (X_("valid-transients"));
110         DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for valid-transients = %1\n",    Properties::valid_transients.property_id));
111         Properties::start.property_id = g_quark_from_static_string (X_("start"));
112         DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for start = %1\n",       Properties::start.property_id));
113         Properties::length.property_id = g_quark_from_static_string (X_("length"));
114         DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for length = %1\n",      Properties::length.property_id));
115         Properties::position.property_id = g_quark_from_static_string (X_("position"));
116         DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for position = %1\n",    Properties::position.property_id));
117         Properties::sync_position.property_id = g_quark_from_static_string (X_("sync-position"));
118         DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for sync-position = %1\n",       Properties::sync_position.property_id));
119         Properties::layer.property_id = g_quark_from_static_string (X_("layer"));
120         DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for layer = %1\n",       Properties::layer.property_id));
121         Properties::ancestral_start.property_id = g_quark_from_static_string (X_("ancestral-start"));
122         DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for ancestral-start = %1\n",     Properties::ancestral_start.property_id));
123         Properties::ancestral_length.property_id = g_quark_from_static_string (X_("ancestral-length"));
124         DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for ancestral-length = %1\n",    Properties::ancestral_length.property_id));
125         Properties::stretch.property_id = g_quark_from_static_string (X_("stretch"));
126         DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for stretch = %1\n",     Properties::stretch.property_id));
127         Properties::shift.property_id = g_quark_from_static_string (X_("shift"));
128         DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for shift = %1\n",       Properties::shift.property_id));
129         Properties::position_lock_style.property_id = g_quark_from_static_string (X_("positional-lock-style"));
130         DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for position_lock_style = %1\n",         Properties::position_lock_style.property_id));
131         Properties::layering_index.property_id = g_quark_from_static_string (X_("layering-index"));
132         DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for layering_index = %1\n",      Properties::layering_index.property_id));
133 }
134
135 void
136 Region::register_properties ()
137 {
138         _xml_node_name = X_("Region");
139
140         add_property (_muted);
141         add_property (_opaque);
142         add_property (_locked);
143         add_property (_automatic);
144         add_property (_whole_file);
145         add_property (_import);
146         add_property (_external);
147         add_property (_sync_marked);
148         add_property (_left_of_split);
149         add_property (_right_of_split);
150         add_property (_hidden);
151         add_property (_position_locked);
152         add_property (_valid_transients);
153         add_property (_start);
154         add_property (_length);
155         add_property (_position);
156         add_property (_sync_position);
157         add_property (_ancestral_start);
158         add_property (_ancestral_length);
159         add_property (_stretch);
160         add_property (_shift);
161         add_property (_position_lock_style);
162         add_property (_layering_index);
163 }
164
165 #define REGION_DEFAULT_STATE(s,l) \
166         _sync_marked (Properties::sync_marked, false) \
167         , _left_of_split (Properties::left_of_split, false) \
168         , _right_of_split (Properties::right_of_split, false) \
169         , _valid_transients (Properties::valid_transients, false) \
170         , _start (Properties::start, (s))       \
171         , _length (Properties::length, (l))     \
172         , _position (Properties::position, 0) \
173         , _sync_position (Properties::sync_position, (s)) \
174         , _muted (Properties::muted, false) \
175         , _opaque (Properties::opaque, true) \
176         , _locked (Properties::locked, false) \
177         , _automatic (Properties::automatic, false) \
178         , _whole_file (Properties::whole_file, false) \
179         , _import (Properties::import, false) \
180         , _external (Properties::external, false) \
181         , _hidden (Properties::hidden, false) \
182         , _position_locked (Properties::position_locked, false) \
183         , _ancestral_start (Properties::ancestral_start, (s)) \
184         , _ancestral_length (Properties::ancestral_length, (l)) \
185         , _stretch (Properties::stretch, 1.0) \
186         , _shift (Properties::shift, 1.0) \
187         , _position_lock_style (Properties::position_lock_style, _type == DataType::AUDIO ? AudioTime : MusicTime) \
188         , _layering_index (Properties::layering_index, 0)
189
190 #define REGION_COPY_STATE(other) \
191           _sync_marked (Properties::sync_marked, other->_sync_marked) \
192         , _left_of_split (Properties::left_of_split, other->_left_of_split) \
193         , _right_of_split (Properties::right_of_split, other->_right_of_split) \
194         , _valid_transients (Properties::valid_transients, other->_valid_transients) \
195         , _start(Properties::start, other->_start)              \
196         , _length(Properties::length, other->_length)           \
197         , _position(Properties::position, other->_position)     \
198         , _sync_position(Properties::sync_position, other->_sync_position) \
199         , _muted (Properties::muted, other->_muted)             \
200         , _opaque (Properties::opaque, other->_opaque)          \
201         , _locked (Properties::locked, other->_locked)          \
202         , _automatic (Properties::automatic, other->_automatic) \
203         , _whole_file (Properties::whole_file, other->_whole_file) \
204         , _import (Properties::import, other->_import)          \
205         , _external (Properties::external, other->_external)    \
206         , _hidden (Properties::hidden, other->_hidden)          \
207         , _position_locked (Properties::position_locked, other->_position_locked) \
208         , _ancestral_start (Properties::ancestral_start, other->_ancestral_start) \
209         , _ancestral_length (Properties::ancestral_length, other->_ancestral_length) \
210         , _stretch (Properties::stretch, other->_stretch)       \
211         , _shift (Properties::shift, other->_shift)             \
212         , _position_lock_style (Properties::position_lock_style, other->_position_lock_style) \
213         , _layering_index (Properties::layering_index, other->_layering_index)
214
215 /* derived-from-derived constructor (no sources in constructor) */
216 Region::Region (Session& s, framepos_t start, framecnt_t length, const string& name, DataType type)
217         : SessionObject(s, name)
218         , _type(type)
219         , REGION_DEFAULT_STATE(start,length)
220         , _last_length (length)
221         , _last_position (0)
222         , _first_edit (EditChangesNothing)
223         , _layer (0)
224 {
225         register_properties ();
226
227         /* no sources at this point */
228 }
229
230 /** Basic Region constructor (many sources) */
231 Region::Region (const SourceList& srcs)
232         : SessionObject(srcs.front()->session(), "toBeRenamed")
233         , _type (srcs.front()->type())
234         , REGION_DEFAULT_STATE(0,0)
235         , _last_length (0)
236         , _last_position (0)
237         , _first_edit (EditChangesNothing)
238         , _layer (0)
239 {
240         register_properties ();
241
242         _type = srcs.front()->type();
243
244         use_sources (srcs);
245
246         assert(_sources.size() > 0);
247         assert (_type == srcs.front()->type());
248 }
249
250 /** Create a new Region from an existing one */
251 Region::Region (boost::shared_ptr<const Region> other)
252         : SessionObject(other->session(), other->name())
253         , _type (other->data_type())
254         , REGION_COPY_STATE (other)
255         , _last_length (other->_last_length)
256         , _last_position(other->_last_position) \
257         , _first_edit (EditChangesNothing)
258         , _layer (other->_layer)
259 {
260         register_properties ();
261
262         /* override state that may have been incorrectly inherited from the other region
263          */
264
265         _position = 0;
266         _locked = false;
267         _whole_file = false;
268         _hidden = false;
269
270         use_sources (other->_sources);
271
272         _position_lock_style = other->_position_lock_style;
273         _first_edit = other->_first_edit;
274
275         _start = 0; // It seems strange _start is not inherited here?
276
277         /* sync pos is relative to start of file. our start-in-file is now zero,
278            so set our sync position to whatever the the difference between
279            _start and _sync_pos was in the other region.
280
281            result is that our new sync pos points to the same point in our source(s)
282            as the sync in the other region did in its source(s).
283
284            since we start at zero in our source(s), it is not possible to use a sync point that
285            is before the start. reset it to _start if that was true in the other region.
286         */
287
288         if (other->sync_marked()) {
289                 if (other->_start < other->_sync_position) {
290                         /* sync pos was after the start point of the other region */
291                         _sync_position = other->_sync_position - other->_start;
292                 } else {
293                         /* sync pos was before the start point of the other region. not possible here. */
294                         _sync_marked = false;
295                         _sync_position = _start;
296                 }
297         } else {
298                 _sync_marked = false;
299                 _sync_position = _start;
300         }
301
302         if (Profile->get_sae()) {
303                 /* reset sync point to start if its ended up
304                    outside region bounds.
305                 */
306
307                 if (_sync_position < _start || _sync_position >= _start + _length) {
308                         _sync_marked = false;
309                         _sync_position = _start;
310                 }
311         }
312
313         assert (_type == other->data_type());
314 }
315
316 /** Create a new Region from part of an existing one.
317
318     the start within \a other is given by \a offset
319     (i.e. relative to the start of \a other's sources, the start is \a offset + \a other.start()
320 */
321 Region::Region (boost::shared_ptr<const Region> other, frameoffset_t offset)
322         : SessionObject(other->session(), other->name())
323         , _type (other->data_type())
324         , REGION_COPY_STATE (other)
325         , _last_length (other->_last_length)
326         , _last_position(other->_last_position) \
327         , _first_edit (EditChangesNothing)
328         , _layer (other->_layer)
329 {
330         register_properties ();
331
332         /* override state that may have been incorrectly inherited from the other region
333          */
334
335         _position = 0;
336         _locked = false;
337         _whole_file = false;
338         _hidden = false;
339
340         use_sources (other->_sources);
341
342         _start = other->_start + offset;
343
344         /* if the other region had a distinct sync point
345            set, then continue to use it as best we can.
346            otherwise, reset sync point back to start.
347         */
348
349         if (other->sync_marked()) {
350                 if (other->_sync_position < _start) {
351                         _sync_marked = false;
352                         _sync_position = _start;
353                 } else {
354                         _sync_position = other->_sync_position;
355                 }
356         } else {
357                 _sync_marked = false;
358                 _sync_position = _start;
359         }
360
361         if (Profile->get_sae()) {
362                 /* reset sync point to start if its ended up
363                    outside region bounds.
364                 */
365
366                 if (_sync_position < _start || _sync_position >= _start + _length) {
367                         _sync_marked = false;
368                         _sync_position = _start;
369                 }
370         }
371
372         assert (_type == other->data_type());
373 }
374
375 /** Create a copy of @param other but with different sources. Used by filters */
376 Region::Region (boost::shared_ptr<const Region> other, const SourceList& srcs)
377         : SessionObject (other->session(), other->name())
378         , _type (srcs.front()->type())
379         , REGION_COPY_STATE (other)
380         , _last_length (other->_last_length)
381         , _last_position (other->_last_position)
382         , _first_edit (EditChangesID)
383         , _layer (other->_layer)
384 {
385         register_properties ();
386
387         _locked = false;
388         _position_locked = false;
389
390         other->_first_edit = EditChangesName;
391
392         if (other->_extra_xml) {
393                 _extra_xml = new XMLNode (*other->_extra_xml);
394         } else {
395                 _extra_xml = 0;
396         }
397
398         use_sources (srcs);
399         assert(_sources.size() > 0);
400 }
401
402 Region::~Region ()
403 {
404         DEBUG_TRACE (DEBUG::Destruction, string_compose ("Region %1 destructor @ %2\n", _name, this));
405         drop_sources ();
406 }
407
408 void
409 Region::set_playlist (boost::weak_ptr<Playlist> wpl)
410 {
411         _playlist = wpl.lock();
412 }
413
414 bool
415 Region::set_name (const std::string& str)
416 {
417         if (_name != str) {
418                 SessionObject::set_name(str); // EMIT SIGNAL NameChanged()
419                 assert(_name == str);
420
421                 send_change (Properties::name);
422         }
423
424         return true;
425 }
426
427 void
428 Region::set_length (framecnt_t len)
429 {
430         //cerr << "Region::set_length() len = " << len << endl;
431         if (locked()) {
432                 return;
433         }
434
435         if (_length != len && len != 0) {
436
437                 /* check that the current _position wouldn't make the new
438                    length impossible.
439                 */
440
441                 if (max_framepos - len < _position) {
442                         return;
443                 }
444
445                 if (!verify_length (len)) {
446                         return;
447                 }
448
449
450                 _last_length = _length;
451                 set_length_internal (len);
452                 _whole_file = false;
453                 first_edit ();
454                 maybe_uncopy ();
455                 invalidate_transients ();
456
457                 if (!property_changes_suspended()) {
458                         recompute_at_end ();
459                 }
460
461                 send_change (Properties::length);
462         }
463 }
464
465 void
466 Region::set_length_internal (framecnt_t len)
467 {
468         _length = len;
469 }
470
471 void
472 Region::maybe_uncopy ()
473 {
474         /* this does nothing but marked a semantic moment once upon a time */
475 }
476
477 void
478 Region::first_edit ()
479 {
480         boost::shared_ptr<Playlist> pl (playlist());
481
482         if (_first_edit != EditChangesNothing && pl) {
483
484                 _name = RegionFactory::new_region_name (_name);
485                 _first_edit = EditChangesNothing;
486
487                 send_change (Properties::name);
488
489                 RegionFactory::CheckNewRegion (shared_from_this());
490         }
491 }
492
493 bool
494 Region::at_natural_position () const
495 {
496         boost::shared_ptr<Playlist> pl (playlist());
497
498         if (!pl) {
499                 return false;
500         }
501
502         boost::shared_ptr<Region> whole_file_region = get_parent();
503
504         if (whole_file_region) {
505                 if (_position == whole_file_region->position() + _start) {
506                         return true;
507                 }
508         }
509
510         return false;
511 }
512
513 void
514 Region::move_to_natural_position ()
515 {
516         boost::shared_ptr<Playlist> pl (playlist());
517
518         if (!pl) {
519                 return;
520         }
521
522         boost::shared_ptr<Region> whole_file_region = get_parent();
523
524         if (whole_file_region) {
525                 set_position (whole_file_region->position() + _start);
526         }
527 }
528
529 void
530 Region::special_set_position (framepos_t pos)
531 {
532         /* this is used when creating a whole file region as
533            a way to store its "natural" or "captured" position.
534         */
535
536         _position = _position;
537         _position = pos;
538 }
539
540 void
541 Region::set_position_lock_style (PositionLockStyle ps)
542 {
543         if (_position_lock_style != ps) {
544
545                 boost::shared_ptr<Playlist> pl (playlist());
546
547                 _position_lock_style = ps;
548
549                 if (_position_lock_style == MusicTime) {
550                         _session.tempo_map().bbt_time (_position, _bbt_time);
551                 }
552
553                 send_change (Properties::position_lock_style);
554         }
555 }
556
557 void
558 Region::update_after_tempo_map_change ()
559 {
560         boost::shared_ptr<Playlist> pl (playlist());
561
562         if (!pl || _position_lock_style != MusicTime) {
563                 return;
564         }
565
566         TempoMap& map (_session.tempo_map());
567         framepos_t pos = map.frame_time (_bbt_time);
568         set_position_internal (pos, false);
569
570         /* do this even if the position is the same. this helps out
571            a GUI that has moved its representation already.
572         */
573         send_change (Properties::position);
574 }
575
576 void
577 Region::set_position (framepos_t pos)
578 {
579         if (!can_move()) {
580                 return;
581         }
582
583         set_position_internal (pos, true);
584
585         /* do this even if the position is the same. this helps out
586            a GUI that has moved its representation already.
587         */
588         send_change (Properties::position);
589
590 }
591
592 void
593 Region::set_position_internal (framepos_t pos, bool allow_bbt_recompute)
594 {
595         /* We emit a change of Properties::position even if the position hasn't changed
596            (see Region::set_position), so we must always set this up so that
597            e.g. Playlist::notify_region_moved doesn't use an out-of-date last_position.
598         */
599         _last_position = _position;
600         
601         if (_position != pos) {
602                 _position = pos;
603
604                 /* check that the new _position wouldn't make the current
605                    length impossible - if so, change the length.
606
607                    XXX is this the right thing to do?
608                 */
609
610                 if (max_framepos - _length < _position) {
611                         _last_length = _length;
612                         _length = max_framepos - _position;
613                 }
614
615                 if (allow_bbt_recompute) {
616                         recompute_position_from_lock_style ();
617                 }
618
619                 //invalidate_transients ();
620         }
621 }
622
623 void
624 Region::recompute_position_from_lock_style ()
625 {
626         if (_position_lock_style == MusicTime) {
627                 _session.tempo_map().bbt_time (_position, _bbt_time);
628         }
629 }
630
631 void
632 Region::nudge_position (frameoffset_t n)
633 {
634         if (locked()) {
635                 return;
636         }
637
638         if (n == 0) {
639                 return;
640         }
641
642         framepos_t new_position = _position;
643
644         if (n > 0) {
645                 if (_position > max_framepos - n) {
646                         new_position = max_framepos;
647                 } else {
648                         new_position += n;
649                 }
650         } else {
651                 if (_position < -n) {
652                         new_position = 0;
653                 } else {
654                         new_position += n;
655                 }
656         }
657
658         set_position_internal (new_position, true);
659
660         send_change (Properties::position);
661 }
662
663 void
664 Region::set_ancestral_data (framepos_t s, framecnt_t l, float st, float sh)
665 {
666         _ancestral_length = l;
667         _ancestral_start = s;
668         _stretch = st;
669         _shift = sh;
670 }
671
672 void
673 Region::set_start (framepos_t pos)
674 {
675         if (locked() || position_locked()) {
676                 return;
677         }
678         /* This just sets the start, nothing else. It effectively shifts
679            the contents of the Region within the overall extent of the Source,
680            without changing the Region's position or length
681         */
682
683         if (_start != pos) {
684
685                 if (!verify_start (pos)) {
686                         return;
687                 }
688
689                 _start = pos;
690                 _whole_file = false;
691                 first_edit ();
692                 invalidate_transients ();
693
694                 send_change (Properties::start);
695         }
696 }
697
698 void
699 Region::trim_start (framepos_t new_position)
700 {
701         if (locked() || position_locked()) {
702                 return;
703         }
704         framepos_t new_start;
705         frameoffset_t const start_shift = new_position - _position;
706
707         if (start_shift > 0) {
708
709                 if (_start > max_framepos - start_shift) {
710                         new_start = max_framepos;
711                 } else {
712                         new_start = _start + start_shift;
713                 }
714
715                 if (!verify_start (new_start)) {
716                         return;
717                 }
718
719         } else if (start_shift < 0) {
720
721                 if (_start < -start_shift) {
722                         new_start = 0;
723                 } else {
724                         new_start = _start + start_shift;
725                 }
726
727         } else {
728                 return;
729         }
730
731         if (new_start == _start) {
732                 return;
733         }
734
735         _start = new_start;
736         _whole_file = false;
737         first_edit ();
738
739         send_change (Properties::start);
740 }
741
742 void
743 Region::trim_front (framepos_t new_position)
744 {
745         modify_front (new_position, false);
746 }
747
748 void
749 Region::cut_front (framepos_t new_position)
750 {
751         modify_front (new_position, true);
752 }
753
754 void
755 Region::cut_end (framepos_t new_endpoint)
756 {
757         modify_end (new_endpoint, true);
758 }
759
760 void
761 Region::modify_front (framepos_t new_position, bool reset_fade)
762 {
763         if (locked()) {
764                 return;
765         }
766
767         framepos_t end = last_frame();
768         framepos_t source_zero;
769
770         if (_position > _start) {
771                 source_zero = _position - _start;
772         } else {
773                 source_zero = 0; // its actually negative, but this will work for us
774         }
775
776         if (new_position < end) { /* can't trim it zero or negative length */
777
778                 framecnt_t newlen = 0;
779                 framepos_t delta = 0;
780
781                 if (!can_trim_start_before_source_start ()) {
782                         /* can't trim it back past where source position zero is located */
783                         new_position = max (new_position, source_zero);
784                 }
785
786                 if (new_position > _position) {
787                         newlen = _length - (new_position - _position);
788                         delta = -1 * (new_position - _position);
789                 } else {
790                         newlen = _length + (_position - new_position);
791                         delta = _position - new_position;
792                 }
793
794                 trim_to_internal (new_position, newlen);
795
796                 if (reset_fade) {
797                         _right_of_split = true;
798                 }
799
800                 if (!property_changes_suspended()) {
801                         recompute_at_start ();
802                 }
803
804                 if (_transients.size() > 0){
805                         adjust_transients(delta);
806                 }
807         }
808 }
809
810 void
811 Region::modify_end (framepos_t new_endpoint, bool reset_fade)
812 {
813         if (locked()) {
814                 return;
815         }
816
817         if (new_endpoint > _position) {
818                 trim_to_internal (_position, new_endpoint - _position);
819                 if (reset_fade) {
820                         _left_of_split = true;
821                 }
822                 if (!property_changes_suspended()) {
823                         recompute_at_end ();
824                 }
825         }
826 }
827
828 /** @param new_endpoint New region end point, such that, for example,
829  *  a region at 0 of length 10 has an endpoint of 9.
830  */
831
832 void
833 Region::trim_end (framepos_t new_endpoint)
834 {
835         modify_end (new_endpoint, false);
836 }
837
838 void
839 Region::trim_to (framepos_t position, framecnt_t length)
840 {
841         if (locked()) {
842                 return;
843         }
844
845         trim_to_internal (position, length);
846
847         if (!property_changes_suspended()) {
848                 recompute_at_start ();
849                 recompute_at_end ();
850         }
851 }
852
853 void
854 Region::trim_to_internal (framepos_t position, framecnt_t length)
855 {
856         framepos_t new_start;
857
858         if (locked()) {
859                 return;
860         }
861
862         frameoffset_t const start_shift = position - _position;
863
864         if (start_shift > 0) {
865
866                 if (_start > max_framepos - start_shift) {
867                         new_start = max_framepos;
868                 } else {
869                         new_start = _start + start_shift;
870                 }
871
872         } else if (start_shift < 0) {
873
874                 if (_start < -start_shift && !can_trim_start_before_source_start ()) {
875                         new_start = 0;
876                 } else {
877                         new_start = _start + start_shift;
878                 }
879
880         } else {
881                 new_start = _start;
882         }
883
884         if (!verify_start_and_length (new_start, length)) {
885                 return;
886         }
887
888         PropertyChange what_changed;
889
890         if (_start != new_start) {
891                 _start = new_start;
892                 what_changed.add (Properties::start);
893         }
894
895         /* Set position before length, otherwise for MIDI regions this bad thing happens:
896          * 1. we call set_length_internal; length in beats is computed using the region's current
897          *    (soon-to-be old) position
898          * 2. we call set_position_internal; position is set and length in frames re-computed using
899          *    length in beats from (1) but at the new position, which is wrong if the region
900          *    straddles a tempo/meter change.
901          */
902
903         if (_position != position) {
904                 if (!property_changes_suspended()) {
905                         _last_position = _position;
906                 }
907                 set_position_internal (position, true);
908                 what_changed.add (Properties::position);
909         }
910
911         if (_length != length) {
912                 if (!property_changes_suspended()) {
913                         _last_length = _length;
914                 }
915                 set_length_internal (length);
916                 what_changed.add (Properties::length);
917         }
918
919         _whole_file = false;
920
921         PropertyChange start_and_length;
922
923         start_and_length.add (Properties::start);
924         start_and_length.add (Properties::length);
925
926         if (what_changed.contains (start_and_length)) {
927                 first_edit ();
928         }
929
930         if (!what_changed.empty()) {
931                 send_change (what_changed);
932         }
933 }
934
935 void
936 Region::set_hidden (bool yn)
937 {
938         if (hidden() != yn) {
939                 _hidden = yn;
940                 send_change (Properties::hidden);
941         }
942 }
943
944 void
945 Region::set_whole_file (bool yn)
946 {
947         _whole_file = yn;
948         /* no change signal */
949 }
950
951 void
952 Region::set_automatic (bool yn)
953 {
954         _automatic = yn;
955         /* no change signal */
956 }
957
958 void
959 Region::set_muted (bool yn)
960 {
961         if (muted() != yn) {
962                 _muted = yn;
963                 send_change (Properties::muted);
964         }
965 }
966
967 void
968 Region::set_opaque (bool yn)
969 {
970         if (opaque() != yn) {
971                 _opaque = yn;
972                 send_change (Properties::opaque);
973         }
974 }
975
976 void
977 Region::set_locked (bool yn)
978 {
979         if (locked() != yn) {
980                 _locked = yn;
981                 send_change (Properties::locked);
982         }
983 }
984
985 void
986 Region::set_position_locked (bool yn)
987 {
988         if (position_locked() != yn) {
989                 _position_locked = yn;
990                 send_change (Properties::locked);
991         }
992 }
993
994 /** Set the region's sync point.
995  *  @param absolute_pos Session time.
996  */
997 void
998 Region::set_sync_position (framepos_t absolute_pos)
999 {
1000         /* position within our file */
1001         framepos_t const file_pos = _start + (absolute_pos - _position);
1002
1003         if (file_pos != _sync_position) {
1004                 _sync_marked = true;
1005                 _sync_position = file_pos;
1006                 if (!property_changes_suspended()) {
1007                         maybe_uncopy ();
1008                 }
1009
1010                 send_change (Properties::sync_position);
1011         }
1012 }
1013
1014 void
1015 Region::clear_sync_position ()
1016 {
1017         if (sync_marked()) {
1018                 _sync_marked = false;
1019                 if (!property_changes_suspended()) {
1020                         maybe_uncopy ();
1021                 }
1022
1023                 send_change (Properties::sync_position);
1024         }
1025 }
1026
1027 /* @return the sync point relative the first frame of the region */
1028 frameoffset_t
1029 Region::sync_offset (int& dir) const
1030 {
1031         if (sync_marked()) {
1032                 if (_sync_position > _start) {
1033                         dir = 1;
1034                         return _sync_position - _start;
1035                 } else {
1036                         dir = -1;
1037                         return _start - _sync_position;
1038                 }
1039         } else {
1040                 dir = 0;
1041                 return 0;
1042         }
1043 }
1044
1045 framepos_t
1046 Region::adjust_to_sync (framepos_t pos) const
1047 {
1048         int sync_dir;
1049         frameoffset_t offset = sync_offset (sync_dir);
1050
1051         // cerr << "adjusting pos = " << pos << " to sync at " << _sync_position << " offset = " << offset << " with dir = " << sync_dir << endl;
1052
1053         if (sync_dir > 0) {
1054                 if (pos > offset) {
1055                         pos -= offset;
1056                 } else {
1057                         pos = 0;
1058                 }
1059         } else {
1060                 if (max_framepos - pos > offset) {
1061                         pos += offset;
1062                 }
1063         }
1064
1065         return pos;
1066 }
1067
1068 /** @return Sync position in session time */
1069 framepos_t
1070 Region::sync_position() const
1071 {
1072         if (sync_marked()) {
1073                 return _position - _start + _sync_position;
1074         } else {
1075                 /* if sync has not been marked, use the start of the region */
1076                 return _position;
1077         }
1078 }
1079
1080 void
1081 Region::raise ()
1082 {
1083         boost::shared_ptr<Playlist> pl (playlist());
1084         if (pl) {
1085                 pl->raise_region (shared_from_this ());
1086         }
1087 }
1088
1089 void
1090 Region::lower ()
1091 {
1092         boost::shared_ptr<Playlist> pl (playlist());
1093         if (pl) {
1094                 pl->lower_region (shared_from_this ());
1095         }
1096 }
1097
1098
1099 void
1100 Region::raise_to_top ()
1101 {
1102         boost::shared_ptr<Playlist> pl (playlist());
1103         if (pl) {
1104                 pl->raise_region_to_top (shared_from_this());
1105         }
1106 }
1107
1108 void
1109 Region::lower_to_bottom ()
1110 {
1111         boost::shared_ptr<Playlist> pl (playlist());
1112         if (pl) {
1113                 pl->lower_region_to_bottom (shared_from_this());
1114         }
1115 }
1116
1117 void
1118 Region::set_layer (layer_t l)
1119 {
1120         _layer = l;
1121 }
1122
1123 XMLNode&
1124 Region::state ()
1125 {
1126         XMLNode *node = new XMLNode ("Region");
1127         char buf[64];
1128         char buf2[64];
1129         LocaleGuard lg (X_("POSIX"));
1130         const char* fe = NULL;
1131
1132         add_properties (*node);
1133
1134         id().print (buf, sizeof (buf));
1135         node->add_property ("id", buf);
1136         node->add_property ("type", _type.to_string());
1137
1138         switch (_first_edit) {
1139         case EditChangesNothing:
1140                 fe = X_("nothing");
1141                 break;
1142         case EditChangesName:
1143                 fe = X_("name");
1144                 break;
1145         case EditChangesID:
1146                 fe = X_("id");
1147                 break;
1148         default: /* should be unreachable but makes g++ happy */
1149                 fe = X_("nothing");
1150                 break;
1151         }
1152
1153         node->add_property ("first-edit", fe);
1154
1155         /* note: flags are stored by derived classes */
1156
1157         if (_position_lock_style != AudioTime) {
1158                 stringstream str;
1159                 str << _bbt_time;
1160                 node->add_property ("bbt-position", str.str());
1161         }
1162
1163         for (uint32_t n=0; n < _sources.size(); ++n) {
1164                 snprintf (buf2, sizeof(buf2), "source-%d", n);
1165                 _sources[n]->id().print (buf, sizeof(buf));
1166                 node->add_property (buf2, buf);
1167         }
1168
1169         for (uint32_t n=0; n < _master_sources.size(); ++n) {
1170                 snprintf (buf2, sizeof(buf2), "master-source-%d", n);
1171                 _master_sources[n]->id().print (buf, sizeof (buf));
1172                 node->add_property (buf2, buf);
1173         }
1174
1175         /* Only store nested sources for the whole-file region that acts
1176            as the parent/root of all regions using it.
1177         */
1178
1179         if (_whole_file && max_source_level() > 0) {
1180
1181                 XMLNode* nested_node = new XMLNode (X_("NestedSource"));
1182
1183                 /* region is compound - get its playlist and
1184                    store that before we list the region that
1185                    needs it ...
1186                 */
1187
1188                 for (SourceList::const_iterator s = _sources.begin(); s != _sources.end(); ++s) {
1189                         nested_node->add_child_nocopy ((*s)->get_state ());
1190                 }
1191
1192                 if (nested_node) {
1193                         node->add_child_nocopy (*nested_node);
1194                 }
1195         }
1196
1197         if (_extra_xml) {
1198                 node->add_child_copy (*_extra_xml);
1199         }
1200
1201         return *node;
1202 }
1203
1204 XMLNode&
1205 Region::get_state ()
1206 {
1207         return state ();
1208 }
1209
1210 int
1211 Region::set_state (const XMLNode& node, int version)
1212 {
1213         PropertyChange what_changed;
1214         return _set_state (node, version, what_changed, true);
1215 }
1216
1217 int
1218 Region::_set_state (const XMLNode& node, int /*version*/, PropertyChange& what_changed, bool send)
1219 {
1220         const XMLProperty* prop;
1221
1222         Stateful::save_extra_xml (node);
1223
1224         what_changed = set_values (node);
1225
1226         set_id (node);
1227
1228         if (_position_lock_style == MusicTime) {
1229                 if ((prop = node.property ("bbt-position")) == 0) {
1230                         /* missing BBT info, revert to audio time locking */
1231                         _position_lock_style = AudioTime;
1232                 } else {
1233                         if (sscanf (prop->value().c_str(), "%d|%d|%d",
1234                                     &_bbt_time.bars,
1235                                     &_bbt_time.beats,
1236                                     &_bbt_time.ticks) != 3) {
1237                                 _position_lock_style = AudioTime;
1238                         }
1239                 }
1240         }
1241
1242         /* fix problems with old sessions corrupted by impossible
1243            values for _stretch or _shift
1244         */
1245         if (_stretch == 0.0f) {
1246                 _stretch = 1.0f;
1247         }
1248
1249         if (_shift == 0.0f) {
1250                 _shift = 1.0f;
1251         }
1252
1253         if (send) {
1254                 send_change (what_changed);
1255         }
1256
1257         /* Quick fix for 2.x sessions when region is muted */
1258         if ((prop = node.property (X_("flags")))) {
1259                 if (string::npos != prop->value().find("Muted")){
1260                         set_muted (true);
1261                 }
1262         }
1263
1264
1265         return 0;
1266 }
1267
1268 void
1269 Region::suspend_property_changes ()
1270 {
1271         Stateful::suspend_property_changes ();
1272         _last_length = _length;
1273         _last_position = _position;
1274 }
1275
1276 void
1277 Region::mid_thaw (const PropertyChange& what_changed)
1278 {
1279         if (what_changed.contains (Properties::length)) {
1280                 if (what_changed.contains (Properties::position)) {
1281                         recompute_at_start ();
1282                 }
1283                 recompute_at_end ();
1284         }
1285 }
1286
1287 void
1288 Region::send_change (const PropertyChange& what_changed)
1289 {
1290         if (what_changed.empty()) {
1291                 return;
1292         }
1293
1294         Stateful::send_change (what_changed);
1295
1296         if (!Stateful::property_changes_suspended()) {
1297
1298                 /* Try and send a shared_pointer unless this is part of the constructor.
1299                    If so, do nothing.
1300                 */
1301
1302                 try {
1303                         boost::shared_ptr<Region> rptr = shared_from_this();
1304                         RegionPropertyChanged (rptr, what_changed);
1305                 } catch (...) {
1306                         /* no shared_ptr available, relax; */
1307                 }
1308         }
1309 }
1310
1311 bool
1312 Region::overlap_equivalent (boost::shared_ptr<const Region> other) const
1313 {
1314         return coverage (other->first_frame(), other->last_frame()) != OverlapNone;
1315 }
1316
1317 bool
1318 Region::equivalent (boost::shared_ptr<const Region> other) const
1319 {
1320         return _start == other->_start &&
1321                 _position == other->_position &&
1322                 _length == other->_length;
1323 }
1324
1325 bool
1326 Region::size_equivalent (boost::shared_ptr<const Region> other) const
1327 {
1328         return _start == other->_start &&
1329                 _length == other->_length;
1330 }
1331
1332 bool
1333 Region::region_list_equivalent (boost::shared_ptr<const Region> other) const
1334 {
1335         return size_equivalent (other) && source_equivalent (other) && _name == other->_name;
1336 }
1337
1338 void
1339 Region::source_deleted (boost::weak_ptr<Source>)
1340 {
1341         drop_sources ();
1342
1343         if (!_session.deletion_in_progress()) {
1344                 /* this is a very special case: at least one of the region's
1345                    sources has bee deleted, so invalidate all references to
1346                    ourselves. Do NOT do this during session deletion, because
1347                    then we run the risk that this will actually result
1348                    in this object being deleted (as refcnt goes to zero)
1349                    while emitting DropReferences.
1350                 */
1351
1352                 drop_references ();
1353         }
1354 }
1355
1356 vector<string>
1357 Region::master_source_names ()
1358 {
1359         SourceList::iterator i;
1360
1361         vector<string> names;
1362         for (i = _master_sources.begin(); i != _master_sources.end(); ++i) {
1363                 names.push_back((*i)->name());
1364         }
1365
1366         return names;
1367 }
1368
1369 void
1370 Region::set_master_sources (const SourceList& srcs)
1371 {
1372         for (SourceList::const_iterator i = _master_sources.begin (); i != _master_sources.end(); ++i) {
1373                 (*i)->dec_use_count ();
1374         }
1375
1376         _master_sources = srcs;
1377         assert (_sources.size() == _master_sources.size());
1378
1379         for (SourceList::const_iterator i = _master_sources.begin (); i != _master_sources.end(); ++i) {
1380                 (*i)->inc_use_count ();
1381         }
1382 }
1383
1384 bool
1385 Region::source_equivalent (boost::shared_ptr<const Region> other) const
1386 {
1387         if (!other)
1388                 return false;
1389
1390         if ((_sources.size() != other->_sources.size()) ||
1391             (_master_sources.size() != other->_master_sources.size())) {
1392                 return false;
1393         }
1394
1395         SourceList::const_iterator i;
1396         SourceList::const_iterator io;
1397
1398         for (i = _sources.begin(), io = other->_sources.begin(); i != _sources.end() && io != other->_sources.end(); ++i, ++io) {
1399                 if ((*i)->id() != (*io)->id()) {
1400                         return false;
1401                 }
1402         }
1403
1404         for (i = _master_sources.begin(), io = other->_master_sources.begin(); i != _master_sources.end() && io != other->_master_sources.end(); ++i, ++io) {
1405                 if ((*i)->id() != (*io)->id()) {
1406                         return false;
1407                 }
1408         }
1409
1410         return true;
1411 }
1412
1413 std::string
1414 Region::source_string () const
1415 {
1416         //string res = itos(_sources.size());
1417
1418         stringstream res;
1419         res << _sources.size() << ":";
1420
1421         SourceList::const_iterator i;
1422
1423         for (i = _sources.begin(); i != _sources.end(); ++i) {
1424                 res << (*i)->id() << ":";
1425         }
1426
1427         for (i = _master_sources.begin(); i != _master_sources.end(); ++i) {
1428                 res << (*i)->id() << ":";
1429         }
1430
1431         return res.str();
1432 }
1433
1434 bool
1435 Region::uses_source (boost::shared_ptr<const Source> source) const
1436 {
1437         for (SourceList::const_iterator i = _sources.begin(); i != _sources.end(); ++i) {
1438                 if (*i == source) {
1439                         return true;
1440                 }
1441
1442                 boost::shared_ptr<PlaylistSource> ps = boost::dynamic_pointer_cast<PlaylistSource> (*i);
1443
1444                 if (ps) {
1445                         if (ps->playlist()->uses_source (source)) {
1446                                 return true;
1447                         }
1448                 }
1449         }
1450
1451         return false;
1452 }
1453
1454 framecnt_t
1455 Region::source_length(uint32_t n) const
1456 {
1457         assert (n < _sources.size());
1458         return _sources[n]->length (_position - _start);
1459 }
1460
1461 bool
1462 Region::verify_length (framecnt_t len)
1463 {
1464         if (source() && (source()->destructive() || source()->length_mutable())) {
1465                 return true;
1466         }
1467
1468         framecnt_t maxlen = 0;
1469
1470         for (uint32_t n = 0; n < _sources.size(); ++n) {
1471                 maxlen = max (maxlen, source_length(n) - _start);
1472         }
1473
1474         len = min (len, maxlen);
1475
1476         return true;
1477 }
1478
1479 bool
1480 Region::verify_start_and_length (framepos_t new_start, framecnt_t& new_length)
1481 {
1482         if (source() && (source()->destructive() || source()->length_mutable())) {
1483                 return true;
1484         }
1485
1486         framecnt_t maxlen = 0;
1487
1488         for (uint32_t n = 0; n < _sources.size(); ++n) {
1489                 maxlen = max (maxlen, source_length(n) - new_start);
1490         }
1491
1492         new_length = min (new_length, maxlen);
1493
1494         return true;
1495 }
1496
1497 bool
1498 Region::verify_start (framepos_t pos)
1499 {
1500         if (source() && (source()->destructive() || source()->length_mutable())) {
1501                 return true;
1502         }
1503
1504         for (uint32_t n = 0; n < _sources.size(); ++n) {
1505                 if (pos > source_length(n) - _length) {
1506                         return false;
1507                 }
1508         }
1509         return true;
1510 }
1511
1512 bool
1513 Region::verify_start_mutable (framepos_t& new_start)
1514 {
1515         if (source() && (source()->destructive() || source()->length_mutable())) {
1516                 return true;
1517         }
1518
1519         for (uint32_t n = 0; n < _sources.size(); ++n) {
1520                 if (new_start > source_length(n) - _length) {
1521                         new_start = source_length(n) - _length;
1522                 }
1523         }
1524         return true;
1525 }
1526
1527 boost::shared_ptr<Region>
1528 Region::get_parent() const
1529 {
1530         boost::shared_ptr<Playlist> pl (playlist());
1531
1532         if (pl) {
1533                 boost::shared_ptr<Region> r;
1534                 boost::shared_ptr<Region const> grrr2 = boost::dynamic_pointer_cast<Region const> (shared_from_this());
1535
1536                 if (grrr2 && (r = _session.find_whole_file_parent (grrr2))) {
1537                         return boost::static_pointer_cast<Region> (r);
1538                 }
1539         }
1540
1541         return boost::shared_ptr<Region>();
1542 }
1543
1544 int
1545 Region::apply (Filter& filter, Progress* progress)
1546 {
1547         return filter.run (shared_from_this(), progress);
1548 }
1549
1550
1551 void
1552 Region::invalidate_transients ()
1553 {
1554         _valid_transients = false;
1555         _transients.clear ();
1556
1557         send_change (PropertyChange (Properties::valid_transients));
1558 }
1559
1560 void
1561 Region::drop_sources ()
1562 {
1563         for (SourceList::const_iterator i = _sources.begin (); i != _sources.end(); ++i) {
1564                 (*i)->dec_use_count ();
1565         }
1566
1567         _sources.clear ();
1568
1569         for (SourceList::const_iterator i = _master_sources.begin (); i != _master_sources.end(); ++i) {
1570                 (*i)->dec_use_count ();
1571         }
1572
1573         _master_sources.clear ();
1574 }
1575
1576 void
1577 Region::use_sources (SourceList const & s)
1578 {
1579         set<boost::shared_ptr<Source> > unique_srcs;
1580
1581         for (SourceList::const_iterator i = s.begin (); i != s.end(); ++i) {
1582
1583                 _sources.push_back (*i);
1584                 (*i)->inc_use_count ();
1585                 _master_sources.push_back (*i);
1586                 (*i)->inc_use_count ();
1587
1588                 /* connect only once to DropReferences, even if sources are replicated
1589                  */
1590
1591                 if (unique_srcs.find (*i) == unique_srcs.end ()) {
1592                         unique_srcs.insert (*i);
1593                         (*i)->DropReferences.connect_same_thread (*this, boost::bind (&Region::source_deleted, this, boost::weak_ptr<Source>(*i)));
1594                 }
1595         }
1596 }
1597
1598 Trimmable::CanTrim
1599 Region::can_trim () const
1600 {
1601         CanTrim ct = CanTrim (0);
1602
1603         if (locked()) {
1604                 return ct;
1605         }
1606
1607         /* if not locked, we can always move the front later, and the end earlier
1608          */
1609
1610         ct = CanTrim (ct | FrontTrimLater | EndTrimEarlier);
1611
1612         if (start() != 0 || can_trim_start_before_source_start ()) {
1613                 ct = CanTrim (ct | FrontTrimEarlier);
1614         }
1615
1616         if (!_sources.empty()) {
1617                 if ((start() + length()) < _sources.front()->length (0)) {
1618                         ct = CanTrim (ct | EndTrimLater);
1619                 }
1620         }
1621
1622         return ct;
1623 }
1624
1625 uint32_t
1626 Region::max_source_level () const
1627 {
1628         uint32_t lvl = 0;
1629
1630         for (SourceList::const_iterator i = _sources.begin(); i != _sources.end(); ++i) {
1631                 lvl = max (lvl, (*i)->level());
1632         }
1633
1634         return lvl;
1635 }
1636
1637 bool
1638 Region::is_compound () const
1639 {
1640         return max_source_level() > 0;
1641 }
1642
1643 void
1644 Region::post_set (const PropertyChange& pc)
1645 {
1646         if (pc.contains (Properties::position)) {
1647                 recompute_position_from_lock_style ();
1648         }
1649 }
1650