diff --git a/src/cantor.cpp b/src/cantor.cpp index 7d9af95d..1d23caae 100644 --- a/src/cantor.cpp +++ b/src/cantor.cpp @@ -1,647 +1,647 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2009 Alexander Rieder */ #include "cantor.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "lib/backend.h" #include "lib/panelpluginhandler.h" #include "lib/panelplugin.h" #include "lib/worksheetaccess.h" #include "settings.h" #include "ui_settings.h" #include "backendchoosedialog.h" CantorShell::CantorShell() : KParts::MainWindow(), m_part(nullptr) { // set the shell's ui resource file setXMLFile(QLatin1String("cantor_shell.rc")); // then, setup our actions setupActions(); createGUI(nullptr); m_tabWidget=new QTabWidget(this); m_tabWidget->setTabsClosable(true); m_tabWidget->setMovable(true); m_tabWidget->setDocumentMode(true); setCentralWidget(m_tabWidget); connect(m_tabWidget, SIGNAL(currentChanged(int)), this, SLOT(activateWorksheet(int))); connect(m_tabWidget, SIGNAL(tabCloseRequested(int)), this, SLOT(closeTab(int))); // apply the saved mainwindow settings, if any, and ask the mainwindow // to automatically save settings if changed: window size, toolbar // position, icon size, etc. setAutoSaveSettings(); setDockOptions(QMainWindow::AnimatedDocks|QMainWindow::AllowTabbedDocks|QMainWindow::VerticalTabs); updateNewSubmenu(); } void CantorShell::load(const QUrl &url) { if (!m_part||!m_part->url().isEmpty() || m_part->isModified() ) { addWorksheet(QLatin1String("null")); m_tabWidget->setCurrentIndex(m_parts.size()-1); } m_part->openUrl( url ); } bool CantorShell::hasAvailableBackend() { bool hasBackend=false; foreach(Cantor::Backend* b, Cantor::Backend::availableBackends()) { if(b->isEnabled()) hasBackend=true; } return hasBackend; } void CantorShell::setupActions() { QAction* openNew = KStandardAction::openNew(this, SLOT(fileNew()), actionCollection()); openNew->setPriority(QAction::LowPriority); QAction* open = KStandardAction::open(this, SLOT(fileOpen()), actionCollection()); open->setPriority(QAction::LowPriority); KStandardAction::close (this, SLOT(closeTab()), actionCollection()); KStandardAction::quit(qApp, SLOT(closeAllWindows()), actionCollection()); createStandardStatusBarAction(); //setStandardToolBarMenuEnabled(true); KStandardAction::keyBindings(this, SLOT(optionsConfigureKeys()), actionCollection()); KStandardAction::configureToolbars(this, SLOT(configureToolbars()), actionCollection()); KStandardAction::preferences(this, SLOT(showSettings()), actionCollection()); QAction * downloadExamples = new QAction(i18n("Download Example Worksheets"), actionCollection()); downloadExamples->setIcon(QIcon::fromTheme(QLatin1String("get-hot-new-stuff"))); actionCollection()->addAction(QLatin1String("file_example_download"), downloadExamples); connect(downloadExamples, SIGNAL(triggered()), this, SLOT(downloadExamples())); QAction * openExample =new QAction(i18n("&Open Example"), actionCollection()); openExample->setIcon(QIcon::fromTheme(QLatin1String("document-open"))); actionCollection()->addAction(QLatin1String("file_example_open"), openExample); connect(openExample, SIGNAL(triggered()), this, SLOT(openExample())); QAction* toPreviousTab = new QAction(i18n("Go to previous worksheet"), actionCollection()); actionCollection()->addAction(QLatin1String("go_to_previous_tab"), toPreviousTab); actionCollection()->setDefaultShortcut(toPreviousTab, Qt::CTRL+Qt::Key_PageDown); connect(toPreviousTab, &QAction::triggered, toPreviousTab, [this](){ const int index = m_tabWidget->currentIndex()-1; if (index >= 0) m_tabWidget->setCurrentIndex(index); else m_tabWidget->setCurrentIndex(m_tabWidget->count()-1); }); addAction(toPreviousTab); QAction* toNextTab = new QAction(i18n("Go to next worksheet"), actionCollection()); actionCollection()->addAction(QLatin1String("go_to_next_tab"), toNextTab); actionCollection()->setDefaultShortcut(toNextTab, Qt::CTRL+Qt::Key_PageUp); connect(toNextTab, &QAction::triggered, toNextTab, [this](){ const int index = m_tabWidget->currentIndex()+1; if (index < m_tabWidget->count()) m_tabWidget->setCurrentIndex(index); else m_tabWidget->setCurrentIndex(0); }); addAction(toNextTab); } void CantorShell::saveProperties(KConfigGroup & /*config*/) { // the 'config' object points to the session managed // config file. anything you write here will be available // later when this app is restored } void CantorShell::readProperties(const KConfigGroup & /*config*/) { // the 'config' object points to the session managed // config file. this function is automatically called whenever // the app is being restored. read in here whatever you wrote // in 'saveProperties' } void CantorShell::fileNew() { if (sender()->inherits("QAction")) { QAction * a = qobject_cast(sender()); QString backendName = a->data().toString(); if (!backendName.isEmpty()) { addWorksheet(backendName); return; } } addWorksheet(); } void CantorShell::optionsConfigureKeys() { KShortcutsDialog dlg( KShortcutsEditor::AllActions, KShortcutsEditor::LetterShortcutsDisallowed, this ); dlg.addCollection( actionCollection(), i18n("Cantor") ); if (m_part) dlg.addCollection( m_part->actionCollection(), i18n("Cantor") ); dlg.configure( true ); } void CantorShell::fileOpen() { // this slot is called whenever the File->Open menu is selected, // the Open shortcut is pressed (usually CTRL+O) or the Open toolbar // button is clicked - QUrl url = QFileDialog::getOpenFileUrl(this, i18n("Open file"), QUrl(), i18n("Cantor Worksheet (*.cws);;Jupyter notebook (*.ipynb)")); + QUrl url = QFileDialog::getOpenFileUrl(this, i18n("Open file"), QUrl(), i18n("Cantor Worksheet (*.cws)") + QLatin1String(";;") + i18n("Jupyter Notebook (*.ipynb)")); if (url.isEmpty() == false) { // About this function, the style guide ( // http://developer.kde.org/documentation/standards/kde/style/basics/index.html ) // says that it should open a new window if the document is _not_ // in its initial state. This is what we do here.. /*if ( m_part->url().isEmpty() && ! m_part->isModified() ) { // we open the file in this window... load( url ); } else { // we open the file in a new window... CantorShell* newWin = new CantorShell; newWin->load( url ); newWin->show(); }*/ load( url ); } } void CantorShell::addWorksheet() { if(hasAvailableBackend()) //There is no point in asking for the backend, if no one is available { QString backend = Settings::self()->defaultBackend(); if (backend.isEmpty()) { QPointer dlg=new BackendChooseDialog(this); if(dlg->exec()) { backend = dlg->backendName(); addWorksheet(backend); } delete dlg; } else { addWorksheet(backend); } }else { QTextBrowser *browser=new QTextBrowser(this); QString backendList=QLatin1String("
    "); int backendListSize = 0; foreach(Cantor::Backend* b, Cantor::Backend::availableBackends()) { if(!b->requirementsFullfilled()) //It's disabled because of misssing dependencies, not because of some other reason(like eg. nullbackend) { backendList+=QString::fromLatin1("
  • %1: %2
  • ").arg(b->name(), b->url()); ++backendListSize; } } browser->setHtml(i18np("

    No Backend Found

    \n" \ "
    You could try:\n" \ "
      " \ "
    • Changing the settings in the config dialog;
    • " \ "
    • Installing packages for the following program:
    • " \ " %2 " \ "
    " \ "
    " , "

    No Backend Found

    \n" \ "
    You could try:\n" \ "
      " \ "
    • Changing the settings in the config dialog;
    • " \ "
    • Installing packages for one of the following programs:
    • " \ " %2 " \ "
    " \ "
    " , backendListSize, backendList )); browser->setObjectName(QLatin1String("ErrorMessage")); m_tabWidget->addTab(browser, i18n("Error")); } } void CantorShell::addWorksheet(const QString& backendName) { static int sessionCount=1; // this routine will find and load our Part. it finds the Part by // name which is a bad idea usually.. but it's alright in this // case since our Part is made for this Shell KPluginFactory* factory = KPluginLoader(QLatin1String("cantorpart")).factory(); if (factory) { Cantor::Backend* backend = Cantor::Backend::getBackend(backendName); if (backend) { if (backend->isEnabled() || backendName == QLatin1String("null")) { // now that the Part is loaded, we cast it to a Part to get our hands on it KParts::ReadWritePart* part = factory->create(m_tabWidget, QVariantList()<addTab(part->widget(), QIcon::fromTheme(backend->icon()), i18n("Session %1", sessionCount++)); m_tabWidget->setCurrentIndex(tab); // Setting focus on worksheet view, because Qt clear focus of added widget inside addTab // This fix https://bugs.kde.org/show_bug.cgi?id=395976 part->widget()->findChild()->setFocus(); } else { qDebug()<<"error creating part "; } } else { KMessageBox::error(this, i18n("%1 backend installed, but inactive. Please check installation and Cantor settings", backendName), i18n("Cantor")); } } else KMessageBox::error(this, i18n("Backend %1 is not installed", backendName), i18n("Cantor")); } else { // if we couldn't find our Part, we exit since the Shell by // itself can't do anything useful KMessageBox::error(this, i18n("Could not find the Cantor Part.")); qApp->quit(); // we return here, cause qApp->quit() only means "exit the // next time we enter the event loop... return; } } void CantorShell::activateWorksheet(int index) { QObject* pluginHandler=m_part->findChild(QLatin1String("PanelPluginHandler")); if (pluginHandler) disconnect(pluginHandler,SIGNAL(pluginsChanged()), this, SLOT(updatePanel())); // Save part state before change worksheet if (m_part) { QStringList visiblePanelNames; foreach (QDockWidget* doc, m_panels) { if (doc->widget() && doc->widget()->isVisible()) visiblePanelNames << doc->objectName(); } m_pluginsVisibility[m_part] = visiblePanelNames; } m_part=findPart(m_tabWidget->widget(index)); if(m_part) { createGUI(m_part); QObject* pluginHandler=m_part->findChild(QLatin1String("PanelPluginHandler")); connect(pluginHandler, SIGNAL(pluginsChanged()), this, SLOT(updatePanel())); updatePanel(); } else qDebug()<<"selected part doesn't exist"; m_tabWidget->setCurrentIndex(index); } void CantorShell::setTabCaption(const QString& caption, const QIcon& icon) { if (caption.isEmpty()) return; KParts::ReadWritePart* part=dynamic_cast(sender()); m_tabWidget->setTabText(m_tabWidget->indexOf(part->widget()), caption); m_tabWidget->setTabIcon(m_tabWidget->indexOf(part->widget()), icon); } void CantorShell::closeTab(int index) { if (!reallyClose(false)) { return; } QWidget* widget = nullptr; if (index >= 0) { widget = m_tabWidget->widget(index); } else if (m_part) { widget = m_part->widget(); } if (!widget) { qWarning() << "Could not find widget by tab index" << index; return; } m_tabWidget->removeTab(index); if(widget->objectName()==QLatin1String("ErrorMessage")) { widget->deleteLater(); }else { KParts::ReadWritePart* part= findPart(widget); if(part) { m_parts.removeAll(part); m_pluginsVisibility.remove(part); delete part; } } updatePanel(); } bool CantorShell::reallyClose(bool checkAllParts) { if(checkAllParts && m_parts.count() > 1) { bool modified = false; foreach( KParts::ReadWritePart* const part, m_parts) { if(part->isModified()) { modified = true; break; } } if(!modified) return true; int want_save = KMessageBox::warningYesNo( this, i18n("Multiple unsaved Worksheets are opened. Do you want to close them?"), i18n("Close Cantor")); switch (want_save) { case KMessageBox::Yes: return true; case KMessageBox::No: return false; } } if (m_part && m_part->isModified() ) { int want_save = KMessageBox::warningYesNoCancel( this, i18n("The current project has been modified. Do you want to save it?"), i18n("Save Project")); switch (want_save) { case KMessageBox::Yes: m_part->save(); if(m_part->waitSaveComplete()) { return true; } else { m_part->setModified(true); return false; } case KMessageBox::Cancel: return false; case KMessageBox::No: return true; } } return true; } void CantorShell::closeEvent(QCloseEvent* event) { if(!reallyClose()) { event->ignore(); } else { KParts::MainWindow::closeEvent(event); } } void CantorShell::showSettings() { KConfigDialog *dialog = new KConfigDialog(this, QLatin1String("settings"), Settings::self()); QWidget *generalSettings = new QWidget; Ui::SettingsBase base; base.setupUi(generalSettings); base.kcfg_DefaultBackend->addItems(Cantor::Backend::listAvailableBackends()); dialog->addPage(generalSettings, i18n("General"), QLatin1String("preferences-other")); foreach(Cantor::Backend* backend, Cantor::Backend::availableBackends()) { if (backend->config()) //It has something to configure, so add it to the dialog dialog->addPage(backend->settingsWidget(dialog), backend->config(), backend->name(), backend->icon()); } dialog->show(); } void CantorShell::downloadExamples() { KNS3::DownloadDialog dialog; dialog.exec(); foreach (const KNS3::Entry& e, dialog.changedEntries()) { qDebug() << "Changed Entry: " << e.name(); } } void CantorShell::openExample() { QString dir = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QLatin1String("/examples"); if (dir.isEmpty()) return; QDir().mkpath(dir); QStringList files=QDir(dir).entryList(QDir::Files); QPointer dlg=new QDialog(this); QListWidget* list=new QListWidget(dlg); foreach(const QString& file, files) { QString name=file; name.remove(QRegExp(QLatin1String("-.*\\.hotstuff-access$"))); list->addItem(name); } QVBoxLayout *mainLayout = new QVBoxLayout; dlg->setLayout(mainLayout); mainLayout->addWidget(list); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); mainLayout->addWidget(buttonBox); buttonBox->button(QDialogButtonBox::Ok)->setIcon(QApplication::style()->standardIcon(QStyle::SP_DialogOkButton)); buttonBox->button(QDialogButtonBox::Cancel)->setIcon(QApplication::style()->standardIcon(QStyle::SP_DialogCancelButton)); connect(buttonBox, SIGNAL(accepted()), dlg, SLOT(accept()) ); connect(buttonBox, SIGNAL(rejected()), dlg, SLOT(reject()) ); if (dlg->exec()==QDialog::Accepted&&list->currentRow()>=0) { const QString& selectedFile=files[list->currentRow()]; QUrl url = QUrl::fromLocalFile(QDir(dir).absoluteFilePath(selectedFile)); qDebug()<<"loading file "<widget()==widget) return part; } return nullptr; } void CantorShell::updatePanel() { unplugActionList(QLatin1String("view_show_panel_list")); //remove all of the previous panels (but do not delete the widgets) foreach(QDockWidget* dock, m_panels) { QWidget* widget=dock->widget(); if(widget!=nullptr) { widget->setParent(this); widget->hide(); } dock->deleteLater(); } m_panels.clear(); QList panelActions; Cantor::PanelPluginHandler* handler=m_part->findChild(QLatin1String("PanelPluginHandler")); if(!handler) { qDebug()<<"no PanelPluginHandle found for this part"; return; } QDockWidget* last=nullptr; QList plugins=handler->plugins(); const bool isNewWorksheet = !m_pluginsVisibility.contains(m_part); foreach(Cantor::PanelPlugin* plugin, plugins) { if(plugin==nullptr) { qDebug()<<"somethings wrong"; continue; } qDebug()<<"adding panel for "<name(); plugin->setParentWidget(this); QDockWidget* docker=new QDockWidget(plugin->name(), this); docker->setObjectName(plugin->name()); docker->setWidget(plugin->widget()); addDockWidget ( Qt::RightDockWidgetArea, docker ); // Set visibility for dock from saved info if (!isNewWorksheet) if (m_pluginsVisibility[m_part].contains(plugin->name())) docker->show(); else docker->hide(); else docker->show(); if(last!=nullptr) tabifyDockWidget(last, docker); last=docker; connect(plugin, SIGNAL(visibilityRequested()), docker, SLOT(raise())); m_panels.append(docker); //Create the action to show/hide this panel panelActions<toggleViewAction(); } plugActionList(QLatin1String("view_show_panel_list"), panelActions); updateNewSubmenu(); } void CantorShell::updateNewSubmenu() { unplugActionList(QLatin1String("new_worksheet_with_backend_list")); QList newBackendActions; foreach (Cantor::Backend* backend, Cantor::Backend::availableBackends()) { if (!backend->isEnabled()) continue; QAction * action = new QAction(QIcon::fromTheme(backend->icon()), backend->name(), nullptr); action->setData(backend->name()); connect(action, SIGNAL(triggered()), this, SLOT(fileNew())); newBackendActions << action; } plugActionList(QLatin1String("new_worksheet_with_backend_list"), newBackendActions); } Cantor::WorksheetAccessInterface* CantorShell::currentWorksheetAccessInterface() { Cantor::WorksheetAccessInterface* wa=m_part->findChild(Cantor::WorksheetAccessInterface::Name); if (!wa) qDebug()<<"failed to access worksheet access interface for current part"; return wa; } diff --git a/src/cantor_part.cpp b/src/cantor_part.cpp index 28c1e83e..f9bfb6bf 100644 --- a/src/cantor_part.cpp +++ b/src/cantor_part.cpp @@ -1,960 +1,969 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2009 Alexander Rieder */ #include "cantor_part.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "worksheet.h" #include "worksheetview.h" #include "searchbar.h" #include "scripteditor/scripteditorwidget.h" #include "lib/backend.h" #include "lib/extension.h" #include "lib/assistant.h" #include "lib/panelpluginhandler.h" #include "lib/panelplugin.h" #include "lib/worksheetaccess.h" #include "settings.h" //A concrete implementation of the WorksheetAccesssInterface class WorksheetAccessInterfaceImpl : public Cantor::WorksheetAccessInterface { public: WorksheetAccessInterfaceImpl(QObject* parent, Worksheet* worksheet) : WorksheetAccessInterface(parent), m_worksheet(worksheet) { qDebug()<<"new worksheetaccess interface"; connect(worksheet, SIGNAL(modified()), this, SIGNAL(modified())); } ~WorksheetAccessInterfaceImpl() override = default; QByteArray saveWorksheetToByteArray() override { return m_worksheet->saveToByteArray(); } void loadWorksheetFromByteArray(QByteArray* data) override { m_worksheet->load(data); } Cantor::Session* session() override { return m_worksheet->session(); } void evaluate() override { m_worksheet->evaluate(); } void interrupt() override { m_worksheet->interrupt(); } private: Worksheet* m_worksheet; }; CantorPart::CantorPart( QWidget *parentWidget, QObject *parent, const QVariantList & args ): KParts::ReadWritePart(parent), m_searchBar(nullptr), m_initProgressDlg(nullptr), m_showProgressDlg(true), m_showBackendHelp(nullptr), m_statusBarBlocked(false), m_sessionStatusCounter(0) { m_panelHandler=new Cantor::PanelPluginHandler(this); connect(m_panelHandler, SIGNAL(pluginsChanged()), this, SLOT(pluginsChanged())); QString backendName; if(args.isEmpty()) backendName=QLatin1String("null"); else backendName=args.first().toString(); for (const QVariant& arg : args) { if (arg.toString() == QLatin1String("--noprogress") ) { qWarning()<<"not showing the progress bar by request"; m_showProgressDlg=false; } } Cantor::Backend* b=Cantor::Backend::getBackend(backendName); qDebug()<<"Backend "<name()<<" offers extensions: "<extensions(); auto* collection = actionCollection(); //central widget QWidget* widget = new QWidget(parentWidget); QVBoxLayout* layout = new QVBoxLayout(widget); m_worksheet=new Worksheet(b, widget); m_worksheetview=new WorksheetView(m_worksheet, widget); m_worksheetview->setEnabled(false); //disable input until the session has successfully logged in and emits the ready signal connect(m_worksheet, SIGNAL(modified()), this, SLOT(setModified())); connect(m_worksheet, SIGNAL(showHelp(QString)), this, SIGNAL(showHelp(QString))); connect(m_worksheet, SIGNAL(loaded()), this, SLOT(initialized())); connect(collection, SIGNAL(inserted(QAction*)), m_worksheet, SLOT(registerShortcut(QAction*))); layout->addWidget(m_worksheetview); setWidget(widget); //create WorksheetAccessInterface, used at the moment by LabPlot only to access Worksheet's API Cantor::WorksheetAccessInterface* iface = new WorksheetAccessInterfaceImpl(this, m_worksheet); Q_UNUSED(iface); //initialize actions m_worksheet->createActions(collection); KStandardAction::saveAs(this, SLOT(fileSaveAs()), collection); m_save = KStandardAction::save(this, SLOT(save()), collection); m_save->setPriority(QAction::LowPriority); QAction* savePlain = new QAction(i18n("Save Plain Text"), collection); collection->addAction(QLatin1String("file_save_plain"), savePlain); savePlain->setIcon(QIcon::fromTheme(QLatin1String("document-save"))); connect(savePlain, SIGNAL(triggered()), this, SLOT(fileSavePlain())); QAction* undo = KStandardAction::undo(m_worksheet, SIGNAL(undo()), collection); undo->setPriority(QAction::LowPriority); connect(m_worksheet, SIGNAL(undoAvailable(bool)), undo, SLOT(setEnabled(bool))); m_editActions.push_back(undo); QAction* redo = KStandardAction::redo(m_worksheet, SIGNAL(redo()), collection); redo->setPriority(QAction::LowPriority); connect(m_worksheet, SIGNAL(redoAvailable(bool)), redo, SLOT(setEnabled(bool))); m_editActions.push_back(redo); QAction* cut = KStandardAction::cut(m_worksheet, SIGNAL(cut()), collection); cut->setPriority(QAction::LowPriority); connect(m_worksheet, SIGNAL(cutAvailable(bool)), cut, SLOT(setEnabled(bool))); m_editActions.push_back(cut); QAction* copy = KStandardAction::copy(m_worksheet, SIGNAL(copy()), collection); copy->setPriority(QAction::LowPriority); connect(m_worksheet, SIGNAL(copyAvailable(bool)), copy, SLOT(setEnabled(bool))); QAction* paste = KStandardAction::paste(m_worksheet, SLOT(paste()), collection); paste->setPriority(QAction::LowPriority); connect(m_worksheet, SIGNAL(pasteAvailable(bool)), paste, SLOT(setEnabled(bool))); m_editActions.push_back(paste); QAction* find = KStandardAction::find(this, SLOT(showSearchBar()), collection); find->setPriority(QAction::LowPriority); QAction* replace = KStandardAction::replace(this, SLOT(showExtendedSearchBar()), collection); replace->setPriority(QAction::LowPriority); m_editActions.push_back(replace); m_findNext = KStandardAction::findNext(this, SLOT(findNext()), collection); m_findNext->setEnabled(false); m_findPrev = KStandardAction::findPrev(this, SLOT(findPrev()), collection); m_findPrev->setEnabled(false); QAction* latexExport = new QAction(i18n("Export to LaTeX"), collection); collection->addAction(QLatin1String("file_export_latex"), latexExport); latexExport->setIcon(QIcon::fromTheme(QLatin1String("document-export"))); connect(latexExport, SIGNAL(triggered()), this, SLOT(exportToLatex())); QAction* print = KStandardAction::print(this, SLOT(print()), collection); print->setPriority(QAction::LowPriority); QAction* printPreview = KStandardAction::printPreview(this, SLOT(printPreview()), collection); printPreview->setPriority(QAction::LowPriority); KStandardAction::zoomIn(m_worksheetview, SLOT(zoomIn()), collection); KStandardAction::zoomOut(m_worksheetview, SLOT(zoomOut()), collection); KStandardAction::actualSize(m_worksheetview, SLOT(actualSize()), collection); m_evaluate = new QAction(i18n("Evaluate Worksheet"), collection); collection->addAction(QLatin1String("evaluate_worksheet"), m_evaluate); m_evaluate->setIcon(QIcon::fromTheme(QLatin1String("system-run"))); collection->setDefaultShortcut(m_evaluate, Qt::CTRL+Qt::Key_E); connect(m_evaluate, SIGNAL(triggered()), this, SLOT(evaluateOrInterrupt())); m_editActions.push_back(m_evaluate); m_typeset = new KToggleAction(i18n("Typeset using LaTeX"), collection); m_typeset->setChecked(Settings::self()->typesetDefault()); // Disable until login, because we use session command for this action m_typeset->setEnabled(false); collection->addAction(QLatin1String("enable_typesetting"), m_typeset); connect(m_typeset, SIGNAL(toggled(bool)), this, SLOT(enableTypesetting(bool))); m_highlight = new KToggleAction(i18n("Syntax Highlighting"), collection); m_highlight->setChecked(Settings::self()->highlightDefault()); collection->addAction(QLatin1String("enable_highlighting"), m_highlight); connect(m_highlight, SIGNAL(toggled(bool)), m_worksheet, SLOT(enableHighlighting(bool))); m_completion = new KToggleAction(i18n("Completion"), collection); m_completion->setChecked(Settings::self()->completionDefault()); collection->addAction(QLatin1String("enable_completion"), m_completion); connect(m_completion, SIGNAL(toggled(bool)), m_worksheet, SLOT(enableCompletion(bool))); m_exprNumbering = new KToggleAction(i18n("Line Numbers"), collection); m_exprNumbering->setChecked(Settings::self()->expressionNumberingDefault()); collection->addAction(QLatin1String("enable_expression_numbers"), m_exprNumbering); connect(m_exprNumbering, SIGNAL(toggled(bool)), m_worksheet, SLOT(enableExpressionNumbering(bool))); m_animateWorksheet = new KToggleAction(i18n("Animate Worksheet"), collection); m_animateWorksheet->setChecked(Settings::self()->animationDefault()); collection->addAction(QLatin1String("enable_animations"), m_animateWorksheet); connect(m_animateWorksheet, SIGNAL(toggled(bool)), m_worksheet, SLOT(enableAnimations(bool))); m_restart = new QAction(i18n("Restart Backend"), collection); collection->addAction(QLatin1String("restart_backend"), m_restart); m_restart->setIcon(QIcon::fromTheme(QLatin1String("system-reboot"))); connect(m_restart, SIGNAL(triggered()), this, SLOT(restartBackend())); m_restart->setEnabled(false); // No need show restart button before login m_editActions.push_back(m_restart); QAction* evaluateCurrent = new QAction(QIcon::fromTheme(QLatin1String("media-playback-start")), i18n("Evaluate Entry"), collection); collection->addAction(QLatin1String("evaluate_current"), evaluateCurrent); collection->setDefaultShortcut(evaluateCurrent, Qt::SHIFT + Qt::Key_Return); connect(evaluateCurrent, SIGNAL(triggered()), m_worksheet, SLOT(evaluateCurrentEntry())); m_editActions.push_back(evaluateCurrent); QAction* insertCommandEntry = new QAction(QIcon::fromTheme(QLatin1String("run-build")), i18n("Insert Command Entry"), collection); collection->addAction(QLatin1String("insert_command_entry"), insertCommandEntry); collection->setDefaultShortcut(insertCommandEntry, Qt::CTRL + Qt::Key_Return); connect(insertCommandEntry, SIGNAL(triggered()), m_worksheet, SLOT(insertCommandEntry())); m_editActions.push_back(insertCommandEntry); QAction* insertTextEntry = new QAction(QIcon::fromTheme(QLatin1String("draw-text")), i18n("Insert Text Entry"), collection); collection->addAction(QLatin1String("insert_text_entry"), insertTextEntry); connect(insertTextEntry, SIGNAL(triggered()), m_worksheet, SLOT(insertTextEntry())); m_editActions.push_back(insertTextEntry); #ifdef Discount_FOUND QAction* insertMarkdownEntry = new QAction(QIcon::fromTheme(QLatin1String("text-x-markdown")), i18n("Insert Markdown Entry"), collection); collection->addAction(QLatin1String("insert_markdown_entry"), insertMarkdownEntry); connect(insertMarkdownEntry, SIGNAL(triggered()), m_worksheet, SLOT(insertMarkdownEntry())); m_editActions.push_back(insertMarkdownEntry); #endif #ifdef WITH_EPS QAction* insertLatexEntry = new QAction(QIcon::fromTheme(QLatin1String("text-x-tex")), i18n("Insert Latex Entry"), collection); collection->addAction(QLatin1String("insert_latex_entry"), insertLatexEntry); connect(insertLatexEntry, SIGNAL(triggered()), m_worksheet, SLOT(insertLatexEntry())); m_editActions.push_back(insertLatexEntry); #endif QAction* insertPageBreakEntry = new QAction(QIcon::fromTheme(QLatin1String("go-next-view-page")), i18n("Insert Page Break"), collection); collection->addAction(QLatin1String("insert_page_break_entry"), insertPageBreakEntry); connect(insertPageBreakEntry, SIGNAL(triggered()), m_worksheet, SLOT(insertPageBreakEntry())); m_editActions.push_back(insertPageBreakEntry); QAction* insertImageEntry = new QAction(QIcon::fromTheme(QLatin1String("image-x-generic")), i18n("Insert Image"), collection); collection->addAction(QLatin1String("insert_image_entry"), insertImageEntry); connect(insertImageEntry, SIGNAL(triggered()), m_worksheet, SLOT(insertImageEntry())); m_editActions.push_back(insertImageEntry); QAction* removeCurrent = new QAction(QIcon::fromTheme(QLatin1String("edit-delete")), i18n("Remove current Entry"), collection); collection->addAction(QLatin1String("remove_current"), removeCurrent); collection->setDefaultShortcut(removeCurrent, Qt::ShiftModifier + Qt::Key_Delete); connect(removeCurrent, SIGNAL(triggered()), m_worksheet, SLOT(removeCurrentEntry())); m_editActions.push_back(removeCurrent); m_showBackendHelp = new QAction(i18n("Show %1 Help", b->name()) , collection); m_showBackendHelp->setIcon(QIcon::fromTheme(QLatin1String("help-contents"))); collection->addAction(QLatin1String("backend_help"), m_showBackendHelp); connect(m_showBackendHelp, SIGNAL(triggered()), this, SLOT(showBackendHelp())); // Disabled, because uploading to kde store from program don't work // See https://phabricator.kde.org/T9980 for details // If this situation will changed, then uncomment this action /* QAction* publishWorksheet = new QAction(i18n("Publish Worksheet"), collection); publishWorksheet->setIcon(QIcon::fromTheme(QLatin1String("get-hot-new-stuff"))); collection->addAction(QLatin1String("file_publish_worksheet"), publishWorksheet); connect(publishWorksheet, SIGNAL(triggered()), this, SLOT(publishWorksheet())); */ KToggleAction* showEditor = new KToggleAction(i18n("Show Script Editor"), collection); showEditor->setChecked(false); collection->addAction(QLatin1String("show_editor"), showEditor); connect(showEditor, SIGNAL(toggled(bool)), this, SLOT(showScriptEditor(bool))); showEditor->setEnabled(b->extensions().contains(QLatin1String("ScriptExtension"))); QAction* showCompletion = new QAction(i18n("Show Completion"), collection); collection->addAction(QLatin1String("show_completion"), showCompletion); QList showCompletionShortcuts; showCompletionShortcuts << Qt::Key_Tab << Qt::CTRL + Qt::Key_Space; collection->setDefaultShortcuts(showCompletion, showCompletionShortcuts); connect(showCompletion, SIGNAL(triggered()), m_worksheet, SLOT(showCompletion())); m_editActions.push_back(showCompletion); // set our XML-UI resource file setXMLFile(QLatin1String("cantor_part.rc")); // we are read-write by default setReadWrite(true); // we are not modified since we haven't done anything yet setModified(false); initialized(); } CantorPart::~CantorPart() { if (m_scriptEditor) { disconnect(m_scriptEditor, SIGNAL(destroyed()), this, SLOT(scriptEditorClosed())); delete m_scriptEditor; } if (m_searchBar) delete m_searchBar; } void CantorPart::setReadWrite(bool rw) { // notify your internal widget of the read-write state m_worksheetview->setInteractive(rw); ReadWritePart::setReadWrite(rw); } void CantorPart::setReadOnly() { for (QAction* action : m_editActions) action->setEnabled(false); if (m_showBackendHelp) { m_showBackendHelp->setEnabled(false); m_showBackendHelp->setVisible(false); } } void CantorPart::setModified(bool modified) { // get a handle on our Save action and make sure it is valid if (!m_save) return; // if so, we either enable or disable it based on the current state m_save->setEnabled(modified); // in any event, we want our parent to do it's thing ReadWritePart::setModified(modified); } KAboutData& CantorPart::createAboutData() { // the non-i18n name here must be the same as the directory in // which the part's rc file is installed ('partrcdir' in the Makefile) static KAboutData about(QLatin1String("cantorpart"), QLatin1String("Cantor"), QLatin1String(CANTOR_VERSION), i18n("CantorPart"), KAboutLicense::GPL, i18n("(C) 2009-2015 Alexander Rieder"), QString(), QLatin1String("http://edu.kde.org/cantor")); about.addAuthor( i18n("Alexander Rieder"), QString(), QLatin1String("alexanderrieder@gmail.com") ); return about; } bool CantorPart::openFile() { //don't crash if for some reason the worksheet is invalid if(m_worksheet==nullptr) { qWarning()<<"trying to open in an invalid cantor part"; return false; } QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); QElapsedTimer timer; timer.start(); const bool rc = m_worksheet->load(localFilePath()); QApplication::restoreOverrideCursor(); if (rc) { qDebug()<< "Worksheet successfully loaded in " << (float)timer.elapsed()/1000 << " seconds"; updateCaption(); // We modified, but it we load file now, so no need in save option setModified(false); } return rc; } bool CantorPart::saveFile() { // if we aren't read-write, return immediately if (isReadWrite() == false) return false; qDebug()<<"saving to: "<save( localFilePath() ); setModified(false); return true; } void CantorPart::fileSaveAs() { // this slot is called whenever the File->Save As menu is selected - QString worksheetFilter = i18n("Cantor Worksheet (*.cws)"); - QString filter = worksheetFilter; + static const QString& worksheetFilter = i18n("Cantor Worksheet (*.cws)"); + static const QString& notebookFilter = i18n("Jupyter Notebook (*.ipynb)"); + QString filter = worksheetFilter + QLatin1String(";;") + notebookFilter; if (!m_worksheet->isReadOnly()) { //if the backend supports scripts, also append their scriptFile endings to the filter Cantor::Backend * const backend=m_worksheet->session()->backend(); if (backend->extensions().contains(QLatin1String("ScriptExtension"))) { Cantor::ScriptExtension* e=dynamic_cast(backend->extension(QLatin1String("ScriptExtension"))); filter+=QLatin1String(";;")+e->scriptFileFilter(); } } QString selectedFilter; QString file_name = QFileDialog::getSaveFileName(widget(), i18n("Save as"), QString(), filter, &selectedFilter); if (file_name.isEmpty()) return; //depending on user's selection, save as a worksheet or as a plain script file if (selectedFilter == worksheetFilter) { if (!file_name.endsWith(QLatin1String(".cws"))) file_name += QLatin1String(".cws"); + m_worksheet->setType(Worksheet::CantorWorksheet); + saveAs(QUrl::fromLocalFile(file_name)); + } + else if (selectedFilter == notebookFilter) + { + if (!file_name.endsWith(QLatin1String(".ipynb"))) + file_name += QLatin1String(".ipynb"); + m_worksheet->setType(Worksheet::JupyterNotebook); saveAs(QUrl::fromLocalFile(file_name)); } else m_worksheet->savePlain(file_name); updateCaption(); } void CantorPart::fileSavePlain() { QString file_name = QFileDialog::getSaveFileName(widget(), i18n("Save"), QString(), QString()); if (!file_name.isEmpty()) m_worksheet->savePlain(file_name); } void CantorPart::exportToLatex() { QString file_name = QFileDialog::getSaveFileName(widget(), i18n("Export to LaTeX"), QString(), QString()); if (file_name.isEmpty() == false) { if (!file_name.endsWith(QLatin1String(".tex"))) file_name += QLatin1String(".tex"); m_worksheet->saveLatex(file_name); } } void CantorPart::guiActivateEvent( KParts::GUIActivateEvent * event ) { KParts::ReadWritePart::guiActivateEvent(event); if(event->activated()) { if(m_scriptEditor) m_scriptEditor->show(); }else { if(m_scriptEditor) m_scriptEditor->hide(); } } void CantorPart::evaluateOrInterrupt() { qDebug()<<"evalorinterrupt"; if(m_worksheet->isRunning()) m_worksheet->interrupt(); else m_worksheet->evaluate(); } void CantorPart::restartBackend() { bool restart = false; if (Settings::self()->warnAboutSessionRestart()) { KMessageBox::ButtonCode tmp; // If we want the question box, but it is disable, then enable it if (!KMessageBox::shouldBeShownYesNo(QLatin1String("WarnAboutSessionRestart"), tmp)) KMessageBox::enableMessage(QLatin1String("WarnAboutSessionRestart")); const QString& name = m_worksheet->session()->backend()->name(); KMessageBox::ButtonCode rc = KMessageBox::questionYesNo(widget(), i18n("All the available calculation results will be lost. Do you really want to restart %1?", name), i18n("Restart %1?", name), KStandardGuiItem::yes(), KStandardGuiItem::no(), QLatin1String("WarnAboutSessionRestart") ); // Update setting's value Settings::self()->setWarnAboutSessionRestart( KMessageBox::shouldBeShownYesNo(QLatin1String("WarnAboutSessionRestart"), tmp)); Settings::self()->save(); restart = rc == KMessageBox::ButtonCode::Yes; } else { KMessageBox::ButtonCode rc; KMessageBox::shouldBeShownYesNo(QLatin1String("WarnAboutSessionRestart"), rc); restart = rc == KMessageBox::ButtonCode::Yes; } if (restart) { m_worksheet->session()->logout(); m_worksheet->loginToSession(); } } void CantorPart::worksheetStatusChanged(Cantor::Session::Status status) { qDebug()<<"wsStatusChange"<session()->status() == Cantor::Session::Running && m_sessionStatusCounter == count) { m_evaluate->setText(i18n("Interrupt")); m_evaluate->setShortcut(Qt::CTRL+Qt::Key_I); m_evaluate->setIcon(QIcon::fromTheme(QLatin1String("dialog-close"))); setStatusMessage(i18n("Calculating...")); } }); }else if (status==Cantor::Session::Done) { m_evaluate->setText(i18n("Evaluate Worksheet")); m_evaluate->setShortcut(Qt::CTRL+Qt::Key_E); m_evaluate->setIcon(QIcon::fromTheme(QLatin1String("system-run"))); setStatusMessage(i18n("Ready")); } } void CantorPart::showSessionError(const QString& message) { qDebug()<<"Error: "<isReadOnly()) { connect(m_worksheet->session(), SIGNAL(statusChanged(Cantor::Session::Status)), this, SLOT(worksheetStatusChanged(Cantor::Session::Status))); connect(m_worksheet->session(), SIGNAL(loginStarted()),this, SLOT(worksheetSessionLoginStarted())); connect(m_worksheet->session(), SIGNAL(loginDone()),this, SLOT(worksheetSessionLoginDone())); connect(m_worksheet->session(), SIGNAL(error(QString)), this, SLOT(showSessionError(QString))); loadAssistants(); m_panelHandler->setSession(m_worksheet->session()); adjustGuiToSession(); // Don't set modification flag, if we add command entry in empty worksheet const bool modified = this->isModified(); if (m_worksheet->isEmpty()) m_worksheet->appendCommandEntry(); setModified(modified); } else { setReadOnly(); // Clear assistants for (KXMLGUIClient* client: childClients()) { Cantor::Assistant* assistant = dynamic_cast(client); if (assistant) { factory()->removeClient(client); removeChildClient(client); assistant->deleteLater(); } } } m_worksheetview->setEnabled(true); m_worksheetview->setFocus(); setStatusMessage(i18n("Initialization complete")); updateCaption(); } void CantorPart::worksheetSessionLoginStarted() { setStatusMessage(i18n("Initializing...")); QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); } void CantorPart::worksheetSessionLoginDone() { setStatusMessage(i18n("Ready")); m_typeset->setEnabled(true); m_restart->setEnabled(true); QApplication::restoreOverrideCursor(); } void CantorPart::enableTypesetting(bool enable) { m_worksheet->session()->setTypesettingEnabled(enable); } void CantorPart::showBackendHelp() { qDebug()<<"showing backends help"; Cantor::Backend* backend=m_worksheet->session()->backend(); QUrl url = backend->helpUrl(); qDebug()<<"launching url "<isReadOnly()) emit setCaption(filename, QIcon::fromTheme(m_worksheet->session()->backend()->icon())); else emit setCaption(filename+QLatin1Char(' ')+i18n("[read-only]"), QIcon()); } void CantorPart::pluginsChanged() { for (auto* plugin : m_panelHandler->plugins()) connect(plugin, SIGNAL(requestRunCommand(QString)), this, SLOT(runCommand(QString))); } void CantorPart::loadAssistants() { qDebug()<<"loading assistants..."; QStringList assistantDirs; for (const QString& dir : QCoreApplication::libraryPaths()) assistantDirs << dir + QDir::separator() + QLatin1String("cantor/assistants"); QPluginLoader loader; for (const QString& dir : assistantDirs) { qDebug() << "dir: " << dir; QStringList assistants; QDir assistantDir = QDir(dir); assistants = assistantDir.entryList(); for (const QString& assistant : assistants) { if (assistant==QLatin1String(".") || assistant==QLatin1String("..")) continue; loader.setFileName(dir + QDir::separator() + assistant); if (!loader.load()){ qDebug() << "Error while loading assistant: " << assistant; continue; } KPluginFactory* factory = KPluginLoader(loader.fileName()).factory(); Cantor::Assistant* plugin = factory->create(this); Cantor::Backend* backend=worksheet()->session()->backend(); KPluginMetaData info(loader); plugin->setPluginInfo(info); plugin->setBackend(backend); bool supported=true; for (const QString& req : plugin->requiredExtensions()) supported=supported && backend->extensions().contains(req); if(supported) { qDebug() << "plugin " << info.name() << " is supported by " << backend->name() << ", requires extensions " << plugin->requiredExtensions(); plugin->initActions(); connect(plugin, SIGNAL(requested()), this, SLOT(runAssistant())); }else { qDebug() << "plugin " << info.name() << " is not supported by "<name(); removeChildClient(plugin); plugin->deleteLater(); } } } } void CantorPart::runAssistant() { Cantor::Assistant* a=qobject_cast(sender()); QStringList cmds=a->run(widget()); qDebug()<appendCommandEntry(cmd); } void CantorPart::showSearchBar() { if (!m_searchBar) { m_searchBar = new SearchBar(widget(), m_worksheet); widget()->layout()->addWidget(m_searchBar); connect(m_searchBar, SIGNAL(destroyed(QObject*)), this, SLOT(searchBarDeleted())); } m_findNext->setEnabled(true); m_findPrev->setEnabled(true); m_searchBar->showStandard(); m_searchBar->setFocus(); } void CantorPart::showExtendedSearchBar() { if (!m_searchBar) { m_searchBar = new SearchBar(widget(), m_worksheet); widget()->layout()->addWidget(m_searchBar); connect(m_searchBar, SIGNAL(destroyed(QObject*)), this, SLOT(searchBarDeleted())); } m_findNext->setEnabled(true); m_findPrev->setEnabled(true); m_searchBar->showExtended(); m_searchBar->setFocus(); } void CantorPart::findNext() { if (m_searchBar) m_searchBar->next(); } void CantorPart::findPrev() { if (m_searchBar) m_searchBar->prev(); } void CantorPart::searchBarDeleted() { m_searchBar = nullptr; m_findNext->setEnabled(false); m_findPrev->setEnabled(false); } void CantorPart::adjustGuiToSession() { Cantor::Backend::Capabilities capabilities = m_worksheet->session()->backend()->capabilities(); #ifdef WITH_EPS m_typeset->setVisible(capabilities.testFlag(Cantor::Backend::LaTexOutput)); #else m_typeset->setVisible(false); #endif m_completion->setVisible(capabilities.testFlag(Cantor::Backend::Completion)); //this is 0 on the first call if(m_showBackendHelp) m_showBackendHelp->setText(i18n("Show %1 Help", m_worksheet->session()->backend()->name())); } void CantorPart::publishWorksheet() { int ret = KMessageBox::questionYesNo(widget(), i18n("Do you want to upload current Worksheet to public web server?"), i18n("Question - Cantor")); if (ret != KMessageBox::Yes) return; if (isModified()||url().isEmpty()) { ret = KMessageBox::warningContinueCancel(widget(), i18n("The Worksheet is not saved. You should save it before uploading."), i18n("Warning - Cantor"), KStandardGuiItem::save(), KStandardGuiItem::cancel()); if (ret != KMessageBox::Continue) return; if (!saveFile()) return; } qDebug()<<"uploading file "<session()->backend()->id().toLower()), widget()); dialog.setUploadFile(url()); dialog.exec(); } void CantorPart::print() { QPrinter printer; QPointer dialog = new QPrintDialog(&printer, widget()); // TODO: Re-enable print selection //if (m_worksheet->textCursor().hasSelection()) // dialog->addEnabledOption(QAbstractPrintDialog::PrintSelection); if (dialog->exec() == QDialog::Accepted) m_worksheet->print(&printer); delete dialog; } void CantorPart::printPreview() { QPrintPreviewDialog *dialog = new QPrintPreviewDialog(widget()); connect(dialog, SIGNAL(paintRequested(QPrinter*)), m_worksheet, SLOT(print(QPrinter*))); dialog->exec(); } void CantorPart::showScriptEditor(bool show) { if(show) { if (m_scriptEditor) { return; } Cantor::ScriptExtension* scriptE=dynamic_cast(m_worksheet->session()->backend()->extension(QLatin1String("ScriptExtension"))); if (!scriptE) { return; } m_scriptEditor=new ScriptEditorWidget(scriptE->scriptFileFilter(), scriptE->highlightingMode(), widget()->window()); connect(m_scriptEditor, SIGNAL(runScript(QString)), this, SLOT(runScript(QString))); connect(m_scriptEditor, SIGNAL(destroyed()), this, SLOT(scriptEditorClosed())); m_scriptEditor->show(); }else { m_scriptEditor->deleteLater(); } } void CantorPart::scriptEditorClosed() { QAction* showEditor = actionCollection()->action(QLatin1String("show_editor")); if (showEditor) { showEditor->setChecked(false); } } void CantorPart::runScript(const QString& file) { Cantor::Backend* backend=m_worksheet->session()->backend(); if(!backend->extensions().contains(QLatin1String("ScriptExtension"))) { KMessageBox::error(widget(), i18n("This backend does not support scripts."), i18n("Error - Cantor")); return; } Cantor::ScriptExtension* scriptE=dynamic_cast(backend->extension(QLatin1String("ScriptExtension"))); m_worksheet->appendCommandEntry(scriptE->runExternalScript(file)); } void CantorPart::blockStatusBar() { m_statusBarBlocked=true; } void CantorPart::unblockStatusBar() { m_statusBarBlocked=false; if(!m_cachedStatusMessage.isNull()) setStatusMessage(m_cachedStatusMessage); m_cachedStatusMessage.clear(); } void CantorPart::setStatusMessage(const QString& message) { if(!m_statusBarBlocked) emit setStatusBarText(message); else m_cachedStatusMessage=message; } void CantorPart::showImportantStatusMessage(const QString& message) { setStatusMessage(message); blockStatusBar(); QTimer::singleShot(3000, this, SLOT(unblockStatusBar())); } K_PLUGIN_FACTORY_WITH_JSON(CantorPartFactory, "cantor_part.json", registerPlugin();) #include "cantor_part.moc" diff --git a/src/commandentry.cpp b/src/commandentry.cpp index 9d1a5432..a678978b 100644 --- a/src/commandentry.cpp +++ b/src/commandentry.cpp @@ -1,1229 +1,1297 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2009 Alexander Rieder Copyright (C) 2012 Martin Kuettler Copyright (C) 2018 Alexander Semke */ #include "commandentry.h" #include "resultitem.h" #include "loadedexpression.h" #include "lib/result.h" #include "lib/helpresult.h" +#include "lib/epsresult.h" +#include "lib/latexresult.h" #include "lib/completionobject.h" #include "lib/syntaxhelpobject.h" #include "lib/session.h" #include #include #include #include #include #include #include #include #include #include +#include #include #include const QString CommandEntry::Prompt = QLatin1String(">>> "); const QString CommandEntry::MidPrompt = QLatin1String(">> "); const QString CommandEntry::HidePrompt = QLatin1String("> "); const double CommandEntry::HorizontalSpacing = 4; const double CommandEntry::VerticalSpacing = 4; static const int colorsCount = 26; static QColor colors[colorsCount] = {QColor(255,255,255), QColor(0,0,0), QColor(192,0,0), QColor(255,0,0), QColor(255,192,192), //red QColor(0,192,0), QColor(0,255,0), QColor(192,255,192), //green QColor(0,0,192), QColor(0,0,255), QColor(192,192,255), //blue QColor(192,192,0), QColor(255,255,0), QColor(255,255,192), //yellow QColor(0,192,192), QColor(0,255,255), QColor(192,255,255), //cyan QColor(192,0,192), QColor(255,0,255), QColor(255,192,255), //magenta QColor(192,88,0), QColor(255,128,0), QColor(255,168,88), //orange QColor(128,128,128), QColor(160,160,160), QColor(195,195,195) //grey }; CommandEntry::CommandEntry(Worksheet* worksheet) : WorksheetEntry(worksheet), m_promptItem(new WorksheetTextItem(this, Qt::NoTextInteraction)), m_commandItem(new WorksheetTextItem(this, Qt::TextEditorInteraction)), m_resultsCollapsed(false), m_errorItem(nullptr), m_expression(nullptr), m_completionObject(nullptr), m_syntaxHelpObject(nullptr), m_evaluationOption(DoNothing), m_menusInitialized(false), m_backgroundColorActionGroup(nullptr), m_backgroundColorMenu(nullptr), m_textColorActionGroup(nullptr), m_textColorMenu(nullptr), m_fontMenu(nullptr) { m_promptItem->setPlainText(Prompt); m_promptItem->setItemDragable(true); m_commandItem->enableCompletion(true); KColorScheme scheme = KColorScheme(QPalette::Normal, KColorScheme::View); m_commandItem->setBackgroundColor(scheme.background(KColorScheme::AlternateBackground).color()); m_promptItemAnimation = new QPropertyAnimation(m_promptItem, "opacity"); m_promptItemAnimation->setDuration(600); m_promptItemAnimation->setStartValue(1); m_promptItemAnimation->setKeyValueAt(0.5, 0); m_promptItemAnimation->setEndValue(1); connect(m_promptItemAnimation, &QPropertyAnimation::finished, this, &CommandEntry::animatePromptItem); connect(m_commandItem, &WorksheetTextItem::tabPressed, this, &CommandEntry::showCompletion); connect(m_commandItem, &WorksheetTextItem::backtabPressed, this, &CommandEntry::selectPreviousCompletion); connect(m_commandItem, &WorksheetTextItem::applyCompletion, this, &CommandEntry::applySelectedCompletion); connect(m_commandItem, SIGNAL(execute()), this, SLOT(evaluate())); connect(m_commandItem, &WorksheetTextItem::moveToPrevious, this, &CommandEntry::moveToPreviousItem); connect(m_commandItem, &WorksheetTextItem::moveToNext, this, &CommandEntry::moveToNextItem); connect(m_commandItem, SIGNAL(receivedFocus(WorksheetTextItem*)), worksheet, SLOT(highlightItem(WorksheetTextItem*))); connect(m_promptItem, &WorksheetTextItem::drag, this, &CommandEntry::startDrag); connect(worksheet, SIGNAL(updatePrompt()), this, SLOT(updatePrompt())); } CommandEntry::~CommandEntry() { if (m_completionBox) m_completionBox->deleteLater(); } int CommandEntry::type() const { return Type; } void CommandEntry::initMenus() { //background color const QString colorNames[colorsCount] = {i18n("White"), i18n("Black"), i18n("Dark Red"), i18n("Red"), i18n("Light Red"), i18n("Dark Green"), i18n("Green"), i18n("Light Green"), i18n("Dark Blue"), i18n("Blue"), i18n("Light Blue"), i18n("Dark Yellow"), i18n("Yellow"), i18n("Light Yellow"), i18n("Dark Cyan"), i18n("Cyan"), i18n("Light Cyan"), i18n("Dark Magenta"), i18n("Magenta"), i18n("Light Magenta"), i18n("Dark Orange"), i18n("Orange"), i18n("Light Orange"), i18n("Dark Grey"), i18n("Grey"), i18n("Light Grey") }; //background color m_backgroundColorActionGroup = new QActionGroup(this); m_backgroundColorActionGroup->setExclusive(true); connect(m_backgroundColorActionGroup, &QActionGroup::triggered, this, &CommandEntry::backgroundColorChanged); m_backgroundColorMenu = new QMenu(i18n("Background Color")); m_backgroundColorMenu->setIcon(QIcon::fromTheme(QLatin1String("format-fill-color"))); QPixmap pix(16,16); QPainter p(&pix); for (int i=0; isetCheckable(true); m_backgroundColorMenu->addAction(action); } //text color m_textColorActionGroup = new QActionGroup(this); m_textColorActionGroup->setExclusive(true); connect(m_textColorActionGroup, &QActionGroup::triggered, this, &CommandEntry::textColorChanged); m_textColorMenu = new QMenu(i18n("Text Color")); m_textColorMenu->setIcon(QIcon::fromTheme(QLatin1String("format-text-color"))); for (int i=0; isetCheckable(true); m_textColorMenu->addAction(action); } //font m_fontMenu = new QMenu(i18n("Font")); m_fontMenu->setIcon(QIcon::fromTheme(QLatin1String("preferences-desktop-font"))); QAction* action = new QAction(QIcon::fromTheme(QLatin1String("format-text-bold")), i18n("Bold")); action->setCheckable(true); connect(action, &QAction::triggered, this, &CommandEntry::fontBoldTriggered); m_fontMenu->addAction(action); action = new QAction(QIcon::fromTheme(QLatin1String("format-text-italic")), i18n("Italic")); action->setCheckable(true); connect(action, &QAction::triggered, this, &CommandEntry::fontItalicTriggered); m_fontMenu->addAction(action); m_fontMenu->addSeparator(); action = new QAction(QIcon::fromTheme(QLatin1String("format-font-size-less")), i18n("Increase Size")); connect(action, &QAction::triggered, this, &CommandEntry::fontIncreaseTriggered); m_fontMenu->addAction(action); action = new QAction(QIcon::fromTheme(QLatin1String("format-font-size-more")), i18n("Decrease Size")); connect(action, &QAction::triggered, this, &CommandEntry::fontDecreaseTriggered); m_fontMenu->addAction(action); m_fontMenu->addSeparator(); action = new QAction(QIcon::fromTheme(QLatin1String("preferences-desktop-font")), i18n("Select")); connect(action, &QAction::triggered, this, &CommandEntry::fontSelectTriggered); m_fontMenu->addAction(action); m_menusInitialized = true; } void CommandEntry::backgroundColorChanged(QAction* action) { int index = m_backgroundColorActionGroup->actions().indexOf(action); if (index == -1 || index>=colorsCount) index = 0; m_commandItem->setBackgroundColor(colors[index]); } void CommandEntry::textColorChanged(QAction* action) { int index = m_textColorActionGroup->actions().indexOf(action); if (index == -1 || index>=colorsCount) index = 0; m_commandItem->setDefaultTextColor(colors[index]); } void CommandEntry::fontBoldTriggered() { QAction* action = static_cast(QObject::sender()); QFont font = m_commandItem->font(); font.setBold(action->isChecked()); m_commandItem->setFont(font); } void CommandEntry::fontItalicTriggered() { QAction* action = static_cast(QObject::sender()); QFont font = m_commandItem->font(); font.setItalic(action->isChecked()); m_commandItem->setFont(font); } void CommandEntry::fontIncreaseTriggered() { QFont font = m_commandItem->font(); const int currentSize = font.pointSize(); QFontDatabase fdb; QList sizes = fdb.pointSizes(font.family(), font.styleName()); for (int i = 0; i < sizes.size(); ++i) { const int size = sizes.at(i); if (currentSize == size) { if (i + 1 < sizes.size()) { font.setPointSize(sizes.at(i+1)); m_commandItem->setFont(font); } break; } } } void CommandEntry::fontDecreaseTriggered() { QFont font = m_commandItem->font(); const int currentSize = font.pointSize(); QFontDatabase fdb; QList sizes = fdb.pointSizes(font.family(), font.styleName()); for (int i = 0; i < sizes.size(); ++i) { const int size = sizes.at(i); if (currentSize == size) { if (i - 1 >= 0) { font.setPointSize(sizes.at(i-1)); m_commandItem->setFont(font); } break; } } } void CommandEntry::fontSelectTriggered() { bool ok; QFont font = QFontDialog::getFont(&ok, m_commandItem->font(), nullptr); if (ok) m_commandItem->setFont(font); } void CommandEntry::populateMenu(QMenu* menu, QPointF pos) { if (!m_menusInitialized) initMenus(); if (!m_resultItems.isEmpty()) { if (m_resultsCollapsed) menu->addAction(i18n("Show Results"), this, &CommandEntry::expandResults); else menu->addAction(i18n("Hide Results"), this, &CommandEntry::collapseResults); } menu->addMenu(m_backgroundColorMenu); menu->addMenu(m_textColorMenu); menu->addMenu(m_fontMenu); menu->addSeparator(); WorksheetEntry::populateMenu(menu, pos); } void CommandEntry::moveToNextItem(int pos, qreal x) { WorksheetTextItem* item = qobject_cast(sender()); if (!item) return; if (item == m_commandItem) { if (m_informationItems.isEmpty() || !currentInformationItem()->isEditable()) moveToNextEntry(pos, x); else currentInformationItem()->setFocusAt(pos, x); } else if (item == currentInformationItem()) { moveToNextEntry(pos, x); } } void CommandEntry::moveToPreviousItem(int pos, qreal x) { WorksheetTextItem* item = qobject_cast(sender()); if (!item) return; if (item == m_commandItem || item == nullptr) { moveToPreviousEntry(pos, x); } else if (item == currentInformationItem()) { m_commandItem->setFocusAt(pos, x); } } QString CommandEntry::command() { QString cmd = m_commandItem->toPlainText(); cmd.replace(QChar::ParagraphSeparator, QLatin1Char('\n')); //Replace the U+2029 paragraph break by a Normal Newline cmd.replace(QChar::LineSeparator, QLatin1Char('\n')); //Replace the line break by a Normal Newline return cmd; } void CommandEntry::setExpression(Cantor::Expression* expr) { /* if ( m_expression ) { if (m_expression->status() == Cantor::Expression::Computing) { qDebug() << "OLD EXPRESSION STILL ACTIVE"; m_expression->interrupt(); } m_expression->deleteLater(); }*/ // Delete any previous error if(m_errorItem) { m_errorItem->deleteLater(); m_errorItem = nullptr; } foreach(WorksheetTextItem* item, m_informationItems) { item->deleteLater(); } m_informationItems.clear(); // Delete any previous result clearResultItems(); m_expression = expr; m_resultsCollapsed = false; connect(expr, SIGNAL(gotResult()), this, SLOT(updateEntry())); connect(expr, SIGNAL(resultsCleared()), this, SLOT(clearResultItems())); connect(expr, SIGNAL(resultRemoved(int)), this, SLOT(removeResultItem(int))); connect(expr, SIGNAL(resultReplaced(int)), this, SLOT(replaceResultItem(int))); connect(expr, SIGNAL(idChanged()), this, SLOT(updatePrompt())); connect(expr, SIGNAL(statusChanged(Cantor::Expression::Status)), this, SLOT(expressionChangedStatus(Cantor::Expression::Status))); connect(expr, SIGNAL(needsAdditionalInformation(QString)), this, SLOT(showAdditionalInformationPrompt(QString))); connect(expr, SIGNAL(statusChanged(Cantor::Expression::Status)), this, SLOT(updatePrompt())); updatePrompt(); if(expr->result()) { worksheet()->gotResult(expr); updateEntry(); } expressionChangedStatus(expr->status()); } Cantor::Expression* CommandEntry::expression() { return m_expression; } bool CommandEntry::acceptRichText() { return false; } void CommandEntry::setContent(const QString& content) { m_commandItem->setPlainText(content); } void CommandEntry::setContent(const QDomElement& content, const KZip& file) { m_commandItem->setPlainText(content.firstChildElement(QLatin1String("Command")).text()); LoadedExpression* expr=new LoadedExpression( worksheet()->session() ); expr->loadFromXml(content, file); //background color QDomElement backgroundElem = content.firstChildElement(QLatin1String("Background")); if (!backgroundElem.isNull()) { QColor color; color.setRed(backgroundElem.attribute(QLatin1String("red")).toInt()); color.setGreen(backgroundElem.attribute(QLatin1String("green")).toInt()); color.setBlue(backgroundElem.attribute(QLatin1String("blue")).toInt()); m_commandItem->setBackgroundColor(color); } //text properties QDomElement textElem = content.firstChildElement(QLatin1String("Text")); if (!textElem.isNull()) { //text color QDomElement colorElem = textElem.firstChildElement(QLatin1String("Color")); QColor color; color.setRed(colorElem.attribute(QLatin1String("red")).toInt()); color.setGreen(colorElem.attribute(QLatin1String("green")).toInt()); color.setBlue(colorElem.attribute(QLatin1String("blue")).toInt()); m_commandItem->setDefaultTextColor(color); //font properties QDomElement fontElem = textElem.firstChildElement(QLatin1String("Font")); QFont font; font.setFamily(fontElem.attribute(QLatin1String("family"))); font.setPointSize(fontElem.attribute(QLatin1String("pointSize")).toInt()); font.setWeight(fontElem.attribute(QLatin1String("weight")).toInt()); font.setItalic(fontElem.attribute(QLatin1String("italic")).toInt()); m_commandItem->setFont(font); } setExpression(expr); } void CommandEntry::setContentFromJupyter(const QJsonObject& cell) { const QJsonValue& source = cell.value(QLatin1String("source")); QString code; if (source.isString()) code = source.toString(); else if (source.isArray()) for (const QJsonValue& line : source.toArray()) code += line.toString(); m_commandItem->setPlainText(code); LoadedExpression* expr=new LoadedExpression( worksheet()->session() ); expr->loadFromJupyter(cell); setExpression(expr); } +QJsonValue CommandEntry::toJupyterJson() +{ + QJsonObject entry; + + entry.insert(QLatin1String("cell_type"), QLatin1String("code")); + + QJsonValue executionCountValue; + if (expression() && expression()->id() != -1) + executionCountValue = QJsonValue(expression()->id()); + entry.insert(QLatin1String("execution_count"), executionCountValue); + + // Jupyter TODO: support metadata info, collapsed for example + QJsonObject metadata; + entry.insert(QLatin1String("metadata"), metadata); + + QJsonArray text; + const QStringList& lines = command().split(QLatin1Char('\n')); + for (int i = 0; i < lines.size(); i++) + { + QString line = lines[i]; + // Don't add \n to last line + if (i != lines.size() - 1) + line.append(QLatin1Char('\n')); + text.append(line); + } + entry.insert(QLatin1String("source"), text); + + QJsonArray outputs; + if (expression()) + for (Cantor::Result * const result: expression()->results()) + { + const QJsonValue& resultJson = result->toJupyterJson(); + + // Jupyter TODO: Convert EpsResult here? + if (result->type() == Cantor::EpsResult::Type || result->type() == Cantor::LatexResult::Type) + { + QJsonObject root; + + root.insert(QLatin1String("output_type"), QLatin1String("display_data")); + + QJsonObject data; + data.insert(QLatin1String("text/plain"), QString()); + + const QImage& image = worksheet()->epsRenderer()->renderToImage(result->data().toUrl()); + + QByteArray ba; + QBuffer buffer(&ba); + buffer.open(QIODevice::WriteOnly); + image.save(&buffer, "PNG"); + data.insert(QLatin1String("image/png"), QString::fromLatin1(ba.toBase64())); + + root.insert(QLatin1String("data"), data); + + // Jupyter TODO: handle metadata? + root.insert(QLatin1String("metadata"), QJsonObject()); + + outputs.append(root); + } + else if (!resultJson.isNull()) + outputs.append(resultJson); + } + entry.insert(QLatin1String("outputs"), outputs); + + return entry; +} void CommandEntry::showCompletion() { const QString line = currentLine(); if(!worksheet()->completionEnabled() || line.trimmed().isEmpty()) { if (m_commandItem->hasFocus()) m_commandItem->insertTab(); return; } else if (isShowingCompletionPopup()) { QString comp = m_completionObject->completion(); qDebug() << "command" << m_completionObject->command(); qDebug() << "completion" << comp; if (comp != m_completionObject->command() || !m_completionObject->hasMultipleMatches()) { if (m_completionObject->hasMultipleMatches()) { completeCommandTo(comp, PreliminaryCompletion); } else { completeCommandTo(comp, FinalCompletion); m_completionBox->hide(); } } else { m_completionBox->down(); } } else { int p = m_commandItem->textCursor().positionInBlock(); Cantor::CompletionObject* tco=worksheet()->session()->completionFor(line, p); if(tco) setCompletion(tco); } } void CommandEntry::selectPreviousCompletion() { if (isShowingCompletionPopup()) m_completionBox->up(); } QString CommandEntry::toPlain(const QString& commandSep, const QString& commentStartingSeq, const QString& commentEndingSeq) { Q_UNUSED(commentStartingSeq); Q_UNUSED(commentEndingSeq); if (command().isEmpty()) return QString(); return command() + commandSep; } QDomElement CommandEntry::toXml(QDomDocument& doc, KZip* archive) { QDomElement exprElem = doc.createElement( QLatin1String("Expression") ); QDomElement cmdElem = doc.createElement( QLatin1String("Command") ); cmdElem.appendChild(doc.createTextNode( command() )); exprElem.appendChild(cmdElem); // save results of the expression if they exist if (expression()) for (Cantor::Result * const result: expression()->results()) { const QDomElement& resultElem = result->toXml(doc); exprElem.appendChild(resultElem); if (archive) result->saveAdditionalData(archive); } //save the background color if it differs from the default one const QColor& backgroundColor = m_commandItem->backgroundColor(); KColorScheme scheme = KColorScheme(QPalette::Normal, KColorScheme::View); if (backgroundColor != scheme.background(KColorScheme::AlternateBackground).color()) { QDomElement colorElem = doc.createElement( QLatin1String("Background") ); colorElem.setAttribute(QLatin1String("red"), QString::number(backgroundColor.red())); colorElem.setAttribute(QLatin1String("green"), QString::number(backgroundColor.green())); colorElem.setAttribute(QLatin1String("blue"), QString::number(backgroundColor.blue())); exprElem.appendChild(colorElem); } //save the text properties if they differ from default values const QFont& font = m_commandItem->font(); if (font != QFontDatabase::systemFont(QFontDatabase::FixedFont)) { QDomElement textElem = doc.createElement(QLatin1String("Text")); //font properties QDomElement fontElem = doc.createElement(QLatin1String("Font")); fontElem.setAttribute(QLatin1String("family"), font.family()); fontElem.setAttribute(QLatin1String("pointSize"), QString::number(font.pointSize())); fontElem.setAttribute(QLatin1String("weight"), QString::number(font.weight())); fontElem.setAttribute(QLatin1String("italic"), QString::number(font.italic())); textElem.appendChild(fontElem); //text color const QColor& textColor = m_commandItem->defaultTextColor(); QDomElement colorElem = doc.createElement( QLatin1String("Color") ); colorElem.setAttribute(QLatin1String("red"), QString::number(textColor.red())); colorElem.setAttribute(QLatin1String("green"), QString::number(textColor.green())); colorElem.setAttribute(QLatin1String("blue"), QString::number(textColor.blue())); textElem.appendChild(colorElem); exprElem.appendChild(textElem); } return exprElem; } QString CommandEntry::currentLine() { if (!m_commandItem->hasFocus()) return QString(); QTextBlock block = m_commandItem->textCursor().block(); return block.text(); } bool CommandEntry::evaluateCurrentItem() { // we can't use m_commandItem->hasFocus() here, because // that doesn't work when the scene doesn't have the focus, // e.g. when an assistant is used. if (m_commandItem == worksheet()->focusItem()) { return evaluate(); } else if (informationItemHasFocus()) { addInformation(); return true; } return false; } bool CommandEntry::evaluate(EvaluationOption evalOp) { removeContextHelp(); QToolTip::hideText(); QString cmd = command(); m_evaluationOption = evalOp; if(cmd.isEmpty()) { removeResults(); foreach(WorksheetTextItem* item, m_informationItems) { item->deleteLater(); } m_informationItems.clear(); recalculateSize(); evaluateNext(m_evaluationOption); return false; } Cantor::Expression* expr; expr = worksheet()->session()->evaluateExpression(cmd); connect(expr, SIGNAL(gotResult()), worksheet(), SLOT(gotResult())); setExpression(expr); return true; } void CommandEntry::interruptEvaluation() { Cantor::Expression *expr = expression(); if(expr) expr->interrupt(); } void CommandEntry::updateEntry() { qDebug() << "update Entry"; Cantor::Expression* expr = expression(); if (expr == nullptr || expr->results().isEmpty()) return; if (expr->results().last()->type() == Cantor::HelpResult::Type) return; // Help is handled elsewhere //CommandEntry::updateEntry() is only called if the worksheet view is resized //or when we got a new result(s). //In the second case the number of results and the number of result graphic objects is different //and we add a new graphic objects for the new result(s) (new result(s) are located in the end). if (m_resultItems.size() < expr->results().size()) { if (m_resultsCollapsed) expandResults(); for (int i = m_resultItems.size(); i < expr->results().size(); i++) m_resultItems << ResultItem::create(this, expr->results()[i]); animateSizeChange(); } } void CommandEntry::expressionChangedStatus(Cantor::Expression::Status status) { switch (status) { case Cantor::Expression::Computing: { //change the background of the promt item and start animating it (fade in/out). //don't start the animation immediately in order to avoid unwanted flickering for "short" commands, //start the animation after 1 second passed. if (worksheet()->animationsEnabled()) { const int id = m_expression->id(); QTimer::singleShot(1000, this, [this, id] () { if(m_expression->status() == Cantor::Expression::Computing && m_expression->id() == id) m_promptItemAnimation->start(); }); } break; } case Cantor::Expression::Error: case Cantor::Expression::Interrupted: m_promptItemAnimation->stop(); m_promptItem->setOpacity(1.); m_commandItem->setFocusAt(WorksheetTextItem::BottomRight, 0); if(!m_errorItem) { m_errorItem = new WorksheetTextItem(this, Qt::TextSelectableByMouse); } if (status == Cantor::Expression::Error) m_errorItem->setHtml(m_expression->errorMessage()); else m_errorItem->setHtml(i18n("Interrupted")); recalculateSize(); break; case Cantor::Expression::Done: m_promptItemAnimation->stop(); m_promptItem->setOpacity(1.); evaluateNext(m_evaluationOption); m_evaluationOption = DoNothing; break; default: break; } } void CommandEntry::animatePromptItem() { if(m_expression->status() == Cantor::Expression::Computing) m_promptItemAnimation->start(); } bool CommandEntry::isEmpty() { if (m_commandItem->toPlainText().trimmed().isEmpty()) { if (!m_resultItems.isEmpty()) return false; return true; } return false; } bool CommandEntry::focusEntry(int pos, qreal xCoord) { if (aboutToBeRemoved()) return false; WorksheetTextItem* item; if (pos == WorksheetTextItem::TopLeft || pos == WorksheetTextItem::TopCoord) item = m_commandItem; else if (m_informationItems.size() && currentInformationItem()->isEditable()) item = currentInformationItem(); else item = m_commandItem; item->setFocusAt(pos, xCoord); return true; } void CommandEntry::setCompletion(Cantor::CompletionObject* tc) { if (m_completionObject) delete m_completionObject; m_completionObject = tc; connect(m_completionObject, &Cantor::CompletionObject::done, this, &CommandEntry::showCompletions); connect(m_completionObject, &Cantor::CompletionObject::lineDone, this, &CommandEntry::completeLineTo); } void CommandEntry::showCompletions() { disconnect(m_completionObject, &Cantor::CompletionObject::done, this, &CommandEntry::showCompletions); QString completion = m_completionObject->completion(); qDebug()<<"completion: "<allMatches(); if(m_completionObject->hasMultipleMatches()) { completeCommandTo(completion); QToolTip::showText(QPoint(), QString(), worksheetView()); if (!m_completionBox) m_completionBox = new KCompletionBox(worksheetView()); m_completionBox->clear(); m_completionBox->setItems(m_completionObject->allMatches()); QList items = m_completionBox->findItems(m_completionObject->command(), Qt::MatchFixedString|Qt::MatchCaseSensitive); if (!items.empty()) m_completionBox->setCurrentItem(items.first()); m_completionBox->setTabHandling(false); m_completionBox->setActivateOnSelect(true); connect(m_completionBox.data(), &KCompletionBox::activated, this, &CommandEntry::applySelectedCompletion); connect(m_commandItem->document(), SIGNAL(contentsChanged()), this, SLOT(completedLineChanged())); connect(m_completionObject, &Cantor::CompletionObject::done, this, &CommandEntry::updateCompletions); m_commandItem->activateCompletion(true); m_completionBox->popup(); m_completionBox->move(getPopupPosition()); } else { completeCommandTo(completion, FinalCompletion); } } bool CommandEntry::isShowingCompletionPopup() { return m_completionBox && m_completionBox->isVisible(); } void CommandEntry::applySelectedCompletion() { QListWidgetItem* item = m_completionBox->currentItem(); if(item) completeCommandTo(item->text(), FinalCompletion); m_completionBox->hide(); } void CommandEntry::completedLineChanged() { if (!isShowingCompletionPopup()) { // the completion popup is not visible anymore, so let's clean up removeContextHelp(); return; } const QString line = currentLine(); //FIXME: For some reason, this slot constantly triggeres, so I have added checking, is this update really needed if (line != m_completionObject->command()) m_completionObject->updateLine(line, m_commandItem->textCursor().positionInBlock()); } void CommandEntry::updateCompletions() { if (!m_completionObject) return; QString completion = m_completionObject->completion(); qDebug()<<"completion: "<allMatches(); if(m_completionObject->hasMultipleMatches() || !completion.isEmpty()) { QToolTip::showText(QPoint(), QString(), worksheetView()); m_completionBox->setItems(m_completionObject->allMatches()); QList items = m_completionBox->findItems(m_completionObject->command(), Qt::MatchFixedString|Qt::MatchCaseSensitive); if (!items.empty()) m_completionBox->setCurrentItem(items.first()); else if (m_completionBox->items().count() == 1) m_completionBox->setCurrentRow(0); else m_completionBox->clearSelection(); m_completionBox->move(getPopupPosition()); } else { removeContextHelp(); } } void CommandEntry::completeCommandTo(const QString& completion, CompletionMode mode) { qDebug() << "completion: " << completion; Cantor::CompletionObject::LineCompletionMode cmode; if (mode == FinalCompletion) { cmode = Cantor::CompletionObject::FinalCompletion; Cantor::SyntaxHelpObject* obj = worksheet()->session()->syntaxHelpFor(completion); if(obj) setSyntaxHelp(obj); } else { cmode = Cantor::CompletionObject::PreliminaryCompletion; if(m_syntaxHelpObject) m_syntaxHelpObject->deleteLater(); m_syntaxHelpObject=nullptr; } m_completionObject->completeLine(completion, cmode); } void CommandEntry::completeLineTo(const QString& line, int index) { qDebug() << "line completion: " << line; QTextCursor cursor = m_commandItem->textCursor(); cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::MoveAnchor); cursor.movePosition(QTextCursor::StartOfBlock, QTextCursor::KeepAnchor); int startPosition = cursor.position(); cursor.insertText(line); cursor.setPosition(startPosition + index); m_commandItem->setTextCursor(cursor); if (m_syntaxHelpObject) { m_syntaxHelpObject->fetchSyntaxHelp(); // If we are about to show syntax help, then this was the final // completion, and we should clean up. removeContextHelp(); } } void CommandEntry::setSyntaxHelp(Cantor::SyntaxHelpObject* sh) { if(m_syntaxHelpObject) m_syntaxHelpObject->deleteLater(); m_syntaxHelpObject=sh; connect(sh, SIGNAL(done()), this, SLOT(showSyntaxHelp())); } void CommandEntry::showSyntaxHelp() { QString msg = m_syntaxHelpObject->toHtml(); const QPointF cursorPos = m_commandItem->cursorPosition(); // QToolTip don't support  , but support multiple spaces msg.replace(QLatin1String(" "), QLatin1String(" ")); // Don't support " too; msg.replace(QLatin1String("""), QLatin1String("\"")); QToolTip::showText(toGlobalPosition(cursorPos), msg, worksheetView()); } void CommandEntry::resultDeleted() { qDebug()<<"result got removed..."; } void CommandEntry::addInformation() { WorksheetTextItem *answerItem = currentInformationItem(); answerItem->setTextInteractionFlags(Qt::TextSelectableByMouse); QString inf = answerItem->toPlainText(); inf.replace(QChar::ParagraphSeparator, QLatin1Char('\n')); inf.replace(QChar::LineSeparator, QLatin1Char('\n')); qDebug()<<"adding information: "<addInformation(inf); } void CommandEntry::showAdditionalInformationPrompt(const QString& question) { WorksheetTextItem* questionItem = new WorksheetTextItem(this, Qt::TextSelectableByMouse); WorksheetTextItem* answerItem = new WorksheetTextItem(this, Qt::TextEditorInteraction); //change the color and the font for when asking for additional information in order to //better discriminate from the usual input in the command entry KColorScheme scheme = KColorScheme(QPalette::Normal, KColorScheme::View); QColor color = scheme.foreground(KColorScheme::PositiveText).color(); QFont font; font.setItalic(true); questionItem->setFont(font); questionItem->setDefaultTextColor(color); answerItem->setFont(font); answerItem->setDefaultTextColor(color); questionItem->setPlainText(question); m_informationItems.append(questionItem); m_informationItems.append(answerItem); connect(answerItem, &WorksheetTextItem::moveToPrevious, this, &CommandEntry::moveToPreviousItem); connect(answerItem, &WorksheetTextItem::moveToNext, this, &CommandEntry::moveToNextItem); connect(answerItem, &WorksheetTextItem::execute, this, &CommandEntry::addInformation); answerItem->setFocus(); recalculateSize(); } void CommandEntry::removeResults() { //clear the Result objects if(m_expression) m_expression->clearResults(); } void CommandEntry::removeResult(Cantor::Result* result) { if (m_expression) m_expression->removeResult(result); } void CommandEntry::removeResultItem(int index) { fadeOutItem(m_resultItems[index]->graphicsObject()); m_resultItems.remove(index); recalculateSize(); } void CommandEntry::clearResultItems() { //fade out all result graphic objects for(auto* item : m_resultItems) fadeOutItem(item->graphicsObject()); m_resultItems.clear(); recalculateSize(); } void CommandEntry::replaceResultItem(int index) { ResultItem* previousItem = m_resultItems[index]; m_resultItems[index] = ResultItem::create(this, m_expression->results()[index]); previousItem->deleteLater(); recalculateSize(); } void CommandEntry::removeContextHelp() { disconnect(m_commandItem->document(), SIGNAL(contentsChanged()), this, SLOT(completedLineChanged())); m_commandItem->activateCompletion(false); if (m_completionBox) m_completionBox->hide(); } void CommandEntry::updatePrompt(const QString& postfix) { KColorScheme color = KColorScheme( QPalette::Normal, KColorScheme::View); m_promptItem->setPlainText(QLatin1String("")); QTextCursor c = m_promptItem->textCursor(); QTextCharFormat cformat = c.charFormat(); cformat.clearForeground(); c.setCharFormat(cformat); cformat.setFontWeight(QFont::Bold); //insert the session id if available if(m_expression && worksheet()->showExpressionIds()&&m_expression->id()!=-1) c.insertText(QString::number(m_expression->id()),cformat); //detect the correct color for the prompt, depending on the //Expression state if(m_expression) { if(m_expression ->status() == Cantor::Expression::Computing&&worksheet()->isRunning()) cformat.setForeground(color.foreground(KColorScheme::PositiveText)); else if(m_expression ->status() == Cantor::Expression::Queued) cformat.setForeground(color.foreground(KColorScheme::InactiveText)); else if(m_expression ->status() == Cantor::Expression::Error) cformat.setForeground(color.foreground(KColorScheme::NegativeText)); else if(m_expression ->status() == Cantor::Expression::Interrupted) cformat.setForeground(color.foreground(KColorScheme::NeutralText)); else cformat.setFontWeight(QFont::Normal); } c.insertText(postfix, cformat); recalculateSize(); } WorksheetTextItem* CommandEntry::currentInformationItem() { if (m_informationItems.isEmpty()) return nullptr; return m_informationItems.last(); } bool CommandEntry::informationItemHasFocus() { if (m_informationItems.isEmpty()) return false; return m_informationItems.last()->hasFocus(); } bool CommandEntry::focusWithinThisItem() { return focusItem() != nullptr; } QPoint CommandEntry::getPopupPosition() { const QPointF cursorPos = m_commandItem->cursorPosition(); const QPoint globalPos = toGlobalPosition(cursorPos); const QDesktopWidget* desktop = QApplication::desktop(); const QRect screenRect = desktop->screenGeometry(globalPos); if (globalPos.y() + m_completionBox->height() < screenRect.bottom()) { return (globalPos); } else { QTextBlock block = m_commandItem->textCursor().block(); QTextLayout* layout = block.layout(); int pos = m_commandItem->textCursor().position() - block.position(); QTextLine line = layout->lineForTextPosition(pos); int dy = - m_completionBox->height() - line.height() - line.leading(); return QPoint(globalPos.x(), globalPos.y() + dy); } } void CommandEntry::invalidate() { qDebug() << "ToDo: Invalidate here"; } bool CommandEntry::wantToEvaluate() { return !isEmpty(); } QPoint CommandEntry::toGlobalPosition(QPointF localPos) { const QPointF scenePos = mapToScene(localPos); const QPoint viewportPos = worksheetView()->mapFromScene(scenePos); return worksheetView()->viewport()->mapToGlobal(viewportPos); } WorksheetCursor CommandEntry::search(const QString& pattern, unsigned flags, QTextDocument::FindFlags qt_flags, const WorksheetCursor& pos) { if (pos.isValid() && pos.entry() != this) return WorksheetCursor(); WorksheetCursor p = pos; QTextCursor cursor; if (flags & WorksheetEntry::SearchCommand) { cursor = m_commandItem->search(pattern, qt_flags, p); if (!cursor.isNull()) return WorksheetCursor(this, m_commandItem, cursor); } if (p.textItem() == m_commandItem) p = WorksheetCursor(); if (m_errorItem && flags & WorksheetEntry::SearchError) { cursor = m_errorItem->search(pattern, qt_flags, p); if (!cursor.isNull()) return WorksheetCursor(this, m_errorItem, cursor); } if (p.textItem() == m_errorItem) p = WorksheetCursor(); for (auto* resultItem : m_resultItems) { WorksheetTextItem* textResult = dynamic_cast (resultItem); if (textResult && flags & WorksheetEntry::SearchResult) { cursor = textResult->search(pattern, qt_flags, p); if (!cursor.isNull()) return WorksheetCursor(this, textResult, cursor); } } return WorksheetCursor(); } void CommandEntry::layOutForWidth(qreal w, bool force) { if (w == size().width() && !force) return; m_promptItem->setPos(0,0); double x = 0 + m_promptItem->width() + HorizontalSpacing; double y = 0; double width = 0; m_commandItem->setGeometry(x,y, w-x); width = qMax(width, m_commandItem->width()); y += qMax(m_commandItem->height(), m_promptItem->height()); foreach(WorksheetTextItem* information, m_informationItems) { y += VerticalSpacing; y += information->setGeometry(x,y,w-x); width = qMax(width, information->width()); } if (m_errorItem) { y += VerticalSpacing; y += m_errorItem->setGeometry(x,y,w-x); width = qMax(width, m_errorItem->width()); } for (auto* resultItem : m_resultItems) { if (!resultItem || !resultItem->graphicsObject()->isVisible()) continue; y += VerticalSpacing; y += resultItem->setGeometry(x, y, w-x); width = qMax(width, resultItem->width()); } y += VerticalMargin; QSizeF s(x+ width, y); if (animationActive()) { updateSizeAnimation(s); } else { setSize(s); } } void CommandEntry::startRemoving() { m_promptItem->setItemDragable(false); WorksheetEntry::startRemoving(); } WorksheetTextItem* CommandEntry::highlightItem() { return m_commandItem; } void CommandEntry::collapseResults() { for(auto* item : m_resultItems) { fadeOutItem(item->graphicsObject(), nullptr); item->graphicsObject()->hide(); } m_resultsCollapsed = true; if (worksheet()->animationsEnabled()) { QTimer::singleShot(100, this, &CommandEntry::setMidPrompt); QTimer::singleShot(200, this, &CommandEntry::setHidePrompt); } else setHidePrompt(); animateSizeChange(); } void CommandEntry::expandResults() { for(auto* item : m_resultItems) { fadeInItem(item->graphicsObject(), nullptr); item->graphicsObject()->show(); } m_resultsCollapsed = false; if (worksheet()->animationsEnabled()) { QTimer::singleShot(100, this, &CommandEntry::setMidPrompt); QTimer::singleShot(200, this, SLOT(updatePrompt())); } else this->updatePrompt(); animateSizeChange(); } void CommandEntry::setHidePrompt() { updatePrompt(HidePrompt); } void CommandEntry::setMidPrompt() { updatePrompt(MidPrompt); } diff --git a/src/commandentry.h b/src/commandentry.h index 637ed558..37a5ade5 100644 --- a/src/commandentry.h +++ b/src/commandentry.h @@ -1,185 +1,186 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2009 Alexander Rieder Copyright (C) 2012 Martin Kuettler Copyright (C) 2018 Alexander Semke */ #ifndef COMMANDENTRY_H #define COMMANDENTRY_H #include #include #include "worksheetentry.h" #include "lib/expression.h" class Worksheet; class ResultItem; class QTimer; class QJsonObject; namespace Cantor{ class Result; class CompletionObject; class SyntaxHelpObject; } class CommandEntry : public WorksheetEntry { Q_OBJECT public: static const QString Prompt; static const QString MidPrompt; static const QString HidePrompt; explicit CommandEntry(Worksheet* worksheet); ~CommandEntry() override; enum {Type = UserType + 2}; int type() const override; QString command(); void setExpression(Cantor::Expression* expr); Cantor::Expression* expression(); QString currentLine(); bool isEmpty() override; void setContent(const QString& content) override; void setContent(const QDomElement& content, const KZip& file) override; - void setContentFromJupyter(const QJsonObject& cell); + void setContentFromJupyter(const QJsonObject& cell) override; QDomElement toXml(QDomDocument& doc, KZip* archive) override; + QJsonValue toJupyterJson() override; QString toPlain(const QString& commandSep, const QString& commentStartingSeq, const QString& commentEndingSeq) override; void setCompletion(Cantor::CompletionObject* tc); void setSyntaxHelp(Cantor::SyntaxHelpObject* sh); bool acceptRichText() override; void removeContextHelp(); void interruptEvaluation() override; bool isShowingCompletionPopup(); bool focusEntry(int pos = WorksheetTextItem::TopLeft, qreal xCoord = 0) override; void layOutForWidth(qreal w, bool force = false) override; WorksheetTextItem* highlightItem() override; WorksheetCursor search(const QString& pattern, unsigned flags, QTextDocument::FindFlags qt_flags, const WorksheetCursor& pos = WorksheetCursor()) override; public Q_SLOTS: bool evaluateCurrentItem() override; bool evaluate(WorksheetEntry::EvaluationOption evalOp = FocusNext) override; void addInformation(); void removeResults(); void removeResult(Cantor::Result* result); void showCompletion() override; void selectPreviousCompletion(); void updateEntry() override; void updatePrompt(const QString& postfix = CommandEntry::Prompt); void expressionChangedStatus(Cantor::Expression::Status status); void showAdditionalInformationPrompt(const QString& question); void showCompletions(); void applySelectedCompletion(); void completedLineChanged(); void showSyntaxHelp(); void completeLineTo(const QString& line, int index); void startRemoving() override; void moveToNextItem(int pos, qreal x); void moveToPreviousItem(int pos, qreal x); void populateMenu(QMenu* menu, QPointF pos) override; protected: bool wantToEvaluate() override; private: WorksheetTextItem* currentInformationItem(); bool informationItemHasFocus(); bool focusWithinThisItem(); QPoint getPopupPosition(); QPoint toGlobalPosition(QPointF localPos); void initMenus(); private: enum CompletionMode { PreliminaryCompletion, FinalCompletion }; private Q_SLOTS: void invalidate(); void resultDeleted(); void clearResultItems(); void collapseResults(); void expandResults(); void removeResultItem(int index); void replaceResultItem(int index); void updateCompletions(); void completeCommandTo(const QString& completion, CommandEntry::CompletionMode mode = PreliminaryCompletion); void backgroundColorChanged(QAction*); void textColorChanged(QAction*); void fontBoldTriggered(); void fontItalicTriggered(); void fontIncreaseTriggered(); void fontDecreaseTriggered(); void fontSelectTriggered(); void animatePromptItem(); void setMidPrompt(); void setHidePrompt(); private: static const double HorizontalSpacing; static const double VerticalSpacing; WorksheetTextItem* m_promptItem; WorksheetTextItem* m_commandItem; QVector m_resultItems; bool m_resultsCollapsed; WorksheetTextItem* m_errorItem; QList m_informationItems; Cantor::Expression* m_expression; Cantor::CompletionObject* m_completionObject; QPointer m_completionBox; Cantor::SyntaxHelpObject* m_syntaxHelpObject; EvaluationOption m_evaluationOption; QPropertyAnimation* m_promptItemAnimation; bool m_menusInitialized; //formatting QActionGroup* m_backgroundColorActionGroup; QMenu* m_backgroundColorMenu; QActionGroup* m_textColorActionGroup; QMenu* m_textColorMenu; QMenu* m_fontMenu; }; #endif // COMMANDENTRY_H diff --git a/src/imageentry.cpp b/src/imageentry.cpp index 9563c643..1a55c307 100644 --- a/src/imageentry.cpp +++ b/src/imageentry.cpp @@ -1,337 +1,349 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2012 martin Kuettler */ #include "imageentry.h" #include "worksheetimageitem.h" #include "actionbar.h" #include #include #include #include +#include ImageEntry::ImageEntry(Worksheet* worksheet) : WorksheetEntry(worksheet) { m_imageItem = nullptr; m_textItem = new WorksheetTextItem(this); m_imageWatcher = new QFileSystemWatcher(this); m_displaySize.width = -1; m_displaySize.height = -1; m_printSize.width = -1; m_printSize.height = -1; m_displaySize.widthUnit = ImageSize::Auto; m_displaySize.heightUnit = ImageSize::Auto; m_printSize.widthUnit = ImageSize::Auto; m_printSize.heightUnit = ImageSize::Auto; m_useDisplaySizeForPrinting = true; connect(m_imageWatcher, &QFileSystemWatcher::fileChanged, this, &ImageEntry::updateEntry); setFlag(QGraphicsItem::ItemIsFocusable); updateEntry(); startConfigDialog(); } void ImageEntry::populateMenu(QMenu* menu, QPointF pos) { menu->addAction(QIcon::fromTheme(QLatin1String("configure")), i18n("Configure Image"), this, SLOT(startConfigDialog())); menu->addSeparator(); WorksheetEntry::populateMenu(menu, pos); } bool ImageEntry::isEmpty() { return false; } int ImageEntry::type() const { return Type; } bool ImageEntry::acceptRichText() { return false; } void ImageEntry::setContent(const QString& content) { Q_UNUSED(content); return; } void ImageEntry::setContent(const QDomElement& content, const KZip& file) { Q_UNUSED(file); static QStringList unitNames; if (unitNames.isEmpty()) unitNames << QLatin1String("(auto)") << QLatin1String("px") << QLatin1String("%"); QDomElement pathElement = content.firstChildElement(QLatin1String("Path")); QDomElement displayElement = content.firstChildElement(QLatin1String("Display")); QDomElement printElement = content.firstChildElement(QLatin1String("Print")); m_imagePath = pathElement.text(); m_displaySize.width = displayElement.attribute(QLatin1String("width")).toDouble(); m_displaySize.height = displayElement.attribute(QLatin1String("height")).toDouble(); m_displaySize.widthUnit = unitNames.indexOf(displayElement.attribute(QLatin1String("widthUnit"))); m_displaySize.heightUnit = unitNames.indexOf(displayElement.attribute(QLatin1String("heightUnit"))); m_useDisplaySizeForPrinting = printElement.attribute(QLatin1String("useDisplaySize")).toInt(); m_printSize.width = printElement.attribute(QLatin1String("width")).toDouble(); m_printSize.height = printElement.attribute(QLatin1String("height")).toDouble(); m_printSize.widthUnit = unitNames.indexOf(printElement.attribute(QLatin1String("widthUnit"))); m_printSize.heightUnit = unitNames.indexOf(printElement.attribute(QLatin1String("heightUnit"))); updateEntry(); } +void ImageEntry::setContentFromJupyter(const QJsonObject& cell) +{ + // Jupyter TODO: Add realization +} + +QJsonValue ImageEntry::toJupyterJson() +{ + // Jupyter TODO: Add realization + return QJsonValue(); +} + QDomElement ImageEntry::toXml(QDomDocument& doc, KZip* archive) { Q_UNUSED(archive); static QStringList unitNames; if (unitNames.isEmpty()) unitNames << QLatin1String("(auto)") << QLatin1String("px") << QLatin1String("%"); QDomElement image = doc.createElement(QLatin1String("Image")); QDomElement path = doc.createElement(QLatin1String("Path")); QDomText pathText = doc.createTextNode(m_imagePath); path.appendChild(pathText); image.appendChild(path); QDomElement display = doc.createElement(QLatin1String("Display")); display.setAttribute(QLatin1String("width"), m_displaySize.width); display.setAttribute(QLatin1String("widthUnit"), unitNames[m_displaySize.widthUnit]); display.setAttribute(QLatin1String("height"), m_displaySize.height); display.setAttribute(QLatin1String("heightUnit"), unitNames[m_displaySize.heightUnit]); image.appendChild(display); QDomElement print = doc.createElement(QLatin1String("Print")); print.setAttribute(QLatin1String("useDisplaySize"), m_useDisplaySizeForPrinting); print.setAttribute(QLatin1String("width"), m_printSize.width); print.setAttribute(QLatin1String("widthUnit"), unitNames[m_printSize.widthUnit]); print.setAttribute(QLatin1String("height"), m_printSize.height); print.setAttribute(QLatin1String("heightUnit"), unitNames[m_printSize.heightUnit]); image.appendChild(print); // For the conversion to latex QDomElement latexSize = doc.createElement(QLatin1String("LatexSizeString")); QString sizeString; if (m_useDisplaySizeForPrinting) sizeString = latexSizeString(m_displaySize); else sizeString = latexSizeString(m_printSize); QDomText latexSizeString = doc.createTextNode(sizeString); latexSize.appendChild(latexSizeString); image.appendChild(latexSize); return image; } QString ImageEntry::toPlain(const QString& commandSep, const QString& commentStartingSeq, const QString& commentEndingSeq) { Q_UNUSED(commandSep); return commentStartingSeq + QLatin1String("image: ") + m_imagePath + commentEndingSeq; } QString ImageEntry::latexSizeString(const ImageSize& imgSize) { // We use the transformation 1 px = 1/72 in ( = 1 pt in Latex) QString sizeString=QLatin1String(""); if (imgSize.widthUnit == ImageSize::Auto && imgSize.heightUnit == ImageSize::Auto) return QLatin1String(""); if (imgSize.widthUnit == ImageSize::Percent) { if (imgSize.heightUnit == ImageSize::Auto || (imgSize.heightUnit == ImageSize::Percent && imgSize.width == imgSize.height)) return QLatin1String("[scale=") + QString::number(imgSize.width / 100) + QLatin1String("]"); // else? We could set the size based on the actual image size } else if (imgSize.widthUnit == ImageSize::Auto && imgSize.heightUnit == ImageSize::Percent) { return QLatin1String("[scale=") + QString::number(imgSize.height / 100) + QLatin1String("]"); } if (imgSize.heightUnit == ImageSize::Pixel) sizeString = QLatin1String("height=") + QString::number(imgSize.height) + QLatin1String("pt"); if (imgSize.widthUnit == ImageSize::Pixel) { if (!sizeString.isEmpty()) sizeString += QLatin1String(","); sizeString += QLatin1String("width=") + QString::number(imgSize.width) + QLatin1String("pt"); } return QLatin1String("[") + sizeString + QLatin1String("]"); } void ImageEntry::interruptEvaluation() { } bool ImageEntry::evaluate(EvaluationOption evalOp) { evaluateNext(evalOp); return true; } qreal ImageEntry::height() { if (m_imageItem && m_imageItem->isVisible()) return m_imageItem->height(); else return m_textItem->height(); } void ImageEntry::updateEntry() { qreal oldHeight = height(); if (m_imagePath.isEmpty()) { m_textItem->setPlainText(i18n("Right click here to insert image")); m_textItem->setVisible(true); if (m_imageItem) m_imageItem->setVisible(false); } else { if (!m_imageItem) m_imageItem = new WorksheetImageItem(this); if (m_imagePath.endsWith(QLatin1String(".eps"), Qt::CaseInsensitive)) { m_imageItem->setEps(QUrl::fromLocalFile(m_imagePath)); } else { QImage img(m_imagePath); m_imageItem->setImage(img); } if (!m_imageItem->imageIsValid()) { const QString msg = i18n("Cannot load image %1", m_imagePath); m_textItem->setPlainText(msg); m_textItem->setVisible(true); m_imageItem->setVisible(false); } else { QSizeF size; if (worksheet()->isPrinting() && ! m_useDisplaySizeForPrinting) size = imageSize(m_printSize); else size = imageSize(m_displaySize); // Hack: Eps images need to be scaled if (m_imagePath.endsWith(QLatin1String(".eps"), Qt::CaseInsensitive)) size /= worksheet()->epsRenderer()->scale(); m_imageItem->setSize(size); qDebug() << size; m_textItem->setVisible(false); m_imageItem->setVisible(true); } } qDebug() << oldHeight << height(); if (oldHeight != height()) recalculateSize(); } QSizeF ImageEntry::imageSize(const ImageSize& imgSize) { const QSize& srcSize = m_imageItem->imageSize(); qreal w = 0.0; qreal h = 0.0; if (imgSize.heightUnit == ImageSize::Percent) h = srcSize.height() * imgSize.height / 100; else if (imgSize.heightUnit == ImageSize::Pixel) h = imgSize.height; if (imgSize.widthUnit == ImageSize::Percent) w = srcSize.width() * imgSize.width / 100; else if (imgSize.widthUnit == ImageSize::Pixel) w = imgSize.width; if (imgSize.widthUnit == ImageSize::Auto) { if (imgSize.heightUnit == ImageSize::Auto) return QSizeF(srcSize.width(), srcSize.height()); else if (h == 0) w = 0; else w = h / srcSize.height() * srcSize.width(); } else if (imgSize.heightUnit == ImageSize::Auto) { if (w == 0) h = 0; else h = w / srcSize.width() * srcSize.height(); } return QSizeF(w,h); } void ImageEntry::startConfigDialog() { ImageSettingsDialog* dialog = new ImageSettingsDialog(worksheet()->worksheetView()); dialog->setData(m_imagePath, m_displaySize, m_printSize, m_useDisplaySizeForPrinting); connect(dialog, &ImageSettingsDialog::dataChanged, this, &ImageEntry::setImageData); dialog->show(); } void ImageEntry::setImageData(const QString& path, const ImageSize& displaySize, const ImageSize& printSize, bool useDisplaySizeForPrinting) { if (path != m_imagePath) { m_imageWatcher->removePath(m_imagePath); m_imageWatcher->addPath(path); m_imagePath = path; } m_displaySize = displaySize; m_printSize = printSize; m_useDisplaySizeForPrinting = useDisplaySizeForPrinting; updateEntry(); } void ImageEntry::addActionsToBar(ActionBar* actionBar) { actionBar->addButton(QIcon::fromTheme(QLatin1String("configure")), i18n("Configure Image"), this, SLOT(startConfigDialog())); } void ImageEntry::layOutForWidth(qreal w, bool force) { if (size().width() == w && !force) return; double width; if (m_imageItem && m_imageItem->isVisible()) { m_imageItem->setGeometry(0, 0, w, true); width = m_imageItem->width(); } else { m_textItem->setGeometry(0, 0, w, true); width = m_textItem->width(); } setSize(QSizeF(width, height() + VerticalMargin)); } bool ImageEntry::wantToEvaluate() { return false; } bool ImageEntry::wantFocus() { return false; } diff --git a/src/imageentry.h b/src/imageentry.h index f0ff44f9..a495d882 100644 --- a/src/imageentry.h +++ b/src/imageentry.h @@ -1,85 +1,87 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2012 Martin Kuettler */ #ifndef IMAGEENTRY_H #define IMAGEENTRY_H #include "worksheetentry.h" #include "imagesettingsdialog.h" #include class Worksheet; class ActionBar; class WorksheetImageItem; class QFileSystemWatcher; class ImageEntry : public WorksheetEntry { Q_OBJECT public: explicit ImageEntry(Worksheet* worksheet); ~ImageEntry() override = default; enum {Type = UserType + 4}; int type() const override; bool isEmpty() override; bool acceptRichText() override; void setContent(const QString& content) override; void setContent(const QDomElement& content, const KZip& file) override; + void setContentFromJupyter(const QJsonObject & cell) override; QDomElement toXml(QDomDocument& doc, KZip* archive) override; + QJsonValue toJupyterJson() override; QString toPlain(const QString& commandSep, const QString& commentStartingSeq, const QString& commentEndingSeq) override; QSizeF imageSize(const ImageSize& imgSize); void interruptEvaluation() override; void layOutForWidth(qreal w, bool force = false) override; public Q_SLOTS: bool evaluate(WorksheetEntry::EvaluationOption evalOp = FocusNext) override; void updateEntry() override; void populateMenu(QMenu* menu, QPointF pos) override; void startConfigDialog(); void setImageData(const QString& path, const ImageSize& displaySize, const ImageSize& printSize, bool useDisplaySizeForPrinting); protected: bool wantToEvaluate() override; bool wantFocus() override; qreal height(); QString latexSizeString(const ImageSize& imgSize); void addActionsToBar(ActionBar* actionBar) override; private: QString m_imagePath; ImageSize m_displaySize; ImageSize m_printSize; bool m_useDisplaySizeForPrinting; WorksheetImageItem* m_imageItem; WorksheetTextItem* m_textItem; QFileSystemWatcher* m_imageWatcher; }; #endif /* IMAGEENTRY_H */ diff --git a/src/latexentry.cpp b/src/latexentry.cpp index 2982181f..557e1266 100644 --- a/src/latexentry.cpp +++ b/src/latexentry.cpp @@ -1,412 +1,424 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2009 Alexander Rieder Copyright (C) 2012 Martin Kuettler */ #include "latexentry.h" #include "worksheetentry.h" #include "worksheet.h" #include "epsrenderer.h" #include "lib/defaulthighlighter.h" #include "lib/latexrenderer.h" #include "config-cantor.h" #include #include #include #include #include #include +#include #include LatexEntry::LatexEntry(Worksheet* worksheet) : WorksheetEntry(worksheet), m_textItem(new WorksheetTextItem(this, Qt::TextEditorInteraction)) { connect(m_textItem, &WorksheetTextItem::moveToPrevious, this, &LatexEntry::moveToPreviousEntry); connect(m_textItem, &WorksheetTextItem::moveToNext, this, &LatexEntry::moveToNextEntry); connect(m_textItem, SIGNAL(execute()), this, SLOT(evaluate())); connect(m_textItem, &WorksheetTextItem::doubleClick, this, &LatexEntry::resolveImagesAtCursor); } void LatexEntry::populateMenu(QMenu* menu, QPointF pos) { bool imageSelected = false; QTextCursor cursor = m_textItem->textCursor(); const QChar repl = QChar::ObjectReplacementCharacter; if (cursor.hasSelection()) { QString selection = m_textItem->textCursor().selectedText(); imageSelected = selection.contains(repl); } else { // we need to try both the current cursor and the one after the that cursor = m_textItem->cursorForPosition(pos); for (int i = 2; i; --i) { int p = cursor.position(); if (m_textItem->document()->characterAt(p-1) == repl && cursor.charFormat().hasProperty(EpsRenderer::CantorFormula)) { m_textItem->setTextCursor(cursor); imageSelected = true; break; } cursor.movePosition(QTextCursor::NextCharacter); } } if (imageSelected) { menu->addAction(i18n("Show LaTeX code"), this, SLOT(resolveImagesAtCursor())); menu->addSeparator(); } WorksheetEntry::populateMenu(menu, pos); } int LatexEntry::type() const { return Type; } bool LatexEntry::isEmpty() { return m_textItem->document()->isEmpty(); } bool LatexEntry::acceptRichText() { return false; } bool LatexEntry::focusEntry(int pos, qreal xCoord) { if (aboutToBeRemoved()) return false; m_textItem->setFocusAt(pos, xCoord); return true; } void LatexEntry::setContent(const QString& content) { m_textItem->setPlainText(content); } void LatexEntry::setContent(const QDomElement& content, const KZip& file) { QString latexCode = content.text(); qDebug() << latexCode; m_textItem->document()->clear(); QTextCursor cursor = m_textItem->textCursor(); cursor.movePosition(QTextCursor::Start); QString imagePath; bool useLatexCode = true; if(content.hasAttribute(QLatin1String("filename"))) { const KArchiveEntry* imageEntry=file.directory()->entry(content.attribute(QLatin1String("filename"))); if (imageEntry&&imageEntry->isFile()) { const KArchiveFile* imageFile=static_cast(imageEntry); const QString& dir=QStandardPaths::writableLocation(QStandardPaths::TempLocation); imageFile->copyTo(dir); imagePath = dir + QDir::separator() + imageFile->name(); #ifdef LIBSPECTRE_FOUND QTextImageFormat format = worksheet()->epsRenderer()->render(m_textItem->document(), QUrl::fromLocalFile(imagePath)); qDebug()<<"rendering successful? " << !format.name().isEmpty(); format.setProperty(EpsRenderer::CantorFormula, EpsRenderer::LatexFormula); format.setProperty(EpsRenderer::ImagePath, imagePath); format.setProperty(EpsRenderer::Code, latexCode); cursor.insertText(QString(QChar::ObjectReplacementCharacter), format); useLatexCode = false; m_textItem->denyEditing(); #endif } } if (useLatexCode && content.hasAttribute(QLatin1String("image"))) { const QByteArray& ba = QByteArray::fromBase64(content.attribute(QLatin1String("image")).toLatin1()); QImage image; if (image.loadFromData(ba)) { // Create unique internal url for this loaded image QUrl internal; internal.setScheme(QLatin1String("internal")); internal.setPath(QUuid::createUuid().toString()); m_textItem->document()->addResource(QTextDocument::ImageResource, internal, QVariant(image)); QTextImageFormat format; format.setName(internal.url()); format.setWidth(image.width()); format.setHeight(image.height()); format.setProperty(EpsRenderer::CantorFormula, EpsRenderer::LatexFormula); if (!imagePath.isEmpty()) format.setProperty(EpsRenderer::ImagePath, imagePath); format.setProperty(EpsRenderer::Code, latexCode); cursor.insertText(QString(QChar::ObjectReplacementCharacter), format); useLatexCode = false; m_textItem->denyEditing(); } } if (useLatexCode) cursor.insertText(latexCode); } +void LatexEntry::setContentFromJupyter(const QJsonObject& cell) +{ + // Jupyter TODO: Add realization +} + +QJsonValue LatexEntry::toJupyterJson() +{ + // Jupyter TODO: Add realization + return QJsonValue(); +} + QDomElement LatexEntry::toXml(QDomDocument& doc, KZip* archive) { QDomElement el = doc.createElement(QLatin1String("Latex")); el.appendChild( doc.createTextNode( latexCode() )); QTextCursor cursor = m_textItem->document()->find(QString(QChar::ObjectReplacementCharacter)); if (!cursor.isNull()) { QTextImageFormat format=cursor.charFormat().toImageFormat(); QString fileName = format.property(EpsRenderer::ImagePath).toString(); // Check, if eps file exists, and if not true, rerender latex code bool isEpsFileExists = QFile::exists(fileName); #ifdef LIBSPECTRE_FOUND if (!isEpsFileExists && renderLatexCode()) { cursor = m_textItem->document()->find(QString(QChar::ObjectReplacementCharacter)); format=cursor.charFormat().toImageFormat(); fileName = format.property(EpsRenderer::ImagePath).toString(); isEpsFileExists = QFile::exists(fileName); } #endif if (isEpsFileExists && archive) { const QUrl& url=QUrl::fromLocalFile(fileName); archive->addLocalFile(url.toLocalFile(), url.fileName()); el.setAttribute(QLatin1String("filename"), url.fileName()); } // Save also rendered QImage, if exist. QUrl internal; internal.setUrl(format.name()); const QImage& image = m_textItem->document()->resource(QTextDocument::ImageResource, internal).value(); if (!image.isNull()) { QByteArray ba; QBuffer buffer(&ba); buffer.open(QIODevice::WriteOnly); image.save(&buffer, "PNG"); el.setAttribute(QLatin1String("image"), QString::fromLatin1(ba.toBase64())); } } return el; } QString LatexEntry::toPlain(const QString& commandSep, const QString& commentStartingSeq, const QString& commentEndingSeq) { Q_UNUSED(commandSep); if (commentStartingSeq.isEmpty()) return QString(); QString text = latexCode(); if (!commentEndingSeq.isEmpty()) return commentStartingSeq + text + commentEndingSeq + QLatin1String("\n"); return commentStartingSeq + text.replace(QLatin1String("\n"), QLatin1String("\n") + commentStartingSeq) + QLatin1String("\n"); } void LatexEntry::interruptEvaluation() { } bool LatexEntry::evaluate(EvaluationOption evalOp) { bool success = false; if (isOneImageOnly()) { success = true; } else { success = renderLatexCode(); } qDebug()<<"rendering successful? "<document()->find(QString(QChar::ObjectReplacementCharacter)); while (!cursor.isNull()) { qDebug()<<"found a formula... rendering the eps..."; QTextCharFormat format=cursor.charFormat(); const QUrl& url=QUrl::fromLocalFile(format.property(EpsRenderer::ImagePath).toString()); QSizeF s = worksheet()->epsRenderer()->renderToResource(m_textItem->document(), url); qDebug()<<"rendering successful? "<< s.isValid(); cursor.movePosition(QTextCursor::NextCharacter); cursor = m_textItem->document()->find(QString(QChar::ObjectReplacementCharacter), cursor); } } void LatexEntry::resolveImagesAtCursor() { QTextCursor cursor = m_textItem->textCursor(); if (!cursor.hasSelection()) cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor); cursor.insertText(m_textItem->resolveImages(cursor)); m_textItem->allowEditing(); } QString LatexEntry::latexCode() { QTextCursor cursor = m_textItem->textCursor(); cursor.movePosition(QTextCursor::Start); cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); QString code = m_textItem->resolveImages(cursor); code.replace(QChar::ParagraphSeparator, QLatin1Char('\n')); //Replace the U+2029 paragraph break by a Normal Newline code.replace(QChar::LineSeparator, QLatin1Char('\n')); //Replace the line break by a Normal Newline return code; } bool LatexEntry::isOneImageOnly() { QTextCursor cursor = m_textItem->textCursor(); cursor.movePosition(QTextCursor::Start); cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); return (cursor.selectionEnd() == 1 && cursor.selectedText() == QString(QChar::ObjectReplacementCharacter)); } int LatexEntry::searchText(const QString& text, const QString& pattern, QTextDocument::FindFlags qt_flags) { Qt::CaseSensitivity caseSensitivity; if (qt_flags & QTextDocument::FindCaseSensitively) caseSensitivity = Qt::CaseSensitive; else caseSensitivity = Qt::CaseInsensitive; int position; if (qt_flags & QTextDocument::FindBackward) position = text.lastIndexOf(pattern, -1, caseSensitivity); else position = text.indexOf(pattern, 0, caseSensitivity); return position; } WorksheetCursor LatexEntry::search(const QString& pattern, unsigned flags, QTextDocument::FindFlags qt_flags, const WorksheetCursor& pos) { if (!(flags & WorksheetEntry::SearchLaTeX)) return WorksheetCursor(); if (pos.isValid() && (pos.entry() != this || pos.textItem() != m_textItem)) return WorksheetCursor(); QTextCursor textCursor = m_textItem->search(pattern, qt_flags, pos); int position = 0; QString latex; const QString repl = QString(QChar::ObjectReplacementCharacter); QTextCursor latexCursor = m_textItem->search(repl, qt_flags, pos); while (!latexCursor.isNull()) { latex = m_textItem->resolveImages(latexCursor); position = searchText(latex, pattern, qt_flags); if (position >= 0) { break; } WorksheetCursor c(this, m_textItem, latexCursor); latexCursor = m_textItem->search(repl, qt_flags, c); } if (latexCursor.isNull()) { if (textCursor.isNull()) return WorksheetCursor(); else return WorksheetCursor(this, m_textItem, textCursor); } else { if (textCursor.isNull() || latexCursor < textCursor) { int start = latexCursor.selectionStart(); latexCursor.insertText(latex); QTextCursor c = m_textItem->textCursor(); c.setPosition(start + position); c.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, pattern.length()); return WorksheetCursor(this, m_textItem, c); } else { return WorksheetCursor(this, m_textItem, textCursor); } } } void LatexEntry::layOutForWidth(qreal w, bool force) { if (size().width() == w && !force) return; m_textItem->setGeometry(0, 0, w); setSize(QSizeF(m_textItem->width(), m_textItem->height() + VerticalMargin)); } bool LatexEntry::wantToEvaluate() { return !isOneImageOnly(); } bool LatexEntry::renderLatexCode() { bool success = false; QString latex = latexCode(); Cantor::LatexRenderer* renderer = new Cantor::LatexRenderer(this); renderer->setLatexCode(latex); renderer->setEquationOnly(false); renderer->setMethod(Cantor::LatexRenderer::LatexMethod); renderer->renderBlocking(); QTextImageFormat formulaFormat; if (renderer->renderingSuccessful()) { EpsRenderer* epsRend = worksheet()->epsRenderer(); formulaFormat = epsRend->render(m_textItem->document(), renderer); success = !formulaFormat.name().isEmpty(); } if(success) { QTextCursor cursor = m_textItem->textCursor(); cursor.movePosition(QTextCursor::Start); cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); cursor.insertText(QString(QChar::ObjectReplacementCharacter), formulaFormat); m_textItem->denyEditing(); } delete renderer; return success; } diff --git a/src/latexentry.h b/src/latexentry.h index 14ed8c23..c5d9c009 100644 --- a/src/latexentry.h +++ b/src/latexentry.h @@ -1,79 +1,81 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2009 Alexander Rieder Copyright (C) 2012 Martin Kuettler */ #ifndef LATEXENTRY_H #define LATEXENTRY_H #include "worksheetentry.h" #include "worksheettextitem.h" class LatexEntry : public WorksheetEntry { Q_OBJECT public: explicit LatexEntry(Worksheet* worksheet); ~LatexEntry() override = default; enum {Type = UserType + 5}; int type() const override; bool isEmpty() override; bool acceptRichText() override; bool focusEntry(int pos = WorksheetTextItem::TopLeft, qreal xCoord = 0) override; void setContent(const QString& content) override; void setContent(const QDomElement& content, const KZip& file) override; + void setContentFromJupyter(const QJsonObject & cell) override; QDomElement toXml(QDomDocument& doc, KZip* archive) override; + QJsonValue toJupyterJson() override; QString toPlain(const QString& commandSep, const QString& commentStartingSeq, const QString& commentEndingSeq) override; void interruptEvaluation() override; void layOutForWidth(qreal w, bool force = false) override; int searchText(const QString& text, const QString& pattern, QTextDocument::FindFlags qt_flags); WorksheetCursor search(const QString& pattern, unsigned flags, QTextDocument::FindFlags qt_flags, const WorksheetCursor& pos = WorksheetCursor()) override; public Q_SLOTS: bool evaluate(WorksheetEntry::EvaluationOption evalOp = FocusNext) override; void resolveImagesAtCursor(); void updateEntry() override; void populateMenu(QMenu* menu, QPointF pos) override; protected: bool wantToEvaluate() override; private: QString latexCode(); bool renderLatexCode(); bool isOneImageOnly(); private: WorksheetTextItem* m_textItem; }; #endif // LATEXENTRY_H diff --git a/src/lib/animationresult.cpp b/src/lib/animationresult.cpp index deb23bb3..46ac8c0b 100644 --- a/src/lib/animationresult.cpp +++ b/src/lib/animationresult.cpp @@ -1,105 +1,111 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2009 Alexander Rieder */ #include "animationresult.h" using namespace Cantor; #include #include #include #include #include #include #include class Cantor::AnimationResultPrivate { public: AnimationResultPrivate() = default; QUrl url; QMovie* movie; QString alt; }; AnimationResult::AnimationResult(const QUrl &url, const QString& alt ) : d(new AnimationResultPrivate) { d->url=url; d->alt=alt; d->movie=new QMovie(); d->movie->setFileName(url.toLocalFile()); } AnimationResult::~AnimationResult() { delete d->movie; delete d; } QString AnimationResult::toHtml() { return QStringLiteral("\"%2\"/").arg(d->url.toLocalFile(), d->alt); } QVariant AnimationResult::data() { return QVariant::fromValue(static_cast(d->movie)); } QUrl AnimationResult::url() { return d->url; } int AnimationResult::type() { return AnimationResult::Type; } QString AnimationResult::mimeType() { QMimeDatabase db; QMimeType type = db.mimeTypeForUrl(d->url); return type.name(); } QDomElement AnimationResult::toXml(QDomDocument& doc) { qDebug()<<"saving imageresult "<url.fileName()); qDebug()<<"done"; return e; } +QJsonValue Cantor::AnimationResult::toJupyterJson() +{ + // Jupyter TODO: add support for this result type + return QJsonValue(); +} + void AnimationResult::saveAdditionalData(KZip* archive) { archive->addLocalFile(d->url.toLocalFile(), d->url.fileName()); } void AnimationResult::save(const QString& filename) { //just copy over the file.. KIO::file_copy(d->url, QUrl::fromLocalFile(filename), -1, KIO::HideProgressInfo); } diff --git a/src/lib/animationresult.h b/src/lib/animationresult.h index 0d452f1c..929a2f74 100644 --- a/src/lib/animationresult.h +++ b/src/lib/animationresult.h @@ -1,55 +1,56 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2009 Alexander Rieder */ #ifndef _ANIMATIONRESULT_H #define _ANIMATIONRESULT_H #include "result.h" namespace Cantor { class AnimationResultPrivate; class CANTOR_EXPORT AnimationResult : public Result { public: enum{Type=6}; explicit AnimationResult( const QUrl& url, const QString& alt=QString() ); ~AnimationResult() override; QString toHtml() override; QVariant data() override; QUrl url() override; int type() override; QString mimeType() override; QDomElement toXml(QDomDocument& doc) override; + QJsonValue toJupyterJson() override; void saveAdditionalData(KZip* archive) override; void save(const QString& filename) override; private: AnimationResultPrivate* d; }; } #endif /* _ANIMATIONRESULT_H */ diff --git a/src/lib/epsresult.cpp b/src/lib/epsresult.cpp index 32148c21..14883d5b 100644 --- a/src/lib/epsresult.cpp +++ b/src/lib/epsresult.cpp @@ -1,99 +1,110 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2009 Alexander Rieder */ #include "epsresult.h" using namespace Cantor; #include #include +#include + #include #include class Cantor::EpsResultPrivate{ public: QUrl url; }; EpsResult::EpsResult(const QUrl& url) : d(new EpsResultPrivate) { d->url=url; #ifndef WITH_EPS qDebug()<<"Creating an EpsResult in an environment compiled without EPS support!"; #endif } EpsResult::~EpsResult() { delete d; } QString EpsResult::toHtml() { return QStringLiteral("").arg(d->url.url()); } QString EpsResult::toLatex() { return QStringLiteral(" \\begin{center} \n \\includegraphics[width=12cm]{%1}\n \\end{center}").arg(d->url.fileName()); } QVariant EpsResult::data() { return QVariant(d->url); } QUrl EpsResult::url() { return d->url; } int EpsResult::type() { return EpsResult::Type; } QString EpsResult::mimeType() { return QStringLiteral("image/x-eps"); } QDomElement EpsResult::toXml(QDomDocument& doc) { qDebug()<<"saving imageresult "<url.fileName()); qDebug()<<"done"; return e; } +QJsonValue Cantor::EpsResult::toJupyterJson() +{ + // Juputer TODO: Technically, I could convert .eps file to Image via EpsRenderer + // But EpsRender don't available from Cantor core library + // I will handle this result type in worksheet, but it's not look nice... + + return QJsonValue(); +} + void EpsResult::saveAdditionalData(KZip* archive) { archive->addLocalFile(d->url.toLocalFile(), d->url.fileName()); } void EpsResult::save(const QString& filename) { //just copy over the eps file.. KIO::file_copy(d->url, QUrl::fromLocalFile(filename), -1, KIO::HideProgressInfo); } diff --git a/src/lib/epsresult.h b/src/lib/epsresult.h index 66bc4e66..261284d4 100644 --- a/src/lib/epsresult.h +++ b/src/lib/epsresult.h @@ -1,58 +1,59 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2009 Alexander Rieder */ #ifndef _EPSRESULT_H #define _EPSRESULT_H #include "result.h" #include "cantor_export.h" #include namespace Cantor { class EpsResultPrivate; class CANTOR_EXPORT EpsResult : public Result { public: enum {Type=5}; explicit EpsResult( const QUrl& url); ~EpsResult() override; QString toHtml() override; QString toLatex() override; QVariant data() override; QUrl url() override; int type() override; QString mimeType() override; QDomElement toXml(QDomDocument& doc) override; + QJsonValue toJupyterJson() override; void saveAdditionalData(KZip* archive) override; void save(const QString& filename) override; private: EpsResultPrivate* d; }; } #endif /* _EPSRESULT_H */ diff --git a/src/lib/helpresult.cpp b/src/lib/helpresult.cpp index 070f069d..d74800fc 100644 --- a/src/lib/helpresult.cpp +++ b/src/lib/helpresult.cpp @@ -1,80 +1,88 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2009 Alexander Rieder */ +#include + #include "helpresult.h" using namespace Cantor; class Cantor::HelpResultPrivate { public: HelpResultPrivate() = default; ~HelpResultPrivate() = default; QString html; }; HelpResult::HelpResult(const QString& text, bool isHtml) : d(new HelpResultPrivate) { QString html; if (!isHtml) { html = text.toHtmlEscaped(); html.replace(QLatin1Char(' '), QLatin1String(" ")); html.replace(QLatin1Char('\n'), QLatin1String("
    \n")); } else html = text; d->html = html; } int HelpResult::type() { return HelpResult::Type; } QDomElement HelpResult::toXml(QDomDocument& doc) { //No need to save results of a help request QDomElement e=doc.createElement(QStringLiteral("Result")); e.setAttribute(QStringLiteral("type"), QStringLiteral("help")); return e; } +QJsonValue Cantor::HelpResult::toJupyterJson() +{ + // No need to save help result + return QJsonValue(); +} + QString HelpResult::toHtml() { return d->html; } QVariant HelpResult::data() { return QVariant(d->html); } QString HelpResult::mimeType() { return QStringLiteral("text/html"); } void HelpResult::save(const QString& filename) { //No need to save results of a help request Q_UNUSED(filename); } diff --git a/src/lib/helpresult.h b/src/lib/helpresult.h index 8422c91b..e12d543f 100644 --- a/src/lib/helpresult.h +++ b/src/lib/helpresult.h @@ -1,56 +1,57 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2009 Alexander Rieder */ #ifndef _HELPRESULT_H #define _HELPRESULT_H #include "textresult.h" namespace Cantor { /** this is basically a TextResult, just with a different Type so that the application can show it in another way than the normal results **/ class HelpResultPrivate; class CANTOR_EXPORT HelpResult: public Result { public: enum {Type=3}; explicit HelpResult( const QString& text, bool isHtml=false); ~HelpResult() override = default; QVariant data() override; QString toHtml() override; int type() override; QString mimeType() override; QDomElement toXml(QDomDocument& doc) override; + QJsonValue toJupyterJson() override; void save(const QString& filename) override; private: HelpResultPrivate* d; }; } #endif /* _HELPRESULT_H */ diff --git a/src/lib/imageresult.cpp b/src/lib/imageresult.cpp index e839be51..bf1acf87 100644 --- a/src/lib/imageresult.cpp +++ b/src/lib/imageresult.cpp @@ -1,115 +1,145 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2009 Alexander Rieder */ #include "imageresult.h" using namespace Cantor; #include #include #include #include +#include class Cantor::ImageResultPrivate { public: ImageResultPrivate() = default; QUrl url; QImage img; QString alt; }; ImageResult::ImageResult(const QUrl &url, const QString& alt) : d(new ImageResultPrivate) { d->url=url; d->alt=alt; } ImageResult::~ImageResult() { delete d; } QString ImageResult::toHtml() { return QStringLiteral("\"%2\"/").arg(d->url.toLocalFile(), d->alt); } QString ImageResult::toLatex() { return QStringLiteral(" \\begin{center} \n \\includegraphics[width=12cm]{%1} \n \\end{center}").arg(d->url.fileName()); } QVariant ImageResult::data() { if(d->img.isNull()) d->img.load(d->url.toLocalFile()); return QVariant(d->img); } QUrl ImageResult::url() { return d->url; } int ImageResult::type() { return ImageResult::Type; } QString ImageResult::mimeType() { const QList formats=QImageWriter::supportedImageFormats(); QString mimetype; foreach(const QByteArray& format, formats) { mimetype+=QLatin1String("image/"+format.toLower()+' '); } qDebug()<<"type: "<url.fileName()); qDebug()<<"done"; return e; } +QJsonValue Cantor::ImageResult::toJupyterJson() +{ + QJsonObject root; + + root.insert(QLatin1String("output_type"), QLatin1String("display_data")); + + QJsonObject data; + data.insert(QLatin1String("text/plain"), d->alt); + + QImage image; + if (d->img.isNull()) + image.load(d->url.toLocalFile()); + else + image = d->img; + + QByteArray ba; + QBuffer buffer(&ba); + buffer.open(QIODevice::WriteOnly); + image.save(&buffer, "PNG"); + data.insert(QLatin1String("image/png"), QString::fromLatin1(ba.toBase64())); + + root.insert(QLatin1String("data"), data); + + // Jupyter TODO: handle metadata? + root.insert(QLatin1String("metadata"), QJsonObject()); + + return root; +} + void ImageResult::saveAdditionalData(KZip* archive) { archive->addLocalFile(d->url.toLocalFile(), d->url.fileName()); } void ImageResult::save(const QString& filename) { //load into memory and let Qt save it, instead of just copying d->url //to give possibility to convert file format QImage img=data().value(); img.save(filename); } diff --git a/src/lib/imageresult.h b/src/lib/imageresult.h index 9b3dde46..fe4fad5b 100644 --- a/src/lib/imageresult.h +++ b/src/lib/imageresult.h @@ -1,57 +1,58 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2009 Alexander Rieder */ #ifndef _IMAGERESULT_H #define _IMAGERESULT_H #include "result.h" #include namespace Cantor { class ImageResultPrivate; class CANTOR_EXPORT ImageResult : public Result { public: enum{Type=2}; explicit ImageResult( const QUrl& url, const QString& alt=QString()); ~ImageResult() override; QString toHtml() override; QString toLatex() override; QVariant data() override; QUrl url() override; int type() override; QString mimeType() override; QDomElement toXml(QDomDocument& doc) override; + QJsonValue toJupyterJson() override; void saveAdditionalData(KZip* archive) override; void save(const QString& filename) override; private: ImageResultPrivate* d; }; } #endif /* _IMAGERESULT_H */ diff --git a/src/lib/latexresult.cpp b/src/lib/latexresult.cpp index 5b89215a..043feede 100644 --- a/src/lib/latexresult.cpp +++ b/src/lib/latexresult.cpp @@ -1,149 +1,155 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2009 Alexander Rieder */ #include "latexresult.h" using namespace Cantor; #include #include +#include #include class Cantor::LatexResultPrivate { public: LatexResultPrivate() { showCode=false; } bool showCode; QString code; QString plain; }; LatexResult::LatexResult(const QString& code, const QUrl &url, const QString& plain) : EpsResult( url ), d(new LatexResultPrivate) { d->code=code; d->plain=plain; } LatexResult::~LatexResult() { delete d; } int LatexResult::type() { return LatexResult::Type; } QString LatexResult::mimeType() { if(isCodeShown()) return QStringLiteral("text/plain"); else return EpsResult::mimeType(); } QString LatexResult::code() { return d->code; } QString LatexResult::plain() { return d->plain; } bool LatexResult::isCodeShown() { return d->showCode; } void LatexResult::showCode() { d->showCode=true; } void LatexResult::showRendered() { d->showCode=false; } QVariant LatexResult::data() { if(isCodeShown()) return QVariant(code()); else return EpsResult::data(); } QString LatexResult::toHtml() { if (isCodeShown()) { QString s=code(); return s.toHtmlEscaped(); } else { return EpsResult::toHtml(); } } QString LatexResult::toLatex() { return code(); } QDomElement LatexResult::toXml(QDomDocument& doc) { qDebug()<<"saving textresult "< */ #ifndef _LATEXRESULT_H #define _LATEXRESULT_H #include "epsresult.h" #include "cantor_export.h" namespace Cantor{ class LatexResultPrivate; /**Class used for LaTeX results, it is basically an Eps result, but it exports a different type, and additionally stores the LaTeX code, used to generate the Eps, so it can be retrieved later **/ class CANTOR_EXPORT LatexResult : public EpsResult { public: enum {Type=7}; LatexResult( const QString& code, const QUrl& url, const QString& plain = QString()); ~LatexResult() override; - + int type() override; QString mimeType() override; bool isCodeShown(); void showCode(); void showRendered(); QString code(); QString plain(); QString toHtml() override; QString toLatex() override; QVariant data() override; QDomElement toXml(QDomDocument& doc) override; - + QJsonValue toJupyterJson() override; + void save(const QString& filename) override; private: LatexResultPrivate* d; }; } #endif /* _LATEXRESULT_H */ diff --git a/src/lib/result.h b/src/lib/result.h index 71ff9b71..8b09265f 100644 --- a/src/lib/result.h +++ b/src/lib/result.h @@ -1,113 +1,117 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2009 Alexander Rieder */ #ifndef _RESULT_H #define _RESULT_H #include #include #include "cantor_export.h" class KZip; namespace Cantor { class ResultPrivate; /** * Base class for different results, like text, image, animation. etc. */ class CANTOR_EXPORT Result { public: /** * Default constructor */ Result( ); /** * Destructor */ virtual ~Result(); /** * returns html code, that represents this result, * e.g. an img tag for images * @return html code representing this result */ virtual QString toHtml() = 0; /** * returns latex code, that represents this result * e.g. a includegraphics command for images * it falls back to toHtml if not implemented * @return latex code representing this result */ virtual QString toLatex(); /** * returns data associated with this result * (text/images/etc) * @return data associated with this result */ virtual QVariant data() = 0; /** * returns an url, data for this result resides at * @return an url, data for this result resides at */ virtual QUrl url(); /** * returns an unique number, representing the type of this * result. Every subclass should define their own Type. * @return the type of this result */ virtual int type() = 0; /** * returns the mimetype, this result is * @return the mimetype, this result is */ virtual QString mimeType() = 0; /** * returns a DomElement, containing the information of the result * @param doc DomDocument used for storing the information * @return DomElement, containing the information of the result */ virtual QDomElement toXml(QDomDocument& doc) = 0; /** * saves all the data, that can't be saved in xml * in an extra file in the archive. */ virtual void saveAdditionalData(KZip* archive); + /** + * return a Jupyter json object, containing the information of the result + */ + virtual QJsonValue toJupyterJson() = 0; /** * saves this to a file * @param filename name of the file */ virtual void save(const QString& filename) = 0; private: ResultPrivate* d; }; } #endif /* _RESULT_H */ diff --git a/src/lib/textresult.cpp b/src/lib/textresult.cpp index 21fbf9ca..923ba257 100644 --- a/src/lib/textresult.cpp +++ b/src/lib/textresult.cpp @@ -1,135 +1,161 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2009 Alexander Rieder */ #include "textresult.h" using namespace Cantor; #include #include #include +#include +#include QString rtrim(const QString& s) { QString result = s; while (result.count() > 0 && result[result.count()-1].isSpace() ) { result = result.left(result.count() -1 ); } return result; } class Cantor::TextResultPrivate { public: TextResultPrivate() { format=TextResult::PlainTextFormat; } QString data; QString plain; TextResult::Format format; }; TextResult::TextResult(const QString& data) : d(new TextResultPrivate) { d->data=rtrim(data); d->plain=d->data; } TextResult::TextResult(const QString& data, const QString& plain) : d(new TextResultPrivate) { d->data=rtrim(data); d->plain=rtrim(plain); } TextResult::~TextResult() { delete d; } QString TextResult::toHtml() { QString s=d->data.toHtmlEscaped(); s.replace(QLatin1Char('\n'), QLatin1String("
    \n")); s.replace(QLatin1Char(' '), QLatin1String(" ")); return s; } QVariant TextResult::data() { return QVariant(d->data); } QString TextResult::plain() { return d->plain; } int TextResult::type() { return TextResult::Type; } QString TextResult::mimeType() { qDebug()<<"format: "<format; } void TextResult::setFormat(TextResult::Format f) { d->format=f; } QDomElement TextResult::toXml(QDomDocument& doc) { qDebug()<<"saving textresult "<data.split(QLatin1Char('\n')); + for (int i = 0; i < lines.size(); i++) + { + QString line = lines[i]; + // Don't add \n to last line + if (i != lines.size() - 1) + line.append(QLatin1Char('\n')); + text.append(line); + } + root.insert(QLatin1String("text"), text); + + return root; +} + void TextResult::save(const QString& filename) { QFile file(filename); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) return; QTextStream stream(&file); stream<data; file.close(); } diff --git a/src/lib/textresult.h b/src/lib/textresult.h index bd1856e8..772eef2c 100644 --- a/src/lib/textresult.h +++ b/src/lib/textresult.h @@ -1,61 +1,62 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2009 Alexander Rieder */ #ifndef _TEXTRESULT_H #define _TEXTRESULT_H #include "result.h" #include "cantor_export.h" namespace Cantor { class TextResultPrivate; class CANTOR_EXPORT TextResult : public Result { public: enum { Type=1 }; enum Format { PlainTextFormat, LatexFormat}; TextResult(const QString& text); TextResult(const QString& text, const QString& plain); ~TextResult() override; QString toHtml() override; QVariant data() override; QString plain(); int type() override; QString mimeType() override; Format format(); void setFormat(Format f); QDomElement toXml(QDomDocument& doc) override; + QJsonValue toJupyterJson() override; void save(const QString& filename) override; private: TextResultPrivate* d; }; } #endif /* _TEXTRESULT_H */ diff --git a/src/markdownentry.cpp b/src/markdownentry.cpp index e48cd434..bf7c6e3b 100644 --- a/src/markdownentry.cpp +++ b/src/markdownentry.cpp @@ -1,472 +1,490 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2018 Yifei Wu */ #include "markdownentry.h" #include #include +#include #include #ifdef Discount_FOUND extern "C" { #include } #endif #include MarkdownEntry::MarkdownEntry(Worksheet* worksheet) : WorksheetEntry(worksheet), m_textItem(new WorksheetTextItem(this, Qt::TextEditorInteraction)), rendered(false) { m_textItem->enableRichText(false); m_textItem->setOpenExternalLinks(true); m_textItem->installEventFilter(this); connect(m_textItem, &WorksheetTextItem::moveToPrevious, this, &MarkdownEntry::moveToPreviousEntry); connect(m_textItem, &WorksheetTextItem::moveToNext, this, &MarkdownEntry::moveToNextEntry); connect(m_textItem, SIGNAL(execute()), this, SLOT(evaluate())); } bool MarkdownEntry::isEmpty() { return m_textItem->document()->isEmpty(); } int MarkdownEntry::type() const { return Type; } bool MarkdownEntry::acceptRichText() { return false; } bool MarkdownEntry::focusEntry(int pos, qreal xCoord) { if (aboutToBeRemoved()) return false; m_textItem->setFocusAt(pos, xCoord); return true; } void MarkdownEntry::setContent(const QString& content) { rendered = false; plain = content; setPlainText(plain); } void MarkdownEntry::setContent(const QDomElement& content, const KZip& file) { Q_UNUSED(file); rendered = content.attribute(QLatin1String("rendered"), QLatin1String("1")) == QLatin1String("1"); QDomElement htmlEl = content.firstChildElement(QLatin1String("HTML")); if(!htmlEl.isNull()) html = htmlEl.text(); else { html = QLatin1String(""); rendered = false; // No html provided. Assume that it hasn't been rendered. } QDomElement plainEl = content.firstChildElement(QLatin1String("Plain")); if(!plainEl.isNull()) plain = plainEl.text(); else { plain = QLatin1String(""); html = QLatin1String(""); // No plain text provided. The entry shouldn't render anything, or the user can't re-edit it. } if(rendered) setRenderedHtml(html); else setPlainText(plain); } void MarkdownEntry::setContentFromJupyter(const QJsonObject& cell) { const QJsonValue& source = cell.value(QLatin1String("source")); QString code; if (source.isString()) code = source.toString(); else if (source.isArray()) for (const QJsonValue& line : source.toArray()) code += line.toString(); setPlainText(adaptJupyterMarkdown(code)); } QDomElement MarkdownEntry::toXml(QDomDocument& doc, KZip* archive) { Q_UNUSED(archive); if(!rendered) plain = m_textItem->toPlainText(); QDomElement el = doc.createElement(QLatin1String("Markdown")); el.setAttribute(QLatin1String("rendered"), (int)rendered); QDomElement plainEl = doc.createElement(QLatin1String("Plain")); plainEl.appendChild(doc.createTextNode(plain)); el.appendChild(plainEl); if(rendered) { QDomElement htmlEl = doc.createElement(QLatin1String("HTML")); htmlEl.appendChild(doc.createTextNode(html)); el.appendChild(htmlEl); } return el; } +QJsonValue MarkdownEntry::toJupyterJson() +{ + QJsonObject entry; + + entry.insert(QLatin1String("cell_type"), QLatin1String("markdown")); + + // Jupyter TODO: Handle metadata + entry.insert(QLatin1String("metadata"), QJsonObject()); + + QJsonArray text; + for (QString line : plain.split(QLatin1Char('\n'))) + { + line.append(QLatin1Char('\n')); + text.append(line); + } + entry.insert(QLatin1String("source"), text); + + return entry; +} + QString MarkdownEntry::toPlain(const QString& commandSep, const QString& commentStartingSeq, const QString& commentEndingSeq) { Q_UNUSED(commandSep); if (commentStartingSeq.isEmpty()) return QString(); - if(!rendered) - plain = m_textItem->toPlainText(); - - QString text = QString(plain); + QString text(plain); if (!commentEndingSeq.isEmpty()) return commentStartingSeq + text + commentEndingSeq + QLatin1String("\n"); return commentStartingSeq + text.replace(QLatin1String("\n"), QLatin1String("\n") + commentStartingSeq) + QLatin1String("\n"); } void MarkdownEntry::interruptEvaluation() { } bool MarkdownEntry::evaluate(EvaluationOption evalOp) { if(!rendered) { plain = m_textItem->toPlainText(); rendered = renderMarkdown(plain); } if (rendered) { // Render math in $$...$$ via Latex QTextCursor cursor = findLatexCode(); while (!cursor.isNull()) { QString latexCode = cursor.selectedText(); qDebug()<<"found latex: " << latexCode; latexCode.remove(0, 2); latexCode.remove(latexCode.length() - 2, 2); latexCode.replace(QChar::ParagraphSeparator, QLatin1Char('\n')); latexCode.replace(QChar::LineSeparator, QLatin1Char('\n')); Cantor::LatexRenderer* renderer=new Cantor::LatexRenderer(this); renderer->setLatexCode(latexCode); renderer->setEquationOnly(true); renderer->setEquationType(Cantor::LatexRenderer::InlineEquation); renderer->setMethod(Cantor::LatexRenderer::LatexMethod); renderer->renderBlocking(); bool success; QTextImageFormat formulaFormat; if (renderer->renderingSuccessful()) { EpsRenderer* epsRend = worksheet()->epsRenderer(); formulaFormat = epsRend->render(m_textItem->document(), renderer); success = !formulaFormat.name().isEmpty(); } else { success = false; } qDebug()<<"rendering successful? "<search(pattern, qt_flags, pos); if (textCursor.isNull()) return WorksheetCursor(); else return WorksheetCursor(this, m_textItem, textCursor); } void MarkdownEntry::layOutForWidth(qreal w, bool force) { if (size().width() == w && !force) return; m_textItem->setGeometry(0, 0, w); setSize(QSizeF(m_textItem->width(), m_textItem->height() + VerticalMargin)); } bool MarkdownEntry::eventFilter(QObject* object, QEvent* event) { if(object == m_textItem) { if(event->type() == QEvent::GraphicsSceneMouseDoubleClick) { QGraphicsSceneMouseEvent* mouseEvent = dynamic_cast(event); if(!mouseEvent) return false; if(mouseEvent->button() == Qt::LeftButton && rendered) { setPlainText(plain); m_textItem->setCursorPosition(mouseEvent->pos()); m_textItem->textCursor().clearSelection(); rendered = false; return true; } } } return false; } bool MarkdownEntry::wantToEvaluate() { return !rendered; } void MarkdownEntry::setRenderedHtml(const QString& html) { m_textItem->setHtml(html); m_textItem->denyEditing(); } void MarkdownEntry::setPlainText(const QString& plain) { QTextDocument* doc = m_textItem->document(); doc->setPlainText(plain); m_textItem->setDocument(doc); m_textItem->allowEditing(); } QTextCursor MarkdownEntry::findLatexCode(const QTextCursor& cursor) const { QTextDocument *doc = m_textItem->document(); QTextCursor startCursor; if (cursor.isNull()) startCursor = doc->find(QLatin1String("$$")); else startCursor = doc->find(QLatin1String("$$"), cursor); if (startCursor.isNull()) return startCursor; const QTextCursor endCursor = doc->find(QLatin1String("$$"), startCursor); if (endCursor.isNull()) return endCursor; startCursor.setPosition(startCursor.selectionStart()); startCursor.setPosition(endCursor.position(), QTextCursor::KeepAnchor); return startCursor; } enum class ParserState {Text, CodeQuote, SingleDollar, DoubleDollar}; QString MarkdownEntry::adaptJupyterMarkdown(const QString& markdown) { QString input = markdown; QString tail, out; do { out += convert(input, tail); input = tail; } while (!tail.isEmpty()); out.replace(QLatin1String("\\\\$"), QLatin1String("$")); return out; } QString MarkdownEntry::convert(const QString& markdown, QString& tail) { static const QLatin1Char CODE_QUOTE('`'); static const QLatin1Char DOLLAR('$'); static const QLatin1Char ESCAPER('\\'); QString buf; QChar prev[2]; ParserState state = ParserState::Text; QString out; // Double dollar state int length = 0; // Quote state int quoteLevel = 0; int quoteSequence = 0; bool beginQuote = true; for (const QChar& sym : markdown) { const bool isEscaping = prev[0] == ESCAPER; switch (state) { case ParserState::Text: { if (sym == CODE_QUOTE && !isEscaping) { state = ParserState::CodeQuote; } const bool isDoubleEscaping = isEscaping && prev[1] == ESCAPER; if (sym == DOLLAR && !isDoubleEscaping) { state = ParserState::SingleDollar; // no write to out variable break; } out += sym; } break; case ParserState::CodeQuote: buf += sym; if (sym == CODE_QUOTE && !isEscaping) { if (beginQuote) { quoteLevel++; } else { quoteSequence++; if (quoteSequence == quoteLevel) { state = ParserState::Text; out += buf; // clean up state buf.clear(); beginQuote = true; quoteLevel = 0; quoteSequence = 0; } } } else if (beginQuote) { beginQuote = false; quoteSequence = 0; } break; case ParserState::SingleDollar: if (sym == DOLLAR) { if (isEscaping) buf += sym; else { // So we have double dollars ($$) and we need go to double dollar state if (buf.isEmpty()) { out += DOLLAR + DOLLAR; state = ParserState::DoubleDollar; } else { // Main purpose of this code // $...$ -> $$...$$ // because Cantor supports only $$...$$ out += DOLLAR + DOLLAR + buf + DOLLAR + DOLLAR; buf.clear(); state = ParserState::Text; } } } else buf += sym; break; // Jupyter TODO: Strange logic // if we have $$ $...$ (eof) // After converting we will ahve $$ $$...$$ (eof) // And it will be evaluate in wrong way ($$ $$) case ParserState::DoubleDollar: buf += sym; length++; if (sym == DOLLAR && prev[0] == DOLLAR && prev[1] != ESCAPER && length >= 3) { state = ParserState::Text; out += buf; buf.clear(); length = 0; } break; } // Shift previous symbols prev[1] = prev[0]; prev[0] = sym; } tail = buf; return out; } diff --git a/src/markdownentry.h b/src/markdownentry.h index b37b837d..d3673229 100644 --- a/src/markdownentry.h +++ b/src/markdownentry.h @@ -1,81 +1,82 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2018 Yifei Wu */ #ifndef MARKDOWNENTRY_H #define MARKDOWNENTRY_H #include "worksheetentry.h" #include "worksheettextitem.h" class QJsonObject; class MarkdownEntry : public WorksheetEntry { Q_OBJECT public: explicit MarkdownEntry(Worksheet* worksheet); ~MarkdownEntry() override = default; enum {Type = UserType + 7}; int type() const override; bool isEmpty() override; bool acceptRichText() override; bool focusEntry(int pos = WorksheetTextItem::TopLeft, qreal xCoord=0) override; void setContent(const QString& content) override; void setContent(const QDomElement& content, const KZip& file) override; - void setContentFromJupyter(const QJsonObject& cell); + void setContentFromJupyter(const QJsonObject& cell) override; QDomElement toXml(QDomDocument& doc, KZip* archive) override; + QJsonValue toJupyterJson() override; QString toPlain(const QString& commandSep, const QString& commentStartingSeq, const QString& commentEndingSeq) override; void interruptEvaluation() override; void layOutForWidth(qreal w, bool force = false) override; WorksheetCursor search(const QString& pattern, unsigned flags, QTextDocument::FindFlags qt_flags, const WorksheetCursor& pos = WorksheetCursor()) override; public Q_SLOTS: bool evaluate(WorksheetEntry::EvaluationOption evalOp = FocusNext) override; void updateEntry() override; protected: bool renderMarkdown(QString& plain); bool eventFilter(QObject* object, QEvent* event) override; bool wantToEvaluate() override; void setRenderedHtml(const QString& html); void setPlainText(const QString& plain); QTextCursor findLatexCode(const QTextCursor& cursor = QTextCursor()) const; QString adaptJupyterMarkdown(const QString& markdown); QString convert(const QString& markdown, QString& tail); protected: WorksheetTextItem* m_textItem; QString plain; QString html; bool rendered; }; #endif //MARKDOWNENTRY_H diff --git a/src/pagebreakentry.cpp b/src/pagebreakentry.cpp index 10a6dd4d..ea798b37 100644 --- a/src/pagebreakentry.cpp +++ b/src/pagebreakentry.cpp @@ -1,152 +1,165 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2012 Martin Kuettler */ #include "pagebreakentry.h" #include #include #include +#include #include #include PageBreakEntry::PageBreakEntry(Worksheet* worksheet) : WorksheetEntry(worksheet) { m_msgItem = new WorksheetTextItem(this); QTextCursor cursor = m_msgItem->textCursor(); KColorScheme color = KColorScheme(QPalette::Normal, KColorScheme::View); QTextCharFormat cformat(cursor.charFormat()); cformat.setForeground(color.foreground(KColorScheme::InactiveText)); cursor.insertText(i18n("--- Page Break ---"), cformat); setFlag(QGraphicsItem::ItemIsFocusable); } bool PageBreakEntry::isEmpty() { return false; } int PageBreakEntry::type() const { return Type; } void PageBreakEntry::populateMenu(QMenu* menu, QPointF pos) { WorksheetEntry::populateMenu(menu, pos); } bool PageBreakEntry::acceptRichText() { return false; } void PageBreakEntry::setContent(const QString& content) { Q_UNUSED(content); return; } void PageBreakEntry::setContent(const QDomElement& content, const KZip& file) { Q_UNUSED(content); Q_UNUSED(file); return; } +void PageBreakEntry::setContentFromJupyter(const QJsonObject& cell) +{ + Q_UNUSED(cell); + return; +} + +QJsonValue PageBreakEntry::toJupyterJson() +{ + // There isn't something like our page break in Jupyter + return QJsonValue(); +} + QDomElement PageBreakEntry::toXml(QDomDocument& doc, KZip* archive) { Q_UNUSED(archive); QDomElement pgbrk = doc.createElement(QLatin1String("PageBreak")); return pgbrk; } QString PageBreakEntry::toPlain(const QString& commandSep, const QString& commentStartingSeq, const QString& commentEndingSeq) { Q_UNUSED(commandSep); return commentStartingSeq + QLatin1String("page break") + commentEndingSeq; } void PageBreakEntry::interruptEvaluation() { return; } void PageBreakEntry::layOutForWidth(qreal w, bool force) { if (size().width() == w && !force) return; if (m_msgItem->isVisible()) { m_msgItem->setGeometry(0, 0, w, true); setSize(QSizeF(m_msgItem->width(), m_msgItem->height() + VerticalMargin)); } else { setSize(QSizeF(w, 0)); } } bool PageBreakEntry::evaluate(EvaluationOption evalOp) { evaluateNext(evalOp); return true; } void PageBreakEntry::updateEntry() { if (worksheet()->isPrinting()) { m_msgItem->setVisible(false); recalculateSize(); } else if (!m_msgItem->isVisible()) { m_msgItem->setVisible(true); recalculateSize(); } return; } /* void PageBreakEntry::paint(QPainter* painter, const QStyleOptionGraphicsItem*, QWidget*) { if (worksheet()->isPrinting()) { QPaintDevice* device = painter->paintEngine()->paintDevice(); QPrinter* printer = qobject_cast(device); if (printer) printer->newPage(); } } */ bool PageBreakEntry::wantToEvaluate() { return false; } bool PageBreakEntry::wantFocus() { return false; } diff --git a/src/pagebreakentry.h b/src/pagebreakentry.h index 952532ce..04b9a480 100644 --- a/src/pagebreakentry.h +++ b/src/pagebreakentry.h @@ -1,67 +1,69 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2012 Martin Kuettler */ #ifndef PAGEBREAKENTRY_H #define PAGEBREAKENTRY_H #include "worksheetentry.h" class WorksheetTextItem; class PageBreakEntry : public WorksheetEntry { Q_OBJECT public: explicit PageBreakEntry(Worksheet* worksheet); ~PageBreakEntry() override = default; enum {Type = UserType + 3}; int type() const override; bool isEmpty() override; bool acceptRichText() override; void setContent(const QString& content) override; void setContent(const QDomElement& content, const KZip& file) override; + void setContentFromJupyter(const QJsonObject & cell) override; QDomElement toXml(QDomDocument& doc, KZip* archive) override; + QJsonValue toJupyterJson() override; QString toPlain(const QString& commandSep, const QString& commentStartingSeq, const QString& commentEndingSeq) override; void interruptEvaluation() override; void layOutForWidth(qreal w, bool force = false) override; //void paint(QPainter* painter, const QStyleOptionGraphicsItem * option, // QWidget * widget = 0); public Q_SLOTS: bool evaluate(WorksheetEntry::EvaluationOption evalOp = FocusNext) override; void updateEntry() override; void populateMenu(QMenu* menu, QPointF pos) override; protected: bool wantToEvaluate() override; bool wantFocus() override; private: WorksheetTextItem* m_msgItem; }; #endif /* PAGEBREAKENTRY_H */ diff --git a/src/placeholderentry.cpp b/src/placeholderentry.cpp index 30866135..781f71cc 100644 --- a/src/placeholderentry.cpp +++ b/src/placeholderentry.cpp @@ -1,113 +1,126 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2012 Martin Kuettler */ #include "placeholderentry.h" #include +#include PlaceHolderEntry::PlaceHolderEntry(Worksheet* worksheet, QSizeF s) : WorksheetEntry(worksheet) { setSize(s); } int PlaceHolderEntry::type() const { return Type; } bool PlaceHolderEntry::isEmpty() { /* // This is counter-intuitive. isEmpty() is used to find out whether a new // CommandEntry needs to be appended, and a PlaceHolderEntry should never // prevent that. return false; */ return true; } bool PlaceHolderEntry::acceptRichText() { return false; } void PlaceHolderEntry::setContent(const QString&) { } void PlaceHolderEntry::setContent(const QDomElement&, const KZip&) { } +void PlaceHolderEntry::setContentFromJupyter(const QJsonObject& cell) +{ + Q_UNUSED(cell); + return; +} + +QJsonValue PlaceHolderEntry::toJupyterJson() +{ + return QJsonValue(); +} + + QDomElement PlaceHolderEntry::toXml(QDomDocument&, KZip*) { return QDomElement(); } QString PlaceHolderEntry::toPlain(const QString&, const QString&, const QString&){ return QString(); } void PlaceHolderEntry::interruptEvaluation() { return; } void PlaceHolderEntry::layOutForWidth(qreal w, bool force) { Q_UNUSED(w); Q_UNUSED(force); } bool PlaceHolderEntry::evaluate(EvaluationOption evalOp) { evaluateNext(evalOp); return true; } void PlaceHolderEntry::updateEntry() { } bool PlaceHolderEntry::wantToEvaluate() { return false; } void PlaceHolderEntry::changeSize(QSizeF s) { if (!worksheet()->animationsEnabled()) { setSize(s); worksheet()->updateEntrySize(this); return; } if (aboutToBeRemoved()) return; if (animationActive()) endAnimation(); QPropertyAnimation* sizeAn = sizeChangeAnimation(s); sizeAn->setEasingCurve(QEasingCurve::InOutQuad); sizeAn->start(QAbstractAnimation::DeleteWhenStopped); } diff --git a/src/placeholderentry.h b/src/placeholderentry.h index 7bb466e7..386eb4a3 100644 --- a/src/placeholderentry.h +++ b/src/placeholderentry.h @@ -1,55 +1,57 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2012 Martin Kuettler */ #ifndef PLACEHOLDERENTRY_H #define PLACEHOLDERENTRY_H #include "worksheetentry.h" class PlaceHolderEntry : public WorksheetEntry { public: PlaceHolderEntry(Worksheet* worksheet, QSizeF s); ~PlaceHolderEntry() override = default; enum {Type = UserType + 6}; int type() const override; bool isEmpty() override; bool acceptRichText() override; void setContent(const QString&) override; void setContent(const QDomElement&, const KZip&) override; + void setContentFromJupyter(const QJsonObject & cell) override; QDomElement toXml(QDomDocument&, KZip*) override; + QJsonValue toJupyterJson() override; QString toPlain(const QString&, const QString&, const QString&) override; void interruptEvaluation() override; void layOutForWidth(qreal w, bool force = false) override; public Q_SLOTS: bool evaluate(WorksheetEntry::EvaluationOption evalOp = FocusNext) override; void updateEntry() override; void changeSize(QSizeF s); protected: bool wantToEvaluate() override; }; #endif //PLACEHOLDERENTRY_H diff --git a/src/textentry.cpp b/src/textentry.cpp index b7685c7e..7902d852 100644 --- a/src/textentry.cpp +++ b/src/textentry.cpp @@ -1,357 +1,369 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2009 Alexander Rieder Copyright (C) 2012 Martin Kuettler */ #include "textentry.h" #include "worksheettextitem.h" #include "epsrenderer.h" #include "latexrenderer.h" #include - +#include #include + #include TextEntry::TextEntry(Worksheet* worksheet) : WorksheetEntry(worksheet), m_textItem(new WorksheetTextItem(this, Qt::TextEditorInteraction)) { m_textItem->enableRichText(true); connect(m_textItem, &WorksheetTextItem::moveToPrevious, this, &TextEntry::moveToPreviousEntry); connect(m_textItem, &WorksheetTextItem::moveToNext, this, &TextEntry::moveToNextEntry); connect(m_textItem, SIGNAL(execute()), this, SLOT(evaluate())); connect(m_textItem, &WorksheetTextItem::doubleClick, this, &TextEntry::resolveImagesAtCursor); } void TextEntry::populateMenu(QMenu* menu, QPointF pos) { bool imageSelected = false; QTextCursor cursor = m_textItem->textCursor(); const QChar repl = QChar::ObjectReplacementCharacter; if (cursor.hasSelection()) { QString selection = m_textItem->textCursor().selectedText(); imageSelected = selection.contains(repl); } else { // we need to try both the current cursor and the one after the that cursor = m_textItem->cursorForPosition(pos); qDebug() << cursor.position(); for (int i = 2; i; --i) { int p = cursor.position(); if (m_textItem->document()->characterAt(p-1) == repl && cursor.charFormat().hasProperty(EpsRenderer::CantorFormula)) { m_textItem->setTextCursor(cursor); imageSelected = true; break; } cursor.movePosition(QTextCursor::NextCharacter); } } if (imageSelected) { menu->addAction(i18n("Show LaTeX code"), this, SLOT(resolveImagesAtCursor())); menu->addSeparator(); } WorksheetEntry::populateMenu(menu, pos); } bool TextEntry::isEmpty() { return m_textItem->document()->isEmpty(); } int TextEntry::type() const { return Type; } bool TextEntry::acceptRichText() { return true; } bool TextEntry::focusEntry(int pos, qreal xCoord) { if (aboutToBeRemoved()) return false; m_textItem->setFocusAt(pos, xCoord); return true; } void TextEntry::setContent(const QString& content) { m_textItem->setPlainText(content); } void TextEntry::setContent(const QDomElement& content, const KZip& file) { Q_UNUSED(file); if(content.firstChildElement(QLatin1String("body")).isNull()) return; QDomDocument doc = QDomDocument(); QDomNode n = doc.importNode(content.firstChildElement(QLatin1String("body")), true); doc.appendChild(n); QString html = doc.toString(); qDebug() << html; m_textItem->setHtml(html); } +void TextEntry::setContentFromJupyter(const QJsonObject& cell) +{ + // Jupyter TODO: Add realization +} + +QJsonValue TextEntry::toJupyterJson() +{ + // Jupyter TODO: Add realization + return QJsonValue(); +} + QDomElement TextEntry::toXml(QDomDocument& doc, KZip* archive) { Q_UNUSED(archive); bool needsEval=false; //make sure that the latex code is shown instead of the rendered formulas QTextCursor cursor = m_textItem->document()->find(QString(QChar::ObjectReplacementCharacter)); while(!cursor.isNull()) { QTextCharFormat format = cursor.charFormat(); if (format.hasProperty(EpsRenderer::CantorFormula)) { showLatexCode(cursor); needsEval=true; } cursor = m_textItem->document()->find(QString(QChar::ObjectReplacementCharacter), cursor); } const QString& html = m_textItem->toHtml(); qDebug() << html; QDomElement el = doc.createElement(QLatin1String("Text")); QDomDocument myDoc = QDomDocument(); myDoc.setContent(html); el.appendChild(myDoc.documentElement().firstChildElement(QLatin1String("body"))); if (needsEval) evaluate(); return el; } QString TextEntry::toPlain(const QString& commandSep, const QString& commentStartingSeq, const QString& commentEndingSeq) { Q_UNUSED(commandSep); if (commentStartingSeq.isEmpty()) return QString(); /* // would this be plain enough? QTextCursor cursor = m_textItem->textCursor(); cursor.movePosition(QTextCursor::Start); cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); QString text = m_textItem->resolveImages(cursor); text.replace(QChar::ParagraphSeparator, '\n'); text.replace(QChar::LineSeparator, '\n'); */ QString text = m_textItem->toPlainText(); if (!commentEndingSeq.isEmpty()) return commentStartingSeq + text + commentEndingSeq + QLatin1String("\n"); return commentStartingSeq + text.replace(QLatin1String("\n"), QLatin1String("\n") + commentStartingSeq) + QLatin1String("\n"); } void TextEntry::interruptEvaluation() { } bool TextEntry::evaluate(EvaluationOption evalOp) { QTextCursor cursor = findLatexCode(); while (!cursor.isNull()) { QString latexCode = cursor.selectedText(); qDebug()<<"found latex: "<setLatexCode(latexCode); renderer->setEquationOnly(true); renderer->setEquationType(Cantor::LatexRenderer::InlineEquation); renderer->setMethod(Cantor::LatexRenderer::LatexMethod); renderer->renderBlocking(); bool success; QTextImageFormat formulaFormat; if (renderer->renderingSuccessful()) { EpsRenderer* epsRend = worksheet()->epsRenderer(); formulaFormat = epsRend->render(m_textItem->document(), renderer); success = !formulaFormat.name().isEmpty(); } else { success = false; } qDebug()<<"rendering successful? "<document()->find(QString(QChar::ObjectReplacementCharacter)); while(!cursor.isNull()) { QTextCharFormat format = cursor.charFormat(); if (format.hasProperty(EpsRenderer::CantorFormula)) { qDebug() << "found a formula... rendering the eps..."; QUrl url = format.property(EpsRenderer::ImagePath).toUrl(); QSizeF s = worksheet()->epsRenderer()->renderToResource(m_textItem->document(), url); qDebug() << "rendering successful? " << s.isValid(); //cursor.deletePreviousChar(); //cursor.insertText(QString(QChar::ObjectReplacementCharacter), format); } cursor = m_textItem->document()->find(QString(QChar::ObjectReplacementCharacter), cursor); } } void TextEntry::resolveImagesAtCursor() { QTextCursor cursor = m_textItem->textCursor(); if (!cursor.hasSelection()) cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor); cursor.insertText(m_textItem->resolveImages(cursor)); } QTextCursor TextEntry::findLatexCode(const QTextCursor& cursor) const { QTextDocument *doc = m_textItem->document(); QTextCursor startCursor; if (cursor.isNull()) startCursor = doc->find(QLatin1String("$$")); else startCursor = doc->find(QLatin1String("$$"), cursor); if (startCursor.isNull()) return startCursor; const QTextCursor endCursor = doc->find(QLatin1String("$$"), startCursor); if (endCursor.isNull()) return endCursor; startCursor.setPosition(startCursor.selectionStart()); startCursor.setPosition(endCursor.position(), QTextCursor::KeepAnchor); return startCursor; } QString TextEntry::showLatexCode(QTextCursor& cursor) { QString latexCode = cursor.charFormat().property(EpsRenderer::Code).toString(); cursor.deletePreviousChar(); latexCode = QLatin1String("$$") + latexCode + QLatin1String("$$"); cursor.insertText(latexCode); return latexCode; } int TextEntry::searchText(const QString& text, const QString& pattern, QTextDocument::FindFlags qt_flags) { Qt::CaseSensitivity caseSensitivity; if (qt_flags & QTextDocument::FindCaseSensitively) caseSensitivity = Qt::CaseSensitive; else caseSensitivity = Qt::CaseInsensitive; int position; if (qt_flags & QTextDocument::FindBackward) position = text.lastIndexOf(pattern, -1, caseSensitivity); else position = text.indexOf(pattern, 0, caseSensitivity); return position; } WorksheetCursor TextEntry::search(const QString& pattern, unsigned flags, QTextDocument::FindFlags qt_flags, const WorksheetCursor& pos) { if (!(flags & WorksheetEntry::SearchText) || (pos.isValid() && pos.entry() != this)) return WorksheetCursor(); QTextCursor textCursor = m_textItem->search(pattern, qt_flags, pos); int position = 0; QTextCursor latexCursor; QString latex; if (flags & WorksheetEntry::SearchLaTeX) { const QString repl = QString(QChar::ObjectReplacementCharacter); latexCursor = m_textItem->search(repl, qt_flags, pos); while (!latexCursor.isNull()) { latex = m_textItem->resolveImages(latexCursor); position = searchText(latex, pattern, qt_flags); if (position >= 0) { break; } WorksheetCursor c(this, m_textItem, latexCursor); latexCursor = m_textItem->search(repl, qt_flags, c); } } if (latexCursor.isNull()) { if (textCursor.isNull()) return WorksheetCursor(); else return WorksheetCursor(this, m_textItem, textCursor); } else { if (textCursor.isNull() || latexCursor < textCursor) { int start = latexCursor.selectionStart(); latexCursor.insertText(latex); QTextCursor c = m_textItem->textCursor(); c.setPosition(start + position); c.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, pattern.length()); return WorksheetCursor(this, m_textItem, c); } else { return WorksheetCursor(this, m_textItem, textCursor); } } } void TextEntry::layOutForWidth(qreal w, bool force) { if (size().width() == w && !force) return; m_textItem->setGeometry(0, 0, w); setSize(QSizeF(m_textItem->width(), m_textItem->height() + VerticalMargin)); } bool TextEntry::wantToEvaluate() { return !findLatexCode().isNull(); } diff --git a/src/textentry.h b/src/textentry.h index 8b31a07a..e9ba3261 100644 --- a/src/textentry.h +++ b/src/textentry.h @@ -1,90 +1,92 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2009 Alexander Rieder Copyright (C) 2012 Martin Kuettler */ #ifndef TEXTENTRY_H #define TEXTENTRY_H #include #include #include #include #include #include #include #include "worksheetentry.h" #include "worksheettextitem.h" class TextEntry : public WorksheetEntry { Q_OBJECT public: explicit TextEntry(Worksheet* worksheet); ~TextEntry() override = default; enum {Type = UserType + 1}; int type() const override; bool isEmpty() override; bool acceptRichText() override; bool focusEntry(int pos = WorksheetTextItem::TopLeft, qreal xCoord=0) override; // do we need/get this? //bool worksheetContextMenuEvent(...); //void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event); void setContent(const QString& content) override; void setContent(const QDomElement& content, const KZip& file) override; + void setContentFromJupyter(const QJsonObject& cell) override; QDomElement toXml(QDomDocument& doc, KZip* archive) override; + QJsonValue toJupyterJson() override; QString toPlain(const QString& commandSep, const QString& commentStartingSeq, const QString& commentEndingSeq) override; void interruptEvaluation() override; void layOutForWidth(qreal w, bool force = false) override; int searchText(const QString& text, const QString& pattern, QTextDocument::FindFlags qt_flags); WorksheetCursor search(const QString& pattern, unsigned flags, QTextDocument::FindFlags qt_flags, const WorksheetCursor& pos = WorksheetCursor()) override; public Q_SLOTS: bool evaluate(WorksheetEntry::EvaluationOption evalOp = FocusNext) override; void resolveImagesAtCursor(); void updateEntry() override; void populateMenu(QMenu* menu, QPointF pos) override; protected: bool wantToEvaluate() override; private: QTextCursor findLatexCode(const QTextCursor& cursor = QTextCursor()) const; QString showLatexCode(QTextCursor& cursor); private: WorksheetTextItem* m_textItem; }; #endif //TEXTENTRY_H diff --git a/src/worksheet.cpp b/src/worksheet.cpp index c6d553ff..ab44031a 100644 --- a/src/worksheet.cpp +++ b/src/worksheet.cpp @@ -1,2165 +1,2223 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2009 Alexander Rieder Copyright (C) 2012 Martin Kuettler */ #include "worksheet.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "settings.h" #include "commandentry.h" #include "textentry.h" #include "markdownentry.h" #include "latexentry.h" #include "imageentry.h" #include "pagebreakentry.h" #include "placeholderentry.h" #include "lib/backend.h" #include "lib/extension.h" #include "lib/helpresult.h" #include "lib/session.h" #include "lib/defaulthighlighter.h" #include "lib/backend.h" #include const double Worksheet::LeftMargin = 4; const double Worksheet::RightMargin = 4; const double Worksheet::TopMargin = 12; const double Worksheet::EntryCursorLength = 30; const double Worksheet::EntryCursorWidth = 2; Worksheet::Worksheet(Cantor::Backend* backend, QWidget* parent) : QGraphicsScene(parent) { m_session = backend->createSession(); m_highlighter = nullptr; m_firstEntry = nullptr; m_lastEntry = nullptr; m_lastFocusedTextItem = nullptr; m_dragEntry = nullptr; m_placeholderEntry = nullptr; m_viewWidth = 0; m_protrusion = 0; m_dragScrollTimer = nullptr; m_choosenCursorEntry = nullptr; m_isCursorEntryAfterLastEntry = false; m_entryCursorItem = addLine(0,0,0,0); const QColor& color = (palette().color(QPalette::Base).lightness() < 128) ? Qt::white : Qt::black; QPen pen(color); pen.setWidth(EntryCursorWidth); m_entryCursorItem->setPen(pen); m_entryCursorItem->hide(); m_cursorItemTimer = new QTimer(this); connect(m_cursorItemTimer, &QTimer::timeout, this, &Worksheet::animateEntryCursor); m_cursorItemTimer->start(500); m_isPrinting = false; m_loginDone = false; m_readOnly = false; m_isLoadingFromFile = false; enableHighlighting(Settings::self()->highlightDefault()); enableCompletion(Settings::self()->completionDefault()); enableExpressionNumbering(Settings::self()->expressionNumberingDefault()); enableAnimations(Settings::self()->animationDefault()); } Worksheet::~Worksheet() { // This is necessary, because a SeachBar might access firstEntry() // while the scene is deleted. Maybe there is a better solution to // this problem, but I can't seem to find it. m_firstEntry = nullptr; if (m_loginDone) m_session->logout(); if (m_session) { disconnect(m_session, 0, 0, 0); if (m_session->status() != Cantor::Session::Disable) m_session->logout(); m_session->deleteLater(); m_session = nullptr; } } void Worksheet::loginToSession() { m_session->login(); #ifdef WITH_EPS session()->setTypesettingEnabled(Settings::self()->typesetDefault()); #else session()->setTypesettingEnabled(false); #endif m_loginDone = true; } void Worksheet::print(QPrinter* printer) { m_epsRenderer.useHighResolution(true); m_isPrinting = true; QRect pageRect = printer->pageRect(); qreal scale = 1; // todo: find good scale for page size // todo: use epsRenderer()->scale() for printing ? const qreal width = pageRect.width()/scale; const qreal height = pageRect.height()/scale; setViewSize(width, height, scale, true); QPainter painter(printer); painter.scale(scale, scale); painter.setRenderHint(QPainter::Antialiasing); WorksheetEntry* entry = firstEntry(); qreal y = TopMargin; while (entry) { qreal h = 0; do { if (entry->type() == PageBreakEntry::Type) { entry = entry->next(); break; } h += entry->size().height(); entry = entry->next(); } while (entry && h + entry->size().height() <= height); render(&painter, QRectF(0, 0, width, height), QRectF(0, y, width, h)); y += h; if (entry) printer->newPage(); } //render(&painter); painter.end(); m_isPrinting = false; m_epsRenderer.useHighResolution(false); m_epsRenderer.setScale(-1); // force update in next call to setViewSize, worksheetView()->updateSceneSize(); // ... which happens in here } bool Worksheet::isPrinting() { return m_isPrinting; } void Worksheet::setViewSize(qreal w, qreal h, qreal s, bool forceUpdate) { Q_UNUSED(h); m_viewWidth = w; if (s != m_epsRenderer.scale() || forceUpdate) { m_epsRenderer.setScale(s); for (WorksheetEntry *entry = firstEntry(); entry; entry = entry->next()) entry->updateEntry(); } updateLayout(); } void Worksheet::updateLayout() { bool cursorRectVisible = false; bool atEnd = worksheetView()->isAtEnd(); if (currentTextItem()) { QRectF cursorRect = currentTextItem()->sceneCursorRect(); cursorRectVisible = worksheetView()->isVisible(cursorRect); } const qreal w = m_viewWidth - LeftMargin - RightMargin; qreal y = TopMargin; const qreal x = LeftMargin; for (WorksheetEntry *entry = firstEntry(); entry; entry = entry->next()) y += entry->setGeometry(x, y, w); setSceneRect(QRectF(0, 0, m_viewWidth + m_protrusion, y)); if (cursorRectVisible) makeVisible(worksheetCursor()); else if (atEnd) worksheetView()->scrollToEnd(); drawEntryCursor(); } void Worksheet::updateEntrySize(WorksheetEntry* entry) { bool cursorRectVisible = false; bool atEnd = worksheetView()->isAtEnd(); if (currentTextItem()) { QRectF cursorRect = currentTextItem()->sceneCursorRect(); cursorRectVisible = worksheetView()->isVisible(cursorRect); } qreal y = entry->y() + entry->size().height(); for (entry = entry->next(); entry; entry = entry->next()) { entry->setY(y); y += entry->size().height(); } setSceneRect(QRectF(0, 0, m_viewWidth + m_protrusion, y)); if (cursorRectVisible) makeVisible(worksheetCursor()); else if (atEnd) worksheetView()->scrollToEnd(); drawEntryCursor(); } void Worksheet::addProtrusion(qreal width) { if (m_itemProtrusions.contains(width)) ++m_itemProtrusions[width]; else m_itemProtrusions.insert(width, 1); if (width > m_protrusion) { m_protrusion = width; qreal y = lastEntry()->size().height() + lastEntry()->y(); setSceneRect(QRectF(0, 0, m_viewWidth + m_protrusion, y)); } } void Worksheet::updateProtrusion(qreal oldWidth, qreal newWidth) { removeProtrusion(oldWidth); addProtrusion(newWidth); } void Worksheet::removeProtrusion(qreal width) { if (--m_itemProtrusions[width] == 0) { m_itemProtrusions.remove(width); if (width == m_protrusion) { qreal max = -1; for (qreal p : m_itemProtrusions.keys()) { if (p > max) max = p; } m_protrusion = max; qreal y = lastEntry()->size().height() + lastEntry()->y(); setSceneRect(QRectF(0, 0, m_viewWidth + m_protrusion, y)); } } } bool Worksheet::isEmpty() { return !m_firstEntry; } bool Worksheet::isLoadingFromFile() { return m_isLoadingFromFile; } void Worksheet::makeVisible(WorksheetEntry* entry) { QRectF r = entry->boundingRect(); r = entry->mapRectToScene(r); r.adjust(0, -10, 0, 10); worksheetView()->makeVisible(r); } void Worksheet::makeVisible(const WorksheetCursor& cursor) { if (cursor.textCursor().isNull()) { if (cursor.entry()) makeVisible(cursor.entry()); return; } QRectF r = cursor.textItem()->sceneCursorRect(cursor.textCursor()); QRectF er = cursor.entry()->boundingRect(); er = cursor.entry()->mapRectToScene(er); er.adjust(0, -10, 0, 10); r.adjust(0, qMax(qreal(-100.0), er.top() - r.top()), 0, qMin(qreal(100.0), er.bottom() - r.bottom())); worksheetView()->makeVisible(r); } WorksheetView* Worksheet::worksheetView() { return qobject_cast(views().first()); } void Worksheet::setModified() { emit modified(); } WorksheetCursor Worksheet::worksheetCursor() { WorksheetEntry* entry = currentEntry(); WorksheetTextItem* item = currentTextItem(); if (!entry || !item) return WorksheetCursor(); return WorksheetCursor(entry, item, item->textCursor()); } void Worksheet::setWorksheetCursor(const WorksheetCursor& cursor) { if (!cursor.isValid()) return; if (m_lastFocusedTextItem) m_lastFocusedTextItem->clearSelection(); m_lastFocusedTextItem = cursor.textItem(); cursor.textItem()->setTextCursor(cursor.textCursor()); } WorksheetEntry* Worksheet::currentEntry() { QGraphicsItem* item = focusItem(); // Entry cursor activate if (m_choosenCursorEntry || m_isCursorEntryAfterLastEntry) return nullptr; if (!item /*&& !hasFocus()*/) item = m_lastFocusedTextItem; /*else m_focusItem = item;*/ while (item && (item->type() < QGraphicsItem::UserType || item->type() >= QGraphicsItem::UserType + 100)) item = item->parentItem(); if (item) { WorksheetEntry* entry = qobject_cast(item->toGraphicsObject()); if (entry && entry->aboutToBeRemoved()) { if (entry->isAncestorOf(m_lastFocusedTextItem)) m_lastFocusedTextItem = nullptr; return nullptr; } return entry; } return nullptr; } WorksheetEntry* Worksheet::firstEntry() { return m_firstEntry; } WorksheetEntry* Worksheet::lastEntry() { return m_lastEntry; } void Worksheet::setFirstEntry(WorksheetEntry* entry) { if (m_firstEntry) disconnect(m_firstEntry, SIGNAL(aboutToBeDeleted()), this, SLOT(invalidateFirstEntry())); m_firstEntry = entry; if (m_firstEntry) connect(m_firstEntry, SIGNAL(aboutToBeDeleted()), this, SLOT(invalidateFirstEntry()), Qt::DirectConnection); } void Worksheet::setLastEntry(WorksheetEntry* entry) { if (m_lastEntry) disconnect(m_lastEntry, SIGNAL(aboutToBeDeleted()), this, SLOT(invalidateLastEntry())); m_lastEntry = entry; if (m_lastEntry) connect(m_lastEntry, SIGNAL(aboutToBeDeleted()), this, SLOT(invalidateLastEntry()), Qt::DirectConnection); } void Worksheet::invalidateFirstEntry() { if (m_firstEntry) setFirstEntry(m_firstEntry->next()); } void Worksheet::invalidateLastEntry() { if (m_lastEntry) setLastEntry(m_lastEntry->previous()); } WorksheetEntry* Worksheet::entryAt(qreal x, qreal y) { QGraphicsItem* item = itemAt(x, y, QTransform()); while (item && (item->type() <= QGraphicsItem::UserType || item->type() >= QGraphicsItem::UserType + 100)) item = item->parentItem(); if (item) return qobject_cast(item->toGraphicsObject()); return nullptr; } WorksheetEntry* Worksheet::entryAt(QPointF p) { return entryAt(p.x(), p.y()); } void Worksheet::focusEntry(WorksheetEntry *entry) { if (!entry) return; entry->focusEntry(); resetEntryCursor(); //bool rt = entry->acceptRichText(); //setActionsEnabled(rt); //setAcceptRichText(rt); //ensureCursorVisible(); } void Worksheet::startDrag(WorksheetEntry* entry, QDrag* drag) { if (m_readOnly) return; resetEntryCursor(); m_dragEntry = entry; WorksheetEntry* prev = entry->previous(); WorksheetEntry* next = entry->next(); m_placeholderEntry = new PlaceHolderEntry(this, entry->size()); m_placeholderEntry->setPrevious(prev); m_placeholderEntry->setNext(next); if (prev) prev->setNext(m_placeholderEntry); else setFirstEntry(m_placeholderEntry); if (next) next->setPrevious(m_placeholderEntry); else setLastEntry(m_placeholderEntry); m_dragEntry->hide(); Qt::DropAction action = drag->exec(); qDebug() << action; if (action == Qt::MoveAction && m_placeholderEntry) { qDebug() << "insert in new position"; prev = m_placeholderEntry->previous(); next = m_placeholderEntry->next(); } m_dragEntry->setPrevious(prev); m_dragEntry->setNext(next); if (prev) prev->setNext(m_dragEntry); else setFirstEntry(m_dragEntry); if (next) next->setPrevious(m_dragEntry); else setLastEntry(m_dragEntry); m_dragEntry->show(); m_dragEntry->focusEntry(); const QPointF scenePos = worksheetView()->sceneCursorPos(); if (entryAt(scenePos) != m_dragEntry) m_dragEntry->hideActionBar(); updateLayout(); if (m_placeholderEntry) { m_placeholderEntry->setPrevious(nullptr); m_placeholderEntry->setNext(nullptr); m_placeholderEntry->hide(); m_placeholderEntry->deleteLater(); m_placeholderEntry = nullptr; } m_dragEntry = nullptr; } void Worksheet::evaluate() { qDebug()<<"evaluate worksheet"; if (!m_loginDone && !m_readOnly) loginToSession(); firstEntry()->evaluate(WorksheetEntry::EvaluateNext); emit modified(); } void Worksheet::evaluateCurrentEntry() { if (!m_loginDone && !m_readOnly) loginToSession(); WorksheetEntry* entry = currentEntry(); if(!entry) return; entry->evaluateCurrentItem(); } bool Worksheet::completionEnabled() { return m_completionEnabled; } void Worksheet::showCompletion() { WorksheetEntry* current = currentEntry(); current->showCompletion(); } WorksheetEntry* Worksheet::appendEntry(const int type) { WorksheetEntry* entry = WorksheetEntry::create(type, this); if (entry) { qDebug() << "Entry Appended"; entry->setPrevious(lastEntry()); if (lastEntry()) lastEntry()->setNext(entry); if (!firstEntry()) setFirstEntry(entry); setLastEntry(entry); updateLayout(); makeVisible(entry); focusEntry(entry); emit modified(); } return entry; } WorksheetEntry* Worksheet::appendCommandEntry() { return appendEntry(CommandEntry::Type); } WorksheetEntry* Worksheet::appendTextEntry() { return appendEntry(TextEntry::Type); } WorksheetEntry* Worksheet::appendMarkdownEntry() { return appendEntry(MarkdownEntry::Type); } WorksheetEntry* Worksheet::appendPageBreakEntry() { return appendEntry(PageBreakEntry::Type); } WorksheetEntry* Worksheet::appendImageEntry() { return appendEntry(ImageEntry::Type); } WorksheetEntry* Worksheet::appendLatexEntry() { return appendEntry(LatexEntry::Type); } void Worksheet::appendCommandEntry(const QString& text) { WorksheetEntry* entry = lastEntry(); if(!entry->isEmpty()) { entry = appendCommandEntry(); } if (entry) { focusEntry(entry); entry->setContent(text); evaluateCurrentEntry(); } } WorksheetEntry* Worksheet::insertEntry(const int type, WorksheetEntry* current) { if (!current) current = currentEntry(); if (!current) return appendEntry(type); WorksheetEntry *next = current->next(); WorksheetEntry *entry = nullptr; if (!next || next->type() != type || !next->isEmpty()) { entry = WorksheetEntry::create(type, this); entry->setPrevious(current); entry->setNext(next); current->setNext(entry); if (next) next->setPrevious(entry); else setLastEntry(entry); updateLayout(); emit modified(); } else { entry = next; } focusEntry(entry); makeVisible(entry); return entry; } WorksheetEntry* Worksheet::insertTextEntry(WorksheetEntry* current) { return insertEntry(TextEntry::Type, current); } WorksheetEntry* Worksheet::insertMarkdownEntry(WorksheetEntry* current) { return insertEntry(MarkdownEntry::Type, current); } WorksheetEntry* Worksheet::insertCommandEntry(WorksheetEntry* current) { return insertEntry(CommandEntry::Type, current); } WorksheetEntry* Worksheet::insertImageEntry(WorksheetEntry* current) { return insertEntry(ImageEntry::Type, current); } WorksheetEntry* Worksheet::insertPageBreakEntry(WorksheetEntry* current) { return insertEntry(PageBreakEntry::Type, current); } WorksheetEntry* Worksheet::insertLatexEntry(WorksheetEntry* current) { return insertEntry(LatexEntry::Type, current); } void Worksheet::insertCommandEntry(const QString& text) { WorksheetEntry* entry = insertCommandEntry(); if(entry&&!text.isNull()) { entry->setContent(text); evaluateCurrentEntry(); } } WorksheetEntry* Worksheet::insertEntryBefore(int type, WorksheetEntry* current) { if (!current) current = currentEntry(); if (!current) return nullptr; WorksheetEntry *prev = current->previous(); WorksheetEntry *entry = nullptr; if(!prev || prev->type() != type || !prev->isEmpty()) { entry = WorksheetEntry::create(type, this); entry->setNext(current); entry->setPrevious(prev); current->setPrevious(entry); if (prev) prev->setNext(entry); else setFirstEntry(entry); updateLayout(); emit modified(); } else entry = prev; focusEntry(entry); return entry; } WorksheetEntry* Worksheet::insertTextEntryBefore(WorksheetEntry* current) { return insertEntryBefore(TextEntry::Type, current); } WorksheetEntry* Worksheet::insertMarkdownEntryBefore(WorksheetEntry* current) { return insertEntryBefore(MarkdownEntry::Type, current); } WorksheetEntry* Worksheet::insertCommandEntryBefore(WorksheetEntry* current) { return insertEntryBefore(CommandEntry::Type, current); } WorksheetEntry* Worksheet::insertPageBreakEntryBefore(WorksheetEntry* current) { return insertEntryBefore(PageBreakEntry::Type, current); } WorksheetEntry* Worksheet::insertImageEntryBefore(WorksheetEntry* current) { return insertEntryBefore(ImageEntry::Type, current); } WorksheetEntry* Worksheet::insertLatexEntryBefore(WorksheetEntry* current) { return insertEntryBefore(LatexEntry::Type, current); } void Worksheet::interrupt() { if (m_session->status() == Cantor::Session::Running) { m_session->interrupt(); emit updatePrompt(); } } void Worksheet::interruptCurrentEntryEvaluation() { currentEntry()->interruptEvaluation(); } void Worksheet::highlightItem(WorksheetTextItem* item) { if (!m_highlighter) return; QTextDocument *oldDocument = m_highlighter->document(); QList > formats; if (oldDocument) { for (QTextBlock b = oldDocument->firstBlock(); b.isValid(); b = b.next()) { formats.append(b.layout()->additionalFormats()); } } // Not every highlighter is a Cantor::DefaultHighligther (e.g. the // highlighter for KAlgebra) Cantor::DefaultHighlighter* hl = qobject_cast(m_highlighter); if (hl) { hl->setTextItem(item); } else { m_highlighter->setDocument(item->document()); } if (oldDocument) { QTextCursor cursor(oldDocument); cursor.beginEditBlock(); for (QTextBlock b = oldDocument->firstBlock(); b.isValid(); b = b.next()) { b.layout()->setAdditionalFormats(formats.first()); formats.pop_front(); } cursor.endEditBlock(); } } void Worksheet::rehighlight() { if(m_highlighter) { // highlight every entry WorksheetEntry* entry; for (entry = firstEntry(); entry; entry = entry->next()) { WorksheetTextItem* item = entry->highlightItem(); if (!item) continue; highlightItem(item); m_highlighter->rehighlight(); } entry = currentEntry(); WorksheetTextItem* textitem = entry ? entry->highlightItem() : nullptr; if (textitem && textitem->hasFocus()) highlightItem(textitem); } else { // remove highlighting from entries WorksheetEntry* entry; for (entry = firstEntry(); entry; entry = entry->next()) { WorksheetTextItem* item = entry->highlightItem(); if (!item) continue; QTextCursor cursor(item->document()); cursor.beginEditBlock(); for (QTextBlock b = item->document()->firstBlock(); b.isValid(); b = b.next()) { b.layout()->clearAdditionalFormats(); } cursor.endEditBlock(); } update(); } } void Worksheet::enableHighlighting(bool highlight) { if(highlight) { if(m_highlighter) m_highlighter->deleteLater(); if (!m_readOnly) m_highlighter=session()->syntaxHighlighter(this); else m_highlighter=nullptr; if(!m_highlighter) m_highlighter=new Cantor::DefaultHighlighter(this); connect(m_highlighter, SIGNAL(rulesChanged()), this, SLOT(rehighlight())); }else { if(m_highlighter) m_highlighter->deleteLater(); m_highlighter=nullptr; } rehighlight(); } void Worksheet::enableCompletion(bool enable) { m_completionEnabled=enable; } Cantor::Session* Worksheet::session() { return m_session; } bool Worksheet::isRunning() { return m_session && m_session->status()==Cantor::Session::Running; } bool Worksheet::isReadOnly() { return m_readOnly; } bool Worksheet::showExpressionIds() { return m_showExpressionIds; } bool Worksheet::animationsEnabled() { return m_animationsEnabled; } void Worksheet::enableAnimations(bool enable) { m_animationsEnabled = enable; } void Worksheet::enableExpressionNumbering(bool enable) { m_showExpressionIds=enable; emit updatePrompt(); } QDomDocument Worksheet::toXML(KZip* archive) { QDomDocument doc( QLatin1String("CantorWorksheet") ); QDomElement root=doc.createElement( QLatin1String("Worksheet") ); root.setAttribute(QLatin1String("backend"), (m_session ? m_session->backend()->name(): m_backendName)); doc.appendChild(root); for( WorksheetEntry* entry = firstEntry(); entry; entry = entry->next()) { QDomElement el = entry->toXml(doc, archive); root.appendChild( el ); } return doc; } +QJsonDocument Worksheet::toJupyterJson() +{ + QJsonDocument doc; + QJsonObject root; + + QJsonObject metadata; + + QJsonObject kernalInfo; + kernalInfo.insert(QLatin1String("name"), m_backendName.toLower()); + kernalInfo.insert(QLatin1String("display_name"),m_backendName); + metadata.insert(QLatin1String("kernelspec"), kernalInfo); + + root.insert(QLatin1String("metadata"), metadata); + + root.insert(QLatin1String("nbformat"), 4); + root.insert(QLatin1String("nbformat_minor"), 1); + + QJsonArray cells; + for( WorksheetEntry* entry = firstEntry(); entry; entry = entry->next()) + { + const QJsonValue entryJson = entry->toJupyterJson(); + + if (!entryJson.isNull()) + cells.append(entryJson); + } + root.insert(QLatin1String("cells"), cells); + + doc.setObject(root); + return doc; +} + void Worksheet::save( const QString& filename ) { QFile file(filename); if ( !file.open(QIODevice::WriteOnly) ) { KMessageBox::error( worksheetView(), i18n( "Cannot write file %1." , filename ), i18n( "Error - Cantor" )); return; } save(&file); } QByteArray Worksheet::saveToByteArray() { QBuffer buffer; save(&buffer); return buffer.buffer(); } void Worksheet::save( QIODevice* device) { qDebug()<<"saving to filename"; - KZip zipFile( device ); + switch (m_type) + { + case CantorWorksheet: + { + KZip zipFile( device ); + if ( !zipFile.open(QIODevice::WriteOnly) ) + { + KMessageBox::error( worksheetView(), + i18n( "Cannot write file." ), + i18n( "Error - Cantor" )); + return; + } - if ( !zipFile.open(QIODevice::WriteOnly) ) - { - KMessageBox::error( worksheetView(), - i18n( "Cannot write file." ), - i18n( "Error - Cantor" )); - return; - } + QByteArray content = toXML(&zipFile).toByteArray(); + qDebug()<<"content: "<isWritable()) + { + KMessageBox::error( worksheetView(), + i18n( "Cannot write file." ), + i18n( "Error - Cantor" )); + return; + } - /*zipFile.close();*/ + const QJsonDocument& doc = toJupyterJson(); + device->write(doc.toJson(QJsonDocument::Indented)); + break; + } + } } void Worksheet::savePlain(const QString& filename) { QFile file(filename); if(!file.open(QIODevice::WriteOnly)) { KMessageBox::error(worksheetView(), i18n("Error saving file %1", filename), i18n("Error - Cantor")); return; } QString cmdSep=QLatin1String(";\n"); QString commentStartingSeq = QLatin1String(""); QString commentEndingSeq = QLatin1String(""); if (!m_readOnly) { Cantor::Backend * const backend=session()->backend(); if (backend->extensions().contains(QLatin1String("ScriptExtension"))) { Cantor::ScriptExtension* e=dynamic_cast(backend->extension(QLatin1String(("ScriptExtension")))); cmdSep=e->commandSeparator(); commentStartingSeq = e->commentStartingSequence(); commentEndingSeq = e->commentEndingSequence(); } } else KMessageBox::information(worksheetView(), i18n("In read-only mode Cantor couldn't guarantee, that the export will be valid for %1", m_backendName), i18n("Cantor")); QTextStream stream(&file); for(WorksheetEntry * entry = firstEntry(); entry; entry = entry->next()) { const QString& str=entry->toPlain(cmdSep, commentStartingSeq, commentEndingSeq); if(!str.isEmpty()) stream << str + QLatin1Char('\n'); } file.close(); } void Worksheet::saveLatex(const QString& filename) { qDebug()<<"exporting to Latex: " <) stream << out.replace(QLatin1String("&"), QLatin1String("&")) .replace(QLatin1String(">"), QLatin1String(">")) .replace(QLatin1String("<"), QLatin1String("<")); file.close(); } bool Worksheet::load(const QString& filename ) { QFile file(filename); if (!file.open(QIODevice::ReadOnly)) { KMessageBox::error(worksheetView(), i18n("Couldn't open the file %1", filename), i18n("Cantor")); return false; } bool rc = load(&file); if (rc && !m_readOnly) m_session->setWorksheetPath(filename); return rc; } void Worksheet::load(QByteArray* data) { QBuffer buf(data); load(&buf); } bool Worksheet::load(QIODevice* device) { if (!device->isReadable()) { KMessageBox::error(worksheetView(), i18n("Couldn't open the selected file for reading"), i18n("Cantor")); return false; } KZip archive(device); if (archive.open(QIODevice::ReadOnly)) return loadCantorWorksheet(archive); else { qDebug() <<"not a zip file"; // Go to begin of data, we need read all data in second time device->seek(0); QJsonParseError error; const QJsonDocument& doc = QJsonDocument::fromJson(device->readAll(), &error); if (error.error != QJsonParseError::NoError) { qDebug()<<"not a json file, parsing failed with error: " << error.errorString(); QApplication::restoreOverrideCursor(); KMessageBox::error(worksheetView(), i18n("The selected file is not a valid Cantor or Jupyter project file."), i18n("Cantor")); return false; } else return loadJupyterNotebook(doc); } } bool Worksheet::loadCantorWorksheet(const KZip& archive) { m_type = Type::CantorWorksheet; const KArchiveEntry* contentEntry=archive.directory()->entry(QLatin1String("content.xml")); if (!contentEntry->isFile()) { qDebug()<<"content.xml file not found in the zip archive"; QApplication::restoreOverrideCursor(); KMessageBox::error(worksheetView(), i18n("The selected file is not a valid Cantor project file."), i18n("Cantor")); return false; } const KArchiveFile* content = static_cast(contentEntry); QByteArray data = content->data(); // qDebug()<<"read: "<isEnabled()) { QApplication::restoreOverrideCursor(); KMessageBox::information(worksheetView(), i18n("There are some problems with the %1 backend,\n"\ "please check your configuration or install the needed packages.\n" "You will only be able to view this worksheet.", m_backendName), i18n("Cantor")); m_readOnly = true; } if (m_readOnly) { // TODO: Handle this here? for (QAction* action : m_richTextActionList) action->setEnabled(false); } m_isLoadingFromFile = true; //cleanup the worksheet and all it contains delete m_session; m_session=nullptr; m_loginDone = false; //file can only be loaded in a worksheet that was not eidted/modified yet (s.a. CantorShell::load()) //in this case on the default "first entry" is available -> delete it. if (m_firstEntry) { delete m_firstEntry; m_firstEntry = nullptr; } resetEntryCursor(); if (!m_readOnly) m_session=b->createSession(); qDebug()<<"loading entries"; QDomElement expressionChild = root.firstChildElement(); WorksheetEntry* entry = nullptr; while (!expressionChild.isNull()) { QString tag = expressionChild.tagName(); if (tag == QLatin1String("Expression")) { entry = appendCommandEntry(); entry->setContent(expressionChild, archive); } else if (tag == QLatin1String("Text")) { entry = appendTextEntry(); entry->setContent(expressionChild, archive); } else if (tag == QLatin1String("Markdown")) { entry = appendMarkdownEntry(); entry->setContent(expressionChild, archive); } else if (tag == QLatin1String("Latex")) { entry = appendLatexEntry(); entry->setContent(expressionChild, archive); } else if (tag == QLatin1String("PageBreak")) { entry = appendPageBreakEntry(); entry->setContent(expressionChild, archive); } else if (tag == QLatin1String("Image")) { entry = appendImageEntry(); entry->setContent(expressionChild, archive); } if (m_readOnly && entry) { entry->setAcceptHoverEvents(false); entry = nullptr; } expressionChild = expressionChild.nextSiblingElement(); } if (m_readOnly) clearFocus(); m_isLoadingFromFile = false; //Set the Highlighting, depending on the current state //If the session isn't logged in, use the default enableHighlighting( m_highlighter!=nullptr || Settings::highlightDefault() ); emit loaded(); return true; } bool Worksheet::loadJupyterNotebook(const QJsonDocument& doc) { m_type = Type::JupyterNotebook; static const QLatin1String cellsKey("cells"); static const QLatin1String metadataKey("metadata"); static const QLatin1String nbformatKey("nbformat"); static const QLatin1String nbformatMinorKey("nbformat_minor"); static const QSet notebookScheme = QSet::fromList({cellsKey, metadataKey, nbformatKey, nbformatMinorKey}); bool isNotebook = doc.isObject() && QSet::fromList(doc.object().keys()) == notebookScheme && doc.object().value(cellsKey).isArray() && doc.object().value(metadataKey).isObject() && doc.object().value(nbformatKey).isDouble() && doc.object().value(nbformatMinorKey).isDouble(); if (!isNotebook) { QApplication::restoreOverrideCursor(); showInvalidNotebookSchemeError(); return false; } QJsonObject notebookObject = doc.object(); int nbformatMajor = notebookObject.value(nbformatKey).toInt(); int nbformatMinor = notebookObject.value(nbformatMinorKey).toInt(); if (QT_VERSION_CHECK(nbformatMajor, nbformatMinor, 0) < QT_VERSION_CHECK(4,1,0)) { QApplication::restoreOverrideCursor(); KMessageBox::error(worksheetView(), i18n("Cantor doesn't support import Jupyter notebooks with version lower that 4.1 (detected %1.%2)",nbformatMajor, nbformatMinor), i18n("Cantor")); return false; } const QJsonArray& cells = notebookObject.value(cellsKey).toArray(); const QJsonObject& metadata = notebookObject.value(metadataKey).toObject(); const QJsonObject& kernalspec = metadata.value(QLatin1String("kernelspec")).toObject(); const QJsonValue& name = kernalspec.value(QLatin1String("name")); qDebug() << kernalspec << name; if (kernalspec.isEmpty() || name.type() == QJsonValue::Undefined) { QApplication::restoreOverrideCursor(); showInvalidNotebookSchemeError(); return false; } // Jupyter TODO: Check, that adaptBackendName will correct m_backendName = adaptBackendName(name.toString()); Cantor::Backend* backend = Cantor::Backend::getBackend(m_backendName); if (!backend) { QApplication::restoreOverrideCursor(); KMessageBox::information(worksheetView(), i18n("%1 backend was not found. Editing and executing entries is not possible", m_backendName), i18n("Cantor")); m_readOnly = true; } else m_readOnly = false; if(!m_readOnly && !backend->isEnabled()) { QApplication::restoreOverrideCursor(); KMessageBox::information(worksheetView(), i18n("There are some problems with the %1 backend,\n"\ "please check your configuration or install the needed packages.\n" "You will only be able to view this worksheet.", m_backendName), i18n("Cantor")); m_readOnly = true; } if (m_readOnly) { // Jupyter TODO: Handle this here? Again? for (QAction* action : m_richTextActionList) action->setEnabled(false); } m_isLoadingFromFile = true; if (m_session) delete m_session; m_session = nullptr; m_loginDone = false; if (m_firstEntry) { delete m_firstEntry; m_firstEntry = nullptr; } resetEntryCursor(); if (!m_readOnly) m_session=backend->createSession(); qDebug() << "loading jupyter entries"; static const QLatin1String cellTypeKey("cell_type"); static const QLatin1String sourceKey("source"); // Jupyter TODO: handle error, like no object, no 'cell_type', etc // Maybe forward back to cantor shell and UI message about failed - WorksheetEntry* worksheetEntry = nullptr; + WorksheetEntry* entry = nullptr; for (QJsonArray::const_iterator iter = cells.begin(); iter != cells.end(); iter++) { bool isCellObject = iter->isObject(); if (!isCellObject) continue; const QJsonObject& cell = iter->toObject(); bool isJupyterCell = cell.contains(cellTypeKey) && cell.contains(metadataKey) && cell.contains(sourceKey); if (!isJupyterCell) continue; QString cellType = cell.value(cellTypeKey).toString(); if (cellType == QLatin1String("code")) { // Jupyter TODO output of runned cells - CommandEntry* entry = static_cast(appendCommandEntry()); + entry = appendCommandEntry(); entry->setContentFromJupyter(cell); - worksheetEntry = entry; } else if (cellType == QLatin1String("markdown")) { // Jupyter TODO: improve finding $$...$$, current realization don't support escaping - MarkdownEntry* entry = static_cast(appendMarkdownEntry()); + entry = appendMarkdownEntry(); entry->setContentFromJupyter(cell); entry->evaluate(WorksheetEntry::EvaluationOption::DoNothing); - worksheetEntry = entry; } else if (cellType == QLatin1String("raw")) { // Jupyter TODO: handle this type, but how? // Text entries will be fine, but will be problems with saving user .cws as .ipynb // Invatigate } - if (m_readOnly && worksheetEntry) + if (m_readOnly && entry) { - worksheetEntry->setAcceptHoverEvents(false); - worksheetEntry = nullptr; + entry->setAcceptHoverEvents(false); + entry = nullptr; } } if (m_readOnly) clearFocus(); m_isLoadingFromFile = false; enableHighlighting( m_highlighter!=nullptr || Settings::highlightDefault() ); emit loaded(); return true; } QString Worksheet::adaptBackendName(const QString& jupyterBackendName) { QString backendName = jupyterBackendName; if (jupyterBackendName == QLatin1String("sagemath")) backendName = QLatin1String("sage"); else if (jupyterBackendName.startsWith(QLatin1String("julia"))) backendName = QLatin1String("julia"); return backendName; } void Worksheet::showInvalidNotebookSchemeError() { KMessageBox::error(worksheetView(), i18n("The file is not a valid Jupyter notebook file."), i18n("Cantor")); } void Worksheet::gotResult(Cantor::Expression* expr) { if(expr==nullptr) expr=qobject_cast(sender()); if(expr==nullptr) return; //We're only interested in help results, others are handled by the WorksheetEntry for (auto* result : expr->results()) { if(result && result->type()==Cantor::HelpResult::Type) { QString help = result->toHtml(); //Do some basic LaTeX replacing help.replace(QRegExp(QLatin1String("\\\\code\\{([^\\}]*)\\}")), QLatin1String("\\1")); help.replace(QRegExp(QLatin1String("\\$([^\\$])\\$")), QLatin1String("\\1")); emit showHelp(help); //TODO: break after the first help result found, not clear yet how to handle multiple requests for help within one single command (e.g. ??ev;??int). break; } } } void Worksheet::removeCurrentEntry() { qDebug()<<"removing current entry"; WorksheetEntry* entry=currentEntry(); if(!entry) return; // In case we just removed this if (entry->isAncestorOf(m_lastFocusedTextItem)) m_lastFocusedTextItem = nullptr; entry->startRemoving(); } EpsRenderer* Worksheet::epsRenderer() { return &m_epsRenderer; } QMenu* Worksheet::createContextMenu() { QMenu *menu = new QMenu(worksheetView()); connect(menu, SIGNAL(aboutToHide()), menu, SLOT(deleteLater())); return menu; } void Worksheet::populateMenu(QMenu *menu, QPointF pos) { WorksheetEntry* entry = entryAt(pos); if (entry && !entry->isAncestorOf(m_lastFocusedTextItem)) { WorksheetTextItem* item = qgraphicsitem_cast(itemAt(pos, QTransform())); if (item && item->isEditable()) m_lastFocusedTextItem = item; } if (!isRunning()) menu->addAction(QIcon::fromTheme(QLatin1String("system-run")), i18n("Evaluate Worksheet"), this, SLOT(evaluate()), 0); else menu->addAction(QIcon::fromTheme(QLatin1String("process-stop")), i18n("Interrupt"), this, SLOT(interrupt()), 0); menu->addSeparator(); if (entry) { QMenu* insert = new QMenu(menu); QMenu* insertBefore = new QMenu(menu); insert->addAction(QIcon::fromTheme(QLatin1String("run-build")), i18n("Command Entry"), entry, SLOT(insertCommandEntry())); insert->addAction(QIcon::fromTheme(QLatin1String("draw-text")), i18n("Text Entry"), entry, SLOT(insertTextEntry())); #ifdef Discount_FOUND insert->addAction(QIcon::fromTheme(QLatin1String("text-x-markdown")), i18n("Markdown Entry"), entry, SLOT(insertMarkdownEntry())); #endif #ifdef WITH_EPS insert->addAction(QIcon::fromTheme(QLatin1String("text-x-tex")), i18n("LaTeX Entry"), entry, SLOT(insertLatexEntry())); #endif insert->addAction(QIcon::fromTheme(QLatin1String("image-x-generic")), i18n("Image"), entry, SLOT(insertImageEntry())); insert->addAction(QIcon::fromTheme(QLatin1String("go-next-view-page")), i18n("Page Break"), entry, SLOT(insertPageBreakEntry())); insertBefore->addAction(QIcon::fromTheme(QLatin1String("run-build")), i18n("Command Entry"), entry, SLOT(insertCommandEntryBefore())); insertBefore->addAction(QIcon::fromTheme(QLatin1String("draw-text")), i18n("Text Entry"), entry, SLOT(insertTextEntryBefore())); #ifdef Discount_FOUND insertBefore->addAction(QIcon::fromTheme(QLatin1String("text-x-markdown")), i18n("Markdown Entry"), entry, SLOT(insertMarkdownEntryBefore())); #endif #ifdef WITH_EPS insertBefore->addAction(QIcon::fromTheme(QLatin1String("text-x-tex")), i18n("LaTeX Entry"), entry, SLOT(insertLatexEntryBefore())); #endif insertBefore->addAction(QIcon::fromTheme(QLatin1String("image-x-generic")), i18n("Image"), entry, SLOT(insertImageEntryBefore())); insertBefore->addAction(QIcon::fromTheme(QLatin1String("go-next-view-page")), i18n("Page Break"), entry, SLOT(insertPageBreakEntryBefore())); insert->setTitle(i18n("Insert Entry After")); insert->setIcon(QIcon::fromTheme(QLatin1String("edit-table-insert-row-below"))); insertBefore->setTitle(i18n("Insert Entry Before")); insertBefore->setIcon(QIcon::fromTheme(QLatin1String("edit-table-insert-row-above"))); menu->addMenu(insert); menu->addMenu(insertBefore); } else { menu->addAction(QIcon::fromTheme(QLatin1String("run-build")), i18n("Insert Command Entry"), this, SLOT(appendCommandEntry())); menu->addAction(QIcon::fromTheme(QLatin1String("draw-text")), i18n("Insert Text Entry"), this, SLOT(appendTextEntry())); #ifdef Discount_FOUND menu->addAction(QIcon::fromTheme(QLatin1String("text-x-markdown")), i18n("Insert Markdown Entry"), this, SLOT(appendMarkdownEntry())); #endif #ifdef WITH_EPS menu->addAction(QIcon::fromTheme(QLatin1String("text-x-tex")), i18n("Insert LaTeX Entry"), this, SLOT(appendLatexEntry())); #endif menu->addAction(QIcon::fromTheme(QLatin1String("image-x-generic")), i18n("Insert Image"), this, SLOT(appendImageEntry())); menu->addAction(QIcon::fromTheme(QLatin1String("go-next-view-page")), i18n("Insert Page Break"), this, SLOT(appendPageBreakEntry())); } } void Worksheet::contextMenuEvent(QGraphicsSceneContextMenuEvent *event) { if (m_readOnly) return; // forward the event to the items QGraphicsScene::contextMenuEvent(event); if (!event->isAccepted()) { event->accept(); QMenu *menu = createContextMenu(); populateMenu(menu, event->scenePos()); menu->popup(event->screenPos()); } } void Worksheet::mousePressEvent(QGraphicsSceneMouseEvent* event) { QGraphicsScene::mousePressEvent(event); /* if (event->button() == Qt::LeftButton && !focusItem() && lastEntry() && event->scenePos().y() > lastEntry()->y() + lastEntry()->size().height()) lastEntry()->focusEntry(WorksheetTextItem::BottomRight); */ if (!m_readOnly) updateEntryCursor(event); } void Worksheet::keyPressEvent(QKeyEvent *keyEvent) { if (m_readOnly) return; // If we choose entry by entry cursor and press text button (not modifiers, for example, like Control) if ((m_choosenCursorEntry || m_isCursorEntryAfterLastEntry) && !keyEvent->text().isEmpty()) addEntryFromEntryCursor(); QGraphicsScene::keyPressEvent(keyEvent); } void Worksheet::createActions(KActionCollection* collection) { // Mostly copied from KRichTextWidget::createActions(KActionCollection*) // It would be great if this wasn't necessary. // Text color QAction * action; /* This is "format-stroke-color" in KRichTextWidget */ action = new QAction(QIcon::fromTheme(QLatin1String("format-text-color")), i18nc("@action", "Text &Color..."), collection); action->setIconText(i18nc("@label text color", "Color")); action->setPriority(QAction::LowPriority); m_richTextActionList.append(action); collection->addAction(QLatin1String("format_text_foreground_color"), action); connect(action, SIGNAL(triggered()), this, SLOT(setTextForegroundColor())); // Text color action = new QAction(QIcon::fromTheme(QLatin1String("format-fill-color")), i18nc("@action", "Text &Highlight..."), collection); action->setPriority(QAction::LowPriority); m_richTextActionList.append(action); collection->addAction(QLatin1String("format_text_background_color"), action); connect(action, SIGNAL(triggered()), this, SLOT(setTextBackgroundColor())); // Font Family m_fontAction = new KFontAction(i18nc("@action", "&Font"), collection); m_richTextActionList.append(m_fontAction); collection->addAction(QLatin1String("format_font_family"), m_fontAction); connect(m_fontAction, SIGNAL(triggered(QString)), this, SLOT(setFontFamily(QString))); // Font Size m_fontSizeAction = new KFontSizeAction(i18nc("@action", "Font &Size"), collection); m_richTextActionList.append(m_fontSizeAction); collection->addAction(QLatin1String("format_font_size"), m_fontSizeAction); connect(m_fontSizeAction, SIGNAL(fontSizeChanged(int)), this, SLOT(setFontSize(int))); // Bold m_boldAction = new KToggleAction(QIcon::fromTheme(QLatin1String("format-text-bold")), i18nc("@action boldify selected text", "&Bold"), collection); m_boldAction->setPriority(QAction::LowPriority); QFont bold; bold.setBold(true); m_boldAction->setFont(bold); m_richTextActionList.append(m_boldAction); collection->addAction(QLatin1String("format_text_bold"), m_boldAction); collection->setDefaultShortcut(m_boldAction, Qt::CTRL + Qt::Key_B); connect(m_boldAction, SIGNAL(triggered(bool)), this, SLOT(setTextBold(bool))); // Italic m_italicAction = new KToggleAction(QIcon::fromTheme(QLatin1String("format-text-italic")), i18nc("@action italicize selected text", "&Italic"), collection); m_italicAction->setPriority(QAction::LowPriority); QFont italic; italic.setItalic(true); m_italicAction->setFont(italic); m_richTextActionList.append(m_italicAction); collection->addAction(QLatin1String("format_text_italic"), m_italicAction); collection->setDefaultShortcut(m_italicAction, Qt::CTRL + Qt::Key_I); connect(m_italicAction, SIGNAL(triggered(bool)), this, SLOT(setTextItalic(bool))); // Underline m_underlineAction = new KToggleAction(QIcon::fromTheme(QLatin1String("format-text-underline")), i18nc("@action underline selected text", "&Underline"), collection); m_underlineAction->setPriority(QAction::LowPriority); QFont underline; underline.setUnderline(true); m_underlineAction->setFont(underline); m_richTextActionList.append(m_underlineAction); collection->addAction(QLatin1String("format_text_underline"), m_underlineAction); collection->setDefaultShortcut(m_underlineAction, Qt::CTRL + Qt::Key_U); connect(m_underlineAction, SIGNAL(triggered(bool)), this, SLOT(setTextUnderline(bool))); // Strike m_strikeOutAction = new KToggleAction(QIcon::fromTheme(QLatin1String("format-text-strikethrough")), i18nc("@action", "&Strike Out"), collection); m_strikeOutAction->setPriority(QAction::LowPriority); m_richTextActionList.append(m_strikeOutAction); collection->addAction(QLatin1String("format_text_strikeout"), m_strikeOutAction); collection->setDefaultShortcut(m_strikeOutAction, Qt::CTRL + Qt::Key_L); connect(m_strikeOutAction, SIGNAL(triggered(bool)), this, SLOT(setTextStrikeOut(bool))); // Alignment QActionGroup *alignmentGroup = new QActionGroup(this); // Align left m_alignLeftAction = new KToggleAction(QIcon::fromTheme(QLatin1String("format-justify-left")), i18nc("@action", "Align &Left"), collection); m_alignLeftAction->setPriority(QAction::LowPriority); m_alignLeftAction->setIconText(i18nc("@label left justify", "Left")); m_richTextActionList.append(m_alignLeftAction); collection->addAction(QLatin1String("format_align_left"), m_alignLeftAction); connect(m_alignLeftAction, SIGNAL(triggered()), this, SLOT(setAlignLeft())); alignmentGroup->addAction(m_alignLeftAction); // Align center m_alignCenterAction = new KToggleAction(QIcon::fromTheme(QLatin1String("format-justify-center")), i18nc("@action", "Align &Center"), collection); m_alignCenterAction->setPriority(QAction::LowPriority); m_alignCenterAction->setIconText(i18nc("@label center justify", "Center")); m_richTextActionList.append(m_alignCenterAction); collection->addAction(QLatin1String("format_align_center"), m_alignCenterAction); connect(m_alignCenterAction, SIGNAL(triggered()), this, SLOT(setAlignCenter())); alignmentGroup->addAction(m_alignCenterAction); // Align right m_alignRightAction = new KToggleAction(QIcon::fromTheme(QLatin1String("format-justify-right")), i18nc("@action", "Align &Right"), collection); m_alignRightAction->setPriority(QAction::LowPriority); m_alignRightAction->setIconText(i18nc("@label right justify", "Right")); m_richTextActionList.append(m_alignRightAction); collection->addAction(QLatin1String("format_align_right"), m_alignRightAction); connect(m_alignRightAction, SIGNAL(triggered()), this, SLOT(setAlignRight())); alignmentGroup->addAction(m_alignRightAction); // Align justify m_alignJustifyAction = new KToggleAction(QIcon::fromTheme(QLatin1String("format-justify-fill")), i18nc("@action", "&Justify"), collection); m_alignJustifyAction->setPriority(QAction::LowPriority); m_alignJustifyAction->setIconText(i18nc("@label justify fill", "Justify")); m_richTextActionList.append(m_alignJustifyAction); collection->addAction(QLatin1String("format_align_justify"), m_alignJustifyAction); connect(m_alignJustifyAction, SIGNAL(triggered()), this, SLOT(setAlignJustify())); alignmentGroup->addAction(m_alignJustifyAction); /* // List style KSelectAction* selAction; selAction = new KSelectAction(QIcon::fromTheme("format-list-unordered"), i18nc("@title:menu", "List Style"), collection); QStringList listStyles; listStyles << i18nc("@item:inmenu no list style", "None") << i18nc("@item:inmenu disc list style", "Disc") << i18nc("@item:inmenu circle list style", "Circle") << i18nc("@item:inmenu square list style", "Square") << i18nc("@item:inmenu numbered lists", "123") << i18nc("@item:inmenu lowercase abc lists", "abc") << i18nc("@item:inmenu uppercase abc lists", "ABC"); selAction->setItems(listStyles); selAction->setCurrentItem(0); action = selAction; m_richTextActionList.append(action); collection->addAction("format_list_style", action); connect(action, SIGNAL(triggered(int)), this, SLOT(_k_setListStyle(int))); connect(action, SIGNAL(triggered()), this, SLOT(_k_updateMiscActions())); // Indent action = new QAction(QIcon::fromTheme("format-indent-more"), i18nc("@action", "Increase Indent"), collection); action->setPriority(QAction::LowPriority); m_richTextActionList.append(action); collection->addAction("format_list_indent_more", action); connect(action, SIGNAL(triggered()), this, SLOT(indentListMore())); connect(action, SIGNAL(triggered()), this, SLOT(_k_updateMiscActions())); // Dedent action = new QAction(QIcon::fromTheme("format-indent-less"), i18nc("@action", "Decrease Indent"), collection); action->setPriority(QAction::LowPriority); m_richTextActionList.append(action); collection->addAction("format_list_indent_less", action); connect(action, SIGNAL(triggered()), this, SLOT(indentListLess())); connect(action, SIGNAL(triggered()), this, SLOT(_k_updateMiscActions())); */ } WorksheetTextItem* Worksheet::lastFocusedTextItem() { return m_lastFocusedTextItem; } void Worksheet::updateFocusedTextItem(WorksheetTextItem* newItem) { // No need update and emit signals about editing actions in readonly // So support only copy action and reset selection if (m_readOnly) { if (m_lastFocusedTextItem && m_lastFocusedTextItem != newItem) { disconnect(this, SIGNAL(copy()), m_lastFocusedTextItem, SLOT(copy())); m_lastFocusedTextItem->clearSelection(); } if (newItem && m_lastFocusedTextItem != newItem) { connect(this, SIGNAL(copy()), newItem, SLOT(copy())); emit copyAvailable(newItem->isCopyAvailable()); } else if (!newItem) { emit copyAvailable(false); } m_lastFocusedTextItem = newItem; return; } if (m_lastFocusedTextItem && m_lastFocusedTextItem != newItem) { disconnect(m_lastFocusedTextItem, SIGNAL(undoAvailable(bool)), this, SIGNAL(undoAvailable(bool))); disconnect(m_lastFocusedTextItem, SIGNAL(redoAvailable(bool)), this, SIGNAL(redoAvailable(bool))); disconnect(this, SIGNAL(undo()), m_lastFocusedTextItem, SLOT(undo())); disconnect(this, SIGNAL(redo()), m_lastFocusedTextItem, SLOT(redo())); disconnect(m_lastFocusedTextItem, SIGNAL(cutAvailable(bool)), this, SIGNAL(cutAvailable(bool))); disconnect(m_lastFocusedTextItem, SIGNAL(copyAvailable(bool)), this, SIGNAL(copyAvailable(bool))); disconnect(m_lastFocusedTextItem, SIGNAL(pasteAvailable(bool)), this, SIGNAL(pasteAvailable(bool))); disconnect(this, SIGNAL(cut()), m_lastFocusedTextItem, SLOT(cut())); disconnect(this, SIGNAL(copy()), m_lastFocusedTextItem, SLOT(copy())); m_lastFocusedTextItem->clearSelection(); } if (newItem && m_lastFocusedTextItem != newItem) { setAcceptRichText(newItem->richTextEnabled()); emit undoAvailable(newItem->isUndoAvailable()); emit redoAvailable(newItem->isRedoAvailable()); connect(newItem, SIGNAL(undoAvailable(bool)), this, SIGNAL(undoAvailable(bool))); connect(newItem, SIGNAL(redoAvailable(bool)), this, SIGNAL(redoAvailable(bool))); connect(this, SIGNAL(undo()), newItem, SLOT(undo())); connect(this, SIGNAL(redo()), newItem, SLOT(redo())); emit cutAvailable(newItem->isCutAvailable()); emit copyAvailable(newItem->isCopyAvailable()); emit pasteAvailable(newItem->isPasteAvailable()); connect(newItem, SIGNAL(cutAvailable(bool)), this, SIGNAL(cutAvailable(bool))); connect(newItem, SIGNAL(copyAvailable(bool)), this, SIGNAL(copyAvailable(bool))); connect(newItem, SIGNAL(pasteAvailable(bool)), this, SIGNAL(pasteAvailable(bool))); connect(this, SIGNAL(cut()), newItem, SLOT(cut())); connect(this, SIGNAL(copy()), newItem, SLOT(copy())); } else if (!newItem) { emit undoAvailable(false); emit redoAvailable(false); emit cutAvailable(false); emit copyAvailable(false); emit pasteAvailable(false); } m_lastFocusedTextItem = newItem; } /*! * handles the paste action triggered in cantor_part. * Pastes into the last focused text item. * In case the "new entry"-cursor is currently shown, * a new entry is created first which the content will be pasted into. */ void Worksheet::paste() { if (m_choosenCursorEntry || m_isCursorEntryAfterLastEntry) addEntryFromEntryCursor(); m_lastFocusedTextItem->paste(); } void Worksheet::setRichTextInformation(const RichTextInfo& info) { m_boldAction->setChecked(info.bold); m_italicAction->setChecked(info.italic); m_underlineAction->setChecked(info.underline); m_strikeOutAction->setChecked(info.strikeOut); m_fontAction->setFont(info.font); if (info.fontSize > 0) m_fontSizeAction->setFontSize(info.fontSize); if (info.align & Qt::AlignLeft) m_alignLeftAction->setChecked(true); else if (info.align & Qt::AlignCenter) m_alignCenterAction->setChecked(true); else if (info.align & Qt::AlignRight) m_alignRightAction->setChecked(true); else if (info.align & Qt::AlignJustify) m_alignJustifyAction->setChecked(true); } void Worksheet::setAcceptRichText(bool b) { if (!m_readOnly) for(QAction * action : m_richTextActionList) action->setEnabled(b); } WorksheetTextItem* Worksheet::currentTextItem() { QGraphicsItem* item = focusItem(); if (!item) item = m_lastFocusedTextItem; while (item && item->type() != WorksheetTextItem::Type) item = item->parentItem(); return qgraphicsitem_cast(item); } void Worksheet::setTextForegroundColor() { WorksheetTextItem* item = currentTextItem(); if (item) item->setTextForegroundColor(); } void Worksheet::setTextBackgroundColor() { WorksheetTextItem* item = currentTextItem(); if (item) item->setTextBackgroundColor(); } void Worksheet::setTextBold(bool b) { WorksheetTextItem* item = currentTextItem(); if (item) item->setTextBold(b); } void Worksheet::setTextItalic(bool b) { WorksheetTextItem* item = currentTextItem(); if (item) item->setTextItalic(b); } void Worksheet::setTextUnderline(bool b) { WorksheetTextItem* item = currentTextItem(); if (item) item->setTextUnderline(b); } void Worksheet::setTextStrikeOut(bool b) { WorksheetTextItem* item = currentTextItem(); if (item) item->setTextStrikeOut(b); } void Worksheet::setAlignLeft() { WorksheetTextItem* item = currentTextItem(); if (item) item->setAlignment(Qt::AlignLeft); } void Worksheet::setAlignRight() { WorksheetTextItem* item = currentTextItem(); if (item) item->setAlignment(Qt::AlignRight); } void Worksheet::setAlignCenter() { WorksheetTextItem* item = currentTextItem(); if (item) item->setAlignment(Qt::AlignCenter); } void Worksheet::setAlignJustify() { WorksheetTextItem* item = currentTextItem(); if (item) item->setAlignment(Qt::AlignJustify); } void Worksheet::setFontFamily(const QString& font) { WorksheetTextItem* item = currentTextItem(); if (item) item->setFontFamily(font); } void Worksheet::setFontSize(int size) { WorksheetTextItem* item = currentTextItem(); if (item) item->setFontSize(size); } bool Worksheet::isShortcut(const QKeySequence& sequence) { return m_shortcuts.contains(sequence); } void Worksheet::registerShortcut(QAction* action) { for (auto& shortcut : action->shortcuts()) m_shortcuts.insert(shortcut, action); connect(action, SIGNAL(changed()), this, SLOT(updateShortcut())); } void Worksheet::updateShortcut() { QAction* action = qobject_cast(sender()); if (!action) return; // delete the old shortcuts of this action QList shortcuts = m_shortcuts.keys(action); for (auto& shortcut : shortcuts) m_shortcuts.remove(shortcut); // add the new shortcuts for (auto& shortcut : action->shortcuts()) m_shortcuts.insert(shortcut, action); } void Worksheet::dragEnterEvent(QGraphicsSceneDragDropEvent* event) { qDebug() << "enter"; if (m_dragEntry) event->accept(); else QGraphicsScene::dragEnterEvent(event); } void Worksheet::dragLeaveEvent(QGraphicsSceneDragDropEvent* event) { if (!m_dragEntry) { QGraphicsScene::dragLeaveEvent(event); return; } qDebug() << "leave"; event->accept(); if (m_placeholderEntry) { m_placeholderEntry->startRemoving(); m_placeholderEntry = nullptr; } } void Worksheet::dragMoveEvent(QGraphicsSceneDragDropEvent* event) { if (!m_dragEntry) { QGraphicsScene::dragMoveEvent(event); return; } QPointF pos = event->scenePos(); WorksheetEntry* entry = entryAt(pos); WorksheetEntry* prev = nullptr; WorksheetEntry* next = nullptr; if (entry) { if (pos.y() < entry->y() + entry->size().height()/2) { prev = entry->previous(); next = entry; } else if (pos.y() >= entry->y() + entry->size().height()/2) { prev = entry; next = entry->next(); } } else { WorksheetEntry* last = lastEntry(); if (last && pos.y() > last->y() + last->size().height()) { prev = last; next = nullptr; } } if (prev || next) { PlaceHolderEntry* oldPlaceHolder = m_placeholderEntry; if (prev && prev->type() == PlaceHolderEntry::Type && (!prev->aboutToBeRemoved() || prev->stopRemoving())) { m_placeholderEntry = qgraphicsitem_cast(prev); m_placeholderEntry->changeSize(m_dragEntry->size()); } else if (next && next->type() == PlaceHolderEntry::Type && (!next->aboutToBeRemoved() || next->stopRemoving())) { m_placeholderEntry = qgraphicsitem_cast(next); m_placeholderEntry->changeSize(m_dragEntry->size()); } else { m_placeholderEntry = new PlaceHolderEntry(this, QSizeF(0,0)); m_placeholderEntry->setPrevious(prev); m_placeholderEntry->setNext(next); if (prev) prev->setNext(m_placeholderEntry); else setFirstEntry(m_placeholderEntry); if (next) next->setPrevious(m_placeholderEntry); else setLastEntry(m_placeholderEntry); m_placeholderEntry->changeSize(m_dragEntry->size()); } if (oldPlaceHolder && oldPlaceHolder != m_placeholderEntry) oldPlaceHolder->startRemoving(); updateLayout(); } const QPoint viewPos = worksheetView()->mapFromScene(pos); const int viewHeight = worksheetView()->viewport()->height(); if ((viewPos.y() < 10 || viewPos.y() > viewHeight - 10) && !m_dragScrollTimer) { m_dragScrollTimer = new QTimer(this); m_dragScrollTimer->setSingleShot(true); m_dragScrollTimer->setInterval(100); connect(m_dragScrollTimer, SIGNAL(timeout()), this, SLOT(updateDragScrollTimer())); m_dragScrollTimer->start(); } event->accept(); } void Worksheet::dropEvent(QGraphicsSceneDragDropEvent* event) { if (!m_dragEntry) QGraphicsScene::dropEvent(event); event->accept(); } void Worksheet::updateDragScrollTimer() { if (!m_dragScrollTimer) return; const QPoint viewPos = worksheetView()->viewCursorPos(); const QWidget* viewport = worksheetView()->viewport(); const int viewHeight = viewport->height(); if (!m_dragEntry || !(viewport->rect().contains(viewPos)) || (viewPos.y() >= 10 && viewPos.y() <= viewHeight - 10)) { delete m_dragScrollTimer; m_dragScrollTimer = nullptr; return; } if (viewPos.y() < 10) worksheetView()->scrollBy(-10*(10 - viewPos.y())); else worksheetView()->scrollBy(10*(viewHeight - viewPos.y())); m_dragScrollTimer->start(); } void Worksheet::updateEntryCursor(QGraphicsSceneMouseEvent* event) { // determine the worksheet entry near which the entry cursor will be shown resetEntryCursor(); if (event->button() == Qt::LeftButton && !focusItem()) { const qreal y = event->scenePos().y(); for (WorksheetEntry* entry = firstEntry(); entry; entry = entry->next()) { if (entry == firstEntry() && y < entry->y() ) { m_choosenCursorEntry = firstEntry(); break; } else if (entry->y() < y && (entry->next() && y < entry->next()->y())) { m_choosenCursorEntry = entry->next(); break; } else if (entry->y() < y && entry == lastEntry()) { m_isCursorEntryAfterLastEntry = true; break; } } } if (m_choosenCursorEntry || m_isCursorEntryAfterLastEntry) drawEntryCursor(); } void Worksheet::addEntryFromEntryCursor() { qDebug() << "Add new entry from entry cursor"; if (m_isCursorEntryAfterLastEntry) insertCommandEntry(lastEntry()); else insertCommandEntryBefore(m_choosenCursorEntry); resetEntryCursor(); } void Worksheet::animateEntryCursor() { if ((m_choosenCursorEntry || m_isCursorEntryAfterLastEntry) && m_entryCursorItem) m_entryCursorItem->setVisible(!m_entryCursorItem->isVisible()); } void Worksheet::resetEntryCursor() { m_choosenCursorEntry = nullptr; m_isCursorEntryAfterLastEntry = false; m_entryCursorItem->hide(); } void Worksheet::drawEntryCursor() { if (m_entryCursorItem && (m_choosenCursorEntry || (m_isCursorEntryAfterLastEntry && lastEntry()))) { qreal x; qreal y; if (m_isCursorEntryAfterLastEntry) { x = lastEntry()->x(); y = lastEntry()->y() + lastEntry()->size().height() - (EntryCursorWidth - 1); } else { x = m_choosenCursorEntry->x(); y = m_choosenCursorEntry->y(); } m_entryCursorItem->setLine(x,y,x+EntryCursorLength,y); m_entryCursorItem->show(); } } + +void Worksheet::setType(Worksheet::Type type) +{ + m_type = type; +} + +Worksheet::Type Worksheet::type() const +{ + return m_type; +} diff --git a/src/worksheet.h b/src/worksheet.h index f7bb6db0..5d56b4be 100644 --- a/src/worksheet.h +++ b/src/worksheet.h @@ -1,315 +1,320 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2009 Alexander Rieder Copyright (C) 2012 Martin Kuettler */ #ifndef WORKSHEET_H #define WORKSHEET_H #include #include #include #include #include #include #include #include "worksheetview.h" #include "epsrenderer.h" #include "worksheetcursor.h" namespace Cantor { class Backend; class Session; class Expression; } class WorksheetEntry; class PlaceHolderEntry; class WorksheetTextItem; class QAction; class QDrag; class QPrinter; class KActionCollection; class KToggleAction; class KFontAction; class KFontSizeAction; class Worksheet : public QGraphicsScene { Q_OBJECT public: enum Type { CantorWorksheet, JupyterNotebook }; Worksheet(Cantor::Backend* backend, QWidget* parent); ~Worksheet() override; Cantor::Session* session(); void loginToSession(); bool isRunning(); bool isReadOnly(); bool showExpressionIds(); bool animationsEnabled(); bool isPrinting(); void setViewSize(qreal w, qreal h, qreal s, bool forceUpdate = false); WorksheetView* worksheetView(); void makeVisible(WorksheetEntry*); void makeVisible(const WorksheetCursor&); void setModified(); void startDrag(WorksheetEntry* entry, QDrag* drag); void createActions(KActionCollection*); QMenu* createContextMenu(); void populateMenu(QMenu* menu, QPointF pos); EpsRenderer* epsRenderer(); bool isEmpty(); bool isLoadingFromFile(); WorksheetEntry* currentEntry(); WorksheetEntry* firstEntry(); WorksheetEntry* lastEntry(); WorksheetTextItem* currentTextItem(); WorksheetTextItem* lastFocusedTextItem(); WorksheetCursor worksheetCursor(); void setWorksheetCursor(const WorksheetCursor&); // For WorksheetEntry::startDrag void resetEntryCursor(); void addProtrusion(qreal width); void updateProtrusion(qreal oldWidth, qreal newWidth); void removeProtrusion(qreal width); bool isShortcut(const QKeySequence&); + void setType(Worksheet::Type type); + Worksheet::Type type() const; + // richtext struct RichTextInfo { bool bold; bool italic; bool underline; bool strikeOut; QString font; qreal fontSize; Qt::Alignment align; }; public Q_SLOTS: WorksheetEntry* appendCommandEntry(); void appendCommandEntry(const QString& text); WorksheetEntry* appendTextEntry(); WorksheetEntry* appendMarkdownEntry(); WorksheetEntry* appendImageEntry(); WorksheetEntry* appendPageBreakEntry(); WorksheetEntry* appendLatexEntry(); WorksheetEntry* insertCommandEntry(WorksheetEntry* current = nullptr); void insertCommandEntry(const QString& text); WorksheetEntry* insertTextEntry(WorksheetEntry* current = nullptr); WorksheetEntry* insertMarkdownEntry(WorksheetEntry* current = nullptr); WorksheetEntry* insertImageEntry(WorksheetEntry* current = nullptr); WorksheetEntry* insertPageBreakEntry(WorksheetEntry* current = nullptr); WorksheetEntry* insertLatexEntry(WorksheetEntry* current = nullptr); WorksheetEntry* insertCommandEntryBefore(WorksheetEntry* current = nullptr); WorksheetEntry* insertTextEntryBefore(WorksheetEntry* current = nullptr); WorksheetEntry* insertMarkdownEntryBefore(WorksheetEntry* current = nullptr); WorksheetEntry* insertImageEntryBefore(WorksheetEntry* current = nullptr); WorksheetEntry* insertPageBreakEntryBefore(WorksheetEntry* current = nullptr); WorksheetEntry* insertLatexEntryBefore(WorksheetEntry* current = nullptr); void updateLayout(); void updateEntrySize(WorksheetEntry*); void print(QPrinter*); void paste(); void focusEntry(WorksheetEntry*); void evaluate(); void evaluateCurrentEntry(); void interrupt(); void interruptCurrentEntryEvaluation(); bool completionEnabled(); //void showCompletion(); void highlightItem(WorksheetTextItem*); void rehighlight(); void enableHighlighting(bool); void enableCompletion(bool); void enableExpressionNumbering(bool); void enableAnimations(bool); QDomDocument toXML(KZip* archive = nullptr); void save(const QString& filename); void save(QIODevice*); QByteArray saveToByteArray(); void savePlain(const QString& filename); void saveLatex(const QString& filename); bool load(QIODevice*); void load(QByteArray* data); bool load(const QString& filename); void gotResult(Cantor::Expression* expr = nullptr); void removeCurrentEntry(); void setFirstEntry(WorksheetEntry*); void setLastEntry(WorksheetEntry*); void invalidateFirstEntry(); void invalidateLastEntry(); void updateFocusedTextItem(WorksheetTextItem*); void updateDragScrollTimer(); void registerShortcut(QAction*); void updateShortcut(); // richtext void setRichTextInformation(const Worksheet::RichTextInfo&); void setAcceptRichText(bool b); void setTextForegroundColor(); void setTextBackgroundColor(); void setTextBold(bool b); void setTextItalic(bool b); void setTextUnderline(bool b); void setTextStrikeOut(bool b); void setAlignLeft(); void setAlignRight(); void setAlignCenter(); void setAlignJustify(); void setFontFamily(const QString&); void setFontSize(int size); Q_SIGNALS: void modified(); void loaded(); void showHelp(const QString&); void updatePrompt(); void undoAvailable(bool); void redoAvailable(bool); void undo(); void redo(); void cutAvailable(bool); void copyAvailable(bool); void pasteAvailable(bool); void cut(); void copy(); protected: void contextMenuEvent(QGraphicsSceneContextMenuEvent*) override; void mousePressEvent(QGraphicsSceneMouseEvent*) override; void dragEnterEvent(QGraphicsSceneDragDropEvent*) override; void dragLeaveEvent(QGraphicsSceneDragDropEvent*) override; void dragMoveEvent(QGraphicsSceneDragDropEvent*) override; void dropEvent(QGraphicsSceneDragDropEvent*) override; void keyPressEvent(QKeyEvent*) override; + QJsonDocument toJupyterJson(); + private Q_SLOTS: void showCompletion(); //void checkEntriesForSanity(); WorksheetEntry* appendEntry(int type); WorksheetEntry* insertEntry(int type, WorksheetEntry* current = nullptr); WorksheetEntry* insertEntryBefore(int type, WorksheetEntry* current = nullptr); void animateEntryCursor(); private: WorksheetEntry* entryAt(qreal x, qreal y); WorksheetEntry* entryAt(QPointF p); WorksheetEntry* entryAt(int row); void updateEntryCursor(QGraphicsSceneMouseEvent*); void addEntryFromEntryCursor(); void drawEntryCursor(); int entryCount(); bool loadCantorWorksheet(const KZip& archive); bool loadJupyterNotebook(const QJsonDocument& doc); QString adaptBackendName(const QString& jupyterBackendName); void showInvalidNotebookSchemeError(); private: static const double LeftMargin; static const double RightMargin; static const double TopMargin; static const double EntryCursorLength; static const double EntryCursorWidth; Cantor::Session *m_session; QSyntaxHighlighter* m_highlighter; EpsRenderer m_epsRenderer; WorksheetEntry* m_firstEntry; WorksheetEntry* m_lastEntry; WorksheetEntry* m_dragEntry; WorksheetEntry* m_choosenCursorEntry; bool m_isCursorEntryAfterLastEntry; QTimer* m_cursorItemTimer; QGraphicsLineItem* m_entryCursorItem; PlaceHolderEntry* m_placeholderEntry; WorksheetTextItem* m_lastFocusedTextItem; QTimer* m_dragScrollTimer; double m_viewWidth; double m_protrusion; QMap m_itemProtrusions; QMap m_shortcuts; QList m_richTextActionList; KToggleAction* m_boldAction; KToggleAction* m_italicAction; KToggleAction* m_underlineAction; KToggleAction* m_strikeOutAction; KFontAction* m_fontAction; KFontSizeAction* m_fontSizeAction; KToggleAction* m_alignLeftAction; KToggleAction* m_alignCenterAction; KToggleAction* m_alignRightAction; KToggleAction* m_alignJustifyAction; bool m_completionEnabled; bool m_showExpressionIds; bool m_animationsEnabled; bool m_loginDone; bool m_isPrinting; bool m_isLoadingFromFile; bool m_readOnly; - Type m_type; + Type m_type = CantorWorksheet; QString m_backendName; }; #endif // WORKSHEET_H diff --git a/src/worksheetentry.h b/src/worksheetentry.h index 36914ed3..7dc2c210 100644 --- a/src/worksheetentry.h +++ b/src/worksheetentry.h @@ -1,190 +1,192 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2012 Martin Kuettler */ #ifndef WORKSHEETENTRY_H #define WORKSHEETENTRY_H #include #include #include "worksheet.h" #include "worksheettextitem.h" #include "worksheetcursor.h" class TextEntry; class MarkdownEntry; class CommandEntry; class ImageEntry; class PageBreakEntry; class LaTeXEntry; class WorksheetTextItem; class ActionBar; class QPainter; class QWidget; class QPropertyAnimation; struct AnimationData; class WorksheetEntry : public QGraphicsObject { Q_OBJECT public: explicit WorksheetEntry(Worksheet* worksheet); ~WorksheetEntry() override; enum {Type = UserType}; int type() const override; virtual bool isEmpty()=0; static WorksheetEntry* create(int t, Worksheet* worksheet); WorksheetEntry* next() const; WorksheetEntry* previous() const; void setNext(WorksheetEntry*); void setPrevious(WorksheetEntry*); QRectF boundingRect() const override; void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget = nullptr) override; virtual bool acceptRichText() = 0; virtual void setContent(const QString& content)=0; virtual void setContent(const QDomElement& content, const KZip& file)=0; + virtual void setContentFromJupyter(const QJsonObject& cell)=0; virtual QDomElement toXml(QDomDocument& doc, KZip* archive)=0; + virtual QJsonValue toJupyterJson()=0; virtual QString toPlain(const QString& commandSep, const QString& commentStartingSeq, const QString& commentEndingSeq)=0; virtual void interruptEvaluation()=0; virtual void showCompletion(); virtual bool focusEntry(int pos = WorksheetTextItem::TopLeft, qreal xCoord = 0); virtual qreal setGeometry(qreal x, qreal y, qreal w); virtual void layOutForWidth(qreal w, bool force = false) = 0; QPropertyAnimation* sizeChangeAnimation(QSizeF s = QSizeF()); virtual void populateMenu(QMenu* menu, QPointF pos); bool aboutToBeRemoved(); QSizeF size(); enum EvaluationOption { DoNothing, FocusNext, EvaluateNext }; virtual WorksheetTextItem* highlightItem(); bool hasActionBar(); enum SearchFlag {SearchCommand=1, SearchResult=2, SearchError=4, SearchText=8, SearchLaTeX=16, SearchAll=31}; virtual WorksheetCursor search(const QString& pattern, unsigned flags, QTextDocument::FindFlags qt_flags, const WorksheetCursor& pos = WorksheetCursor()); public Q_SLOTS: virtual bool evaluate(WorksheetEntry::EvaluationOption evalOp = FocusNext) = 0; virtual bool evaluateCurrentItem(); virtual void updateEntry() = 0; void insertCommandEntry(); void insertTextEntry(); void insertMarkdownEntry(); void insertLatexEntry(); void insertImageEntry(); void insertPageBreakEntry(); void insertCommandEntryBefore(); void insertTextEntryBefore(); void insertMarkdownEntryBefore(); void insertLatexEntryBefore(); void insertImageEntryBefore(); void insertPageBreakEntryBefore(); virtual void sizeAnimated(); virtual void startRemoving(); bool stopRemoving(); void moveToPreviousEntry(int pos = WorksheetTextItem::BottomRight, qreal x = 0); void moveToNextEntry(int pos = WorksheetTextItem::TopLeft, qreal x = 0); void recalculateSize(); // similar to recalculateSize, but the size change is animated void animateSizeChange(); // animate the size change and the opacity of item void fadeInItem(QGraphicsObject* item = nullptr, const char* slot = nullptr); void fadeOutItem(QGraphicsObject* item = nullptr, const char* slot = "deleteLater()"); void endAnimation(); void showActionBar(); void hideActionBar(); void startDrag(QPointF grabPos = QPointF()); Q_SIGNALS: void aboutToBeDeleted(); protected: Worksheet* worksheet(); WorksheetView* worksheetView(); void contextMenuEvent(QGraphicsSceneContextMenuEvent *event) override; void keyPressEvent(QKeyEvent *event) override; void evaluateNext(EvaluationOption opt); void hoverEnterEvent(QGraphicsSceneHoverEvent* event) override; void hoverLeaveEvent(QGraphicsSceneHoverEvent* event) override; void setSize(QSizeF size); bool animationActive(); void updateSizeAnimation(QSizeF size); void invokeSlotOnObject(const char* slot, QObject* obj); virtual void addActionsToBar(ActionBar* actionBar); virtual bool wantToEvaluate() = 0; virtual bool wantFocus(); protected Q_SLOTS: virtual void remove(); void deleteActionBar(); void deleteActionBarAnimation(); protected: static const qreal VerticalMargin; private: QSizeF m_size; WorksheetEntry* m_prev; WorksheetEntry* m_next; Q_PROPERTY(QSizeF size READ size WRITE setSize) AnimationData* m_animation; ActionBar* m_actionBar; QPropertyAnimation* m_actionBarAnimation; bool m_aboutToBeRemoved; }; #endif // WORKSHEETENTRY_H