Integrating to a Qt interface

Introduction

In this tutorial, we'll see how we can integrate a SFML view into a Qt interface. The typical way of adding a new feature to a Qt interface is to write a custom widget ; that's what we'll do here.

Creating the SFML custom widget

To create a custom Qt widget, we need to inherit from the QWidget base class. And as we want our widget to be an SFML rendering window too, we'll inherit from sf::RenderWindow.

#include <SFML/Graphics.hpp>
#include <Qt/qwidget.h>

class QSFMLCanvas : public QWidget, public sf::RenderWindow
{
    ...
};

Then, what will this class need to work ? First, it will need a standard constructor for defining the usual widget parameters : parent widget, position, size. We add a last parameter which is the duration of one frame (the inverse of the framerate) ; as Qt doesn't provide an idle event (which would be called each time the event queue is empty), we'll have to refresh the widget manually. The best way to do this is to launch a timer, and connect it to a function that refreshes the widget at the specified rate. The default value of 0 will make the timer trigger a refresh whenever there is no other event to process, which is exactly what an idle event would do.

Then, we need to override the show event : this is a good place to initialize our SFML window. We can't do it in the constructor, because at this time the widget doesn't have its final position and size yet.
We also override the paint event, to refresh the SFML view.

We'll define two functions for the derived classes : OnInit(), which will be called as soon as the SFML view has been created, and OnUpdate(), which will be called before each refresh to let the derived class draw things into the widget.

#include <SFML/Graphics.hpp>
#include <Qt/qwidget.h>
#include <Qt/qtimer.h>

class QSFMLCanvas : public QWidget, public sf::RenderWindow
{
public :

    QSFMLCanvas(QWidget* Parent, const QPoint& Position, const QSize& Size, unsigned int FrameTime = 0);

    virtual ~QSFMLCanvas();

private :

    virtual void OnInit();

    virtual void OnUpdate();

    virtual QPaintEngine* paintEngine() const;

    virtual void showEvent(QShowEvent*);

    virtual void paintEvent(QPaintEvent*);

    QTimer myTimer;
    bool   myInitialized;
};

Let's now have a look at the implementation.

QSFMLCanvas::QSFMLCanvas(QWidget* Parent, const QPoint& Position, const QSize& Size, unsigned int FrameTime) :
QWidget       (Parent),
myInitialized (false)
{
    // Setup some states to allow direct rendering into the widget
    setAttribute(Qt::WA_PaintOnScreen);
    setAttribute(Qt::WA_OpaquePaintEvent);
    setAttribute(Qt::WA_NoSystemBackground);

    // Set strong focus to enable keyboard events to be received
    setFocusPolicy(Qt::StrongFocus);

    // Setup the widget geometry
    move(Position);
    resize(Size);

    // Setup the timer
    myTimer.setInterval(FrameTime);
}

First, the constructor sets some attributes to allow direct rendering into the widget. WA_PaintOnScreen tells Qt that we will not use its painting functions, and paint directly into the widget. WA_NoSystemBackground and WA_OpaquePaintEvent prevent from drawing the widget's background, which could cause some flickering.
We also set the Qt::StrongFocus policy, to enable keyboard events to be received by the widget.
Then, we set the widget's position and size, and set the timer interval to the requested frame time.

#ifdef Q_WS_X11
    #include <Qt/qx11info_x11.h>
    #include <X11/Xlib.h>
#endif

void QSFMLCanvas::showEvent(QShowEvent*)
{
    if (!myInitialized)
    {
        // Under X11, we need to flush the commands sent to the server to ensure that
        // SFML will get an updated view of the windows
        #ifdef Q_WS_X11
            XFlush(QX11Info::display());
        #endif

        // Create the SFML window with the widget handle
        Create(winId());

        // Let the derived class do its specific stuff
        OnInit();

        // Setup the timer to trigger a refresh at specified framerate
        connect(&myTimer, SIGNAL(timeout()), this, SLOT(repaint()));
        myTimer.start();

        myInitialized = true;
    }
}

In the showEvent function, which is called when the widget is shown, we create the SFML window. This is done simply by calling the Create function with the internal identifier of the window, which is given by the winId function. Under X11 (Unix), we first need to put a system call to flush the message queue, to make sure SFML will see the window.
Once the SFML window has been initialized, we can tell the derived class by calling the OnInit virtual function.
Finally, we connect the timer with the repaint function, which will refresh the widget and trigger a paint event. And of course, we start it.

QPaintEngine* QSFMLCanvas::paintEngine() const
{
    return 0;
}

We make the paintEvent function return a null paint engine. This functions works together with the WA_PaintOnScreen flag to tell Qt that we're not using any of its built-in paint engines.

void QSFMLCanvas::paintEvent(QPaintEvent*)
{
    // Let the derived class do its specific stuff
    OnUpdate();

    // Display on screen
    Display();
}

The paintEvent function is quite straight-forward : we notify the derived class that a refresh is about to be performed, and we call Display to update the widget with our rendered frame.

Using our Qt-SFML widget

The QSFMLCanvas we just wrote is not usable directly, it is meant to be derived. So, let's create a derived widget that will do some nice drawing :

class MyCanvas : public QSFMLCanvas
{
public :

    MyCanvas(QWidget* Parent, const QPoint& Position, const QSize& Size) :
    QSFMLCanvas(Parent, Position, Size)
    {

    }

private :

    void OnInit()
    {
        // Load the image
        myImage.LoadFromFile("datas/qt/sfml.png");

        // Setup the sprite
        mySprite.SetImage(myImage);
        mySprite.SetCenter(mySprite.GetSize() / 2.f);
    }

    void OnUpdate()
    {
        // Clear screen
        Clear(sf::Color(0, 128, 0));

        // Rotate the sprite
        mySprite.Rotate(GetFrameTime() * 100.f);

        // Draw it
        Draw(mySprite);
    }

    sf::Image  myImage;
    sf::Sprite mySprite;
};

Nothing very complicated here : we override OnInit to load and initialize our resources, and OnUpdate to draw them.

We can now create a regular Qt window, and put an instance of our custom widget into it :

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

    // Create the main frame
    QFrame* MainFrame = new QFrame;
    MainFrame->setWindowTitle("Qt SFML");
    MainFrame->resize(400, 400);
    MainFrame->show();

    // Create a SFML view inside the main frame
    MyCanvas* SFMLView = new MyCanvas(MainFrame, QPoint(20, 20), QSize(360, 360));
    SFMLView->show();

    return App.exec();
}

Conclusion

Integration of SFML into a Qt interface is easy with the custom widget we just wrote, feel free to use and improve it.
If you want to see how SFML integrates into a wxWidgets interface, you can go to the next tutorial.