File indexing completed on 2024-05-12 03:48:24

0001 /*
0002     File                 : CantorWorksheetView.cpp
0003     Project              : LabPlot
0004     Description          : View class for CantorWorksheet
0005     --------------------------------------------------------------------
0006     SPDX-FileCopyrightText: 2015 Garvit Khatri <garvitdelhi@gmail.com>
0007     SPDX-FileCopyrightText: 2016-2023 Alexander Semke <alexander.semke@web.de>
0008     SPDX-License-Identifier: GPL-2.0-or-later
0009 */
0010 
0011 #include "CantorWorksheetView.h"
0012 #include "backend/cantorWorksheet/CantorWorksheet.h"
0013 #include "backend/core/column/Column.h"
0014 #include "kdefrontend/spreadsheet/PlotDataDialog.h"
0015 #include "kdefrontend/spreadsheet/StatisticsDialog.h"
0016 
0017 #include <QHBoxLayout>
0018 #include <QLabel>
0019 #include <QMenu>
0020 #include <QTimer>
0021 #include <QToolBar>
0022 
0023 #include <KLocalizedString>
0024 #include <KParts/ReadWritePart>
0025 #include <KToggleAction>
0026 
0027 CantorWorksheetView::CantorWorksheetView(CantorWorksheet* worksheet)
0028     : QWidget()
0029     , m_worksheet(worksheet) {
0030     auto* layout = new QHBoxLayout(this);
0031     layout->setContentsMargins(0, 0, 0, 0);
0032     m_part = worksheet->part();
0033     if (m_part) {
0034         layout->addWidget(m_part->widget());
0035         initActions();
0036         connect(m_worksheet, &CantorWorksheet::requestProjectContextMenu, this, &CantorWorksheetView::createContextMenu);
0037         connect(m_worksheet, &CantorWorksheet::statusChanged, this, &CantorWorksheetView::statusChanged);
0038     } else {
0039         QString msg = QStringLiteral("<b>") + i18n("Failed to initialize %1.", m_worksheet->backendName()) + QStringLiteral("</b><br>");
0040         msg += worksheet->error();
0041         QLabel* label = new QLabel(msg);
0042         label->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
0043         layout->addWidget(label);
0044     }
0045 }
0046 
0047 void CantorWorksheetView::initActions() {
0048     m_actionGroup = new QActionGroup(this);
0049     m_actionGroup->setExclusive(false);
0050 
0051     // general notebook specific actions
0052     m_zoomIn = new QAction(QIcon::fromTheme(QLatin1String("zoom-in")), i18n("Zoom In"), m_actionGroup);
0053     m_zoomIn->setData(QStringLiteral("view_zoom_in"));
0054     m_zoomIn->setShortcut(Qt::CTRL + Qt::Key_Plus);
0055 
0056     m_zoomOut = new QAction(QIcon::fromTheme(QLatin1String("zoom-out")), i18n("Zoom Out"), m_actionGroup);
0057     m_zoomOut->setData(QStringLiteral("view_zoom_out"));
0058     m_zoomOut->setShortcut(Qt::CTRL + Qt::Key_Minus);
0059 
0060     m_find = new QAction(QIcon::fromTheme(QLatin1String("edit-find")), i18n("Find"), m_actionGroup);
0061     m_find->setData(QStringLiteral("edit_find"));
0062     m_find->setShortcut(Qt::CTRL + Qt::Key_F);
0063 
0064     m_replace = new QAction(QIcon::fromTheme(QLatin1String("edit-find-replace")), i18n("Replace"), m_actionGroup);
0065     m_replace->setData(QStringLiteral("edit_replace"));
0066     m_replace->setShortcut(Qt::CTRL + Qt::Key_R);
0067 
0068     m_restartBackendAction = new QAction(QIcon::fromTheme(QLatin1String("system-reboot")), i18n("Restart Backend"), m_actionGroup);
0069     m_restartBackendAction->setData(QStringLiteral("restart_backend"));
0070 
0071     m_evaluateWorsheetAction = new QAction(QIcon::fromTheme(QLatin1String("system-run")), i18n("Evaluate Notebook"), m_actionGroup);
0072     m_evaluateWorsheetAction->setData(QStringLiteral("evaluate_worksheet"));
0073 
0074     // all other actions are initialized in initMenus() since they are only required for the main and context menu
0075 
0076     connect(m_actionGroup, &QActionGroup::triggered, this, &CantorWorksheetView::triggerAction);
0077 }
0078 
0079 void CantorWorksheetView::initMenus() {
0080     // initialize the remaining actions
0081 
0082     // entry specific actions
0083     m_evaluateEntryAction = new QAction(QIcon::fromTheme(QLatin1String("media-playback-start")), i18n("Evaluate Entry"), m_actionGroup);
0084     m_evaluateEntryAction->setShortcut(Qt::SHIFT + Qt::Key_Return);
0085     m_evaluateEntryAction->setData(QStringLiteral("evaluate_current"));
0086 
0087     m_removeCurrentEntryAction = new QAction(QIcon::fromTheme(QLatin1String("edit-delete")), i18n("Remove Current Entry"), m_actionGroup);
0088     m_removeCurrentEntryAction->setData(QStringLiteral("remove_current"));
0089 
0090     // actions for the "Add New" menu
0091     auto* insertCommandEntryAction = new QAction(QIcon::fromTheme(QLatin1String("run-build")), i18n("Command"), m_actionGroup);
0092     insertCommandEntryAction->setData(QStringLiteral("insert_command_entry"));
0093     insertCommandEntryAction->setShortcut(Qt::CTRL + Qt::Key_Return);
0094 
0095     auto* insertTextEntryAction = new QAction(QIcon::fromTheme(QLatin1String("draw-text")), i18n("Text"), m_actionGroup);
0096     insertTextEntryAction->setData(QStringLiteral("insert_text_entry"));
0097 
0098     // markdown entry is only available if cantor was compiled with libdiscovery (cantor 18.12 and later)
0099     QAction* insertMarkdownEntryAction = nullptr;
0100     if (m_part->action("insert_markdown_entry")) {
0101         insertMarkdownEntryAction = new QAction(QIcon::fromTheme(QLatin1String("text-x-markdown")), i18n("Markdown"), m_actionGroup);
0102         insertMarkdownEntryAction->setData(QStringLiteral("insert_markdown_entry"));
0103     }
0104 
0105     auto* insertLatexEntryAction = new QAction(QIcon::fromTheme(QLatin1String("text-x-tex")), i18n("LaTeX"), m_actionGroup);
0106     insertLatexEntryAction->setData(QStringLiteral("insert_latex_entry"));
0107 
0108     auto* insertImageEntryAction = new QAction(QIcon::fromTheme(QLatin1String("image-x-generic")), i18n("Image"), m_actionGroup);
0109     insertImageEntryAction->setData(QStringLiteral("insert_image_entry"));
0110 
0111     auto* insertPageBreakAction = new QAction(QIcon::fromTheme(QLatin1String("go-next-view-page")), i18n("Page Break"), m_actionGroup);
0112     insertPageBreakAction->setData(QStringLiteral("insert_page_break_entry"));
0113 
0114     //  auto* insertHorizLineAction = new QAction(QIcon::fromTheme(QLatin1String("newline")), i18n("Horizontal Line"), m_actionGroup);
0115     //  insertHorizLineAction->setData(QStringLiteral("insert_horizontal_line_entry"));
0116 
0117     //  auto* insertHierarchyEntryAction = new QAction(QIcon::fromTheme(QLatin1String("view-list-tree")), i18n("Hierarchy"), m_actionGroup);
0118     //  insertHierarchyEntryAction->setData(QStringLiteral("insert_hierarchy_entry"));
0119 
0120     // actions for "assistants", that are backend specific and not always available
0121     QAction* computeEigenvectorsAction = nullptr;
0122     if (m_part->action("eigenvectors_assistant")) {
0123         computeEigenvectorsAction = new QAction(i18n("Compute Eigenvectors"), m_actionGroup);
0124         computeEigenvectorsAction->setData(QStringLiteral("eigenvectors_assistant"));
0125     }
0126 
0127     QAction* createMatrixAction = nullptr;
0128     if (m_part->action("creatematrix_assistant")) {
0129         createMatrixAction = new QAction(i18n("Create Matrix"), m_actionGroup);
0130         createMatrixAction->setData(QStringLiteral("creatematrix_assistant"));
0131     }
0132 
0133     QAction* computeEigenvaluesAction = nullptr;
0134     if (m_part->action("eigenvalues_assistant")) {
0135         computeEigenvaluesAction = new QAction(i18n("Compute Eigenvalues"), m_actionGroup);
0136         computeEigenvaluesAction->setData(QStringLiteral("eigenvalues_assistant"));
0137     }
0138 
0139     QAction* invertMatrixAction = nullptr;
0140     if (m_part->action("invertmatrix_assistant")) {
0141         invertMatrixAction = new QAction(i18n("Invert Matrix"), m_actionGroup);
0142         invertMatrixAction->setData(QStringLiteral("invertmatrix_assistant"));
0143     }
0144 
0145     QAction* differentiationAction = nullptr;
0146     if (m_part->action("differentiate_assistant")) {
0147         differentiationAction = new QAction(i18n("Differentiation"), m_actionGroup);
0148         differentiationAction->setData(QStringLiteral("differentiate_assistant"));
0149     }
0150 
0151     QAction* integrationAction = nullptr;
0152     if (m_part->action("integrate_assistant")) {
0153         integrationAction = new QAction(i18n("Integration"), m_actionGroup);
0154         integrationAction->setData(QStringLiteral("integrate_assistant"));
0155     }
0156 
0157     QAction* solveEquationsAction = nullptr;
0158     if (m_part->action("solve_assistant")) {
0159         solveEquationsAction = new QAction(i18n("Solve Equations"), m_actionGroup);
0160         solveEquationsAction->setData(QStringLiteral("solve_assistant"));
0161     }
0162 
0163     // menus
0164 
0165     //"Add New"
0166     m_addNewMenu = new QMenu(i18n("Add New"), m_part->widget());
0167     m_addNewMenu->setIcon(QIcon::fromTheme(QLatin1String("list-add")));
0168 
0169     m_addNewMenu->addAction(insertCommandEntryAction);
0170     m_addNewMenu->addAction(insertTextEntryAction);
0171     if (insertMarkdownEntryAction)
0172         m_addNewMenu->addAction(insertMarkdownEntryAction);
0173     m_addNewMenu->addAction(insertLatexEntryAction);
0174     m_addNewMenu->addAction(insertImageEntryAction);
0175     m_addNewMenu->addSeparator();
0176     m_addNewMenu->addAction(insertPageBreakAction);
0177     //  m_addNewMenu->addAction(insertHorizLineAction);
0178     //  m_addNewMenu->addAction(insertHierarchyEntryAction);
0179 
0180     //"Assistants"
0181     if (invertMatrixAction || createMatrixAction || computeEigenvectorsAction || computeEigenvaluesAction) {
0182         m_linearAlgebraMenu = new QMenu(i18n("Linear Algebra"), m_part->widget());
0183         if (invertMatrixAction)
0184             m_linearAlgebraMenu->addAction(invertMatrixAction);
0185         if (createMatrixAction)
0186             m_linearAlgebraMenu->addAction(createMatrixAction);
0187         if (computeEigenvectorsAction)
0188             m_linearAlgebraMenu->addAction(computeEigenvectorsAction);
0189         if (computeEigenvaluesAction)
0190             m_linearAlgebraMenu->addAction(computeEigenvaluesAction);
0191     }
0192 
0193     if (solveEquationsAction || integrationAction || differentiationAction) {
0194         m_calculateMenu = new QMenu(i18n("Calculate"), m_part->widget());
0195         if (solveEquationsAction)
0196             m_calculateMenu->addAction(solveEquationsAction);
0197         if (integrationAction)
0198             m_calculateMenu->addAction(integrationAction);
0199         if (differentiationAction)
0200             m_calculateMenu->addAction(differentiationAction);
0201     }
0202 
0203     //"Notebook Settings"
0204     m_settingsMenu = new QMenu(i18n("Settings"), m_part->widget());
0205     m_settingsMenu->setIcon(QIcon::fromTheme(QLatin1String("settings-configure")));
0206     m_settingsMenu->addAction(m_part->action("enable_expression_numbers"));
0207     m_settingsMenu->addAction(m_part->action("enable_highlighting"));
0208     m_settingsMenu->addAction(m_part->action("enable_completion"));
0209     m_settingsMenu->addAction(m_part->action("enable_animations"));
0210     m_settingsMenu->addSeparator();
0211     m_settingsMenu->addAction(m_part->action("enable_typesetting"));
0212 }
0213 
0214 /*!
0215  * Populates the menu \c menu with the CantorWorksheet and CantorWorksheet view relevant actions.
0216  * The menu is used
0217  *   - as the context menu in CantorWorksheetView
0218  *   - as the "CantorWorksheet menu" in the main menu-bar (called form MainWin)
0219  *   - as a part of the CantorWorksheet context menu in project explorer
0220  */
0221 void CantorWorksheetView::createContextMenu(QMenu* menu) {
0222     Q_ASSERT(menu);
0223     if (!m_part)
0224         return;
0225 
0226     QAction* firstAction = nullptr;
0227     // if we're populating the context menu for the project explorer, then
0228     // there're already actions available there. Skip the first title-action
0229     // and insert the action at the beginning of the menu.
0230     if (menu->actions().size() > 1)
0231         firstAction = menu->actions().at(1);
0232 
0233     if (!m_addNewMenu)
0234         initMenus();
0235 
0236     menu->insertAction(firstAction, m_evaluateWorsheetAction);
0237     menu->insertSeparator(firstAction);
0238     menu->insertAction(firstAction, m_evaluateEntryAction);
0239     menu->addSeparator();
0240     menu->insertMenu(firstAction, m_addNewMenu);
0241     menu->insertSeparator(firstAction);
0242     menu->insertAction(firstAction, m_removeCurrentEntryAction);
0243 
0244     // results related actions
0245     menu->insertSeparator(firstAction);
0246     menu->addAction(m_part->action("all_entries_collapse_results"));
0247     menu->addAction(m_part->action("all_entries_uncollapse_results"));
0248     menu->addAction(m_part->action("all_entries_remove_all_results"));
0249 
0250     // assistants, if available
0251     if (m_linearAlgebraMenu || m_calculateMenu) {
0252         menu->insertSeparator(firstAction);
0253         auto* menuAssistants = new QMenu(i18n("Assistants"), m_part->widget());
0254         menuAssistants->setIcon(QIcon::fromTheme(QLatin1String("quickwizard")));
0255         if (m_linearAlgebraMenu)
0256             menuAssistants->addMenu(m_linearAlgebraMenu);
0257         if (m_calculateMenu)
0258             menuAssistants->addMenu(m_calculateMenu);
0259 
0260         menu->insertMenu(firstAction, menuAssistants);
0261     }
0262 
0263     menu->insertSeparator(firstAction);
0264     menu->insertAction(firstAction, m_zoomIn);
0265     menu->insertAction(firstAction, m_zoomOut);
0266     menu->insertSeparator(firstAction);
0267     menu->insertAction(firstAction, m_find);
0268     menu->insertAction(firstAction, m_replace);
0269     menu->insertSeparator(firstAction);
0270     menu->insertMenu(firstAction, m_settingsMenu);
0271     menu->insertSeparator(firstAction);
0272     menu->insertAction(firstAction, m_restartBackendAction);
0273     menu->insertSeparator(firstAction);
0274 }
0275 
0276 /*!
0277  * adds column specific actions in SpreadsheetView to the context menu shown in the project explorer.
0278  */
0279 void CantorWorksheetView::fillColumnContextMenu(QMenu* menu, Column* column) {
0280     if (!column)
0281         return; // should never happen, since the sender is always a Column
0282 
0283     m_contextMenuColumn = column;
0284 
0285     if (!m_plotDataMenu) {
0286         auto* plotDataActionGroup = new QActionGroup(this);
0287         connect(plotDataActionGroup, &QActionGroup::triggered, this, &CantorWorksheetView::plotData);
0288         m_plotDataMenu = new QMenu(i18n("Plot Data"), this);
0289         PlotDataDialog::fillMenu(m_plotDataMenu, plotDataActionGroup);
0290 
0291         m_statisticsAction = new QAction(QIcon::fromTheme(QStringLiteral("view-statistics")), i18n("Variable Statistics..."), this);
0292         connect(m_statisticsAction, &QAction::triggered, this, &CantorWorksheetView::showStatistics);
0293     }
0294 
0295     const bool hasValues = column->hasValues();
0296     const bool plottable = column->isPlottable();
0297 
0298     QAction* firstAction = menu->actions().at(1);
0299     menu->insertMenu(firstAction, m_plotDataMenu);
0300     menu->insertSeparator(firstAction);
0301     m_plotDataMenu->setEnabled(plottable && hasValues);
0302 
0303     menu->insertSeparator(firstAction);
0304     menu->insertAction(firstAction, m_statisticsAction);
0305     m_statisticsAction->setEnabled(hasValues);
0306 }
0307 
0308 void CantorWorksheetView::fillToolBar(QToolBar* toolbar) {
0309     if (!m_part)
0310         return;
0311     toolbar->addAction(m_evaluateWorsheetAction);
0312     toolbar->addAction(m_find);
0313     toolbar->addAction(m_zoomIn);
0314     toolbar->addAction(m_zoomOut);
0315     toolbar->addSeparator();
0316     toolbar->addAction(m_restartBackendAction);
0317 }
0318 
0319 /*!
0320  * Slot for actions triggered
0321  */
0322 void CantorWorksheetView::triggerAction(QAction* action) {
0323     const auto& name = action->data().toString();
0324     if (!name.isEmpty()) {
0325         auto* action = m_part->action(name.toStdString().c_str());
0326         if (action)
0327             action->trigger();
0328     }
0329 }
0330 
0331 CantorWorksheetView::~CantorWorksheetView() {
0332     if (m_part)
0333         m_part->widget()->setParent(nullptr);
0334 }
0335 
0336 void CantorWorksheetView::statusChanged(Cantor::Session::Status status) {
0337     if (status == Cantor::Session::Running) {
0338         m_evaluateWorsheetAction->setText(i18n("Interrupt"));
0339         m_evaluateWorsheetAction->setIcon(QIcon::fromTheme(QLatin1String("dialog-close")));
0340         Q_EMIT m_worksheet->statusInfo(i18n("Calculating..."));
0341     } else {
0342         m_evaluateWorsheetAction->setText(i18n("Evaluate Notebook"));
0343         m_evaluateWorsheetAction->setIcon(QIcon::fromTheme(QLatin1String("system-run")));
0344         Q_EMIT m_worksheet->statusInfo(i18n("Ready"));
0345     }
0346 }
0347 
0348 void CantorWorksheetView::plotData(QAction* action) {
0349     if (!m_contextMenuColumn)
0350         return;
0351 
0352     auto type = static_cast<PlotDataDialog::PlotType>(action->data().toInt());
0353     auto* dlg = new PlotDataDialog(m_worksheet, type);
0354     dlg->setSelectedColumns(QVector<Column*>({m_contextMenuColumn}));
0355     dlg->exec();
0356 }
0357 
0358 void CantorWorksheetView::showStatistics() {
0359     if (!m_contextMenuColumn)
0360         return;
0361 
0362     QString dlgTitle(i18n("%1: variable statistics", m_contextMenuColumn->name()));
0363     auto* dlg = new StatisticsDialog(dlgTitle, QVector<Column*>({m_contextMenuColumn}));
0364     dlg->setModal(true);
0365     dlg->show();
0366     QApplication::processEvents(QEventLoop::AllEvents, 0);
0367     QTimer::singleShot(0, this, [=]() {
0368         dlg->showStatistics();
0369     });
0370 }