"Fossies" - the Fresh Open Source Software Archive

Member "labplot-2.8.2/src/kdefrontend/MainWin.cpp" (24 Feb 2021, 94285 Bytes) of package /linux/privat/labplot-2.8.2.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "MainWin.cpp" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 2.8.1_vs_2.8.2.

    1 /***************************************************************************
    2     File                 : MainWin.cc
    3     Project              : LabPlot
    4     Description          : Main window of the application
    5     --------------------------------------------------------------------
    6     Copyright            : (C) 2008-2018 Stefan Gerlach (stefan.gerlach@uni.kn)
    7     Copyright            : (C) 2009-2020 Alexander Semke (alexander.semke@web.de)
    8 
    9  ***************************************************************************/
   10 
   11 /***************************************************************************
   12  *                                                                         *
   13  *  This program is free software; you can redistribute it and/or modify   *
   14  *  it under the terms of the GNU General Public License as published by   *
   15  *  the Free Software Foundation; either version 2 of the License, or      *
   16  *  (at your option) any later version.                                    *
   17  *                                                                         *
   18  *  This program is distributed in the hope that it will be useful,        *
   19  *  but WITHOUT ANY WARRANTY; without even the implied warranty of         *
   20  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the          *
   21  *  GNU General Public License for more details.                           *
   22  *                                                                         *
   23  *   You should have received a copy of the GNU General Public License     *
   24  *   along with this program; if not, write to the Free Software           *
   25  *   Foundation, Inc., 51 Franklin Street, Fifth Floor,                    *
   26  *   Boston, MA  02110-1301  USA                                           *
   27  *                                                                         *
   28  ***************************************************************************/
   29 
   30 #include "MainWin.h"
   31 
   32 #include "backend/core/Project.h"
   33 #include "backend/core/Folder.h"
   34 #include "backend/core/AspectTreeModel.h"
   35 #include "backend/core/Workbook.h"
   36 #include "backend/spreadsheet/Spreadsheet.h"
   37 #include "backend/matrix/Matrix.h"
   38 #include "backend/worksheet/Worksheet.h"
   39 #include "backend/datasources/LiveDataSource.h"
   40 #include "backend/datasources/DatasetHandler.h"
   41 #ifdef HAVE_LIBORIGIN
   42 #include "backend/datasources/projects/OriginProjectParser.h"
   43 #endif
   44 #ifdef HAVE_CANTOR_LIBS
   45 #include "backend/cantorWorksheet/CantorWorksheet.h"
   46 #endif
   47 #include "backend/datapicker/Datapicker.h"
   48 #include "backend/note/Note.h"
   49 #include "backend/lib/macros.h"
   50 #include "backend/worksheet/plots/cartesian/CartesianPlot.h"
   51 
   52 #ifdef HAVE_MQTT
   53 #include "backend/datasources/MQTTClient.h"
   54 #endif
   55 
   56 #include "commonfrontend/core/PartMdiView.h"
   57 #include "commonfrontend/ProjectExplorer.h"
   58 #include "commonfrontend/matrix/MatrixView.h"
   59 #include "commonfrontend/spreadsheet/SpreadsheetView.h"
   60 #include "commonfrontend/worksheet/WorksheetView.h"
   61 #ifdef HAVE_CANTOR_LIBS
   62 #include "commonfrontend/cantorWorksheet/CantorWorksheetView.h"
   63 #endif
   64 #include "commonfrontend/datapicker/DatapickerView.h"
   65 #include "commonfrontend/datapicker/DatapickerImageView.h"
   66 #include "commonfrontend/note/NoteView.h"
   67 #include "commonfrontend/widgets/MemoryWidget.h"
   68 
   69 #include "kdefrontend/datasources/ImportFileDialog.h"
   70 #include "kdefrontend/datasources/ImportDatasetDialog.h"
   71 #include "kdefrontend/datasources/ImportDatasetWidget.h"
   72 #include "kdefrontend/datasources/ImportProjectDialog.h"
   73 #include "kdefrontend/datasources/ImportSQLDatabaseDialog.h"
   74 #include <kdefrontend/dockwidgets/CursorDock.h>
   75 #include "kdefrontend/dockwidgets/ProjectDock.h"
   76 #include "kdefrontend/HistoryDialog.h"
   77 #include "kdefrontend/SettingsDialog.h"
   78 #include "kdefrontend/GuiObserver.h"
   79 #include "kdefrontend/widgets/LabelWidget.h"
   80 #include "kdefrontend/widgets/FITSHeaderEditDialog.h"
   81 #include "DatasetModel.h"
   82 // #include "welcomescreen/WelcomeScreenHelper.h"
   83 
   84 #ifdef HAVE_KUSERFEEDBACK
   85 #include <KUserFeedback/ApplicationVersionSource>
   86 #include <KUserFeedback/PlatformInfoSource>
   87 #include <KUserFeedback/QtVersionSource>
   88 #include <KUserFeedback/ScreenInfoSource>
   89 #include <KUserFeedback/StartCountSource>
   90 #include <KUserFeedback/UsageTimeSource>
   91 #endif
   92 
   93 #ifdef Q_OS_MAC
   94 #include "3rdparty/kdmactouchbar/src/kdmactouchbar.h"
   95 #endif
   96 
   97 #include <QMdiArea>
   98 #include <QMenu>
   99 #include <QDockWidget>
  100 #include <QStackedWidget>
  101 #include <QUndoStack>
  102 #include <QCloseEvent>
  103 #include <QFileDialog>
  104 #include <QMimeData>
  105 #include <QElapsedTimer>
  106 #include <QHash>
  107 #include <QStatusBar>
  108 #include <QTemporaryFile>
  109 #include <QTimeLine>
  110 // #include <QtWidgets>
  111 // #include <QtQuickWidgets/QQuickWidget>
  112 // #include <QQuickItem>
  113 // #include <QQuickView>
  114 // #include <QQmlApplicationEngine>
  115 // #include <QQmlContext>
  116 
  117 #include <KActionCollection>
  118 #include <KConfigGroup>
  119 #include <KStandardAction>
  120 #include <kxmlguifactory.h>
  121 #include <KMessageBox>
  122 #include <KToolBar>
  123 #include <KLocalizedString>
  124 #include <KFilterDev>
  125 #include <KRecentFilesAction>
  126 #include <KActionMenu>
  127 #include <KColorScheme>
  128 #include <KColorSchemeManager>
  129 #include <KToggleFullScreenAction>
  130 #include <kconfigwidgets_version.h>
  131 
  132 #ifdef HAVE_CANTOR_LIBS
  133 #include <cantor/backend.h>
  134 #include <KConfigDialog>
  135 #include <KCoreConfigSkeleton>
  136 #include <KConfigSkeleton>
  137 
  138 //required to parse Cantor and Jupyter files
  139 #include <QBuffer>
  140 #include <KZip>
  141 #include <QJsonDocument>
  142 #include <QJsonParseError>
  143 #endif
  144 
  145 /*!
  146 \class MainWin
  147 \brief Main application window.
  148 
  149 \ingroup kdefrontend
  150 */
  151 MainWin::MainWin(QWidget *parent, const QString& filename)
  152     : KXmlGuiWindow(parent),
  153     m_schemeManager(new KColorSchemeManager(this)) {
  154 
  155     initGUI(filename);
  156     setAcceptDrops(true);
  157 
  158 #ifdef HAVE_KUSERFEEDBACK
  159     m_userFeedbackProvider.setProductIdentifier(QStringLiteral("org.kde.labplot"));
  160     m_userFeedbackProvider.setFeedbackServer(QUrl(QStringLiteral("https://telemetry.kde.org/")));
  161     m_userFeedbackProvider.setSubmissionInterval(7);
  162     m_userFeedbackProvider.setApplicationStartsUntilEncouragement(5);
  163     m_userFeedbackProvider.setEncouragementDelay(30);
  164 
  165     // software version info
  166     m_userFeedbackProvider.addDataSource(new KUserFeedback::ApplicationVersionSource);
  167     m_userFeedbackProvider.addDataSource(new KUserFeedback::QtVersionSource);
  168 
  169     // info about the machine
  170     m_userFeedbackProvider.addDataSource(new KUserFeedback::PlatformInfoSource);
  171     m_userFeedbackProvider.addDataSource(new KUserFeedback::ScreenInfoSource);
  172 
  173     // usage info
  174     m_userFeedbackProvider.addDataSource(new KUserFeedback::StartCountSource);
  175     m_userFeedbackProvider.addDataSource(new KUserFeedback::UsageTimeSource);
  176 #endif
  177 }
  178 
  179 MainWin::~MainWin() {
  180     //save the current settings in MainWin
  181     m_recentProjectsAction->saveEntries( KSharedConfig::openConfig()->group("Recent Files") );
  182 
  183     KConfigGroup group = KSharedConfig::openConfig()->group("MainWin");
  184     group.writeEntry(QLatin1String("geometry"), saveGeometry());
  185     group.writeEntry(QLatin1String("WindowState"), saveState());
  186     group.writeEntry(QLatin1String("lastOpenFileFilter"), m_lastOpenFileFilter);
  187     group.writeEntry(QLatin1String("ShowMemoryInfo"), (m_memoryInfoWidget != nullptr));
  188     KSharedConfig::openConfig()->sync();
  189 
  190     //if welcome screen is shown, save its settings prior to deleting it
  191 //  if (dynamic_cast<QQuickWidget*>(centralWidget()))
  192 //      QMetaObject::invokeMethod(m_welcomeWidget->rootObject(), "saveWidgetDimensions");
  193 
  194     if (m_project) {
  195 //      if (dynamic_cast<QQuickWidget*>(centralWidget()) == nullptr)
  196 //          m_mdiArea->closeAllSubWindows();
  197 
  198         disconnect(m_project, nullptr, this, nullptr);
  199         delete m_project;
  200     }
  201 
  202     if (m_aspectTreeModel)
  203         delete m_aspectTreeModel;
  204 
  205     if (m_guiObserver)
  206         delete m_guiObserver;
  207 
  208 //  if (m_welcomeScreenHelper)
  209 //      delete m_welcomeScreenHelper;
  210 }
  211 
  212 void MainWin::showPresenter() {
  213     const Worksheet* w = dynamic_cast<Worksheet*>(m_currentAspect);
  214     if (w) {
  215         auto* view = static_cast<WorksheetView*>(w->view());
  216         view->presenterMode();
  217     } else {
  218         //currently active object is not a worksheet but we're asked to start in the presenter mode
  219         //determine the first available worksheet and show it in the presenter mode
  220         auto worksheets = m_project->children<Worksheet>();
  221         if (worksheets.size() > 0) {
  222             auto* view = static_cast<WorksheetView*>(worksheets.constFirst()->view());
  223             view->presenterMode();
  224         } else {
  225             QMessageBox::information(this, i18n("Presenter Mode"),
  226                 i18n("No worksheets are available in the project. The presenter mode will not be started."));
  227         }
  228     }
  229 }
  230 
  231 AspectTreeModel* MainWin::model() const {
  232     return m_aspectTreeModel;
  233 }
  234 
  235 Project* MainWin::project() const {
  236     return m_project;
  237 }
  238 
  239 void MainWin::initGUI(const QString& fileName) {
  240     statusBar()->showMessage(i18nc("%1 is the LabPlot version", "Welcome to LabPlot %1", QLatin1String(LVERSION)));
  241 
  242     initActions();
  243 
  244 #ifdef Q_OS_MAC
  245     // setup touchbar before GUI (otherwise actions in the toolbar are not selectable)
  246     m_touchBar = new KDMacTouchBar(this);
  247     setupGUI(Default, QLatin1String("/Applications/labplot2.app/Contents/Resources/labplot2ui.rc"));
  248     //m_touchBar->setTouchButtonStyle(KDMacTouchBar::IconOnly);
  249 #else
  250     setupGUI(Default, KXMLGUIClient::xmlFile());    // should be "labplot2ui.rc"
  251 #endif
  252 
  253     DEBUG(Q_FUNC_INFO << ", Component name: " << STDSTRING(KXMLGUIClient::componentName()));
  254     DEBUG(Q_FUNC_INFO << ", XML file: " << STDSTRING(KXMLGUIClient::xmlFile()) << " (should be \"labplot2ui.rc\")");
  255 
  256     //all toolbars created via the KXMLGUI framework are locked on default:
  257     // * on the very first program start, unlock all toolbars
  258     // * on later program starts, set stored lock status
  259     //Furthermore, we want to show icons only after the first program start.
  260     KConfigGroup groupMain = KSharedConfig::openConfig()->group("MainWindow");
  261     if (groupMain.exists()) {
  262         //KXMLGUI framework automatically stores "Disabled" for the key "ToolBarsMovable"
  263         //in case the toolbars are locked -> load this value
  264         const QString& str = groupMain.readEntry(QLatin1String("ToolBarsMovable"), "");
  265         bool locked = (str == QLatin1String("Disabled"));
  266         KToolBar::setToolBarsLocked(locked);
  267     } else {
  268         //first start
  269         KToolBar::setToolBarsLocked(false);
  270 
  271         //show icons only
  272         for (auto* container : factory()->containers(QLatin1String("ToolBar"))) {
  273             auto* toolbar = dynamic_cast<QToolBar*>(container);
  274             if (toolbar)
  275                 toolbar->setToolButtonStyle(Qt::ToolButtonIconOnly);
  276         }
  277     }
  278 
  279     initMenus();
  280 
  281     auto* mainToolBar = qobject_cast<QToolBar*>(factory()->container("main_toolbar", this));
  282     if (!mainToolBar) {
  283         QMessageBox::critical(this, i18n("GUI configuration file not found"), i18n("%1 file was not found. Please check your installation.", KXMLGUIClient::xmlFile()));
  284         //TODO: the application is not really usable if the rc file was not found. We should quit the application. The following line crashes
  285         //the application because of the splash screen. We need to find another solution.
  286 //      QMetaObject::invokeMethod(this, "close", Qt::QueuedConnection); //call close as soon as we enter the eventloop
  287         return;
  288     }
  289 
  290     auto* tbImport = new QToolButton(mainToolBar);
  291     tbImport->setPopupMode(QToolButton::MenuButtonPopup);
  292     tbImport->setMenu(m_importMenu);
  293     tbImport->setDefaultAction(m_importFileAction);
  294     auto* lastAction = mainToolBar->actions().at(mainToolBar->actions().count() - 1);
  295     mainToolBar->insertWidget(lastAction, tbImport);
  296 
  297     qobject_cast<QMenu*>(factory()->container("import", this))->setIcon(QIcon::fromTheme("document-import"));
  298     setWindowIcon(QIcon::fromTheme("LabPlot2", QGuiApplication::windowIcon()));
  299     setAttribute( Qt::WA_DeleteOnClose );
  300 
  301     //make the status bar of a fixed size in order to avoid height changes when placing a ProgressBar there.
  302     QFont font;
  303     font.setFamily(font.defaultFamily());
  304     QFontMetrics fm(font);
  305     statusBar()->setFixedHeight(fm.height() + 5);
  306 
  307     //load recently used projects
  308     m_recentProjectsAction->loadEntries( KSharedConfig::openConfig()->group("Recent Files") );
  309 
  310     //General Settings
  311     const KConfigGroup& group = KSharedConfig::openConfig()->group("Settings_General");
  312 
  313     //title bar
  314     m_titleBarMode = static_cast<MainWin::TitleBarMode>(group.readEntry("TitleBar", 0));
  315 
  316     //auto-save
  317     m_autoSaveActive = group.readEntry<bool>("AutoSave", false);
  318     int interval = group.readEntry("AutoSaveInterval", 1);
  319     interval = interval*60*1000;
  320     m_autoSaveTimer.setInterval(interval);
  321     connect(&m_autoSaveTimer, &QTimer::timeout, this, &MainWin::autoSaveProject);
  322 
  323     if (!fileName.isEmpty()) {
  324         createMdiArea();
  325         setCentralWidget(m_mdiArea);
  326 #ifdef HAVE_LIBORIGIN
  327         if (Project::isLabPlotProject(fileName) || OriginProjectParser::isOriginProject(fileName)) {
  328 #else
  329         if (Project::isLabPlotProject(fileName)) {
  330 #endif
  331             QTimer::singleShot(0, this, [=] () { openProject(fileName); });
  332         } else {
  333             newProject();
  334             QTimer::singleShot(0, this, [=] () { importFileDialog(fileName); });
  335         }
  336 
  337     } else {
  338         //There is no file to open. Depending on the settings do nothing,
  339         //create a new project or open the last used project.
  340         LoadOnStart load = (LoadOnStart)group.readEntry("LoadOnStart", static_cast<int>(LoadOnStart::Nothing));
  341         if (load != LoadOnStart::WelcomeScreen) {
  342             createMdiArea();
  343             setCentralWidget(m_mdiArea);
  344 
  345             if (load == LoadOnStart::NewProject)    //create new project
  346                 newProject();
  347             else if (load == LoadOnStart::NewProjectWorksheet) { //create new project with a worksheet
  348                 newProject();
  349                 newWorksheet();
  350             } else if (load == LoadOnStart::LastProject) { //open last used project
  351                 const QString& path = KSharedConfig::openConfig()->group("MainWin").readEntry("LastOpenProject", "");
  352                 if (!path.isEmpty())
  353                     openProject(path);
  354             }
  355 
  356             updateGUIOnProjectChanges();
  357         } else{ //welcome screen
  358 //          m_showWelcomeScreen = true;
  359 //          m_welcomeWidget = createWelcomeScreen();
  360 //          setCentralWidget(m_welcomeWidget);
  361         }
  362     }
  363 
  364     //read the settings of MainWin
  365     const KConfigGroup& groupMainWin = KSharedConfig::openConfig()->group(QLatin1String("MainWin"));
  366 
  367     //status bar
  368     bool visible = groupMainWin.readEntry(QLatin1String("ShowStatusBar"), true);
  369     statusBar()->setVisible(visible);
  370     m_toggleStatusBarAction->setChecked(visible);
  371     m_toggleMemoryInfoAction->setEnabled(visible);
  372 
  373     //show memory info
  374     visible = groupMainWin.readEntry(QLatin1String("ShowMemoryInfo"), true);
  375     if (visible) {
  376         m_memoryInfoWidget = new MemoryWidget(statusBar());
  377         statusBar()->addPermanentWidget(m_memoryInfoWidget);
  378     }
  379     m_toggleMemoryInfoAction->setChecked(visible);
  380 
  381     //restore the geometry
  382     restoreGeometry(groupMainWin.readEntry("geometry", QByteArray()));
  383 
  384     m_lastOpenFileFilter = groupMainWin.readEntry(QLatin1String("lastOpenFileFilter"), QString());
  385 }
  386 
  387 /**
  388  * @brief Creates a new welcome screen to be set as central widget.
  389  */
  390 /*
  391 QQuickWidget* MainWin::createWelcomeScreen() {
  392     QSize maxSize = qApp->primaryScreen()->availableSize();
  393     resize(maxSize);
  394     setMinimumSize(700, 400);
  395     showMaximized();
  396 
  397     KToolBar* toolbar = toolBar();
  398     if (toolbar)
  399         toolbar->setVisible(false);
  400 
  401     QList<QVariant> recentList;
  402     for (QUrl& url : m_recentProjectsAction->urls())
  403         recentList.append(QVariant(url));
  404 
  405     //Set context property
  406     QQuickWidget* quickWidget = new QQuickWidget(this);
  407     QQmlContext* ctxt = quickWidget->rootContext();
  408     QVariant variant(recentList);
  409     ctxt->setContextProperty("recentProjects", variant);
  410 
  411     //Create helper object
  412     if (m_welcomeScreenHelper)
  413         delete m_welcomeScreenHelper;
  414     m_welcomeScreenHelper = new WelcomeScreenHelper();
  415     connect(m_welcomeScreenHelper, &WelcomeScreenHelper::openExampleProject,
  416             this, QOverload<const QString&>::of(&MainWin::openProject));
  417 
  418     ctxt->setContextProperty("datasetModel", m_welcomeScreenHelper->getDatasetModel());
  419     ctxt->setContextProperty("helper", m_welcomeScreenHelper);
  420 
  421     quickWidget->setSource(QUrl(QLatin1String("qrc:///main.qml")));
  422     quickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView);
  423     QObject *item = quickWidget->rootObject();
  424 
  425     //connect qml's signals
  426     QObject::connect(item, SIGNAL(recentProjectClicked(QUrl)), this, SLOT(openRecentProject(QUrl)));
  427     QObject::connect(item, SIGNAL(datasetClicked(QString,QString,QString)), m_welcomeScreenHelper, SLOT(datasetClicked(QString,QString,QString)));
  428     QObject::connect(item, SIGNAL(openDataset()), this, SLOT(openDatasetExample()));
  429     QObject::connect(item, SIGNAL(openExampleProject(QString)), m_welcomeScreenHelper, SLOT(exampleProjectClicked(QString)));
  430     emit m_welcomeScreenHelper->showFirstDataset();
  431 
  432     return quickWidget;
  433 }
  434 */
  435 /**
  436  * @brief Initiates resetting the layout of the welcome screen
  437  */
  438 /*
  439 void MainWin::resetWelcomeScreen() {
  440     if (dynamic_cast<QQuickWidget*>(centralWidget()))
  441         QMetaObject::invokeMethod(m_welcomeWidget->rootObject(), "restoreOriginalLayout");
  442 }
  443 */
  444 
  445 /**
  446  * @brief Creates a new MDI area, to replace the Welcome Screen as central widget
  447  */
  448 void MainWin::createMdiArea() {
  449     KToolBar* toolbar = toolBar();
  450     if (toolbar)
  451         toolbar->setVisible(true);
  452 
  453     //Save welcome screen's dimensions.
  454 //  if (m_showWelcomeScreen)
  455 //      QMetaObject::invokeMethod(m_welcomeWidget->rootObject(), "saveWidgetDimensions");
  456 
  457     m_mdiArea = new QMdiArea;
  458     setCentralWidget(m_mdiArea);
  459     connect(m_mdiArea, &QMdiArea::subWindowActivated, this, &MainWin::handleCurrentSubWindowChanged);
  460 
  461     //set the view mode of the mdi area
  462     KConfigGroup group = KSharedConfig::openConfig()->group( "Settings_General" );
  463     int viewMode = group.readEntry("ViewMode", 0);
  464     if (viewMode == 1) {
  465         m_mdiArea->setViewMode(QMdiArea::TabbedView);
  466         int tabPosition = group.readEntry("TabPosition", 0);
  467         m_mdiArea->setTabPosition(QTabWidget::TabPosition(tabPosition));
  468         m_mdiArea->setTabsClosable(true);
  469         m_mdiArea->setTabsMovable(true);
  470         m_tileWindowsAction->setVisible(false);
  471         m_cascadeWindowsAction->setVisible(false);
  472     }
  473 
  474     connect(m_closeWindowAction, &QAction::triggered, m_mdiArea, &QMdiArea::closeActiveSubWindow);
  475     connect(m_closeAllWindowsAction, &QAction::triggered, m_mdiArea, &QMdiArea::closeAllSubWindows);
  476     connect(m_tileWindowsAction, &QAction::triggered, m_mdiArea, &QMdiArea::tileSubWindows);
  477     connect(m_cascadeWindowsAction, &QAction::triggered, m_mdiArea, &QMdiArea::cascadeSubWindows);
  478     connect(m_nextWindowAction, &QAction::triggered, m_mdiArea, &QMdiArea::activateNextSubWindow);
  479     connect(m_prevWindowAction, &QAction::triggered, m_mdiArea, &QMdiArea::activatePreviousSubWindow);
  480 }
  481 
  482 void MainWin::initActions() {
  483     // ******************** File-menu *******************************
  484     //add some standard actions
  485     m_newProjectAction = KStandardAction::openNew(this, &MainWin::newProject, actionCollection());
  486     m_openProjectAction = KStandardAction::open(this, static_cast<void (MainWin::*)()>(&MainWin::openProject), actionCollection());
  487     m_recentProjectsAction = KStandardAction::openRecent(this, &MainWin::openRecentProject, actionCollection());
  488     m_closeAction = KStandardAction::close(this, &MainWin::closeProject, actionCollection());
  489     actionCollection()->setDefaultShortcut(m_closeAction, QKeySequence()); //remove the shortcut, QKeySequence::Close will be used for closing sub-windows
  490     m_saveAction = KStandardAction::save(this, &MainWin::saveProject, actionCollection());
  491     m_saveAsAction = KStandardAction::saveAs(this, &MainWin::saveProjectAs, actionCollection());
  492     m_printAction = KStandardAction::print(this, &MainWin::print, actionCollection());
  493     m_printPreviewAction = KStandardAction::printPreview(this, &MainWin::printPreview, actionCollection());
  494 
  495     m_toggleFullScreenAction = KStandardAction::fullScreen(this, &MainWin::toggleFullScreen, this, actionCollection());
  496 
  497     //QDEBUG(Q_FUNC_INFO << ", preferences action name:" << KStandardAction::name(KStandardAction::Preferences))
  498     KStandardAction::preferences(this, &MainWin::settingsDialog, actionCollection());
  499     // QAction* action = actionCollection()->action(KStandardAction::name(KStandardAction::Preferences)));
  500     KStandardAction::quit(this, &MainWin::close, actionCollection());
  501 
  502     //New Folder/Workbook/Spreadsheet/Matrix/Worksheet/Datasources
  503     m_newWorkbookAction = new QAction(QIcon::fromTheme("labplot-workbook-new"),i18n("Workbook"),this);
  504     actionCollection()->addAction("new_workbook", m_newWorkbookAction);
  505     m_newWorkbookAction->setWhatsThis(i18n("Creates a new workbook for collection spreadsheets, matrices and plots"));
  506     connect(m_newWorkbookAction, &QAction::triggered, this, &MainWin::newWorkbook);
  507 
  508     m_newDatapickerAction = new QAction(QIcon::fromTheme("color-picker-black"), i18n("Datapicker"), this);
  509     m_newDatapickerAction->setWhatsThis(i18n("Creates a data picker for getting data from a picture"));
  510     actionCollection()->addAction("new_datapicker", m_newDatapickerAction);
  511     connect(m_newDatapickerAction, &QAction::triggered, this, &MainWin::newDatapicker);
  512 
  513     m_newSpreadsheetAction = new QAction(QIcon::fromTheme("labplot-spreadsheet-new"),i18n("Spreadsheet"),this);
  514 //  m_newSpreadsheetAction->setShortcut(Qt::CTRL+Qt::Key_Equal);
  515     m_newSpreadsheetAction->setWhatsThis(i18n("Creates a new spreadsheet for data editing"));
  516     actionCollection()->addAction("new_spreadsheet", m_newSpreadsheetAction);
  517     connect(m_newSpreadsheetAction, &QAction::triggered, this, &MainWin::newSpreadsheet);
  518 
  519     m_newMatrixAction = new QAction(QIcon::fromTheme("labplot-matrix-new"),i18n("Matrix"),this);
  520 //  m_newMatrixAction->setShortcut(Qt::CTRL+Qt::Key_Equal);
  521     m_newMatrixAction->setWhatsThis(i18n("Creates a new matrix for data editing"));
  522     actionCollection()->addAction("new_matrix", m_newMatrixAction);
  523     connect(m_newMatrixAction, &QAction::triggered, this, &MainWin::newMatrix);
  524 
  525     m_newWorksheetAction = new QAction(QIcon::fromTheme("labplot-worksheet-new"),i18n("Worksheet"),this);
  526 //  m_newWorksheetAction->setShortcut(Qt::ALT+Qt::Key_X);
  527     m_newWorksheetAction->setWhatsThis(i18n("Creates a new worksheet for data plotting"));
  528     actionCollection()->addAction("new_worksheet", m_newWorksheetAction);
  529     connect(m_newWorksheetAction, &QAction::triggered, this, &MainWin::newWorksheet);
  530 
  531     m_newNotesAction = new QAction(QIcon::fromTheme("document-new"),i18n("Note"),this);
  532     m_newNotesAction->setWhatsThis(i18n("Creates a new note for arbitrary text"));
  533     actionCollection()->addAction("new_notes", m_newNotesAction);
  534     connect(m_newNotesAction, &QAction::triggered, this, &MainWin::newNotes);
  535 
  536 //  m_newScriptAction = new QAction(QIcon::fromTheme("insert-text"),i18n("Note/Script"),this);
  537 //  actionCollection()->addAction("new_script", m_newScriptAction);
  538 //  connect(m_newScriptAction, &QAction::triggered,SLOT(newScript()));
  539 
  540     m_newFolderAction = new QAction(QIcon::fromTheme("folder-new"),i18n("Folder"),this);
  541     m_newFolderAction->setWhatsThis(i18n("Creates a new folder to collect sheets and other elements"));
  542     actionCollection()->addAction("new_folder", m_newFolderAction);
  543     connect(m_newFolderAction, &QAction::triggered, this, &MainWin::newFolder);
  544 
  545     //"New file datasources"
  546     m_newLiveDataSourceAction = new QAction(QIcon::fromTheme("application-octet-stream"),i18n("Live Data Source"),this);
  547     m_newLiveDataSourceAction->setWhatsThis(i18n("Creates a live data source to read data from a real time device"));
  548     actionCollection()->addAction("new_live_datasource", m_newLiveDataSourceAction);
  549     connect(m_newLiveDataSourceAction, &QAction::triggered, this, &MainWin::newLiveDataSourceActionTriggered);
  550 
  551     //Import/Export
  552     m_importFileAction = new QAction(QIcon::fromTheme("document-import"), i18n("Import from File"), this);
  553     actionCollection()->setDefaultShortcut(m_importFileAction, Qt::CTRL+Qt::SHIFT+Qt::Key_I);
  554     m_importFileAction->setWhatsThis(i18n("Import data from a regular file"));
  555     actionCollection()->addAction("import_file", m_importFileAction);
  556     connect(m_importFileAction, &QAction::triggered, this, [=]() {importFileDialog();});
  557 
  558     m_importSqlAction = new QAction(QIcon::fromTheme("network-server-database"), i18n("From SQL Database"), this);
  559     m_importSqlAction->setWhatsThis(i18n("Import data from a SQL database"));
  560     actionCollection()->addAction("import_sql", m_importSqlAction);
  561     connect(m_importSqlAction, &QAction::triggered, this, &MainWin::importSqlDialog);
  562 
  563     m_importDatasetAction = new QAction(QIcon::fromTheme(QLatin1String("database-index")), i18n("From Dataset Collection"), this);
  564     m_importDatasetAction->setWhatsThis(i18n("Imports data from an online dataset"));
  565     actionCollection()->addAction("import_dataset_datasource", m_importDatasetAction);
  566     connect(m_importDatasetAction, &QAction::triggered, this, &MainWin::importDatasetDialog);
  567 
  568     m_importLabPlotAction = new QAction(QIcon::fromTheme("project-open"), i18n("LabPlot Project"), this);
  569     m_importLabPlotAction->setWhatsThis(i18n("Import a project from a LabPlot project file (.lml)"));
  570     actionCollection()->addAction("import_labplot", m_importLabPlotAction);
  571     connect(m_importLabPlotAction, &QAction::triggered, this, &MainWin::importProjectDialog);
  572 
  573 #ifdef HAVE_LIBORIGIN
  574     m_importOpjAction = new QAction(QIcon::fromTheme("project-open"), i18n("Origin Project (OPJ)"), this);
  575     m_importOpjAction->setWhatsThis(i18n("Import a project from an OriginLab Origin project file (.opj)"));
  576     actionCollection()->addAction("import_opj", m_importOpjAction);
  577     connect(m_importOpjAction, &QAction::triggered, this, &MainWin::importProjectDialog);
  578 #endif
  579 
  580     m_exportAction = new QAction(QIcon::fromTheme("document-export"), i18n("Export"), this);
  581     m_exportAction->setWhatsThis(i18n("Export selected element"));
  582     actionCollection()->setDefaultShortcut(m_exportAction, Qt::CTRL+Qt::SHIFT+Qt::Key_E);
  583     actionCollection()->addAction("export", m_exportAction);
  584     connect(m_exportAction, &QAction::triggered, this, &MainWin::exportDialog);
  585 
  586     m_editFitsFileAction = new QAction(QIcon::fromTheme("editor"), i18n("FITS Metadata Editor"), this);
  587     m_editFitsFileAction->setWhatsThis(i18n("Open editor to edit FITS meta data"));
  588     actionCollection()->addAction("edit_fits", m_editFitsFileAction);
  589     connect(m_editFitsFileAction, &QAction::triggered, this, &MainWin::editFitsFileDialog);
  590 
  591     // Edit
  592     //Undo/Redo-stuff
  593     m_undoAction = KStandardAction::undo(this, SLOT(undo()), actionCollection());
  594     m_redoAction = KStandardAction::redo(this, SLOT(redo()), actionCollection());
  595     m_historyAction = new QAction(QIcon::fromTheme("view-history"), i18n("Undo/Redo History"),this);
  596     actionCollection()->addAction("history", m_historyAction);
  597     connect(m_historyAction, &QAction::triggered, this, &MainWin::historyDialog);
  598 
  599 #ifdef Q_OS_MAC
  600     m_undoIconOnlyAction = new QAction(m_undoAction->icon(), QString());
  601     connect(m_undoIconOnlyAction, &QAction::triggered, this, &MainWin::undo);
  602 
  603     m_redoIconOnlyAction = new QAction(m_redoAction->icon(), QString());
  604     connect(m_redoIconOnlyAction, &QAction::triggered, this, &MainWin::redo);
  605 #endif
  606     // TODO: more menus
  607     //  Appearance
  608     // Analysis: see WorksheetView.cpp
  609     // Drawing
  610     // Script
  611 
  612     //Windows
  613     m_closeWindowAction  = new QAction(i18n("&Close"), this);
  614     actionCollection()->setDefaultShortcut(m_closeWindowAction, QKeySequence::Close);
  615     m_closeWindowAction->setStatusTip(i18n("Close the active window"));
  616     actionCollection()->addAction("close window", m_closeWindowAction);
  617 
  618     m_closeAllWindowsAction = new QAction(i18n("Close &All"), this);
  619     m_closeAllWindowsAction->setStatusTip(i18n("Close all the windows"));
  620     actionCollection()->addAction("close all windows", m_closeAllWindowsAction);
  621 
  622     m_tileWindowsAction = new QAction(i18n("&Tile"), this);
  623     m_tileWindowsAction->setStatusTip(i18n("Tile the windows"));
  624     actionCollection()->addAction("tile windows", m_tileWindowsAction);
  625 
  626     m_cascadeWindowsAction = new QAction(i18n("&Cascade"), this);
  627     m_cascadeWindowsAction->setStatusTip(i18n("Cascade the windows"));
  628     actionCollection()->addAction("cascade windows", m_cascadeWindowsAction);
  629 
  630     m_nextWindowAction = new QAction(QIcon::fromTheme("go-next-view"), i18n("Ne&xt"), this);
  631     actionCollection()->setDefaultShortcut(m_nextWindowAction, QKeySequence::NextChild);
  632     m_nextWindowAction->setStatusTip(i18n("Move the focus to the next window"));
  633     actionCollection()->addAction("next window", m_nextWindowAction);
  634 
  635     m_prevWindowAction = new QAction(QIcon::fromTheme("go-previous-view"), i18n("Pre&vious"), this);
  636     actionCollection()->setDefaultShortcut(m_prevWindowAction, QKeySequence::PreviousChild);
  637     m_prevWindowAction->setStatusTip(i18n("Move the focus to the previous window"));
  638     actionCollection()->addAction("previous window", m_prevWindowAction);
  639 
  640     //Actions for window visibility
  641     auto* windowVisibilityActions = new QActionGroup(this);
  642     windowVisibilityActions->setExclusive(true);
  643 
  644     m_visibilityFolderAction = new QAction(QIcon::fromTheme("folder"), i18n("Current &Folder Only"), windowVisibilityActions);
  645     m_visibilityFolderAction->setCheckable(true);
  646     m_visibilityFolderAction->setData(static_cast<int>(Project::MdiWindowVisibility::folderOnly));
  647 
  648     m_visibilitySubfolderAction = new QAction(QIcon::fromTheme("folder-documents"), i18n("Current Folder and &Subfolders"), windowVisibilityActions);
  649     m_visibilitySubfolderAction->setCheckable(true);
  650     m_visibilitySubfolderAction->setData(static_cast<int>(Project::MdiWindowVisibility::folderAndSubfolders));
  651 
  652     m_visibilityAllAction = new QAction(i18n("&All"), windowVisibilityActions);
  653     m_visibilityAllAction->setCheckable(true);
  654     m_visibilityAllAction->setData(static_cast<int>(Project::MdiWindowVisibility::allMdiWindows));
  655 
  656     connect(windowVisibilityActions, &QActionGroup::triggered, this, &MainWin::setMdiWindowVisibility);
  657 
  658     //show/hide the status bar
  659     m_toggleStatusBarAction = new QAction(i18n("Show Status Bar"));
  660     m_toggleStatusBarAction->setCheckable(true);
  661     m_toggleStatusBarAction->setChecked(true);
  662     connect(m_toggleStatusBarAction, &QAction::triggered, this, &MainWin::toggleStatusBar);
  663 
  664     //show/hide the status bar
  665     m_toggleMemoryInfoAction = new QAction(i18n("Show Memory Usage"));
  666     m_toggleMemoryInfoAction->setCheckable(true);
  667     m_toggleMemoryInfoAction->setChecked(true);
  668     connect(m_toggleMemoryInfoAction, &QAction::triggered, this, &MainWin::toggleMemoryInfo);
  669 
  670     //Actions for hiding/showing the dock widgets
  671     auto* docksActions = new QActionGroup(this);
  672     docksActions->setExclusive(false);
  673 
  674     m_toggleProjectExplorerDockAction = new QAction(QIcon::fromTheme("view-list-tree"), i18n("Project Explorer"), docksActions);
  675     m_toggleProjectExplorerDockAction->setCheckable(true);
  676     m_toggleProjectExplorerDockAction->setChecked(true);
  677     actionCollection()->addAction("toggle_project_explorer_dock", m_toggleProjectExplorerDockAction);
  678 
  679     m_togglePropertiesDockAction = new QAction(QIcon::fromTheme("view-list-details"), i18n("Properties Explorer"), docksActions);
  680     m_togglePropertiesDockAction->setCheckable(true);
  681     m_togglePropertiesDockAction->setChecked(true);
  682     actionCollection()->addAction("toggle_properties_explorer_dock", m_togglePropertiesDockAction);
  683 
  684     connect(docksActions, &QActionGroup::triggered, this, &MainWin::toggleDockWidget);
  685 
  686     //global search
  687     QAction* searchAction = new QAction(actionCollection());
  688     searchAction->setShortcut(QKeySequence::Find);
  689     connect(searchAction, &QAction::triggered, this, [=]() {
  690         if (m_project) {
  691             if (!m_projectExplorerDock->isVisible()) {
  692                 m_toggleProjectExplorerDockAction->setChecked(true);
  693                 toggleDockWidget(m_toggleProjectExplorerDockAction);
  694             }
  695             m_projectExplorer->search();
  696         }
  697     });
  698     this->addAction(searchAction);
  699 }
  700 
  701 void MainWin::initMenus() {
  702     //add the actions to toggle the status bar and the project and properties explorer widgets to the "View" menu.
  703     //this menu is created automatically when the default "full screen" action is created in initActions().
  704     auto* menu = dynamic_cast<QMenu*>(factory()->container("view", this));
  705 
  706     if (menu) {
  707         menu->addSeparator();
  708         menu->addAction(m_toggleStatusBarAction);
  709         menu->addSeparator();
  710         menu->addAction(m_toggleMemoryInfoAction);
  711         menu->addSeparator();
  712         menu->addAction(m_toggleProjectExplorerDockAction);
  713         menu->addAction(m_togglePropertiesDockAction);
  714     }
  715 
  716     //menu in the main toolbar for adding new aspects
  717     menu = dynamic_cast<QMenu*>(factory()->container("new", this));
  718     menu->setIcon(QIcon::fromTheme("window-new"));
  719 
  720     //menu in the project explorer and in the toolbar for adding new aspects
  721     m_newMenu = new QMenu(i18n("Add New"), this);
  722     m_newMenu->setIcon(QIcon::fromTheme("window-new"));
  723     m_newMenu->addAction(m_newFolderAction);
  724     m_newMenu->addAction(m_newWorkbookAction);
  725     m_newMenu->addAction(m_newSpreadsheetAction);
  726     m_newMenu->addAction(m_newMatrixAction);
  727     m_newMenu->addAction(m_newWorksheetAction);
  728     m_newMenu->addAction(m_newNotesAction);
  729     m_newMenu->addAction(m_newDatapickerAction);
  730     m_newMenu->addSeparator();
  731     m_newMenu->addAction(m_newLiveDataSourceAction);
  732 
  733     //import menu
  734     m_importMenu = new QMenu(this);
  735     m_importMenu->setIcon(QIcon::fromTheme("document-import"));
  736     m_importMenu ->addAction(m_importFileAction);
  737     m_importMenu ->addAction(m_importSqlAction);
  738     m_importMenu->addAction(m_importDatasetAction);
  739     m_importMenu->addSeparator();
  740     m_importMenu->addAction(m_importLabPlotAction);
  741 #ifdef HAVE_LIBORIGIN
  742     m_importMenu ->addAction(m_importOpjAction);
  743 #endif
  744 
  745 #ifdef HAVE_CANTOR_LIBS
  746     m_newMenu->addSeparator();
  747     m_newCantorWorksheetMenu = new QMenu(i18n("CAS Worksheet"), this);
  748     m_newCantorWorksheetMenu->setIcon(QIcon::fromTheme("archive-insert"));
  749 
  750     //"Adding Cantor backends to menu and context menu"
  751     QStringList m_availableBackend = Cantor::Backend::listAvailableBackends();
  752     if (m_availableBackend.count() > 0) {
  753         unplugActionList(QLatin1String("backends_list"));
  754         QList<QAction*> newBackendActions;
  755         for (auto* backend : Cantor::Backend::availableBackends()) {
  756             if (!backend->isEnabled()) continue;
  757             QAction* action = new QAction(QIcon::fromTheme(backend->icon()), backend->name(), this);
  758             action->setData(backend->name());
  759             newBackendActions << action;
  760             m_newCantorWorksheetMenu->addAction(action);
  761         }
  762 
  763         connect(m_newCantorWorksheetMenu, &QMenu::triggered, this, &MainWin::newCantorWorksheet);
  764         plugActionList(QLatin1String("backends_list"), newBackendActions);
  765     }
  766     m_newMenu->addMenu(m_newCantorWorksheetMenu);
  767 #else
  768     delete this->guiFactory()->container("cas_worksheet", this);
  769     delete this->guiFactory()->container("new_cas_worksheet", this);
  770     delete this->guiFactory()->container("cas_worksheet_toolbar", this);
  771 #endif
  772 
  773     //menu subwindow visibility policy
  774     m_visibilityMenu = new QMenu(i18n("Window Visibility Policy"), this);
  775     m_visibilityMenu->setIcon(QIcon::fromTheme("window-duplicate"));
  776     m_visibilityMenu ->addAction(m_visibilityFolderAction);
  777     m_visibilityMenu ->addAction(m_visibilitySubfolderAction);
  778     m_visibilityMenu ->addAction(m_visibilityAllAction);
  779 
  780     //menu for editing files
  781     m_editMenu = new QMenu(i18n("Edit"), this);
  782     m_editMenu->addAction(m_editFitsFileAction);
  783 
  784     //set the action for the current color scheme checked
  785     KConfigGroup group = KSharedConfig::openConfig()->group(QLatin1String("Settings_General"));
  786 #if KCONFIGWIDGETS_VERSION >= QT_VERSION_CHECK(5, 67, 0)    // KColorSchemeManager has a system default option
  787     QString schemeName = group.readEntry("ColorScheme");
  788 #else
  789     KConfigGroup generalGlobalsGroup = KSharedConfig::openConfig(QLatin1String("kdeglobals"))->group("General");
  790     QString defaultSchemeName = generalGlobalsGroup.readEntry("ColorScheme", QStringLiteral("Breeze"));
  791     QString schemeName = group.readEntry("ColorScheme", defaultSchemeName);
  792 #endif
  793     KActionMenu* schemesMenu = m_schemeManager->createSchemeSelectionMenu(i18n("Color Scheme"), schemeName, this);
  794     schemesMenu->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-color")));
  795 
  796     QMenu* settingsMenu = dynamic_cast<QMenu*>(factory()->container("settings", this));
  797     if (settingsMenu)
  798         settingsMenu->insertMenu(settingsMenu->actions().constFirst(), schemesMenu->menu());
  799 
  800     connect(schemesMenu->menu(), &QMenu::triggered, this, &MainWin::colorSchemeChanged);
  801 
  802 #ifdef HAVE_CANTOR_LIBS
  803     QAction* action = new QAction(QIcon::fromTheme(QLatin1String("cantor")), i18n("Configure CAS"), this);
  804     connect(action, &QAction::triggered, this, &MainWin::cantorSettingsDialog);
  805     action->setMenuRole(QAction::NoRole);   // prevent macOS Qt heuristics to select this action for preferences
  806     if (settingsMenu)
  807         settingsMenu->addAction(action);
  808 #endif
  809 }
  810 
  811 void MainWin::colorSchemeChanged(QAction* action) {
  812     QString schemeName = KLocalizedString::removeAcceleratorMarker(action->text());
  813 
  814     //background of the mdi area is not updated on theme changes, do it here.
  815     QModelIndex index = m_schemeManager->indexForScheme(schemeName);
  816     const QPalette& palette = KColorScheme::createApplicationPalette( KSharedConfig::openConfig(index.data(Qt::UserRole).toString()) );
  817     const QBrush& brush = palette.brush(QPalette::Dark);
  818     m_mdiArea->setBackground(brush);
  819 
  820     //save the selected color scheme
  821     KConfigGroup group = KSharedConfig::openConfig()->group(QLatin1String("Settings_General"));
  822     group.writeEntry("ColorScheme", schemeName);
  823     group.sync();
  824 }
  825 
  826 /*!
  827     Asks to save the project if it was modified.
  828     \return \c true if the project still needs to be saved ("cancel" clicked), \c false otherwise.
  829  */
  830 bool MainWin::warnModified() {
  831     if (m_project->hasChanged()) {
  832         int want_save = KMessageBox::warningYesNoCancel( this,
  833             i18n("The current project %1 has been modified. Do you want to save it?", m_project->name()),
  834             i18n("Save Project"));
  835         switch (want_save) {
  836         case KMessageBox::Yes:
  837             return !saveProject();
  838         case KMessageBox::No:
  839             break;
  840         case KMessageBox::Cancel:
  841             return true;
  842         }
  843     }
  844 
  845     return false;
  846 }
  847 
  848 /*!
  849  * updates the state of actions, menus and toolbars (enabled or disabled)
  850  * on project changes (project closes and opens)
  851  */
  852 void MainWin::updateGUIOnProjectChanges() {
  853     if (m_closing)
  854         return;
  855 
  856     KXMLGUIFactory* factory = this->guiFactory();
  857     if (factory->container("worksheet", this) == nullptr) {
  858         //no worksheet menu found, most probably labplot2ui.rc
  859         //was not properly installed -> return here in order not to crash
  860         return;
  861     }
  862 
  863     //disable all menus if there is no project
  864     bool hasProject = (m_project != nullptr);
  865     m_saveAction->setEnabled(hasProject);
  866     m_saveAsAction->setEnabled(hasProject);
  867     m_printAction->setEnabled(hasProject);
  868     m_printPreviewAction->setEnabled(hasProject);
  869     m_importFileAction->setEnabled(hasProject);
  870     m_importSqlAction->setEnabled(hasProject);
  871 #ifdef HAVE_LIBORIGIN
  872     m_importOpjAction->setEnabled(hasProject);
  873 #endif
  874     m_exportAction->setEnabled(hasProject);
  875     m_newWorkbookAction->setEnabled(hasProject);
  876     m_newSpreadsheetAction->setEnabled(hasProject);
  877     m_newMatrixAction->setEnabled(hasProject);
  878     m_newWorksheetAction->setEnabled(hasProject);
  879     m_newDatapickerAction->setEnabled(hasProject);
  880     m_closeAction->setEnabled(hasProject);
  881     m_toggleProjectExplorerDockAction->setEnabled(hasProject);
  882     m_togglePropertiesDockAction->setEnabled(hasProject);
  883 
  884     if (!m_mdiArea || !m_mdiArea->currentSubWindow()) {
  885         factory->container("spreadsheet", this)->setEnabled(false);
  886         factory->container("matrix", this)->setEnabled(false);
  887         factory->container("worksheet", this)->setEnabled(false);
  888         factory->container("analysis", this)->setEnabled(false);
  889         factory->container("datapicker", this)->setEnabled(false);
  890         factory->container("spreadsheet_toolbar", this)->hide();
  891         factory->container("worksheet_toolbar", this)->hide();
  892         factory->container("cartesian_plot_toolbar", this)->hide();
  893 //      factory->container("histogram_toolbar",this)->hide();
  894 //      factory->container("barchart_toolbar",this)->hide();
  895         factory->container("datapicker_toolbar", this)->hide();
  896 #ifdef HAVE_CANTOR_LIBS
  897         factory->container("cas_worksheet", this)->setEnabled(false);
  898         factory->container("cas_worksheet_toolbar", this)->hide();
  899 #endif
  900     }
  901 
  902     factory->container("new", this)->setEnabled(hasProject);
  903     factory->container("edit", this)->setEnabled(hasProject);
  904     factory->container("import", this)->setEnabled(hasProject);
  905 
  906     updateTitleBar();
  907 
  908 #ifdef Q_OS_MAC
  909     m_touchBar->clear();
  910 
  911     if (!hasProject) {
  912         m_touchBar->addAction(m_newProjectAction);
  913         m_touchBar->addAction(m_openProjectAction);
  914     } else {
  915         m_touchBar->addAction(m_importFileAction);
  916         m_touchBar->addAction(m_newWorksheetAction);
  917         m_touchBar->addAction(m_newSpreadsheetAction);
  918         m_touchBar->addAction(m_newMatrixAction);
  919     }
  920 #endif
  921     // undo/redo actions are disabled in both cases - when the project is closed or opened
  922     m_undoAction->setEnabled(false);
  923     m_redoAction->setEnabled(false);
  924 }
  925 
  926 /*
  927  * updates the state of actions, menus and toolbars (enabled or disabled)
  928  * depending on the currently active window (worksheet or spreadsheet).
  929  */
  930 void MainWin::updateGUI() {
  931     if (m_project == nullptr || m_project->isLoading())
  932         return;
  933 
  934     if (m_closing || m_projectClosing)
  935         return;
  936 
  937     KXMLGUIFactory* factory = this->guiFactory();
  938     if (factory->container("worksheet", this) == nullptr) {
  939         //no worksheet menu found, most probably labplot2ui.rc
  940         //was not properly installed -> return here in order not to crash
  941         return;
  942     }
  943 
  944     //reset the touchbar
  945 #ifdef Q_OS_MAC
  946     m_touchBar->clear();
  947 
  948     m_touchBar->addAction(m_undoIconOnlyAction);
  949     m_touchBar->addAction(m_redoIconOnlyAction);
  950     m_touchBar->addSeparator();
  951 #endif
  952 
  953     if (!m_mdiArea || !m_mdiArea->currentSubWindow()) {
  954         factory->container("spreadsheet", this)->setEnabled(false);
  955         factory->container("matrix", this)->setEnabled(false);
  956         factory->container("worksheet", this)->setEnabled(false);
  957         factory->container("analysis", this)->setEnabled(false);
  958         factory->container("datapicker", this)->setEnabled(false);
  959         factory->container("spreadsheet_toolbar", this)->hide();
  960         factory->container("worksheet_toolbar", this)->hide();
  961 //      factory->container("histogram_toolbar",this)->hide();
  962 //      factory->container("barchart_toolbar",this)->hide();
  963         factory->container("cartesian_plot_toolbar", this)->hide();
  964         factory->container("datapicker_toolbar", this)->hide();
  965 #ifdef HAVE_CANTOR_LIBS
  966         factory->container("cas_worksheet", this)->setEnabled(false);
  967         factory->container("cas_worksheet_toolbar", this)->hide();
  968 #endif
  969         return;
  970     }
  971 
  972 
  973 #ifdef Q_OS_MAC
  974     if (dynamic_cast<Folder*>(m_currentAspect)) {
  975         m_touchBar->addAction(m_newWorksheetAction);
  976         m_touchBar->addAction(m_newSpreadsheetAction);
  977         m_touchBar->addAction(m_newMatrixAction);
  978     }
  979 #endif
  980 
  981     Q_ASSERT(m_currentAspect);
  982 
  983     //Handle the Worksheet-object
  984     const Worksheet* w = dynamic_cast<Worksheet*>(m_currentAspect);
  985     if (!w)
  986         w = dynamic_cast<Worksheet*>(m_currentAspect->parent(AspectType::Worksheet));
  987     if (w) {
  988         //populate worksheet menu
  989         auto* view = qobject_cast<WorksheetView*>(w->view());
  990         auto* menu = qobject_cast<QMenu*>(factory->container("worksheet", this));
  991         menu->clear();
  992         view->createContextMenu(menu);
  993         menu->setEnabled(true);
  994 
  995         //populate analysis menu
  996         menu = qobject_cast<QMenu*>(factory->container("analysis", this));
  997         menu->clear();
  998         view->createAnalysisMenu(menu);
  999         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