Files
create/src/Gui/MainWindow.cpp
André Caldas 89dbab9b0e Avoids using getNameInDocument() to test if DocumentObject is attached to a Document.
This patch substitutes by isAttachedToDocument() (almost) everywhere where
getNameInDocument() is used for this purpose.

The very few places not touched by this patch demand a (just a little) less trivial change.
When we change the returning type of getNameInDocument() to std::string,
those places will be easily found, because they shall generate a compiler error
(converting std::string to bool).

Rationale:
The fact that getNameInDocument() return nullptr to indicate
that the object is not attached to a document is responsible for lots of bugs
where the developer does not check for "nullptr".

The idea is to eliminate all those uses of getNameInDocument() and, in the near future,
make getNameInDocument() return always a valid std::string.
2023-12-11 17:37:58 +01:00

2607 lines
90 KiB
C++

/***************************************************************************
* Copyright (c) 2005 Werner Mayer <wmayer[at]users.sourceforge.net> *
* *
* 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 "PreCompiled.h"
#ifndef _PreComp_
# include <QActionGroup>
# include <QApplication>
# include <QByteArray>
# include <QCheckBox>
# include <QClipboard>
# include <QCloseEvent>
# include <QContextMenuEvent>
# include <QDesktopServices>
# include <QDockWidget>
# include <QFontMetrics>
# include <QKeySequence>
# include <QLabel>
# include <QMdiSubWindow>
# include <QMenu>
# include <QMessageBox>
# include <QMimeData>
# include <QPainter>
# include <QRegularExpression>
# include <QRegularExpressionMatch>
# include <QScreen>
# include <QSettings>
# include <QSignalMapper>
# include <QStatusBar>
# include <QThread>
# include <QTimer>
# include <QToolBar>
# include <QUrlQuery>
# include <QWhatsThis>
# include <QPushButton>
#endif
#if defined(Q_OS_WIN) && QT_VERSION < QT_VERSION_CHECK(6,0,0)
# include <QtPlatformHeaders/QWindowsWindowFunctions>
#endif
#include <boost/algorithm/string/predicate.hpp>
#include <App/Application.h>
#include <App/Document.h>
#include <App/DocumentObject.h>
#include <App/DocumentObjectGroup.h>
#include <Base/ConsoleObserver.h>
#include <Base/Parameter.h>
#include <Base/Exception.h>
#include <Base/FileInfo.h>
#include <Base/Interpreter.h>
#include <Base/Stream.h>
#include <Base/Tools.h>
#include <Base/UnitsApi.h>
#include <DAGView/DAGView.h>
#include <TaskView/TaskView.h>
#include "MainWindow.h"
#include "Action.h"
#include "Assistant.h"
#include "BitmapFactory.h"
#include "ComboView.h"
#include "Command.h"
#include "DockWindowManager.h"
#include "DownloadManager.h"
#include "FileDialog.h"
#include "MenuManager.h"
#include "NotificationArea.h"
#include "OverlayManager.h"
#include "ProgressBar.h"
#include "PropertyView.h"
#include "PythonConsole.h"
#include "ReportView.h"
#include "SelectionView.h"
#include "Splashscreen.h"
#include "ToolBarManager.h"
#include "ToolBoxManager.h"
#include "Tree.h"
#include "WaitCursor.h"
#include "WorkbenchManager.h"
#include "Workbench.h"
#include "MergeDocuments.h"
#include "ViewProviderExtern.h"
#include "SpaceballEvent.h"
#include "View3DInventor.h"
#include "View3DInventorViewer.h"
#include "DlgObjectSelection.h"
#include "Tools.h"
#include <App/Color.h>
FC_LOG_LEVEL_INIT("MainWindow",false,true,true)
#if defined(Q_OS_WIN32)
#define slots
#endif
using namespace Gui;
using namespace Gui::DockWnd;
using namespace std;
MainWindow* MainWindow::instance = nullptr;
namespace Gui {
/**
* The CustomMessageEvent class is used to send messages as events in the methods
* Error(), Warning() and Message() of the StatusBarObserver class to the main window
* to display them on the status bar instead of printing them directly to the status bar.
*
* This makes the usage of StatusBarObserver thread-safe.
* @author Werner Mayer
*/
class CustomMessageEvent : public QEvent
{
public:
CustomMessageEvent(int t, const QString& s, int timeout=0)
: QEvent(QEvent::User), _type(t), msg(s), _timeout(timeout)
{ }
~CustomMessageEvent() override = default;
int type() const
{ return _type; }
const QString& message() const
{ return msg; }
int timeout() const
{ return _timeout; }
private:
int _type;
QString msg;
int _timeout;
};
/**
* The DimensionWidget class is aiming at providing a widget used in the status bar that will:
* - Allow application to display dimension information such as the viewportsize
* - Provide a popup menu allowing user to change the used unit schema (and update if changed elsewhere)
*/
class DimensionWidget : public QPushButton, WindowParameter
{
Q_OBJECT
public:
explicit DimensionWidget(QWidget* parent): QPushButton(parent), WindowParameter("Units")
{
setFlat(true);
setText(qApp->translate("Gui::MainWindow", "Dimension"));
setMinimumWidth(120);
//create the action buttons
auto* menu = new QMenu(this);
auto* actionGrp = new QActionGroup(menu);
int num = static_cast<int>(Base::UnitSystem::NumUnitSystemTypes);
for (int i = 0; i < num; i++) {
QAction* action = menu->addAction(QStringLiteral("UnitSchema%1").arg(i));
actionGrp->addAction(action);
action->setCheckable(true);
action->setData(i);
}
QObject::connect(actionGrp, &QActionGroup::triggered, this, [this](QAction* action) {
int userSchema = action->data().toInt();
setUserSchema(userSchema);
// Force PropertyEditor refresh until we find a better way. Q_EMIT something?
const auto views = getMainWindow()->findChildren<PropertyView*>();
for(auto view : views) {
bool show = view->showAll();
view->setShowAll(!show);
view->setShowAll(show);
}
} );
setMenu(menu);
retranslateUi();
unitChanged();
getWindowParameter()->Attach(this);
}
~DimensionWidget() override
{
getWindowParameter()->Detach(this);
}
void OnChange(Base::Subject<const char*> &rCaller, const char * sReason) override
{
Q_UNUSED(rCaller)
if (strcmp(sReason, "UserSchema") == 0) {
unitChanged();
}
}
void changeEvent(QEvent *event) override
{
if (event->type() == QEvent::LanguageChange) {
retranslateUi();
}
else {
QPushButton::changeEvent(event);
}
}
void setUserSchema(int userSchema)
{
App::Document* doc = App::GetApplication().getActiveDocument();
if ( doc != nullptr ) {
if (doc->UnitSystem.getValue() != userSchema )
doc->UnitSystem.setValue(userSchema);
} else
getWindowParameter()->SetInt("UserSchema", userSchema);
unitChanged();
Base::UnitsApi::setSchema(static_cast<Base::UnitSystem>(userSchema));
// Update the main window to show the unit change
Gui::Application::Instance->onUpdate();
}
private:
void unitChanged()
{
ParameterGrp::handle hGrpu = App::GetApplication().GetParameterGroupByPath
("User parameter:BaseApp/Preferences/Units");
bool ignore = hGrpu->GetBool("IgnoreProjectSchema", false);
App::Document* doc = App::GetApplication().getActiveDocument();
int userSchema = getWindowParameter()->GetInt("UserSchema", 0);
if ( doc != nullptr && ! ignore) {
userSchema = doc->UnitSystem.getValue();
}
auto actions = menu()->actions();
if(Q_UNLIKELY(userSchema < 0 || userSchema >= actions.size())) {
userSchema = 0;
}
actions[userSchema]->setChecked(true);
}
void retranslateUi() {
auto actions = menu()->actions();
int maxSchema = static_cast<int>(Base::UnitSystem::NumUnitSystemTypes);
assert(actions.size() <= maxSchema);
for(int i = 0; i < maxSchema ; i++)
{
actions[i]->setText(Base::UnitsApi::getDescription(static_cast<Base::UnitSystem>(i)));
}
}
};
// -------------------------------------
// Pimpl class
struct MainWindowP
{
DimensionWidget* sizeLabel;
QLabel* actionLabel;
QTimer* actionTimer;
QTimer* statusTimer;
QTimer* activityTimer;
QTimer saveStateTimer;
QTimer restoreStateTimer;
QMdiArea* mdiArea;
QPointer<MDIView> activeView;
QSignalMapper* windowMapper;
SplashScreen* splashscreen;
StatusBarObserver* status;
bool whatsthis;
QString whatstext;
Assistant* assistant;
int currentStatusType = 100;
int actionUpdateDelay = 0;
QMap<QString, QPointer<UrlHandler> > urlHandler;
std::string hiddenDockWindows;
boost::signals2::scoped_connection connParam;
ParameterGrp::handle hGrp;
bool _restoring = false;
QTime _showNormal;
void restoreWindowState(const QByteArray &);
};
class MDITabbar : public QTabBar
{
public:
explicit MDITabbar( QWidget * parent = nullptr ) : QTabBar(parent)
{
menu = new QMenu(this);
setDrawBase(false);
setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Fixed);
}
~MDITabbar() override
{
delete menu;
}
protected:
void contextMenuEvent ( QContextMenuEvent * e ) override
{
menu->clear();
CommandManager& cMgr = Application::Instance->commandManager();
if (tabRect(currentIndex()).contains(e->pos()))
cMgr.getCommandByName("Std_CloseActiveWindow")->addTo(menu);
cMgr.getCommandByName("Std_CloseAllWindows")->addTo(menu);
menu->addSeparator();
cMgr.getCommandByName("Std_CascadeWindows")->addTo(menu);
cMgr.getCommandByName("Std_TileWindows")->addTo(menu);
menu->addSeparator();
cMgr.getCommandByName("Std_Windows")->addTo(menu);
menu->popup(e->globalPos());
}
private:
QMenu* menu;
};
#if defined(Q_OS_WIN32)
class MainWindowTabBar : public QTabBar
{
public:
MainWindowTabBar(QWidget *parent) : QTabBar(parent)
{
setExpanding(false);
}
protected:
bool event(QEvent *e)
{
// show the tooltip if tab is too small to fit label
if (e->type() != QEvent::ToolTip)
return QTabBar::event(e);
QSize size = this->size();
QSize hint = sizeHint();
if (shape() == QTabBar::RoundedWest || shape() == QTabBar::RoundedEast) {
size.transpose();
hint.transpose();
}
if (size.width() < hint.width())
return QTabBar::event(e);
e->accept();
return true;
}
void tabInserted (int index)
{
// get all dock windows
QList<QDockWidget*> dw = getMainWindow()->findChildren<QDockWidget*>();
for (QList<QDockWidget*>::iterator it = dw.begin(); it != dw.end(); ++it) {
// compare tab text and window title to get the right dock window
if (this->tabText(index) == (*it)->windowTitle()) {
QWidget* dock = (*it)->widget();
if (dock) {
QIcon icon = dock->windowIcon();
if (!icon.isNull())
setTabIcon(index, icon);
}
break;
}
}
}
};
#endif
} // namespace Gui
/* TRANSLATOR Gui::MainWindow */
MainWindow::MainWindow(QWidget * parent, Qt::WindowFlags f)
: QMainWindow( parent, f/*WDestructiveClose*/ )
{
d = new MainWindowP;
d->splashscreen = nullptr;
d->activeView = nullptr;
d->whatsthis = false;
d->assistant = new Assistant();
// global access
instance = this;
d->connParam = App::GetApplication().GetUserParameter().signalParamChanged.connect(
[this](ParameterGrp *Param, ParameterGrp::ParamType, const char *Name, const char *) {
if (Param != d->hGrp || !Name)
return;
if (boost::equals(Name, "StatusBar")) {
if(auto sb = getMainWindow()->statusBar())
sb->setVisible(d->hGrp->GetBool("StatusBar", sb->isVisible()));
}
else if (boost::equals(Name, "MainWindowState")) {
OverlayManager::instance()->reload(OverlayManager::ReloadMode::ReloadPause);
d->restoreStateTimer.start(100);
}
});
d->hGrp = App::GetApplication().GetParameterGroupByPath(
"User parameter:BaseApp/Preferences/MainWindow");
d->saveStateTimer.setSingleShot(true);
connect(&d->saveStateTimer, &QTimer::timeout, [this](){this->saveWindowSettings();});
d->restoreStateTimer.setSingleShot(true);
connect(&d->restoreStateTimer, &QTimer::timeout, [this](){
d->restoreWindowState(QByteArray::fromBase64(d->hGrp->GetASCII("MainWindowState").c_str()));
ToolBarManager::getInstance()->restoreState();
OverlayManager::instance()->reload(OverlayManager::ReloadMode::ReloadResume);
});
// support for grouped dragging of dockwidgets
// https://woboq.com/blog/qdockwidget-changes-in-56.html
setDockOptions(dockOptions() | QMainWindow::GroupedDragging);
// Create the layout containing the workspace and a tab bar
d->mdiArea = new QMdiArea();
// Movable tabs
d->mdiArea->setTabsMovable(true);
d->mdiArea->setTabPosition(QTabWidget::South);
d->mdiArea->setViewMode(QMdiArea::TabbedView);
auto tab = d->mdiArea->findChild<QTabBar*>();
if (tab) {
tab->setTabsClosable(true);
// The tabs might be very wide
tab->setExpanding(false);
tab->setObjectName(QString::fromLatin1("mdiAreaTabBar"));
}
d->mdiArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
d->mdiArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
d->mdiArea->setOption(QMdiArea::DontMaximizeSubWindowOnActivation, false);
d->mdiArea->setActivationOrder(QMdiArea::ActivationHistoryOrder);
d->mdiArea->setBackground(QBrush(QColor(160,160,160)));
setCentralWidget(d->mdiArea);
statusBar()->setObjectName(QString::fromLatin1("statusBar"));
connect(statusBar(), &QStatusBar::messageChanged, this, &MainWindow::statusMessageChanged);
// labels and progressbar
d->status = new StatusBarObserver();
d->actionLabel = new QLabel(statusBar());
// d->actionLabel->setMinimumWidth(120);
d->sizeLabel = new DimensionWidget(statusBar());
statusBar()->addWidget(d->actionLabel, 1);
QProgressBar* progressBar = Gui::SequencerBar::instance()->getProgressBar(statusBar());
statusBar()->addPermanentWidget(progressBar, 0);
statusBar()->addPermanentWidget(d->sizeLabel, 0);
auto hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/NotificationArea");
auto notificationAreaEnabled = hGrp->GetBool("NotificationAreaEnabled", true);
if(notificationAreaEnabled) {
NotificationArea* notificationArea = new NotificationArea(statusBar());
notificationArea->setObjectName(QString::fromLatin1("notificationArea"));
notificationArea->setStyleSheet(QStringLiteral("text-align:left;"));
statusBar()->addPermanentWidget(notificationArea);
}
// clears the action label
d->actionTimer = new QTimer( this );
d->actionTimer->setObjectName(QString::fromLatin1("actionTimer"));
connect(d->actionTimer, &QTimer::timeout, d->actionLabel, &QLabel::clear);
// clear status type
d->statusTimer = new QTimer( this );
d->statusTimer->setObjectName(QString::fromLatin1("statusTimer"));
connect(d->statusTimer, &QTimer::timeout, this, &MainWindow::clearStatus);
// update gui timer
d->activityTimer = new QTimer(this);
d->activityTimer->setObjectName(QString::fromLatin1("activityTimer"));
connect(d->activityTimer, &QTimer::timeout, this, &MainWindow::_updateActions);
d->activityTimer->setSingleShot(false);
d->activityTimer->start(150);
// update view-sensitive commands when clipboard has changed
QClipboard *clipbd = QApplication::clipboard();
connect(clipbd, &QClipboard::dataChanged, this, &MainWindow::updateEditorActions);
d->windowMapper = new QSignalMapper(this);
// connection between workspace, window menu and tab bar
#if QT_VERSION < QT_VERSION_CHECK(5,15,0)
connect(d->windowMapper, qOverload<QWidget*>(&QSignalMapper::mapped),
this, &MainWindow::onSetActiveSubWindow);
#elif QT_VERSION < QT_VERSION_CHECK(6,0,0)
connect(d->windowMapper, &QSignalMapper::mappedWidget,
this, &MainWindow::onSetActiveSubWindow);
#else
connect(d->windowMapper, &QSignalMapper::mappedObject,
this, [=](QObject* object) {
onSetActiveSubWindow(qobject_cast<QWidget*>(object));
});
#endif
connect(d->mdiArea, &QMdiArea::subWindowActivated,
this, &MainWindow::onWindowActivated);
setupDockWindows();
// accept drops on the window, get handled in dropEvent, dragEnterEvent
setAcceptDrops(true);
statusBar()->showMessage(tr("Ready"), 2001);
}
MainWindow::~MainWindow()
{
delete d->status;
delete d;
instance = nullptr;
}
MainWindow* MainWindow::getInstance()
{
// MainWindow has a public constructor
return instance;
}
// Helper function to update dock widget according to the user parameter
// settings, e.g. register/unregister, enable/disable, show/hide.
template<class T>
static inline void _updateDockWidget(const char *name,
bool enabled,
bool show,
Qt::DockWidgetArea pos,
T callback)
{
auto pDockMgr = DockWindowManager::instance();
auto widget = pDockMgr->findRegisteredDockWindow(name);
if (!enabled) {
if(widget) {
pDockMgr->removeDockWindow(widget);
pDockMgr->unregisterDockWindow(name);
widget->deleteLater();
}
return;
}
// Use callback to perform specific update for each type of dock widget
widget = callback(widget);
if(!widget)
return;
DockWindowManager::instance()->registerDockWindow(name, widget);
if(show) {
auto dock = pDockMgr->addDockWindow(
widget->objectName().toUtf8().constData(), widget, pos);
if(dock) {
if(!dock->toggleViewAction()->isChecked())
dock->toggleViewAction()->activate(QAction::Trigger);
OverlayManager::instance()->refresh(dock);
}
}
}
void MainWindow::initDockWindows(bool show)
{
updateTreeView(show);
updatePropertyView(show);
updateComboView(show);
updateTaskView(show);
updateDAGView(show);
}
void MainWindow::setupDockWindows()
{
// Report view must be created before PythonConsole!
setupReportView();
setupPythonConsole();
setupSelectionView();
setupTaskView();
initDockWindows(false);
std::vector<QTabWidget::TabPosition> tabPos = {QTabWidget::North,
QTabWidget::South,
QTabWidget::West,
QTabWidget::East};
long value = d->hGrp->GetInt("LeftDockWidgetAreaTabPos", long(tabPos.front()));
if (value >= 0 && value < long(tabPos.size())) {
setTabPosition(Qt::LeftDockWidgetArea, tabPos[value]);
}
}
bool MainWindow::setupTaskView()
{
// Task view
if (d->hiddenDockWindows.find("Std_TaskView") == std::string::npos) {
auto taskView = new Gui::TaskView::TaskView(this);
taskView->setObjectName
(QString::fromLatin1(QT_TRANSLATE_NOOP("QDockWidget","Tasks")));
taskView->setMinimumWidth(210);
DockWindowManager* pDockMgr = DockWindowManager::instance();
pDockMgr->registerDockWindow("Std_TaskView", taskView);
return true;
}
return false;
}
bool MainWindow::setupSelectionView()
{
// Selection view
if (d->hiddenDockWindows.find("Std_SelectionView") == std::string::npos) {
auto pcSelectionView = new SelectionView(nullptr, this);
pcSelectionView->setObjectName
(QString::fromLatin1(QT_TRANSLATE_NOOP("QDockWidget","Selection view")));
pcSelectionView->setMinimumWidth(210);
DockWindowManager* pDockMgr = DockWindowManager::instance();
pDockMgr->registerDockWindow("Std_SelectionView", pcSelectionView);
return true;
}
return false;
}
bool MainWindow::setupReportView()
{
// Report view
if (d->hiddenDockWindows.find("Std_ReportView") == std::string::npos) {
auto pcReport = new ReportOutput(this);
pcReport->setWindowIcon(BitmapFactory().pixmap("MacroEditor"));
pcReport->setObjectName
(QString::fromLatin1(QT_TRANSLATE_NOOP("QDockWidget","Report view")));
DockWindowManager* pDockMgr = DockWindowManager::instance();
pDockMgr->registerDockWindow("Std_ReportView", pcReport);
auto rvObserver = new ReportOutputObserver(pcReport);
qApp->installEventFilter(rvObserver);
return true;
}
return false;
}
bool MainWindow::setupPythonConsole()
{
// Python console
if (d->hiddenDockWindows.find("Std_PythonView") == std::string::npos) {
auto pcPython = new PythonConsole(this);
pcPython->setWindowIcon(Gui::BitmapFactory().iconFromTheme("applications-python"));
pcPython->setObjectName
(QString::fromLatin1(QT_TRANSLATE_NOOP("QDockWidget","Python console")));
DockWindowManager* pDockMgr = DockWindowManager::instance();
pDockMgr->registerDockWindow("Std_PythonView", pcPython);
return true;
}
return false;
}
bool MainWindow::updateTreeView(bool show)
{
if (d->hiddenDockWindows.find("Std_TreeView") == std::string::npos) {
ParameterGrp::handle group = App::GetApplication().GetUserParameter().
GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("DockWindows")->GetGroup("TreeView");
bool enabled = group->GetBool("Enabled", false);
_updateDockWidget("Std_TreeView", enabled, show, Qt::RightDockWidgetArea,
[](QWidget *widget) {
if (widget) {
return widget;
}
auto tree = new TreeDockWidget(0,getMainWindow());
tree->setObjectName(QStringLiteral(QT_TRANSLATE_NOOP("QDockWidget","Tree view")));
tree->setMinimumWidth(210);
widget = tree;
return widget;
});
return enabled;
}
return false;
}
bool MainWindow::updatePropertyView(bool show)
{
// Property view
if (d->hiddenDockWindows.find("Std_PropertyView") == std::string::npos) {
ParameterGrp::handle group = App::GetApplication().GetUserParameter().
GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("DockWindows")->GetGroup("PropertyView");
bool enabled = group->GetBool("Enabled", false);
_updateDockWidget("Std_PropertyView", enabled, show, Qt::RightDockWidgetArea,
[](QWidget *widget) {
if (widget) {
return widget;
}
auto pcPropView = new PropertyDockView(0, getMainWindow());
pcPropView->setObjectName(QStringLiteral(QT_TRANSLATE_NOOP("QDockWidget","Property view")));
pcPropView->setMinimumWidth(210);
widget = pcPropView;
return widget;
});
return enabled;
}
return false;
}
bool MainWindow::updateTaskView(bool show)
{
//Task List (task watcher).
if (d->hiddenDockWindows.find("Std_TaskWatcher") == std::string::npos) {
//work through parameter.
ParameterGrp::handle group = App::GetApplication().GetUserParameter().
GetGroup("BaseApp/Preferences/DockWindows/TaskWatcher");
bool enabled = group->GetBool("Enabled", false);
group->SetBool("Enabled", enabled); //ensure entry exists.
_updateDockWidget("Std_TaskWatcher", enabled, show, Qt::RightDockWidgetArea,
[](QWidget *widget) {
if (widget) {
return widget;
}
widget = new TaskView::TaskView(getMainWindow());
widget->setObjectName(QStringLiteral(QT_TRANSLATE_NOOP("QDockWidget","Task List")));
return widget;
});
return enabled;
}
return false;
}
bool MainWindow::updateComboView(bool show)
{
// Combo view
if (d->hiddenDockWindows.find("Std_ComboView") == std::string::npos) {
ParameterGrp::handle group = App::GetApplication().GetUserParameter().
GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("DockWindows")->GetGroup("ComboView");
bool enable = group->GetBool("Enabled", true);
_updateDockWidget("Std_ComboView", enable, show, Qt::LeftDockWidgetArea,
[](QWidget *widget) {
auto pcComboView = qobject_cast<ComboView*>(widget);
if (widget) {
return widget;
}
pcComboView = new ComboView(nullptr, getMainWindow());
pcComboView->setObjectName(QStringLiteral(QT_TRANSLATE_NOOP("QDockWidget", "Model")));
pcComboView->setMinimumWidth(150);
widget = pcComboView;
return widget;
});
return enable;
}
return false;
}
bool MainWindow::updateDAGView(bool show)
{
//Dag View.
if (d->hiddenDockWindows.find("Std_DAGView") == std::string::npos) {
ParameterGrp::handle group = App::GetApplication().GetUserParameter().
GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("DockWindows")->GetGroup("DAGView");
bool enabled = group->GetBool("Enabled", false);
_updateDockWidget("Std_DAGView", enabled, show, Qt::RightDockWidgetArea,
[](QWidget *widget) {
if (widget) {
return widget;
}
auto dagDockWindow = new DAG::DockWindow(nullptr, getMainWindow());
dagDockWindow->setObjectName(QStringLiteral(QT_TRANSLATE_NOOP("QDockWidget","DAG View")));
widget = dagDockWindow;
return widget;
});
return enabled;
}
return false;
}
QMenu* MainWindow::createPopupMenu ()
{
QMenu* menu = QMainWindow::createPopupMenu();
Workbench* wb = WorkbenchManager::instance()->active();
if (wb) {
MenuItem item;
wb->createMainWindowPopupMenu(&item);
if (item.hasItems()) {
menu->addSeparator();
QList<MenuItem*> items = item.getItems();
for (const auto & item : items) {
if (item->command() == "Separator") {
menu->addSeparator();
}
else {
Command* cmd = Application::Instance->commandManager().getCommandByName(item->command().c_str());
if (cmd) cmd->addTo(menu);
}
}
}
}
return menu;
}
void MainWindow::tile()
{
d->mdiArea->tileSubWindows();
}
void MainWindow::cascade()
{
d->mdiArea->cascadeSubWindows();
}
void MainWindow::closeActiveWindow ()
{
d->mdiArea->closeActiveSubWindow();
}
int MainWindow::confirmSave(const char *docName, QWidget *parent, bool addCheckbox) {
QMessageBox box(parent?parent:this);
box.setIcon(QMessageBox::Question);
box.setWindowTitle(QObject::tr("Unsaved document"));
if(docName)
box.setText(QObject::tr("Do you want to save your changes to document '%1' before closing?")
.arg(QString::fromUtf8(docName)));
else
box.setText(QObject::tr("Do you want to save your changes to document before closing?"));
box.setInformativeText(QObject::tr("If you don't save, your changes will be lost."));
box.setStandardButtons(QMessageBox::Discard | QMessageBox::Cancel | QMessageBox::Save);
box.setDefaultButton(QMessageBox::Save);
box.setEscapeButton(QMessageBox::Cancel);
QCheckBox checkBox(QObject::tr("Apply answer to all"));
ParameterGrp::handle hGrp;
if(addCheckbox) {
hGrp = App::GetApplication().GetUserParameter().
GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("General");
checkBox.setChecked(hGrp->GetBool("ConfirmAll",false));
checkBox.blockSignals(true);
box.addButton(&checkBox, QMessageBox::ResetRole);
}
// add shortcuts
QAbstractButton* saveBtn = box.button(QMessageBox::Save);
if (saveBtn->shortcut().isEmpty()) {
QString text = saveBtn->text();
text.prepend(QLatin1Char('&'));
saveBtn->setShortcut(QKeySequence::mnemonic(text));
}
QAbstractButton* discardBtn = box.button(QMessageBox::Discard);
if (discardBtn->shortcut().isEmpty()) {
QString text = discardBtn->text();
text.prepend(QLatin1Char('&'));
discardBtn->setShortcut(QKeySequence::mnemonic(text));
}
int res = ConfirmSaveResult::Cancel;
box.adjustSize(); // Silence warnings from Qt on Windows
switch (box.exec())
{
case QMessageBox::Save:
res = checkBox.isChecked()?ConfirmSaveResult::SaveAll:ConfirmSaveResult::Save;
break;
case QMessageBox::Discard:
res = checkBox.isChecked()?ConfirmSaveResult::DiscardAll:ConfirmSaveResult::Discard;
break;
}
if(addCheckbox && res)
hGrp->SetBool("ConfirmAll",checkBox.isChecked());
return res;
}
bool MainWindow::closeAllDocuments (bool close)
{
auto docs = App::GetApplication().getDocuments();
try {
docs = App::Document::getDependentDocuments(docs, true);
}
catch(Base::Exception &e) {
e.ReportException();
}
bool checkModify = true;
bool saveAll = false;
int failedSaves = 0;
for (auto doc : docs) {
auto gdoc = Application::Instance->getDocument(doc);
if (!gdoc)
continue;
if (!gdoc->canClose(false))
return false;
if (!gdoc->isModified()
|| doc->testStatus(App::Document::PartialDoc)
|| doc->testStatus(App::Document::TempDoc))
continue;
bool save = saveAll;
if (!save && checkModify) {
int res = confirmSave(doc->Label.getStrValue().c_str(), this, docs.size()>1);
switch (res)
{
case ConfirmSaveResult::Cancel:
return false;
case ConfirmSaveResult::SaveAll:
saveAll = true;
/* FALLTHRU */
case ConfirmSaveResult::Save:
save = true;
break;
case ConfirmSaveResult::DiscardAll:
checkModify = false;
}
}
if (save && !gdoc->save())
failedSaves++;
}
if (failedSaves > 0) {
int ret = QMessageBox::question(
getMainWindow(),
QObject::tr("%1 Document(s) not saved").arg(QString::number(failedSaves)),
QObject::tr("Some documents could not be saved. Do you want to cancel closing?"),
QMessageBox::Discard | QMessageBox::Cancel,
QMessageBox::Discard);
if (ret == QMessageBox::Cancel)
return false;
}
if (close)
App::GetApplication().closeAllDocuments();
return true;
}
void MainWindow::activateNextWindow ()
{
auto tab = d->mdiArea->findChild<QTabBar*>();
if (tab && tab->count() > 0) {
int index = (tab->currentIndex() + 1) % tab->count();
tab->setCurrentIndex(index);
}
}
void MainWindow::activatePreviousWindow ()
{
auto tab = d->mdiArea->findChild<QTabBar*>();
if (tab && tab->count() > 0) {
int index = (tab->currentIndex() + tab->count() - 1) % tab->count();
tab->setCurrentIndex(index);
}
}
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);
}
}
// emit this signal
Q_EMIT workbenchActivated(name);
updateActions(true);
}
void MainWindow::whatsThis()
{
QWhatsThis::enterWhatsThisMode();
}
void MainWindow::showDocumentation(const QString& help)
{
Base::PyGILStateLocker lock;
PyObject* module = PyImport_ImportModule("Help");
if (module) {
Py_DECREF(module);
Gui::Command::addModule(Gui::Command::Gui,"Help");
Gui::Command::doCommand(Gui::Command::Gui,"Help.show(\"%s\")", help.toStdString().c_str());
}
else {
PyErr_Clear();
QUrl url(help);
if (url.scheme().isEmpty()) {
QMessageBox msgBox(getMainWindow());
msgBox.setWindowTitle(tr("Help addon needed!"));
msgBox.setText(tr("The Help system of %1 is now handled by the \"Help\" addon. "
"It can easily be installed via the Addons Manager").arg(QString(qApp->applicationName())));
QAbstractButton* pButtonAddonMgr = msgBox.addButton(tr("Open Addon Manager"), QMessageBox::YesRole);
msgBox.addButton(QMessageBox::Ok);
msgBox.exec();
if (msgBox.clickedButton() == pButtonAddonMgr) {
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Addons");
hGrp->SetASCII("SelectedAddon", "Help");
Gui::Command::doCommand(Gui::Command::Gui,"Gui.runCommand('Std_AddonMgr',0)");
}
}
else {
QDesktopServices::openUrl(url);
}
}
}
bool MainWindow::event(QEvent *e)
{
if (e->type() == QEvent::EnterWhatsThisMode) {
// Unfortunately, for top-level widgets such as menus or dialogs we
// won't be notified when the user clicks the link in the hypertext of
// the what's this text. Thus, we have to install the main window to
// the application to observe what happens in eventFilter().
d->whatstext.clear();
if (!d->whatsthis) {
d-> whatsthis = true;
qApp->installEventFilter(this);
}
}
else if (e->type() == QEvent::LeaveWhatsThisMode) {
// Here we can't do anything because this event is sent
// before the WhatThisClicked event is sent. Thus, we handle
// this in eventFilter().
}
else if (e->type() == QEvent::WhatsThisClicked) {
auto wt = static_cast<QWhatsThisClickedEvent*>(e);
showDocumentation(wt->href());
}
else if (e->type() == QEvent::ApplicationWindowIconChange) {
// if application icon changes apply it to the main window and the "About..." dialog
this->setWindowIcon(QApplication::windowIcon());
Command* about = Application::Instance->commandManager().getCommandByName("Std_About");
if (about) {
Action* action = about->getAction();
if (action) action->setIcon(QApplication::windowIcon());
}
}
else if (e->type() == Spaceball::ButtonEvent::ButtonEventType) {
auto buttonEvent = dynamic_cast<Spaceball::ButtonEvent *>(e);
if (!buttonEvent)
return true;
buttonEvent->setHandled(true);
//only going to respond to button press events.
if (buttonEvent->buttonStatus() != Spaceball::BUTTON_PRESSED)
return true;
ParameterGrp::handle group = App::GetApplication().GetUserParameter().GetGroup("BaseApp")->
GetGroup("Spaceball")->GetGroup("Buttons");
QByteArray groupName(QVariant(buttonEvent->buttonNumber()).toByteArray());
if (group->HasGroup(groupName.data())) {
ParameterGrp::handle commandGroup = group->GetGroup(groupName.data());
std::string commandName(commandGroup->GetASCII("Command"));
if (commandName.empty())
return true;
else
Application::Instance->commandManager().runCommandByName(commandName.c_str());
}
else
return true;
}
else if (e->type() == Spaceball::MotionEvent::MotionEventType) {
auto motionEvent = dynamic_cast<Spaceball::MotionEvent *>(e);
if (!motionEvent)
return true;
motionEvent->setHandled(true);
Gui::Document *doc = Application::Instance->activeDocument();
if (!doc)
return true;
auto temp = dynamic_cast<View3DInventor *>(doc->getActiveView());
if (!temp)
return true;
View3DInventorViewer *view = temp->getViewer();
if (view) {
Spaceball::MotionEvent anotherEvent(*motionEvent);
qApp->sendEvent(view, &anotherEvent);
}
return true;
}else if(e->type() == QEvent::StatusTip) {
// make sure warning and error message don't get blocked by tooltips
if(std::abs(d->currentStatusType) <= MainWindow::Wrn)
return true;
}
return QMainWindow::event(e);
}
bool MainWindow::eventFilter(QObject* o, QEvent* e)
{
if (o != this) {
if (e->type() == QEvent::WindowStateChange) {
// notify all mdi views when the active view receives a show normal, show minimized
// or show maximized event
auto view = qobject_cast<MDIView*>(o);
if (view) { // emit this signal
Qt::WindowStates oldstate = static_cast<QWindowStateChangeEvent*>(e)->oldState();
Qt::WindowStates newstate = view->windowState();
if (oldstate != newstate)
Q_EMIT windowStateChanged(view);
}
}
// We don't want to show the bubble help for the what's this text but want to
// start the help viewer with the according key word.
// Thus, we have to observe WhatThis events if called for a widget, use its text and
// must avoid to make the bubble widget visible.
if (e->type() == QEvent::WhatsThis) {
if (!o->isWidgetType())
return false;
// clicked on a widget in what's this mode
auto w = static_cast<QWidget *>(o);
d->whatstext = w->whatsThis();
}
if (e->type() == QEvent::WhatsThisClicked) {
// if the widget is a top-level window
if (o->isWidgetType() && qobject_cast<QWidget*>(o)->isWindow()) {
// re-direct to the widget
QApplication::sendEvent(this, e);
}
}
// special treatment for menus because they directly call QWhatsThis::showText()
// whereby we must be informed for which action the help should be shown
if (o->inherits("QMenu") && QWhatsThis::inWhatsThisMode()) {
bool whatthis = false;
if (e->type() == QEvent::KeyPress) {
auto ke = static_cast<QKeyEvent*>(e);
if (ke->key() == Qt::Key_Return || ke->key() == Qt::Key_Enter || ke->key() == Qt::Key_F1)
whatthis = true;
}
else if (e->type() == QEvent::MouseButtonRelease)
whatthis = true;
else if (e->type() == QEvent::EnterWhatsThisMode)
whatthis = true;
if (whatthis) {
QAction* cur = static_cast<QMenu*>(o)->activeAction();
if (cur) {
// get the help text for later usage
QString s = cur->whatsThis();
if (s.isEmpty())
s = static_cast<QMenu*>(o)->whatsThis();
d->whatstext = s;
}
}
}
if (o->inherits("QWhatsThat") && e->type() == QEvent::Show) {
// the bubble help should become visible which we avoid by marking the widget
// that it is out of range. Instead of, we show the help viewer
if (!d->whatstext.isEmpty()) {
QWhatsThisClickedEvent e(d->whatstext);
QApplication::sendEvent(this, &e);
}
static_cast<QWidget *>(o)->setAttribute(Qt::WA_OutsideWSRange);
o->deleteLater();
return true;
}
if (o->inherits("QWhatsThat") && e->type() == QEvent::Hide) {
// leave what's this mode
if (d->whatsthis) {
d->whatsthis = false;
d->whatstext.clear();
qApp->removeEventFilter(this);
}
}
}
return QMainWindow::eventFilter(o, 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) {
child = new QMdiSubWindow(d->mdiArea->viewport());
child->setAttribute(Qt::WA_DeleteOnClose);
child->setWidget(view);
child->setWindowIcon(view->windowIcon());
QMenu* menu = child->systemMenu();
// See StdCmdCloseActiveWindow (#0002631)
QList<QAction*> acts = menu->actions();
for (auto & act : acts) {
if (act->shortcut() == QKeySequence(QKeySequence::Close)) {
act->setShortcuts(QList<QKeySequence>());
break;
}
}
QAction* action = menu->addAction(tr("Close All"));
connect(action, &QAction::triggered, d->mdiArea, &QMdiArea::closeAllSubWindows);
d->mdiArea->addSubWindow(child);
}
connect(view, &MDIView::message, this, &MainWindow::showMessage);
connect(this, &MainWindow::windowStateChanged, view, &MDIView::windowStateChanged);
// listen to the incoming events of the view
view->installEventFilter(this);
// show the very first window in maximized mode
if (isempty)
view->showMaximized();
else
view->show();
}
/**
* Removes the instance of Gui::MDiView from the main window and sends am 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)
{
// free all connections
disconnect(view, &MDIView::message, this, &MainWindow::showMessage);
disconnect(this, &MainWindow::windowStateChanged, view, &MDIView::windowStateChanged);
view->removeEventFilter(this);
// check if the focus widget is a child of the view
QWidget* foc = this->focusWidget();
if (foc) {
QWidget* par = foc->parentWidget();
while (par) {
if (par == view) {
foc->clearFocus();
break;
}
par = par->parentWidget();
}
}
QWidget* parent = view->parentWidget();
// The call of 'd->mdiArea->removeSubWindow(parent)' causes the QMdiSubWindow
// to lose its parent and thus the notification in QMdiSubWindow::closeEvent
// of other mdi windows to get maximized if this window is maximized will fail.
// However, we must let it here otherwise deleting MDI child views directly can
// cause other problems.
//
// The above mentioned problem can be fixed by setParent(0) which triggers a
// ChildRemoved event being handled properly inside QMidArea::viewportEvent()
//
auto subwindow = qobject_cast<QMdiSubWindow*>(parent);
if(subwindow && d->mdiArea->subWindowList().contains(subwindow)) {
subwindow->setParent(nullptr);
assert(!d->mdiArea->subWindowList().contains(subwindow));
// d->mdiArea->removeSubWindow(parent);
}
if(close)
parent->deleteLater();
updateActions();
}
void MainWindow::tabChanged(MDIView* view)
{
Q_UNUSED(view);
updateActions();
}
void MainWindow::tabCloseRequested(int index)
{
auto tab = d->mdiArea->findChild<QTabBar*>();
if (index < 0 || index >= tab->count())
return;
const QList<QMdiSubWindow *> subWindows = d->mdiArea->subWindowList();
Q_ASSERT(index < subWindows.size());
QMdiSubWindow *subWindow = d->mdiArea->subWindowList().at(index);
Q_ASSERT(subWindow);
subWindow->close();
updateActions();
}
void MainWindow::onSetActiveSubWindow(QWidget *window)
{
if (!window)
return;
d->mdiArea->setActiveSubWindow(qobject_cast<QMdiSubWindow *>(window));
updateActions();
}
void MainWindow::setActiveWindow(MDIView* view)
{
if (!view || d->activeView == view)
return;
onSetActiveSubWindow(view->parentWidget());
d->activeView = view;
Application::Instance->viewActivated(view);
}
void MainWindow::onWindowActivated(QMdiSubWindow* w)
{
if (!w)
return;
auto view = dynamic_cast<MDIView*>(w->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 = w->property("ownWB").toString();
if (! currWb.isEmpty()) {
this->activateWorkbench(currWb);
}
else {
w->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);
}
void MainWindow::onWindowsMenuAboutToShow()
{
QList<QMdiSubWindow*> windows = d->mdiArea->subWindowList(QMdiArea::CreationOrder);
QWidget* active = d->mdiArea->activeSubWindow();
// We search for the 'Std_WindowsMenu' command that provides the list of actions
CommandManager& cMgr = Application::Instance->commandManager();
Command* cmd = cMgr.getCommandByName("Std_WindowsMenu");
QList<QAction*> actions = qobject_cast<ActionGroup*>(cmd->getAction())->actions();
// do the connection only once
static bool firstShow = true;
if (firstShow) {
firstShow = false;
QAction* last = actions.isEmpty() ? 0 : actions.last();
for (const auto & action : actions) {
if (action == last)
break; // this is a separator
connect(action, &QAction::triggered, d->windowMapper, qOverload<>(&QSignalMapper::map));
}
}
int numWindows = std::min<int>(actions.count()-1, windows.count());
for (int index = 0; index < numWindows; index++) {
QWidget* child = windows.at(index);
QAction* action = actions.at(index);
QString text;
QString title = child->windowTitle();
int lastIndex = title.lastIndexOf(QString::fromLatin1("[*]"));
if (lastIndex > 0) {
title = title.left(lastIndex);
if (child->isWindowModified())
title = QString::fromLatin1("%1*").arg(title);
}
if (index < 9)
text = QString::fromLatin1("&%1 %2").arg(index+1).arg(title);
else
text = QString::fromLatin1("%1 %2").arg(index+1).arg(title);
action->setText(text);
action->setVisible(true);
action->setChecked(child == active);
d->windowMapper->setMapping(action, child);
}
// if less windows than actions
for (int index = numWindows; index < actions.count(); index++)
actions[index]->setVisible(false);
// show the separator
if (numWindows > 0)
actions.last()->setVisible(true);
}
void MainWindow::onToolBarMenuAboutToShow()
{
auto menu = static_cast<QMenu*>(sender());
menu->clear();
QList<QToolBar*> dock = this->findChildren<QToolBar*>();
for (const auto & it : dock) {
if (it->parentWidget() == this) {
QAction* action = it->toggleViewAction();
action->setToolTip(tr("Toggles this toolbar"));
action->setStatusTip(tr("Toggles this toolbar"));
action->setWhatsThis(tr("Toggles this toolbar"));
menu->addAction(action);
}
}
menu->addSeparator();
Application::Instance->commandManager().getCommandByName("Std_ToggleToolBarLock")->addTo(menu);
}
void MainWindow::onDockWindowMenuAboutToShow()
{
auto menu = static_cast<QMenu*>(sender());
menu->clear();
QList<QDockWidget*> dock = this->findChildren<QDockWidget*>();
for (auto & it : dock) {
QAction* action = it->toggleViewAction();
action->setToolTip(tr("Toggles this dockable window"));
action->setStatusTip(tr("Toggles this dockable window"));
action->setWhatsThis(tr("Toggles this dockable window"));
menu->addAction(action);
}
}
void MainWindow::setDockWindowMenu(QMenu* menu)
{
connect(menu, &QMenu::aboutToShow, this, &MainWindow::onDockWindowMenuAboutToShow);
}
void MainWindow::setToolBarMenu(QMenu* menu)
{
connect(menu, &QMenu::aboutToShow, this, &MainWindow::onToolBarMenuAboutToShow);
}
void MainWindow::setWindowsMenu(QMenu* menu)
{
connect(menu, &QMenu::aboutToShow, this, &MainWindow::onWindowsMenuAboutToShow);
}
QList<QWidget*> MainWindow::windows(QMdiArea::WindowOrder order) const
{
QList<QWidget*> mdis;
QList<QMdiSubWindow*> wnds = d->mdiArea->subWindowList(order);
for (const auto & wnd : wnds) {
mdis << wnd->widget();
}
return mdis;
}
MDIView* MainWindow::activeWindow() const
{
// each activated window notifies this main window when it is activated
return d->activeView;
}
void MainWindow::closeEvent (QCloseEvent * e)
{
Application::Instance->tryClose(e);
if (e->isAccepted()) {
// Send close event to all non-modal dialogs
QList<QDialog*> dialogs = this->findChildren<QDialog*>();
// It is possible that closing a dialog internally closes further dialogs. Thus,
// we have to check the pointer before.
QVector< QPointer<QDialog> > dialogs_ptr;
for (const auto & dialog : dialogs) {
dialogs_ptr.append(dialog);
}
for (auto & it : dialogs_ptr) {
if (!it.isNull())
it->close();
}
QList<MDIView*> mdis = this->findChildren<MDIView*>();
// Force to close any remaining (passive) MDI child views
for (auto & mdi : mdis) {
mdi->hide();
mdi->deleteLater();
}
if (Workbench* wb = WorkbenchManager::instance()->active())
wb->removeTaskWatcher();
Q_EMIT mainWindowClosed();
d->activityTimer->stop();
// https://forum.freecad.org/viewtopic.php?f=8&t=67748
// When the session manager jumps in it can happen that the closeEvent()
// function is triggered twice and for the second call the main window might be
// invisible. In this case the window settings shouldn't be saved.
if (isVisible())
saveWindowSettings();
delete d->assistant;
d->assistant = nullptr;
// See createMimeDataFromSelection
QVariant prop = this->property("x-documentobject-file");
if (!prop.isNull()) {
Base::FileInfo fi((const char*)prop.toByteArray());
if (fi.exists())
fi.deleteFile();
}
if (this->property("QuitOnClosed").isValid()) {
QApplication::closeAllWindows();
qApp->quit(); // stop the event loop
}
}
}
void MainWindow::showEvent(QShowEvent* e)
{
std::clog << "Show main window" << std::endl;
QMainWindow::showEvent(e);
}
void MainWindow::hideEvent(QHideEvent* e)
{
std::clog << "Hide main window" << std::endl;
QMainWindow::hideEvent(e);
}
void MainWindow::processMessages(const QList<QByteArray> & msg)
{
// handle all the messages to open files
try {
WaitCursor wc;
std::list<std::string> files;
QByteArray action("OpenFile:");
for (const auto & it : msg) {
if (it.startsWith(action))
files.emplace_back(it.mid(action.size()).constData());
}
files = App::Application::processFiles(files);
for (const auto & file : files) {
QString filename = QString::fromUtf8(file.c_str(), file.size());
FileDialog::setWorkingDirectory(filename);
}
}
catch (const Base::SystemExitException&) {
}
}
void MainWindow::delayedStartup()
{
// automatically run unit tests in Gui
if (App::Application::Config()["RunMode"] == "Internal") {
QTimer::singleShot(1000, this, []{
try {
Base::Interpreter().runString(
"import sys\n"
"import FreeCAD\n"
"import QtUnitGui\n\n"
"testCase = FreeCAD.ConfigGet(\"TestCase\")\n"
"QtUnitGui.addTest(testCase)\n"
"QtUnitGui.setTest(testCase)\n"
"result = QtUnitGui.runTest()\n"
"sys.stdout.flush()\n"
"sys.exit(0 if result else 1)");
}
catch (const Base::SystemExitException&) {
throw;
}
catch (const Base::Exception& e) {
e.ReportException();
}
});
return;
}
// processing all command line files
try {
std::list<std::string> files = App::Application::getCmdLineFiles();
files = App::Application::processFiles(files);
for (const auto & file : files) {
QString filename = QString::fromUtf8(file.c_str(), file.size());
FileDialog::setWorkingDirectory(filename);
}
}
catch (const Base::SystemExitException&) {
throw;
}
const std::map<std::string,std::string>& cfg = App::Application::Config();
auto it = cfg.find("StartHidden");
if (it != cfg.end()) {
QApplication::quit();
return;
}
// TODO: Check for deprecated settings
Application::Instance->checkForDeprecatedSettings();
// Create new document?
ParameterGrp::handle hGrp = WindowParameter::getDefaultParameter()->GetGroup("Document");
if (hGrp->GetBool("CreateNewDoc", false)) {
if (App::GetApplication().getDocuments().empty()){
Application::Instance->commandManager().runCommandByName("Std_New");
}
}
if (hGrp->GetBool("RecoveryEnabled", true)) {
Application::Instance->checkForPreviousCrashes();
}
}
void MainWindow::appendRecentFile(const QString& filename)
{
auto recent = this->findChild<RecentFilesAction *>
(QString::fromLatin1("recentFiles"));
if (recent) {
recent->appendFile(filename);
}
}
void MainWindow::appendRecentMacro(const QString& filename)
{
auto recent = this->findChild<RecentMacrosAction *>
(QString::fromLatin1("recentMacros"));
if (recent) {
recent->appendFile(filename);
}
}
void MainWindow::updateActions(bool delay)
{
//make it safe to call before the main window is actually created
if (!instance)
return;
if (!d->activityTimer->isActive()) {
// If for some reason updateActions() is called from a worker thread
// we must avoid to directly call QTimer::start() because this leaves
// the whole application in a weird state
if (d->activityTimer->thread() != QThread::currentThread()) {
QMetaObject::invokeMethod(d->activityTimer, "start", Qt::QueuedConnection,
Q_ARG(int, 150));
}
else {
d->activityTimer->start(150);
}
}
else if (delay) {
if (!d->actionUpdateDelay)
d->actionUpdateDelay = 1;
}
else {
d->actionUpdateDelay = -1;
}
}
void MainWindow::_updateActions()
{
if (isVisible() && d->actionUpdateDelay <= 0) {
FC_LOG("update actions");
d->activityTimer->stop();
Application::Instance->commandManager().testActive();
}
d->actionUpdateDelay = 0;
}
void MainWindow::updateEditorActions()
{
Command* cmd = nullptr;
CommandManager& mgr = Application::Instance->commandManager();
cmd = mgr.getCommandByName("Std_Cut");
if (cmd) cmd->testActive();
cmd = mgr.getCommandByName("Std_Copy");
if (cmd) cmd->testActive();
cmd = mgr.getCommandByName("Std_Paste");
if (cmd) cmd->testActive();
cmd = mgr.getCommandByName("Std_Undo");
if (cmd) cmd->testActive();
cmd = mgr.getCommandByName("Std_Redo");
if (cmd) cmd->testActive();
}
void MainWindow::switchToTopLevelMode()
{
QList<QDockWidget*> dw = this->findChildren<QDockWidget*>();
for (auto & it : dw) {
it->setParent(nullptr, Qt::Window);
it->show();
}
QList<QWidget*> mdi = getMainWindow()->windows();
for (auto & it : mdi) {
it->setParent(nullptr, Qt::Window);
it->show();
}
}
void MainWindow::switchToDockedMode()
{
// Search for all top-level MDI views
QWidgetList toplevel = QApplication::topLevelWidgets();
for (const auto & it : toplevel) {
auto view = qobject_cast<MDIView*>(it);
if (view)
view->setCurrentViewMode(MDIView::Child);
}
}
void MainWindow::loadWindowSettings()
{
QString vendor = QString::fromUtf8(App::Application::Config()["ExeVendor"].c_str());
QString application = QString::fromUtf8(App::Application::Config()["ExeName"].c_str());
int major = (QT_VERSION >> 0x10) & 0xff;
int minor = (QT_VERSION >> 0x08) & 0xff;
QString qtver = QStringLiteral("Qt%1.%2").arg(major).arg(minor);
QSettings config(vendor, application);
QRect rect = QApplication::primaryScreen()->availableGeometry();
int maxHeight = rect.height();
int maxWidth = rect.width();
config.beginGroup(qtver);
QPoint pos = config.value(QStringLiteral("Position"), this->pos()).toPoint();
maxWidth -= pos.x();
maxHeight -= pos.y();
QSize size = config.value(QStringLiteral("Size"), QSize(maxWidth, maxHeight)).toSize();
bool max = config.value(QStringLiteral("Maximized"), false).toBool();
bool showStatusBar = config.value(QStringLiteral("StatusBar"), true).toBool();
QByteArray windowState = config.value(QStringLiteral("MainWindowState")).toByteArray();
config.endGroup();
std::string geometry = d->hGrp->GetASCII("Geometry");
std::istringstream iss(geometry);
int x,y,w,h;
if (iss >> x >> y >> w >> h) {
pos = QPoint(x, y);
size = QSize(w, h);
}
max = d->hGrp->GetBool("Maximized", max);
showStatusBar = d->hGrp->GetBool("StatusBar", showStatusBar);
std::string wstate = d->hGrp->GetASCII("MainWindowState");
if (!wstate.empty()) {
windowState = QByteArray::fromBase64(wstate.c_str());
}
resize(size);
int x1{},x2{},y1{},y2{};
// make sure that the main window is not totally out of the visible rectangle
rect.getCoords(&x1, &y1, &x2, &y2);
pos.setX(qMin(qMax(pos.x(),x1-this->width()+30),x2-30));
pos.setY(qMin(qMax(pos.y(),y1-10),y2-10));
this->move(pos);
Base::StateLocker guard(d->_restoring);
d->restoreWindowState(windowState);
std::clog << "Main window restored" << std::endl;
max ? showMaximized() : show();
// make menus and tooltips usable in fullscreen under Windows, see issue #7563
#if defined(Q_OS_WIN) && QT_VERSION < QT_VERSION_CHECK(6,0,0)
if (QWindow* win = this->windowHandle()) {
QWindowsWindowFunctions::setHasBorderInFullScreen(win, true);
}
#endif
statusBar()->setVisible(showStatusBar);
ToolBarManager::getInstance()->restoreState();
std::clog << "Toolbars restored" << std::endl;
OverlayManager::instance()->restore();
}
bool MainWindow::isRestoringWindowState() const
{
return d->_restoring;
}
void MainWindowP::restoreWindowState(const QByteArray &windowState)
{
if (windowState.isEmpty())
return;
Base::StateLocker guard(_restoring);
// tmp. disable the report window to suppress some bothering warnings
if (Base::Console().IsMsgTypeEnabled("ReportOutput", Base::ConsoleSingleton::MsgType_Wrn)) {
Base::Console().SetEnabledMsgType("ReportOutput", Base::ConsoleSingleton::MsgType_Wrn, false);
getMainWindow()->restoreState(windowState);
Base::Console().SetEnabledMsgType("ReportOutput", Base::ConsoleSingleton::MsgType_Wrn, true);
} else
getMainWindow()->restoreState(windowState);
Base::ConnectionBlocker block(connParam);
// as a notification for user code on window state restore
hGrp->SetBool("WindowStateRestored", !hGrp->GetBool("WindowStateRestored", false));
}
void MainWindow::saveWindowSettings(bool canDelay)
{
if (isRestoringWindowState())
return;
if (canDelay) {
d->saveStateTimer.start(100);
return;
}
QString vendor = QString::fromUtf8(App::Application::Config()["ExeVendor"].c_str());
QString application = QString::fromUtf8(App::Application::Config()["ExeName"].c_str());
int major = (QT_VERSION >> 0x10) & 0xff;
int minor = (QT_VERSION >> 0x08) & 0xff;
QString qtver = QStringLiteral("Qt%1.%2").arg(major).arg(minor);
QSettings config(vendor, application);
#if 0
config.beginGroup(qtver);
config.setValue(QStringLiteral("Size"), this->size());
config.setValue(QStringLiteral("Position"), this->pos());
config.setValue(QStringLiteral("Maximized"), this->isMaximized());
config.setValue(QStringLiteral("MainWindowState"), this->saveState());
config.setValue(QStringLiteral("StatusBar"), this->statusBar()->isVisible());
config.endGroup();
#else
// We are migrating from saving qt main window layout state in QSettings to
// FreeCAD parameters, for more control.
#endif
Base::ConnectionBlocker block(d->connParam);
d->hGrp->SetBool("Maximized", this->isMaximized());
d->hGrp->SetBool("StatusBar", this->statusBar()->isVisible());
d->hGrp->SetASCII("MainWindowState", this->saveState().toBase64().constData());
std::ostringstream ss;
QRect rect(this->pos(), this->size());
ss << rect.left() << " " << rect.top() << " " << rect.width() << " " << rect.height();
d->hGrp->SetASCII("Geometry", ss.str().c_str());
DockWindowManager::instance()->saveState();
OverlayManager::instance()->save();
ToolBarManager::getInstance()->saveState();
}
void MainWindow::startSplasher()
{
// startup splasher
// when running in verbose mode no splasher
if (!(App::Application::Config()["Verbose"] == "Strict") &&
(App::Application::Config()["RunMode"] == "Gui")) {
ParameterGrp::handle hGrp = App::GetApplication().GetUserParameter().
GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("General");
// first search for an external image file
if (hGrp->GetBool("ShowSplasher", true)) {
d->splashscreen = new SplashScreen(this->splashImage());
if (!hGrp->GetBool("ShowSplasherMessages", true)) {
d->splashscreen->setShowMessages(false);
}
d->splashscreen->show();
}
else {
d->splashscreen = nullptr;
}
}
}
void MainWindow::stopSplasher()
{
if (d->splashscreen) {
d->splashscreen->finish(this);
delete d->splashscreen;
d->splashscreen = nullptr;
}
}
QPixmap MainWindow::aboutImage() const
{
// See if we have a custom About screen image set
QPixmap about_image;
QFileInfo fi(QString::fromLatin1("images:about_image.png"));
if (fi.isFile() && fi.exists())
about_image.load(fi.filePath(), "PNG");
std::string about_path = App::Application::Config()["AboutImage"];
if (!about_path.empty() && about_image.isNull()) {
QString path = QString::fromUtf8(about_path.c_str());
if (QDir(path).isRelative()) {
QString home = QString::fromStdString(App::Application::getHomePath());
path = QFileInfo(QDir(home), path).absoluteFilePath();
}
about_image.load(path);
// Now try the icon paths
if (about_image.isNull()) {
about_image = Gui::BitmapFactory().pixmap(about_path.c_str());
}
}
return about_image;
}
/**
* Displays a warning about this being a developer build. Designed for display in the Splashscreen.
* \param painter The painter to draw the warning into
* \param startPosition The painter-space coordinates to start the warning box at.
* \param maxSize The maximum extents for the box that is drawn. If the text exceeds this size it
* will be scaled down to fit.
* \note The text string is translatable, so its length is somewhat unpredictable. It is always
* displayed as two lines, regardless of the length of the text (e.g. no wrapping is done). Only the
* width is considered, the height simply follows from the font size.
*/
void MainWindow::renderDevBuildWarning(
QPainter &painter,
const QPoint startPosition,
const QSize maxSize)
{
// Create a background box that fades out the artwork for better legibility
QColor fader (Qt::black);
constexpr float halfDensity (0.5);
fader.setAlphaF(halfDensity);
QBrush fillBrush(fader, Qt::BrushStyle::SolidPattern);
painter.setBrush(fillBrush);
// Construct the lines of text and figure out how much space they need
const auto devWarningLine1 = tr("WARNING: This is a development version.");
const auto devWarningLine2 = tr("Please do not use it in a production environment.");
QFontMetrics fontMetrics(painter.font()); // Try to use the existing font
int padding = QtTools::horizontalAdvance(fontMetrics, QLatin1String("M")); // Arbitrary
int line1Width = QtTools::horizontalAdvance(fontMetrics, devWarningLine1);
int line2Width = QtTools::horizontalAdvance(fontMetrics, devWarningLine2);
int boxWidth = std::max(line1Width,line2Width) + 2 * padding;
int lineHeight = fontMetrics.lineSpacing();
if (boxWidth > maxSize.width()) {
// Especially if the text was translated, there is a chance that using the existing font
// will exceed the width of the Splashscreen graphic. Resize down so that it fits, no matter
// how long the text strings are.
float reductionFactor = static_cast<float>(maxSize.width()) / static_cast<float>(boxWidth);
int newFontSize = static_cast<int>(painter.font().pointSize() * reductionFactor);
padding *= reductionFactor;
QFont newFont = painter.font();
newFont.setPointSize(newFontSize);
painter.setFont(newFont);
lineHeight = painter.fontMetrics().lineSpacing();
boxWidth = maxSize.width();
}
constexpr float lineExpansionFactor(2.3F);
int boxHeight = static_cast<int>(lineHeight*lineExpansionFactor);
// Draw the background rectangle and the text
painter.drawRect(startPosition.x(), startPosition.y(), boxWidth, boxHeight);
painter.drawText(startPosition.x()+padding, startPosition.y()+lineHeight, devWarningLine1);
painter.drawText(startPosition.x()+padding, startPosition.y()+2*lineHeight, devWarningLine2);
}
QPixmap MainWindow::splashImage() const
{
// search in the UserAppData dir as very first
QPixmap splash_image;
QFileInfo fi(QString::fromLatin1("images:splash_image.png"));
if (fi.isFile() && fi.exists())
splash_image.load(fi.filePath(), "PNG");
// if no image was found try the config
std::string splash_path = App::Application::Config()["SplashScreen"];
if (splash_image.isNull()) {
QString path = QString::fromUtf8(splash_path.c_str());
if (QDir(path).isRelative()) {
QString home = QString::fromStdString(App::Application::getHomePath());
path = QFileInfo(QDir(home), path).absoluteFilePath();
}
splash_image.load(path);
}
// now try the icon paths
float pixelRatio (1.0);
if (splash_image.isNull()) {
if (qApp->devicePixelRatio() > 1.0) {
// For HiDPI screens, we have a double-resolution version of the splash image
splash_path += "2x";
splash_image = Gui::BitmapFactory().pixmap(splash_path.c_str());
splash_image.setDevicePixelRatio(2.0);
pixelRatio = 2.0;
}
else {
splash_image = Gui::BitmapFactory().pixmap(splash_path.c_str());
}
}
// include application name and version number
std::map<std::string,std::string>::const_iterator tc = App::Application::Config().find("SplashInfoColor");
if (tc != App::Application::Config().end()) {
QString title = qApp->applicationName();
QString major = QString::fromLatin1(App::Application::Config()["BuildVersionMajor"].c_str());
QString minor = QString::fromLatin1(App::Application::Config()["BuildVersionMinor"].c_str());
QString point = QString::fromLatin1(App::Application::Config()["BuildVersionPoint"].c_str());
QString suffix = QString::fromLatin1(App::Application::Config()["BuildVersionSuffix"].c_str());
QString version = QString::fromLatin1("%1.%2.%3%4").arg(major, minor, point, suffix);
QString position, fontFamily;
std::map<std::string,std::string>::const_iterator te = App::Application::Config().find("SplashInfoExeName");
std::map<std::string,std::string>::const_iterator tv = App::Application::Config().find("SplashInfoVersion");
std::map<std::string,std::string>::const_iterator tp = App::Application::Config().find("SplashInfoPosition");
std::map<std::string,std::string>::const_iterator tf = App::Application::Config().find("SplashInfoFont");
if (te != App::Application::Config().end())
title = QString::fromUtf8(te->second.c_str());
if (tv != App::Application::Config().end())
version = QString::fromUtf8(tv->second.c_str());
if (tp != App::Application::Config().end())
position = QString::fromUtf8(tp->second.c_str());
if (tf != App::Application::Config().end())
fontFamily = QString::fromUtf8(tf->second.c_str());
QPainter painter;
painter.begin(&splash_image);
if (!fontFamily.isEmpty()) {
QFont font = painter.font();
if (font.fromString(fontFamily))
painter.setFont(font);
}
QFont fontExe = painter.font();
fontExe.setPointSizeF(20.0);
QFontMetrics metricExe(fontExe);
int l = QtTools::horizontalAdvance(metricExe, title);
if (title == QLatin1String("FreeCAD")) {
l = 0.0; // "FreeCAD" text is already part of the splashscreen, version goes below it
}
int w = splash_image.width();
int h = splash_image.height();
QFont fontVer = painter.font();
fontVer.setPointSizeF(14.0);
QFontMetrics metricVer(fontVer);
int v = QtTools::horizontalAdvance(metricVer, version);
int x = -1, y = -1;
QRegularExpression rx(QLatin1String("(\\d+).(\\d+)"));
auto match = rx.match(position);
if (match.hasMatch()) {
x = match.captured(1).toInt();
y = match.captured(2).toInt();
}
else {
x = w - (l + v + 10);
y = h - 20;
}
QColor color;
color.setNamedColor(QString::fromLatin1(tc->second.c_str()));
if (color.isValid()) {
painter.setPen(color);
painter.setFont(fontExe);
if (title != QLatin1String("FreeCAD")) {
// FreeCAD's Splashscreen already contains the EXE name, no need to draw it
painter.drawText(x, y, title);
}
painter.setFont(fontVer);
painter.drawText(x + (l + 5), y, version);
if (suffix == QLatin1String("dev")) {
const int lineHeight = metricVer.lineSpacing();
const int padding {10}; // Distance from the edge of the graphic's bounding box
QPoint startPosition(padding, y + lineHeight);
QSize maxSize(w/pixelRatio - 2*padding, lineHeight * 3);
MainWindow::renderDevBuildWarning(painter, startPosition, maxSize);
}
painter.end();
}
}
return splash_image;
}
/**
* Drops the event \a e and tries to open the files.
*/
void MainWindow::dropEvent (QDropEvent* e)
{
const QMimeData* data = e->mimeData();
if (data->hasUrls()) {
// load the files into the active document if there is one, otherwise let create one
loadUrls(App::GetApplication().getActiveDocument(), data->urls());
}
else {
QMainWindow::dropEvent(e);
}
}
void MainWindow::dragEnterEvent (QDragEnterEvent * e)
{
// Here we must allow uri drafs and check them in dropEvent
const QMimeData* data = e->mimeData();
if (data->hasUrls()) {
e->accept();
}
else {
e->ignore();
}
}
static QLatin1String _MimeDocObj("application/x-documentobject");
static QLatin1String _MimeDocObjX("application/x-documentobject-x");
static QLatin1String _MimeDocObjFile("application/x-documentobject-file");
static QLatin1String _MimeDocObjXFile("application/x-documentobject-x-file");
QMimeData * MainWindow::createMimeDataFromSelection () const
{
std::vector<App::DocumentObject*> sel;
std::set<App::DocumentObject*> objSet;
for(auto &s : Selection().getCompleteSelection()) {
if(s.pObject && s.pObject->isAttachedToDocument() && objSet.insert(s.pObject).second)
sel.push_back(s.pObject);
}
if(sel.empty())
return nullptr;
auto all = App::Document::getDependencyList(sel);
if (all.size() > sel.size()) {
DlgObjectSelection dlg(sel,getMainWindow());
if(dlg.exec()!=QDialog::Accepted)
return nullptr;
sel = dlg.getSelections();
if(sel.empty())
return nullptr;
}
std::vector<App::Document*> unsaved;
bool hasXLink = App::PropertyXLink::hasXLink(sel,&unsaved);
if(!unsaved.empty()) {
QMessageBox::critical(getMainWindow(), tr("Unsaved document"),
tr("The exported object contains external link. Please save the document"
"at least once before exporting."));
return nullptr;
}
unsigned int memsize=1000; // ~ for the meta-information
for (const auto & it : sel)
memsize += it->getMemSize();
// if less than ~10 MB
bool use_buffer=(memsize < 0xA00000);
QByteArray res;
if(use_buffer) {
try {
res.reserve(memsize);
}
catch (const std::bad_alloc &) {
use_buffer = false;
}
}
WaitCursor wc;
QString mime;
if (use_buffer) {
mime = hasXLink?_MimeDocObjX:_MimeDocObj;
Base::ByteArrayOStreambuf buf(res);
std::ostream str(&buf);
// need this instance to call MergeDocuments::Save()
App::Document* doc = sel.front()->getDocument();
MergeDocuments mimeView(doc);
doc->exportObjects(sel, str);
}
else {
mime = hasXLink?_MimeDocObjXFile:_MimeDocObjFile;
static Base::FileInfo fi(App::Application::getTempFileName());
Base::ofstream str(fi, std::ios::out | std::ios::binary);
// need this instance to call MergeDocuments::Save()
App::Document* doc = sel.front()->getDocument();
MergeDocuments mimeView(doc);
doc->exportObjects(sel, str);
str.close();
res = fi.filePath().c_str();
// store the path name as a custom property and
// delete this file when closing the application
const_cast<MainWindow*>(this)->setProperty("x-documentobject-file", res);
}
auto mimeData = new QMimeData();
mimeData->setData(mime,res);
return mimeData;
}
bool MainWindow::canInsertFromMimeData (const QMimeData * source) const
{
if (!source)
return false;
return source->hasUrls() ||
source->hasFormat(_MimeDocObj) || source->hasFormat(_MimeDocObjX) ||
source->hasFormat(_MimeDocObjFile) || source->hasFormat(_MimeDocObjXFile);
}
void MainWindow::insertFromMimeData (const QMimeData * mimeData)
{
if (!mimeData)
return;
bool fromDoc = false;
bool hasXLink = false;
QString format;
if(mimeData->hasFormat(_MimeDocObj))
format = _MimeDocObj;
else if(mimeData->hasFormat(_MimeDocObjX)) {
format = _MimeDocObjX;
hasXLink = true;
}else if(mimeData->hasFormat(_MimeDocObjFile)) {
format = _MimeDocObjFile;
fromDoc = true;
}else if(mimeData->hasFormat(_MimeDocObjXFile)) {
format = _MimeDocObjXFile;
fromDoc = true;
hasXLink = true;
}else {
if (mimeData->hasUrls())
loadUrls(App::GetApplication().getActiveDocument(), mimeData->urls());
return;
}
App::Document* doc = App::GetApplication().getActiveDocument();
if(!doc) doc = App::GetApplication().newDocument();
if(hasXLink && !doc->isSaved()) {
int ret = QMessageBox::question(getMainWindow(), tr("Unsaved document"),
tr("To link to external objects, the document must be saved at least once.\n"
"Do you want to save the document now?"),
QMessageBox::Yes,QMessageBox::No);
if(ret != QMessageBox::Yes || !Application::Instance->getDocument(doc)->saveAs())
return;
}
if(!fromDoc) {
QByteArray res = mimeData->data(format);
doc->openTransaction("Paste");
Base::ByteArrayIStreambuf buf(res);
std::istream in(nullptr);
in.rdbuf(&buf);
MergeDocuments mimeView(doc);
std::vector<App::DocumentObject*> newObj = mimeView.importObjects(in);
std::vector<App::DocumentObjectGroup*> grp = Gui::Selection().getObjectsOfType<App::DocumentObjectGroup>();
if (grp.size() == 1) {
Gui::Document* gui = Application::Instance->getDocument(doc);
if (gui)
gui->addRootObjectsToGroup(newObj, grp.front());
}
doc->commitTransaction();
}
else {
QByteArray res = mimeData->data(format);
doc->openTransaction("Paste");
Base::FileInfo fi((const char*)res);
Base::ifstream str(fi, std::ios::in | std::ios::binary);
MergeDocuments mimeView(doc);
std::vector<App::DocumentObject*> newObj = mimeView.importObjects(str);
str.close();
std::vector<App::DocumentObjectGroup*> grp = Gui::Selection().getObjectsOfType<App::DocumentObjectGroup>();
if (grp.size() == 1) {
Gui::Document* gui = Application::Instance->getDocument(doc);
if (gui)
gui->addRootObjectsToGroup(newObj, grp.front());
}
doc->commitTransaction();
}
}
void MainWindow::setUrlHandler(const QString &scheme, Gui::UrlHandler* handler)
{
d->urlHandler[scheme] = handler;
}
void MainWindow::unsetUrlHandler(const QString &scheme)
{
d->urlHandler.remove(scheme);
}
void MainWindow::loadUrls(App::Document* doc, const QList<QUrl>& urls)
{
QStringList files;
for (const auto & it : urls) {
QMap<QString, QPointer<UrlHandler> >::iterator jt = d->urlHandler.find(it.scheme());
if (jt != d->urlHandler.end() && !jt->isNull()) {
// delegate the loading to the url handler
(*jt)->openUrl(doc, it);
continue;
}
QFileInfo info(it.toLocalFile());
if (info.exists() && info.isFile()) {
if (info.isSymLink())
info.setFile(info.symLinkTarget());
std::vector<std::string> module = App::GetApplication()
.getImportModules(info.completeSuffix().toLatin1());
if (module.empty()) {
module = App::GetApplication()
.getImportModules(info.suffix().toLatin1());
}
if (!module.empty()) {
// ok, we support files with this extension
files << info.absoluteFilePath();
}
else {
Base::Console().Message("No support to load file '%s'\n",
(const char*)info.absoluteFilePath().toUtf8());
}
}
else if (it.scheme().toLower() == QLatin1String("http")) {
Gui::Dialog::DownloadManager* dm = Gui::Dialog::DownloadManager::getInstance();
dm->download(dm->redirectUrl(it));
}
else if (it.scheme().toLower() == QLatin1String("https")) {
QUrl url = it;
QUrlQuery urlq(url);
if (urlq.hasQueryItem(QLatin1String("sid"))) {
urlq.removeAllQueryItems(QLatin1String("sid"));
url.setQuery(urlq);
url.setScheme(QLatin1String("http"));
}
Gui::Dialog::DownloadManager* dm = Gui::Dialog::DownloadManager::getInstance();
dm->download(dm->redirectUrl(url));
}
else if (it.scheme().toLower() == QLatin1String("ftp")) {
Gui::Dialog::DownloadManager::getInstance()->download(it);
}
}
QByteArray docName = doc ? QByteArray(doc->getName()) : qApp->translate("StdCmdNew","Unnamed").toUtf8();
SelectModule::Dict dict = SelectModule::importHandler(files);
// load the files with the associated modules
for (SelectModule::Dict::iterator it = dict.begin(); it != dict.end(); ++it) {
// if the passed document name doesn't exist the module should create it, if needed
Application::Instance->importFrom(it.key().toUtf8(), docName, it.value().toLatin1());
}
}
void MainWindow::changeEvent(QEvent *e)
{
if (e->type() == QEvent::LanguageChange) {
d->sizeLabel->setText(tr("Dimension"));
CommandManager& rclMan = Application::Instance->commandManager();
std::vector<Command*> cmd = rclMan.getAllCommands();
for (auto & it : cmd)
it->languageChange();
// reload current workbench to retranslate all actions and window titles
Workbench* wb = WorkbenchManager::instance()->active();
if (wb) wb->retranslate();
}
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);
}
}
}
}
else {
QMainWindow::changeEvent(e);
}
}
void MainWindow::clearStatus() {
d->currentStatusType = 100;
statusBar()->setStyleSheet(QString::fromLatin1("#statusBar{}"));
}
void MainWindow::statusMessageChanged() {
if(d->currentStatusType<0)
d->currentStatusType = -d->currentStatusType;
else {
// here probably means the status bar message is changed by QMainWindow
// internals, e.g. for displaying tooltip and stuff. Set reset what
// we've changed.
d->statusTimer->stop();
clearStatus();
}
}
void MainWindow::showMessage(const QString& message, int timeout) {
if(QApplication::instance()->thread() != QThread::currentThread()) {
QApplication::postEvent(this, new CustomMessageEvent(MainWindow::Tmp,message,timeout));
return;
}
d->actionLabel->setText(message.simplified());
if(timeout) {
d->actionTimer->setSingleShot(true);
d->actionTimer->start(timeout);
}else
d->actionTimer->stop();
}
void MainWindow::showStatus(int type, const QString& message)
{
if(QApplication::instance()->thread() != QThread::currentThread()) {
QApplication::postEvent(this,
new CustomMessageEvent(type,message));
return;
}
if(d->currentStatusType < type)
return;
d->statusTimer->setSingleShot(true);
// TODO: hardcode?
int timeout = 5000;
d->statusTimer->start(timeout);
QFontMetrics fm(statusBar()->font());
QString msg = fm.elidedText(message, Qt::ElideMiddle, this->d->actionLabel->width());
switch(type) {
case MainWindow::Err:
statusBar()->setStyleSheet(d->status->err);
break;
case MainWindow::Wrn:
statusBar()->setStyleSheet(d->status->wrn);
break;
case MainWindow::Pane:
statusBar()->setStyleSheet(QString::fromLatin1("#statusBar{}"));
break;
default:
statusBar()->setStyleSheet(d->status->msg);
break;
}
d->currentStatusType = -type;
statusBar()->showMessage(msg.simplified(), timeout);
}
// set text to the pane
void MainWindow::setPaneText(int i, QString text)
{
if (i==1) {
showStatus(MainWindow::Pane, text);
}
else if (i==2) {
d->sizeLabel->setText(text);
}
}
void MainWindow::setUserSchema(int userSchema)
{
d->sizeLabel->setUserSchema(userSchema);
}
void MainWindow::customEvent(QEvent* e)
{
if (e->type() == QEvent::User) {
auto ce = static_cast<Gui::CustomMessageEvent*>(e);
QString msg = ce->message();
switch(ce->type()) {
case MainWindow::Log: {
if (msg.startsWith(QLatin1String("#Inventor V2.1 ascii "))) {
Gui::Document *d = Application::Instance->activeDocument();
if (d) {
auto view = new ViewProviderExtern();
try {
view->setModeByString("1",msg.toLatin1().constData());
d->setAnnotationViewProvider("Vdbg",view);
}
catch (...) {
delete view;
}
}
}
break;
} case MainWindow::Tmp: {
showMessage(msg, ce->timeout());
break;
} default:
showStatus(ce->type(),msg);
}
}
else if (e->type() == ActionStyleEvent::EventType) {
QList<TaskView::TaskView*> tasks = findChildren<TaskView::TaskView*>();
if (static_cast<ActionStyleEvent*>(e)->getType() == ActionStyleEvent::Clear) {
for (auto & task : tasks) {
task->clearActionStyle();
}
}
else {
for (auto & task : tasks) {
task->restoreActionStyle();
}
}
}
}
QMdiArea *MainWindow::getMdiArea() const
{
return d->mdiArea;
}
// ----------------------------------------------------------
StatusBarObserver::StatusBarObserver()
: WindowParameter("OutputWindow")
{
msg = QString::fromLatin1("#statusBar{color: #000000}"); // black
wrn = QString::fromLatin1("#statusBar{color: #ffaa00}"); // orange
err = QString::fromLatin1("#statusBar{color: #ff0000}"); // red
Base::Console().AttachObserver(this);
getWindowParameter()->Attach(this);
getWindowParameter()->NotifyAll();
}
StatusBarObserver::~StatusBarObserver()
{
getWindowParameter()->Detach(this);
Base::Console().DetachObserver(this);
}
void StatusBarObserver::OnChange(Base::Subject<const char*> &rCaller, const char * sReason)
{
ParameterGrp& rclGrp = ((ParameterGrp&)rCaller);
auto format = QString::fromLatin1("#statusBar{color: %1}");
if (strcmp(sReason, "colorText") == 0) {
unsigned long col = rclGrp.GetUnsigned( sReason );
this->msg = format.arg(App::Color::fromPackedRGB<QColor>(col).name());
}
else if (strcmp(sReason, "colorWarning") == 0) {
unsigned long col = rclGrp.GetUnsigned( sReason );
this->wrn = format.arg(App::Color::fromPackedRGB<QColor>(col).name());
}
else if (strcmp(sReason, "colorError") == 0) {
unsigned long col = rclGrp.GetUnsigned( sReason );
this->err = format.arg(App::Color::fromPackedRGB<QColor>(col).name());
}
else if (strcmp(sReason, "colorCritical") == 0) {
unsigned long col = rclGrp.GetUnsigned( sReason );
this->critical = format.arg(QColor((col >> 24) & 0xff,(col >> 16) & 0xff,(col >> 8) & 0xff).name());
}
}
void StatusBarObserver::SendLog(const std::string& notifiername, const std::string& msg, Base::LogStyle level,
Base::IntendedRecipient recipient, Base::ContentType content)
{
(void) notifiername;
// Do not log untranslated messages, or messages intended only to a developer to status bar
if( recipient == Base::IntendedRecipient::Developer ||
content == Base::ContentType::Untranslated ||
content == Base::ContentType::Untranslatable )
return;
int messageType = -1;
switch(level){
case Base::LogStyle::Warning:
messageType = MainWindow::Wrn;
break;
case Base::LogStyle::Message:
messageType = MainWindow::Msg;
break;
case Base::LogStyle::Error:
messageType = MainWindow::Err;
break;
case Base::LogStyle::Log:
messageType = MainWindow::Log;
break;
case Base::LogStyle::Critical:
messageType = MainWindow::Critical;
break;
default:
break;
}
// Send the event to the main window to allow thread-safety. Qt will delete it when done.
auto ev = new CustomMessageEvent(messageType, QString::fromUtf8(msg.c_str()));
QApplication::postEvent(getMainWindow(), ev);
}
// -------------------------------------------------------------
int ActionStyleEvent::EventType = -1;
ActionStyleEvent::ActionStyleEvent(Style type)
: QEvent(QEvent::Type(EventType)), type(type)
{
}
ActionStyleEvent::Style ActionStyleEvent::getType() const
{
return type;
}
#include "moc_MainWindow.cpp"
#include "MainWindow.moc"