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