Wednesday, December 3, 2008

Embedding Qt Widgets into QtWebKit

Qt has it's awesome built-in WebKit support which makes it extremely easy to have full-featured browser/html-viewer capabilities in your application (including JavaScript!).


It is possible to embedd any Qt Widget into your QWebPage. The necessary steps for that are quite simple. You have to derive from QWebPage and overload the createPlugin() function, make sure that PluginsEnabled is set for the QWebPage's settings and assign that WebPage to any QWebView.


It is now possible to embed widgets that are known to Qt's runtime MetaType-system into a WebView. You can make a widget accessible using both, the Q_DECLARE_METATYPE macro and the qRegisterMetaType function.


To show the widget, you have to add an HTML object-Tag to your page, like this:


<object type="application/x-qt-plugin"; classid="YourClass" name="myObject" />
It's now visible and can even be manipulated through JavaScript. You can access it's properties and it's public slots.


I've create a small demo that shows you how to do it and what is possible. It consists of a QMake-project (.pro-file), two pairs of header and implementation files for the MyWebKit/MyWebPage and MyWidget classes and a demo HTML page. It should compile and run on any supported platform (Windows, Linux, Mac). Of course only when QtWebKit is enabled in the Qt installation.


Step 1


First, we should derive from the necessary QtWebKit-classes to create our own MyWebView class that always has Qt plug-ins enabled.


MyWebKit.h



#ifndef MY_WEBKIT_H
#define MY_WEBKIT_H
#include
#include

// Derive from QWebPage, because a WebPage handles
// plugin creation
class MyWebPage: public QWebPage
{
Q_OBJECT
protected:
QObject *createPlugin(
const QString &classid,
const QUrl &url,
const QStringList ¶mNames,
const QStringList & paramValues);
public:
MyWebPage(QObject *parent = 0);
};

// Derive a new class from QWebView for convenience.
// Otherwise you'd always have to create a QWebView
// and a MyWebPage and assign the MyWebPage object
// to the QWebView. This class does that for you
// automatically.
class MyWebView: public QWebView
{
Q_OBJECT
private:
MyWebPage m_page;
public:
MyWebView(QWidget *parent = 0);
};

#endif


MyWebKit.cpp



#include "MyWebKit.h"

#include

MyWebPage::MyWebPage(QObject *parent):
QWebPage(parent)
{
// Enable plugin support
settings()->setAttribute(QWebSettings::PluginsEnabled, true);
}

QObject *MyWebPage::createPlugin(
const QString &classid,
const QUrl &url,
const QStringList ¶mNames,
const QStringList & paramValues)
{
// Create the widget using QUiLoader.
// This means that the widgets don't need to be registered
// with the meta object system.
// On the other hand, non-gui objects can't be created this
// way. When we'd like to create non-visual objects in
// Html to use them via JavaScript, we'd use a different
// mechanism than this.
QUiLoader loader;
return loader.createWidget(classid, view());
}

MyWebView::MyWebView(QWidget *parent):
QWebView(parent),
m_page(this)
{
// Set the page of our own PageView class, MyPageView,
// because only objects of this class will handle
// object-tags correctly.
setPage(&m_page);
}

It's now possible to use Qt classes using the above-mentioned object tags.


Step 2


The second step is to create a class that's known by the Qt runtime meta type system. We can't directly use Qt widgets in this way, because runtime meta types need copy-
constructors. So we derive from a Qt widget and add a kinda dull copy-constructor to it.

MyWidget.h



#ifndef MY_WIDGET_H
#define MY_WIDGET_H

#include
#include

class MyCalendarWidget: public QCalendarWidget
{
Q_OBJECT
public:
MyCalendarWidget(QWidget *parent = 0);
// Q_DECLARE_METATYPE requires a copy-constructor
MyCalendarWidget(const MyCalendarWidget ©);
};
Q_DECLARE_METATYPE(MyCalendarWidget)


#endif

We use a calendar widget because it's something that doesn't already exist in HTML and could be quite useful. Additionally, it has some few properties that we'd want to access from JavaScript.


Step 3


The final step is to build the HTML page that embeds the widget and executes some JavaScript on it. Here's my example:

Test.html



<html>
<head>
<title>QtWebKit Plug-in Test</title>
</head>
<body>
<object type="application/x-qt-plugin" classid="MyCalendarWidget" name="calendar" height=300 width=500></object>
<script>
calendar.setGridVisible(true);
calendar.setCurrentPage(1985, 5);
</script>
</body>
</html>

The example set the gridVisible property to true and shows the month that I am born in. Of course, the possibilities seem endless! :-)


Conclusion


The only thing that I am still missing is connecting signals to JavaScript functions, similar to what you do with AJAX. It's possible to export non-visual objects and use them from within JavaScript, too (think of a database connection, for example).


You can download the complete project, which should work out-of-the-box when you have Qt with WebKit support installed, here