Merge pull request #22544 from jffmichi/undockfullscreen

Gui: Allow undocking and fullscreen for all MDI windows
This commit is contained in:
Kacper Donat
2025-07-25 00:18:20 +02:00
committed by GitHub
14 changed files with 344 additions and 308 deletions

View File

@@ -1682,7 +1682,7 @@ void StdViewDock::activated(int iMsg)
bool StdViewDock::isActive()
{
MDIView* view = getMainWindow()->activeWindow();
return (qobject_cast<View3DInventor*>(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<View3DInventor*>(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<View3DInventor*>(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<View3DInventor*>(view)) {
// update the action group if needed
auto pActGrp = qobject_cast<ActionGroup*>(_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<ActionGroup*>(_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;
}

View File

@@ -2016,7 +2016,7 @@ void Document::addRootObjectsToGroup(const std::vector<App::DocumentObject*>& 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<View3DInventor>()) {
auto view3D = new View3DInventor(this, getMainWindow());
auto firstView = static_cast<View3DInventor*>(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 App::DocumentObject*,ViewProviderDocumentObject*>::const_iterator It1;
std::vector<App::DocumentObject*> child_vps;
for (It1=d->_ViewProviderMap.begin();It1!=d->_ViewProviderMap.end();++It1) {
view3D->getViewer()->addViewProvider(It1->second);
std::vector<App::DocumentObject*> children = It1->second->claimChildren3D();
child_vps.insert(child_vps.end(), children.begin(), children.end());
}
std::map<std::string,ViewProvider*>::const_iterator It2;
for (It2=d->_ViewProviderMapAnnotation.begin();It2!=d->_ViewProviderMapAnnotation.end();++It2) {
view3D->getViewer()->addViewProvider(It2->second);
std::vector<App::DocumentObject*> 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();
}

View File

@@ -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

View File

@@ -25,6 +25,7 @@
#ifndef _PreComp_
# include <boost/signals2.hpp>
# include <boost/core/ignore_unused.hpp>
# include <QAction>
# include <QApplication>
# include <QEvent>
# include <QCloseEvent>
@@ -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<QActionEvent*>(event);
QAction* action = actionEvent->action();
if (!action->isSeparator()) {
QList<QAction*> 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<QMdiSubWindow*>(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<QMdiSubWindow*>(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<QMdiSubWindow*>(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<QAction*> acts = getMainWindow()->findChildren<QAction*>();
addActions(acts);
// To be notfified for new actions
qApp->installEventFilter(this);
}
else if (mode == Child) {
qApp->removeEventFilter(this);
QList<QAction*> 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);
}
}

View File

@@ -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;

View File

@@ -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<QWidget*>(object));
connect(d->windowMapper, &QSignalMapper::mappedObject, this, [=, this](QObject* object) {
setActiveSubWindow(qobject_cast<QWidget*>(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<QMdiSubWindow*>(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<QMdiSubWindow*>(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<QMdiSubWindow*>(window);
if (!mdi) {
return;
d->mdiArea->setActiveSubWindow(qobject_cast<QMdiSubWindow *>(window));
updateActions();
}
auto view = qobject_cast<MDIView*>(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<QMdiSubWindow*>(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<MDIView*>(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<MDIView*>(mdi->widget());
if (view && getMainWindow()->activeWindow() != view) {
d->activeView = view;
Application::Instance->viewActivated(view);
}
}
setActiveSubWindow(mdi);
}
}
else {

View File

@@ -318,7 +318,7 @@ private Q_SLOTS:
/**
* \internal
*/
void onSetActiveSubWindow(QWidget *window);
void setActiveSubWindow(QWidget*);
/**
* Activates the associated tab to this widget.
*/

View File

@@ -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<Gui::View3DInventor*>(doc->getActiveView());
Gui::View3DInventor* view = qobject_cast<Gui::View3DInventor*>(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<Gui::View3DInventor*>(doc->getActiveView());
Gui::View3DInventor* view = qobject_cast<Gui::View3DInventor*>(doc->getActiveView());
if (view) {
Gui::View3DInventorViewer* viewer = view->getViewer();
if (viewer->isEditing()) {

View File

@@ -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<View3DInventor>()) {
Gui::View3DInventorViewer* view = static_cast<View3DInventor*>(mdi)->getViewer();
this->grp = static_cast<SoGroup *>(view->getSceneGraph());
auto view = qobject_cast<View3DInventor*>(doc->getActiveView());
if (view) {
this->grp = static_cast<SoGroup*>(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);
}
}
}

View File

@@ -24,7 +24,6 @@
#ifndef _PreComp_
# include <string>
# include <QAction>
# include <QApplication>
# include <QKeyEvent>
# include <QEvent>
@@ -39,6 +38,7 @@
# include <QPrintDialog>
# include <QPrintPreviewDialog>
# include <QStackedWidget>
# include <QSurfaceFormat>
# include <QTimer>
# include <QUrl>
# include <QWindow>
@@ -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<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)
@@ -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<QAction*> acts = getMainWindow()->findChildren<QAction*>();
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<QAction*> acts = this->actions();
for (QAction* it : acts)
this->removeAction(it);
// Step two
auto mdi = qobject_cast<QMdiSubWindow*>(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<QActionEvent*>(e);
QAction* action = a->action();
if (!action->isSeparator()) {
QList<QAction*> actions = this->actions();
if (!actions.contains(action))
this->addAction(action);
}
}
return false;
}
void View3DInventor::keyPressEvent (QKeyEvent* e)
{
// See StdViewDockUndockFullscreen::activated()

View File

@@ -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();

View File

@@ -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<Gui::View3DInventor*>(doc->getActiveView());
if (view)
{
const auto view = qobject_cast<Gui::View3DInventor*>(doc->getActiveView());
if (view) {
Gui::View3DInventorViewer* viewer = view->getViewer();
if (viewer->isSelecting())
{
if (viewer->isSelecting()) {
return;
}
}

View File

@@ -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<ViewProviderPageExtension>();

View File

@@ -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<MDIViewPage> m_mdiView;
std::string m_pageName;
QGVPage* m_graphicsView;
QPointer<QGVPage> m_graphicsView;
QGSPage* m_graphicsScene;
};