File indexing completed on 2022-12-06 18:50:12

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