Skip to content

The PySide Effect - work begins on Smoke based QtScript and Python bindings

Monday, 12 October 2009  |  richard dale

I was most surprised when the PySide Python bindings project was announced a few weeks ago. Simon Edwards wrote that "To be honest I'm not all that happy with the current situation." Meanwhile, I wasn't too happy that they had worked for eight months in secret without talking to the KDE bindings community either. I think that the PyQt/PyKDE bindings are very high quality and really well maintained, but if someone insists there really must be an LPGL'd Python binding, I personally would much prefer that it was based on 'Smoke'.

The actual effect of the announcement on me, has been to stimulate a flurry of activity. It is a bit similar to when Trolltech announced that they were working on a Java binding, QtJambi, a year or two ago. I found out about it on one of Aaron's blogs, and for me it meant that over two man years work was totally down the tubes. But I didn't get depressed about it, as I was pretty sick of working on Java bindings by then. I don't even like Java much, and the QtRuby bindings were proving much more popular, and it was starting to feel like I was only flogging a dead horse. However, I had been designing a version of both Java and C# bindings for Qt4 that made use of Proxies/Transparent proxies, and I didn't want to waste the design. I went into high gear after the QtJambi announcement and really got going on C# stuff, inventing the name 'Qyoto' for the project about a week later. I blogged about the dramatic progress I had made, Arno Rehn joined the project soon after. Arno has made some pretty major contributions to the Smoke bindings in general, and not just C#. So if it wasn't for the stimulus of the QtJambi competition, we would have certainly been worse off.

So the 'PySide Effect' has been pretty similar for me to the 'QtJambi Jolt'; they were both like giant attacks on my bindings projects immune systems, but have actually had very healthy effects in the long term.

I started studying the Python C api, and reviewing the code for various Python bindings projects, and thinking about how a Smoke based implementation would work. I sent my findings to the PySide mailing list Using 'Smoke' as a basis for a Qt Python binding, and started a lengthy thread. The PySide guys were pretty reasonable considering how critical I was of their current approach using Boost::Python, and even suggested that I set up an experimental PySide clone of a PySmoke binding in their gitorious project.

Meanwhile, the Amarok guys had been having problems with the large size and slow startup time of the Qt Labs QtScript bindings. As a result of the discussion on Simon's blog about the PySide announcement, Ian Monroe did a 'Proof of Concept' of a Smoke based QtScript binding. By following the documentation on TechBase he was able to very nearly get started. He just needed my help with an extra thing you had to do when an instance had been created, which I think we had left off the docs as it was quite new. So I spent a couple of hours sorting that problem out, and mailed a patch to Ian to get the 'hello world' app working.

After not much more than two man weeks work, the Smoke QtScript bindings are really starting to come together. You can call methods, override virtual methods, the marshallers for all the Qt types like lists and so on are pretty complete. The main thing that isn't implemented yet are slots and signals in the QtScript style. I got the AnalogClock.qs example working, by adding C++ style SLOT() and SIGNAL() functions like this:


function SLOT(str) {
    return "1" + str;
}

function SIGNAL(str) {
    return "2" + str;
}

function AnalogClock(parent) {
    QWidget.call(this, parent);

    var timer = new QTimer(this);
        
    // FIXME: We need to implement slots/signals handling
    //timer.timeout.connect(this, "update()");
    
    // Use C++ style syntax for now:
    this.connect(timer, SIGNAL("timeout()"), this, SLOT("update()"));

    timer.start(1000);

    this.setWindowTitle("Analog Clock");
    this.resize(200, 200);
}

AnalogClock.prototype = new QWidget();

AnalogClock.prototype.paintEvent = function() {
    var side = Math.min(this.width(), this.height());
    var time = new Date();

    var painter = new QPainter();
    painter.begin(this);
    painter.setRenderHint(QPainter.Antialiasing);
    painter.translate(this.width() / 2, this.height() / 2);
    painter.scale(side / 200.0, side / 200.0);

    painter.setPen(new QPen(Qt.NoPen));
    painter.setBrush(new QBrush(AnalogClock.hourColor));

    painter.save();
    painter.rotate(30.0 * ((time.getHours() + time.getMinutes() / 60.0)));
    painter.drawConvexPolygon(AnalogClock.hourHand);
    painter.drawLine(0, 0, 100, 100);
    painter.restore();

    painter.setPen(AnalogClock.hourColor);

    for (var i = 0; i < 12; ++i) {
        painter.drawLine(88, 0, 96, 0);
        painter.rotate(30.0);
    }

    painter.setPen(new QPen(Qt.NoPen));
    painter.setBrush(new QBrush(AnalogClock.minuteColor));

    painter.save();
    painter.rotate(6.0 * (time.getMinutes() + time.getSeconds() / 60.0));
    painter.drawConvexPolygon(AnalogClock.minuteHand);
    painter.restore();

    painter.setPen(AnalogClock.minuteColor);

    for (var j = 0; j < 60; ++j) {
        if ((j % 5) != 0)
	        painter.drawLine(92, 0, 96, 0);
        painter.rotate(6.0);
    }
    painter.end();
};

AnalogClock.hourColor = new QColor(127, 0, 127);

AnalogClock.minuteColor = new QColor(0, 127, 127, 191);

AnalogClock.hourHand = new QPolygon([new QPoint(7, 8),
                                     new QPoint(-7, 8),
                                     new QPoint(0, -40)]);
AnalogClock.minuteHand = new QPolygon([new QPoint(7, 8),
                                       new QPoint(-7, 8),
                                       new QPoint(0, -70)]);

var clock = new AnalogClock();
clock.show();

This morning I compared the QtScript Smoke AnalogClock with the Qt Labs one, and Python and Ruby versions. In terms of memory use and start up time, it is the best of the lot. About 10 times less memory use and 3 times faster than the Qt Labs one. On my very slow netbook (1.2 MHz Via7), it take about 10% CPU time to refresh the clock once a second, while Qt Labs QtScript takes 33%! According to the KDE System Monitor, the QtScript Smoke clock takes up 2.3 Mb, while the Qt Labs one takes up 20.2 Mb. In comparison both Ruby and Python were smaller that the Qt Labs clock, but more than twice the memory still of the Smoke based QtScript one. Python and Ruby briefly consumed 1% of the CPU, to update the clock once a second. So it seems that QtScript is pretty dog slow from this test whichever version you use, and I don't know if it will be a problem with a Plasma binding yet.

Anyhow, please have a look at the project on gitorious and try it out - I really think it is nearly useful. Any help with getting it finished or performance measurements is really welcome.