As Benjamin mentioned in his blog entry, the WebKit API in Qt 4.4 has this cool feature that lets you embed any widget into a QWebView. My first idea was to use this feature to embed Qt Designer forms, using QUiLoader to load the form and Qt Script to script the form logic. After spending an hour or so to get everything set up, I’d like to highlight this as one area where “everything just comes together”(tm) — where the shiny new Qt 4.4 stuff is combined with the oldies-but-goldies features of yore to produce a synergistic consummation that tickles the brain. Hmm, anyway.
On the HTML side, I’m adding a tag like so:
<object type="application/x-qtform" width="500" height="400">
<param name="form" value="http://chaos.troll.no/~khansen/calculator.ui"/>
<param name="script" value="http://chaos.troll.no/~khansen/calculator.js"/>
</object>
The tag identifies a form and script that the plugin will download and use. Next, I create a QWebPluginFactory subclass that WebKit will invoke to create my plugin. Ordinarily I’d write the plugin-related classes in C++, but since the Qt Script Bindings Generator is getting into real good shape now (and because I’m a die-hard script kiddie at heart), I’ve written everything in Qt Script. The plugin factory looks as follows:
function MyWebPluginFactory(parent)
{
QWebPluginFactory.call(this, parent); // call base class constructor
}
MyWebPluginFactory.prototype = new QWebPluginFactory();
MyWebPluginFactory.prototype.create = function(mimeType, url, argumentNames, argumentValues)
{
if (mimeType != "application/x-qtform")
return null;
var formUrl = getArgumentValue("form", argumentNames, argumentValues);
var scriptUrl = getArgumentValue("script", argumentNames, argumentValues);
if (formUrl == undefined)
return null;
return new MyWebPlugin(new QUrl(formUrl), new QUrl(scriptUrl));
}
The QWebPluginFactory::create() function is reimplemented to handle the application/x-qtform mimetype. The URLs of the form and script are passed to the new MyWebPlugin instance; this is the widget that is actually embedded in the view. The MyWebPlugin proceeds to download the form and script and lazily initializes itself as the necessary data becomes available. Here’s the full MyWebPlugin implementation:
function MyWebPlugin(formUrl, scriptUrl, parent)
{
QWidget.call(this, parent); // call base class constructor
this.initialized = false;
this.formReply = this.downloadFile(formUrl, this.formDownloaded);
this.scriptReply = this.downloadFile(scriptUrl, this.scriptDownloaded);
}
MyWebPlugin.prototype = new QWidget();
MyWebPlugin.prototype.downloadFile = function(url, callback)
{
if (this.accessManager == undefined)
this.accessManager = new QNetworkAccessManager();
var reply = this.accessManager.get(new QNetworkRequest(url));
reply.finished.connect(this, callback);
return reply;
}
MyWebPlugin.prototype.formDownloaded = function()
{
var loader = new QUiLoader();
this.form = loader.load(this.formReply);
var layout = new QVBoxLayout(this);
layout.addWidget(this.form, 0, Qt.AlignCenter);
this.initialize();
}
MyWebPlugin.prototype.scriptDownloaded = function()
{
var stream = new QTextStream(this.scriptReply);
this.script = stream.readAll();
this.initialize();
}
MyWebPlugin.prototype.initialize = function()
{
if (this.initialized)
return;
if ((this.form == undefined) || (this.script == undefined))
return;
var ctor = eval(this.script);
if (typeof ctor != "function")
return;
this.instance = new ctor(this.form);
this.initialized = true;
}
The new networking API introduced in Qt 4.4 (QNetworkAccessManager and friends) is used to download the files. (In a more elaborate plugin, you’d probably want to show progress information while data is being downloaded, by using QNetworkReply’s downloadProgress() signal.) Also worth noting is that because QNetworkReply is a QIODevice, creating the form and preparing the script once the downloads are finished is strikingly elegant. The “protocol” used to apply the script to the form is this: The script is evaluated, and if the result is a function, that function is called with the form as argument. The function then hooks functionality to the form’s components. Time to create a view and try it out:
var view = new QWebView();
view.settings().setAttribute(QWebSettings.PluginsEnabled, true);
var factory = new MyWebPluginFactory();
view.page().setPluginFactory(factory);
view.load(new QUrl("script-calculator.html"));
view.show();
QCoreApplication.exec();
Enable plugins, set the plugin factory, load your page and GO!

I’ve taken the HTML for the documentation of the Qt Script Calculator example, which contains an image of the application in action, and replaced the image tag with an object tag that loads the form and script. So rather than staring at a screenshot, you can actually use the calculator right there in the documentation page; the form and the script can be the same as those that the stand-alone calculator app uses. Neat-o.
If you want to play with it, the code can be found in the examples folder of the Qt Bindings generator (WebKitPlugins.qs).
Possibly related posts:
10 comments
And can we hope that this will lead to a web application framework in Qt some day? Perhaps a framework that runs on Apache. Ideally, Qt UIs will be automatically changed into web applications…. Wowww! Qt would be a real total solution for programming of any kind…
I was wondering that it would be great to be able to add SWF flash “apps” as widgets to plasma, since there are litterally tons of flash stuff out there.
Could we add SWF “app(let)s” to the desktop using the QtWebKit integration?
Hi Kent,
thanks for this example. This is exactly what I was searching for in the last days. I hope changing it to pure C++ code won’t be too difficult – but I guess this shouldn’t be too difficult (I don’t know anything about QT Script so far). Time to try it out this weekend
Thomas
Since Plasma will support loading Dashboard Widgets using QtWebKit, I think that’d likely mean you could (possibly requiring a few minor changes to how Dashboard widgets are loaded).
You /may/ not wanna do that, though, as Flash has a tendency to take down the host process with it and isn’t of the highest performance either. With Composite support and the Input Redirection Extension you might be able to do something similar to how video overlays work, having a Plasma widget that includes a large enough blank area, and then in another process creates a single window which is just the flash animation, the composite manager will paint the contents of that window into the Plasma widget, and any input over that plasma widget will be redirected to the actual window hosting the flash animation. Similar to how it works in Konqueror with NSPluginView, but using Composite instead of DCOP (I think it used DCOP?), and it should mean that the Flash plugin couldn’t cause Plasma to crash.
This makes for an interesting target for our QxtWeb library I’m taking a few minutes’ break from hacking on. I was planning on parsing out the .ui file and generating HTML server-side, but this is an interesting and attractive possibility.
Hi Kent,
This makes QtScript much more useful, thanks. There are some performance issues with the bindings as they are currently generated which could be a problem if a QtScript script needs to be run during or shortly after startup.
This is partly because the bindings libraries themselves are large (19M for libqtscript_gui.so.1.0.0 vs. 9M for libQtGui.so.4.4.0 – both compiled in release mode without debug info as far as I can tell) and take a while to load with cold caches but also because the init function for each library creates the prototypes for every class in the library on startup. This also unfortunately uses quite a bit of memory (~17M writeable for the qs_eval tool provided).
Robert: Thanks, we’re aware of the issues you point out and will work to improve this.
Hi Kent,
Does this mean that we have a XUL alternative available for Webkit?
Manik
Hello.
I tried to do it in C++.
It works, but the widget insered into the webpage don’t moved with it when you scroll the webViewer.
It stay always to the same position in the webViewer screen and create artefact until you refresh the webViewer
Have you the same error???
Thanks
Hello.
I test your code with qr4.4RC1 – visual 2009 express.
It doesn’t work
is it normal.
thanks
Comments on this entry are closed.