GtkmmOgre         A GtkmmOgreWidget for embedding Ogre render windows in Gtkmm

A simple setup in 5 files showing a skeleton application with an OgreWidget embedded into Gtkmm.

What is Gtkmm?

Gtkmm is a C++ wrapper around the open source crossplatform GUI toolkit GTK+ and can be found here:
www.gtkmm.org. It is licensed under LGPL, so in short you can use it for commercial and closed source
applications linking dynamically to it. One of its perks is a well working rapid gui building tool with the name glade, which you can
find here: http://glade.gnome.org/, apart from that it is very mature and has an easy to read syntax.

Win32 Setup:

Coming with Visual Studio props, it is very easy to setup and use on windows:

http://live.gnome.org/gtkmm/MSWindows

http://live.gnome.org/gtkmm/MSWindows/UsingMSVC



Win32 binaries for Glade can be found here:

http://gladewin32.sourceforge.net/

Known problems:

This doesn't work with gtkmm3.0, but works fine with 2.4.get_prefered_height_vfunc(), get_prefered_width_vfunc(), get_preferred_width_for_height_vfunc() and get_preferred_height_for_width_vfunc() have to be added (examples available in Programming with Gtkmm2: Custom Widgets) as well as a few other small including changing all instances of
set_state_flags(Gtk::NO_WINDOW);
to
set_has_window(false);
.

Posted:
Jul 31 2011, Ogre version 1.7, gtkmm version 3.0

With the current stable version of gtkmm 2.16 embedding Ogre in DirectX mode is broken and will cause
the text of your other widgets, labels and menus to disappear. This bug is was introduced with gtk version 2.12.
The last known version that works with DirectX is gtkmm 2.10. However, OpenGL works fine.

The corresponding bug-report for Gtk 2.12 @ bugzilla.gnome.org

Posted:
-- Nauk 9 May 2009, Ogre Version 1.61, Gtkmm 2.16

The workaround to make any embedding of Ogre's D3D RenderSystem work with GTK+ is to set the floating point mode to 'Consistent' in the RenderSystem options before initializing Ogre::Root. This option is available in the config dialog, or can be set programatically with
rendersystem->setConfigOption("Floating-point mode", "Consistent");
Considering the necessity of consistent mode, it would be wise to force the value in code after presenting the config dialog, if you're using it. Consistent mode tells the RenderSystem to create the D3D device with D3DCREATE_FPU_PRESERVE. Fastest tells the RenderSystem to switch the FPU to single precision. Cairo requires double precision floating point math to render text.

The Code

main.cpp:
#include <gtkmm/main.h>
    #include "ogrewindow.h"

    int main (int argc, char *argv[])
    {
        Ogre::Root* root = new Ogre::Root();

        if (!root->showConfigDialog())
            return -1;

        root->initialise(false);

        Gtk::Main kit(argc, argv);

        OgreWindow oWindow;
        oWindow.show();

        while (!oWindow.hasExited())
        { 
            kit.iteration();
            // you could also call renderOneFrame() here instead.
        }       

        delete root;   
        return 0;
    }
ogrewindow.h:
#ifndef OGREWINDOW_H
    #define OGREWINDOW_H

    #include <gtkmm.h>
    #include <gtkmm/button.h>
    #include <gtkmm/window.h>

    #include "ogrewidget.h"

    class OgreWindow : public Gtk::Window
    {

    public:
      OgreWindow();
      virtual ~OgreWindow();
      virtual bool on_delete_event(GdkEventAny* event);
      bool hasExited() { return mExited; } 
     
      virtual bool on_key_press_event(GdkEventKey *event);
      virtual bool on_key_release_event(GdkEventKey *event); 

    protected:
      OgreWidget mOgreWidget;
      bool mExited;
    };

    #endif

ogrewindow.cpp:
#include "ogrewindow.h"
    #include <iostream>

    OgreWindow::OgreWindow() :
        mOgreWidget(),
        mExited(false)
    {
      set_border_width(10);
     
      Gtk::VBox *vb = new Gtk::VBox(false,10);
     
      Gtk::Button *mb = new Gtk::Button("Some Button");
     
      vb->pack_start(*mb,true,true,10);
      vb->pack_start(mOgreWidget,true,true,10);
     
      add(*vb); 
      show_all();
    }

    OgreWindow::~OgreWindow()
    {
    }

    bool OgreWindow::on_delete_event(GdkEventAny* event)
    {
        mExited = true;
        return false;
    }

    bool OgreWindow::on_key_press_event(GdkEventKey *event) {
       std::cout << "keydown\n";
       return true;
    }

    bool OgreWindow::on_key_release_event(GdkEventKey *event) {
       std::cout << "keyup\n";
       return true;
    }

ogrewidget.h:
#ifndef OGREWIDGET_H
    #define OGREWIDGET_H

    #include <gtkmm.h>
    #include <glibmm/timer.h>

    #include <Ogre.h>

    class OgreWidget : public Gtk::Widget
    {
    public:
      OgreWidget();
      virtual ~OgreWidget();

    protected:
      void createScene();

      virtual void on_size_request(Gtk::Requisition* requisition);
      virtual void on_size_allocate(Gtk::Allocation& allocation);
      virtual void on_map();
      virtual void on_unmap();
      virtual void on_realize();
      virtual void on_unrealize();
      virtual bool on_expose_event(GdkEventExpose* event);
      virtual bool on_idle();
     
      virtual bool on_motion_notify_event(GdkEventMotion *event);
      virtual bool on_button_press_event(GdkEventButton *event);
      virtual bool on_button_release_event(GdkEventButton *event);

      Glib::RefPtr<Gdk::Window> mRefGdkWindow;
       
      Ogre::RenderWindow* mRenderWindow;
      Ogre::SceneManager* mSceneMgr;
      Ogre::Viewport* mViewport;
      Ogre::Camera* mCamera;

    };
    #endif
ogrewidget.cpp:
#include "ogrewidget.h"

    #include <gdkmm/drawable.h>
    #include <gdkmm/general.h>
    #include <iostream>
    #include <cstring>

    #ifdef WIN32
       #include <gdk/gdkwin32.h>
    #endif

    OgreWidget::OgreWidget() :
      Glib::ObjectBase("ogrewidget"),
      Gtk::Widget(),
      mRenderWindow(0), mCamera(0), mSceneMgr(0), mViewport(0)
    {
      set_flags(Gtk::NO_WINDOW);
      std::cout << "GType name: " << G_OBJECT_TYPE_NAME(gobj()) << std::endl;
    }

    OgreWidget::~OgreWidget()
    {
    }

    void OgreWidget::on_size_request(Gtk::Requisition* requisition)
    {
      *requisition = Gtk::Requisition();

      requisition->width = 800;
      requisition->height = 600;
    }

    void OgreWidget::on_size_allocate(Gtk::Allocation& allocation)
    {
      set_allocation(allocation);

      if(mRefGdkWindow)
      {
        mRefGdkWindow->move_resize( allocation.get_x(), allocation.get_y(),
                allocation.get_width(), allocation.get_height() );
      }
     
      if (mRenderWindow)
      {
          mRenderWindow->windowMovedOrResized();
          mCamera->setAspectRatio(Ogre::Real(allocation.get_width()) / Ogre::Real(allocation.get_height()));     
          on_expose_event(NULL);
      }
    }

    void OgreWidget::on_map()
    {
      //Call base class:
      Gtk::Widget::on_map();
    }

    void OgreWidget::on_unmap()
    {
      //Call base class:
      Gtk::Widget::on_unmap();
    }

    void OgreWidget::on_realize()
    {
       //Call base class:
       Gtk::Widget::on_realize();
       
       Gtk::Allocation allocation = get_allocation();
             
       if(!mRefGdkWindow)
       {
          //Create the GdkWindow:
          GdkWindowAttr attributes;
          memset(&attributes, 0, sizeof(attributes));

          //Set initial position and size of the Gdk::Window:
          attributes.x = allocation.get_x();
          attributes.y = allocation.get_y();
          attributes.width = allocation.get_width();
          attributes.height = allocation.get_height();

          attributes.event_mask = get_events () | Gdk::EXPOSURE_MASK | Gdk::ALL_EVENTS_MASK ;
          attributes.window_type = GDK_WINDOW_CHILD;
          attributes.wclass = GDK_INPUT_OUTPUT;

          mRefGdkWindow = Gdk::Window::create(get_window(), &attributes, GDK_WA_X | GDK_WA_Y);
     
       }
       
       if (!mRenderWindow)
       {
          Ogre::NameValuePairList params;
    #ifdef WIN32
       params["externalWindowHandle"] = Ogre::StringConverter::toString((unsigned long)GDK_WINDOW_HWND(mRefGdkWindow->gobj()));
    #else
    GdkWindow* parent = mRefGdkWindow->gobj();
       GdkDisplay* display = gdk_drawable_get_display(GDK_DRAWABLE(parent));
       GdkScreen* screen = gdk_drawable_get_screen(GDK_DRAWABLE(parent));

    Display* xdisplay = GDK_DISPLAY_XDISPLAY(display);
       Screen* xscreen = GDK_SCREEN_XSCREEN(screen);
       int screen_number = XScreenNumberOfScreen(xscreen);
       XID xid_parent = GDK_WINDOW_XWINDOW(parent);

       params["externalWindowHandle"] =
          Ogre::StringConverter::toString(reinterpret_cast<unsigned long>(xdisplay)) + ":" +
          Ogre::StringConverter::toString(static_cast<unsigned int>(screen_number)) + ":" +
          Ogre::StringConverter::toString(static_cast<unsigned long>(xid_parent));
    #endif
          mRenderWindow = Ogre::Root::getSingleton().createRenderWindow("Gtk+Ogre Widget",
             allocation.get_width(), allocation.get_height(), false, &params);
         
         mRenderWindow->setAutoUpdated(false);
         
        unset_flags(Gtk::NO_WINDOW);
       
         set_window(mRefGdkWindow);

          set_double_buffered(true);
          //make the widget receive expose events
         mRefGdkWindow->set_user_data(gobj());
         mRefGdkWindow->set_back_pixmap(Glib::RefPtr<Gdk::Pixmap>(),false);
         
          createScene();     
         
          // Start idle function for frame update/rendering
          Glib::signal_idle().connect( sigc::mem_fun(*this, &OgreWidget::on_idle) );
                 
       }
    }

    void OgreWidget::on_unrealize()
    {
      mRefGdkWindow.clear();
      //Call base class:
      Gtk::Widget::on_unrealize();
    }

    bool OgreWidget::on_expose_event(GdkEventExpose* event)
    {
       
      if (mRenderWindow)
      {
          Ogre::Root::getSingletonPtr()->_fireFrameStarted();
          mRenderWindow->update();
          Ogre::Root::getSingletonPtr()->_fireFrameEnded(); 

      } 
      return true;
    }

    bool OgreWidget::on_idle()
    {
        on_expose_event(0);
        return true;
    }

    void OgreWidget::createScene()
    {
        // Set default mipmap level & texture filtering
        Ogre::TextureManager::getSingleton().setDefaultNumMipmaps(5);
        Ogre::MaterialManager::getSingleton().setDefaultTextureFiltering(Ogre::TFO_TRILINEAR);

        // Create scene manager
        mSceneMgr = Ogre::Root::getSingletonPtr()->createSceneManager(Ogre::ST_GENERIC, "SceneManager");

        // Create the camera
        mCamera = mSceneMgr->createCamera("Camera");

        // Create one viewport, entire window
        mViewport = mRenderWindow->addViewport(mCamera);

        // Alter the camera aspect ratio to match the viewport
        mCamera->setAspectRatio(Ogre::Real(mViewport->getActualWidth()) / Ogre::Real(mViewport->getActualHeight()));           
    }

    bool OgreWidget::on_motion_notify_event(GdkEventMotion *event) {
       std::cout << ".";
       return true;
    }

    bool OgreWidget::on_button_press_event(GdkEventButton *event) {
       std::cout << "button press\n";
       return true;
    }

    bool OgreWidget::on_button_release_event(GdkEventButton *event) {
       std::cout << "button release\n";
       return true;
    }