Using tempates to create a map of sigc::connection from functors

47 views Asked by At

I want to create a class where I store connections from widgets to callbacks. So in Gtk++ you would get a widget and connect it to a function:

Gtk::Button* button; 
builder->get_widget("fooButton", button);
button->signal_clicked().connect(sigc::mem_fun(this, &ThisClassName::onButtonPressed)); // ThisClassName being the name of *this object's class

I want to have a class where I store a list of these connections. The goal is to be able to defer the establishment of the connections, to be able to disconnect or block all connections and reconnect/unblock them. So I created the following WidgetConnector:

#ifndef WIDGETCONNECTOR_H
#define WIDGETCONNECTOR_H

#include <functional>
#include <map>
#include <algorithm>

class WidgetConnector {
public:
    using ConnectionProxy = std::function<sigc::connection()>;

    WidgetConnector(const Glib::RefPtr<Gtk::Builder>& builder)
        : mBuilder(builder) { assert(mBuilder); }
    virtual ~WidgetConnector() {}

    template<class T_Widget, auto S, typename... T>
    T_Widget& getConnectedWidget(std::string label, const typename sigc::slot< void(T...)>& slot) {
        T_Widget* w = nullptr;
        mBuilder->get_widget(label, w);
        assert(w);

        using signal_type = decltype(std::mem_fun(S));
        const signal_type& widgetSignal = std::mem_fun(S);

        // delegate calls to connectWidgetWithSignal() which shall be called after the interface is build
        // to avoid any callbacks during build. This is done by storing the functors in a map.
        // Note: all captures must be copied so that they are valid during delegated call
        mConnectRefList[label] = std::make_unique<ConnectionProxy>([w, slot, widgetSignal]() {
            return widgetSignal(w).connect(slot);
        });
        return *w; // Return the object and get a reference to it (no nullptr testing anymore)
    }

    void connectWidgetsWithSignals() {
        for (auto const& [label, functor] : mConnectRefList)
        {
            connectWidgetWithSignal(label);
        }
    }

    void connectWidgetWithSignal(std::string label, bool doOverwrite = false) {
        auto connectionRef = (*mConnectRefList.at(label))();
        addDisconnectRef(label, connectionRef, doOverwrite);
    }

    template<typename CONN>
    void addDisconnectRef(std::string label, CONN connectRef, bool doOverwrite = false) {
        try {
            if (mDisconnectRefList[label].connected() && doOverwrite)
                mDisconnectRefList[label].disconnect();
            if (! mDisconnectRefList[label].connected()) {
                mDisconnectRefList[label] = connectRef;
                mIsConnected = true;
            }
        }
        catch (...) {
            std::cerr << "No connection set up for " << label << std::endl;
        }
    }

    void disconnectWidgetsWithSignals() {
        for (auto const& [label, functor] : mDisconnectRefList)
        {
            disconnectWidgetWithSignal(label);
        }
        mIsConnected = false;
    }

    void disconnectWidgetWithSignal(std::string label) {
        //if (mConnectRefList.find(label) != mConnectRefList.end()) return; // not connected
        try {
            if (mDisconnectRefList.at(label).connected())
                mDisconnectRefList.at(label).disconnect();
        }
        catch (...) {
            std::cerr << "No connection set up for " << label << std::endl;
        }
    }

private:
    const Glib::RefPtr<Gtk::Builder> mBuilder;
    std::map<std::string, std::unique_ptr<ConnectionProxy>> mConnectRefList; //!< list of SignalProxies for connect  calls
    std::map<std::string, sigc::connection> mDisconnectRefList; //!< list of SignalProxies for disconnect calls
};

#endif // WIDGETCONNECTOR_H

When inheriting from this class I can easily retrieve widgets, connect them and later disconnect and reconnect them:

auto& w = getConnectedWidget<Gtk::Button, Gtk::Button::signal_clicked>("fooButton", 
                       static_cast<const typename sigc::slot<void()>>(sigc::mem_fun(this,   
                       &ThisClassName::onButtonPressed)));
// do some more stuff with w, e.g. w.set_sensitive()
// till now the connections are merely stored
connectWidgetsWithSignals();
// now the connections are actually connected and executing on fired signals

Despite the looks of *getConnectedWidget *arguments: const typename sigc::slot< void(T...)> I cannot use this template function to store a connection with a function that has arguments, or a function that returns e.g. bool instead of void. So with

void onButtonPressed() {...}

everything works fine but

void onButtonPressed(int x) {...} or
bool onButtonPressed() { ... return true; }

does not compile.

How do I need to correct the class/template function to make this work?

0

There are 0 answers