/**************************************************************************\ * Copyright (c) Kongsberg Oil & Gas Technologies AS * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of the copyright holder nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \**************************************************************************/ /*! \class SIM::Coin3D::Quarter::QuarterWidget QuarterWidget.h Quarter/QuarterWidget.h \brief The QuarterWidget class is the main class in Quarter. It provides a widget for Coin rendering. It provides scenegraph management and event handling. If you want to modify the GL format for an existing QuarterWidget, you can set up a new GL context for the widget, e.g.: \code QGLContext * context = new QGLContext(QGLFormat(QGL::SampleBuffers), viewer); if (context->create()) { viewer->setContext(context); } \endcode */ #ifdef _MSC_VER #pragma warning(disable : 4267) #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if COIN_MAJOR_VERSION >= 4 #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include "QuarterWidget.h" #include "InteractionMode.h" #include "QuarterP.h" #include "QuarterWidgetP.h" #include "eventhandlers/EventFilter.h" #include "eventhandlers/DragDropHandler.h" using namespace SIM::Coin3D::Quarter; /*! \enum SIM::Coin3D::Quarter::QuarterWidget::TransparencyType Various settings for how to do rendering of transparent objects in the scene. Some of the settings will provide faster rendering, while others gives you better quality rendering. See \ref SoGLRenderAction::TransparencyType for a full description of the modes */ /*! \enum SIM::Coin3D::Quarter::QuarterWidget::RenderMode Sets how rendering of primitives is done. See \ref SoRenderManager::RenderMode for a full description of the modes */ /*! \enum SIM::Coin3D::Quarter::QuarterWidget::StereoMode Sets how stereo rendering is performed. See \ref SoRenderManager::StereoMode for a full description of the modes */ enum StereoMode { MONO = SoRenderManager::MONO, ANAGLYPH = SoRenderManager::ANAGLYPH, QUAD_BUFFER = SoRenderManager::QUAD_BUFFER, INTERLEAVED_ROWS = SoRenderManager::INTERLEAVED_ROWS, INTERLEAVED_COLUMNS = SoRenderManager::INTERLEAVED_COLUMNS }; #define PRIVATE(obj) obj->pimpl #ifndef GL_MULTISAMPLE_BIT_EXT #define GL_MULTISAMPLE_BIT_EXT 0x20000000 #endif //We need to avoid buffer swapping when initializing a QPainter on this widget class CustomGLWidget : public QOpenGLWidget { public: QSurfaceFormat myFormat; CustomGLWidget(const QSurfaceFormat& format, QWidget* parent = nullptr, const QOpenGLWidget* shareWidget = nullptr, Qt::WindowFlags f = Qt::WindowFlags()) : QOpenGLWidget(parent, f), myFormat(format) { Q_UNUSED(shareWidget); QSurfaceFormat surfaceFormat(format); surfaceFormat.setSwapBehavior(QSurfaceFormat::DoubleBuffer); // With the settings below we could determine deprecated OpenGL API // but can't do this since otherwise it will complain about almost any // OpenGL call in Coin3d //surfaceFormat.setMajorVersion(3); //surfaceFormat.setMinorVersion(2); //surfaceFormat.setProfile(QSurfaceFormat::CoreProfile); #if defined (_DEBUG) && 0 surfaceFormat.setOption(QSurfaceFormat::DebugContext); #endif setFormat(surfaceFormat); } ~CustomGLWidget() { } void initializeGL() { QOpenGLContext *context = QOpenGLContext::currentContext(); #if defined (_DEBUG) && 0 if (context && context->hasExtension(QByteArrayLiteral("GL_KHR_debug"))) { QOpenGLDebugLogger *logger = new QOpenGLDebugLogger(this); connect(logger, &QOpenGLDebugLogger::messageLogged, this, &CustomGLWidget::handleLoggedMessage); if (logger->initialize()) logger->startLogging(QOpenGLDebugLogger::SynchronousLogging); } #endif if (context) { connect(context, &QOpenGLContext::aboutToBeDestroyed, this, &CustomGLWidget::aboutToDestroyGLContext, Qt::DirectConnection); } connect(this, &CustomGLWidget::resized, this, &CustomGLWidget::slotResized); } // paintGL() is invoked when e.g. using the method grabFramebuffer of this class // \code // from PySide2 import QtWidgets // mw = Gui.getMainWindow() // mdi = mw.findChild(QtWidgets.QMdiArea) // gl = mdi.findChild(QtWidgets.QOpenGLWidget) // img = gl.grabFramebuffer() // \endcode void paintGL() { QuarterWidget* qw = qobject_cast(parentWidget()); if (qw) { qw->redraw(); } } void aboutToDestroyGLContext() { // With Qt 5.9 a signal is emitted while the QuarterWidget is being destroyed. // At this state its type is a QWidget, not a QuarterWidget any more. QuarterWidget* qw = qobject_cast(parent()); if (!qw) return; QMetaObject::invokeMethod(parent(), "aboutToDestroyGLContext", Qt::DirectConnection, QGenericReturnArgument()); } bool event(QEvent *e) { // If a debug logger is activated then Qt's default implementation // first releases the context before stopping the logger. However, // the logger needs the active context and thus crashes because it's // null. if (e->type() == QEvent::WindowChangeInternal) { if (!qApp->testAttribute(Qt::AA_ShareOpenGLContexts)) { QOpenGLDebugLogger* logger = this->findChild(); if (logger) { logger->stopLogging(); delete logger; } } } return QOpenGLWidget::event(e); } void handleLoggedMessage(const QOpenGLDebugMessage &message) { qDebug() << message; } void showEvent(QShowEvent*) { update(); // force update when changing window mode } void slotResized() { update(); // fixes flickering on some systems } }; /*! constructor */ QuarterWidget::QuarterWidget(const QtGLFormat & format, QWidget * parent, const QtGLWidget * sharewidget, Qt::WindowFlags f) : inherited(parent) { Q_UNUSED(f); this->constructor(format, sharewidget); } /*! constructor */ QuarterWidget::QuarterWidget(QWidget * parent, const QtGLWidget * sharewidget, Qt::WindowFlags f) : inherited(parent) { Q_UNUSED(f); this->constructor(QtGLFormat(), sharewidget); } /*! constructor */ QuarterWidget::QuarterWidget(QtGLContext * context, QWidget * parent, const QtGLWidget * sharewidget, Qt::WindowFlags f) : inherited(parent) { Q_UNUSED(f); this->constructor(context->format(), sharewidget); } void QuarterWidget::constructor(const QtGLFormat & format, const QtGLWidget * sharewidget) { QGraphicsScene* scene = new QGraphicsScene(this); setScene(scene); setViewport(new CustomGLWidget(format, this, sharewidget)); setFrameStyle(QFrame::NoFrame); setAutoFillBackground(false); viewport()->setAutoFillBackground(false); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); PRIVATE(this) = new QuarterWidgetP(this, sharewidget); PRIVATE(this)->sorendermanager = new SoRenderManager; PRIVATE(this)->initialsorendermanager = true; PRIVATE(this)->soeventmanager = new SoEventManager; PRIVATE(this)->initialsoeventmanager = true; PRIVATE(this)->processdelayqueue = true; //Mind the order of initialization as the XML state machine uses //callbacks which depends on other state being initialized PRIVATE(this)->eventfilter = new EventFilter(this); PRIVATE(this)->interactionmode = new InteractionMode(this); PRIVATE(this)->currentStateMachine = nullptr; PRIVATE(this)->headlight = new SoDirectionalLight; PRIVATE(this)->headlight->ref(); PRIVATE(this)->sorendermanager->setAutoClipping(SoRenderManager::VARIABLE_NEAR_PLANE); PRIVATE(this)->sorendermanager->setRenderCallback(QuarterWidgetP::rendercb, this); PRIVATE(this)->sorendermanager->setBackgroundColor(SbColor4f(0.0f, 0.0f, 0.0f, 0.0f)); PRIVATE(this)->sorendermanager->activate(); PRIVATE(this)->sorendermanager->addPreRenderCallback(QuarterWidgetP::prerendercb, PRIVATE(this)); PRIVATE(this)->sorendermanager->addPostRenderCallback(QuarterWidgetP::postrendercb, PRIVATE(this)); PRIVATE(this)->soeventmanager->setNavigationState(SoEventManager::MIXED_NAVIGATION); // set up a cache context for the default SoGLRenderAction PRIVATE(this)->sorendermanager->getGLRenderAction()->setCacheContext(this->getCacheContextId()); this->setMouseTracking(true); // Qt::StrongFocus means the widget will accept keyboard focus by // both tabbing and clicking this->setFocusPolicy(Qt::StrongFocus); this->installEventFilter(PRIVATE(this)->eventfilter); this->installEventFilter(PRIVATE(this)->interactionmode); initialized = false; } void QuarterWidget::replaceViewport() { CustomGLWidget* oldvp = static_cast(viewport()); CustomGLWidget* newvp = new CustomGLWidget(oldvp->myFormat, this); PRIVATE(this)->replaceGLWidget(newvp); setViewport(newvp); setAutoFillBackground(false); viewport()->setAutoFillBackground(false); } void QuarterWidget::aboutToDestroyGLContext() { } /*! destructor */ QuarterWidget::~QuarterWidget() { if (PRIVATE(this)->currentStateMachine) { this->removeStateMachine(PRIVATE(this)->currentStateMachine); delete PRIVATE(this)->currentStateMachine; } PRIVATE(this)->headlight->unref(); PRIVATE(this)->headlight = nullptr; this->setSceneGraph(nullptr); this->setSoRenderManager(nullptr); this->setSoEventManager(nullptr); delete PRIVATE(this)->eventfilter; delete PRIVATE(this); } /*! You can set the cursor you want to use for a given navigation state. See the Coin documentation on navigation for information about available states */ void QuarterWidget::setStateCursor(const SbName & state, const QCursor & cursor) { assert(QuarterP::statecursormap); // will overwrite the value of an existing item QuarterP::statecursormap->insert(state, cursor); } /*! Maps a state to a cursor \param[in] state Named state in the statemachine \retval Cursor corresponding to the given state */ QCursor QuarterWidget::stateCursor(const SbName & state) { assert(QuarterP::statecursormap); return QuarterP::statecursormap->value(state); } /*! \property QuarterWidget::headlightEnabled \copydetails QuarterWidget::setHeadlightEnabled */ /*! Enable/disable the headlight. This will toggle the SoDirectionalLight::on field (returned from getHeadlight()). */ void QuarterWidget::setHeadlightEnabled(bool onoff) { PRIVATE(this)->headlight->on = onoff; } /*! Returns true if the headlight is on, false if it is off */ bool QuarterWidget::headlightEnabled() const { return PRIVATE(this)->headlight->on.getValue(); } /*! Returns the light used for the headlight. */ SoDirectionalLight * QuarterWidget::getHeadlight() const { return PRIVATE(this)->headlight; } /*! \property QuarterWidget::clearZBuffer \copydetails QuarterWidget::setClearZBuffer */ /*! Specify if you want the z buffer to be cleared before redraw. This is on by default. */ void QuarterWidget::setClearZBuffer(bool onoff) { PRIVATE(this)->clearzbuffer = onoff; } /*! Returns true if the z buffer is cleared before rendering. */ bool QuarterWidget::clearZBuffer() const { return PRIVATE(this)->clearzbuffer; } /*! \property QuarterWidget::clearWindow \copydetails QuarterWidget::setClearWindow */ /*! Specify if you want the rendering buffer to be cleared before rendering. This is on by default. */ void QuarterWidget::setClearWindow(bool onoff) { PRIVATE(this)->clearwindow = onoff; } /*! Returns true if the rendering buffer is cleared before rendering. */ bool QuarterWidget::clearWindow() const { return PRIVATE(this)->clearwindow; } /*! \property QuarterWidget::interactionModeEnabled \copydetails QuarterWidget::setInteractionModeEnabled */ /*! Enable/disable interaction mode. Specifies whether you may use the alt-key to enter interaction mode. */ void QuarterWidget::setInteractionModeEnabled(bool onoff) { PRIVATE(this)->interactionmode->setEnabled(onoff); } /*! Returns true if interaction mode is enabled, false otherwise. */ bool QuarterWidget::interactionModeEnabled() const { return PRIVATE(this)->interactionmode->enabled(); } /*! \property QuarterWidget::interactionModeOn \copydetails QuarterWidget::setInteractionModeOn */ /*! Turn interaction mode on or off. */ void QuarterWidget::setInteractionModeOn(bool onoff) { PRIVATE(this)->interactionmode->setOn(onoff); } /*! Returns true if interaction mode is on. */ bool QuarterWidget::interactionModeOn() const { return PRIVATE(this)->interactionmode->on(); } /*! Returns the Coin cache context id for this widget. */ uint32_t QuarterWidget::getCacheContextId() const { return PRIVATE(this)->getCacheContextId(); } /*! \property QuarterWidget::transparencyType \copydetails QuarterWidget::setTransparencyType */ /*! Sets the transparency type to be used for the scene. */ void QuarterWidget::setTransparencyType(TransparencyType type) { assert(PRIVATE(this)->sorendermanager); PRIVATE(this)->sorendermanager->getGLRenderAction()->setTransparencyType((SoGLRenderAction::TransparencyType)type); PRIVATE(this)->sorendermanager->scheduleRedraw(); } /*! \retval The current \ref TransparencyType */ QuarterWidget::TransparencyType QuarterWidget::transparencyType() const { assert(PRIVATE(this)->sorendermanager); SoGLRenderAction * action = PRIVATE(this)->sorendermanager->getGLRenderAction(); return static_cast(action->getTransparencyType()); } /*! \property QuarterWidget::renderMode \copydetails QuarterWidget::setRenderMode */ /*! \copydoc RenderMode */ void QuarterWidget::setRenderMode(RenderMode mode) { assert(PRIVATE(this)->sorendermanager); PRIVATE(this)->sorendermanager->setRenderMode(static_cast(mode)); PRIVATE(this)->sorendermanager->scheduleRedraw(); } /*! \retval The current \ref RenderMode */ QuarterWidget::RenderMode QuarterWidget::renderMode() const { assert(PRIVATE(this)->sorendermanager); return static_cast(PRIVATE(this)->sorendermanager->getRenderMode()); } /*! \property QuarterWidget::stereoMode \copydetails QuarterWidget::setStereoMode */ /*! \copydoc StereoMode */ void QuarterWidget::setStereoMode(StereoMode mode) { assert(PRIVATE(this)->sorendermanager); PRIVATE(this)->sorendermanager->setStereoMode(static_cast(mode)); PRIVATE(this)->sorendermanager->scheduleRedraw(); } /*! \retval The current \ref StereoMode */ QuarterWidget::StereoMode QuarterWidget::stereoMode() const { assert(PRIVATE(this)->sorendermanager); return static_cast(PRIVATE(this)->sorendermanager->getStereoMode()); } /*! \property QuarterWidget::devicePixelRatio \copydetails QuarterWidget::devicePixelRatio */ /*! The ratio between logical and physical pixel sizes -- obtained from the window that the widget is located within, and updated whenever any change occurs, emitting a devicePixelRatioChanged signal. Only available for version Qt 5.6 and above (will be 1.0 for all previous versions) */ qreal QuarterWidget::devicePixelRatio() const { return PRIVATE(this)->device_pixel_ratio; } /*! Sets the Inventor scenegraph to be rendered */ void QuarterWidget::setSceneGraph(SoNode * node) { if (node == PRIVATE(this)->scene) { return; } if (PRIVATE(this)->scene) { PRIVATE(this)->scene->unref(); PRIVATE(this)->scene = nullptr; } SoCamera * camera = nullptr; SoSeparator * superscene = nullptr; bool viewall = false; if (node) { PRIVATE(this)->scene = node; PRIVATE(this)->scene->ref(); superscene = new SoSeparator; superscene->addChild(PRIVATE(this)->headlight); // if the scene does not contain a camera, add one if (!(camera = PRIVATE(this)->searchForCamera(node))) { camera = new SoPerspectiveCamera; superscene->addChild(camera); viewall = true; } superscene->addChild(node); } PRIVATE(this)->soeventmanager->setCamera(camera); PRIVATE(this)->sorendermanager->setCamera(camera); PRIVATE(this)->soeventmanager->setSceneGraph(superscene); PRIVATE(this)->sorendermanager->setSceneGraph(superscene); if (viewall) { this->viewAll(); } if (superscene) { superscene->touch(); } } /*! Returns pointer to root of scene graph */ SoNode * QuarterWidget::getSceneGraph() const { return PRIVATE(this)->scene; } /*! Set the render manager for the widget. */ void QuarterWidget::setSoRenderManager(SoRenderManager * manager) { bool carrydata = false; SoNode * scene = nullptr; SoCamera * camera = nullptr; SbViewportRegion vp; if (PRIVATE(this)->sorendermanager && manager) { scene = PRIVATE(this)->sorendermanager->getSceneGraph(); camera = PRIVATE(this)->sorendermanager->getCamera(); vp = PRIVATE(this)->sorendermanager->getViewportRegion(); carrydata = true; } // ref before deleting the old scene manager to avoid that the nodes are deleted if (scene) scene->ref(); if (camera) camera->ref(); if (PRIVATE(this)->initialsorendermanager) { delete PRIVATE(this)->sorendermanager; PRIVATE(this)->initialsorendermanager = false; } PRIVATE(this)->sorendermanager = manager; if (carrydata) { PRIVATE(this)->sorendermanager->setSceneGraph(scene); PRIVATE(this)->sorendermanager->setCamera(camera); PRIVATE(this)->sorendermanager->setViewportRegion(vp); } if (scene) scene->unref(); if (camera) camera->unref(); } /*! Returns a pointer to the render manager. */ SoRenderManager * QuarterWidget::getSoRenderManager() const { return PRIVATE(this)->sorendermanager; } /*! Set the Coin event manager for the widget. */ void QuarterWidget::setSoEventManager(SoEventManager * manager) { bool carrydata = false; SoNode * scene = nullptr; SoCamera * camera = nullptr; SbViewportRegion vp; if (PRIVATE(this)->soeventmanager && manager) { scene = PRIVATE(this)->soeventmanager->getSceneGraph(); camera = PRIVATE(this)->soeventmanager->getCamera(); vp = PRIVATE(this)->soeventmanager->getViewportRegion(); carrydata = true; } // ref before deleting the old scene manager to avoid that the nodes are deleted if (scene) scene->ref(); if (camera) camera->ref(); if (PRIVATE(this)->initialsoeventmanager) { delete PRIVATE(this)->soeventmanager; PRIVATE(this)->initialsoeventmanager = false; } PRIVATE(this)->soeventmanager = manager; if (carrydata) { PRIVATE(this)->soeventmanager->setSceneGraph(scene); PRIVATE(this)->soeventmanager->setCamera(camera); PRIVATE(this)->soeventmanager->setViewportRegion(vp); } if (scene) scene->unref(); if (camera) camera->unref(); } /*! Returns a pointer to the event manager */ SoEventManager * QuarterWidget::getSoEventManager() const { return PRIVATE(this)->soeventmanager; } /*! Returns a pointer to the event filter */ EventFilter * QuarterWidget::getEventFilter() const { return PRIVATE(this)->eventfilter; } /*! Reposition the current camera to display the entire scene */ void QuarterWidget::viewAll() { const SbName viewallevent("sim.coin3d.coin.navigation.ViewAll"); for (int c = 0; c < PRIVATE(this)->soeventmanager->getNumSoScXMLStateMachines(); ++c) { SoScXMLStateMachine * sostatemachine = PRIVATE(this)->soeventmanager->getSoScXMLStateMachine(c); if (sostatemachine->isActive()) { sostatemachine->queueEvent(viewallevent); sostatemachine->processEventQueue(); } } } /*! Sets the current camera in seekmode, if supported by the underlying navigation system. Camera typically seeks towards what the mouse is pointing at. */ void QuarterWidget::seek() { const SbName seekevent("sim.coin3d.coin.navigation.Seek"); for (int c = 0; c < PRIVATE(this)->soeventmanager->getNumSoScXMLStateMachines(); ++c) { SoScXMLStateMachine * sostatemachine = PRIVATE(this)->soeventmanager->getSoScXMLStateMachine(c); if (sostatemachine->isActive()) { sostatemachine->queueEvent(seekevent); sostatemachine->processEventQueue(); } } } bool QuarterWidget::updateDevicePixelRatio() { qreal dev_pix_ratio = 1.0; QWidget* winwidg = window(); QWindow* win = nullptr; if(winwidg) { win = winwidg->windowHandle(); } if(win) { dev_pix_ratio = win->devicePixelRatio(); } else { dev_pix_ratio = ((QGuiApplication*)QGuiApplication::instance())->devicePixelRatio(); } if(PRIVATE(this)->device_pixel_ratio != dev_pix_ratio) { PRIVATE(this)->device_pixel_ratio = dev_pix_ratio; emit devicePixelRatioChanged(dev_pix_ratio); return true; } return false; } /*! Overridden from QGLWidget to resize the Coin scenegraph */ void QuarterWidget::resizeEvent(QResizeEvent* event) { updateDevicePixelRatio(); qreal dev_pix_ratio = devicePixelRatio(); int width = static_cast(dev_pix_ratio * event->size().width()); int height = static_cast(dev_pix_ratio * event->size().height()); SbViewportRegion vp(width, height); PRIVATE(this)->sorendermanager->setViewportRegion(vp); PRIVATE(this)->soeventmanager->setViewportRegion(vp); if (scene()) scene()->setSceneRect(QRect(QPoint(0, 0), event->size())); QGraphicsView::resizeEvent(event); } /*! Overridden from QGLWidget to render the scenegraph */ void QuarterWidget::paintEvent(QPaintEvent* event) { if (updateDevicePixelRatio()) { qreal dev_pix_ratio = devicePixelRatio(); int width = static_cast(dev_pix_ratio * this->width()); int height = static_cast(dev_pix_ratio * this->height()); SbViewportRegion vp(width, height); PRIVATE(this)->sorendermanager->setViewportRegion(vp); PRIVATE(this)->soeventmanager->setViewportRegion(vp); } if(!initialized) { this->getSoRenderManager()->reinitialize(); initialized = true; } getSoRenderManager()->activate(); glMatrixMode(GL_PROJECTION); QtGLWidget* w = static_cast(this->viewport()); if (!w->isValid()) { qWarning() << "No valid GL context found!"; return; } //assert(w->isValid() && "No valid GL context found!"); // We might have to process the delay queue here since we don't know // if paintGL() is called from Qt, and we might have some sensors // waiting to trigger (the redraw sensor has a lower priority than a // normal field sensor to guarantee that your sensor is processed // before the next redraw). Disable autorendering while we do this // to avoid recursive redraws. // We set the PRIVATE(this)->processdelayqueue = false in redraw() // to avoid processing the delay queue when paintGL() is triggered // by us, and we don't want to process the delay queue in those // cases PRIVATE(this)->autoredrawenabled = false; if(PRIVATE(this)->processdelayqueue && SoDB::getSensorManager()->isDelaySensorPending()) { // processing the sensors might trigger a redraw in another // context. Release this context temporarily w->doneCurrent(); SoDB::getSensorManager()->processDelayQueue(false); w->makeCurrent(); } assert(w->isValid() && "No valid GL context found!"); // Causes an OpenGL error on resize //glDrawBuffer(w->format().swapBehavior() == QSurfaceFormat::DoubleBuffer ? GL_BACK : GL_FRONT); w->makeCurrent(); this->actualRedraw(); //start the standard graphics view processing for all widgets and graphic items. As //QGraphicsView initaliizes a QPainter which changes the Opengl context in an unpredictable //manner we need to store the context and recreate it after Qt is done. glPushAttrib(GL_MULTISAMPLE_BIT_EXT); inherited::paintEvent(event); glPopAttrib(); // Causes an OpenGL error on resize //if (w->format().swapBehavior() == QSurfaceFormat::DoubleBuffer) // w->context()->swapBuffers(w->context()->surface()); PRIVATE(this)->autoredrawenabled = true; // process the delay queue the next time we enter this function, // unless we get here after a call to redraw(). PRIVATE(this)->processdelayqueue = true; } bool QuarterWidget::viewportEvent(QEvent* event) { // If no item is selected still let the graphics scene handle it but // additionally handle it by this viewer. This is e.g. needed when // resizing a widget item because the cursor may already be outside // this widget. if (event->type() == QEvent::Wheel || event->type() == QEvent::MouseButtonDblClick || event->type() == QEvent::MouseButtonPress) { QMouseEvent* mouse = static_cast(event); QGraphicsItem *item = itemAt(mouse->pos()); if (!item) { QGraphicsView::viewportEvent(event); return false; } } else if (event->type() == QEvent::MouseMove || event->type() == QEvent::MouseButtonRelease) { QGraphicsScene* glScene = this->scene(); if (!(glScene && glScene->mouseGrabberItem())) { QGraphicsView::viewportEvent(event); return false; } } return QGraphicsView::viewportEvent(event); } /*! Used for rendering the scene. Usually Coin/Quarter will automatically redraw the scene graph at regular intervals, after the scene is modified. However, if you want to disable this functionality and gain full control over when the scene is rendered yourself, you can turn off autoredraw in the render manager and render the scene by calling this method. */ void QuarterWidget::redraw() { // we're triggering the next paintGL(). Set a flag to remember this // to avoid that we process the delay queue in paintGL() PRIVATE(this)->processdelayqueue = false; // When stylesheet is used, there is recursive repaint warning caused by // repaint() here. It happens when switching active documents. Based on call // stacks, it happens like this, the repaint event first triggers a series // calls of QWidgetPrivate::paintSiblingsRecrusive(), and then reaches one of // the QuarterWidget. From its paintEvent(), it calls // SoSensorManager::processDelayQueue(), which triggers redraw() of another // QuarterWidget. And if repaint() is called here, it will trigger another // series call of QWidgetPrivate::paintSiblingRecursive(), and eventually // back to the first QuarterWidget, at which time the "Recursive repaint // detected" Qt warning message will be printed. // // Note that, the recursive repaint is not infinite due to setting // 'processdelayqueue = false' above. However, it does cause annoying // flickering, and actually crash on Windows. this->viewport()->update(); } /*! Overridden from QGLWidget to render the scenegraph */ void QuarterWidget::actualRedraw() { PRIVATE(this)->sorendermanager->render(PRIVATE(this)->clearwindow, PRIVATE(this)->clearzbuffer); } /*! Passes an event to the eventmanager. \param[in] event to pass \retval Returns true if the event was successfully processed */ bool QuarterWidget::processSoEvent(const SoEvent * event) { return event && PRIVATE(this)->soeventmanager && PRIVATE(this)->soeventmanager->processEvent(event); } /*! \property QuarterWidget::backgroundColor \copydoc QuarterWidget::setBackgroundColor */ /*! Set backgroundcolor to a given QColor Remember that QColors are given in integers between 0 and 255, as opposed to SbColor4f which is in [0 ,1]. The default alpha value for a QColor is 255, but you'll probably want to set it to zero before using it as an OpenGL clear color. */ void QuarterWidget::setBackgroundColor(const QColor & color) { SbColor4f bgcolor(SbClamp(color.red() / 255.0, 0.0, 1.0), SbClamp(color.green() / 255.0, 0.0, 1.0), SbClamp(color.blue() / 255.0, 0.0, 1.0), SbClamp(color.alpha() / 255.0, 0.0, 1.0)); PRIVATE(this)->sorendermanager->setBackgroundColor(bgcolor); PRIVATE(this)->sorendermanager->scheduleRedraw(); } /*! Returns color used for clearing the rendering area before rendering the scene. */ QColor QuarterWidget::backgroundColor() const { SbColor4f bg = PRIVATE(this)->sorendermanager->getBackgroundColor(); return QColor(SbClamp(int(bg[0] * 255.0), 0, 255), SbClamp(int(bg[1] * 255.0), 0, 255), SbClamp(int(bg[2] * 255.0), 0, 255), SbClamp(int(bg[3] * 255.0), 0, 255)); } /*! Returns the context menu used by the widget. */ QMenu * QuarterWidget::getContextMenu() const { return PRIVATE(this)->contextMenu(); } /*! \retval Is context menu enabled? */ bool QuarterWidget::contextMenuEnabled() const { return PRIVATE(this)->contextmenuenabled; } /*! \property QuarterWidget::contextMenuEnabled \copydetails QuarterWidget::setContextMenuEnabled */ /*! Controls the display of the contextmenu \param[in] yes Context menu on? */ void QuarterWidget::setContextMenuEnabled(bool yes) { PRIVATE(this)->contextmenuenabled = yes; } /*! Convenience method that adds a state machine to the current SoEventManager. It also initializes the scene graph root and active camera for the state machine, and finally it sets up the default Quarter cursor handling. \sa removeStateMachine */ void QuarterWidget::addStateMachine(SoScXMLStateMachine * statemachine) { SoEventManager * em = this->getSoEventManager(); em->addSoScXMLStateMachine(statemachine); statemachine->setSceneGraphRoot(this->getSoRenderManager()->getSceneGraph()); statemachine->setActiveCamera(this->getSoRenderManager()->getCamera()); statemachine->addStateChangeCallback(QuarterWidgetP::statechangecb, PRIVATE(this)); } /*! Convenience method that removes a state machine to the current SoEventManager. \sa addStateMachine */ void QuarterWidget::removeStateMachine(SoScXMLStateMachine * statemachine) { SoEventManager * em = this->getSoEventManager(); statemachine->setSceneGraphRoot(nullptr); statemachine->setActiveCamera(nullptr); em->removeSoScXMLStateMachine(statemachine); } /*! See \ref QWidget::minimumSizeHint */ QSize QuarterWidget::minimumSizeHint() const { return QSize(50, 50); } /*! Returns a list of grouped actions that corresponds to the TransparencyType enum. If you want to create a menu in your application that controls the transparency type used in QuarterWidget, add these actions to the menu. */ QList QuarterWidget::transparencyTypeActions() const { return PRIVATE(this)->transparencyTypeActions(); } /*! Returns a list of grouped actions that corresponds to the StereoMode enum. If you want to create a menu in your application that controls the stereo mode used in QuarterWidget, add these actions to the menu. */ QList QuarterWidget::stereoModeActions() const { return PRIVATE(this)->stereoModeActions(); } /*! Returns a list of grouped actions that corresponds to the RenderMode enum. If you want to create a menu in your application that controls the render mode type used in QuarterWidget, add these actions to the menu. */ QList QuarterWidget::renderModeActions() const { return PRIVATE(this)->renderModeActions(); } /*! \property QuarterWidget::navigationModeFile A url pointing to a navigation mode file which is a scxml file that defines the possible states for the Coin navigation system Supports: \li \b coin for internal coinresources \li \b file for filesystem path to resources \sa scxml */ /*! Removes any navigationModeFile set. */ void QuarterWidget::resetNavigationModeFile() { this->setNavigationModeFile(QUrl()); } /*! Sets a navigation mode file. Supports the schemes "coin" and "file" \param[in] url Url to the resource */ void QuarterWidget::setNavigationModeFile(const QUrl & url) { QString filename; if (url.scheme()=="coin") { filename = url.path(); //Workaround for differences between url scheme, and Coin internal //scheme in Coin 3.0. if (filename[0]=='/') { filename.remove(0,1); } filename = url.scheme()+':'+filename; } else if (url.scheme()=="file") filename = url.toLocalFile(); else if (url.isEmpty()) { if (PRIVATE(this)->currentStateMachine) { this->removeStateMachine(PRIVATE(this)->currentStateMachine); delete PRIVATE(this)->currentStateMachine; PRIVATE(this)->currentStateMachine = nullptr; PRIVATE(this)->navigationModeFile = url; } return; } else { qDebug()<= 4 stateMachine = ScXML::readBuffer(SbByteBuffer(fileContents.size(), fileContents.constData())); #else stateMachine = ScXML::readBuffer(fileContents.constData()); #endif file.close(); } } if (stateMachine && stateMachine->isOfType(SoScXMLStateMachine::getClassTypeId())) { SoScXMLStateMachine * newsm = static_cast(stateMachine); if (PRIVATE(this)->currentStateMachine) { this->removeStateMachine(PRIVATE(this)->currentStateMachine); delete PRIVATE(this)->currentStateMachine; } this->addStateMachine(newsm); newsm->initialize(); PRIVATE(this)->currentStateMachine = newsm; } else { if (stateMachine) delete stateMachine; qDebug()<navigationModeFile = url; if (QUrl(DEFAULT_NAVIGATIONFILE) == PRIVATE(this)->navigationModeFile ) { // set up default cursors for the examiner navigation states //FIXME: It may be overly restrictive to not do this for arbitrary //navigation systems? - BFG 20090117 this->setStateCursor("interact", Qt::ArrowCursor); this->setStateCursor("idle", Qt::OpenHandCursor); this->setStateCursor("rotate", Qt::ClosedHandCursor); this->setStateCursor("pan", Qt::SizeAllCursor); this->setStateCursor("zoom", Qt::SizeVerCursor); this->setStateCursor("dolly", Qt::SizeVerCursor); this->setStateCursor("seek", Qt::CrossCursor); this->setStateCursor("spin", Qt::OpenHandCursor); } } /*! \retval The current navigationModeFile */ const QUrl & QuarterWidget::navigationModeFile() const { return PRIVATE(this)->navigationModeFile; } #undef PRIVATE