Appendix B - OpenSG with QT

underscore.png

Chapter Overview


>General
>QTDesigner
>Compiling
>Passive Window with QT / Plain OpenGL Code


Throughout the tutorials we used a GLUT Window for rendering, but as I mentioned earlier, there are some more options. QT (from Trolltech - www.trolltech.com) is a very common and powerful cross platform tool for rapid GUI development. This appendix will show you how to create a simple application that is capable to render OpenSG content, though this will not (and can not) be an introduction to QT development. If you are new to QT, have a look at their website where you can find some tutorials dealing with QT stuff only. Additionally the last section will deal with a passive window that is integrated in an QT environment.

General

We are going to develop a simple application that features one incredible button which will allow us to load a file from disk. The most space will be taken by the area in which the model will be rendered in. Here is an image what the application will look like. The model was loaded by clicking the load button and then selecting the appropriate file with a standard dialog.

qtdesigner_final.png

The application we will create

I will show you step by step what you have to do, but I also assume that you have some basic knowledge of QT. However, don't worry if you have not - this one was my very first QT application ;-)

Prerequisites

Well, you might have guessed it, you will need QT. On nearly every Linux box running a X server, QT will be already installed. On Windows and Mac OS X, you have to download and install it. You can get it from http://www.trolltech.com. Please note, that if you need to compile QT yourself, that it might take more than a hour to finish. Make sure to also compile the "designer" tool, an application that will allow you to develop QT GUI's with ease (if you know where to click what). Please follow the installation instructions found within the package you downloaded.

In addition to that, you need to compile OpenSG with QT support (Windows binaries have it already compiled in). Here is an example of the configure command

./configure --enable-glut --enable-tif --enable-png --enable-jpg --enable-qt --with-qt=/usr/qt/3/

The configure script might be able to find the location of qt itself, if it does not you have to add the last parameter.

Note: Your path to QT might be different. If you compiled it yourself, it is possibly /usr/local/qt/3/

The rest of the process is just the same as usual. You are now ready to create QT powered OpenSG applications... or OpenSG powered QT applications ;-)

QTDesigner

We start our work with the designer from QT. On Linux boxes this application is found in "your_qt_folder/bin". I believe Windows users will have an entry in their start menu. After startup, you should see a window like below - if it is totally different, consider that you may have started the wrong application.

qtdesigner_newdialog.png

Designer window after startup

As we are not going to develop a complex application with multiple windows, we need not to create a project. Just select the "Dialog" icon, as seen above, and click on "OK".

You see now an empty window in the middle of the screen titled with "Form1", which is actually your application. On the right hand side you have two toolbox windows named "Object Explorer" and "Property Editor/Signal Handlers". The latter will be used to set attributes of the selected object, like the size or name of the window. On the left hand side is the "Toolbox" where you can select different GUI elements to place in your window.

Building the GUI

First of all we drop in all GUI elements we will need. In the toolbox select "Common Widgets", if not already selected. Now click on the first entry "PushButton" and drag a little rectangular area at the top of the "Form1" window. Choose a reasonable size for a button. Now again in the toolbox select "Custom Widgets", where you should find a "OSGQGLManagedDesignerWidget". If not, depending on your installation, you need to create it first. If you have it installed you can skip the next section

Creating the OSGQLManagedDesignerWidget

In the menu bar select "Tools > Custom > Edit Custom Widgets...". You should see the following window, which might be totally empty

qtdesigner_customwidget.png

Creating a new custom widget

Click on the "New Widget" button and edit the attributes found on the right hand side. Make sure the "Definition" tab is selected. Enter the following

Class : OSGQGLManagedDesignerWidget
Headerfile : OpenSG/OSGQGLManagedDesignerWidget_qt.h
Dropdownfield : Global (instead of local)

All other options should be left as they are. Please make sure that everything is spelled correctly, else your application will not compile correctly.

Place the Custom Widget

Now select the OSGQGLManagedDesignerWidget, if you haven't already, and drag a rectangle that will take up all space left. Your window "Form1" should now look like this

qtdesigner_form1.png

The QT window

Edit the Properties

Now we are going to give our elements some reasonable names and values. If you select an object (like the button or the custom widget), you will see the corresponding properties in the "Property Editor/Signal Handlers" Window on the right side. Select the push button and change it's name from "PushButton1" to "loadButton". The caption of the button will not change, as we altered the internal name only - look for the property called text and change the value to "load". Now the button says "load".

Change the name of the OSGQGLManagedDesignerWidget to "osgWidget". Finally select the window itself and change the name from "Form1" to "MainWindow" as well as the "caption" property from "Form1" to "OpenSG Viewer".

Creating a Slot

The button has no function yet, so we will change that know. Select the load button and then click on the "members" tab found in the object explorer. Click with the right mouse button on the line that says "Slots" and select "Edit..." from the context menu. The "Edit Functions" window will appear. Click on "New Function". Enter "load()" instead of newFunction() as function name. Also change the specifier "virtual" to "non virtual" and type from "function" to "slot". The window should now look like this

qtdesigner_editfunctions.png

Edit Functions window

Click on "OK" to close that window. We need to connect the parts with each other. Press F3 on your keyboard and click on the load button. In the new window click on "No Signal" below Signal and select clicked(). Set Receiver to "MainWindow" and finally set slot to load() - click OK.

Now click on the load() slot, found beneath the public specifier. That will open a new window, where you find the body of the load() function. Here we can add code to perform the actions we want it to do. Replace the contents of the file with the following code - of course you can leave out the comments...

#include <qfiledialog.h>
#include <OpenSG/OSGSceneFileHandler.h>
#include <OpenSG/OSGSimpleGeometry.h>

void MainWindow::load()
{
    // this command will open a file select dialog, allowing wrl, osg, and bin file types. The selected
    // filename along with it's path will be stored in 's'
    QString s(QFileDialog::getOpenFileName(QString::null, "Szenenformate (*.wrl *.osg *.bin)"));
    if (!s.isEmpty()) 
    {
        char *filename;
        //create a correct filename
        filename = (char*)s.latin1();
        NodePtr n = SceneFileHandler::the().read(filename);

        //load that file - if it fails for some reason, we crate a torus
        if (n != NullFC)
            osgWidget->getManager().setRoot(n);
        else
            osgWidget->getManager().setRoot(makeTorus(0.5,2,16,16));
    }
}

Now it is about time to save everything. Select "File > Save All". I advise you to create a new folder - let us name it OpenSGViewer. QT suggests the filename "mainwindow.ui", which is alright for us. Just click "OK" after you have switched to the new folder. If you have a look in that folder you will see that there are two files now: "mainwindow.ui" and "mainwindow.ui.h". The latter contains exactly what we have written in the editor, where as the first one is created automatically by QT and describes the GUI.

Main File

For a complete application, the important main() function is still missing. So using your favorite editor, create a file called main.cpp and save it at the same location as both other files. Add this code to that file

#include <qapplication.h>
#include <OpenSG/OSGConfig.h>
#include <OpenSG/OSGNode.h>

#include "mainwindow.h"

OSG_USING_NAMESPACE;

int main(int argc, char **argv)
{
    osgInit(argc, argv);
    
    QApplication app (argc, argv);

    MainWindow main;
    app.setMainWidget(&main);
    main.show();

    int result = app.exec();
    return result;
}

Actually this is quite simple. At the very beginning we initialize OpenSG (as usual) and QT. Afterwards we create an instance of our own widget, set it as the applications main window and execute it.

Compiling

Compiling involves three more steps than usual, because the designer does not produce C++ code right away. This is intend, because in this way it is much easier to extend an application with the designer, without destroying code that was added in between. Execute the following commands one after another. First we create C++ source files from the generated ui file.

This will generate the header file

	uic -o mainwindow.h mainwindow.ui

and this the cpp file

	uic -i mainwindow.h -o mainwindow.cpp mainwindow.ui

Now invoke the next command

	moc -o moc_mainwindow.cpp mainwindow.h

and finally the "usual" compiler command

	g++ -D_GNU_SOURCE -DQT_CLEAN_NAMESPACE -DOSG_WITH_GLUT -DOSG_WITH_QT -DOSG_WITH_TIF 
	-DOSG_WITH_PNG -DOSG_WITH_JPG -I$QTDIR/include -I/usr/local/include -ansi
	-f-template-depth-100 mainwindow.cpp main.cpp moc_mainwindow.cpp -L/usr/local/lib/dbg
	-L/usr/X11R6/lib -L$QTDIR/lib -lOSGWindowGLUT -lOSGWindowQT -lOSGSystem -lOSGBase -lGLU
	-lGL -lglut -lqt -lXmu -lXi -lXt -lpthread -ldl -o OpenSGViewer

Yes, really that is one single command! However, you might have to adjust some things. First, if you build the optimized OpenSG library you have to use -L/usr/local/lib/opt instead of -L/usr/local/lib/dbg. Also you have to make sure $QTDIR is defined correctly. If not you can also specify the full path here. I hope your terminal supports copy and paste ;-)

After compiling you can execute the application with

	./OpenSGViewer

Click on the load button and select any valid file - if you have no such files on your own, take one from the tutorial/progs/data folder. Maybe you have to move the camera to see anything, depending on the file you loaded.

You can find all source files in the tutorial/progs/qt folder!

Here you go!

Passive Window with QT / Plain OpenGL Code

In section Windows you have learned that passive windows do not handle OpenGL contexts and are designed to fit easily into existing applications. I will present to you another example application which will use a passive window and still offering all basic features we used so far, like loading of files and navigation. This example application is implemented with QT, too, but it should be no big deal to use any other window manager like MFC, xwWidges, etc. (it is just that I have no clue how MFC stuff works ;-), but if you do, feel free to contribute an example...)

If you read the online tutorial right from the beginning, you have seen a lot of different problems and solutions and you have rendered a few million polygons with OpenGL so far - but you have never ever used a single OpenGL command, like Vertex3f(....) or the like. Well, that actually should not bother us, but what if you want or even have to insert plain OpenGL code for some reason? Using the tools you know so far this would not be possible, but as you might expect, this will be possible by using a passive window. This, too, I will show you in the upcoming tutorial.

This time we don't need the designer (although you can, if you want to design a more advanced GUI), but we will have a window class derived from QGLWidget. Please do not mistake this with OSGQGLManagedDesignerWidget we used it the previous application. The QGLWidget has really nothing to do with OpenSG and thus does not support any OpenSG functionality on it's own. As the name already says, it offers support for OpenGL.

Let us have a look at the code first, here is the header file OpenSGWidget.h, found in progs/qt/OpenSGWidget.h

#include <qgl.h>
#include <qapplication.h>
#include <OpenSG/OSGConfig.h>
#include <OpenSG/OSGSceneFileHandler.h>
#include <OpenSG/OSGSimpleGeometry.h>
#include <OpenSG/OSGPassiveWindow.h>
#include <OpenSG/OSGSimpleSceneManager.h>

OSG_USING_NAMESPACE

// our widget class will be derived from QGLWidget,
// a widget offering support for plain OpenGL

class OpenSGWidget : public QGLWidget{
public:
    OpenSGWidget( QGLFormat f, QWidget *parent=0, const char *name=0 );
    SimpleSceneManager *getManager(void);

protected:
    // these replace the callback functions
    // you know from the other tutorials
    void initializeGL();
    void resizeGL(int, int);
    void paintGL();
    void mousePressEvent(QMouseEvent *ev);
    void mouseMoveEvent(QMouseEvent *ev);
    void mouseReleaseEvent(QMouseEvent *ev);
    void wheelEvent(QWheelEvent *ev);

    SimpleSceneManager *mMgr;
    // no GLUT window this time, but a passive one
    PassiveWindowPtr mPwin;
};

Next the coressponding .cpp file

#include "OpenSGWidget.h"

//constructor
OpenSGWidget::OpenSGWidget(QGLFormat f, QWidget *parent, const char *name ) : QGLWidget( f, parent, name ){ 
    mMgr = new SimpleSceneManager;
    mPwin = PassiveWindow::create();
    mMgr->setWindow(mPwin);
}

SimpleSceneManager *OpenSGWidget::getManager(void){
    return mMgr;
}
 
void OpenSGWidget::initializeGL(){
    mPwin->init();  
}

void OpenSGWidget::resizeGL(int width, int height){
    mMgr->resize(width,height);
}

// this replaces the display function
void OpenSGWidget::paintGL(){
    mMgr->redraw();
}

void OpenSGWidget::mousePressEvent(QMouseEvent *ev){
    UInt32 button;
    
    // translate the button value from qt to OpenSG
    switch (ev->button()){
        case LeftButton:  button = SimpleSceneManager::MouseLeft;   break;
        case MidButton:   button = SimpleSceneManager::MouseMiddle; break;
        case RightButton: button = SimpleSceneManager::MouseRight;  break;
        default:          return;
    }
    
    //passs the event to the manager object
    mMgr->mouseButtonPress(button, ev->x(), ev->y());
    update();
}

void OpenSGWidget::mouseReleaseEvent(QMouseEvent *ev){
     UInt32 button;

    switch (ev->button()){
        case LeftButton:  button = SimpleSceneManager::MouseLeft;   break;
        case MidButton:   button = SimpleSceneManager::MouseMiddle; break;
        case RightButton: button = SimpleSceneManager::MouseRight;  break;
        default:          return;
    }
     
     mMgr->mouseButtonRelease(button, ev->x(), ev->y());
     update();
 }

void OpenSGWidget::mouseMoveEvent(QMouseEvent *ev){
    mMgr->mouseMove(ev->x(), ev->y());
    update();
}

void OpenSGWidget::wheelEvent(QWheelEvent *ev){
    mMgr->mouseButtonPress(ev->delta() > 0 ? SimpleSceneManager::MouseUp : SimpleSceneManager::MouseDown, ev->x(), ev->y());
    
    ev->accept();
    update();
}

int main( int argc, char **argv ){
     osgInit(argc,argv);

    //fire up the widget
     QApplication application(argc, argv);
     OpenSGWidget widget(QGLFormat(QGL::DoubleBuffer | QGL::DepthBuffer | QGL::Rgba | QGL::DirectRendering));

     NodePtr scene;

    //Load stuff from the command line, 
    //if present
     if(argc > 1){
         scene = Node::create();
         GroupPtr g = Group::create();

         beginEditCP(scene);
            scene->setCore(g);

            for(UInt16 i = 1; i < argc; ++i)
            scene->addChild(SceneFileHandler::the().read(argv[i]));
         endEditCP(scene);
     }else{
         scene = makeTorus(.5, 3, 16, 16);
     }

     widget.getManager()->setRoot(scene);
     widget.getManager()->showAll();

     application.setMainWidget( &widget );
     widget.show();
     // replaces the glutMainLoop()
     // giving control to the QT Main Loop
     return application.exec();
}

This time you won't need to call the 'uic' and 'moc' commands like in the last example, because we have not used the designer to create an interface. However hacking the long OpenSG parameters and other attributes into the terminal is quite time robbing, so I offer you a Makefile which also can be taken for your future projects.

# This Makefile will compile the OpenSGWidget application (2nd tutorial in qt.dox)
# The first tutorial has to be compiled by hand (see qt.dox)


# define the kind of library you have installed ('dbg' or 'opt')
LIBTYPE = dbg

C++ = g++

CCFLAGS = -D_GNU_SOURCE -DQT_CLEAN_NAMESPACE -DOSG_WITH_GLUT -DOSG_WITH_QT 
    -DOSG_WITH_JPG -DOSG_WITH_PNG -D_OSG_HAVE_CONFIGURED_H_ -DQT_NO_XINERAMA 
    -DQT_NO_XRENDER -DQT_NO_XFTFREERYPE -D_QT_NO_XKB -DQT_NO_SM_SUPPORT 
    -DQT_NO_IMAGEIO_MNG -DQT_NO_IMAGEIO_JPEG -DQT_NO_STYLE_AQUA -DQT_NO_STYE_MAC 
    -DQT_NO_STYLE_INTERLACE -DQT_NO_STYLE_COMPACT -use_readonly_const 
    -ftemplate-depth-100 -Wno-deprecated -pipe -funroll-loops 
LDFLAGS = -L/usr/local/lib/$(LIBTYPE) -L/usr/qt/3/lib -L/usr/X11R6/lib -Wall
LIBS = -lOSGSystem -lOSGBase -lglut -lGLU -lGL -lXmu -lXi -lXt -lqt -lpthread -ldl
DEBUG = -c -O3
INCLUDE = /usr/qt/3/include

SOURCES = OpenSGWidget.cpp

OBJECTS = $(SOURCES:.cpp=.o)
EXECUTABLE = OSGWidget

all: $(SOURCES) $(EXECUTABLE)

$(EXECUTABLE): $(OBJECTS)
    $(C++) $(LDFLAGS) $(LIBS) $(OBJECTS) -o $@

.cpp.o:
    $(C++) $(CCFLAGS) -I$(INCLUDE) $(DEBUG) $< -o $@

clean:
    rm -rf *.o OSGWidget

If you want to use it for your own projects you simply have to add your source files to the line that says "SOURCES = ....". Additionally it might increase performance if you add some platform specific parameters to the CCFLAGS, like "-march=athlon" or the like.

So if you have saved all three file in the same folder (or just use the premade ones) then fire up a terminal cd to the directory and call "make". After make finishes type "./OSGWidget" which should show you a window with a torus (like always ;-) ). You can add any number of files as parameters to the command line call which will then be loaded.

So far so good, know we want to add some plain OpenGL code. Well, if you are using a passive window this is really very easy, as you only need to add it to the paintGL() method. So please replace your painGL() method with the following code

void OpenSGWidget::paintGL(){
    mMgr->redraw();
    glBegin(GL_QUADS);
        glColor3f(1,0,0);
        glVertex3f(2.5,2.5,0);
        glVertex3f(-2.5,2.5,0);
        glVertex3f(-2.5,-2.5,0);
        glVertex3f(2.5,-2.5,0);
    glEnd();
}

Compile the application and run it again - you now should see the torus (or whichever files you have loaded) and a red quad, which was defined by "real" and "raw" OpenGL commands. In this way it is possible to drive an application with OpenGL graphics that does not use the scenegraph at all - although I can't imagine what that should be good for anyway ;-)

passiveWindow_with_QT.png

A Passive Window with QT and a Raw OpenGL Drawn Quad

Next Chapter : Appendix C - More Documentation and Tools


Generated on 8 Feb 2010 for OpenSG by  doxygen 1.6.1