FreeCAD

Форк
0
/
MainWindow.cpp 
2719 строк · 94.1 Кб
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 <QMenuBar>
41
# include <QMessageBox>
42
# include <QMimeData>
43
# include <QPainter>
44
# include <QRegularExpression>
45
# include <QRegularExpressionMatch>
46
# include <QScreen>
47
# include <QSettings>
48
# include <QSignalMapper>
49
# include <QStatusBar>
50
# include <QThread>
51
# include <QTimer>
52
# include <QToolBar>
53
# include <QUrlQuery>
54
# include <QWhatsThis>
55
# include <QWindow>
56
# include <QPushButton>
57
#endif
58

59
#if defined(Q_OS_WIN)
60
    #if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
61
        #include <QtPlatformHeaders/QWindowsWindowFunctions>
62
    #else
63
        #include <qpa/qplatformwindow_p.h>
64
    #endif
65
#endif
66

67
#include <boost/algorithm/string/predicate.hpp>
68

69
#include <App/Application.h>
70
#include <App/Document.h>
71
#include <App/DocumentObject.h>
72
#include <App/DocumentObjectGroup.h>
73
#include <Base/ConsoleObserver.h>
74
#include <Base/Parameter.h>
75
#include <Base/Exception.h>
76
#include <Base/FileInfo.h>
77
#include <Base/Interpreter.h>
78
#include <Base/Stream.h>
79
#include <Base/Tools.h>
80
#include <Base/UnitsApi.h>
81
#include <DAGView/DAGView.h>
82
#include <TaskView/TaskView.h>
83

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

109
#include "MergeDocuments.h"
110
#include "ViewProviderExtern.h"
111

112
#include "SpaceballEvent.h"
113
#include "View3DInventor.h"
114
#include "View3DInventorViewer.h"
115
#include "DlgObjectSelection.h"
116
#include "Tools.h"
117
#include <App/Color.h>
118

119
FC_LOG_LEVEL_INIT("MainWindow",false,true,true)
120

121
#if defined(Q_OS_WIN32)
122
#define slots
123
#endif
124

125
using namespace Gui;
126
using namespace Gui::DockWnd;
127
using namespace std;
128

129

130
MainWindow* MainWindow::instance = nullptr;
131

132
namespace Gui {
133

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

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

170
public:
171
    explicit DimensionWidget(QWidget* parent): QPushButton(parent), WindowParameter("Units")
172
    {
173
        setFlat(true);
174
        setText(qApp->translate("Gui::MainWindow", "Dimension"));
175
        setMinimumWidth(120);
176

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

204
    ~DimensionWidget() override
205
    {
206
        getWindowParameter()->Detach(this);
207
    }
208

209
    void OnChange(Base::Subject<const char*> &rCaller, const char * sReason) override
210
    {
211
        Q_UNUSED(rCaller)
212
        if (strcmp(sReason, "UserSchema") == 0) {
213
            unitChanged();
214
        }
215
    }
216

217
    void changeEvent(QEvent *event) override
218
    {
219
        if (event->type() == QEvent::LanguageChange) {
220
            retranslateUi();
221
        }
222
        else {
223
            QPushButton::changeEvent(event);
224
        }
225
    }
226

227
    void setUserSchema(int userSchema)
228
    {
229
        App::Document* doc = App::GetApplication().getActiveDocument();
230
        if ( doc != nullptr ) {
231
            if (doc->UnitSystem.getValue() != userSchema )
232
                doc->UnitSystem.setValue(userSchema);
233
        } else
234
            getWindowParameter()->SetInt("UserSchema", userSchema);
235

236
        unitChanged();
237
        Base::UnitsApi::setSchema(static_cast<Base::UnitSystem>(userSchema));
238
        // Update the main window to show the unit change
239
        Gui::Application::Instance->onUpdate();
240
    }
241

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

260
    void retranslateUi() {
261
        auto actions = menu()->actions();
262
        int maxSchema = static_cast<int>(Base::UnitSystem::NumUnitSystemTypes);
263
        assert(actions.size() <= maxSchema);
264
        for(int i = 0; i < maxSchema ; i++)
265
        {
266
            actions[i]->setText(Base::UnitsApi::getDescription(static_cast<Base::UnitSystem>(i)));
267
        }
268
    }
269
};
270

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

302
class MDITabbar : public QTabBar
303
{
304
public:
305
    explicit MDITabbar( QWidget * parent = nullptr ) : QTabBar(parent)
306
    {
307
        menu = new QMenu(this);
308
        setDrawBase(false);
309
        setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Fixed);
310
    }
311

312
    ~MDITabbar() override
313
    {
314
        delete menu;
315
    }
316

317
protected:
318
    void contextMenuEvent ( QContextMenuEvent * e ) override
319
    {
320
        menu->clear();
321
        CommandManager& cMgr = Application::Instance->commandManager();
322
        if (tabRect(currentIndex()).contains(e->pos()))
323
            cMgr.getCommandByName("Std_CloseActiveWindow")->addTo(menu);
324
        cMgr.getCommandByName("Std_CloseAllWindows")->addTo(menu);
325
        menu->addSeparator();
326
        cMgr.getCommandByName("Std_CascadeWindows")->addTo(menu);
327
        cMgr.getCommandByName("Std_TileWindows")->addTo(menu);
328
        menu->addSeparator();
329
        cMgr.getCommandByName("Std_Windows")->addTo(menu);
330
        menu->popup(e->globalPos());
331
    }
332

333
private:
334
    QMenu* menu;
335
};
336

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

382
} // namespace Gui
383

384
/* TRANSLATOR Gui::MainWindow */
385

386
MainWindow::MainWindow(QWidget * parent, Qt::WindowFlags f)
387
  : QMainWindow( parent, f/*WDestructiveClose*/ )
388
{
389
    d = new MainWindowP;
390
    d->splashscreen = nullptr;
391
    d->activeView = nullptr;
392
    d->whatsthis = false;
393
    d->assistant = new Assistant();
394

395
#if (QT_VERSION >= QT_VERSION_CHECK(6,0,0))
396
    // this forces QT to switch to OpenGL mode, this prevents delay and flickering of the window
397
    // after opening project and prevent issues with double initialization of the window
398
    //
399
    // https://stackoverflow.com/questions/76026196/how-to-force-qt-to-use-the-opengl-window-type
400
    new QOpenGLWidget(this);
401
#endif
402

403
    // global access
404
    instance = this;
405

406
    d->connParam = App::GetApplication().GetUserParameter().signalParamChanged.connect(
407
        [this](ParameterGrp *Param, ParameterGrp::ParamType, const char *Name, const char *) {
408
            if (Param != d->hGrp || !Name)
409
                return;
410
            if (boost::equals(Name, "StatusBar")) {
411
                if(auto sb = getMainWindow()->statusBar())
412
                    sb->setVisible(d->hGrp->GetBool("StatusBar", sb->isVisible()));
413
            }
414
            else if (boost::equals(Name, "MainWindowState")) {
415
                OverlayManager::instance()->reload(OverlayManager::ReloadMode::ReloadPause);
416
                d->restoreStateTimer.start(100);
417
            }
418
        });
419

420
    d->hGrp = App::GetApplication().GetParameterGroupByPath(
421
            "User parameter:BaseApp/Preferences/MainWindow");
422
    d->saveStateTimer.setSingleShot(true);
423
    connect(&d->saveStateTimer, &QTimer::timeout, [this](){this->saveWindowSettings();});
424

425
    d->restoreStateTimer.setSingleShot(true);
426
    connect(&d->restoreStateTimer, &QTimer::timeout, [this](){
427
        d->restoreWindowState(QByteArray::fromBase64(d->hGrp->GetASCII("MainWindowState").c_str()));
428
        ToolBarManager::getInstance()->restoreState();
429
        OverlayManager::instance()->reload(OverlayManager::ReloadMode::ReloadResume);
430
    });
431

432
    // support for grouped dragging of dockwidgets
433
    // https://woboq.com/blog/qdockwidget-changes-in-56.html
434
    setDockOptions(dockOptions() | QMainWindow::GroupedDragging);
435

436
    // Create the layout containing the workspace and a tab bar
437
    d->mdiArea = new QMdiArea();
438
    // Movable tabs
439
    d->mdiArea->setTabsMovable(true);
440
    d->mdiArea->setTabPosition(QTabWidget::South);
441
    d->mdiArea->setViewMode(QMdiArea::TabbedView);
442
    auto tab = d->mdiArea->findChild<QTabBar*>();
443
    if (tab) {
444
        tab->setTabsClosable(true);
445
        // The tabs might be very wide
446
        tab->setExpanding(false);
447
        tab->setObjectName(QString::fromLatin1("mdiAreaTabBar"));
448
    }
449
    d->mdiArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
450
    d->mdiArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
451
    d->mdiArea->setOption(QMdiArea::DontMaximizeSubWindowOnActivation, false);
452
    d->mdiArea->setActivationOrder(QMdiArea::ActivationHistoryOrder);
453
    d->mdiArea->setBackground(QBrush(QColor(160,160,160)));
454
    setCentralWidget(d->mdiArea);
455

456
    statusBar()->setObjectName(QString::fromLatin1("statusBar"));
457
    connect(statusBar(), &QStatusBar::messageChanged, this, &MainWindow::statusMessageChanged);
458

459
    // labels and progressbar
460
    d->status = new StatusBarObserver();
461
    d->actionLabel = new QLabel(statusBar());
462
    d->actionLabel->setTextInteractionFlags(Qt::TextSelectableByMouse);
463
    // d->actionLabel->setMinimumWidth(120);
464

465
    d->sizeLabel = new DimensionWidget(statusBar());
466

467
    statusBar()->addWidget(d->actionLabel, 1);
468
    QProgressBar* progressBar = Gui::SequencerBar::instance()->getProgressBar(statusBar());
469
    statusBar()->addPermanentWidget(progressBar, 0);
470
    statusBar()->addPermanentWidget(d->sizeLabel, 0);
471

472
    d->rightSideLabel = new QLabel(statusBar());
473
    d->rightSideLabel->setTextInteractionFlags(Qt::TextSelectableByMouse);
474
    statusBar()->addPermanentWidget(d->rightSideLabel);
475

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

478
    auto notificationAreaEnabled = hGrp->GetBool("NotificationAreaEnabled", true);
479

480
    if(notificationAreaEnabled) {
481
        NotificationArea* notificationArea = new NotificationArea(statusBar());
482
        notificationArea->setObjectName(QString::fromLatin1("notificationArea"));
483
        notificationArea->setStyleSheet(QStringLiteral("text-align:left;"));
484
        statusBar()->addPermanentWidget(notificationArea);
485
    }
486

487
    // clears the action label
488
    d->actionTimer = new QTimer( this );
489
    d->actionTimer->setObjectName(QString::fromLatin1("actionTimer"));
490
    connect(d->actionTimer, &QTimer::timeout, d->actionLabel, &QLabel::clear);
491

492
    // clear status type
493
    d->statusTimer = new QTimer( this );
494
    d->statusTimer->setObjectName(QString::fromLatin1("statusTimer"));
495
    connect(d->statusTimer, &QTimer::timeout, this, &MainWindow::clearStatus);
496

497
    // update gui timer
498
    d->activityTimer = new QTimer(this);
499
    d->activityTimer->setObjectName(QString::fromLatin1("activityTimer"));
500
    connect(d->activityTimer, &QTimer::timeout, this, &MainWindow::_updateActions);
501
    d->activityTimer->setSingleShot(false);
502
    d->activityTimer->start(150);
503

504
    // update view-sensitive commands when clipboard has changed
505
    QClipboard *clipbd = QApplication::clipboard();
506
    connect(clipbd, &QClipboard::dataChanged, this, &MainWindow::updateEditorActions);
507

508
    d->windowMapper = new QSignalMapper(this);
509

510
    // connection between workspace, window menu and tab bar
511
#if QT_VERSION < QT_VERSION_CHECK(5,15,0)
512
    connect(d->windowMapper, qOverload<QWidget*>(&QSignalMapper::mapped),
513
            this, &MainWindow::onSetActiveSubWindow);
514
#elif QT_VERSION < QT_VERSION_CHECK(6,0,0)
515
    connect(d->windowMapper, &QSignalMapper::mappedWidget,
516
            this, &MainWindow::onSetActiveSubWindow);
517
#else
518
    connect(d->windowMapper, &QSignalMapper::mappedObject,
519
            this, [=](QObject* object) {
520
        onSetActiveSubWindow(qobject_cast<QWidget*>(object));
521
    });
522
#endif
523
    connect(d->mdiArea, &QMdiArea::subWindowActivated,
524
            this, &MainWindow::onWindowActivated);
525

526
    setupDockWindows();
527

528
    // accept drops on the window, get handled in dropEvent, dragEnterEvent
529
    setAcceptDrops(true);
530

531
    statusBar()->showMessage(tr("Ready"), 2001);
532
}
533

534
MainWindow::~MainWindow()
535
{
536
    delete d->status;
537
    delete d;
538
    instance = nullptr;
539
}
540

541
MainWindow* MainWindow::getInstance()
542
{
543
    // MainWindow has a public constructor
544
    return instance;
545
}
546

547
// Helper function to update dock widget according to the user parameter
548
// settings, e.g. register/unregister, enable/disable, show/hide.
549
template<class T>
550
static inline void _updateDockWidget(const char *name,
551
                                    bool enabled,
552
                                    bool show,
553
                                    Qt::DockWidgetArea pos,
554
                                    T callback)
555
{
556
    auto pDockMgr = DockWindowManager::instance();
557
    auto widget = pDockMgr->findRegisteredDockWindow(name);
558
    if (!enabled) {
559
        if(widget) {
560
            pDockMgr->removeDockWindow(widget);
561
            pDockMgr->unregisterDockWindow(name);
562
            widget->deleteLater();
563
        }
564
        return;
565
    }
566
    // Use callback to perform specific update for each type of dock widget
567
    widget = callback(widget);
568
    if(!widget)
569
        return;
570
    DockWindowManager::instance()->registerDockWindow(name, widget);
571
    if(show) {
572
        auto dock = pDockMgr->addDockWindow(
573
                widget->objectName().toUtf8().constData(), widget, pos);
574
        if(dock) {
575
            if(!dock->toggleViewAction()->isChecked())
576
                dock->toggleViewAction()->activate(QAction::Trigger);
577
            OverlayManager::instance()->refresh(dock);
578
        }
579
    }
580
}
581

582
void MainWindow::initDockWindows(bool show)
583
{
584
    updateTreeView(show);
585
    updatePropertyView(show);
586
    updateComboView(show);
587
    updateTaskView(show);
588
    updateDAGView(show);
589
}
590

591
void MainWindow::setupDockWindows()
592
{
593
    // Report view must be created before PythonConsole!
594
    setupReportView();
595
    setupPythonConsole();
596
    setupSelectionView();
597
    setupTaskView();
598

599
    initDockWindows(false);
600

601
    std::vector<QTabWidget::TabPosition> tabPos = {QTabWidget::North,
602
                                                   QTabWidget::South,
603
                                                   QTabWidget::West,
604
                                                   QTabWidget::East};
605
    long value = d->hGrp->GetInt("LeftDockWidgetAreaTabPos", long(tabPos.front()));
606
    if (value >= 0 && value < long(tabPos.size())) {
607
        setTabPosition(Qt::LeftDockWidgetArea, tabPos[value]);
608
    }
609
}
610

611
bool MainWindow::setupTaskView()
612
{
613
    // Task view
614
    if (d->hiddenDockWindows.find("Std_TaskView") == std::string::npos) {
615
        // clang-format off
616
        auto group = App::GetApplication().GetUserParameter()
617
                      .GetGroup("BaseApp")
618
                     ->GetGroup("Preferences")
619
                     ->GetGroup("DockWindows")
620
                     ->GetGroup("TaskView");
621
        // clang-format on
622
        auto taskView = new Gui::TaskView::TaskView(this);
623
        bool restore = group->GetBool("RestoreWidth", taskView->shouldRestoreWidth());
624
        taskView->setRestoreWidth(restore);
625
        taskView->setObjectName(QString::fromLatin1(QT_TRANSLATE_NOOP("QDockWidget","Tasks")));
626
        taskView->setMinimumWidth(210);
627

628
        DockWindowManager* pDockMgr = DockWindowManager::instance();
629
        pDockMgr->registerDockWindow("Std_TaskView", taskView);
630
        return true;
631
    }
632

633
    return false;
634
}
635

636
bool MainWindow::setupSelectionView()
637
{
638
    // Selection view
639
    if (d->hiddenDockWindows.find("Std_SelectionView") == std::string::npos) {
640
        auto pcSelectionView = new SelectionView(nullptr, this);
641
        pcSelectionView->setObjectName
642
            (QString::fromLatin1(QT_TRANSLATE_NOOP("QDockWidget","Selection view")));
643
        pcSelectionView->setMinimumWidth(210);
644

645
        DockWindowManager* pDockMgr = DockWindowManager::instance();
646
        pDockMgr->registerDockWindow("Std_SelectionView", pcSelectionView);
647
        return true;
648
    }
649

650
    return false;
651
}
652

653
bool MainWindow::setupReportView()
654
{
655
    // Report view
656
    if (d->hiddenDockWindows.find("Std_ReportView") == std::string::npos) {
657
        auto pcReport = new ReportOutput(this);
658
        pcReport->setWindowIcon(BitmapFactory().pixmap("MacroEditor"));
659
        pcReport->setObjectName
660
            (QString::fromLatin1(QT_TRANSLATE_NOOP("QDockWidget","Report view")));
661

662
        DockWindowManager* pDockMgr = DockWindowManager::instance();
663
        pDockMgr->registerDockWindow("Std_ReportView", pcReport);
664

665
        auto rvObserver = new ReportOutputObserver(pcReport);
666
        qApp->installEventFilter(rvObserver);
667
        return true;
668
    }
669

670
    return false;
671
}
672

673
bool MainWindow::setupPythonConsole()
674
{
675
    // Python console
676
    if (d->hiddenDockWindows.find("Std_PythonView") == std::string::npos) {
677
        auto pcPython = new PythonConsole(this);
678
        pcPython->setWindowIcon(Gui::BitmapFactory().iconFromTheme("applications-python"));
679
        pcPython->setObjectName
680
            (QString::fromLatin1(QT_TRANSLATE_NOOP("QDockWidget","Python console")));
681

682
        DockWindowManager* pDockMgr = DockWindowManager::instance();
683
        pDockMgr->registerDockWindow("Std_PythonView", pcPython);
684
        return true;
685
    }
686

687
    return false;
688
}
689

690
bool MainWindow::updateTreeView(bool show)
691
{
692
    if (d->hiddenDockWindows.find("Std_TreeView") == std::string::npos) {
693
        ParameterGrp::handle group = App::GetApplication().GetUserParameter().
694
                GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("DockWindows")->GetGroup("TreeView");
695
        bool enabled = group->GetBool("Enabled", false);
696
        _updateDockWidget("Std_TreeView", enabled, show, Qt::RightDockWidgetArea,
697
            [](QWidget *widget) {
698
                if (widget) {
699
                    return widget;
700
                }
701

702
                auto tree = new TreeDockWidget(0,getMainWindow());
703
                tree->setObjectName(QStringLiteral(QT_TRANSLATE_NOOP("QDockWidget","Tree view")));
704
                tree->setMinimumWidth(210);
705
                widget = tree;
706
                return widget;
707
            });
708

709
        return enabled;
710
    }
711

712
    return false;
713
}
714

715
bool MainWindow::updatePropertyView(bool show)
716
{
717
    // Property view
718
    if (d->hiddenDockWindows.find("Std_PropertyView") == std::string::npos) {
719
        ParameterGrp::handle group = App::GetApplication().GetUserParameter().
720
                GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("DockWindows")->GetGroup("PropertyView");
721
        bool enabled = group->GetBool("Enabled", false);
722
        _updateDockWidget("Std_PropertyView", enabled, show, Qt::RightDockWidgetArea,
723
            [](QWidget *widget) {
724
                if (widget) {
725
                    return widget;
726
                }
727

728
                auto pcPropView = new PropertyDockView(0, getMainWindow());
729
                pcPropView->setObjectName(QStringLiteral(QT_TRANSLATE_NOOP("QDockWidget","Property view")));
730
                pcPropView->setMinimumWidth(210);
731
                widget = pcPropView;
732
                return widget;
733
            });
734

735
        return enabled;
736
    }
737

738
    return false;
739
}
740

741
bool MainWindow::updateTaskView(bool show)
742
{
743
    //Task List (task watcher).
744
    if (d->hiddenDockWindows.find("Std_TaskWatcher") == std::string::npos) {
745
        //work through parameter.
746
        ParameterGrp::handle group = App::GetApplication().GetUserParameter().
747
              GetGroup("BaseApp/Preferences/DockWindows/TaskWatcher");
748
        bool enabled = group->GetBool("Enabled", false);
749
        group->SetBool("Enabled", enabled); //ensure entry exists.
750
        _updateDockWidget("Std_TaskWatcher", enabled, show, Qt::RightDockWidgetArea,
751
            [](QWidget *widget) {
752
                if (widget) {
753
                    return widget;
754
                }
755

756
                widget = new TaskView::TaskView(getMainWindow());
757
                widget->setObjectName(QStringLiteral(QT_TRANSLATE_NOOP("QDockWidget","Task List")));
758
                return widget;
759
            });
760

761
        return enabled;
762
    }
763

764
    return false;
765
}
766

767
bool MainWindow::updateComboView(bool show)
768
{
769
    // Combo view
770
    if (d->hiddenDockWindows.find("Std_ComboView") == std::string::npos) {
771
        ParameterGrp::handle group = App::GetApplication().GetUserParameter().
772
                GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("DockWindows")->GetGroup("ComboView");
773
        bool enable = group->GetBool("Enabled", true);
774
        _updateDockWidget("Std_ComboView", enable, show, Qt::LeftDockWidgetArea,
775
            [](QWidget *widget) {
776
                auto pcComboView = qobject_cast<ComboView*>(widget);
777
                if (widget) {
778
                    return widget;
779
                }
780

781
                pcComboView = new ComboView(nullptr, getMainWindow());
782
                pcComboView->setObjectName(QStringLiteral(QT_TRANSLATE_NOOP("QDockWidget", "Model")));
783
                pcComboView->setMinimumWidth(150);
784
                widget = pcComboView;
785
                return widget;
786
            });
787

788
        return enable;
789
    }
790

791
    return false;
792
}
793

794
bool MainWindow::updateDAGView(bool show)
795
{
796
    //Dag View.
797
    if (d->hiddenDockWindows.find("Std_DAGView") == std::string::npos) {
798
        ParameterGrp::handle group = App::GetApplication().GetUserParameter().
799
              GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("DockWindows")->GetGroup("DAGView");
800
        bool enabled = group->GetBool("Enabled", false);
801
        _updateDockWidget("Std_DAGView", enabled, show, Qt::RightDockWidgetArea,
802
            [](QWidget *widget) {
803
                if (widget) {
804
                    return widget;
805
                }
806

807
                auto dagDockWindow = new DAG::DockWindow(nullptr, getMainWindow());
808
                dagDockWindow->setObjectName(QStringLiteral(QT_TRANSLATE_NOOP("QDockWidget","DAG View")));
809
                widget = dagDockWindow;
810
                return widget;
811
            });
812

813
        return enabled;
814
    }
815

816
    return false;
817
}
818

819
QMenu* MainWindow::createPopupMenu ()
820
{
821
    QMenu *menu = new QMenu(this);
822
    populateDockWindowMenu(menu);
823
    menu->addSeparator();
824
    populateToolBarMenu(menu);
825
    menu->addSeparator();
826
    Workbench* wb = WorkbenchManager::instance()->active();
827
    if (wb) {
828
        MenuItem item;
829
        wb->createMainWindowPopupMenu(&item);
830
        if (item.hasItems()) {
831
            menu->addSeparator();
832
            QList<MenuItem*> items = item.getItems();
833
            for (const auto & item : items) {
834
                if (item->command() == "Separator") {
835
                    menu->addSeparator();
836
                }
837
                else {
838
                    Command* cmd = Application::Instance->commandManager().getCommandByName(item->command().c_str());
839
                    if (cmd) cmd->addTo(menu);
840
                }
841
            }
842
        }
843
    }
844

845
    return menu;
846
}
847

848
void MainWindow::tile()
849
{
850
    d->mdiArea->tileSubWindows();
851
}
852

853
void MainWindow::cascade()
854
{
855
    d->mdiArea->cascadeSubWindows();
856
}
857

858
void MainWindow::closeActiveWindow ()
859
{
860
    d->mdiArea->closeActiveSubWindow();
861
}
862

863
int MainWindow::confirmSave(const char *docName, QWidget *parent, bool addCheckbox) {
864
    QMessageBox box(parent?parent:this);
865
    box.setIcon(QMessageBox::Question);
866
    box.setWindowFlags(box.windowFlags() | Qt::WindowStaysOnTopHint);
867
    box.setWindowTitle(QObject::tr("Unsaved document"));
868
    if(docName)
869
        box.setText(QObject::tr("Do you want to save your changes to document '%1' before closing?")
870
                    .arg(QString::fromUtf8(docName)));
871
    else
872
        box.setText(QObject::tr("Do you want to save your changes to document before closing?"));
873

874
    box.setInformativeText(QObject::tr("If you don't save, your changes will be lost."));
875
    box.setStandardButtons(QMessageBox::Discard | QMessageBox::Cancel | QMessageBox::Save);
876
    box.setDefaultButton(QMessageBox::Save);
877
    box.setEscapeButton(QMessageBox::Cancel);
878

879
    QCheckBox checkBox(QObject::tr("Apply answer to all"));
880
    ParameterGrp::handle hGrp;
881
    if(addCheckbox) {
882
         hGrp = App::GetApplication().GetUserParameter().
883
            GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("General");
884
        checkBox.setChecked(hGrp->GetBool("ConfirmAll",false));
885
        checkBox.blockSignals(true);
886
        box.addButton(&checkBox, QMessageBox::ResetRole);
887
    }
888

889
    // add shortcuts
890
    QAbstractButton* saveBtn = box.button(QMessageBox::Save);
891
    if (saveBtn->shortcut().isEmpty()) {
892
        QString text = saveBtn->text();
893
        text.prepend(QLatin1Char('&'));
894
        saveBtn->setShortcut(QKeySequence::mnemonic(text));
895
    }
896

897
    QAbstractButton* discardBtn = box.button(QMessageBox::Discard);
898
    if (discardBtn->shortcut().isEmpty()) {
899
        QString text = discardBtn->text();
900
        text.prepend(QLatin1Char('&'));
901
        discardBtn->setShortcut(QKeySequence::mnemonic(text));
902
    }
903

904
    int res = ConfirmSaveResult::Cancel;
905
    box.adjustSize(); // Silence warnings from Qt on Windows
906
    switch (box.exec())
907
    {
908
    case QMessageBox::Save:
909
        res = checkBox.isChecked()?ConfirmSaveResult::SaveAll:ConfirmSaveResult::Save;
910
        break;
911
    case QMessageBox::Discard:
912
        res = checkBox.isChecked()?ConfirmSaveResult::DiscardAll:ConfirmSaveResult::Discard;
913
        break;
914
    }
915
    if(addCheckbox && res)
916
        hGrp->SetBool("ConfirmAll",checkBox.isChecked());
917
    return res;
918
}
919

920
bool MainWindow::closeAllDocuments (bool close)
921
{
922
    auto docs = App::GetApplication().getDocuments();
923
    try {
924
        docs = App::Document::getDependentDocuments(docs, true);
925
    }
926
    catch(Base::Exception &e) {
927
        e.ReportException();
928
    }
929

930
    bool checkModify = true;
931
    bool saveAll = false;
932
    int failedSaves = 0;
933

934
    for (auto doc : docs) {
935
        auto gdoc = Application::Instance->getDocument(doc);
936
        if (!gdoc)
937
            continue;
938
        if (!gdoc->canClose(false))
939
            return false;
940
        if (!gdoc->isModified()
941
                || doc->testStatus(App::Document::PartialDoc)
942
                || doc->testStatus(App::Document::TempDoc))
943
            continue;
944
        bool save = saveAll;
945
        if (!save && checkModify) {
946
            int res = confirmSave(doc->Label.getStrValue().c_str(), this, docs.size()>1);
947
            switch (res)
948
            {
949
            case ConfirmSaveResult::Cancel:
950
                return false;
951
            case ConfirmSaveResult::SaveAll:
952
                saveAll = true;
953
                /* FALLTHRU */
954
            case ConfirmSaveResult::Save:
955
                save = true;
956
                break;
957
            case ConfirmSaveResult::DiscardAll:
958
                checkModify = false;
959
            }
960
        }
961

962
        if (save && !gdoc->save())
963
            failedSaves++;
964
    }
965

966
    if (failedSaves > 0) {
967
        int ret = QMessageBox::question(
968
            getMainWindow(),
969
            QObject::tr("%1 Document(s) not saved").arg(QString::number(failedSaves)),
970
            QObject::tr("Some documents could not be saved. Do you want to cancel closing?"),
971
            QMessageBox::Discard | QMessageBox::Cancel,
972
            QMessageBox::Discard);
973
        if (ret == QMessageBox::Cancel)
974
            return false;
975
    }
976

977
    if (close)
978
        App::GetApplication().closeAllDocuments();
979

980
    return true;
981
}
982

983
void MainWindow::activateNextWindow ()
984
{
985
    auto tab = d->mdiArea->findChild<QTabBar*>();
986
    if (tab && tab->count() > 0) {
987
        int index = (tab->currentIndex() + 1) % tab->count();
988
        tab->setCurrentIndex(index);
989
    }
990
}
991

992
void MainWindow::activatePreviousWindow ()
993
{
994
    auto tab = d->mdiArea->findChild<QTabBar*>();
995
    if (tab && tab->count() > 0) {
996
        int index = (tab->currentIndex() + tab->count() - 1) % tab->count();
997
        tab->setCurrentIndex(index);
998
    }
999
}
1000

1001
void MainWindow::activateWorkbench(const QString& name)
1002
{
1003
    ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/View");
1004
    bool saveWB = hGrp->GetBool("SaveWBbyTab", false);
1005
    QMdiSubWindow* subWin = d->mdiArea->activeSubWindow();
1006
    if (subWin && saveWB) {
1007
        QString currWb = subWin->property("ownWB").toString();
1008
        if (currWb.isEmpty() || currWb != name) {
1009
            subWin->setProperty("ownWB", name);
1010
        }
1011
    }
1012
    // emit this signal
1013
    Q_EMIT workbenchActivated(name);
1014
    updateActions(true);
1015
}
1016

1017
void MainWindow::whatsThis()
1018
{
1019
    QWhatsThis::enterWhatsThisMode();
1020
}
1021

1022
void MainWindow::showDocumentation(const QString& help)
1023
{
1024
    Base::PyGILStateLocker lock;
1025
    try {
1026
        PyObject* module = PyImport_ImportModule("Help");
1027
        if (module) {
1028
            Py_DECREF(module);
1029
            Gui::Command::addModule(Gui::Command::Gui,"Help");
1030
            Gui::Command::doCommand(Gui::Command::Gui,"Help.show(\"%s\")", help.toStdString().c_str());
1031
        }
1032
    }
1033
    catch (const Base::Exception& e) {
1034
        e.ReportException();
1035
    }
1036
}
1037

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

1116
bool MainWindow::eventFilter(QObject* o, QEvent* e)
1117
{
1118
    if (o != this) {
1119
        if (e->type() == QEvent::WindowStateChange) {
1120
            // notify all mdi views when the active view receives a show normal, show minimized
1121
            // or show maximized event
1122
            auto view = qobject_cast<MDIView*>(o);
1123
            if (view) { // emit this signal
1124
                Qt::WindowStates oldstate = static_cast<QWindowStateChangeEvent*>(e)->oldState();
1125
                Qt::WindowStates newstate = view->windowState();
1126
                if (oldstate != newstate)
1127
                    Q_EMIT windowStateChanged(view);
1128
            }
1129
        }
1130

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

1194
    return QMainWindow::eventFilter(o, e);
1195
}
1196

1197
void MainWindow::addWindow(MDIView* view)
1198
{
1199
    // make workspace parent of view
1200
    bool isempty = d->mdiArea->subWindowList().isEmpty();
1201
    auto child = qobject_cast<QMdiSubWindow*>(view->parentWidget());
1202
    if(!child) {
1203
        child = new QMdiSubWindow(d->mdiArea->viewport());
1204
        child->setAttribute(Qt::WA_DeleteOnClose);
1205
        child->setWidget(view);
1206
        child->setWindowIcon(view->windowIcon());
1207
        QMenu* menu = child->systemMenu();
1208

1209
        // See StdCmdCloseActiveWindow (#0002631)
1210
        QList<QAction*> acts = menu->actions();
1211
        for (auto & act : acts) {
1212
            if (act->shortcut() == QKeySequence(QKeySequence::Close)) {
1213
                act->setShortcuts(QList<QKeySequence>());
1214
                break;
1215
            }
1216
        }
1217

1218
        QAction* action = menu->addAction(tr("Close All"));
1219
        connect(action, &QAction::triggered, d->mdiArea, &QMdiArea::closeAllSubWindows);
1220
        d->mdiArea->addSubWindow(child);
1221
    }
1222

1223
    connect(view, &MDIView::message, this, &MainWindow::showMessage);
1224
    connect(this, &MainWindow::windowStateChanged, view, &MDIView::windowStateChanged);
1225

1226
    // listen to the incoming events of the view
1227
    view->installEventFilter(this);
1228

1229
    // show the very first window in maximized mode
1230
    if (isempty)
1231
        view->showMaximized();
1232
    else
1233
        view->show();
1234
}
1235

1236
/**
1237
 * Removes the instance of Gui::MDiView from the main window and sends am event
1238
 * to the parent widget, a QMdiSubWindow to delete itself.
1239
 * If you want to avoid that the Gui::MDIView instance gets destructed too you
1240
 * must reparent it afterwards, e.g. set parent to NULL.
1241
 */
1242
void MainWindow::removeWindow(Gui::MDIView* view, bool close)
1243
{
1244
    // free all connections
1245
    disconnect(view, &MDIView::message, this, &MainWindow::showMessage);
1246
    disconnect(this, &MainWindow::windowStateChanged, view, &MDIView::windowStateChanged);
1247

1248
    view->removeEventFilter(this);
1249

1250
    // check if the focus widget is a child of the view
1251
    QWidget* foc = this->focusWidget();
1252
    if (foc) {
1253
        QWidget* par = foc->parentWidget();
1254
        while (par) {
1255
            if (par == view) {
1256
                foc->clearFocus();
1257
                break;
1258
            }
1259
            par = par->parentWidget();
1260
        }
1261
    }
1262

1263
    QWidget* parent = view->parentWidget();
1264

1265
    // The call of 'd->mdiArea->removeSubWindow(parent)' causes the QMdiSubWindow
1266
    // to lose its parent and thus the notification in QMdiSubWindow::closeEvent
1267
    // of other mdi windows to get maximized if this window is maximized will fail.
1268
    // However, we must let it here otherwise deleting MDI child views directly can
1269
    // cause other problems.
1270
    //
1271
    // The above mentioned problem can be fixed by setParent(0) which triggers a
1272
    // ChildRemoved event being handled properly inside QMidArea::viewportEvent()
1273
    //
1274
    auto subwindow = qobject_cast<QMdiSubWindow*>(parent);
1275
    if(subwindow && d->mdiArea->subWindowList().contains(subwindow)) {
1276
        subwindow->setParent(nullptr);
1277

1278
        assert(!d->mdiArea->subWindowList().contains(subwindow));
1279
        // d->mdiArea->removeSubWindow(parent);
1280
    }
1281

1282
    if(close)
1283
        parent->deleteLater();
1284
    updateActions();
1285
}
1286

1287
void MainWindow::tabChanged(MDIView* view)
1288
{
1289
    Q_UNUSED(view)
1290
    updateActions();
1291
}
1292

1293
void MainWindow::tabCloseRequested(int index)
1294
{
1295
    auto tab = d->mdiArea->findChild<QTabBar*>();
1296
    if (index < 0 || index >= tab->count())
1297
        return;
1298

1299
    const QList<QMdiSubWindow *> subWindows = d->mdiArea->subWindowList();
1300
    Q_ASSERT(index < subWindows.size());
1301

1302
    QMdiSubWindow *subWindow = d->mdiArea->subWindowList().at(index);
1303
    Q_ASSERT(subWindow);
1304
    subWindow->close();
1305
    updateActions();
1306
}
1307

1308
void MainWindow::onSetActiveSubWindow(QWidget *window)
1309
{
1310
    if (!window)
1311
        return;
1312
    d->mdiArea->setActiveSubWindow(qobject_cast<QMdiSubWindow *>(window));
1313
    updateActions();
1314
}
1315

1316
void MainWindow::setActiveWindow(MDIView* view)
1317
{
1318
    if (!view || d->activeView == view)
1319
        return;
1320
    onSetActiveSubWindow(view->parentWidget());
1321
    d->activeView = view;
1322
    Application::Instance->viewActivated(view);
1323
}
1324

1325
void MainWindow::onWindowActivated(QMdiSubWindow* mdi)
1326
{
1327
    if (!mdi) {
1328
        setWindowTitle(QString());
1329
        setWindowModified(false);
1330
        return;
1331
    }
1332

1333
    auto view = dynamic_cast<MDIView*>(mdi->widget());
1334

1335
    // set active the appropriate window (it needs not to be part of mdiIds, e.g. directly after creation)
1336
    if (view)
1337
    {
1338
        d->activeView = view;
1339
        Application::Instance->viewActivated(view);
1340
    }
1341

1342
    ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/View");
1343
    bool saveWB = hGrp->GetBool("SaveWBbyTab", false);
1344
    if (saveWB) {
1345
        QString currWb = mdi->property("ownWB").toString();
1346
        if (! currWb.isEmpty()) {
1347
            this->activateWorkbench(currWb);
1348
        }
1349
        else {
1350
            mdi->setProperty("ownWB", QString::fromStdString(WorkbenchManager::instance()->active()->name()));
1351
        }
1352
    }
1353

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

1367
    updateActions(true);
1368
}
1369

1370
void MainWindow::onWindowsMenuAboutToShow()
1371
{
1372
    QList<QMdiSubWindow*> windows = d->mdiArea->subWindowList(QMdiArea::CreationOrder);
1373
    QWidget* active = d->mdiArea->activeSubWindow();
1374

1375
    // We search for the 'Std_WindowsMenu' command that provides the list of actions
1376
    CommandManager& cMgr = Application::Instance->commandManager();
1377
    Command* cmd = cMgr.getCommandByName("Std_WindowsMenu");
1378
    QList<QAction*> actions = qobject_cast<ActionGroup*>(cmd->getAction())->actions();
1379

1380
    // do the connection only once
1381
    static bool firstShow = true;
1382
    if (firstShow) {
1383
        firstShow = false;
1384
        QAction* last = actions.isEmpty() ? 0 : actions.last();
1385
        for (const auto & action : actions) {
1386
            if (action == last)
1387
                break; // this is a separator
1388
            connect(action, &QAction::triggered, d->windowMapper, qOverload<>(&QSignalMapper::map));
1389
        }
1390
    }
1391

1392
    int numWindows = std::min<int>(actions.count()-1, windows.count());
1393
    for (int index = 0; index < numWindows; index++) {
1394
        QWidget* child = windows.at(index);
1395
        QAction* action = actions.at(index);
1396
        QString text;
1397
        QString title = child->windowTitle();
1398
        int lastIndex = title.lastIndexOf(QString::fromLatin1("[*]"));
1399
        if (lastIndex > 0) {
1400
            title = title.left(lastIndex);
1401
            if (child->isWindowModified())
1402
                title = QString::fromLatin1("%1*").arg(title);
1403
        }
1404
        if (index < 9)
1405
            text = QString::fromLatin1("&%1 %2").arg(index+1).arg(title);
1406
        else
1407
            text = QString::fromLatin1("%1 %2").arg(index+1).arg(title);
1408
        action->setText(text);
1409
        action->setVisible(true);
1410
        action->setChecked(child == active);
1411
        d->windowMapper->setMapping(action, child);
1412
    }
1413

1414
    // if less windows than actions
1415
    for (int index = numWindows; index < actions.count(); index++)
1416
        actions[index]->setVisible(false);
1417
    // show the separator
1418
    if (numWindows > 0)
1419
        actions.last()->setVisible(true);
1420
}
1421

1422
void MainWindow::onToolBarMenuAboutToShow()
1423
{
1424
    auto menu = static_cast<QMenu*>(sender());
1425
    menu->clear();
1426
    populateToolBarMenu(menu);
1427

1428
    menu->addSeparator();
1429

1430
    Application::Instance->commandManager().getCommandByName("Std_ToggleToolBarLock")->addTo(menu);
1431
}
1432

1433
void MainWindow::populateToolBarMenu(QMenu *menu)
1434
{
1435
    QList<QToolBar*> toolbars = this->findChildren<QToolBar*>();
1436
    for (const auto & toolbar : toolbars) {
1437
        if (auto parent = toolbar->parentWidget()) {
1438
            if (parent == this
1439
                    || parent == statusBar()
1440
                    || parent->parentWidget() == statusBar()
1441
                    || parent->parentWidget() == menuBar()) {
1442
                QAction* action = toolbar->toggleViewAction();
1443
                action->setToolTip(tr("Toggles this toolbar"));
1444
                action->setStatusTip(tr("Toggles this toolbar"));
1445
                action->setWhatsThis(tr("Toggles this toolbar"));
1446
                menu->addAction(action);
1447
            }
1448
        }
1449
    }
1450
}
1451

1452
void MainWindow::onDockWindowMenuAboutToShow()
1453
{
1454
    auto menu = static_cast<QMenu*>(sender());
1455
    menu->clear();
1456
    populateDockWindowMenu(menu);
1457
}
1458

1459
void MainWindow::populateDockWindowMenu(QMenu *menu)
1460
{
1461
    QList<QDockWidget*> dock = this->findChildren<QDockWidget*>();
1462
    for (auto & it : dock) {
1463
        QAction* action = it->toggleViewAction();
1464
        action->setToolTip(tr("Toggles this dockable window"));
1465
        action->setStatusTip(tr("Toggles this dockable window"));
1466
        action->setWhatsThis(tr("Toggles this dockable window"));
1467
        menu->addAction(action);
1468
    }
1469
}
1470

1471
void MainWindow::setDockWindowMenu(QMenu* menu)
1472
{
1473
    connect(menu, &QMenu::aboutToShow, this, &MainWindow::onDockWindowMenuAboutToShow);
1474
}
1475

1476
void MainWindow::setToolBarMenu(QMenu* menu)
1477
{
1478
    connect(menu, &QMenu::aboutToShow, this, &MainWindow::onToolBarMenuAboutToShow);
1479
}
1480

1481
void MainWindow::setWindowsMenu(QMenu* menu)
1482
{
1483
    connect(menu, &QMenu::aboutToShow, this, &MainWindow::onWindowsMenuAboutToShow);
1484
}
1485

1486
QList<QWidget*> MainWindow::windows(QMdiArea::WindowOrder order) const
1487
{
1488
    QList<QWidget*> mdis;
1489
    QList<QMdiSubWindow*> wnds = d->mdiArea->subWindowList(order);
1490
    for (const auto & wnd : wnds) {
1491
        mdis << wnd->widget();
1492
    }
1493
    return mdis;
1494
}
1495

1496
MDIView* MainWindow::activeWindow() const
1497
{
1498
    // each activated window notifies this main window when it is activated
1499
    return d->activeView;
1500
}
1501

1502
void MainWindow::closeEvent (QCloseEvent * e)
1503
{
1504
    Application::Instance->tryClose(e);
1505
    if (e->isAccepted()) {
1506
        // Send close event to all non-modal dialogs
1507
        QList<QDialog*> dialogs = this->findChildren<QDialog*>();
1508
        // It is possible that closing a dialog internally closes further dialogs. Thus,
1509
        // we have to check the pointer before.
1510
        QVector< QPointer<QDialog> > dialogs_ptr;
1511
        for (const auto & dialog : dialogs) {
1512
            dialogs_ptr.append(dialog);
1513
        }
1514
        for (auto & it : dialogs_ptr) {
1515
            if (!it.isNull())
1516
                it->close();
1517
        }
1518
        QList<MDIView*> mdis = this->findChildren<MDIView*>();
1519
        // Force to close any remaining (passive) MDI child views
1520
        for (auto & mdi : mdis) {
1521
            mdi->hide();
1522
            mdi->deleteLater();
1523
        }
1524

1525
        if (Workbench* wb = WorkbenchManager::instance()->active())
1526
            wb->removeTaskWatcher();
1527

1528
        Q_EMIT  mainWindowClosed();
1529
        d->activityTimer->stop();
1530

1531
        // https://forum.freecad.org/viewtopic.php?f=8&t=67748
1532
        // When the session manager jumps in it can happen that the closeEvent()
1533
        // function is triggered twice and for the second call the main window might be
1534
        // invisible. In this case the window settings shouldn't be saved.
1535
        if (isVisible())
1536
            saveWindowSettings();
1537

1538
        delete d->assistant;
1539
        d->assistant = nullptr;
1540

1541
        // See createMimeDataFromSelection
1542
        QVariant prop = this->property("x-documentobject-file");
1543
        if (!prop.isNull()) {
1544
            Base::FileInfo fi((const char*)prop.toByteArray());
1545
            if (fi.exists())
1546
                fi.deleteFile();
1547
        }
1548

1549
        if (this->property("QuitOnClosed").isValid()) {
1550
            QApplication::closeAllWindows();
1551
            qApp->quit(); // stop the event loop
1552
        }
1553
    }
1554
}
1555

1556
void MainWindow::showEvent(QShowEvent* e)
1557
{
1558
    std::clog << "Show main window" << std::endl;
1559
    QMainWindow::showEvent(e);
1560
}
1561

1562
void MainWindow::hideEvent(QHideEvent* e)
1563
{
1564
    std::clog << "Hide main window" << std::endl;
1565
    QMainWindow::hideEvent(e);
1566
}
1567

1568
void MainWindow::processMessages(const QList<QString> & msg)
1569
{
1570
    // handle all the messages to open files
1571
    try {
1572
        WaitCursor wc;
1573
        std::list<std::string> files;
1574
        QString action = QString::fromStdString("OpenFile:");
1575
        for (const auto & it : msg) {
1576
            if (it.startsWith(action))
1577
                files.emplace_back(it.mid(action.size()).toStdString());
1578
        }
1579
        files = App::Application::processFiles(files);
1580
        for (const auto & file : files) {
1581
            QString filename = QString::fromUtf8(file.c_str(), file.size());
1582
            FileDialog::setWorkingDirectory(filename);
1583
        }
1584
    }
1585
    catch (const Base::SystemExitException&) {
1586
    }
1587
}
1588

1589
void MainWindow::delayedStartup()
1590
{
1591
    // automatically run unit tests in Gui
1592
    if (App::Application::Config()["RunMode"] == "Internal") {
1593
        QTimer::singleShot(1000, this, []{
1594
            try {
1595
                    string command =
1596
                    "import sys\n"
1597
                    "import FreeCAD\n"
1598
                    "import QtUnitGui\n\n"
1599
                    "testCase = FreeCAD.ConfigGet(\"TestCase\")\n"
1600
                    "QtUnitGui.addTest(testCase)\n"
1601
                    "QtUnitGui.setTest(testCase)\n"
1602
                    "result = QtUnitGui.runTest()\n"
1603
                    "sys.stdout.flush()\n";
1604
                    if (App::Application::Config()["ExitTests"] == "yes") {
1605
                        command += "sys.exit(0 if result else 1)";
1606
                    }
1607
                    Base::Interpreter().runString(command.c_str());
1608
            }
1609
            catch (const Base::SystemExitException&) {
1610
                throw;
1611
            }
1612
            catch (const Base::Exception& e) {
1613
                e.ReportException();
1614
            }
1615
        });
1616
        return;
1617
    }
1618

1619
    // processing all command line files
1620
    try {
1621
        std::list<std::string> files = App::Application::getCmdLineFiles();
1622
        files = App::Application::processFiles(files);
1623
        for (const auto & file : files) {
1624
            QString filename = QString::fromUtf8(file.c_str(), file.size());
1625
            FileDialog::setWorkingDirectory(filename);
1626
        }
1627
    }
1628
    catch (const Base::SystemExitException&) {
1629
        throw;
1630
    }
1631

1632
    const std::map<std::string,std::string>& cfg = App::Application::Config();
1633
    auto it = cfg.find("StartHidden");
1634
    if (it != cfg.end()) {
1635
        QApplication::quit();
1636
        return;
1637
    }
1638

1639
    // TODO: Check for deprecated settings
1640
    Application::Instance->checkForDeprecatedSettings();
1641

1642
    // Create new document?
1643
    ParameterGrp::handle hGrp = WindowParameter::getDefaultParameter()->GetGroup("Document");
1644
    if (hGrp->GetBool("CreateNewDoc", false)) {
1645
        if (App::GetApplication().getDocuments().empty()){
1646
            Application::Instance->commandManager().runCommandByName("Std_New");
1647
        }
1648
    }
1649

1650
    if (hGrp->GetBool("RecoveryEnabled", true)) {
1651
        Application::Instance->checkForPreviousCrashes();
1652
    }
1653
}
1654

1655
void MainWindow::appendRecentFile(const QString& filename)
1656
{
1657
    auto recent = this->findChild<RecentFilesAction *>
1658
        (QString::fromLatin1("recentFiles"));
1659
    if (recent) {
1660
        recent->appendFile(filename);
1661
    }
1662
}
1663

1664
void MainWindow::appendRecentMacro(const QString& filename)
1665
{
1666
    auto recent = this->findChild<RecentMacrosAction *>
1667
        (QString::fromLatin1("recentMacros"));
1668
    if (recent) {
1669
        recent->appendFile(filename);
1670
    }
1671
}
1672

1673
void MainWindow::updateActions(bool delay)
1674
{
1675
    //make it safe to call before the main window is actually created
1676
    if (!instance)
1677
        return;
1678

1679
    if (!d->activityTimer->isActive()) {
1680
        // If for some reason updateActions() is called from a worker thread
1681
        // we must avoid to directly call QTimer::start() because this leaves
1682
        // the whole application in a weird state
1683
        if (d->activityTimer->thread() != QThread::currentThread()) {
1684
            QMetaObject::invokeMethod(d->activityTimer, "start", Qt::QueuedConnection,
1685
                Q_ARG(int, 150));
1686
        }
1687
        else {
1688
            d->activityTimer->start(150);
1689
        }
1690
    }
1691
    else if (delay) {
1692
        if (!d->actionUpdateDelay)
1693
            d->actionUpdateDelay = 1;
1694
    }
1695
    else {
1696
        d->actionUpdateDelay = -1;
1697
    }
1698
}
1699

1700
void MainWindow::_updateActions()
1701
{
1702
    if (isVisible() && d->actionUpdateDelay <= 0) {
1703
        FC_LOG("update actions");
1704
        d->activityTimer->stop();
1705
        Application::Instance->commandManager().testActive();
1706
    }
1707

1708
    d->actionUpdateDelay = 0;
1709

1710
    if (auto view = activeWindow()) {
1711
        setWindowTitle(view->buildWindowTitle());
1712
        if (auto document = view->getGuiDocument()) {
1713
            setWindowModified(document->isModified());
1714
        }
1715
    }
1716
}
1717

1718
void MainWindow::updateEditorActions()
1719
{
1720
    Command* cmd = nullptr;
1721
    CommandManager& mgr = Application::Instance->commandManager();
1722

1723
    cmd = mgr.getCommandByName("Std_Cut");
1724
    if (cmd) cmd->testActive();
1725

1726
    cmd = mgr.getCommandByName("Std_Copy");
1727
    if (cmd) cmd->testActive();
1728

1729
    cmd = mgr.getCommandByName("Std_Paste");
1730
    if (cmd) cmd->testActive();
1731

1732
    cmd = mgr.getCommandByName("Std_Undo");
1733
    if (cmd) cmd->testActive();
1734

1735
    cmd = mgr.getCommandByName("Std_Redo");
1736
    if (cmd) cmd->testActive();
1737
}
1738

1739
void MainWindow::switchToTopLevelMode()
1740
{
1741
    QList<QDockWidget*> dw = this->findChildren<QDockWidget*>();
1742
    for (auto & it : dw) {
1743
        it->setParent(nullptr, Qt::Window);
1744
        it->show();
1745
    }
1746
    QList<QWidget*> mdi = getMainWindow()->windows();
1747
    for (auto & it : mdi) {
1748
        it->setParent(nullptr, Qt::Window);
1749
        it->show();
1750
    }
1751
}
1752

1753
void MainWindow::switchToDockedMode()
1754
{
1755
    // Search for all top-level MDI views
1756
    QWidgetList toplevel = QApplication::topLevelWidgets();
1757
    for (const auto & it : toplevel) {
1758
        auto view = qobject_cast<MDIView*>(it);
1759
        if (view)
1760
            view->setCurrentViewMode(MDIView::Child);
1761
    }
1762
}
1763

1764
void MainWindow::loadWindowSettings()
1765
{
1766
    QString vendor = QString::fromUtf8(App::Application::Config()["ExeVendor"].c_str());
1767
    QString application = QString::fromUtf8(App::Application::Config()["ExeName"].c_str());
1768
    int major = (QT_VERSION >> 0x10) & 0xff;
1769
    int minor = (QT_VERSION >> 0x08) & 0xff;
1770
    QString qtver = QStringLiteral("Qt%1.%2").arg(major).arg(minor);
1771
    QSettings config(vendor, application);
1772

1773
    QRect rect = QApplication::primaryScreen()->availableGeometry();
1774
    int maxHeight = rect.height();
1775
    int maxWidth = rect.width();
1776

1777
    config.beginGroup(qtver);
1778
    QPoint pos = config.value(QStringLiteral("Position"), this->pos()).toPoint();
1779
    maxWidth -= pos.x();
1780
    maxHeight -= pos.y();
1781
    QSize size = config.value(QStringLiteral("Size"), QSize(maxWidth, maxHeight)).toSize();
1782
    bool max = config.value(QStringLiteral("Maximized"), false).toBool();
1783
    bool showStatusBar = config.value(QStringLiteral("StatusBar"), true).toBool();
1784
    QByteArray windowState = config.value(QStringLiteral("MainWindowState")).toByteArray();
1785
    config.endGroup();
1786

1787

1788
    std::string geometry = d->hGrp->GetASCII("Geometry");
1789
    std::istringstream iss(geometry);
1790
    int x,y,w,h;
1791
    if (iss >> x >> y >> w >> h) {
1792
        pos = QPoint(x, y);
1793
        size = QSize(w, h);
1794
    }
1795

1796
    max = d->hGrp->GetBool("Maximized", max);
1797
    showStatusBar = d->hGrp->GetBool("StatusBar", showStatusBar);
1798
    std::string wstate = d->hGrp->GetASCII("MainWindowState");
1799
    if (!wstate.empty()) {
1800
        windowState = QByteArray::fromBase64(wstate.c_str());
1801
    }
1802

1803
    resize(size);
1804
    int x1{},x2{},y1{},y2{};
1805
    // make sure that the main window is not totally out of the visible rectangle
1806
    rect.getCoords(&x1, &y1, &x2, &y2);
1807
    pos.setX(qMin(qMax(pos.x(),x1-this->width()+30),x2-30));
1808
    pos.setY(qMin(qMax(pos.y(),y1-10),y2-10));
1809
    this->move(pos);
1810

1811
    Base::StateLocker guard(d->_restoring);
1812

1813
    d->restoreWindowState(windowState);
1814
    std::clog << "Main window restored" << std::endl;
1815

1816
    max ? showMaximized() : show();
1817

1818
    // make menus and tooltips usable in fullscreen under Windows, see issue #7563
1819
#if defined(Q_OS_WIN)
1820
    #if QT_VERSION < QT_VERSION_CHECK(6,0,0)
1821
        if (QWindow* win = this->windowHandle()) {
1822
            QWindowsWindowFunctions::setHasBorderInFullScreen(win, true);
1823
        }
1824
    #else
1825
        using namespace QNativeInterface::Private;
1826
        if (auto *windowsWindow = dynamic_cast<QWindowsWindow*>(this->windowHandle())) {
1827
            windowsWindow->setHasBorderInFullScreen(true);
1828
        }
1829
    #endif
1830
#endif
1831

1832
    statusBar()->setVisible(showStatusBar);
1833

1834
    ToolBarManager::getInstance()->restoreState();
1835
    std::clog << "Toolbars restored" << std::endl;
1836

1837
    OverlayManager::instance()->restore();
1838
}
1839

1840
bool MainWindow::isRestoringWindowState() const
1841
{
1842
    return d->_restoring;
1843
}
1844

1845
void MainWindowP::restoreWindowState(const QByteArray &windowState)
1846
{
1847
    if (windowState.isEmpty())
1848
        return;
1849

1850
    Base::StateLocker guard(_restoring);
1851

1852
    // tmp. disable the report window to suppress some bothering warnings
1853
    if (Base::Console().IsMsgTypeEnabled("ReportOutput", Base::ConsoleSingleton::MsgType_Wrn)) {
1854
        Base::Console().SetEnabledMsgType("ReportOutput", Base::ConsoleSingleton::MsgType_Wrn, false);
1855
        getMainWindow()->restoreState(windowState);
1856
        Base::Console().SetEnabledMsgType("ReportOutput", Base::ConsoleSingleton::MsgType_Wrn, true);
1857
    } else
1858
        getMainWindow()->restoreState(windowState);
1859

1860
    Base::ConnectionBlocker block(connParam);
1861
    // as a notification for user code on window state restore
1862
    hGrp->SetBool("WindowStateRestored", !hGrp->GetBool("WindowStateRestored", false));
1863
}
1864

1865
void MainWindow::saveWindowSettings(bool canDelay)
1866
{
1867
    if (isRestoringWindowState())
1868
        return;
1869

1870
    if (canDelay) {
1871
        d->saveStateTimer.start(100);
1872
        return;
1873
    }
1874

1875
    QString vendor = QString::fromUtf8(App::Application::Config()["ExeVendor"].c_str());
1876
    QString application = QString::fromUtf8(App::Application::Config()["ExeName"].c_str());
1877
    int major = (QT_VERSION >> 0x10) & 0xff;
1878
    int minor = (QT_VERSION >> 0x08) & 0xff;
1879
    QString qtver = QStringLiteral("Qt%1.%2").arg(major).arg(minor);
1880
    QSettings config(vendor, application);
1881

1882
#if 0
1883
    config.beginGroup(qtver);
1884
    config.setValue(QStringLiteral("Size"), this->size());
1885
    config.setValue(QStringLiteral("Position"), this->pos());
1886
    config.setValue(QStringLiteral("Maximized"), this->isMaximized());
1887
    config.setValue(QStringLiteral("MainWindowState"), this->saveState());
1888
    config.setValue(QStringLiteral("StatusBar"), this->statusBar()->isVisible());
1889
    config.endGroup();
1890
#else
1891
    // We are migrating from saving qt main window layout state in QSettings to
1892
    // FreeCAD parameters, for more control.
1893
#endif
1894

1895
    Base::ConnectionBlocker block(d->connParam);
1896
    d->hGrp->SetBool("Maximized", this->isMaximized());
1897
    d->hGrp->SetBool("StatusBar", this->statusBar()->isVisible());
1898
    d->hGrp->SetASCII("MainWindowState", this->saveState().toBase64().constData());
1899

1900
    std::ostringstream ss;
1901
    QRect rect(this->pos(), this->size());
1902
    ss << rect.left() << " " << rect.top() << " " << rect.width() << " " << rect.height();
1903
    d->hGrp->SetASCII("Geometry", ss.str().c_str());
1904

1905
    DockWindowManager::instance()->saveState();
1906
    OverlayManager::instance()->save();
1907
    ToolBarManager::getInstance()->saveState();
1908
}
1909

1910
void MainWindow::startSplasher()
1911
{
1912
    // startup splasher
1913
    // when running in verbose mode no splasher
1914
    if (!(App::Application::Config()["Verbose"] == "Strict") &&
1915
         (App::Application::Config()["RunMode"] == "Gui")) {
1916
        ParameterGrp::handle hGrp = App::GetApplication().GetUserParameter().
1917
            GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("General");
1918
        // first search for an external image file
1919
        if (hGrp->GetBool("ShowSplasher", true)) {
1920
            d->splashscreen = new SplashScreen(this->splashImage());
1921

1922
            if (!hGrp->GetBool("ShowSplasherMessages", false)) {
1923
                d->splashscreen->setShowMessages(false);
1924
            }
1925

1926
            d->splashscreen->show();
1927
        }
1928
        else {
1929
            d->splashscreen = nullptr;
1930
        }
1931
    }
1932
}
1933

1934
void MainWindow::stopSplasher()
1935
{
1936
    if (d->splashscreen) {
1937
        d->splashscreen->finish(this);
1938
        delete d->splashscreen;
1939
        d->splashscreen = nullptr;
1940
    }
1941
}
1942

1943
QPixmap MainWindow::aboutImage() const
1944
{
1945
    // See if we have a custom About screen image set
1946
    QPixmap about_image;
1947
    QFileInfo fi(QString::fromLatin1("images:about_image.png"));
1948
    if (fi.isFile() && fi.exists())
1949
        about_image.load(fi.filePath(), "PNG");
1950

1951
    std::string about_path = App::Application::Config()["AboutImage"];
1952
    if (!about_path.empty() && about_image.isNull()) {
1953
        QString path = QString::fromUtf8(about_path.c_str());
1954
        if (QDir(path).isRelative()) {
1955
            QString home = QString::fromStdString(App::Application::getHomePath());
1956
            path = QFileInfo(QDir(home), path).absoluteFilePath();
1957
        }
1958
        about_image.load(path);
1959

1960
        // Now try the icon paths
1961
        if (about_image.isNull()) {
1962
            about_image = Gui::BitmapFactory().pixmap(about_path.c_str());
1963
        }
1964
    }
1965

1966
    return about_image;
1967
}
1968

1969
/**
1970
 * Displays a warning about this being a developer build. Designed for display in the Splashscreen.
1971
 * \param painter The painter to draw the warning into
1972
 * \param startPosition The painter-space coordinates to start the warning box at.
1973
 * \param maxSize The maximum extents for the box that is drawn. If the text exceeds this size it
1974
 * will be scaled down to fit.
1975
 * \note The text string is translatable, so its length is somewhat unpredictable. It is always
1976
 * displayed as two lines, regardless of the length of the text (e.g. no wrapping is done). Only the
1977
 * width is considered, the height simply follows from the font size.
1978
 */
1979
void MainWindow::renderDevBuildWarning(
1980
    QPainter &painter,
1981
    const QPoint startPosition,
1982
    const QSize maxSize,
1983
    QColor color)
1984
{
1985
    // Create a background box that fades out the artwork for better legibility
1986
    QColor fader (Qt::white);
1987
    constexpr float halfDensity (0.65F);
1988
    fader.setAlphaF(halfDensity);
1989
    QBrush fillBrush(fader, Qt::BrushStyle::SolidPattern);
1990
    painter.setBrush(fillBrush);
1991

1992
    // Construct the lines of text and figure out how much space they need
1993
    const auto devWarningLine1 = tr("WARNING: This is a development version.");
1994
    const auto devWarningLine2 = tr("Please do not use it in a production environment.");
1995
    QFontMetrics fontMetrics(painter.font()); // Try to use the existing font
1996
    int padding = QtTools::horizontalAdvance(fontMetrics, QLatin1String("M")); // Arbitrary
1997
    int line1Width = QtTools::horizontalAdvance(fontMetrics, devWarningLine1);
1998
    int line2Width = QtTools::horizontalAdvance(fontMetrics, devWarningLine2);
1999
    int boxWidth = std::max(line1Width,line2Width) + 2 * padding;
2000
    int lineHeight = fontMetrics.lineSpacing();
2001
    if (boxWidth > maxSize.width()) {
2002
        // Especially if the text was translated, there is a chance that using the existing font
2003
        // will exceed the width of the Splashscreen graphic. Resize down so that it fits, no matter
2004
        // how long the text strings are.
2005
        float reductionFactor = static_cast<float>(maxSize.width()) / static_cast<float>(boxWidth);
2006
        int newFontSize = static_cast<int>(painter.font().pointSize() * reductionFactor);
2007
        padding *= reductionFactor;
2008
        QFont newFont = painter.font();
2009
        newFont.setPointSize(newFontSize);
2010
        painter.setFont(newFont);
2011
        lineHeight = painter.fontMetrics().lineSpacing();
2012
        boxWidth = maxSize.width();
2013
    }
2014
    constexpr float lineExpansionFactor(2.3F);
2015
    int boxHeight = static_cast<int>(lineHeight*lineExpansionFactor);
2016

2017
    // Draw the background rectangle and the text
2018
    painter.setPen(color);
2019
    painter.drawRect(startPosition.x(), startPosition.y(), boxWidth, boxHeight);
2020
    painter.drawText(startPosition.x()+padding, startPosition.y()+lineHeight, devWarningLine1);
2021
    painter.drawText(startPosition.x()+padding, startPosition.y()+2*lineHeight, devWarningLine2);
2022
}
2023

2024
QPixmap MainWindow::splashImage() const
2025
{
2026
    // search in the UserAppData dir as very first
2027
    QPixmap splash_image;
2028
    QFileInfo fi(QString::fromLatin1("images:splash_image.png"));
2029
    if (fi.isFile() && fi.exists())
2030
        splash_image.load(fi.filePath(), "PNG");
2031

2032
    // if no image was found try the config
2033
    std::string splash_path = App::Application::Config()["SplashScreen"];
2034
    if (splash_image.isNull()) {
2035
        QString path = QString::fromUtf8(splash_path.c_str());
2036
        if (QDir(path).isRelative()) {
2037
            QString home = QString::fromStdString(App::Application::getHomePath());
2038
            path = QFileInfo(QDir(home), path).absoluteFilePath();
2039
        }
2040

2041
        splash_image.load(path);
2042
    }
2043

2044
    // now try the icon paths
2045
    float pixelRatio (1.0);
2046
    if (splash_image.isNull()) {
2047
        // determine the count of splashes
2048
        QStringList pixmaps = Gui::BitmapFactory().findIconFiles().filter(QString::fromStdString(splash_path));
2049
        // divide by 2 since there's two sets (normal and 2x)
2050
        // minus 1 to ignore the default splash that isn't numbered
2051
        int splash_count = pixmaps.count()/2 - 1;
2052

2053
        // set a random splash path
2054
        if (splash_count > 0) {
2055
            int random = rand() % splash_count;
2056
            splash_path += std::to_string(random);
2057
        }
2058
        if (qApp->devicePixelRatio() > 1.0) {
2059
            // For HiDPI screens, we have a double-resolution version of the splash image
2060
            splash_path += "_2x";
2061
            splash_image = Gui::BitmapFactory().pixmap(splash_path.c_str());
2062
            splash_image.setDevicePixelRatio(2.0);
2063
            pixelRatio = 2.0;
2064
        }
2065
        else {
2066
            splash_image = Gui::BitmapFactory().pixmap(splash_path.c_str());
2067
        }
2068
    }
2069

2070
    // include application name and version number
2071
    std::map<std::string,std::string>::const_iterator tc = App::Application::Config().find("SplashInfoColor");
2072
    std::map<std::string,std::string>::const_iterator wc = App::Application::Config().find("SplashWarningColor");
2073
    if (tc != App::Application::Config().end() && wc != App::Application::Config().end()) {
2074
        QString title = qApp->applicationName();
2075
        QString major   = QString::fromLatin1(App::Application::Config()["BuildVersionMajor"].c_str());
2076
        QString minor   = QString::fromLatin1(App::Application::Config()["BuildVersionMinor"].c_str());
2077
        QString point   = QString::fromLatin1(App::Application::Config()["BuildVersionPoint"].c_str());
2078
        QString suffix  = QString::fromLatin1(App::Application::Config()["BuildVersionSuffix"].c_str());
2079
        QString version = QString::fromLatin1("%1.%2.%3%4").arg(major, minor, point, suffix);
2080
        QString position, fontFamily;
2081

2082
        std::map<std::string,std::string>::const_iterator te = App::Application::Config().find("SplashInfoExeName");
2083
        std::map<std::string,std::string>::const_iterator tv = App::Application::Config().find("SplashInfoVersion");
2084
        std::map<std::string,std::string>::const_iterator tp = App::Application::Config().find("SplashInfoPosition");
2085
        std::map<std::string,std::string>::const_iterator tf = App::Application::Config().find("SplashInfoFont");
2086
        if (te != App::Application::Config().end())
2087
            title = QString::fromUtf8(te->second.c_str());
2088
        if (tv != App::Application::Config().end())
2089
            version = QString::fromUtf8(tv->second.c_str());
2090
        if (tp != App::Application::Config().end())
2091
            position = QString::fromUtf8(tp->second.c_str());
2092
        if (tf != App::Application::Config().end())
2093
            fontFamily = QString::fromUtf8(tf->second.c_str());
2094

2095
        QPainter painter;
2096
        painter.begin(&splash_image);
2097
        if (!fontFamily.isEmpty()) {
2098
            QFont font = painter.font();
2099
            if (font.fromString(fontFamily))
2100
                painter.setFont(font);
2101
        }
2102

2103
        QFont fontExe = painter.font();
2104
        fontExe.setPointSizeF(20.0);
2105
        QFontMetrics metricExe(fontExe);
2106
        int l = QtTools::horizontalAdvance(metricExe, title);
2107
        if (title == QLatin1String("FreeCAD")) {
2108
            l = 0.0; // "FreeCAD" text is already part of the splashscreen, version goes below it
2109
        }
2110
        int w = splash_image.width();
2111
        int h = splash_image.height();
2112

2113
        QFont fontVer = painter.font();
2114
        fontVer.setPointSizeF(11.0);
2115
        QFontMetrics metricVer(fontVer);
2116
        int v = QtTools::horizontalAdvance(metricVer, version);
2117

2118
        int x = -1, y = -1;
2119
        QRegularExpression rx(QLatin1String("(\\d+).(\\d+)"));
2120
        auto match = rx.match(position);
2121
        if (match.hasMatch()) {
2122
            x = match.captured(1).toInt();
2123
            y = match.captured(2).toInt();
2124
        }
2125
        else {
2126
            x = w - (l + v + 10);
2127
            y = h - 20;
2128
        }
2129

2130
        QColor color(QString::fromLatin1(tc->second.c_str()));
2131
        if (color.isValid()) {
2132
            painter.setPen(color);
2133
            painter.setFont(fontExe);
2134
            if (title != QLatin1String("FreeCAD")) {
2135
                // FreeCAD's Splashscreen already contains the EXE name, no need to draw it
2136
                painter.drawText(x, y, title);
2137
            }
2138
            painter.setFont(fontVer);
2139
            painter.drawText(x + (l + 235), y - 7, version);
2140
            QColor warningColor(QString::fromLatin1(wc->second.c_str()));
2141
            if (suffix == QLatin1String("dev") && warningColor.isValid()) {
2142
                fontVer.setPointSizeF(14.0);
2143
                painter.setFont(fontVer);
2144
                const int lineHeight = metricVer.lineSpacing();
2145
                const int padding {45}; // Distance from the edge of the graphic's bounding box
2146
                QPoint startPosition(padding, y + lineHeight*2);
2147
                QSize maxSize(w/pixelRatio - 2*padding, lineHeight * 3);
2148
                MainWindow::renderDevBuildWarning(painter, startPosition, maxSize, warningColor);
2149
            }
2150
            painter.end();
2151
        }
2152
    }
2153

2154
    return splash_image;
2155
}
2156

2157
/**
2158
 * Drops the event \a e and tries to open the files.
2159
 */
2160
void MainWindow::dropEvent (QDropEvent* e)
2161
{
2162
    const QMimeData* data = e->mimeData();
2163
    if (data->hasUrls()) {
2164
        // load the files into the active document if there is one, otherwise let create one
2165
        loadUrls(App::GetApplication().getActiveDocument(), data->urls());
2166
    }
2167
    else {
2168
        QMainWindow::dropEvent(e);
2169
    }
2170
}
2171

2172
void MainWindow::dragEnterEvent (QDragEnterEvent * e)
2173
{
2174
    // Here we must allow uri drafs and check them in dropEvent
2175
    const QMimeData* data = e->mimeData();
2176
    if (data->hasUrls()) {
2177
        e->accept();
2178
    }
2179
    else {
2180
        e->ignore();
2181
    }
2182
}
2183

2184
static QLatin1String _MimeDocObj("application/x-documentobject");
2185
static QLatin1String _MimeDocObjX("application/x-documentobject-x");
2186
static QLatin1String _MimeDocObjFile("application/x-documentobject-file");
2187
static QLatin1String _MimeDocObjXFile("application/x-documentobject-x-file");
2188

2189
QMimeData * MainWindow::createMimeDataFromSelection () const
2190
{
2191
    std::vector<App::DocumentObject*> sel;
2192
    std::set<App::DocumentObject*> objSet;
2193
    for(auto &s : Selection().getCompleteSelection()) {
2194
        if(s.pObject && s.pObject->isAttachedToDocument() && objSet.insert(s.pObject).second)
2195
            sel.push_back(s.pObject);
2196
    }
2197
    if(sel.empty())
2198
        return nullptr;
2199

2200
    auto all = App::Document::getDependencyList(sel);
2201
    if (all.size() > sel.size()) {
2202
        DlgObjectSelection dlg(sel,getMainWindow());
2203
        if(dlg.exec()!=QDialog::Accepted)
2204
            return nullptr;
2205
        sel = dlg.getSelections();
2206
        if(sel.empty())
2207
            return nullptr;
2208
    }
2209

2210
    std::vector<App::Document*> unsaved;
2211
    bool hasXLink = App::PropertyXLink::hasXLink(sel,&unsaved);
2212
    if(!unsaved.empty()) {
2213
        QMessageBox::critical(getMainWindow(), tr("Unsaved document"),
2214
            tr("The exported object contains external link. Please save the document"
2215
                "at least once before exporting."));
2216
        return nullptr;
2217
    }
2218

2219
    unsigned int memsize=1000; // ~ for the meta-information
2220
    for (const auto & it : sel)
2221
        memsize += it->getMemSize();
2222

2223
    // if less than ~10 MB
2224
    bool use_buffer=(memsize < 0xA00000);
2225
    QByteArray res;
2226
    if(use_buffer) {
2227
        try {
2228
            res.reserve(memsize);
2229
        }
2230
        catch (const std::bad_alloc &) {
2231
            use_buffer = false;
2232
        }
2233
    }
2234

2235
    WaitCursor wc;
2236
    QString mime;
2237
    if (use_buffer) {
2238
        mime = hasXLink?_MimeDocObjX:_MimeDocObj;
2239
        Base::ByteArrayOStreambuf buf(res);
2240
        std::ostream str(&buf);
2241
        // need this instance to call MergeDocuments::Save()
2242
        App::Document* doc = sel.front()->getDocument();
2243
        MergeDocuments mimeView(doc);
2244
        doc->exportObjects(sel, str);
2245
    }
2246
    else {
2247
        mime = hasXLink?_MimeDocObjXFile:_MimeDocObjFile;
2248
        static Base::FileInfo fi(App::Application::getTempFileName());
2249
        Base::ofstream str(fi, std::ios::out | std::ios::binary);
2250
        // need this instance to call MergeDocuments::Save()
2251
        App::Document* doc = sel.front()->getDocument();
2252
        MergeDocuments mimeView(doc);
2253
        doc->exportObjects(sel, str);
2254
        str.close();
2255
        res = fi.filePath().c_str();
2256

2257
        // store the path name as a custom property and
2258
        // delete this file when closing the application
2259
        const_cast<MainWindow*>(this)->setProperty("x-documentobject-file", res);
2260
    }
2261

2262
    auto mimeData = new QMimeData();
2263
    mimeData->setData(mime,res);
2264
    return mimeData;
2265
}
2266

2267
bool MainWindow::canInsertFromMimeData (const QMimeData * source) const
2268
{
2269
    if (!source)
2270
        return false;
2271
    return source->hasUrls() ||
2272
        source->hasFormat(_MimeDocObj) || source->hasFormat(_MimeDocObjX) ||
2273
        source->hasFormat(_MimeDocObjFile) || source->hasFormat(_MimeDocObjXFile);
2274
}
2275

2276
void MainWindow::insertFromMimeData (const QMimeData * mimeData)
2277
{
2278
    if (!mimeData)
2279
        return;
2280
    bool fromDoc = false;
2281
    bool hasXLink = false;
2282
    QString format;
2283
    if(mimeData->hasFormat(_MimeDocObj))
2284
        format = _MimeDocObj;
2285
    else if(mimeData->hasFormat(_MimeDocObjX)) {
2286
        format = _MimeDocObjX;
2287
        hasXLink = true;
2288
    }else if(mimeData->hasFormat(_MimeDocObjFile)) {
2289
        format = _MimeDocObjFile;
2290
        fromDoc = true;
2291
    }else if(mimeData->hasFormat(_MimeDocObjXFile)) {
2292
        format = _MimeDocObjXFile;
2293
        fromDoc = true;
2294
        hasXLink = true;
2295
    }else {
2296
        if (mimeData->hasUrls())
2297
            loadUrls(App::GetApplication().getActiveDocument(), mimeData->urls());
2298
        return;
2299
    }
2300

2301
    App::Document* doc = App::GetApplication().getActiveDocument();
2302
    if(!doc) doc = App::GetApplication().newDocument();
2303

2304
    if(hasXLink && !doc->isSaved()) {
2305
        int ret = QMessageBox::question(getMainWindow(), tr("Unsaved document"),
2306
            tr("To link to external objects, the document must be saved at least once.\n"
2307
               "Do you want to save the document now?"),
2308
            QMessageBox::Yes,QMessageBox::No);
2309
        if(ret != QMessageBox::Yes || !Application::Instance->getDocument(doc)->saveAs())
2310
            return;
2311
    }
2312
    if(!fromDoc) {
2313
        QByteArray res = mimeData->data(format);
2314

2315
        doc->openTransaction("Paste");
2316
        Base::ByteArrayIStreambuf buf(res);
2317
        std::istream in(nullptr);
2318
        in.rdbuf(&buf);
2319
        MergeDocuments mimeView(doc);
2320
        std::vector<App::DocumentObject*> newObj = mimeView.importObjects(in);
2321
        std::vector<App::DocumentObjectGroup*> grp = Gui::Selection().getObjectsOfType<App::DocumentObjectGroup>();
2322
        if (grp.size() == 1) {
2323
            Gui::Document* gui = Application::Instance->getDocument(doc);
2324
            if (gui)
2325
                gui->addRootObjectsToGroup(newObj, grp.front());
2326
        }
2327
        doc->commitTransaction();
2328
    }
2329
    else {
2330
        QByteArray res = mimeData->data(format);
2331

2332
        doc->openTransaction("Paste");
2333
        Base::FileInfo fi((const char*)res);
2334
        Base::ifstream str(fi, std::ios::in | std::ios::binary);
2335
        MergeDocuments mimeView(doc);
2336
        std::vector<App::DocumentObject*> newObj = mimeView.importObjects(str);
2337
        str.close();
2338
        std::vector<App::DocumentObjectGroup*> grp = Gui::Selection().getObjectsOfType<App::DocumentObjectGroup>();
2339
        if (grp.size() == 1) {
2340
            Gui::Document* gui = Application::Instance->getDocument(doc);
2341
            if (gui)
2342
                gui->addRootObjectsToGroup(newObj, grp.front());
2343
        }
2344
        doc->commitTransaction();
2345
    }
2346
}
2347

2348
void MainWindow::setUrlHandler(const QString &scheme, Gui::UrlHandler* handler)
2349
{
2350
    d->urlHandler[scheme] = handler;
2351
}
2352

2353
void MainWindow::unsetUrlHandler(const QString &scheme)
2354
{
2355
    d->urlHandler.remove(scheme);
2356
}
2357

2358
void MainWindow::loadUrls(App::Document* doc, const QList<QUrl>& urls)
2359
{
2360
    QStringList files;
2361
    for (const auto & it : urls) {
2362
        QMap<QString, QPointer<UrlHandler> >::iterator jt = d->urlHandler.find(it.scheme());
2363
        if (jt != d->urlHandler.end() && !jt->isNull()) {
2364
            // delegate the loading to the url handler
2365
            (*jt)->openUrl(doc, it);
2366
            continue;
2367
        }
2368

2369
        QFileInfo info(it.toLocalFile());
2370
        if (info.exists() && info.isFile()) {
2371
            if (info.isSymLink())
2372
                info.setFile(info.symLinkTarget());
2373
            std::vector<std::string> module = App::GetApplication()
2374
                .getImportModules(info.completeSuffix().toLatin1());
2375
            if (module.empty()) {
2376
                module = App::GetApplication()
2377
                    .getImportModules(info.suffix().toLatin1());
2378
            }
2379
            if (!module.empty()) {
2380
                // ok, we support files with this extension
2381
                files << info.absoluteFilePath();
2382
            }
2383
            else {
2384
                Base::Console().Message("No support to load file '%s'\n",
2385
                    (const char*)info.absoluteFilePath().toUtf8());
2386
            }
2387
        }
2388
        else if (it.scheme().toLower() == QLatin1String("http")) {
2389
            Gui::Dialog::DownloadManager* dm = Gui::Dialog::DownloadManager::getInstance();
2390
            dm->download(dm->redirectUrl(it));
2391
        }
2392

2393
        else if (it.scheme().toLower() == QLatin1String("https")) {
2394
            QUrl url = it;
2395
            QUrlQuery urlq(url);
2396
            if (urlq.hasQueryItem(QLatin1String("sid"))) {
2397
                urlq.removeAllQueryItems(QLatin1String("sid"));
2398
                url.setQuery(urlq);
2399
                url.setScheme(QLatin1String("http"));
2400
            }
2401
            Gui::Dialog::DownloadManager* dm = Gui::Dialog::DownloadManager::getInstance();
2402
            dm->download(dm->redirectUrl(url));
2403
        }
2404

2405
        else if (it.scheme().toLower() == QLatin1String("ftp")) {
2406
            Gui::Dialog::DownloadManager::getInstance()->download(it);
2407
        }
2408
    }
2409

2410
    QByteArray docName = doc ? QByteArray(doc->getName()) : qApp->translate("StdCmdNew","Unnamed").toUtf8();
2411
    SelectModule::Dict dict = SelectModule::importHandler(files);
2412
    // load the files with the associated modules
2413
    for (SelectModule::Dict::iterator it = dict.begin(); it != dict.end(); ++it) {
2414
        // if the passed document name doesn't exist the module should create it, if needed
2415
        Application::Instance->importFrom(it.key().toUtf8(), docName, it.value().toLatin1());
2416
    }
2417
}
2418

2419
void MainWindow::changeEvent(QEvent *e)
2420
{
2421
    if (e->type() == QEvent::LanguageChange) {
2422
        d->sizeLabel->setText(tr("Dimension"));
2423

2424
        CommandManager& rclMan = Application::Instance->commandManager();
2425
        std::vector<Command*> cmd = rclMan.getAllCommands();
2426
        for (auto & it : cmd)
2427
            it->languageChange();
2428

2429
        // reload current workbench to retranslate all actions and window titles
2430
        Workbench* wb = WorkbenchManager::instance()->active();
2431
        if (wb) wb->retranslate();
2432
    }
2433
    else if (e->type() == QEvent::ActivationChange) {
2434
        if (isActiveWindow()) {
2435
            QMdiSubWindow* mdi = d->mdiArea->currentSubWindow();
2436
            if (mdi) {
2437
                auto view = dynamic_cast<MDIView*>(mdi->widget());
2438
                if (view && getMainWindow()->activeWindow() != view) {
2439
                    d->activeView = view;
2440
                    Application::Instance->viewActivated(view);
2441
                }
2442
            }
2443
        }
2444
    }
2445
    else {
2446
        QMainWindow::changeEvent(e);
2447
    }
2448
}
2449

2450
void MainWindow::clearStatus() {
2451
    d->currentStatusType = 100;
2452
    statusBar()->setStyleSheet(QString::fromLatin1("#statusBar{}"));
2453
}
2454

2455
void MainWindow::statusMessageChanged() {
2456
    if(d->currentStatusType<0)
2457
        d->currentStatusType = -d->currentStatusType;
2458
    else {
2459
        // here probably means the status bar message is changed by QMainWindow
2460
        // internals, e.g. for displaying tooltip and stuff. Set reset what
2461
        // we've changed.
2462
        d->statusTimer->stop();
2463
        clearStatus();
2464
    }
2465
}
2466

2467
void MainWindow::showMessage(const QString& message, int timeout) {
2468
    if(QApplication::instance()->thread() != QThread::currentThread()) {
2469
        QApplication::postEvent(this, new CustomMessageEvent(MainWindow::Tmp,message,timeout));
2470
        return;
2471
    }
2472
    d->actionLabel->setText(message.simplified());
2473
    if(timeout) {
2474
        d->actionTimer->setSingleShot(true);
2475
        d->actionTimer->start(timeout);
2476
    }else
2477
        d->actionTimer->stop();
2478
}
2479

2480
void MainWindow::setRightSideMessage(const QString& message)
2481
{
2482
    d->rightSideLabel->setText(message.simplified());
2483
}
2484

2485
void MainWindow::showStatus(int type, const QString& message)
2486
{
2487
    if(QApplication::instance()->thread() != QThread::currentThread()) {
2488
        QApplication::postEvent(this,
2489
                new CustomMessageEvent(type,message));
2490
        return;
2491
    }
2492

2493
    if(d->currentStatusType < type)
2494
        return;
2495

2496
    d->statusTimer->setSingleShot(true);
2497
    // TODO: hardcode?
2498
    int timeout = 5000;
2499
    d->statusTimer->start(timeout);
2500

2501
    QFontMetrics fm(statusBar()->font());
2502
    QString msg = fm.elidedText(message, Qt::ElideMiddle, this->d->actionLabel->width());
2503
    switch(type) {
2504
    case MainWindow::Err:
2505
        statusBar()->setStyleSheet(d->status->err);
2506
        break;
2507
    case MainWindow::Wrn:
2508
        statusBar()->setStyleSheet(d->status->wrn);
2509
        break;
2510
    case MainWindow::Pane:
2511
        statusBar()->setStyleSheet(QString::fromLatin1("#statusBar{}"));
2512
        break;
2513
    default:
2514
        statusBar()->setStyleSheet(d->status->msg);
2515
        break;
2516
    }
2517
    d->currentStatusType = -type;
2518
    statusBar()->showMessage(msg.simplified(), timeout);
2519
}
2520

2521

2522
// set text to the pane
2523
void MainWindow::setPaneText(int i, QString text)
2524
{
2525
    if (i==1) {
2526
        showStatus(MainWindow::Pane, text);
2527
    }
2528
    else if (i==2) {
2529
        d->sizeLabel->setText(text);
2530
    }
2531
}
2532

2533

2534
void MainWindow::setUserSchema(int userSchema)
2535
{
2536
    d->sizeLabel->setUserSchema(userSchema);
2537
}
2538

2539

2540
void MainWindow::customEvent(QEvent* e)
2541
{
2542
    if (e->type() == QEvent::User) {
2543
        auto ce = static_cast<Gui::CustomMessageEvent*>(e);
2544
        QString msg = ce->message();
2545
        switch(ce->type()) {
2546
        case MainWindow::Log: {
2547
            if (msg.startsWith(QLatin1String("#Inventor V2.1 ascii "))) {
2548
                Gui::Document *d = Application::Instance->activeDocument();
2549
                if (d) {
2550
                    auto view = new ViewProviderExtern();
2551
                    try {
2552
                        view->setModeByString("1",msg.toLatin1().constData());
2553
                        d->setAnnotationViewProvider("Vdbg",view);
2554
                    }
2555
                    catch (...) {
2556
                        delete view;
2557
                    }
2558
                }
2559
            }
2560
            break;
2561
        } case MainWindow::Tmp: {
2562
            showMessage(msg, ce->timeout());
2563
            break;
2564
        } default:
2565
            showStatus(ce->type(),msg);
2566
        }
2567
    }
2568
    else if (e->type() == ActionStyleEvent::EventType) {
2569
        QList<TaskView::TaskView*> tasks = findChildren<TaskView::TaskView*>();
2570
        if (static_cast<ActionStyleEvent*>(e)->getType() == ActionStyleEvent::Clear) {
2571
            for (auto & task : tasks) {
2572
                task->clearActionStyle();
2573
            }
2574
        }
2575
        else {
2576
            for (auto & task : tasks) {
2577
                task->restoreActionStyle();
2578
            }
2579
        }
2580
    }
2581
}
2582

2583
QMdiArea *MainWindow::getMdiArea() const
2584
{
2585
    return d->mdiArea;
2586
}
2587

2588
void MainWindow::setWindowTitle(const QString& string)
2589
{
2590
    QString title;
2591
    QString appname = QCoreApplication::applicationName();
2592
    if (appname.isEmpty()) {
2593
        appname = QString::fromLatin1(App::Application::Config()["ExeName"].c_str());
2594
    }
2595

2596
    // allow to disable version number
2597
    ParameterGrp::handle hGen = App::GetApplication().GetParameterGroupByPath(
2598
        "User parameter:BaseApp/Preferences/General");
2599
    bool showVersion = hGen->GetBool("ShowVersionInTitle", true);
2600

2601
    if (showVersion) {
2602
        // set main window title with FreeCAD Version
2603
        auto config = App::Application::Config();
2604
        QString major = QString::fromUtf8(config["BuildVersionMajor"].c_str());
2605
        QString minor = QString::fromUtf8(config["BuildVersionMinor"].c_str());
2606
        QString point = QString::fromUtf8(config["BuildVersionPoint"].c_str());
2607
        QString suffix = QString::fromUtf8(config["BuildVersionSuffix"].c_str());
2608
        title = QString::fromUtf8("%1 %2.%3.%4%5").arg(appname, major, minor, point, suffix);
2609
    }
2610
    else {
2611
        title = appname;
2612
    }
2613

2614
    if (!string.isEmpty()) {
2615
        title = QString::fromUtf8("[*] %1 - %2").arg(string, title);
2616
    }
2617

2618
    QMainWindow::setWindowTitle(title);
2619
}
2620

2621
    // ----------------------------------------------------------
2622

2623
    StatusBarObserver::StatusBarObserver()
2624
        : WindowParameter("OutputWindow")
2625
    {
2626
        msg = QString::fromLatin1("#statusBar{color: #000000}");  // black
2627
        wrn = QString::fromLatin1("#statusBar{color: #ffaa00}");  // orange
2628
        err = QString::fromLatin1("#statusBar{color: #ff0000}");  // red
2629
        Base::Console().AttachObserver(this);
2630
        getWindowParameter()->Attach(this);
2631
        getWindowParameter()->NotifyAll();
2632
    }
2633

2634
    StatusBarObserver::~StatusBarObserver()
2635
    {
2636
        getWindowParameter()->Detach(this);
2637
        Base::Console().DetachObserver(this);
2638
    }
2639

2640
    void StatusBarObserver::OnChange(Base::Subject<const char*> & rCaller, const char* sReason)
2641
    {
2642
        ParameterGrp& rclGrp = ((ParameterGrp&)rCaller);
2643
        auto format = QString::fromLatin1("#statusBar{color: %1}");
2644
        if (strcmp(sReason, "colorText") == 0) {
2645
            unsigned long col = rclGrp.GetUnsigned(sReason);
2646
            this->msg = format.arg(App::Color::fromPackedRGB<QColor>(col).name());
2647
        }
2648
        else if (strcmp(sReason, "colorWarning") == 0) {
2649
            unsigned long col = rclGrp.GetUnsigned(sReason);
2650
            this->wrn = format.arg(App::Color::fromPackedRGB<QColor>(col).name());
2651
        }
2652
        else if (strcmp(sReason, "colorError") == 0) {
2653
            unsigned long col = rclGrp.GetUnsigned(sReason);
2654
            this->err = format.arg(App::Color::fromPackedRGB<QColor>(col).name());
2655
        }
2656
        else if (strcmp(sReason, "colorCritical") == 0) {
2657
            unsigned long col = rclGrp.GetUnsigned(sReason);
2658
            this->critical = format.arg(
2659
                QColor((col >> 24) & 0xff, (col >> 16) & 0xff, (col >> 8) & 0xff).name());
2660
        }
2661
    }
2662

2663
    void StatusBarObserver::SendLog(const std::string& notifiername,
2664
                                    const std::string& msg,
2665
                                    Base::LogStyle level,
2666
                                    Base::IntendedRecipient recipient,
2667
                                    Base::ContentType content)
2668
    {
2669
        (void)notifiername;
2670

2671
        // Do not log untranslated messages, or messages intended only to a developer to status bar
2672
        if (recipient == Base::IntendedRecipient::Developer
2673
            || content == Base::ContentType::Untranslated
2674
            || content == Base::ContentType::Untranslatable)
2675
            return;
2676

2677
        int messageType = -1;
2678
        switch (level) {
2679
            case Base::LogStyle::Warning:
2680
                messageType = MainWindow::Wrn;
2681
                break;
2682
            case Base::LogStyle::Message:
2683
                messageType = MainWindow::Msg;
2684
                break;
2685
            case Base::LogStyle::Error:
2686
                messageType = MainWindow::Err;
2687
                break;
2688
            case Base::LogStyle::Log:
2689
                messageType = MainWindow::Log;
2690
                break;
2691
            case Base::LogStyle::Critical:
2692
                messageType = MainWindow::Critical;
2693
                break;
2694
            default:
2695
                break;
2696
        }
2697

2698
        // Send the event to the main window to allow thread-safety. Qt will delete it when done.
2699
        auto ev = new CustomMessageEvent(messageType, QString::fromUtf8(msg.c_str()));
2700
        QApplication::postEvent(getMainWindow(), ev);
2701
    }
2702

2703
    // -------------------------------------------------------------
2704

2705
    int ActionStyleEvent::EventType = -1;
2706

2707
    ActionStyleEvent::ActionStyleEvent(Style type)
2708
        : QEvent(QEvent::Type(EventType))
2709
        , type(type)
2710
    {}
2711

2712
    ActionStyleEvent::Style ActionStyleEvent::getType() const
2713
    {
2714
        return type;
2715
    }
2716

2717

2718
#include "moc_MainWindow.cpp"
2719
#include "MainWindow.moc"
2720

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

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

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

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