2009/07/01

Qt and Javascript^WQtScript

Today (at dawn) I had my first experience with Javascript inside Qt. The language is actually referred as QtScript by Qt documentation. The thing is simply great. Executing simple Javascript code is simple as


QScriptEngine engine;
qDebug() << engine.evaluate("a = 1; b = 2; a + b;").toInt();

As expected, the "protocol" between C++ and Javascript objects has its rules. Javascript can't "see" the current application's context. For exemple, if we want to make an object available to Javascript, we must add it to the global scope:

engine.globalObject().setProperty("close_button", closeButton);

When an object is made available in Javascript context, the following features (among others) are made available from Javascript side:

a) properties can be set and get using the obj.property syntax. Yet another incentive to use Qt properties as much as possible in our C++ classes.

b) public slot methods can be called (as normal methods) from Javascript, since Qt has introspection information about them. Too good that exporting methods as slots costs nothing.

c) signals and slots can be used (connected and fired at will). Slot functions may be written in Javascript. The only limitation is that signals must be defined in C++; you can not define a new signal in Javascript.

Non-slot methods must be encapsulated to be made available. The easiest way is to connect them to properties, which in turn are always available in Javascript space.

Perhaps unexpectedly, class constructors are not made automatically available to Javascript. Constructors and C++ free functions must be added to Javascript by encapsulating them in QScriptValue objects and then adding them to Javascript scope the same way we did with closeButton.

This means that, for now, QtScript can not be used to write whole applications, because it always depends on a C++ supportive "shell" to make the right objects and classes available for scripting.

I guess the problem can be mitigated somewhat by using factories in C++; if you use factory("AnyClass") to instantiate objects, it is just a matter of exporting the factory() to Javascript. Of course, this leaves the Qt classes themselves out.

If we want to use some Javascript in our Qt apps, there are some architectural trends that we must follow. Using properties, signals/slots and factories instead of static method calls everytime we can. And leave behind the anal-retentive mania of keeping most methods protected/private. Those techniques make life easier in C++ anyway, so why not?

2009/06/29

Dynamic-language tricks in Qt

Signals and slots: implementation

It's funny how, after all those years, only now I moved my ass to actually know how Qt signal/slot mechanism is actually implemented. I guess the main reason is, after having known languages like Python and Objective C, it is kind of instinctive to search for dynamic features in Qt, to the point that it feels impossible to do something with bare C++ (without Qt Core at least).

In the past, this mechanism just sounded like yet another UI toolkit event handling scheme, with the extra handicap of being "impure C++" (based on an IDL compiler). Good times back then, when I believed in C++.

Well, I took the time to read the Qt headers and the MOC generated code, and I must say I envy the guys that created it, in particular because they did it back then at 1990s. Some random details:

1) "signals", "slots", "emit" and other Qt words are #define'd to absolutely nothing, so they don't cause any special behavior in developer's C++ code, which can be compiled and debugged normally.

2) Slots are normal C++ methods, laid out by the developer. The only special thing is that MOC adds some introspection information about them in moc_classname.cpp: method name, which includes the signature (i.e. parameter count and typing), and a big switch/case statement which maps slot string names into slot method calls.

3) Signals are also normal C++ methods; that's why "emit method()" works even though emit is #define'd as nothing. But the developer does not write signal method bodies; MOC does that in moc_classname.cpp. Of course, the signal method's body just forwards the call to Qt's signal dispatcher.

4) Signal methods cast all parameters to void* and put them in an array -- a very opaque way of marshaling the parameters. This array is passed to the signal dispatcher.

5) The switch/case that translates slot names into actual method calls does the "unpacking" of this array of void*s. It does that believing blindly that the array contains the expected parameter types and quantity.

6) Since signal and slot names include the parameter signature, it is easy to test whether a given signal and a given slot have compatible signatures; it is just a matter of comparing strings character-wise. Better yet, it just needs to be tested at connect(). That's why the void* array of parameters can be casted "blindly" back to the original types.

7) The switch/case includes signals, too, so they can be invoked dynamically as well as slots. The purpose of this seems to be signal cascading (connecting a signal to another signal).

8) SIGNAL() and SLOT() simply convert what is inside parenthesis into a common string, which is the method signature. It's a simple macro #argument trick.

Thinking about the fact that every signal method has a real implementation, signals/slots feel more like a delegation mechanism (and not just an UI event propagation thing), because calling a (signal) method on one object causes the call of another (slot) method on another object.

Beyond connections: dynamic method calling

Given that enough introspection information about slot methods is stored in moc_sourcecode.cpp, the next question is: how can I invoke dynamically a method, without having to connect to a signal. It seems that it was not (easily) possible before Qt 4. In Qt 4, there is a

QMetaObject::invokeMethod(object, method_name, param1, param2...)

that does the desired trick.

Problem is, how this call will know parameter types, so the "call signature" can be verified against target slot's signature? One solution would be to trust blindly our parameter list -- if it doesn't fit the target method signature, a segmentation fault would take place. Not exactly an elegant solution.

The actual Qt solution is to wrap every argument with Q_ARG(). The argument types must be known by QMetaObject system so it "knows" the type and can do all checks and conversions. You can register your own classes in QMetaObject, allowing almost anything to be passed as Q_ARG(). If the method invocation has a wrong signature, a friendly error message will be displayed by Qt.

Google Search returns very few entries for "QMetaObject invokeMethod". I guess this technique is rarely employed. But I see a lot of potential in it. (I'll never get used to using multiple inheritance in C++ just because we need to call *one* slimy observer method, or using downcasts pervasively.)

Properties

The meta-object system, as well as the ability to register new types, is important in another Qt cornerstone: properties. Every object which is descendant of QObject can have properties added, set and recovered dynamically, without any previous specification in header file:

obj->setProperty("bla", 1);

int x = obj->property("bla").toInt();

As expected, object->property() can not return the desired property directly, because properties may be of any type. This method returns a QVariant object, which in turn can be converted to the native type.

Using Q_PROPERTY() in the class header is needed only if you want the property to have collateral effects (i.e. cause a method call) when read and/or written. The methods abovementioned will still work for such getter/setter properties..

A more radical approach to add collateral effects to properties, is to extend property() and setProperty() methods themselves, intercepting every property request and potentially acting upon all of them. Those QObject methods are virtual, so it is a perfectly legal trick.

Of course, all those tricks are commonplace in Python and some of them are easy to do even in Objective C, but allowing those things in C++ is another story.

2009/06/12

Livro: A cabeça de Steve Jobs

Eis um livro que puxa o saco do Steve do início ao fim, fazendo um #define defeito qualidade. Na verdade, o livro deveria ter o título de "A cabeça do fanboy Apple". Embora eu mesmo seja, no tempo presente, um fanboy , nunca consegui entender por que tinha gente em 2000 (colegas da Conectiva, #d00dzers) que pagava pau para o Mac com um sistema operacional bonitinho, porém muito instável e incompatível com tudo e todos (e na época, a definição de "razoavelmente estável era o Windows 98, mind you!).

O fato mais incrível citado no livro era que, nos tempos negros da segunda década de 1990, quando os Macs estavam relegados ao fundão das lojas, fanboys davam plantão como "vendedores" voluntários sem qualquer remuneração, tentando convencer pessoas que iam comprar um PC a levar um Mac...

Não admira o livro saiu barato; o filme "Piratas de Silicon Valley" foi muito mais esclarecedor, ao menos a respeito dos fatos até 1997.

2009/06/10

Hack of the day: making MF 622 (Claro 3G modem) work on Mac, without the CD

I was set to make my ZTE MF622 modem (Claro 3G Internet plan) with my Mac. The supplied CD has the Mac driver/application, but I did not have the CD around this time.

After a lot of unfruitful search for Mac drivers for this modem, I stumbled with MF626 drivers in the Australian site of ZTE manufacturer: http://www.zte.com.au/main/zte-downloads.htm. There is no driver for MF622 in there, but there was a MF626 driver. 626 is "near" 622, and I had a vague notion that both modems have the same USB ID, so they could be driven by the same application.

It was a triple leap of faith: an application tailored not for my telecom and not for my modem, and coming from Australia (sorry, I could not resist to joke :) No better options at hand, I tried it, configuring a Claro account. It worked! The only "special" detail is that APN must be configured to bandalarga.claro.com.br. Without that, it does not work.

This APN thing seems to be the only roadblock to configure the GSM modem directly on System Preferences. Setting APN translates to a simple AT command to the modem, but in order to insert an AT command in Mac, I'd probably need to create a new modem profile... something to be played with in a rainy Sunday.

I will keep this Telstra package in a safe storage for sure. The page seems to be maintained, so there will likely be updates when Slow Leopard is released.

2009/06/09

(UPDATED) Compositing window manager in Linux under VMWare?

Prima facie it seems not to be possible to enable compositing (*) in Linux running as a virtual machine, Trying to "enable desktop effects" in Ubuntu does not work. But xorg-vmware driver supports compositing out of the box, without any configuration needed on xorg.conf. So what?

The trick is to activate it directly on Metacity window manager GConf, without configuring any desktop effects. Opening gconf-editor, going to apps -> metacity -> general and enabling compositing_manager solves the issue.

Once you do this, window shadows show up immediately. Gnome-terminal can display a truly transparent background (once reloaded). I had some Qt examples that did translucent windows and OSX widget-like screen elements, and they all worked after Metacity configuration.

This was tested with Ubuntu Intrepid running as a virtual machine in VMWare Fusion 2.04. I am not sure whether "enabling desktop effects" fails because of some bug or because it needs OpenGL. By the way, OpenGL applications also work in that Linux, albeit slowly (tested with Billard-GL). If OpenGL were the problem, desktop effects would be just slow.

(*) Composition: X11 extension that allows windows to be rendered in offscreen buffers, and then composed together on-screen by the window manager. This feature allows truly translucent windows like Mac OS X, and also allows transformations and manipulations on window images. Composition is obviously faster when hardware OpenGL can be used for that.