File indexing completed on 2024-05-12 03:49:10

0001 /*
0002     File                 : MainWin.cc
0003     Project              : LabPlot
0004     Description          : Main window of the application
0005     --------------------------------------------------------------------
0006     SPDX-FileCopyrightText: 2008-2018 Stefan Gerlach <stefan.gerlach@uni.kn>
0007     SPDX-FileCopyrightText: 2009-2023 Alexander Semke <alexander.semke@web.de>
0008     SPDX-License-Identifier: GPL-2.0-or-later
0009 */
0010 
0011 #include "MainWin.h"
0012 
0013 #include "backend/core/AspectTreeModel.h"
0014 #include "backend/core/Folder.h"
0015 #include "backend/core/Project.h"
0016 #include "backend/core/Settings.h"
0017 #include "backend/core/Workbook.h"
0018 #include "backend/datasources/DatasetHandler.h"
0019 #include "backend/datasources/LiveDataSource.h"
0020 #include "backend/matrix/Matrix.h"
0021 #include "backend/spreadsheet/Spreadsheet.h"
0022 #include "backend/worksheet/Worksheet.h"
0023 #ifdef HAVE_LIBORIGIN
0024 #include "backend/datasources/projects/OriginProjectParser.h"
0025 #endif
0026 #ifdef HAVE_CANTOR_LIBS
0027 #include "backend/cantorWorksheet/CantorWorksheet.h"
0028 #endif
0029 #include "backend/datapicker/Datapicker.h"
0030 #include "backend/lib/XmlStreamReader.h"
0031 #include "backend/lib/macros.h"
0032 #include "backend/note/Note.h"
0033 #include "backend/worksheet/plots/cartesian/CartesianPlot.h"
0034 
0035 #ifdef HAVE_MQTT
0036 #include "backend/datasources/MQTTClient.h"
0037 #endif
0038 
0039 #include "commonfrontend/ProjectExplorer.h"
0040 #include "commonfrontend/core/ContentDockWidget.h"
0041 #include "commonfrontend/matrix/MatrixView.h"
0042 #include "commonfrontend/spreadsheet/SpreadsheetView.h"
0043 #include "commonfrontend/worksheet/WorksheetView.h"
0044 #ifdef HAVE_CANTOR_LIBS
0045 #include "commonfrontend/cantorWorksheet/CantorWorksheetView.h"
0046 #endif
0047 #include "commonfrontend/datapicker/DatapickerImageView.h"
0048 #include "commonfrontend/datapicker/DatapickerView.h"
0049 #include "commonfrontend/note/NoteView.h"
0050 #include "commonfrontend/widgets/MemoryWidget.h"
0051 
0052 #include "kdefrontend/GuiObserver.h"
0053 #include "kdefrontend/HistoryDialog.h"
0054 #include "kdefrontend/SettingsDialog.h"
0055 #include "kdefrontend/colormaps/ColorMapsDialog.h"
0056 #include "kdefrontend/datasources/ImportDatasetDialog.h"
0057 #include "kdefrontend/datasources/ImportDatasetWidget.h"
0058 #include "kdefrontend/datasources/ImportFileDialog.h"
0059 #include "kdefrontend/datasources/ImportProjectDialog.h"
0060 #include "kdefrontend/datasources/ImportSQLDatabaseDialog.h"
0061 #include "kdefrontend/dockwidgets/CursorDock.h"
0062 #include "kdefrontend/dockwidgets/ProjectDock.h"
0063 #include "kdefrontend/examples/ExamplesDialog.h"
0064 #include "kdefrontend/widgets/FITSHeaderEditDialog.h"
0065 #include "kdefrontend/widgets/LabelWidget.h"
0066 #include "kdefrontend/worksheet/WorksheetPreviewWidget.h"
0067 
0068 #ifdef HAVE_KUSERFEEDBACK
0069 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
0070 #include <KUserFeedbackQt6/ApplicationVersionSource>
0071 #include <KUserFeedbackQt6/PlatformInfoSource>
0072 #include <KUserFeedbackQt6/QtVersionSource>
0073 #include <KUserFeedbackQt6/ScreenInfoSource>
0074 #include <KUserFeedbackQt6/StartCountSource>
0075 #include <KUserFeedbackQt6/UsageTimeSource>
0076 #else
0077 #include <KUserFeedback/ApplicationVersionSource>
0078 #include <KUserFeedback/PlatformInfoSource>
0079 #include <KUserFeedback/QtVersionSource>
0080 #include <KUserFeedback/ScreenInfoSource>
0081 #include <KUserFeedback/StartCountSource>
0082 #include <KUserFeedback/UsageTimeSource>
0083 #endif
0084 #endif
0085 
0086 #ifdef HAVE_PURPOSE
0087 #include <Purpose/AlternativesModel>
0088 #include <purpose_version.h>
0089 #if PURPOSE_VERSION >= QT_VERSION_CHECK(5, 104, 0)
0090 #include <Purpose/Menu>
0091 #else
0092 #include <PurposeWidgets/Menu>
0093 #endif
0094 #include <QMimeType>
0095 #endif
0096 
0097 #include <DockManager.h>
0098 
0099 #ifdef HAVE_TOUCHBAR
0100 #include "3rdparty/kdmactouchbar/src/kdmactouchbar.h"
0101 #endif
0102 
0103 #include <QActionGroup>
0104 #include <QCloseEvent>
0105 #include <QDockWidget>
0106 #include <QElapsedTimer>
0107 #include <QFileDialog>
0108 #include <QMenu>
0109 #include <QMenuBar>
0110 #include <QMimeData>
0111 #include <QStackedWidget>
0112 #include <QStatusBar>
0113 #include <QTemporaryFile>
0114 #include <QTimeLine>
0115 #include <QUndoStack>
0116 // #include <QtWidgets>
0117 // #include <QtQuickWidgets/QQuickWidget>
0118 // #include <QQuickItem>
0119 // #include <QQuickView>
0120 // #include <QQmlApplicationEngine>
0121 // #include <QQmlContext>
0122 
0123 #include <KActionCollection>
0124 #include <KActionMenu>
0125 #include <KColorScheme>
0126 #include <KColorSchemeManager>
0127 #include <kconfigwidgets_version.h>
0128 #if KCONFIGWIDGETS_VERSION >= QT_VERSION_CHECK(5, 107, 0)
0129 #include <KColorSchemeMenu>
0130 #endif
0131 #include <KCompressionDevice>
0132 #include <KConfigGroup>
0133 #include <KLocalizedString>
0134 #include <KMessageBox>
0135 #include <KRecentFilesAction>
0136 #include <KStandardAction>
0137 #include <KToggleAction>
0138 #include <KToggleFullScreenAction>
0139 #include <KToolBar>
0140 #include <kxmlguifactory.h>
0141 
0142 #ifdef HAVE_CANTOR_LIBS
0143 #include <KConfigDialog>
0144 #include <KConfigSkeleton>
0145 #include <KCoreConfigSkeleton>
0146 #include <cantor/backend.h>
0147 
0148 // required to parse Cantor and Jupyter files
0149 #include <KZip>
0150 #include <QBuffer>
0151 #include <QJsonDocument>
0152 #include <QJsonParseError>
0153 #endif
0154 
0155 /*!
0156 \class MainWin
0157 \brief Main application window.
0158 
0159 \ingroup kdefrontend
0160 */
0161 MainWin::MainWin(QWidget* parent, const QString& filename)
0162     : KXmlGuiWindow(parent)
0163     , m_schemeManager(new KColorSchemeManager(this)) {
0164     initGUI(filename);
0165     setAcceptDrops(true);
0166 
0167 #ifdef HAVE_KUSERFEEDBACK
0168     m_userFeedbackProvider.setProductIdentifier(QStringLiteral("org.kde.labplot"));
0169     m_userFeedbackProvider.setFeedbackServer(QUrl(QStringLiteral("https://telemetry.kde.org/")));
0170     m_userFeedbackProvider.setSubmissionInterval(7);
0171     m_userFeedbackProvider.setApplicationStartsUntilEncouragement(5);
0172     m_userFeedbackProvider.setEncouragementDelay(30);
0173 
0174     // software version info
0175     m_userFeedbackProvider.addDataSource(new KUserFeedback::ApplicationVersionSource);
0176     m_userFeedbackProvider.addDataSource(new KUserFeedback::QtVersionSource);
0177 
0178     // info about the machine
0179     m_userFeedbackProvider.addDataSource(new KUserFeedback::PlatformInfoSource);
0180     m_userFeedbackProvider.addDataSource(new KUserFeedback::ScreenInfoSource);
0181 
0182     // usage info
0183     m_userFeedbackProvider.addDataSource(new KUserFeedback::StartCountSource);
0184     m_userFeedbackProvider.addDataSource(new KUserFeedback::UsageTimeSource);
0185 #endif
0186 }
0187 
0188 MainWin::~MainWin() {
0189     // save the current settings in MainWin
0190     m_recentProjectsAction->saveEntries(Settings::group(QStringLiteral("Recent Files")));
0191 
0192     KConfigGroup group = Settings::group(QStringLiteral("MainWin"));
0193     group.writeEntry(QLatin1String("geometry"), saveGeometry());
0194     group.writeEntry(QLatin1String("WindowState"), saveState());
0195     group.writeEntry(QLatin1String("lastOpenFileFilter"), m_lastOpenFileFilter);
0196     group.writeEntry(QLatin1String("ShowMemoryInfo"), (m_memoryInfoWidget != nullptr));
0197     Settings::sync();
0198 
0199     // if welcome screen is shown, save its settings prior to deleting it
0200     //  if (dynamic_cast<QQuickWidget*>(centralWidget()))
0201     //      QMetaObject::invokeMethod(m_welcomeWidget->rootObject(), "saveWidgetDimensions");
0202 
0203     if (m_project) {
0204         delete m_guiObserver;
0205         delete m_aspectTreeModel;
0206         disconnect(m_project, nullptr, this, nullptr);
0207         delete m_project;
0208     }
0209 
0210     //  delete m_welcomeScreenHelper;
0211 }
0212 
0213 void MainWin::showPresenter() {
0214     const auto* w = dynamic_cast<Worksheet*>(m_currentAspect);
0215     if (w) {
0216         auto* view = static_cast<WorksheetView*>(w->view());
0217         view->presenterMode();
0218     } else {
0219         // currently active object is not a worksheet but we're asked to start in the presenter mode
0220         // determine the first available worksheet and show it in the presenter mode
0221         auto worksheets = m_project->children<Worksheet>();
0222         if (worksheets.size() > 0) {
0223             auto* view = static_cast<WorksheetView*>(worksheets.constFirst()->view());
0224             view->presenterMode();
0225         } else {
0226             QMessageBox::information(this, i18n("Presenter Mode"), i18n("No worksheets are available in the project. The presenter mode will not be started."));
0227         }
0228     }
0229 }
0230 
0231 AspectTreeModel* MainWin::model() const {
0232     return m_aspectTreeModel;
0233 }
0234 
0235 Project* MainWin::project() const {
0236     return m_project;
0237 }
0238 
0239 void MainWin::initGUI(const QString& fileName) {
0240     if (statusBar()->isEnabled())
0241         statusBar()->showMessage(i18nc("%1 is the LabPlot version", "Welcome to LabPlot %1", QLatin1String(LVERSION)));
0242 
0243     initActions();
0244 
0245 #ifdef Q_OS_MAC
0246 #ifdef HAVE_TOUCHBAR
0247     // setup touchbar before GUI (otherwise actions in the toolbar are not selectable)
0248     m_touchBar = new KDMacTouchBar(this);
0249     // m_touchBar->setTouchButtonStyle(KDMacTouchBar::IconOnly);
0250 #endif
0251     setUnifiedTitleAndToolBarOnMac(true);
0252 #endif
0253     setupGUI();
0254 
0255     // all toolbars created via the KXMLGUI framework are locked on default:
0256     //  * on the very first program start, unlock all toolbars
0257     //  * on later program starts, set stored lock status
0258     // Furthermore, we want to show icons only after the first program start.
0259     KConfigGroup groupMain = Settings::group(QStringLiteral("MainWindow"));
0260     if (groupMain.exists()) {
0261         // KXMLGUI framework automatically stores "Disabled" for the key "ToolBarsMovable"
0262         // in case the toolbars are locked -> load this value
0263         const QString& str = groupMain.readEntry(QLatin1String("ToolBarsMovable"), "");
0264         bool locked = (str == QLatin1String("Disabled"));
0265         KToolBar::setToolBarsLocked(locked);
0266     }
0267 
0268     // in case we're starting for the first time, put all toolbars into the IconOnly mode
0269     // and maximize the main window. The occurence of LabPlot's own section "MainWin"
0270     // indicates whether this is the first start or not
0271     groupMain = Settings::group(QStringLiteral("MainWin"));
0272     if (!groupMain.exists()) {
0273         // first start
0274         KToolBar::setToolBarsLocked(false);
0275 
0276         // show icons only
0277         const auto& toolbars = factory()->containers(QLatin1String("ToolBar"));
0278         for (auto* container : toolbars) {
0279             auto* toolbar = dynamic_cast<QToolBar*>(container);
0280             if (toolbar)
0281                 toolbar->setToolButtonStyle(Qt::ToolButtonIconOnly);
0282         }
0283 
0284         showMaximized();
0285     }
0286 
0287     initMenus();
0288 
0289     auto* mainToolBar = qobject_cast<QToolBar*>(factory()->container(QLatin1String("main_toolbar"), this));
0290 
0291 #ifdef HAVE_CANTOR_LIBS
0292     auto* tbNotebook = new QToolButton(mainToolBar);
0293     tbNotebook->setPopupMode(QToolButton::MenuButtonPopup);
0294     tbNotebook->setMenu(m_newNotebookMenu);
0295     tbNotebook->setDefaultAction(m_configureCASAction);
0296     auto* lastAction = mainToolBar->actions().at(mainToolBar->actions().count() - 2);
0297     mainToolBar->insertWidget(lastAction, tbNotebook);
0298 #endif
0299 
0300     auto* tbImport = new QToolButton(mainToolBar);
0301     tbImport->setPopupMode(QToolButton::MenuButtonPopup);
0302     tbImport->setMenu(m_importMenu);
0303     tbImport->setDefaultAction(m_importFileAction);
0304     auto* lastAction_ = mainToolBar->actions().at(mainToolBar->actions().count() - 1);
0305     mainToolBar->insertWidget(lastAction_, tbImport);
0306 
0307     // hamburger menu
0308 #if KCONFIGWIDGETS_VERSION >= QT_VERSION_CHECK(5, 81, 0)
0309     m_hamburgerMenu = KStandardAction::hamburgerMenu(nullptr, nullptr, actionCollection());
0310     toolBar()->addAction(m_hamburgerMenu);
0311     m_hamburgerMenu->hideActionsOf(toolBar());
0312     m_hamburgerMenu->setMenuBar(menuBar());
0313 #endif
0314 
0315     setWindowIcon(QIcon::fromTheme(QLatin1String("LabPlot2"), QGuiApplication::windowIcon()));
0316     setAttribute(Qt::WA_DeleteOnClose);
0317 
0318     // make the status bar of a fixed size in order to avoid height changes when placing a ProgressBar there.
0319     QFont font;
0320     font.setFamily(font.defaultFamily());
0321     QFontMetrics fm(font);
0322     statusBar()->setFixedHeight(fm.height() + 5);
0323 
0324     // load recently used projects
0325     m_recentProjectsAction->loadEntries(Settings::group(QStringLiteral("Recent Files")));
0326 
0327     // General Settings
0328     auto group = Settings::group(QStringLiteral("Settings_General"));
0329 
0330     // title bar
0331     m_titleBarMode = static_cast<MainWin::TitleBarMode>(group.readEntry("TitleBar", 0));
0332 
0333     // auto-save
0334     m_autoSaveActive = group.readEntry<bool>("AutoSave", false);
0335     int interval = group.readEntry("AutoSaveInterval", 1);
0336     interval = interval * 60 * 1000;
0337     m_autoSaveTimer.setInterval(interval);
0338     connect(&m_autoSaveTimer, &QTimer::timeout, this, &MainWin::autoSaveProject);
0339 
0340     if (!fileName.isEmpty()) {
0341         createADS();
0342         if (Project::isSupportedProject(fileName)) {
0343             QTimer::singleShot(0, this, [=]() {
0344                 openProject(fileName);
0345             });
0346         } else {
0347             newProject();
0348             QTimer::singleShot(0, this, [=]() {
0349                 importFileDialog(fileName);
0350             });
0351         }
0352     } else {
0353         // There is no file to open. Depending on the settings do nothing,
0354         // create a new project or open the last used project.
0355         auto load = (LoadOnStart)group.readEntry("LoadOnStart", static_cast<int>(LoadOnStart::NewProject));
0356 
0357         // in case we're starting with the settings created with an older version where the LoadOnStart enum had more values
0358         // or in case the config file was manipulated, we need to ensure we start with proper values and properly initialize the docks
0359         // by mapping the old/manipulated values to the new/correct values:
0360         // * old value 0 - "do nothing" -> map to new values "new project" and "with worksheet" which are default
0361         // * old value 1 - "new project" -> map to new values "new project" and "with worksheet" which are default
0362         // * old value 2 - "new project with worksheet" -> map to new values "new project" and "with worksheet" which are default
0363         // * old value 3 - "new project with spreadsheet" -> map to new values "new project" and "with spreadsheet"
0364         // * old value 4 - "last project" -> map to the new "last project"
0365         // * any higher values or <0, manipulated file -> map to the new default values
0366         if (load > LoadOnStart::LastProject) {
0367             int oldLoad = static_cast<int>(load);
0368             if (oldLoad == 2) { // old "new project with worksheet"
0369                 load = LoadOnStart::NewProject;
0370                 group.writeEntry(QStringLiteral("LoadOnStart"), static_cast<int>(load));
0371                 group.writeEntry(QStringLiteral("NewProject"), static_cast<int>(NewProject::WithWorksheet));
0372             } else if (oldLoad == 3) { // old "new project with spreadsheet"
0373                 load = LoadOnStart::NewProject;
0374                 group.writeEntry(QStringLiteral("LoadOnStart"), static_cast<int>(load));
0375                 group.writeEntry(QStringLiteral("NewProject"), static_cast<int>(NewProject::WithSpreadsheet));
0376             } else if (oldLoad == 4) { // old "last project"
0377                 load = LoadOnStart::LastProject;
0378                 group.writeEntry(QStringLiteral("LoadOnStart"), static_cast<int>(load));
0379             } else if (oldLoad > 4 || oldLoad < 0) {
0380                 load = LoadOnStart::NewProject;
0381                 group.writeEntry(QStringLiteral("LoadOnStart"), static_cast<int>(load));
0382             }
0383         }
0384 
0385         switch (load) {
0386         case LoadOnStart::NewProject:
0387             createADS();
0388             newProject();
0389             break;
0390         case LoadOnStart::LastProject: {
0391             createADS();
0392             const QString& path = Settings::group(QStringLiteral("MainWin")).readEntry("LastOpenProject", "");
0393             if (!path.isEmpty())
0394                 openProject(path);
0395             else
0396                 newProject();
0397             break;
0398         }
0399         case LoadOnStart::WelcomeScreen:
0400             // TODO:
0401             // m_showWelcomeScreen = true;
0402             // m_welcomeWidget = createWelcomeScreen();
0403             // setCentralWidget(m_welcomeWidget);
0404             break;
0405         }
0406     }
0407 
0408     // read the settings of MainWin
0409     const KConfigGroup& groupMainWin = Settings::group(QStringLiteral("MainWin"));
0410 
0411     // show memory info
0412     m_memoryInfoAction->setEnabled(statusBar()->isEnabled()); // disable/enable menu with statusbar
0413     bool memoryInfoShown = groupMainWin.readEntry(QLatin1String("ShowMemoryInfo"), true);
0414     DEBUG(Q_FUNC_INFO << ", memory info enabled in config: " << memoryInfoShown)
0415     m_memoryInfoAction->setChecked(memoryInfoShown);
0416     if (memoryInfoShown)
0417         toggleMemoryInfo();
0418 
0419     // restore the geometry
0420     restoreGeometry(groupMainWin.readEntry("geometry", QByteArray()));
0421 
0422     m_lastOpenFileFilter = groupMainWin.readEntry(QLatin1String("lastOpenFileFilter"), QString());
0423 }
0424 
0425 /**
0426  * @brief Creates a new welcome screen to be set as central widget.
0427  */
0428 /*
0429 QQuickWidget* MainWin::createWelcomeScreen() {
0430     QSize maxSize = qApp->primaryScreen()->availableSize();
0431     resize(maxSize);
0432     setMinimumSize(700, 400);
0433     showMaximized();
0434 
0435     KToolBar* toolbar = toolBar();
0436     if (toolbar)
0437         toolbar->setVisible(false);
0438 
0439     QList<QVariant> recentList;
0440     for (QUrl& url : m_recentProjectsAction->urls())
0441         recentList.append(QVariant(url));
0442 
0443     //Set context property
0444     QQuickWidget* quickWidget = new QQuickWidget(this);
0445     QQmlContext* ctxt = quickWidget->rootContext();
0446     QVariant variant(recentList);
0447     ctxt->setContextProperty("recentProjects", variant);
0448 
0449     //Create helper object
0450     if (m_welcomeScreenHelper)
0451         delete m_welcomeScreenHelper;
0452     m_welcomeScreenHelper = new WelcomeScreenHelper();
0453     connect(m_welcomeScreenHelper, &WelcomeScreenHelper::openExampleProject,
0454             this, QOverload<const QString&>::of(&MainWin::openProject));
0455 
0456     ctxt->setContextProperty("datasetModel", m_welcomeScreenHelper->getDatasetModel());
0457     ctxt->setContextProperty("helper", m_welcomeScreenHelper);
0458 
0459     quickWidget->setSource(QUrl(QLatin1String("qrc:///main.qml")));
0460     quickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView);
0461     QObject *item = quickWidget->rootObject();
0462 
0463     //connect qml's signals
0464     QObject::connect(item, SIGNAL(recentProjectClicked(QUrl)), this, SLOT(openRecentProject(QUrl)));
0465     QObject::connect(item, SIGNAL(datasetClicked(QString,QString,QString)), m_welcomeScreenHelper, SLOT(datasetClicked(QString,QString,QString)));
0466     QObject::connect(item, SIGNAL(openDataset()), this, SLOT(openDatasetExample()));
0467     QObject::connect(item, SIGNAL(openExampleProject(QString)), m_welcomeScreenHelper, SLOT(exampleProjectClicked(QString)));
0468     Q_EMIT m_welcomeScreenHelper->showFirstDataset();
0469 
0470     return quickWidget;
0471 }
0472 */
0473 /**
0474  * @brief Initiates resetting the layout of the welcome screen
0475  */
0476 /*
0477 void MainWin::resetWelcomeScreen() {
0478     if (dynamic_cast<QQuickWidget*>(centralWidget()))
0479         QMetaObject::invokeMethod(m_welcomeWidget->rootObject(), "restoreOriginalLayout");
0480 }
0481 */
0482 
0483 void MainWin::createADS() {
0484     KToolBar* toolbar = toolBar();
0485     if (toolbar)
0486         toolbar->setVisible(true);
0487 
0488     // Save welcome screen's dimensions.
0489     //  if (m_showWelcomeScreen)
0490     //      QMetaObject::invokeMethod(m_welcomeWidget->rootObject(), "saveWidgetDimensions");
0491 
0492     // As per documentation the configuration Flags must be set prior a DockManager will be created!
0493     // https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/blob/master/doc/user-guide.md#configuration-flags
0494     ads::CDockManager::setConfigFlag(ads::CDockManager::XmlCompressionEnabled, false);
0495     ads::CDockManager::setConfigFlag(ads::CDockManager::FocusHighlighting, true);
0496     ads::CDockManager::setConfigFlag(ads::CDockManager::MiddleMouseButtonClosesTab, true);
0497     // must be after the config flags!
0498     ads::CDockManager::setAutoHideConfigFlags(ads::CDockManager::DefaultAutoHideConfig);
0499     ads::CDockManager::setAutoHideConfigFlag(ads::CDockManager::AutoHideShowOnMouseOver, true);
0500 
0501     m_DockManager = new ads::CDockManager(this);
0502     connect(m_DockManager, &ads::CDockManager::focusedDockWidgetChanged, this, &MainWin::dockFocusChanged); // TODO: seems not to work
0503     // setCentralWidget(m_DockManager); // Automatically done by CDockManager
0504     connect(m_DockManager, &ads::CDockManager::dockWidgetAboutToBeRemoved, this, &MainWin::dockWidgetAboutToBeRemoved);
0505     connect(m_DockManager, &ads::CDockManager::dockWidgetRemoved, this, &MainWin::dockWidgetRemoved);
0506 
0507     connect(m_closeWindowAction, &QAction::triggered, [this] {
0508         m_DockManager->removeDockWidget(m_currentDock);
0509     });
0510     connect(m_closeAllWindowsAction, &QAction::triggered, [this]() {
0511         for (auto dock : m_DockManager->dockWidgetsMap()) {
0512             // Do not remove them, because it makes no sense
0513             if (specialDock(dock))
0514                 continue;
0515             m_DockManager->removeDockWidget(dock);
0516         }
0517     });
0518 
0519     connect(m_nextWindowAction, &QAction::triggered, this, &MainWin::activateNextDock);
0520     connect(m_prevWindowAction, &QAction::triggered, this, &MainWin::activatePreviousDock);
0521 }
0522 
0523 bool MainWin::specialDock(ads::CDockWidget* dock) {
0524     return dock == m_projectExplorerDock || dock == m_propertiesDock || dock == m_worksheetPreviewDock;
0525 }
0526 
0527 void MainWin::changeVisibleAllDocks(bool visible) {
0528     for (auto dock : m_DockManager->dockWidgetsMap())
0529         dock->toggleView(visible);
0530 }
0531 
0532 void MainWin::activateNextDock() {
0533     const auto* focusedDock = m_DockManager->focusedDockWidget();
0534 
0535     auto itrForward = m_DockManager->dockWidgetsMap().constBegin();
0536 
0537     bool focusedFound = false;
0538     while (itrForward != m_DockManager->dockWidgetsMap().constEnd()) {
0539         auto* dock = itrForward.value();
0540         if (focusedFound) {
0541             if (!specialDock(dock)) {
0542                 dock->toggleView(true);
0543                 m_DockManager->setDockWidgetFocused(dock);
0544                 return;
0545             }
0546         }
0547 
0548         if (dock == focusedDock)
0549             focusedFound = true;
0550         itrForward++;
0551     }
0552 
0553     if (!focusedFound) {
0554         if (!m_DockManager->dockWidgetsMap().count())
0555             return;
0556         auto* dock = m_DockManager->dockWidgetsMap().first();
0557         dock->toggleView(true);
0558         m_DockManager->setDockWidgetFocused(dock);
0559         return;
0560     }
0561 
0562     // wrap around
0563     auto itrWrap = m_DockManager->dockWidgetsMap().constBegin();
0564     while (itrWrap != m_DockManager->dockWidgetsMap().constEnd()) {
0565         auto* dock = itrWrap.value();
0566         if (!specialDock(dock)) {
0567             dock->toggleView(true);
0568             m_DockManager->setDockWidgetFocused(dock);
0569             return;
0570         }
0571         itrWrap++;
0572     }
0573 }
0574 
0575 void MainWin::activatePreviousDock() {
0576     const auto* focusedDock = m_DockManager->focusedDockWidget();
0577 
0578     auto itrForward = QMapIterator<QString, ads::CDockWidget*>(m_DockManager->dockWidgetsMap());
0579     itrForward.toBack();
0580 
0581     bool focusedFound = false;
0582     while (itrForward.hasPrevious()) {
0583         itrForward.previous();
0584         auto* dock = itrForward.value();
0585         if (focusedFound) {
0586             if (!specialDock(dock)) {
0587                 dock->toggleView(true);
0588                 m_DockManager->setDockWidgetFocused(dock);
0589                 return;
0590             }
0591         }
0592 
0593         if (dock == focusedDock) {
0594             focusedFound = true;
0595         }
0596     }
0597 
0598     if (!focusedFound) {
0599         if (!m_DockManager->dockWidgetsMap().count())
0600             return;
0601         auto* dock = m_DockManager->dockWidgetsMap().first();
0602         dock->toggleView(true);
0603         m_DockManager->setDockWidgetFocused(dock);
0604         return;
0605     }
0606 
0607     // wrap around
0608     auto itrWrap = QMapIterator<QString, ads::CDockWidget*>(m_DockManager->dockWidgetsMap());
0609     itrWrap.toBack();
0610     while (itrWrap.hasPrevious()) {
0611         itrWrap.previous();
0612         auto* dock = itrWrap.value();
0613         if (!specialDock(dock)) {
0614             dock->toggleView(true);
0615             m_DockManager->setDockWidgetFocused(dock);
0616             return;
0617         }
0618     }
0619 }
0620 
0621 void MainWin::dockWidgetRemoved(ads::CDockWidget* w) {
0622     if (w == m_projectExplorerDock) {
0623         delete m_projectExplorerDock;
0624         m_projectExplorerDock = nullptr;
0625     } else if (w == m_propertiesDock) {
0626         delete m_propertiesDock;
0627         m_propertiesDock = nullptr;
0628     } else if (w == m_worksheetPreviewDock) {
0629         delete m_worksheetPreviewDock;
0630         m_worksheetPreviewDock = nullptr;
0631     }
0632 
0633     if (w == m_currentAspectDock)
0634         m_currentAspectDock = nullptr;
0635 }
0636 
0637 void MainWin::dockWidgetAboutToBeRemoved(ads::CDockWidget* w) {
0638     if (w == m_projectExplorerDock) {
0639         delete m_projectExplorer;
0640         m_projectExplorer = nullptr;
0641     } else if (w == m_propertiesDock)
0642         delete m_propertiesDock->widget();
0643     else if (w == m_worksheetPreviewDock)
0644         delete m_worksheetPreviewDock->widget();
0645 }
0646 
0647 void MainWin::dockFocusChanged(ads::CDockWidget* old, ads::CDockWidget* now) {
0648     Q_UNUSED(old);
0649     if (!now) {
0650         updateGUI();
0651         return;
0652     }
0653 
0654     auto* view = dynamic_cast<ContentDockWidget*>(now);
0655     if (!view)
0656         return; // project explorer or propertiesexplorer can be ignored
0657     if (view == m_currentDock) {
0658         // do nothing, if the current sub-window gets selected again.
0659         // This event happens, when labplot loses the focus (modal window is opened or the user switches to another application)
0660         // and gets it back (modal window is closed or the user switches back to labplot).
0661         return;
0662     } else
0663         m_currentDock = view;
0664 
0665     updateGUI();
0666     if (!m_suppressCurrentSubWindowChangedEvent)
0667         m_projectExplorer->setCurrentAspect(view->part());
0668 }
0669 
0670 void MainWin::initActions() {
0671     // ******************** File-menu *******************************
0672     // add some standard actions
0673     m_newProjectAction = KStandardAction::openNew(
0674         this,
0675         [=]() {
0676             newProject(true);
0677         },
0678         actionCollection());
0679     m_openProjectAction = KStandardAction::open(this, static_cast<void (MainWin::*)()>(&MainWin::openProject), actionCollection());
0680     m_recentProjectsAction = KStandardAction::openRecent(this, &MainWin::openRecentProject, actionCollection());
0681     // m_closeAction = KStandardAction::close(this, &MainWin::closeProject, actionCollection());
0682     // actionCollection()->setDefaultShortcut(m_closeAction, QKeySequence()); // remove the shortcut, QKeySequence::Close will be used for closing sub-windows
0683     m_saveAction = KStandardAction::save(this, &MainWin::saveProject, actionCollection());
0684     m_saveAsAction = KStandardAction::saveAs(this, &MainWin::saveProjectAs, actionCollection());
0685     m_printAction = KStandardAction::print(this, &MainWin::print, actionCollection());
0686     m_printPreviewAction = KStandardAction::printPreview(this, &MainWin::printPreview, actionCollection());
0687 
0688     QAction* openExample = new QAction(i18n("&Open Example"), actionCollection());
0689     openExample->setIcon(QIcon::fromTheme(QLatin1String("folder-documents")));
0690     actionCollection()->addAction(QLatin1String("file_example_open"), openExample);
0691     connect(openExample, &QAction::triggered, this, [=]() {
0692         auto* dlg = new ExamplesDialog(this);
0693         if (dlg->exec() == QDialog::Accepted)
0694             openProject(dlg->path());
0695         delete dlg;
0696     });
0697 
0698     m_fullScreenAction = KStandardAction::fullScreen(this, &MainWin::toggleFullScreen, this, actionCollection());
0699 
0700     // QDEBUG(Q_FUNC_INFO << ", preferences action name:" << KStandardAction::name(KStandardAction::Preferences))
0701     KStandardAction::preferences(this, &MainWin::settingsDialog, actionCollection());
0702     // QAction* action = actionCollection()->action(KStandardAction::name(KStandardAction::Preferences)));
0703     KStandardAction::quit(this, &MainWin::close, actionCollection());
0704 
0705     // New Folder/Workbook/Spreadsheet/Matrix/Worksheet/Datasources
0706     m_newWorkbookAction = new QAction(QIcon::fromTheme(QLatin1String("labplot-workbook-new")), i18n("Workbook"), this);
0707     actionCollection()->addAction(QLatin1String("new_workbook"), m_newWorkbookAction);
0708     m_newWorkbookAction->setWhatsThis(i18n("Creates a new workbook for collection spreadsheets, matrices and plots"));
0709     connect(m_newWorkbookAction, &QAction::triggered, this, &MainWin::newWorkbook);
0710 
0711     m_newDatapickerAction = new QAction(QIcon::fromTheme(QLatin1String("color-picker-black")), i18n("Data Extractor"), this);
0712     m_newDatapickerAction->setWhatsThis(i18n("Creates a data extractor for getting data from a picture"));
0713     actionCollection()->addAction(QLatin1String("new_datapicker"), m_newDatapickerAction);
0714     connect(m_newDatapickerAction, &QAction::triggered, this, &MainWin::newDatapicker);
0715 
0716     m_newSpreadsheetAction = new QAction(QIcon::fromTheme(QLatin1String("labplot-spreadsheet-new")), i18n("Spreadsheet"), this);
0717     //  m_newSpreadsheetAction->setShortcut(Qt::CTRL+Qt::Key_Equal);
0718     m_newSpreadsheetAction->setWhatsThis(i18n("Creates a new spreadsheet for data editing"));
0719     actionCollection()->addAction(QLatin1String("new_spreadsheet"), m_newSpreadsheetAction);
0720     connect(m_newSpreadsheetAction, &QAction::triggered, this, &MainWin::newSpreadsheet);
0721 
0722     m_newMatrixAction = new QAction(QIcon::fromTheme(QLatin1String("labplot-matrix-new")), i18n("Matrix"), this);
0723     //  m_newMatrixAction->setShortcut(Qt::CTRL+Qt::Key_Equal);
0724     m_newMatrixAction->setWhatsThis(i18n("Creates a new matrix for data editing"));
0725     actionCollection()->addAction(QLatin1String("new_matrix"), m_newMatrixAction);
0726     connect(m_newMatrixAction, &QAction::triggered, this, &MainWin::newMatrix);
0727 
0728     m_newWorksheetAction = new QAction(QIcon::fromTheme(QLatin1String("labplot-worksheet-new")), i18n("Worksheet"), this);
0729     //  m_newWorksheetAction->setShortcut(Qt::ALT+Qt::Key_X);
0730     m_newWorksheetAction->setWhatsThis(i18n("Creates a new worksheet for data plotting"));
0731     actionCollection()->addAction(QLatin1String("new_worksheet"), m_newWorksheetAction);
0732     connect(m_newWorksheetAction, &QAction::triggered, this, &MainWin::newWorksheet);
0733 
0734     m_newNotesAction = new QAction(QIcon::fromTheme(QLatin1String("document-new")), i18n("Note"), this);
0735     m_newNotesAction->setWhatsThis(i18n("Creates a new note for arbitrary text"));
0736     actionCollection()->addAction(QLatin1String("new_notes"), m_newNotesAction);
0737     connect(m_newNotesAction, &QAction::triggered, this, &MainWin::newNotes);
0738 
0739     m_newFolderAction = new QAction(QIcon::fromTheme(QLatin1String("folder-new")), i18n("Folder"), this);
0740     m_newFolderAction->setWhatsThis(i18n("Creates a new folder to collect sheets and other elements"));
0741     actionCollection()->addAction(QLatin1String("new_folder"), m_newFolderAction);
0742     connect(m_newFolderAction, &QAction::triggered, this, &MainWin::newFolder);
0743 
0744     //"New file datasources"
0745     m_newLiveDataSourceAction = new QAction(QIcon::fromTheme(QLatin1String("edit-text-frame-update")), i18n("Live Data Source..."), this);
0746     m_newLiveDataSourceAction->setWhatsThis(i18n("Creates a live data source to read data from a real time device"));
0747     actionCollection()->addAction(QLatin1String("new_live_datasource"), m_newLiveDataSourceAction);
0748     connect(m_newLiveDataSourceAction, &QAction::triggered, this, &MainWin::newLiveDataSource);
0749 
0750     // Import/Export
0751     m_importFileAction = new QAction(QIcon::fromTheme(QLatin1String("document-import")), i18n("From File..."), this);
0752     actionCollection()->setDefaultShortcut(m_importFileAction, Qt::CTRL | Qt::SHIFT | Qt::Key_I);
0753     m_importFileAction->setWhatsThis(i18n("Import data from a regular file"));
0754     connect(m_importFileAction, &QAction::triggered, this, [=]() {
0755         importFileDialog();
0756     });
0757 
0758     // second "import from file" action, with a shorter name, to be used in the sub-menu of the "Import"-menu.
0759     // the first action defined above will be used in the toolbar and touchbar where we need the more detailed name "Import From File".
0760     m_importFileAction_2 = new QAction(QIcon::fromTheme(QLatin1String("document-import")), i18n("From File..."), this);
0761     actionCollection()->addAction(QLatin1String("import_file"), m_importFileAction_2);
0762     m_importFileAction_2->setWhatsThis(i18n("Import data from a regular file"));
0763     connect(m_importFileAction_2, &QAction::triggered, this, [=]() {
0764         importFileDialog();
0765     });
0766 
0767     m_importSqlAction = new QAction(QIcon::fromTheme(QLatin1String("network-server-database")), i18n("From SQL Database..."), this);
0768     m_importSqlAction->setWhatsThis(i18n("Import data from a SQL database"));
0769     actionCollection()->addAction(QLatin1String("import_sql"), m_importSqlAction);
0770     connect(m_importSqlAction, &QAction::triggered, this, &MainWin::importSqlDialog);
0771 
0772     m_importDatasetAction = new QAction(QIcon::fromTheme(QLatin1String("database-index")), i18n("From Dataset Collection..."), this);
0773     m_importDatasetAction->setWhatsThis(i18n("Imports data from an online dataset"));
0774     actionCollection()->addAction(QLatin1String("import_dataset_datasource"), m_importDatasetAction);
0775     connect(m_importDatasetAction, &QAction::triggered, this, &MainWin::importDatasetDialog);
0776 
0777     m_importLabPlotAction = new QAction(QIcon::fromTheme(QLatin1String("project-open")), i18n("LabPlot Project..."), this);
0778     m_importLabPlotAction->setWhatsThis(i18n("Import a project from a LabPlot project file (.lml)"));
0779     actionCollection()->addAction(QLatin1String("import_labplot"), m_importLabPlotAction);
0780     connect(m_importLabPlotAction, &QAction::triggered, this, &MainWin::importProjectDialog);
0781 
0782 #ifdef HAVE_LIBORIGIN
0783     m_importOpjAction = new QAction(QIcon::fromTheme(QLatin1String("project-open")), i18n("Origin Project (OPJ)..."), this);
0784     m_importOpjAction->setWhatsThis(i18n("Import a project from an OriginLab Origin project file (.opj)"));
0785     actionCollection()->addAction(QLatin1String("import_opj"), m_importOpjAction);
0786     connect(m_importOpjAction, &QAction::triggered, this, &MainWin::importProjectDialog);
0787 #endif
0788 
0789     m_exportAction = new QAction(QIcon::fromTheme(QLatin1String("document-export")), i18n("Export..."), this);
0790     m_exportAction->setWhatsThis(i18n("Export selected element"));
0791     actionCollection()->setDefaultShortcut(m_exportAction, Qt::CTRL | Qt::SHIFT | Qt::Key_E);
0792     actionCollection()->addAction(QLatin1String("export"), m_exportAction);
0793     connect(m_exportAction, &QAction::triggered, this, &MainWin::exportDialog);
0794 
0795 #ifdef HAVE_PURPOSE
0796     m_shareAction = new QAction(QIcon::fromTheme(QLatin1String("document-share")), i18n("Share"), this);
0797     actionCollection()->addAction(QLatin1String("share"), m_shareAction);
0798 #endif
0799 
0800     // Tools
0801     auto* action = new QAction(QIcon::fromTheme(QLatin1String("color-management")), i18n("Color Maps Browser"), this);
0802     action->setWhatsThis(i18n("Open dialog to browse through the available color maps."));
0803     actionCollection()->addAction(QLatin1String("color_maps"), action);
0804     connect(action, &QAction::triggered, this, [=]() {
0805         auto* dlg = new ColorMapsDialog(this);
0806         dlg->exec();
0807         delete dlg;
0808     });
0809 
0810 #ifdef HAVE_FITS
0811     action = new QAction(QIcon::fromTheme(QLatin1String("editor")), i18n("FITS Metadata Editor..."), this);
0812     action->setWhatsThis(i18n("Open editor to edit FITS meta data"));
0813     actionCollection()->addAction(QLatin1String("edit_fits"), action);
0814     connect(action, &QAction::triggered, this, &MainWin::editFitsFileDialog);
0815 #endif
0816 
0817     // Edit
0818     // Undo/Redo-stuff
0819     m_undoAction = KStandardAction::undo(this, SLOT(undo()), actionCollection());
0820     m_redoAction = KStandardAction::redo(this, SLOT(redo()), actionCollection());
0821     m_historyAction = new QAction(QIcon::fromTheme(QLatin1String("view-history")), i18n("Undo/Redo History..."), this);
0822     actionCollection()->addAction(QLatin1String("history"), m_historyAction);
0823     connect(m_historyAction, &QAction::triggered, this, &MainWin::historyDialog);
0824 
0825 #ifdef Q_OS_MAC
0826     m_undoIconOnlyAction = new QAction(m_undoAction->icon(), QString());
0827     connect(m_undoIconOnlyAction, &QAction::triggered, this, &MainWin::undo);
0828 
0829     m_redoIconOnlyAction = new QAction(m_redoAction->icon(), QString());
0830     connect(m_redoIconOnlyAction, &QAction::triggered, this, &MainWin::redo);
0831 #endif
0832     // TODO: more menus
0833     //  Appearance
0834     // Analysis: see WorksheetView.cpp
0835     // Drawing
0836     // Script
0837 
0838     // Windows
0839     m_closeWindowAction = new QAction(i18n("&Close"), this);
0840     actionCollection()->setDefaultShortcut(m_closeWindowAction, QKeySequence::Close);
0841     m_closeWindowAction->setStatusTip(i18n("Close the active window"));
0842     actionCollection()->addAction(QLatin1String("close window"), m_closeWindowAction);
0843 
0844     m_closeAllWindowsAction = new QAction(i18n("Close &All"), this);
0845     m_closeAllWindowsAction->setStatusTip(i18n("Close all the windows"));
0846     actionCollection()->addAction(QLatin1String("close all windows"), m_closeAllWindowsAction);
0847 
0848     m_nextWindowAction = new QAction(QIcon::fromTheme(QLatin1String("go-next-view")), i18n("Ne&xt"), this);
0849     actionCollection()->setDefaultShortcut(m_nextWindowAction, QKeySequence::NextChild);
0850     m_nextWindowAction->setStatusTip(i18n("Move the focus to the next window"));
0851     actionCollection()->addAction(QLatin1String("next window"), m_nextWindowAction);
0852 
0853     m_prevWindowAction = new QAction(QIcon::fromTheme(QLatin1String("go-previous-view")), i18n("Pre&vious"), this);
0854     actionCollection()->setDefaultShortcut(m_prevWindowAction, QKeySequence::PreviousChild);
0855     m_prevWindowAction->setStatusTip(i18n("Move the focus to the previous window"));
0856     actionCollection()->addAction(QLatin1String("previous window"), m_prevWindowAction);
0857 
0858     // Actions for window visibility
0859     auto* windowVisibilityActions = new QActionGroup(this);
0860     windowVisibilityActions->setExclusive(true);
0861 
0862     m_visibilityFolderAction = new QAction(QIcon::fromTheme(QLatin1String("folder")), i18n("Current &Folder Only"), windowVisibilityActions);
0863     m_visibilityFolderAction->setCheckable(true);
0864     m_visibilityFolderAction->setData(static_cast<int>(Project::DockVisibility::folderOnly));
0865 
0866     m_visibilitySubfolderAction =
0867         new QAction(QIcon::fromTheme(QLatin1String("folder-documents")), i18n("Current Folder and &Subfolders"), windowVisibilityActions);
0868     m_visibilitySubfolderAction->setCheckable(true);
0869     m_visibilitySubfolderAction->setData(static_cast<int>(Project::DockVisibility::folderAndSubfolders));
0870 
0871     m_visibilityAllAction = new QAction(i18n("&All"), windowVisibilityActions);
0872     m_visibilityAllAction->setCheckable(true);
0873     m_visibilityAllAction->setData(static_cast<int>(Project::DockVisibility::allDocks));
0874 
0875     connect(windowVisibilityActions, &QActionGroup::triggered, this, &MainWin::setDockVisibility);
0876 
0877     // show/hide the status and menu bars
0878     //  KMainWindow should provide a menu that allows showing/hiding of the statusbar via showStatusbar()
0879     //  see https://api.kde.org/frameworks/kxmlgui/html/classKXmlGuiWindow.html#a3d7371171cafabe30cb3bb7355fdfed1
0880     // KXMLGUI framework automatically stores "Disabled" for the key "StatusBar"
0881     KConfigGroup groupMain = Settings::group(QStringLiteral("MainWindow"));
0882     const QString& str = groupMain.readEntry(QLatin1String("StatusBar"), "");
0883     bool statusBarDisabled = (str == QLatin1String("Disabled"));
0884     DEBUG(Q_FUNC_INFO << ", statusBar enabled in config: " << !statusBarDisabled)
0885     createStandardStatusBarAction();
0886     m_statusBarAction = KStandardAction::showStatusbar(this, &MainWin::toggleStatusBar, actionCollection());
0887     m_statusBarAction->setChecked(!statusBarDisabled);
0888     statusBar()->setEnabled(!statusBarDisabled); // setVisible() does not work
0889 
0890     KStandardAction::showMenubar(this, &MainWin::toggleMenuBar, actionCollection());
0891 
0892     // show/hide the memory usage widget
0893     m_memoryInfoAction = new QAction(i18n("Show Memory Usage"), this);
0894     m_memoryInfoAction->setCheckable(true);
0895     connect(m_memoryInfoAction, &QAction::triggered, this, &MainWin::toggleMemoryInfo);
0896 
0897     // Actions for hiding/showing the dock widgets
0898     auto* docksActions = new QActionGroup(this);
0899     docksActions->setExclusive(false);
0900 
0901     m_projectExplorerDockAction = new QAction(QIcon::fromTheme(QLatin1String("view-list-tree")), i18n("Project Explorer"), docksActions);
0902     m_projectExplorerDockAction->setCheckable(true);
0903     m_projectExplorerDockAction->setChecked(true);
0904     actionCollection()->addAction(QLatin1String("toggle_project_explorer_dock"), m_projectExplorerDockAction);
0905 
0906     m_propertiesDockAction = new QAction(QIcon::fromTheme(QLatin1String("view-list-details")), i18n("Properties Explorer"), docksActions);
0907     m_propertiesDockAction->setCheckable(true);
0908     m_propertiesDockAction->setChecked(true);
0909     actionCollection()->addAction(QLatin1String("toggle_properties_explorer_dock"), m_propertiesDockAction);
0910 
0911     m_worksheetPreviewAction = new QAction(QIcon::fromTheme(QLatin1String("view-preview")), i18n("Worksheet Preview"), docksActions);
0912     m_worksheetPreviewAction->setCheckable(true);
0913     m_worksheetPreviewAction->setChecked(true);
0914     actionCollection()->addAction(QLatin1String("toggle_worksheet_preview_dock"), m_worksheetPreviewAction);
0915 
0916     connect(docksActions, &QActionGroup::triggered, this, &MainWin::toggleDockWidget);
0917 
0918     // global search
0919     m_searchAction = new QAction(actionCollection());
0920     m_searchAction->setShortcut(QKeySequence::Find);
0921     connect(m_searchAction, &QAction::triggered, this, [=]() {
0922         if (m_project) {
0923             if (!m_projectExplorerDock->isVisible()) {
0924                 m_projectExplorerDockAction->setChecked(true);
0925                 toggleDockWidget(m_projectExplorerDockAction);
0926             }
0927             m_projectExplorer->search();
0928         }
0929     });
0930     this->addAction(m_searchAction);
0931 
0932 #ifdef HAVE_CANTOR_LIBS
0933     // configure CAS backends
0934     m_configureCASAction = new QAction(QIcon::fromTheme(QLatin1String("cantor")), i18n("Configure CAS..."), this);
0935     m_configureCASAction->setWhatsThis(i18n("Opens the settings for Computer Algebra Systems to modify the available systems or to enable new ones"));
0936     m_configureCASAction->setMenuRole(QAction::NoRole); // prevent macOS Qt heuristics to select this action for preferences
0937     actionCollection()->addAction(QLatin1String("configure_cas"), m_configureCASAction);
0938     connect(m_configureCASAction, &QAction::triggered, this, &MainWin::cantorSettingsDialog);
0939 #endif
0940 }
0941 
0942 void MainWin::initMenus() {
0943 #ifdef HAVE_PURPOSE
0944     m_shareMenu = new Purpose::Menu(this);
0945     m_shareMenu->model()->setPluginType(QStringLiteral("Export"));
0946     connect(m_shareMenu, &Purpose::Menu::finished, this, &MainWin::shareActionFinished);
0947     m_shareAction->setMenu(m_shareMenu);
0948 #endif
0949 
0950     // add the actions to toggle the status bar and the project and properties explorer widgets to the "View" menu.
0951     // this menu is created automatically when the default "full screen" action is created in initActions().
0952     auto* menu = dynamic_cast<QMenu*>(factory()->container(QLatin1String("view"), this));
0953     if (menu) {
0954         menu->addSeparator();
0955         menu->addAction(m_projectExplorerDockAction);
0956         menu->addAction(m_propertiesDockAction);
0957         menu->addAction(m_worksheetPreviewAction);
0958     }
0959 
0960     // menu in the main toolbar for adding new aspects
0961     menu = dynamic_cast<QMenu*>(factory()->container(QLatin1String("new"), this));
0962     if (menu)
0963         menu->setIcon(QIcon::fromTheme(QLatin1String("window-new")));
0964 
0965     // menu in the project explorer and in the toolbar for adding new aspects
0966     m_newMenu = new QMenu(i18n("Add New"), this);
0967     m_newMenu->setIcon(QIcon::fromTheme(QLatin1String("window-new")));
0968     m_newMenu->addAction(m_newFolderAction);
0969     m_newMenu->addAction(m_newWorkbookAction);
0970     m_newMenu->addAction(m_newSpreadsheetAction);
0971     m_newMenu->addAction(m_newMatrixAction);
0972     m_newMenu->addAction(m_newWorksheetAction);
0973     m_newMenu->addAction(m_newNotesAction);
0974     m_newMenu->addAction(m_newDatapickerAction);
0975     m_newMenu->addSeparator();
0976     m_newMenu->addAction(m_newLiveDataSourceAction);
0977 
0978     // import menu
0979     m_importMenu = new QMenu(this);
0980     m_importMenu->setIcon(QIcon::fromTheme(QLatin1String("document-import")));
0981     m_importMenu->addAction(m_importFileAction_2);
0982     m_importMenu->addAction(m_importSqlAction);
0983     m_importMenu->addAction(m_importDatasetAction);
0984     m_importMenu->addSeparator();
0985     m_importMenu->addAction(m_importLabPlotAction);
0986 #ifdef HAVE_LIBORIGIN
0987     m_importMenu->addAction(m_importOpjAction);
0988 #endif
0989 
0990     // icon for the menu "import" in the main menu created via the rc file
0991     menu = qobject_cast<QMenu*>(factory()->container(QLatin1String("import"), this));
0992     menu->setIcon(QIcon::fromTheme(QLatin1String("document-import")));
0993 
0994     // menu subwindow visibility policy
0995     m_visibilityMenu = new QMenu(i18n("Window Visibility"), this);
0996     m_visibilityMenu->setIcon(QIcon::fromTheme(QLatin1String("window-duplicate")));
0997     m_visibilityMenu->addAction(m_visibilityFolderAction);
0998     m_visibilityMenu->addAction(m_visibilitySubfolderAction);
0999     m_visibilityMenu->addAction(m_visibilityAllAction);
1000 
1001 //  //menu for editing files
1002 //  m_editMenu = new QMenu(i18n("Edit"), this);
1003 #ifdef HAVE_FITS
1004 //  m_editMenu->addAction(m_editFitsFileAction);
1005 #endif
1006 
1007     // set the action for the current color scheme checked
1008     auto group = Settings::group(QStringLiteral("Settings_General"));
1009 #if KCONFIGWIDGETS_VERSION >= QT_VERSION_CHECK(5, 67, 0) // KColorSchemeManager has a system default option
1010     QString schemeName = group.readEntry("ColorScheme");
1011 #else
1012     auto generalGlobalsGroup = KSharedConfig::openConfig(QLatin1String("kdeglobals"))->group("General");
1013     QString defaultSchemeName = generalGlobalsGroup.readEntry("ColorScheme", QStringLiteral("Breeze"));
1014     QString schemeName = group.readEntry("ColorScheme", defaultSchemeName);
1015 #endif
1016     // default dark scheme on Windows is not optimal (Breeze dark is better)
1017     // we can't find out if light or dark mode is used, so we don't switch to Breeze/Breeze dark here
1018     DEBUG(Q_FUNC_INFO << ", Color scheme = " << STDSTRING(schemeName))
1019 #if KCONFIGWIDGETS_VERSION >= QT_VERSION_CHECK(5, 107, 0) // use KColorSchemeMenu::createMenu and set the text and check an action manually
1020     auto* schemesMenu = KColorSchemeMenu::createMenu(m_schemeManager, this);
1021     schemesMenu->setText(i18n("Color Scheme"));
1022 #else
1023     auto* schemesMenu = m_schemeManager->createSchemeSelectionMenu(i18n("Color Scheme"), schemeName, this);
1024 #endif
1025     schemesMenu->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-color")));
1026     connect(schemesMenu->menu(), &QMenu::triggered, this, &MainWin::colorSchemeChanged);
1027 
1028     QMenu* settingsMenu = dynamic_cast<QMenu*>(factory()->container(QLatin1String("settings"), this));
1029     if (settingsMenu) {
1030         auto* action = settingsMenu->insertSeparator(settingsMenu->actions().constFirst());
1031         settingsMenu->insertMenu(action, schemesMenu->menu());
1032 
1033         // add m_memoryInfoAction after the "Show status bar" action
1034         auto actions = settingsMenu->actions();
1035         const int index = actions.indexOf(m_statusBarAction);
1036         settingsMenu->insertAction(actions.at(index + 1), m_memoryInfoAction);
1037     }
1038 
1039     // Cantor backends to menu and context menu
1040 #ifdef HAVE_CANTOR_LIBS
1041     auto backendNames = Cantor::Backend::listAvailableBackends();
1042 #if !defined(NDEBUG) || defined(Q_OS_WIN) || defined(Q_OS_MACOS)
1043     WARN(Q_FUNC_INFO << ", " << backendNames.count() << " Cantor backends available:")
1044     for (const auto& b : backendNames)
1045         WARN("Backend: " << STDSTRING(b))
1046 #endif
1047 
1048     if (!backendNames.isEmpty()) {
1049         // sub-menu shown in the main toolbar
1050         m_newNotebookMenu = new QMenu(this);
1051 
1052         // sub-menu shown in the main menu bar
1053         auto* menu = dynamic_cast<QMenu*>(factory()->container(QLatin1String("new_notebook"), this));
1054         if (menu) {
1055             menu->setIcon(QIcon::fromTheme(QLatin1String("cantor")));
1056             m_newMenu->addSeparator();
1057             m_newMenu->addMenu(menu);
1058             updateNotebookActions();
1059         }
1060 
1061         if (settingsMenu)
1062             settingsMenu->addAction(m_configureCASAction);
1063     }
1064 #else
1065     delete this->guiFactory()->container(QStringLiteral("notebook"), this);
1066     delete this->guiFactory()->container(QStringLiteral("new_notebook"), this);
1067     delete this->guiFactory()->container(QStringLiteral("notebook_toolbar"), this);
1068 #endif
1069 }
1070 
1071 void MainWin::colorSchemeChanged(QAction* action) {
1072     QString schemeName = KLocalizedString::removeAcceleratorMarker(action->text());
1073 
1074     // background of the mdi area is not updated on theme changes, do it here.
1075     // QModelIndex index = m_schemeManager->indexForScheme(schemeName);
1076     // const QPalette& palette = KColorScheme::createApplicationPalette(KSharedConfig::openConfig(index.data(Qt::UserRole).toString()));
1077     // const QBrush& brush = palette.brush(QPalette::Dark);
1078     // m_DockManager->setBackground(brush);
1079 
1080     // save the selected color scheme
1081     KConfigGroup group = Settings::group(QStringLiteral("Settings_General"));
1082     group.writeEntry(QStringLiteral("ColorScheme"), schemeName);
1083     group.sync();
1084 }
1085 
1086 /*!
1087     Asks to save the project if it was modified.
1088     \return \c true if the project still needs to be saved ("cancel" clicked), \c false otherwise.
1089  */
1090 bool MainWin::warnModified() {
1091     if (m_project->hasChanged()) {
1092 #if KCONFIGWIDGETS_VERSION >= QT_VERSION_CHECK(5, 100, 0)
1093         int option = KMessageBox::warningTwoActionsCancel(this,
1094                                                           i18n("The current project %1 has been modified. Do you want to save it?", m_project->name()),
1095                                                           i18n("Save Project"),
1096                                                           KStandardGuiItem::save(),
1097                                                           KStandardGuiItem::dontSave());
1098         switch (option) {
1099         case KMessageBox::PrimaryAction:
1100             return !saveProject();
1101         case KMessageBox::SecondaryAction:
1102             break;
1103         case KMessageBox::Cancel:
1104             return true;
1105         }
1106 #else
1107         int option = KMessageBox::warningYesNoCancel(this,
1108                                                      i18n("The current project %1 has been modified. Do you want to save it?", m_project->name()),
1109                                                      i18n("Save Project"));
1110         switch (option) {
1111         case KMessageBox::Yes:
1112             return !saveProject();
1113         case KMessageBox::No:
1114             break;
1115         case KMessageBox::Cancel:
1116             return true;
1117         }
1118 #endif
1119     }
1120 
1121     return false;
1122 }
1123 
1124 /*!
1125  * updates the state of actions, menus and toolbars (enabled or disabled)
1126  * on project changes (project closes and opens)
1127  */
1128 void MainWin::updateGUIOnProjectChanges(const QByteArray& windowState) {
1129     // return;
1130     if (m_closing)
1131         return;
1132 
1133     auto* factory = this->guiFactory();
1134     if (!m_DockManager || !m_DockManager->focusedDockWidget()) {
1135         factory->container(QLatin1String("spreadsheet"), this)->setEnabled(false);
1136         factory->container(QLatin1String("matrix"), this)->setEnabled(false);
1137         factory->container(QLatin1String("worksheet"), this)->setEnabled(false);
1138         factory->container(QLatin1String("datapicker"), this)->setEnabled(false);
1139         factory->container(QLatin1String("spreadsheet_toolbar"), this)->hide();
1140         factory->container(QLatin1String("worksheet_toolbar"), this)->hide();
1141         factory->container(QLatin1String("cartesian_plot_toolbar"), this)->hide();
1142         factory->container(QLatin1String("datapicker_toolbar"), this)->hide();
1143 #ifdef HAVE_CANTOR_LIBS
1144         factory->container(QLatin1String("notebook"), this)->setEnabled(false);
1145         factory->container(QLatin1String("notebook_toolbar"), this)->hide();
1146 #endif
1147     }
1148 
1149     updateTitleBar();
1150 
1151     // undo/redo actions are disabled in both cases - when the project is closed or opened
1152     m_undoAction->setEnabled(false);
1153     m_redoAction->setEnabled(false);
1154 
1155     if (!windowState.isEmpty()) {
1156         for (auto dock : m_DockManager->dockWidgetsMap()) {
1157             auto* d = dynamic_cast<ContentDockWidget*>(dock);
1158             if (d)
1159                 d->part()->suppressDeletion(true);
1160         }
1161         changeVisibleAllDocks(false);
1162         for (auto dock : m_DockManager->dockWidgetsMap()) {
1163             auto* d = dynamic_cast<ContentDockWidget*>(dock);
1164             if (d)
1165                 d->part()->suppressDeletion(false);
1166         }
1167         m_DockManager->restoreState(windowState);
1168     } else {
1169         // They might be not available, if at startup the "Do nothing" option is selected
1170         if (m_projectExplorerDock)
1171             m_projectExplorerDock->toggleView(true);
1172         if (m_propertiesDock)
1173             m_propertiesDock->toggleView(true);
1174         if (m_worksheetPreviewDock)
1175             m_worksheetPreviewDock->toggleView(true);
1176     }
1177 }
1178 
1179 /*
1180  * updates the state of actions, menus and toolbars (enabled or disabled)
1181  * depending on the currently active window (worksheet or spreadsheet).
1182  */
1183 void MainWin::updateGUI() {
1184     if (!m_project || m_project->isLoading())
1185         return;
1186 
1187     if (m_closing || m_projectClosing)
1188         return;
1189 
1190 #ifdef HAVE_TOUCHBAR
1191     // reset the touchbar
1192     m_touchBar->clear();
1193     m_touchBar->addAction(m_undoIconOnlyAction);
1194     m_touchBar->addAction(m_redoIconOnlyAction);
1195     m_touchBar->addSeparator();
1196 #endif
1197 
1198     auto* factory = this->guiFactory();
1199     if (!m_DockManager || !m_DockManager->focusedDockWidget()) {
1200         factory->container(QLatin1String("spreadsheet"), this)->setEnabled(false);
1201         factory->container(QLatin1String("matrix"), this)->setEnabled(false);
1202         factory->container(QLatin1String("worksheet"), this)->setEnabled(false);
1203         factory->container(QLatin1String("datapicker"), this)->setEnabled(false);
1204         factory->container(QLatin1String("spreadsheet_toolbar"), this)->hide();
1205         factory->container(QLatin1String("worksheet_toolbar"), this)->hide();
1206         factory->container(QLatin1String("cartesian_plot_toolbar"), this)->hide();
1207         factory->container(QLatin1String("datapicker_toolbar"), this)->hide();
1208 #ifdef HAVE_CANTOR_LIBS
1209         factory->container(QLatin1String("notebook"), this)->setEnabled(false);
1210         factory->container(QLatin1String("notebook_toolbar"), this)->hide();
1211 #endif
1212         m_printAction->setEnabled(false);
1213         m_printPreviewAction->setEnabled(false);
1214         m_exportAction->setEnabled(false);
1215         return;
1216     } else {
1217         m_printAction->setEnabled(true);
1218         m_printPreviewAction->setEnabled(true);
1219         m_exportAction->setEnabled(true);
1220     }
1221 
1222 #ifdef HAVE_TOUCHBAR
1223     if (dynamic_cast<Folder*>(m_currentAspect)) {
1224         m_touchBar->addAction(m_newWorksheetAction);
1225         m_touchBar->addAction(m_newSpreadsheetAction);
1226         m_touchBar->addAction(m_newMatrixAction);
1227     }
1228 #endif
1229 
1230     Q_ASSERT(m_currentAspect);
1231     // Handle the Worksheet-object
1232     const auto* w = dynamic_cast<Worksheet*>(m_currentAspect);
1233     if (!w)
1234         w = dynamic_cast<Worksheet*>(m_currentAspect->parent(AspectType::Worksheet));
1235 
1236     if (w) {
1237         bool update = (w != m_lastWorksheet);
1238         m_lastWorksheet = w;
1239 
1240         // populate worksheet menu
1241         auto* view = qobject_cast<WorksheetView*>(w->view());
1242         auto* menu = qobject_cast<QMenu*>(factory->container(QLatin1String("worksheet"), this));
1243         if (update) {
1244             menu->clear();
1245             view->createContextMenu(menu);
1246         }
1247         menu->setEnabled(true);
1248 
1249         // populate worksheet-toolbar
1250         auto* toolbar = qobject_cast<QToolBar*>(factory->container(QLatin1String("worksheet_toolbar"), this));
1251         if (update) {
1252             toolbar->clear();
1253             view->fillToolBar(toolbar);
1254         }
1255         toolbar->setVisible(true);
1256         toolbar->setEnabled(true);
1257 
1258         // populate the toolbar for cartesian plots
1259         toolbar = qobject_cast<QToolBar*>(factory->container(QLatin1String("cartesian_plot_toolbar"), this));
1260         if (update) {
1261             toolbar->clear();
1262             view->fillCartesianPlotToolBar(toolbar);
1263         }
1264         toolbar->setVisible(true);
1265         toolbar->setEnabled(true);
1266 
1267         // populate the touchbar on Mac
1268 #ifdef HAVE_TOUCHBAR
1269         view->fillTouchBar(m_touchBar);
1270 #endif
1271         // hide the spreadsheet toolbar
1272         factory->container(QLatin1String("spreadsheet_toolbar"), this)->setVisible(false);
1273     } else {
1274         factory->container(QLatin1String("worksheet"), this)->setEnabled(false);
1275         factory->container(QLatin1String("worksheet_toolbar"), this)->setVisible(false);
1276         //      factory->container(QLatin1String("drawing"), this)->setEnabled(false);
1277         factory->container(QLatin1String("worksheet_toolbar"), this)->setEnabled(false);
1278         factory->container(QLatin1String("cartesian_plot_toolbar"), this)->setEnabled(false);
1279     }
1280 
1281     // Handle the Spreadsheet-object
1282     const auto* spreadsheet = this->activeSpreadsheet();
1283     if (!spreadsheet)
1284         spreadsheet = dynamic_cast<Spreadsheet*>(m_currentAspect->parent(AspectType::Spreadsheet));
1285     if (spreadsheet) {
1286         bool update = (spreadsheet != m_lastSpreadsheet);
1287         m_lastSpreadsheet = spreadsheet;
1288 
1289         // populate spreadsheet-menu
1290         auto* view = qobject_cast<SpreadsheetView*>(spreadsheet->view());
1291         auto* menu = qobject_cast<QMenu*>(factory->container(QLatin1String("spreadsheet"), this));
1292         if (update) {
1293             menu->clear();
1294             view->createContextMenu(menu);
1295         }
1296         menu->setEnabled(true);
1297 
1298         // populate spreadsheet-toolbar
1299         auto* toolbar = qobject_cast<QToolBar*>(factory->container(QLatin1String("spreadsheet_toolbar"), this));
1300         if (update) {
1301             toolbar->clear();
1302             view->fillToolBar(toolbar);
1303         }
1304         toolbar->setVisible(true);
1305         toolbar->setEnabled(true);
1306 
1307         // populate the touchbar on Mac
1308 #ifdef HAVE_TOUCHBAR
1309         m_touchBar->addAction(m_importFileAction);
1310         view->fillTouchBar(m_touchBar);
1311 #endif
1312 
1313         // spreadsheet has it's own search, unregister the shortcut for the global search here
1314         m_searchAction->setShortcut(QKeySequence());
1315     } else {
1316         factory->container(QLatin1String("spreadsheet"), this)->setEnabled(false);
1317         factory->container(QLatin1String("spreadsheet_toolbar"), this)->setVisible(false);
1318         m_searchAction->setShortcut(QKeySequence::Find);
1319     }
1320 
1321     // Handle the Matrix-object
1322     const auto* matrix = dynamic_cast<Matrix*>(m_currentAspect);
1323     if (!matrix)
1324         matrix = dynamic_cast<Matrix*>(m_currentAspect->parent(AspectType::Matrix));
1325     if (matrix) {
1326         // populate matrix-menu
1327         auto* view = qobject_cast<MatrixView*>(matrix->view());
1328         auto* menu = qobject_cast<QMenu*>(factory->container(QLatin1String("matrix"), this));
1329         menu->clear();
1330         view->createContextMenu(menu);
1331         menu->setEnabled(true);
1332 
1333         // populate the touchbar on Mac
1334 #ifdef HAVE_TOUCHBAR
1335         m_touchBar->addAction(m_importFileAction);
1336         // view->fillTouchBar(m_touchBar);
1337 #endif
1338     } else
1339         factory->container(QLatin1String("matrix"), this)->setEnabled(false);
1340 
1341 #ifdef HAVE_CANTOR_LIBS
1342     const auto* cantorworksheet = dynamic_cast<CantorWorksheet*>(m_currentAspect);
1343     if (!cantorworksheet)
1344         cantorworksheet = dynamic_cast<CantorWorksheet*>(m_currentAspect->parent(AspectType::CantorWorksheet));
1345     if (cantorworksheet) {
1346         auto* view = qobject_cast<CantorWorksheetView*>(cantorworksheet->view());
1347         auto* menu = qobject_cast<QMenu*>(factory->container(QLatin1String("notebook"), this));
1348         menu->clear();
1349         view->createContextMenu(menu);
1350         menu->setEnabled(true);
1351 
1352         auto* toolbar = qobject_cast<QToolBar*>(factory->container(QLatin1String("notebook_toolbar"), this));
1353         toolbar->setVisible(true);
1354         toolbar->clear();
1355         view->fillToolBar(toolbar);
1356     } else {
1357         // no Cantor worksheet selected -> deactivate Cantor worksheet related menu and toolbar
1358         factory->container(QLatin1String("notebook"), this)->setEnabled(false);
1359         factory->container(QLatin1String("notebook_toolbar"), this)->setVisible(false);
1360     }
1361 #endif
1362 
1363     const auto* datapicker = dynamic_cast<Datapicker*>(m_currentAspect);
1364     if (!datapicker)
1365         datapicker = dynamic_cast<Datapicker*>(m_currentAspect->parent(AspectType::Datapicker));
1366     if (!datapicker) {
1367         if (m_currentAspect && m_currentAspect->type() == AspectType::DatapickerCurve)
1368             datapicker = dynamic_cast<Datapicker*>(m_currentAspect->parentAspect());
1369     }
1370 
1371     if (datapicker) {
1372         // populate datapicker-menu
1373         auto* view = qobject_cast<DatapickerView*>(datapicker->view());
1374         auto* menu = qobject_cast<QMenu*>(factory->container(QLatin1String("datapicker"), this));
1375         menu->clear();
1376         view->createContextMenu(menu);
1377         menu->setEnabled(true);
1378 
1379         // populate spreadsheet-toolbar
1380         auto* toolbar = qobject_cast<QToolBar*>(factory->container(QLatin1String("datapicker_toolbar"), this));
1381         toolbar->clear();
1382         view->fillToolBar(toolbar);
1383         toolbar->setVisible(true);
1384     } else {
1385         factory->container(QLatin1String("datapicker"), this)->setEnabled(false);
1386         factory->container(QLatin1String("datapicker_toolbar"), this)->setVisible(false);
1387     }
1388 }
1389 
1390 /*!
1391     creates a new empty project. Returns \c true, if a new project was created.
1392     the parameter \c createInitialContent whether a default content (new worksheet, etc. )
1393     has to be created automatically (it's \c false if a project is opened, \c true if a new project is created).
1394 */
1395 bool MainWin::newProject(bool createInitialContent) {
1396     // close the current project, if available
1397     if (!closeProject())
1398         return false;
1399 
1400     QApplication::processEvents(QEventLoop::AllEvents, 100);
1401 
1402     m_project = new Project();
1403     undoStackIndexLastSave = 0;
1404     m_currentAspect = m_project;
1405     m_currentFolder = m_project;
1406 
1407     KConfigGroup group = Settings::group(QStringLiteral("Settings_General"));
1408     auto vis = Project::DockVisibility(group.readEntry("DockVisibility", 0));
1409     m_project->setDockVisibility(vis);
1410     if (vis == Project::DockVisibility::folderOnly)
1411         m_visibilityFolderAction->setChecked(true);
1412     else if (vis == Project::DockVisibility::folderAndSubfolders)
1413         m_visibilitySubfolderAction->setChecked(true);
1414     else
1415         m_visibilityAllAction->setChecked(true);
1416 
1417     m_aspectTreeModel = new AspectTreeModel(m_project, this);
1418     connect(m_aspectTreeModel, &AspectTreeModel::statusInfo, [=](const QString& text) {
1419         statusBar()->showMessage(text);
1420     });
1421 
1422     // newProject is called for the first time, there is no project explorer yet
1423     //-> initialize the project explorer,  the GUI-observer and the dock widgets.
1424     if (!m_projectExplorer) {
1425         const auto groupMainWin = Settings::group(QStringLiteral("MainWin"));
1426 
1427         // project explorer
1428         m_projectExplorerDock = new ads::CDockWidget(i18nc("@title:window", "Project Explorer"));
1429         m_projectExplorerDock->setObjectName(QLatin1String("projectexplorer"));
1430         m_projectExplorerDock->setWindowTitle(m_projectExplorerDock->windowTitle().replace(QLatin1String("&"), QString()));
1431         m_projectExplorerDock->toggleViewAction()->setText(QLatin1String(""));
1432 
1433         m_projectExplorer = new ProjectExplorer(m_projectExplorerDock);
1434         m_projectExplorerDock->setWidget(m_projectExplorer);
1435 
1436         connect(m_projectExplorer, &ProjectExplorer::currentAspectChanged, this, &MainWin::handleCurrentAspectChanged);
1437         connect(m_projectExplorer, &ProjectExplorer::activateView, this, &MainWin::activateSubWindowForAspect);
1438         connect(m_projectExplorerDock, &ads::CDockWidget::viewToggled, this, &MainWin::projectExplorerDockVisibilityChanged);
1439 
1440         // Properties dock
1441         m_propertiesDock = new ads::CDockWidget(i18nc("@title:window", "Properties"));
1442         m_propertiesDock->setObjectName(QLatin1String("aspect_properties_dock"));
1443         m_propertiesDock->setWindowTitle(m_propertiesDock->windowTitle().replace(QLatin1String("&"), QString()));
1444 
1445         // worksheet preview
1446         m_worksheetPreviewDock = new ads::CDockWidget(i18nc("@title:window", "Worksheet Preview"));
1447         m_worksheetPreviewDock->setObjectName(QLatin1String("worksheetpreview"));
1448         m_worksheetPreviewDock->setWindowTitle(m_worksheetPreviewDock->windowTitle().replace(QLatin1String("&"), QString()));
1449         m_worksheetPreviewDock->toggleViewAction()->setText(QLatin1String(""));
1450         connect(m_worksheetPreviewDock, &ads::CDockWidget::viewToggled, this, &MainWin::worksheetPreviewDockVisibilityChanged);
1451 
1452         m_worksheetPreviewWidget = new WorksheetPreviewWidget(m_worksheetPreviewDock);
1453         m_worksheetPreviewDock->setWidget(m_worksheetPreviewWidget);
1454 
1455         // restore the position of the dock widgets:
1456         //"WindowState" doesn't always contain the positions of the dock widgets,
1457         // user opened the application and closed it without creating a new project
1458         // and with this the dock widgets - this creates a "WindowState" section in the settings without dock widgets positions.
1459         // So, we set our default positions first and then read from the saved "WindowState" section
1460         m_DockManager->addDockWidget(ads::LeftDockWidgetArea, m_projectExplorerDock);
1461         m_DockManager->addDockWidget(ads::RightDockWidgetArea, m_propertiesDock);
1462         m_DockManager->addDockWidget(ads::RightDockWidgetArea, m_worksheetPreviewDock, m_projectExplorerDock->dockAreaWidget());
1463         if (groupMainWin.keyList().indexOf(QLatin1String("WindowState")) != -1)
1464             restoreState(groupMainWin.readEntry("WindowState", QByteArray()));
1465 
1466         auto* scrollArea = new QScrollArea(m_propertiesDock);
1467         scrollArea->setWidgetResizable(true);
1468 
1469         stackedWidget = new QStackedWidget(scrollArea);
1470         scrollArea->setWidget(stackedWidget); // stacked widget inside scroll area
1471         m_propertiesDock->setWidget(scrollArea); // scroll area inside dock
1472 
1473         connect(m_propertiesDock, &ads::CDockWidget::viewToggled, this, &MainWin::propertiesDockVisibilityChanged);
1474     }
1475 
1476     m_projectExplorer->setModel(m_aspectTreeModel);
1477     m_projectExplorer->setProject(m_project);
1478     m_projectExplorer->setCurrentAspect(m_project);
1479     m_worksheetPreviewWidget->setProject(m_project);
1480 
1481     m_newProjectAction->setEnabled(false);
1482 #ifdef HAVE_PURPOSE
1483     m_shareAction->setEnabled(false); // sharing is only possible after the project was saved to a file
1484 #endif
1485 
1486     m_guiObserver = new GuiObserver(this); // initialize after all docks were createad
1487     m_guiObserver->selectedAspectsChanged({static_cast<AbstractAspect*>(m_project)}); // Trigger showing properties
1488 
1489     connect(m_project, &Project::childAspectAdded, this, &MainWin::handleAspectAdded);
1490     connect(m_project, &Project::childAspectRemoved, this, &MainWin::handleAspectRemoved);
1491     connect(m_project, &Project::childAspectAboutToBeRemoved, this, &MainWin::handleAspectAboutToBeRemoved);
1492     connect(m_project, SIGNAL(statusInfo(QString)), statusBar(), SLOT(showMessage(QString)));
1493     connect(m_project, &Project::changed, this, &MainWin::projectChanged);
1494     connect(m_project, &Project::requestProjectContextMenu, this, &MainWin::createContextMenu);
1495     connect(m_project, &Project::requestFolderContextMenu, this, &MainWin::createFolderContextMenu);
1496     connect(m_project, &Project::mdiWindowVisibilityChanged, this, &MainWin::updateDockWindowVisibility);
1497     connect(m_project, &Project::closeRequested, this, &MainWin::closeProject);
1498 
1499     // depending on the settings, create the default project content (add a worksheet, etc.)
1500     if (createInitialContent) {
1501         const auto newProject = (NewProject)group.readEntry(QStringLiteral("NewProject"), static_cast<int>(NewProject::WithWorksheet));
1502         switch (newProject) {
1503         case NewProject::WithWorksheet:
1504             newWorksheet();
1505             break;
1506         case NewProject::WithSpreadsheet:
1507             newSpreadsheet();
1508             break;
1509         case NewProject::WithWorksheetSpreadsheet:
1510             newWorksheet();
1511             newSpreadsheet();
1512             break;
1513         case NewProject::WithNotebook: {
1514 #ifdef HAVE_CANTOR_LIBS
1515             const auto& backend = group.readEntry(QLatin1String("LoadOnStartNotebook"), QString());
1516             if (Cantor::Backend::listAvailableBackends().indexOf(backend) != -1)
1517                 addAspectToProject(new CantorWorksheet(backend));
1518 #endif
1519             break;
1520         }
1521         }
1522 
1523         m_project->setChanged(false); // the project was initialized on startup, nothing has changed from user's perspective
1524     }
1525 
1526     m_undoViewEmptyLabel = i18n("%1: created", m_project->name());
1527     updateGUIOnProjectChanges();
1528 
1529     return true;
1530 }
1531 
1532 void MainWin::openProject() {
1533     bool supportOthers = false;
1534     QString allExtensions = Project::supportedExtensions();
1535     QString extensions = i18n("LabPlot Projects (%1)", allExtensions);
1536     if (m_lastOpenFileFilter.isEmpty())
1537         m_lastOpenFileFilter = extensions;
1538 
1539 #ifdef HAVE_LIBORIGIN
1540     extensions += QLatin1String(";;") + i18n("Origin Projects (%1)", OriginProjectParser::supportedExtensions());
1541     allExtensions += QLatin1String(" ") + OriginProjectParser::supportedExtensions();
1542     supportOthers = true;
1543 #endif
1544 
1545 #ifdef HAVE_CANTOR_LIBS
1546     extensions += QLatin1String(";;") + i18n("Cantor Projects (*.cws)");
1547     extensions += QLatin1String(";;") + i18n("Jupyter Notebooks (*.ipynb)");
1548     allExtensions += QLatin1String(" *.cws *.ipynb");
1549     supportOthers = true;
1550 #endif
1551 
1552     // add an entry for "All supported files" if we support more than labplot
1553     if (supportOthers)
1554         extensions = i18n("All supported files (%1)", allExtensions) + QLatin1String(";;") + extensions;
1555 
1556     KConfigGroup group = Settings::group(QStringLiteral("MainWin"));
1557     const QString& dir = group.readEntry("LastOpenDir", "");
1558     const QString& path = QFileDialog::getOpenFileName(this, i18nc("@title:window", "Open Project"), dir, extensions, &m_lastOpenFileFilter);
1559     if (path.isEmpty()) // "Cancel" was clicked
1560         return;
1561 
1562     this->openProject(path);
1563 
1564     // save new "last open directory"
1565     int pos = path.lastIndexOf(QLatin1String("/"));
1566     if (pos != -1) {
1567         const QString& newDir = path.left(pos);
1568         if (newDir != dir)
1569             group.writeEntry("LastOpenDir", newDir);
1570     }
1571 }
1572 
1573 void MainWin::openProject(const QString& filename) {
1574     if (m_project && filename == m_project->fileName()) {
1575         KMessageBox::information(this, i18n("The project file %1 is already opened.", filename), i18n("Open Project"));
1576         return;
1577     }
1578 
1579     // check whether the file can be opened for reading at all before closing the current project
1580     // and creating a new project and trying to load
1581     QFile file(filename);
1582     if (!file.exists()) {
1583         KMessageBox::error(this, i18n("The project file %1 doesn't exist.", filename), i18n("Open Project"));
1584         return;
1585     }
1586 
1587     if (!file.open(QIODevice::ReadOnly)) {
1588         KMessageBox::error(this, i18n("Couldn't read the project file %1.", filename), i18n("Open Project"));
1589         return;
1590     } else
1591         file.close();
1592 
1593     if (!newProject(false))
1594         return;
1595 
1596     WAIT_CURSOR;
1597     statusBar()->showMessage(i18n("Loading %1...", filename));
1598     QApplication::processEvents(QEventLoop::AllEvents, 0);
1599     m_project->setFileName(filename);
1600     QElapsedTimer timer;
1601     timer.start();
1602     bool rc = false;
1603     if (Project::isLabPlotProject(filename)) {
1604         rc = m_project->load(filename);
1605     }
1606 #ifdef HAVE_LIBORIGIN
1607     else if (OriginProjectParser::isOriginProject(filename)) {
1608         OriginProjectParser parser;
1609         parser.setProjectFileName(filename);
1610         parser.importTo(m_project, QStringList()); // TODO: add return code
1611         rc = true;
1612     }
1613 #endif
1614 
1615 #ifdef HAVE_CANTOR_LIBS
1616     else if (QFileInfo(filename).completeSuffix() == QLatin1String("cws")) {
1617         QFile file(filename);
1618         KZip archive(&file);
1619         rc = archive.open(QIODevice::ReadOnly);
1620         if (rc) {
1621             const auto* contentEntry = archive.directory()->entry(QLatin1String("content.xml"));
1622             if (contentEntry && contentEntry->isFile()) {
1623                 const auto* contentFile = static_cast<const KArchiveFile*>(contentEntry);
1624                 QByteArray data = contentFile->data();
1625                 archive.close();
1626 
1627                 // determine the name of the backend
1628                 QDomDocument doc;
1629                 doc.setContent(data);
1630                 QString backendName = doc.documentElement().attribute(QLatin1String("backend"));
1631 
1632                 if (!backendName.isEmpty()) {
1633                     // create new Cantor worksheet and load the data
1634                     auto* worksheet = new CantorWorksheet(backendName);
1635                     worksheet->setName(QFileInfo(filename).fileName());
1636                     worksheet->setComment(filename);
1637 
1638                     rc = file.open(QIODevice::ReadOnly);
1639                     if (rc) {
1640                         QByteArray content = file.readAll();
1641                         rc = worksheet->init(&content);
1642                         if (rc)
1643                             m_project->addChild(worksheet);
1644                         else {
1645                             delete worksheet;
1646                             RESET_CURSOR;
1647                             QMessageBox::critical(this, i18n("Failed to open project"), i18n("Failed to process the content of the file '%1'.", filename));
1648                         }
1649                     } else {
1650                         RESET_CURSOR;
1651                         QMessageBox::critical(this, i18n("Failed to open project"), i18n("Failed to open the file '%1'.", filename));
1652                     }
1653                 } else {
1654                     RESET_CURSOR;
1655                     rc = false;
1656                     QMessageBox::critical(this, i18n("Failed to open project"), i18n("Failed to process the content of the file '%1'.", filename));
1657                 }
1658             } else {
1659                 RESET_CURSOR;
1660                 rc = false;
1661                 QMessageBox::critical(this, i18n("Failed to open project"), i18n("Failed to process the content of the file '%1'.", filename));
1662             }
1663         } else {
1664             RESET_CURSOR;
1665             QMessageBox::critical(this, i18n("Failed to open project"), i18n("Failed to open the file '%1'.", filename));
1666         }
1667     } else if (QFileInfo(filename).completeSuffix() == QLatin1String("ipynb")) {
1668         QFile file(filename);
1669         rc = file.open(QIODevice::ReadOnly);
1670         if (rc) {
1671             QByteArray content = file.readAll();
1672             QJsonParseError error;
1673             // TODO: use QJsonDocument& doc = QJsonDocument::fromJson(content, &error); if minimum Qt version is at least 5.10
1674             const QJsonDocument& jsonDoc = QJsonDocument::fromJson(content, &error);
1675             const QJsonObject& doc = jsonDoc.object();
1676             if (error.error == QJsonParseError::NoError) {
1677                 // determine the backend name
1678                 QString backendName;
1679                 // TODO: use doc["metadata"]["kernelspec"], etc. if minimum Qt version is at least 5.10
1680                 if ((doc[QLatin1String("metadata")] != QJsonValue::Undefined && doc[QLatin1String("metadata")].isObject())
1681                     && (doc[QLatin1String("metadata")].toObject()[QLatin1String("kernelspec")] != QJsonValue::Undefined
1682                         && doc[QLatin1String("metadata")].toObject()[QLatin1String("kernelspec")].isObject())) {
1683                     QString kernel;
1684                     if (doc[QLatin1String("metadata")].toObject()[QLatin1String("kernelspec")].toObject()[QLatin1String("name")] != QJsonValue::Undefined)
1685                         kernel = doc[QLatin1String("metadata")].toObject()[QLatin1String("kernelspec")].toObject()[QLatin1String("name")].toString();
1686 
1687                     if (!kernel.isEmpty()) {
1688                         if (kernel.startsWith(QLatin1String("julia")))
1689                             backendName = QLatin1String("julia");
1690                         else if (kernel == QLatin1String("sagemath"))
1691                             backendName = QLatin1String("sage");
1692                         else if (kernel == QLatin1String("ir"))
1693                             backendName = QLatin1String("r");
1694                         else if (kernel == QLatin1String("python3") || kernel == QLatin1String("python2"))
1695                             backendName = QLatin1String("python");
1696                         else
1697                             backendName = kernel;
1698                     } else
1699                         backendName = doc[QLatin1String("metadata")].toObject()[QLatin1String("kernelspec")].toObject()[QLatin1String("language")].toString();
1700 
1701                     if (!backendName.isEmpty()) {
1702                         // create new Cantor worksheet and load the data
1703                         auto* worksheet = new CantorWorksheet(backendName);
1704                         worksheet->setName(QFileInfo(filename).fileName());
1705                         worksheet->setComment(filename);
1706                         rc = worksheet->init(&content);
1707                         if (rc)
1708                             m_project->addChild(worksheet);
1709                         else {
1710                             delete worksheet;
1711                             RESET_CURSOR;
1712                             QMessageBox::critical(this, i18n("Failed to open project"), i18n("Failed to process the content of the file '%1'.", filename));
1713                         }
1714                     } else {
1715                         RESET_CURSOR;
1716                         rc = false;
1717                         QMessageBox::critical(this, i18n("Failed to open project"), i18n("Failed to process the content of the file '%1'.", filename));
1718                     }
1719                 } else {
1720                     RESET_CURSOR;
1721                     rc = false;
1722                     QMessageBox::critical(this, i18n("Failed to open project"), i18n("Failed to process the content of the file '%1'.", filename));
1723                 }
1724             }
1725         } else {
1726             RESET_CURSOR;
1727             rc = false;
1728             QMessageBox::critical(this, i18n("Failed to open project"), i18n("Failed to open the file '%1'.", filename));
1729         }
1730     }
1731 #endif
1732 
1733     m_project->setChanged(false);
1734 
1735     if (!rc) {
1736         closeProject();
1737         RESET_CURSOR;
1738         return;
1739     }
1740 
1741     m_project->undoStack()->clear();
1742     m_undoViewEmptyLabel = i18n("%1: opened", m_project->name());
1743     m_recentProjectsAction->addUrl(QUrl(filename));
1744     updateGUIOnProjectChanges(m_project->windowState().toUtf8());
1745     updateGUI(); // there are most probably worksheets or spreadsheets in the open project -> update the GUI
1746     if (m_project->windowState().toUtf8().isEmpty())
1747         updateDockWindowVisibility();
1748     m_saveAction->setEnabled(false);
1749     m_newProjectAction->setEnabled(true);
1750 #ifdef HAVE_PURPOSE
1751     m_shareAction->setEnabled(true);
1752     fillShareMenu();
1753 #endif
1754     statusBar()->showMessage(i18n("Project successfully opened (in %1 seconds).", (float)timer.elapsed() / 1000));
1755 
1756     KConfigGroup group = Settings::group(QStringLiteral("MainWin"));
1757     group.writeEntry("LastOpenProject", filename);
1758 
1759     if (m_autoSaveActive)
1760         m_autoSaveTimer.start();
1761 
1762     RESET_CURSOR;
1763 }
1764 
1765 void MainWin::openRecentProject(const QUrl& url) {
1766     //  if (dynamic_cast<QQuickWidget*>(centralWidget())) {
1767     //      createMdiArea();
1768     //      setCentralWidget(m_mdiArea);
1769     //  }
1770 
1771     if (url.isLocalFile()) // fix for Windows
1772         this->openProject(url.toLocalFile());
1773     else
1774         this->openProject(url.path());
1775 }
1776 
1777 /*!
1778     Closes the current project, if available. Return \c true, if the project was closed.
1779 */
1780 bool MainWin::closeProject() {
1781     if (m_project == nullptr)
1782         return true; // nothing to close
1783 
1784     if (warnModified())
1785         return false;
1786 
1787     // clear the worksheet preview before deleting the project and before deleting the dock widgets
1788     // so we don't need to react on current aspect changes
1789     if (m_worksheetPreviewWidget)
1790         m_worksheetPreviewWidget->setProject(nullptr);
1791 
1792     if (!m_closing) {
1793         //      if (dynamic_cast<QQuickWidget*>(centralWidget()) && m_showWelcomeScreen) {
1794         //          m_welcomeWidget = createWelcomeScreen();
1795         //          setCentralWidget(m_welcomeWidget);
1796         //      }
1797     }
1798 
1799     for (auto dock : m_DockManager->dockWidgetsMap()) {
1800         // No need to delete them, because they are used everywhere and can be reused
1801         if (specialDock(dock))
1802             continue;
1803         m_DockManager->removeDockWidget(dock);
1804     }
1805 
1806     m_projectClosing = true;
1807     statusBar()->clearMessage();
1808     delete m_guiObserver;
1809     m_guiObserver = nullptr;
1810     delete m_aspectTreeModel;
1811     m_aspectTreeModel = nullptr;
1812     delete m_project;
1813     m_project = nullptr;
1814     m_projectClosing = false;
1815 
1816     // update the UI if we're just closing a project
1817     // and not closing(quitting) the application
1818     if (!m_closing) {
1819         m_projectExplorerDock->toggleView(false);
1820         m_propertiesDock->toggleView(false);
1821         m_worksheetPreviewDock->toggleView(false);
1822         m_currentAspect = nullptr;
1823         m_currentFolder = nullptr;
1824         updateGUIOnProjectChanges();
1825         m_newProjectAction->setEnabled(true);
1826 
1827         if (m_autoSaveActive)
1828             m_autoSaveTimer.stop();
1829     }
1830 
1831     if (cursorDock) {
1832         delete cursorDock;
1833         cursorDock = nullptr;
1834         cursorWidget = nullptr; // is deleted, because it's the child of cursorDock
1835     }
1836 
1837     return true;
1838 }
1839 
1840 bool MainWin::saveProject() {
1841     QString fileName = m_project->fileName();
1842     if (fileName.isEmpty())
1843         return saveProjectAs();
1844     else {
1845         // don't overwrite OPJ files
1846         if (fileName.endsWith(QLatin1String(".opj"), Qt::CaseInsensitive))
1847             fileName.replace(QLatin1String(".opj"), QLatin1String(".lml"));
1848         return save(fileName);
1849     }
1850 }
1851 
1852 bool MainWin::saveProjectAs() {
1853     KConfigGroup conf = Settings::group(QStringLiteral("MainWin"));
1854     const QString& dir = conf.readEntry("LastOpenDir", "");
1855     QString path = QFileDialog::getSaveFileName(this,
1856                                                 i18nc("@title:window", "Save Project As"),
1857                                                 dir + m_project->fileName(),
1858                                                 i18n("LabPlot Projects (*.lml *.lml.gz *.lml.bz2 *.lml.xz *.LML *.LML.GZ *.LML.BZ2 *.LML.XZ)"));
1859     // The "Automatically select filename extension (.lml)" option does not change anything
1860 
1861     if (path.isEmpty()) // "Cancel" was clicked
1862         return false;
1863 
1864     if (!path.endsWith(QLatin1String(".lml"), Qt::CaseInsensitive))
1865         path.append(QLatin1String(".lml"));
1866 
1867     // save new "last open directory"
1868     int pos = path.lastIndexOf(QLatin1String("/"));
1869     if (pos != -1) {
1870         const QString& newDir = path.left(pos);
1871         if (newDir != dir)
1872             conf.writeEntry("LastOpenDir", newDir);
1873     }
1874 
1875     return save(path);
1876 }
1877 
1878 /*!
1879  * auxiliary function that does the actual saving of the project
1880  */
1881 bool MainWin::save(const QString& fileName) {
1882     QTemporaryFile tempFile(QDir::tempPath() + QLatin1Char('/') + QLatin1String("labplot_save_XXXXXX"));
1883     if (!tempFile.open()) {
1884         KMessageBox::error(this, i18n("Couldn't open the temporary file for writing."));
1885         return false;
1886     }
1887 
1888     WAIT_CURSOR;
1889     const QString& tempFileName = tempFile.fileName();
1890     DEBUG("Using temporary file " << STDSTRING(tempFileName))
1891     tempFile.close();
1892 
1893     QIODevice* file;
1894     // if file ending is .lml, do xz compression or gzip compression in compatibility mode
1895     const KConfigGroup group = Settings::group(QStringLiteral("Settings_General"));
1896     if (fileName.endsWith(QLatin1String(".lml"))) {
1897         if (group.readEntry("CompatibleSave", false))
1898             file = new KCompressionDevice(tempFileName, KCompressionDevice::GZip);
1899         else
1900             file = new KCompressionDevice(tempFileName, KCompressionDevice::Xz);
1901     } else // use file ending to find out how to compress file
1902         file = new KCompressionDevice(tempFileName);
1903     if (!file)
1904         file = new QFile(tempFileName);
1905 
1906     bool ok;
1907     if (file->open(QIODevice::WriteOnly)) {
1908         m_project->setFileName(fileName);
1909 
1910         QPixmap thumbnail;
1911         const auto& windows = m_DockManager->dockWidgetsMap();
1912         if (!windows.isEmpty()) {
1913             // determine the bounding rectangle surrounding all visible sub-windows
1914             QRect rect;
1915             for (auto* window : windows)
1916                 rect = rect.united(window->frameGeometry());
1917 
1918             thumbnail = centralWidget()->grab(rect);
1919         }
1920 
1921         QXmlStreamWriter writer(file);
1922         auto windowState = m_DockManager->saveState();
1923         // This conversion is fine, because in the dockmanager xml compression is turned off
1924         m_project->setWindowState(QString::fromStdString(windowState.data()));
1925         m_project->setFileName(fileName);
1926         m_project->save(thumbnail, &writer);
1927         m_project->setChanged(false);
1928         undoStackIndexLastSave = m_project->undoStack()->index();
1929         file->close();
1930 
1931         // target file must not exist
1932         if (QFile::exists(fileName))
1933             QFile::remove(fileName);
1934 
1935         // do not rename temp file. Qt still holds a handle (which fails renaming on Windows) and deletes it
1936         bool rc = QFile::copy(tempFileName, fileName);
1937         if (rc) {
1938             updateTitleBar();
1939             statusBar()->showMessage(i18n("Project saved"));
1940             m_saveAction->setEnabled(false);
1941             m_recentProjectsAction->addUrl(QUrl(fileName));
1942             ok = true;
1943 
1944             // if the project dock is visible, refresh the shown content
1945             //(version and modification time might have been changed)
1946             if (stackedWidget->currentWidget() == m_guiObserver->m_projectDock)
1947                 m_guiObserver->m_projectDock->setProject(m_project);
1948 
1949             // we have a file name now
1950             //  -> auto save can be activated now if not happened yet
1951             if (m_autoSaveActive && !m_autoSaveTimer.isActive())
1952                 m_autoSaveTimer.start();
1953         } else {
1954             RESET_CURSOR;
1955             KMessageBox::error(this, i18n("Couldn't save the file '%1'.", fileName));
1956             ok = false;
1957         }
1958     } else {
1959         RESET_CURSOR;
1960         KMessageBox::error(this, i18n("Couldn't open the file '%1' for writing.", fileName));
1961         ok = false;
1962     }
1963 
1964     delete file;
1965 
1966 #ifdef HAVE_PURPOSE
1967     m_shareAction->setEnabled(true); // sharing is possible after the project was saved to a file
1968     fillShareMenu();
1969 #endif
1970 
1971     RESET_CURSOR;
1972     return ok;
1973 }
1974 
1975 /*!
1976  * automatically saves the project in the specified time interval.
1977  */
1978 void MainWin::autoSaveProject() {
1979     // don't auto save when there are no changes or the file name
1980     // was not provided yet (the project was never explicitly saved yet).
1981     if (!m_project->hasChanged() || m_project->fileName().isEmpty())
1982         return;
1983 
1984     this->saveProject();
1985 }
1986 
1987 void MainWin::updateTitleBar() {
1988     QString title;
1989     if (m_project) {
1990         switch (m_titleBarMode) {
1991         case TitleBarMode::ShowProjectName:
1992             title = m_project->name();
1993             break;
1994         case TitleBarMode::ShowFileName:
1995             if (m_project->fileName().isEmpty())
1996                 title = m_project->name();
1997             else {
1998                 QFileInfo fi(m_project->fileName());
1999                 title = fi.baseName();
2000             }
2001             break;
2002         case TitleBarMode::ShowFilePath:
2003             if (m_project->fileName().isEmpty())
2004                 title = m_project->name();
2005             else
2006                 title = m_project->fileName();
2007         }
2008 
2009         if (m_project->hasChanged())
2010             title += QLatin1String("    [") + i18n("Changed") + QLatin1Char(']');
2011     } else
2012         title = QLatin1String("LabPlot");
2013 
2014     setCaption(title);
2015 }
2016 
2017 /*!
2018     prints the current sheet (worksheet, spreadsheet or matrix)
2019 */
2020 void MainWin::print() {
2021     if (!m_currentAspectDock)
2022         return;
2023 
2024     AbstractPart* part = static_cast<ContentDockWidget*>(m_currentAspectDock)->part();
2025     statusBar()->showMessage(i18n("Preparing printing of %1", part->name()));
2026     if (part->printView())
2027         statusBar()->showMessage(i18n("%1 printed", part->name()));
2028     else
2029         statusBar()->clearMessage();
2030 }
2031 
2032 void MainWin::printPreview() {
2033     if (!m_currentAspectDock)
2034         return;
2035 
2036     AbstractPart* part = static_cast<ContentDockWidget*>(m_currentAspectDock)->part();
2037     statusBar()->showMessage(i18n("Preparing printing of %1", part->name()));
2038     if (part->printPreview())
2039         statusBar()->showMessage(i18n("%1 printed", part->name()));
2040     else
2041         statusBar()->clearMessage();
2042 }
2043 
2044 /**************************************************************************************/
2045 
2046 /*!
2047     adds a new Folder to the project.
2048 */
2049 void MainWin::newFolder() {
2050     Folder* folder = new Folder(i18n("Folder"));
2051     this->addAspectToProject(folder);
2052 }
2053 
2054 /*!
2055     adds a new Workbook to the project.
2056 */
2057 void MainWin::newWorkbook() {
2058     auto* workbook = new Workbook(i18n("Workbook"));
2059     this->addAspectToProject(workbook);
2060 }
2061 
2062 /*!
2063     adds a new Datapicker to the project.
2064 */
2065 void MainWin::newDatapicker() {
2066     auto* datapicker = new Datapicker(i18n("Data Extractor"));
2067     this->addAspectToProject(datapicker);
2068 }
2069 
2070 /*!
2071     adds a new Spreadsheet to the project.
2072 */
2073 void MainWin::newSpreadsheet() {
2074     auto* spreadsheet = new Spreadsheet(i18n("Spreadsheet"));
2075 
2076     // if the current active window is a workbook or one of its children,
2077     // add the new matrix to the workbook
2078     auto* workbook = dynamic_cast<Workbook*>(m_currentAspect);
2079     if (!workbook)
2080         workbook = static_cast<Workbook*>(m_currentAspect->parent(AspectType::Workbook));
2081 
2082     if (workbook)
2083         workbook->addChild(spreadsheet);
2084     else
2085         this->addAspectToProject(spreadsheet);
2086 }
2087 
2088 /*!
2089     adds a new Matrix to the project.
2090 */
2091 void MainWin::newMatrix() {
2092     Matrix* matrix = new Matrix(i18n("Matrix"));
2093 
2094     // if the current active window is a workbook or one of its children,
2095     // add the new matrix to the workbook
2096     auto* workbook = dynamic_cast<Workbook*>(m_currentAspect);
2097     if (!workbook)
2098         workbook = static_cast<Workbook*>(m_currentAspect->parent(AspectType::Workbook));
2099 
2100     if (workbook)
2101         workbook->addChild(matrix);
2102     else
2103         this->addAspectToProject(matrix);
2104 }
2105 
2106 /*!
2107     adds a new Worksheet to the project.
2108 */
2109 void MainWin::newWorksheet() {
2110     auto* worksheet = new Worksheet(i18n("Worksheet"));
2111     this->addAspectToProject(worksheet);
2112 }
2113 
2114 /*!
2115     adds a new Note to the project.
2116 */
2117 void MainWin::newNotes() {
2118     Note* notes = new Note(i18n("Note"));
2119     this->addAspectToProject(notes);
2120 }
2121 
2122 /*!
2123     returns a pointer to a \c Spreadsheet object, if the currently active Mdi-Subwindow
2124     or if the currently selected tab in a \c WorkbookView is a \c SpreadsheetView
2125     Otherwise returns \c 0.
2126 */
2127 Spreadsheet* MainWin::activeSpreadsheet() const {
2128     //  if (dynamic_cast<QQuickWidget*>(centralWidget()))
2129     //      return nullptr;
2130 
2131     if (!m_currentAspect)
2132         return nullptr;
2133 
2134     Spreadsheet* spreadsheet = nullptr;
2135     if (m_currentAspect->type() == AspectType::Spreadsheet)
2136         spreadsheet = dynamic_cast<Spreadsheet*>(m_currentAspect);
2137     else {
2138         // check whether one of spreadsheet columns is selected and determine the spreadsheet
2139         auto* parent = m_currentAspect->parentAspect();
2140         if (parent && parent->type() == AspectType::Spreadsheet)
2141             spreadsheet = dynamic_cast<Spreadsheet*>(parent);
2142     }
2143 
2144     return spreadsheet;
2145 }
2146 
2147 #ifdef HAVE_CANTOR_LIBS
2148 /*
2149     adds a new Cantor Spreadsheet to the project.
2150 */
2151 void MainWin::newCantorWorksheet() {
2152     auto* action = static_cast<QAction*>(QObject::sender());
2153     auto* cantorworksheet = new CantorWorksheet(action->data().toString());
2154     this->addAspectToProject(cantorworksheet);
2155 }
2156 
2157 /********************************************************************************/
2158 #endif
2159 
2160 /*!
2161     called if there were changes in the project.
2162     Adds "changed" to the window caption and activates the save-Action.
2163 */
2164 void MainWin::projectChanged() {
2165     updateTitleBar();
2166     m_newProjectAction->setEnabled(true);
2167     m_saveAction->setEnabled(true);
2168     m_undoAction->setEnabled(true);
2169 }
2170 
2171 void MainWin::handleAspectAdded(const AbstractAspect* aspect) {
2172     // register the signal-slot connections for aspects having a view.
2173     // if a folder or a workbook is being added, loop recursively through their children
2174     // and register the connections.
2175     const auto* part = dynamic_cast<const AbstractPart*>(aspect);
2176     if (part) {
2177         //      connect(part, &AbstractPart::importFromFileRequested, this, &MainWin::importFileDialog);
2178         connect(part, &AbstractPart::importFromFileRequested, this, [=]() {
2179             importFileDialog();
2180         });
2181         connect(part, &AbstractPart::importFromSQLDatabaseRequested, this, &MainWin::importSqlDialog);
2182         // TODO: export, print and print preview should be handled in the views and not in MainWin.
2183         connect(part, &AbstractPart::exportRequested, this, &MainWin::exportDialog);
2184         connect(part, &AbstractPart::printRequested, this, &MainWin::print);
2185         connect(part, &AbstractPart::printPreviewRequested, this, &MainWin::printPreview);
2186         connect(part, &AbstractPart::showRequested, this, &MainWin::handleShowSubWindowRequested);
2187 
2188         const auto* worksheet = dynamic_cast<const Worksheet*>(aspect);
2189         if (worksheet) {
2190             connect(worksheet, &Worksheet::cartesianPlotMouseModeChanged, this, &MainWin::cartesianPlotMouseModeChanged);
2191             connect(worksheet, &Worksheet::propertiesExplorerRequested, this, &MainWin::propertiesExplorerRequested);
2192         } else if (aspect->type() == AspectType::Workbook) {
2193             for (auto* child : aspect->children<AbstractAspect>())
2194                 handleAspectAdded(child);
2195         }
2196     } else if (aspect->type() == AspectType::Folder)
2197         for (auto* child : aspect->children<AbstractAspect>())
2198             handleAspectAdded(child);
2199 }
2200 
2201 void MainWin::handleAspectRemoved(const AbstractAspect* parent, const AbstractAspect* /*before*/, const AbstractAspect* aspect) {
2202     // no need to react on removal of
2203     //  - AbstractSimpleFilter
2204     //  - columns in the data spreadsheet of a datapicker curve,
2205     //    this can only happen when changing the error type and is done on the level of DatapickerImage
2206     if (!aspect->inherits(AspectType::AbstractFilter) && !(parent->parentAspect() && parent->parentAspect()->type() == AspectType::DatapickerCurve))
2207         m_projectExplorer->setCurrentAspect(parent);
2208 }
2209 
2210 void MainWin::handleAspectAboutToBeRemoved(const AbstractAspect* aspect) {
2211     const auto* part = dynamic_cast<const AbstractPart*>(aspect);
2212     if (!part)
2213         return;
2214 
2215     const auto* workbook = dynamic_cast<const Workbook*>(aspect->parentAspect());
2216     auto* datapicker = dynamic_cast<const Datapicker*>(aspect->parentAspect());
2217     if (!datapicker)
2218         datapicker = dynamic_cast<const Datapicker*>(aspect->parentAspect()->parentAspect());
2219 
2220     if (!workbook && !datapicker && part->dockWidgetExists()) {
2221         ContentDockWidget* win = part->dockWidget();
2222         if (win)
2223             m_DockManager->removeDockWidget(win);
2224     }
2225 }
2226 
2227 /*!
2228     called when the current aspect in the tree of the project explorer was changed.
2229     Selects the new aspect.
2230 */
2231 void MainWin::handleCurrentAspectChanged(AbstractAspect* aspect) {
2232     DEBUG(Q_FUNC_INFO)
2233     if (!aspect)
2234         aspect = m_project; // should never happen, just in case
2235 
2236     m_suppressCurrentSubWindowChangedEvent = true;
2237     if (aspect->folder() != m_currentFolder) {
2238         m_currentFolder = aspect->folder();
2239         updateDockWindowVisibility();
2240     }
2241 
2242     m_currentAspect = aspect;
2243 
2244     // activate the corresponding MDI sub window for the current aspect
2245     activateSubWindowForAspect(aspect);
2246     m_suppressCurrentSubWindowChangedEvent = false;
2247 
2248     updateGUI();
2249 }
2250 
2251 void MainWin::activateSubWindowForAspect(const AbstractAspect* aspect) {
2252     Q_ASSERT(aspect);
2253     const auto* part = dynamic_cast<const AbstractPart*>(aspect);
2254     if (part) {
2255         ContentDockWidget* win{nullptr};
2256 
2257         // for aspects being children of a Workbook, we show workbook's window, otherwise the window of the selected part
2258         const auto* workbook = dynamic_cast<const Workbook*>(aspect->parentAspect());
2259         auto* datapicker = dynamic_cast<const Datapicker*>(aspect->parentAspect());
2260         if (!datapicker)
2261             datapicker = dynamic_cast<const Datapicker*>(aspect->parentAspect()->parentAspect());
2262 
2263         if (workbook)
2264             win = workbook->dockWidget();
2265         else if (datapicker)
2266             win = datapicker->dockWidget();
2267         else
2268             win = part->dockWidget();
2269 
2270         auto* dock = m_DockManager->findDockWidget(win->objectName());
2271         if (m_DockManager && dock == nullptr) {
2272             // Add new dock if not found
2273             if (m_DockManager->dockWidgetsMap().count() == 2 || !m_currentAspectDock) {
2274                 // If only project explorer and properties dock exist place it right to the project explorer
2275                 m_DockManager->addDockWidget(ads::RightDockWidgetArea,
2276                                              win,
2277                                              m_projectExplorerDock->dockAreaWidget()); // Right of the project explorer by default
2278             } else {
2279                 // Add dock on top of the current aspect, so it is directly visible
2280                 m_DockManager->addDockWidget(ads::CenterDockWidgetArea, win, m_currentAspectDock->dockAreaWidget());
2281             }
2282             win->show();
2283 
2284             // Qt provides its own "system menu" for every sub-window. The shortcut for the close-action
2285             // in this menu collides with our global m_closeAction.
2286             // remove the shortcuts in the system menu to avoid this collision.
2287             for (QAction* action : win->titleBarActions())
2288                 action->setShortcut(QKeySequence());
2289         } else if (m_DockManager)
2290             dock->toggleView(true);
2291 
2292         m_currentAspectDock = win;
2293         if (m_DockManager)
2294             m_DockManager->setDockWidgetFocused(win);
2295     } else {
2296         // activate the mdiView of the parent, if a child was selected
2297         const AbstractAspect* parent = aspect->parentAspect();
2298         if (parent) {
2299             activateSubWindowForAspect(parent);
2300 
2301             // if the parent's parent is a Workbook (a column of a spreadsheet in workbook was selected),
2302             // we need to select the corresponding tab in WorkbookView too
2303             if (parent->parentAspect()) {
2304                 auto* workbook = dynamic_cast<Workbook*>(parent->parentAspect());
2305                 auto* datapicker = dynamic_cast<Datapicker*>(parent->parentAspect());
2306                 if (!datapicker)
2307                     datapicker = dynamic_cast<Datapicker*>(parent->parentAspect()->parentAspect());
2308 
2309                 if (workbook)
2310                     workbook->childSelected(parent);
2311                 else if (datapicker)
2312                     datapicker->childSelected(parent);
2313             }
2314         }
2315     }
2316     return;
2317 }
2318 
2319 void MainWin::setDockVisibility(QAction* action) {
2320     m_project->setDockVisibility((Project::DockVisibility)(action->data().toInt()));
2321 }
2322 
2323 /*!
2324     shows the sub window of a worksheet, matrix or a spreadsheet.
2325     Used if the window was closed before and the user asks to show
2326     the window again via the context menu in the project explorer.
2327 */
2328 void MainWin::handleShowSubWindowRequested() {
2329     if (m_currentAspect)
2330         activateSubWindowForAspect(m_currentAspect);
2331 }
2332 
2333 /*!
2334     this is called on a right click on the root folder in the project explorer
2335 */
2336 void MainWin::createContextMenu(QMenu* menu) const {
2337     QAction* firstAction = nullptr;
2338     // if we're populating the context menu for the project explorer, then
2339     // there're already actions available there. Skip the first title-action
2340     // and insert the action at the beginning of the menu.
2341     if (menu->actions().size() > 1)
2342         firstAction = menu->actions().at(1);
2343 
2344     menu->insertMenu(firstAction, m_newMenu);
2345 
2346     menu->insertSeparator(firstAction);
2347     menu->insertMenu(firstAction, m_visibilityMenu);
2348     menu->insertSeparator(firstAction);
2349 }
2350 
2351 /*!
2352     this is called on a right click on a non-root folder in the project explorer
2353 */
2354 void MainWin::createFolderContextMenu(const Folder*, QMenu* menu) const {
2355     // Folder provides it's own context menu. Add a separator before adding additional actions.
2356     menu->addSeparator();
2357     this->createContextMenu(menu);
2358 }
2359 
2360 void MainWin::undo() {
2361     WAIT_CURSOR;
2362     m_project->undoStack()->undo();
2363     m_redoAction->setEnabled(true);
2364 
2365     const int index = m_project->undoStack()->index();
2366     if (index == 0)
2367         m_undoAction->setEnabled(false);
2368 
2369     const bool changed = (index != undoStackIndexLastSave);
2370     m_saveAction->setEnabled(changed);
2371     m_project->setChanged(changed);
2372     updateTitleBar();
2373     RESET_CURSOR;
2374 }
2375 
2376 void MainWin::redo() {
2377     WAIT_CURSOR;
2378     m_project->undoStack()->redo();
2379     m_undoAction->setEnabled(true);
2380 
2381     const int index = m_project->undoStack()->index();
2382     if (index == m_project->undoStack()->count())
2383         m_redoAction->setEnabled(false);
2384 
2385     const bool changed = (index != undoStackIndexLastSave);
2386     m_saveAction->setEnabled(changed);
2387     m_project->setChanged(changed);
2388     updateTitleBar();
2389     RESET_CURSOR;
2390 }
2391 
2392 /*!
2393     Shows/hides docks depending on the current visibility policy.
2394 */
2395 void MainWin::updateDockWindowVisibility() const {
2396     auto windows = m_DockManager->dockWidgetsMap();
2397     switch (m_project->dockVisibility()) {
2398     case Project::DockVisibility::allDocks:
2399         for (auto* window : windows)
2400             window->toggleView(true);
2401 
2402         break;
2403     case Project::DockVisibility::folderOnly:
2404         for (auto* window : windows) {
2405             auto* view = dynamic_cast<ContentDockWidget*>(window);
2406             if (view) {
2407                 bool visible = view->part()->folder() == m_currentFolder;
2408                 window->toggleView(visible);
2409             }
2410         }
2411         break;
2412     case Project::DockVisibility::folderAndSubfolders:
2413         for (auto* window : windows) {
2414             auto* view = dynamic_cast<ContentDockWidget*>(window);
2415             if (view) {
2416                 bool visible = view->part()->isDescendantOf(m_currentFolder);
2417                 window->toggleView(visible);
2418             }
2419         }
2420         break;
2421     }
2422 }
2423 
2424 void MainWin::toggleDockWidget(QAction* action) {
2425     if (action->objectName() == QLatin1String("toggle_project_explorer_dock")) {
2426         if (m_projectExplorerDock->isVisible())
2427             m_projectExplorerDock->toggleView(false);
2428         //          toggleHideWidget(m_projectExplorerDock, true);
2429         else
2430             m_projectExplorerDock->toggleView(true);
2431         //          toggleShowWidget(m_projectExplorerDock, true);
2432     } else if (action->objectName() == QLatin1String("toggle_properties_explorer_dock")) {
2433         if (m_propertiesDock->isVisible())
2434             m_propertiesDock->toggleView(false);
2435         //          toggleHideWidget(m_propertiesDock, false);
2436         else
2437             m_propertiesDock->toggleView(true);
2438         //          toggleShowWidget(m_propertiesDock, false);
2439     } else if (action->objectName() == QLatin1String("toggle_worksheet_preview_dock"))
2440         m_worksheetPreviewDock->toggleView(!m_worksheetPreviewDock->isVisible());
2441 }
2442 
2443 void MainWin::toggleStatusBar(bool checked) {
2444     statusBar()->setVisible(checked); // show/hide statusbar
2445     statusBar()->setEnabled(checked);
2446     // enabled/disable memory info menu with statusbar
2447     m_memoryInfoAction->setEnabled(checked);
2448 }
2449 
2450 void MainWin::toggleMemoryInfo() {
2451     DEBUG(Q_FUNC_INFO)
2452     if (m_memoryInfoWidget) {
2453         statusBar()->removeWidget(m_memoryInfoWidget);
2454         delete m_memoryInfoWidget;
2455         m_memoryInfoWidget = nullptr;
2456     } else {
2457         m_memoryInfoWidget = new MemoryWidget(statusBar());
2458         statusBar()->addPermanentWidget(m_memoryInfoWidget);
2459     }
2460 }
2461 
2462 void MainWin::toggleMenuBar(bool checked) {
2463     menuBar()->setVisible(checked);
2464 }
2465 
2466 void MainWin::propertiesExplorerRequested() {
2467     if (!m_propertiesDock->isVisible())
2468         m_propertiesDock->toggleView(true);
2469 }
2470 
2471 /*
2472 void MainWin::toggleHideWidget(QWidget* widget, bool hideToLeft)
2473 {
2474     auto* timeline = new QTimeLine(800, this);
2475     timeline->setEasingCurve(QEasingCurve::InOutQuad);
2476 
2477     connect(timeline, &QTimeLine::valueChanged, [=] {
2478         const qreal value = timeline->currentValue();
2479         const int widgetWidth = widget->width();
2480         const int widgetPosY = widget->pos().y();
2481 
2482         int moveX = 0;
2483         if (hideToLeft) {
2484             moveX = static_cast<int>(value * widgetWidth) - widgetWidth;
2485         }
2486         else {
2487             const int frameRight = this->frameGeometry().right();
2488             moveX = frameRight - static_cast<int>(value * widgetWidth);
2489         }
2490         widget->move(moveX, widgetPosY);
2491     });
2492     timeline->setDirection(QTimeLine::Backward);
2493     timeline->start();
2494 
2495     connect(timeline, &QTimeLine::finished, [widget] {widget->hide();});
2496     connect(timeline, &QTimeLine::finished, timeline, &QTimeLine::deleteLater);
2497 }
2498 
2499 void MainWin::toggleShowWidget(QWidget* widget, bool showToRight)
2500 {
2501     auto* timeline = new QTimeLine(800, this);
2502     timeline->setEasingCurve(QEasingCurve::InOutQuad);
2503 
2504     connect(timeline, &QTimeLine::valueChanged, [=]() {
2505         if (widget->isHidden()) {
2506             widget->show();
2507         }
2508         const qreal value = timeline->currentValue();
2509         const int widgetWidth = widget->width();
2510         const int widgetPosY = widget->pos().y();
2511         int moveX = 0;
2512 
2513         if (showToRight) {
2514             moveX = static_cast<int>(value * widgetWidth) - widgetWidth;
2515         }
2516         else {
2517             const int frameRight = this->frameGeometry().right();
2518             moveX = frameRight - static_cast<int>(value * widgetWidth);
2519         }
2520         widget->move(moveX, widgetPosY);
2521     });
2522 
2523     timeline->setDirection(QTimeLine::Forward);
2524     timeline->start();
2525 
2526     connect(timeline, &QTimeLine::finished, timeline, &QTimeLine::deleteLater);
2527 }
2528 */
2529 void MainWin::projectExplorerDockVisibilityChanged(bool visible) {
2530     m_projectExplorerDockAction->setChecked(visible);
2531 }
2532 
2533 void MainWin::propertiesDockVisibilityChanged(bool visible) {
2534     m_propertiesDockAction->setChecked(visible);
2535 }
2536 
2537 void MainWin::worksheetPreviewDockVisibilityChanged(bool visible) {
2538     m_worksheetPreviewAction->setChecked(visible);
2539 }
2540 
2541 void MainWin::cursorDockVisibilityChanged(bool visible) {
2542     // if the cursor dock was closed, switch to the "Select and Edit" mouse mode
2543     if (!visible) {
2544         //      auto* worksheet = activeWorksheet();
2545         // TODO:
2546     }
2547 }
2548 
2549 void MainWin::cartesianPlotMouseModeChanged(CartesianPlot::MouseMode mode) {
2550     if (mode != CartesianPlot::MouseMode::Cursor) {
2551         if (cursorDock)
2552             cursorDock->toggleView(false);
2553     } else {
2554         if (!cursorDock) {
2555             cursorDock = new ads::CDockWidget(i18n("Cursor"), this);
2556             cursorWidget = new CursorDock(cursorDock);
2557             cursorDock->setWidget(cursorWidget);
2558             connect(cursorDock, &ads::CDockWidget::viewToggled, this, &MainWin::cursorDockVisibilityChanged);
2559             m_DockManager->addDockWidget(ads::CenterDockWidgetArea, cursorDock, m_propertiesDock->dockAreaWidget());
2560         } else
2561             focusCursorDock();
2562 
2563         auto* worksheet = static_cast<Worksheet*>(QObject::sender());
2564         connect(cursorWidget, &CursorDock::cursorUsed, this, &MainWin::focusCursorDock);
2565         cursorWidget->setWorksheet(worksheet);
2566         cursorDock->toggleView(true);
2567     }
2568 }
2569 
2570 void MainWin::focusCursorDock() {
2571     if (cursorDock) {
2572         cursorDock->toggleView(true);
2573         m_DockManager->setDockWidgetFocused(cursorDock);
2574     }
2575 }
2576 
2577 #ifdef HAVE_PURPOSE
2578 void MainWin::fillShareMenu() {
2579     if (!m_shareMenu)
2580         return;
2581 
2582     m_shareMenu->clear(); // clear the menu, it will be refilled with the new file URL below
2583     QMimeType mime;
2584     m_shareMenu->model()->setInputData(
2585         QJsonObject{{QStringLiteral("mimeType"), mime.name()}, {QStringLiteral("urls"), QJsonArray{QUrl::fromLocalFile(m_project->fileName()).toString()}}});
2586     m_shareMenu->reload();
2587 }
2588 
2589 void MainWin::shareActionFinished(const QJsonObject& output, int error, const QString& message) {
2590     if (error)
2591         KMessageBox::error(this, i18n("There was a problem sharing the project: %1", message), i18n("Share"));
2592     else {
2593         const QString url = output[QStringLiteral("url")].toString();
2594         if (url.isEmpty())
2595             statusBar()->showMessage(i18n("Project shared successfully"));
2596         else
2597             KMessageBox::information(widget(),
2598                                      i18n("You can find the shared project at: <a href=\"%1\">%1</a>", url),
2599                                      i18n("Share"),
2600                                      QString(),
2601                                      KMessageBox::Notify | KMessageBox::AllowLink);
2602     }
2603 }
2604 #endif
2605 
2606 void MainWin::toggleFullScreen(bool t) {
2607     m_fullScreenAction->setFullScreen(this, t);
2608 }
2609 
2610 void MainWin::closeEvent(QCloseEvent* event) {
2611     m_closing = true;
2612     if (!this->closeProject()) {
2613         m_closing = false;
2614         event->ignore();
2615     }
2616 }
2617 
2618 void MainWin::dragEnterEvent(QDragEnterEvent* event) {
2619     event->accept();
2620 }
2621 
2622 void MainWin::dropEvent(QDropEvent* event) {
2623     if (event->mimeData() && !event->mimeData()->urls().isEmpty()) {
2624         QUrl url = event->mimeData()->urls().at(0);
2625         const QString& fileName = url.toLocalFile();
2626         if (Project::isSupportedProject(fileName))
2627             openProject(fileName);
2628         else {
2629             if (!m_project)
2630                 newProject();
2631             importFileDialog(fileName);
2632         }
2633 
2634         event->accept();
2635     } else
2636         event->ignore();
2637 }
2638 
2639 void MainWin::updateLocale() {
2640     // Set default locale
2641     QLocale::Language numberLocaleLanguage =
2642         static_cast<QLocale::Language>(Settings::group(QStringLiteral("Settings_General"))
2643                                            .readEntry(QLatin1String("DecimalSeparatorLocale"), static_cast<int>(QLocale::Language::AnyLanguage)));
2644     QLocale::NumberOptions numberOptions = static_cast<QLocale::NumberOptions>(
2645         Settings::group(QStringLiteral("Settings_General")).readEntry(QLatin1String("NumberOptions"), static_cast<int>(QLocale::DefaultNumberOptions)));
2646     QLocale l(numberLocaleLanguage == QLocale::AnyLanguage ? QLocale() : numberLocaleLanguage);
2647     l.setNumberOptions(numberOptions);
2648     QLocale::setDefault(l);
2649 }
2650 
2651 void MainWin::handleSettingsChanges() {
2652     const KConfigGroup group = Settings::group(QStringLiteral("Settings_General"));
2653 
2654     // title bar
2655     MainWin::TitleBarMode titleBarMode = static_cast<MainWin::TitleBarMode>(group.readEntry("TitleBar", 0));
2656     if (titleBarMode != m_titleBarMode) {
2657         m_titleBarMode = titleBarMode;
2658         updateTitleBar();
2659     }
2660 
2661     // view mode
2662     //  if (dynamic_cast<QQuickWidget*>(centralWidget()) == nullptr) {
2663     //  QMdiArea::ViewMode viewMode = QMdiArea::ViewMode(group.readEntry("ViewMode", 0));
2664     //  if (m_mdiArea->viewMode() != viewMode) {
2665     //      m_mdiArea->setViewMode(viewMode);
2666     //      if (viewMode == QMdiArea::SubWindowView)
2667     //          this->updateMdiWindowVisibility();
2668     //  }
2669 
2670     //  }
2671 
2672     // window visibility
2673     auto vis = Project::DockVisibility(group.readEntry("DockVisibility", 0));
2674     if (m_project && (vis != m_project->dockVisibility())) {
2675         if (vis == Project::DockVisibility::folderOnly)
2676             m_visibilityFolderAction->setChecked(true);
2677         else if (vis == Project::DockVisibility::folderAndSubfolders)
2678             m_visibilitySubfolderAction->setChecked(true);
2679         else
2680             m_visibilityAllAction->setChecked(true);
2681         m_project->setDockVisibility(vis);
2682     }
2683 
2684     // autosave
2685     bool autoSave = group.readEntry("AutoSave", 0);
2686     if (m_autoSaveActive != autoSave) {
2687         m_autoSaveActive = autoSave;
2688         if (autoSave)
2689             m_autoSaveTimer.start();
2690         else
2691             m_autoSaveTimer.stop();
2692     }
2693 
2694     int interval = group.readEntry("AutoSaveInterval", 1);
2695     interval *= 60 * 1000;
2696     if (interval != m_autoSaveTimer.interval())
2697         m_autoSaveTimer.setInterval(interval);
2698 
2699     // update the locale and the units in the dock widgets
2700     updateLocale();
2701     if (stackedWidget) {
2702         for (int i = 0; i < stackedWidget->count(); ++i) {
2703             auto* widget = stackedWidget->widget(i);
2704             auto* dock = dynamic_cast<BaseDock*>(widget);
2705             if (dock) {
2706                 dock->updateLocale();
2707                 dock->updateUnits();
2708             } else {
2709                 auto* labelWidget = dynamic_cast<LabelWidget*>(widget);
2710                 if (labelWidget)
2711                     labelWidget->updateUnits();
2712             }
2713         }
2714     }
2715 
2716     // update spreadsheet header
2717     if (m_project) {
2718         const auto& spreadsheets = m_project->children<Spreadsheet>(AbstractAspect::ChildIndexFlag::Recursive);
2719         for (auto* spreadsheet : spreadsheets) {
2720             spreadsheet->updateHorizontalHeader();
2721             spreadsheet->updateLocale();
2722         }
2723     }
2724 
2725     // bool showWelcomeScreen = group.readEntry<bool>(QLatin1String("ShowWelcomeScreen"), true);
2726     // if (m_showWelcomeScreen != showWelcomeScreen)
2727     //  m_showWelcomeScreen = showWelcomeScreen;
2728 }
2729 
2730 void MainWin::openDatasetExample() {
2731     newProject();
2732     //  addAspectToProject(m_welcomeScreenHelper->releaseConfiguredSpreadsheet());
2733 }
2734 
2735 /***************************************************************************************/
2736 /************************************** dialogs ***************************************/
2737 /***************************************************************************************/
2738 /*!
2739   shows the dialog with the Undo-history.
2740 */
2741 void MainWin::historyDialog() {
2742     if (!m_project->undoStack())
2743         return;
2744 
2745     auto* dialog = new HistoryDialog(this, m_project->undoStack(), m_undoViewEmptyLabel);
2746     int index = m_project->undoStack()->index();
2747     if (dialog->exec() != QDialog::Accepted) {
2748         if (m_project->undoStack()->count() != 0)
2749             m_project->undoStack()->setIndex(index);
2750     }
2751 
2752     // disable undo/redo-actions if the history was cleared
2753     //(in both cases, when accepted or rejected in the dialog)
2754     if (m_project->undoStack()->count() == 0) {
2755         m_undoAction->setEnabled(false);
2756         m_redoAction->setEnabled(false);
2757     }
2758 }
2759 
2760 /*!
2761   Opens the dialog to import data to the selected workbook, spreadsheet or matrix
2762 */
2763 void MainWin::importFileDialog(const QString& fileName) {
2764     DEBUG(Q_FUNC_INFO);
2765     auto* dlg = new ImportFileDialog(this, false, fileName);
2766 
2767     // select existing container
2768     if (m_currentAspect->type() == AspectType::Spreadsheet || m_currentAspect->type() == AspectType::Matrix || m_currentAspect->type() == AspectType::Workbook)
2769         dlg->setCurrentIndex(m_projectExplorer->currentIndex());
2770     else if (m_currentAspect->type() == AspectType::Column && m_currentAspect->parentAspect()->type() == AspectType::Spreadsheet)
2771         dlg->setCurrentIndex(m_aspectTreeModel->modelIndexOfAspect(m_currentAspect->parentAspect()));
2772 
2773     if (dlg->exec() == QDialog::Accepted) {
2774         dlg->importTo(statusBar());
2775         m_project->setChanged(true);
2776     }
2777 
2778     delete dlg;
2779     DEBUG(Q_FUNC_INFO << " DONE");
2780 }
2781 
2782 void MainWin::importSqlDialog() {
2783     DEBUG(Q_FUNC_INFO);
2784     auto* dlg = new ImportSQLDatabaseDialog(this);
2785 
2786     // select existing container
2787     if (m_currentAspect->type() == AspectType::Spreadsheet || m_currentAspect->type() == AspectType::Matrix || m_currentAspect->type() == AspectType::Workbook)
2788         dlg->setCurrentIndex(m_projectExplorer->currentIndex());
2789     else if (m_currentAspect->type() == AspectType::Column && m_currentAspect->parentAspect()->type() == AspectType::Spreadsheet)
2790         dlg->setCurrentIndex(m_aspectTreeModel->modelIndexOfAspect(m_currentAspect->parentAspect()));
2791 
2792     if (dlg->exec() == QDialog::Accepted) {
2793         dlg->importTo(statusBar());
2794         m_project->setChanged(true);
2795     }
2796 
2797     delete dlg;
2798     DEBUG(Q_FUNC_INFO << " DONE");
2799 }
2800 
2801 void MainWin::importProjectDialog() {
2802     DEBUG(Q_FUNC_INFO);
2803 
2804     ImportProjectDialog::ProjectType type;
2805     if (QObject::sender() == m_importOpjAction)
2806         type = ImportProjectDialog::ProjectType::Origin;
2807     else
2808         type = ImportProjectDialog::ProjectType::LabPlot;
2809 
2810     auto* dlg = new ImportProjectDialog(this, type);
2811 
2812     // set current folder
2813     dlg->setCurrentFolder(m_currentFolder);
2814 
2815     if (dlg->exec() == QDialog::Accepted) {
2816         dlg->importTo(statusBar());
2817         m_project->setChanged(true);
2818     }
2819 
2820     delete dlg;
2821     DEBUG(Q_FUNC_INFO << ", DONE");
2822 }
2823 
2824 /*!
2825  * \brief opens a dialog to import datasets
2826  */
2827 void MainWin::importDatasetDialog() {
2828     auto* dlg = new ImportDatasetDialog(this);
2829     if (dlg->exec() == QDialog::Accepted) {
2830         auto* spreadsheet = new Spreadsheet(i18n("Dataset%1", 1));
2831         auto* dataset = new DatasetHandler(spreadsheet);
2832         dlg->importToDataset(dataset, statusBar());
2833 
2834         QTimer timer;
2835         timer.setSingleShot(true);
2836         QEventLoop loop;
2837         connect(dataset, &DatasetHandler::downloadCompleted, &loop, &QEventLoop::quit);
2838         connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
2839         timer.start(1500);
2840         loop.exec();
2841 
2842         if (timer.isActive()) {
2843             timer.stop();
2844             addAspectToProject(spreadsheet);
2845         }
2846         delete dataset;
2847     }
2848     delete dlg;
2849 }
2850 
2851 /*!
2852   opens the dialog for the export of the currently active worksheet, spreadsheet or matrix.
2853  */
2854 void MainWin::exportDialog() {
2855     if (!m_currentAspectDock)
2856         return;
2857 
2858     AbstractPart* part = static_cast<ContentDockWidget*>(m_currentAspectDock)->part();
2859     if (part->exportView())
2860         statusBar()->showMessage(i18n("%1 exported", part->name()));
2861 }
2862 
2863 void MainWin::editFitsFileDialog() {
2864     auto* editDialog = new FITSHeaderEditDialog(this);
2865     if (editDialog->exec() == QDialog::Accepted) {
2866         if (editDialog->saved())
2867             statusBar()->showMessage(i18n("FITS files saved"));
2868     }
2869 }
2870 
2871 /*!
2872   adds a new file data source to the current project.
2873 */
2874 void MainWin::newLiveDataSource() {
2875     auto* dlg = new ImportFileDialog(this, true);
2876     if (dlg->exec() == QDialog::Accepted) {
2877         if (dlg->sourceType() == LiveDataSource::SourceType::MQTT) {
2878 #ifdef HAVE_MQTT
2879             auto* mqttClient = new MQTTClient(i18n("MQTT Client%1", 1));
2880             dlg->importToMQTT(mqttClient);
2881 
2882             // doesn't make sense to have more MQTTClients connected to the same broker
2883             auto clients = m_project->children<const MQTTClient>(AbstractAspect::ChildIndexFlag::Recursive);
2884             bool found = false;
2885             for (const auto* client : clients) {
2886                 if (client->clientHostName() == mqttClient->clientHostName() && client->clientPort() == mqttClient->clientPort()) {
2887                     found = true;
2888                     break;
2889                 }
2890             }
2891 
2892             if (!found) {
2893                 mqttClient->setName(mqttClient->clientHostName());
2894                 addAspectToProject(mqttClient);
2895             } else {
2896                 delete mqttClient;
2897                 QMessageBox::warning(this, i18n("Warning"), i18n("There already is a MQTTClient with this host!"));
2898             }
2899 #endif
2900         } else {
2901             auto* dataSource = new LiveDataSource(i18n("Live data source%1", 1), false);
2902             dlg->importToLiveDataSource(dataSource, statusBar());
2903             addAspectToProject(dataSource);
2904         }
2905     }
2906     delete dlg;
2907 }
2908 
2909 void MainWin::addAspectToProject(AbstractAspect* aspect) {
2910     const QModelIndex& index = m_projectExplorer->currentIndex();
2911     if (index.isValid()) {
2912         auto* parent = static_cast<AbstractAspect*>(index.internalPointer());
2913 #ifdef HAVE_MQTT
2914         // doesn't make sense to add a new MQTTClient to an existing MQTTClient or to any of its successors
2915         QString className = QLatin1String(parent->metaObject()->className());
2916         auto* clientAncestor = parent->ancestor<MQTTClient>();
2917         if (className == QLatin1String("MQTTClient"))
2918             parent = parent->parentAspect();
2919         else if (clientAncestor != nullptr)
2920             parent = clientAncestor->parentAspect();
2921 #endif
2922         parent->folder()->addChild(aspect);
2923     } else
2924         m_project->addChild(aspect);
2925 }
2926 
2927 void MainWin::settingsDialog() {
2928     auto* dlg = new SettingsDialog(this);
2929     connect(dlg, &SettingsDialog::settingsChanged, this, &MainWin::handleSettingsChanges);
2930     //  connect (dlg, &SettingsDialog::resetWelcomeScreen, this, &MainWin::resetWelcomeScreen);
2931     dlg->exec();
2932 }
2933 
2934 #ifdef HAVE_CANTOR_LIBS
2935 void MainWin::cantorSettingsDialog() {
2936     static auto* emptyConfig = new KCoreConfigSkeleton();
2937     auto* dlg = new KConfigDialog(this, QLatin1String("Cantor Settings"), emptyConfig);
2938     for (auto* backend : Cantor::Backend::availableBackends())
2939         if (backend->config()) // It has something to configure, so add it to the dialog
2940             dlg->addPage(backend->settingsWidget(dlg), backend->config(), backend->name(), backend->icon());
2941 
2942     // in case the settings were modified (we only need paths), update the "add new notebook" actions
2943     // to get the new list of available backend systems in the menu
2944     connect(dlg, &KConfigDialog::settingsChanged, this, [=]() {
2945         updateNotebookActions();
2946     });
2947 
2948     dlg->show();
2949 
2950     DEBUG(Q_FUNC_INFO << ", found " << Cantor::Backend::availableBackends().size() << " backends")
2951     if (Cantor::Backend::availableBackends().size() == 0)
2952         KMessageBox::error(nullptr, i18n("No Cantor backends found. Please install the ones you want to use."));
2953 }
2954 
2955 void MainWin::updateNotebookActions() {
2956     auto* menu = static_cast<QMenu*>(factory()->container(QLatin1String("new_notebook"), this));
2957     unplugActionList(QLatin1String("backends_list"));
2958     QList<QAction*> newBackendActions;
2959     menu->clear();
2960     for (auto* backend : Cantor::Backend::availableBackends()) {
2961         if (!backend->isEnabled())
2962             continue;
2963 
2964         auto* action = new QAction(QIcon::fromTheme(backend->icon()), backend->name(), this);
2965         action->setData(backend->name());
2966         action->setWhatsThis(i18n("Creates a new %1 notebook", backend->name()));
2967         actionCollection()->addAction(QLatin1String("notebook_") + backend->name(), action);
2968         connect(action, &QAction::triggered, this, &MainWin::newCantorWorksheet);
2969         newBackendActions << action;
2970         menu->addAction(action);
2971         m_newNotebookMenu->addAction(action);
2972     }
2973 
2974     plugActionList(QLatin1String("backends_list"), newBackendActions);
2975 
2976     menu->addSeparator();
2977     menu->addAction(m_configureCASAction);
2978 
2979     m_newNotebookMenu->addSeparator();
2980     m_newNotebookMenu->addAction(m_configureCASAction);
2981 }
2982 #endif