diff --git a/src/Gui/CommandView.cpp b/src/Gui/CommandView.cpp index 26eb5434fc..30dfdb0911 100644 --- a/src/Gui/CommandView.cpp +++ b/src/Gui/CommandView.cpp @@ -1682,7 +1682,7 @@ void StdViewDock::activated(int iMsg) bool StdViewDock::isActive() { MDIView* view = getMainWindow()->activeWindow(); - return (qobject_cast(view) ? true : false); + return view != nullptr; } //=========================================================================== @@ -1711,7 +1711,7 @@ void StdViewUndock::activated(int iMsg) bool StdViewUndock::isActive() { MDIView* view = getMainWindow()->activeWindow(); - return (qobject_cast(view) ? true : false); + return view != nullptr; } //=========================================================================== @@ -1773,7 +1773,7 @@ void StdViewFullscreen::activated(int iMsg) bool StdViewFullscreen::isActive() { MDIView* view = getMainWindow()->activeWindow(); - return (qobject_cast(view) ? true : false); + return view != nullptr; } //=========================================================================== @@ -1825,67 +1825,61 @@ void StdViewDockUndockFullscreen::activated(int iMsg) if (!view) // no active view return; - // nothing to do when the view is docked and 'Docked' is pressed - if (iMsg == 0 && view->currentViewMode() == MDIView::Child) + const auto oldmode = view->currentViewMode(); + auto mode = (MDIView::ViewMode)iMsg; + + // Pressing the same button again toggles the view back to docked. + if (mode == oldmode) { + mode = MDIView::Child; + } + + if (mode == oldmode) { return; + } + // Change the view mode after an mdi view was already visible doesn't // work well with Qt5 any more because of some strange OpenGL behaviour. // A workaround is to clone the mdi view, set its view mode and delete // the original view. - Gui::Document* doc = Gui::Application::Instance->activeDocument(); - if (doc) { - Gui::MDIView* clone = doc->cloneView(view); - if (!clone) - return; - const char* ppReturn = nullptr; - if (view->onMsg("GetCamera", &ppReturn)) { - std::string sMsg = "SetCamera "; - sMsg += ppReturn; + bool needsClone = mode == MDIView::Child || oldmode == MDIView::Child; + Gui::MDIView* clone = needsClone ? view->clone() : nullptr; - const char** pReturnIgnore=nullptr; - clone->onMsg(sMsg.c_str(), pReturnIgnore); - } - - if (iMsg==0) { + if (clone) { + if (mode == MDIView::Child) { getMainWindow()->addWindow(clone); } - else if (iMsg==1) { - if (view->currentViewMode() == MDIView::TopLevel) - getMainWindow()->addWindow(clone); - else - clone->setCurrentViewMode(MDIView::TopLevel); - } - else if (iMsg==2) { - if (view->currentViewMode() == MDIView::FullScreen) - getMainWindow()->addWindow(clone); - else - clone->setCurrentViewMode(MDIView::FullScreen); + else { + clone->setCurrentViewMode(mode); } + // destroy the old view view->deleteSelf(); } + else { + // no clone needed, simply change the view mode + view->setCurrentViewMode(mode); + } } bool StdViewDockUndockFullscreen::isActive() { MDIView* view = getMainWindow()->activeWindow(); - if (qobject_cast(view)) { - // update the action group if needed - auto pActGrp = qobject_cast(_pcAction); - if (pActGrp) { - int index = pActGrp->checkedAction(); - int mode = (int)(view->currentViewMode()); - if (index != mode) { - // active window has changed with another view mode - pActGrp->setCheckedAction(mode); - } - } + if (!view) + return false; - return true; + // update the action group if needed + auto pActGrp = qobject_cast(_pcAction); + if (pActGrp) { + int index = pActGrp->checkedAction(); + int mode = (int)(view->currentViewMode()); + if (index != mode) { + // active window has changed with another view mode + pActGrp->setCheckedAction(mode); + } } - return false; + return true; } diff --git a/src/Gui/Document.cpp b/src/Gui/Document.cpp index 50d97b3b17..8fe1118b88 100644 --- a/src/Gui/Document.cpp +++ b/src/Gui/Document.cpp @@ -2016,7 +2016,7 @@ void Document::addRootObjectsToGroup(const std::vector& ob } } -MDIView *Document::createView(const Base::Type& typeId) +MDIView* Document::createView(const Base::Type& typeId, CreateViewMode mode) { if (!typeId.isDerivedFrom(MDIView::getClassTypeId())) return nullptr; @@ -2062,11 +2062,16 @@ MDIView *Document::createView(const Base::Type& typeId) for (App::DocumentObject* obj : child_vps) view3D->getViewer()->removeViewProvider(getViewProvider(obj)); - const char* name = getDocument()->Label.getValue(); - QString title = QStringLiteral("%1 : %2[*]") - .arg(QString::fromUtf8(name)).arg(d->_iWinCount++); + // When cloning the view, don't increment the window counter as the old view will be deleted + // shortly after. + if (mode != CreateViewMode::Clone) { + const char* name = getDocument()->Label.getValue(); + QString title = + QStringLiteral("%1 : %2[*]").arg(QString::fromUtf8(name)).arg(d->_iWinCount++); + + view3D->setWindowTitle(title); + } - view3D->setWindowTitle(title); view3D->setWindowModified(this->isModified()); view3D->resize(400, 300); @@ -2075,65 +2080,19 @@ MDIView *Document::createView(const Base::Type& typeId) view3D->onMsg(cameraSettings.c_str(),&ppReturn); } - getMainWindow()->addWindow(view3D); + // When cloning the view, don't add the view to the main window. The whole purpose of the + // workaround using cloned views is that the view can be shown in undocked/fullscreen mode + // without having been docked before. + if (mode != CreateViewMode::Clone) { + getMainWindow()->addWindow(view3D); + } + view3D->getViewer()->redraw(); return view3D; } return nullptr; } -Gui::MDIView* Document::cloneView(Gui::MDIView* oldview) -{ - if (!oldview) - return nullptr; - - if (oldview->is()) { - auto view3D = new View3DInventor(this, getMainWindow()); - - auto firstView = static_cast(oldview); - std::string overrideMode = firstView->getViewer()->getOverrideMode(); - view3D->getViewer()->setOverrideMode(overrideMode); - - view3D->getViewer()->setAxisCross(firstView->getViewer()->hasAxisCross()); - - // attach the viewproviders. we need to make sure that we only attach the toplevel ones - // and not viewproviders which are claimed by other providers. To ensure this we first - // add all providers and then remove the ones already claimed - std::map::const_iterator It1; - std::vector child_vps; - for (It1=d->_ViewProviderMap.begin();It1!=d->_ViewProviderMap.end();++It1) { - view3D->getViewer()->addViewProvider(It1->second); - std::vector children = It1->second->claimChildren3D(); - child_vps.insert(child_vps.end(), children.begin(), children.end()); - } - std::map::const_iterator It2; - for (It2=d->_ViewProviderMapAnnotation.begin();It2!=d->_ViewProviderMapAnnotation.end();++It2) { - view3D->getViewer()->addViewProvider(It2->second); - std::vector children = It2->second->claimChildren3D(); - child_vps.insert(child_vps.end(), children.begin(), children.end()); - } - - for (App::DocumentObject* obj : child_vps) - view3D->getViewer()->removeViewProvider(getViewProvider(obj)); - - view3D->setWindowTitle(oldview->windowTitle()); - view3D->setWindowModified(oldview->isWindowModified()); - view3D->setWindowIcon(oldview->windowIcon()); - view3D->resize(oldview->size()); - - // FIXME: Add parameter to define behaviour by the calling instance - // View provider editing - if (d->_editViewProvider) { - firstView->getViewer()->resetEditingViewProvider(); - view3D->getViewer()->setEditingViewProvider(d->_editViewProvider, d->_editMode); - } - - return view3D; - } - - return nullptr; -} - const char *Document::getCameraSettings() const { return cameraSettings.size()>10?cameraSettings.c_str()+10:cameraSettings.c_str(); } diff --git a/src/Gui/Document.h b/src/Gui/Document.h index ae27120a6b..d1d186796a 100644 --- a/src/Gui/Document.h +++ b/src/Gui/Document.h @@ -58,6 +58,12 @@ class Application; class DocumentPy; class TransactionViewProvider; +enum class CreateViewMode +{ + Normal, + Clone +}; + /** The Gui Document * This is the document on GUI level. Its main responsibility is keeping * track off open windows for a document and warning on unsaved closes. @@ -186,9 +192,7 @@ public: Gui::MDIView* getViewOfViewProvider(const Gui::ViewProvider*) const; Gui::MDIView* getViewOfNode(SoNode*) const; /// Create a new view - MDIView *createView(const Base::Type& typeId); - /// Create a clone of the given view - Gui::MDIView* cloneView(Gui::MDIView*); + MDIView* createView(const Base::Type& typeId, CreateViewMode mode = CreateViewMode::Normal); /** send messages to the active view * Send a specific massage to the active view and is able to receive a * return message diff --git a/src/Gui/MDIView.cpp b/src/Gui/MDIView.cpp index 253e8bb9b2..cad506c5d3 100644 --- a/src/Gui/MDIView.cpp +++ b/src/Gui/MDIView.cpp @@ -25,6 +25,7 @@ #ifndef _PreComp_ # include # include +# include # include # include # include @@ -129,6 +130,29 @@ void MDIView::deleteSelf() _pcDocument = nullptr; } +MDIView* MDIView::clone() +{ + return nullptr; +} + +void MDIView::cloneFrom(const MDIView& from) +{ + setWindowTitle(from.windowTitle()); + setWindowIcon(from.windowIcon()); + resize(from.size()); + + // wstate is updated when changing from top-level mode to something else. This hasn't happened + // yet if the original widget is currently in top-level mode. In this case we want to use the + // actual windowState of the original widget instead of it's wstate. + + if (from.currentViewMode() == TopLevel) { + wstate = from.windowState(); + } + else { + wstate = from.wstate; + } +} + PyObject* MDIView::getPyObject() { if (!pythonObject) @@ -207,7 +231,7 @@ bool MDIView::canClose() return true; } -void MDIView::closeEvent(QCloseEvent *e) +void MDIView::closeEvent(QCloseEvent* e) { if (canClose()) { e->accept(); @@ -353,15 +377,15 @@ QSize MDIView::minimumSizeHint () const return {400, 300}; } -void MDIView::changeEvent(QEvent *e) + +void MDIView::changeEvent(QEvent* e) { switch (e->type()) { case QEvent::ActivationChange: { // Forces this top-level window to be the active view of the main window if (isActiveWindow()) { - if (getMainWindow()->activeWindow() != this) - getMainWindow()->setActiveWindow(this); + getMainWindow()->setActiveWindow(this); } } break; case QEvent::WindowTitleChange: @@ -377,6 +401,29 @@ void MDIView::changeEvent(QEvent *e) } } +bool MDIView::eventFilter(QObject* watched, QEvent* event) +{ + // As long as this widget is a top-level window (either in 'TopLevel' or 'FullScreen' mode) we + // need to be notified when an action is added to a widget. This action must also be added to + // this window to allow one to make use of its shortcut (if defined). + // Note: We don't need to care about removing an action if its parent widget gets destroyed. + // This does the action itself for us. + + if (watched != this && event->type() == QEvent::ActionAdded) { + auto actionEvent = static_cast(event); + QAction* action = actionEvent->action(); + + if (!action->isSeparator()) { + QList acts = actions(); + if (!acts.contains(action)) { + addAction(action); + } + } + } + + return false; +} + #if defined(Q_WS_X11) // To fix bug #0000345 move function declaration to here extern void qt_x11_wait_for_window_manager( QWidget* w ); // defined in qwidget_x11.cpp @@ -384,74 +431,86 @@ extern void qt_x11_wait_for_window_manager( QWidget* w ); // defined in qwidget_ void MDIView::setCurrentViewMode(ViewMode mode) { + const ViewMode oldmode = MDIView::currentViewMode(); + if (oldmode == mode) { + return; + } + + if (oldmode == Child) { + // remove window from MDIArea + if (qobject_cast(parentWidget())) { + getMainWindow()->removeWindow(this, false); + setParent(nullptr); + } + } + else if (oldmode == TopLevel) { + // backup maximize state for top-level mode + wstate = windowState(); + } + switch (mode) { // go to normal mode case Child: - { - if (this->currentMode == FullScreen) { - showNormal(); - setWindowFlags(windowFlags() & ~Qt::Window); - } - else if (this->currentMode == TopLevel) { - this->wstate = windowState(); - setWindowFlags( windowFlags() & ~Qt::Window ); - } + getMainWindow()->addWindow(this); + break; - if (this->currentMode != Child) { - this->currentMode = Child; - getMainWindow()->addWindow(this); - getMainWindow()->activateWindow(); - update(); - } - } break; // go to top-level mode case TopLevel: - { - if (this->currentMode == Child) { - if (qobject_cast(this->parentWidget())) - getMainWindow()->removeWindow(this,false); - setWindowFlags(windowFlags() | Qt::Window); - setParent(nullptr, Qt::Window | Qt::WindowTitleHint | Qt::WindowSystemMenuHint | - Qt::WindowMinMaxButtonsHint); - if (this->wstate & Qt::WindowMaximized) - showMaximized(); - else - showNormal(); + if (wstate & Qt::WindowMaximized) { + // Only calling showMaximized doesn't work when the widget is currently in + // full-screen mode. We need to exit full-screen mode first or the widget will end + // up in normal mode. Same if the window is in child mode but maximized. + setWindowState(windowState() & ~(Qt::WindowMaximized | Qt::WindowFullScreen)); + showMaximized(); + } + else { + showNormal(); + } + break; + + // go to full-screen mode + case FullScreen: + showFullScreen(); + break; + } + + currentMode = mode; #if defined(Q_WS_X11) - //extern void qt_x11_wait_for_window_manager( QWidget* w ); // defined in qwidget_x11.cpp - qt_x11_wait_for_window_manager(this); + if (mode == TopLevel && oldmode == Child) { + // extern void qt_x11_wait_for_window_manager( QWidget* w ); // defined in + // qwidget_x11.cpp + qt_x11_wait_for_window_manager(this); + } #endif - activateWindow(); - } - else if (this->currentMode == FullScreen) { - if (this->wstate & Qt::WindowMaximized) - showMaximized(); - else - showNormal(); - } - this->currentMode = TopLevel; - update(); - } break; - // go to fullscreen mode - case FullScreen: - { - if (this->currentMode == Child) { - if (qobject_cast(this->parentWidget())) - getMainWindow()->removeWindow(this,false); - setWindowFlags(windowFlags() | Qt::Window); - setParent(nullptr, Qt::Window); - showFullScreen(); - } - else if (this->currentMode == TopLevel) { - this->wstate = windowState(); - showFullScreen(); - } + activateWindow(); - this->currentMode = FullScreen; - update(); - } break; + if (oldmode == Child) { + // To make a global shortcut working from this window we need to add + // all existing actions from the mainwindow and its sub-widgets + + QList acts = getMainWindow()->findChildren(); + addActions(acts); + + // To be notfified for new actions + qApp->installEventFilter(this); + } + else if (mode == Child) { + qApp->removeEventFilter(this); + QList acts = actions(); + for (QAction* it : acts) { + removeAction(it); + } + + // When switching from undocked to docked mode, the widget position is somehow not updated + // correctly. In this case mapToGlobal(Point()) returns {0, 0} even though the widget is + // clearly not at the top-left corner of the screen. We fix this by briefly changing the + // maximum size of the widget. + + const auto oldsize = maximumSize(); + setMaximumSize({1, 1}); + setMaximumSize(oldsize); } } diff --git a/src/Gui/MDIView.h b/src/Gui/MDIView.h index 64c92ee4d8..5511ee0421 100644 --- a/src/Gui/MDIView.h +++ b/src/Gui/MDIView.h @@ -70,6 +70,8 @@ public: */ ~MDIView() override; + virtual MDIView* clone(); + /// get called when the document is updated void onRelabel(Gui::Document *pDoc) override; virtual void viewAll(); @@ -177,9 +179,13 @@ protected Q_SLOTS: virtual void windowStateChanged(QWidget*); protected: - void closeEvent(QCloseEvent *e) override; + void closeEvent(QCloseEvent* e) override; /** \internal */ - void changeEvent(QEvent *e) override; + void changeEvent(QEvent* e) override; + + bool eventFilter(QObject* watched, QEvent* e) override; + + void cloneFrom(const MDIView& from); protected: PyObject* pythonObject; diff --git a/src/Gui/MainWindow.cpp b/src/Gui/MainWindow.cpp index 20d1ba7c98..7b166eaffb 100644 --- a/src/Gui/MainWindow.cpp +++ b/src/Gui/MainWindow.cpp @@ -447,12 +447,10 @@ MainWindow::MainWindow(QWidget * parent, Qt::WindowFlags f) // connection between workspace, window menu and tab bar #if QT_VERSION < QT_VERSION_CHECK(6,0,0) - connect(d->windowMapper, &QSignalMapper::mappedWidget, - this, &MainWindow::onSetActiveSubWindow); + connect(d->windowMapper, &QSignalMapper::mappedWidget, this, &MainWindow::setActiveSubWindow); #else - connect(d->windowMapper, &QSignalMapper::mappedObject, - this, [=, this](QObject* object) { - onSetActiveSubWindow(qobject_cast(object)); + connect(d->windowMapper, &QSignalMapper::mappedObject, this, [=, this](QObject* object) { + setActiveSubWindow(qobject_cast(object)); }); #endif connect(d->mdiArea, &QMdiArea::subWindowActivated, @@ -960,15 +958,16 @@ void MainWindow::activatePreviousWindow () void MainWindow::activateWorkbench(const QString& name) { - ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/View"); - bool saveWB = hGrp->GetBool("SaveWBbyTab", false); - QMdiSubWindow* subWin = d->mdiArea->activeSubWindow(); - if (subWin && saveWB) { - QString currWb = subWin->property("ownWB").toString(); - if (currWb.isEmpty() || currWb != name) { - subWin->setProperty("ownWB", name); - } + // remember workbench by tab (if enabled) + + const ParameterGrp::handle hGrp = + App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/View"); + const bool saveWB = hGrp->GetBool("SaveWBbyTab", false); + MDIView* view = activeWindow(); + if (view && saveWB) { + view->setProperty("ownWB", name); } + // emit this signal Q_EMIT workbenchActivated(name); updateActions(true); @@ -1157,6 +1156,7 @@ bool MainWindow::eventFilter(QObject* o, QEvent* e) void MainWindow::addWindow(MDIView* view) { // make workspace parent of view + bool isempty = d->mdiArea->subWindowList().isEmpty(); auto child = qobject_cast(view->parentWidget()); if(!child) { @@ -1186,7 +1186,9 @@ void MainWindow::addWindow(MDIView* view) // listen to the incoming events of the view view->installEventFilter(this); - // show the very first window in maximized mode + // Show the new window. This will also call onWindowActivated. The very first window is shown in + // maximized mode. + if (isempty) view->showMaximized(); else @@ -1194,13 +1196,18 @@ void MainWindow::addWindow(MDIView* view) } /** - * Removes the instance of Gui::MDiView from the main window and sends am event + * Removes the instance of Gui::MDIView from the main window and sends n event * to the parent widget, a QMdiSubWindow to delete itself. * If you want to avoid that the Gui::MDIView instance gets destructed too you * must reparent it afterwards, e.g. set parent to NULL. */ void MainWindow::removeWindow(Gui::MDIView* view, bool close) { + if (view->currentViewMode() != MDIView::Child) { + FC_WARN("tried to remove an MDIView that is not currently in child mode"); + return; + } + // free all connections disconnect(view, &MDIView::message, this, &MainWindow::showMessage); disconnect(this, &MainWindow::windowStateChanged, view, &MDIView::windowStateChanged); @@ -1234,6 +1241,7 @@ void MainWindow::removeWindow(Gui::MDIView* view, bool close) auto subwindow = qobject_cast(parent); if(subwindow && d->mdiArea->subWindowList().contains(subwindow)) { subwindow->setParent(nullptr); + subwindow->deleteLater(); assert(!d->mdiArea->subWindowList().contains(subwindow)); } @@ -1264,21 +1272,68 @@ void MainWindow::tabCloseRequested(int index) updateActions(); } -void MainWindow::onSetActiveSubWindow(QWidget *window) +void MainWindow::setActiveSubWindow(QWidget* window) { - if (!window) + auto mdi = qobject_cast(window); + if (!mdi) { return; - d->mdiArea->setActiveSubWindow(qobject_cast(window)); - updateActions(); + } + + auto view = qobject_cast(mdi->widget()); + setActiveWindow(view); } void MainWindow::setActiveWindow(MDIView* view) { - if (!view || d->activeView == view) + if (!view) { return; - onSetActiveSubWindow(view->parentWidget()); + } + + // always update the focus and active sub window + + // We need the explicit call to setFocus because it seems the focus window and the + // activeSubWindow in the QMdiView can diverge when calling setActiveWindow while the MainWindow + // is not currently active. In this case Qt will later set the previous focus window as active, + // which will call onWindowActivated and activate the wrong window. This e.g. happens when + // switching from a 3d view to a spreadsheet using the "Windows..." dialog or when docking a + // spreadsheet that was in top-level/fullscreen mode. Why this could only be reproduced with a + // spreadsheet remains a mystery. + + view->setFocus(); + + auto subwindow = qobject_cast(view->parentWidget()); + if (subwindow) { + d->mdiArea->setActiveSubWindow(subwindow); + } + + // if active view changed, notify rest of the application + + if (view == d->activeView) { + return; + } + d->activeView = view; Application::Instance->viewActivated(view); + + // activate/remember workbench by tab (if enabled) + + const ParameterGrp::handle hGrp = + App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/View"); + const bool saveWB = hGrp->GetBool("SaveWBbyTab", false); + if (saveWB) { + const QString currWb = view->property("ownWB").toString(); + if (!currWb.isEmpty()) { + this->activateWorkbench(currWb); + } + else { + const std::string name = WorkbenchManager::instance()->active()->name(); + view->setProperty("ownWB", QString::fromStdString(name)); + } + } + + // update actions + + updateActions(); } void MainWindow::onWindowActivated(QMdiSubWindow* mdi) @@ -1289,41 +1344,11 @@ void MainWindow::onWindowActivated(QMdiSubWindow* mdi) return; } + // set active the appropriate window (it needs not to be part of mdiIds, e.g. directly after + // creation) + auto view = dynamic_cast(mdi->widget()); - - // set active the appropriate window (it needs not to be part of mdiIds, e.g. directly after creation) - if (view) - { - d->activeView = view; - Application::Instance->viewActivated(view); - } - - ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/View"); - bool saveWB = hGrp->GetBool("SaveWBbyTab", false); - if (saveWB) { - QString currWb = mdi->property("ownWB").toString(); - if (! currWb.isEmpty()) { - this->activateWorkbench(currWb); - } - else { - mdi->setProperty("ownWB", QString::fromStdString(WorkbenchManager::instance()->active()->name())); - } - } - - // Even if windowActivated() signal is emitted mdi doesn't need to be a top-level window. - // This happens e.g. if two windows are top-level and one of them gets docked again. - // QWorkspace emits the signal then even though the other window is in front. - // The consequence is that the docked window becomes the active window and not the undocked - // window on top. This means that all accel events, menu and toolbar actions get redirected - // to the (wrong) docked window. - // But just testing whether the view is active and ignore it if not leads to other more serious problems - - // at least under Linux. It seems to be a problem with the window manager. - // Under Windows it seems to work though it's not really sure that it works reliably. - // Result: So, we accept the first problem to be sure to avoid the second one. - if ( !view /*|| !mdi->isActiveWindow()*/ ) - return; // either no MDIView or no valid object or no top-level window - - updateActions(true); + setActiveWindow(view); } void MainWindow::onWindowsMenuAboutToShow() @@ -2179,13 +2204,7 @@ void MainWindow::changeEvent(QEvent *e) else if (e->type() == QEvent::ActivationChange) { if (isActiveWindow()) { QMdiSubWindow* mdi = d->mdiArea->currentSubWindow(); - if (mdi) { - auto view = dynamic_cast(mdi->widget()); - if (view && getMainWindow()->activeWindow() != view) { - d->activeView = view; - Application::Instance->viewActivated(view); - } - } + setActiveSubWindow(mdi); } } else { diff --git a/src/Gui/MainWindow.h b/src/Gui/MainWindow.h index f6ea2791f1..fbe998526b 100644 --- a/src/Gui/MainWindow.h +++ b/src/Gui/MainWindow.h @@ -318,7 +318,7 @@ private Q_SLOTS: /** * \internal */ - void onSetActiveSubWindow(QWidget *window); + void setActiveSubWindow(QWidget*); /** * Activates the associated tab to this widget. */ diff --git a/src/Gui/PreferencePages/DlgSettingsGeneral.cpp b/src/Gui/PreferencePages/DlgSettingsGeneral.cpp index 12cda33bfb..c1da64fbaa 100644 --- a/src/Gui/PreferencePages/DlgSettingsGeneral.cpp +++ b/src/Gui/PreferencePages/DlgSettingsGeneral.cpp @@ -91,7 +91,7 @@ DlgSettingsGeneral::DlgSettingsGeneral( QWidget* parent ) ui->themesCombobox->setEnabled(true); Gui::Document* doc = Gui::Application::Instance->activeDocument(); if (doc) { - Gui::View3DInventor* view = static_cast(doc->getActiveView()); + Gui::View3DInventor* view = qobject_cast(doc->getActiveView()); if (view) { Gui::View3DInventorViewer* viewer = view->getViewer(); if (viewer->isEditing()) { @@ -639,7 +639,7 @@ void DlgSettingsGeneral::recreatePreferencePackMenu() button->setEnabled(true); Gui::Document* doc = Gui::Application::Instance->activeDocument(); if (doc) { - Gui::View3DInventor* view = static_cast(doc->getActiveView()); + Gui::View3DInventor* view = qobject_cast(doc->getActiveView()); if (view) { Gui::View3DInventorViewer* viewer = view->getViewer(); if (viewer->isEditing()) { diff --git a/src/Gui/TextureMapping.cpp b/src/Gui/TextureMapping.cpp index 72a5e565dd..ecede365c4 100644 --- a/src/Gui/TextureMapping.cpp +++ b/src/Gui/TextureMapping.cpp @@ -142,14 +142,13 @@ void TextureMapping::onFileChooserFileNameSelected(const QString& s) if (!this->grp) { Gui::Document* doc = Gui::Application::Instance->activeDocument(); if (doc) { - Gui::MDIView* mdi = doc->getActiveView(); - if (mdi && mdi->isDerivedFrom()) { - Gui::View3DInventorViewer* view = static_cast(mdi)->getViewer(); - this->grp = static_cast(view->getSceneGraph()); + auto view = qobject_cast(doc->getActiveView()); + if (view) { + this->grp = static_cast(view->getViewer()->getSceneGraph()); this->grp->ref(); - this->grp->insertChild(this->tex,1); + this->grp->insertChild(this->tex, 1); if (ui->checkEnv->isChecked()) - this->grp->insertChild(this->env,2); + this->grp->insertChild(this->env, 2); } } } diff --git a/src/Gui/View3DInventor.cpp b/src/Gui/View3DInventor.cpp index 7b2f2b241f..06ea16a6ee 100644 --- a/src/Gui/View3DInventor.cpp +++ b/src/Gui/View3DInventor.cpp @@ -24,7 +24,6 @@ #ifndef _PreComp_ # include -# include # include # include # include @@ -39,6 +38,7 @@ # include # include # include +# include # include # include # include @@ -94,9 +94,12 @@ void GLOverlayWidget::paintEvent(QPaintEvent*) 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) +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 @@ -189,6 +192,27 @@ void View3DInventor::deleteSelf() MDIView::deleteSelf(); } +View3DInventor* View3DInventor::clone() +{ + auto mdiView = _pcDocument->createView(getClassTypeId(), CreateViewMode::Clone); + auto view3D = static_cast(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) @@ -722,33 +746,27 @@ void View3DInventor::dragEnterEvent (QDragEnterEvent * e) e->ignore(); } -void View3DInventor::setCurrentViewMode(ViewMode newmode) +void View3DInventor::setCurrentViewMode(ViewMode mode) { - ViewMode oldmode = MDIView::currentViewMode(); - if (oldmode == newmode) + ViewMode oldmode = currentViewMode(); + if (mode == oldmode) { return; - - if (newmode == 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(newmode); + 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(); + } + } - // Internally the QOpenGLWidget switches of the multi-sampling and there is no - // way to switch it on again. So as a workaround we just re-create a new viewport - // The method is private but defined as slot to avoid to call it by accident. - //int index = _viewer->metaObject()->indexOfMethod("replaceViewport()"); - //if (index >= 0) { - // _viewer->qt_metacall(QMetaObject::InvokeMetaMethod, index, 0); - //} + 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. @@ -760,26 +778,18 @@ void View3DInventor::setCurrentViewMode(ViewMode newmode) // // 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) { - // To make a global shortcut working from this window we need to add - // all existing actions from the mainwindow and its sub-widgets - QList acts = getMainWindow()->findChildren(); - this->addActions(acts); _viewer->getGLWidget()->setFocusProxy(this); - // To be notfified for new actions - qApp->installEventFilter(this); } - else if (newmode == Child) { + else if (mode == Child) { _viewer->getGLWidget()->setFocusProxy(nullptr); - qApp->removeEventFilter(this); - QList acts = this->actions(); - for (QAction* it : acts) - this->removeAction(it); // Step two auto mdi = qobject_cast(parentWidget()); - if (mdi && mdi->layout()) + if (mdi && mdi->layout()) { mdi->layout()->invalidate(); + } } } @@ -874,27 +884,6 @@ RayPickInfo View3DInventor::getObjInfoRay(Base::Vector3d* startvec, Base::Vector return ret; } -bool View3DInventor::eventFilter(QObject* watched, QEvent* e) -{ - // As long as this widget is a top-level window (either in 'TopLevel' or 'FullScreen' mode) we - // need to be notified when an action is added to a widget. This action must also be added to - // this window to allow one to make use of its shortcut (if defined). - // Note: We don't need to care about removing an action if its parent widget gets destroyed. - // This does the action itself for us. - if (watched != this && e->type() == QEvent::ActionAdded) { - auto a = static_cast(e); - QAction* action = a->action(); - - if (!action->isSeparator()) { - QList actions = this->actions(); - if (!actions.contains(action)) - this->addAction(action); - } - } - - return false; -} - void View3DInventor::keyPressEvent (QKeyEvent* e) { // See StdViewDockUndockFullscreen::activated() diff --git a/src/Gui/View3DInventor.h b/src/Gui/View3DInventor.h index efa6416844..de36d6b1c9 100644 --- a/src/Gui/View3DInventor.h +++ b/src/Gui/View3DInventor.h @@ -85,6 +85,8 @@ public: View3DInventor(Gui::Document* pcDocument, QWidget* parent, const QOpenGLWidget* sharewidget = nullptr, Qt::WindowFlags wflags=Qt::WindowFlags()); ~View3DInventor() override; + View3DInventor* clone() override; + /// Message handler bool onMsg(const char* pMsg, const char** ppReturn) override; bool onHasMsg(const char* pMsg) const override; @@ -132,9 +134,6 @@ public Q_SLOTS: protected Q_SLOTS: void stopAnimating(); -public: - bool eventFilter(QObject*, QEvent* ) override; - private: void applySettings(); diff --git a/src/Gui/ViewProvider.cpp b/src/Gui/ViewProvider.cpp index 1f04a65a10..45fca8baf4 100644 --- a/src/Gui/ViewProvider.cpp +++ b/src/Gui/ViewProvider.cpp @@ -227,12 +227,10 @@ void ViewProvider::eventCallback(void * ud, SoEventCallback * node) // react only on key release // Let first selection mode terminate Gui::Document* doc = Gui::Application::Instance->activeDocument(); - auto view = static_cast(doc->getActiveView()); - if (view) - { + const auto view = qobject_cast(doc->getActiveView()); + if (view) { Gui::View3DInventorViewer* viewer = view->getViewer(); - if (viewer->isSelecting()) - { + if (viewer->isSelecting()) { return; } } diff --git a/src/Mod/TechDraw/Gui/ViewProviderPage.cpp b/src/Mod/TechDraw/Gui/ViewProviderPage.cpp index d4fe91dd35..56231fb757 100644 --- a/src/Mod/TechDraw/Gui/ViewProviderPage.cpp +++ b/src/Mod/TechDraw/Gui/ViewProviderPage.cpp @@ -556,6 +556,16 @@ void ViewProviderPage::setGrid() } } +QGSPage* ViewProviderPage::getQGSPage() const +{ + return m_graphicsScene; +} + +QGVPage* ViewProviderPage::getQGVPage() const +{ + return m_graphicsView; +} + ViewProviderPageExtension* ViewProviderPage::getVPPExtension() const { auto vpe = getExtensionByType(); diff --git a/src/Mod/TechDraw/Gui/ViewProviderPage.h b/src/Mod/TechDraw/Gui/ViewProviderPage.h index 9075cde2aa..a08364059d 100644 --- a/src/Mod/TechDraw/Gui/ViewProviderPage.h +++ b/src/Mod/TechDraw/Gui/ViewProviderPage.h @@ -131,8 +131,8 @@ public: void setGrid(); - QGSPage* getQGSPage() const { return m_graphicsScene; } - QGVPage* getQGVPage() const { return m_graphicsView; } + QGSPage* getQGSPage() const; + QGVPage* getQGVPage() const; ViewProviderPageExtension* getVPPExtension() const; @@ -149,7 +149,7 @@ protected: private: QPointer m_mdiView; std::string m_pageName; - QGVPage* m_graphicsView; + QPointer m_graphicsView; QGSPage* m_graphicsScene; };