diff d1/qt/Signal.d @ 344:96a75b1e5b26

project structure changes
author Max Samukha <maxter@spambox.com>
date Fri, 14 May 2010 12:14:37 +0300
parents qt/d1/qt/Signal.d@1f6923c8cba0
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/d1/qt/Signal.d	Fri May 14 12:14:37 2010 +0300
@@ -0,0 +1,1267 @@
+/**
+ *
+ *  Copyright: Copyright QtD Team, 2008-2009
+ *  Authors: Max Samukha, Eldar Insafutdinov
+ *  License: <a href="http://www.boost.org/LICENSE_1_0.txt>Boost License 1.0</a>
+ *
+ *  Copyright QtD Team, 2008-2009
+ *  Distributed under the Boost Software License, Version 1.0.
+ *  (See accompanying file boost-license-1.0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+ *
+ */
+module qt.Signal;
+
+public import qt.QGlobal;
+import tango.core.Exception;
+import tango.core.Traits;
+import tango.core.Thread;
+public import tango.core.Tuple;
+import tango.stdc.stdlib : crealloc = realloc, cfree = free;
+import tango.stdc.string : memmove;
+
+private: // private by default
+
+alias void delegate(Object) DEvent;
+
+extern(C) void rt_attachDisposeEvent(Object o, DEvent e);
+extern(C) void rt_detachDisposeEvent(Object o, DEvent e);
+extern(C) Object _d_toObject(void* p);
+
+void realloc(T)(ref T[] a, size_t length)
+{
+    a = (cast(T*)crealloc(a.ptr, length * T.sizeof))[0..length];
+    if (!a.ptr)
+        new OutOfMemoryException(__FILE__, __LINE__);
+}
+
+void append(T)(ref T[] a, T element)
+{
+    auto newLen = a.length + 1;
+    a = (cast(T*)crealloc(a.ptr, newLen * T.sizeof))[0..newLen];
+    if (!a.ptr)
+        new OutOfMemoryException(__FILE__, __LINE__);
+    a[newLen - 1] = element;
+}
+
+void move(T)(ref T[] a, size_t src, size_t dest, size_t length)
+{
+    if (a.length > 1)
+        memmove(a.ptr + dest, a.ptr + src, length * T.sizeof);
+}
+
+// COMPILER BUG: Though this is private cannot name it 'remove' because of conflicts
+// with Array.remove
+void erase(T)(ref T[] a, size_t i) 
+{
+    auto newLen = a.length - 1;
+    move(a, i + 1, i, newLen);
+    realloc(a, newLen);
+}
+
+version (QtdUnittest)
+{
+    unittest
+    {
+        int[] a;
+        realloc(a, 16);
+        assert(a.length == 16);
+        foreach (i, ref e; a)
+            e = i;
+        realloc(a, 4096);
+        assert(a.length == 4096);
+        foreach (i, e; a[0..16])
+            assert(e == i);
+        cfree(a.ptr);
+    }
+}
+
+// TODO: This one should be replaced with an appropriate library function
+char[] __toString(long v)
+{
+    if (v == 0)
+        return "0";
+
+    char[] ret;
+
+    bool neg;
+    if (v < 0)
+    {
+        neg = true;
+        v = -v;
+    }
+
+    while (v != 0)
+    {
+        ret = cast(char)(v % 10 + '0') ~ ret;
+        v = cast(long)(v / 10);
+    }
+
+    if (neg)
+        ret = "-" ~ ret;
+
+    return ret;
+}
+
+template ToString(long i)
+{
+    const string ToString = __toString(i);
+}
+
+//TODO: should be in the standard library
+struct STuple(A...)
+{
+    static string genSTuple()
+    {
+        string r = "";
+        foreach (i, e; A)
+            r ~= A[i].stringof ~ " _" ~ ToString!(i) ~ ";";
+        return r;
+    }
+
+    mixin (genSTuple);
+    template at(size_t i) { mixin("alias _" ~ ToString!(i) ~ " at;"); };
+}
+
+enum SignalEventId
+{
+    firstSlotConnected,
+    lastSlotDisconnected
+}
+
+public class SignalException : Exception
+{
+    this(char[] msg)
+    {
+        super(msg);
+    }
+}
+
+struct Fn
+{
+    void* funcptr;
+
+    static typeof(*this) opCall(R, A...)(R function(A) fn)
+    {
+        typeof(*this) r;
+        r.funcptr = fn;
+        return r;
+    }
+
+    template call(R)
+    {
+        R call(A...)(A args)
+        {
+            alias R function(A) Fn;
+            return (cast(Fn)funcptr)(args);
+        }
+    }
+
+    S get(S)()
+    {
+        static assert (is(typeof(*S.init) == function));
+        return cast(S)funcptr;
+    }
+}
+
+struct Dg
+{
+    void* context;
+    void* funcptr;
+
+    static typeof(*this) opCall(R, A...)(R delegate(A) dg)
+    {
+        typeof(*this) r;
+        r.context = dg.ptr;
+        r.funcptr = dg.funcptr;
+        return r;
+    }
+
+    template call(R)
+    {
+        R call(A...)(A args)
+        {
+            R delegate(A) dg; // BUG: parameter storage classes are ignored
+            dg.ptr = context;
+            dg.funcptr = cast(typeof(dg.funcptr))funcptr;
+            return dg(args);
+        }
+    }
+
+    S get(S)()
+    {
+        static assert (is(S == delegate));
+        S r;
+        r.ptr = context;
+        r.funcptr = cast(typeof(r.funcptr))funcptr;
+        return r;
+    }
+}
+
+struct Slot(R)
+{
+    alias R Receiver;
+
+    Receiver receiver;
+    Dg invoker;
+    ConnectionFlags flags;
+
+    static if (is(Receiver == Dg))
+    {
+        static const isDelegate = true;
+                
+        bool isDisposed()
+        {
+            return !receiver.funcptr;
+        }
+        
+        void dispose()
+        {
+            receiver.funcptr = null;
+            receiver.context = null;
+        }
+
+        Object getObject()
+        {           
+            return flags & ConnectionFlags.NoObject || !receiver.context
+                ? null : _d_toObject(receiver.context);
+        }
+    }
+    else
+        static const isDelegate = false;
+}
+
+enum SlotListId
+{
+    Func, // function pointers
+    Weak, // object delegates stored in C heap
+    Strong // delegates stored in GC heap
+}
+
+/**
+    Used to specify the type of a signal-to-slot connection.
+
+    Examples:
+----
+class Sender
+{
+    mixin Signal!("changed");
+    void change()
+    {
+        changed.emit;
+    }
+}
+
+
+class Receiver
+{
+    void alarm() {}
+}
+
+void main()
+{
+    auto s = new Sender;
+    auto r = new Receiver;
+    s.changed.connect(&r.alarm); // now s weakly references r
+
+    r = null;
+    // collect garbage (assume there is no more reachable pointers
+    // to the receiver and it gets finalized)
+    ...
+
+    s.change;
+    // weak reference to the receiving object
+    // has been removed from the sender's connection lists.
+
+    r = new Receiver;
+    s.changed.connect(&r.alarm, ConnectionFlags.Strong);
+
+    r = null;
+    // collect garbage
+    ...
+    // the receiving object has not been finalized because s strongly references it.
+
+    s.change; // the receiver is called.
+    delete r;
+    s.change; // the receiver is disconnected from the sender.
+
+    static void foo()
+    {
+    }
+
+    s.changed.connect(&foo);
+    s.changed.emit; // foo is called.
+    s.changed.disconnect(&foo); // must be explicitly disconnected.
+
+    void bar()
+    {
+    }
+
+    // ConnectionFlags.NoObject must be specified for delegates
+    // to non-static local functions or struct member functions.
+    s.changed.connect(&bar, ConnectionFlags.NoObject);
+    s.changed.emit; // bar is called.
+    s.changed.disconnect(&bar); // must be explicitly disconnected.
+}
+----
+*/
+public enum ConnectionFlags : ubyte
+{
+    ///
+    None,
+    /**
+        The receiver will be stored as weak reference (implied if ConnectionFlags.NoObject is not specified).
+        If the signal receiver is not a function pointer or a delegate referencing a D class instance.
+        the sender will not be notified when the receiving object is deleted and emitting the signal
+        connected to that receiving object will result in undefined behavior.
+    */
+    Weak                = 0x0001,
+    /**
+        The receiver is stored as strong reference (implied if ConnectionFlags.NoObject is specified).
+    */
+    Strong              = 0x0002,
+    /**
+        Must be specified if the receiver is not a function pointer or a delegate referencing a D class instance.
+    */
+    NoObject            = 0x0004
+
+    // Queued           = 0x0004,
+    // BlockingQueued   = 0x0008
+}
+
+
+struct SlotList(SlotT, bool strong = false)
+{
+    alias SlotT SlotType;
+    SlotType[] data;
+    
+    void length(size_t length)
+    {
+        static if (strong)
+            data.length = length;
+        else
+            realloc(data, length);
+    }
+
+    SlotType* add(SlotType slot)
+    {
+        auto oldLen = data.length;
+        length = oldLen + 1;
+        auto p = &data[oldLen];
+        *p = slot;
+        return p;
+    }
+
+    SlotType* get(int slotId)
+    {
+        return &data[slotId];
+    }
+
+    void remove(int slotId)
+    {
+        move(data, slotId, slotId + 1, data.length - slotId - 1);
+        data = data[0..$ - 1];
+    }
+
+    size_t length()
+    {
+        return data.length;
+    }
+
+    void free()
+    {
+        static if (!strong)
+            cfree(data.ptr);
+    }
+}
+
+public alias void delegate(int signalId, SignalEventId event) SignalEvent;
+
+struct Receivers
+{
+    struct Data
+    {
+        Object object;
+        int refs;
+    }
+    
+    Data[] data;
+    void add(Object receiver, DEvent disposeEvent)
+    {        
+        foreach (ref d; data)
+        {
+            if (d.object is receiver)
+            {               
+                d.refs++;              
+                return;
+            }
+        }
+        
+        append(data, Data(receiver, 1));
+        rt_attachDisposeEvent(receiver, disposeEvent);
+    }
+    
+    void remove(Object receiver, DEvent disposeEvent)
+    {
+        foreach (i, ref d; data)
+        {
+            if (d.object is receiver)
+            {
+                assert (d.refs);
+                d.refs--;
+                if (!d.refs)
+                {
+                    .erase(data, i);                    
+                    rt_detachDisposeEvent(receiver, disposeEvent);
+                }
+                return;
+            }
+        }
+        
+        assert (false);
+    }
+    
+    // remove all refarences for receiver, receiver has been disposed
+    void removeAll(Object receiver)
+    {
+        foreach (i, ref d; data)
+        {
+            if (d.object is receiver) 
+            {
+                .erase(data, i);
+                return;
+            }
+        }
+    }
+    
+    // remove all references for all receivers, detaching dispose events
+    void free(DEvent disposeEvent)
+    {
+        foreach (i, ref d; data)
+            rt_detachDisposeEvent(d.object, disposeEvent);
+        cfree(data.ptr);
+        data = null;
+    }
+}
+
+struct SignalConnections
+{
+    bool isInUse;
+
+    STuple!(
+        SlotList!(Slot!(Fn)),
+        SlotList!(Slot!(Dg)),
+        SlotList!(Slot!(Dg), true)
+    ) slotLists;
+
+    STuple!(
+        Fn[],
+        Dg[]
+    ) delayedDisconnects;
+
+    void addDelayedDisconnect(Fn r)
+    {
+        delayedDisconnects.at!(0) ~= r;
+    }
+
+    void addDelayedDisconnect(Dg r)
+    {
+        delayedDisconnects.at!(1) ~= r;
+    }
+
+    SlotListType!(slotListId)* getSlotList(int slotListId)()
+    {
+        return &slotLists.tupleof[slotListId];
+    }
+
+    bool hasSlots()
+    {
+        foreach(i, e; slotLists.tupleof)
+        {
+            if (slotLists.tupleof[i].length)
+                return true;
+        }
+        return false;
+    }
+
+    int slotCount()
+    {
+        int count;
+        foreach(i, e; slotLists.tupleof)
+            count += slotLists.at!(i).length;
+        return count;
+    }
+
+    void slotListLengths(int[] lengths)
+    {
+        foreach(i, e; slotLists.tupleof)
+             lengths[i] = slotLists.at!(i).length;
+    }
+
+    SlotType!(slotListId)* addSlot(int slotListId)(SlotType!(slotListId) slot)
+    {        
+        return getSlotList!(slotListId).add(slot);
+    }
+
+    void removeSlot(int slotListId)(int slotId)
+    {
+        slotLists.at!(slotListId).remove(slotId);
+    }
+    
+    void free()
+    {        
+        foreach(i, e; slotLists.tupleof)
+        {
+            static if (is(typeof(slotLists.at!(i).free)))
+                slotLists.at!(i).free;
+        }
+    }
+    
+    void onReceiverDisposed(Object receiver)
+    {
+        foreach (i, e; slotLists.tupleof)
+        {
+            static if (slotLists.at!(i).SlotType.isDelegate)
+            {
+                foreach (ref slot; slotLists.at!(i).data)
+                {
+                    if (slot.getObject is receiver)
+                        slot.dispose;
+                }
+            }
+        }
+    }
+
+    template SlotListType(int slotListId)
+    {
+        alias typeof(slotLists.tupleof)[slotListId] SlotListType;
+    }
+
+    template SlotType(int slotListId)
+    {
+        alias SlotListType!(slotListId).SlotType SlotType;
+    }
+
+    template ReceiverType(int slotListId)
+    {
+        alias SlotType!(slotListId).Receiver ReceiverType;
+    }
+
+    static const slotListCount = slotLists.tupleof.length;
+}
+
+
+private ThreadLocal!(Object) signalSender_;
+static this()
+{
+    signalSender_ = new ThreadLocal!(Object);
+}
+
+/**
+    If called from a slot, returns the object
+    that is emitting the signal. Otherwise, returns null.
+*/
+public Object signalSender() {
+    return signalSender_.val;
+}
+
+public final class SignalHandler
+{
+    SignalConnections[] connections;
+    Receivers receivers;
+    Object owner;
+    int blocked;
+    
+    SignalEvent signalEvent;
+   
+    alias SignalConnections.SlotType SlotType;
+    alias SignalConnections.ReceiverType ReceiverType;
+
+    public this(Object owner_) {
+        owner = owner_;        
+    }
+
+    private SignalConnections* getConnections(int signalId)
+    {
+        if (signalId < connections.length)
+            return &connections[signalId];
+        return null;
+    }
+
+    private SlotType!(slotListId)* addSlot(int slotListId)(int signalId, ReceiverType!(slotListId) receiver,
+        Dg invoker, ConnectionFlags flags)
+    {
+        if (signalId >= connections.length)
+            connections.length = signalId + 1;
+        auto slot = connections[signalId].addSlot!(slotListId)(SlotType!(slotListId)(receiver, invoker, flags));
+        
+        static if (slot.isDelegate)
+        {
+            if (!(flags & ConnectionFlags.NoObject))
+                receivers.add(_d_toObject(receiver.context), &onReceiverDisposed);
+        }
+
+        if (signalEvent && connections[signalId].slotCount == 1)
+            signalEvent(signalId, SignalEventId.firstSlotConnected);
+       
+        return slot;
+    }
+    
+    void onReceiverDisposed(Object receiver)
+    {
+        synchronized(this)
+        {
+            foreach(ref c; connections)
+                c.onReceiverDisposed(receiver);
+            receivers.removeAll(receiver);
+        }
+    }
+    
+    private void removeSlot(int slotListId)(int signalId, int slotId)
+    {
+        auto slot = connections[signalId].getSlotList!(slotListId).get(slotId);
+        static if (slot.isDelegate)
+        {
+            if (auto obj = slot.getObject)
+                receivers.remove(obj, &onReceiverDisposed);
+        }
+        
+        connections[signalId].removeSlot!(slotListId)(slotId);
+
+        if (signalEvent && !connections[signalId].slotCount)
+            signalEvent(signalId, SignalEventId.lastSlotDisconnected);
+    }
+    
+   
+    size_t slotCount(int signalId)
+    {
+        synchronized(this)
+        {
+            auto con = getConnections(signalId);
+            if (con)
+                return con.slotCount;
+            return 0;
+        }
+    }
+
+    void connect(Receiver)(int signalId, Receiver receiver,
+        Dg invoker, ConnectionFlags flags)
+    {
+        synchronized(this)
+        {
+            static if (is(typeof(receiver.context)))
+            {
+                Object obj;
+                if ((flags & ConnectionFlags.NoObject))
+                {
+                    // strong by default
+                    if (flags & ConnectionFlags.Weak)
+                        addSlot!(SlotListId.Weak)(signalId, receiver, invoker, flags);
+                    else
+                        addSlot!(SlotListId.Strong)(signalId, receiver, invoker, flags);
+                }
+                else
+                {
+                    // weak by default
+                    if (flags & ConnectionFlags.Strong)
+                        addSlot!(SlotListId.Strong)(signalId, receiver, invoker, flags);
+                    else
+                        addSlot!(SlotListId.Weak)(signalId, receiver, invoker, flags);
+                }
+            }
+            else
+            {
+                flags |= ConnectionFlags.NoObject;
+                addSlot!(SlotListId.Func)(signalId, receiver, invoker, flags);
+            }
+        }
+    }
+
+    void disconnect(Receiver)(int signalId, Receiver receiver)
+    {
+        synchronized(this)
+        {
+            auto cons = getConnections(signalId);
+            if (!cons)
+                return;
+
+            // if called from a slot being executed by this signal, delay disconnection
+            // until all slots has been called.
+            if (cons.isInUse)
+            {
+                cons.addDelayedDisconnect(receiver);
+                return;
+            }
+
+        TOP:
+            foreach (slotListId, e; cons.slotLists.tupleof)
+            {
+                /// COMPILER BUG: ReceiverType is evaluated to expression instead of type.
+                static if (is(typeof(cons.ReceiverType!(slotListId)) == Receiver))
+                {
+                    auto slotList = cons.getSlotList!(slotListId);
+                    for (int slotId; slotId < slotList.length;)
+                    {
+                        auto slot = slotList.get(slotId);
+                        static if (slot.isDelegate)
+                        {
+                            if (slot.isDisposed)
+                            {
+                                removeSlot!(slotListId)(signalId, slotId);
+                                continue;
+                            }
+                        }
+
+                        if (slot.receiver == receiver)
+                        {
+                            removeSlot!(slotListId)(signalId, slotId);
+                            break TOP;
+                        }
+
+                        slotId++;
+                    }
+                }
+            }
+        }
+    }
+
+    void emit(A...)(size_t signalId, A args)
+    {
+        synchronized(this)
+        {
+            if (signalId >= connections.length || blocked)
+                return;
+            auto cons = &connections[signalId];
+
+            if (cons.hasSlots)
+            {
+                {
+                    cons.isInUse = true;
+                    signalSender_.val = owner;
+                    scope(exit)
+                    {
+                        cons.isInUse = false;
+                        signalSender_.val = null;
+                    }
+
+                    // Store the lengths to avoid calling new slots
+                    // connected in the slots being called.
+                    // dmd bug: int[cons.slotListCount] fails
+                    static const c = cons.slotListCount;
+                    int[c] lengths = void;
+                    cons.slotListLengths(lengths);
+
+                    foreach (slotListId, e; cons.slotLists.tupleof)
+                    {
+                        auto slotList = cons.getSlotList!(slotListId);
+                        for (size_t slotId; slotId < lengths[slotListId];)
+                        {
+                            auto slot = slotList.get(slotId);
+                            static if (slot.isDelegate)
+                            {
+                                if (slot.isDisposed)
+                                {
+                                    removeSlot!(slotListId)(signalId, slotId);
+                                    lengths[slotListId]--;
+                                    continue;
+                                }
+                            }
+
+                            slot.invoker.call!(void)(slot.receiver, args);
+                            ++slotId;
+                        }
+                    }
+                }
+
+
+                // process delayed disconnects if any
+                foreach(i, e; cons.delayedDisconnects.tupleof)
+                {
+                    if (cons.delayedDisconnects.at!(i).length)
+                    {
+                        foreach (d; cons.delayedDisconnects.at!(i))
+                            disconnect(signalId, d);
+                        cons.delayedDisconnects.at!(i).length = 0;
+                    }
+                }
+            }
+        }
+    }
+
+    // Adjusts signal arguments and calls the slot. S - slot signature, A - signal arguments
+    private void invokeSlot(S, Receiver, A...)(Receiver r, A args)
+    {
+        r.get!(S)()(args[0..ParameterTupleOf!(S).length]);
+    }
+
+    void blockSignals()
+    {
+        synchronized(this)
+            blocked++;
+    }
+
+    void unblockSignals()
+    {
+        synchronized(this)
+        {
+            if(!blocked)
+                throw new SignalException("Signals are not blocked");
+            blocked--;
+        }
+    }
+
+    ~this()
+    {
+        receivers.free(&onReceiverDisposed);
+        foreach(ref c; connections)
+            c.free;
+    }
+}
+
+public template SignalHandlerOps()
+{
+    static assert (is(typeof(this.signalHandler)),
+        "SignalHandlerOps is already instantiated in " ~ typeof(this).stringof ~ " or one of its base classes");
+
+protected:
+    SignalHandler signalHandler_; // manages signal-to-slot connections
+
+    final SignalHandler signalHandler()
+    {
+        if (!signalHandler_)
+        {
+            signalHandler_ = new SignalHandler(this);
+            onSignalHandlerCreated(signalHandler_);
+        }
+        return signalHandler_;
+    }
+
+    void onSignalHandlerCreated(ref SignalHandler sh)
+    {
+    }
+
+public:
+    final void blockSignals()
+    {
+        signalHandler.blockSignals();
+    }
+
+    final void unblockSignals()
+    {
+        signalHandler.unblockSignals();
+    }
+    
+    template connect(string signalName, A...)
+    {
+        static void connect(T, Func)(T sender, Func func, ConnectionFlags flags = ConnectionFlags.None)
+        {
+            static if (isFnOrDg!(Func)) // D1 has no constraints
+        {
+            alias findSignal!(T, signalName, Func, A).result sig;
+            auto sh = sender.signalHandler();
+            static if (isFn!(Func))
+                alias Fn Callable;
+            else
+                alias Dg Callable;
+            auto invoker = Dg(&sh.invokeSlot!(typeof(func), Callable, sig[2..$]));
+            sh.connect(sig[1], Callable(func), invoker, flags);
+        }
+        else
+        {
+            static assert(false, "The slot must be a function or delegate type.");
+        }
+        }
+    }
+
+    template disconnect(string signalName, A...)
+    {
+        static void connect(T, Func)(T sender, Func func, ConnectionFlags flags = ConnectionFlags.None)
+        {
+            static if (isFnOrDg!(Func)) // D1 has no constraints
+        {
+            alias findSignal!(T, signalName, Func, A).result sig;
+            auto sh = sender.signalHandler();
+            static if (isFn!(Func))
+                alias Fn Callable;
+            else
+                alias Dg Callable;
+            sh.disconnect(sig[1], Callable(func));
+        }
+        else
+        {
+            static assert(false, "The slot must be a function or delegate type.");
+        }
+        }
+    }
+/*
+    template slotCount(string signalName, A...)
+    {
+        debug static void slotCount(T)(T sender)
+        {
+            alias findSignal!(T, signalName, Func, A).result sig;
+            auto sh = sender.signalHandler();
+            return sh.slotCount(sig[1]);
+        }
+    }
+    */
+}
+
+/**
+    New implementation.
+*/
+
+const string signalPrefix = "__signal";
+
+template TupleWrapper(A...) { alias A at; }
+
+template isDg(Dg)
+{
+    enum { isDg = is(Dg == delegate) }
+}
+
+template isFn(Fn)
+{
+    enum { isFn = is(typeof(*Fn.init) == function) }
+}
+
+template isFnOrDg(Dg)
+{
+    enum { isFnOrDg = isFn!(Dg) || isDg!(Dg) }
+}
+
+string joinArgs(A...)()
+{
+    string res = "";
+    static if(A.length)
+    {
+        res = A[0].stringof;
+        foreach(k; A[1..$])
+            res ~= "," ~ k.stringof;
+    }
+    return res;
+}
+
+template SlotPred(T1, T2)
+{
+    enum { SlotPred = is(T1 : T2) }
+}
+
+template CheckSlot(alias Needle, alias Source)
+{
+    static if(Needle.at.length <= Source.at.length)
+        enum { CheckSlot = CheckArgs!(Needle, Source, SlotPred, 0).value }
+    else
+        enum { CheckSlot = false }
+}
+
+template SignalPred(T1, T2)
+{
+    enum { SignalPred = is(T1 == T2) }
+}
+
+template CheckSignal(alias Needle, alias Source)
+{
+    static if(Needle.at.length == Source.at.length)
+        enum { CheckSignal = CheckArgs!(Needle, Source, SignalPred, 0).value }
+    else
+        enum { CheckSignal = false }
+}
+
+template CheckArgs(alias Needle, alias Source, alias pred, int i)
+{
+    static if (i < Needle.at.length)
+    {
+        static if (pred!(Needle.at[i], Source.at[i]))
+            enum { value = CheckArgs!(Needle, Source, pred, i + 1).value }
+        else
+            enum { value = false }
+    }
+    else
+    {
+        enum { value = true }
+    }
+}
+
+template SigByNamePred(string name, SlotArgs...)
+{
+    template SigByNamePred(source...)
+    {
+        static if (source[0] == name) // only instantiate CheckSlot if names match
+            enum { SigByNamePred = CheckSlot!(TupleWrapper!(SlotArgs), TupleWrapper!(source[2 .. $])) }
+        else
+            enum { SigByNamePred = false }
+    }
+}
+
+template SigBySignPred(string name, SigArgs...)
+{
+    template SigBySignPred(source...)
+    {
+        static if (source[0] == name) // only instantiate CheckSignal if names match
+            enum { SigBySignPred = CheckSignal!(TupleWrapper!(SigArgs), TupleWrapper!(source[2 .. $])) }
+        else
+            enum { SigBySignPred = false }
+    }
+}
+
+template staticSymbolName(string prefix, int id)
+{
+    const string staticSymbolName = prefix ~ ToString!(id);
+}
+
+template signatureString(string name, A...)
+{
+    const string signatureString = name ~ "(" ~ joinArgs!(A) ~ ")";
+}
+
+template findSymbolImpl(string prefix, C, int id, alias pred)
+{
+    static if ( is(typeof(mixin("C." ~ staticSymbolName!(prefix, id)))) )
+    {
+        mixin ("alias C." ~ staticSymbolName!(prefix, id) ~ " current;");
+        static if (pred!(current))
+            alias current result;
+        else
+            alias findSymbolImpl!(prefix, C, id + 1, pred).result result;
+    }
+    else
+    {
+        alias void result;
+    }
+}
+
+template findSymbol(string prefix, C, alias pred)
+{
+    alias findSymbolImpl!(prefix, C, 0, pred).result findSymbol;
+}
+
+template findSignal(C, string name, Receiver, SigArgs...)
+{
+    alias TupleWrapper!(ParameterTupleOf!(Receiver)) SlotArgsWr;
+    static if (SigArgs.length > 0)
+    {
+        alias findSymbol!(signalPrefix, C, SigBySignPred!(name, SigArgs)) result;
+        static if (is(result == void))
+            static assert(0, "Signal " ~ name ~ "(" ~ joinArgs!(SigArgs)() ~ ") was not found.");
+        else
+            static if (!CheckSlot!(SlotArgsWr, TupleWrapper!(result[2 .. $])))
+                static assert(0, "Signature of slot is incompatible with signal " ~ name ~ ".");
+    }
+    else
+    {
+        alias findSymbol!(signalPrefix, C, SigByNamePred!(name, SlotArgsWr.at)) result;
+        static if (is(result == void))
+            static assert(0, "Signal " ~ name ~ " was not found.");
+    }
+}
+
+
+public string SignalEmitter(A...)(SignalType signalType, string name, int index)
+{
+    string fullArgs, args;
+    static if (A.length)
+    {
+        fullArgs = A[0].stringof ~ " a0";
+        args = ", a0";
+        foreach(i, _; A[1..$])
+        {
+            fullArgs ~= ", " ~ A[i+1].stringof ~ " a" ~ __toString(i+1);
+            args ~= ", a" ~ __toString(i+1);
+        }
+    }
+    string attribute;
+    string sigName = name;
+    if (signalType == SignalType.BindQtSignal)
+        name ~= "_emit";
+    else
+        attribute = "protected ";
+    string str = attribute ~ "void " ~ name ~ "(" ~ fullArgs ~ ")" ~
+                 "{ this.signalHandler.emit(" ~ __toString(index) ~ args ~ "); }";
+    return str;
+}
+
+/**
+    Examples:
+----
+struct Args
+{
+    bool cancel;
+}
+
+class C
+{
+    private int _x;
+    // reference parameters are not supported yet,
+    // so we pass arguments by pointer.
+    mixin Signal!("xChanging", int, Args*);
+    mixin Signal!("xChanged");
+
+    void x(int v)
+    {
+        if (v != _x)
+        {
+            Args args;
+            xChanging.emit(v, &args);
+            if (!args.cancel)
+            {
+                _x = v;
+                xChanged.emit;
+            }
+        }
+    }
+}
+----
+*/
+
+enum SignalType
+{
+    BindQtSignal,
+    NewSignal
+}
+
+template BindQtSignal(string name, A...)
+{
+    mixin SignalImpl!(0, name, SignalType.BindQtSignal, A);
+}
+
+template Signal(string name, A...)
+{
+    mixin SignalImpl!(0, name, SignalType.NewSignal, A);
+}
+
+template SignalImpl(int index, string name, SignalType signalType, A...)
+{
+    static if (is(typeof(mixin(typeof(this).stringof ~ ".__signal" ~ ToString!(index)))))
+        mixin SignalImpl!(index + 1, name, signalType, A);
+    else
+    {
+        // mixed-in once
+        static if (!is(typeof(this.signalHandler)))
+            mixin SignalHandlerOps;
+
+        mixin (SignalEmitter!(A)(signalType, name, index));
+        mixin("public alias Tuple!(\"" ~ name ~ "\", index, A) __signal" ~ ToString!(index) ~ ";");
+    }
+}
+
+extern(C) alias void function(void*) SlotConnector;
+
+debug (UnitTest)
+{
+    class A
+    {
+        mixin Signal!("scorched", int);
+
+        int signalId1 = -1;
+        int signalId2 = -1;
+
+        void onFirstConnect(int sId)
+        {
+            signalId1 = sId;
+        }
+
+        void onLastDisconnect(int sId)
+        {
+            signalId2 = sId;
+        }
+
+        this()
+        {
+            signalHandler.firstSlotConnected = &onFirstConnect;
+            signalHandler.lastSlotDisconnected = &onLastDisconnect;
+        }
+    }
+
+    class B : A
+    {
+        mixin Signal!("booed", int);
+
+        int bazSum;
+        void baz(int i)
+        {
+            bazSum += i;
+        }
+    }
+
+    class C : A
+    {
+        mixin Signal!("cooked");
+    }
+}
+
+unittest
+{
+    static int fooSum;
+    static int barSum;
+
+    static void foo(int i)
+    {
+        fooSum += i;
+    }
+
+    void bar(long i)
+    {
+        barSum += i;
+    }
+
+    auto a = new A;
+    auto b = new B;
+    auto c = new C;
+    assert(b.scorched.signalId == 0);
+    assert(b.booed.signalId == 1);
+    assert(c.cooked.signalId == 1);
+
+    auto sh = b.signalHandler;
+
+    b.scorched.connect(&foo);
+    assert(sh.connections.length == 1);
+    assert(b.signalId1 == 0);
+    auto scCons = &sh.connections[0];
+
+    assert(scCons.getSlotList!(SlotListId.Func).length == 1);
+    b.scorched.emit(1);
+    assert(fooSum == 1);
+
+    b.scorched.connect(&bar, ConnectionFlags.NoObject);
+    assert(sh.connections.length == 1);
+    assert(scCons.getSlotList!(SlotListId.Strong).length == 1);
+    b.scorched.emit(1);
+    assert (fooSum == 2 && barSum == 1);
+
+    b.scorched.connect(&b.baz);
+    assert(scCons.getSlotList!(SlotListId.Weak).length == 1);
+    b.scorched.emit(1);
+    assert (fooSum == 3 && barSum == 2 && b.bazSum == 1);
+
+    b.scorched.disconnect(&bar);
+    assert(scCons.slotCount == 2);
+    b.scorched.disconnect(&b.baz);
+    assert(scCons.slotCount == 1);
+    b.scorched.disconnect(&foo);
+    assert(scCons.slotCount == 0);
+    assert(b.signalId2 == 0);
+
+    fooSum = 0;
+    void connectFoo()
+    {
+        b.scorched.connect(&foo);
+        b.scorched.disconnect(&connectFoo);
+    }
+
+    b.scorched.connect(&connectFoo, ConnectionFlags.NoObject);
+    b.scorched.emit(1);
+    assert(scCons.getSlotList!(SlotListId.Func).length == 1);
+    assert(scCons.getSlotList!(SlotListId.Strong).length == 0);
+    assert(!fooSum);
+
+    auto r = new B();
+    b.scorched.connect(&r.baz);
+    assert(scCons.getSlotList!(SlotListId.Weak).length == 1);
+    b.scorched.emit(1);
+    assert(r.bazSum == 1);
+    assert(fooSum == 1);
+
+    delete(r);
+    assert(scCons.getSlotList!(SlotListId.Weak).length == 1);
+    b.scorched.emit(1);
+    assert(scCons.getSlotList!(SlotListId.Weak).length == 0);
+}
\ No newline at end of file