/**************************************************************************** Copyright (c) 2018 GeometryFactory Sarl (France). Copyright (C) 2002-2014 Gilles Debunne. All rights reserved. This file is part of a fork of the QGLViewer library version 2.7.0. *****************************************************************************/ // $URL: https://github.com/CGAL/cgal/blob/v5.1/GraphicsView/include/CGAL/Qt/qglviewer_impl.h $ // $Id: qglviewer_impl.h 58b10a3 2020-03-26T18:58:50+01:00 Sébastien Loriot // SPDX-License-Identifier: GPL-3.0-only #ifdef CGAL_HEADER_ONLY #define CGAL_INLINE_FUNCTION inline #include #else #define CGAL_INLINE_FUNCTION #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace CGAL{ // Static private variable CGAL_INLINE_FUNCTION QList &CGAL::QGLViewer::QGLViewerPool() { static QList QGLViewerPool_; void* p = qApp->property("qglviewer pool").value(); if(p == 0) { p = (void*)(&QGLViewerPool_); qApp->setProperty("qglviewer pool", QVariant::fromValue(p)); } return *static_cast * >(p); } /*! \mainpage libCGAL::QGLViewer is a free C++ library based on Qt that enables the quick creation of OpenGL 3D viewers. It features a powerful camera trackball and simple applications simply require an implementation of the draw() method. This makes it a tool of choice for OpenGL beginners and assignments. It provides mouse manipulated frames, interpolated keyFrames, object selection, and much more. It is fully customizable and easy to extend to create complex applications, with a possible Qt GUI. libCGAL::QGLViewer is not a 3D viewer that can be used directly to view 3D scenes in various formats. It is more likely to be the starting point for the coding of such a viewer. libCGAL::QGLViewer is based on the Qt toolkit and hence compiles on any architecture (Unix-Linux, Mac, Windows, ...). Full reference documentation and many examples are provided. See the project main page for details on the project and installation steps. */ CGAL_INLINE_FUNCTION void CGAL::QGLViewer::defaultConstructor() { setFocusPolicy(::Qt::StrongFocus); CGAL::QGLViewer::QGLViewerPool().append(this); camera_ = new qglviewer::Camera(this); setCamera(camera()); setDefaultShortcuts(); setDefaultMouseBindings(); fpsTime_.start(); fpsCounter_ = 0; f_p_s_ = 0.0; fpsString_ = tr("%1Hz", "Frames per seconds, in Hertz").arg("?"); visualHint_ = 0; previousPathId_ = 0; // prevPos_ is not initialized since pos() is not meaningful here. // It will be set when setFullScreen(false) is called after // setFullScreen(true) // #CONNECTION# default values in initFromDOMElement() manipulatedFrame_ = nullptr; manipulatedFrameIsACamera_ = false; mouseGrabberIsAManipulatedFrame_ = false; mouseGrabberIsAManipulatedCameraFrame_ = false; displayMessage_ = false; connect(&messageTimer_, SIGNAL(timeout()), SLOT(hideMessage())); messageTimer_.setSingleShot(true); helpWidget_ = nullptr; setMouseGrabber(nullptr); setSceneRadius(1.0); showEntireScene(); setStateFileName(".qglviewer.xml"); // #CONNECTION# default values in initFromDOMElement() setAxisIsDrawn(false); setGridIsDrawn(false); setFPSIsDisplayed(false); setCameraIsEdited(false); setTextIsEnabled(true); // Make sure move() is not called, which would call initializeGL() fullScreen_ = false; setFullScreen(false); animationTimerId_ = 0; stopAnimation(); setAnimationPeriod(40); // 25Hz selectBuffer_ = nullptr; setSelectBufferSize(4 * 1000); setSelectRegionWidth(3); setSelectRegionHeight(3); setSelectedName(-1); bufferTextureId_ = 0; bufferTextureMaxU_ = 0.0; bufferTextureMaxV_ = 0.0; bufferTextureWidth_ = 0; bufferTextureHeight_ = 0; previousBufferTextureFormat_ = 0; previousBufferTextureInternalFormat_ = 0; currentlyPressedKey_ = ::Qt::Key(0); setAttribute(::Qt::WA_NoSystemBackground); axisIsDrawn_ = true; _offset = CGAL::qglviewer::Vec(0,0,0); stored_fbo = nullptr; is_sharing = false; is_linked = false; shared_context = nullptr; } CGAL_INLINE_FUNCTION CGAL::QGLViewer::QGLViewer(QWidget *parent, ::Qt::WindowFlags flags) : QOpenGLWidget(parent, flags) { defaultConstructor(); } CGAL_INLINE_FUNCTION CGAL::QGLViewer::QGLViewer(QOpenGLContext* context, QWidget *parent, ::Qt::WindowFlags flags) : QOpenGLWidget(parent, flags) { defaultConstructor(); shared_context = context; is_sharing = true; } /*! Virtual destructor. The viewer is replaced by \c nullptr in the QGLViewerPool() (in order to preserve other viewer's indexes) and allocated memory is released. The camera() is deleted and should be copied before if it is shared by an other viewer. */ CGAL_INLINE_FUNCTION CGAL::QGLViewer::~QGLViewer() { // See closeEvent comment. Destructor is called (and not closeEvent) only when // the widget is embedded. Hence we saveToFile here. It is however a bad idea // if virtual domElement() has been overloaded ! if (parent()) // saveStateToFileForAllViewers(); CGAL::QGLViewer::QGLViewerPool().removeAll(this); camera()->deleteLater(); delete[] selectBuffer_; if (helpWidget()) { // Needed for Qt 4 which has no main widget. helpWidget()->close(); delete helpWidget_; } } /*! Initializes the CGAL::QGLViewer OpenGL context and then calls user-defined init(). This method is automatically called once, before the first call to paintGL(). Overload init() instead of this method to modify viewer specific OpenGL state. If a 4.3 context could not be set, a ES 2.0 context will be used instead. \see `isOpenGL_4_3()` */ CGAL_INLINE_FUNCTION void CGAL::QGLViewer::initializeGL() { if(!is_sharing) { QSurfaceFormat format = context()->format(); context()->format().setOption(QSurfaceFormat::DebugContext); if ( !context()->isValid() || format.majorVersion() != 4 || QCoreApplication::arguments().contains(QStringLiteral("--old"))) { format.setDepthBufferSize(24); format.setStencilBufferSize(8); format.setVersion(2,0); format.setRenderableType(QSurfaceFormat::OpenGLES); format.setSamples(0); format.setOption(QSurfaceFormat::DebugContext); QSurfaceFormat::setDefaultFormat(format); needNewContext(); qDebug()<<"GL 4.3 context initialization failed. "; is_ogl_4_3 = false; } else { is_ogl_4_3 = true; } QSurfaceFormat cur_f = QOpenGLContext::currentContext()->format(); const char* rt =(cur_f.renderableType() == QSurfaceFormat::OpenGLES) ? "GLES" : "GL"; qDebug()<<"Using context " <setFormat(shared_context->format()); context()->setShareContext(shared_context); context()->create(); makeCurrent(); } connect(context(), &QOpenGLContext::aboutToBeDestroyed, this, &CGAL::QGLViewer::contextIsDestroyed); QOpenGLFunctions::initializeOpenGLFunctions(); glEnable(GL_VERTEX_PROGRAM_POINT_SIZE); // Default colors setForegroundColor(QColor(180, 180, 180)); setBackgroundColor(QColor(51, 51, 51)); // Clear the buffer where we're going to draw glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Calls user defined method. Default emits a signal. init(); //OpenGL buffers and programs initialization for(int i=0; iframe()->isManipulated()) fastDraw(); else draw(); // Add visual hints: axis, camera, grid... postDraw(); Q_EMIT drawFinished(true); } /*! Sets OpenGL state before draw(). Default behavior clears screen and sets the projection and modelView matrices: \code glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); camera()->loadProjectionMatrix(); camera()->loadModelViewMatrix(); \endcode Emits the drawNeeded() signal once this is done (see the callback example). */ CGAL_INLINE_FUNCTION void CGAL::QGLViewer::preDraw() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // GL_PROJECTION matrix camera()->loadProjectionMatrix(); // GL_MODELVIEW matrix camera()->loadModelViewMatrix(); Q_EMIT drawNeeded(); } /*! Called after draw() to draw viewer visual hints. Default implementation displays axis, grid, FPS... when the respective flags are sets. See the multiSelect and thumbnail examples for an overloading illustration. The GLContext (color, LIGHTING, BLEND...) is \e not modified by this method, so that in draw(), the user can rely on the OpenGL context he defined. Respect this convention (by pushing/popping the different attributes) if you overload this method. */ CGAL_INLINE_FUNCTION void CGAL::QGLViewer::postDraw() { // Pivot point, line when camera rolls, zoom region if (gridIsDrawn()) { drawGrid(camera()->sceneRadius()); } if (axisIsDrawn()) { drawAxis(1.0); } drawVisualHints(); // FPS computation const unsigned int maxCounter = 20; if (++fpsCounter_ == maxCounter) { f_p_s_ = 1000.0 * maxCounter / fpsTime_.restart(); fpsString_ = tr("%1Hz", "Frames per seconds, in Hertz") .arg(f_p_s_, 0, 'f', ((f_p_s_ < 10.0) ? 1 : 0)); fpsCounter_ = 0; } glDisable(GL_DEPTH_TEST); if (FPSIsDisplayed()) displayFPS(); if (displayMessage_) drawText(10, height() - 10, message_); } /*! Draws a simplified version of the scene to guarantee interactive camera displacements. This method is called instead of draw() when the CGAL::qglviewer::Camera::frame() is CGAL::qglviewer::ManipulatedCameraFrame::isManipulated(). Default implementation simply calls draw(). Overload this method if your scene is too complex to allow for interactive camera manipulation. See the fastDraw example for an illustration. */ CGAL_INLINE_FUNCTION void CGAL::QGLViewer::fastDraw() { draw(); } /*! Starts (\p edit = \c true, default) or stops (\p edit=\c false) the edition of the camera(). Current implementation is limited to paths display. Get current state using cameraIsEdited(). \attention This method sets the CGAL::qglviewer::Camera::zClippingCoefficient() to 5.0 when \p edit is \c true, so that the Camera paths (see CGAL::qglviewer::Camera::keyFrameInterpolator()) are not clipped. It restores the previous value when \p edit is \c false. */ CGAL_INLINE_FUNCTION void CGAL::QGLViewer::setCameraIsEdited(bool edit) { cameraIsEdited_ = edit; if (edit) { previousCameraZClippingCoefficient_ = camera()->zClippingCoefficient(); // #CONNECTION# 5.0 also used in domElement() and in initFromDOMElement(). camera()->setZClippingCoefficient(5.0); } else camera()->setZClippingCoefficient(previousCameraZClippingCoefficient_); Q_EMIT cameraIsEditedChanged(edit); update(); } // Key bindings. 0 means not defined CGAL_INLINE_FUNCTION void CGAL::QGLViewer::setDefaultShortcuts() { // D e f a u l t a c c e l e r a t o r s setShortcut(qglviewer::DRAW_AXIS, ::Qt::Key_A); setShortcut(qglviewer::DRAW_GRID, ::Qt::Key_G); setShortcut(qglviewer::DISPLAY_FPS, ::Qt::Key_F); setShortcut(qglviewer::ENABLE_TEXT, ::Qt::SHIFT + ::Qt::Key_Question); setShortcut(qglviewer::EXIT_VIEWER, ::Qt::Key_Escape); setShortcut(qglviewer::CAMERA_MODE, ::Qt::Key_Space); setShortcut(qglviewer::FULL_SCREEN, ::Qt::ALT + ::Qt::Key_Return); setShortcut(qglviewer::ANIMATION, ::Qt::Key_Return); setShortcut(qglviewer::HELP, ::Qt::Key_H); setShortcut(qglviewer::EDIT_CAMERA, ::Qt::Key_C); setShortcut(qglviewer::MOVE_CAMERA_LEFT, ::Qt::Key_Left); setShortcut(qglviewer::MOVE_CAMERA_RIGHT, ::Qt::Key_Right); setShortcut(qglviewer::MOVE_CAMERA_UP, ::Qt::Key_Up); setShortcut(qglviewer::MOVE_CAMERA_DOWN, ::Qt::Key_Down); setShortcut(qglviewer::INCREASE_FLYSPEED, ::Qt::Key_Plus); setShortcut(qglviewer::DECREASE_FLYSPEED, ::Qt::Key_Minus); keyboardActionDescription_[qglviewer::DISPLAY_FPS] = tr("Toggles the display of the FPS", "DISPLAY_FPS action description"); keyboardActionDescription_[qglviewer::FULL_SCREEN] = tr("Toggles full screen display", "FULL_SCREEN action description"); keyboardActionDescription_[qglviewer::DRAW_AXIS] = tr( "Toggles the display of the world axis", "DRAW_AXIS action description"); keyboardActionDescription_[qglviewer::DRAW_GRID] = tr("Toggles the display of the XY grid", "DRAW_GRID action description"); keyboardActionDescription_[qglviewer::CAMERA_MODE] = tr( "Changes camera mode (observe or fly)", "CAMERA_MODE action description"); keyboardActionDescription_[qglviewer::HELP] = tr("Opens this help window", "HELP action description"); keyboardActionDescription_[qglviewer::ANIMATION] = tr("Starts/stops the animation", "ANIMATION action description"); keyboardActionDescription_[qglviewer::EDIT_CAMERA] = tr("Toggles camera paths display", "EDIT_CAMERA action description"); // TODO change keyboardActionDescription_[qglviewer::ENABLE_TEXT] = tr("Toggles the display of the text", "ENABLE_TEXT action description"); keyboardActionDescription_[qglviewer::EXIT_VIEWER] = tr("Exits program", "EXIT_VIEWER action description"); keyboardActionDescription_[qglviewer::MOVE_CAMERA_LEFT] = tr("Moves camera left", "MOVE_CAMERA_LEFT action description"); keyboardActionDescription_[qglviewer::MOVE_CAMERA_RIGHT] = tr("Moves camera right", "MOVE_CAMERA_RIGHT action description"); keyboardActionDescription_[qglviewer::MOVE_CAMERA_UP] = tr("Moves camera up", "MOVE_CAMERA_UP action description"); keyboardActionDescription_[qglviewer::MOVE_CAMERA_DOWN] = tr("Moves camera down", "MOVE_CAMERA_DOWN action description"); keyboardActionDescription_[qglviewer::INCREASE_FLYSPEED] = tr("Increases fly speed", "INCREASE_FLYSPEED action description"); keyboardActionDescription_[qglviewer::DECREASE_FLYSPEED] = tr("Decreases fly speed", "DECREASE_FLYSPEED action description"); // K e y f r a m e s s h o r t c u t k e y s setPathKey(::Qt::Key_F1, 1); setPathKey(::Qt::Key_F2, 2); setPathKey(::Qt::Key_F3, 3); setPathKey(::Qt::Key_F4, 4); setPathKey(::Qt::Key_F5, 5); setPathKey(::Qt::Key_F6, 6); setPathKey(::Qt::Key_F7, 7); setPathKey(::Qt::Key_F8, 8); setPathKey(::Qt::Key_F9, 9); setPathKey(::Qt::Key_F10, 10); setPathKey(::Qt::Key_F11, 11); setPathKey(::Qt::Key_F12, 12); setAddKeyFrameKeyboardModifiers(::Qt::AltModifier); setPlayPathKeyboardModifiers(::Qt::NoModifier); } // M o u s e b e h a v i o r CGAL_INLINE_FUNCTION void CGAL::QGLViewer::setDefaultMouseBindings() { const ::Qt::KeyboardModifiers cameraKeyboardModifiers = ::Qt::NoModifier; const ::Qt::KeyboardModifiers frameKeyboardModifiers = ::Qt::ControlModifier; //#CONNECTION# toggleCameraMode() for (int handler = 0; handler < 2; ++handler) { qglviewer::MouseHandler mh = (qglviewer::MouseHandler)(handler); ::Qt::KeyboardModifiers modifiers = (mh == qglviewer::FRAME) ? frameKeyboardModifiers : cameraKeyboardModifiers; setMouseBinding(modifiers, ::Qt::LeftButton, mh, qglviewer::ROTATE); setMouseBinding(modifiers, ::Qt::MidButton, mh, qglviewer::ZOOM); setMouseBinding(modifiers, ::Qt::RightButton, mh, qglviewer::TRANSLATE); setMouseBinding(::Qt::Key_R, modifiers, ::Qt::LeftButton, mh, qglviewer::SCREEN_ROTATE); setWheelBinding(modifiers, mh, qglviewer::ZOOM); } // Z o o m o n r e g i o n setMouseBinding(::Qt::ShiftModifier, ::Qt::MidButton, qglviewer::CAMERA, qglviewer::ZOOM_ON_REGION); // S e l e c t setMouseBinding(::Qt::ShiftModifier, ::Qt::LeftButton, qglviewer::SELECT); setMouseBinding(::Qt::ShiftModifier, ::Qt::RightButton, qglviewer::RAP_FROM_PIXEL); // D o u b l e c l i c k setMouseBinding(::Qt::NoModifier, ::Qt::LeftButton, qglviewer::ALIGN_CAMERA, true); setMouseBinding(::Qt::NoModifier, ::Qt::MidButton, qglviewer::SHOW_ENTIRE_SCENE, true); setMouseBinding(::Qt::NoModifier, ::Qt::RightButton, qglviewer::CENTER_SCENE, true); setMouseBinding(frameKeyboardModifiers, ::Qt::LeftButton, qglviewer::ALIGN_FRAME, true); // middle double click makes no sense for manipulated frame setMouseBinding(frameKeyboardModifiers, ::Qt::RightButton, qglviewer::CENTER_FRAME, true); // A c t i o n s w i t h k e y m o d i f i e r s setMouseBinding(::Qt::Key_Z, ::Qt::NoModifier, ::Qt::LeftButton, qglviewer::ZOOM_ON_PIXEL); setMouseBinding(::Qt::Key_Z, ::Qt::NoModifier, ::Qt::RightButton, qglviewer::ZOOM_TO_FIT); #ifdef Q_OS_MAC // Specific Mac bindings for touchpads. Two fingers emulate a wheelEvent which // zooms. There is no right button available : make Option key + left emulate // the right button. A Control+Left indeed emulates a right click (OS X system // configuration), but it does no seem to support dragging. Done at the end to // override previous settings. const ::Qt::KeyboardModifiers macKeyboardModifiers = ::Qt::AltModifier; setMouseBinding(macKeyboardModifiers, ::Qt::LeftButton, qglviewer::CAMERA, qglviewer::TRANSLATE); setMouseBinding(macKeyboardModifiers, ::Qt::LeftButton, qglviewer::CENTER_SCENE, true); setMouseBinding(frameKeyboardModifiers | macKeyboardModifiers, ::Qt::LeftButton, qglviewer::CENTER_FRAME, true); setMouseBinding(frameKeyboardModifiers | macKeyboardModifiers, ::Qt::LeftButton, qglviewer::FRAME, qglviewer::TRANSLATE); #endif } /*! Associates a new CGAL::qglviewer::Camera to the viewer. You should only use this method when you derive a new class from CGAL::qglviewer::Camera and want to use one of its instances instead of the original class. It you simply want to save and restore Camera positions, use CGAL::qglviewer::Camera::addKeyFrameToPath() and CGAL::qglviewer::Camera::playPath() instead. This method silently ignores \c nullptr \p camera pointers. The calling method is responsible for deleting the previous camera pointer in order to prevent memory leaks if needed. The sceneRadius() and sceneCenter() of \p camera are set to the \e current CGAL::QGLViewer values. All the \p camera CGAL::qglviewer::Camera::keyFrameInterpolator() CGAL::qglviewer::KeyFrameInterpolator::interpolated() signals are connected to the viewer update() slot. The connections with the previous viewer's camera are removed. */ CGAL_INLINE_FUNCTION void CGAL::QGLViewer::setCamera(qglviewer::Camera *const camera) { if (!camera) return; camera->setSceneRadius(sceneRadius()); camera->setSceneCenter(sceneCenter()); camera->setScreenWidthAndHeight(width(), height()); // Disconnect current camera from this viewer. disconnect(this->camera()->frame(), SIGNAL(manipulated()), this, SLOT(update())); disconnect(this->camera()->frame(), SIGNAL(spun()), this, SLOT(update())); // Connect camera frame to this viewer. connect(camera->frame(), SIGNAL(manipulated()), SLOT(update())); connect(camera->frame(), SIGNAL(spun()), SLOT(update())); connectAllCameraKFIInterpolatedSignals(false); camera_ = camera; connectAllCameraKFIInterpolatedSignals(); previousCameraZClippingCoefficient_ = this->camera()->zClippingCoefficient(); } CGAL_INLINE_FUNCTION void CGAL::QGLViewer::connectAllCameraKFIInterpolatedSignals(bool connection) { for (QMap::ConstIterator it = camera()->kfi_.begin(), end = camera()->kfi_.end(); it != end; ++it) { if (connection) connect(camera()->keyFrameInterpolator(it.key()), SIGNAL(interpolated()), SLOT(update())); else disconnect(camera()->keyFrameInterpolator(it.key()), SIGNAL(interpolated()), this, SLOT(update())); } if (connection) connect(camera()->interpolationKfi_, SIGNAL(interpolated()), SLOT(update())); else disconnect(camera()->interpolationKfi_, SIGNAL(interpolated()), this, SLOT(update())); } /*! Draws a representation of \p light. Called in draw(), this method is useful to debug or display your light setup. Light drawing depends on the type of light (point, spot, directional). The method retrieves the light setup using \c glGetLightfv. Position and define your lights before calling this method. Light is drawn using its diffuse color. Disabled lights are not displayed. Drawing size is proportional to sceneRadius(). Use \p scale to rescale it. See the drawLight example for an illustration. \attention You need to enable \c GL_COLOR_MATERIAL before calling this method. \c glColor is set to the light diffuse color. */ CGAL_INLINE_FUNCTION void CGAL::QGLViewer::drawLight(GLenum, qreal ) const { } #if (QT_VERSION >= QT_VERSION_CHECK(5, 4, 0)) CGAL_INLINE_FUNCTION void CGAL::QGLViewer::renderText(int x, int y, const QString &str, const QFont &font) { QColor fontColor = QColor(0, 0, 0, 255); // Render text QPainter painter(this); painter.setPen(fontColor); painter.setFont(font); painter.drawText(x, y, str); painter.end(); } CGAL_INLINE_FUNCTION void CGAL::QGLViewer::renderText(double x, double y, double z, const QString &str, const QFont &font) { using CGAL::qglviewer::Vec; const Vec proj = camera_->projectedCoordinatesOf(Vec(x, y, z)); renderText(int(proj.x), int(proj.y), str, font); } #endif /*! Draws \p text at position \p x, \p y (expressed in screen coordinates pixels, origin in the upper left corner of the widget). The default QApplication::font() is used to render the text when no \p fnt is specified. Use QApplication::setFont() to define this default font. You should disable \c GL_LIGHTING and \c GL_DEPTH_TEST before this method so that colors are properly rendered. This method can be used in conjunction with the CGAL::qglviewer::Camera::projectedCoordinatesOf() method to display a text attached to an object. In your draw() method use: \code CGAL::qglviewer::Vec screenPos = camera()->projectedCoordinatesOf(myFrame.position()); drawText((int)screenPos[0], (int)screenPos[1], "My Object"); \endcode See the screenCoordSystem example for an illustration. Text is displayed only when textIsEnabled() (default). This mechanism allows the user to conveniently remove all the displayed text with a single keyboard shortcut. See also displayMessage() to drawText() for only a short amount of time. Use renderText(x,y,z, text) instead if you want to draw a text located at a specific 3D position instead of 2D screen coordinates (fixed size text, facing the camera). The \c GL_MODELVIEW and \c GL_PROJECTION matrices are not modified by this method. */ CGAL_INLINE_FUNCTION void CGAL::QGLViewer::drawText(int x, int y, const QString &text, const QFont &fnt) { if (!textIsEnabled()) return; renderText(x, y, text, fnt); } /*! Briefly displays a message in the lower left corner of the widget. Convenient to provide feedback to the user. \p message is displayed during \p delay milliseconds (default is 2 seconds) using drawText(). This method should not be called in draw(). If you want to display a text in each draw(), use drawText() instead. If this method is called when a message is already displayed, the new message replaces the old one. Use setTextIsEnabled() (default shortcut is '?') to enable or disable text (and hence messages) display. */ CGAL_INLINE_FUNCTION void CGAL::QGLViewer::displayMessage(const QString &message, int delay) { message_ = message; displayMessage_ = true; // Was set to single shot in defaultConstructor. messageTimer_.start(delay); if (textIsEnabled()) update(); } CGAL_INLINE_FUNCTION void CGAL::QGLViewer::hideMessage() { displayMessage_ = false; if (textIsEnabled()) update(); } /*! Displays the averaged currentFPS() frame rate in the upper left corner of the widget. update() should be called in a loop in order to have a meaningful value (this is the case when you continuously move the camera using the mouse or when animationIsStarted()). setAnimationPeriod(0) to make this loop as fast as possible in order to reach and measure the maximum available frame rate. When FPSIsDisplayed() is \c true (default is \c false), this method is called by postDraw() to display the currentFPS(). Use QApplication::setFont() to define the font (see drawText()). */ CGAL_INLINE_FUNCTION void CGAL::QGLViewer::displayFPS() { drawText(10, int(1.5 * ((QApplication::font().pixelSize() > 0) ? QApplication::font().pixelSize() : QApplication::font().pointSize())), fpsString_); } /*! Modify the projection matrix so that drawing can be done directly with 2D screen coordinates. Once called, the \p x and \p y coordinates passed to \c glVertex are expressed in pixels screen coordinates. The origin (0,0) is in the upper left corner of the widget by default. This follows the Qt standards, so that you can directly use the \c pos() provided by for instance \c QMouseEvent. Set \p upward to \c true to place the origin in the \e lower left corner, thus following the OpenGL and mathematical standards. It is always possible to switch between the two representations using \c newY = height() - \c y. You need to call stopScreenCoordinatesSystem() at the end of the drawing block to restore the previous camera matrix. In practice, this method should be used in draw(). It sets an appropriate orthographic projection matrix and then sets \c glMatrixMode to \c GL_MODELVIEW. See the screenCoordSystem, multiSelect and backgroundImage examples for an illustration. You may want to disable \c GL_LIGHTING, to enable \c GL_LINE_SMOOTH or \c GL_BLEND to draw when this method is used. If you want to link 2D drawings to 3D objects, use CGAL::qglviewer::Camera::projectedCoordinatesOf() to compute the 2D projection on screen of a 3D point (see the screenCoordSystem example). See also drawText(). In this mode, you should use z values that are in the [0.0, 1.0[ range (0.0 corresponding to the near clipping plane and 1.0 being just beyond the far clipping plane). This interval matches the values that can be read from the z-buffer. Note that if you use the convenient \c glVertex2i() to provide coordinates, the implicit 0.0 z coordinate will make your drawings appear \e on \e top of the rest of the scene. */ CGAL_INLINE_FUNCTION void CGAL::QGLViewer::startScreenCoordinatesSystem(bool ) const { } /*! Stops the pixel coordinate drawing block started by startScreenCoordinatesSystem(). The \c GL_MODELVIEW and \c GL_PROJECTION matrices modified in startScreenCoordinatesSystem() are restored. \c glMatrixMode is set to \c GL_MODELVIEW. */ CGAL_INLINE_FUNCTION void CGAL::QGLViewer::stopScreenCoordinatesSystem() const { } /*! Overloading of the \c QObject method. If animationIsStarted(), calls animate() and draw(). */ CGAL_INLINE_FUNCTION void CGAL::QGLViewer::timerEvent(QTimerEvent *) { if (animationIsStarted()) { animate(); update(); } } /*! Starts the animation loop. See animationIsStarted(). */ CGAL_INLINE_FUNCTION void CGAL::QGLViewer::startAnimation() { animationTimerId_ = startTimer(animationPeriod()); animationStarted_ = true; } /*! Stops animation. See animationIsStarted(). */ CGAL_INLINE_FUNCTION void CGAL::QGLViewer::stopAnimation() { animationStarted_ = false; if (animationTimerId_ != 0) killTimer(animationTimerId_); } /*! Overloading of the \c QWidget method. Saves the viewer state using saveStateToFile() and then calls QOpenGLWidget::closeEvent(). */ CGAL_INLINE_FUNCTION void CGAL::QGLViewer::closeEvent(QCloseEvent *e) { // When the user clicks on the window close (x) button: // - If the viewer is a top level window, closeEvent is called and then saves // to file. - Otherwise, nothing happen s:( When the user press the // EXIT_VIEWER keyboard shortcut: - If the viewer is a top level window, // saveStateToFile() is also called - Otherwise, closeEvent is NOT called and // keyPressEvent does the job. /* After tests: E : Embedded widget N : Widget created with new C : closeEvent called D : destructor called E N C D y y y n y n y y n n y y closeEvent is called iif the widget is NOT embedded. Destructor is called iif the widget is created on the stack or if widget (resp. parent if embedded) is created with WDestructiveClose flag. closeEvent always before destructor. Close using qApp->closeAllWindows or (x) is identical. */ // #CONNECTION# Also done for EXIT_VIEWER in keyPressEvent(). saveStateToFile(); QOpenGLWidget::closeEvent(e); } /*! Simple wrapper method: calls \c select(event->pos()). Emits \c pointSelected(e) which is useful only if you rely on the Qt signal-slot mechanism and you did not overload CGAL::QGLViewer. If you choose to derive your own viewer class, simply overload select() (or probably simply drawWithNames(), see the select example) to implement your selection mechanism. This method is called when you use the CGAL::QGLViewer::SELECT mouse binding(s) (default is Shift + left button). Use setMouseBinding() to change this. */ CGAL_INLINE_FUNCTION void CGAL::QGLViewer::select(const QMouseEvent *event) { // For those who don't derive but rather rely on the signal-slot mechanism. Q_EMIT pointSelected(event); select(event->pos()); } /*! This method performs a selection in the scene from pixel coordinates. It is called when the user clicks on the CGAL::QGLViewer::SELECT CGAL::QGLViewer::ClickAction binded button(s) (default is Shift + LeftButton). This template method successively calls four other methods: \code beginSelection(point); drawWithNames(); endSelection(point); postSelection(point); \endcode The default implementation of these methods is as follows (see the methods' documentation for more details): \arg beginSelection() sets the \c GL_SELECT mode with the appropriate picking matrices. A rectangular frustum (of size defined by selectRegionWidth() and selectRegionHeight()) centered on \p point is created. \arg drawWithNames() is empty and should be overloaded. It draws each selectable object of the scene, enclosed by calls to \c glPushName() / \c glPopName() to tag the object with an integer id. \arg endSelection() then restores \c GL_RENDER mode and analyzes the selectBuffer() to set in selectedName() the id of the object that was drawn in the region. If several object are in the region, the closest one in the depth buffer is chosen. If no object has been drawn under cursor, selectedName() is set to -1. \arg postSelection() is empty and can be overloaded for possible signal/display/interface update. See the \c glSelectBuffer() man page for details on this \c GL_SELECT mechanism. This default implementation is quite limited: only the closer object is selected, and only one level of names can be pushed. However, this reveals sufficient in many cases and you usually only have to overload drawWithNames() to implement a simple object selection process. See the select example for an illustration. If you need a more complex selection process (such as a point, edge or triangle selection, which is easier with a 2 or 3 levels selectBuffer() heap, and which requires a finer depth sorting to privilege point over edge and edges over triangles), overload the endSelection() method. Use setSelectRegionWidth(), setSelectRegionHeight() and setSelectBufferSize() to tune the select buffer configuration. See the multiSelect example for an illustration. \p point is the center pixel (origin in the upper left corner) of the selection region. Use CGAL::qglviewer::Camera::convertClickToLine() to transform these coordinates in a 3D ray if you want to perform an analytical intersection. \attention \c GL_SELECT mode seems to report wrong results when used in conjunction with backface culling. If you encounter problems try to \c glDisable(GL_CULL_FACE). */ CGAL_INLINE_FUNCTION void CGAL::QGLViewer::select(const QPoint &point) { beginSelection(point); drawWithNames(); endSelection(point); postSelection(point); } /*! This method should prepare the selection. It is called by select() before drawWithNames(). */ CGAL_INLINE_FUNCTION void CGAL::QGLViewer::beginSelection(const QPoint &point) { makeCurrent(); glEnable(GL_SCISSOR_TEST); glScissor(point.x(), camera()->screenHeight()-1-point.y(), 1, 1); } /*! This method is called by select() after scene elements were drawn by drawWithNames(). It clears the OpenGL state set by beginSelection*/ CGAL_INLINE_FUNCTION void CGAL::QGLViewer::endSelection(const QPoint &point) { Q_UNUSED(point); glDisable(GL_SCISSOR_TEST); } /*! Sets the selectBufferSize(). The previous selectBuffer() is deleted and a new one is created. */ CGAL_INLINE_FUNCTION void CGAL::QGLViewer::setSelectBufferSize(int size) { if (selectBuffer_) delete[] selectBuffer_; selectBufferSize_ = size; selectBuffer_ = new GLuint[selectBufferSize()]; } static QString mouseButtonsString(::Qt::MouseButtons b) { QString result(""); bool addAmpersand = false; if (b & ::Qt::LeftButton) { result += CGAL::QGLViewer::tr("Left", "left mouse button"); addAmpersand = true; } if (b & ::Qt::MidButton) { if (addAmpersand) result += " & "; result += CGAL::QGLViewer::tr("Middle", "middle mouse button"); addAmpersand = true; } if (b & ::Qt::RightButton) { if (addAmpersand) result += " & "; result += CGAL::QGLViewer::tr("Right", "right mouse button"); } return result; } CGAL_INLINE_FUNCTION void CGAL::QGLViewer::performClickAction(qglviewer::ClickAction ca, const QMouseEvent *const e) { // Note: action that need it should call update(). switch (ca) { // # CONNECTION setMouseBinding prevents adding NO_CLICK_ACTION in // clickBinding_ This case should hence not be possible. Prevents unused case // warning. case qglviewer::NO_CLICK_ACTION: break; case qglviewer::ZOOM_ON_PIXEL: camera()->interpolateToZoomOnPixel(e->pos()); break; case qglviewer::ZOOM_TO_FIT: camera()->interpolateToFitScene(); break; case qglviewer::SELECT: select(e); update(); break; case qglviewer::RAP_FROM_PIXEL: if (!camera()->setPivotPointFromPixel(e->pos())) camera()->setPivotPoint(sceneCenter()); setVisualHintsMask(1); update(); break; case qglviewer::RAP_IS_CENTER: camera()->setPivotPoint(sceneCenter()); setVisualHintsMask(1); update(); break; case qglviewer::CENTER_FRAME: if (manipulatedFrame()) manipulatedFrame()->projectOnLine(camera()->position(), camera()->viewDirection()); break; case qglviewer::CENTER_SCENE: camera()->centerScene(); break; case qglviewer::SHOW_ENTIRE_SCENE: camera()->showEntireScene(); break; case qglviewer::ALIGN_FRAME: if (manipulatedFrame()) manipulatedFrame()->alignWithFrame(camera()->frame()); break; case qglviewer::ALIGN_CAMERA: qglviewer::Frame *frame = new qglviewer::Frame(); frame->setTranslation(camera()->pivotPoint()); camera()->frame()->alignWithFrame(frame, true); delete frame; break; } } /*! Overloading of the \c QWidget method. When the user clicks on the mouse: \arg if a mouseGrabber() is defined, CGAL::qglviewer::MouseGrabber::mousePressEvent() is called, \arg otherwise, the camera() or the manipulatedFrame() interprets the mouse displacements, depending on mouse bindings. Mouse bindings customization can be achieved using setMouseBinding() and setWheelBinding(). See the mouse page for a complete description of mouse bindings. See the mouseMoveEvent() documentation for an example of more complex mouse behavior customization using overloading. \note When the mouseGrabber() is a manipulatedFrame(), the modifier keys are not taken into account. This allows for a direct manipulation of the manipulatedFrame() when the mouse hovers, which is probably what is expected. */ CGAL_INLINE_FUNCTION void CGAL::QGLViewer::mousePressEvent(QMouseEvent *e) { //#CONNECTION# mouseDoubleClickEvent has the same structure //#CONNECTION# mouseString() concatenates bindings description in inverse // order. ClickBindingPrivate cbp(e->modifiers(), e->button(), false, (::Qt::MouseButtons)(e->buttons() & ~(e->button())), currentlyPressedKey_); if (clickBinding_.contains(cbp)) { performClickAction(clickBinding_[cbp], e); } else if (mouseGrabber()) { if (mouseGrabberIsAManipulatedFrame_) { for (QMap::ConstIterator it = mouseBinding_.begin(), end = mouseBinding_.end(); it != end; ++it) if ((it.value().handler == qglviewer::FRAME) && (it.key().button == e->button())) { qglviewer::ManipulatedFrame *mf = dynamic_cast(mouseGrabber()); if (mouseGrabberIsAManipulatedCameraFrame_) { mf->qglviewer::ManipulatedFrame::startAction(it.value().action, it.value().withConstraint); mf->qglviewer::ManipulatedFrame::mousePressEvent(e, camera()); } else { mf->startAction(it.value().action, it.value().withConstraint); mf->mousePressEvent(e, camera()); } break; } } else mouseGrabber()->mousePressEvent(e, camera()); update(); } else { //#CONNECTION# wheelEvent has the same structure const MouseBindingPrivate mbp(e->modifiers(), e->button(), currentlyPressedKey_); if (mouseBinding_.contains(mbp)) { MouseActionPrivate map = mouseBinding_[mbp]; switch (map.handler) { case qglviewer::CAMERA: camera()->frame()->startAction(map.action, map.withConstraint); camera()->frame()->mousePressEvent(e, camera()); break; case qglviewer::FRAME: if (manipulatedFrame()) { if (manipulatedFrameIsACamera_) { manipulatedFrame()->ManipulatedFrame::startAction( map.action, map.withConstraint); manipulatedFrame()->ManipulatedFrame::mousePressEvent(e, camera()); } else { manipulatedFrame()->startAction(map.action, map.withConstraint); manipulatedFrame()->mousePressEvent(e, camera()); } } break; } if (map.action == qglviewer::SCREEN_ROTATE) // Display visual hint line update(); } else e->ignore(); } } /*! Overloading of the \c QWidget method. Mouse move event is sent to the mouseGrabber() (if any) or to the camera() or the manipulatedFrame(), depending on mouse bindings (see setMouseBinding()). If you want to define your own mouse behavior, do something like this: \code CGAL_INLINE_FUNCTION void Viewer::mousePressEvent(QMouseEvent* e) { if ((e->button() == myButton) && (e->modifiers() == myModifiers)) myMouseBehavior = true; else CGAL::QGLViewer::mousePressEvent(e); } CGAL_INLINE_FUNCTION void Viewer::mouseMoveEvent(QMouseEvent *e) { if (myMouseBehavior) // Use e->x() and e->y() as you want... else CGAL::QGLViewer::mouseMoveEvent(e); } CGAL_INLINE_FUNCTION void Viewer::mouseReleaseEvent(QMouseEvent* e) { if (myMouseBehavior) myMouseBehavior = false; else CGAL::QGLViewer::mouseReleaseEvent(e); } \endcode */ CGAL_INLINE_FUNCTION void CGAL::QGLViewer::mouseMoveEvent(QMouseEvent *e) { if (mouseGrabber()) { mouseGrabber()->checkIfGrabsMouse(e->x(), e->y(), camera()); if (mouseGrabber()->grabsMouse()) if (mouseGrabberIsAManipulatedCameraFrame_) (dynamic_cast(mouseGrabber())) ->qglviewer::ManipulatedFrame::mouseMoveEvent(e, camera()); else mouseGrabber()->mouseMoveEvent(e, camera()); else setMouseGrabber(nullptr); update(); } if (!mouseGrabber()) { //#CONNECTION# mouseReleaseEvent has the same structure if (camera()->frame()->isManipulated()) { camera()->frame()->mouseMoveEvent(e, camera()); // #CONNECTION# manipulatedCameraFrame::mouseMoveEvent specific if at the // beginning if (camera()->frame()->action_ == qglviewer::ZOOM_ON_REGION) update(); } else // ! if ((manipulatedFrame()) && (manipulatedFrame()->isManipulated())) if (manipulatedFrameIsACamera_) manipulatedFrame()->ManipulatedFrame::mouseMoveEvent(e, camera()); else manipulatedFrame()->mouseMoveEvent(e, camera()); else if (hasMouseTracking()) { Q_FOREACH (qglviewer::MouseGrabber *mg, qglviewer::MouseGrabber::MouseGrabberPool()) { mg->checkIfGrabsMouse(e->x(), e->y(), camera()); if (mg->grabsMouse()) { setMouseGrabber(mg); // Check that MouseGrabber is not disabled if (mouseGrabber() == mg) { update(); break; } } } } } } /*! Overloading of the \c QWidget method. Calls the mouseGrabber(), camera() or manipulatedFrame \c mouseReleaseEvent method. See the mouseMoveEvent() documentation for an example of mouse behavior customization. */ CGAL_INLINE_FUNCTION void CGAL::QGLViewer::mouseReleaseEvent(QMouseEvent *e) { if (mouseGrabber()) { if (mouseGrabberIsAManipulatedCameraFrame_) (dynamic_cast(mouseGrabber())) ->qglviewer::ManipulatedFrame::mouseReleaseEvent(e, camera()); else mouseGrabber()->mouseReleaseEvent(e, camera()); mouseGrabber()->checkIfGrabsMouse(e->x(), e->y(), camera()); if (!(mouseGrabber()->grabsMouse())) setMouseGrabber(nullptr); // update(); } else //#CONNECTION# mouseMoveEvent has the same structure if (camera()->frame()->isManipulated()) { camera()->frame()->mouseReleaseEvent(e, camera()); } else if ((manipulatedFrame()) && (manipulatedFrame()->isManipulated())) { if (manipulatedFrameIsACamera_) manipulatedFrame()->ManipulatedFrame::mouseReleaseEvent(e, camera()); else manipulatedFrame()->mouseReleaseEvent(e, camera()); } else e->ignore(); // Not absolutely needed (see above commented code for the optimal version), // but may reveal useful for specific applications. update(); } /*! Overloading of the \c QWidget method. If defined, the wheel event is sent to the mouseGrabber(). It is otherwise sent according to wheel bindings (see setWheelBinding()). */ CGAL_INLINE_FUNCTION void CGAL::QGLViewer::wheelEvent(QWheelEvent *e) { if (mouseGrabber()) { if (mouseGrabberIsAManipulatedFrame_) { for (QMap::ConstIterator it = wheelBinding_.begin(), end = wheelBinding_.end(); it != end; ++it) if (it.value().handler == qglviewer::FRAME) { qglviewer::ManipulatedFrame *mf = dynamic_cast(mouseGrabber()); if (mouseGrabberIsAManipulatedCameraFrame_) { mf->qglviewer::ManipulatedFrame::startAction(it.value().action, it.value().withConstraint); mf->qglviewer::ManipulatedFrame::wheelEvent(e, camera()); } else { mf->startAction(it.value().action, it.value().withConstraint); mf->wheelEvent(e, camera()); } break; } } else mouseGrabber()->wheelEvent(e, camera()); update(); } else { //#CONNECTION# mousePressEvent has the same structure WheelBindingPrivate wbp(e->modifiers(), currentlyPressedKey_); if (wheelBinding_.contains(wbp)) { MouseActionPrivate map = wheelBinding_[wbp]; switch (map.handler) { case qglviewer::CAMERA: camera()->frame()->startAction(map.action, map.withConstraint); camera()->frame()->wheelEvent(e, camera()); break; case qglviewer::FRAME: if (manipulatedFrame()) { if (manipulatedFrameIsACamera_) { manipulatedFrame()->ManipulatedFrame::startAction( map.action, map.withConstraint); manipulatedFrame()->ManipulatedFrame::wheelEvent(e, camera()); } else { manipulatedFrame()->startAction(map.action, map.withConstraint); manipulatedFrame()->wheelEvent(e, camera()); } } break; } } else e->ignore(); } } /*! Overloading of the \c QWidget method. The behavior of the mouse double click depends on the mouse binding. See setMouseBinding() and the mouse page. */ CGAL_INLINE_FUNCTION void CGAL::QGLViewer::mouseDoubleClickEvent(QMouseEvent *e) { //#CONNECTION# mousePressEvent has the same structure ClickBindingPrivate cbp(e->modifiers(), e->button(), true, (::Qt::MouseButtons)(e->buttons() & ~(e->button())), currentlyPressedKey_); if (clickBinding_.contains(cbp)) performClickAction(clickBinding_[cbp], e); else if (mouseGrabber()) mouseGrabber()->mouseDoubleClickEvent(e, camera()); else e->ignore(); } /*! Sets the isFullScreen() state. If the CGAL::QGLViewer is embedded in an other QWidget (see CGAL_INLINE_FUNCTION QWidget::topLevelWidget()), this widget is displayed in full screen instead. */ CGAL_INLINE_FUNCTION void CGAL::QGLViewer::setFullScreen(bool fullScreen) { if (fullScreen_ == fullScreen) return; fullScreen_ = fullScreen; QWidget *tlw = topLevelWidget(); if (isFullScreen()) { prevPos_ = topLevelWidget()->pos(); tlw->showFullScreen(); tlw->move(0, 0); } else { tlw->showNormal(); tlw->move(prevPos_); } } /*! Directly defines the mouseGrabber(). You should not call this method directly as it bypasses the CGAL::qglviewer::MouseGrabber::checkIfGrabsMouse() test performed by mouseMoveEvent(). If the MouseGrabber is disabled (see mouseGrabberIsEnabled()), this method silently does nothing. */ CGAL_INLINE_FUNCTION void CGAL::QGLViewer::setMouseGrabber(qglviewer::MouseGrabber *mouseGrabber) { if (!mouseGrabberIsEnabled(mouseGrabber)) return; mouseGrabber_ = mouseGrabber; mouseGrabberIsAManipulatedFrame_ = (dynamic_cast(mouseGrabber) != nullptr); mouseGrabberIsAManipulatedCameraFrame_ = ((dynamic_cast(mouseGrabber) != nullptr) && (mouseGrabber != camera()->frame())); Q_EMIT mouseGrabberChanged(mouseGrabber); } /*! Sets the mouseGrabberIsEnabled() state. */ CGAL_INLINE_FUNCTION void CGAL::QGLViewer::setMouseGrabberIsEnabled( const CGAL::qglviewer::MouseGrabber *const mouseGrabber, bool enabled) { if (enabled) disabledMouseGrabbers_.remove(reinterpret_cast(mouseGrabber)); else disabledMouseGrabbers_[reinterpret_cast(mouseGrabber)]; } CGAL_INLINE_FUNCTION QString CGAL::QGLViewer::mouseActionString(qglviewer::MouseAction ma) { switch (ma) { case CGAL::qglviewer::NO_MOUSE_ACTION: return QString(); case CGAL::qglviewer::ROTATE: return CGAL::QGLViewer::tr("Rotates", "ROTATE mouse action"); case CGAL::qglviewer::ZOOM: return CGAL::QGLViewer::tr("Zooms", "ZOOM mouse action"); case CGAL::qglviewer::TRANSLATE: return CGAL::QGLViewer::tr("Translates", "TRANSLATE mouse action"); case CGAL::qglviewer::MOVE_FORWARD: return CGAL::QGLViewer::tr("Moves forward", "MOVE_FORWARD mouse action"); case CGAL::qglviewer::LOOK_AROUND: return CGAL::QGLViewer::tr("Looks around", "LOOK_AROUND mouse action"); case CGAL::qglviewer::MOVE_BACKWARD: return CGAL::QGLViewer::tr("Moves backward", "MOVE_BACKWARD mouse action"); case CGAL::qglviewer::SCREEN_ROTATE: return CGAL::QGLViewer::tr("Rotates in screen plane", "SCREEN_ROTATE mouse action"); case CGAL::qglviewer::ROLL: return CGAL::QGLViewer::tr("Rolls", "ROLL mouse action"); case CGAL::qglviewer::DRIVE: return CGAL::QGLViewer::tr("Drives", "DRIVE mouse action"); case CGAL::qglviewer::SCREEN_TRANSLATE: return CGAL::QGLViewer::tr("Horizontally/Vertically translates", "SCREEN_TRANSLATE mouse action"); case CGAL::qglviewer::ZOOM_ON_REGION: return CGAL::QGLViewer::tr("Zooms on region for", "ZOOM_ON_REGION mouse action"); } return QString(); } CGAL_INLINE_FUNCTION QString CGAL::QGLViewer::clickActionString(CGAL::qglviewer::ClickAction ca) { switch (ca) { case CGAL::qglviewer::NO_CLICK_ACTION: return QString(); case CGAL::qglviewer::ZOOM_ON_PIXEL: return CGAL::QGLViewer::tr("Zooms on pixel", "ZOOM_ON_PIXEL click action"); case CGAL::qglviewer::ZOOM_TO_FIT: return CGAL::QGLViewer::tr("Zooms to fit scene", "ZOOM_TO_FIT click action"); case CGAL::qglviewer::SELECT: return CGAL::QGLViewer::tr("Selects", "SELECT click action"); case CGAL::qglviewer::RAP_FROM_PIXEL: return CGAL::QGLViewer::tr("Sets pivot point", "RAP_FROM_PIXEL click action"); case CGAL::qglviewer::RAP_IS_CENTER: return CGAL::QGLViewer::tr("Resets pivot point", "RAP_IS_CENTER click action"); case CGAL::qglviewer::CENTER_FRAME: return CGAL::QGLViewer::tr("Centers manipulated frame", "CENTER_FRAME click action"); case CGAL::qglviewer::CENTER_SCENE: return CGAL::QGLViewer::tr("Centers scene", "CENTER_SCENE click action"); case CGAL::qglviewer::SHOW_ENTIRE_SCENE: return CGAL::QGLViewer::tr("Shows entire scene", "SHOW_ENTIRE_SCENE click action"); case CGAL::qglviewer::ALIGN_FRAME: return CGAL::QGLViewer::tr("Aligns manipulated frame", "ALIGN_FRAME click action"); case CGAL::qglviewer::ALIGN_CAMERA: return CGAL::QGLViewer::tr("Aligns camera", "ALIGN_CAMERA click action"); } return QString(); } static QString keyString(unsigned int key) { #if QT_VERSION >= 0x040100 return QKeySequence(int(key)).toString(QKeySequence::NativeText); #else return QString(QKeySequence(key)); #endif } CGAL_INLINE_FUNCTION QString CGAL::QGLViewer::formatClickActionPrivate(ClickBindingPrivate cbp) { bool buttonsBefore = cbp.buttonsBefore != ::Qt::NoButton; QString keyModifierString = keyString(cbp.modifiers + cbp.key); if (!keyModifierString.isEmpty()) { #ifdef Q_OS_MAC // modifiers never has a '+' sign. Add one space to clearly separate // modifiers (and possible key) from button keyModifierString += " "; #else // modifiers might be of the form : 'S' or 'Ctrl+S' or 'Ctrl+'. For // consistency, add an other '+' if needed, no spaces if (!keyModifierString.endsWith('+')) keyModifierString += "+"; #endif } return tr("%1%2%3%4%5%6", "Modifier / button or wheel / double click / with " "/ button / pressed") .arg(keyModifierString) .arg(mouseButtonsString(cbp.button) + (cbp.button == ::Qt::NoButton ? tr("Wheel", "Mouse wheel") : "")) .arg(cbp.doubleClick ? tr(" double click", "Suffix after mouse button") : "") .arg(buttonsBefore ? tr(" with ", "As in : Left button with Ctrl pressed") : "") .arg(buttonsBefore ? mouseButtonsString(cbp.buttonsBefore) : "") .arg(buttonsBefore ? tr(" pressed", "As in : Left button with Ctrl pressed") : ""); } CGAL_INLINE_FUNCTION bool CGAL::QGLViewer::isValidShortcutKey(int key) { return (key >= ::Qt::Key_Any && key < ::Qt::Key_Escape) || (key >= ::Qt::Key_F1 && key <= ::Qt::Key_F35); } /*! Defines a custom mouse binding description, displayed in the help() window's Mouse tab. Same as calling setMouseBindingDescription(::Qt::Key, ::Qt::KeyboardModifiers, ::Qt::MouseButton, QString, bool, ::Qt::MouseButtons), with a key value of ::Qt::Key(0) (i.e. binding description when no regular key needs to be pressed). */ CGAL_INLINE_FUNCTION void CGAL::QGLViewer::setMouseBindingDescription(::Qt::KeyboardModifiers modifiers, ::Qt::MouseButton button, QString description, bool doubleClick, ::Qt::MouseButtons buttonsBefore) { setMouseBindingDescription(::Qt::Key(0), modifiers, button, description, doubleClick, buttonsBefore); } /*! Defines a custom mouse binding description, displayed in the help() window's Mouse tab. \p modifiers is a combination of ::Qt::KeyboardModifiers (\c ::Qt::ControlModifier, \c ::Qt::AltModifier, \c ::Qt::ShiftModifier, \c ::Qt::MetaModifier). Possibly combined using the \c "|" operator. \p button is one of the ::Qt::MouseButtons (\c ::Qt::LeftButton, \c ::Qt::MidButton, \c ::Qt::RightButton...). \p doubleClick indicates whether or not the user has to double click this button to perform the described action. \p buttonsBefore lists the buttons that need to be pressed before the double click. Set an empty \p description to \e remove a mouse binding description. \code // The R key combined with the Left mouse button rotates the camera in the screen plane. setMouseBindingDescription(::Qt::Key_R, ::Qt::NoModifier, ::Qt::LeftButton, "Rotates camera in screen plane"); // A left button double click toggles full screen setMouseBindingDescription(::Qt::NoModifier, ::Qt::LeftButton, "Toggles full screen mode", true); // Removes the description of Ctrl+Right button setMouseBindingDescription(::Qt::ControlModifier, ::Qt::RightButton, ""); \endcode Overload mouseMoveEvent() and friends to implement your custom mouse behavior (see the mouseMoveEvent() documentation for an example). See the keyboardAndMouse example for an illustration. Use setMouseBinding() and setWheelBinding() to change the standard mouse action bindings. */ CGAL_INLINE_FUNCTION void CGAL::QGLViewer::setMouseBindingDescription( ::Qt::Key key, ::Qt::KeyboardModifiers modifiers, ::Qt::MouseButton button, QString description, bool doubleClick, ::Qt::MouseButtons buttonsBefore) { ClickBindingPrivate cbp(modifiers, button, doubleClick, buttonsBefore, key); if (description.isEmpty()) mouseDescription_.remove(cbp); else mouseDescription_[cbp] = description; } static QString tableLine(const QString &left, const QString &right) { static bool even = false; const QString tdtd(""); const QString tdtr("\n"); QString res(""; else res += "#ffffff\">"; res += "" + left + tdtd + right + tdtr; even = !even; return res; } /*! Returns a QString that describes the application mouse bindings, displayed in the help() window \c Mouse tab. Result is a table that describes custom application mouse binding descriptions defined using setMouseBindingDescription() as well as standard mouse bindings (defined using setMouseBinding() and setWheelBinding()). See the mouse page for details on mouse bindings. See also helpString() and keyboardString(). */ CGAL_INLINE_FUNCTION QString CGAL::QGLViewer::mouseString() const { QString text( "
\n"); const QString trtd("\n"); const QString tdtd("\n") .arg(tr("Button(s)", "Buttons column header in help window mouse tab")) .arg(tr("Description", "Description column header in help window mouse tab")); QMap mouseBinding; // User-defined mouse bindings come first. for (QMap::ConstIterator itm = mouseDescription_.begin(), endm = mouseDescription_.end(); itm != endm; ++itm) mouseBinding[itm.key()] = itm.value(); for (QMap::ConstIterator it = mouseBinding.begin(), end = mouseBinding.end(); it != end; ++it) { // Should not be needed (see setMouseBindingDescription()) if (it.value().isNull()) continue; text += tableLine(formatClickActionPrivate(it.key()), it.value()); } // Optional separator line if (!mouseBinding.isEmpty()) { mouseBinding.clear(); text += QString("\n") .arg(tr("Standard mouse bindings", "In help window mouse tab")); } // Then concatenates the descriptions of wheelBinding_, mouseBinding_ and // clickBinding_. The order is significant and corresponds to the priorities // set in mousePressEvent() (reverse priority order, last one overwrites // previous) #CONNECTION# mousePressEvent() order for (QMap::ConstIterator itmb = mouseBinding_.begin(), endmb = mouseBinding_.end(); itmb != endmb; ++itmb) { ClickBindingPrivate cbp(itmb.key().modifiers, itmb.key().button, false, ::Qt::NoButton, itmb.key().key); QString text = mouseActionString(itmb.value().action); if (!text.isNull()) { switch (itmb.value().handler) { case qglviewer::CAMERA: text += " " + tr("camera", "Suffix after action"); break; case qglviewer::FRAME: text += " " + tr("manipulated frame", "Suffix after action"); break; } if (!(itmb.value().withConstraint)) text += "*"; } mouseBinding[cbp] = text; } for (QMap::ConstIterator itw = wheelBinding_.begin(), endw = wheelBinding_.end(); itw != endw; ++itw) { ClickBindingPrivate cbp(itw.key().modifiers, ::Qt::NoButton, false, ::Qt::NoButton, itw.key().key); QString text = mouseActionString(itw.value().action); if (!text.isNull()) { switch (itw.value().handler) { case qglviewer::CAMERA: text += " " + tr("camera", "Suffix after action"); break; case qglviewer::FRAME: text += " " + tr("manipulated frame", "Suffix after action"); break; } if (!(itw.value().withConstraint)) text += "*"; } mouseBinding[cbp] = text; } for (QMap::ConstIterator itcb = clickBinding_.begin(), endcb = clickBinding_.end(); itcb != endcb; ++itcb) mouseBinding[itcb.key()] = clickActionString(itcb.value()); for (QMap::ConstIterator it2 = mouseBinding.begin(), end2 = mouseBinding.end(); it2 != end2; ++it2) { if (it2.value().isNull()) continue; text += tableLine(formatClickActionPrivate(it2.key()), it2.value()); } text += "
"); const QString tdtr("
"); text += QString("
%1%2
%1
"; return text; } /*! Defines a custom keyboard shortcut description, that will be displayed in the help() window \c Keyboard tab. The \p key definition is given as an \c int using Qt enumerated values. Set an empty \p description to remove a shortcut description: \code setKeyDescription(::Qt::Key_W, "Toggles wireframe display"); setKeyDescription(::Qt::CTRL+::Qt::Key_L, "Loads a new scene"); // Removes a description setKeyDescription(::Qt::CTRL+::Qt::Key_C, ""); \endcode See the keyboardAndMouse example for illustration and the keyboard page for details. */ CGAL_INLINE_FUNCTION void CGAL::QGLViewer::setKeyDescription(unsigned int key, QString description) { if (description.isEmpty()) keyDescription_.remove(key); else keyDescription_[key] = description; } CGAL_INLINE_FUNCTION QString CGAL::QGLViewer::cameraPathKeysString() const { if (pathIndex_.isEmpty()) return QString(); QVector< ::Qt::Key> keys; keys.reserve(pathIndex_.count()); for (QMap< ::Qt::Key, unsigned int>::ConstIterator i = pathIndex_.begin(), endi = pathIndex_.end(); i != endi; ++i) keys.push_back(i.key()); std::sort(keys.begin(), keys.end()); QVector< ::Qt::Key>::const_iterator it = keys.begin(), end = keys.end(); QString res = keyString(*it); const int maxDisplayedKeys = 6; int nbDisplayedKeys = 0; ::Qt::Key previousKey = (*it); int state = 0; ++it; while ((it != end) && (nbDisplayedKeys < maxDisplayedKeys - 1)) { switch (state) { case 0: if ((*it) == previousKey + 1) state++; else { res += ", " + keyString(*it); nbDisplayedKeys++; } break; case 1: if ((*it) == previousKey + 1) state++; else { res += ", " + keyString(previousKey); res += ", " + keyString(*it); nbDisplayedKeys += 2; state = 0; } break; default: if ((*it) != previousKey + 1) { res += ".." + keyString(previousKey); res += ", " + keyString(*it); nbDisplayedKeys += 2; state = 0; } break; } previousKey = *it; ++it; } if (state == 1) res += ", " + keyString(previousKey); if (state == 2) res += ".." + keyString(previousKey); if (it != end) res += "..."; return res; } /*! Returns a QString that describes the application keyboard shortcut bindings, and that will be displayed in the help() window \c Keyboard tab. Default value is a table that describes the custom shortcuts defined using setKeyDescription() as well as the \e standard CGAL::QGLViewer::KeyboardAction shortcuts (defined using setShortcut()). See the keyboard page for details on key customization. See also helpString() and mouseString(). */ CGAL_INLINE_FUNCTION QString CGAL::QGLViewer::keyboardString() const { QString text( "
\n"); text += QString("\n") .arg(CGAL::QGLViewer::tr("Key(s)", "Keys column header in help window mouse tab")) .arg(CGAL::QGLViewer::tr( "Description", "Description column header in help window mouse tab")); QMap keyDescription; // 1 - User defined key descriptions for (QMap::ConstIterator kd = keyDescription_.begin(), kdend = keyDescription_.end(); kd != kdend; ++kd) keyDescription[kd.key()] = kd.value(); // Add to text in sorted order for (QMap::ConstIterator kb = keyDescription.begin(), endb = keyDescription.end(); kb != endb; ++kb) text += tableLine(keyString(kb.key()), kb.value()); // 2 - Optional separator line if (!keyDescription.isEmpty()) { keyDescription.clear(); text += QString("\n") .arg(CGAL::QGLViewer::tr("Standard viewer keys", "In help window keys tab")); } // 3 - KeyboardAction bindings description for (QMap::ConstIterator it = keyboardBinding_.begin(), end = keyboardBinding_.end(); it != end; ++it) if ((it.value() != 0) && ((!cameraIsInRotateMode()) || ((it.key() != qglviewer::INCREASE_FLYSPEED) && (it.key() != qglviewer::DECREASE_FLYSPEED)))) keyDescription[it.value()] = keyboardActionDescription_[it.key()]; // Add to text in sorted order for (QMap::ConstIterator kb2 = keyDescription.begin(), endb2 = keyDescription.end(); kb2 != endb2; ++kb2) text += tableLine(keyString(kb2.key()), kb2.value()); // 4 - Camera paths keys description const QString cpks = cameraPathKeysString(); if (!cpks.isNull()) { text += "\n"; text += tableLine( keyString(playPathKeyboardModifiers()) + "" + CGAL::QGLViewer::tr("Fx", "Generic function key (F1..F12)") + "", CGAL::QGLViewer::tr("Plays path (or resets saved position)")); text += tableLine( keyString(addKeyFrameKeyboardModifiers()) + "" + CGAL::QGLViewer::tr("Fx", "Generic function key (F1..F12)") + "", CGAL::QGLViewer::tr("Adds a key frame to path (or defines a position)")); text += tableLine( keyString(addKeyFrameKeyboardModifiers()) + "" + CGAL::QGLViewer::tr("Fx", "Generic function key (F1..F12)") + "+" + CGAL::QGLViewer::tr("Fx", "Generic function key (F1..F12)") + "", CGAL::QGLViewer::tr("Deletes path (or saved position)")); } text += "
%1%2
%1
\n"; text += CGAL::QGLViewer::tr("Camera paths are controlled using the %1 keys " "(noted Fx below):", "Help window key tab camera keys") .arg(cpks) + "
"; return text; } /*! Displays the help window "About" tab. See help() for details. */ CGAL_INLINE_FUNCTION void CGAL::QGLViewer::aboutQGLViewer() { help(); helpWidget()->setCurrentIndex(3); } /*! Opens a modal help window that includes four tabs, respectively filled with helpString(), keyboardString(), mouseString() and about libCGAL::QGLViewer. Rich html-like text can be used (see the QStyleSheet documentation). This method is called when the user presses the CGAL::QGLViewer::HELP key (default is 'H'). You can use helpWidget() to access to the help widget (to add/remove tabs, change layout...). The helpRequired() signal is emitted. */ CGAL_INLINE_FUNCTION void CGAL::QGLViewer::help() { Q_EMIT helpRequired(); bool resize = false; int width = 600; int height = 400; static QString label[] = {tr("&Help", "Help window tab title"), tr("&Keyboard", "Help window tab title"), tr("&Mouse", "Help window tab title"), tr("&About", "Help window about title")}; if (!helpWidget()) { // Qt4 requires a nullptr parent... helpWidget_ = new QTabWidget(nullptr); helpWidget()->setWindowTitle(tr("Help", "Help window title")); resize = true; for (int i = 0; i < 4; ++i) { QTextEdit *tab = new QTextEdit(nullptr); tab->setReadOnly(true); helpWidget()->insertTab(i, tab, label[i]); if (i == 3) { #include "resources/qglviewer-icon.xpm" QPixmap pixmap(qglviewer_icon); tab->document()->addResource(QTextDocument::ImageResource, QUrl("mydata://qglviewer-icon.xpm"), QVariant(pixmap)); } } } for (int i = 0; i < 4; ++i) { QString text; switch (i) { case 0: text = helpString(); break; case 1: text = keyboardString(); break; case 2: text = mouseString(); break; case 3: text = QString("

") + tr("

libQGLViewer

" "

Forked from version 2.7.0


" "A versatile 3D viewer based on OpenGL and Qt
" "Copyright 2002-%2 Gilles Debunne
" "%3") .arg("2014") .arg("http://www.libqglviewer.com") + QString("
"); break; default: break; } QTextEdit *textEdit = (QTextEdit *)(helpWidget()->widget(i)); textEdit->setHtml(text); textEdit->setText(text); if (resize && (textEdit->height() > height)) height = textEdit->height(); } if (resize) helpWidget()->resize(width, height + 40); // 40 pixels is ~ tabs' height helpWidget()->show(); helpWidget()->raise(); } /*! Overloading of the \c QWidget method. Default keyboard shortcuts are defined using setShortcut(). Overload this method to implement a specific keyboard binding. Call the original method if you do not catch the event to preserve the viewer default key bindings: \code void CGAL_INLINE_FUNCTION Viewer::keyPressEvent(QKeyEvent *e) { // Defines the Alt+R shortcut. if ((e->key() == ::Qt::Key_R) && (e->modifiers() == ::Qt::AltModifier)) { myResetFunction(); update(); // Refresh display } else CGAL::QGLViewer::keyPressEvent(e); } // With Qt 2 or 3, you would retrieve modifiers keys using : // const ::Qt::ButtonState modifiers = (::Qt::ButtonState)(e->state() & ::Qt::KeyButtonMask); \endcode When you define a new keyboard shortcut, use setKeyDescription() to provide a short description which is displayed in the help() window Keyboard tab. See the keyboardAndMouse example for an illustration. CGAL_INLINE_FUNCTION See also QOpenGLWidget::keyReleaseEvent(). */ CGAL_INLINE_FUNCTION void CGAL::QGLViewer::keyPressEvent(QKeyEvent *e) { if (e->key() == 0) { e->ignore(); return; } const ::Qt::Key key = ::Qt::Key(e->key()); const ::Qt::KeyboardModifiers modifiers = e->modifiers(); QMap::ConstIterator it = keyboardBinding_ .begin(), end = keyboardBinding_.end(); const unsigned int target = key | modifiers; while ((it != end) && (it.value() != target)) ++it; if (it != end) handleKeyboardAction(it.key()); else if (pathIndex_.contains(::Qt::Key(key))) { // Camera paths unsigned int index = pathIndex_[::Qt::Key(key)]; // not safe, but try to double press on two viewers at the same time ! static QElapsedTimer doublePress; if (modifiers == playPathKeyboardModifiers()) { qint64 elapsed = doublePress.restart(); if ((elapsed < 250) && (index == previousPathId_)) camera()->resetPath(index); else { // Stop previous interpolation before starting a new one. if (index != previousPathId_) { qglviewer::KeyFrameInterpolator *previous = camera()->keyFrameInterpolator(previousPathId_); if ((previous) && (previous->interpolationIsStarted())) previous->resetInterpolation(); } camera()->playPath(index); } previousPathId_ = index; } else if (modifiers == addKeyFrameKeyboardModifiers()) { qint64 elapsed = doublePress.restart(); if ((elapsed < 250) && (index == previousPathId_)) { if (camera()->keyFrameInterpolator(index)) { disconnect(camera()->keyFrameInterpolator(index), SIGNAL(interpolated()), this, SLOT(update())); if (camera()->keyFrameInterpolator(index)->numberOfKeyFrames() > 1) displayMessage( tr("Path %1 deleted", "Feedback message").arg(index)); else displayMessage( tr("Position %1 deleted", "Feedback message").arg(index)); camera()->deletePath(index); } } else { bool nullBefore = (camera()->keyFrameInterpolator(index) == nullptr); camera()->addKeyFrameToPath(index); if (nullBefore) connect(camera()->keyFrameInterpolator(index), SIGNAL(interpolated()), SLOT(update())); int nbKF = camera()->keyFrameInterpolator(index)->numberOfKeyFrames(); if (nbKF > 1) displayMessage(tr("Path %1, position %2 added", "Feedback message") .arg(index) .arg(nbKF)); else displayMessage( tr("Position %1 saved", "Feedback message").arg(index)); } previousPathId_ = index; } update(); } else { if (isValidShortcutKey(key)) currentlyPressedKey_ = key; e->ignore(); } } CGAL_INLINE_FUNCTION void CGAL::QGLViewer::keyReleaseEvent(QKeyEvent *e) { if (isValidShortcutKey(e->key())) currentlyPressedKey_ = ::Qt::Key(0); } CGAL_INLINE_FUNCTION void CGAL::QGLViewer::handleKeyboardAction(qglviewer::KeyboardAction id) { switch (id) { case qglviewer::DRAW_AXIS: toggleAxisIsDrawn(); break; case qglviewer::DRAW_GRID: toggleGridIsDrawn(); break; case qglviewer::DISPLAY_FPS: toggleFPSIsDisplayed(); break; case qglviewer::ENABLE_TEXT: toggleTextIsEnabled(); break; case qglviewer::EXIT_VIEWER: saveStateToFileForAllViewers(); qApp->closeAllWindows(); break; case qglviewer::FULL_SCREEN: toggleFullScreen(); break; case qglviewer::ANIMATION: toggleAnimation(); break; case qglviewer::HELP: help(); break; case qglviewer::EDIT_CAMERA: toggleCameraIsEdited(); break; case qglviewer::CAMERA_MODE: toggleCameraMode(); displayMessage(cameraIsInRotateMode() ? tr("Camera in observer mode", "Feedback message") : tr("Camera in fly mode", "Feedback message")); break; case qglviewer::MOVE_CAMERA_LEFT: camera()->frame()->translate(camera()->frame()->inverseTransformOf( qglviewer::Vec(-10.0 * camera()->flySpeed(), 0.0, 0.0))); update(); break; case qglviewer::MOVE_CAMERA_RIGHT: camera()->frame()->translate(camera()->frame()->inverseTransformOf( qglviewer::Vec(10.0 * camera()->flySpeed(), 0.0, 0.0))); update(); break; case qglviewer::MOVE_CAMERA_UP: camera()->frame()->translate(camera()->frame()->inverseTransformOf( qglviewer::Vec(0.0, 10.0 * camera()->flySpeed(), 0.0))); update(); break; case qglviewer::MOVE_CAMERA_DOWN: camera()->frame()->translate(camera()->frame()->inverseTransformOf( qglviewer::Vec(0.0, -10.0 * camera()->flySpeed(), 0.0))); update(); break; case qglviewer::INCREASE_FLYSPEED: camera()->setFlySpeed(camera()->flySpeed() * 1.5); break; case qglviewer::DECREASE_FLYSPEED: camera()->setFlySpeed(camera()->flySpeed() / 1.5); break; } } /*! Callback method used when the widget size is modified. If you overload this method, first call the inherited method. Also called when the widget is created, before its first display. */ CGAL_INLINE_FUNCTION void CGAL::QGLViewer::resizeGL(int width, int height) { QOpenGLWidget::resizeGL(width, height); glViewport(0, 0, GLint(width), GLint(height)); camera()->setScreenWidthAndHeight(this->width(), this->height()); } ////////////////////////////////////////////////////////////////////////// // K e y b o a r d s h o r t c u t s // ////////////////////////////////////////////////////////////////////////// /*! Defines the shortcut() that triggers a given CGAL::QGLViewer::KeyboardAction. Here are some examples: \code // Press 'Q' to exit application setShortcut(EXIT_VIEWER, ::Qt::Key_Q); // Alt+M toggles camera mode setShortcut(CAMERA_MODE, ::Qt::ALT + ::Qt::Key_M); // The DISPLAY_FPS action is disabled setShortcut(DISPLAY_FPS, 0); \endcode Only one shortcut can be assigned to a given CGAL::QGLViewer::KeyboardAction (new bindings replace previous ones). If several KeyboardAction are binded to the same shortcut, only one of them is active. */ CGAL_INLINE_FUNCTION void CGAL::QGLViewer::setShortcut(qglviewer::KeyboardAction action, unsigned int key) { keyboardBinding_[action] = key; } /*! Returns the keyboard shortcut associated to a given CGAL::QGLViewer::KeyboardAction. Result is an \c unsigned \c int defined using Qt enumerated values, as in \c ::Qt::Key_Q or \c ::Qt::CTRL + ::Qt::Key_X. Use ::Qt::MODIFIER_MASK to separate the key from the state keys. Returns \c 0 if the KeyboardAction is disabled (not binded). Set using setShortcut(). If you want to define keyboard shortcuts for custom actions (say, open a scene file), overload keyPressEvent() and then setKeyDescription(). These shortcuts and their descriptions are automatically included in the help() window \c Keyboard tab. See the keyboard page for details and default values and the keyboardAndMouse example for a practical illustration. */ CGAL_INLINE_FUNCTION unsigned int CGAL::QGLViewer::shortcut(qglviewer::KeyboardAction action) const { if (keyboardBinding_.contains(action)) return keyboardBinding_[action]; else return 0; } /////// Key Frames associated keys /////// /*! Returns the keyboard key associated to camera Key Frame path \p index. Default values are F1..F12 for indexes 1..12. addKeyFrameKeyboardModifiers() (resp. playPathKeyboardModifiers()) define the state key(s) that must be pressed with this key to add a KeyFrame to (resp. to play) the associated Key Frame path. If you quickly press twice the pathKey(), the path is reset (resp. deleted). Use camera()->keyFrameInterpolator( \p index ) to retrieve the KeyFrameInterpolator that defines the path. If several keys are binded to a given \p index (see setPathKey()), one of them is returned. Returns \c 0 if no key is associated with this index. See also the keyboard page. */ CGAL_INLINE_FUNCTION ::Qt::Key CGAL::QGLViewer::pathKey(unsigned int index) const { for (QMap< ::Qt::Key, unsigned int>::ConstIterator it = pathIndex_.begin(), end = pathIndex_.end(); it != end; ++it) if (it.value() == index) return it.key(); return ::Qt::Key(0); } /*! Sets the pathKey() associated with the camera Key Frame path \p index. Several keys can be binded to the same \p index. Use a negated \p key value to delete the binding (the \p index value is then ignored): \code // Press 'space' to play/pause/add/delete camera path of index 0. setPathKey(::Qt::Key_Space, 0); // Remove this binding setPathKey(-::Qt::Key_Space); \endcode */ CGAL_INLINE_FUNCTION void CGAL::QGLViewer::setPathKey(int key, unsigned int index) { ::Qt::Key k = ::Qt::Key(abs(key)); if (key < 0) pathIndex_.remove(k); else pathIndex_[k] = index; } /*! Sets the playPathKeyboardModifiers() value. */ CGAL_INLINE_FUNCTION void CGAL::QGLViewer::setPlayPathKeyboardModifiers(::Qt::KeyboardModifiers modifiers) { playPathKeyboardModifiers_ = modifiers; } /*! Sets the addKeyFrameKeyboardModifiers() value. */ CGAL_INLINE_FUNCTION void CGAL::QGLViewer::setAddKeyFrameKeyboardModifiers( ::Qt::KeyboardModifiers modifiers) { addKeyFrameKeyboardModifiers_ = modifiers; } /*! Returns the keyboard modifiers that must be pressed with a pathKey() to add the current camera position to a KeyFrame path. It can be \c ::Qt::NoModifier, \c ::Qt::ControlModifier, \c ::Qt::ShiftModifier, \c ::Qt::AltModifier, \c ::Qt::MetaModifier or a combination of these (using the bitwise '|' operator). Default value is ::Qt::AltModifier. Defined using setAddKeyFrameKeyboardModifiers(). See also playPathKeyboardModifiers(). */ CGAL_INLINE_FUNCTION ::Qt::KeyboardModifiers CGAL::QGLViewer::addKeyFrameKeyboardModifiers() const { return addKeyFrameKeyboardModifiers_; } /*! Returns the keyboard modifiers that must be pressed with a pathKey() to play a camera KeyFrame path. It can be \c ::Qt::NoModifier, \c ::Qt::ControlModifier, \c ::Qt::ShiftModifier, \c ::Qt::AltModifier, \c ::Qt::MetaModifier or a combination of these (using the bitwise '|' operator). Default value is ::Qt::NoModifier. Defined using setPlayPathKeyboardModifiers(). See also addKeyFrameKeyboardModifiers(). */ CGAL_INLINE_FUNCTION ::Qt::KeyboardModifiers CGAL::QGLViewer::playPathKeyboardModifiers() const { return playPathKeyboardModifiers_; } //////////////////////////////////////////////////////////////////////////////// // M o u s e b e h a v i o r s t a t e k e y s // //////////////////////////////////////////////////////////////////////////////// /*! Defines a MouseAction binding. Same as calling setMouseBinding(::Qt::Key, ::Qt::KeyboardModifiers, ::Qt::MouseButton, MouseHandler, MouseAction, bool), with a key value of ::Qt::Key(0) (i.e. no regular extra key needs to be pressed to perform this action). */ CGAL_INLINE_FUNCTION void CGAL::QGLViewer::setMouseBinding(::Qt::KeyboardModifiers modifiers, ::Qt::MouseButton button, qglviewer::MouseHandler handler, qglviewer::MouseAction action, bool withConstraint) { setMouseBinding(::Qt::Key(0), modifiers, button, handler, action, withConstraint); } /*! Associates a MouseAction to any mouse \p button, while keyboard \p modifiers and \p key are pressed. The receiver of the mouse events is a MouseHandler (CGAL::QGLViewer::CAMERA or CGAL::QGLViewer::FRAME). The parameters should read: when the mouse \p button is pressed, while the keyboard \p modifiers and \p key are down, activate \p action on \p handler. Use ::Qt::NoModifier to indicate that no modifier key is needed, and a \p key value of 0 if no regular key has to be pressed (or simply use setMouseBinding(::Qt::KeyboardModifiers, ::Qt::MouseButton, MouseHandler, MouseAction, bool)). Use the '|' operator to combine modifiers: \code // The R key combined with the Left mouse button rotates the camera in the screen plane. setMouseBinding(::Qt::Key_R, ::Qt::NoModifier, ::Qt::LeftButton, CAMERA, SCREEN_ROTATE); // Alt + Shift and Left button rotates the manipulatedFrame(). setMouseBinding(::Qt::AltModifier | ::Qt::ShiftModifier, ::Qt::LeftButton, FRAME, ROTATE); \endcode If \p withConstraint is \c true (default), the possible CGAL::qglviewer::Frame::constraint() of the associated Frame will be enforced during motion. The list of all possible MouseAction, some binding examples and default bindings are provided in the mouse page. See the keyboardAndMouse example for an illustration. If no mouse button is specified, the binding is ignored. If an action was previously associated with this keyboard and button combination, it is silently overwritten (call mouseAction() before to check). To remove a specific mouse binding, use \p NO_MOUSE_ACTION as the \p action. See also setMouseBinding(::Qt::KeyboardModifiers, ::Qt::MouseButtons, ClickAction, bool, int), setWheelBinding() and clearMouseBindings(). */ CGAL_INLINE_FUNCTION void CGAL::QGLViewer::setMouseBinding(::Qt::Key key, ::Qt::KeyboardModifiers modifiers, ::Qt::MouseButton button, qglviewer::MouseHandler handler, qglviewer::MouseAction action, bool withConstraint) { if ((handler == qglviewer::FRAME) && ((action == qglviewer::MOVE_FORWARD) || (action == qglviewer::MOVE_BACKWARD) || (action == qglviewer::ROLL) || (action == qglviewer::LOOK_AROUND) || (action == qglviewer::ZOOM_ON_REGION))) { qWarning("Cannot bind %s to FRAME", mouseActionString(action).toLatin1().constData()); return; } if (button == ::Qt::NoButton) { qWarning("No mouse button specified in setMouseBinding"); return; } MouseActionPrivate map; map.handler = handler; map.action = action; map.withConstraint = withConstraint; MouseBindingPrivate mbp(modifiers, button, key); if (action == qglviewer::NO_MOUSE_ACTION) mouseBinding_.remove(mbp); else mouseBinding_.insert(mbp, map); ClickBindingPrivate cbp(modifiers, button, false, ::Qt::NoButton, key); clickBinding_.remove(cbp); } /*! Defines a ClickAction binding. Same as calling setMouseBinding(::Qt::Key, ::Qt::KeyboardModifiers, ::Qt::MouseButton, ClickAction, bool, ::Qt::MouseButtons), with a key value of ::Qt::Key(0) (i.e. no regular key needs to be pressed to activate this action). */ CGAL_INLINE_FUNCTION void CGAL::QGLViewer::setMouseBinding(::Qt::KeyboardModifiers modifiers, ::Qt::MouseButton button, qglviewer::ClickAction action, bool doubleClick, ::Qt::MouseButtons buttonsBefore) { setMouseBinding(::Qt::Key(0), modifiers, button, action, doubleClick, buttonsBefore); } /*! Associates a ClickAction to a button and keyboard key and modifier(s) combination. The parameters should read: when \p button is pressed, while the \p modifiers and \p key keys are down, and possibly as a \p doubleClick, then perform \p action. Use ::Qt::NoModifier to indicate that no modifier key is needed, and a \p key value of 0 if no regular key has to be pressed (or simply use setMouseBinding(::Qt::KeyboardModifiers, ::Qt::MouseButton, ClickAction, bool, ::Qt::MouseButtons)). If \p buttonsBefore is specified (valid only when \p doubleClick is \c true), then this (or these) other mouse button(s) has (have) to be pressed \e before the double click occurs in order to execute \p action. The list of all possible ClickAction, some binding examples and default bindings are listed in the mouse page. See also the setMouseBinding() documentation. See the keyboardAndMouse example for an illustration. The binding is ignored if ::Qt::NoButton is specified as \p buttons. See also setMouseBinding(::Qt::KeyboardModifiers, ::Qt::MouseButtons, MouseHandler, MouseAction, bool), setWheelBinding() and clearMouseBindings(). */ CGAL_INLINE_FUNCTION void CGAL::QGLViewer::setMouseBinding(::Qt::Key key, ::Qt::KeyboardModifiers modifiers, ::Qt::MouseButton button, qglviewer::ClickAction action, bool doubleClick, ::Qt::MouseButtons buttonsBefore) { if ((buttonsBefore != ::Qt::NoButton) && !doubleClick) { qWarning("Buttons before is only meaningful when doubleClick is true in " "setMouseBinding()."); return; } if (button == ::Qt::NoButton) { qWarning("No mouse button specified in setMouseBinding"); return; } ClickBindingPrivate cbp(modifiers, button, doubleClick, buttonsBefore, key); // #CONNECTION performClickAction comment on NO_CLICK_ACTION if (action == qglviewer::NO_CLICK_ACTION) clickBinding_.remove(cbp); else clickBinding_.insert(cbp, action); if ((!doubleClick) && (buttonsBefore == ::Qt::NoButton)) { MouseBindingPrivate mbp(modifiers, button, key); mouseBinding_.remove(mbp); } } /*! Defines a mouse wheel binding. Same as calling setWheelBinding(::Qt::Key, ::Qt::KeyboardModifiers, MouseHandler, MouseAction, bool), with a key value of ::Qt::Key(0) (i.e. no regular key needs to be pressed to activate this action). */ CGAL_INLINE_FUNCTION void CGAL::QGLViewer::setWheelBinding(::Qt::KeyboardModifiers modifiers, qglviewer::MouseHandler handler, qglviewer::MouseAction action, bool withConstraint) { setWheelBinding(::Qt::Key(0), modifiers, handler, action, withConstraint); } /*! Associates a MouseAction and a MouseHandler to a mouse wheel event. This method is very similar to setMouseBinding(), but specific to the wheel. In the current implementation only CGAL::QGLViewer::ZOOM can be associated with CGAL::QGLViewer::FRAME, while CGAL::QGLViewer::CAMERA can receive CGAL::QGLViewer::ZOOM and CGAL::QGLViewer::MOVE_FORWARD. The difference between CGAL::QGLViewer::ZOOM and CGAL::QGLViewer::MOVE_FORWARD is that CGAL::QGLViewer::ZOOM speed depends on the distance to the object, while CGAL::QGLViewer::MOVE_FORWARD moves at a constant speed defined by CGAL::qglviewer::Camera::flySpeed(). */ CGAL_INLINE_FUNCTION void CGAL::QGLViewer::setWheelBinding(::Qt::Key key, ::Qt::KeyboardModifiers modifiers, qglviewer::MouseHandler handler, qglviewer::MouseAction action, bool withConstraint) { //#CONNECTION# ManipulatedFrame::wheelEvent and // ManipulatedCameraFrame::wheelEvent switches if ((action != qglviewer::ZOOM) && (action != qglviewer::MOVE_FORWARD) && (action != qglviewer::MOVE_BACKWARD) && (action != qglviewer::NO_MOUSE_ACTION)) { qWarning("Cannot bind %s to wheel", mouseActionString(action).toLatin1().constData()); return; } if ((handler == qglviewer::FRAME) && (action != qglviewer::ZOOM) && (action != qglviewer::NO_MOUSE_ACTION)) { qWarning("Cannot bind %s to FRAME wheel", mouseActionString(action).toLatin1().constData()); return; } MouseActionPrivate map; map.handler = handler; map.action = action; map.withConstraint = withConstraint; WheelBindingPrivate wbp(modifiers, key); if (action == qglviewer::NO_MOUSE_ACTION) wheelBinding_.remove(wbp); else wheelBinding_[wbp] = map; } /*! Clears all the default mouse bindings. After this call, you will have to use setMouseBinding() and setWheelBinding() to restore the mouse bindings you are interested in. */ CGAL_INLINE_FUNCTION void CGAL::QGLViewer::clearMouseBindings() { mouseBinding_.clear(); clickBinding_.clear(); wheelBinding_.clear(); } /*! Clears all the default keyboard shortcuts. After this call, you will have to use setShortcut() to define your own keyboard shortcuts. */ CGAL_INLINE_FUNCTION void CGAL::QGLViewer::clearShortcuts() { keyboardBinding_.clear(); pathIndex_.clear(); } /*! Returns the MouseAction the will be triggered when the mouse \p button is pressed, while the keyboard \p modifiers and \p key are pressed. Returns CGAL::QGLViewer::NO_MOUSE_ACTION if no action is associated with this combination. Use 0 for \p key to indicate that no regular key needs to be pressed. For instance, to know which motion corresponds to Alt+LeftButton, do: \code MouseAction ma = mouseAction(0, ::Qt::AltModifier, ::Qt::LeftButton); if (ma != CGAL::QGLViewer::NO_MOUSE_ACTION) ... \endcode Use mouseHandler() to know which object (CGAL::QGLViewer::CAMERA or CGAL::QGLViewer::FRAME) will execute this action. */ CGAL_INLINE_FUNCTION qglviewer::MouseAction CGAL::QGLViewer::mouseAction(::Qt::Key key, ::Qt::KeyboardModifiers modifiers, ::Qt::MouseButton button) const { MouseBindingPrivate mbp(modifiers, button, key); if (mouseBinding_.contains(mbp)) return mouseBinding_[mbp].action; else return qglviewer::NO_MOUSE_ACTION; } /*! Returns the MouseHandler which will be activated when the mouse \p button is pressed, while the \p modifiers and \p key are pressed. If no action is associated with this combination, returns \c -1. Use 0 for \p key and ::Qt::NoModifier for \p modifiers to represent the lack of a key press. For instance, to know which handler receives the Alt+LeftButton, do: \code int mh = mouseHandler(0, ::Qt::AltModifier, ::Qt::LeftButton); if (mh == CGAL::QGLViewer::CAMERA) ... \endcode Use mouseAction() to know which action (see the MouseAction enum) will be performed on this handler. */ CGAL_INLINE_FUNCTION int CGAL::QGLViewer::mouseHandler(::Qt::Key key, ::Qt::KeyboardModifiers modifiers, ::Qt::MouseButton button) const { MouseBindingPrivate mbp(modifiers, button, key); if (mouseBinding_.contains(mbp)) return mouseBinding_[mbp].handler; else return -1; } /*! Returns the keyboard state that triggers \p action on \p handler \p withConstraint using the mouse wheel. If such a binding exists, results are stored in the \p key and \p modifiers parameters. If the MouseAction \p action is not bound, \p key is set to the illegal -1 value. If several keyboard states trigger the MouseAction, one of them is returned. See also setMouseBinding(), getClickActionBinding() and getMouseActionBinding(). */ CGAL_INLINE_FUNCTION void CGAL::QGLViewer::getWheelActionBinding(qglviewer::MouseHandler handler, qglviewer::MouseAction action, bool withConstraint, ::Qt::Key &key, ::Qt::KeyboardModifiers &modifiers) const { for (QMap::ConstIterator it = wheelBinding_.begin(), end = wheelBinding_.end(); it != end; ++it) if ((it.value().handler == handler) && (it.value().action == action) && (it.value().withConstraint == withConstraint)) { key = it.key().key; modifiers = it.key().modifiers; return; } key = ::Qt::Key_unknown; modifiers = ::Qt::NoModifier; } /*! Returns the mouse and keyboard state that triggers \p action on \p handler \p withConstraint. If such a binding exists, results are stored in the \p key, \p modifiers and \p button parameters. If the MouseAction \p action is not bound, \p button is set to \c ::Qt::NoButton. If several mouse and keyboard states trigger the MouseAction, one of them is returned. See also setMouseBinding(), getClickActionBinding() and getWheelActionBinding(). */ CGAL_INLINE_FUNCTION void CGAL::QGLViewer::getMouseActionBinding(qglviewer::MouseHandler handler, qglviewer::MouseAction action, bool withConstraint, ::Qt::Key &key, ::Qt::KeyboardModifiers &modifiers, ::Qt::MouseButton &button) const { for (QMap::ConstIterator it = mouseBinding_.begin(), end = mouseBinding_.end(); it != end; ++it) { if ((it.value().handler == handler) && (it.value().action == action) && (it.value().withConstraint == withConstraint)) { key = it.key().key; modifiers = it.key().modifiers; button = it.key().button; return; } } key = ::Qt::Key(0); modifiers = ::Qt::NoModifier; button = ::Qt::NoButton; } /*! Returns the MouseAction (if any) that is performed when using the wheel, when the \p modifiers and \p key keyboard keys are pressed. Returns NO_MOUSE_ACTION if no such binding has been defined using setWheelBinding(). Same as mouseAction(), but for the wheel action. See also wheelHandler(). */ qglviewer::MouseAction CGAL_INLINE_FUNCTION CGAL::QGLViewer::wheelAction(::Qt::Key key, ::Qt::KeyboardModifiers modifiers) const { WheelBindingPrivate wbp(modifiers, key); if (wheelBinding_.contains(wbp)) return wheelBinding_[wbp].action; else return qglviewer::NO_MOUSE_ACTION; } /*! Returns the MouseHandler (if any) that receives wheel events when the \p modifiers and \p key keyboard keys are pressed. Returns -1 if no no such binding has been defined using setWheelBinding(). See also wheelAction(). */ CGAL_INLINE_FUNCTION int CGAL::QGLViewer::wheelHandler(::Qt::Key key, ::Qt::KeyboardModifiers modifiers) const { WheelBindingPrivate wbp(modifiers, key); if (wheelBinding_.contains(wbp)) return wheelBinding_[wbp].handler; else return -1; } /*! Same as mouseAction(), but for the ClickAction set using setMouseBinding(). Returns NO_CLICK_ACTION if no click action is associated with this keyboard and mouse buttons combination. */ CGAL_INLINE_FUNCTION CGAL::qglviewer::ClickAction CGAL::QGLViewer::clickAction(::Qt::Key key, ::Qt::KeyboardModifiers modifiers, ::Qt::MouseButton button, bool doubleClick, ::Qt::MouseButtons buttonsBefore) const { ClickBindingPrivate cbp(modifiers, button, doubleClick, buttonsBefore, key); if (clickBinding_.contains(cbp)) return clickBinding_[cbp]; else return qglviewer::NO_CLICK_ACTION; } /*! Returns the mouse and keyboard state that triggers \p action. If such a binding exists, results are stored in the \p key, \p modifiers, \p button, \p doubleClick and \p buttonsBefore parameters. If the ClickAction \p action is not bound, \p button is set to \c ::Qt::NoButton. If several mouse buttons trigger in the ClickAction, one of them is returned. See also setMouseBinding(), getMouseActionBinding() and getWheelActionBinding(). */ CGAL_INLINE_FUNCTION void CGAL::QGLViewer::getClickActionBinding(qglviewer::ClickAction action, ::Qt::Key &key, ::Qt::KeyboardModifiers &modifiers, ::Qt::MouseButton &button, bool &doubleClick, ::Qt::MouseButtons &buttonsBefore) const { for (QMap::ConstIterator it = clickBinding_.begin(), end = clickBinding_.end(); it != end; ++it) if (it.value() == action) { modifiers = it.key().modifiers; button = it.key().button; doubleClick = it.key().doubleClick; buttonsBefore = it.key().buttonsBefore; key = it.key().key; return; } modifiers = ::Qt::NoModifier; button = ::Qt::NoButton; doubleClick = false; buttonsBefore = ::Qt::NoButton; key = ::Qt::Key(0); } /*! This function should be used in conjunction with toggleCameraMode(). It returns \c true when at least one mouse button is binded to the \c ROTATE mouseAction. This is crude way of determining which "mode" the camera is in. */ CGAL_INLINE_FUNCTION bool CGAL::QGLViewer::cameraIsInRotateMode() const { //#CONNECTION# used in toggleCameraMode() and keyboardString() ::Qt::Key key; ::Qt::KeyboardModifiers modifiers; ::Qt::MouseButton button; getMouseActionBinding(qglviewer::CAMERA, qglviewer::ROTATE, true /*constraint*/, key, modifiers, button); return button != ::Qt::NoButton; } /*! Swaps between two predefined camera mouse bindings. The first mode makes the camera observe the scene while revolving around the CGAL::qglviewer::Camera::pivotPoint(). The second mode is designed for walkthrough applications and simulates a flying camera. Practically, the three mouse buttons are respectively binded to: \arg In rotate mode: CGAL::QGLViewer::ROTATE, CGAL::QGLViewer::ZOOM, CGAL::QGLViewer::TRANSLATE. \arg In fly mode: CGAL::QGLViewer::MOVE_FORWARD, CGAL::QGLViewer::LOOK_AROUND, CGAL::QGLViewer::MOVE_BACKWARD. The current mode is determined by checking if a mouse button is binded to CGAL::QGLViewer::ROTATE for the CGAL::QGLViewer::CAMERA. The state key that was previously used to move the camera is preserved. */ CGAL_INLINE_FUNCTION void CGAL::QGLViewer::toggleCameraMode() { ::Qt::Key key; ::Qt::KeyboardModifiers modifiers; ::Qt::MouseButton button; getMouseActionBinding(qglviewer::CAMERA, qglviewer::ROTATE, true /*constraint*/, key, modifiers, button); bool rotateMode = button != ::Qt::NoButton; if (!rotateMode) { getMouseActionBinding(qglviewer::CAMERA, qglviewer::MOVE_FORWARD, true /*constraint*/, key, modifiers, button); } //#CONNECTION# setDefaultMouseBindings() if (rotateMode) { camera()->frame()->updateSceneUpVector(); camera()->frame()->stopSpinning(); setMouseBinding(modifiers, ::Qt::LeftButton, qglviewer::CAMERA, qglviewer::MOVE_FORWARD); setMouseBinding(modifiers, ::Qt::MidButton, qglviewer::CAMERA, qglviewer::LOOK_AROUND); setMouseBinding(modifiers, ::Qt::RightButton, qglviewer::CAMERA, qglviewer::MOVE_BACKWARD); setMouseBinding(::Qt::Key_R, modifiers, ::Qt::LeftButton, qglviewer::CAMERA, qglviewer::ROLL); setMouseBinding(::Qt::NoModifier, ::Qt::LeftButton, qglviewer::NO_CLICK_ACTION, true); setMouseBinding(::Qt::NoModifier, ::Qt::MidButton, qglviewer::NO_CLICK_ACTION, true); setMouseBinding(::Qt::NoModifier, ::Qt::RightButton, qglviewer::NO_CLICK_ACTION, true); setWheelBinding(modifiers, qglviewer::CAMERA, qglviewer::MOVE_FORWARD); } else { // Should stop flyTimer. But unlikely and not easy. setMouseBinding(modifiers, ::Qt::LeftButton, qglviewer::CAMERA, qglviewer::ROTATE); setMouseBinding(modifiers, ::Qt::MidButton, qglviewer::CAMERA, qglviewer::ZOOM); setMouseBinding(modifiers, ::Qt::RightButton, qglviewer::CAMERA, qglviewer::TRANSLATE); setMouseBinding(::Qt::Key_R, modifiers, ::Qt::LeftButton, qglviewer::CAMERA, qglviewer::SCREEN_ROTATE); setMouseBinding(::Qt::NoModifier, ::Qt::LeftButton, qglviewer::ALIGN_CAMERA, true); setMouseBinding(::Qt::NoModifier, ::Qt::MidButton, qglviewer::SHOW_ENTIRE_SCENE, true); setMouseBinding(::Qt::NoModifier, ::Qt::RightButton, qglviewer::CENTER_SCENE, true); setWheelBinding(modifiers, qglviewer::CAMERA, qglviewer::ZOOM); } } //////////////////////////////////////////////////////////////////////////////// // M a n i p u l a t e d f r a m e s // //////////////////////////////////////////////////////////////////////////////// /*! Sets the viewer's manipulatedFrame(). Several objects can be manipulated simultaneously, as is done the multiSelect example. Defining the \e own viewer's camera()->frame() as the manipulatedFrame() is possible and will result in a classical camera manipulation. See the luxo example for an illustration. Note that a CGAL::qglviewer::ManipulatedCameraFrame can be set as the manipulatedFrame(): it is possible to manipulate the camera of a first viewer in a second viewer. */ CGAL_INLINE_FUNCTION void CGAL::QGLViewer::setManipulatedFrame(qglviewer::ManipulatedFrame *frame) { if (manipulatedFrame()) { manipulatedFrame()->stopSpinning(); if (manipulatedFrame() != camera()->frame()) { disconnect(manipulatedFrame(), SIGNAL(manipulated()), this, SLOT(update())); disconnect(manipulatedFrame(), SIGNAL(spun()), this, SLOT(update())); } } manipulatedFrame_ = frame; manipulatedFrameIsACamera_ = ((manipulatedFrame() != camera()->frame()) && (dynamic_cast(manipulatedFrame()) != nullptr)); if (manipulatedFrame()) { // Prevent multiple connections, that would result in useless display // updates if (manipulatedFrame() != camera()->frame()) { connect(manipulatedFrame(), SIGNAL(manipulated()), SLOT(update())); connect(manipulatedFrame(), SIGNAL(spun()), SLOT(update())); } } } #ifndef DOXYGEN //////////////////////////////////////////////////////////////////////////////// // V i s u a l H i n t s // //////////////////////////////////////////////////////////////////////////////// /*! Draws viewer related visual hints. CGAL_INLINE_FUNCTION Displays the new CGAL::qglviewer::Camera::pivotPoint() when it is changed. See the mouse page for details. Also draws a line between CGAL::qglviewer::Camera::pivotPoint() and mouse cursor when the camera is rotated around the camera Z axis. See also setVisualHintsMask() and resetVisualHints(). The hint color is foregroundColor(). \note These methods may become more interesting one day. The current design is too limited and should be improved when other visual hints must be drawn. Limitation : One needs to have access to visualHint_ to overload this method. Removed from the documentation for this reason. */ CGAL_INLINE_FUNCTION void CGAL::QGLViewer::drawVisualHints() { // G r i d rendering_program.bind(); vaos[GRID].bind(); QMatrix4x4 mvpMatrix; double mat[16]; camera()->getModelViewProjectionMatrix(mat); for(int i=0; i < 16; i++) { mvpMatrix.data()[i] = (float)mat[i]; } QMatrix4x4 mvMatrix; for(int i=0; i < 16; i++) { mvMatrix.data()[i] = float(camera()->orientation().inverse().matrix()[i]); } rendering_program.setUniformValue("mvp_matrix", mvpMatrix); rendering_program.setUniformValue("color", QColor(::Qt::lightGray)); glDrawArrays(GL_LINES, 0, static_cast(grid_size)); vaos[GRID].release(); rendering_program.release(); rendering_program_light.bind(); vaos[GRID_AXIS].bind(); rendering_program_light.setUniformValue("mvp_matrix", mvpMatrix); rendering_program_light.setUniformValue("mv_matrix", mvMatrix); glDrawArrays(GL_TRIANGLES, 0, static_cast(g_axis_size/9)); vaos[GRID_AXIS].release(); // glPolygonMode(GL_FRONT_AND_BACK,GL_FILL); glEnable(GL_POLYGON_OFFSET_FILL); glPolygonOffset(3.0f,-3.0f); //A x i s CGAL::qglviewer::Camera::Type camera_type = camera()->type(); camera()->setType(CGAL::qglviewer::Camera::ORTHOGRAPHIC); for(int i=0; i < 16; i++) { mvMatrix.data()[i] = float(camera()->orientation().inverse().matrix()[i]); } mvpMatrix.setToIdentity(); mvpMatrix.ortho(-1,1,-1,1,-1,1); mvpMatrix = mvpMatrix*mvMatrix; rendering_program_light.setUniformValue("mvp_matrix", mvpMatrix); rendering_program_light.setUniformValue("mv_matrix", mvMatrix); camera()->setType(camera_type); vaos[AXIS].bind(); int viewport[4]; int scissor[4]; // The viewport and the scissor are changed to fit the upper right // corner. Original values are saved. glGetIntegerv(GL_VIEWPORT, viewport); glGetIntegerv(GL_SCISSOR_BOX, scissor); // Axis viewport size, in pixels int size = 100; glViewport(width()*devicePixelRatio()-size, height()*devicePixelRatio()-size, size, size); glScissor (width()*devicePixelRatio()-size, height()*devicePixelRatio()-size, size, size); glDrawArrays(GL_TRIANGLES, 0, static_cast(axis_size / 9)); // The viewport and the scissor are restored. glScissor(scissor[0],scissor[1],scissor[2],scissor[3]); glViewport(viewport[0],viewport[1],viewport[2],viewport[3]); vaos[AXIS].release(); rendering_program_light.release(); //P i v o t - P o i n t if (visualHint_ & 1) { std::vector vertices; for(int i=0; i< 4; ++i) { float x = float(std::pow(-1, i)*(1-i/2)); float y = float(std::pow(-1, i)*(i/2)); vertices.push_back(x); vertices.push_back(y); vertices.push_back(0); } rendering_program.bind(); vaos[PIVOT_POINT].bind(); vbos[Pivot_point].bind(); vbos[Pivot_point].allocate(vertices.data(),static_cast(vertices.size()*sizeof(float))); rendering_program.enableAttributeArray("vertex"); rendering_program.setAttributeBuffer("vertex",GL_FLOAT,0,3); vbos[Pivot_point].release(); mvpMatrix.setToIdentity(); mvpMatrix.ortho(-1,1,-1,1,-1,1); size=30*devicePixelRatio(); rendering_program.setUniformValue("mvp_matrix", mvpMatrix); glViewport(GLint((camera()->projectedCoordinatesOf(camera()->pivotPoint()).x-size/2)*devicePixelRatio()), GLint((height() - camera()->projectedCoordinatesOf(camera()->pivotPoint()).y-size/2)*devicePixelRatio()), size, size); glScissor (GLint((camera()->projectedCoordinatesOf(camera()->pivotPoint()).x-size/2)*devicePixelRatio()), GLint((height() - camera()->projectedCoordinatesOf(camera()->pivotPoint()).y-size/2)*devicePixelRatio()), size, size); rendering_program.setUniformValue("color", QColor(::Qt::black)); glDisable(GL_DEPTH_TEST); glDrawArrays(GL_LINES, 0, static_cast(4)); rendering_program.setUniformValue("color", QColor(::Qt::white)); glDrawArrays(GL_LINES, 0, static_cast(4)); glEnable(GL_DEPTH_TEST); // The viewport and the scissor are restored. glScissor(scissor[0],scissor[1],scissor[2],scissor[3]); glViewport(viewport[0],viewport[1],viewport[2],viewport[3]); vaos[PIVOT_POINT].release(); rendering_program.release(); } } /*! Defines the mask that will be used to drawVisualHints(). The only available mask is currently 1, corresponding to the display of the CGAL::qglviewer::Camera::pivotPoint(). resetVisualHints() is automatically called after \p delay milliseconds (default is 2 seconds). */ CGAL_INLINE_FUNCTION void CGAL::QGLViewer::setVisualHintsMask(int mask, int delay) { visualHint_ = visualHint_ | mask; QTimer::singleShot(delay, this, SLOT(resetVisualHints())); } /*! Reset the mask used by drawVisualHints(). Called by setVisualHintsMask() * after 2 seconds to reset the display. */ CGAL_INLINE_FUNCTION void CGAL::QGLViewer::resetVisualHints() { visualHint_ = 0; } #endif //////////////////////////////////////////////////////////////////////////////// // A x i s a n d G r i d d i s p l a y l i s t s // //////////////////////////////////////////////////////////////////////////////// /*! Draws a 3D arrow between the 3D point \p from and the 3D point \p to. \p data is filled with the three components of a point, then its normal, and then its color, which makes it filled like this: [P1.x-P1.y-P1.z-N1.x-N1.y-N1.z-C1.r-C1.g-C1.b|P2.x-P2.y-P2.z-N2.x-N2.y-N2.z-C2.r-C2.g-C2.b|...] */ CGAL_INLINE_FUNCTION void CGAL::QGLViewer::drawArrow(double r,double R, int prec, CGAL::qglviewer::Vec from, CGAL::qglviewer::Vec to, CGAL::qglviewer::Vec color, std::vector &data) { using std::cos; using std::sin; using std::acos; CGAL::qglviewer::Vec temp = to-from; QVector3D dir = QVector3D(float(temp.x), float(temp.y), float(temp.z)); QMatrix4x4 mat; mat.setToIdentity(); mat.translate(float(from.x), float(from.y), float(from.z)); mat.scale(dir.length()); dir.normalize(); float angle = 0.0; if(std::sqrt((dir.x()*dir.x()+dir.y()*dir.y())) > 1) angle = 90.0f; else angle = float(acos(dir.y()/std::sqrt(dir.lengthSquared()))*180.0/CGAL_PI); QVector3D axis; axis = QVector3D(dir.z(), 0, -dir.x()); mat.rotate(angle, axis); //Head const float Rf = static_cast(R); for(int d = 0; d<360; d+= 360/prec) { float D = (float) (d * CGAL_PI / 180.); float a = (float) std::atan(Rf / 0.33); QVector4D p(0., 1., 0, 1.); QVector4D n(Rf*sin(D), sin(a), Rf*cos(D), 1.); QVector4D pR = mat*p; QVector4D nR = mat*n; //point A1 data.push_back(pR.x()); data.push_back(pR.y()); data.push_back(pR.z()); data.push_back(nR.x()); data.push_back(nR.y()); data.push_back(nR.z()); data.push_back((float)color.x); data.push_back((float)color.y); data.push_back((float)color.z); //point B1 p = QVector4D(Rf*sin(D), 0.66f, Rf* cos(D), 1.f); n = QVector4D(sin(D), sin(a), cos(D), 1.); pR = mat*p; nR = mat*n; data.push_back(pR.x()); data.push_back(pR.y()); data.push_back(pR.z()); data.push_back(nR.x()); data.push_back(nR.y()); data.push_back(nR.z()); data.push_back((float)color.x); data.push_back((float)color.y); data.push_back((float)color.z); //point C1 D = float((d+360/prec)*CGAL_PI/180.0); p = QVector4D(Rf* sin(D), 0.66f, Rf* cos(D), 1.f); n = QVector4D(sin(D), sin(a), cos(D), 1.0); pR = mat*p; nR = mat*n; data.push_back(pR.x()); data.push_back(pR.y()); data.push_back(pR.z()); data.push_back(nR.x()); data.push_back(nR.y()); data.push_back(nR.z()); data.push_back((float)color.x); data.push_back((float)color.y); data.push_back((float)color.z); } //cylinder //body of the cylinder const float rf = static_cast(r); for(int d = 0; d<360; d+= 360/prec) { //point A1 float D = float(d*CGAL_PI/180.0); QVector4D p(rf*sin(D), 0.66f, rf*cos(D), 1.f); QVector4D n(sin(D), 0.f, cos(D), 1.f); QVector4D pR = mat*p; QVector4D nR = mat*n; data.push_back(pR.x()); data.push_back(pR.y()); data.push_back(pR.z()); data.push_back(nR.x()); data.push_back(nR.y()); data.push_back(nR.z()); data.push_back(float(color.x)); data.push_back(float(color.y)); data.push_back(float(color.z)); //point B1 p = QVector4D(rf * sin(D),0,rf*cos(D), 1.0); n = QVector4D(sin(D), 0, cos(D), 1.0); pR = mat*p; nR = mat*n; data.push_back(pR.x()); data.push_back(pR.y()); data.push_back(pR.z()); data.push_back(nR.x()); data.push_back(nR.y()); data.push_back(nR.z()); data.push_back(float(color.x)); data.push_back(float(color.y)); data.push_back(float(color.z)); //point C1 D = float((d+360/prec)*CGAL_PI/180.0); p = QVector4D(rf * sin(D),0,rf*cos(D), 1.0); n = QVector4D(sin(D), 0, cos(D), 1.0); pR = mat*p; nR = mat*n; data.push_back(pR.x()); data.push_back(pR.y()); data.push_back(pR.z()); data.push_back(nR.x()); data.push_back(nR.y()); data.push_back(nR.z()); data.push_back(float(color.x)); data.push_back(float(color.y)); data.push_back(float(color.z)); //point A2 D = float((d+360/prec)*CGAL_PI/180.0); p = QVector4D(rf * sin(D),0,rf*cos(D), 1.0); n = QVector4D(sin(D), 0, cos(D), 1.0); pR = mat*p; nR = mat*n; data.push_back(pR.x()); data.push_back(pR.y()); data.push_back(pR.z()); data.push_back(nR.x()); data.push_back(nR.y()); data.push_back(nR.z()); data.push_back(float(color.x)); data.push_back(float(color.y)); data.push_back(float(color.z)); //point B2 p = QVector4D(rf * sin(D), 0.66f, rf*cos(D), 1.f); n = QVector4D(sin(D), 0, cos(D), 1.0); pR = mat*p; nR = mat*n; data.push_back(pR.x()); data.push_back(pR.y()); data.push_back(pR.z()); data.push_back(nR.x()); data.push_back(nR.y()); data.push_back(nR.z()); data.push_back(float(color.x)); data.push_back(float(color.y)); data.push_back(float(color.z)); //point C2 D = float(d*CGAL_PI/180.0); p = QVector4D(rf * sin(D), 0.66f, rf*cos(D), 1.f); n = QVector4D(sin(D), 0.f, cos(D), 1.f); pR = mat*p; nR = mat*n; data.push_back(pR.x()); data.push_back(pR.y()); data.push_back(pR.z()); data.push_back(nR.x()); data.push_back(nR.y()); data.push_back(nR.z()); data.push_back(float(color.x)); data.push_back(float(color.y)); data.push_back(float(color.z)); } } /*! Draws an XYZ axis, with a given size (default is 1.0). The axis orientation matches the current modelView matrix state: three arrows (red, green and blue) of length \p length are drawn along the positive X, Y and Z directions in the top right corner of the screen. X arrow is red, Y arrow is green and Z arrow is blue.*/ CGAL_INLINE_FUNCTION void CGAL::QGLViewer::drawAxis(qreal length) { std::vector data; data.resize(0); drawArrow(0.06,0.12,10, CGAL::qglviewer::Vec(0,0,0),CGAL::qglviewer::Vec(length,0,0),CGAL::qglviewer::Vec(1,0,0), data); drawArrow(0.06,0.12,10, CGAL::qglviewer::Vec(0,0,0),CGAL::qglviewer::Vec(0,length,0),CGAL::qglviewer::Vec(0,1,0), data); drawArrow(0.06,0.12,10, CGAL::qglviewer::Vec(0,0,0),CGAL::qglviewer::Vec(0,0,length),CGAL::qglviewer::Vec(0,0,1), data); rendering_program_light.bind(); vaos[AXIS].bind(); vbos[Axis].bind(); vbos[Axis].allocate(data.data(), static_cast(data.size() * sizeof(float))); rendering_program_light.enableAttributeArray("vertex"); rendering_program_light.setAttributeBuffer("vertex",GL_FLOAT,0,3, static_cast(9*sizeof(float))); rendering_program_light.enableAttributeArray("normal"); rendering_program_light.setAttributeBuffer("normal",GL_FLOAT,3*sizeof(float),3, static_cast(9*sizeof(float))); rendering_program_light.enableAttributeArray("colors"); rendering_program_light.setAttributeBuffer("colors",GL_FLOAT,6*sizeof(float),3, static_cast(9*sizeof(float))); vbos[Axis].release(); vaos[AXIS].release(); axis_size = data.size(); rendering_program_light.release(); } /*! Draws a grid in the XY plane, centered on (0,0,0) (defined in the current coordinate system). \p size (OpenGL units) and \p nbSubdivisions define its geometry.*/ CGAL_INLINE_FUNCTION void CGAL::QGLViewer::drawGrid(qreal size, int nbSubdivisions) { //The Grid std::vector v_Grid; for (int i=0; i<=nbSubdivisions; ++i) { const float pos = float(size*(2.0*i/nbSubdivisions-1.0)); v_Grid.push_back(pos); v_Grid.push_back(float(-size)); v_Grid.push_back(0.f); v_Grid.push_back(pos); v_Grid.push_back(float(+size)); v_Grid.push_back(0.f); v_Grid.push_back(float(-size)); v_Grid.push_back(pos); v_Grid.push_back(0.f); v_Grid.push_back( float(size)); v_Grid.push_back( pos); v_Grid.push_back( 0.f); } rendering_program.bind(); vaos[GRID].bind(); vbos[Grid].bind(); vbos[Grid].allocate(v_Grid.data(),static_cast(v_Grid.size()*sizeof(float))); rendering_program.enableAttributeArray("vertex"); rendering_program.setAttributeBuffer("vertex",GL_FLOAT,0,3); vbos[Grid].release(); vaos[GRID].release(); rendering_program.release(); grid_size = v_Grid.size(); //The Axis std::vector d_axis; d_axis.resize(0); //d_axis is filled by drawArrow always this way : V.x V.y V.z N.x N.y N.z C.r C.g C.b, so it is possible //to use a single buffer with offset and stride drawArrow(0.005*size,0.02*size,10, CGAL::qglviewer::Vec(0,0,0),CGAL::qglviewer::Vec(size,0,0),CGAL::qglviewer::Vec(1,0,0), d_axis); drawArrow(0.005*size,0.02*size,10, CGAL::qglviewer::Vec(0,0,0),CGAL::qglviewer::Vec(0,size,0),CGAL::qglviewer::Vec(0,1,0), d_axis); rendering_program_light.bind(); vaos[GRID_AXIS].bind(); vbos[Grid_axis].bind(); vbos[Grid_axis].allocate(d_axis.data(), static_cast(d_axis.size() * sizeof(float))); rendering_program_light.enableAttributeArray("vertex"); rendering_program_light.setAttributeBuffer("vertex",GL_FLOAT,0,3, static_cast(9*sizeof(float))); rendering_program_light.enableAttributeArray("normal"); rendering_program_light.setAttributeBuffer("normal",GL_FLOAT,3*sizeof(float),3, static_cast(9*sizeof(float))); rendering_program_light.enableAttributeArray("colors"); rendering_program_light.setAttributeBuffer("colors",GL_FLOAT,6*sizeof(float),3, static_cast(9*sizeof(float))); vbos[Grid_axis].release(); vaos[GRID_AXIS].release(); rendering_program_light.release(); g_axis_size = d_axis.size(); } //////////////////////////////////////////////////////////////////////////////// // S t a t i c m e t h o d s : Q G L V i e w e r P o o l // //////////////////////////////////////////////////////////////////////////////// /*! saveStateToFile() is called on all the CGAL::QGLViewers using the QGLViewerPool(). */ CGAL_INLINE_FUNCTION void CGAL::QGLViewer::saveStateToFileForAllViewers() { Q_FOREACH (CGAL::QGLViewer *viewer, CGAL::QGLViewer::QGLViewerPool()) { if (viewer) viewer->saveStateToFile(); } } ////////////////////////////////////////////////////////////////////////// // S a v e s t a t e b e t w e e n s e s s i o n s // ////////////////////////////////////////////////////////////////////////// /*! Returns the state file name. Default value is \c .qglviewer.xml. This is the name of the XML file where saveStateToFile() saves the viewer state (camera state, widget geometry, display flags... see domElement()) on exit. Use restoreStateFromFile() to restore this state later (usually in your init() method). Setting this value to \c QString() will disable the automatic state file saving that normally occurs on exit. If more than one viewer are created by the application, this function will return a numbered file name (as in ".qglviewer1.xml", ".qglviewer2.xml"... using QGLViewer::QGLViewerIndex()) for extra viewers. Each viewer will then read back its own information in restoreStateFromFile(), provided that the viewers are created in the same order, which is usually the case. */ CGAL_INLINE_FUNCTION QString CGAL::QGLViewer::stateFileName() const { QString name = stateFileName_; if (!name.isEmpty() && QGLViewer::QGLViewerIndex(this) > 0) { QFileInfo fi(name); if (fi.suffix().isEmpty()) name += QString::number(QGLViewer::QGLViewerIndex(this)); else name = fi.absolutePath() + '/' + fi.completeBaseName() + QString::number(QGLViewer::QGLViewerIndex(this)) + "." + fi.suffix(); } return name; } /*! Saves in stateFileName() an XML representation of the CGAL::QGLViewer state, obtained from domElement(). Use restoreStateFromFile() to restore this viewer state. This method is automatically called when a viewer is closed (using Escape or using the window's upper right \c x close button). setStateFileName() to \c QString() to prevent this. */ CGAL_INLINE_FUNCTION void CGAL::QGLViewer::saveStateToFile() { QString name = stateFileName(); if (name.isEmpty()) return; QFileInfo fileInfo(name); if (fileInfo.isDir()) { QMessageBox::warning( this, tr("Save to file error", "Message box window title"), tr("State file name (%1) references a directory instead of a file.") .arg(name)); return; } const QString dirName = fileInfo.absolutePath(); if (!QFileInfo(dirName).exists()) { QDir dir; if (!(dir.mkdir(dirName))) { QMessageBox::warning(this, tr("Save to file error", "Message box window title"), tr("Unable to create directory %1").arg(dirName)); return; } } // Write the DOM tree to file QFile f(name); if (f.open(QIODevice::WriteOnly)) { QTextStream out(&f); QDomDocument doc("QGLVIEWER"); doc.appendChild(domElement("CGAL::QGLViewer", doc)); doc.save(out, 2); f.flush(); f.close(); } else QMessageBox::warning( this, tr("Save to file error", "Message box window title"), tr("Unable to save to file %1").arg(name) + ":\n" + f.errorString()); } /*! Restores the CGAL::QGLViewer state from the stateFileName() file using initFromDOMElement(). States are saved using saveStateToFile(), which is automatically called on viewer exit. Returns \c true when the restoration is successful. Possible problems are an non existing or unreadable stateFileName() file, an empty stateFileName() or an XML syntax error. A manipulatedFrame() should be defined \e before calling this method, so that its state can be restored. Initialization code put \e after this function will override saved values: \code void Viewer::init() { // Default initialization goes here (including the declaration of a possible manipulatedFrame). if (!restoreStateFromFile()) showEntireScene(); // Previous state cannot be restored: fit camera to scene. // Specific initialization that overrides file savings goes here. } \endcode */ CGAL_INLINE_FUNCTION bool CGAL::QGLViewer::restoreStateFromFile() { QString name = stateFileName(); if (name.isEmpty()) return false; QFileInfo fileInfo(name); if (!fileInfo.isFile()) // No warning since it would be displayed at first start. return false; if (!fileInfo.isReadable()) { QMessageBox::warning( this, tr("Problem in state restoration", "Message box window title"), tr("File %1 is not readable.").arg(name)); return false; } // Read the DOM tree form file QFile f(name); if (f.open(QIODevice::ReadOnly)) { QDomDocument doc; doc.setContent(&f); f.close(); QDomElement main = doc.documentElement(); initFromDOMElement(main); } else { QMessageBox::warning( this, tr("Open file error", "Message box window title"), tr("Unable to open file %1").arg(name) + ":\n" + f.errorString()); return false; } return true; } /*! Returns an XML \c QDomElement that represents the CGAL::QGLViewer. Used by saveStateToFile(). restoreStateFromFile() uses initFromDOMElement() to restore the CGAL::QGLViewer state from the resulting \c QDomElement. \p name is the name of the QDomElement tag. \p doc is the \c QDomDocument factory used to create QDomElement. The created QDomElement contains state values (axisIsDrawn(), FPSIsDisplayed(), isFullScreen()...), viewer geometry, as well as camera() (see CGAL::qglviewer::Camera::domElement()) and manipulatedFrame() (if defined, see CGAL::qglviewer::ManipulatedFrame::domElement()) states. Overload this method to add your own attributes to the state file: \code CGAL_INLINE_FUNCTION QDomElement Viewer::domElement(const QString& name, QDomDocument& document) const { // Creates a custom node for a light QDomElement de = document.createElement("Light"); de.setAttribute("state", (lightIsOn()?"on":"off")); // Note the include of the ManipulatedFrame domElement method. de.appendChild(lightManipulatedFrame()->domElement("LightFrame", document)); // Get default state domElement and append custom node CGAL_INLINE_FUNCTION QDomElement res = CGAL::QGLViewer::domElement(name, document); res.appendChild(de); return res; } \endcode See initFromDOMElement() for the associated restoration code. \attention For the manipulatedFrame(), CGAL::qglviewer::Frame::constraint() and CGAL::qglviewer::Frame::referenceFrame() are not saved. See CGAL::qglviewer::Frame::domElement(). */ CGAL_INLINE_FUNCTION QDomElement CGAL::QGLViewer::domElement(const QString &name, QDomDocument &document) const { QDomElement de = document.createElement(name); QDomElement stateNode = document.createElement("State"); // hasMouseTracking() is not saved stateNode.appendChild(DomUtils::QColorDomElement( foregroundColor(), "foregroundColor", document)); stateNode.appendChild(DomUtils::QColorDomElement( backgroundColor(), "backgroundColor", document)); // Revolve or fly camera mode is not saved de.appendChild(stateNode); QDomElement displayNode = document.createElement("Display"); DomUtils::setBoolAttribute(displayNode, "axisIsDrawn", axisIsDrawn()); DomUtils::setBoolAttribute(displayNode, "gridIsDrawn", gridIsDrawn()); DomUtils::setBoolAttribute(displayNode, "FPSIsDisplayed", FPSIsDisplayed()); DomUtils::setBoolAttribute(displayNode, "cameraIsEdited", cameraIsEdited()); // textIsEnabled() is not saved de.appendChild(displayNode); QDomElement geometryNode = document.createElement("Geometry"); DomUtils::setBoolAttribute(geometryNode, "fullScreen", isFullScreen()); if (isFullScreen()) { geometryNode.setAttribute("prevPosX", QString::number(prevPos_.x())); geometryNode.setAttribute("prevPosY", QString::number(prevPos_.y())); } else { QWidget *tlw = topLevelWidget(); geometryNode.setAttribute("width", QString::number(tlw->width())); geometryNode.setAttribute("height", QString::number(tlw->height())); geometryNode.setAttribute("posX", QString::number(tlw->pos().x())); geometryNode.setAttribute("posY", QString::number(tlw->pos().y())); } de.appendChild(geometryNode); // Restore original Camera zClippingCoefficient before saving. if (cameraIsEdited()) camera()->setZClippingCoefficient(previousCameraZClippingCoefficient_); de.appendChild(camera()->domElement("Camera", document)); if (cameraIsEdited()) // #CONNECTION# 5.0 from setCameraIsEdited() camera()->setZClippingCoefficient(5.0); if (manipulatedFrame()) de.appendChild( manipulatedFrame()->domElement("ManipulatedFrame", document)); return de; } /*! Restores the CGAL::QGLViewer state from a \c QDomElement created by domElement(). Used by restoreStateFromFile() to restore the CGAL::QGLViewer state from a file. Overload this method to retrieve custom attributes from the CGAL::QGLViewer state file. This code corresponds to the one given in the domElement() documentation: \code CGAL_INLINE_FUNCTION void Viewer::initFromDOMElement(const QDomElement& element) { // Restore standard state CGAL_INLINE_FUNCTION CGAL::QGLViewer::initFromDOMElement(element); QDomElement child=element.firstChild().toElement(); while (!child.isNull()) { if (child.tagName() == "Light") { if (child.hasAttribute("state")) setLightOn(child.attribute("state").toLower() == "on"); // Assumes there is only one child. Otherwise you need to parse child's children recursively. QDomElement lf = child.firstChild().toElement(); if (!lf.isNull() && lf.tagName() == "LightFrame") lightManipulatedFrame()->initFromDomElement(lf); } child = child.nextSibling().toElement(); } } \endcode CGAL_INLINE_FUNCTION See also CGAL::qglviewer::Camera::initFromDOMElement(), CGAL::qglviewer::ManipulatedFrame::initFromDOMElement(). \note The manipulatedFrame() \e pointer is not modified by this method. If defined, its state is simply set from the \p element values. */ CGAL_INLINE_FUNCTION void CGAL::QGLViewer::initFromDOMElement(const QDomElement &element) { QDomElement child = element.firstChild().toElement(); bool tmpCameraIsEdited = cameraIsEdited(); while (!child.isNull()) { if (child.tagName() == "State") { // #CONNECTION# default values from defaultConstructor() // setMouseTracking(DomUtils::boolFromDom(child, "mouseTracking", false)); // if ((child.attribute("cameraMode", "revolve") == "fly") && // (cameraIsInRevolveMode())) toggleCameraMode(); QDomElement ch = child.firstChild().toElement(); while (!ch.isNull()) { if (ch.tagName() == "foregroundColor") setForegroundColor(DomUtils::QColorFromDom(ch)); if (ch.tagName() == "backgroundColor") setBackgroundColor(DomUtils::QColorFromDom(ch)); ch = ch.nextSibling().toElement(); } } if (child.tagName() == "Display") { // #CONNECTION# default values from defaultConstructor() setAxisIsDrawn(DomUtils::boolFromDom(child, "axisIsDrawn", false)); setGridIsDrawn(DomUtils::boolFromDom(child, "gridIsDrawn", false)); setFPSIsDisplayed(DomUtils::boolFromDom(child, "FPSIsDisplayed", false)); // See comment below. tmpCameraIsEdited = DomUtils::boolFromDom(child, "cameraIsEdited", false); // setTextIsEnabled(DomUtils::boolFromDom(child, "textIsEnabled", true)); } if (child.tagName() == "Geometry") { setFullScreen(DomUtils::boolFromDom(child, "fullScreen", false)); if (isFullScreen()) { prevPos_.setX(DomUtils::intFromDom(child, "prevPosX", 0)); prevPos_.setY(DomUtils::intFromDom(child, "prevPosY", 0)); } else { int width = DomUtils::intFromDom(child, "width", 600); int height = DomUtils::intFromDom(child, "height", 400); topLevelWidget()->resize(width, height); camera()->setScreenWidthAndHeight(this->width(), this->height()); QPoint pos; pos.setX(DomUtils::intFromDom(child, "posX", 0)); pos.setY(DomUtils::intFromDom(child, "posY", 0)); topLevelWidget()->move(pos); } } if (child.tagName() == "Camera") { connectAllCameraKFIInterpolatedSignals(false); camera()->initFromDOMElement(child); connectAllCameraKFIInterpolatedSignals(); } if ((child.tagName() == "ManipulatedFrame") && (manipulatedFrame())) manipulatedFrame()->initFromDOMElement(child); child = child.nextSibling().toElement(); } // The Camera always stores its "real" zClippingCoef in domElement(). If it is // edited, its "real" coef must be saved and the coef set to 5.0, as is done // in setCameraIsEdited(). BUT : Camera and Display are read in an arbitrary // order. We must initialize Camera's "real" coef BEFORE calling // setCameraIsEdited. Hence this temp cameraIsEdited and delayed call cameraIsEdited_ = tmpCameraIsEdited; if (cameraIsEdited_) { previousCameraZClippingCoefficient_ = camera()->zClippingCoefficient(); // #CONNECTION# 5.0 from setCameraIsEdited. camera()->setZClippingCoefficient(5.0); } } CGAL_INLINE_FUNCTION void CGAL::QGLViewer::copyBufferToTexture(GLint , GLenum ) { } /*! Returns the texture id of the texture created by copyBufferToTexture(). Use glBindTexture() to use this texture. Note that this is already done by copyBufferToTexture(). Returns \c 0 is copyBufferToTexture() was never called or if the texure was deleted using glDeleteTextures() since then. */ CGAL_INLINE_FUNCTION GLuint CGAL::QGLViewer::bufferTextureId() const { return 0; } CGAL_INLINE_FUNCTION void CGAL::QGLViewer::setOffset(CGAL::qglviewer::Vec offset) { this->_offset = offset; } CGAL_INLINE_FUNCTION CGAL::qglviewer::Vec CGAL::QGLViewer::offset()const { return _offset; } CGAL_INLINE_FUNCTION qreal CGAL::QGLViewer::sceneRadius() const { return camera()->sceneRadius(); } CGAL_INLINE_FUNCTION CGAL::qglviewer::Vec CGAL::QGLViewer::sceneCenter() const { return camera()->sceneCenter(); } CGAL_INLINE_FUNCTION void CGAL::QGLViewer::setSceneRadius(qreal radius) { camera()->setSceneRadius(radius); } CGAL_INLINE_FUNCTION void CGAL::QGLViewer::setSceneCenter(const CGAL::qglviewer::Vec ¢er) { camera()->setSceneCenter(center); } CGAL_INLINE_FUNCTION void CGAL::QGLViewer::setSceneBoundingBox(const CGAL::qglviewer::Vec &min, const CGAL::qglviewer::Vec &max) { camera()->setSceneBoundingBox(min + offset(), max + offset()); } CGAL_INLINE_FUNCTION void CGAL::QGLViewer::showEntireScene() { camera()->showEntireScene(); update(); } CGAL_INLINE_FUNCTION QImage* CGAL::QGLViewer::takeSnapshot( CGAL::qglviewer::SnapShotBackground background_color, QSize finalSize, double oversampling, bool expand) { makeCurrent(); qreal aspectRatio = width() / static_cast(height()); GLfloat alpha = 1.0f; QColor previousBGColor = backgroundColor(); switch(background_color) { case CGAL::qglviewer::CURRENT_BACKGROUND: break; case CGAL::qglviewer::TRANSPARENT_BACKGROUND: setBackgroundColor(QColor(::Qt::transparent)); alpha = 0.0f; break; case CGAL::qglviewer::CHOOSE_BACKGROUND: QColor c = QColorDialog::getColor(); if(c.isValid()) { setBackgroundColor(c); } else return nullptr; break; } QSize subSize(int(width()/oversampling), int(height()/oversampling)); QSize size=QSize(width(), height()); qreal newAspectRatio = finalSize.width() / static_cast(finalSize.height()); qreal zNear = camera()->zNear(); qreal zFar = camera()->zFar(); qreal xMin, yMin; if(camera()->type()==CGAL::qglviewer::Camera::PERSPECTIVE) { if ((expand && (newAspectRatio>aspectRatio)) || (!expand && (newAspectRatiofieldOfView() / 2.0); xMin = newAspectRatio * yMin; } else { xMin = zNear * tan(camera()->fieldOfView() / 2.0) * aspectRatio; yMin = xMin / newAspectRatio; } } else { double xy[6]; camera()->getFrustum(xy); if ((expand && (newAspectRatio>aspectRatio)) || (!expand && (newAspectRatioisNull()) { QMessageBox::warning(this, "Image saving error", "Unable to create resulting image", QMessageBox::Ok, QMessageBox::NoButton); setBackgroundColor(previousBGColor); return nullptr; } qreal scaleX = subSize.width() / static_cast(finalSize.width()); qreal scaleY = subSize.height() / static_cast(finalSize.height()); qreal deltaX = 2.0 * xMin * scaleX; qreal deltaY = 2.0 * yMin * scaleY; int nbX = finalSize.width() / subSize.width(); int nbY = finalSize.height() / subSize.height(); // Extra subimage on the right/bottom border(s) if needed if (nbX * subSize.width() < finalSize.width()) nbX++; if (nbY * subSize.height() < finalSize.height()) nbY++; GLdouble frustum[6]; camera()->getFrustum(frustum); QOpenGLFramebufferObject fbo(size,QOpenGLFramebufferObject::CombinedDepthStencil, GL_TEXTURE_2D, GL_RGBA32F); stored_fbo = &fbo; for (int i=0; isetFrustum(frustum); preDraw(); draw(); fbo.release(); QImage snapshot = fbo.toImage(); QImage subImage = snapshot.scaled(subSize, ::Qt::IgnoreAspectRatio, ::Qt::SmoothTransformation); // Copy subImage in image for (int ii=0; iiwidth()) break; for (int jj=0; jjheight()) break; image->setPixel(fi, fj, subImage.pixel(ii,jj)); } } } if(background_color !=0) setBackgroundColor(previousBGColor); camera()->setFrustum(frustum); stored_fbo = nullptr; return image; } CGAL_INLINE_FUNCTION QOpenGLFramebufferObject* CGAL::QGLViewer::getStoredFrameBuffer() const { return stored_fbo; } CGAL_INLINE_FUNCTION void CGAL::QGLViewer::setStoredFrameBuffer(QOpenGLFramebufferObject *fbo) { stored_fbo = fbo; } CGAL_INLINE_FUNCTION void CGAL::QGLViewer::saveSnapshot() { qreal aspectRatio = width() / static_cast(height()); static ImageInterface* imageInterface = nullptr; if (!imageInterface) imageInterface = new ImageInterface(this, aspectRatio); imageInterface->imgWidth->setValue(width()); imageInterface->imgHeight->setValue(height()); if (imageInterface->exec() == QDialog::Rejected) return; QSize finalSize(imageInterface->imgWidth->value(), imageInterface->imgHeight->value()); bool expand = imageInterface->expandFrustum->isChecked(); QString fileName = QFileDialog::getSaveFileName(this, tr("Save Snapshot"), "", tr("Image Files (*.png *.jpg *.bmp)")); if(fileName.isEmpty()) { return; } QImage* image= takeSnapshot(qglviewer::SnapShotBackground(imageInterface->color_comboBox->currentIndex()), finalSize, imageInterface->oversampling->value(), expand); if(image) { image->save(fileName); delete image; } } } CGAL_INLINE_FUNCTION bool CGAL::QGLViewer::isSharing() const { return is_sharing; }