// SPDX-License-Identifier: LGPL-2.1-or-later /*************************************************************************** * Copyright (c) 2013 Werner Mayer * * * * This file is part of the FreeCAD CAx development system. * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Library General Public * * License as published by the Free Software Foundation; either * * version 2 of the License, or (at your option) any later version. * * * * This library is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this library; see the file COPYING.LIB. If not, * * write to the Free Software Foundation, Inc., 59 Temple Place, * * Suite 330, Boston, MA 02111-1307, USA * * * ***************************************************************************/ #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 #include #include "GLGraphicsView.h" #include #include #include #include #include #include using namespace Gui; // http://doc.qt.digia.com/qq/qq26-openglcanvas.html GraphicsView::GraphicsView() { } GraphicsView::~GraphicsView() { } bool GraphicsView::viewportEvent(QEvent* event) { if (event->type() == QEvent::Paint || event->type() == QEvent::Resize) { return QGraphicsView::viewportEvent(event); } else if (event->type() == QEvent::MouseMove || event->type() == QEvent::Wheel || event->type() == QEvent::MouseButtonDblClick || event->type() == QEvent::MouseButtonRelease || event->type() == QEvent::MouseButtonPress) { QMouseEvent* mouse = static_cast(event); QGraphicsItem *item = itemAt(mouse->pos()); if (!item) { QGraphicsView::viewportEvent(event); return false; } return QGraphicsView::viewportEvent(event); } return QGraphicsView::viewportEvent(event); } void GraphicsView::resizeEvent(QResizeEvent *event) { if (scene()) scene()->setSceneRect( QRect(QPoint(0, 0), event->size())); QGraphicsView::resizeEvent(event); } // ---------------------------------------------------------------------------- class SceneEventFilter::Private { public: QList devices; GraphicsScene * scene; QPoint globalmousepos; SbVec2s windowsize; void trackWindowSize(QResizeEvent * event) { this->windowsize = SbVec2s(event->size().width(), event->size().height()); Q_FOREACH(SIM::Coin3D::Quarter::InputDevice * device, this->devices) { device->setWindowSize(this->windowsize); } } void trackPointerPosition(QMouseEvent * event) { assert(this->windowsize[1] != -1); this->globalmousepos = event->globalPos(); SbVec2s mousepos(event->pos().x(), this->windowsize[1] - event->pos().y() - 1); Q_FOREACH(SIM::Coin3D::Quarter::InputDevice * device, this->devices) { device->setMousePosition(mousepos); } } }; #define PRIVATE(obj) obj->pimpl SceneEventFilter::SceneEventFilter(QObject * parent) : QObject(parent) { PRIVATE(this) = new Private; PRIVATE(this)->scene = dynamic_cast(parent); assert(PRIVATE(this)->scene); PRIVATE(this)->windowsize = SbVec2s(PRIVATE(this)->scene->width(), PRIVATE(this)->scene->height()); PRIVATE(this)->devices += new SIM::Coin3D::Quarter::Mouse; PRIVATE(this)->devices += new SIM::Coin3D::Quarter::Keyboard; #ifdef HAVE_SPACENAV_LIB PRIVATE(this)->devices += new SpaceNavigatorDevice; #endif // HAVE_SPACENAV_LIB } SceneEventFilter::~SceneEventFilter() { qDeleteAll(PRIVATE(this)->devices); delete PRIVATE(this); } /*! Adds a device for event translation */ void SceneEventFilter::registerInputDevice(SIM::Coin3D::Quarter::InputDevice * device) { PRIVATE(this)->devices += device; } /*! Removes a device from event translation */ void SceneEventFilter::unregisterInputDevice(SIM::Coin3D::Quarter::InputDevice * device) { int i = PRIVATE(this)->devices.indexOf(device); if (i != -1) { PRIVATE(this)->devices.removeAt(i); } } /*! Translates Qt Events into Coin events and passes them on to the event QuarterWidget for processing. If the event can not be translated or processed, it is forwarded to Qt and the method returns false. */ bool SceneEventFilter::eventFilter(QObject *, QEvent * qevent) { // Convert the scene event back to a standard event std::unique_ptr sceneev; switch (qevent->type()) { case QEvent::GraphicsSceneMouseMove: { QGraphicsSceneMouseEvent* ev = static_cast(qevent); sceneev.reset(new QMouseEvent(QEvent::MouseMove, ev->pos().toPoint(), ev->button(), ev->buttons(), ev->modifiers())); qevent = sceneev.get(); break; } case QEvent::GraphicsSceneMousePress: { QGraphicsSceneMouseEvent* ev = static_cast(qevent); sceneev.reset(new QMouseEvent(QEvent::MouseButtonPress, ev->pos().toPoint(), ev->button(), ev->buttons(), ev->modifiers())); qevent = sceneev.get(); break; } case QEvent::GraphicsSceneMouseRelease: { QGraphicsSceneMouseEvent* ev = static_cast(qevent); sceneev.reset(new QMouseEvent(QEvent::MouseButtonRelease, ev->pos().toPoint(), ev->button(), ev->buttons(), ev->modifiers())); qevent = sceneev.get(); break; } case QEvent::GraphicsSceneMouseDoubleClick: { QGraphicsSceneMouseEvent* ev = static_cast(qevent); sceneev.reset(new QMouseEvent(QEvent::MouseButtonDblClick, ev->pos().toPoint(), ev->button(), ev->buttons(), ev->modifiers())); qevent = sceneev.get(); break; } case QEvent::GraphicsSceneWheel: { #if QT_VERSION < QT_VERSION_CHECK(6,0,0) QGraphicsSceneWheelEvent* ev = static_cast(qevent); sceneev.reset(new QWheelEvent(ev->pos().toPoint(), ev->delta(), ev->buttons(), ev->modifiers(), ev->orientation())); qevent = sceneev.get(); #endif break; } case QEvent::GraphicsSceneResize: { QGraphicsSceneResizeEvent* ev = static_cast(qevent); sceneev.reset(new QResizeEvent(ev->newSize().toSize(), ev->oldSize().toSize())); qevent = sceneev.get(); break; } default: break; } // make sure every device has updated screen size and mouse position // before translating events switch (qevent->type()) { case QEvent::MouseMove: case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: case QEvent::MouseButtonDblClick: PRIVATE(this)->trackPointerPosition(dynamic_cast(qevent)); break; case QEvent::Resize: PRIVATE(this)->trackWindowSize(dynamic_cast(qevent)); break; default: break; } // translate QEvent into SoEvent and see if it is handled by scene // graph Q_FOREACH(SIM::Coin3D::Quarter::InputDevice * device, PRIVATE(this)->devices) { const SoEvent * soevent = device->translateEvent(qevent); if (soevent && PRIVATE(this)->scene->processSoEvent(soevent)) { //return true; } } return false; } /*! Returns mouse position in global coordinates */ const QPoint & SceneEventFilter::globalMousePosition(void) const { return PRIVATE(this)->globalmousepos; } #undef PRIVATE // ---------------------------------------------------------------------------- QDialog *GraphicsScene::createDialog(const QString &windowTitle) const { QDialog *dialog = new QDialog(0, Qt::Window | Qt::CustomizeWindowHint | Qt::WindowTitleHint); dialog->setWindowOpacity(0.8); dialog->setWindowTitle(windowTitle); dialog->setLayout(new QVBoxLayout); return dialog; } GraphicsScene::GraphicsScene() : m_backgroundColor(0, 170, 255) , m_lastTime(0) , m_distance(1.4f) { headlight = new SoDirectionalLight(); headlight->ref(); sceneNode = new SoSeparator(); sceneNode->ref(); this->addEllipse(20,20, 120, 60); QWidget *controls = createDialog(tr("Controls")); QCheckBox *wireframe = new QCheckBox(tr("Render as wireframe")); controls->layout()->addWidget(wireframe); QCheckBox *normals = new QCheckBox(tr("Display normals vectors")); controls->layout()->addWidget(normals); QPushButton *colorButton = new QPushButton(tr("Choose model color")); controls->layout()->addWidget(colorButton); QWidget *instructions = createDialog(tr("Instructions")); instructions->layout()->addWidget(new QLabel(tr("Use mouse wheel to zoom model, and click and drag to rotate model"))); instructions->layout()->addWidget(new QLabel(tr("Move the sun around to change the light position"))); QGraphicsProxyWidget* g1 = addWidget(instructions); g1->setWindowFlags(Qt::Window | Qt::CustomizeWindowHint | Qt::WindowTitleHint); QGraphicsProxyWidget* g2 = addWidget(controls); g2->setWindowFlags(Qt::Window | Qt::CustomizeWindowHint | Qt::WindowTitleHint); controls->setAttribute(Qt::WA_TranslucentBackground); QPointF pos(10, 10); Q_FOREACH (QGraphicsItem *item, items()) { item->setFlag(QGraphicsItem::ItemIsMovable); item->setCacheMode(QGraphicsItem::DeviceCoordinateCache); const QRectF rect = item->boundingRect(); item->setPos(pos.x() - rect.x(), pos.y() - rect.y()); pos += QPointF(0, 10 + rect.height()); } #if QT_VERSION < QT_VERSION_CHECK(6,0,0) m_time.start(); #endif sorendermanager = new SoRenderManager; sorendermanager->setAutoClipping(SoRenderManager::VARIABLE_NEAR_PLANE); sorendermanager->setBackgroundColor(SbColor4f(0.0f, 0.0f, 0.0f, 0.0f)); sorendermanager->activate(); soeventmanager = new SoEventManager; soeventmanager->setNavigationState(SoEventManager::MIXED_NAVIGATION); eventfilter = new SceneEventFilter(this); connect(this, SIGNAL(sceneRectChanged(const QRectF&)), this, SLOT(onSceneRectChanged(const QRectF&))); } GraphicsScene::~GraphicsScene() { headlight->unref(); sceneNode->unref(); delete sorendermanager; delete soeventmanager; delete eventfilter; } void GraphicsScene::viewAll() { SoCamera* cam = sorendermanager->getCamera(); if (cam) cam->viewAll(sceneNode, sorendermanager->getViewportRegion()); } SceneEventFilter * GraphicsScene::getEventFilter(void) const { return eventfilter; } bool GraphicsScene::processSoEvent(const SoEvent * event) { if (event) { return soeventmanager && soeventmanager->processEvent(event); } return false; } void GraphicsScene::addStateMachine(SoScXMLStateMachine * statemachine) { SoEventManager * em = this->getSoEventManager(); em->addSoScXMLStateMachine(statemachine); statemachine->setSceneGraphRoot(this->getSoRenderManager()->getSceneGraph()); statemachine->setActiveCamera(this->getSoRenderManager()->getCamera()); } void GraphicsScene::removeStateMachine(SoScXMLStateMachine * statemachine) { SoEventManager * em = this->getSoEventManager(); statemachine->setSceneGraphRoot(NULL); statemachine->setActiveCamera(NULL); em->removeSoScXMLStateMachine(statemachine); } void GraphicsScene::setNavigationModeFile(const QUrl & url) { QString filename; if (url.scheme()== QLatin1String("coin")) { filename = url.path(); //FIXME: This conditional needs to be implemented when the //CoinResources systems if working //Workaround for differences between url scheme, and Coin internal //scheme in Coin 3.0. if (filename[0] == QLatin1Char('/')) { filename.remove(0,1); } filename = url.scheme() + QLatin1Char(':') + filename; } else if (url.scheme() == QLatin1String("file")) filename = url.toLocalFile(); else if (url.isEmpty()) { return; } else { return; } QByteArray filenametmp = filename.toLocal8Bit(); ScXMLStateMachine * stateMachine = NULL; if (filenametmp.startsWith("coin:")){ stateMachine = ScXML::readFile(filenametmp.data()); } else { //Use Qt to read the file in case it is a Qt resource QFile file(QString::fromLatin1(filenametmp)); if (file.open(QIODevice::ReadOnly)) { QByteArray fileContents = file.readAll(); stateMachine = ScXML::readBuffer(SbByteBuffer(fileContents.size(), fileContents.constData())); file.close(); } } if (stateMachine && stateMachine->isOfType(SoScXMLStateMachine::getClassTypeId())) { SoScXMLStateMachine * newsm = static_cast(stateMachine); this->addStateMachine(newsm); newsm->initialize(); } else { if (stateMachine) delete stateMachine; return; } } SoNode* GraphicsScene::getSceneGraph() const { return sceneNode; } SoCamera * GraphicsScene::searchForCamera(SoNode * root) { SoSearchAction sa; sa.setInterest(SoSearchAction::FIRST); sa.setType(SoCamera::getClassTypeId()); sa.apply(root); if (sa.getPath()) { SoNode * node = sa.getPath()->getTail(); if (node && node->isOfType(SoCamera::getClassTypeId())) { return (SoCamera *) node; } } return NULL; } void GraphicsScene::setSceneGraph(SoNode * node) { if (node == sceneNode) { return; } if (sceneNode) { sceneNode->unref(); sceneNode = NULL; } SoCamera * camera = NULL; SoSeparator * superscene = NULL; bool viewall = false; if (node) { sceneNode = node; sceneNode->ref(); superscene = new SoSeparator; superscene->addChild(headlight); // if the scene does not contain a camera, add one if (!(camera = searchForCamera(node))) { camera = new SoOrthographicCamera; superscene->addChild(camera); viewall = true; } superscene->addChild(node); } soeventmanager->setCamera(camera); sorendermanager->setCamera(camera); soeventmanager->setSceneGraph(superscene); sorendermanager->setSceneGraph(superscene); if (viewall) { this->viewAll(); } if (superscene) { superscene->touch(); } } SoRenderManager * GraphicsScene::getSoRenderManager(void) const { return sorendermanager; } SoEventManager * GraphicsScene::getSoEventManager(void) const { return soeventmanager; } void GraphicsScene::onSceneRectChanged(const QRectF & rect) { SbViewportRegion vp(rect.width(), rect.height()); sorendermanager->setViewportRegion(vp); soeventmanager->setViewportRegion(vp); } void GraphicsScene::drawBackground(QPainter *painter, const QRectF &) { if (painter->paintEngine()->type() != QPaintEngine::OpenGL && painter->paintEngine()->type() != QPaintEngine::OpenGL2) { qWarning("GraphicsScene: drawBackground needs a QGLWidget to be set as viewport on the graphics view"); return; } #if QT_VERSION < QT_VERSION_CHECK(6,0,0) const int delta = m_time.elapsed() - m_lastTime; m_lastTime += delta; #endif sorendermanager->render(true/*PRIVATE(this)->clearwindow*/, false/*PRIVATE(this)->clearzbuffer*/); painter->save(); painter->fillRect(40,40,40,60,Qt::lightGray); painter->drawText(50,50, QStringLiteral("Done with QPainter")); painter->restore(); QTimer::singleShot(20, this, SLOT(update())); } void GraphicsScene::setBackgroundColor(const QColor& color) { if (color.isValid()) { m_backgroundColor = color; update(); SbColor4f bgcolor(SbClamp(color.red() / 255.0, 0.0, 1.0), SbClamp(color.green() / 255.0, 0.0, 1.0), SbClamp(color.blue() / 255.0, 0.0, 1.0), SbClamp(color.alpha() / 255.0, 0.0, 1.0)); sorendermanager->setBackgroundColor(bgcolor); sorendermanager->scheduleRedraw(); } } void GraphicsScene::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { QGraphicsScene::mouseMoveEvent(event); if (event->isAccepted()) return; if (event->buttons() & Qt::LeftButton) { event->accept(); update(); } } void GraphicsScene::mousePressEvent(QGraphicsSceneMouseEvent *event) { QGraphicsScene::mousePressEvent(event); if (event->isAccepted()) return; #if QT_VERSION < QT_VERSION_CHECK(6,0,0) m_mouseEventTime = m_time.elapsed(); #endif event->accept(); } void GraphicsScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { QGraphicsScene::mouseReleaseEvent(event); if (event->isAccepted()) return; event->accept(); update(); } void GraphicsScene::wheelEvent(QGraphicsSceneWheelEvent *event) { QGraphicsScene::wheelEvent(event); if (event->isAccepted()) return; event->accept(); update(); } // ---------------------------------------------------------------------------- GraphicsView3D::GraphicsView3D(Gui::Document* doc, QWidget* parent) : Gui::MDIView(doc, parent), m_scene(new GraphicsScene()), m_view(new GraphicsView) { m_view->installEventFilter(m_scene->getEventFilter()); m_view->setViewportUpdateMode(QGraphicsView::FullViewportUpdate); m_view->setScene(m_scene); m_scene->setNavigationModeFile(QUrl(QStringLiteral("coin:///scxml/navigation/examiner.xml"))); std::vector v = doc->getViewProvidersOfType(ViewProvider::getClassTypeId()); SoSeparator* root = new SoSeparator(); for (std::vector::iterator it = v.begin(); it != v.end(); ++it) root->addChild((*it)->getRoot()); m_scene->setSceneGraph(root); setCentralWidget(m_view); m_scene->viewAll(); // attach parameter Observer hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/View"); hGrp->Attach(this); OnChange(*hGrp,"BackgroundColor"); } GraphicsView3D::~GraphicsView3D() { hGrp->Detach(this); delete m_view; delete m_scene; } void GraphicsView3D::OnChange(ParameterGrp::SubjectType &rCaller,ParameterGrp::MessageType Reason) { const ParameterGrp& rGrp = static_cast(rCaller); if (strcmp(Reason, "BackgroundColor") == 0) { unsigned long col1 = rGrp.GetUnsigned("BackgroundColor",3940932863UL); float r1,g1,b1; r1 = ((col1 >> 24) & 0xff) / 255.0; g1 = ((col1 >> 16) & 0xff) / 255.0; b1 = ((col1 >> 8) & 0xff) / 255.0; m_scene->setBackgroundColor(QColor::fromRgbF(r1, g1, b1)); } } #include "moc_GLGraphicsView.cpp"