Template EventListener


#1

I had been developing some pure virtual classes that would follow the listener, listenerCallback model used throughout juce. for my particular needs, I needed to be able to pass arguments to the listeners. I started to notice a pretty consistent pattern and since I’m still using JUCE 4.3.1, the ListenerList was the only option, but I didn’t like how it worked (seemed old, used macros…). so I got some help from Freenode and ##c+±general and came up with this really clever c++14 compatible EventListener notifier thing that I felt would benefit some folks:

template<class F, F> struct EventListener; //generic template

template<class ListenerClass,
    typename ReturnType,
    typename... Args,
    ReturnType(ListenerClass::*memberFunction)(Args...)> //specialized template
struct EventListener< ReturnType(ListenerClass::*)(Args...), memberFunction>
{
public:
    void add(ListenerClass* listener)
    {
        ScopedLock sl(lock);
        listeners.addIfNotAlreadyThere(listener);
    }
    void remove(ListenerClass* listener)
    {
        ScopedLock sl(lock);
        listeners.removeFirstMatchingValue(listener);
    }
    template<class... T>
    void notify(T&&... args)
    {
        ScopedLock sl(lock);
        for( int i = listeners.size(); --i >= 0; )
        {
            (listeners.getUnchecked(i)->*memberFunction)(args...);
        }
    }
private:
    CriticalSection lock;
    Array<ListenerClass*> listeners;
};

Here’s an example of the usage:

///responds when a key on an instance of a MidiKeyboardComponent is moused over
struct KeyboardMouseOverListener
{
    virtual ~KeyboardMouseOverListener() {}
    virtual void handleKeyboardMouseOver(int midiNote) = 0;
};
 
struct Window : public KeyboardMouseOverListener, public Component
{
    void lightUp();
    void handleKeyboardMouseOver(int midiNote) override { lightUp(); }
};

class PianoRollView : public Component
{
public:
    void postMouseEvent(const MouseEvent& e) override;
    void mouseEnter(const MouseEvent& e) override;
    void mouseExit(const MouseEvent& e) override;
    EventListener<decltype(&KeyboardMouseOverListener::handleKeyboardMouseOver),
                  &KeyboardMouseOverListener::handleKeyboardMouseOver> keyboardMouseOverListeners; 
private:
    int getKeyUnderCursor(const MouseEvent& e);
      
};
void PianoRollView::postMouseEvent(const juce::MouseEvent &e)
{
    keyboardMouseOverListeners.notify( getKeyUnderCursor(e) );
}
 
void PianoRollView::mouseEnter(const juce::MouseEvent &e)
{
    postMouseEvent(e);
}
void PianoRollView::mouseExit(const juce::MouseEvent& e)
{
    postMouseEvent(e);
}
PianoRollView pianoRollView;
Window window;
pianoRollView.keyboardMouseOverListeners.add(&window);

since EventListener<>::notify() accepts variadic arguments, you can pass any number of arguments to it, which is rad.

the c++17 version is even simpler:

template<typename T> struct EventListenerHelper;

template<typename Class, typename Return, typename... Args>
struct EventListenerHelper<Return(Class::*)(Args...)> {
	using cls = Class;
};

template<auto memberFunction> struct EventListener
{
    using ListenerClass = typename EventListenerHelper<decltype(memberFunction)>::cls;

    void add(ListenerClass* listener) { /*etc*/ }
    void remove(ListenerClass* listener) { /*etc*/ }
    template<class... T> void notify(T&&... args) { /*etc*/ }
    //...etc lock and listeners array
};

EventListener<&KeyboardMouseOverListener::handleKeyboardMouseOver> keyboardMouseOverListeners; 

I’m sure that there are other ways with lambdas, but if you’re still using the Listener/ListenerCallback model in your code, this works pretty well, especially the variadic arguments in notify(...)