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"