/* 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 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 ** 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; 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 include support for QuTimeSpan values ////////////////////TICKET #760 ** ** @see TimeControl_test ** */ #ifndef LIB_TIME_CONTROL_H #define LIB_TIME_CONTROL_H #include "lib/time/mutation.hpp" #include "lib/time/timevalue.hpp" #include "lib/time/control-impl.hpp" namespace lib { namespace time { /** * Frontend/Interface: controller-element to retrieve * 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&) */ 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&) const; void operator() (Offset const&) const; void operator() (int) const; /** 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(); }; /* === 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) 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) 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) const { 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); } /* ==== Implementation of the Mutation interface ==== */ 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