/* CONTROL.hpp - a life time control for feedback and mutation Copyright (C) Lumiera.org 2011, Hermann Vosseler 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. */ /** @file control.hpp ** Manipulating and monitoring time entities with life changes. ** This is an control- and callback element to handle any kind of "running" ** time entities. This element is to be provided by the client and then attached ** to the target time entity as a time::Mutation. Internally, a life connection to ** the target is built, allowing both to ** - to manipulate the target by invoking the function operator ** - to receive change notifications by installing a callback functor. ** ** The actual type of the changes and modifications is specified as template parameter; ** when later attached to some time entity as a Mutation, the actual changes to be performed ** depend both on this change type and the type of the target time entity (double dispatch). ** The behaviour is similar to applying a static time::Mutation ** ** \par relevance ** This control element is intended to be used for all kinds of editing and monitoring ** of time-like entities -- be it the running time display in a GUI widget, a ruler marker ** which can be dragged, a modifiable selection or the animated playhead cursor. ** ** \par implementation notes ** - the validity of a given combination of change and target is checked immediately, ** when connecting to the target. Depending on the situation, the actual changes later ** are subject to specific treatment (e.g. frame quantisation) ** - by default time::Control is not threadsafe. But, as each change is basically ** processed within its own call context (function invocation), parallelism is only ** a concern with respect to the value finally visible within the target. ** - the change notification is processed right away, after applying the change to the ** target; in all cases, the effective change value is what will be propagated, \em not ** the content of the target after applying the change ** ** @todo WIP-WIP-WIP ** */ #ifndef LIB_TIME_CONTROL_H #define LIB_TIME_CONTROL_H #include "lib/error.hpp" #include "lib/meta/util.hpp" #include "lib/time/mutation.hpp" #include "lib/time/timevalue.hpp" //#include "lib/symbol.hpp" //#include //#include //#include #include #include #include //#include namespace lib { namespace time { //using lib::Symbol; //using std::string; //using lib::Literal; //LUMIERA_ERROR_DECLARE (INVALID_MUTATION); ///< Changing a time value in this way was not designated namespace mutation { using boost::enable_if; using boost::disable_if; using lumiera::typelist::is_sameType; using std::tr1::placeholders::_1; using std::tr1::function; using std::tr1::bind; using std::tr1::ref; template struct Mutabor; /** * Implementation building block: impose changes to a Time element. * The Mutator supports attaching a target time entity (through * the Mutation interface), which then will be subject to any * received value changes, offsets and grid nudging. * * @todo WIP-WIP-WIP */ template class Mutator : public Mutation { typedef function ValueSetter; typedef function Ofsetter; typedef function Nudger; static TI imposeValueChange(TimeValue& target, TI const&); static TI imposeOffset (TimeValue& target, Offset const&); static TI imposeNudge (TimeValue& target, int); static TI changeDuration (Duration& target, TI const&); // static TI nudgeDuration (Duration& target, int); template struct MutationPolicy; friend class Mutabor; protected: mutable ValueSetter setVal_; mutable Ofsetter offset_; mutable Nudger nudge_; void ensure_isArmed() { if (!setVal_) throw error::State("feeding time/value change " "while not (yet) connected to any target to change" ,error::LUMIERA_ERROR_UNCONNECTED); } public: // using default construction and copy template void bind_to (TAR& target) const; void unbind(); }; template struct Mutabor { static void imposeChange (TimeValue& target, TI const& newVal) { Mutator::imposeChange (target,newVal); } }; namespace { // metaprogramming helpers to pick the suitable Instantiation... template struct isDurationZ { static const bool value = is_sameType::value; }; template struct isTimeSpanZ { static const bool value = is_sameType::value; }; template struct isQuTime { static const bool value = is_sameType::value; }; template struct isSpecialCase { static const bool value = isDurationZ::value; }; template inline bool isDuration() { return is_sameType::value; } template inline bool isTimeSpan() { return is_sameType::value; } } template struct Builder { static TI buildChangedValue (TAR const& target) { return TI(target); } }; template struct Builder { static TimeSpan buildChangedValue (TAR const& target) { return TimeSpan (target, Duration::NIL); /////////////TODO how to feed the "new value" duration???? } }; template<> struct Builder { static TimeSpan buildChangedValue (Duration const& targetDuration) { return TimeSpan (Time::ZERO, targetDuration); } }; template<> struct Builder { static TimeSpan buildChangedValue (TimeSpan& target) { return target; } }; #ifdef LIB_TIME_TIMEQUQNT_H template struct Builder { static QuTime buildChangedValue (TAR& target) { return QuTime (target ,getDefaultGridFallback() //////////////////TICKET #810 ); } }; template<> struct Builder { static QuTime buildChangedValue (QuTime& target) { return target; } }; #endif template struct Adap : Mutator , Builder { template static TI processValueChange (TAR& target, SRC const& change) { imposeChange (target,change); return buildChangedValue(target); } static TI useLengthAsChange (TAR& target, TimeSpan const& change) { return processValueChange(target, change.duration()); } static TI mutateLength (TimeSpan& target, Duration const& change) { Mutator::imposeChange (target.duration(), change); return Builder::buildChangedValue(target); } static TimeSpan mutateTimeSpan (TimeSpan& target, TimeSpan const& change) { Mutator::imposeChange (target.duration(), change.duration()); Mutator::imposeChange (target,change.start()); return Builder::buildChangedValue(target); } static TI dontChange (TAR& target) { // note: not touching the target return buildChangedValue(target); } }; template struct Policy { static function buildChangeHandler (TAR& target) { return bind (Adap::template processValueChange, ref(target), _1 ); } }; // special treatment of Durations as target... namespace { template struct canMutateDuration { static const bool value = is_sameType::value || is_sameType::value || is_sameType::value; }; } template struct Policy, Duration>::type> { static function buildChangeHandler (Duration& target) { return bind (Adap::dontChange, ref(target) ); } }; // template // struct Policy // { // static function // buildChangeHandler (Duration& target) // { // return bind (Adap::template processValueChange, ref(target), _1 ); // } // }; template struct Policy { static function buildChangeHandler (Duration& target) { return bind (Adap::useLengthAsChange, ref(target), _1 ); } }; // special treatment for TimeSpan values... template struct Policy { static function buildChangeHandler (TimeSpan& target) { return bind (Adap::mutateLength, ref(target), _1 ); } }; template<> struct Policy { static function buildChangeHandler (TimeSpan& target) { return bind (Adap::mutateTimeSpan, ref(target), _1 ); } }; /* template struct Policy { static function buildChangeHandler (TAR& target) { return bind (Adap::imposeOffset, ref(target), _1 ); } }; template struct Policy { static function buildChangeHandler (TAR& target) { return bind (Adap::imposeNudge, ref(target), _1 ); } }; */ template TI Mutator::imposeValueChange (TimeValue& target, TI const& newVal) { return TI (Mutation::imposeChange (target,newVal)); } template TI Mutator::imposeOffset (TimeValue& target, Offset const& off) { return TI (Mutation::imposeChange (target, TimeVar(target)+off)); } template TI Mutator::imposeNudge (TimeValue& target, int off_by_steps) { return TI (Mutation::imposeChange (target, TimeVar(target)+Time(FSecs(off_by_steps)))); //////////////////TICKET #810 } // special cases... template TI Mutator::changeDuration (Duration&, TI const&) { return TI (Time::ZERO); } template<> Duration Mutator::changeDuration (Duration& target, Duration const& changedDur) { return Duration (Mutation::imposeChange (target,changedDur)); } template<> TimeSpan Mutator::changeDuration (Duration& target, TimeSpan const& timeSpan_to_impose) { return TimeSpan (timeSpan_to_impose.start() ,Duration(Mutation::imposeChange (target,timeSpan_to_impose.duration())) ); } template<> TimeSpan Mutator::imposeValueChange (TimeValue& target, TimeSpan const& newVal) { Mutation::imposeChange (target,newVal); return newVal; } template<> TimeSpan Mutator::imposeOffset (TimeValue& target, Offset const& off) { return TimeSpan (Mutation::imposeChange (target, TimeVar(target)+off), Duration::NIL); } template<> TimeSpan Mutator::imposeNudge (TimeValue& target, int off_by_steps) { return TimeSpan (Mutation::imposeChange (target, TimeVar(target)+Time(FSecs(off_by_steps))), Duration::NIL); } #ifdef LIB_TIME_TIMEQUQNT_H template<> QuTime Mutator::changeDuration (Duration&, QuTime const& irrelevantChange) { return QuTime (Time::ZERO, PQuant(irrelevantChange)); } template<> QuTime Mutator::imposeValueChange (TimeValue& target, QuTime const& grid_aligned_Value_to_set) { PQuant quantiser (grid_aligned_Value_to_set); TimeValue appliedChange = quantiser->materialise(grid_aligned_Value_to_set); Mutation::imposeChange (target, appliedChange); return QuTime (target, quantiser); } template<> QuTime Mutator::imposeOffset (TimeValue& target, Offset const& off) { return QuTime (Mutation::imposeChange (target, TimeVar(target)+off) ,getDefaultGridFallback() //////////////////TICKET #810 ); } template<> QuTime Mutator::imposeNudge (TimeValue& target, int off_by_steps) { return QuTime (Mutation::imposeChange (target, TimeVar(target)+Time(FSecs(off_by_steps))) ,getDefaultGridFallback() //////////////////TICKET #810 ); } #endif //(End)quantisation special case template template struct Mutator::MutationPolicy< typename disable_if< isSpecialCase, TI>::type, TAR> { static function buildChangeHandler (TAR& target) { return bind (Mutator::imposeValueChange, ref(target), _1 ); } }; template template struct Mutator::MutationPolicy { static function buildChangeHandler (TAR& target) { return bind (Mutator::imposeOffset, ref(target), _1 ); } }; template template struct Mutator::MutationPolicy { static function buildChangeHandler (TAR& target) { return bind (Mutator::imposeNudge, ref(target), _1 ); } }; //special cases for changing Durations.... template template struct Mutator::MutationPolicy< typename enable_if< isDurationZ, TI>::type, TAR> { static function buildChangeHandler (Duration& target) { return bind (Mutator::changeDuration, ref(target), _1 ); } }; // // template // template // struct Mutator::MutationPolicy // { // static function // buildChangeHandler (Duration& target) // { // return bind (Mutator::changeDuration, ref(target), _1 ); // } // }; // template // template<> // struct Mutator::MutationPolicy // { // static function // buildChangeHandler (Duration& target) // { // return bind (Mutator::nudgeDuration, ref(target), _1 ); // } // }; template template void Mutator::bind_to (TAR& target) const { setVal_ = Policy::buildChangeHandler (target); offset_ = Policy::buildChangeHandler (target); nudge_ = Policy::buildChangeHandler (target); } template void Mutator::unbind() { setVal_ = ValueSetter(); offset_ = Ofsetter(); nudge_ = Nudger(); } /** * Implementation building block: propagate changes to listeners. * The Propagator manages a set of callback signals, allowing to * propagate notifications for changed Time values. * * @todo WIP-WIP-WIP */ template class Propagator { typedef function ChangeSignal; typedef std::vector ListenerList; ListenerList listeners_; public: /** install notification receiver */ template void attach (SIG const& toNotify) { ChangeSignal newListener (ref(toNotify)); listeners_.push_back (newListener); } /** disconnect any observers */ void disconnnect() { listeners_.clear(); } /** publish a change */ TI operator() (TI const& changedVal) const { typedef typename ListenerList::const_iterator Iter; Iter p = listeners_.begin(); Iter e = listeners_.end(); for ( ; p!=e; ++p ) (*p) (changedVal); return changedVal; } }; } /** * Frontend/Interface: controller-element for retrieving and * changing running time values * * @see time::Mutation * @see time::TimeSpan#accept(Mutation const&) * @todo WIP-WIP-WIP */ template class Control : public mutation::Mutator { mutation::Propagator notifyListeners_; virtual void change (Duration&) const; virtual void change (TimeSpan&) const; virtual void change (QuTime&) const; public: void operator() (TI const&); void operator() (Offset const&); void operator() (int); /** install a callback functor to be invoked as notification * for any changes imposed onto the observed time entity. * @param toNotify object with \c operator()(TI const&) */ template void connectChangeNotification (SIG const& toNotify); /** disconnect from observed entity and * cease any change notification */ void disconnnect(); }; /* === implementation === */ template void Control::operator () (TI const& newValue) { this->ensure_isArmed(); notifyListeners_( this->setVal_(newValue)); } template void Control::operator () (Offset const& adjustment) { this->ensure_isArmed(); notifyListeners_( this->offset_(adjustment)); } template void Control::operator () (int offset_by_steps) { this->ensure_isArmed(); notifyListeners_( this->nudge_(offset_by_steps)); } template void Control::disconnnect() { notifyListeners_.disconnect(); this->unbind(); } template template void Control::connectChangeNotification (SIG const& toNotify) { if (this->offset_) { // we're already connected: thus propagate current value TI currentValue = this->offset_(Offset::ZERO); toNotify (currentValue); } notifyListeners_.attach (toNotify); } template void Control::change (Duration& targetDuration) const { this->bind_to (targetDuration); } template void Control::change (TimeSpan& targetInterval) const { this->bind_to (targetInterval); } template void Control::change (QuTime& targetQuTime) const { this->bind_to (targetQuTime); } }} // lib::time #endif