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

15 comments:

  1. Hi, actually I got a document here explaining how to connect C++ signals to JavaScript functions. If you send me your e-mail I can send it to you. You can reach me at arend at hyves nl.

    Cheers!

    ReplyDelete
  2. I follow the step as you mentioned in your article, however, the progressbar appears in the left-top of the webpage, could I change the plugin's position at running time? my email:guannaixuan@gmail.com
    I hope you could give me some tips~

    ReplyDelete
  3. Can you please send "how to connect c++ signals to java script" document to email:balaji.ramani@siptech.com

    ReplyDelete
  4. @bala: Note that the document describes the connection of signals to JavaScript functions in quite high level terms.

    In the mean time, we have also open-sourced our product, so you can see a real-life example on how to do this as well. You can download it from: http://www.hyves.nl/chat/download/?platform=src
    Please note that you will need to subscribe for a free account at http://www.hyves.nl, but otherwise there are no strings attached :)

    ReplyDelete
  5. Hmm.... http://www.hyves.nl/ is an empty website for me.

    ReplyDelete
  6. Hate to admit this... but it seems we just experienced some downtime. The site should be ok again now.

    ReplyDelete
  7. hello, can you please send "how to connect c++ signals to java script" document to email:arnaudleroy@sfr.fr

    ReplyDelete
  8. I'd like to read that document as well if you still have it (bruckart@gmail.com).

    Great webpage!

    ReplyDelete
  9. Thanks for that
    it helped me to dive into webkit.

    want to add, that the dirname d: (the DESTDIR in the .pro of the zip archive) gave errors in make (multiple targets) so i just renamed it.

    florian

    ReplyDelete
  10. This article was just what I was looking for and helped me over a difficult hurdle with Qt. Thanks for sharing this!

    Don

    ReplyDelete
  11. Howdy, good article here. I've got a working plug-in in qwebkit but I can't get HTML content to render over it (respecting z-index). Do you have any insight here?

    -Rick

    ReplyDelete
  12. Hi Rick,
    personally I don't think this is possible - respecting z-index is something that the renderer of QWebView does. Placing a plug-in into your HTML scene creates a new widget that is on top of the QWebView widget, hence QWebView can't interact with it and is always below it.

    ReplyDelete
  13. This was exactly what I was looking for. Thanks!

    ReplyDelete
  14. Seems to be the only example on the Web on the topic! Thanks!

    ReplyDelete