/* 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/time/mutation.hpp" #include "lib/time/timevalue.hpp" //#include "lib/symbol.hpp" //#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 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 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); template struct MutationPolicy; 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 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)))); } template template struct Mutator::MutationPolicy { 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 ); } }; template template void Mutator::bind_to (TAR& target) const { setVal_ = MutationPolicy ::buildChangeHandler (target); offset_ = MutationPolicy::buildChangeHandler (target); nudge_ = MutationPolicy ::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