From 4345382316704bf6bb6b3941e3c8008db395b988 Mon Sep 17 00:00:00 2001 From: Robert Osfield Date: Thu, 8 May 2008 16:39:10 +0000 Subject: [PATCH] From Jeremy Moles, osgviewerGTK example --- CMakeLists.txt | 10 + examples/CMakeLists.txt | 4 + examples/osgviewerGTK/CMakeLists.txt | 8 + examples/osgviewerGTK/osggtkdrawingarea.cpp | 148 +++++++++++++ examples/osgviewerGTK/osggtkdrawingarea.h | 207 +++++++++++++++++ examples/osgviewerGTK/osgviewerGTK.cpp | 234 ++++++++++++++++++++ 6 files changed, 611 insertions(+) create mode 100644 examples/osgviewerGTK/CMakeLists.txt create mode 100644 examples/osgviewerGTK/osggtkdrawingarea.cpp create mode 100644 examples/osgviewerGTK/osggtkdrawingarea.h create mode 100644 examples/osgviewerGTK/osgviewerGTK.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 141258746..3cbae38ce 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -211,6 +211,16 @@ FIND_PACKAGE(ZLIB) FIND_PACKAGE(GDAL) FIND_PACKAGE(CURL) +INCLUDE(FindPkgConfig) + +PKG_CHECK_MODULES(GTK gtk+-2.0) + +IF(WIN32) + PKG_CHECK_MODULES(GTKGL gtkglext-win32-1.0) +ELSE(WIN32) + PKG_CHECK_MODULES(GTKGL gtkglext-x11-1.0) +ENDIF(WIN32) + SET(wxWidgets_USE_LIBS base core gl net) FIND_PACKAGE(wxWidgets) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index ca89a7a9d..1bce25305 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -113,6 +113,10 @@ IF(DYNAMIC_OPENSCENEGRAPH) ADD_SUBDIRECTORY(osgviewerSDL) ENDIF(SDL_FOUND) + IF (GTKGL_FOUND) + ADD_SUBDIRECTORY(osgviewerGTK) + ENDIF(GTKGL_FOUND) + IF (FOX_FOUND) ADD_SUBDIRECTORY(osgviewerFOX) ENDIF(FOX_FOUND) diff --git a/examples/osgviewerGTK/CMakeLists.txt b/examples/osgviewerGTK/CMakeLists.txt new file mode 100644 index 000000000..17622b70b --- /dev/null +++ b/examples/osgviewerGTK/CMakeLists.txt @@ -0,0 +1,8 @@ +SET(TARGET_SRC osggtkdrawingarea.cpp osgviewerGTK.cpp) +SET(TARGET_H osggtkdrawingarea.h) + +INCLUDE_DIRECTORIES(${GTK_INCLUDE_DIRS} ${GTKGL_INCLUDE_DIRS}) +LINK_DIRECTORIES(${GTK_LIBRARY_DIRS} ${GTKGL_LIBRARY_DIRS}) +LINK_LIBRARIES(osgViewer ${GTK_LIBRARIES} ${GTKGL_LIBRARIES}) + +SETUP_EXAMPLE(osgviewerGTK) diff --git a/examples/osgviewerGTK/osggtkdrawingarea.cpp b/examples/osgviewerGTK/osggtkdrawingarea.cpp new file mode 100644 index 000000000..c4eccd6a9 --- /dev/null +++ b/examples/osgviewerGTK/osggtkdrawingarea.cpp @@ -0,0 +1,148 @@ +// by: Jeremy Moles 2007 + +#include "osggtkdrawingarea.h" + +OSGGTKDrawingArea::OSGGTKDrawingArea(): +_widget (gtk_drawing_area_new()), +_glconfig (0), +_context (0), +_drawable (0), +_state (0), +_queue (*getEventQueue()) { + setCameraManipulator(new osgGA::TrackballManipulator()); +} + +OSGGTKDrawingArea::~OSGGTKDrawingArea() { +} + +bool OSGGTKDrawingArea::createWidget(int width, int height) { + _glconfig = gdk_gl_config_new_by_mode(static_cast( + GDK_GL_MODE_RGBA | + GDK_GL_MODE_DEPTH | + GDK_GL_MODE_DOUBLE + )); + + if(not _glconfig) { + osg::notify(osg::FATAL) << "Fail!" << std::endl; + + return false; + } + + gtk_widget_set_size_request(_widget, width, height); + + gtk_widget_set_gl_capability( + _widget, + _glconfig, + 0, + true, + GDK_GL_RGBA_TYPE + ); + + gtk_widget_add_events( + _widget, + GDK_BUTTON1_MOTION_MASK | + GDK_BUTTON2_MOTION_MASK | + GDK_BUTTON3_MOTION_MASK | + GDK_POINTER_MOTION_MASK | + GDK_BUTTON_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK | + GDK_KEY_PRESS_MASK | + GDK_KEY_RELEASE_MASK | + GDK_VISIBILITY_NOTIFY_MASK + ); + + // We do this so that we don't have to suck up ALL the input to the + // window, but instead just when the drawing area is focused. + g_object_set(_widget, "can-focus", true, NULL); + + _connect("realize", G_CALLBACK(&OSGGTKDrawingArea::_srealize)); + _connect("unrealize", G_CALLBACK(&OSGGTKDrawingArea::_sunrealize)); + _connect("expose_event", G_CALLBACK(&OSGGTKDrawingArea::_sexpose_event)); + _connect("configure_event", G_CALLBACK(&OSGGTKDrawingArea::_sconfigure_event)); + _connect("motion_notify_event", G_CALLBACK(&OSGGTKDrawingArea::_smotion_notify_event)); + _connect("button_press_event", G_CALLBACK(&OSGGTKDrawingArea::_sbutton_press_event)); + _connect("button_release_event", G_CALLBACK(&OSGGTKDrawingArea::_sbutton_press_event)); + _connect("key_press_event", G_CALLBACK(&OSGGTKDrawingArea::_skey_press_event)); + + setUpViewerAsEmbeddedInWindow(0, 0, width, height); + + return true; +} + +void OSGGTKDrawingArea::_realize(GtkWidget* widget) { + _context = gtk_widget_get_gl_context(widget); + _drawable = gtk_widget_get_gl_drawable(widget); + + gtkRealize(); +} + +void OSGGTKDrawingArea::_unrealize(GtkWidget* widget) { + gtkUnrealize(); +} + +bool OSGGTKDrawingArea::_expose_event(GtkWidget* widget, GdkEventExpose* event) { + if(not gtkGLBegin()) return false; + + frame(); + + gtkGLSwap(); + gtkGLEnd(); + + return gtkExpose(); +} + +bool OSGGTKDrawingArea::_configure_event(GtkWidget* widget, GdkEventConfigure* event) { + gtkGLBegin(); + + _queue.windowResize(0, 0, event->width, event->height); + + gtkGLEnd(); + + return gtkConfigure(event->width, event->height); +} + +bool OSGGTKDrawingArea::_motion_notify_event(GtkWidget* widget, GdkEventMotion* event) { + _state = event->state; + + _queue.mouseMotion(event->x, event->y); + + return gtkMotionNotify(event->x, event->y); +} + +bool OSGGTKDrawingArea::_button_press_event(GtkWidget* widget, GdkEventButton* event) { + _state = event->state; + + if(event->type == GDK_BUTTON_PRESS) { + if(event->button == 1) gtk_widget_grab_focus(_widget); + + _queue.mouseButtonPress(event->x, event->y, event->button); + + return gtkButtonPress(event->x, event->y, event->button); + } + + else if(event->type == GDK_BUTTON_RELEASE) { + _queue.mouseButtonRelease(event->x, event->y, event->button); + + return gtkButtonRelease(event->x, event->y, event->button); + } + + else return false; +} + +bool OSGGTKDrawingArea::_key_press_event(GtkWidget* widget, GdkEventKey* event) { + _state = event->state; + + if(event->type == GDK_KEY_PRESS) { + _queue.keyPress(event->keyval); + + return gtkKeyPress(event->keyval); + } + + else if(event->type == GDK_KEY_RELEASE) { + _queue.keyRelease(event->keyval); + + return gtkKeyRelease(event->keyval); + } + + else return false; +} diff --git a/examples/osgviewerGTK/osggtkdrawingarea.h b/examples/osgviewerGTK/osggtkdrawingarea.h new file mode 100644 index 000000000..08d1a2ba2 --- /dev/null +++ b/examples/osgviewerGTK/osggtkdrawingarea.h @@ -0,0 +1,207 @@ +// by: Jeremy Moles 2007 + +#include +#include +#include +#include + +// This is an implementation of SimpleViewer that is designed to be subclassed +// and used as a GtkDrawingArea in a GTK application. Because of the implemention +// of GTK, I was unable to derive from GtkWidget and instead had to "wrap" it. +// Conceptually, however, you can think of an OSGGTKDrawingArea as both an OSG +// Viewer AND GtkDrawingArea. +// +// While it is possible to use this class directly, it won't end up doing anything +// interesting without calls to queueDraw, which ideally are done in the user's +// subclass implementation (see: osgviewerGTK). +class OSGGTKDrawingArea : public osgViewer::Viewer { + GtkWidget* _widget; + GdkGLConfig* _glconfig; + GdkGLContext* _context; + GdkGLDrawable* _drawable; + + unsigned int _state; + + osgGA::EventQueue& _queue; + + static OSGGTKDrawingArea* _self(gpointer self) { + return static_cast(self); + } + + // A simple helper function to connect us to the various GTK signals. + void _connect(const char* name, GCallback callback) { + g_signal_connect(G_OBJECT(_widget), name, callback, this); + } + + void _realize (GtkWidget*); + void _unrealize (GtkWidget*); + bool _expose_event (GtkWidget*, GdkEventExpose*); + bool _configure_event (GtkWidget*, GdkEventConfigure*); + bool _motion_notify_event (GtkWidget*, GdkEventMotion*); + bool _button_press_event (GtkWidget*, GdkEventButton*); + bool _key_press_event (GtkWidget*, GdkEventKey*); + + // The following functions are static "wrappers" so that we can invoke the + // bound methods of a class instance by passing the "this" pointer as the + // self argument and invoking it explicitly. + static void _srealize(GtkWidget* widget, gpointer self) { + _self(self)->_realize(widget); + } + + static void _sunrealize(GtkWidget* widget, gpointer self) { + _self(self)->_unrealize(widget); + } + + static bool _sexpose_event(GtkWidget* widget, GdkEventExpose* expose, gpointer self) { + return _self(self)->_expose_event(widget, expose); + } + + static bool _sconfigure_event( + GtkWidget* widget, + GdkEventConfigure* event, + gpointer self + ) { + return _self(self)->_configure_event(widget, event); + } + + static bool _smotion_notify_event( + GtkWidget* widget, + GdkEventMotion* event, + gpointer self + ) { + return _self(self)->_motion_notify_event(widget, event); + } + + static bool _sbutton_press_event( + GtkWidget* widget, + GdkEventButton* event, + gpointer self + ) { + return _self(self)->_button_press_event(widget, event); + } + + static bool _skey_press_event( + GtkWidget* widget, + GdkEventKey* event, + gpointer self + ) { + return _self(self)->_key_press_event(widget, event); + } + +protected: + // You can override these in your subclass if you'd like. :) + // Right now they're fairly uninformative, but they could be easily extended. + // Note that the "state" information isn't passed around to each function + // but is instead stored and abstracted internally. See below. + + virtual void gtkRealize () {}; + virtual void gtkUnrealize () {}; + virtual bool gtkExpose () { + return true; + }; + + // The new width and height. + virtual bool gtkConfigure(int, int) { + return true; + }; + + // The "normalized" coordinates of the mouse. + virtual bool gtkMotionNotify(double, double) { + return true; + }; + + // The "normalized" coordinates of the mouse and the mouse button code on down. + virtual bool gtkButtonPress(double, double, unsigned int) { + return true; + }; + + // The "normalized" coordinates of the mouse and mouse button code on release. + virtual bool gtkButtonRelease(double, double, unsigned int) { + return true; + } + + // The X key value on down. + virtual bool gtkKeyPress(unsigned int) { + return true; + }; + + // The X key value on release. + virtual bool gtkKeyRelease(unsigned int) { + return true; + }; + + // These functions wrap state tests of the most recent state in the + // GtkDrawingArea. + + inline bool stateShift() { + return _state & GDK_SHIFT_MASK; + } + + inline bool stateLock() { + return _state & GDK_LOCK_MASK; + } + + inline bool stateControl() { + return _state & GDK_CONTROL_MASK; + } + + inline bool stateMod() { + return _state & ( + GDK_MOD1_MASK | + GDK_MOD2_MASK | + GDK_MOD3_MASK | + GDK_MOD4_MASK | + GDK_MOD5_MASK + ); + } + + inline bool stateButton() { + return _state & ( + GDK_BUTTON1_MASK | + GDK_BUTTON2_MASK | + GDK_BUTTON3_MASK | + GDK_BUTTON4_MASK | + GDK_BUTTON5_MASK + ); + } + +public: + OSGGTKDrawingArea (); + ~OSGGTKDrawingArea (); + + bool createWidget(int, int); + + GtkWidget* getWidget() { + return _widget; + } + + bool gtkGLBegin() { + if(_drawable and _context) return gdk_gl_drawable_gl_begin(_drawable, _context); + + else return false; + } + + void gtkGLEnd() { + if(_drawable) gdk_gl_drawable_gl_end(_drawable); + } + + // Because of GTK's internal double buffering, I'm not sure if we're really + // taking advantage of OpenGL's internal swapping. + bool gtkGLSwap() { + if(_drawable and gdk_gl_drawable_is_double_buffered(_drawable)) { + gdk_gl_drawable_swap_buffers(_drawable); + + return true; + } + + else { + glFlush(); + + return false; + } + } + + void queueDraw() { + gtk_widget_queue_draw(_widget); + } +}; diff --git a/examples/osgviewerGTK/osgviewerGTK.cpp b/examples/osgviewerGTK/osgviewerGTK.cpp new file mode 100644 index 000000000..28d7570ca --- /dev/null +++ b/examples/osgviewerGTK/osgviewerGTK.cpp @@ -0,0 +1,234 @@ +// by: Jeremy Moles 2007 + +#include +#include +#include +#include + +#include "osggtkdrawingarea.h" + +const char* HELP_TEXT = + "Use CTRL or SHIFT plus right-click to pull up a fake menu.\n" + "Use the standard TrackballManipulator keys to rotate the loaded\n" + "model (with caveats; the model won't keep rotating).\n" + "\n" + "OpenSceneGraph Project, 2008" +; + +// If you want to see how to connect class method to callbacks, take a look at the +// implementation of OSGGTKDrawingArea. It's dirty, but it's the only way I could +// come up with. +bool activate(GtkWidget* widget, gpointer) { + GtkWidget* label = gtk_bin_get_child(GTK_BIN(widget)); + + std::cout << "MENU: " << gtk_label_get_label(GTK_LABEL(label)) << std::endl; + + return true; +} + +// Our derived OSGGTKDrawingArea "widget." Redraws occur while the mouse buttons +// are held down and mouse motion is detected. +// +// This is the easiest way to demonstrate the use of OSGGTKDrawingArea. We override +// a few of the event methods to setup our menu and to issue redraws. Note that an +// unmodified OSGGTKDrawingArea never calls queueDraw, so OSG is never asked to render +// itself. +class ExampleOSGGTKDrawingArea : public OSGGTKDrawingArea { + GtkWidget* _menu; + + unsigned int _tid; + + // A helper function to easily setup our menu entries. + void _menuAdd(const std::string& title) { + GtkWidget* item = gtk_menu_item_new_with_label(title.c_str()); + + gtk_menu_shell_append(GTK_MENU_SHELL(_menu), item); + + g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(activate), 0); + } + + bool _clicked(GtkWidget* widget) { + const char* text = gtk_label_get_label( + GTK_LABEL(gtk_bin_get_child(GTK_BIN(widget))) + ); + + if(not std::strncmp(text, "Close", 5)) gtk_main_quit(); + + else if(not std::strncmp(text, "Open File", 9)) { + GtkWidget* of = gtk_file_chooser_dialog_new( + "Please select an OSG file...", + GTK_WINDOW(gtk_widget_get_toplevel(getWidget())), + GTK_FILE_CHOOSER_ACTION_OPEN, + GTK_STOCK_CANCEL, + GTK_RESPONSE_CANCEL, + GTK_STOCK_OPEN, + GTK_RESPONSE_ACCEPT, + NULL + ); + + if(gtk_dialog_run(GTK_DIALOG(of)) == GTK_RESPONSE_ACCEPT) { + char* file = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(of)); + + osg::ref_ptr model = osgDB::readNodeFile(file); + + if(model.valid()) { + setSceneData(model.get()); + + queueDraw(); + } + + g_free(file); + } + + gtk_widget_destroy(of); + } + + // Assume we're wanting FPS toggling. + else { + if(not _tid) { + _tid = g_timeout_add( + 15, + (GSourceFunc)(ExampleOSGGTKDrawingArea::timeout), + this + ); + + gtk_button_set_label(GTK_BUTTON(widget), "Toggle 60 FPS (off)"); + } + + else { + g_source_remove(_tid); + gtk_button_set_label(GTK_BUTTON(widget), "Toggle 60 FPS (on)"); + + _tid = 0; + } + } + + return true; + } + +protected: + // Check right-click release to see if we need to popup our menu. + bool gtkButtonRelease(double, double, unsigned int button) { + if(button == 3 and (stateControl() or stateShift())) gtk_menu_popup( + GTK_MENU(_menu), + 0, + 0, + 0, + 0, + button, + 0 + ); + + return true; + } + + // Our "main" drawing pump. Since our app is just a model viewer, we use + // click+motion as our criteria for issuing OpenGL refreshes. + bool gtkMotionNotify(double, double) { + if(stateButton()) queueDraw(); + + return true; + } + +public: + ExampleOSGGTKDrawingArea(): + OSGGTKDrawingArea (), + _menu (gtk_menu_new()), + _tid (0) { + _menuAdd("Option"); + _menuAdd("Another Option"); + _menuAdd("Still More Options"); + + gtk_widget_show_all(_menu); + + getCamera()->setStats(new osg::Stats("omg")); + } + + ~ExampleOSGGTKDrawingArea() {} + + // Public so that we can use this as a callback in main(). + static bool clicked(GtkWidget* widget, gpointer self) { + return static_cast(self)->_clicked(widget); + } + + //static gboolean timeout(GtkWidget* widget) { + static bool timeout(void* self) { + static_cast(self)->queueDraw(); + + return true; + } +}; + +// Our main() function! FINALLY! Most of this code is GTK stuff, so it's mostly boilerplate. +// If we wanted to get real jiggy with it we could use Glade and cut down about 20 lines of +// code or so. +int main(int argc, char** argv) { + gtk_init(&argc, &argv); + gtk_gl_init(&argc, &argv); + + ExampleOSGGTKDrawingArea da; + + if(da.createWidget(640, 480)) { + if(argc >= 2) { + osg::ref_ptr model = osgDB::readNodeFile(argv[1]); + + if(model.valid()) da.setSceneData(model.get()); + } + + GtkWidget* window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + GtkWidget* vbox1 = gtk_vbox_new(false, 3); + GtkWidget* vbox2 = gtk_vbox_new(false, 3); + GtkWidget* hbox = gtk_hbox_new(false, 3); + GtkWidget* label = gtk_label_new(""); + GtkWidget* buttons[] = { + gtk_button_new_with_label("Open File"), + gtk_button_new_with_label("Toggle 60 FPS (on)"), + gtk_button_new_with_label("Close") + }; + + gtk_label_set_use_markup(GTK_LABEL(label), true); + gtk_label_set_label(GTK_LABEL(label), HELP_TEXT); + + for(unsigned int i = 0; i < sizeof(buttons) / sizeof(GtkWidget*); i++) { + gtk_box_pack_start( + GTK_BOX(vbox2), + buttons[i], + false, + false, + 0 + ); + + g_signal_connect( + G_OBJECT(buttons[i]), + "clicked", + G_CALLBACK(ExampleOSGGTKDrawingArea::clicked), + &da + ); + } + + gtk_window_set_title(GTK_WINDOW(window), "osgviewerGTK"); + + gtk_box_pack_start(GTK_BOX(hbox), vbox2, true, true, 2); + gtk_box_pack_start(GTK_BOX(hbox), label, true, true, 2); + + gtk_box_pack_start(GTK_BOX(vbox1), da.getWidget(), true, true, 2); + gtk_box_pack_start(GTK_BOX(vbox1), hbox, false, false, 2); + + gtk_container_set_reallocate_redraws(GTK_CONTAINER(window), true); + gtk_container_add(GTK_CONTAINER(window), vbox1); + + g_signal_connect( + G_OBJECT(window), + "delete_event", + G_CALLBACK(gtk_main_quit), + 0 + ); + + gtk_widget_show_all(window); + gtk_main(); + } + + else return 1; + + return 0; +}