/* Copyright 2004, 2005 Bastiaan Veelo This file is part of dcouple. Dcouple 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. You are free to negociate a different license with the copyright holder. Dcouple 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 dcouple; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Some concepts in this file were inspired by Andy Friesen's listener.d (http://andy.tadan.us/d/listener.d). */ module dcouple.signal; private import dcouple.slot; private import dcouple.signalslot; //! An interface for the management of signals. /*! Provides an interface that must be implemented by classes that own \link Signal Signals \endlink in a managed way. */ interface SignalManager { /*! Deriving classes must implement this function to keep a list of \link Signal Signals \endlink that it owns, and disconnect() them in the destructor (so that connected \link Slot Slots \endlink remove their reference). Also, deriving classes must provide and use a method (say, .discard()) to disconnect all signals when the object is no longer used, so that it may be garbage collected. */ void register(GenericSignal); void deregister(GenericSignal); public: bool signalsBlocked(); bool signalsBlocked(bool block); } /*! Implementation of a SignalManager. To be mixed in: \code class MyClass : SignalManager { mixin SignalManagement; //... } \endcode */ template SignalManagement() { /* ~this() { deleteSignals(); } */ protected: /*! SignalManager implementation */ void register(GenericSignal s) { // _signals[s] = s; int len = _signals.length; int n = len-1; while (n >= 0 && _signals[n] is null) --n; if (n < len-1) _signals[n+1] = s; else _signals ~= s; } void deregister(GenericSignal s) { //_signals.remove(s); if (_signals.length == 0) return; bool found = false; int n; for (n=0 ; n < _signals.length-1; ++n) { if (!found && _signals[n] is s) found = true; if (found) _signals[n] = _signals[n+1]; if (_signals[n] is null) break; } if (found || _signals[length-1] is s) _signals[length-1] = null; } public: /*! Returns true if signals are blocked; otherwise returns false. Signals are not blocked by default. \sa blockSignals() */ bool signalsBlocked() { return _signalsBlocked; } /*! Blocks ordinary signals if \a block is true, or unblocks signals if \a block is false. Emitted signals disappear into hyperspace if signals are blocked. Note that a Signal will be emitted even if the signals for this object have been blocked. \returns new block value. \warning Returned value is opposite from QObject::blockSignals. */ bool signalsBlocked(bool block) { return _signalsBlocked = block; } protected: GenericSignal[] _signals; void deleteSignals() { debug printf("SignalManager::deleteSignals entered.\n_signals:\n"); debug foreach(GenericSignal s; _signals) printf("signal %p.\n",s); foreach(GenericSignal s; _signals) { if (s is null) break; // prevent s from manipulating _signals: s.freeFrom(this); debug printf("SignalManager::deleteSignals: deleting %p.\n", s); delete s; pragma(msg, "_signals contains dangling references. OK?"); } } private: bool _signalsBlocked = false; } //! An interface shared by all Signals /*! \todo access specifiers*/ interface GenericSignal //abstract class GenericSignal { //! Disconnect all. /*! Breaks all connections from this Signal. */ //void disconnect(); void disconnect(); SignalManager owner(); pragma(msg, "Want access specifier here."); void genericConnect(GenericSlot); void genericDisconnect(GenericSlot); uint numberOfArguments(); char[][] arguments(); void freeFrom(SignalManager); } /*! The generic part of \link Signal Signals. \endlink */ private template SignalGenericCore() { //! Constructor. /*! The Signal \link SignalManager::register(Signal) registers \endlink itself with its owner. */ //this() { this(null); } //this(SignalManager owner) this(SignalManager owner=null) { debug printf("signal %p newed.\n", this); _owner = owner; if (owner) owner.register(this); initArguments(); } //! Destructor. /*! Disconnects all connected \link Slot Slots \endlink, and \link SignalManager::deregister(Signal) de-registers \endlink itself with its owner. */ ~this() { debug printf("signal %p is destructing.\n", this); disconnect(); /* Strictly spoken, destructors are not allowed to reference other objects, because the order in which the GC calls destructors is not defined, so those objects may not exist anymore. The disconnect() function above breaks this rule indirectly. But this particular case is OK, because we only consider references to CompatibleSlots that *are still alive*. Slots that are already destructed have removed our reference to them, just like we have removed references to us in other slots in disconnect(). */ if(_owner) _owner.deregister(this); debug printf("signal %p is done destructing.\n", this); } /*! Establishes a connection between this Signal and Slot \a s, and vice versa (i.e., registers this Signal within Slot \a s). */ void connect(CompatibleSlot s) { debug printf("Signal::connect entered.\n"); connectToo(s); s.connectToo(this); } package void connectToo(CompatibleSlot s) { debug printf("Signal::connectToo entered.\n"); debug printf("_slots before:\n"); debug foreach( CompatibleSlot s; _slots ) printf( "slot %p\n", s); //if(s in _slots) {} else { // _slots[s] = s; int len = _slots.length; int n = len-1; while (n >= 0 && _slots[n] is null) --n; if (n < len-1) _slots[n+1] = s; else _slots ~= s; debug printf("_slots after:\n"); debug foreach( CompatibleSlot s; _slots ) printf( "slot %p\n", s); } void genericConnect(GenericSlot gs) { debug printf( "Signal::genericConnect entered.\n"); CompatibleSlot s = cast(CompatibleSlot) gs; if( s ) connect(s); else { if (_owner) printf("dcouple WARNING: %.*s.%.*s!(", _owner.classinfo.name, this.classinfo.name); else printf("dcouple WARNING: %.*s!(", this.classinfo.name); if(numberOfArguments()>0) printf("%.*s",arguments()[0]); for(int i=1; i0) printf("%.*s",gs.arguments()[0]); for(int i=1; i0) printf("%.*s",arguments()[0]); for(int i=1; i0) printf("%.*s",gs.arguments()[0]); for(int i=1; i= 0; i-- ) { if( _slots[i] !is null ) disconnect(_slots[i]); } } /*! \returns the number of connected \link Slot Slots. \endlink */ int count() { return _slots.length; } uint numberOfArguments() { return _arguments.length; } char[][] arguments() { return _arguments; } SignalManager owner() { return _owner; } void freeFrom(SignalManager owner) { // Only the real owner can set us free. assert(owner is _owner); _owner = null; // Do not deregister, owner is in charge. } protected: //! This Signal is managed by _owner. SignalManager _owner; //! Collection of connected \link Slot Slots. \endlink CompatibleSlot[] _slots; char[][] _arguments; // Can be static? } //! A signal for the connection to slots. /*! A Signal should be owned by a \link SignalManager managing \endlink class, and can be \link connect() connected \endlink to one or more \link Slot Slots, \endlink possibly in other classes. These \link Slot Slots \endlink are notified when the Signal is \link emit() emitted. \endlink */ class Signal() : GenericSignal { alias Slot!() CompatibleSlot; mixin SignalGenericCore; /*! Causes all connected \link Slot Slots \endlink to be notified. \todo We would like to only allow emit() to be called by the owner. Should we require a this pointer to be passed which we can check? A bit hackish, and Signals that are not owned can not be protected this way. We could just assume that the user does not call emit from strange places, but it may cause a security risk in some application. */ void emit() { debug printf("Signal!() emit() entered.\n"); debug printf("_slots:\n"); debug foreach(CompatibleSlot s; _slots) printf("slot %p\n", s); if (_owner && _owner.signalsBlocked()) return; foreach( CompatibleSlot s; _slots ) { if (s is null) break; debug printf("emitting on %p\n", s); s(); } } void initArguments() {} } class Signal(T1) : GenericSignal { alias Slot!(T1) CompatibleSlot; mixin SignalGenericCore; /*! Causes all connected \link Slot Slots \endlink to be notified. */ void emit(T1 t1) { if (_owner && _owner.signalsBlocked()) return; foreach( CompatibleSlot s; _slots ) { if (s is null) break; s(t1); } } void initArguments() { _arguments ~= typeid(T1).toString(); } } class Signal(T1,T2) : GenericSignal { alias Slot!(T1,T2) CompatibleSlot; mixin SignalGenericCore; /*! Causes all connected \link Slot Slots \endlink to be notified. */ void emit(T1 t1, T2 t2) { if (_owner && _owner.signalsBlocked()) return; foreach( CompatibleSlot s; _slots ) { if (s is null) break; s(t1, t2); } } void initArguments() { _arguments ~= typeid(T1).toString(); _arguments ~= typeid(T2).toString(); } } class Signal(T1,T2,T3) : GenericSignal { alias Slot!(T1,T2,T3) CompatibleSlot; mixin SignalGenericCore; /*! Causes all connected \link Slot Slots \endlink to be notified. */ void emit(T1 t1, T2 t2, T3 t3) { if (_owner && _owner.signalsBlocked()) return; foreach( CompatibleSlot s; _slots ) { if (s is null) break; s(t1, t2, t3); } } void initArguments() { _arguments ~= typeid(T1).toString(); _arguments ~= typeid(T2).toString(); _arguments ~= typeid(T3).toString(); } } class Signal(T1,T2,T3,T4) : GenericSignal { alias Slot!(T1,T2,T3,T4) CompatibleSlot; mixin SignalGenericCore; /*! Causes all connected \link Slot Slots \endlink to be notified. */ void emit(T1 t1, T2 t2, T3 t3, T4 t4) { if (_owner && _owner.signalsBlocked()) return; foreach( CompatibleSlot s; _slots ) { if (s is null) break; s(t1, t2, t3, t4); } } void initArguments() { _arguments ~= typeid(T1).toString(); _arguments ~= typeid(T2).toString(); _arguments ~= typeid(T3).toString(); _arguments ~= typeid(T4).toString(); } } class Signal(T1,T2,T3,T4,T5) : GenericSignal { alias Slot!(T1,T2,T3,T4,T5) CompatibleSlot; mixin SignalGenericCore; /*! Causes all connected \link Slot Slots \endlink to be notified. */ void emit(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5) { if (_owner && _owner.signalsBlocked()) return; foreach( CompatibleSlot s; _slots ) { if (s is null) break; s(t1, t2, t3, t4, t5); } } void initArguments() { _arguments ~= typeid(T1).toString(); _arguments ~= typeid(T2).toString(); _arguments ~= typeid(T3).toString(); _arguments ~= typeid(T4).toString(); _arguments ~= typeid(T5).toString(); } } class Signal(T1,T2,T3,T4,T5,T6) : GenericSignal { alias Slot!(T1,T2,T3,T4,T5,T6) CompatibleSlot; mixin SignalGenericCore; /*! Causes all connected \link Slot Slots \endlink to be notified. */ void emit(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6) { if (_owner && _owner.signalsBlocked()) return; foreach( CompatibleSlot s; _slots ) { if (s is null) break; s(t1, t2, t3, t4, t5, t6); } } void initArguments() { _arguments ~= typeid(T1).toString(); _arguments ~= typeid(T2).toString(); _arguments ~= typeid(T3).toString(); _arguments ~= typeid(T4).toString(); _arguments ~= typeid(T5).toString(); _arguments ~= typeid(T6).toString(); } } class Signal(T1,T2,T3,T4,T5,T6,T7) : GenericSignal { alias Slot!(T1,T2,T3,T4,T5,T6,T7) CompatibleSlot; mixin SignalGenericCore; /*! Causes all connected \link Slot Slots \endlink to be notified. */ void emit(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7) { if (_owner && _owner.signalsBlocked()) return; foreach( CompatibleSlot s; _slots ) { if (s is null) break; s(t1, t2, t3, t4, t5, t6, t7); } } void initArguments() { _arguments ~= typeid(T1).toString(); _arguments ~= typeid(T2).toString(); _arguments ~= typeid(T3).toString(); _arguments ~= typeid(T4).toString(); _arguments ~= typeid(T5).toString(); _arguments ~= typeid(T6).toString(); _arguments ~= typeid(T7).toString(); } }