Files
create/src/Gui/View3DInventor.cpp
forbes fd371573fd
Some checks failed
Build and Test / build (pull_request) Has been cancelled
feat(ui): move editing context breadcrumb to viewport overlay (#232)
Convert BreadcrumbToolBar from a QToolBar in the MainWindow toolbar area
to a QFrame overlay at the top-left of each 3D viewport.

Changes:
- BreadcrumbToolBar: change base class from QToolBar to QFrame, use
  QHBoxLayout instead of toolbar actions, semi-transparent overlay
  background with rounded corners
- MainWindow: remove breadcrumb toolbar creation, toolbar break, and
  signal connection
- View3DInventor: create per-view BreadcrumbToolBar as a child of the
  GL viewer widget, positioned at (8,8) with event filter to keep it
  raised on viewport resize

Each 3D view now has its own breadcrumb instance connected to the
EditingContextResolver singleton. This reclaims the full-width toolbar
row and keeps the editing context visually tied to the viewport.
2026-02-14 19:17:42 -06:00

982 lines
31 KiB
C++

/***************************************************************************
* Copyright (c) 2004 Jürgen Riegel <juergen.riegel@web.de> *
* *
* 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 <string>
#include <QApplication>
#include <QKeyEvent>
#include <QEvent>
#include <QDropEvent>
#include <QDragEnterEvent>
#include <QLayout>
#include <QMdiSubWindow>
#include <QMessageBox>
#include <QMimeData>
#include <QPainter>
#include <QPrinter>
#include <QPrintDialog>
#include <QPrintPreviewDialog>
#include <QStackedWidget>
#include <QSurfaceFormat>
#include <QTimer>
#include <QUrl>
#include <QWindow>
#include <Inventor/actions/SoGetPrimitiveCountAction.h>
#include <Inventor/fields/SoSFString.h>
#include <Inventor/nodes/SoOrthographicCamera.h>
#include <Inventor/nodes/SoPerspectiveCamera.h>
#include <Inventor/nodes/SoSeparator.h>
#include <Inventor/SoPickedPoint.h>
#include <App/Application.h>
#include <App/Document.h>
#include <App/GeoFeature.h>
#include <Base/Builder3D.h>
#include <Base/Console.h>
#include <Base/Interpreter.h>
#include <Gui/PreferencePages/DlgSettingsPDF.h>
#include "View3DInventor.h"
#include "View3DSettings.h"
#include "Application.h"
#include "BreadcrumbToolBar.h"
#include "BitmapFactory.h"
#include "EditingContext.h"
#include "Camera.h"
#include "Document.h"
#include "FileDialog.h"
#include "MainWindow.h"
#include "NaviCube.h"
#include "Navigation/NavigationStyle.h"
#include "SoFCDB.h"
#include "SoFCSelectionAction.h"
#include "SoFCVectorizeSVGAction.h"
#include "View3DInventorViewer.h"
#include "View3DPy.h"
#include "ViewProvider.h"
#include "ViewProviderDocumentObject.h"
#include "WaitCursor.h"
#include "Utilities.h"
using namespace Gui;
void GLOverlayWidget::paintEvent(QPaintEvent*)
{
QPainter paint(this);
paint.drawImage(0, 0, image);
paint.end();
}
/* TRANSLATOR Gui::View3DInventor */
TYPESYSTEM_SOURCE_ABSTRACT(Gui::View3DInventor, Gui::MDIView)
View3DInventor::View3DInventor(
Gui::Document* pcDocument,
QWidget* parent,
const QOpenGLWidget* sharewidget,
Qt::WindowFlags wflags
)
: MDIView(pcDocument, parent, wflags)
, _viewerPy(nullptr)
{
stack = new QStackedWidget(this);
// important for highlighting
setMouseTracking(true);
// accept drops on the window, get handled in dropEvent, dragEnterEvent
setAcceptDrops(true);
// anti-aliasing settings
bool smoothing = false;
bool glformat = false;
int samples = View3DInventorViewer::getNumSamples();
QSurfaceFormat f;
if (samples > 1) {
glformat = true;
f.setSamples(samples);
}
else if (samples > 0) {
smoothing = true;
}
if (glformat) {
_viewer = new View3DInventorViewer(f, this, sharewidget);
}
else {
_viewer = new View3DInventorViewer(this, sharewidget);
}
if (smoothing) {
_viewer->getSoRenderManager()->getGLRenderAction()->setSmoothing(true);
}
// create the inventor widget and set the defaults
_viewer->setDocument(this->_pcDocument);
stack->addWidget(_viewer->getWidget());
// https://forum.freecad.org/viewtopic.php?f=3&t=6055&sid=150ed90cbefba50f1e2ad4b4e6684eba
// describes a minor error but trying to fix it leads to a major issue
// https://forum.freecad.org/viewtopic.php?f=3&t=6085&sid=3f4bcab8007b96aaf31928b564190fd7
// so the change is commented out
// By default, the wheel events are processed by the 3d view AND the mdi area.
//_viewer->getGLWidget()->setAttribute(Qt::WA_NoMousePropagation);
setCentralWidget(stack);
// apply the user settings
applySettings();
// Breadcrumb overlay at top-left of viewport
_breadcrumb = new BreadcrumbToolBar(_viewer->getWidget());
_breadcrumb->move(8, 8);
_breadcrumb->raise();
_breadcrumb->show();
_viewer->getWidget()->installEventFilter(this);
auto* ctxResolver = EditingContextResolver::instance();
connect(ctxResolver, &EditingContextResolver::contextChanged,
_breadcrumb, &BreadcrumbToolBar::updateContext);
// Apply the current context immediately
_breadcrumb->updateContext(ctxResolver->currentContext());
stopSpinTimer = new QTimer(this);
connect(stopSpinTimer, &QTimer::timeout, this, &View3DInventor::stopAnimating);
setWindowIcon(Gui::BitmapFactory().pixmap("Document"));
}
View3DInventor::~View3DInventor()
{
if (_pcDocument) {
SoCamera* Cam = _viewer->getSoRenderManager()->getCamera();
if (Cam) {
_pcDocument->saveCameraSettings(SoFCDB::writeNodesToString(Cam).c_str());
}
}
viewSettings.reset();
// If we destroy this viewer by calling 'delete' directly the focus proxy widget which is
// defined by a widget in SoQtViewer isn't reset. This widget becomes a dangling pointer and
// makes the application crash. (Probably it's better to destroy this viewer by calling
// close().) See also Gui::Document::~Document().
QWidget* foc = qApp->focusWidget();
if (foc) {
QWidget* par = foc->parentWidget();
while (par) {
if (par == this) {
foc->setFocusProxy(nullptr);
foc->clearFocus();
break;
}
par = par->parentWidget();
}
}
if (_viewerPy) {
Base::PyGILStateLocker lock;
Py_DECREF(_viewerPy);
}
// here is from time to time trouble!!!
delete _viewer;
}
void View3DInventor::deleteSelf()
{
_viewer->setSceneGraph(nullptr);
_viewer->setDocument(nullptr);
MDIView::deleteSelf();
}
View3DInventor* View3DInventor::clone()
{
auto mdiView = _pcDocument->createView(getClassTypeId(), CreateViewMode::Clone);
auto view3D = static_cast<View3DInventor*>(mdiView);
view3D->cloneFrom(*this);
view3D->getViewer()->setAxisCross(getViewer()->hasAxisCross());
// FIXME: Add parameter to define behaviour by the calling instance
// View provider editing
int editMode;
ViewProvider* editViewProvider = _pcDocument->getInEdit(nullptr, nullptr, &editMode);
if (editViewProvider) {
getViewer()->resetEditingViewProvider();
view3D->getViewer()->setEditingViewProvider(editViewProvider, editMode);
}
return view3D;
}
PyObject* View3DInventor::getPyObject()
{
if (!_viewerPy) {
_viewerPy = new View3DInventorPy(this);
}
Py_INCREF(_viewerPy);
return _viewerPy;
}
void View3DInventor::applySettings()
{
viewSettings = std::make_unique<View3DSettings>(
App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/View"),
_viewer
);
naviSettings = std::make_unique<NaviCubeSettings>(
App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/NaviCube"),
_viewer
);
viewSettings->applySettings();
naviSettings->applySettings();
}
void View3DInventor::onRename(Gui::Document* pDoc)
{
SoSFString name;
name.setValue(pDoc->getDocument()->getName());
SoFCDocumentAction cAct(name);
cAct.apply(_viewer->getSceneGraph());
}
void View3DInventor::onUpdate()
{
#ifdef FC_LOGUPDATECHAIN
Base::Console().log("Acti: Gui::View3DInventor::onUpdate()");
#endif
update();
_viewer->redraw();
}
void View3DInventor::viewAll()
{
_viewer->viewAll();
}
const char* View3DInventor::getName() const
{
return "View3DInventor";
}
void View3DInventor::print()
{
QPrinter printer(QPrinter::ScreenResolution);
printer.setFullPage(true);
restorePrinterSettings(&printer);
QPrintDialog dlg(&printer, this);
if (dlg.exec() == QDialog::Accepted) {
Gui::WaitCursor wc;
print(&printer);
savePrinterSettings(&printer);
}
}
void View3DInventor::printPdf()
{
QString filename = FileDialog::getSaveFileName(
this,
tr("Export PDF"),
QString(),
QStringLiteral("%1 (*.pdf)").arg(tr("PDF file"))
);
if (!filename.isEmpty()) {
Gui::WaitCursor wc;
QPrinter printer(QPrinter::ScreenResolution);
// setPdfVersion sets the printed PDF Version to what is chosen in
// Preferences/Import-Export/PDF more details under:
// https://www.kdab.com/creating-pdfa-documents-qt/
printer.setPdfVersion(Gui::Dialog::DlgSettingsPDF::evaluatePDFVersion());
printer.setOutputFormat(QPrinter::PdfFormat);
printer.setPageOrientation(QPageLayout::Landscape);
printer.setOutputFileName(filename);
printer.setCreator(QString::fromStdString(App::Application::getNameWithVersion()));
print(&printer);
}
}
void View3DInventor::printPreview()
{
QPrinter printer(QPrinter::ScreenResolution);
printer.setFullPage(true);
restorePrinterSettings(&printer);
QPrintPreviewDialog dlg(&printer, this);
connect(
&dlg,
&QPrintPreviewDialog::paintRequested,
this,
qOverload<QPrinter*>(&View3DInventor::print)
);
dlg.exec();
savePrinterSettings(&printer);
}
void View3DInventor::print(QPrinter* printer)
{
QPainter p(printer);
p.setRenderHints(QPainter::Antialiasing);
if (!p.isActive() && !printer->outputFileName().isEmpty()) {
qApp->setOverrideCursor(Qt::ArrowCursor);
QMessageBox::critical(
this,
tr("Opening file failed"),
tr("Can't open file '%1' for writing.").arg(printer->outputFileName())
);
qApp->restoreOverrideCursor();
return;
}
QRect rect = printer->pageLayout().paintRectPixels(printer->resolution());
QImage img;
_viewer->imageFromFramebuffer(rect.width(), rect.height(), 8, QColor(255, 255, 255), img);
p.drawImage(0, 0, img);
p.end();
}
bool View3DInventor::containsViewProvider(const ViewProvider* vp) const
{
return _viewer->containsViewProvider(vp);
}
// **********************************************************************************
bool View3DInventor::onMsg(const char* pMsg, const char** ppReturn)
{
if (strcmp("ViewFit", pMsg) == 0) {
_viewer->viewAll();
return true;
}
else if (strcmp("ViewVR", pMsg) == 0) {
// call the VR portion of the viewer
_viewer->viewVR();
return true;
}
else if (strcmp("ViewSelection", pMsg) == 0) {
_viewer->viewSelection();
return true;
}
else if (strcmp("SetStereoRedGreen", pMsg) == 0) {
_viewer->setStereoMode(Quarter::SoQTQuarterAdaptor::ANAGLYPH);
return true;
}
else if (strcmp("SetStereoQuadBuff", pMsg) == 0) {
_viewer->setStereoMode(Quarter::SoQTQuarterAdaptor::QUAD_BUFFER);
return true;
}
else if (strcmp("SetStereoInterleavedRows", pMsg) == 0) {
_viewer->setStereoMode(Quarter::SoQTQuarterAdaptor::INTERLEAVED_ROWS);
return true;
}
else if (strcmp("SetStereoInterleavedColumns", pMsg) == 0) {
_viewer->setStereoMode(Quarter::SoQTQuarterAdaptor::INTERLEAVED_COLUMNS);
return true;
}
else if (strcmp("SetStereoOff", pMsg) == 0) {
_viewer->setStereoMode(Quarter::SoQTQuarterAdaptor::MONO);
return true;
}
else if (strcmp("GetCamera", pMsg) == 0) {
SoCamera* Cam = _viewer->getSoRenderManager()->getCamera();
if (!Cam) {
return false;
}
*ppReturn = SoFCDB::writeNodesToString(Cam).c_str();
return true;
}
else if (strncmp("SetCamera", pMsg, 9) == 0) {
return setCamera(pMsg + 10);
}
else if (strncmp("Dump", pMsg, 4) == 0) {
dump(pMsg + 5);
return true;
}
else if (strcmp("ViewBottom", pMsg) == 0) {
_viewer->setCameraOrientation(Camera::rotation(Camera::Bottom));
_viewer->viewAll();
return true;
}
else if (strcmp("ViewFront", pMsg) == 0) {
_viewer->setCameraOrientation(Camera::rotation(Camera::Front));
_viewer->viewAll();
return true;
}
else if (strcmp("ViewLeft", pMsg) == 0) {
_viewer->setCameraOrientation(Camera::rotation(Camera::Left));
_viewer->viewAll();
return true;
}
else if (strcmp("ViewRear", pMsg) == 0) {
_viewer->setCameraOrientation(Camera::rotation(Camera::Rear));
_viewer->viewAll();
return true;
}
else if (strcmp("ViewRight", pMsg) == 0) {
_viewer->setCameraOrientation(Camera::rotation(Camera::Right));
_viewer->viewAll();
return true;
}
else if (strcmp("ViewTop", pMsg) == 0) {
_viewer->setCameraOrientation(Camera::rotation(Camera::Top));
_viewer->viewAll();
return true;
}
else if (strcmp("ViewAxo", pMsg) == 0) {
_viewer->setCameraOrientation(Camera::rotation(Camera::Isometric));
_viewer->viewAll();
return true;
}
else if (strcmp("OrthographicCamera", pMsg) == 0) {
_viewer->setCameraType(SoOrthographicCamera::getClassTypeId());
return true;
}
else if (strcmp("PerspectiveCamera", pMsg) == 0) {
_viewer->setCameraType(SoPerspectiveCamera::getClassTypeId());
return true;
}
else if (strcmp("Undo", pMsg) == 0) {
getGuiDocument()->undo(1);
return true;
}
else if (strcmp("Redo", pMsg) == 0) {
getGuiDocument()->redo(1);
return true;
}
else if (strcmp("Save", pMsg) == 0) {
getGuiDocument()->save();
return true;
}
else if (strcmp("SaveAs", pMsg) == 0) {
getGuiDocument()->saveAs();
return true;
}
else if (strcmp("SaveCopy", pMsg) == 0) {
getGuiDocument()->saveCopy();
return true;
}
else if (strcmp("AlignToSelection", pMsg) == 0) {
_viewer->alignToSelection();
return true;
}
else if (strcmp("ZoomIn", pMsg) == 0) {
View3DInventorViewer* viewer = getViewer();
viewer->navigationStyle()->zoomIn();
return true;
}
else if (strcmp("ZoomOut", pMsg) == 0) {
View3DInventorViewer* viewer = getViewer();
viewer->navigationStyle()->zoomOut();
return true;
}
return false;
}
bool View3DInventor::onHasMsg(const char* pMsg) const
{
if (strcmp("CanPan", pMsg) == 0) {
return true;
}
else if (strcmp("Save", pMsg) == 0) {
return true;
}
else if (strcmp("SaveAs", pMsg) == 0) {
return true;
}
else if (strcmp("SaveCopy", pMsg) == 0) {
return true;
}
else if (strcmp("Undo", pMsg) == 0) {
App::Document* doc = getAppDocument();
return doc && doc->getAvailableUndos() > 0;
}
else if (strcmp("Redo", pMsg) == 0) {
App::Document* doc = getAppDocument();
return doc && doc->getAvailableRedos() > 0;
}
else if (strcmp("Print", pMsg) == 0) {
return true;
}
else if (strcmp("PrintPreview", pMsg) == 0) {
return true;
}
else if (strcmp("PrintPdf", pMsg) == 0) {
return true;
}
else if (strcmp("SetStereoRedGreen", pMsg) == 0) {
return true;
}
else if (strcmp("SetStereoQuadBuff", pMsg) == 0) {
return true;
}
else if (strcmp("SetStereoInterleavedRows", pMsg) == 0) {
return true;
}
else if (strcmp("SetStereoInterleavedColumns", pMsg) == 0) {
return true;
}
else if (strcmp("SetStereoOff", pMsg) == 0) {
return true;
}
else if (strcmp("ViewFit", pMsg) == 0) {
return true;
}
else if (strcmp("ViewVR", pMsg) == 0) {
#ifdef BUILD_VR
return true;
#else
return false;
#endif
}
else if (strcmp("ViewSelection", pMsg) == 0) {
return true;
}
else if (strcmp("ViewBottom", pMsg) == 0) {
return true;
}
else if (strcmp("ViewFront", pMsg) == 0) {
return true;
}
else if (strcmp("ViewLeft", pMsg) == 0) {
return true;
}
else if (strcmp("ViewRear", pMsg) == 0) {
return true;
}
else if (strcmp("ViewRight", pMsg) == 0) {
return true;
}
else if (strcmp("ViewTop", pMsg) == 0) {
return true;
}
else if (strcmp("ViewAxo", pMsg) == 0) {
return true;
}
else if (strcmp("GetCamera", pMsg) == 0) {
return true;
}
else if (strncmp("SetCamera", pMsg, 9) == 0) {
return true;
}
else if (strncmp("Dump", pMsg, 4) == 0) {
return true;
}
else if (strcmp("AlignToSelection", pMsg) == 0) {
return true;
}
if (strcmp("ZoomIn", pMsg) == 0) {
return true;
}
if (strcmp("ZoomOut", pMsg) == 0) {
return true;
}
if (strcmp("AllowsOverlayOnHover", pMsg) == 0) {
return true;
}
return false;
}
bool View3DInventor::setCamera(const char* pCamera)
{
SoCamera* CamViewer = _viewer->getSoRenderManager()->getCamera();
if (!CamViewer) {
throw Base::RuntimeError("No camera set so far…");
}
SoInput in;
in.setBuffer((void*)pCamera, std::strlen(pCamera));
SoNode* Cam;
SoDB::read(&in, Cam);
if (!Cam || !Cam->isOfType(SoCamera::getClassTypeId())) {
throw Base::RuntimeError("Camera settings failed to read");
}
// this is to make sure to reliably delete the node
CoinPtr<SoNode> camPtr(Cam, true);
// toggle between perspective and orthographic camera
if (Cam->getTypeId() != CamViewer->getTypeId()) {
_viewer->setCameraType(Cam->getTypeId());
CamViewer = _viewer->getSoRenderManager()->getCamera();
}
SoPerspectiveCamera* CamViewerP = nullptr;
SoOrthographicCamera* CamViewerO = nullptr;
if (CamViewer->getTypeId() == SoPerspectiveCamera::getClassTypeId()) {
CamViewerP = static_cast<SoPerspectiveCamera*>(CamViewer); // safe downward cast, knows the type
}
else if (CamViewer->getTypeId() == SoOrthographicCamera::getClassTypeId()) {
CamViewerO = static_cast<SoOrthographicCamera*>(CamViewer); // safe downward cast, knows
// the type
}
if (Cam->getTypeId() == SoPerspectiveCamera::getClassTypeId()) {
if (CamViewerP) {
CamViewerP->position = static_cast<SoPerspectiveCamera*>(Cam)->position;
CamViewerP->orientation = static_cast<SoPerspectiveCamera*>(Cam)->orientation;
CamViewerP->nearDistance = static_cast<SoPerspectiveCamera*>(Cam)->nearDistance;
CamViewerP->farDistance = static_cast<SoPerspectiveCamera*>(Cam)->farDistance;
CamViewerP->focalDistance = static_cast<SoPerspectiveCamera*>(Cam)->focalDistance;
}
else {
throw Base::TypeError("Camera type mismatch");
}
}
else if (Cam->getTypeId() == SoOrthographicCamera::getClassTypeId()) {
if (CamViewerO) {
CamViewerO->viewportMapping = static_cast<SoOrthographicCamera*>(Cam)->viewportMapping;
CamViewerO->position = static_cast<SoOrthographicCamera*>(Cam)->position;
CamViewerO->orientation = static_cast<SoOrthographicCamera*>(Cam)->orientation;
CamViewerO->nearDistance = static_cast<SoOrthographicCamera*>(Cam)->nearDistance;
CamViewerO->farDistance = static_cast<SoOrthographicCamera*>(Cam)->farDistance;
CamViewerO->focalDistance = static_cast<SoOrthographicCamera*>(Cam)->focalDistance;
CamViewerO->aspectRatio = static_cast<SoOrthographicCamera*>(Cam)->aspectRatio;
CamViewerO->height = static_cast<SoOrthographicCamera*>(Cam)->height;
}
else {
throw Base::TypeError("Camera type mismatch");
}
}
return true;
}
void View3DInventor::toggleClippingPlane()
{
_viewer->toggleClippingPlane();
}
bool View3DInventor::hasClippingPlane() const
{
return _viewer->hasClippingPlane();
}
void View3DInventor::setOverlayWidget(QWidget* widget)
{
removeOverlayWidget();
stack->addWidget(widget);
stack->setCurrentIndex(1);
}
void View3DInventor::removeOverlayWidget()
{
stack->setCurrentIndex(0);
QWidget* overlay = stack->widget(1);
if (overlay) {
stack->removeWidget(overlay);
}
}
void View3DInventor::setOverrideCursor(const QCursor& aCursor)
{
_viewer->getWidget()->setCursor(aCursor);
}
void View3DInventor::restoreOverrideCursor()
{
_viewer->getWidget()->setCursor(QCursor(Qt::ArrowCursor));
}
// defined in SoFCDB.cpp
extern SoNode* replaceSwitchesInSceneGraph(SoNode*);
void View3DInventor::dump(const char* filename, bool onlyVisible)
{
SoGetPrimitiveCountAction action;
action.setCanApproximate(true);
action.apply(_viewer->getSceneGraph());
SoNode* node = _viewer->getSceneGraph();
if (onlyVisible) {
node = replaceSwitchesInSceneGraph(node);
node->ref();
}
if (action.getTriangleCount() > 100000 || action.getPointCount() > 30000
|| action.getLineCount() > 10000) {
_viewer->dumpToFile(node, filename, true);
}
else {
_viewer->dumpToFile(node, filename, false);
}
if (onlyVisible) {
node->unref();
}
}
void View3DInventor::windowStateChanged(QWidget* view)
{
bool canStartTimer = false;
if (this != view) {
// If both views are child widgets of the workspace and view is maximized this view
// must be hidden, hence we can start the timer.
// Note: If view is top-level or fullscreen it doesn't necessarily hide the other view
// e.g. if it is on a second monitor.
canStartTimer = (!this->isWindow() && !view->isWindow() && view->isMaximized());
}
else if (isMinimized()) {
// I am the active view but minimized
canStartTimer = true;
}
if (canStartTimer) {
int msecs = viewSettings->stopAnimatingIfDeactivated();
if (!stopSpinTimer->isActive() && msecs >= 0) { // if < 0 do not stop rotation
stopSpinTimer->setSingleShot(true);
stopSpinTimer->start(msecs);
}
}
else if (stopSpinTimer->isActive()) {
// If this view may be visible again we can stop the timer
stopSpinTimer->stop();
}
}
void View3DInventor::stopAnimating()
{
_viewer->stopAnimating();
}
/**
* Drops the event \a e and writes the right Python command.
*/
void View3DInventor::dropEvent(QDropEvent* e)
{
const QMimeData* data = e->mimeData();
if (data->hasUrls()) {
getMainWindow()->loadUrls(getAppDocument(), data->urls());
}
else {
MDIView::dropEvent(e);
}
}
void View3DInventor::dragEnterEvent(QDragEnterEvent* e)
{
// Here we must allow uri drags and check them in dropEvent
const QMimeData* data = e->mimeData();
if (data->hasUrls()) {
e->accept();
}
else {
e->ignore();
}
}
void View3DInventor::setCurrentViewMode(ViewMode mode)
{
ViewMode oldmode = currentViewMode();
if (mode == oldmode) {
return;
}
if (mode == Child) {
// Fix in two steps:
// The mdi view got a QWindow when it became a top-level widget and when resetting it to a
// child widget the QWindow must be deleted because it has an impact on resize events and
// may break the layout of mdi view inside the QMdiSubWindow. In the second step below the
// layout must be invalidated after it's again a child widget to make sure the mdi view fits
// into the QMdiSubWindow.
QWindow* winHandle = this->windowHandle();
if (winHandle) {
winHandle->destroy();
}
}
MDIView::setCurrentViewMode(mode);
// This widget becomes the focus proxy of the embedded GL widget if we leave
// the 'Child' mode. If we reenter 'Child' mode the focus proxy is reset to 0.
// If we change from 'TopLevel' mode to 'Fullscreen' mode or vice versa nothing
// happens.
// Grabbing keyboard when leaving 'Child' mode (as done in a recent version) should
// be avoided because when two or more windows are either in 'TopLevel' or 'Fullscreen'
// mode only the last window gets all key event even if it is not the active one.
//
// It is important to set the focus proxy to get all key events otherwise we would lose
// control after redirecting the first key event to the GL widget.
if (oldmode == Child) {
_viewer->getGLWidget()->setFocusProxy(this);
}
else if (mode == Child) {
_viewer->getGLWidget()->setFocusProxy(nullptr);
// Step two
auto mdi = qobject_cast<QMdiSubWindow*>(parentWidget());
if (mdi && mdi->layout()) {
mdi->layout()->invalidate();
}
}
}
RayPickInfo View3DInventor::getObjInfoRay(Base::Vector3d* startvec, Base::Vector3d* dirvec)
{
double vsx, vsy, vsz;
double vdx, vdy, vdz;
vsx = startvec->x;
vsy = startvec->y;
vsz = startvec->z;
vdx = dirvec->x;
vdy = dirvec->y;
vdz = dirvec->z;
// near plane clipping is required to avoid false intersections
float nearClippingPlane = 0.1F;
RayPickInfo ret = {false, Base::Vector3d(), "", "", std::nullopt, std::nullopt, std::nullopt};
SoRayPickAction action(getViewer()->getSoRenderManager()->getViewportRegion());
action.setRay(SbVec3f(vsx, vsy, vsz), SbVec3f(vdx, vdy, vdz), nearClippingPlane);
action.apply(getViewer()->getSoRenderManager()->getSceneGraph());
SoPickedPoint* Point = action.getPickedPoint();
if (!Point) {
return ret;
}
ret.point = Base::convertTo<Base::Vector3d>(Point->getPoint());
ViewProvider* vp = getViewer()->getViewProviderByPath(Point->getPath());
if (vp && vp->isDerivedFrom<ViewProviderDocumentObject>()) {
if (!vp->isSelectable()) {
return ret;
}
auto vpd = static_cast<ViewProviderDocumentObject*>(vp);
if (vp->useNewSelectionModel()) {
std::string subname;
if (!vp->getElementPicked(Point, subname)) {
return ret;
}
auto obj = vpd->getObject();
if (!obj) {
return ret;
}
if (!subname.empty()) {
App::ElementNamePair elementName;
auto sobj = App::GeoFeature::resolveElement(obj, subname.c_str(), elementName);
if (!sobj) {
return ret;
}
if (sobj != obj) {
ret.parentObject = obj->getExportName();
ret.subName = subname;
obj = sobj;
}
subname = !elementName.oldName.empty() ? elementName.oldName : elementName.newName;
}
ret.document = obj->getDocument()->getName();
ret.object = obj->getNameInDocument();
ret.component = subname;
ret.isValid = true;
}
else {
ret.document = vpd->getObject()->getDocument()->getName();
ret.object = vpd->getObject()->getNameInDocument();
// search for a SoFCSelection node
SoFCDocumentObjectAction objaction;
objaction.apply(Point->getPath());
if (objaction.isHandled()) {
ret.component = objaction.componentName.getString();
}
}
// ok, found the node of interest
ret.isValid = true;
}
else {
// custom nodes not in a VP: search for a SoFCSelection node
SoFCDocumentObjectAction objaction;
objaction.apply(Point->getPath());
if (objaction.isHandled()) {
ret.document = objaction.documentName.getString();
ret.object = objaction.objectName.getString();
ret.component = objaction.componentName.getString();
// ok, found the node of interest
ret.isValid = true;
}
}
return ret;
}
void View3DInventor::keyPressEvent(QKeyEvent* e)
{
// See StdViewDockUndockFullscreen::activated()
// With Qt5 one cannot directly use 'setCurrentViewMode'
// of an MDI view because it causes rendering problems.
// The only reliable solution is to clone the MDI view,
// set its view mode and close the original MDI view.
QMainWindow::keyPressEvent(e);
}
void View3DInventor::keyReleaseEvent(QKeyEvent* e)
{
QMainWindow::keyReleaseEvent(e);
}
void View3DInventor::focusInEvent(QFocusEvent*)
{
_viewer->getGLWidget()->setFocus();
}
void View3DInventor::contextMenuEvent(QContextMenuEvent* e)
{
MDIView::contextMenuEvent(e);
}
bool View3DInventor::eventFilter(QObject* obj, QEvent* ev)
{
if (obj == _viewer->getWidget() && ev->type() == QEvent::Resize) {
// Keep breadcrumb at top-left after viewport resize
if (_breadcrumb) {
_breadcrumb->raise();
}
}
return MDIView::eventFilter(obj, ev);
}
void View3DInventor::customEvent(QEvent* e)
{
if (e->type() == QEvent::User) {
auto se = static_cast<NavigationStyleEvent*>(e);
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
"User parameter:BaseApp/Preferences/View"
);
if (hGrp->GetBool("SameStyleForAllViews", true)) {
hGrp->SetASCII("NavigationStyle", se->style().getName());
}
else {
_viewer->setNavigationType(se->style());
}
}
}
#include "moc_View3DInventor.cpp"