QT lambda slots

I’ve toyed with QT connections trying to create anonymous function when connecting a slot to a signal. For instance, I am trying to do the following:

menu->addAction( QIcon("some_icon.png"), "Open", this, SLOT(OnOpenTriggered()) );

So far so good, but sometime I find it cumbersome to define all the slots. Create a declaration, a definition, etc…

I would like to declare the body right there! Here’s what I would like to do using std::function or a c++ lambda:

menu->addAction( QIcon("some_icon.png"), "Open", _Q, _Q->Call( [this](){
    // body here...
    this->showNormal(); // e.g.
} ) );

I’ve achieved that by creating a global object named _Q which is responsible to maintained the QT meta object information about all connection that needs to be called.

Here’s the header of the class in question:

class GlobalQTReceiver : public QObject
{
    Q_GADGET;
public:
 
    typedef std::function<void()> Func;
 
    GlobalQTReceiver();
 
    const char* Call(Func slotCallback);
 
protected:
 
    virtual const QMetaObject *metaObject() const override;
    virtual int qt_metacall(QMetaObject::Call, int, void **) override;
    virtual void* qt_metacast(const char *_clname) override;
 
protected Q_SLOTS:
 
    void func1();
 
private:
 
    struct FuncInfo {
        std::string name;
        std::string slotName;
        Func callback;
    };
 
    void FillMetaStructs();
 
    int mNextId;
    std::vector<FuncInfo> mFuncs;
    std::vector<uint> mMetaData;
    std::vector<char> mMetaString;
    QMetaObject mMetaObject;
};
 
extern QTUtils::Internal::GlobalQTReceiver* _Q;

And here’s the implementation:

 
GlobalQTReceiver::GlobalQTReceiver()
    : mNextId(0)
{
    FillMetaStructs();
}
 
const char* GlobalQTReceiver::Call( std::function<void()> slotCallback )
{
    std::stringstream ss;
    ss << "func" << mNextId++ << "()";
 
    FuncInfo fi;
    fi.name = ss.str();
    fi.slotName = "1"+fi.name;
    fi.callback = slotCallback;
 
    mFuncs.push_back( fi );
    FillMetaStructs();
 
    return mFuncs.back().slotName.c_str();
}
 
int GlobalQTReceiver::qt_metacall(QMetaObject::Call call, int id, void** args)
{
    Q_ASSERT( call == QMetaObject::InvokeMetaMethod );
 
    id = QObject::qt_metacall(call, id, args);
    if (id < 0)
        return id;
 
    Func slotCallback = mFuncs[id].callback;
    id -= mFuncs.size();
 
    slotCallback();
 
    return id;
}
 
void* GlobalQTReceiver::qt_metacast( const char *_clname )
{
    Q_ASSERT( !"not supported" );
    return nullptr;
}
 
const QMetaObject* GlobalQTReceiver::metaObject() const 
{
    return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &mMetaObject;
}
 
void GlobalQTReceiver::FillMetaStructs()
{
    const char objectName[] = "QTUtils::Internal::GlobalQTReceiver";
    mMetaString.clear();
    mMetaString.insert(mMetaString.begin(), objectName, objectName+sizeof(objectName));
    mMetaString.push_back('\0');
 
      /* content:
      6,       // revision
        0,       // classname
        0,    0, // classinfo
        7,   14, // methods
        0,    0, // properties
        0,    0, // enums/sets
        0,    0, // constructors
        0,       // flags
        0,       // signalCount
        */
    mMetaData.clear();
    mMetaData.push_back( 6 ); // revision
    mMetaData.push_back( 0 ); // classname
    mMetaData.push_back( 0 ); mMetaData.push_back( 0 ); // classinfo
    mMetaData.push_back( mFuncs.size() ); mMetaData.push_back( 14 ); // methods
    mMetaData.push_back( 0 ); mMetaData.push_back( 0 ); // properties
    mMetaData.push_back( 0 ); mMetaData.push_back( 0 ); // enum/sets
    mMetaData.push_back( 0 ); mMetaData.push_back( 0 ); // constructors
    mMetaData.push_back( 0 ); // flags
    mMetaData.push_back( 0 ); // signalCount
 
    // slots: signature, parameters, type, tag, flags
    int offset = sizeof(objectName)+1;
    for (auto it = mFuncs.begin(), end = mFuncs.end(); it != end; ++it)
    {
        size_t funcNameLength = it->name.size();
        const char* funcName = it->name.c_str();
        mMetaString.insert(mMetaString.end(), funcName, funcName+funcNameLength);
        mMetaString.push_back('\0');
 
        mMetaData.push_back( offset ); // signature
        mMetaData.push_back( 36 ); // parameters
        mMetaData.push_back( 36 ); // type
        mMetaData.push_back( 36 ); // tag
        mMetaData.push_back( 0x09 ); // flags
        offset += it->name.size()+1;
    }
 
    // eod
    mMetaData.push_back( 0 ); 
 
    mMetaObject.d.superdata  = &QObject::staticMetaObject;
    mMetaObject.d.stringdata = &mMetaString[0];
    mMetaObject.d.data       = &mMetaData[0];
    mMetaObject.d.extradata  = nullptr;
}

The idea here is that we maintain the QMetaObject needed to dispatch the call, when qt_metacall is called, to the proper anonymous functions. The magic is done in FillMetaStructs() to maintain the meta object structure each time a new connection is made when Call(…) is called.

This entry was posted in News and tagged , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *