Let’s talk about hybrid applications for a moment. I’m unsure how widely the term is used elsewhere, but in Qt parlance it refers to an application which mixes web and native content. This pattern, for lack of a better word, is seen with some frequency in iOS applications (e.g. GMail and, until recently, Facebook). In the mobile world, it’s an attractive proposition to implement your app once in HTML5, yet deploy what appears to be a native app on any number of platforms. Sounds a lot like the promise of a certain C++ framework…
What then, is the attraction for a desktop application developer such as myself? In the case of my esteemed and wonderful employer, you may have existing web applications (3rd party or otherwise) whose functionality you want to include in your own. This is accomplished through the use of QtWebKit, a Qt implementation of the browser engine used by Safari and Chrome. Simply add a QWebView to your Qt application and point it at the URL of your choice. Voila, a hybrid application!
A browser engine happens to be, among other things, very good at rendering rich text. QTextBrowser may seem the obvious first choice, but supports only a limited subset of CSS. We do enjoy our pretty. For this, and a variety of other reasons, we decided to take advantage of QtWebKit to render certain rich text documents in our applications. Pleased though we were with our rounded borders and fancy stylings, we had blundered into a bit of a problem. These documents weren’t just composed of text, but also images.
Bear with me while I set the stage just a bit further, we’ll be into the meat of this post shortly. For reasons outside the scope of this blog post, images are loaded over the network and cached in a model (the document being only one of many locations where these images are view-able). Were these images stored on an accessible web server, QWebView would know exactly what to do. Another scenario QWebView handles readily, is resolving URLs pointing to a resource compiled into the application binary. Alas, these images are loaded by other means into the application at run-time!
The problem then, is to find a way to render QPixMaps in a QWebView that are loaded into the application during run-time. The solution we went with, was to implement a custom web plugin. Though your mind may jump to thoughts of Flash, Shockwave, et al., it’s nothing nearly so complicated. The principle is the same: we’re enabling functionality the browser doesn’t posses by default.
The critical pieces of creating a plugin are to have a custom subclass of QWebPluginFactory and a QWidget subclass. The role of the plugin factory, is to provide the QWebView any custom plugins written by the application developer. We only need to provide one plugin in this example, so these methods will be pretty easy to implement. In the HTML, you’ll have something along the lines of…
<object type="type/subtype" width="?" height="?" image_id="?"/>
It’s at this point in the HTML document where the plugin will draw an image. Examples of types and subtypes can be found on Wikipedia. The following three arguments are specific to my image plugin, you can add whatever information you think your plugin will need to know to do it’s work. Width and height are obvious, the image_id will be a known identifier for the image I want to be loaded by the plugin.
Somewhere (perhaps in your ui) you’ll have created a QWebView for your application. You’ll need to set your custom plugin factory (in this example, the PluginFactory class), and ensure that the QWebPage rendered in the view has plugins enabled.
QWebView* view = new QWebView(this);
view->page()->setPluginFactory(new PluginFactory(this));
view->page()->settings()->setAttribute(QWebSettings::PluginsEnabled, true);
The QWebPage will now know to talk to our plugin factory when objects of an unknown mimetype appear. The PluginFactory needs to override two methods, create() and plugins(). The former will return the plugin itself as a QObject, while the latter informs the QWebPage of which plugin types it can handle. Note that although the plugin factory returns a QObject, only QWidget and QGraphicsWidget subclasses will work in practice.
class PluginFactory : public QWebPluginFactory
{
PluginFactory(QObject *parent) : QWebPluginFactory(parent)
{
}
QObject *WebPluginFactory::create(const QString &mimeType, const QUrl &url, const QStringList &argumentNames, const QStringList &argumentValues) const
{
if(ImagePlugin::pluginInfo().mimeTypes.contains(mimeType))
return new ImagePlugin(argumentNames, argumentValues);
else
return 0;
}
QList<QWebPluginFactory::Plugin> WebPluginFactory::plugins() const
{
return QList<QWebPluginFactory::Plugin>() ImagePlugin::pluginInfo();
}
}
All my plugin needs to do is render a QPixMap; a QLabel subclass will serve this purpose just fine. The constructor will take in the arguments from the object tag (i.e. height, width, image_id), and grab the image via whatever crazy means you (or I) choose to load the images at run time. Once we’ve retrieved the image, we can set it as the pixmap for the label. The plugin info method is called by the PluginFactory, it identifies the plugin and registers the mime type/subtype it’s responsible for.
class ImagePlugin: public QLabel
{
ImagePlugin(const QStringList &argNames, const QStringList &argValues, QWidget *parent) : QLabel(parent)
{
uint imageId = QVariant(argumentValues.at(argumentNames.indexOf("image_id"))).toUInt();
// retrieve the matching pixmap, however you choose implement this in your application
setPixmap(pixmap);
}
QWebPluginFactory::Plugin ImagePlugin::pluginInfo()
{
QWebPluginFactory::MimeType mimeType;
mimeType.name = "type/subtype";
mimeType.description = "Image loaded at runtime";
QWebPluginFactory::Plugin plugin;
plugin.name = "Image Viewer";
plugin.description = "QLabel to show an image in a QWebView";
plugin.mimeTypes = QList<QWebPluginFactory::MimeType>() mimeType;
return plugin;
}
}
With that, we have implemented a working plugin for our hybrid application! In conclusion, should you find yourself rendering a rich text document with images from a remote server, maybe you should just write a web application instead of creating silly plugins for your hybrid application. Still, pretty cool to be able to write QWidget plugins to handle custom mime types, eh? Embedding a QLabel is a trivial example, you could get really creative thinking up all sorts of weird uses for the power of Qt, embedded in a webpage.
P.S. Blogger dynamic views seem to not take kindly to the syntax highlighting tools I’ve tried (without embedding some code directly into each blog post, which is just blahrg). Once I find a better solution, change templates, or move to a something more accommodating of syntax highlighting, I’ll remove the pastebin embeds.