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