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


shamaz said...

Rivo, you should blog a bit more about astrodoge :). It looks very good
I think this is the first I've ever seen about it. Great work !

Rivo Laks said...

shamaz: yeah, I'll try and blog a bit more often in general :-)
astrododge will most likely be the topic of my next post.

Saurabh Garg said...

I was searching for something exactly like this and came across this blog entry. I just wanted to say thanks as this has made my job much easier. Also I think there is no need for that #define protected public hack. Since GLWidget is inherited from QGLWidget, why not make the necessary functions public in GLWidget?


Rivo Laks said...

@Saurabh: for some reason, I thought that you can't override access levels in C++ and e.g. make protected method a public one. But I just tested and actually you can.
I have to think about it a bit more; it wouldn't be very nice from OOP perspective since actually external classes shouldn't probably be able to call paintGL() or resizeGL(), but then again, the current solution is even uglier, so I'll probably make the change :)

Bernhard said...

Hi Rivo

Great Work! I've also searched for something like that. However, you cannot use the #define protected public hack for Win32. Windows defines private and public entry points with different names in the library, so you will get linker errors. I have done my own hack, which allows to use your lib also under windows. The major changes where only public functions in the GLWidget like:
virtual void myGlInit() {this->glInit();}
virtual bool myEvent(QEvent *event) {return this->event(event);}

and so on for the protected things in GLWidget, which Saurabh mentionned

Then you only have to use these public functions in widgetproxy and it works fine, even under windows.

billconan said...

nice job.

hi, is it possible to manipulate the rendered widget? say, draw the widget on a 3d surface and rotate it in 3D . i want to implement some fancy ui with opengl and qt, but i don't know how to draw my widget on a texture. that's why i searched your blog.