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(...)