Templated callback handlers


#1

I’ve always found it nicer to have dedicated handlers for widgets instead of the if/else if/else if… construct, and with JUCE now having Listener subclasses of most widgets, I made these template classes for handling callbacks from them, hopefully you’ll find them usable. Also attached is a templated message handling construct so you can have dedicated handler functions for different message types (using RTTI for dispatching).

template<typename Type, typename Widget>
class ListenerBase : public Widget::Listener
{
protected:
    typedef void (Type::*TListenerCallback)(void);
    typedef std::map<Widget*, TListenerCallback> cb_map_type;
    cb_map_type m_callbacks;

    void notify(Widget* key)
    {
        typename cb_map_type::iterator it = m_callbacks.find(key);
        if (it != m_callbacks.end())
        {
            (((Type*)this)->*(it->second))();
        }
    }

public:
    void setHandler(Widget* key, TListenerCallback callback)
    {
        removeHandler(key);
        key->addListener(this);
        m_callbacks[key] = callback;
    }
    void removeHandler(Widget* key)
    {
        typename cb_map_type::iterator it = m_callbacks.find(key);
        if (it != m_callbacks.end())
        {
            key->removeListener(this);
            m_callbacks.erase(it);
        }
    }
};

template<typename Type>
class ButtonListenerHandler : public ListenerBase<Type, Button>
{
protected:
    typedef ListenerBase<Type, Button> ButtonListenerBase;
    virtual void buttonClicked (Button* button)
    {
        ButtonListenerBase::notify(button);
    }
};

template<typename Type>
class ComboBoxListenerHandler : public ListenerBase<Type, ComboBox>
{
protected:
    typedef ListenerBase<Type, ComboBox> ComboBoxListenerBase;
    virtual void comboBoxChanged (ComboBox* comboBoxThatHasChanged)
    {
        ComboBoxListenerBase::notify(comboBoxThatHasChanged);
    }
};

template<typename Type>
class LabelListenerHandler : public ListenerBase<Type, Label>
{
protected:
    typedef ListenerBase<Type, Label> LabelListenerBase;
    virtual void labelTextChanged(Label* labelThatHasChanged)
    {
        LabelListenerBase::notify(labelThatHasChanged);
    }
};

//////////////////////////////////////////////////////////////////////////
// Message listener helper
class MessageListenerHandlerBase
{
protected:
    virtual void onMessage(const Message& message) = 0;
    friend class MessageListenerBase;
};

class MessageListenerBase : public MessageListener
{
protected:
    template<class MessageType>
    void registerListener(MessageListenerHandlerBase* listener)
    {
        m_listenerMap[typeid(MessageType).name()] = listener;
    }

private:
    virtual void handleMessage (const Message& message)
    {
        message_listener_t::iterator it = m_listenerMap.find(typeid(message).name());
        if (it != m_listenerMap.end())
        {
            (it->second)->onMessage(message);
        }
    }

    typedef std::map<const char*, MessageListenerHandlerBase*> message_listener_t;
    message_listener_t m_listenerMap;
};

template<class MessageType>
class MessageListenerHandler : public virtual MessageListenerBase
                             , public MessageListenerHandlerBase
{
protected:
    MessageListenerHandler()
    {
        registerListener<MessageType>(this);
    }

    virtual void onMessage(const MessageType& message) = 0;

private:
    virtual void onMessage(const Message& message)
    {
        // static_cast would be nicer, but it requires full knowledge of MessageType (which excludes fwd declarations)
        onMessage(reinterpret_cast<const MessageType&>(message));
    }
};

Usage of the widget stuff:

class MyClass : public ButtonListenerHandler<MyClass>
                    , public ComboBoxListenerHandler<MyClass>
{
...
    using ButtonListenerHandler<MyClass>::setHandler;
    using ComboBoxListenerHandler<MyClass>::setHandler;

    void onButtonClicked();
    void onComboBoxChanged();
};

MyClass::MyClass()
{
...
    setHandler(m_button, &MyClass::onButtonClicked);
    setHandler(m_combobox, &MyClass::onComboBoxChanged);
}

Note the using declarations which are needed if you use more than one of the widget callback handlers.

Or, as per Jules idea:

class MyClass
{
private:
    Button* m_button1;
    Button* m_button2;
    ButtonListenerHandler<MyClass> m_btnHandler;

    void onButton1Clicked();
    void onButton2Clicked();
};

MyClass::MyClass()
{
...
   m_btnHandler.setHandler(m_button1, &MyClass::onButton1Clicked);
   m_btnHandler.setHandler(m_button2, &MyClass::onButton2Clicked);
}

Usage of the templated message callback handler:

class MessageClass : public Message
{
public:
    MessageClass() {;}
};

// Note: MessageClass could also be fwd declared, and defined in the cpp file instead

class MyClass : public MessageListenerHandler<MessageClass>
{
public:
...
protected:
    virtual void onMessage(const MessageClass& message);
};

#2

Nice! I like that!

With a few tweaks, I guess it’d be possible to make it allow the ButtonListenerHandler objects to be nested inside the parent class rather than needing to use inheritance…


#3

Yes, but I’ve always been a big fan of multiple inheritance :slight_smile:


#4

The tweak was minimal, all that was needed to set public access for setHandler/removeHandler. Edited accordingly :slight_smile:


#5

Hmm… Good stuff.

Would it be better to use RAII rather than the ‘setHandler’ stuff? Then you could do things like this:

[code]class MyClass
{
public:
MyClass()
{
connectors.add (new ButtonListenerConnector (myButton, &MyClass::onButtonClicked);
connectors.add (new ComboBoxListenerConnector (myCombo, &MyClass::onComboBoxChanged);
}

private:
OwnedArray connectors;
};
[/code]

It’d need a non-templated base class that all the listener types derive from, but that wouldn’t be a big deal.

This approach appeals to me more, because it’d keep all the type-specific stuff out of the class’s header.


#6

[quote=“jules”]It’d need a non-templated base class that all the listener types derive from, but that wouldn’t be a big deal.

This approach appeals to me more, because it’d keep all the type-specific stuff out of the class’s header.[/quote]

Yeah I think also that would be very nice… and very… jucey ? :wink:


#7

…or even a custom class to hold all the connections, e.g.

[code]class MyClass
{
public:
MyClass()
{
connectors.addCallback (myButton, &MyClass::onButtonClicked);
connectors.addCallback (myCombo, &MyClass::onComboBoxChanged);
}

private:
ListenerCalbackList connectors;
};[/code]

…which produces clearer user-code, but reduces encapsulation because the ListenerCalbackList class would need to know about all the different types that are supported.


#8

‘setHandler’ is pretty convenient though if you have a button that can change ‘personality’, like a play/stop button. ‘setHandler’ is then used to switch to the correct button handler code.


#9

It rings the cutty “signal/slot” bell to me.
Anyway,

I don’t follow you here. You can still add an templated overload for addCallback like:

template <class TypeWithListener, class Function>
void addCallback(TypeWithListener & obj, Function & func)
{
    connectors.addConnector(makeGeneralPurposeConnector(obj, func));
} 

struct GeneralPurposeConnectorBase
{
    virtual void callFunc() = 0;
};

template<Listener, ListenerFunc, class, func>
struct GeneralPurposeConnector : public GeneralPurposeConnectorBase
{
    Listener, ListenerFunc, class, func;
    void callFunc()
    {
        Listener.(*ListenerFunc)(class, Func);
    }
};

template <class Obj, class Func>
GeneralPurposeConnectorBase * makeGeneralPurposeConnector(Obj obj, Func func) { Obj::Listener listener; return new GeneralPurposeConnector(listener, obj, func); }
template <class Func>
GeneralPurposeConnectorBase * makeGeneralPurposeConnector<Button>(Button obj, Func func) { return new GeneralPurposeConnector(ButtonListener(), obj, func); }
...etc...