File indexing completed on 2024-04-28 15:14:02

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