Add new SharedStatefulProperty which manages a shared_ptr to
authorCarl Hetherington <carl@carlh.net>
Sat, 16 Jun 2012 17:20:10 +0000 (17:20 +0000)
committerCarl Hetherington <carl@carlh.net>
Sat, 16 Jun 2012 17:20:10 +0000 (17:20 +0000)
some Stateful object, and a subclass to use this for
AutomationList.  SharedStatefulProperty will manage undo / redo
using full copies of the XML state, like MementoCommand,
but does it within the Property undo system.

git-svn-id: svn://localhost/ardour2/branches/3.0@12740 d708f5d6-7413-0410-9779-e7cbd77b26cf

18 files changed:
libs/ardour/ardour/audioregion.h
libs/ardour/ardour/automation_list.h
libs/ardour/audioregion.cc
libs/ardour/automation_list.cc
libs/ardour/globals.cc
libs/ardour/test/automation_list_property_test.cc [new file with mode: 0644]
libs/ardour/test/automation_list_property_test.h [new file with mode: 0644]
libs/ardour/test/data/automation_list_property_test1.ref [new file with mode: 0644]
libs/ardour/test/data/automation_list_property_test2.ref [new file with mode: 0644]
libs/ardour/test/data/automation_list_property_test3.ref [new file with mode: 0644]
libs/ardour/test/data/automation_list_property_test4.ref [new file with mode: 0644]
libs/ardour/test/test_util.cc [new file with mode: 0644]
libs/ardour/test/test_util.h [new file with mode: 0644]
libs/ardour/wscript
libs/evoral/evoral/ControlList.hpp
libs/evoral/src/ControlList.cpp
libs/pbd/pbd/properties.h
libs/pbd/xml++.cc

index 5fdc0ac04174768cf24ed502c9f999cab3bda312..e38c98ea05fbf559328cc6dae1e90a79bf4fc337 100644 (file)
@@ -46,16 +46,11 @@ namespace Properties {
        extern PBD::PropertyDescriptor<bool> fade_in_active;
        extern PBD::PropertyDescriptor<bool> fade_out_active;
        extern PBD::PropertyDescriptor<float> scale_amplitude;
-
-       /* the envelope and fades are not scalar items and so
-          currently (2010/02) are not stored using Property.
-          However, these descriptors enable us to notify
-          about changes to them via PropertyChange.
-       */
-
-       extern PBD::PropertyDescriptor<bool> envelope;
-       extern PBD::PropertyDescriptor<bool> fade_in;
-       extern PBD::PropertyDescriptor<bool> fade_out;
+       extern PBD::PropertyDescriptor<boost::shared_ptr<AutomationList> > fade_in;
+       extern PBD::PropertyDescriptor<boost::shared_ptr<AutomationList> > inverse_fade_in;
+       extern PBD::PropertyDescriptor<boost::shared_ptr<AutomationList> > fade_out;
+       extern PBD::PropertyDescriptor<boost::shared_ptr<AutomationList> > inverse_fade_out;
+       extern PBD::PropertyDescriptor<boost::shared_ptr<AutomationList> > envelope;
 }
 
 class Playlist;
@@ -99,11 +94,11 @@ class AudioRegion : public Region
        bool fade_out_is_short() const { return _fade_out_is_short; }
        void set_fade_out_is_short (bool yn);
 
-       boost::shared_ptr<AutomationList> fade_in()  { return _fade_in; }
-       boost::shared_ptr<AutomationList> inverse_fade_in()  { return _inverse_fade_in; }
-       boost::shared_ptr<AutomationList> fade_out() { return _fade_out; }
-       boost::shared_ptr<AutomationList> inverse_fade_out()  { return _inverse_fade_out; }
-       boost::shared_ptr<AutomationList> envelope() { return _envelope; }
+       boost::shared_ptr<AutomationList> fade_in()  { return _fade_in.val (); }
+       boost::shared_ptr<AutomationList> inverse_fade_in()  { return _inverse_fade_in.val (); }
+       boost::shared_ptr<AutomationList> fade_out() { return _fade_out.val (); }
+       boost::shared_ptr<AutomationList> inverse_fade_out()  { return _inverse_fade_out.val (); }
+       boost::shared_ptr<AutomationList> envelope() { return _envelope.val (); }
 
        Evoral::Range<framepos_t> body_range () const;
 
@@ -231,15 +226,15 @@ class AudioRegion : public Region
        void connect_to_analysis_changed ();
        void connect_to_header_position_offset_changed ();
 
-       Automatable _automatable;
 
-       boost::shared_ptr<AutomationList> _fade_in;
-       boost::shared_ptr<AutomationList> _inverse_fade_in;
-       boost::shared_ptr<AutomationList> _fade_out;
-       boost::shared_ptr<AutomationList> _inverse_fade_out;
-       boost::shared_ptr<AutomationList> _envelope;
-       uint32_t                          _fade_in_suspended;
-       uint32_t                          _fade_out_suspended;
+       AutomationListProperty _fade_in;
+       AutomationListProperty _inverse_fade_in;
+       AutomationListProperty _fade_out;
+       AutomationListProperty _inverse_fade_out;
+       AutomationListProperty _envelope;
+       Automatable            _automatable;
+       uint32_t               _fade_in_suspended;
+       uint32_t               _fade_out_suspended;
 
        boost::shared_ptr<ARDOUR::Region> get_single_other_xfade_region (bool start) const;
 
index 58d1fe4acf92352634e6d36e76d284e66125b7a9..01c9d0641ac2bd1f41886d1a047b6d2c360aa1c5 100644 (file)
@@ -29,6 +29,7 @@
 #include "pbd/undo.h"
 #include "pbd/xml++.h"
 #include "pbd/statefuldestructible.h"
+#include "pbd/properties.h"
 
 #include "ardour/ardour.h"
 
 
 namespace ARDOUR {
 
+class AutomationList;
+
+/** A SharedStatefulProperty for AutomationLists */
+class AutomationListProperty : public PBD::SharedStatefulProperty<AutomationList>
+{
+public:
+       AutomationListProperty (PBD::PropertyDescriptor<boost::shared_ptr<AutomationList> > d, Ptr p)
+               : PBD::SharedStatefulProperty<AutomationList> (d.property_id, p)
+       {}
+
+       AutomationListProperty (PBD::PropertyDescriptor<boost::shared_ptr<AutomationList> > d, Ptr o, Ptr c)
+               : PBD::SharedStatefulProperty<AutomationList> (d.property_id, o, c)
+       {}
+       
+       PBD::PropertyBase* clone () const;
+       
+private:
+       /* No copy-construction nor assignment */
+       AutomationListProperty (AutomationListProperty const &);
+       AutomationListProperty& operator= (AutomationListProperty const &);
+};
+
 class AutomationList : public PBD::StatefulDestructible, public Evoral::ControlList
 {
   public:
@@ -82,6 +105,8 @@ class AutomationList : public PBD::StatefulDestructible, public Evoral::ControlL
        XMLNode& state (bool full);
        XMLNode& serialize_events ();
 
+       bool operator!= (const AutomationList &) const;
+
   private:
        void create_curve_if_necessary ();
        int deserialize_events (const XMLNode&);
index 50a38b7bad4c577822be2a42d91a46fad95466ce..4b85819d6e322030a93d1b6a4e7c082af0b1595f 100644 (file)
@@ -65,6 +65,11 @@ namespace ARDOUR {
                PBD::PropertyDescriptor<bool> fade_out_is_short;
                PBD::PropertyDescriptor<bool> fade_in_is_xfade;
                PBD::PropertyDescriptor<bool> fade_in_is_short;
+               PBD::PropertyDescriptor<boost::shared_ptr<AutomationList> > fade_in;
+               PBD::PropertyDescriptor<boost::shared_ptr<AutomationList> > inverse_fade_in;
+               PBD::PropertyDescriptor<boost::shared_ptr<AutomationList> > fade_out;
+               PBD::PropertyDescriptor<boost::shared_ptr<AutomationList> > inverse_fade_out;
+               PBD::PropertyDescriptor<boost::shared_ptr<AutomationList> > envelope;
        }
 }
 
@@ -164,6 +169,16 @@ AudioRegion::make_property_quarks ()
        DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for fade-in-is-xfade = %1\n",    Properties::fade_in_is_xfade.property_id));
        Properties::fade_in_is_short.property_id = g_quark_from_static_string (X_("fade-in-is-short"));
        DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for fade-in-is-short = %1\n",    Properties::fade_in_is_short.property_id));
+       Properties::fade_in.property_id = g_quark_from_static_string (X_("FadeIn"));
+       DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for FadeIn = %1\n",              Properties::fade_in.property_id));
+       Properties::inverse_fade_in.property_id = g_quark_from_static_string (X_("InverseFadeIn"));
+       DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for InverseFadeIn = %1\n",       Properties::inverse_fade_in.property_id));
+       Properties::fade_out.property_id = g_quark_from_static_string (X_("FadeOut"));
+       DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for FadeOut = %1\n",             Properties::fade_out.property_id));
+       Properties::inverse_fade_out.property_id = g_quark_from_static_string (X_("InverseFadeOut"));
+       DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for InverseFadeOut = %1\n",      Properties::inverse_fade_out.property_id));
+       Properties::envelope.property_id = g_quark_from_static_string (X_("Envelope"));
+       DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for Envelope = %1\n",            Properties::envelope.property_id));
 }
 
 void
@@ -181,6 +196,11 @@ AudioRegion::register_properties ()
        add_property (_fade_out_is_short);
        add_property (_fade_in_is_xfade);
        add_property (_fade_in_is_short);
+       add_property (_fade_in);
+       add_property (_inverse_fade_in);
+       add_property (_fade_out);
+       add_property (_inverse_fade_out);
+       add_property (_envelope);
 }
 
 #define AUDIOREGION_STATE_DEFAULT \
@@ -193,7 +213,11 @@ AudioRegion::register_properties ()
        , _fade_in_is_xfade (Properties::fade_in_is_xfade, false) \
        , _fade_out_is_xfade (Properties::fade_out_is_xfade, false) \
        , _fade_in_is_short (Properties::fade_in_is_short, false) \
-       , _fade_out_is_short (Properties::fade_out_is_short, false) 
+       , _fade_out_is_short (Properties::fade_out_is_short, false) \
+       , _fade_in (Properties::fade_in, boost::shared_ptr<AutomationList> (new AutomationList (Evoral::Parameter (FadeInAutomation)))) \
+       , _inverse_fade_in (Properties::inverse_fade_in, boost::shared_ptr<AutomationList> (new AutomationList (Evoral::Parameter (FadeInAutomation)))) \
+       , _fade_out (Properties::fade_out, boost::shared_ptr<AutomationList> (new AutomationList (Evoral::Parameter (FadeOutAutomation)))) \
+       , _inverse_fade_out (Properties::inverse_fade_out, boost::shared_ptr<AutomationList> (new AutomationList (Evoral::Parameter (FadeOutAutomation))))
 
 #define AUDIOREGION_COPY_STATE(other) \
        _envelope_active (Properties::envelope_active, other->_envelope_active) \
@@ -205,7 +229,11 @@ AudioRegion::register_properties ()
        , _fade_in_is_xfade (Properties::fade_in_is_xfade, other->_fade_in_is_xfade) \
        , _fade_out_is_xfade (Properties::fade_out_is_xfade, other->_fade_out_is_xfade) \
        , _fade_in_is_short (Properties::fade_in_is_short, other->_fade_in_is_short) \
-       , _fade_out_is_short (Properties::fade_out_is_short, other->_fade_out_is_short)
+       , _fade_out_is_short (Properties::fade_out_is_short, other->_fade_out_is_short) \
+       , _fade_in (Properties::fade_in, boost::shared_ptr<AutomationList> (new AutomationList (*other->_fade_in.val()))) \
+       , _inverse_fade_in (Properties::fade_in, boost::shared_ptr<AutomationList> (new AutomationList (*other->_inverse_fade_in.val()))) \
+       , _fade_out (Properties::fade_in, boost::shared_ptr<AutomationList> (new AutomationList (*other->_fade_out.val()))) \
+       , _inverse_fade_out (Properties::fade_in, boost::shared_ptr<AutomationList> (new AutomationList (*other->_inverse_fade_out.val())))
 /* a Session will reset these to its chosen defaults by calling AudioRegion::set_default_fade() */
 
 void
@@ -227,12 +255,8 @@ AudioRegion::init ()
 AudioRegion::AudioRegion (Session& s, framepos_t start, framecnt_t len, std::string name)
        : Region (s, start, len, name, DataType::AUDIO)
        , AUDIOREGION_STATE_DEFAULT
+       , _envelope (Properties::envelope, boost::shared_ptr<AutomationList> (new AutomationList (Evoral::Parameter(EnvelopeAutomation))))
        , _automatable (s)
-       , _fade_in (new AutomationList(Evoral::Parameter(FadeInAutomation)))
-       , _inverse_fade_in (new AutomationList(Evoral::Parameter(FadeInAutomation)))
-       , _fade_out (new AutomationList(Evoral::Parameter(FadeOutAutomation)))
-       , _inverse_fade_out (new AutomationList(Evoral::Parameter(FadeOutAutomation)))
-       , _envelope (new AutomationList(Evoral::Parameter(EnvelopeAutomation)))
        , _fade_in_suspended (0)
        , _fade_out_suspended (0)
 {
@@ -244,12 +268,8 @@ AudioRegion::AudioRegion (Session& s, framepos_t start, framecnt_t len, std::str
 AudioRegion::AudioRegion (const SourceList& srcs)
        : Region (srcs)
        , AUDIOREGION_STATE_DEFAULT
+       , _envelope (Properties::envelope, boost::shared_ptr<AutomationList> (new AutomationList (Evoral::Parameter(EnvelopeAutomation))))
        , _automatable(srcs[0]->session())
-       , _fade_in (new AutomationList(Evoral::Parameter(FadeInAutomation)))
-       , _inverse_fade_in (new AutomationList(Evoral::Parameter(FadeInAutomation)))
-       , _fade_out (new AutomationList(Evoral::Parameter(FadeOutAutomation)))
-       , _inverse_fade_out (new AutomationList(Evoral::Parameter(FadeOutAutomation)))
-       , _envelope (new AutomationList(Evoral::Parameter(EnvelopeAutomation)))
        , _fade_in_suspended (0)
        , _fade_out_suspended (0)
 {
@@ -260,15 +280,11 @@ AudioRegion::AudioRegion (const SourceList& srcs)
 AudioRegion::AudioRegion (boost::shared_ptr<const AudioRegion> other)
        : Region (other)
        , AUDIOREGION_COPY_STATE (other)
-       , _automatable (other->session())
-       , _fade_in (new AutomationList (*other->_fade_in))
-       , _inverse_fade_in (new AutomationList(*other->_inverse_fade_in))
-       , _fade_out (new AutomationList (*other->_fade_out))
-       , _inverse_fade_out (new AutomationList (*other->_inverse_fade_out))
          /* As far as I can see, the _envelope's times are relative to region position, and have nothing
             to do with sources (and hence _start).  So when we copy the envelope, we just use the supplied offset.
          */
-       , _envelope (new AutomationList (*other->_envelope, 0, other->_length))
+       , _envelope (Properties::envelope, boost::shared_ptr<AutomationList> (new AutomationList (*other->_envelope.val(), 0, other->_length)))
+       , _automatable (other->session())
        , _fade_in_suspended (0)
        , _fade_out_suspended (0)
 {
@@ -286,15 +302,11 @@ AudioRegion::AudioRegion (boost::shared_ptr<const AudioRegion> other)
 AudioRegion::AudioRegion (boost::shared_ptr<const AudioRegion> other, framecnt_t offset)
        : Region (other, offset)
        , AUDIOREGION_COPY_STATE (other)
-       , _automatable (other->session())
-       , _fade_in (new AutomationList (*other->_fade_in))
-       , _inverse_fade_in (new AutomationList(*other->_inverse_fade_in))
-       , _fade_out (new AutomationList (*other->_fade_out))
-       , _inverse_fade_out (new AutomationList (*other->_inverse_fade_out))
          /* As far as I can see, the _envelope's times are relative to region position, and have nothing
             to do with sources (and hence _start).  So when we copy the envelope, we just use the supplied offset.
          */
-       , _envelope (new AutomationList (*other->_envelope, offset, other->_length))
+       , _envelope (Properties::envelope, boost::shared_ptr<AutomationList> (new AutomationList (*other->_envelope.val(), offset, other->_length)))
+       , _automatable (other->session())
        , _fade_in_suspended (0)
        , _fade_out_suspended (0)
 {
@@ -312,12 +324,8 @@ AudioRegion::AudioRegion (boost::shared_ptr<const AudioRegion> other, framecnt_t
 AudioRegion::AudioRegion (boost::shared_ptr<const AudioRegion> other, const SourceList& srcs)
        : Region (boost::static_pointer_cast<const Region>(other), srcs)
        , AUDIOREGION_COPY_STATE (other)
+       , _envelope (Properties::envelope, boost::shared_ptr<AutomationList> (new AutomationList (*other->_envelope.val())))
        , _automatable (other->session())
-       , _fade_in (new AutomationList (*other->_fade_in))
-       , _inverse_fade_in (new AutomationList(*other->_inverse_fade_in))
-       , _fade_out (new AutomationList (*other->_fade_out))
-       , _inverse_fade_out (new AutomationList (*other->_inverse_fade_out))
-       , _envelope (new AutomationList (*other->_envelope))
        , _fade_in_suspended (0)
        , _fade_out_suspended (0)
 {
@@ -335,12 +343,8 @@ AudioRegion::AudioRegion (boost::shared_ptr<const AudioRegion> other, const Sour
 AudioRegion::AudioRegion (SourceList& srcs)
        : Region (srcs)
        , AUDIOREGION_STATE_DEFAULT
+       , _envelope (Properties::envelope, boost::shared_ptr<AutomationList> (new AutomationList(Evoral::Parameter(EnvelopeAutomation))))
        , _automatable(srcs[0]->session())
-       , _fade_in (new AutomationList(Evoral::Parameter(FadeInAutomation)))
-       , _inverse_fade_in (new AutomationList(Evoral::Parameter(FadeInAutomation)))
-       , _fade_out (new AutomationList(Evoral::Parameter(FadeOutAutomation)))
-       , _inverse_fade_out (new AutomationList(Evoral::Parameter(FadeOutAutomation)))
-       , _envelope (new AutomationList(Evoral::Parameter(EnvelopeAutomation)))
        , _fade_in_suspended (0)
        , _fade_out_suspended (0)
 {
@@ -988,7 +992,7 @@ void
 AudioRegion::set_fade_in (boost::shared_ptr<AutomationList> f)
 {
        _fade_in->freeze ();
-       *_fade_in = *f;
+       *(_fade_in.val()) = *f;
        _fade_in->thaw ();
        _default_fade_in = false;
 
@@ -1010,23 +1014,23 @@ AudioRegion::set_fade_in (FadeShape shape, framecnt_t len)
        case FadeLinear:
                _fade_in->fast_simple_add (0.0, 0.0);
                _fade_in->fast_simple_add (len, 1.0);
-               reverse_curve (_inverse_fade_in, _fade_in);
+               reverse_curve (_inverse_fade_in.val(), _fade_in.val());
                break;
 
        case FadeFast:
-               generate_db_fade (_fade_in, len, 10, -60);
-               reverse_curve (c1, _fade_in);
+               generate_db_fade (_fade_in.val(), len, 10, -60);
+               reverse_curve (c1, _fade_in.val());
                _fade_in->copy_events (*c1);
-               generate_inverse_power_curve (_inverse_fade_in, _fade_in);
+               generate_inverse_power_curve (_inverse_fade_in.val(), _fade_in.val());
                break;
 
        case FadeSlow:
                generate_db_fade (c1, len, 10, -1);  // start off with a slow fade
                generate_db_fade (c2, len, 10, -80); // end with a fast fade
-               merge_curves (_fade_in, c1, c2);
-               reverse_curve (c3, _fade_in);
+               merge_curves (_fade_in.val(), c1, c2);
+               reverse_curve (c3, _fade_in.val());
                _fade_in->copy_events (*c3);
-               generate_inverse_power_curve (_inverse_fade_in, _fade_in);
+               generate_inverse_power_curve (_inverse_fade_in.val(), _fade_in.val());
                break;
 
        case FadeConstantPower:
@@ -1035,7 +1039,7 @@ AudioRegion::set_fade_in (FadeShape shape, framecnt_t len)
                        _fade_in->fast_simple_add (len*dist, sin (dist*M_PI/2));
                }
                _fade_in->fast_simple_add (len, 1.0);
-               reverse_curve (_inverse_fade_in, _fade_in);
+               reverse_curve (_inverse_fade_in.val(), _fade_in.val());
                break;
                
        case FadeSymmetric:
@@ -1053,9 +1057,9 @@ AudioRegion::set_fade_in (FadeShape shape, framecnt_t len)
                        _fade_in->fast_simple_add (len* (breakpoint+((1.0-breakpoint)*(double)i/(double)num_steps)), coeff);
                }
                _fade_in->fast_simple_add (len, VERY_SMALL_SIGNAL);
-               reverse_curve (c3, _fade_in);
+               reverse_curve (c3, _fade_in.val());
                _fade_in->copy_events (*c3);
-               reverse_curve (_inverse_fade_in, _fade_in );
+               reverse_curve (_inverse_fade_in.val(), _fade_in.val());
                break;
        }
 
@@ -1068,7 +1072,7 @@ void
 AudioRegion::set_fade_out (boost::shared_ptr<AutomationList> f)
 {
        _fade_out->freeze ();
-       *_fade_out = *f;
+       *(_fade_out.val()) = *f;
        _fade_out->thaw ();
        _default_fade_out = false;
 
@@ -1089,19 +1093,19 @@ AudioRegion::set_fade_out (FadeShape shape, framecnt_t len)
        case FadeLinear:
                _fade_out->fast_simple_add (0.0, 1.0);
                _fade_out->fast_simple_add (len, VERY_SMALL_SIGNAL);
-               reverse_curve (_inverse_fade_out, _fade_out);
+               reverse_curve (_inverse_fade_out.val(), _fade_out.val());
                break;
                
        case FadeFast: 
-               generate_db_fade (_fade_out, len, 10, -60);
-               generate_inverse_power_curve (_inverse_fade_out, _fade_out);
+               generate_db_fade (_fade_out.val(), len, 10, -60);
+               generate_inverse_power_curve (_inverse_fade_out.val(), _fade_out.val());
                break;
                
        case FadeSlow: 
                generate_db_fade (c1, len, 10, -1);  //start off with a slow fade
                generate_db_fade (c2, len, 10, -80);  //end with a fast fade
-               merge_curves (_fade_out, c1, c2);
-               generate_inverse_power_curve (_inverse_fade_out, _fade_out);
+               merge_curves (_fade_out.val(), c1, c2);
+               generate_inverse_power_curve (_inverse_fade_out.val(), _fade_out.val());
                break;
 
        case FadeConstantPower:
@@ -1113,7 +1117,7 @@ AudioRegion::set_fade_out (FadeShape shape, framecnt_t len)
                        _fade_out->fast_simple_add ((len * dist), cos(dist*M_PI/2));
                }
                _fade_out->fast_simple_add (len, VERY_SMALL_SIGNAL);
-               reverse_curve (_inverse_fade_out, _fade_out);
+               reverse_curve (_inverse_fade_out.val(), _fade_out.val());
                break;
                
        case FadeSymmetric:
@@ -1132,7 +1136,7 @@ AudioRegion::set_fade_out (FadeShape shape, framecnt_t len)
                        _fade_out->fast_simple_add (len* (breakpoint+((1.0-breakpoint)*(double)i/(double)num_steps)), coeff);
                }
                _fade_out->fast_simple_add (len, VERY_SMALL_SIGNAL);
-               reverse_curve (_inverse_fade_out, _fade_out);
+               reverse_curve (_inverse_fade_out.val(), _fade_out.val());
                break;
        }
 
index 54d838f6e286c87b72d3cf4e632f2ab4cb6e43f6..26ca7b097a06bffe4d89163f8ea74f8eabff6e4a 100644 (file)
@@ -510,3 +510,24 @@ AutomationList::set_state (const XMLNode& node, int version)
        return 0;
 }
 
+bool
+AutomationList::operator!= (AutomationList const & other) const
+{
+       return (
+               static_cast<ControlList const &> (*this) != static_cast<ControlList const &> (other) ||
+               _state != other._state ||
+               _style != other._style ||
+               _touching != other._touching
+               );
+}
+
+PBD::PropertyBase *
+AutomationListProperty::clone () const
+{
+       return new AutomationListProperty (
+               this->property_id(),
+               boost::shared_ptr<AutomationList> (new AutomationList (*this->_old.get())),
+               boost::shared_ptr<AutomationList> (new AutomationList (*this->_current.get()))
+               );
+}
+       
index 35aef9dc868e7481b6f6f71306790147631cf799..1c6b98155c93b3945023707a151d139ae35145bd 100644 (file)
@@ -115,34 +115,6 @@ extern void setup_enum_writer ();
 */
 PBD::PropertyChange ARDOUR::bounds_change;
 
-namespace ARDOUR {
-       namespace Properties {
-
-               /* the envelope and fades are not scalar items and so
-                  currently (2010/02) are not stored using Property.
-                  However, these descriptors enable us to notify
-                  about changes to them via PropertyChange.
-
-                  Declared in ardour/audioregion.h ...
-               */
-
-               PBD::PropertyDescriptor<bool> fade_in;
-               PBD::PropertyDescriptor<bool> fade_out;
-               PBD::PropertyDescriptor<bool> envelope;
-       }
-}
-
-void
-ARDOUR::make_property_quarks ()
-{
-       Properties::fade_in.property_id = g_quark_from_static_string (X_("fade_in_FAKE"));
-        DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for fade_in_FAKE = %1\n",       Properties::fade_in.property_id));
-       Properties::fade_out.property_id = g_quark_from_static_string (X_("fade_out_FAKE"));
-        DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for fade_out_FAKE = %1\n",      Properties::fade_out.property_id));
-       Properties::envelope.property_id = g_quark_from_static_string (X_("envelope_FAKE"));
-        DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for envelope_FAKE = %1\n",      Properties::envelope.property_id));
-}
-
 void
 setup_hardware_optimization (bool try_optimization)
 {
@@ -248,7 +220,6 @@ ARDOUR::init (bool use_windows_vst, bool try_optimization)
        PBD::ID::init ();
        SessionEvent::init_event_pool ();
 
-       make_property_quarks ();
        SessionObject::make_property_quarks ();
        Region::make_property_quarks ();
        MidiRegion::make_property_quarks ();
diff --git a/libs/ardour/test/automation_list_property_test.cc b/libs/ardour/test/automation_list_property_test.cc
new file mode 100644 (file)
index 0000000..9d1f448
--- /dev/null
@@ -0,0 +1,123 @@
+/*
+    Copyright (C) 2012 Paul Davis
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include "pbd/properties.h"
+#include "pbd/stateful_diff_command.h"
+#include "ardour/automation_list.h"
+#include "automation_list_property_test.h"
+#include "test_util.h"
+
+CPPUNIT_TEST_SUITE_REGISTRATION (AutomationListPropertyTest);
+
+using namespace std;
+using namespace PBD;
+using namespace ARDOUR;
+
+void
+AutomationListPropertyTest::basicTest ()
+{
+       PropertyDescriptor<boost::shared_ptr<AutomationList> > descriptor;
+       descriptor.property_id = g_quark_from_static_string ("FadeIn");
+       AutomationListProperty property (
+               descriptor,
+               boost::shared_ptr<AutomationList> (new AutomationList (Evoral::Parameter (FadeInAutomation)))
+               );
+
+       property.clear_changes ();
+
+       /* No change since we just cleared them */
+       CPPUNIT_ASSERT_EQUAL (false, property.changed());
+       
+       property->add (1, 2);
+       property->add (3, 4);
+
+       /* Now it has changed */
+       CPPUNIT_ASSERT_EQUAL (true, property.changed());
+
+       XMLNode* foo = new XMLNode ("test");
+       property.get_changes_as_xml (foo);
+       check_xml (foo, "../libs/ardour/test/data/automation_list_property_test1.ref");
+
+       /* Do some more */
+       property.clear_changes ();
+       CPPUNIT_ASSERT_EQUAL (false, property.changed());
+       property->add (5, 6);
+       property->add (7, 8);
+       CPPUNIT_ASSERT_EQUAL (true, property.changed());
+       foo = new XMLNode ("test");
+       property.get_changes_as_xml (foo);
+       check_xml (foo, "../libs/ardour/test/data/automation_list_property_test2.ref");
+}
+
+/** Here's a StatefulDestructible class that has a AutomationListProperty */
+class Fred : public StatefulDestructible
+{
+public:
+       Fred ()
+               : _jim (_descriptor, boost::shared_ptr<AutomationList> (new AutomationList (Evoral::Parameter (FadeInAutomation))))
+
+       {
+               add_property (_jim);
+       }
+
+       XMLNode & get_state () {
+               XMLNode* n = new XMLNode ("State");
+               add_properties (*n);
+               return *n;
+       }
+
+       int set_state (XMLNode const & node, int) {
+               set_values (node);
+               return 0;
+       }
+
+       static void make_property_quarks () {
+               _descriptor.property_id = g_quark_from_static_string ("FadeIn");
+       }
+
+       AutomationListProperty _jim;
+       static PropertyDescriptor<boost::shared_ptr<AutomationList> > _descriptor;
+};
+
+PropertyDescriptor<boost::shared_ptr<AutomationList> > Fred::_descriptor;
+
+void
+AutomationListPropertyTest::undoTest ()
+{
+       Fred::make_property_quarks ();
+
+       boost::shared_ptr<Fred> sheila (new Fred);
+
+       /* Add some data */
+       sheila->_jim->add (1, 2);
+       sheila->_jim->add (3, 4);
+
+       /* Do a `command' */
+       sheila->clear_changes ();
+       sheila->_jim->add (5, 6);
+       sheila->_jim->add (7, 8);
+       StatefulDiffCommand sdc (sheila);
+
+       /* Undo */
+       sdc.undo ();
+       check_xml (&sheila->get_state(), "../libs/ardour/test/data/automation_list_property_test3.ref");
+
+       /* Redo */
+       sdc.redo ();
+       check_xml (&sheila->get_state(), "../libs/ardour/test/data/automation_list_property_test4.ref");
+}
diff --git a/libs/ardour/test/automation_list_property_test.h b/libs/ardour/test/automation_list_property_test.h
new file mode 100644 (file)
index 0000000..8885d23
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+    Copyright (C) 2012 Paul Davis
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+
+class AutomationListPropertyTest : public CppUnit::TestFixture
+{
+       CPPUNIT_TEST_SUITE (AutomationListPropertyTest);
+       CPPUNIT_TEST (basicTest);
+       CPPUNIT_TEST (undoTest);
+       CPPUNIT_TEST_SUITE_END ();
+
+public:
+       void basicTest ();
+       void undoTest ();
+};
diff --git a/libs/ardour/test/data/automation_list_property_test1.ref b/libs/ardour/test/data/automation_list_property_test1.ref
new file mode 100644 (file)
index 0000000..b7c11f1
--- /dev/null
@@ -0,0 +1,17 @@
+<test>
+  <FadeIn>
+    <from>
+      <AutomationList automation-id="fadein" id="162" default="0" min-yval="0" max-yval="2" interpolation-style="Linear" state="Off" style="Absolute">
+      </AutomationList>
+    </from>
+    <to>
+      <AutomationList automation-id="fadein" id="162" default="0" min-yval="0" max-yval="2" interpolation-style="Linear" state="Off" style="Absolute">
+        <events>
+            1 2
+3 4
+
+        </events>
+      </AutomationList>
+    </to>
+  </FadeIn>
+</test>
diff --git a/libs/ardour/test/data/automation_list_property_test2.ref b/libs/ardour/test/data/automation_list_property_test2.ref
new file mode 100644 (file)
index 0000000..17df0e4
--- /dev/null
@@ -0,0 +1,24 @@
+<test>
+  <FadeIn>
+    <from>
+      <AutomationList automation-id="fadein" id="162" default="0" min-yval="0" max-yval="2" interpolation-style="Linear" state="Off" style="Absolute">
+        <events>
+            1 2
+3 4
+
+        </events>
+      </AutomationList>
+    </from>
+    <to>
+      <AutomationList automation-id="fadein" id="162" default="0" min-yval="0" max-yval="2" interpolation-style="Linear" state="Off" style="Absolute">
+        <events>
+            1 2
+3 4
+5 6
+7 8
+
+        </events>
+      </AutomationList>
+    </to>
+  </FadeIn>
+</test>
diff --git a/libs/ardour/test/data/automation_list_property_test3.ref b/libs/ardour/test/data/automation_list_property_test3.ref
new file mode 100644 (file)
index 0000000..edc1469
--- /dev/null
@@ -0,0 +1,11 @@
+<State>
+  <FadeIn>
+    <AutomationList automation-id="fadein" id="166" default="0" min-yval="0" max-yval="2" interpolation-style="Linear" state="Off" style="Absolute">
+      <events>
+          1 2
+3 4
+
+      </events>
+    </AutomationList>
+  </FadeIn>
+</State>
diff --git a/libs/ardour/test/data/automation_list_property_test4.ref b/libs/ardour/test/data/automation_list_property_test4.ref
new file mode 100644 (file)
index 0000000..5a6024a
--- /dev/null
@@ -0,0 +1,13 @@
+<State>
+  <FadeIn>
+    <AutomationList automation-id="fadein" id="166" default="0" min-yval="0" max-yval="2" interpolation-style="Linear" state="Off" style="Absolute">
+      <events>
+          1 2
+3 4
+5 6
+7 8
+
+      </events>
+    </AutomationList>
+  </FadeIn>
+</State>
diff --git a/libs/ardour/test/test_util.cc b/libs/ardour/test/test_util.cc
new file mode 100644 (file)
index 0000000..f1237f7
--- /dev/null
@@ -0,0 +1,20 @@
+#include <fstream>
+#include <sstream>
+#include "pbd/xml++.h"
+#include <cppunit/extensions/HelperMacros.h>
+
+using namespace std;
+
+void
+check_xml (XMLNode* node, string ref_file)
+{
+       system ("rm -f libs/ardour/test/test.xml");
+       ofstream f ("libs/ardour/test/test.xml");
+       node->dump (f);
+       f.close ();
+
+       stringstream cmd;
+       cmd << "diff -u libs/ardour/test/test.xml " << ref_file;
+       CPPUNIT_ASSERT_EQUAL (0, system (cmd.str().c_str ()));
+}
+       
diff --git a/libs/ardour/test/test_util.h b/libs/ardour/test/test_util.h
new file mode 100644 (file)
index 0000000..84766dd
--- /dev/null
@@ -0,0 +1,2 @@
+
+extern void check_xml (XMLNode *, std::string);
index c16c0469ec3d3ba11aa75990f76459be479cb517..2eb279b535979f903e96a82f0f83fc40f23f9b52 100644 (file)
@@ -424,10 +424,12 @@ def build(bld):
         testobj              = bld(features = 'cxx cxxprogram')
         testobj.source       = '''
                 test/dummy_lxvst.cc
+                test/test_util.cc
                 test/test_needing_session.cc
                 test/audio_region_test.cc
                 test/test_globals.cc
                 test/audio_region_read_test.cc
+                test/automation_list_property_test.cc
                 test/bbt_test.cc
                 test/tempo_test.cc
                 test/interpolation_test.cc
index 40e2e5462506160dd43ba7ea0451123cff541213..b1ec7897cfd2674814cdb3850dad56fbd751ec82 100644 (file)
@@ -255,6 +255,8 @@ public:
         static void set_thinning_factor (double d);
         static double thinning_factor() { return _thinning_factor; }
 
+       bool operator!= (ControlList const &) const;
+
 protected:
 
        /** Called by unlocked_eval() to handle cases of 3 or more control points. */
index 9a820294efb7da7144f9f775abe63b2cce49d9f5..53200398a5eb6564ed1f8d1c49b44bb225547477 100644 (file)
@@ -1529,5 +1529,33 @@ ControlList::set_thinning_factor (double v)
        _thinning_factor = v;
 }
 
+bool
+ControlList::operator!= (ControlList const & other) const
+{
+       if (_events.size() != other._events.size()) {
+               return true;
+       }
+
+       EventList::const_iterator i = _events.begin ();
+       EventList::const_iterator j = other._events.begin ();
+
+       while (i != _events.end() && (*i)->when == (*j)->when && (*i)->value == (*j)->value) {
+               ++i;
+               ++j;
+       }
+
+       if (i != _events.end ()) {
+               return true;
+       }
+       
+       return (
+               _parameter != other._parameter ||
+               _interpolation != other._interpolation ||
+               _min_yval != other._min_yval ||
+               _max_yval != other._max_yval ||
+               _default_value != other._default_value
+               );
+}
+
 } // namespace Evoral
 
index d961046760fa866280711d895fb97e65fa88150a..e65929c60ce8e234d8ab401637d6882627d32f96 100644 (file)
@@ -30,6 +30,7 @@
 #include "pbd/property_basics.h"
 #include "pbd/property_list.h"
 #include "pbd/enumwriter.h"
+#include "pbd/stateful.h"
 
 namespace PBD {
 
@@ -341,7 +342,121 @@ private:
        /* no copy-construction */
        EnumProperty (EnumProperty const &);
 };
+
+/** A Property which holds a shared_ptr to a Stateful object,
+ *  and handles undo using the somewhat inefficient approach
+ *  of saving the complete XML state of its object before and
+ *  after changes.  A sort of half-way house between the old
+ *  complete-state undo system and the new difference-based
+ *  one.
+ */
+template <class T>
+class SharedStatefulProperty : public PropertyBase
+{
+public:
+       typedef boost::shared_ptr<T> Ptr;
        
+       SharedStatefulProperty (PropertyID d, Ptr p)
+               : PropertyBase (d)
+               , _current (p)
+       {
+               
+       }
+
+       SharedStatefulProperty (PropertyID d, Ptr o, Ptr c)
+               : PropertyBase (d)
+               , _old (o)
+               , _current (c)
+       {
+               
+       }
+
+       bool set_value (XMLNode const & node) {
+
+               /* Look for our node */
+               XMLNode* n = node.child (property_name ());
+               if (!n) {
+                       return false;
+               }
+
+               /* And there should be one child which is the state of our T */
+               XMLNodeList const & children = n->children ();
+               if (children.size() != 1) {
+                       return false;
+               }
+
+               _current->set_state (*children.front (), Stateful::current_state_version);
+               return true;
+       }
+
+       void get_value (XMLNode & node) const {
+               XMLNode* n = node.add_child (property_name ());
+               n->add_child_nocopy (_current->get_state ());
+       }
+
+       void clear_changes () {
+               /* We are starting to change things, so _old gets set up
+                  with the current state.
+               */
+               _old.reset (new T (*_current.get()));
+       }
+
+       bool changed () const {
+               /* Expensive, but, hey; this requires operator!= in
+                  our T
+               */
+               return (*_old != *_current);
+       }
+
+       void invert () {
+               _current.swap (_old);
+       }
+
+       void get_changes_as_xml (XMLNode* history_node) const {
+               /* We express the diff as before and after state, just
+                  as MementoCommand does.
+               */
+               XMLNode* p = history_node->add_child (property_name ());
+               XMLNode* from = p->add_child ("from");
+               from->add_child_nocopy (_old->get_state ());
+               XMLNode* to = p->add_child ("to");
+               to->add_child_nocopy (_current->get_state ());
+       }
+
+       void get_changes_as_properties (PropertyList& changes, Command *) const {
+               if (changed ()) {
+                       changes.add (clone ());
+               }
+       }
+
+       void apply_changes (PropertyBase const * p) {
+               *_current = *(dynamic_cast<SharedStatefulProperty const *> (p))->val ();
+       }
+
+       Ptr val () const {
+               return _current;
+       }
+
+       T* operator-> () const {
+               return _current.operator-> ();
+       }
+
+       operator bool () const {
+               return _current;
+       }
+
+protected:
+
+       Ptr _old;
+       Ptr _current;
+
+private:
+
+       /* No copy-construction nor assignment */
+       SharedStatefulProperty (SharedStatefulProperty<T> const &);
+       SharedStatefulProperty<T>& operator= (SharedStatefulProperty<T> const &);
+};
+
 } /* namespace PBD */
 
 #include "pbd/property_list_impl.h"
index e2ccd677380dd9cf474a018848e00064ff8f6b2b..3046f971ab17772a4d1c651a10c97b6c9161a1ee 100644 (file)
@@ -652,13 +652,19 @@ static XMLSharedNodeList* find_impl(xmlXPathContext* ctxt, const string& xpath)
 void
 XMLNode::dump (ostream& s, string p) const
 {
-       s << p << _name << " ";
-       for (XMLPropertyList::const_iterator i = _proplist.begin(); i != _proplist.end(); ++i) {
-               s << (*i)->name() << "=" << (*i)->value() << " ";
-       }
-       s << "\n";
-       
-       for (XMLNodeList::const_iterator i = _children.begin(); i != _children.end(); ++i) {
-               (*i)->dump (s, p + "  ");
+       if (_is_content) {
+               s << p << "  " << content() << "\n";
+       } else {
+               s << p << "<" << _name;
+               for (XMLPropertyList::const_iterator i = _proplist.begin(); i != _proplist.end(); ++i) {
+                       s << " " << (*i)->name() << "=\"" << (*i)->value() << "\"";
+               }
+               s << ">\n";
+               
+               for (XMLNodeList::const_iterator i = _children.begin(); i != _children.end(); ++i) {
+                       (*i)->dump (s, p + "  ");
+               }
+               
+               s << p << "</" << _name << ">\n";
        }
 }