paste this into a file called SignalSlotEventSystem.h :Â
#ifndef SIGNALSLOTEVENTSYSTEM_H_INCLUDED
#define SIGNALSLOTEVENTSYSTEM_H_INCLUDED
#include <vector>
#include <algorithm>
template<class... Args> class Event; //class prototype
class BaseDelegate
{
public:
virtual ~BaseDelegate(){ }
};
template<class... Args> class AbstractDelegate : public BaseDelegate
{
protected:
virtual ~AbstractDelegate()
{
for(auto i : events) i->disconnect(*this); //calls Event::disconnect(*this) on each event
}
friend class Event<Args...>;
void add(Event<Args...> *newEvent)
{
events.push_back(newEvent);
}
void remove(Event<Args...> *eventToRemove)
{
events.erase(std::remove(events.begin(), events.end(), eventToRemove), events.end());
}
virtual void call(Args... args) = 0;
std::vector<Event<Args...>*> events; //a vector of events that we respond to.
};
template<class T, class... Args> class ConcreteDelegate : public AbstractDelegate<Args...>
{
public:
ConcreteDelegate(T *t, void(T::*f)(Args...), Event<Args...> &s)
: t(t), f(f)
{
s.connect(*this); //adds
}
private:
ConcreteDelegate(const ConcreteDelegate&);
void operator=(const ConcreteDelegate&);
friend class Event<Args...>; //this lets Event<> objects access ConcreteDelegate::call(), which is private
virtual void call(Args... args)
{
(t->*f)(args...);
}
T *t; //pointer to object instance.
void(T::*f)(Args...); //pointer to object instance method with <Args...> signature
};
template<class... Args> class Event
{
public:
Event(){ }
~Event()
{
for(auto i: delegates)
i->remove(this); //calls AbstractDelegate::remove()
}
void connect(AbstractDelegate<Args...> &delegateToConnect)
{
delegates.push_back(&delegateToConnect);
delegateToConnect.add(this); //calls AbstractDelegate::add()
}
void disconnect(AbstractDelegate<Args...> &delegateToDisconnect)
{
delegates.erase(std::remove(delegates.begin(), delegates.end(), &delegateToDisconnect), delegates.end());
}
void operator()(Args... args)
{
for(auto i: delegates) i->call(args...); //calls the function pointer stored on the ConcreteDelegate object with the templated args.
}
private:
Event(const Event&);
void operator=(const Event&);
std::vector< AbstractDelegate<Args...>* > delegates; //a list of delegates with this argument signature
};
class Delegate
{
public:
Delegate(){ }
~Delegate()
{
for(auto i: delegates) delete i; //automatically calls ConcreteDelegate::AbstractDelegate::~AbstractDelegate(), which calls Event::disconnect()
}
/**
Connects an event to an object's instance method for handling said event.
T *t is the object with the instance method
void(T::*f) is the instance method on the object. use the format &MyClass::MyMethod with no arguments.
event is the event that your method should respond to. usually defined in a global location, such as an AppEvents class that holds each type of event.
usage:
1) add an AppEvents member to any class that needs to trigger events.
2) add a Delegate to each class that will respond to these events.
3) pass a reference to the AppEvents member to these classes with the delegate
4) connect your class methods to the events via this method.
5) trigger an event by calling AppEvents::event(Args...)
*/
template<class T, class... Args> void connect(T *t, //Object instance
void(T::*f)(Args...), //Object instance method that handles the args
Event<Args...> &event) //Event that carries the args
{
delegates.push_back(new ConcreteDelegate<T, Args...>(t, f, event));
}
private:
Delegate(const Delegate&);
void operator=(const Delegate&);
std::vector<BaseDelegate*> delegates;
};
#endif /* SIGNALSLOTEVENTSYSTEM_H_INCLUDED */
now create an AppEvents.h file that #include "SignalSlotEventSystem.h" and add some events as such:Â
#include "SignalSlotEventSystem.h"
#include "../JuceLibraryCode/JuceHeader.h"
class AppEvents {
Event<int, const MidiMessage &, const String&> IMS_Event;
Event<float, float, int, float> FFIF_Event;
Event<bool, String> BS_Event;
Event<String, float, bool> SFB_Event;
};
now add an instance of this AppEvents class to your project along with an instance of the Delegate class from the SignalSlotEventSystem.h in each class that is gonna use it:Â
class MainContentComponent : Component
{
public:
MainContentComponent();
~MainContentComponent() {}
void paint(Graphics &g) override;
void resized() override;
void someMethod(int, const MidiMessage&, const String &) {}
void someOtherMethod( float, float, int, float ) {}
void fireEvents();
private:
SharedResourcePointer<AppEvents> appEvents;
Delegate delegate;
};
class OtherComponent : Component
{
public:
OtherComponent();
~OtherComponent() {}
void paint(Graphics &g) override;
void resized() override;
void someMethod(int, const MidiMessage&, const String &) {}
void fireEvents();
private:
SharedResourcePointer<AppEvents> appEvents;
Delegate delegate;
};
Next, set up your connections with the delegate in each class, probably in your constructor
MainContentComponent::MainContentComponent() {
delegate.connect(this, &MainContentComponent::someMethod, appEvents.IMS_Event);
delegate.connect(this, &MainContentComponent::someOtherMethod, appEvents.FFIF_Event);
}
OtherComponent::OtherComponent() {
delegate.connect(this, &OtherComponent::someMethod, appEvents.IMS_Event);
}
Finally, trigger the events when you need to!
void MainContentComponent::fireEvents() {
appEvents.IMS_Event( 25, MidiMessage::noteOn(1, 42, 1.f), "Bb13#9" );
appEvents.FFIF_Event( 2.3, 3.2, 7, 4.625 );
appEvents.BS_Event( true, "This event has no connection to receive it" );
}
void OtherComponent::fireEvents() {
appEvents.IMS_Event( 382, MidiMessage::noteOn(1, 26, 1.f), "16 + 5 = 21" );
appEvents.FFIF_Event( 68.1, 69.1, 33, 0.125 );
appEvents.SFB_Event( "This won't go anywhere", 3.3, false );
}
it works AWESOME, and you can have a ton of events that use identical signatures but have different names, likeÂ
Event<MidiMessage> NoteOnEvent;
Event<MidiMessage> DrawbarEvent;
Event<MidiMessage> SustainPedalEvent;
Event<MidiMessage> NoteOffEvent;
This makes it suuuuuper easy to set up handlers for specific message types instead of having to use a bunch of if/switch statements!Â
Thanks to the OP for sharing this idea and that link!
Hopefully this post of mine will help you use it, because that link was kinda confusing to read thru!Â