File indexing completed on 2024-05-05 11:56:14

0001 /*
0002     SPDX-License-Identifier: GPL-2.0-or-later
0003     SPDX-FileCopyrightText: 2009 Alexander Rieder <alexanderrieder@gmail.com>
0004     SPDX-FileCopyrightText: 2017-2022 Alexander Semke <alexander.semke@web.de>
0005     SPDX-FileCopyrightText: 2019 Sirgienko Nikita <warquark@gmail.com>
0006 */
0007 
0008 #include <config-cantor.h>
0009 
0010 #include <array>
0011 #include <cmath>
0012 
0013 #include "cantor_part.h"
0014 #include "lib/assistant.h"
0015 #include "lib/backend.h"
0016 #include "lib/extension.h"
0017 #include "lib/worksheetaccess.h"
0018 #include "scripteditor/scripteditorwidget.h"
0019 #include "searchbar.h"
0020 #include "settings.h"
0021 #include "worksheet.h"
0022 #include "worksheetview.h"
0023 
0024 #include <KAboutData>
0025 #include <KActionCollection>
0026 #include <KLocalizedString>
0027 #include <KMessageBox>
0028 #include <KNS3/UploadDialog>
0029 #include <KParts/GUIActivateEvent>
0030 #include <KPluginFactory>
0031 #include <KIO/OpenUrlJob>
0032 #include <KIO/JobUiDelegate>
0033 #include <KStandardAction>
0034 #include <KToggleAction>
0035 #include <KSelectAction>
0036 #include <KXMLGUIFactory>
0037 #include <KZip>
0038 
0039 #include <QElapsedTimer>
0040 #include <QFile>
0041 #include <QFileDialog>
0042 #include <QIcon>
0043 #include <QPrinter>
0044 #include <QPrintPreviewDialog>
0045 #include <QPrintDialog>
0046 #include <QProgressDialog>
0047 #include <QTextStream>
0048 #include <QTimer>
0049 
0050 //A concrete implementation of the WorksheetAccesssInterface
0051 class WorksheetAccessInterfaceImpl : public Cantor::WorksheetAccessInterface
0052 {
0053 
0054   public:
0055     WorksheetAccessInterfaceImpl(QObject* parent, Worksheet* worksheet) :   WorksheetAccessInterface(parent),  m_worksheet(worksheet)
0056     {
0057         qDebug()<<"new worksheetaccess interface";
0058         connect(worksheet, &Worksheet::modified, this, &WorksheetAccessInterfaceImpl::modified);
0059     }
0060 
0061     ~WorksheetAccessInterfaceImpl() override = default;
0062 
0063     QByteArray saveWorksheetToByteArray() override
0064     {
0065         return m_worksheet->saveToByteArray();
0066     }
0067 
0068     void loadWorksheetFromByteArray(QByteArray* data) override
0069     {
0070         m_worksheet->load(data);
0071     }
0072 
0073     Cantor::Session* session() override
0074     {
0075         return m_worksheet->session();
0076     }
0077 
0078   void evaluate() override
0079     {
0080         m_worksheet->evaluate();
0081     }
0082 
0083     void interrupt() override
0084     {
0085         m_worksheet->interrupt();
0086     }
0087 
0088   private:
0089     Worksheet* m_worksheet;
0090 };
0091 
0092 CantorPart::CantorPart(QWidget* parentWidget, QObject* parent, const QVariantList& args ): KParts::ReadWritePart(parent)
0093 {
0094     QString backendName;
0095     if(!args.isEmpty())
0096         backendName = args.first().toString();
0097 
0098     Cantor::Backend* b = nullptr;
0099     if (!backendName.isEmpty())
0100     {
0101         b = Cantor::Backend::getBackend(backendName);
0102         if (b)
0103             qDebug()<<"Backend "<<b->name()<<" offers extensions: "<<b->extensions();
0104     }
0105 
0106     //central widget
0107     QWidget* widget = new QWidget(parentWidget);
0108     QVBoxLayout* layout = new QVBoxLayout(widget);
0109     m_worksheet = new Worksheet(b, widget);
0110     m_worksheetview = new WorksheetView(m_worksheet, widget);
0111     m_worksheetview->setEnabled(false); //disable input until the session has successfully logged in and emits the ready signal
0112     connect(m_worksheet, &Worksheet::modified, this, static_cast<void (KParts::ReadWritePart::*)()>(&KParts::ReadWritePart::setModified));
0113     connect(m_worksheet, &Worksheet::modified, this, &CantorPart::updateCaption);
0114     connect(m_worksheet, &Worksheet::showHelp, this, &CantorPart::showHelp);
0115     connect(m_worksheet, &Worksheet::loaded, this, &CantorPart::initialized);
0116     connect(m_worksheet, &Worksheet::hierarchyChanged, this, &CantorPart::hierarchyChanged);
0117     connect(m_worksheet, &Worksheet::hierarhyEntryNameChange, this, &CantorPart::hierarhyEntryNameChange);
0118     connect(this, &CantorPart::requestScrollToHierarchyEntry, m_worksheet, &Worksheet::requestScrollToHierarchyEntry);
0119     connect(this, &CantorPart::settingsChanges, m_worksheet, &Worksheet::handleSettingsChanges);
0120     connect(m_worksheet, &Worksheet::requestDocumentation, this, &CantorPart::documentationRequested);
0121 
0122     layout->addWidget(m_worksheetview);
0123     setWidget(widget);
0124 
0125     //create WorksheetAccessInterface, used at the moment by LabPlot only to access Worksheet's API
0126     Cantor::WorksheetAccessInterface* iface = new WorksheetAccessInterfaceImpl(this, m_worksheet);
0127     Q_UNUSED(iface);
0128 
0129     //initialize actions
0130     auto* collection = actionCollection();
0131     connect(collection, &KActionCollection::inserted, m_worksheet, &Worksheet::registerShortcut);
0132     m_worksheet->setActionCollection(collection);
0133 
0134     KStandardAction::saveAs(this, SLOT(fileSaveAs()), collection);
0135     m_save = KStandardAction::save(this, SLOT(save()), collection);
0136     m_save->setPriority(QAction::LowPriority);
0137 
0138     QAction* savePlain = new QAction(QIcon::fromTheme(QLatin1String("document-save")), i18n("Save Plain Text"), collection);
0139     collection->addAction(QLatin1String("file_save_plain"), savePlain);
0140     connect(savePlain, &QAction::triggered, this, &CantorPart::fileSavePlain);
0141 
0142     QAction* undo = KStandardAction::undo(m_worksheet, SIGNAL(undo()), collection);
0143     undo->setPriority(QAction::LowPriority);
0144     connect(m_worksheet, &Worksheet::undoAvailable, undo, &QAction::setEnabled);
0145     m_editActions.push_back(undo);
0146 
0147     QAction* redo = KStandardAction::redo(m_worksheet, SIGNAL(redo()), collection);
0148     redo->setPriority(QAction::LowPriority);
0149     connect(m_worksheet, &Worksheet::redoAvailable, redo, &QAction::setEnabled);
0150     m_editActions.push_back(redo);
0151 
0152     QAction* cut = KStandardAction::cut(m_worksheet, SIGNAL(cut()), collection);
0153     cut->setPriority(QAction::LowPriority);
0154     connect(m_worksheet, &Worksheet::cutAvailable, cut, &QAction::setEnabled);
0155     m_editActions.push_back(cut);
0156 
0157     QAction* copy = KStandardAction::copy(m_worksheet, SIGNAL(copy()), collection);
0158     copy->setPriority(QAction::LowPriority);
0159     connect(m_worksheet, &Worksheet::copyAvailable, copy, &QAction::setEnabled);
0160 
0161     QAction* paste = KStandardAction::paste(m_worksheet, SLOT(paste()), collection);
0162     paste->setPriority(QAction::LowPriority);
0163     connect(m_worksheet, &Worksheet::pasteAvailable, paste, &QAction::setEnabled);
0164     m_editActions.push_back(paste);
0165 
0166     QAction* find = KStandardAction::find(this, SLOT(showSearchBar()), collection);
0167     find->setPriority(QAction::LowPriority);
0168 
0169     QAction* replace = KStandardAction::replace(this, SLOT(showExtendedSearchBar()), collection);
0170     replace->setPriority(QAction::LowPriority);
0171     m_editActions.push_back(replace);
0172 
0173     m_findNext = KStandardAction::findNext(this, SLOT(findNext()), collection);
0174     m_findNext->setEnabled(false);
0175 
0176     m_findPrev = KStandardAction::findPrev(this, SLOT(findPrev()), collection);
0177     m_findPrev->setEnabled(false);
0178 
0179     QAction* pdfExport = new QAction(QIcon::fromTheme(QLatin1String("application-pdf")), i18n("Export to PDF"), collection);
0180     collection->addAction(QLatin1String("file_export_pdf"), pdfExport);
0181     connect(pdfExport, &QAction::triggered, this, &CantorPart::exportToPDF);
0182 
0183     QAction* latexExport = new QAction(QIcon::fromTheme(QLatin1String("text-x-tex")), i18n("Export to LaTeX"), collection);
0184     collection->addAction(QLatin1String("file_export_latex"), latexExport);
0185     connect(latexExport, &QAction::triggered, this, &CantorPart::exportToLatex);
0186 
0187     QAction* print = KStandardAction::print(this, SLOT(print()), collection);
0188     print->setPriority(QAction::LowPriority);
0189 
0190     QAction* printPreview = KStandardAction::printPreview(this, SLOT(printPreview()), collection);
0191     printPreview->setPriority(QAction::LowPriority);
0192 
0193     KStandardAction::zoomIn(m_worksheetview, SLOT(zoomIn()), collection);
0194     KStandardAction::zoomOut(m_worksheetview, SLOT(zoomOut()), collection);
0195     KStandardAction::actualSize(m_worksheetview, SLOT(actualSize()), collection);
0196     connect(m_worksheetview, &WorksheetView::scaleFactorChanged, this, &CantorPart::updateZoomWidgetValue);
0197 
0198     m_evaluate = new QAction(QIcon::fromTheme(QLatin1String("system-run")), i18n("Evaluate Worksheet"), collection);
0199     collection->addAction(QLatin1String("evaluate_worksheet"), m_evaluate);
0200     collection->setDefaultShortcut(m_evaluate, Qt::CTRL+Qt::Key_E);
0201     connect(m_evaluate, &QAction::triggered, this, &CantorPart::evaluateOrInterrupt);
0202     m_editActions.push_back(m_evaluate);
0203 
0204     //
0205     m_zoom = new KSelectAction(QIcon::fromTheme(QLatin1String("page-zoom")), i18n("Zoom"), collection);
0206     connect(m_zoom, static_cast<void (KSelectAction::*)(const QString&)>(&KSelectAction::textTriggered), this, &CantorPart::zoomValueEdited);
0207     static constexpr std::array<double, 8> ZoomValues = {0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 2.0, 4.0};
0208     QStringList zoomNames;
0209     for (double zoomValue : ZoomValues)
0210     {
0211         const QString zoomName = QString::fromStdString(std::to_string(static_cast<int>(zoomValue * 100)));
0212         const QString i18nZoomName = i18nc("Zoom percentage value %1 will be replaced by the actual zoom factor value, so make sure you include it in your translation in order to not to break anything", "%1%", zoomName);
0213         const QRegularExpressionMatch match = m_zoomRegexp.match(i18nZoomName);
0214         if (match.hasMatch() && match.captured(1) == zoomName) {
0215             zoomNames << i18nZoomName;
0216         } else {
0217             qWarning() << "Wrong translation of zoom percentage. Please fill a bug";
0218             zoomNames << QStringLiteral("%1%").arg(zoomName);
0219         }
0220     }
0221     m_zoom->setItems(zoomNames);
0222     m_zoom->setEditable(true);
0223     Q_ASSERT(std::find(ZoomValues.begin(), ZoomValues.end(), 1.0) != ZoomValues.end());
0224     m_zoom->setCurrentItem(std::distance(ZoomValues.begin(), std::find(ZoomValues.begin(), ZoomValues.end(), 1.0)));
0225     collection->addAction(QLatin1String("zoom_selection_action"), m_zoom);
0226 
0227     m_typeset = new KToggleAction(i18n("Typeset using LaTeX"), collection);
0228     m_typeset->setChecked(Settings::self()->typesetDefault());
0229     collection->addAction(QLatin1String("enable_typesetting"), m_typeset);
0230     connect(m_typeset, &KToggleAction::toggled, this, &CantorPart::enableTypesetting);
0231 
0232     m_highlight = new KToggleAction(i18n("Syntax Highlighting"), collection);
0233     m_highlight->setChecked(Settings::self()->highlightDefault());
0234     collection->addAction(QLatin1String("enable_highlighting"), m_highlight);
0235     connect(m_highlight, &KToggleAction::toggled, m_worksheet, &Worksheet::enableHighlighting);
0236 
0237     m_completion = new KToggleAction(i18n("Completion"), collection);
0238     m_completion->setChecked(Settings::self()->completionDefault());
0239     collection->addAction(QLatin1String("enable_completion"), m_completion);
0240     connect(m_completion, &KToggleAction::toggled, m_worksheet, &Worksheet::enableCompletion);
0241 
0242     m_exprNumbering = new KToggleAction(i18n("Line Numbers"), collection);
0243     m_exprNumbering->setChecked(Settings::self()->expressionNumberingDefault());
0244     collection->addAction(QLatin1String("enable_expression_numbers"), m_exprNumbering);
0245     connect(m_exprNumbering, &KToggleAction::toggled, m_worksheet, &Worksheet::enableExpressionNumbering);
0246 
0247     m_animateWorksheet = new KToggleAction(i18n("Animations"), collection);
0248     m_animateWorksheet->setChecked(Settings::self()->animationDefault());
0249     collection->addAction(QLatin1String("enable_animations"), m_animateWorksheet);
0250     connect(m_animateWorksheet, &KToggleAction::toggled, m_worksheet, &Worksheet::enableAnimations);
0251 
0252     if (MathRenderer::mathRenderAvailable())
0253     {
0254         m_embeddedMath= new KToggleAction(i18n("Embedded Math"), collection);
0255         m_embeddedMath->setChecked(Settings::self()->embeddedMathDefault());
0256         collection->addAction(QLatin1String("enable_embedded_math"), m_embeddedMath);
0257         connect(m_embeddedMath, &KToggleAction::toggled, m_worksheet, &Worksheet::enableEmbeddedMath);
0258     }
0259 
0260     m_restart = new QAction(QIcon::fromTheme(QLatin1String("system-reboot")), i18n("Restart Backend"), collection);
0261     collection->addAction(QLatin1String("restart_backend"), m_restart);
0262     connect(m_restart, &QAction::triggered, this, &CantorPart::restartBackend);
0263     m_restart->setEnabled(false); // No need show restart button before login
0264     m_editActions.push_back(m_restart);
0265 
0266     QAction* evaluateCurrent = new QAction(QIcon::fromTheme(QLatin1String("media-playback-start")), i18n("Evaluate Entry"), collection);
0267     collection->addAction(QLatin1String("evaluate_current"),  evaluateCurrent);
0268     collection->setDefaultShortcut(evaluateCurrent, Qt::SHIFT + Qt::Key_Return);
0269     connect(evaluateCurrent, &QAction::triggered, m_worksheet, &Worksheet::evaluateCurrentEntry);
0270     m_editActions.push_back(evaluateCurrent);
0271 
0272     QAction* insertCommandEntry = new QAction(QIcon::fromTheme(QLatin1String("run-build")), i18n("Insert Command Entry"), collection);
0273     collection->addAction(QLatin1String("insert_command_entry"),  insertCommandEntry);
0274     collection->setDefaultShortcut(insertCommandEntry, Qt::CTRL + Qt::Key_Return);
0275     connect(insertCommandEntry, SIGNAL(triggered()), m_worksheet, SLOT(insertCommandEntry()));
0276     m_editActions.push_back(insertCommandEntry);
0277 
0278     QAction* insertTextEntry = new QAction(QIcon::fromTheme(QLatin1String("draw-text")), i18n("Insert Text Entry"), collection);
0279     collection->addAction(QLatin1String("insert_text_entry"),  insertTextEntry);
0280     connect(insertTextEntry, SIGNAL(triggered()), m_worksheet, SLOT(insertTextEntry()));
0281     m_editActions.push_back(insertTextEntry);
0282 
0283 #ifdef Discount_FOUND
0284     QAction* insertMarkdownEntry = new QAction(QIcon::fromTheme(QLatin1String("text-x-markdown")), i18n("Insert Markdown Entry"), collection);
0285     collection->addAction(QLatin1String("insert_markdown_entry"),  insertMarkdownEntry);
0286     connect(insertMarkdownEntry, SIGNAL(triggered()), m_worksheet, SLOT(insertMarkdownEntry()));
0287     m_editActions.push_back(insertMarkdownEntry);
0288 #endif
0289 
0290 #ifdef WITH_EPS
0291     QAction* insertLatexEntry = new QAction(QIcon::fromTheme(QLatin1String("text-x-tex")), i18n("Insert LaTeX Entry"), collection);
0292     collection->addAction(QLatin1String("insert_latex_entry"),  insertLatexEntry);
0293     connect(insertLatexEntry, SIGNAL(triggered()), m_worksheet, SLOT(insertLatexEntry()));
0294     m_editActions.push_back(insertLatexEntry);
0295 #endif
0296 
0297     QAction* insertPageBreakEntry = new QAction(QIcon::fromTheme(QLatin1String("go-next-view-page")), i18n("Insert Page Break"), collection);
0298     collection->addAction(QLatin1String("insert_page_break_entry"), insertPageBreakEntry);
0299     connect(insertPageBreakEntry, SIGNAL(triggered()), m_worksheet, SLOT(insertPageBreakEntry()));
0300     m_editActions.push_back(insertPageBreakEntry);
0301 
0302     QAction* insertImageEntry = new QAction(QIcon::fromTheme(QLatin1String("image-x-generic")), i18n("Insert Image"), collection);
0303     collection->addAction(QLatin1String("insert_image_entry"), insertImageEntry);
0304     connect(insertImageEntry, SIGNAL(triggered()), m_worksheet, SLOT(insertImageEntry()));
0305     m_editActions.push_back(insertImageEntry);
0306 
0307     QAction* collapseAllEntries = new QAction(QIcon(), i18n("Collapse All Results"), collection);
0308     collection->addAction(QLatin1String("all_entries_collapse_results"), collapseAllEntries);
0309     connect(collapseAllEntries, &QAction::triggered, m_worksheet, &Worksheet::collapseAllResults);
0310     m_editActions.push_back(collapseAllEntries);
0311 
0312     QAction* uncollapseAllEntries = new QAction(QIcon(), i18n("Expand All Results"), collection);
0313     collection->addAction(QLatin1String("all_entries_uncollapse_results"), uncollapseAllEntries );
0314     connect(uncollapseAllEntries , &QAction::triggered, m_worksheet, &Worksheet::uncollapseAllResults);
0315     m_editActions.push_back(uncollapseAllEntries);
0316 
0317     QAction* removeAllResults = new QAction(QIcon(), i18n("Remove All Results"), collection);
0318     collection->addAction(QLatin1String("all_entries_remove_all_results"), removeAllResults);
0319     connect(removeAllResults, &QAction::triggered, m_worksheet, &Worksheet::removeAllResults);
0320     m_editActions.push_back(removeAllResults);
0321 
0322     QAction* removeCurrent = new QAction(QIcon::fromTheme(QLatin1String("edit-delete")), i18n("Remove current Entry"), collection);
0323     collection->addAction(QLatin1String("remove_current"), removeCurrent);
0324     collection->setDefaultShortcut(removeCurrent, Qt::ShiftModifier + Qt::Key_Delete);
0325     connect(removeCurrent, &QAction::triggered, m_worksheet, &Worksheet::removeCurrentEntry);
0326     m_editActions.push_back(removeCurrent);
0327 
0328     // Disabled, because uploading to kde store from program don't work
0329     // See https://phabricator.kde.org/T9980 for details
0330     // If this situation will changed, then uncomment this action
0331     /*
0332     QAction* publishWorksheet = new QAction(i18n("Publish Worksheet"), collection);
0333     publishWorksheet->setIcon(QIcon::fromTheme(QLatin1String("get-hot-new-stuff")));
0334     collection->addAction(QLatin1String("file_publish_worksheet"), publishWorksheet);
0335     connect(publishWorksheet, &QAction::triggered, this, SLOT(publishWorksheet()));
0336     */
0337 
0338     KToggleAction* showEditor = new KToggleAction(i18n("Show Script Editor"), collection);
0339     showEditor->setChecked(false);
0340     collection->addAction(QLatin1String("show_editor"), showEditor);
0341     connect(showEditor, &KToggleAction::toggled, this, &CantorPart::showScriptEditor);
0342 
0343     QAction* showCompletion = new QAction(i18n("Show Completion"), collection);
0344     collection->addAction(QLatin1String("show_completion"), showCompletion);
0345     collection->setDefaultShortcut(showCompletion, Qt::CTRL + Qt::Key_Space);
0346     connect(showCompletion, &QAction::triggered, m_worksheet, &Worksheet::showCompletion);
0347     m_editActions.push_back(showCompletion);
0348 
0349     // set our XML-UI resource file
0350     setXMLFile(QLatin1String("cantor_part.rc"));
0351 
0352     // we are read-write by default
0353     setReadWrite(true);
0354 
0355     // we are not modified since we haven't done anything yet
0356     setModified(false);
0357 
0358     if (b)
0359     {
0360         showEditor->setEnabled(b->extensions().contains(QLatin1String("ScriptExtension")));
0361         initialized();
0362     }
0363 }
0364 
0365 CantorPart::~CantorPart()
0366 {
0367     if (m_scriptEditor)
0368     {
0369         disconnect(m_scriptEditor, SIGNAL(destroyed()), this, SLOT(scriptEditorClosed()));
0370         delete m_scriptEditor;
0371     }
0372     if (m_searchBar)
0373         delete m_searchBar;
0374 }
0375 
0376 void CantorPart::setReadWrite(bool rw)
0377 {
0378     // notify your internal widget of the read-write state
0379     m_worksheetview->setInteractive(rw);
0380 
0381     ReadWritePart::setReadWrite(rw);
0382 }
0383 
0384 void CantorPart::setReadOnly()
0385 {
0386     for (QAction* action : m_editActions)
0387         action->setEnabled(false);
0388 }
0389 
0390 void CantorPart::setModified(bool modified)
0391 {
0392     // get a handle on our Save action and make sure it is valid
0393     if (!m_save)
0394         return;
0395 
0396     // if so, we either enable or disable it based on the current state
0397     m_save->setEnabled(modified);
0398 
0399     // in any event, we want our parent to do it's thing
0400     ReadWritePart::setModified(modified);
0401 }
0402 
0403 KAboutData& CantorPart::createAboutData()
0404 {
0405     // the non-i18n name here must be the same as the directory in
0406     // which the part's rc file is installed ('partrcdir' in the Makefile)
0407     static KAboutData about(QLatin1String("cantorpart"),
0408                      QLatin1String("Cantor"),
0409                      QLatin1String(CANTOR_VERSION),
0410                      i18n("CantorPart"),
0411                      KAboutLicense::GPL,
0412                      i18n("(C) 2009-2015 Alexander Rieder"),
0413                      QString(),
0414                      QLatin1String("https://edu.kde.org/cantor/"));
0415 
0416     about.addAuthor( i18n("Alexander Rieder"), QString(), QLatin1String("alexanderrieder@gmail.com") );
0417     return about;
0418 }
0419 
0420 bool CantorPart::openFile()
0421 {
0422     //don't crash if for some reason the worksheet is invalid
0423     if(!m_worksheet)
0424     {
0425         qWarning()<<"trying to open in an invalid cantor part";
0426         return false;
0427     }
0428 
0429     QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
0430     QElapsedTimer timer;
0431     timer.start();
0432     const bool rc = m_worksheet->load(localFilePath());
0433     QApplication::restoreOverrideCursor();
0434 
0435     if (rc) {
0436         qDebug()<< "Worksheet successfully loaded in " <<  (float)timer.elapsed()/1000 << " seconds";
0437         updateCaption();
0438         if (m_worksheet->session() && m_worksheet->session()->backend())
0439             setBackendName(m_worksheet->session()->backend()->id());
0440         // We modified, but it we load file now, so no need in save option
0441         setModified(false);
0442     }
0443 
0444     return rc;
0445 }
0446 
0447 bool CantorPart::saveFile()
0448 {
0449     // if we aren't read-write, return immediately
0450     if (isReadWrite() == false)
0451         return false;
0452 
0453     qDebug()<<"saving to: "<<url();
0454     if (url().isEmpty())
0455         fileSaveAs();
0456     else
0457         m_worksheet->save( localFilePath() );
0458     setModified(false);
0459     updateCaption();
0460 
0461     emit worksheetSave(QUrl::fromLocalFile(localFilePath()));
0462     return true;
0463 }
0464 
0465 void CantorPart::fileSaveAs()
0466 {
0467     // this slot is called whenever the File->Save As menu is selected
0468     static const QString& worksheetFilter = i18n("Cantor Worksheet (*.cws)");
0469     static const QString& notebookFilter = i18n("Jupyter Notebook (*.ipynb)");
0470     QString filter = worksheetFilter + QLatin1String(";;") + notebookFilter;
0471 
0472     if (!m_worksheet->isReadOnly())
0473     {
0474         //if the backend supports scripts, also append their scriptFile endings to the filter
0475         auto* const backend = m_worksheet->session()->backend();
0476         if (backend->extensions().contains(QLatin1String("ScriptExtension")))
0477         {
0478             auto* e = dynamic_cast<Cantor::ScriptExtension*>(backend->extension(QLatin1String("ScriptExtension")));
0479             if (e)
0480                 filter += QLatin1String(";;") + e->scriptFileFilter();
0481         }
0482     }
0483 
0484     QString selectedFilter;
0485     QString file_name = QFileDialog::getSaveFileName(widget(), i18n("Save as"), QString(), filter, &selectedFilter);
0486     if (file_name.isEmpty())
0487         return;
0488 
0489     static const QString jupyterExtension = QLatin1String(".ipynb");
0490     static const QString cantorExtension = QLatin1String(".cws");
0491     // Append file extension, if it isn't specified
0492     // And change filter, if it specified to supported extension
0493     if (file_name.contains(QLatin1String(".")))
0494     {
0495         if (file_name.endsWith(cantorExtension))
0496             selectedFilter = worksheetFilter;
0497         else if (file_name.endsWith(jupyterExtension))
0498             selectedFilter = notebookFilter;
0499     }
0500     else
0501     {
0502         if (selectedFilter == worksheetFilter)
0503             file_name += cantorExtension;
0504         else if (selectedFilter == notebookFilter)
0505             file_name += jupyterExtension;
0506     }
0507 
0508     //depending on user's selection, save as a worksheet, as a Jupyter notebook or as a plain script file
0509     if (selectedFilter == worksheetFilter)
0510     {
0511         m_worksheet->setType(Worksheet::CantorWorksheet);
0512         const QUrl& url = QUrl::fromLocalFile(file_name);
0513         saveAs(url);
0514         emit worksheetSave(url);
0515     }
0516     else if (selectedFilter == notebookFilter)
0517     {
0518         m_worksheet->setType(Worksheet::JupyterNotebook);
0519         const QUrl& url = QUrl::fromLocalFile(file_name);
0520         saveAs(url);
0521         emit worksheetSave(url);
0522     }
0523     else
0524         m_worksheet->savePlain(file_name);
0525 
0526     updateCaption();
0527 }
0528 
0529 void CantorPart::fileSavePlain()
0530 {
0531     QString file_name = QFileDialog::getSaveFileName(widget(), i18n("Save"), QString(), i18n("Text Files (*.txt)"));
0532     if (!file_name.isEmpty())
0533         m_worksheet->savePlain(file_name);
0534 }
0535 
0536 void CantorPart::exportToPDF()
0537 {
0538     QString path = QFileDialog::getSaveFileName(widget(), i18n("Export to PDF"), QString(), i18n("PDF Files (*.pdf)"));
0539     if (path.isEmpty()) // "Cancel" was clicked
0540         return;
0541 
0542     QPrinter printer;
0543     printer.setOutputFormat(QPrinter::PdfFormat);
0544     printer.setOutputFileName(path);
0545     m_worksheet->print(&printer);
0546 }
0547 
0548 void CantorPart::exportToLatex()
0549 {
0550     QString file_name = QFileDialog::getSaveFileName(widget(), i18n("Export to LaTeX"), QString(), i18n("TeX Files (*.tex)"));
0551 
0552     if (file_name.isEmpty() == false)
0553     {
0554         if (!file_name.endsWith(QLatin1String(".tex")))
0555             file_name += QLatin1String(".tex");
0556         m_worksheet->saveLatex(file_name);
0557     }
0558 }
0559 
0560 void CantorPart::guiActivateEvent( KParts::GUIActivateEvent * event )
0561 {
0562     KParts::ReadWritePart::guiActivateEvent(event);
0563     if(event->activated())
0564     {
0565         if(m_scriptEditor)
0566             m_scriptEditor->show();
0567     }else
0568     {
0569         if(m_scriptEditor)
0570             m_scriptEditor->hide();
0571     }
0572 }
0573 
0574 void CantorPart::evaluateOrInterrupt()
0575 {
0576     qDebug()<<"evalorinterrupt";
0577     if(m_worksheet->isRunning())
0578         m_worksheet->interrupt();
0579     else
0580         m_worksheet->evaluate();
0581 }
0582 void CantorPart::restartBackend()
0583 {
0584     bool restart = false;
0585     if (Settings::self()->warnAboutSessionRestart())
0586     {
0587         KMessageBox::ButtonCode tmp;
0588 
0589         // If we want the question box, but it is disable, then enable it
0590         if (!KMessageBox::shouldBeShownYesNo(QLatin1String("WarnAboutSessionRestart"), tmp))
0591             KMessageBox::enableMessage(QLatin1String("WarnAboutSessionRestart"));
0592 
0593         const QString& name = m_worksheet->session()->backend()->name();
0594         KMessageBox::ButtonCode rc = KMessageBox::questionYesNo(widget(),
0595             i18n("All the available calculation results will be lost. Do you really want to restart %1?", name),
0596             i18n("Restart %1?", name),
0597             KStandardGuiItem::yes(),
0598             KStandardGuiItem::no(),
0599             QLatin1String("WarnAboutSessionRestart")
0600         );
0601 
0602         // Update setting's value
0603         // I don't know, that should I do with "No" with "Don't ask me again"
0604         // So hide warning only on "Yes"
0605         Settings::self()->setWarnAboutSessionRestart(
0606                KMessageBox::shouldBeShownYesNo(QLatin1String("WarnAboutSessionRestart"), tmp)
0607             || rc == KMessageBox::ButtonCode::No
0608         );
0609         Settings::self()->save();
0610 
0611         restart = (rc == KMessageBox::ButtonCode::Yes);
0612     }
0613     else
0614     {
0615         restart = true;
0616     }
0617 
0618     if (restart)
0619     {
0620         m_worksheet->session()->logout();
0621         m_worksheet->loginToSession();
0622     }
0623 }
0624 
0625 void CantorPart::worksheetStatusChanged(Cantor::Session::Status status)
0626 {
0627     qDebug()<<"worksheet status changed:" << status;
0628     unsigned int count = ++m_sessionStatusCounter;
0629     switch (status) {
0630     case Cantor::Session::Running:
0631     {
0632         // Useless add a interrupt action without delay, because user physically can't interrupt fast commands
0633         QTimer::singleShot(100, this, [this, count] () {
0634             if(m_worksheet->session()->status() == Cantor::Session::Running && m_sessionStatusCounter == count)
0635             {
0636                 m_evaluate->setText(i18n("Interrupt"));
0637                 m_evaluate->setShortcut(Qt::CTRL+Qt::Key_I);
0638                 m_evaluate->setIcon(QIcon::fromTheme(QLatin1String("dialog-close")));
0639                 setStatusMessage(i18n("Calculating..."));
0640             }
0641         });
0642         break;
0643     }
0644     case Cantor::Session::Done:
0645     {
0646         m_evaluate->setText(i18n("Evaluate Worksheet"));
0647         m_evaluate->setShortcut(Qt::CTRL+Qt::Key_E);
0648         m_evaluate->setIcon(QIcon::fromTheme(QLatin1String("system-run")));
0649 
0650         setStatusMessage(i18n("Ready"));
0651         break;
0652     }
0653     case Cantor::Session::Disable:
0654         setStatusMessage(QString()); //clean the status bar to remove the potential "Calculating...", etc. after the session was closed
0655     }
0656 }
0657 
0658 void CantorPart::showSessionError(const QString& message)
0659 {
0660     qDebug()<<"Error: "<<message;
0661     initialized();
0662     showImportantStatusMessage(i18n("Session Error: %1", message));
0663 }
0664 
0665 void CantorPart::initialized()
0666 {
0667     if (!m_worksheet->isReadOnly())
0668     {
0669         connect(m_worksheet->session(), &Cantor::Session::statusChanged, this, &CantorPart::worksheetStatusChanged);
0670         connect(m_worksheet->session(), &Cantor::Session::loginStarted,this, &CantorPart::worksheetSessionLoginStarted);
0671         connect(m_worksheet->session(), &Cantor::Session::loginDone,this, &CantorPart::worksheetSessionLoginDone);
0672         connect(m_worksheet->session(), &Cantor::Session::error, this, &CantorPart::showSessionError);
0673 
0674         loadAssistants();
0675         adjustGuiToSession();
0676 
0677         // Don't set modification flag, if we add command entry in empty worksheet
0678         const bool modified = this->isModified();
0679         if (m_worksheet->isEmpty())
0680             m_worksheet->appendCommandEntry();
0681         setModified(modified);
0682     }
0683     else
0684     {
0685         setReadOnly();
0686         // Clear assistants
0687         for (KXMLGUIClient* client: childClients())
0688         {
0689             Cantor::Assistant* assistant = dynamic_cast<Cantor::Assistant*>(client);
0690             if (assistant)
0691             {
0692                 if (factory())
0693                     factory()->removeClient(client);
0694                 removeChildClient(client);
0695                 assistant->deleteLater();
0696             }
0697         }
0698     }
0699 
0700     m_worksheetview->setEnabled(true);
0701     m_worksheetview->setFocus();
0702 
0703     setStatusMessage(i18n("Initialization complete"));
0704     updateCaption();
0705 }
0706 
0707 void CantorPart::worksheetSessionLoginStarted() {
0708     setStatusMessage(i18n("Initializing..."));
0709     QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
0710 }
0711 
0712 void CantorPart::worksheetSessionLoginDone() {
0713     setStatusMessage(i18n("Ready"));
0714     m_restart->setEnabled(true);
0715     QApplication::restoreOverrideCursor();
0716 }
0717 
0718 void CantorPart::enableTypesetting(bool enable)
0719 {
0720     m_worksheet->session()->setTypesettingEnabled(enable);
0721 }
0722 
0723 /*!
0724  * called when the current worksheet has requested to show the documentation for \c keyword.
0725  * In case the local documentation is available for the current backend, the signal is
0726  * forwarded to the shell to show the documentation plugin/widget.
0727  * If no local documentation is available, the defaul online URL for the backend documentation
0728  * is openned.
0729  */
0730 void CantorPart::documentationRequested(const QString& keyword) {
0731     auto* backend = m_worksheet->session()->backend();
0732     const auto& group = KSharedConfig::openConfig(QStringLiteral("cantorrc"))->group(backend->name().toLower());
0733     const auto& docNames = group.readEntry(QLatin1String("Names"), QStringList());
0734     if (!docNames.isEmpty())
0735         emit requestDocumentation(keyword);
0736     else
0737         showBackendHelp();
0738 }
0739 
0740 void CantorPart::showBackendHelp()
0741 {
0742     auto* backend = m_worksheet->session()->backend();
0743     auto* job = new KIO::OpenUrlJob(backend->helpUrl());
0744     job->setUiDelegate(new KIO::JobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, widget()));
0745     job->start();
0746     delete job;
0747 }
0748 
0749 Worksheet* CantorPart::worksheet()
0750 {
0751     return m_worksheet;
0752 }
0753 
0754 void CantorPart::updateCaption()
0755 {
0756     QString filename = url().fileName();
0757     //strip away the extension
0758     filename=filename.left(filename.lastIndexOf(QLatin1Char('.')));
0759 
0760     if (!m_worksheet->isReadOnly())
0761     {
0762         if (m_worksheet->session())
0763             emit setCaption(filename, QIcon::fromTheme(m_worksheet->session()->backend()->icon()));
0764     }
0765     else
0766         emit setCaption(filename+QLatin1Char(' ') + i18n("[read-only]"), QIcon());
0767 }
0768 
0769 void CantorPart::loadAssistants()
0770 {
0771     qDebug()<<"loading assistants...";
0772 
0773     const QVector<KPluginMetaData> plugins = KPluginMetaData::findPlugins(QStringLiteral("cantor/assistants"));
0774 
0775     for (const KPluginMetaData &plugin : plugins) {
0776 
0777         const auto result = KPluginFactory::instantiatePlugin<Cantor::Assistant>(plugin, this);
0778 
0779         if (!result) {
0780             qDebug() << "Error while loading assistant plugin: " << result.errorText;
0781             continue;
0782         }
0783 
0784         Cantor::Assistant *assistant = result.plugin;
0785         auto* backend=worksheet()->session()->backend();
0786         assistant->setPluginInfo(plugin);
0787         assistant->setBackend(backend);
0788 
0789         bool supported=true;
0790         for (const QString& req : assistant->requiredExtensions())
0791             supported = supported && backend->extensions().contains(req);
0792 
0793         if(supported)
0794         {
0795             qDebug() << "plugin " << plugin.name() << " is supported by " << backend->name() << ", requires extensions " << assistant->requiredExtensions();
0796             assistant->initActions();
0797             connect(assistant, &Cantor::Assistant::requested, this, &CantorPart::runAssistant);
0798         }else
0799         {
0800             qDebug() << "plugin " << plugin.name() << " is not supported by "<<backend->name();
0801             removeChildClient(assistant);
0802             assistant->deleteLater();
0803         }
0804     }
0805 }
0806 
0807 void CantorPart::runAssistant()
0808 {
0809     Cantor::Assistant* a = qobject_cast<Cantor::Assistant*>(sender());
0810     QStringList cmds = a->run(widget());
0811     if(!cmds.isEmpty())
0812         runCommand(cmds.join(QLatin1String("\n")));
0813 }
0814 
0815 void CantorPart::runCommand(const QString& cmd)
0816 {
0817     m_worksheet->appendCommandEntry(cmd);
0818 }
0819 
0820 void CantorPart::showSearchBar()
0821 {
0822     if (!m_searchBar) {
0823         m_searchBar = new SearchBar(widget(), m_worksheet);
0824         widget()->layout()->addWidget(m_searchBar);
0825         connect(m_searchBar, &SearchBar::destroyed, this, &CantorPart::searchBarDeleted);
0826     }
0827 
0828     m_findNext->setEnabled(true);
0829     m_findPrev->setEnabled(true);
0830 
0831     m_searchBar->showStandard();
0832     m_searchBar->setFocus();
0833 }
0834 
0835 void CantorPart::showExtendedSearchBar()
0836 {
0837     if (!m_searchBar) {
0838         m_searchBar = new SearchBar(widget(), m_worksheet);
0839         widget()->layout()->addWidget(m_searchBar);
0840         connect(m_searchBar, &SearchBar::destroyed, this, &CantorPart::searchBarDeleted);
0841     }
0842 
0843     m_findNext->setEnabled(true);
0844     m_findPrev->setEnabled(true);
0845 
0846     m_searchBar->showExtended();
0847     m_searchBar->setFocus();
0848 }
0849 
0850 void CantorPart::findNext()
0851 {
0852     if (m_searchBar)
0853         m_searchBar->next();
0854 }
0855 
0856 void CantorPart::findPrev()
0857 {
0858     if (m_searchBar)
0859         m_searchBar->prev();
0860 }
0861 
0862 void CantorPart::searchBarDeleted()
0863 {
0864     m_searchBar = nullptr;
0865     m_findNext->setEnabled(false);
0866     m_findPrev->setEnabled(false);
0867 }
0868 
0869 void CantorPart::adjustGuiToSession()
0870 {
0871     auto capabilities = m_worksheet->session()->backend()->capabilities();
0872 #ifdef WITH_EPS
0873     if (Cantor::LatexRenderer::isLatexAvailable())
0874         m_typeset->setVisible(capabilities.testFlag(Cantor::Backend::LaTexOutput));
0875 #else
0876     m_typeset->setVisible(false);
0877 #endif
0878     m_completion->setVisible(capabilities.testFlag(Cantor::Backend::Completion));
0879 }
0880 
0881 void CantorPart::publishWorksheet()
0882 {
0883     int ret = KMessageBox::questionYesNo(widget(),
0884                                          i18n("Do you want to upload current Worksheet to public web server?"),
0885                                          i18n("Question - Cantor"));
0886     if (ret != KMessageBox::Yes) return;
0887 
0888     if (isModified()||url().isEmpty())
0889     {
0890         ret = KMessageBox::warningContinueCancel(widget(),
0891                                                  i18n("The Worksheet is not saved. You should save it before uploading."),
0892                                                  i18n("Warning - Cantor"),  KStandardGuiItem::save(),  KStandardGuiItem::cancel());
0893         if (ret != KMessageBox::Continue) return;
0894         if (!saveFile()) return;
0895     }
0896 
0897     qDebug()<<"uploading file "<<url();
0898 
0899     // upload
0900     //HACK: use different .knsrc files for each category
0901     //remove this once KNS3 gains the ability to select category
0902     KNS3::UploadDialog dialog(QString::fromLatin1("cantor_%1.knsrc").arg(m_worksheet->session()->backend()->id().toLower()), widget());
0903     dialog.setUploadFile(url());
0904     Q_UNUSED(dialog.exec());
0905 }
0906 
0907 void CantorPart::print()
0908 {
0909     QPrinter printer;
0910     QPointer<QPrintDialog> dialog = new QPrintDialog(&printer,  widget());
0911 
0912     // TODO: Re-enable print selection
0913     //if (m_worksheet->textCursor().hasSelection())
0914     //    dialog->addEnabledOption(QAbstractPrintDialog::PrintSelection);
0915 
0916     if (dialog->exec() == QDialog::Accepted)
0917         m_worksheet->print(&printer);
0918 
0919     delete dialog;
0920 }
0921 
0922 void CantorPart::printPreview()
0923 {
0924     QPrintPreviewDialog* dialog = new QPrintPreviewDialog(widget());
0925     connect(dialog, &QPrintPreviewDialog::paintRequested, m_worksheet, &Worksheet::print);
0926     Q_UNUSED(dialog->exec());
0927 }
0928 
0929 void CantorPart::showScriptEditor(bool show)
0930 {
0931     if(show)
0932     {
0933         if (m_scriptEditor)
0934             return;
0935 
0936         auto* scriptE = dynamic_cast<Cantor::ScriptExtension*>(m_worksheet->session()->backend()->extension(QLatin1String("ScriptExtension")));
0937         if (!scriptE)
0938             return;
0939 
0940         m_scriptEditor = new ScriptEditorWidget(scriptE->scriptFileFilter(), scriptE->highlightingMode(), widget()->window());
0941         connect(m_scriptEditor, &ScriptEditorWidget::runScript, this, &CantorPart::runScript);
0942         connect(m_scriptEditor, &ScriptEditorWidget::destroyed, this, &CantorPart::scriptEditorClosed);
0943         m_scriptEditor->show();
0944     }else
0945     {
0946         m_scriptEditor->deleteLater();
0947     }
0948 }
0949 
0950 void CantorPart::scriptEditorClosed()
0951 {
0952     QAction* showEditor = actionCollection()->action(QLatin1String("show_editor"));
0953     if (showEditor)
0954     {
0955         showEditor->setChecked(false);
0956     }
0957 }
0958 
0959 void CantorPart::runScript(const QString& file)
0960 {
0961     auto* backend = m_worksheet->session()->backend();
0962     if(!backend->extensions().contains(QLatin1String("ScriptExtension")))
0963     {
0964         KMessageBox::error(widget(), i18n("This backend does not support scripts."), i18n("Error - Cantor"));
0965         return;
0966     }
0967 
0968     auto* scriptE = dynamic_cast<Cantor::ScriptExtension*>(backend->extension(QLatin1String("ScriptExtension")));
0969     if (scriptE)
0970         m_worksheet->appendCommandEntry(scriptE->runExternalScript(file));
0971 }
0972 
0973 void CantorPart::blockStatusBar()
0974 {
0975     m_statusBarBlocked=true;
0976 }
0977 
0978 void CantorPart::unblockStatusBar()
0979 {
0980     m_statusBarBlocked = false;
0981     if(!m_cachedStatusMessage.isNull())
0982         setStatusMessage(m_cachedStatusMessage);
0983     m_cachedStatusMessage.clear();
0984 }
0985 
0986 void CantorPart::setStatusMessage(const QString& message)
0987 {
0988     if(!m_statusBarBlocked)
0989         emit setStatusBarText(message);
0990     else
0991         m_cachedStatusMessage = message;
0992 }
0993 
0994 void CantorPart::showImportantStatusMessage(const QString& message)
0995 {
0996     setStatusMessage(message);
0997     blockStatusBar();
0998     QTimer::singleShot(3000, this, SLOT(unblockStatusBar()));
0999 }
1000 
1001 void CantorPart::zoomValueEdited(const QString& text)
1002 {
1003     const QRegularExpressionMatch match = m_zoomRegexp.match(text);
1004     if (match.hasMatch())
1005     {
1006         double zoom = match.captured(1).toDouble() / 100.0;
1007         if (m_worksheetview)
1008             m_worksheetview->setScaleFactor(zoom, false);
1009     }
1010 }
1011 
1012 void CantorPart::updateZoomWidgetValue(double zoom)
1013 {
1014     if (m_zoom)
1015     {
1016         double scale = zoom;
1017         scale = round(scale * 100);
1018         const QString& searchText = QString::number((int)scale) + QLatin1String("%");
1019         if (m_currectZoomAction)
1020             m_currectZoomAction->setText(searchText);
1021         else
1022             m_currectZoomAction = m_zoom->addAction(searchText);
1023         m_zoom->setCurrentAction(m_currectZoomAction);
1024     }
1025 }
1026 
1027 K_PLUGIN_FACTORY_WITH_JSON(CantorPartFactory, "cantor_part.json", registerPlugin<CantorPart>();)
1028 #include "cantor_part.moc"