Back to Blog home

WebKit, Designer Forms and Scripting

Published on Friday April 11, 2008 by Kent Hansen in Qt Bindings Generator Qt Script | Comments

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).

Subscribe to Our Blog

Stay up to date with the latest marketing, sales and service tips and news.

The blog comment system has been migrated to a new platform. If you face any issues, please let us know via feedback@qt.io.