diff --git a/CMakeLists.txt b/CMakeLists.txt index f5c4681149..719df3e348 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -229,11 +229,7 @@ IF(BOOST_STATIC) set(Boost_USE_STATIC_LIBS ON) endif() -if (USE_QT) - set (OSG_QT osgQt) -endif() - -find_package(OpenSceneGraph 3.3.4 REQUIRED osgDB osgViewer osgText osgGA osgAnimation osgParticle ${OSG_QT} osgUtil osgFX) +find_package(OpenSceneGraph 3.3.4 REQUIRED osgDB osgViewer osgText osgGA osgAnimation osgParticle osgUtil osgFX) include_directories(${OPENSCENEGRAPH_INCLUDE_DIRS}) @@ -570,6 +566,7 @@ endif(WIN32) # Extern add_subdirectory (extern/osg-ffmpeg-videoplayer) add_subdirectory (extern/oics) +add_subdirectory (extern/osgQt) # Components add_subdirectory (components) diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index c9245ca9fd..6401e42222 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -197,7 +197,7 @@ target_link_libraries(openmw-cs ${OSGVIEWER_LIBRARIES} ${OSGGA_LIBRARIES} ${OSGFX_LIBRARIES} - ${OSGQT_LIBRARIES} + ${EXTERN_OSGQT_LIBRARY} ${Boost_SYSTEM_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index e5b9171e09..ba31c9b8bc 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -6,7 +6,7 @@ #include #include -#include +#include #include #include #include diff --git a/extern/osgQt/CMakeLists.txt b/extern/osgQt/CMakeLists.txt new file mode 100644 index 0000000000..e8a456da99 --- /dev/null +++ b/extern/osgQt/CMakeLists.txt @@ -0,0 +1,21 @@ +set(OSGQT_LIBRARY "osgQt") + +# Sources + +set(OSGQT_SOURCE_FILES + GraphicsWindowQt.cpp +) + +include_directories(${FFMPEG_INCLUDE_DIRS}) +add_library(${OSGQT_LIBRARY} STATIC ${OSGQT_SOURCE_FILES}) + +if (DESIRED_QT_VERSION MATCHES 4) + include(${QT_USE_FILE}) + target_link_libraries(${OSGQT_LIBRARY} ${QT_QTCORE_LIBRARY} ${QT_QTOPENGL_LIBRARY}) +else() + qt5_use_modules(${OSGQT_LIBRARY} Core OpenGL) +endif() + +link_directories(${CMAKE_CURRENT_BINARY_DIR}) + +set(EXTERN_OSGQT_LIBRARY ${OSGQT_LIBRARY} PARENT_SCOPE) diff --git a/extern/osgQt/GraphicsWindowQt b/extern/osgQt/GraphicsWindowQt new file mode 100644 index 0000000000..54d069176b --- /dev/null +++ b/extern/osgQt/GraphicsWindowQt @@ -0,0 +1,193 @@ +/* -*-c++-*- OpenSceneGraph - Copyright (C) 2009 Wang Rui + * + * This library is open source and may be redistributed and/or modified under + * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or + * (at your option) any later version. The full license is in LICENSE file + * included with this distribution, and on the openscenegraph.org website. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * OpenSceneGraph Public License for more details. +*/ + +#ifndef OSGVIEWER_GRAPHICSWINDOWQT +#define OSGVIEWER_GRAPHICSWINDOWQT + +#include + +#include + +#include +#include +#include +#include +#include + +class QInputEvent; +class QGestureEvent; + +namespace osgViewer { + class ViewerBase; +} + +namespace osgQt +{ + +// forward declarations +class GraphicsWindowQt; + +/// The function sets the WindowingSystem to Qt. +void initQtWindowingSystem(); + +/** The function sets the viewer that will be used after entering + * the Qt main loop (QCoreApplication::exec()). + * + * The function also initializes internal structures required for proper + * scene rendering. + * + * The method must be called from main thread. */ +void setViewer( osgViewer::ViewerBase *viewer ); + + +class GLWidget : public QGLWidget +{ + typedef QGLWidget inherited; + +public: + + GLWidget( QWidget* parent = NULL, const QGLWidget* shareWidget = NULL, Qt::WindowFlags f = 0, bool forwardKeyEvents = false ); + GLWidget( QGLContext* context, QWidget* parent = NULL, const QGLWidget* shareWidget = NULL, Qt::WindowFlags f = 0, bool forwardKeyEvents = false ); + GLWidget( const QGLFormat& format, QWidget* parent = NULL, const QGLWidget* shareWidget = NULL, Qt::WindowFlags f = 0, bool forwardKeyEvents = false ); + virtual ~GLWidget(); + + inline void setGraphicsWindow( GraphicsWindowQt* gw ) { _gw = gw; } + inline GraphicsWindowQt* getGraphicsWindow() { return _gw; } + inline const GraphicsWindowQt* getGraphicsWindow() const { return _gw; } + + inline bool getForwardKeyEvents() const { return _forwardKeyEvents; } + virtual void setForwardKeyEvents( bool f ) { _forwardKeyEvents = f; } + + inline bool getTouchEventsEnabled() const { return _touchEventsEnabled; } + void setTouchEventsEnabled( bool e ); + + void setKeyboardModifiers( QInputEvent* event ); + + virtual void keyPressEvent( QKeyEvent* event ); + virtual void keyReleaseEvent( QKeyEvent* event ); + virtual void mousePressEvent( QMouseEvent* event ); + virtual void mouseReleaseEvent( QMouseEvent* event ); + virtual void mouseDoubleClickEvent( QMouseEvent* event ); + virtual void mouseMoveEvent( QMouseEvent* event ); + virtual void wheelEvent( QWheelEvent* event ); + virtual bool gestureEvent( QGestureEvent* event ); + +protected: + + int getNumDeferredEvents() + { + QMutexLocker lock(&_deferredEventQueueMutex); + return _deferredEventQueue.count(); + } + void enqueueDeferredEvent(QEvent::Type eventType, QEvent::Type removeEventType = QEvent::None) + { + QMutexLocker lock(&_deferredEventQueueMutex); + + if (removeEventType != QEvent::None) + { + if (_deferredEventQueue.removeOne(removeEventType)) + _eventCompressor.remove(eventType); + } + + if (_eventCompressor.find(eventType) == _eventCompressor.end()) + { + _deferredEventQueue.enqueue(eventType); + _eventCompressor.insert(eventType); + } + } + void processDeferredEvents(); + + friend class GraphicsWindowQt; + GraphicsWindowQt* _gw; + + QMutex _deferredEventQueueMutex; + QQueue _deferredEventQueue; + QSet _eventCompressor; + + bool _touchEventsEnabled; + + bool _forwardKeyEvents; + qreal _devicePixelRatio; + + virtual void resizeEvent( QResizeEvent* event ); + virtual void moveEvent( QMoveEvent* event ); + virtual void glDraw(); + virtual bool event( QEvent* event ); +}; + +class GraphicsWindowQt : public osgViewer::GraphicsWindow +{ +public: + GraphicsWindowQt( osg::GraphicsContext::Traits* traits, QWidget* parent = NULL, const QGLWidget* shareWidget = NULL, Qt::WindowFlags f = 0 ); + GraphicsWindowQt( GLWidget* widget ); + virtual ~GraphicsWindowQt(); + + inline GLWidget* getGLWidget() { return _widget; } + inline const GLWidget* getGLWidget() const { return _widget; } + + /// deprecated + inline GLWidget* getGraphWidget() { return _widget; } + /// deprecated + inline const GLWidget* getGraphWidget() const { return _widget; } + + struct WindowData : public osg::Referenced + { + WindowData( GLWidget* widget = NULL, QWidget* parent = NULL ): _widget(widget), _parent(parent) {} + GLWidget* _widget; + QWidget* _parent; + }; + + bool init( QWidget* parent, const QGLWidget* shareWidget, Qt::WindowFlags f ); + + static QGLFormat traits2qglFormat( const osg::GraphicsContext::Traits* traits ); + static void qglFormat2traits( const QGLFormat& format, osg::GraphicsContext::Traits* traits ); + static osg::GraphicsContext::Traits* createTraits( const QGLWidget* widget ); + + virtual bool setWindowRectangleImplementation( int x, int y, int width, int height ); + virtual void getWindowRectangle( int& x, int& y, int& width, int& height ); + virtual bool setWindowDecorationImplementation( bool windowDecoration ); + virtual bool getWindowDecoration() const; + virtual void grabFocus(); + virtual void grabFocusIfPointerInWindow(); + virtual void raiseWindow(); + virtual void setWindowName( const std::string& name ); + virtual std::string getWindowName(); + virtual void useCursor( bool cursorOn ); + virtual void setCursor( MouseCursor cursor ); + inline bool getTouchEventsEnabled() const { return _widget->getTouchEventsEnabled(); } + virtual void setTouchEventsEnabled( bool e ) { _widget->setTouchEventsEnabled(e); } + + + virtual bool valid() const; + virtual bool realizeImplementation(); + virtual bool isRealizedImplementation() const; + virtual void closeImplementation(); + virtual bool makeCurrentImplementation(); + virtual bool releaseContextImplementation(); + virtual void swapBuffersImplementation(); + virtual void runOperations(); + + virtual void requestWarpPointer( float x, float y ); + +protected: + + friend class GLWidget; + GLWidget* _widget; + bool _ownsWidget; + QCursor _currentCursor; + bool _realized; +}; + +} + +#endif diff --git a/extern/osgQt/GraphicsWindowQt.cpp b/extern/osgQt/GraphicsWindowQt.cpp new file mode 100644 index 0000000000..2001c8b315 --- /dev/null +++ b/extern/osgQt/GraphicsWindowQt.cpp @@ -0,0 +1,1063 @@ +/* -*-c++-*- OpenSceneGraph - Copyright (C) 2009 Wang Rui + * + * This library is open source and may be redistributed and/or modified under + * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or + * (at your option) any later version. The full license is in LICENSE file + * included with this distribution, and on the openscenegraph.org website. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * OpenSceneGraph Public License for more details. +*/ + +#include "GraphicsWindowQt" + +#include +#include +#include +#include + +#if (QT_VERSION>=QT_VERSION_CHECK(4, 6, 0)) +# define USE_GESTURES +# include +# include +#endif + +using namespace osgQt; + + +class QtKeyboardMap +{ + +public: + QtKeyboardMap() + { + mKeyMap[Qt::Key_Escape ] = osgGA::GUIEventAdapter::KEY_Escape; + mKeyMap[Qt::Key_Delete ] = osgGA::GUIEventAdapter::KEY_Delete; + mKeyMap[Qt::Key_Home ] = osgGA::GUIEventAdapter::KEY_Home; + mKeyMap[Qt::Key_Enter ] = osgGA::GUIEventAdapter::KEY_KP_Enter; + mKeyMap[Qt::Key_End ] = osgGA::GUIEventAdapter::KEY_End; + mKeyMap[Qt::Key_Return ] = osgGA::GUIEventAdapter::KEY_Return; + mKeyMap[Qt::Key_PageUp ] = osgGA::GUIEventAdapter::KEY_Page_Up; + mKeyMap[Qt::Key_PageDown ] = osgGA::GUIEventAdapter::KEY_Page_Down; + mKeyMap[Qt::Key_Left ] = osgGA::GUIEventAdapter::KEY_Left; + mKeyMap[Qt::Key_Right ] = osgGA::GUIEventAdapter::KEY_Right; + mKeyMap[Qt::Key_Up ] = osgGA::GUIEventAdapter::KEY_Up; + mKeyMap[Qt::Key_Down ] = osgGA::GUIEventAdapter::KEY_Down; + mKeyMap[Qt::Key_Backspace ] = osgGA::GUIEventAdapter::KEY_BackSpace; + mKeyMap[Qt::Key_Tab ] = osgGA::GUIEventAdapter::KEY_Tab; + mKeyMap[Qt::Key_Space ] = osgGA::GUIEventAdapter::KEY_Space; + mKeyMap[Qt::Key_Delete ] = osgGA::GUIEventAdapter::KEY_Delete; + mKeyMap[Qt::Key_Alt ] = osgGA::GUIEventAdapter::KEY_Alt_L; + mKeyMap[Qt::Key_Shift ] = osgGA::GUIEventAdapter::KEY_Shift_L; + mKeyMap[Qt::Key_Control ] = osgGA::GUIEventAdapter::KEY_Control_L; + mKeyMap[Qt::Key_Meta ] = osgGA::GUIEventAdapter::KEY_Meta_L; + + mKeyMap[Qt::Key_F1 ] = osgGA::GUIEventAdapter::KEY_F1; + mKeyMap[Qt::Key_F2 ] = osgGA::GUIEventAdapter::KEY_F2; + mKeyMap[Qt::Key_F3 ] = osgGA::GUIEventAdapter::KEY_F3; + mKeyMap[Qt::Key_F4 ] = osgGA::GUIEventAdapter::KEY_F4; + mKeyMap[Qt::Key_F5 ] = osgGA::GUIEventAdapter::KEY_F5; + mKeyMap[Qt::Key_F6 ] = osgGA::GUIEventAdapter::KEY_F6; + mKeyMap[Qt::Key_F7 ] = osgGA::GUIEventAdapter::KEY_F7; + mKeyMap[Qt::Key_F8 ] = osgGA::GUIEventAdapter::KEY_F8; + mKeyMap[Qt::Key_F9 ] = osgGA::GUIEventAdapter::KEY_F9; + mKeyMap[Qt::Key_F10 ] = osgGA::GUIEventAdapter::KEY_F10; + mKeyMap[Qt::Key_F11 ] = osgGA::GUIEventAdapter::KEY_F11; + mKeyMap[Qt::Key_F12 ] = osgGA::GUIEventAdapter::KEY_F12; + mKeyMap[Qt::Key_F13 ] = osgGA::GUIEventAdapter::KEY_F13; + mKeyMap[Qt::Key_F14 ] = osgGA::GUIEventAdapter::KEY_F14; + mKeyMap[Qt::Key_F15 ] = osgGA::GUIEventAdapter::KEY_F15; + mKeyMap[Qt::Key_F16 ] = osgGA::GUIEventAdapter::KEY_F16; + mKeyMap[Qt::Key_F17 ] = osgGA::GUIEventAdapter::KEY_F17; + mKeyMap[Qt::Key_F18 ] = osgGA::GUIEventAdapter::KEY_F18; + mKeyMap[Qt::Key_F19 ] = osgGA::GUIEventAdapter::KEY_F19; + mKeyMap[Qt::Key_F20 ] = osgGA::GUIEventAdapter::KEY_F20; + + mKeyMap[Qt::Key_hyphen ] = '-'; + mKeyMap[Qt::Key_Equal ] = '='; + + mKeyMap[Qt::Key_division ] = osgGA::GUIEventAdapter::KEY_KP_Divide; + mKeyMap[Qt::Key_multiply ] = osgGA::GUIEventAdapter::KEY_KP_Multiply; + mKeyMap[Qt::Key_Minus ] = '-'; + mKeyMap[Qt::Key_Plus ] = '+'; + //mKeyMap[Qt::Key_H ] = osgGA::GUIEventAdapter::KEY_KP_Home; + //mKeyMap[Qt::Key_ ] = osgGA::GUIEventAdapter::KEY_KP_Up; + //mKeyMap[92 ] = osgGA::GUIEventAdapter::KEY_KP_Page_Up; + //mKeyMap[86 ] = osgGA::GUIEventAdapter::KEY_KP_Left; + //mKeyMap[87 ] = osgGA::GUIEventAdapter::KEY_KP_Begin; + //mKeyMap[88 ] = osgGA::GUIEventAdapter::KEY_KP_Right; + //mKeyMap[83 ] = osgGA::GUIEventAdapter::KEY_KP_End; + //mKeyMap[84 ] = osgGA::GUIEventAdapter::KEY_KP_Down; + //mKeyMap[85 ] = osgGA::GUIEventAdapter::KEY_KP_Page_Down; + mKeyMap[Qt::Key_Insert ] = osgGA::GUIEventAdapter::KEY_KP_Insert; + //mKeyMap[Qt::Key_Delete ] = osgGA::GUIEventAdapter::KEY_KP_Delete; + } + + ~QtKeyboardMap() + { + } + + int remapKey(QKeyEvent* event) + { + KeyMap::iterator itr = mKeyMap.find(event->key()); + if (itr == mKeyMap.end()) + { + return int(*(event->text().toLatin1().data())); + } + else + return itr->second; + } + + private: + typedef std::map KeyMap; + KeyMap mKeyMap; +}; + +static QtKeyboardMap s_QtKeyboardMap; + + +/// The object responsible for the scene re-rendering. +class HeartBeat : public QObject { +public: + int _timerId; + osg::Timer _lastFrameStartTime; + osg::observer_ptr< osgViewer::ViewerBase > _viewer; + + virtual ~HeartBeat(); + + void init( osgViewer::ViewerBase *viewer ); + void stopTimer(); + void timerEvent( QTimerEvent *event ); + + static HeartBeat* instance(); +private: + HeartBeat(); + + static QPointer heartBeat; +}; + +QPointer HeartBeat::heartBeat; + +#if (QT_VERSION < QT_VERSION_CHECK(5, 2, 0)) + #define GETDEVICEPIXELRATIO() 1.0 +#else + #define GETDEVICEPIXELRATIO() devicePixelRatio() +#endif + +GLWidget::GLWidget( QWidget* parent, const QGLWidget* shareWidget, Qt::WindowFlags f, bool forwardKeyEvents ) +: QGLWidget(parent, shareWidget, f), +_gw( NULL ), +_touchEventsEnabled( false ), +_forwardKeyEvents( forwardKeyEvents ) +{ + _devicePixelRatio = GETDEVICEPIXELRATIO(); +} + +GLWidget::GLWidget( QGLContext* context, QWidget* parent, const QGLWidget* shareWidget, Qt::WindowFlags f, + bool forwardKeyEvents ) +: QGLWidget(context, parent, shareWidget, f), +_gw( NULL ), +_touchEventsEnabled( false ), +_forwardKeyEvents( forwardKeyEvents ) +{ + _devicePixelRatio = GETDEVICEPIXELRATIO(); +} + +GLWidget::GLWidget( const QGLFormat& format, QWidget* parent, const QGLWidget* shareWidget, Qt::WindowFlags f, + bool forwardKeyEvents ) +: QGLWidget(format, parent, shareWidget, f), +_gw( NULL ), +_touchEventsEnabled( false ), +_forwardKeyEvents( forwardKeyEvents ) +{ + _devicePixelRatio = GETDEVICEPIXELRATIO(); +} + +GLWidget::~GLWidget() +{ + // close GraphicsWindowQt and remove the reference to us + if( _gw ) + { + _gw->close(); + _gw->_widget = NULL; + _gw = NULL; + } +} + +void GLWidget::setTouchEventsEnabled(bool e) +{ +#ifdef USE_GESTURES + if (e==_touchEventsEnabled) + return; + + _touchEventsEnabled = e; + + if (_touchEventsEnabled) + { + grabGesture(Qt::PinchGesture); + } + else + { + ungrabGesture(Qt::PinchGesture); + } +#endif +} + +void GLWidget::processDeferredEvents() +{ + QQueue deferredEventQueueCopy; + { + QMutexLocker lock(&_deferredEventQueueMutex); + deferredEventQueueCopy = _deferredEventQueue; + _eventCompressor.clear(); + _deferredEventQueue.clear(); + } + + while (!deferredEventQueueCopy.isEmpty()) + { + QEvent event(deferredEventQueueCopy.dequeue()); + QGLWidget::event(&event); + } +} + +bool GLWidget::event( QEvent* event ) +{ +#ifdef USE_GESTURES + if ( event->type()==QEvent::Gesture ) + return gestureEvent(static_cast(event)); +#endif + + // QEvent::Hide + // + // workaround "Qt-workaround" that does glFinish before hiding the widget + // (the Qt workaround was seen at least in Qt 4.6.3 and 4.7.0) + // + // Qt makes the context current, performs glFinish, and releases the context. + // This makes the problem in OSG multithreaded environment as the context + // is active in another thread, thus it can not be made current for the purpose + // of glFinish in this thread. + + // QEvent::ParentChange + // + // Reparenting GLWidget may create a new underlying window and a new GL context. + // Qt will then call doneCurrent on the GL context about to be deleted. The thread + // where old GL context was current has no longer current context to render to and + // we cannot make new GL context current in this thread. + + // We workaround above problems by deferring execution of problematic event requests. + // These events has to be enqueue and executed later in a main GUI thread (GUI operations + // outside the main thread are not allowed) just before makeCurrent is called from the + // right thread. The good place for doing that is right after swap in a swapBuffersImplementation. + + if (event->type() == QEvent::Hide) + { + // enqueue only the last of QEvent::Hide and QEvent::Show + enqueueDeferredEvent(QEvent::Hide, QEvent::Show); + return true; + } + else if (event->type() == QEvent::Show) + { + // enqueue only the last of QEvent::Show or QEvent::Hide + enqueueDeferredEvent(QEvent::Show, QEvent::Hide); + return true; + } + else if (event->type() == QEvent::ParentChange) + { + // enqueue only the last QEvent::ParentChange + enqueueDeferredEvent(QEvent::ParentChange); + return true; + } + + // perform regular event handling + return QGLWidget::event( event ); +} + +void GLWidget::setKeyboardModifiers( QInputEvent* event ) +{ + int modkey = event->modifiers() & (Qt::ShiftModifier | Qt::ControlModifier | Qt::AltModifier); + unsigned int mask = 0; + if ( modkey & Qt::ShiftModifier ) mask |= osgGA::GUIEventAdapter::MODKEY_SHIFT; + if ( modkey & Qt::ControlModifier ) mask |= osgGA::GUIEventAdapter::MODKEY_CTRL; + if ( modkey & Qt::AltModifier ) mask |= osgGA::GUIEventAdapter::MODKEY_ALT; + _gw->getEventQueue()->getCurrentEventState()->setModKeyMask( mask ); +} + +void GLWidget::resizeEvent( QResizeEvent* event ) +{ + const QSize& size = event->size(); + + int scaled_width = static_cast(size.width()*_devicePixelRatio); + int scaled_height = static_cast(size.height()*_devicePixelRatio); + _gw->resized( x(), y(), scaled_width, scaled_height); + _gw->getEventQueue()->windowResize( x(), y(), scaled_width, scaled_height ); + _gw->requestRedraw(); +} + +void GLWidget::moveEvent( QMoveEvent* event ) +{ + const QPoint& pos = event->pos(); + int scaled_width = static_cast(width()*_devicePixelRatio); + int scaled_height = static_cast(height()*_devicePixelRatio); + _gw->resized( pos.x(), pos.y(), scaled_width, scaled_height ); + _gw->getEventQueue()->windowResize( pos.x(), pos.y(), scaled_width, scaled_height ); +} + +void GLWidget::glDraw() +{ + _gw->requestRedraw(); +} + +void GLWidget::keyPressEvent( QKeyEvent* event ) +{ + setKeyboardModifiers( event ); + int value = s_QtKeyboardMap.remapKey( event ); + _gw->getEventQueue()->keyPress( value ); + + // this passes the event to the regular Qt key event processing, + // among others, it closes popup windows on ESC and forwards the event to the parent widgets + if( _forwardKeyEvents ) + inherited::keyPressEvent( event ); +} + +void GLWidget::keyReleaseEvent( QKeyEvent* event ) +{ + if( event->isAutoRepeat() ) + { + event->ignore(); + } + else + { + setKeyboardModifiers( event ); + int value = s_QtKeyboardMap.remapKey( event ); + _gw->getEventQueue()->keyRelease( value ); + } + + // this passes the event to the regular Qt key event processing, + // among others, it closes popup windows on ESC and forwards the event to the parent widgets + if( _forwardKeyEvents ) + inherited::keyReleaseEvent( event ); +} + +void GLWidget::mousePressEvent( QMouseEvent* event ) +{ + int button = 0; + switch ( event->button() ) + { + case Qt::LeftButton: button = 1; break; + case Qt::MidButton: button = 2; break; + case Qt::RightButton: button = 3; break; + case Qt::NoButton: button = 0; break; + default: button = 0; break; + } + setKeyboardModifiers( event ); + _gw->getEventQueue()->mouseButtonPress( event->x()*_devicePixelRatio, event->y()*_devicePixelRatio, button ); +} + +void GLWidget::mouseReleaseEvent( QMouseEvent* event ) +{ + int button = 0; + switch ( event->button() ) + { + case Qt::LeftButton: button = 1; break; + case Qt::MidButton: button = 2; break; + case Qt::RightButton: button = 3; break; + case Qt::NoButton: button = 0; break; + default: button = 0; break; + } + setKeyboardModifiers( event ); + _gw->getEventQueue()->mouseButtonRelease( event->x()*_devicePixelRatio, event->y()*_devicePixelRatio, button ); +} + +void GLWidget::mouseDoubleClickEvent( QMouseEvent* event ) +{ + int button = 0; + switch ( event->button() ) + { + case Qt::LeftButton: button = 1; break; + case Qt::MidButton: button = 2; break; + case Qt::RightButton: button = 3; break; + case Qt::NoButton: button = 0; break; + default: button = 0; break; + } + setKeyboardModifiers( event ); + _gw->getEventQueue()->mouseDoubleButtonPress( event->x()*_devicePixelRatio, event->y()*_devicePixelRatio, button ); +} + +void GLWidget::mouseMoveEvent( QMouseEvent* event ) +{ + setKeyboardModifiers( event ); + _gw->getEventQueue()->mouseMotion( event->x()*_devicePixelRatio, event->y()*_devicePixelRatio ); +} + +void GLWidget::wheelEvent( QWheelEvent* event ) +{ + setKeyboardModifiers( event ); + _gw->getEventQueue()->mouseScroll( + event->orientation() == Qt::Vertical ? + (event->delta()>0 ? osgGA::GUIEventAdapter::SCROLL_UP : osgGA::GUIEventAdapter::SCROLL_DOWN) : + (event->delta()>0 ? osgGA::GUIEventAdapter::SCROLL_LEFT : osgGA::GUIEventAdapter::SCROLL_RIGHT) ); +} + +#ifdef USE_GESTURES +static osgGA::GUIEventAdapter::TouchPhase translateQtGestureState( Qt::GestureState state ) +{ + osgGA::GUIEventAdapter::TouchPhase touchPhase; + switch ( state ) + { + case Qt::GestureStarted: + touchPhase = osgGA::GUIEventAdapter::TOUCH_BEGAN; + break; + case Qt::GestureUpdated: + touchPhase = osgGA::GUIEventAdapter::TOUCH_MOVED; + break; + case Qt::GestureFinished: + case Qt::GestureCanceled: + touchPhase = osgGA::GUIEventAdapter::TOUCH_ENDED; + break; + default: + touchPhase = osgGA::GUIEventAdapter::TOUCH_UNKNOWN; + }; + + return touchPhase; +} +#endif + + +bool GLWidget::gestureEvent( QGestureEvent* qevent ) +{ +#ifndef USE_GESTURES + return false; +#else + + bool accept = false; + + if ( QPinchGesture* pinch = static_cast(qevent->gesture(Qt::PinchGesture) ) ) + { + const QPointF qcenterf = pinch->centerPoint(); + const float angle = pinch->totalRotationAngle(); + const float scale = pinch->totalScaleFactor(); + + const QPoint pinchCenterQt = mapFromGlobal(qcenterf.toPoint()); + const osg::Vec2 pinchCenter( pinchCenterQt.x(), pinchCenterQt.y() ); + + //We don't have absolute positions of the two touches, only a scale and rotation + //Hence we create pseudo-coordinates which are reasonable, and centered around the + //real position + const float radius = (width()+height())/4; + const osg::Vec2 vector( scale*cos(angle)*radius, scale*sin(angle)*radius); + const osg::Vec2 p0 = pinchCenter+vector; + const osg::Vec2 p1 = pinchCenter-vector; + + osg::ref_ptr event = 0; + const osgGA::GUIEventAdapter::TouchPhase touchPhase = translateQtGestureState( pinch->state() ); + if ( touchPhase==osgGA::GUIEventAdapter::TOUCH_BEGAN ) + { + event = _gw->getEventQueue()->touchBegan(0 , touchPhase, p0[0], p0[1] ); + } + else if ( touchPhase==osgGA::GUIEventAdapter::TOUCH_MOVED ) + { + event = _gw->getEventQueue()->touchMoved( 0, touchPhase, p0[0], p0[1] ); + } + else + { + event = _gw->getEventQueue()->touchEnded( 0, touchPhase, p0[0], p0[1], 1 ); + } + + if ( event ) + { + event->addTouchPoint( 1, touchPhase, p1[0], p1[1] ); + accept = true; + } + } + + if ( accept ) + qevent->accept(); + + return accept; +#endif +} + + + +GraphicsWindowQt::GraphicsWindowQt( osg::GraphicsContext::Traits* traits, QWidget* parent, const QGLWidget* shareWidget, Qt::WindowFlags f ) +: _realized(false) +{ + + _widget = NULL; + _traits = traits; + init( parent, shareWidget, f ); +} + +GraphicsWindowQt::GraphicsWindowQt( GLWidget* widget ) +: _realized(false) +{ + _widget = widget; + _traits = _widget ? createTraits( _widget ) : new osg::GraphicsContext::Traits; + init( NULL, NULL, 0 ); +} + +GraphicsWindowQt::~GraphicsWindowQt() +{ + close(); + + // remove reference from GLWidget + if ( _widget ) + _widget->_gw = NULL; +} + +bool GraphicsWindowQt::init( QWidget* parent, const QGLWidget* shareWidget, Qt::WindowFlags f ) +{ + // update _widget and parent by WindowData + WindowData* windowData = _traits.get() ? dynamic_cast(_traits->inheritedWindowData.get()) : 0; + if ( !_widget ) + _widget = windowData ? windowData->_widget : NULL; + if ( !parent ) + parent = windowData ? windowData->_parent : NULL; + + // create widget if it does not exist + _ownsWidget = _widget == NULL; + if ( !_widget ) + { + // shareWidget + if ( !shareWidget ) { + GraphicsWindowQt* sharedContextQt = dynamic_cast(_traits->sharedContext.get()); + if ( sharedContextQt ) + shareWidget = sharedContextQt->getGLWidget(); + } + + // WindowFlags + Qt::WindowFlags flags = f | Qt::Window | Qt::CustomizeWindowHint; + if ( _traits->windowDecoration ) + flags |= Qt::WindowTitleHint | Qt::WindowMinMaxButtonsHint | Qt::WindowSystemMenuHint +#if (QT_VERSION_CHECK(4, 5, 0) <= QT_VERSION) + | Qt::WindowCloseButtonHint +#endif + ; + + // create widget + _widget = new GLWidget( traits2qglFormat( _traits.get() ), parent, shareWidget, flags ); + } + + // set widget name and position + // (do not set it when we inherited the widget) + if ( _ownsWidget ) + { + _widget->setWindowTitle( _traits->windowName.c_str() ); + _widget->move( _traits->x, _traits->y ); + if ( !_traits->supportsResize ) _widget->setFixedSize( _traits->width, _traits->height ); + else _widget->resize( _traits->width, _traits->height ); + } + + // initialize widget properties + _widget->setAutoBufferSwap( false ); + _widget->setMouseTracking( true ); + _widget->setFocusPolicy( Qt::WheelFocus ); + _widget->setGraphicsWindow( this ); + useCursor( _traits->useCursor ); + + // initialize State + setState( new osg::State ); + getState()->setGraphicsContext(this); + + // initialize contextID + if ( _traits.valid() && _traits->sharedContext.valid() ) + { + getState()->setContextID( _traits->sharedContext->getState()->getContextID() ); + incrementContextIDUsageCount( getState()->getContextID() ); + } + else + { + getState()->setContextID( osg::GraphicsContext::createNewContextID() ); + } + + // make sure the event queue has the correct window rectangle size and input range + getEventQueue()->syncWindowRectangleWithGraphicsContext(); + + return true; +} + +QGLFormat GraphicsWindowQt::traits2qglFormat( const osg::GraphicsContext::Traits* traits ) +{ + QGLFormat format( QGLFormat::defaultFormat() ); + + format.setAlphaBufferSize( traits->alpha ); + format.setRedBufferSize( traits->red ); + format.setGreenBufferSize( traits->green ); + format.setBlueBufferSize( traits->blue ); + format.setDepthBufferSize( traits->depth ); + format.setStencilBufferSize( traits->stencil ); + format.setSampleBuffers( traits->sampleBuffers ); + format.setSamples( traits->samples ); + + format.setAlpha( traits->alpha>0 ); + format.setDepth( traits->depth>0 ); + format.setStencil( traits->stencil>0 ); + format.setDoubleBuffer( traits->doubleBuffer ); + format.setSwapInterval( traits->vsync ? 1 : 0 ); + format.setStereo( traits->quadBufferStereo ? 1 : 0 ); + + return format; +} + +void GraphicsWindowQt::qglFormat2traits( const QGLFormat& format, osg::GraphicsContext::Traits* traits ) +{ + traits->red = format.redBufferSize(); + traits->green = format.greenBufferSize(); + traits->blue = format.blueBufferSize(); + traits->alpha = format.alpha() ? format.alphaBufferSize() : 0; + traits->depth = format.depth() ? format.depthBufferSize() : 0; + traits->stencil = format.stencil() ? format.stencilBufferSize() : 0; + + traits->sampleBuffers = format.sampleBuffers() ? 1 : 0; + traits->samples = format.samples(); + + traits->quadBufferStereo = format.stereo(); + traits->doubleBuffer = format.doubleBuffer(); + + traits->vsync = format.swapInterval() >= 1; +} + +osg::GraphicsContext::Traits* GraphicsWindowQt::createTraits( const QGLWidget* widget ) +{ + osg::GraphicsContext::Traits *traits = new osg::GraphicsContext::Traits; + + qglFormat2traits( widget->format(), traits ); + + QRect r = widget->geometry(); + traits->x = r.x(); + traits->y = r.y(); + traits->width = r.width(); + traits->height = r.height(); + + traits->windowName = widget->windowTitle().toLocal8Bit().data(); + Qt::WindowFlags f = widget->windowFlags(); + traits->windowDecoration = ( f & Qt::WindowTitleHint ) && + ( f & Qt::WindowMinMaxButtonsHint ) && + ( f & Qt::WindowSystemMenuHint ); + QSizePolicy sp = widget->sizePolicy(); + traits->supportsResize = sp.horizontalPolicy() != QSizePolicy::Fixed || + sp.verticalPolicy() != QSizePolicy::Fixed; + + return traits; +} + +bool GraphicsWindowQt::setWindowRectangleImplementation( int x, int y, int width, int height ) +{ + if ( _widget == NULL ) + return false; + + _widget->setGeometry( x, y, width, height ); + return true; +} + +void GraphicsWindowQt::getWindowRectangle( int& x, int& y, int& width, int& height ) +{ + if ( _widget ) + { + const QRect& geom = _widget->geometry(); + x = geom.x(); + y = geom.y(); + width = geom.width(); + height = geom.height(); + } +} + +bool GraphicsWindowQt::setWindowDecorationImplementation( bool windowDecoration ) +{ + Qt::WindowFlags flags = Qt::Window|Qt::CustomizeWindowHint;//|Qt::WindowStaysOnTopHint; + if ( windowDecoration ) + flags |= Qt::WindowTitleHint|Qt::WindowMinMaxButtonsHint|Qt::WindowSystemMenuHint; + _traits->windowDecoration = windowDecoration; + + if ( _widget ) + { + _widget->setWindowFlags( flags ); + + return true; + } + + return false; +} + +bool GraphicsWindowQt::getWindowDecoration() const +{ + return _traits->windowDecoration; +} + +void GraphicsWindowQt::grabFocus() +{ + if ( _widget ) + _widget->setFocus( Qt::ActiveWindowFocusReason ); +} + +void GraphicsWindowQt::grabFocusIfPointerInWindow() +{ + if ( _widget->underMouse() ) + _widget->setFocus( Qt::ActiveWindowFocusReason ); +} + +void GraphicsWindowQt::raiseWindow() +{ + if ( _widget ) + _widget->raise(); +} + +void GraphicsWindowQt::setWindowName( const std::string& name ) +{ + if ( _widget ) + _widget->setWindowTitle( name.c_str() ); +} + +std::string GraphicsWindowQt::getWindowName() +{ + return _widget ? _widget->windowTitle().toStdString() : ""; +} + +void GraphicsWindowQt::useCursor( bool cursorOn ) +{ + if ( _widget ) + { + _traits->useCursor = cursorOn; + if ( !cursorOn ) _widget->setCursor( Qt::BlankCursor ); + else _widget->setCursor( _currentCursor ); + } +} + +void GraphicsWindowQt::setCursor( MouseCursor cursor ) +{ + if ( cursor==InheritCursor && _widget ) + { + _widget->unsetCursor(); + } + + switch ( cursor ) + { + case NoCursor: _currentCursor = Qt::BlankCursor; break; + case RightArrowCursor: case LeftArrowCursor: _currentCursor = Qt::ArrowCursor; break; + case InfoCursor: _currentCursor = Qt::SizeAllCursor; break; + case DestroyCursor: _currentCursor = Qt::ForbiddenCursor; break; + case HelpCursor: _currentCursor = Qt::WhatsThisCursor; break; + case CycleCursor: _currentCursor = Qt::ForbiddenCursor; break; + case SprayCursor: _currentCursor = Qt::SizeAllCursor; break; + case WaitCursor: _currentCursor = Qt::WaitCursor; break; + case TextCursor: _currentCursor = Qt::IBeamCursor; break; + case CrosshairCursor: _currentCursor = Qt::CrossCursor; break; + case HandCursor: _currentCursor = Qt::OpenHandCursor; break; + case UpDownCursor: _currentCursor = Qt::SizeVerCursor; break; + case LeftRightCursor: _currentCursor = Qt::SizeHorCursor; break; + case TopSideCursor: case BottomSideCursor: _currentCursor = Qt::UpArrowCursor; break; + case LeftSideCursor: case RightSideCursor: _currentCursor = Qt::SizeHorCursor; break; + case TopLeftCorner: _currentCursor = Qt::SizeBDiagCursor; break; + case TopRightCorner: _currentCursor = Qt::SizeFDiagCursor; break; + case BottomRightCorner: _currentCursor = Qt::SizeBDiagCursor; break; + case BottomLeftCorner: _currentCursor = Qt::SizeFDiagCursor; break; + default: break; + }; + if ( _widget ) _widget->setCursor( _currentCursor ); +} + +bool GraphicsWindowQt::valid() const +{ + return _widget && _widget->isValid(); +} + +bool GraphicsWindowQt::realizeImplementation() +{ + // save the current context + // note: this will save only Qt-based contexts + const QGLContext *savedContext = QGLContext::currentContext(); + + // initialize GL context for the widget + if ( !valid() ) + _widget->glInit(); + + // make current + _realized = true; + bool result = makeCurrent(); + _realized = false; + + // fail if we do not have current context + if ( !result ) + { + if ( savedContext ) + const_cast< QGLContext* >( savedContext )->makeCurrent(); + + OSG_WARN << "Window realize: Can make context current." << std::endl; + return false; + } + + _realized = true; + + // make sure the event queue has the correct window rectangle size and input range + getEventQueue()->syncWindowRectangleWithGraphicsContext(); + + // make this window's context not current + // note: this must be done as we will probably make the context current from another thread + // and it is not allowed to have one context current in two threads + if( !releaseContext() ) + OSG_WARN << "Window realize: Can not release context." << std::endl; + + // restore previous context + if ( savedContext ) + const_cast< QGLContext* >( savedContext )->makeCurrent(); + + return true; +} + +bool GraphicsWindowQt::isRealizedImplementation() const +{ + return _realized; +} + +void GraphicsWindowQt::closeImplementation() +{ + if ( _widget ) + _widget->close(); + _realized = false; +} + +void GraphicsWindowQt::runOperations() +{ + // While in graphics thread this is last chance to do something useful before + // graphics thread will execute its operations. + if (_widget->getNumDeferredEvents() > 0) + _widget->processDeferredEvents(); + + if (QGLContext::currentContext() != _widget->context()) + _widget->makeCurrent(); + + GraphicsWindow::runOperations(); +} + +bool GraphicsWindowQt::makeCurrentImplementation() +{ + if (_widget->getNumDeferredEvents() > 0) + _widget->processDeferredEvents(); + + _widget->makeCurrent(); + + return true; +} + +bool GraphicsWindowQt::releaseContextImplementation() +{ + _widget->doneCurrent(); + return true; +} + +void GraphicsWindowQt::swapBuffersImplementation() +{ + _widget->swapBuffers(); + + // FIXME: the processDeferredEvents should really be executed in a GUI (main) thread context but + // I couln't find any reliable way to do this. For now, lets hope non of *GUI thread only operations* will + // be executed in a QGLWidget::event handler. On the other hand, calling GUI only operations in the + // QGLWidget event handler is an indication of a Qt bug. + if (_widget->getNumDeferredEvents() > 0) + _widget->processDeferredEvents(); + + // We need to call makeCurrent here to restore our previously current context + // which may be changed by the processDeferredEvents function. + if (QGLContext::currentContext() != _widget->context()) + _widget->makeCurrent(); +} + +void GraphicsWindowQt::requestWarpPointer( float x, float y ) +{ + if ( _widget ) + QCursor::setPos( _widget->mapToGlobal(QPoint((int)x,(int)y)) ); +} + + +class QtWindowingSystem : public osg::GraphicsContext::WindowingSystemInterface +{ +public: + + QtWindowingSystem() + { + OSG_INFO << "QtWindowingSystemInterface()" << std::endl; + } + + ~QtWindowingSystem() + { + if (osg::Referenced::getDeleteHandler()) + { + osg::Referenced::getDeleteHandler()->setNumFramesToRetainObjects(0); + osg::Referenced::getDeleteHandler()->flushAll(); + } + } + + // Access the Qt windowing system through this singleton class. + static QtWindowingSystem* getInterface() + { + static QtWindowingSystem* qtInterface = new QtWindowingSystem; + return qtInterface; + } + + // Return the number of screens present in the system + virtual unsigned int getNumScreens( const osg::GraphicsContext::ScreenIdentifier& /*si*/ ) + { + OSG_WARN << "osgQt: getNumScreens() not implemented yet." << std::endl; + return 0; + } + + // Return the resolution of specified screen + // (0,0) is returned if screen is unknown + virtual void getScreenSettings( const osg::GraphicsContext::ScreenIdentifier& /*si*/, osg::GraphicsContext::ScreenSettings & /*resolution*/ ) + { + OSG_WARN << "osgQt: getScreenSettings() not implemented yet." << std::endl; + } + + // Set the resolution for given screen + virtual bool setScreenSettings( const osg::GraphicsContext::ScreenIdentifier& /*si*/, const osg::GraphicsContext::ScreenSettings & /*resolution*/ ) + { + OSG_WARN << "osgQt: setScreenSettings() not implemented yet." << std::endl; + return false; + } + + // Enumerates available resolutions + virtual void enumerateScreenSettings( const osg::GraphicsContext::ScreenIdentifier& /*screenIdentifier*/, osg::GraphicsContext::ScreenSettingsList & /*resolution*/ ) + { + OSG_WARN << "osgQt: enumerateScreenSettings() not implemented yet." << std::endl; + } + + // Create a graphics context with given traits + virtual osg::GraphicsContext* createGraphicsContext( osg::GraphicsContext::Traits* traits ) + { + if (traits->pbuffer) + { + OSG_WARN << "osgQt: createGraphicsContext - pbuffer not implemented yet." << std::endl; + return NULL; + } + else + { + osg::ref_ptr< GraphicsWindowQt > window = new GraphicsWindowQt( traits ); + if (window->valid()) return window.release(); + else return NULL; + } + } + +private: + + // No implementation for these + QtWindowingSystem( const QtWindowingSystem& ); + QtWindowingSystem& operator=( const QtWindowingSystem& ); +}; + + +// declare C entry point for static compilation. +extern "C" void graphicswindow_Qt(void) +{ + osg::GraphicsContext::setWindowingSystemInterface(QtWindowingSystem::getInterface()); +} + + +void osgQt::initQtWindowingSystem() +{ + graphicswindow_Qt(); +} + + + +void osgQt::setViewer( osgViewer::ViewerBase *viewer ) +{ + HeartBeat::instance()->init( viewer ); +} + + +/// Constructor. Must be called from main thread. +HeartBeat::HeartBeat() : _timerId( 0 ) +{ +} + + +/// Destructor. Must be called from main thread. +HeartBeat::~HeartBeat() +{ + stopTimer(); +} + +HeartBeat* HeartBeat::instance() +{ + if (!heartBeat) + { + heartBeat = new HeartBeat(); + } + return heartBeat; +} + +void HeartBeat::stopTimer() +{ + if ( _timerId != 0 ) + { + killTimer( _timerId ); + _timerId = 0; + } +} + + +/// Initializes the loop for viewer. Must be called from main thread. +void HeartBeat::init( osgViewer::ViewerBase *viewer ) +{ + if( _viewer == viewer ) + return; + + stopTimer(); + + _viewer = viewer; + + if( viewer ) + { + _timerId = startTimer( 0 ); + _lastFrameStartTime.setStartTick( 0 ); + } +} + + +void HeartBeat::timerEvent( QTimerEvent */*event*/ ) +{ + osg::ref_ptr< osgViewer::ViewerBase > viewer; + if( !_viewer.lock( viewer ) ) + { + // viewer has been deleted -> stop timer + stopTimer(); + return; + } + + // limit the frame rate + if( viewer->getRunMaxFrameRate() > 0.0) + { + double dt = _lastFrameStartTime.time_s(); + double minFrameTime = 1.0 / viewer->getRunMaxFrameRate(); + if (dt < minFrameTime) + OpenThreads::Thread::microSleep(static_cast(1000000.0*(minFrameTime-dt))); + } + else + { + // avoid excessive CPU loading when no frame is required in ON_DEMAND mode + if( viewer->getRunFrameScheme() == osgViewer::ViewerBase::ON_DEMAND ) + { + double dt = _lastFrameStartTime.time_s(); + if (dt < 0.01) + OpenThreads::Thread::microSleep(static_cast(1000000.0*(0.01-dt))); + } + + // record start frame time + _lastFrameStartTime.setStartTick(); + + // make frame + if( viewer->getRunFrameScheme() == osgViewer::ViewerBase::ON_DEMAND ) + { + if( viewer->checkNeedToDoFrame() ) + { + viewer->frame(); + } + } + else + { + viewer->frame(); + } + } +}