From 2099ecbcac6d09a58ed19753db6a7b29cd929b23 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sun, 12 Jun 2011 19:03:12 +0200 Subject: [PATCH] finish and comment the new time::Control facility --- src/lib/time/control-impl.hpp | 203 ++++++++++++ src/lib/time/control-policy.hpp | 400 +++++++++++++++++++++++ src/lib/time/control.hpp | 467 +++++---------------------- tests/40components.tests | 17 +- tests/lib/time/time-control-test.cpp | 70 ++-- 5 files changed, 747 insertions(+), 410 deletions(-) create mode 100644 src/lib/time/control-impl.hpp create mode 100644 src/lib/time/control-policy.hpp diff --git a/src/lib/time/control-impl.hpp b/src/lib/time/control-impl.hpp new file mode 100644 index 000000000..1d2ab6d94 --- /dev/null +++ b/src/lib/time/control-impl.hpp @@ -0,0 +1,203 @@ +/* + CONTROL-IMPL.hpp - time::control implementation building blocks + + 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-impl.hpp + ** Implementation building blocks for time modification and propagation. + ** The time::Control element allows to impose modifications to a connected + ** time value entity and at the same time publish the changes to registered + ** listeners. Due to the various flavours of actual time value entities, this + ** is a complex undertaking, which is implemented here based on policies and + ** template metaprogramming. This header/include defines two building blocks: + ** - the actual Mutator to apply the changes to the target entity + ** - a Propagator to register listeners and forward the changes. + ** + ** \par implementation technique + ** + ** The Mutator uses functor objects to encapsulate the actual modification + ** operations. When attaching to a target time entity to be manipulated, these + ** functor objects will be configured by binding them to the appropriate + ** implementation function. And picking this actual implementation is done + ** through a time::mutation::Policy element, using the concrete time entity + ** types as template parameter. Thus, the actual implementation to be used + ** is determined by the compiler, through the template specialisations + ** contained in control-policy.hpp + ** + ** @note the header control-policy.hpp with the template specialisations + ** is included way down, after the class definitions. This is done + ** so for sake of readability + ** + ** @see TimeControl_test + ** + */ + +#ifndef LIB_TIME_CONTROL_IMPL_H +#define LIB_TIME_CONTROL_IMPL_H + +#include "lib/error.hpp" +#include "lib/time/mutation.hpp" +#include "lib/time/timevalue.hpp" + +#include + + +namespace lib { +namespace time { +namespace mutation { + + + + + /** + * Implementation building block: impose changes to a Time element. + * The purpose of the Mutator is to attach a target time entity, + * which then will be subject to any received value changes, + * offsets and grid nudging. The actual attachment is to be + * performed in a subclass, by using the Mutation interface. + * When attaching to a target, the Mutator will be outfitted + * with a set of suitable functors, incorporating the specific + * behaviour for the concrete combination of input changes + * ("source values") and target object type. This works by + * binding to the appropriate implementation functionality, + * guided by a templated policy class. After installing + * these functors, these decisions remains opaque and + * encapsulated within the functor objects, so the + * mutator object doesn't need to carry this + * type information on the interface + */ + template + class Mutator + : public Mutation + { + typedef function ValueSetter; + typedef function Ofsetter; + typedef function Nudger; + + protected: + mutable ValueSetter setVal_; + mutable Ofsetter offset_; + mutable Nudger nudge_; + + void + ensure_isArmed() const + { + if (!setVal_) + throw error::State("feeding time/value change " + "while not (yet) connected to any target to change" + ,error::LUMIERA_ERROR_UNCONNECTED); + } + + + template + void bind_to (TAR& target) const; + + void unbind(); + + // using default construction and copy + }; + + + + /** + * Implementation building block: propagate changes to listeners. + * The Propagator manages a set of callback signals, allowing to + * propagate notifications for changed Time values. + * + * There are no specific requirements on the acceptable listeners, + * besides exposing a function-call operator to feed the changed + * time value to. Both Mutator and Propagator employ one primary + * template parameter, which is the type of the time values + * to be fed in and propagated. + */ + 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; + } + + // using default construction and copy + }; + +}}} // lib::time::mutation + + +/* ===== Definition of actual operations ===== */ +#include "lib/time/control-policy.hpp" + + + + +template +template +void +lib::time::mutation::Mutator::bind_to (TAR& target) const +{ + using lib::time::mutation::Policy; + + setVal_ = Policy::buildChangeHandler (target); + offset_ = Policy::buildChangeHandler (target); + nudge_ = Policy::buildChangeHandler (target); +} + +template +void +lib::time::mutation::Mutator::unbind() +{ + setVal_ = ValueSetter(); + offset_ = Ofsetter(); + nudge_ = Nudger(); +} + + +#endif diff --git a/src/lib/time/control-policy.hpp b/src/lib/time/control-policy.hpp new file mode 100644 index 000000000..a941c3c96 --- /dev/null +++ b/src/lib/time/control-policy.hpp @@ -0,0 +1,400 @@ +/* + CONTROL-POLICY.hpp - detail definition of actual time changing functionality + + 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-policy.hpp + ** Definition of special cases when imposing a change onto concrete time values. + ** The time::Control element allows to impose modifications to a connected + ** time value entity and at the same time publish the changes to registered + ** listeners. Due to the various flavours of actual time value entities, this + ** is a complex undertaking, which is implemented here based on policies and + ** template metaprogramming. + ** + ** The header control-impl.hpp defines the building blocks for time::Control + ** and then includes this header here to get the concrete template specialisations + ** for time::mutation::Policy. This policy class is templated by time entity types + ** - for \c TI, the \em nominal value type used on the time::Control interface + ** - for \c SRC, the actual type of values to impose as \em change + ** - for \c TAR, the target time value's type, receiving those changes. + ** + ** \par mutating a time value entity + ** + ** Actually imposing a change to the attached time value entity involves several + ** steps. Each of these steps might be adapted specifically, in accordance to + ** the concrete time value types involved. + ** - TimeValue, Time + ** - Offset + ** - Duration + ** - TimeSpan + ** - QuTime (grid aligned time value) + ** - QuTimeSpan (planned as of 6/2011) + ** + ** Moreover, the combination of types needs to be taken into account. For example, + ** it doesn't make sense to apply a Duration value as change to a TimeValue, which + ** has no duration (temporal extension). While a TimeSpan might receive a Duration + ** change, but behaves differently when imposing a Time to manipulate the starting + ** point of the time interval given by the TimeSpan. + ** + ** Incoming changes might be of any of the aforementioned types, and in addition, + ** we might receive \em nudging, which means to increment or decrement the target + ** time value in discrete steps. After maybe adapting these incoming change values, + ** they may be actually \em imposed to the target. In all cases, this is delegated + ** to the time::Mutation base class, which is declared fried to TimeValue and thus + ** has the exceptional ability to manipulate time values, which otherwise are defined + ** to be immutable. Additionally, these protected functions in the time::Mutation + ** baseclass also know how to handle \em nudge values, either by using the native + ** (embedded) time grid of a quantised time value, or by falling back to a standard + ** nudging grid, defined in the session context (TODO as of 6/2011). //////////////////////TICKET #810 + ** + ** After (maybe) imposing a change to the target, the change \em notification value + ** needs to be built. This is the time value entity to be forwarded to registered + ** listeners. This notification value has to be given as the type \c TI, in accordance + ** to the \c time::Control frontend definition used in the concrete usage situation. + ** As this type \c TI might be different to the actual target type, and again different + ** to the type of the change handed in, in some cases this involves a second conversion + ** step, to represent the current state of the target \c TAR in terms of the interface + ** type \c TI. + ** + ** \par changing quantised (grid aligned) time entities + ** + ** The time::Control element includes the capability to handle grid aligned time values, + ** both as target and as change/notification value. This ability is compiled in conditionally, + ** as including mutation.hpp causes several additional includes, which isn't desirable when + ** it comes just to changing plain time values. Thus, to get these additional specialisations, + ** the LIB_TIME_TIMEQUQNT_H header guard needs to be defined, which happens automatically + ** if lib/time/mutation.hpp is included prior to lib/time/control.hpp. + ** + ** As a special convention, any \em quantised (grid aligned) types involved in these + ** time changes will be \em materialised, whenever a type conversion happens. Generally + ** speaking, a quantised time value contains an (opaque) raw time value, plus a reference + ** to a time grid definition to apply. In this context \em materialising means actually + ** to apply this time grid to yield a grid aligned value. Thus, when using a quantised + ** value to impose as change (or to receive a change), its grid aligning nature + ** becomes effective, by applying the \em current definition of the grid to + ** create a fixed (materialised) time value, aligned to that current grid. + ** + ** @todo 6/2011 include all the special cases for QuTimeSpan ////////////////////TICKET #760 + ** + ** @see TimeControl_test + ** + */ + +#ifndef LIB_TIME_CONTROL_POLICY_H +#define LIB_TIME_CONTROL_POLICY_H + +#include "lib/meta/util.hpp" +#include "lib/time/mutation.hpp" +#include "lib/time/timevalue.hpp" + +#include +#include + + +namespace lib { +namespace time { + +namespace mutation { + + 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; + + + + namespace { // metaprogramming helpers to pick a suitable implementation branch... + + template + inline bool + isDuration() + { + return is_sameType::value; + } + + template + inline bool + isTimeSpan() + { + return is_sameType::value; + } + + template + inline T const& + maybeMaterialise (T const& non_grid_aligned_TimeValue) + { + return non_grid_aligned_TimeValue; + } + +#ifdef LIB_TIME_TIMEQUQNT_H + inline QuTime + maybeMaterialise (QuTime const& alignedTime) + { + PQuant const& grid(alignedTime); + return QuTime(grid->materialise(alignedTime), grid); + } +#endif //--quantised-time-support + } + + + /** + * Implementation policy: how to build a new + * notification value of type \c TI, given a + * target time value entity of type \c TAR + */ + 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); + } + }; + template<> + struct Builder + { + static TimeSpan + buildChangedValue (Duration const& targetDuration) + { + return TimeSpan (Time::ZERO, targetDuration); + } + }; + template<> + struct Builder + { + static TimeSpan + buildChangedValue (TimeSpan const& target) + { + return target; + } + }; +#ifdef LIB_TIME_TIMEQUQNT_H + template + struct Builder + { + static QuTime + buildChangedValue (TAR const& target) + { + return QuTime (target + ,getDefaultGridFallback() //////////////////TICKET #810 + ); + } + }; + template<> + struct Builder + { + static QuTime + buildChangedValue (QuTime const& target) + { + return target; + } + }; +#endif //--quantised-time-support + + + + + /** + * Policy to tie the various detail policies together + * for providing actual value change operations. + * The standard case uses the (inherited) time::Mutation + * base implementation to impose a new value onto the + * target entity and then uses the Builder policy to + * create a notification value reflecting this change. + */ + template + struct Link + : Mutator + , Builder + { + + template + static TI + processValueChange (TAR& target, SRC const& change) ///< standard case: plain value change + { + imposeChange (target, maybeMaterialise(change)); + return buildChangedValue (maybeMaterialise(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); + } + }; + + + + /** + * Policy how to impose changes onto a connected target time value entity + * This policy will be parametrised with the concrete time entity types + * involved in the usage situation of time::Control. The purpose of the + * policy is to \em bind a functor object to the concrete implementation + * of the value change applicable for this combination of types. + * This functor will then be stored within time::Control and + * invoked for each actual value change. + * @param TI the nominal (interface) type of the change, propagated to listeners + * @param SRC the actual type of the change to be imposed + * @param TAR the actual type of the target entity to receive the changes + * @note typically either SRC is identical to TI, or it is an + * time::Offset, or an int for \em nudging the target + */ + template + struct Policy + { + static function + buildChangeHandler (TAR& target) + { + return bind (Link::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 canReceiveDuration + { + static const bool value = is_sameType::value + || is_sameType::value; + }; + } + + + /** + * special case: a Duration target value can't be changed by plain time values. + * This specialisation is \em not used (\c disable_if ) when the given change (SRC) + * is applicable to a Duration in a sensible way. We either define explicit + * specialisations (for TimeSpan) or fall back to the default in these cases. + */ + template + struct Policy, + Duration>::type> + { + static function + buildChangeHandler (Duration& target) + { + return bind (Link::dontChange, ref(target) ); + } + }; + + /** + * special case: a Duration change value can't be imposed to a plain time value. + * In these cases, we even propagate a Duration::ZERO to the listeners. + * As above, there are exceptions to this behaviour, where a Duration change + * can sensibly be applied. + */ + template + struct Policy, + Duration>::type, TAR> + { + static function + buildChangeHandler (TAR&) + { + return bind ( ignore_change_and_return_Zero ); + } + + static Duration + ignore_change_and_return_Zero() + { + return Duration::NIL; + } + }; + + template + struct Policy + { + static function + buildChangeHandler (Duration& target) + { + return bind (Link::useLengthAsChange, ref(target), _1 ); + } + }; + + + // special treatment for TimeSpan values--------------------------------------- + + template + struct Policy + { + static function + buildChangeHandler (TimeSpan& target) + { + return bind (Link::mutateLength, ref(target), _1 ); + } + }; + + template<> + struct Policy + { + static function + buildChangeHandler (TimeSpan& target) + { + return bind (Link::mutateTimeSpan, ref(target), _1 ); + } + }; + + + +}}} // namespace lib::time::mutation +#endif diff --git a/src/lib/time/control.hpp b/src/lib/time/control.hpp index ba9fba5ce..cbac65d33 100644 --- a/src/lib/time/control.hpp +++ b/src/lib/time/control.hpp @@ -40,6 +40,46 @@ ** 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 usage scenarios + ** The time::Control element provides mediating functionality, but doesn't assume or provide + ** anything special regarding the usage pattern or the lifecycle, beyond the ability to + ** attach listeners, attach to a (different) target and to detach from all connections. + ** Especially, no assumptions are made about which side is the server or the client + ** and who owns the time::Control element. + ** + ** Thus an interface might accept a time::Control element \em reference (e.g. the + ** lumiera::Play::Controller uses this pattern) -- meaning that the client owns the + ** Control element and might attach listeners, while the implementation (server side) + ** will attach the Control to mutate an time value entity otherwise not disclosed + ** (e.g. the playhead position of the playback process). Of course, in this case + ** the client is responsible for keeping the Control element and all listeners + ** alive, and to invoke Control#disconnect prior to destroying the element. + ** + ** Of course, the reversed usage situation would be possible as well: an interface + ** exposing a time::Control, thus allowing to attach target and listeners, while the + ** actual changes will originate somewhere within the service implementation. + ** + ** Another usage pattern would be to expose a time::Control \c const&, allowing only to + ** impose changes, but not to change the target or listener attachments. To the contrary, + ** when exposing only a time::Mutation \c const& through an interface allows only to + ** attach new target elements, but not to change listeners or feed any value changes. + ** + ** Using time::Control as an implementation building block and just exposing the + ** change (function) operators or the listener attachment through an forwarding sub + ** interface is another option. + ** + ** @note time::Control is default constructible and freely copyable. + ** + ** + ** \par changing quantised (grid aligned) time entities + ** + ** The time::Control element includes the functionality to handle grid aligned time values, + ** both as target and as change/notification value. This ability is compiled in conditionally, + ** as including mutation.hpp causes several additional includes, which isn't desirable when + ** it comes just to changing plain time values. Thus, to get these additional specialisations, + ** the LIB_TIME_TIMEQUQNT_H header guard needs to be defined, which happens automatically + ** if lib/time/mutation.hpp is included prior to lib/time/control.hpp. + ** ** \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 @@ -48,406 +88,42 @@ ** 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 + ** target; of course there is a race between applying the value and building the + ** response value passed on as notification. In all cases, the effective change + ** notification value is built from the state of the target after applying + ** the change, which might or might not reflect the change value passed in. ** - ** @todo WIP-WIP-WIP + ** @todo include support for QuTimeSpan values ////////////////////TICKET #760 ** */ #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/time/control-impl.hpp" -#include -#include -#include namespace lib { namespace time { - 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; - - - - /** - * Implementation building block: impose changes to a Time element. - * The purpose of the Mutator is to attach a target time entity, - * which then will be subject to any received value changes, - * offsets and grid nudging. The actual attachment is to be - * performed in a subclass, by using the Mutation interface. - * When attaching to a target, the Mutator will be outfitted - * with a set of suitable functors, incorporating the specific - * behaviour for the concrete combination of input changes - * ("source values") and target object type. This works by - * binding to the appropriate implementation functionality, - * guided by a templated policy class. After installing - * these functors, these decisions remains opaque and - * encapsulated within the functor objects, so the - * mutator object doesn't need to carry this - * type information on the interface - */ - template - class Mutator - : public Mutation - { - typedef function ValueSetter; - typedef function Ofsetter; - typedef function Nudger; - - 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); - } - - - template - void bind_to (TAR& target) const; - - void unbind(); - - // using default construction and copy - }; - - - - /** - * Implementation building block: propagate changes to listeners. - * The Propagator manages a set of callback signals, allowing to - * propagate notifications for changed Time values. - * - * There are no specific requirements on the acceptable listeners, - * besides exposing a function-call operator to feed the changed - * time value to. Both Mutator and Propagator employ one primary - * template parameter, which is the type of the time values - * to be fed in and propagated. - */ - 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; - } - }; - - - - namespace { // metaprogramming helpers to pick suitable implementation branch... - - template - inline bool - isDuration() - { - return is_sameType::value; - } - - template - inline bool - isTimeSpan() - { - return is_sameType::value; - } - - template - inline T const& - maybeMaterialise (T const& non_grid_aligned_TimeValue) - { - return non_grid_aligned_TimeValue; - } - -#ifdef LIB_TIME_TIMEQUQNT_H - inline QuTime - maybeMaterialise (QuTime const& alignedTime) - { - PQuant const& grid(alignedTime); - return QuTime(grid->materialise(alignedTime), grid); - } -#endif - } - - - 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); - } - }; - template<> - struct Builder - { - static TimeSpan - buildChangedValue (Duration const& targetDuration) - { - return TimeSpan (Time::ZERO, targetDuration); - } - }; - template<> - struct Builder - { - static TimeSpan - buildChangedValue (TimeSpan const& target) - { - return target; - } - }; -#ifdef LIB_TIME_TIMEQUQNT_H - template - struct Builder - { - static QuTime - buildChangedValue (TAR const& target) - { - return QuTime (target - ,getDefaultGridFallback() //////////////////TICKET #810 - ); - } - }; - template<> - struct Builder - { - static QuTime - buildChangedValue (QuTime const& target) - { - return target; - } - }; -#endif - - template - struct Link - : Mutator - , Builder - { - - template - static TI - processValueChange (TAR& target, SRC const& change) - { - imposeChange (target, maybeMaterialise(change)); - return buildChangedValue (maybeMaterialise(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 (Link::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 canReceiveDuration - { - static const bool value = is_sameType::value - || is_sameType::value; - }; - } - - - template - struct Policy, - Duration>::type> - { - static function - buildChangeHandler (Duration& target) - { - return bind (Link::dontChange, ref(target) ); - } - }; - - template - struct Policy, - Duration>::type, TAR> - { - static function - buildChangeHandler (TAR&) - { - return bind ( ignore_change_and_return_Zero ); - } - - static Duration - ignore_change_and_return_Zero() - { - return Duration::NIL; - } - }; - - template - struct Policy - { - static function - buildChangeHandler (Duration& target) - { - return bind (Link::useLengthAsChange, ref(target), _1 ); - } - }; - - // special treatment for TimeSpan values... - - template - struct Policy - { - static function - buildChangeHandler (TimeSpan& target) - { - return bind (Link::mutateLength, ref(target), _1 ); - } - }; - - template<> - struct Policy - { - static function - buildChangeHandler (TimeSpan& target) - { - return bind (Link::mutateTimeSpan, 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(); - } - - } /** * Frontend/Interface: controller-element to retrieve - * and change running time values + * and change running time values. time::Control is + * a mediator element, which can be attached to some + * time value entities as \em mutation, and at the + * same time allows to register listeners. When + * configured this way, \em changes may be fed + * to the function operator(s). These changes + * will be imposed to the connected target + * and the result propagated to the listeners. * * @see time::Mutation * @see time::TimeSpan#accept(Mutation const&) - * @todo WIP-WIP-WIP */ template class Control @@ -460,9 +136,9 @@ namespace time { virtual void change (QuTime&) const; public: - void operator() (TI const&); - void operator() (Offset const&); - void operator() (int); + void operator() (TI const&) const; + void operator() (Offset const&) const; + void operator() (int) const; /** install a callback functor to be invoked as notification @@ -481,27 +157,47 @@ namespace time { /* === forward to implementation === */ + /** impose a new value to the connected target. + * If applicable, the target will afterwards reflect + * that change, and listeners will be notified, passing + * the target's new state. + * @throw error::State if not connected to a target + * @note the actual change in the target also depends + * on the concrete target type and the type of + * the change. By default, the time value is + * changed; this may include grid alignment. + */ template void - Control::operator () (TI const& newValue) + Control::operator () (TI const& newValue) const { this->ensure_isArmed(); notifyListeners_( this->setVal_(newValue)); } + /** impose an offset to the connected target. + * If applicable, the target will be adjusted by the + * time offset, and listeners will be notified. + * @throw error::State if not connected to a target + */ template void - Control::operator () (Offset const& adjustment) + Control::operator () (Offset const& adjustment) const { this->ensure_isArmed(); notifyListeners_( this->offset_(adjustment)); } + /** nudge the connected target by the given offset steps, + * using either the target's own grid (when quantised), + * or a 'natural' nudge grid + * @throw error::State if not connected to a target + */ template void - Control::operator () (int offset_by_steps) + Control::operator () (int offset_by_steps) const { this->ensure_isArmed(); notifyListeners_( @@ -530,6 +226,9 @@ namespace time { notifyListeners_.attach (toNotify); } + + /* ==== Implementation of the Mutation interface ==== */ + template void Control::change (Duration& targetDuration) const diff --git a/tests/40components.tests b/tests/40components.tests index 4541270c7..999817faa 100644 --- a/tests/40components.tests +++ b/tests/40components.tests @@ -688,7 +688,22 @@ return: 0 END -PLANNED "Life changing time specifications with feedback" TimeControl_test < @@ -57,7 +58,10 @@ namespace test{ using lumiera::typelist::InstantiateChainedCombinations; using error::LUMIERA_ERROR_UNCONNECTED; - namespace { + + + namespace { // Test setup and helpers.... + inline string pop (Arg arg) { @@ -105,21 +109,25 @@ namespace test{ return *received_; } }; - } + + }//(End)Test helpers - /**************************************************************** - * @test use the time::Control to push a sequence of modifications - * to various time entities; in all cases, a suitable change - * should be imposed to the target and then a notification signal + /*********************************************************************** + * @test use the time::Control to push a sequence of modifications to + * various time entities; in all cases, a suitable change should + * be imposed to the target and then a notification signal * should be invoked. - * @todo cover the cases..... - * - change to a given value - * - change by an offset - * - change using a grid value - * - apply an (grid) increment + * + * After covering a simple basic case, this test uses + * template metaprogramming techniques to build a matrix of all + * possible type combinations and then performs a standard test + * sequence for each of these type combinations. Within this + * test sequence, verification functions are invoked, which + * are defined with specialisations to adapt for the various + * types to be covered. */ class TimeControl_test : public Test { @@ -176,6 +184,11 @@ namespace test{ } + /** @test cover all possible combinations of input change values + * and target time value entities to be handled by time::Control. + * Each of these cases executes a standard test sequence, which is + * defined in TestCase#performTestSequence + */ void verifyMatrix_of_MutationCases (TimeValue const& o, TimeValue const& c); }; @@ -241,7 +254,7 @@ namespace test{ return QuTime (org, "test_grid_PAL"); } }; - + template struct TestChange @@ -395,17 +408,21 @@ namespace test{ - template + template< class TAR ///< type of the target time value entity to receive changes + , class SRC ///< type of the time value to be imposed as change + , class BASE + > struct TestCase : BASE { void - performTestCases(TimeValue const& org, TimeValue const& c) + performTestSequence(TimeValue const& org, TimeValue const& c) { cout << "Test-Case. Target=" << showType() - <<" <--feed--- " << showType() - <() + << endl; + // test subject Control controller; TAR target = TestTarget::build(org); @@ -453,32 +470,35 @@ namespace test{ // tail recursion: further test combinations.... - BASE::performTestCases(org,c); + BASE::performTestSequence(org,c); } }; struct IterationEnd { - void performTestCases(TimeValue const&, TimeValue const&) { } + void performTestSequence(TimeValue const&, TimeValue const&) { } }; }//(End)Implementation Test-case matrix void - TimeControl_test::verifyMatrix_of_MutationCases (TimeValue const& o, TimeValue const& c) + TimeControl_test::verifyMatrix_of_MutationCases (TimeValue const& origVal, TimeValue const& change) { - typedef Types KindsOfTarget; - typedef Types KindsOfSource; + typedef Types KindsOfTarget; // time entities to receive value changes + typedef Types KindsOfSource; // time entities to be used as change values typedef InstantiateChainedCombinations< KindsOfTarget , KindsOfSource - , TestCase + , TestCase // template to be instantiated for each type , IterationEnd > TestMatrix; - TestMatrix().performTestCases(o,c); + TestMatrix().performTestSequence(origVal, change); } + + + /** Register this test class... */ LAUNCHER (TimeControl_test, "unit common");