FEB
15
2009

QMetaObject/GObject-introspection inter-operability progress

I've been hacking on deriving QMetaObjects for GObject-Introspection data as I described in a recent blog and am making good progress. I've now built a complete heirarchy of QMetaObjects from the gobject-introspection Clutter module which I've been using for testing.

I've checked the current code into playground/bindings/smoke_gobject so other people can play with it. It turns out that QMetaObject and the GObject Introspection models are pretty compatible. They both have single inheritance, properties, signals, runtime method calling (slots in Qt or or dynamic function invocation via ffi in G-I), enums and so on. So we need to add a dynamic runtime to call G-I methods as Qt slots or forward GObject signals to Qt ones. But as far as I can see it looks perfectly doable.

In playground there is code for dumping a .gir module and for generating the QMetaObjects. There are three classes; Smoke::MetaObjectBuilder for generating the QMetaObjects from the G-I repository, Smoke::GObjectProxy for invoking methods on the GObject classes via qt_metacall(), and a Smoke::GObectNamespace class for each G-I namespace with the constructor methods as slots and all the enums and flags for a namespace.

A dump of the Clutter QObjectNamespace instance looks like this:

    Smoke::MetaObjectBuilder::createNamespace("Clutter");
    Smoke::GObjectNamespace * gobjectNamespace = Smoke::MetaObjectBuilder::findNamespace("Clutter");

    const QMetaObject* metaObject = gobjectNamespace->metaObject();
    for(int i = metaObject->methodOffset(); i < metaObject->methodCount(); ++i) {
        printf("%s\n", metaObject->method(i).signature());
    }

// Gives this output:

Actor*Actor(Clutter::Vertex*,Clutter::ActorBox*)
Stage*Stage()
Stage*Stage()
Shader*Shader()
Timeline*Timeline(uint,uint)
Timeline*Timeline(uint)
Alpha*Alpha()
Alpha*Alpha(Clutter::Timeline*,Clutter::AlphaFunc*,void*,GLib::DestroyNotify*)
Group*Group()
BehaviourBspline*BehaviourBspline(Clutter::Alpha*,Clutter::Knot*,uint)
BehaviourDepth*BehaviourDepth(Clutter::Alpha*,int,int)
...

Or dumping the enums and flags for the namespace gives this output:

    for(int i = metaObject->enumeratorOffset(); i < metaObject->enumeratorCount(); ++i) {
        QMetaEnum enumerator = metaObject->enumerator(i);

        printf("%s::%s\n", enumerator.scope(), enumerator.name());
        for (int j = 0; j < enumerator.keyCount(); ++j) {
            if (enumerator.isFlag()) {
                printf("    %s = 0x%4.4x\n", enumerator.key(j), enumerator.value(j));
            } else {
                printf("    %s = %d\n", enumerator.key(j), enumerator.value(j));
            }
        }
    }

// Gives this output:
Clutter::Gravity
    None = 0
    North = 1
    NorthEast = 2
    East = 3
    SouthEast = 4
    South = 5
    SouthWest = 6
    West = 7
    NorthWest = 8
    Center = 9
Clutter::RotateAxis
    XAxis = 0
    YAxis = 1
    ZAxis = 2
Clutter::RotateDirection
    Cw = 0
    Ccw = 1
Clutter::RequestMode
    HeightForWidth = 0
    WidthForHeight = 1
Clutter::ModifierType
    ShiftMask = 0x0001
    LockMask = 0x0002
    ControlMask = 0x0004
    Mod1Mask = 0x0008
    Mod2Mask = 0x0010
    Mod3Mask = 0x0020
    Mod4Mask = 0x0040
    Mod5Mask = 0x0080
    Button1Mask = 0x0100
    Button2Mask = 0x0200
    Button3Mask = 0x0400
    Button4Mask = 0x0800
    Button5Mask = 0x1000
    ...

The other QMetaObjects have slots, signals and properties for each GObject class with the names converted to camel case and Qt types replacing the GTK ones. It looks pretty 'Qt-like' to me. I had originally been thinking of making the binding entirely dynamic, but I now think it would be nice to optionally generate a C++ class heirachy as that wouldn't be too hard. That's all for now - watch this space for more news..