diff --git a/src/backends/julia/juliabackend.kcfg b/src/backends/julia/juliabackend.kcfg index 614d0522..bc24fafa 100644 --- a/src/backends/julia/juliabackend.kcfg +++ b/src/backends/julia/juliabackend.kcfg @@ -1,32 +1,31 @@ QStandardPaths QUrl::fromLocalFile(QStandardPaths::findExecutable(QLatin1String("julia"))) true true - svg diff --git a/src/backends/julia/juliaexpression.cpp b/src/backends/julia/juliaexpression.cpp index 71d0cf46..88d9e6f4 100644 --- a/src/backends/julia/juliaexpression.cpp +++ b/src/backends/julia/juliaexpression.cpp @@ -1,110 +1,106 @@ /* 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) 2016 Ivan Lakhtanov */ #include "juliaexpression.h" #include #include #include "settings.h" #include "juliasession.h" #include "juliakeywords.h" #include "textresult.h" #include "imageresult.h" JuliaExpression::JuliaExpression(Cantor::Session *session, bool internal) : Cantor::Expression(session, internal) { } void JuliaExpression::evaluate() { setStatus(Cantor::Expression::Computing); auto juliaSession = static_cast(session()); juliaSession->enqueueExpression(this); } QString JuliaExpression::internalCommand() { QString cmd = command(); auto juliaSession = static_cast(session()); // Plots integration m_plot_filename.clear(); if (juliaSession->integratePlots() && checkPlotShowingCommands()) { // Simply add plot saving command to the end of execution QStringList inlinePlotFormats; inlinePlotFormats << QLatin1String("svg"); - inlinePlotFormats << QLatin1String("eps"); inlinePlotFormats << QLatin1String("png"); auto inlinePlotFormat = inlinePlotFormats[JuliaSettings::inlinePlotFormat()]; m_plot_filename = QDir::tempPath() + QString::fromLatin1("/cantor-julia-export-%1.%2") .arg(QUuid::createUuid().toString()).arg(inlinePlotFormat); QString saveFigCommand = QString::fromLatin1("\nGR.savefig(\"%1\")\n").arg(m_plot_filename); cmd = cmd.append(saveFigCommand); } return cmd; } void JuliaExpression::finalize(const QString& output, const QString& error, bool wasException) { if (wasException) { setErrorMessage(error); if (!output.isEmpty()) setResult(new Cantor::TextResult(output)); setStatus(Cantor::Expression::Error); } else { - if (!m_plot_filename.isEmpty() - && QFileInfo(m_plot_filename).exists()) { + if (!m_plot_filename.isEmpty() && QFileInfo(m_plot_filename).exists()) { // If we have plot in result, show it - setResult( - new Cantor::ImageResult(QUrl::fromLocalFile(m_plot_filename))); - QDir().remove(m_plot_filename); + setResult(new Cantor::ImageResult(QUrl::fromLocalFile(m_plot_filename))); } else { if (!output.isEmpty()) setResult(new Cantor::TextResult(output)); } setStatus(Cantor::Expression::Done); } } void JuliaExpression::interrupt() { setStatus(Cantor::Expression::Interrupted); } bool JuliaExpression::checkPlotShowingCommands() { for (auto showingCommand : JuliaKeywords::instance()->plotShowingCommands()) { if (command().contains(showingCommand + QLatin1String("("))) { return true; } } return false; } diff --git a/src/backends/julia/settings.ui b/src/backends/julia/settings.ui index 50cc1314..2405b747 100644 --- a/src/backends/julia/settings.ui +++ b/src/backends/julia/settings.ui @@ -1,91 +1,86 @@ JuliaSettingsBase Path to Julia: Let Cantor follow the creation/destruction of variables Enable Variable Management Integrate Plots in Worksheet (start a new session when changed) Inline Plots Intermediate Format: svg - - - eps - - png Qt::Vertical 20 40 KUrlRequester QFrame
kurlrequester.h
diff --git a/src/backends/octave/octavesession.cpp b/src/backends/octave/octavesession.cpp index d1f75873..46cd5dad 100644 --- a/src/backends/octave/octavesession.cpp +++ b/src/backends/octave/octavesession.cpp @@ -1,343 +1,343 @@ /* Copyright (C) 2010 Miha Čančula 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. */ #include "octavesession.h" #include "octaveexpression.h" #include "octavecompletionobject.h" #include "octavesyntaxhelpobject.h" #include "octavehighlighter.h" #include "result.h" #include "textresult.h" #include "settings.h" #include #include #include #include #include #include #include #ifndef Q_OS_WIN #include #endif #include "octavevariablemodel.h" const QRegularExpression OctaveSession::PROMPT_UNCHANGEABLE_COMMAND = QRegularExpression(QStringLiteral("^(?:,|;)+$")); OctaveSession::OctaveSession ( Cantor::Backend* backend ) : Session ( backend), m_process(nullptr), m_prompt(QStringLiteral("CANTOR_OCTAVE_BACKEND_PROMPT:([0-9]+)> ")), m_subprompt(QStringLiteral("CANTOR_OCTAVE_BACKEND_SUBPROMPT:([0-9]+)> ")), m_previousPromptNumber(1), m_syntaxError(false) { setVariableModel(new OctaveVariableModel(this)); } OctaveSession::~OctaveSession() { if (m_process) { m_process->kill(); m_process->deleteLater(); m_process = nullptr; } } void OctaveSession::login() { qDebug() << "login"; if (m_process) return; emit loginStarted(); m_process = new KProcess ( this ); QStringList args; args << QLatin1String("--silent"); args << QLatin1String("--interactive"); args << QLatin1String("--persist"); // Setting prompt and subprompt args << QLatin1String("--eval"); args << QLatin1String("PS1('CANTOR_OCTAVE_BACKEND_PROMPT:\\#> ');"); args << QLatin1String("--eval"); args << QLatin1String("PS2('CANTOR_OCTAVE_BACKEND_SUBPROMPT:\\#> ');"); // Add the cantor script directory to octave script search path const QStringList& scriptDirs = locateAllCantorFiles(QLatin1String("octavebackend"), QStandardPaths::LocateDirectory); if (scriptDirs.isEmpty()) qCritical() << "Octave script directory not found, needed for integrated plots"; else { for (const QString& dir : scriptDirs) args << QLatin1String("--eval") << QString::fromLatin1("addpath \"%1\";").arg(dir); } // Do not show extra text in help commands args << QLatin1String("--eval"); args << QLatin1String("suppress_verbose_help_message(1);"); if (OctaveSettings::integratePlots()) { // Do not show the popup when plotting, rather only print to a file args << QLatin1String("--eval"); args << QLatin1String("set (0, \"defaultfigurevisible\",\"off\");"); args << QLatin1String("--eval"); - args << QLatin1String("graphics_toolkit gnuplot;"); + args << QLatin1String("if strcmp(graphics_toolkit(), \"fltk\") graphics_toolkit(\"gnuplot\") endif;"); } else { args << QLatin1String("--eval"); args << QLatin1String("set (0, \"defaultfigurevisible\",\"on\");"); } m_process->setProgram ( OctaveSettings::path().toLocalFile(), args ); qDebug() << "starting " << m_process->program(); m_process->setOutputChannelMode ( KProcess::SeparateChannels ); m_process->start(); m_process->waitForStarted(); connect ( m_process, SIGNAL (readyReadStandardOutput()), SLOT (readOutput()) ); connect ( m_process, SIGNAL (readyReadStandardError()), SLOT (readError()) ); connect ( m_process, SIGNAL (error(QProcess::ProcessError)), SLOT (processError()) ); if(!OctaveSettings::self()->autorunScripts().isEmpty()){ QString autorunScripts = OctaveSettings::self()->autorunScripts().join(QLatin1String("\n")); evaluateExpression(autorunScripts, OctaveExpression::DeleteOnFinish, true); updateVariables(); } changeStatus(Cantor::Session::Done); emit loginDone(); qDebug()<<"login done"; } void OctaveSession::logout() { qDebug()<<"logout"; if(!m_process) return; disconnect(m_process, nullptr, this, nullptr); if(status() == Cantor::Session::Running) interrupt(); m_process->write("exit\n"); qDebug()<<"send exit command to octave"; if(!m_process->waitForFinished(1000)) { m_process->kill(); qDebug()<<"octave still running, process kill enforced"; } m_process->deleteLater(); m_process = nullptr; expressionQueue().clear(); m_output.clear(); m_previousPromptNumber = 1; Session::logout(); qDebug()<<"logout done"; } void OctaveSession::interrupt() { qDebug() << expressionQueue().size(); if(!expressionQueue().isEmpty()) { qDebug()<<"interrupting " << expressionQueue().first()->command(); if(m_process && m_process->state() != QProcess::NotRunning) { #ifndef Q_OS_WIN const int pid=m_process->pid(); kill(pid, SIGINT); #else ; //TODO: interrupt the process on windows #endif } foreach (Cantor::Expression* expression, expressionQueue()) expression->setStatus(Cantor::Expression::Interrupted); expressionQueue().clear(); // Cleanup inner state and call octave prompt printing // If we move this code for interruption to Session, we need add function for // cleaning before setting Done status m_output.clear(); m_process->write("\n"); qDebug()<<"done interrupting"; } changeStatus(Cantor::Session::Done); } void OctaveSession::processError() { qDebug() << "processError"; emit error(m_process->errorString()); } Cantor::Expression* OctaveSession::evaluateExpression ( const QString& command, Cantor::Expression::FinishingBehavior finishingBehavior, bool internal ) { qDebug() << "evaluating: " << command; OctaveExpression* expression = new OctaveExpression ( this, internal); expression->setCommand ( command ); expression->setFinishingBehavior ( finishingBehavior ); expression->evaluate(); return expression; } void OctaveSession::runFirstExpression() { OctaveExpression* expression = static_cast(expressionQueue().first()); connect(expression, SIGNAL(statusChanged(Cantor::Expression::Status)), this, SLOT(currentExpressionStatusChanged(Cantor::Expression::Status))); QString command = expression->internalCommand(); expression->setStatus(Cantor::Expression::Computing); if (isDoNothingCommand(command)) expression->setStatus(Cantor::Expression::Done); else { m_process->write ( command.toLocal8Bit() ); } } void OctaveSession::readError() { qDebug() << "readError"; QString error = QString::fromLocal8Bit(m_process->readAllStandardError()); if (!expressionQueue().isEmpty() && !error.isEmpty()) { OctaveExpression* const exp = static_cast(expressionQueue().first()); if (m_syntaxError) { m_syntaxError = false; exp->parseError(i18n("Syntax Error")); } else exp->parseError(error); m_output.clear(); } } void OctaveSession::readOutput() { qDebug() << "readOutput"; while (m_process->bytesAvailable() > 0) { QString line = QString::fromLocal8Bit(m_process->readLine()); qDebug()<<"start parsing " << " " << line; QRegularExpressionMatch match = m_prompt.match(line); if (match.hasMatch()) { const int promptNumber = match.captured(1).toInt(); // Add all text before prompt, if exists m_output += QStringRef(&line, 0, match.capturedStart(0)).toString(); if (!expressionQueue().isEmpty()) { const QString& command = expressionQueue().first()->command(); if (m_previousPromptNumber + 1 == promptNumber || isSpecialOctaveCommand(command)) { if (!expressionQueue().isEmpty()) { readError(); static_cast(expressionQueue().first())->parseOutput(m_output); } } else { // Error command don't increase octave prompt number (usually, but not always) readError(); } } m_previousPromptNumber = promptNumber; m_output.clear(); } else if ((match = m_subprompt.match(line)).hasMatch() && match.captured(1).toInt() == m_previousPromptNumber) { // User don't write finished octave statement (for example, write 'a = [1,2, ' only), so // octave print subprompt and waits input finish. m_syntaxError = true; qDebug() << "subprompt catch"; m_process->write(")]'\"\n"); // force exit from subprompt m_output.clear(); } else m_output += line; } } void OctaveSession::currentExpressionStatusChanged(Cantor::Expression::Status status) { qDebug() << "currentExpressionStatusChanged" << status << expressionQueue().first()->command(); switch (status) { case Cantor::Expression::Done: case Cantor::Expression::Error: finishFirstExpression(); break; default: break; } } Cantor::CompletionObject* OctaveSession::completionFor ( const QString& cmd, int index ) { return new OctaveCompletionObject ( cmd, index, this ); } Cantor::SyntaxHelpObject* OctaveSession::syntaxHelpFor ( const QString& cmd ) { return new OctaveSyntaxHelpObject ( cmd, this ); } QSyntaxHighlighter* OctaveSession::syntaxHighlighter ( QObject* parent ) { return new OctaveHighlighter ( parent, this ); } void OctaveSession::runSpecificCommands() { m_process->write("figure(1,'visible','off')"); } bool OctaveSession::isDoNothingCommand(const QString& command) { return PROMPT_UNCHANGEABLE_COMMAND.match(command).hasMatch() || command.isEmpty() || command == QLatin1String("\n"); } bool OctaveSession::isSpecialOctaveCommand(const QString& command) { return command.contains(QLatin1String("completion_matches")); } diff --git a/src/commandentry.cpp b/src/commandentry.cpp index 507135c7..14da1a5f 100644 --- a/src/commandentry.cpp +++ b/src/commandentry.cpp @@ -1,1369 +1,1446 @@ /* 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-2019 Alexander Semke */ #include "commandentry.h" #include "resultitem.h" #include "loadedexpression.h" #include "lib/jupyterutils.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 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_textColorCustom(false), + m_backgroundColorCustom(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", this); 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); m_promptItem->setDoubleClickBehaviour(WorksheetTextItem::DoubleClickEventBehaviour::Simple); connect(m_promptItem, &WorksheetTextItem::doubleClick, this, &CommandEntry::changeResultCollapsingAction); connect(&m_controlElement, &WorksheetControlItem::doubleClick, this, &CommandEntry::changeResultCollapsingAction); 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, &WorksheetTextItem::execute, this, [=]() { evaluate();} ); connect(m_commandItem, &WorksheetTextItem::moveToPrevious, this, &CommandEntry::moveToPreviousItem); connect(m_commandItem, &WorksheetTextItem::moveToNext, this, &CommandEntry::moveToNextItem); connect(m_commandItem, &WorksheetTextItem::receivedFocus, worksheet, &Worksheet::highlightItem); connect(m_promptItem, &WorksheetTextItem::drag, this, &CommandEntry::startDrag); connect(worksheet, &Worksheet::updatePrompt, this, [=]() { updatePrompt();} ); + + m_defaultDefaultTextColor = m_commandItem->defaultTextColor(); } CommandEntry::~CommandEntry() { if (m_completionBox) m_completionBox->deleteLater(); if (m_menusInitialized) { m_backgroundColorMenu->deleteLater(); m_textColorMenu->deleteLater(); m_fontMenu->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); - } + QPixmap pix(16,16); + QPainter p(&pix); + + // Create default action + KColorScheme scheme = KColorScheme(QPalette::Normal, KColorScheme::View); + p.fillRect(pix.rect(), scheme.background(KColorScheme::AlternateBackground).color()); + QAction* action = new QAction(QIcon(pix), i18n("Default"), m_backgroundColorActionGroup); + action->setCheckable(true); + m_backgroundColorMenu->addAction(action); + if (!m_backgroundColorCustom) + action->setChecked(true); + + for (int i=0; isetCheckable(true); + m_backgroundColorMenu->addAction(action); + + if (m_backgroundColorCustom && m_commandItem->backgroundColor() == colors[i]) + action->setChecked(true); + } //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); - } + // Create default action + p.fillRect(pix.rect(), m_defaultDefaultTextColor); + action = new QAction(QIcon(pix), i18n("Default"), m_textColorActionGroup); + action->setCheckable(true); + m_textColorMenu->addAction(action); + if (!m_textColorCustom) + action->setChecked(true); + + for (int i=0; isetCheckable(true); + m_textColorMenu->addAction(action); + + if (m_textColorCustom && m_commandItem->defaultTextColor() == colors[i]) + action->setChecked(true); + } //font QFont font = m_commandItem->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 = new QAction(QIcon::fromTheme(QLatin1String("format-text-bold")), i18n("Bold")); action->setCheckable(true); connect(action, &QAction::triggered, this, &CommandEntry::fontBoldTriggered); m_fontMenu->addAction(action); if (font.bold()) action->setChecked(true); 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); if (font.italic()) action->setChecked(true); 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); + action = new QAction(QIcon::fromTheme(QLatin1String("preferences-desktop-font")), i18n("Reset to Default")); + connect(action, &QAction::triggered, this, &CommandEntry::resetFontTriggered); + 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]); + if (index == 0) + { + KColorScheme scheme = KColorScheme(QPalette::Normal, KColorScheme::View); + m_commandItem->setBackgroundColor(scheme.background(KColorScheme::AlternateBackground).color()); + } + else + m_commandItem->setBackgroundColor(colors[index-1]); } void CommandEntry::textColorChanged(QAction* action) { int index = m_textColorActionGroup->actions().indexOf(action); if (index == -1 || index>=colorsCount) index = 0; - m_commandItem->setDefaultTextColor(colors[index]); + if (index == 0) + { + m_commandItem->setDefaultTextColor(m_defaultDefaultTextColor); + } + else + m_commandItem->setDefaultTextColor(colors[index-1]); } void CommandEntry::fontBoldTriggered() { QAction* action = static_cast(QObject::sender()); QFont font = m_commandItem->font(); font.setBold(action->isChecked()); m_commandItem->setFont(font); } +void CommandEntry::resetFontTriggered() +{ + m_commandItem->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); +} + 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; } for (auto* item : m_informationItems) { item->deleteLater(); } m_informationItems.clear(); // Delete any previous result clearResultItems(); m_expression = expr; m_resultsCollapsed = false; connect(expr, &Cantor::Expression::gotResult, this, &CommandEntry::updateEntry); connect(expr, &Cantor::Expression::resultsCleared, this, &CommandEntry::clearResultItems); connect(expr, &Cantor::Expression::resultRemoved, this, &CommandEntry::removeResultItem); connect(expr, &Cantor::Expression::resultReplaced, this, &CommandEntry::replaceResultItem); connect(expr, &Cantor::Expression::idChanged, this, [=]() { updatePrompt();} ); connect(expr, &Cantor::Expression::statusChanged, this, &CommandEntry::expressionChangedStatus); connect(expr, &Cantor::Expression::needsAdditionalInformation, this, &CommandEntry::showAdditionalInformationPrompt); connect(expr, &Cantor::Expression::statusChanged, this, [=]() { 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); + m_backgroundColorCustom = true; } //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); + if (!colorElem.isNull() && !colorElem.hasAttribute(QLatin1String("default"))) + { + m_defaultDefaultTextColor = m_commandItem->defaultTextColor(); + 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); + m_textColorCustom = true; + } //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); + if (!fontElem.isNull() && !fontElem.hasAttribute(QLatin1String("default"))) + { + 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) { m_commandItem->setPlainText(Cantor::JupyterUtils::getSource(cell)); LoadedExpression* expr=new LoadedExpression( worksheet()->session() ); expr->loadFromJupyter(cell); setExpression(expr); // https://nbformat.readthedocs.io/en/latest/format_description.html#cell-metadata // 'collapsed': + // 'scrolled', 'deletable', 'name', 'tags' don't supported Cantor, so ignore them // 'source_hidden' don't supported // 'format' for raw entry, so ignore // I haven't found 'outputs_hidden' inside Jupyter notebooks, and difference from 'collapsed' // not clear, so also ignore const QJsonObject& metadata = Cantor::JupyterUtils::getMetadata(cell); const QJsonValue& collapsed = metadata.value(QLatin1String("collapsed")); if (collapsed.isBool() && collapsed.toBool() == true && !m_resultItems.isEmpty()) { // Disable animation for hiding results, we don't need animation on document load stage bool animationValue = worksheet()->animationsEnabled(); worksheet()->enableAnimations(false); collapseResults(); worksheet()->enableAnimations(animationValue); } setJupyterMetadata(metadata); } 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); QJsonObject metadata(jupyterMetadata()); if (m_resultsCollapsed) metadata.insert(QLatin1String("collapsed"), true); entry.insert(QLatin1String("metadata"), metadata); Cantor::JupyterUtils::setSource(entry, command()); QJsonArray outputs; if (expression()) { Cantor::Expression::Status status = expression()->status(); if (status == Cantor::Expression::Error || status == Cantor::Expression::Interrupted) { QJsonObject errorOutput; errorOutput.insert(Cantor::JupyterUtils::outputTypeKey, QLatin1String("error")); errorOutput.insert(QLatin1String("ename"), QLatin1String("Unknown")); errorOutput.insert(QLatin1String("evalue"), QLatin1String("Unknown")); QJsonArray traceback; if (status == Cantor::Expression::Error) { const QStringList& error = expression()->errorMessage().split(QLatin1Char('\n')); for (const QString& line: error) traceback.append(line); } else { traceback.append(i18n("Interrupted")); } errorOutput.insert(QLatin1String("traceback"), traceback); outputs.append(errorOutput); } for (auto* result : expression()->results()) { const QJsonValue& resultJson = result->toJupyterJson(); 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()) { const QString& errorMessage = expression()->errorMessage(); if (!errorMessage.isEmpty()) { QDomElement errorElem = doc.createElement( QLatin1String("Error") ); errorElem.appendChild(doc.createTextNode(errorMessage)); exprElem.appendChild(errorElem); } for (auto* 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()) + bool isBackgroundColorNotDefault = false; + // If user can change value from menu (menus have been inited) - check via menu + // If use don't have menu, check if loaded color was custom color + if (m_backgroundColorActionGroup) + isBackgroundColorNotDefault = m_backgroundColorActionGroup->checkedAction()->text() != i18n("Default"); + else + isBackgroundColorNotDefault = m_backgroundColorCustom; + if (isBackgroundColorNotDefault) { + QColor backgroundColor = m_commandItem->backgroundColor(); 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)) + const QColor& textColor = m_commandItem->defaultTextColor(); + bool isFontNotDefault = font != QFontDatabase::systemFont(QFontDatabase::FixedFont); + + bool isTextColorNotDefault = false; + if (m_textColorActionGroup) + isTextColorNotDefault = m_textColorActionGroup->checkedAction()->text() != i18n("Default"); + else + isTextColorNotDefault = m_textColorCustom; + + // Setting both values is necessary for previous Cantor versions compability + // Value, added only for compability reason, marks with attribute + if (isFontNotDefault || isTextColorNotDefault) { QDomElement textElem = doc.createElement(QLatin1String("Text")); //font properties QDomElement fontElem = doc.createElement(QLatin1String("Font")); + if (!isFontNotDefault) + fontElem.setAttribute(QLatin1String("default"), true); 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") ); + if (!isTextColorNotDefault) + colorElem.setAttribute(QLatin1String("default"), true); 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) { if (worksheet()->session()->status() == Cantor::Session::Disable) worksheet()->loginToSession(); removeContextHelp(); QToolTip::hideText(); QString cmd = command(); m_evaluationOption = evalOp; if(cmd.isEmpty()) { removeResults(); for (auto* item : m_informationItems) { item->deleteLater(); } m_informationItems.clear(); recalculateSize(); evaluateNext(m_evaluationOption); return false; } Cantor::Expression* expr = worksheet()->session()->evaluateExpression(cmd); connect(expr, &Cantor::Expression::gotResult, this, [=]() { worksheet()->gotResult(expr); }); 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). // NOTE: LatexResult could request update (change from rendered to code, for example) // So, just update results, if we haven't new results or something similar 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]); } else { for (ResultItem* item: m_resultItems) item->update(); } m_controlElement.isCollapsable = m_resultItems.size() > 0; 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) { QString error = m_expression->errorMessage().toHtmlEscaped(); while (error.endsWith(QLatin1Char('\n'))) error.chop(1); error.replace(QLatin1String("\n"), QLatin1String("
")); error.replace(QLatin1String(" "), QLatin1String(" ")); m_errorItem->setHtml(error); } else m_errorItem->setHtml(i18n("Interrupted")); recalculateSize(); // Mostly we handle setting of modification in WorksheetEntry inside ::evaluateNext. // But command entry wouldn't triger ::evaluateNext for Error and Interrupted states // So, we set it here worksheet()->setModified(); 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(), &QTextDocument::contentsChanged, this, &CommandEntry::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 doesn't support  , but support multiple spaces msg.replace(QLatin1String(" "), QLatin1String(" ")); // Doesn't support ", neither; 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(QString()); 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 QScreen* desktop = QGuiApplication::primaryScreen(); const QRect screenRect = desktop->geometry(); 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 entry_zone_x, qreal w, bool force) { if (w == size().width() && m_commandItem->pos().x() == entry_zone_x && !force) return; m_promptItem->setPos(0, 0); double x = std::max(0 + m_promptItem->width() + HorizontalSpacing, entry_zone_x); double y = 0; double width = 0; const qreal margin = worksheet()->isPrinting() ? 0 : RightMargin; m_commandItem->setGeometry(x, y, w - x - margin); width = qMax(width, m_commandItem->width()+margin); y += qMax(m_commandItem->height(), m_promptItem->height()); foreach(WorksheetTextItem* information, m_informationItems) { y += VerticalSpacing; y += information->setGeometry(x, y, w - x - margin); width = qMax(width, information->width() + margin); } if (m_errorItem) { y += VerticalSpacing; y += m_errorItem->setGeometry(x,y,w - x - margin); width = qMax(width, m_errorItem->width() + margin); } for (auto* resultItem : m_resultItems) { if (!resultItem || !resultItem->graphicsObject()->isVisible()) continue; y += VerticalSpacing; y += resultItem->setGeometry(x, y, w - x - margin); width = qMax(width, resultItem->width() + margin); } 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(); m_controlElement.isCollapsed = true; 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(); m_controlElement.isCollapsed = false; animateSizeChange(); } void CommandEntry::setHidePrompt() { updatePrompt(HidePrompt); } void CommandEntry::setMidPrompt() { updatePrompt(MidPrompt); } void CommandEntry::changeResultCollapsingAction() { if (m_resultItems.size() == 0) return; if (m_resultsCollapsed) expandResults(); else collapseResults(); } qreal CommandEntry::promptItemWidth() { return m_promptItem->width(); } diff --git a/src/commandentry.h b/src/commandentry.h index e11ef154..535d5ad6 100644 --- a/src/commandentry.h +++ b/src/commandentry.h @@ -1,188 +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) 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) 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 entry_zone_x, qreal w, bool force = false) override; qreal promptItemWidth(); 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 changeResultCollapsingAction(); void backgroundColorChanged(QAction*); void textColorChanged(QAction*); void fontBoldTriggered(); void fontItalicTriggered(); void fontIncreaseTriggered(); void fontDecreaseTriggered(); void fontSelectTriggered(); + void resetFontTriggered(); 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; + bool m_textColorCustom; + bool m_backgroundColorCustom; //formatting QActionGroup* m_backgroundColorActionGroup; QMenu* m_backgroundColorMenu; QActionGroup* m_textColorActionGroup; + QColor m_defaultDefaultTextColor; QMenu* m_textColorMenu; QMenu* m_fontMenu; }; #endif // COMMANDENTRY_H