FreeCAD

Форк
0
/
MainWindow.cpp 
2655 строк · 91.8 Кб
1
/***************************************************************************
2
 *   Copyright (c) 2005 Werner Mayer <wmayer[at]users.sourceforge.net>     *
3
 *                                                                         *
4
 *   This file is part of the FreeCAD CAx development system.              *
5
 *                                                                         *
6
 *   This library is free software; you can redistribute it and/or         *
7
 *   modify it under the terms of the GNU Library General Public           *
8
 *   License as published by the Free Software Foundation; either          *
9
 *   version 2 of the License, or (at your option) any later version.      *
10
 *                                                                         *
11
 *   This library  is distributed in the hope that it will be useful,      *
12
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
13
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
14
 *   GNU Library General Public License for more details.                  *
15
 *                                                                         *
16
 *   You should have received a copy of the GNU Library General Public     *
17
 *   License along with this library; see the file COPYING.LIB. If not,    *
18
 *   write to the Free Software Foundation, Inc., 59 Temple Place,         *
19
 *   Suite 330, Boston, MA  02111-1307, USA                                *
20
 *                                                                         *
21
 ***************************************************************************/
22

23

24
#include "PreCompiled.h"
25
#ifndef _PreComp_
26
# include <QActionGroup>
27
# include <QApplication>
28
# include <QByteArray>
29
# include <QCheckBox>
30
# include <QClipboard>
31
# include <QCloseEvent>
32
# include <QContextMenuEvent>
33
# include <QDesktopServices>
34
# include <QDockWidget>
35
# include <QFontMetrics>
36
# include <QKeySequence>
37
# include <QLabel>
38
# include <QMdiSubWindow>
39
# include <QMenu>
40
# include <QMessageBox>
41
# include <QMimeData>
42
# include <QPainter>
43
# include <QRegularExpression>
44
# include <QRegularExpressionMatch>
45
# include <QScreen>
46
# include <QSettings>
47
# include <QSignalMapper>
48
# include <QStatusBar>
49
# include <QThread>
50
# include <QTimer>
51
# include <QToolBar>
52
# include <QUrlQuery>
53
# include <QWhatsThis>
54
# include <QPushButton>
55
#endif
56

57
#if defined(Q_OS_WIN) && QT_VERSION < QT_VERSION_CHECK(6,0,0)
58
# include <QtPlatformHeaders/QWindowsWindowFunctions>
59
#endif
60

61
#include <boost/algorithm/string/predicate.hpp>
62

63
#include <App/Application.h>
64
#include <App/Document.h>
65
#include <App/DocumentObject.h>
66
#include <App/DocumentObjectGroup.h>
67
#include <Base/ConsoleObserver.h>
68
#include <Base/Parameter.h>
69
#include <Base/Exception.h>
70
#include <Base/FileInfo.h>
71
#include <Base/Interpreter.h>
72
#include <Base/Stream.h>
73
#include <Base/Tools.h>
74
#include <Base/UnitsApi.h>
75
#include <DAGView/DAGView.h>
76
#include <TaskView/TaskView.h>
77

78
#include "MainWindow.h"
79
#include "Action.h"
80
#include "Assistant.h"
81
#include "BitmapFactory.h"
82
#include "ComboView.h"
83
#include "Command.h"
84
#include "DockWindowManager.h"
85
#include "DownloadManager.h"
86
#include "FileDialog.h"
87
#include "MenuManager.h"
88
#include "NotificationArea.h"
89
#include "OverlayManager.h"
90
#include "ProgressBar.h"
91
#include "PropertyView.h"
92
#include "PythonConsole.h"
93
#include "ReportView.h"
94
#include "SelectionView.h"
95
#include "Splashscreen.h"
96
#include "ToolBarManager.h"
97
#include "ToolBoxManager.h"
98
#include "Tree.h"
99
#include "WaitCursor.h"
100
#include "WorkbenchManager.h"
101
#include "Workbench.h"
102

103
#include "MergeDocuments.h"
104
#include "ViewProviderExtern.h"
105

106
#include "SpaceballEvent.h"
107
#include "View3DInventor.h"
108
#include "View3DInventorViewer.h"
109
#include "DlgObjectSelection.h"
110
#include "Tools.h"
111
#include <App/Color.h>
112

113
FC_LOG_LEVEL_INIT("MainWindow",false,true,true)
114

115
#if defined(Q_OS_WIN32)
116
#define slots
117
#endif
118

119
using namespace Gui;
120
using namespace Gui::DockWnd;
121
using namespace std;
122

123

124
MainWindow* MainWindow::instance = nullptr;
125

126
namespace Gui {
127

128
/**
129
 * The CustomMessageEvent class is used to send messages as events in the methods
130
 * Error(), Warning() and Message() of the StatusBarObserver class to the main window
131
 * to display them on the status bar instead of printing them directly to the status bar.
132
 *
133
 * This makes the usage of StatusBarObserver thread-safe.
134
 * @author Werner Mayer
135
 */
136
class CustomMessageEvent : public QEvent
137
{
138
public:
139
    CustomMessageEvent(int t, const QString& s, int timeout=0)
140
      : QEvent(QEvent::User), _type(t), msg(s), _timeout(timeout)
141
    { }
142
    ~CustomMessageEvent() override = default;
143
    int type() const
144
    { return _type; }
145
    const QString& message() const
146
    { return msg; }
147
    int timeout() const
148
    { return _timeout; }
149
private:
150
    int _type;
151
    QString msg;
152
    int _timeout;
153
};
154

155
/**
156
 * The DimensionWidget class is aiming at providing a widget used in the status bar that will:
157
 *  - Allow application to display dimension information such as the viewportsize
158
 *  - Provide a popup menu allowing user to change the used unit schema (and update if changed elsewhere)
159
 */
160
class DimensionWidget : public QPushButton, WindowParameter
161
{
162
    Q_OBJECT
163

164
public:
165
    explicit DimensionWidget(QWidget* parent): QPushButton(parent), WindowParameter("Units")
166
    {
167
        setFlat(true);
168
        setText(qApp->translate("Gui::MainWindow", "Dimension"));
169
        setMinimumWidth(120);
170

171
        //create the action buttons
172
        auto* menu = new QMenu(this);
173
        auto* actionGrp = new QActionGroup(menu);
174
        int num = static_cast<int>(Base::UnitSystem::NumUnitSystemTypes);
175
        for (int i = 0; i < num; i++) {
176
            QAction* action = menu->addAction(QStringLiteral("UnitSchema%1").arg(i));
177
            actionGrp->addAction(action);
178
            action->setCheckable(true);
179
            action->setData(i);
180
        }
181
        QObject::connect(actionGrp, &QActionGroup::triggered, this, [this](QAction* action) {
182
            int userSchema = action->data().toInt();
183
            setUserSchema(userSchema);
184
            // Force PropertyEditor refresh until we find a better way.  Q_EMIT something?
185
            const auto views = getMainWindow()->findChildren<PropertyView*>();
186
            for(auto view : views) {
187
                bool show = view->showAll();
188
                view->setShowAll(!show);
189
                view->setShowAll(show);
190
            }
191
        } );
192
        setMenu(menu);
193
        retranslateUi();
194
        unitChanged();
195
        getWindowParameter()->Attach(this);
196
    }
197

198
    ~DimensionWidget() override
199
    {
200
        getWindowParameter()->Detach(this);
201
    }
202

203
    void OnChange(Base::Subject<const char*> &rCaller, const char * sReason) override
204
    {
205
        Q_UNUSED(rCaller)
206
        if (strcmp(sReason, "UserSchema") == 0) {
207
            unitChanged();
208
        }
209
    }
210

211
    void changeEvent(QEvent *event) override
212
    {
213
        if (event->type() == QEvent::LanguageChange) {
214
            retranslateUi();
215
        }
216
        else {
217
            QPushButton::changeEvent(event);
218
        }
219
    }
220

221
    void setUserSchema(int userSchema)
222
    {
223
        App::Document* doc = App::GetApplication().getActiveDocument();
224
        if ( doc != nullptr ) {
225
            if (doc->UnitSystem.getValue() != userSchema )
226
                doc->UnitSystem.setValue(userSchema);
227
        } else
228
            getWindowParameter()->SetInt("UserSchema", userSchema);
229

230
        unitChanged();
231
        Base::UnitsApi::setSchema(static_cast<Base::UnitSystem>(userSchema));
232
        // Update the main window to show the unit change
233
        Gui::Application::Instance->onUpdate();
234
    }
235

236
private:
237
    void unitChanged()
238
    {
239
        ParameterGrp::handle hGrpu = App::GetApplication().GetParameterGroupByPath
240
        ("User parameter:BaseApp/Preferences/Units");
241
        bool ignore = hGrpu->GetBool("IgnoreProjectSchema", false);
242
        App::Document* doc = App::GetApplication().getActiveDocument();
243
        int userSchema = getWindowParameter()->GetInt("UserSchema", 0);
244
        if ( doc != nullptr && ! ignore) {
245
            userSchema = doc->UnitSystem.getValue();
246
        }
247
        auto actions = menu()->actions();
248
        if(Q_UNLIKELY(userSchema < 0 || userSchema >= actions.size())) {
249
            userSchema = 0;
250
        }
251
        actions[userSchema]->setChecked(true);
252
    }
253

254
    void retranslateUi() {
255
        auto actions = menu()->actions();
256
        int maxSchema = static_cast<int>(Base::UnitSystem::NumUnitSystemTypes);
257
        assert(actions.size() <= maxSchema);
258
        for(int i = 0; i < maxSchema ; i++)
259
        {
260
            actions[i]->setText(Base::UnitsApi::getDescription(static_cast<Base::UnitSystem>(i)));
261
        }
262
    }
263
};
264

265
// -------------------------------------
266
// Pimpl class
267
struct MainWindowP
268
{
269
    DimensionWidget* sizeLabel;
270
    QLabel* actionLabel;
271
    QTimer* actionTimer;
272
    QTimer* statusTimer;
273
    QTimer* activityTimer;
274
    QTimer saveStateTimer;
275
    QTimer restoreStateTimer;
276
    QMdiArea* mdiArea;
277
    QPointer<MDIView> activeView;
278
    QSignalMapper* windowMapper;
279
    SplashScreen* splashscreen;
280
    StatusBarObserver* status;
281
    bool whatsthis;
282
    QString whatstext;
283
    Assistant* assistant;
284
    int currentStatusType = 100;
285
    int actionUpdateDelay = 0;
286
    QMap<QString, QPointer<UrlHandler> > urlHandler;
287
    std::string hiddenDockWindows;
288
    boost::signals2::scoped_connection connParam;
289
    ParameterGrp::handle hGrp;
290
    bool _restoring = false;
291
    QTime _showNormal;
292
    void restoreWindowState(const QByteArray &);
293
};
294

295
class MDITabbar : public QTabBar
296
{
297
public:
298
    explicit MDITabbar( QWidget * parent = nullptr ) : QTabBar(parent)
299
    {
300
        menu = new QMenu(this);
301
        setDrawBase(false);
302
        setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Fixed);
303
    }
304

305
    ~MDITabbar() override
306
    {
307
        delete menu;
308
    }
309

310
protected:
311
    void contextMenuEvent ( QContextMenuEvent * e ) override
312
    {
313
        menu->clear();
314
        CommandManager& cMgr = Application::Instance->commandManager();
315
        if (tabRect(currentIndex()).contains(e->pos()))
316
            cMgr.getCommandByName("Std_CloseActiveWindow")->addTo(menu);
317
        cMgr.getCommandByName("Std_CloseAllWindows")->addTo(menu);
318
        menu->addSeparator();
319
        cMgr.getCommandByName("Std_CascadeWindows")->addTo(menu);
320
        cMgr.getCommandByName("Std_TileWindows")->addTo(menu);
321
        menu->addSeparator();
322
        cMgr.getCommandByName("Std_Windows")->addTo(menu);
323
        menu->popup(e->globalPos());
324
    }
325

326
private:
327
    QMenu* menu;
328
};
329

330
#if defined(Q_OS_WIN32)
331
class MainWindowTabBar : public QTabBar
332
{
333
public:
334
    MainWindowTabBar(QWidget *parent) : QTabBar(parent)
335
    {
336
        setExpanding(false);
337
    }
338
protected:
339
    bool event(QEvent *e)
340
    {
341
        // show the tooltip if tab is too small to fit label
342
        if (e->type() != QEvent::ToolTip)
343
            return QTabBar::event(e);
344
        QSize size = this->size();
345
        QSize hint = sizeHint();
346
        if (shape() == QTabBar::RoundedWest || shape() == QTabBar::RoundedEast) {
347
            size.transpose();
348
            hint.transpose();
349
        }
350
        if (size.width() < hint.width())
351
            return QTabBar::event(e);
352
        e->accept();
353
        return true;
354
    }
355
    void tabInserted (int index)
356
    {
357
        // get all dock windows
358
        QList<QDockWidget*> dw = getMainWindow()->findChildren<QDockWidget*>();
359
        for (QList<QDockWidget*>::iterator it = dw.begin(); it != dw.end(); ++it) {
360
            // compare tab text and window title to get the right dock window
361
            if (this->tabText(index) == (*it)->windowTitle()) {
362
                QWidget* dock = (*it)->widget();
363
                if (dock) {
364
                    QIcon icon = dock->windowIcon();
365
                    if (!icon.isNull())
366
                        setTabIcon(index, icon);
367
                }
368
                break;
369
            }
370
        }
371
    }
372
};
373
#endif
374

375
} // namespace Gui
376

377
/* TRANSLATOR Gui::MainWindow */
378

379
MainWindow::MainWindow(QWidget * parent, Qt::WindowFlags f)
380
  : QMainWindow( parent, f/*WDestructiveClose*/ )
381
{
382
    d = new MainWindowP;
383
    d->splashscreen = nullptr;
384
    d->activeView = nullptr;
385
    d->whatsthis = false;
386
    d->assistant = new Assistant();
387

388
    // global access
389
    instance = this;
390

391
    d->connParam = App::GetApplication().GetUserParameter().signalParamChanged.connect(
392
        [this](ParameterGrp *Param, ParameterGrp::ParamType, const char *Name, const char *) {
393
            if (Param != d->hGrp || !Name)
394
                return;
395
            if (boost::equals(Name, "StatusBar")) {
396
                if(auto sb = getMainWindow()->statusBar())
397
                    sb->setVisible(d->hGrp->GetBool("StatusBar", sb->isVisible()));
398
            }
399
            else if (boost::equals(Name, "MainWindowState")) {
400
                OverlayManager::instance()->reload(OverlayManager::ReloadMode::ReloadPause);
401
                d->restoreStateTimer.start(100);
402
            }
403
        });
404

405
    d->hGrp = App::GetApplication().GetParameterGroupByPath(
406
            "User parameter:BaseApp/Preferences/MainWindow");
407
    d->saveStateTimer.setSingleShot(true);
408
    connect(&d->saveStateTimer, &QTimer::timeout, [this](){this->saveWindowSettings();});
409

410
    d->restoreStateTimer.setSingleShot(true);
411
    connect(&d->restoreStateTimer, &QTimer::timeout, [this](){
412
        d->restoreWindowState(QByteArray::fromBase64(d->hGrp->GetASCII("MainWindowState").c_str()));
413
        ToolBarManager::getInstance()->restoreState();
414
        OverlayManager::instance()->reload(OverlayManager::ReloadMode::ReloadResume);
415
    });
416

417
    // support for grouped dragging of dockwidgets
418
    // https://woboq.com/blog/qdockwidget-changes-in-56.html
419
    setDockOptions(dockOptions() | QMainWindow::GroupedDragging);
420

421
    // Create the layout containing the workspace and a tab bar
422
    d->mdiArea = new QMdiArea();
423
    // Movable tabs
424
    d->mdiArea->setTabsMovable(true);
425
    d->mdiArea->setTabPosition(QTabWidget::South);
426
    d->mdiArea->setViewMode(QMdiArea::TabbedView);
427
    auto tab = d->mdiArea->findChild<QTabBar*>();
428
    if (tab) {
429
        tab->setTabsClosable(true);
430
        // The tabs might be very wide
431
        tab->setExpanding(false);
432
        tab->setObjectName(QString::fromLatin1("mdiAreaTabBar"));
433
    }
434
    d->mdiArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
435
    d->mdiArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
436
    d->mdiArea->setOption(QMdiArea::DontMaximizeSubWindowOnActivation, false);
437
    d->mdiArea->setActivationOrder(QMdiArea::ActivationHistoryOrder);
438
    d->mdiArea->setBackground(QBrush(QColor(160,160,160)));
439
    setCentralWidget(d->mdiArea);
440

441
    statusBar()->setObjectName(QString::fromLatin1("statusBar"));
442
    connect(statusBar(), &QStatusBar::messageChanged, this, &MainWindow::statusMessageChanged);
443

444
    // labels and progressbar
445
    d->status = new StatusBarObserver();
446
    d->actionLabel = new QLabel(statusBar());
447
    // d->actionLabel->setMinimumWidth(120);
448

449
    d->sizeLabel = new DimensionWidget(statusBar());
450

451
    statusBar()->addWidget(d->actionLabel, 1);
452
    QProgressBar* progressBar = Gui::SequencerBar::instance()->getProgressBar(statusBar());
453
    statusBar()->addPermanentWidget(progressBar, 0);
454
    statusBar()->addPermanentWidget(d->sizeLabel, 0);
455

456
    auto hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/NotificationArea");
457

458
    auto notificationAreaEnabled = hGrp->GetBool("NotificationAreaEnabled", true);
459

460
    if(notificationAreaEnabled) {
461
        NotificationArea* notificationArea = new NotificationArea(statusBar());
462
        notificationArea->setObjectName(QString::fromLatin1("notificationArea"));
463
        notificationArea->setStyleSheet(QStringLiteral("text-align:left;"));
464
        statusBar()->addPermanentWidget(notificationArea);
465
    }
466
    // clears the action label
467
    d->actionTimer = new QTimer( this );
468
    d->actionTimer->setObjectName(QString::fromLatin1("actionTimer"));
469
    connect(d->actionTimer, &QTimer::timeout, d->actionLabel, &QLabel::clear);
470

471
    // clear status type
472
    d->statusTimer = new QTimer( this );
473
    d->statusTimer->setObjectName(QString::fromLatin1("statusTimer"));
474
    connect(d->statusTimer, &QTimer::timeout, this, &MainWindow::clearStatus);
475

476
    // update gui timer
477
    d->activityTimer = new QTimer(this);
478
    d->activityTimer->setObjectName(QString::fromLatin1("activityTimer"));
479
    connect(d->activityTimer, &QTimer::timeout, this, &MainWindow::_updateActions);
480
    d->activityTimer->setSingleShot(false);
481
    d->activityTimer->start(150);
482

483
    // update view-sensitive commands when clipboard has changed
484
    QClipboard *clipbd = QApplication::clipboard();
485
    connect(clipbd, &QClipboard::dataChanged, this, &MainWindow::updateEditorActions);
486

487
    d->windowMapper = new QSignalMapper(this);
488

489
    // connection between workspace, window menu and tab bar
490
#if QT_VERSION < QT_VERSION_CHECK(5,15,0)
491
    connect(d->windowMapper, qOverload<QWidget*>(&QSignalMapper::mapped),
492
            this, &MainWindow::onSetActiveSubWindow);
493
#elif QT_VERSION < QT_VERSION_CHECK(6,0,0)
494
    connect(d->windowMapper, &QSignalMapper::mappedWidget,
495
            this, &MainWindow::onSetActiveSubWindow);
496
#else
497
    connect(d->windowMapper, &QSignalMapper::mappedObject,
498
            this, [=](QObject* object) {
499
        onSetActiveSubWindow(qobject_cast<QWidget*>(object));
500
    });
501
#endif
502
    connect(d->mdiArea, &QMdiArea::subWindowActivated,
503
            this, &MainWindow::onWindowActivated);
504

505
    setupDockWindows();
506

507
    // accept drops on the window, get handled in dropEvent, dragEnterEvent
508
    setAcceptDrops(true);
509

510
    statusBar()->showMessage(tr("Ready"), 2001);
511
}
512

513
MainWindow::~MainWindow()
514
{
515
    delete d->status;
516
    delete d;
517
    instance = nullptr;
518
}
519

520
MainWindow* MainWindow::getInstance()
521
{
522
    // MainWindow has a public constructor
523
    return instance;
524
}
525

526
// Helper function to update dock widget according to the user parameter
527
// settings, e.g. register/unregister, enable/disable, show/hide.
528
template<class T>
529
static inline void _updateDockWidget(const char *name,
530
                                    bool enabled,
531
                                    bool show,
532
                                    Qt::DockWidgetArea pos,
533
                                    T callback)
534
{
535
    auto pDockMgr = DockWindowManager::instance();
536
    auto widget = pDockMgr->findRegisteredDockWindow(name);
537
    if (!enabled) {
538
        if(widget) {
539
            pDockMgr->removeDockWindow(widget);
540
            pDockMgr->unregisterDockWindow(name);
541
            widget->deleteLater();
542
        }
543
        return;
544
    }
545
    // Use callback to perform specific update for each type of dock widget
546
    widget = callback(widget);
547
    if(!widget)
548
        return;
549
    DockWindowManager::instance()->registerDockWindow(name, widget);
550
    if(show) {
551
        auto dock = pDockMgr->addDockWindow(
552
                widget->objectName().toUtf8().constData(), widget, pos);
553
        if(dock) {
554
            if(!dock->toggleViewAction()->isChecked())
555
                dock->toggleViewAction()->activate(QAction::Trigger);
556
            OverlayManager::instance()->refresh(dock);
557
        }
558
    }
559
}
560

561
void MainWindow::initDockWindows(bool show)
562
{
563
    updateTreeView(show);
564
    updatePropertyView(show);
565
    updateComboView(show);
566
    updateTaskView(show);
567
    updateDAGView(show);
568
}
569

570
void MainWindow::setupDockWindows()
571
{
572
    // Report view must be created before PythonConsole!
573
    setupReportView();
574
    setupPythonConsole();
575
    setupSelectionView();
576
    setupTaskView();
577

578
    initDockWindows(false);
579

580
    std::vector<QTabWidget::TabPosition> tabPos = {QTabWidget::North,
581
                                                   QTabWidget::South,
582
                                                   QTabWidget::West,
583
                                                   QTabWidget::East};
584
    long value = d->hGrp->GetInt("LeftDockWidgetAreaTabPos", long(tabPos.front()));
585
    if (value >= 0 && value < long(tabPos.size())) {
586
        setTabPosition(Qt::LeftDockWidgetArea, tabPos[value]);
587
    }
588
}
589

590
bool MainWindow::setupTaskView()
591
{
592
    // Task view
593
    if (d->hiddenDockWindows.find("Std_TaskView") == std::string::npos) {
594
        auto taskView = new Gui::TaskView::TaskView(this);
595
        taskView->setObjectName
596
            (QString::fromLatin1(QT_TRANSLATE_NOOP("QDockWidget","Tasks")));
597
        taskView->setMinimumWidth(210);
598

599
        DockWindowManager* pDockMgr = DockWindowManager::instance();
600
        pDockMgr->registerDockWindow("Std_TaskView", taskView);
601
        return true;
602
    }
603

604
    return false;
605
}
606

607
bool MainWindow::setupSelectionView()
608
{
609
    // Selection view
610
    if (d->hiddenDockWindows.find("Std_SelectionView") == std::string::npos) {
611
        auto pcSelectionView = new SelectionView(nullptr, this);
612
        pcSelectionView->setObjectName
613
            (QString::fromLatin1(QT_TRANSLATE_NOOP("QDockWidget","Selection view")));
614
        pcSelectionView->setMinimumWidth(210);
615

616
        DockWindowManager* pDockMgr = DockWindowManager::instance();
617
        pDockMgr->registerDockWindow("Std_SelectionView", pcSelectionView);
618
        return true;
619
    }
620

621
    return false;
622
}
623

624
bool MainWindow::setupReportView()
625
{
626
    // Report view
627
    if (d->hiddenDockWindows.find("Std_ReportView") == std::string::npos) {
628
        auto pcReport = new ReportOutput(this);
629
        pcReport->setWindowIcon(BitmapFactory().pixmap("MacroEditor"));
630
        pcReport->setObjectName
631
            (QString::fromLatin1(QT_TRANSLATE_NOOP("QDockWidget","Report view")));
632

633
        DockWindowManager* pDockMgr = DockWindowManager::instance();
634
        pDockMgr->registerDockWindow("Std_ReportView", pcReport);
635

636
        auto rvObserver = new ReportOutputObserver(pcReport);
637
        qApp->installEventFilter(rvObserver);
638
        return true;
639
    }
640

641
    return false;
642
}
643

644
bool MainWindow::setupPythonConsole()
645
{
646
    // Python console
647
    if (d->hiddenDockWindows.find("Std_PythonView") == std::string::npos) {
648
        auto pcPython = new PythonConsole(this);
649
        pcPython->setWindowIcon(Gui::BitmapFactory().iconFromTheme("applications-python"));
650
        pcPython->setObjectName
651
            (QString::fromLatin1(QT_TRANSLATE_NOOP("QDockWidget","Python console")));
652

653
        DockWindowManager* pDockMgr = DockWindowManager::instance();
654
        pDockMgr->registerDockWindow("Std_PythonView", pcPython);
655
        return true;
656
    }
657

658
    return false;
659
}
660

661
bool MainWindow::updateTreeView(bool show)
662
{
663
    if (d->hiddenDockWindows.find("Std_TreeView") == std::string::npos) {
664
        ParameterGrp::handle group = App::GetApplication().GetUserParameter().
665
                GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("DockWindows")->GetGroup("TreeView");
666
        bool enabled = group->GetBool("Enabled", false);
667
        _updateDockWidget("Std_TreeView", enabled, show, Qt::RightDockWidgetArea,
668
            [](QWidget *widget) {
669
                if (widget) {
670
                    return widget;
671
                }
672

673
                auto tree = new TreeDockWidget(0,getMainWindow());
674
                tree->setObjectName(QStringLiteral(QT_TRANSLATE_NOOP("QDockWidget","Tree view")));
675
                tree->setMinimumWidth(210);
676
                widget = tree;
677
                return widget;
678
            });
679

680
        return enabled;
681
    }
682

683
    return false;
684
}
685

686
bool MainWindow::updatePropertyView(bool show)
687
{
688
    // Property view
689
    if (d->hiddenDockWindows.find("Std_PropertyView") == std::string::npos) {
690
        ParameterGrp::handle group = App::GetApplication().GetUserParameter().
691
                GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("DockWindows")->GetGroup("PropertyView");
692
        bool enabled = group->GetBool("Enabled", false);
693
        _updateDockWidget("Std_PropertyView", enabled, show, Qt::RightDockWidgetArea,
694
            [](QWidget *widget) {
695
                if (widget) {
696
                    return widget;
697
                }
698

699
                auto pcPropView = new PropertyDockView(0, getMainWindow());
700
                pcPropView->setObjectName(QStringLiteral(QT_TRANSLATE_NOOP("QDockWidget","Property view")));
701
                pcPropView->setMinimumWidth(210);
702
                widget = pcPropView;
703
                return widget;
704
            });
705

706
        return enabled;
707
    }
708

709
    return false;
710
}
711

712
bool MainWindow::updateTaskView(bool show)
713
{
714
    //Task List (task watcher).
715
    if (d->hiddenDockWindows.find("Std_TaskWatcher") == std::string::npos) {
716
        //work through parameter.
717
        ParameterGrp::handle group = App::GetApplication().GetUserParameter().
718
              GetGroup("BaseApp/Preferences/DockWindows/TaskWatcher");
719
        bool enabled = group->GetBool("Enabled", false);
720
        group->SetBool("Enabled", enabled); //ensure entry exists.
721
        _updateDockWidget("Std_TaskWatcher", enabled, show, Qt::RightDockWidgetArea,
722
            [](QWidget *widget) {
723
                if (widget) {
724
                    return widget;
725
                }
726

727
                widget = new TaskView::TaskView(getMainWindow());
728
                widget->setObjectName(QStringLiteral(QT_TRANSLATE_NOOP("QDockWidget","Task List")));
729
                return widget;
730
            });
731

732
        return enabled;
733
    }
734

735
    return false;
736
}
737

738
bool MainWindow::updateComboView(bool show)
739
{
740
    // Combo view
741
    if (d->hiddenDockWindows.find("Std_ComboView") == std::string::npos) {
742
        ParameterGrp::handle group = App::GetApplication().GetUserParameter().
743
                GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("DockWindows")->GetGroup("ComboView");
744
        bool enable = group->GetBool("Enabled", true);
745
        _updateDockWidget("Std_ComboView", enable, show, Qt::LeftDockWidgetArea,
746
            [](QWidget *widget) {
747
                auto pcComboView = qobject_cast<ComboView*>(widget);
748
                if (widget) {
749
                    return widget;
750
                }
751

752
                pcComboView = new ComboView(nullptr, getMainWindow());
753
                pcComboView->setObjectName(QStringLiteral(QT_TRANSLATE_NOOP("QDockWidget", "Model")));
754
                pcComboView->setMinimumWidth(150);
755
                widget = pcComboView;
756
                return widget;
757
            });
758

759
        return enable;
760
    }
761

762
    return false;
763
}
764

765
bool MainWindow::updateDAGView(bool show)
766
{
767
    //Dag View.
768
    if (d->hiddenDockWindows.find("Std_DAGView") == std::string::npos) {
769
        ParameterGrp::handle group = App::GetApplication().GetUserParameter().
770
              GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("DockWindows")->GetGroup("DAGView");
771
        bool enabled = group->GetBool("Enabled", false);
772
        _updateDockWidget("Std_DAGView", enabled, show, Qt::RightDockWidgetArea,
773
            [](QWidget *widget) {
774
                if (widget) {
775
                    return widget;
776
                }
777

778
                auto dagDockWindow = new DAG::DockWindow(nullptr, getMainWindow());
779
                dagDockWindow->setObjectName(QStringLiteral(QT_TRANSLATE_NOOP("QDockWidget","DAG View")));
780
                widget = dagDockWindow;
781
                return widget;
782
            });
783

784
        return enabled;
785
    }
786

787
    return false;
788
}
789

790
QMenu* MainWindow::createPopupMenu ()
791
{
792
    QMenu* menu = QMainWindow::createPopupMenu();
793
    Workbench* wb = WorkbenchManager::instance()->active();
794
    if (wb) {
795
        MenuItem item;
796
        wb->createMainWindowPopupMenu(&item);
797
        if (item.hasItems()) {
798
            menu->addSeparator();
799
            QList<MenuItem*> items = item.getItems();
800
            for (const auto & item : items) {
801
                if (item->command() == "Separator") {
802
                    menu->addSeparator();
803
                }
804
                else {
805
                    Command* cmd = Application::Instance->commandManager().getCommandByName(item->command().c_str());
806
                    if (cmd) cmd->addTo(menu);
807
                }
808
            }
809
        }
810
    }
811

812
    return menu;
813
}
814

815
void MainWindow::tile()
816
{
817
    d->mdiArea->tileSubWindows();
818
}
819

820
void MainWindow::cascade()
821
{
822
    d->mdiArea->cascadeSubWindows();
823
}
824

825
void MainWindow::closeActiveWindow ()
826
{
827
    d->mdiArea->closeActiveSubWindow();
828
}
829

830
int MainWindow::confirmSave(const char *docName, QWidget *parent, bool addCheckbox) {
831
    QMessageBox box(parent?parent:this);
832
    box.setIcon(QMessageBox::Question);
833
    box.setWindowTitle(QObject::tr("Unsaved document"));
834
    if(docName)
835
        box.setText(QObject::tr("Do you want to save your changes to document '%1' before closing?")
836
                    .arg(QString::fromUtf8(docName)));
837
    else
838
        box.setText(QObject::tr("Do you want to save your changes to document before closing?"));
839

840
    box.setInformativeText(QObject::tr("If you don't save, your changes will be lost."));
841
    box.setStandardButtons(QMessageBox::Discard | QMessageBox::Cancel | QMessageBox::Save);
842
    box.setDefaultButton(QMessageBox::Save);
843
    box.setEscapeButton(QMessageBox::Cancel);
844

845
    QCheckBox checkBox(QObject::tr("Apply answer to all"));
846
    ParameterGrp::handle hGrp;
847
    if(addCheckbox) {
848
         hGrp = App::GetApplication().GetUserParameter().
849
            GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("General");
850
        checkBox.setChecked(hGrp->GetBool("ConfirmAll",false));
851
        checkBox.blockSignals(true);
852
        box.addButton(&checkBox, QMessageBox::ResetRole);
853
    }
854

855
    // add shortcuts
856
    QAbstractButton* saveBtn = box.button(QMessageBox::Save);
857
    if (saveBtn->shortcut().isEmpty()) {
858
        QString text = saveBtn->text();
859
        text.prepend(QLatin1Char('&'));
860
        saveBtn->setShortcut(QKeySequence::mnemonic(text));
861
    }
862

863
    QAbstractButton* discardBtn = box.button(QMessageBox::Discard);
864
    if (discardBtn->shortcut().isEmpty()) {
865
        QString text = discardBtn->text();
866
        text.prepend(QLatin1Char('&'));
867
        discardBtn->setShortcut(QKeySequence::mnemonic(text));
868
    }
869

870
    int res = ConfirmSaveResult::Cancel;
871
    box.adjustSize(); // Silence warnings from Qt on Windows
872
    switch (box.exec())
873
    {
874
    case QMessageBox::Save:
875
        res = checkBox.isChecked()?ConfirmSaveResult::SaveAll:ConfirmSaveResult::Save;
876
        break;
877
    case QMessageBox::Discard:
878
        res = checkBox.isChecked()?ConfirmSaveResult::DiscardAll:ConfirmSaveResult::Discard;
879
        break;
880
    }
881
    if(addCheckbox && res)
882
        hGrp->SetBool("ConfirmAll",checkBox.isChecked());
883
    return res;
884
}
885

886
bool MainWindow::closeAllDocuments (bool close)
887
{
888
    auto docs = App::GetApplication().getDocuments();
889
    try {
890
        docs = App::Document::getDependentDocuments(docs, true);
891
    }
892
    catch(Base::Exception &e) {
893
        e.ReportException();
894
    }
895

896
    bool checkModify = true;
897
    bool saveAll = false;
898
    int failedSaves = 0;
899

900
    for (auto doc : docs) {
901
        auto gdoc = Application::Instance->getDocument(doc);
902
        if (!gdoc)
903
            continue;
904
        if (!gdoc->canClose(false))
905
            return false;
906
        if (!gdoc->isModified()
907
                || doc->testStatus(App::Document::PartialDoc)
908
                || doc->testStatus(App::Document::TempDoc))
909
            continue;
910
        bool save = saveAll;
911
        if (!save && checkModify) {
912
            int res = confirmSave(doc->Label.getStrValue().c_str(), this, docs.size()>1);
913
            switch (res)
914
            {
915
            case ConfirmSaveResult::Cancel:
916
                return false;
917
            case ConfirmSaveResult::SaveAll:
918
                saveAll = true;
919
                /* FALLTHRU */
920
            case ConfirmSaveResult::Save:
921
                save = true;
922
                break;
923
            case ConfirmSaveResult::DiscardAll:
924
                checkModify = false;
925
            }
926
        }
927

928
        if (save && !gdoc->save())
929
            failedSaves++;
930
    }
931

932
    if (failedSaves > 0) {
933
        int ret = QMessageBox::question(
934
            getMainWindow(),
935
            QObject::tr("%1 Document(s) not saved").arg(QString::number(failedSaves)),
936
            QObject::tr("Some documents could not be saved. Do you want to cancel closing?"),
937
            QMessageBox::Discard | QMessageBox::Cancel,
938
            QMessageBox::Discard);
939
        if (ret == QMessageBox::Cancel)
940
            return false;
941
    }
942

943
    if (close)
944
        App::GetApplication().closeAllDocuments();
945

946
    return true;
947
}
948

949
void MainWindow::activateNextWindow ()
950
{
951
    auto tab = d->mdiArea->findChild<QTabBar*>();
952
    if (tab && tab->count() > 0) {
953
        int index = (tab->currentIndex() + 1) % tab->count();
954
        tab->setCurrentIndex(index);
955
    }
956
}
957

958
void MainWindow::activatePreviousWindow ()
959
{
960
    auto tab = d->mdiArea->findChild<QTabBar*>();
961
    if (tab && tab->count() > 0) {
962
        int index = (tab->currentIndex() + tab->count() - 1) % tab->count();
963
        tab->setCurrentIndex(index);
964
    }
965
}
966

967
void MainWindow::activateWorkbench(const QString& name)
968
{
969
    ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/View");
970
    bool saveWB = hGrp->GetBool("SaveWBbyTab", false);
971
    QMdiSubWindow* subWin = d->mdiArea->activeSubWindow();
972
    if (subWin && saveWB) {
973
        QString currWb = subWin->property("ownWB").toString();
974
        if (currWb.isEmpty() || currWb != name) {
975
            subWin->setProperty("ownWB", name);
976
        }
977
    }
978
    // emit this signal
979
    Q_EMIT workbenchActivated(name);
980
    updateActions(true);
981
}
982

983
void MainWindow::whatsThis()
984
{
985
    QWhatsThis::enterWhatsThisMode();
986
}
987

988
void MainWindow::showDocumentation(const QString& help)
989
{
990
    Base::PyGILStateLocker lock;
991
    PyObject* module = PyImport_ImportModule("Help");
992
    if (module) {
993
        Py_DECREF(module);
994
        Gui::Command::addModule(Gui::Command::Gui,"Help");
995
        Gui::Command::doCommand(Gui::Command::Gui,"Help.show(\"%s\")", help.toStdString().c_str());
996
    }
997
    else {
998
        PyErr_Clear();
999
        QUrl url(help);
1000
        if (url.scheme().isEmpty()) {
1001
            QMessageBox msgBox(getMainWindow());
1002
            msgBox.setWindowTitle(tr("Help addon needed!"));
1003
            msgBox.setText(tr("The Help system of %1 is now handled by the \"Help\" addon. "
1004
               "It can easily be installed via the Addons Manager").arg(QString(qApp->applicationName())));
1005
            QAbstractButton* pButtonAddonMgr = msgBox.addButton(tr("Open Addon Manager"), QMessageBox::YesRole);
1006
            msgBox.addButton(QMessageBox::Ok);
1007
            msgBox.exec();
1008
            if (msgBox.clickedButton() == pButtonAddonMgr) {
1009
                ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Addons");
1010
                hGrp->SetASCII("SelectedAddon", "Help");
1011
                Gui::Command::doCommand(Gui::Command::Gui,"Gui.runCommand('Std_AddonMgr',0)");
1012
            }
1013
        }
1014
        else {
1015
            QDesktopServices::openUrl(url);
1016
        }
1017
    }
1018
}
1019

1020
bool MainWindow::event(QEvent *e)
1021
{
1022
    if (e->type() == QEvent::EnterWhatsThisMode) {
1023
        // Unfortunately, for top-level widgets such as menus or dialogs we
1024
        // won't be notified when the user clicks the link in the hypertext of
1025
        // the what's this text. Thus, we have to install the main window to
1026
        // the application to observe what happens in eventFilter().
1027
        d->whatstext.clear();
1028
        if (!d->whatsthis) {
1029
            d-> whatsthis = true;
1030
            qApp->installEventFilter(this);
1031
        }
1032
    }
1033
    else if (e->type() == QEvent::LeaveWhatsThisMode) {
1034
        // Here we can't do anything because this event is sent
1035
        // before the WhatThisClicked event is sent. Thus, we handle
1036
        // this in eventFilter().
1037
    }
1038
    else if (e->type() == QEvent::WhatsThisClicked) {
1039
        auto wt = static_cast<QWhatsThisClickedEvent*>(e);
1040
        showDocumentation(wt->href());
1041
    }
1042
    else if (e->type() == QEvent::ApplicationWindowIconChange) {
1043
        // if application icon changes apply it to the main window and the "About..." dialog
1044
        this->setWindowIcon(QApplication::windowIcon());
1045
        Command* about = Application::Instance->commandManager().getCommandByName("Std_About");
1046
        if (about) {
1047
            Action* action = about->getAction();
1048
            if (action) action->setIcon(QApplication::windowIcon());
1049
        }
1050
    }
1051
    else if (e->type() == Spaceball::ButtonEvent::ButtonEventType) {
1052
        auto buttonEvent = dynamic_cast<Spaceball::ButtonEvent *>(e);
1053
        if (!buttonEvent)
1054
            return true;
1055
        buttonEvent->setHandled(true);
1056
        //only going to respond to button press events.
1057
        if (buttonEvent->buttonStatus() != Spaceball::BUTTON_PRESSED)
1058
            return true;
1059
        ParameterGrp::handle group = App::GetApplication().GetUserParameter().GetGroup("BaseApp")->
1060
                GetGroup("Spaceball")->GetGroup("Buttons");
1061
        QByteArray groupName(QVariant(buttonEvent->buttonNumber()).toByteArray());
1062
        if (group->HasGroup(groupName.data())) {
1063
            ParameterGrp::handle commandGroup = group->GetGroup(groupName.data());
1064
            std::string commandName(commandGroup->GetASCII("Command"));
1065
            if (commandName.empty())
1066
                return true;
1067
            else
1068
                Application::Instance->commandManager().runCommandByName(commandName.c_str());
1069
        }
1070
        else
1071
            return true;
1072
    }
1073
    else if (e->type() == Spaceball::MotionEvent::MotionEventType) {
1074
        auto motionEvent = dynamic_cast<Spaceball::MotionEvent *>(e);
1075
        if (!motionEvent)
1076
            return true;
1077
        motionEvent->setHandled(true);
1078
        Gui::Document *doc = Application::Instance->activeDocument();
1079
        if (!doc)
1080
            return true;
1081
        auto temp = dynamic_cast<View3DInventor *>(doc->getActiveView());
1082
        if (!temp)
1083
            return true;
1084
        View3DInventorViewer *view = temp->getViewer();
1085
        if (view) {
1086
            Spaceball::MotionEvent anotherEvent(*motionEvent);
1087
            qApp->sendEvent(view, &anotherEvent);
1088
        }
1089
        return true;
1090
    }else if(e->type() == QEvent::StatusTip) {
1091
        // make sure warning and error message don't get blocked by tooltips
1092
        if(std::abs(d->currentStatusType) <= MainWindow::Wrn)
1093
            return true;
1094
    }
1095
    return QMainWindow::event(e);
1096
}
1097

1098
bool MainWindow::eventFilter(QObject* o, QEvent* e)
1099
{
1100
    if (o != this) {
1101
        if (e->type() == QEvent::WindowStateChange) {
1102
            // notify all mdi views when the active view receives a show normal, show minimized
1103
            // or show maximized event
1104
            auto view = qobject_cast<MDIView*>(o);
1105
            if (view) { // emit this signal
1106
                Qt::WindowStates oldstate = static_cast<QWindowStateChangeEvent*>(e)->oldState();
1107
                Qt::WindowStates newstate = view->windowState();
1108
                if (oldstate != newstate)
1109
                    Q_EMIT windowStateChanged(view);
1110
            }
1111
        }
1112

1113
        // We don't want to show the bubble help for the what's this text but want to
1114
        // start the help viewer with the according key word.
1115
        // Thus, we have to observe WhatThis events if called for a widget, use its text and
1116
        // must avoid to make the bubble widget visible.
1117
        if (e->type() == QEvent::WhatsThis) {
1118
            if (!o->isWidgetType())
1119
                return false;
1120
            // clicked on a widget in what's this mode
1121
            auto w = static_cast<QWidget *>(o);
1122
            d->whatstext = w->whatsThis();
1123
        }
1124
        if (e->type() == QEvent::WhatsThisClicked) {
1125
            // if the widget is a top-level window
1126
            if (o->isWidgetType() && qobject_cast<QWidget*>(o)->isWindow()) {
1127
                // re-direct to the widget
1128
                QApplication::sendEvent(this, e);
1129
            }
1130
        }
1131
        // special treatment for menus because they directly call QWhatsThis::showText()
1132
        // whereby we must be informed for which action the help should be shown
1133
        if (o->inherits("QMenu") && QWhatsThis::inWhatsThisMode()) {
1134
            bool whatthis = false;
1135
            if (e->type() == QEvent::KeyPress) {
1136
                auto ke = static_cast<QKeyEvent*>(e);
1137
                if (ke->key() == Qt::Key_Return || ke->key() == Qt::Key_Enter || ke->key() == Qt::Key_F1)
1138
                    whatthis = true;
1139
            }
1140
            else if (e->type() == QEvent::MouseButtonRelease)
1141
                whatthis = true;
1142
            else if (e->type() == QEvent::EnterWhatsThisMode)
1143
                whatthis = true;
1144
            if (whatthis) {
1145
                QAction* cur = static_cast<QMenu*>(o)->activeAction();
1146
                if (cur) {
1147
                    // get the help text for later usage
1148
                    QString s = cur->whatsThis();
1149
                    if (s.isEmpty())
1150
                        s = static_cast<QMenu*>(o)->whatsThis();
1151
                    d->whatstext = s;
1152
                }
1153
            }
1154
        }
1155
        if (o->inherits("QWhatsThat") && e->type() == QEvent::Show) {
1156
            // the bubble help should become visible which we avoid by marking the widget
1157
            // that it is out of range. Instead of, we show the help viewer
1158
            if (!d->whatstext.isEmpty()) {
1159
                QWhatsThisClickedEvent e(d->whatstext);
1160
                QApplication::sendEvent(this, &e);
1161
            }
1162
            static_cast<QWidget *>(o)->setAttribute(Qt::WA_OutsideWSRange);
1163
            o->deleteLater();
1164
            return true;
1165
        }
1166
        if (o->inherits("QWhatsThat") && e->type() == QEvent::Hide) {
1167
            // leave what's this mode
1168
            if (d->whatsthis) {
1169
                d->whatsthis = false;
1170
                d->whatstext.clear();
1171
                qApp->removeEventFilter(this);
1172
            }
1173
        }
1174
    }
1175

1176
    return QMainWindow::eventFilter(o, e);
1177
}
1178

1179
void MainWindow::addWindow(MDIView* view)
1180
{
1181
    // make workspace parent of view
1182
    bool isempty = d->mdiArea->subWindowList().isEmpty();
1183
    auto child = qobject_cast<QMdiSubWindow*>(view->parentWidget());
1184
    if(!child) {
1185
        child = new QMdiSubWindow(d->mdiArea->viewport());
1186
        child->setAttribute(Qt::WA_DeleteOnClose);
1187
        child->setWidget(view);
1188
        child->setWindowIcon(view->windowIcon());
1189
        QMenu* menu = child->systemMenu();
1190

1191
        // See StdCmdCloseActiveWindow (#0002631)
1192
        QList<QAction*> acts = menu->actions();
1193
        for (auto & act : acts) {
1194
            if (act->shortcut() == QKeySequence(QKeySequence::Close)) {
1195
                act->setShortcuts(QList<QKeySequence>());
1196
                break;
1197
            }
1198
        }
1199

1200
        QAction* action = menu->addAction(tr("Close All"));
1201
        connect(action, &QAction::triggered, d->mdiArea, &QMdiArea::closeAllSubWindows);
1202
        d->mdiArea->addSubWindow(child);
1203
    }
1204

1205
    connect(view, &MDIView::message, this, &MainWindow::showMessage);
1206
    connect(this, &MainWindow::windowStateChanged, view, &MDIView::windowStateChanged);
1207

1208
    // listen to the incoming events of the view
1209
    view->installEventFilter(this);
1210

1211
    // show the very first window in maximized mode
1212
    if (isempty)
1213
        view->showMaximized();
1214
    else
1215
        view->show();
1216
}
1217

1218
/**
1219
 * Removes the instance of Gui::MDiView from the main window and sends am event
1220
 * to the parent widget, a QMdiSubWindow to delete itself.
1221
 * If you want to avoid that the Gui::MDIView instance gets destructed too you
1222
 * must reparent it afterwards, e.g. set parent to NULL.
1223
 */
1224
void MainWindow::removeWindow(Gui::MDIView* view, bool close)
1225
{
1226
    // free all connections
1227
    disconnect(view, &MDIView::message, this, &MainWindow::showMessage);
1228
    disconnect(this, &MainWindow::windowStateChanged, view, &MDIView::windowStateChanged);
1229

1230
    view->removeEventFilter(this);
1231

1232
    // check if the focus widget is a child of the view
1233
    QWidget* foc = this->focusWidget();
1234
    if (foc) {
1235
        QWidget* par = foc->parentWidget();
1236
        while (par) {
1237
            if (par == view) {
1238
                foc->clearFocus();
1239
                break;
1240
            }
1241
            par = par->parentWidget();
1242
        }
1243
    }
1244

1245
    QWidget* parent = view->parentWidget();
1246

1247
    // The call of 'd->mdiArea->removeSubWindow(parent)' causes the QMdiSubWindow
1248
    // to lose its parent and thus the notification in QMdiSubWindow::closeEvent
1249
    // of other mdi windows to get maximized if this window is maximized will fail.
1250
    // However, we must let it here otherwise deleting MDI child views directly can
1251
    // cause other problems.
1252
    //
1253
    // The above mentioned problem can be fixed by setParent(0) which triggers a
1254
    // ChildRemoved event being handled properly inside QMidArea::viewportEvent()
1255
    //
1256
    auto subwindow = qobject_cast<QMdiSubWindow*>(parent);
1257
    if(subwindow && d->mdiArea->subWindowList().contains(subwindow)) {
1258
        subwindow->setParent(nullptr);
1259

1260
        assert(!d->mdiArea->subWindowList().contains(subwindow));
1261
        // d->mdiArea->removeSubWindow(parent);
1262
    }
1263

1264
    if(close)
1265
        parent->deleteLater();
1266
    updateActions();
1267
}
1268

1269
void MainWindow::tabChanged(MDIView* view)
1270
{
1271
    Q_UNUSED(view)
1272
    updateActions();
1273
}
1274

1275
void MainWindow::tabCloseRequested(int index)
1276
{
1277
    auto tab = d->mdiArea->findChild<QTabBar*>();
1278
    if (index < 0 || index >= tab->count())
1279
        return;
1280

1281
    const QList<QMdiSubWindow *> subWindows = d->mdiArea->subWindowList();
1282
    Q_ASSERT(index < subWindows.size());
1283

1284
    QMdiSubWindow *subWindow = d->mdiArea->subWindowList().at(index);
1285
    Q_ASSERT(subWindow);
1286
    subWindow->close();
1287
    updateActions();
1288
}
1289

1290
void MainWindow::onSetActiveSubWindow(QWidget *window)
1291
{
1292
    if (!window)
1293
        return;
1294
    d->mdiArea->setActiveSubWindow(qobject_cast<QMdiSubWindow *>(window));
1295
    updateActions();
1296
}
1297

1298
void MainWindow::setActiveWindow(MDIView* view)
1299
{
1300
    if (!view || d->activeView == view)
1301
        return;
1302
    onSetActiveSubWindow(view->parentWidget());
1303
    d->activeView = view;
1304
    Application::Instance->viewActivated(view);
1305
}
1306

1307
void MainWindow::onWindowActivated(QMdiSubWindow* mdi)
1308
{
1309
    if (!mdi) {
1310
        setWindowTitle(QString());
1311
        setWindowModified(false);
1312
        return;
1313
    }
1314

1315
    auto view = dynamic_cast<MDIView*>(mdi->widget());
1316

1317
    // set active the appropriate window (it needs not to be part of mdiIds, e.g. directly after creation)
1318
    if (view)
1319
    {
1320
        d->activeView = view;
1321
        Application::Instance->viewActivated(view);
1322
    }
1323

1324
    ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/View");
1325
    bool saveWB = hGrp->GetBool("SaveWBbyTab", false);
1326
    if (saveWB) {
1327
        QString currWb = mdi->property("ownWB").toString();
1328
        if (! currWb.isEmpty()) {
1329
            this->activateWorkbench(currWb);
1330
        }
1331
        else {
1332
            mdi->setProperty("ownWB", QString::fromStdString(WorkbenchManager::instance()->active()->name()));
1333
        }
1334
    }
1335

1336
    // Even if windowActivated() signal is emitted mdi doesn't need to be a top-level window.
1337
    // This happens e.g. if two windows are top-level and one of them gets docked again.
1338
    // QWorkspace emits the signal then even though the other window is in front.
1339
    // The consequence is that the docked window becomes the active window and not the undocked
1340
    // window on top. This means that all accel events, menu and toolbar actions get redirected
1341
    // to the (wrong) docked window.
1342
    // But just testing whether the view is active and ignore it if not leads to other more serious problems -
1343
    // at least under Linux. It seems to be a problem with the window manager.
1344
    // Under Windows it seems to work though it's not really sure that it works reliably.
1345
    // Result: So, we accept the first problem to be sure to avoid the second one.
1346
    if ( !view /*|| !mdi->isActiveWindow()*/ )
1347
        return; // either no MDIView or no valid object or no top-level window
1348

1349
    updateActions(true);
1350
}
1351

1352
void MainWindow::onWindowsMenuAboutToShow()
1353
{
1354
    QList<QMdiSubWindow*> windows = d->mdiArea->subWindowList(QMdiArea::CreationOrder);
1355
    QWidget* active = d->mdiArea->activeSubWindow();
1356

1357
    // We search for the 'Std_WindowsMenu' command that provides the list of actions
1358
    CommandManager& cMgr = Application::Instance->commandManager();
1359
    Command* cmd = cMgr.getCommandByName("Std_WindowsMenu");
1360
    QList<QAction*> actions = qobject_cast<ActionGroup*>(cmd->getAction())->actions();
1361

1362
    // do the connection only once
1363
    static bool firstShow = true;
1364
    if (firstShow) {
1365
        firstShow = false;
1366
        QAction* last = actions.isEmpty() ? 0 : actions.last();
1367
        for (const auto & action : actions) {
1368
            if (action == last)
1369
                break; // this is a separator
1370
            connect(action, &QAction::triggered, d->windowMapper, qOverload<>(&QSignalMapper::map));
1371
        }
1372
    }
1373

1374
    int numWindows = std::min<int>(actions.count()-1, windows.count());
1375
    for (int index = 0; index < numWindows; index++) {
1376
        QWidget* child = windows.at(index);
1377
        QAction* action = actions.at(index);
1378
        QString text;
1379
        QString title = child->windowTitle();
1380
        int lastIndex = title.lastIndexOf(QString::fromLatin1("[*]"));
1381
        if (lastIndex > 0) {
1382
            title = title.left(lastIndex);
1383
            if (child->isWindowModified())
1384
                title = QString::fromLatin1("%1*").arg(title);
1385
        }
1386
        if (index < 9)
1387
            text = QString::fromLatin1("&%1 %2").arg(index+1).arg(title);
1388
        else
1389
            text = QString::fromLatin1("%1 %2").arg(index+1).arg(title);
1390
        action->setText(text);
1391
        action->setVisible(true);
1392
        action->setChecked(child == active);
1393
        d->windowMapper->setMapping(action, child);
1394
    }
1395

1396
    // if less windows than actions
1397
    for (int index = numWindows; index < actions.count(); index++)
1398
        actions[index]->setVisible(false);
1399
    // show the separator
1400
    if (numWindows > 0)
1401
        actions.last()->setVisible(true);
1402
}
1403

1404
void MainWindow::onToolBarMenuAboutToShow()
1405
{
1406
    auto menu = static_cast<QMenu*>(sender());
1407
    menu->clear();
1408
    QList<QToolBar*> dock = this->findChildren<QToolBar*>();
1409
    for (const auto & it : dock) {
1410
        if (it->parentWidget() == this) {
1411
            QAction* action = it->toggleViewAction();
1412
            action->setToolTip(tr("Toggles this toolbar"));
1413
            action->setStatusTip(tr("Toggles this toolbar"));
1414
            action->setWhatsThis(tr("Toggles this toolbar"));
1415
            menu->addAction(action);
1416
        }
1417
    }
1418

1419
    menu->addSeparator();
1420

1421
    Application::Instance->commandManager().getCommandByName("Std_ToggleToolBarLock")->addTo(menu);
1422
}
1423

1424
void MainWindow::onDockWindowMenuAboutToShow()
1425
{
1426
    auto menu = static_cast<QMenu*>(sender());
1427
    menu->clear();
1428
    QList<QDockWidget*> dock = this->findChildren<QDockWidget*>();
1429
    for (auto & it : dock) {
1430
        QAction* action = it->toggleViewAction();
1431
        action->setToolTip(tr("Toggles this dockable window"));
1432
        action->setStatusTip(tr("Toggles this dockable window"));
1433
        action->setWhatsThis(tr("Toggles this dockable window"));
1434
        menu->addAction(action);
1435
    }
1436
}
1437

1438
void MainWindow::setDockWindowMenu(QMenu* menu)
1439
{
1440
    connect(menu, &QMenu::aboutToShow, this, &MainWindow::onDockWindowMenuAboutToShow);
1441
}
1442

1443
void MainWindow::setToolBarMenu(QMenu* menu)
1444
{
1445
    connect(menu, &QMenu::aboutToShow, this, &MainWindow::onToolBarMenuAboutToShow);
1446
}
1447

1448
void MainWindow::setWindowsMenu(QMenu* menu)
1449
{
1450
    connect(menu, &QMenu::aboutToShow, this, &MainWindow::onWindowsMenuAboutToShow);
1451
}
1452

1453
QList<QWidget*> MainWindow::windows(QMdiArea::WindowOrder order) const
1454
{
1455
    QList<QWidget*> mdis;
1456
    QList<QMdiSubWindow*> wnds = d->mdiArea->subWindowList(order);
1457
    for (const auto & wnd : wnds) {
1458
        mdis << wnd->widget();
1459
    }
1460
    return mdis;
1461
}
1462

1463
MDIView* MainWindow::activeWindow() const
1464
{
1465
    // each activated window notifies this main window when it is activated
1466
    return d->activeView;
1467
}
1468

1469
void MainWindow::closeEvent (QCloseEvent * e)
1470
{
1471
    Application::Instance->tryClose(e);
1472
    if (e->isAccepted()) {
1473
        // Send close event to all non-modal dialogs
1474
        QList<QDialog*> dialogs = this->findChildren<QDialog*>();
1475
        // It is possible that closing a dialog internally closes further dialogs. Thus,
1476
        // we have to check the pointer before.
1477
        QVector< QPointer<QDialog> > dialogs_ptr;
1478
        for (const auto & dialog : dialogs) {
1479
            dialogs_ptr.append(dialog);
1480
        }
1481
        for (auto & it : dialogs_ptr) {
1482
            if (!it.isNull())
1483
                it->close();
1484
        }
1485
        QList<MDIView*> mdis = this->findChildren<MDIView*>();
1486
        // Force to close any remaining (passive) MDI child views
1487
        for (auto & mdi : mdis) {
1488
            mdi->hide();
1489
            mdi->deleteLater();
1490
        }
1491

1492
        if (Workbench* wb = WorkbenchManager::instance()->active())
1493
            wb->removeTaskWatcher();
1494

1495
        Q_EMIT  mainWindowClosed();
1496
        d->activityTimer->stop();
1497

1498
        // https://forum.freecad.org/viewtopic.php?f=8&t=67748
1499
        // When the session manager jumps in it can happen that the closeEvent()
1500
        // function is triggered twice and for the second call the main window might be
1501
        // invisible. In this case the window settings shouldn't be saved.
1502
        if (isVisible())
1503
            saveWindowSettings();
1504

1505
        delete d->assistant;
1506
        d->assistant = nullptr;
1507

1508
        // See createMimeDataFromSelection
1509
        QVariant prop = this->property("x-documentobject-file");
1510
        if (!prop.isNull()) {
1511
            Base::FileInfo fi((const char*)prop.toByteArray());
1512
            if (fi.exists())
1513
                fi.deleteFile();
1514
        }
1515

1516
        if (this->property("QuitOnClosed").isValid()) {
1517
            QApplication::closeAllWindows();
1518
            qApp->quit(); // stop the event loop
1519
        }
1520
    }
1521
}
1522

1523
void MainWindow::showEvent(QShowEvent* e)
1524
{
1525
    std::clog << "Show main window" << std::endl;
1526
    QMainWindow::showEvent(e);
1527
}
1528

1529
void MainWindow::hideEvent(QHideEvent* e)
1530
{
1531
    std::clog << "Hide main window" << std::endl;
1532
    QMainWindow::hideEvent(e);
1533
}
1534

1535
void MainWindow::processMessages(const QList<QByteArray> & msg)
1536
{
1537
    // handle all the messages to open files
1538
    try {
1539
        WaitCursor wc;
1540
        std::list<std::string> files;
1541
        QByteArray action("OpenFile:");
1542
        for (const auto & it : msg) {
1543
            if (it.startsWith(action))
1544
                files.emplace_back(it.mid(action.size()).constData());
1545
        }
1546
        files = App::Application::processFiles(files);
1547
        for (const auto & file : files) {
1548
            QString filename = QString::fromUtf8(file.c_str(), file.size());
1549
            FileDialog::setWorkingDirectory(filename);
1550
        }
1551
    }
1552
    catch (const Base::SystemExitException&) {
1553
    }
1554
}
1555

1556
void MainWindow::delayedStartup()
1557
{
1558
    // automatically run unit tests in Gui
1559
    if (App::Application::Config()["RunMode"] == "Internal") {
1560
        QTimer::singleShot(1000, this, []{
1561
            try {
1562
                Base::Interpreter().runString(
1563
                    "import sys\n"
1564
                    "import FreeCAD\n"
1565
                    "import QtUnitGui\n\n"
1566
                    "testCase = FreeCAD.ConfigGet(\"TestCase\")\n"
1567
                    "QtUnitGui.addTest(testCase)\n"
1568
                    "QtUnitGui.setTest(testCase)\n"
1569
                    "result = QtUnitGui.runTest()\n"
1570
                    "sys.stdout.flush()\n"
1571
                    "sys.exit(0 if result else 1)");
1572
            }
1573
            catch (const Base::SystemExitException&) {
1574
                throw;
1575
            }
1576
            catch (const Base::Exception& e) {
1577
                e.ReportException();
1578
            }
1579
        });
1580
        return;
1581
    }
1582

1583
    // processing all command line files
1584
    try {
1585
        std::list<std::string> files = App::Application::getCmdLineFiles();
1586
        files = App::Application::processFiles(files);
1587
        for (const auto & file : files) {
1588
            QString filename = QString::fromUtf8(file.c_str(), file.size());
1589
            FileDialog::setWorkingDirectory(filename);
1590
        }
1591
    }
1592
    catch (const Base::SystemExitException&) {
1593
        throw;
1594
    }
1595

1596
    const std::map<std::string,std::string>& cfg = App::Application::Config();
1597
    auto it = cfg.find("StartHidden");
1598
    if (it != cfg.end()) {
1599
        QApplication::quit();
1600
        return;
1601
    }
1602

1603
    // TODO: Check for deprecated settings
1604
    Application::Instance->checkForDeprecatedSettings();
1605

1606
    // Create new document?
1607
    ParameterGrp::handle hGrp = WindowParameter::getDefaultParameter()->GetGroup("Document");
1608
    if (hGrp->GetBool("CreateNewDoc", false)) {
1609
        if (App::GetApplication().getDocuments().empty()){
1610
            Application::Instance->commandManager().runCommandByName("Std_New");
1611
        }
1612
    }
1613

1614
    if (hGrp->GetBool("RecoveryEnabled", true)) {
1615
        Application::Instance->checkForPreviousCrashes();
1616
    }
1617
}
1618

1619
void MainWindow::appendRecentFile(const QString& filename)
1620
{
1621
    auto recent = this->findChild<RecentFilesAction *>
1622
        (QString::fromLatin1("recentFiles"));
1623
    if (recent) {
1624
        recent->appendFile(filename);
1625
    }
1626
}
1627

1628
void MainWindow::appendRecentMacro(const QString& filename)
1629
{
1630
    auto recent = this->findChild<RecentMacrosAction *>
1631
        (QString::fromLatin1("recentMacros"));
1632
    if (recent) {
1633
        recent->appendFile(filename);
1634
    }
1635
}
1636

1637
void MainWindow::updateActions(bool delay)
1638
{
1639
    //make it safe to call before the main window is actually created
1640
    if (!instance)
1641
        return;
1642

1643
    if (!d->activityTimer->isActive()) {
1644
        // If for some reason updateActions() is called from a worker thread
1645
        // we must avoid to directly call QTimer::start() because this leaves
1646
        // the whole application in a weird state
1647
        if (d->activityTimer->thread() != QThread::currentThread()) {
1648
            QMetaObject::invokeMethod(d->activityTimer, "start", Qt::QueuedConnection,
1649
                Q_ARG(int, 150));
1650
        }
1651
        else {
1652
            d->activityTimer->start(150);
1653
        }
1654
    }
1655
    else if (delay) {
1656
        if (!d->actionUpdateDelay)
1657
            d->actionUpdateDelay = 1;
1658
    }
1659
    else {
1660
        d->actionUpdateDelay = -1;
1661
    }
1662
}
1663

1664
void MainWindow::_updateActions()
1665
{
1666
    if (isVisible() && d->actionUpdateDelay <= 0) {
1667
        FC_LOG("update actions");
1668
        d->activityTimer->stop();
1669
        Application::Instance->commandManager().testActive();
1670
    }
1671

1672
    d->actionUpdateDelay = 0;
1673

1674
    if (auto view = activeWindow()) {
1675
        setWindowTitle(view->buildWindowTitle());
1676
        if (auto document = view->getGuiDocument()) {
1677
            setWindowModified(document->isModified());
1678
        }
1679
    }
1680
}
1681

1682
void MainWindow::updateEditorActions()
1683
{
1684
    Command* cmd = nullptr;
1685
    CommandManager& mgr = Application::Instance->commandManager();
1686

1687
    cmd = mgr.getCommandByName("Std_Cut");
1688
    if (cmd) cmd->testActive();
1689

1690
    cmd = mgr.getCommandByName("Std_Copy");
1691
    if (cmd) cmd->testActive();
1692

1693
    cmd = mgr.getCommandByName("Std_Paste");
1694
    if (cmd) cmd->testActive();
1695

1696
    cmd = mgr.getCommandByName("Std_Undo");
1697
    if (cmd) cmd->testActive();
1698

1699
    cmd = mgr.getCommandByName("Std_Redo");
1700
    if (cmd) cmd->testActive();
1701
}
1702

1703
void MainWindow::switchToTopLevelMode()
1704
{
1705
    QList<QDockWidget*> dw = this->findChildren<QDockWidget*>();
1706
    for (auto & it : dw) {
1707
        it->setParent(nullptr, Qt::Window);
1708
        it->show();
1709
    }
1710
    QList<QWidget*> mdi = getMainWindow()->windows();
1711
    for (auto & it : mdi) {
1712
        it->setParent(nullptr, Qt::Window);
1713
        it->show();
1714
    }
1715
}
1716

1717
void MainWindow::switchToDockedMode()
1718
{
1719
    // Search for all top-level MDI views
1720
    QWidgetList toplevel = QApplication::topLevelWidgets();
1721
    for (const auto & it : toplevel) {
1722
        auto view = qobject_cast<MDIView*>(it);
1723
        if (view)
1724
            view->setCurrentViewMode(MDIView::Child);
1725
    }
1726
}
1727

1728
void MainWindow::loadWindowSettings()
1729
{
1730
    QString vendor = QString::fromUtf8(App::Application::Config()["ExeVendor"].c_str());
1731
    QString application = QString::fromUtf8(App::Application::Config()["ExeName"].c_str());
1732
    int major = (QT_VERSION >> 0x10) & 0xff;
1733
    int minor = (QT_VERSION >> 0x08) & 0xff;
1734
    QString qtver = QStringLiteral("Qt%1.%2").arg(major).arg(minor);
1735
    QSettings config(vendor, application);
1736

1737
    QRect rect = QApplication::primaryScreen()->availableGeometry();
1738
    int maxHeight = rect.height();
1739
    int maxWidth = rect.width();
1740

1741
    config.beginGroup(qtver);
1742
    QPoint pos = config.value(QStringLiteral("Position"), this->pos()).toPoint();
1743
    maxWidth -= pos.x();
1744
    maxHeight -= pos.y();
1745
    QSize size = config.value(QStringLiteral("Size"), QSize(maxWidth, maxHeight)).toSize();
1746
    bool max = config.value(QStringLiteral("Maximized"), false).toBool();
1747
    bool showStatusBar = config.value(QStringLiteral("StatusBar"), true).toBool();
1748
    QByteArray windowState = config.value(QStringLiteral("MainWindowState")).toByteArray();
1749
    config.endGroup();
1750

1751

1752
    std::string geometry = d->hGrp->GetASCII("Geometry");
1753
    std::istringstream iss(geometry);
1754
    int x,y,w,h;
1755
    if (iss >> x >> y >> w >> h) {
1756
        pos = QPoint(x, y);
1757
        size = QSize(w, h);
1758
    }
1759

1760
    max = d->hGrp->GetBool("Maximized", max);
1761
    showStatusBar = d->hGrp->GetBool("StatusBar", showStatusBar);
1762
    std::string wstate = d->hGrp->GetASCII("MainWindowState");
1763
    if (!wstate.empty()) {
1764
        windowState = QByteArray::fromBase64(wstate.c_str());
1765
    }
1766

1767
    resize(size);
1768
    int x1{},x2{},y1{},y2{};
1769
    // make sure that the main window is not totally out of the visible rectangle
1770
    rect.getCoords(&x1, &y1, &x2, &y2);
1771
    pos.setX(qMin(qMax(pos.x(),x1-this->width()+30),x2-30));
1772
    pos.setY(qMin(qMax(pos.y(),y1-10),y2-10));
1773
    this->move(pos);
1774

1775
    Base::StateLocker guard(d->_restoring);
1776

1777
    d->restoreWindowState(windowState);
1778
    std::clog << "Main window restored" << std::endl;
1779

1780
    max ? showMaximized() : show();
1781

1782
    // make menus and tooltips usable in fullscreen under Windows, see issue #7563
1783
#if defined(Q_OS_WIN) && QT_VERSION < QT_VERSION_CHECK(6,0,0)
1784
    if (QWindow* win = this->windowHandle()) {
1785
        QWindowsWindowFunctions::setHasBorderInFullScreen(win, true);
1786
    }
1787
#endif
1788

1789
    statusBar()->setVisible(showStatusBar);
1790

1791
    ToolBarManager::getInstance()->restoreState();
1792
    std::clog << "Toolbars restored" << std::endl;
1793

1794
    OverlayManager::instance()->restore();
1795
}
1796

1797
bool MainWindow::isRestoringWindowState() const
1798
{
1799
    return d->_restoring;
1800
}
1801

1802
void MainWindowP::restoreWindowState(const QByteArray &windowState)
1803
{
1804
    if (windowState.isEmpty())
1805
        return;
1806

1807
    Base::StateLocker guard(_restoring);
1808

1809
    // tmp. disable the report window to suppress some bothering warnings
1810
    if (Base::Console().IsMsgTypeEnabled("ReportOutput", Base::ConsoleSingleton::MsgType_Wrn)) {
1811
        Base::Console().SetEnabledMsgType("ReportOutput", Base::ConsoleSingleton::MsgType_Wrn, false);
1812
        getMainWindow()->restoreState(windowState);
1813
        Base::Console().SetEnabledMsgType("ReportOutput", Base::ConsoleSingleton::MsgType_Wrn, true);
1814
    } else
1815
        getMainWindow()->restoreState(windowState);
1816

1817
    Base::ConnectionBlocker block(connParam);
1818
    // as a notification for user code on window state restore
1819
    hGrp->SetBool("WindowStateRestored", !hGrp->GetBool("WindowStateRestored", false));
1820
}
1821

1822
void MainWindow::saveWindowSettings(bool canDelay)
1823
{
1824
    if (isRestoringWindowState())
1825
        return;
1826

1827
    if (canDelay) {
1828
        d->saveStateTimer.start(100);
1829
        return;
1830
    }
1831

1832
    QString vendor = QString::fromUtf8(App::Application::Config()["ExeVendor"].c_str());
1833
    QString application = QString::fromUtf8(App::Application::Config()["ExeName"].c_str());
1834
    int major = (QT_VERSION >> 0x10) & 0xff;
1835
    int minor = (QT_VERSION >> 0x08) & 0xff;
1836
    QString qtver = QStringLiteral("Qt%1.%2").arg(major).arg(minor);
1837
    QSettings config(vendor, application);
1838

1839
#if 0
1840
    config.beginGroup(qtver);
1841
    config.setValue(QStringLiteral("Size"), this->size());
1842
    config.setValue(QStringLiteral("Position"), this->pos());
1843
    config.setValue(QStringLiteral("Maximized"), this->isMaximized());
1844
    config.setValue(QStringLiteral("MainWindowState"), this->saveState());
1845
    config.setValue(QStringLiteral("StatusBar"), this->statusBar()->isVisible());
1846
    config.endGroup();
1847
#else
1848
    // We are migrating from saving qt main window layout state in QSettings to
1849
    // FreeCAD parameters, for more control.
1850
#endif
1851

1852
    Base::ConnectionBlocker block(d->connParam);
1853
    d->hGrp->SetBool("Maximized", this->isMaximized());
1854
    d->hGrp->SetBool("StatusBar", this->statusBar()->isVisible());
1855
    d->hGrp->SetASCII("MainWindowState", this->saveState().toBase64().constData());
1856

1857
    std::ostringstream ss;
1858
    QRect rect(this->pos(), this->size());
1859
    ss << rect.left() << " " << rect.top() << " " << rect.width() << " " << rect.height();
1860
    d->hGrp->SetASCII("Geometry", ss.str().c_str());
1861

1862
    DockWindowManager::instance()->saveState();
1863
    OverlayManager::instance()->save();
1864
    ToolBarManager::getInstance()->saveState();
1865
}
1866

1867
void MainWindow::startSplasher()
1868
{
1869
    // startup splasher
1870
    // when running in verbose mode no splasher
1871
    if (!(App::Application::Config()["Verbose"] == "Strict") &&
1872
         (App::Application::Config()["RunMode"] == "Gui")) {
1873
        ParameterGrp::handle hGrp = App::GetApplication().GetUserParameter().
1874
            GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("General");
1875
        // first search for an external image file
1876
        if (hGrp->GetBool("ShowSplasher", true)) {
1877
            d->splashscreen = new SplashScreen(this->splashImage());
1878

1879
            if (!hGrp->GetBool("ShowSplasherMessages", true)) {
1880
                d->splashscreen->setShowMessages(false);
1881
            }
1882

1883
            d->splashscreen->show();
1884
        }
1885
        else {
1886
            d->splashscreen = nullptr;
1887
        }
1888
    }
1889
}
1890

1891
void MainWindow::stopSplasher()
1892
{
1893
    if (d->splashscreen) {
1894
        d->splashscreen->finish(this);
1895
        delete d->splashscreen;
1896
        d->splashscreen = nullptr;
1897
    }
1898
}
1899

1900
QPixmap MainWindow::aboutImage() const
1901
{
1902
    // See if we have a custom About screen image set
1903
    QPixmap about_image;
1904
    QFileInfo fi(QString::fromLatin1("images:about_image.png"));
1905
    if (fi.isFile() && fi.exists())
1906
        about_image.load(fi.filePath(), "PNG");
1907

1908
    std::string about_path = App::Application::Config()["AboutImage"];
1909
    if (!about_path.empty() && about_image.isNull()) {
1910
        QString path = QString::fromUtf8(about_path.c_str());
1911
        if (QDir(path).isRelative()) {
1912
            QString home = QString::fromStdString(App::Application::getHomePath());
1913
            path = QFileInfo(QDir(home), path).absoluteFilePath();
1914
        }
1915
        about_image.load(path);
1916

1917
        // Now try the icon paths
1918
        if (about_image.isNull()) {
1919
            about_image = Gui::BitmapFactory().pixmap(about_path.c_str());
1920
        }
1921
    }
1922

1923
    return about_image;
1924
}
1925

1926
/**
1927
 * Displays a warning about this being a developer build. Designed for display in the Splashscreen.
1928
 * \param painter The painter to draw the warning into
1929
 * \param startPosition The painter-space coordinates to start the warning box at.
1930
 * \param maxSize The maximum extents for the box that is drawn. If the text exceeds this size it
1931
 * will be scaled down to fit.
1932
 * \note The text string is translatable, so its length is somewhat unpredictable. It is always
1933
 * displayed as two lines, regardless of the length of the text (e.g. no wrapping is done). Only the
1934
 * width is considered, the height simply follows from the font size.
1935
 */
1936
void MainWindow::renderDevBuildWarning(
1937
    QPainter &painter,
1938
    const QPoint startPosition,
1939
    const QSize maxSize)
1940
{
1941
    // Create a background box that fades out the artwork for better legibility
1942
    QColor fader (Qt::black);
1943
    constexpr float halfDensity (0.5);
1944
    fader.setAlphaF(halfDensity);
1945
    QBrush fillBrush(fader, Qt::BrushStyle::SolidPattern);
1946
    painter.setBrush(fillBrush);
1947

1948
    // Construct the lines of text and figure out how much space they need
1949
    const auto devWarningLine1 = tr("WARNING: This is a development version.");
1950
    const auto devWarningLine2 = tr("Please do not use it in a production environment.");
1951
    QFontMetrics fontMetrics(painter.font()); // Try to use the existing font
1952
    int padding = QtTools::horizontalAdvance(fontMetrics, QLatin1String("M")); // Arbitrary
1953
    int line1Width = QtTools::horizontalAdvance(fontMetrics, devWarningLine1);
1954
    int line2Width = QtTools::horizontalAdvance(fontMetrics, devWarningLine2);
1955
    int boxWidth = std::max(line1Width,line2Width) + 2 * padding;
1956
    int lineHeight = fontMetrics.lineSpacing();
1957
    if (boxWidth > maxSize.width()) {
1958
        // Especially if the text was translated, there is a chance that using the existing font
1959
        // will exceed the width of the Splashscreen graphic. Resize down so that it fits, no matter
1960
        // how long the text strings are.
1961
        float reductionFactor = static_cast<float>(maxSize.width()) / static_cast<float>(boxWidth);
1962
        int newFontSize = static_cast<int>(painter.font().pointSize() * reductionFactor);
1963
        padding *= reductionFactor;
1964
        QFont newFont = painter.font();
1965
        newFont.setPointSize(newFontSize);
1966
        painter.setFont(newFont);
1967
        lineHeight = painter.fontMetrics().lineSpacing();
1968
        boxWidth = maxSize.width();
1969
    }
1970
    constexpr float lineExpansionFactor(2.3F);
1971
    int boxHeight = static_cast<int>(lineHeight*lineExpansionFactor);
1972

1973
    // Draw the background rectangle and the text
1974
    painter.drawRect(startPosition.x(), startPosition.y(), boxWidth, boxHeight);
1975
    painter.drawText(startPosition.x()+padding, startPosition.y()+lineHeight, devWarningLine1);
1976
    painter.drawText(startPosition.x()+padding, startPosition.y()+2*lineHeight, devWarningLine2);
1977
}
1978

1979
QPixmap MainWindow::splashImage() const
1980
{
1981
    // search in the UserAppData dir as very first
1982
    QPixmap splash_image;
1983
    QFileInfo fi(QString::fromLatin1("images:splash_image.png"));
1984
    if (fi.isFile() && fi.exists())
1985
        splash_image.load(fi.filePath(), "PNG");
1986

1987
    // if no image was found try the config
1988
    std::string splash_path = App::Application::Config()["SplashScreen"];
1989
    if (splash_image.isNull()) {
1990
        QString path = QString::fromUtf8(splash_path.c_str());
1991
        if (QDir(path).isRelative()) {
1992
            QString home = QString::fromStdString(App::Application::getHomePath());
1993
            path = QFileInfo(QDir(home), path).absoluteFilePath();
1994
        }
1995

1996
        splash_image.load(path);
1997
    }
1998

1999
    // now try the icon paths
2000
    float pixelRatio (1.0);
2001
    if (splash_image.isNull()) {
2002
        if (qApp->devicePixelRatio() > 1.0) {
2003
            // For HiDPI screens, we have a double-resolution version of the splash image
2004
            splash_path += "2x";
2005
            splash_image = Gui::BitmapFactory().pixmap(splash_path.c_str());
2006
            splash_image.setDevicePixelRatio(2.0);
2007
            pixelRatio = 2.0;
2008
        }
2009
        else {
2010
            splash_image = Gui::BitmapFactory().pixmap(splash_path.c_str());
2011
        }
2012
    }
2013

2014
    // include application name and version number
2015
    std::map<std::string,std::string>::const_iterator tc = App::Application::Config().find("SplashInfoColor");
2016
    if (tc != App::Application::Config().end()) {
2017
        QString title = qApp->applicationName();
2018
        QString major   = QString::fromLatin1(App::Application::Config()["BuildVersionMajor"].c_str());
2019
        QString minor   = QString::fromLatin1(App::Application::Config()["BuildVersionMinor"].c_str());
2020
        QString point   = QString::fromLatin1(App::Application::Config()["BuildVersionPoint"].c_str());
2021
        QString suffix  = QString::fromLatin1(App::Application::Config()["BuildVersionSuffix"].c_str());
2022
        QString version = QString::fromLatin1("%1.%2.%3%4").arg(major, minor, point, suffix);
2023
        QString position, fontFamily;
2024

2025
        std::map<std::string,std::string>::const_iterator te = App::Application::Config().find("SplashInfoExeName");
2026
        std::map<std::string,std::string>::const_iterator tv = App::Application::Config().find("SplashInfoVersion");
2027
        std::map<std::string,std::string>::const_iterator tp = App::Application::Config().find("SplashInfoPosition");
2028
        std::map<std::string,std::string>::const_iterator tf = App::Application::Config().find("SplashInfoFont");
2029
        if (te != App::Application::Config().end())
2030
            title = QString::fromUtf8(te->second.c_str());
2031
        if (tv != App::Application::Config().end())
2032
            version = QString::fromUtf8(tv->second.c_str());
2033
        if (tp != App::Application::Config().end())
2034
            position = QString::fromUtf8(tp->second.c_str());
2035
        if (tf != App::Application::Config().end())
2036
            fontFamily = QString::fromUtf8(tf->second.c_str());
2037

2038
        QPainter painter;
2039
        painter.begin(&splash_image);
2040
        if (!fontFamily.isEmpty()) {
2041
            QFont font = painter.font();
2042
            if (font.fromString(fontFamily))
2043
                painter.setFont(font);
2044
        }
2045

2046
        QFont fontExe = painter.font();
2047
        fontExe.setPointSizeF(20.0);
2048
        QFontMetrics metricExe(fontExe);
2049
        int l = QtTools::horizontalAdvance(metricExe, title);
2050
        if (title == QLatin1String("FreeCAD")) {
2051
            l = 0.0; // "FreeCAD" text is already part of the splashscreen, version goes below it
2052
        }
2053
        int w = splash_image.width();
2054
        int h = splash_image.height();
2055

2056
        QFont fontVer = painter.font();
2057
        fontVer.setPointSizeF(14.0);
2058
        QFontMetrics metricVer(fontVer);
2059
        int v = QtTools::horizontalAdvance(metricVer, version);
2060

2061
        int x = -1, y = -1;
2062
        QRegularExpression rx(QLatin1String("(\\d+).(\\d+)"));
2063
        auto match = rx.match(position);
2064
        if (match.hasMatch()) {
2065
            x = match.captured(1).toInt();
2066
            y = match.captured(2).toInt();
2067
        }
2068
        else {
2069
            x = w - (l + v + 10);
2070
            y = h - 20;
2071
        }
2072

2073
        QColor color;
2074
        color.setNamedColor(QString::fromLatin1(tc->second.c_str()));
2075
        if (color.isValid()) {
2076
            painter.setPen(color);
2077
            painter.setFont(fontExe);
2078
            if (title != QLatin1String("FreeCAD")) {
2079
                // FreeCAD's Splashscreen already contains the EXE name, no need to draw it
2080
                painter.drawText(x, y, title);
2081
            }
2082
            painter.setFont(fontVer);
2083
            painter.drawText(x + (l + 5), y, version);
2084
            if (suffix == QLatin1String("dev")) {
2085
                const int lineHeight = metricVer.lineSpacing();
2086
                const int padding {10}; // Distance from the edge of the graphic's bounding box
2087
                QPoint startPosition(padding, y + lineHeight);
2088
                QSize maxSize(w/pixelRatio - 2*padding, lineHeight * 3);
2089
                MainWindow::renderDevBuildWarning(painter, startPosition, maxSize);
2090
            }
2091
            painter.end();
2092
        }
2093
    }
2094

2095
    return splash_image;
2096
}
2097

2098
/**
2099
 * Drops the event \a e and tries to open the files.
2100
 */
2101
void MainWindow::dropEvent (QDropEvent* e)
2102
{
2103
    const QMimeData* data = e->mimeData();
2104
    if (data->hasUrls()) {
2105
        // load the files into the active document if there is one, otherwise let create one
2106
        loadUrls(App::GetApplication().getActiveDocument(), data->urls());
2107
    }
2108
    else {
2109
        QMainWindow::dropEvent(e);
2110
    }
2111
}
2112

2113
void MainWindow::dragEnterEvent (QDragEnterEvent * e)
2114
{
2115
    // Here we must allow uri drafs and check them in dropEvent
2116
    const QMimeData* data = e->mimeData();
2117
    if (data->hasUrls()) {
2118
        e->accept();
2119
    }
2120
    else {
2121
        e->ignore();
2122
    }
2123
}
2124

2125
static QLatin1String _MimeDocObj("application/x-documentobject");
2126
static QLatin1String _MimeDocObjX("application/x-documentobject-x");
2127
static QLatin1String _MimeDocObjFile("application/x-documentobject-file");
2128
static QLatin1String _MimeDocObjXFile("application/x-documentobject-x-file");
2129

2130
QMimeData * MainWindow::createMimeDataFromSelection () const
2131
{
2132
    std::vector<App::DocumentObject*> sel;
2133
    std::set<App::DocumentObject*> objSet;
2134
    for(auto &s : Selection().getCompleteSelection()) {
2135
        if(s.pObject && s.pObject->isAttachedToDocument() && objSet.insert(s.pObject).second)
2136
            sel.push_back(s.pObject);
2137
    }
2138
    if(sel.empty())
2139
        return nullptr;
2140

2141
    auto all = App::Document::getDependencyList(sel);
2142
    if (all.size() > sel.size()) {
2143
        DlgObjectSelection dlg(sel,getMainWindow());
2144
        if(dlg.exec()!=QDialog::Accepted)
2145
            return nullptr;
2146
        sel = dlg.getSelections();
2147
        if(sel.empty())
2148
            return nullptr;
2149
    }
2150

2151
    std::vector<App::Document*> unsaved;
2152
    bool hasXLink = App::PropertyXLink::hasXLink(sel,&unsaved);
2153
    if(!unsaved.empty()) {
2154
        QMessageBox::critical(getMainWindow(), tr("Unsaved document"),
2155
            tr("The exported object contains external link. Please save the document"
2156
                "at least once before exporting."));
2157
        return nullptr;
2158
    }
2159

2160
    unsigned int memsize=1000; // ~ for the meta-information
2161
    for (const auto & it : sel)
2162
        memsize += it->getMemSize();
2163

2164
    // if less than ~10 MB
2165
    bool use_buffer=(memsize < 0xA00000);
2166
    QByteArray res;
2167
    if(use_buffer) {
2168
        try {
2169
            res.reserve(memsize);
2170
        }
2171
        catch (const std::bad_alloc &) {
2172
            use_buffer = false;
2173
        }
2174
    }
2175

2176
    WaitCursor wc;
2177
    QString mime;
2178
    if (use_buffer) {
2179
        mime = hasXLink?_MimeDocObjX:_MimeDocObj;
2180
        Base::ByteArrayOStreambuf buf(res);
2181
        std::ostream str(&buf);
2182
        // need this instance to call MergeDocuments::Save()
2183
        App::Document* doc = sel.front()->getDocument();
2184
        MergeDocuments mimeView(doc);
2185
        doc->exportObjects(sel, str);
2186
    }
2187
    else {
2188
        mime = hasXLink?_MimeDocObjXFile:_MimeDocObjFile;
2189
        static Base::FileInfo fi(App::Application::getTempFileName());
2190
        Base::ofstream str(fi, std::ios::out | std::ios::binary);
2191
        // need this instance to call MergeDocuments::Save()
2192
        App::Document* doc = sel.front()->getDocument();
2193
        MergeDocuments mimeView(doc);
2194
        doc->exportObjects(sel, str);
2195
        str.close();
2196
        res = fi.filePath().c_str();
2197

2198
        // store the path name as a custom property and
2199
        // delete this file when closing the application
2200
        const_cast<MainWindow*>(this)->setProperty("x-documentobject-file", res);
2201
    }
2202

2203
    auto mimeData = new QMimeData();
2204
    mimeData->setData(mime,res);
2205
    return mimeData;
2206
}
2207

2208
bool MainWindow::canInsertFromMimeData (const QMimeData * source) const
2209
{
2210
    if (!source)
2211
        return false;
2212
    return source->hasUrls() ||
2213
        source->hasFormat(_MimeDocObj) || source->hasFormat(_MimeDocObjX) ||
2214
        source->hasFormat(_MimeDocObjFile) || source->hasFormat(_MimeDocObjXFile);
2215
}
2216

2217
void MainWindow::insertFromMimeData (const QMimeData * mimeData)
2218
{
2219
    if (!mimeData)
2220
        return;
2221
    bool fromDoc = false;
2222
    bool hasXLink = false;
2223
    QString format;
2224
    if(mimeData->hasFormat(_MimeDocObj))
2225
        format = _MimeDocObj;
2226
    else if(mimeData->hasFormat(_MimeDocObjX)) {
2227
        format = _MimeDocObjX;
2228
        hasXLink = true;
2229
    }else if(mimeData->hasFormat(_MimeDocObjFile)) {
2230
        format = _MimeDocObjFile;
2231
        fromDoc = true;
2232
    }else if(mimeData->hasFormat(_MimeDocObjXFile)) {
2233
        format = _MimeDocObjXFile;
2234
        fromDoc = true;
2235
        hasXLink = true;
2236
    }else {
2237
        if (mimeData->hasUrls())
2238
            loadUrls(App::GetApplication().getActiveDocument(), mimeData->urls());
2239
        return;
2240
    }
2241

2242
    App::Document* doc = App::GetApplication().getActiveDocument();
2243
    if(!doc) doc = App::GetApplication().newDocument();
2244

2245
    if(hasXLink && !doc->isSaved()) {
2246
        int ret = QMessageBox::question(getMainWindow(), tr("Unsaved document"),
2247
            tr("To link to external objects, the document must be saved at least once.\n"
2248
               "Do you want to save the document now?"),
2249
            QMessageBox::Yes,QMessageBox::No);
2250
        if(ret != QMessageBox::Yes || !Application::Instance->getDocument(doc)->saveAs())
2251
            return;
2252
    }
2253
    if(!fromDoc) {
2254
        QByteArray res = mimeData->data(format);
2255

2256
        doc->openTransaction("Paste");
2257
        Base::ByteArrayIStreambuf buf(res);
2258
        std::istream in(nullptr);
2259
        in.rdbuf(&buf);
2260
        MergeDocuments mimeView(doc);
2261
        std::vector<App::DocumentObject*> newObj = mimeView.importObjects(in);
2262
        std::vector<App::DocumentObjectGroup*> grp = Gui::Selection().getObjectsOfType<App::DocumentObjectGroup>();
2263
        if (grp.size() == 1) {
2264
            Gui::Document* gui = Application::Instance->getDocument(doc);
2265
            if (gui)
2266
                gui->addRootObjectsToGroup(newObj, grp.front());
2267
        }
2268
        doc->commitTransaction();
2269
    }
2270
    else {
2271
        QByteArray res = mimeData->data(format);
2272

2273
        doc->openTransaction("Paste");
2274
        Base::FileInfo fi((const char*)res);
2275
        Base::ifstream str(fi, std::ios::in | std::ios::binary);
2276
        MergeDocuments mimeView(doc);
2277
        std::vector<App::DocumentObject*> newObj = mimeView.importObjects(str);
2278
        str.close();
2279
        std::vector<App::DocumentObjectGroup*> grp = Gui::Selection().getObjectsOfType<App::DocumentObjectGroup>();
2280
        if (grp.size() == 1) {
2281
            Gui::Document* gui = Application::Instance->getDocument(doc);
2282
            if (gui)
2283
                gui->addRootObjectsToGroup(newObj, grp.front());
2284
        }
2285
        doc->commitTransaction();
2286
    }
2287
}
2288

2289
void MainWindow::setUrlHandler(const QString &scheme, Gui::UrlHandler* handler)
2290
{
2291
    d->urlHandler[scheme] = handler;
2292
}
2293

2294
void MainWindow::unsetUrlHandler(const QString &scheme)
2295
{
2296
    d->urlHandler.remove(scheme);
2297
}
2298

2299
void MainWindow::loadUrls(App::Document* doc, const QList<QUrl>& urls)
2300
{
2301
    QStringList files;
2302
    for (const auto & it : urls) {
2303
        QMap<QString, QPointer<UrlHandler> >::iterator jt = d->urlHandler.find(it.scheme());
2304
        if (jt != d->urlHandler.end() && !jt->isNull()) {
2305
            // delegate the loading to the url handler
2306
            (*jt)->openUrl(doc, it);
2307
            continue;
2308
        }
2309

2310
        QFileInfo info(it.toLocalFile());
2311
        if (info.exists() && info.isFile()) {
2312
            if (info.isSymLink())
2313
                info.setFile(info.symLinkTarget());
2314
            std::vector<std::string> module = App::GetApplication()
2315
                .getImportModules(info.completeSuffix().toLatin1());
2316
            if (module.empty()) {
2317
                module = App::GetApplication()
2318
                    .getImportModules(info.suffix().toLatin1());
2319
            }
2320
            if (!module.empty()) {
2321
                // ok, we support files with this extension
2322
                files << info.absoluteFilePath();
2323
            }
2324
            else {
2325
                Base::Console().Message("No support to load file '%s'\n",
2326
                    (const char*)info.absoluteFilePath().toUtf8());
2327
            }
2328
        }
2329
        else if (it.scheme().toLower() == QLatin1String("http")) {
2330
            Gui::Dialog::DownloadManager* dm = Gui::Dialog::DownloadManager::getInstance();
2331
            dm->download(dm->redirectUrl(it));
2332
        }
2333

2334
        else if (it.scheme().toLower() == QLatin1String("https")) {
2335
            QUrl url = it;
2336
            QUrlQuery urlq(url);
2337
            if (urlq.hasQueryItem(QLatin1String("sid"))) {
2338
                urlq.removeAllQueryItems(QLatin1String("sid"));
2339
                url.setQuery(urlq);
2340
                url.setScheme(QLatin1String("http"));
2341
            }
2342
            Gui::Dialog::DownloadManager* dm = Gui::Dialog::DownloadManager::getInstance();
2343
            dm->download(dm->redirectUrl(url));
2344
        }
2345

2346
        else if (it.scheme().toLower() == QLatin1String("ftp")) {
2347
            Gui::Dialog::DownloadManager::getInstance()->download(it);
2348
        }
2349
    }
2350

2351
    QByteArray docName = doc ? QByteArray(doc->getName()) : qApp->translate("StdCmdNew","Unnamed").toUtf8();
2352
    SelectModule::Dict dict = SelectModule::importHandler(files);
2353
    // load the files with the associated modules
2354
    for (SelectModule::Dict::iterator it = dict.begin(); it != dict.end(); ++it) {
2355
        // if the passed document name doesn't exist the module should create it, if needed
2356
        Application::Instance->importFrom(it.key().toUtf8(), docName, it.value().toLatin1());
2357
    }
2358
}
2359

2360
void MainWindow::changeEvent(QEvent *e)
2361
{
2362
    if (e->type() == QEvent::LanguageChange) {
2363
        d->sizeLabel->setText(tr("Dimension"));
2364

2365
        CommandManager& rclMan = Application::Instance->commandManager();
2366
        std::vector<Command*> cmd = rclMan.getAllCommands();
2367
        for (auto & it : cmd)
2368
            it->languageChange();
2369

2370
        // reload current workbench to retranslate all actions and window titles
2371
        Workbench* wb = WorkbenchManager::instance()->active();
2372
        if (wb) wb->retranslate();
2373
    }
2374
    else if (e->type() == QEvent::ActivationChange) {
2375
        if (isActiveWindow()) {
2376
            QMdiSubWindow* mdi = d->mdiArea->currentSubWindow();
2377
            if (mdi) {
2378
                auto view = dynamic_cast<MDIView*>(mdi->widget());
2379
                if (view && getMainWindow()->activeWindow() != view) {
2380
                    d->activeView = view;
2381
                    Application::Instance->viewActivated(view);
2382
                }
2383
            }
2384
        }
2385
    }
2386
    else {
2387
        QMainWindow::changeEvent(e);
2388
    }
2389
}
2390

2391
void MainWindow::clearStatus() {
2392
    d->currentStatusType = 100;
2393
    statusBar()->setStyleSheet(QString::fromLatin1("#statusBar{}"));
2394
}
2395

2396
void MainWindow::statusMessageChanged() {
2397
    if(d->currentStatusType<0)
2398
        d->currentStatusType = -d->currentStatusType;
2399
    else {
2400
        // here probably means the status bar message is changed by QMainWindow
2401
        // internals, e.g. for displaying tooltip and stuff. Set reset what
2402
        // we've changed.
2403
        d->statusTimer->stop();
2404
        clearStatus();
2405
    }
2406
}
2407

2408
void MainWindow::showMessage(const QString& message, int timeout) {
2409
    if(QApplication::instance()->thread() != QThread::currentThread()) {
2410
        QApplication::postEvent(this, new CustomMessageEvent(MainWindow::Tmp,message,timeout));
2411
        return;
2412
    }
2413
    d->actionLabel->setText(message.simplified());
2414
    if(timeout) {
2415
        d->actionTimer->setSingleShot(true);
2416
        d->actionTimer->start(timeout);
2417
    }else
2418
        d->actionTimer->stop();
2419
}
2420

2421
void MainWindow::showStatus(int type, const QString& message)
2422
{
2423
    if(QApplication::instance()->thread() != QThread::currentThread()) {
2424
        QApplication::postEvent(this,
2425
                new CustomMessageEvent(type,message));
2426
        return;
2427
    }
2428

2429
    if(d->currentStatusType < type)
2430
        return;
2431

2432
    d->statusTimer->setSingleShot(true);
2433
    // TODO: hardcode?
2434
    int timeout = 5000;
2435
    d->statusTimer->start(timeout);
2436

2437
    QFontMetrics fm(statusBar()->font());
2438
    QString msg = fm.elidedText(message, Qt::ElideMiddle, this->d->actionLabel->width());
2439
    switch(type) {
2440
    case MainWindow::Err:
2441
        statusBar()->setStyleSheet(d->status->err);
2442
        break;
2443
    case MainWindow::Wrn:
2444
        statusBar()->setStyleSheet(d->status->wrn);
2445
        break;
2446
    case MainWindow::Pane:
2447
        statusBar()->setStyleSheet(QString::fromLatin1("#statusBar{}"));
2448
        break;
2449
    default:
2450
        statusBar()->setStyleSheet(d->status->msg);
2451
        break;
2452
    }
2453
    d->currentStatusType = -type;
2454
    statusBar()->showMessage(msg.simplified(), timeout);
2455
}
2456

2457

2458
// set text to the pane
2459
void MainWindow::setPaneText(int i, QString text)
2460
{
2461
    if (i==1) {
2462
        showStatus(MainWindow::Pane, text);
2463
    }
2464
    else if (i==2) {
2465
        d->sizeLabel->setText(text);
2466
    }
2467
}
2468

2469

2470
void MainWindow::setUserSchema(int userSchema)
2471
{
2472
    d->sizeLabel->setUserSchema(userSchema);
2473
}
2474

2475

2476
void MainWindow::customEvent(QEvent* e)
2477
{
2478
    if (e->type() == QEvent::User) {
2479
        auto ce = static_cast<Gui::CustomMessageEvent*>(e);
2480
        QString msg = ce->message();
2481
        switch(ce->type()) {
2482
        case MainWindow::Log: {
2483
            if (msg.startsWith(QLatin1String("#Inventor V2.1 ascii "))) {
2484
                Gui::Document *d = Application::Instance->activeDocument();
2485
                if (d) {
2486
                    auto view = new ViewProviderExtern();
2487
                    try {
2488
                        view->setModeByString("1",msg.toLatin1().constData());
2489
                        d->setAnnotationViewProvider("Vdbg",view);
2490
                    }
2491
                    catch (...) {
2492
                        delete view;
2493
                    }
2494
                }
2495
            }
2496
            break;
2497
        } case MainWindow::Tmp: {
2498
            showMessage(msg, ce->timeout());
2499
            break;
2500
        } default:
2501
            showStatus(ce->type(),msg);
2502
        }
2503
    }
2504
    else if (e->type() == ActionStyleEvent::EventType) {
2505
        QList<TaskView::TaskView*> tasks = findChildren<TaskView::TaskView*>();
2506
        if (static_cast<ActionStyleEvent*>(e)->getType() == ActionStyleEvent::Clear) {
2507
            for (auto & task : tasks) {
2508
                task->clearActionStyle();
2509
            }
2510
        }
2511
        else {
2512
            for (auto & task : tasks) {
2513
                task->restoreActionStyle();
2514
            }
2515
        }
2516
    }
2517
}
2518

2519
QMdiArea *MainWindow::getMdiArea() const
2520
{
2521
    return d->mdiArea;
2522
}
2523

2524
void MainWindow::setWindowTitle(const QString& string)
2525
{
2526
    QString title;
2527
    QString appname = QCoreApplication::applicationName();
2528
    if (appname.isEmpty()) {
2529
        appname = QString::fromLatin1(App::Application::Config()["ExeName"].c_str());
2530
    }
2531

2532
    // allow to disable version number
2533
    ParameterGrp::handle hGen = +App::GetApplication().GetParameterGroupByPath(
2534
        "User parameter:BaseApp/Preferences/General");
2535
    bool showVersion = hGen->GetBool("ShowVersionInTitle", true);
2536

2537
    if (showVersion) {
2538
        // set main window title with FreeCAD Version
2539
        auto config = App::Application::Config();
2540
        QString major = QString::fromUtf8(config["BuildVersionMajor"].c_str());
2541
        QString minor = QString::fromUtf8(config["BuildVersionMinor"].c_str());
2542
        QString point = QString::fromUtf8(config["BuildVersionPoint"].c_str());
2543
        QString suffix = QString::fromUtf8(config["BuildVersionSuffix"].c_str());
2544
        title = QString::fromUtf8("%1 %2.%3.%4%5").arg(appname, major, minor, point, suffix);
2545
    }
2546
    else {
2547
        title = appname;
2548
    }
2549

2550
    if (!string.isEmpty()) {
2551
        title = QString::fromUtf8("[*] %1 - %2").arg(string, title);
2552
    }
2553

2554
    QMainWindow::setWindowTitle(title);
2555
}
2556

2557
    // ----------------------------------------------------------
2558

2559
    StatusBarObserver::StatusBarObserver()
2560
        : WindowParameter("OutputWindow")
2561
    {
2562
        msg = QString::fromLatin1("#statusBar{color: #000000}");  // black
2563
        wrn = QString::fromLatin1("#statusBar{color: #ffaa00}");  // orange
2564
        err = QString::fromLatin1("#statusBar{color: #ff0000}");  // red
2565
        Base::Console().AttachObserver(this);
2566
        getWindowParameter()->Attach(this);
2567
        getWindowParameter()->NotifyAll();
2568
    }
2569

2570
    StatusBarObserver::~StatusBarObserver()
2571
    {
2572
        getWindowParameter()->Detach(this);
2573
        Base::Console().DetachObserver(this);
2574
    }
2575

2576
    void StatusBarObserver::OnChange(Base::Subject<const char*> & rCaller, const char* sReason)
2577
    {
2578
        ParameterGrp& rclGrp = ((ParameterGrp&)rCaller);
2579
        auto format = QString::fromLatin1("#statusBar{color: %1}");
2580
        if (strcmp(sReason, "colorText") == 0) {
2581
            unsigned long col = rclGrp.GetUnsigned(sReason);
2582
            this->msg = format.arg(App::Color::fromPackedRGB<QColor>(col).name());
2583
        }
2584
        else if (strcmp(sReason, "colorWarning") == 0) {
2585
            unsigned long col = rclGrp.GetUnsigned(sReason);
2586
            this->wrn = format.arg(App::Color::fromPackedRGB<QColor>(col).name());
2587
        }
2588
        else if (strcmp(sReason, "colorError") == 0) {
2589
            unsigned long col = rclGrp.GetUnsigned(sReason);
2590
            this->err = format.arg(App::Color::fromPackedRGB<QColor>(col).name());
2591
        }
2592
        else if (strcmp(sReason, "colorCritical") == 0) {
2593
            unsigned long col = rclGrp.GetUnsigned(sReason);
2594
            this->critical = format.arg(
2595
                QColor((col >> 24) & 0xff, (col >> 16) & 0xff, (col >> 8) & 0xff).name());
2596
        }
2597
    }
2598

2599
    void StatusBarObserver::SendLog(const std::string& notifiername,
2600
                                    const std::string& msg,
2601
                                    Base::LogStyle level,
2602
                                    Base::IntendedRecipient recipient,
2603
                                    Base::ContentType content)
2604
    {
2605
        (void)notifiername;
2606

2607
        // Do not log untranslated messages, or messages intended only to a developer to status bar
2608
        if (recipient == Base::IntendedRecipient::Developer
2609
            || content == Base::ContentType::Untranslated
2610
            || content == Base::ContentType::Untranslatable)
2611
            return;
2612

2613
        int messageType = -1;
2614
        switch (level) {
2615
            case Base::LogStyle::Warning:
2616
                messageType = MainWindow::Wrn;
2617
                break;
2618
            case Base::LogStyle::Message:
2619
                messageType = MainWindow::Msg;
2620
                break;
2621
            case Base::LogStyle::Error:
2622
                messageType = MainWindow::Err;
2623
                break;
2624
            case Base::LogStyle::Log:
2625
                messageType = MainWindow::Log;
2626
                break;
2627
            case Base::LogStyle::Critical:
2628
                messageType = MainWindow::Critical;
2629
                break;
2630
            default:
2631
                break;
2632
        }
2633

2634
        // Send the event to the main window to allow thread-safety. Qt will delete it when done.
2635
        auto ev = new CustomMessageEvent(messageType, QString::fromUtf8(msg.c_str()));
2636
        QApplication::postEvent(getMainWindow(), ev);
2637
    }
2638

2639
    // -------------------------------------------------------------
2640

2641
    int ActionStyleEvent::EventType = -1;
2642

2643
    ActionStyleEvent::ActionStyleEvent(Style type)
2644
        : QEvent(QEvent::Type(EventType))
2645
        , type(type)
2646
    {}
2647

2648
    ActionStyleEvent::Style ActionStyleEvent::getType() const
2649
    {
2650
        return type;
2651
    }
2652

2653

2654
#include "moc_MainWindow.cpp"
2655
#include "MainWindow.moc"
2656

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.