Thursday, July 24, 2008

How to easily mix QWidgets and OpenGL

Around a month ago there was a post at Trolltech Labs Blogs about embedding widgets in QGLWidget. I had been wanting to do something like that for KGLLib, so I downloaded the demo and took a look. It worked fine, but the code didn't exactly fit my purposes.
The demo did all the OpenGL rendering in the QGraphicsScene subclass. Usually it is done in QGLWidget subclass and I wanted a solution which would be as transparent as possible. Thus the rendering had to stay in GLWidget.
The second problem was the events. The event handling could have been done in QGraphicsView or QGraphicsScene subclass. But if I already have a GLWidget using event handlers, I'd prefer them to continue working as before. So some kind of smart event forwarding was needed to send the events either to the gl widget or to the graphicsview, depending on whether any of the QGV widgets had focus.

I have now solved both problems and the result is a class called WidgetProxy. It takes a GLWidget as an argument and creates a QGraphicsView object, using the GL widget as viewport. The OpenGL rendering is done in your GLWidget as usual and QWidgets can be drawn onto the OpenGL scene. It also tries to do the right thing concerning events: mouse events are sent to whatever is behind the cursor (QGV widget if there is one behind the cursor, otherwise to the GL widget). Keyboard events are sent to whatever has the focus.

It's really easy to use: if you have a toplevel QGLWidget and want to add widgets into it, all you have to do is first create the WidgetProxy object:
WidgetProxy* proxy = new WidgetProxy(myGLWidget);
and then add some widgets:
QWidget* embeddedWindow = proxy->createWindow("Window title"); embeddedWindow->layout()->addWidget(new QLabel("This is an embedded widget!", embeddedWindow));
embeddedWindow->move(10, 10); proxy->addWidget(embeddedWindow);
If the GLWidget is not toplevel but in a layout then the only difference is that you have to add the proxy, not the GLWidget, into the layout.

You can even show() or hide() the GLWidget and it works as before since the WidgetProxy intercepts those events, showing or hiding the QGraphicsView instead.
The aim is to make it possible to have widgets in OpenGL with pretty much a single line of code - often the user of the GL widget doesn't even need to know about it.

The code is now part of my KGLLib, along with an example ("widgets"). KGLLib's code can be found in KDE's SVN, in trunk/playground/libs/kgllib/.