diff --git a/src/backends/julia/juliabackend.cpp b/src/backends/julia/juliabackend.cpp index b74fef67..4965bc6d 100644 --- a/src/backends/julia/juliabackend.cpp +++ b/src/backends/julia/juliabackend.cpp @@ -1,160 +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) 2016 Ivan Lakhtanov */ #include "juliabackend.h" #include #include #include #include "juliasession.h" #include "ui_settings.h" #include "settings.h" #include "juliaextensions.h" JuliaBackend::JuliaBackend(QObject *parent, const QList &args) : Cantor::Backend(parent, args) { setEnabled(true); new JuliaVariableManagementExtension(this); new JuliaPackagingExtension(this); new JuliaPlotExtension(this); new JuliaScriptExtension(this); new JuliaLinearAlgebraExtension(this); } QString JuliaBackend::id() const { return QLatin1String("julia"); } QString JuliaBackend::version() const { return QLatin1String("1.0.0"); } Cantor::Session *JuliaBackend::createSession() { return new JuliaSession(this); } Cantor::Backend::Capabilities JuliaBackend::capabilities() const { Cantor::Backend::Capabilities cap= SyntaxHighlighting| Completion; if (JuliaSettings::variableManagement()) cap |= VariableManagement; return cap; } QString JuliaBackend::description() const { return i18n( "

Julia is a high-level, high-performance dynamic programming " "language for technical computing, with syntax that is familiar to " "users of other technical computing environments. It provides a " "sophisticated compiler, distributed parallel execution, numerical " "accuracy, and an extensive mathematical function library.

" ); } QUrl JuliaBackend::helpUrl() const { return QUrl(i18nc( "The url to the documentation of Julia, please check if there is a" " translated version and use the correct url", "http://docs.julialang.org/en/latest/" )); } bool JuliaBackend::requirementsFullfilled(QString* const reason) const { const QString& replPath = JuliaSettings::self()->replPath().toLocalFile(); QFileInfo info(replPath); if (!info.isExecutable()) { if (reason) *reason = i18n("You should set path to Julia executable"); return false; } if (info.isSymLink()) { if (reason) *reason = i18n("Path to Julia should point directly to julia executable, symlink not allowed"); return false; } // Julia because of C API can handle only MAJOR.MINOR.* versions corresponding to // version, which used to build cantor_juliaserver // So check it and print info about it to user, if versions don't match QProcess getJuliaVersionProcess; getJuliaVersionProcess.setProgram(replPath); getJuliaVersionProcess.setArguments(QStringList()<(); ) #include "juliabackend.moc" diff --git a/src/backends/julia/juliascriptloading.h b/src/backends/julia/juliascriptloading.h index 56c6724d..0e934036 100644 --- a/src/backends/julia/juliascriptloading.h +++ b/src/backends/julia/juliascriptloading.h @@ -1,41 +1,47 @@ /* 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 */ #pragma once #include #include +#include inline QString loadScript(const QString &scriptName) { QString file = QStandardPaths::locate( QStandardPaths::AppDataLocation, QString::fromLatin1("juliabackend/scripts/%1.jl").arg(scriptName) ); if (file.isEmpty()) file = QStandardPaths::locate( QStandardPaths::GenericDataLocation, QString::fromLatin1("cantor/juliabackend/scripts/%1.jl").arg(scriptName) ); QFile text(file); - text.open(QIODevice::ReadOnly); - return QString::fromLatin1(text.readAll()); + if (text.open(QIODevice::ReadOnly)) + return QString::fromUtf8(text.readAll()); + else + { + qWarning() << "Cantor Julia script" << scriptName+QLatin1String(".jl") << "not found - something wrong"; + return QString(); + } } diff --git a/src/backends/maxima/maximaexpression.cpp b/src/backends/maxima/maximaexpression.cpp index e5af03d2..bd2dc65b 100644 --- a/src/backends/maxima/maximaexpression.cpp +++ b/src/backends/maxima/maximaexpression.cpp @@ -1,445 +1,445 @@ /* 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-2012 Alexander Rieder */ #include "maximaexpression.h" #include #include "maximasession.h" #include "textresult.h" #include "epsresult.h" #include "imageresult.h" #include "helpresult.h" #include "latexresult.h" #include "settings.h" #include #include #include #include #include #include #include #include // MaximaExpression use real id from Maxima as expression id, so we don't know id before executing MaximaExpression::MaximaExpression( Cantor::Session* session, bool internal ) : Cantor::Expression(session, internal, -1), m_tempFile(nullptr), m_isHelpRequest(false), m_isHelpRequestAdditional(false), m_isPlot(false), m_plotResult(nullptr), m_plotResultIndex(-1), m_gotErrorContent(false) { } MaximaExpression::~MaximaExpression() { if(m_tempFile) delete m_tempFile; } void MaximaExpression::evaluate() { m_isHelpRequest=false; m_gotErrorContent=false; if(m_tempFile) { delete m_tempFile; m_tempFile = nullptr; m_isPlot = false; m_plotResult = nullptr; m_plotResultIndex = -1; } //check if this is a ?command if(command().startsWith(QLatin1String("??")) || command().startsWith(QLatin1String("describe(")) || command().startsWith(QLatin1String("example(")) || command().startsWith(QLatin1String(":lisp(cl-info::info-exact"))) m_isHelpRequest=true; if(command().contains(QRegExp(QLatin1String("(?:plot2d|plot3d|contour_plot)\\s*\\([^\\)]"))) && MaximaSettings::self()->integratePlots() && !command().contains(QLatin1String("ps_file"))) { m_isPlot=true; #ifdef WITH_EPS m_tempFile=new QTemporaryFile(QDir::tempPath() + QLatin1String("/cantor_maxima-XXXXXX.eps" )); #else m_tempFile=new QTemporaryFile(QDir::tempPath() + QLatin1String("/cantor_maxima-XXXXXX.png")); #endif m_tempFile->open(); m_fileWatch.removePaths(m_fileWatch.files()); m_fileWatch.addPath(m_tempFile->fileName()); connect(&m_fileWatch, &QFileSystemWatcher::fileChanged, this, &MaximaExpression::imageChanged, Qt::UniqueConnection); } const QString& cmd=command(); bool isComment = true; int commentLevel = 0; bool inString = false; for (int i = 0; i < cmd.size(); ++i) { if (cmd[i] == QLatin1Char('\\')) { ++i; // skip the next character if (commentLevel == 0 && !inString) { isComment = false; } } else if (cmd[i] == QLatin1Char('"') && commentLevel == 0) { inString = !inString; isComment = false; } else if (cmd.mid(i,2) == QLatin1String("/*") && !inString) { ++commentLevel; ++i; } else if (cmd.mid(i,2) == QLatin1String("*/") && !inString) { if (commentLevel == 0) { qDebug() << "Comments mismatched!"; setErrorMessage(i18n("Error: Too many */")); setStatus(Cantor::Expression::Error); return; } ++i; --commentLevel; } else if (isComment && commentLevel == 0 && !cmd[i].isSpace()) { isComment = false; } } if (commentLevel > 0) { qDebug() << "Comments mismatched!"; setErrorMessage(i18n("Error: Too many /*")); setStatus(Cantor::Expression::Error); return; } if (inString) { qDebug() << "String not closed"; setErrorMessage(i18n("Error: expected \" before ;")); setStatus(Cantor::Expression::Error); return; } if(isComment) { setStatus(Cantor::Expression::Done); return; } session()->enqueueExpression(this); } void MaximaExpression::interrupt() { qDebug()<<"interrupting"; setStatus(Cantor::Expression::Interrupted); } QString MaximaExpression::internalCommand() { QString cmd=command(); if(m_isPlot) { if(!m_tempFile) { qDebug()<<"plotting without tempFile"; return QString(); } QString fileName = m_tempFile->fileName(); #ifdef WITH_EPS const QString psParam=QLatin1String("[gnuplot_ps_term_command, \"set size 1.0, 1.0; set term postscript eps color solid \"]"); const QString plotParameters = QLatin1String("[ps_file, \"")+ fileName+QLatin1String("\"],")+psParam; #else const QString plotParameters = QLatin1String("[gnuplot_term, \"png size 500,340\"], [gnuplot_out_file, \"")+fileName+QLatin1String("\"]"); #endif cmd.replace(QRegExp(QLatin1String("((plot2d|plot3d|contour_plot)\\s*\\(.*)\\)([;\n$]|$)")), QLatin1String("\\1, ")+plotParameters+QLatin1String(");")); } if (!cmd.endsWith(QLatin1Char('$'))) { if (!cmd.endsWith(QLatin1String(";"))) cmd+=QLatin1Char(';'); } //replace all newlines with spaces, as maxima isn't sensitive about //whitespaces, and without newlines the whole command //is executed at once, without outputting an input //prompt after each line cmd.replace(QLatin1Char('\n'), QLatin1Char(' ')); //lisp-quiet doesn't print a prompt after the command //is completed, which causes the parsing to hang. //replace the command with the non-quiet version cmd.replace(QRegExp(QLatin1String("^:lisp-quiet")), QLatin1String(":lisp")); return cmd; } void MaximaExpression::forceDone() { qDebug()<<"forcing Expression state to DONE"; setResult(nullptr); setStatus(Cantor::Expression::Done); } /*! example output for the simple expression '5+5': latex mode - "\n(%o1) 10\n\\mbox{\\tt\\red(\\mathrm{\\%o1}) \\black}10\n(%i2) \n" text mode - "\n(%o1) 10\n\n(%i2) \n" */ bool MaximaExpression::parseOutput(QString& out) { const int promptStart = out.indexOf(QLatin1String("")); const int promptEnd = out.indexOf(QLatin1String("")); const QString prompt = out.mid(promptStart + 15, promptEnd - promptStart - 15).simplified(); //check whether the result is part of the promt - this is the case when additional input is required from the user if (prompt.contains(QLatin1String(""))) { //text part of the output const int textContentStart = prompt.indexOf(QLatin1String("")); const int textContentEnd = prompt.indexOf(QLatin1String("")); QString textContent = prompt.mid(textContentStart + 13, textContentEnd - textContentStart - 13).trimmed(); qDebug()<<"asking for additional input for " << textContent; emit needsAdditionalInformation(textContent); return true; } qDebug()<<"new input label: " << prompt; QString errorContent; //parse the results int resultStart = out.indexOf(QLatin1String("")); if (resultStart != -1) errorContent += out.mid(0, resultStart); while (resultStart != -1) { int resultEnd = out.indexOf(QLatin1String(""), resultStart + 15); const QString resultContent = out.mid(resultStart + 15, resultEnd - resultStart - 15); parseResult(resultContent); //search for the next openning tag after the current closing tag resultStart = out.indexOf(QLatin1String(""), resultEnd + 16); } //parse the error message, the part outside of the tags int lastResultEnd = out.lastIndexOf(QLatin1String("")); if (lastResultEnd != -1) lastResultEnd += 16; else lastResultEnd = 0; errorContent += out.mid(lastResultEnd, promptStart - lastResultEnd).trimmed(); if (errorContent.isEmpty()) { // For plots we set Done status in imageChanged if (!m_isPlot || m_plotResult) setStatus(Cantor::Expression::Done); } else { qDebug() << "error content: " << errorContent; if (out.contains(QLatin1String("cantor-value-separator"))) { //when fetching variables, in addition to the actual result with variable names and values, //Maxima also writes out the names of the variables to the error buffer. //we don't interpret this as an error. setStatus(Cantor::Expression::Done); } else if(m_isHelpRequest || m_isHelpRequestAdditional) //help messages are also part of the error output { //we've got help result, but maybe additional input is required -> check this const int index = MaximaSession::MaximaInputPrompt.indexIn(prompt.trimmed()); if (index == -1) { // No input label found in the prompt -> additional info is required qDebug()<<"asking for additional input for the help request" << prompt; m_isHelpRequestAdditional = true; emit needsAdditionalInformation(prompt); } //set the help result errorContent.prepend(QLatin1Char(' ')); Cantor::HelpResult* result = new Cantor::HelpResult(errorContent); setResult(result); //if a new input prompt was found, no further input is expected and we're done if (index != -1) { m_isHelpRequestAdditional = false; setStatus(Cantor::Expression::Done); } } else { errorContent = errorContent.replace(QLatin1String("\n\n"), QLatin1String("\n")); setErrorMessage(errorContent); setStatus(Cantor::Expression::Error); } } return true; } void MaximaExpression::parseResult(const QString& resultContent) { //in case we asked for additional input for the help request, //no need to process the result - we're not done yet and maxima is waiting for further input if (m_isHelpRequestAdditional) return; qDebug()<<"result content: " << resultContent; //text part of the output const int textContentStart = resultContent.indexOf(QLatin1String("")); const int textContentEnd = resultContent.indexOf(QLatin1String("")); QString textContent = resultContent.mid(textContentStart + 13, textContentEnd - textContentStart - 13).trimmed(); qDebug()<<"text content: " << textContent; //output label can be a part of the text content -> determine it const QRegExp regex = QRegExp(MaximaSession::MaximaOutputPrompt.pattern()); const int index = regex.indexIn(textContent); QString outputLabel; if (index != -1) // No match, so output don't contain output label outputLabel = textContent.mid(index, regex.matchedLength()).trimmed(); qDebug()<<"output label: " << outputLabel; //extract the expression id bool ok; QString idString = outputLabel.mid(3, outputLabel.length()-4); int id = idString.toInt(&ok); if (ok) setId(id); qDebug()<<"expression id: " << this->id(); //remove the output label from the text content textContent = textContent.remove(outputLabel).trimmed(); //determine the actual result Cantor::Result* result = nullptr; const int latexContentStart = resultContent.indexOf(QLatin1String("")); //Handle system maxima output for plotting commands if (m_isPlot && textContent.endsWith(QString::fromLatin1("\"%1\"]").arg(m_tempFile->fileName()))) { m_plotResultIndex = results().size(); // Gnuplot could generate plot before we parse text output from maxima and after // If we already have plot result, just add it // Else set info message, and replace it by real result in imageChanged function later if (m_plotResult) result = m_plotResult; else result = new Cantor::TextResult(i18n("Waiting for the plot result")); } else if (latexContentStart != -1) { //latex output is available const int latexContentEnd = resultContent.indexOf(QLatin1String("")); QString latexContent = resultContent.mid(latexContentStart + 14, latexContentEnd - latexContentStart - 14).trimmed(); qDebug()<<"latex content: " << latexContent; Cantor::TextResult* textResult; //replace the \mbox{} environment, if available, by the eqnarray environment if (latexContent.indexOf(QLatin1String("\\mbox{")) != -1) { int i; int pcount=0; for(i = latexContent.indexOf(QLatin1String("\\mbox{"))+5; i < latexContent.size(); ++i) { if(latexContent[i]==QLatin1Char('{')) pcount++; else if(latexContent[i]==QLatin1Char('}')) pcount--; if(pcount==0) break; } QString modifiedLatexContent = latexContent.mid(i+1); if(modifiedLatexContent.trimmed().isEmpty()) { //empty content in the \mbox{} environment (e.g. for print() outputs), use the latex string outside of the \mbox{} environment modifiedLatexContent = latexContent.left(latexContent.indexOf(QLatin1String("\\mbox{"))); } modifiedLatexContent.prepend(QLatin1String("\\begin{eqnarray*}")); modifiedLatexContent.append(QLatin1String("\\end{eqnarray*}")); textResult = new Cantor::TextResult(modifiedLatexContent, textContent); qDebug()<<"modified latex content: " << modifiedLatexContent; } else { //no \mbox{} available, use what we've got. textResult = new Cantor::TextResult(latexContent, textContent); } textResult->setFormat(Cantor::TextResult::LatexFormat); result = textResult; } else { //no latex output is availabe, the actual result is part of the textContent string result = new Cantor::TextResult(textContent); } addResult(result); } void MaximaExpression::parseError(const QString& out) { m_errorBuffer.append(out); } void MaximaExpression::addInformation(const QString& information) { qDebug()<<"adding information"; QString inf=information; if(!inf.endsWith(QLatin1Char(';'))) inf+=QLatin1Char(';'); Cantor::Expression::addInformation(inf); - dynamic_cast(session())->sendInputToProcess(inf+QLatin1Char('\n')); + static_cast(session())->sendInputToProcess(inf+QLatin1Char('\n')); } void MaximaExpression::imageChanged() { if(m_tempFile->size()>0) { #ifdef WITH_EPS m_plotResult = new Cantor::EpsResult( QUrl::fromLocalFile(m_tempFile->fileName()) ); #else m_plotResult = new Cantor::ImageResult( QUrl::fromLocalFile(m_tempFile->fileName()) ); #endif // Check, that we already parse maxima output for this plot, and if not, keep it up to this moment // If it's true, replace text info result by real plot and set status as Done if (m_plotResultIndex != -1) { replaceResult(m_plotResultIndex, m_plotResult); if (status() != Cantor::Expression::Error) setStatus(Cantor::Expression::Done); } } } diff --git a/src/backends/maxima/maximavariablemodel.cpp b/src/backends/maxima/maximavariablemodel.cpp index 4d1c77b4..f606a3af 100644 --- a/src/backends/maxima/maximavariablemodel.cpp +++ b/src/backends/maxima/maximavariablemodel.cpp @@ -1,182 +1,182 @@ /* 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 Alexander Rieder */ #include "maximavariablemodel.h" #include "maximasession.h" #include "maximaexpression.h" #include "textresult.h" #include "latexresult.h" #include #include #include "settings.h" //command used to inspect a maxima variable. %1 is the name of that variable const QString MaximaVariableModel::inspectCommand=QLatin1String(":lisp($disp $%1)"); const QString MaximaVariableModel::variableInspectCommand=QLatin1String(":lisp(cantor-inspect $%1)"); MaximaVariableModel::MaximaVariableModel( MaximaSession* session) : Cantor::DefaultVariableModel(session), m_variableExpression(nullptr), m_functionExpression(nullptr) { } void MaximaVariableModel::update() { if (!m_variableExpression) { qDebug()<<"checking for new variables"; const QString& cmd1=variableInspectCommand.arg(QLatin1String("values")); m_variableExpression = static_cast(session()->evaluateExpression(cmd1, Cantor::Expression::FinishingBehavior::DoNotDelete, true)); connect(m_variableExpression, &Cantor::Expression::statusChanged, this, &MaximaVariableModel::parseNewVariables); } if (!m_functionExpression) { qDebug()<<"checking for new functions"; const QString& cmd2=inspectCommand.arg(QLatin1String("functions")); m_functionExpression = static_cast(session()->evaluateExpression(cmd2, Cantor::Expression::FinishingBehavior::DoNotDelete, true)); connect(m_functionExpression, &Cantor::Expression::statusChanged, this, &MaximaVariableModel::parseNewFunctions); } } QList parse(MaximaExpression* expr) { if(!expr || (expr->status()!=Cantor::Expression::Done && expr->errorMessage() != QLatin1String("$DONE")) || expr->results().isEmpty()) { return QList(); } //for parsing of names and values below (old code) we need to combine multiple results back to one string QString text; for (auto* result : expr->results()) { if(result->type()==Cantor::TextResult::Type) - text += dynamic_cast(result)->plain(); + text += static_cast(result)->plain(); else if(expr->result()->type()==Cantor::LatexResult::Type) - text += dynamic_cast(result)->plain(); + text += static_cast(result)->plain(); } const int nameIndex=text.indexOf(QLatin1Char(']')); QString namesString=text.left(nameIndex); //namesString.chop(1); namesString=namesString.mid(1); namesString=namesString.trimmed(); qDebug()<<"variable names: "<(); QStringList variableNames; QString valuesString; bool hasValues = false; QStringList variableValues; if ( namesString.contains(QLatin1Char(')')) ) { //function definition(s): e.g //text = "[f1(x),f2(x,y),f3(x,y,z)]\n$DONE" //nameString = f1(x),f2(x,y),f3(x,y,z) //variableString = "\n$DONE" variableNames = namesString.split(QLatin1String("),")); } else { //variable definition(s): e.g. //text = "[a,b]\n1\n\"-cantor-value-separator-\"\n2\n\"-cantor-value-separator-\"\n($A $B)" //nameString = "[a,b]" //variableString = "\n1\n\"-cantor-value-separator-\"\n2\n\"-cantor-value-separator-\"\n($A $B)" variableNames = namesString.split(QLatin1Char(',')); if (MaximaSettings::self()->variableManagement()) { valuesString = text.mid(nameIndex+1).trimmed(); valuesString = valuesString.remove(QLatin1String("\n")); //lists with many elements have line breaks, remove them variableValues = valuesString.split(QLatin1String("\"-cantor-value-separator-\"")); hasValues = variableValues.isEmpty(); } } qDebug()< variables; variables.reserve(variableNames.size()); for(int i=0;ii) { var.value=variableValues.at(i).trimmed(); var.value=var.value.remove(QLatin1String("\n")); //lists with many elements have line breaks, remove them } else var.value=QLatin1String("unknown"); variables< newVars=parse(m_variableExpression); setVariables(newVars); //the expression is not needed anymore m_variableExpression->deleteLater(); m_variableExpression = nullptr; } void MaximaVariableModel::parseNewFunctions(Cantor::Expression::Status status) { if (status != Cantor::Expression::Done && status != Cantor::Expression::Error) return; qDebug()<<"parsing functions"; // List of variables? QList newFuncs=parse(m_functionExpression); QStringList functions; for (Variable var : newFuncs) functions << var.name.left(var.name.indexOf(QLatin1Char('('))); qDebug() << functions; setFunctions(functions); //the expression is not needed anymore m_functionExpression->deleteLater(); m_functionExpression = nullptr; } MaximaSession* MaximaVariableModel::maximaSession() { return static_cast (session()); } diff --git a/src/backends/octave/octavesyntaxhelpobject.cpp b/src/backends/octave/octavesyntaxhelpobject.cpp index 846c87c7..8218cbd6 100644 --- a/src/backends/octave/octavesyntaxhelpobject.cpp +++ b/src/backends/octave/octavesyntaxhelpobject.cpp @@ -1,73 +1,75 @@ /* 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 "octavesyntaxhelpobject.h" #include "session.h" #include "result.h" #include OctaveSyntaxHelpObject::OctaveSyntaxHelpObject(const QString& command, Cantor::Session* session): SyntaxHelpObject(command, session), m_expression(nullptr) { } void OctaveSyntaxHelpObject::fetchInformation() { if (session()->status() != Cantor::Session::Disable) { qDebug() << "Fetching syntax help for" << command(); QString expr = QString::fromLatin1("help('%1')").arg(command()); m_expression = session()->evaluateExpression(expr, Cantor::Expression::FinishingBehavior::DoNotDelete, true); connect(m_expression, &Cantor::Expression::statusChanged, this, &OctaveSyntaxHelpObject::fetchingDone); } else emit done(); } void OctaveSyntaxHelpObject::fetchingDone(Cantor::Expression::Status status) { switch(status) { case Cantor::Expression::Done: { - Cantor::Result* result = m_expression->result(); - if (result) - { - QString res = result->toHtml(); - res.remove(QLatin1String("
")); - res.remove(0, res.indexOf(QLatin1String("--"))); - setHtml(QLatin1Char(' ') + res.trimmed()); - } + Cantor::Result* result = m_expression->result(); + if (result) + { + QString res = result->toHtml(); + res.remove(QLatin1String("
")); + res.remove(0, res.indexOf(QLatin1String("--"))); + setHtml(QLatin1Char(' ') + res.trimmed()); + } + break; } case Cantor::Expression::Interrupted: case Cantor::Expression::Error: { qDebug() << "fetching expression finished with status" << (status == Cantor::Expression::Error? "Error" : "Interrupted"); break; } + default: return; } m_expression->deleteLater(); m_expression = nullptr; emit done(); } diff --git a/src/backends/python/pythonutils.h b/src/backends/python/pythonutils.h index 89b965fa..3299a3d6 100644 --- a/src/backends/python/pythonutils.h +++ b/src/backends/python/pythonutils.h @@ -1,33 +1,40 @@ /* 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) 2015 Minh Ngo */ #ifndef _PYTHONUTILS_H #define _PYTHONUTILS_H #include +#include inline QString fromSource(const QString& resourceName) { QFile text(resourceName); text.open(QIODevice::ReadOnly); - return QString::fromLatin1(text.readAll()); + if (text.open(QIODevice::ReadOnly)) + return QString::fromUtf8(text.readAll()); + else + { + qWarning() << "Cantor Python resource" << resourceName << "didn't open - something wrong"; + return QString(); + } } #endif diff --git a/src/cantor.cpp b/src/cantor.cpp index f3a51124..6fe7a1fa 100644 --- a/src/cantor.cpp +++ b/src/cantor.cpp @@ -1,705 +1,708 @@ /* 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 #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(); } CantorShell::~CantorShell() { if (m_recentProjectsAction) m_recentProjectsAction->saveEntries(KSharedConfig::openConfig()->group(QLatin1String("Recent Files"))); if (!m_newBackendActions.isEmpty()) { unplugActionList(QLatin1String("new_worksheet_with_backend_list")); qDeleteAll(m_newBackendActions); m_newBackendActions.clear(); } unplugActionList(QLatin1String("view_show_panel_list")); qDeleteAll(m_panels); m_panels.clear(); } 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); } if (!m_part->openUrl( url )) closeTab(m_tabWidget->currentIndex()); if (m_recentProjectsAction) m_recentProjectsAction->addUrl(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); m_recentProjectsAction = KStandardAction::openRecent(this, &CantorShell::load, actionCollection()); m_recentProjectsAction->setPriority(QAction::LowPriority); m_recentProjectsAction->loadEntries(KSharedConfig::openConfig()->group(QLatin1String("Recent Files"))); 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)") + 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 KPluginLoader loader(QLatin1String("cantorpart")); KPluginFactory* factory = loader.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("Failed to find the Cantor Part with error %1", loader.errorString())); 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); + if (part) + { + 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; } } if (m_tabWidget->count() == 0) setCaption(QString()); 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 (plugin->showOnStartup()) docker->show(); else docker->hide(); } else { if (m_pluginsVisibility[m_part].contains(plugin->name())) docker->show(); else docker->hide(); } if(last!=nullptr) tabifyDockWidget(last, docker); last=docker; connect(plugin, &Cantor::PanelPlugin::visibilityRequested, this, &CantorShell::pluginVisibilityRequested); 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")); qDeleteAll(m_newBackendActions); m_newBackendActions.clear(); 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())); m_newBackendActions << action; } plugActionList(QLatin1String("new_worksheet_with_backend_list"), m_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; } void CantorShell::pluginVisibilityRequested() { Cantor::PanelPlugin* plugin = static_cast(sender()); for (QDockWidget* docker: m_panels) { if (plugin->name() == docker->windowTitle()) { if (docker->isHidden()) docker->show(); docker->raise(); } } } void CantorShell::onWorksheetSave(const QUrl& url) { if (m_recentProjectsAction) m_recentProjectsAction->addUrl(url); } diff --git a/src/cantor_part.cpp b/src/cantor_part.cpp index 57a59147..ae9c3989 100644 --- a/src/cantor_part.cpp +++ b/src/cantor_part.cpp @@ -1,1000 +1,1002 @@ /* 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))); if (m_worksheet->mathRenderer()->mathRenderAvailable()) { m_embeddedMath= new KToggleAction(i18n("Embedded Math"), collection); m_embeddedMath->setChecked(Settings::self()->embeddedMathDefault()); collection->addAction(QLatin1String("enable_embedded_math"), m_embeddedMath); connect(m_embeddedMath, SIGNAL(toggled(bool)), m_worksheet, SLOT(enableEmbeddedMath(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); emit worksheetSave(QUrl::fromLocalFile(localFilePath())); return true; } void CantorPart::fileSaveAs() { // this slot is called whenever the File->Save As menu is selected 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(); + if (e) + filter+=QLatin1String(";;")+e->scriptFileFilter(); } } QString selectedFilter; QString file_name = QFileDialog::getSaveFileName(widget(), i18n("Save as"), QString(), filter, &selectedFilter); if (file_name.isEmpty()) return; static const QString jupyterExtension = QLatin1String(".ipynb"); static const QString cantorExtension = QLatin1String(".cws"); // Append file extension, if it isn't specified // And change filter, if it specified to supported extension if (file_name.contains(QLatin1String("."))) { if (file_name.endsWith(cantorExtension)) selectedFilter = worksheetFilter; else if (file_name.endsWith(jupyterExtension)) selectedFilter = notebookFilter; } else { if (selectedFilter == worksheetFilter) file_name += cantorExtension; else if (selectedFilter == notebookFilter) file_name += jupyterExtension; } //depending on user's selection, save as a worksheet, as a Jupyter notebook or as a plain script file if (selectedFilter == worksheetFilter) { m_worksheet->setType(Worksheet::CantorWorksheet); const QUrl& url = QUrl::fromLocalFile(file_name); saveAs(url); emit worksheetSave(url); } else if (selectedFilter == notebookFilter) { m_worksheet->setType(Worksheet::JupyterNotebook); const QUrl& url = QUrl::fromLocalFile(file_name); saveAs(url); emit worksheetSave(url); } 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 // I don't know, that should I do with "No" with "Don't ask me again" // So hide warning only on "Yes" Settings::self()->setWarnAboutSessionRestart( KMessageBox::shouldBeShownYesNo(QLatin1String("WarnAboutSessionRestart"), tmp) || rc == KMessageBox::ButtonCode::No ); Settings::self()->save(); restart = rc == KMessageBox::ButtonCode::Yes; } else { restart = true; } 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(); + Q_UNUSED(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(); + Q_UNUSED(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)); + if (scriptE) + 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/lib/epsrenderer.cpp b/src/lib/epsrenderer.cpp index 9331d6b4..80194d60 100644 --- a/src/lib/epsrenderer.cpp +++ b/src/lib/epsrenderer.cpp @@ -1,162 +1,167 @@ /* 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 "epsrenderer.h" #include #ifdef LIBSPECTRE_FOUND #include "libspectre/spectre.h" #endif #include #include using namespace Cantor; class Cantor::EpsRendererPrivate{ public: double scale{1}; bool useHighRes{false}; }; EpsRenderer::EpsRenderer() : d(new EpsRendererPrivate()) { } +EpsRenderer::~EpsRenderer() +{ + delete d; +} + void EpsRenderer::setScale(qreal scale) { d->scale = scale; } qreal EpsRenderer::scale() { return d->scale; } void EpsRenderer::useHighResolution(bool b) { d->useHighRes = b; } QTextImageFormat EpsRenderer::render(QTextDocument *document, const QUrl &url) { QTextImageFormat epsCharFormat; QUrl internal; internal.setScheme(QLatin1String("internal")); QString path = QUuid::createUuid().toString(); // Remove { and } path.remove(0, 1); path.chop(1); internal.setPath(path); QSizeF s = renderToResource(document, url, internal); if(s.isValid()) { epsCharFormat.setName(internal.url()); epsCharFormat.setWidth(s.width()); epsCharFormat.setHeight(s.height()); } return epsCharFormat; } QTextImageFormat EpsRenderer::render(QTextDocument *document, const Cantor::LatexRenderer* latex) { QTextImageFormat format = render(document, QUrl::fromLocalFile(latex->imagePath())); if (!format.name().isEmpty()) { format.setProperty(CantorFormula, latex->method()); format.setProperty(ImagePath, latex->imagePath()); format.setProperty(Code, latex->latexCode()); } return format; } QSizeF EpsRenderer::renderToResource(QTextDocument *document, const QUrl &url, const QUrl& internal) { QSizeF size; QImage img = renderToImage(url, &size); qDebug() << internal; document->addResource(QTextDocument::ImageResource, internal, QVariant(img) ); return size; } QImage EpsRenderer::renderToImage(const QUrl& url, double scale, bool useHighRes, QSizeF* size) { #ifdef LIBSPECTRE_FOUND SpectreDocument* doc = spectre_document_new(); SpectreRenderContext* rc = spectre_render_context_new(); qDebug() << "rendering eps file: " << url; QByteArray local_file = url.toLocalFile().toUtf8(); spectre_document_load(doc, local_file.data()); bool isEps = spectre_document_is_eps(doc); if (!isEps) qDebug() << "Error: spectre document is not eps! It means, that url is invalid"; int wdoc, hdoc; qreal w, h; double realScale; spectre_document_get_page_size(doc, &wdoc, &hdoc); if(useHighRes) { realScale = 1.2*4.0; //1.2 scaling factor, to make it look nice, 4x for high resolution w = 1.2 * wdoc; h = 1.2 * hdoc; } else { realScale=1.8*scale; w = 1.8 * wdoc; h = 1.8 * hdoc; } qDebug()<<"scale: "<scale, d->useHighRes, size); } diff --git a/src/lib/epsrenderer.h b/src/lib/epsrenderer.h index 9b7dea9c..5a7e9df8 100644 --- a/src/lib/epsrenderer.h +++ b/src/lib/epsrenderer.h @@ -1,66 +1,66 @@ /* 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 EPSRENDERER_H #define EPSRENDERER_H #include #include #include #include #include #include "latexrenderer.h" namespace Cantor { class EpsRendererPrivate; class CANTOR_EXPORT EpsRenderer { public: EpsRenderer(); - ~EpsRenderer() = default; + ~EpsRenderer(); enum FormulaProperties {CantorFormula = 1, ImagePath = 2, Code = 3, Delimiter = 4}; enum FormulaType {LatexFormula = Cantor::LatexRenderer::LatexMethod, MmlFormula = Cantor::LatexRenderer::MmlMethod}; QTextImageFormat render(QTextDocument *document, const QUrl& url); QTextImageFormat render(QTextDocument *document, const Cantor::LatexRenderer* latex); void setScale(qreal scale); qreal scale(); void useHighResolution(bool b); QSizeF renderToResource(QTextDocument *document, const QUrl& url, const QUrl& internal); QImage renderToImage(const QUrl& url, QSizeF* size = nullptr); static QImage renderToImage(const QUrl& url, double scale, bool useHighRes, QSizeF* size = nullptr); private: EpsRendererPrivate* d; }; } #endif //EPSRENDERER_H diff --git a/src/lib/expression.cpp b/src/lib/expression.cpp index 5979398a..164225e7 100644 --- a/src/lib/expression.cpp +++ b/src/lib/expression.cpp @@ -1,308 +1,309 @@ /* 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 "expression.h" #include "latexrenderer.h" using namespace Cantor; #include #include "session.h" #include "result.h" #include "textresult.h" #include "imageresult.h" #include "latexresult.h" #include "settings.h" #include #include #include #include #include #include class Cantor::ExpressionPrivate { public: ExpressionPrivate() : id(-1), status(Expression::Done), session(nullptr), finishingBehavior(Expression::DoNotDelete), internal(false), fileWatcher(nullptr) { } int id; QString command; QString error; QList information; QVector results; Expression::Status status; Session* session; Expression::FinishingBehavior finishingBehavior; bool internal; QFileSystemWatcher* fileWatcher; }; static const QString tex=QLatin1String("\\documentclass[12pt,fleqn]{article} \n "\ "\\usepackage{latexsym,amsfonts,amssymb,ulem} \n "\ "\\usepackage[dvips]{graphicx} \n "\ "\\setlength\\textwidth{5in} \n "\ "\\setlength{\\parindent}{0pt} \n "\ "%1 \n "\ "\\pagestyle{empty} \n "\ "\\begin{document} \n "\ "%2 \n "\ "\\end{document}\n"); Expression::Expression( Session* session, bool internal ) : QObject( session ), d(new ExpressionPrivate) { d->session=session; d->internal = internal; if (!internal && session) d->id=session->nextExpressionId(); else d->id = -1; } Expression::Expression( Session* session, bool internal, int id ) : QObject( session ), d(new ExpressionPrivate) { d->session = session; d->internal = internal; d->id = id; } Expression::~Expression() { qDeleteAll(d->results); if (d->fileWatcher) delete d->fileWatcher; delete d; } void Expression::setCommand(const QString& command) { d->command=command; } QString Expression::command() { return d->command; } QString Expression::internalCommand() { return d->command; } void Expression::setErrorMessage(const QString& error) { d->error=error; } QString Expression::errorMessage() { return d->error; } void Expression::setResult(Result* result) { clearResults(); addResult(result); } void Expression::addResult(Result* result) { if(result!=nullptr) { qDebug()<<"setting result to a type "<type()<<" result"; #ifdef WITH_EPS //If it's text, and latex typesetting is enabled, render it if ( session() && session()->isTypesettingEnabled()&& result->type()==TextResult::Type && - dynamic_cast(result)->format()==TextResult::LatexFormat && + static_cast(result)->format()==TextResult::LatexFormat && !result->toHtml().trimmed().isEmpty() && finishingBehavior()!=DeleteOnFinish && !isInternal() ) { renderResultAsLatex(result); return; } #endif } d->results << result; emit gotResult(); } void Expression::clearResults() { qDeleteAll(d->results); d->results.clear(); emit resultsCleared(); } void Expression::removeResult(Result* result) { int index = d->results.indexOf(result); d->results.remove(index); delete result; emit resultRemoved(index); } void Expression::replaceResult(int index, Result* result) { if (result) { //insert the new result d->results.insert(index, result); //delete the previous result Result* oldResult = d->results.at(index+1); d->results.remove(index+1); delete oldResult; //notify about the replacement emit resultReplaced(index); } } Result* Expression::result() { if (!d->results.isEmpty()) return d->results.first(); else return nullptr; } const QVector& Expression::results() const { return d->results; } void Expression::setStatus(Expression::Status status) { d->status=status; emit statusChanged(status); bool isFinished = status == Expression::Done || status == Expression::Error || status == Expression::Interrupted; if(isFinished && d->finishingBehavior==Expression::DeleteOnFinish) deleteLater(); } Expression::Status Expression::status() { return d->status; } Session* Expression::session() { return d->session; } void Expression::renderResultAsLatex(Result* result) { LatexRenderer* renderer=new LatexRenderer(this); renderer->setLatexCode(result->data().toString().trimmed()); renderer->addHeader(additionalLatexHeaders()); connect(renderer, &LatexRenderer::done, [=] { latexRendered(renderer, result); }); connect(renderer, &LatexRenderer::error, [=] { latexRendered(renderer, result); }); renderer->render(); } void Expression::latexRendered(LatexRenderer* renderer, Result* result) { qDebug()<<"rendered a result to "<imagePath(); //replace the textresult with the rendered latex image result //ImageResult* latex=new ImageResult( d->latexFilename ); if(renderer->renderingSuccessful()) { if (result->type() == TextResult::Type) { - TextResult* r=dynamic_cast(result); + TextResult* r = static_cast(result); LatexResult* latex=new LatexResult(r->data().toString().trimmed(), QUrl::fromLocalFile(renderer->imagePath()), r->plain()); addResult( latex ); } else if (result->type() == LatexResult::Type) { - LatexResult* previousLatexResult=dynamic_cast(result); + LatexResult* previousLatexResult = static_cast(result); LatexResult* latex=new LatexResult(previousLatexResult->data().toString().trimmed(), QUrl::fromLocalFile(renderer->imagePath()), previousLatexResult->plain()); addResult( latex ); } }else { //if rendering with latex was not successful, just use the plain text version //if available TextResult* r=dynamic_cast(result); - addResult(new TextResult(r->plain())); + if (r) + addResult(new TextResult(r->plain())); qDebug()<<"error rendering latex: "<errorMessage(); } delete result; renderer->deleteLater(); } void Expression::addInformation(const QString& information) { d->information.append(information); } QString Expression::additionalLatexHeaders() { return QString(); } QFileSystemWatcher* Expression::fileWatcher() { if (!d->fileWatcher) d->fileWatcher = new QFileSystemWatcher(); return d->fileWatcher; } int Expression::id() { return d->id; } void Expression::setId(int id) { d->id=id; emit idChanged(); } void Expression::setFinishingBehavior(Expression::FinishingBehavior behavior) { d->finishingBehavior=behavior; } Expression::FinishingBehavior Expression::finishingBehavior() { return d->finishingBehavior; } bool Expression::isInternal() { return d->internal; } diff --git a/src/lib/helpresult.cpp b/src/lib/helpresult.cpp index d74800fc..9c838449 100644 --- a/src/lib/helpresult.cpp +++ b/src/lib/helpresult.cpp @@ -1,88 +1,93 @@ /* 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; } +Cantor::HelpResult::~HelpResult() +{ + delete d; +} + 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 e12d543f..29c5b7ad 100644 --- a/src/lib/helpresult.h +++ b/src/lib/helpresult.h @@ -1,57 +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; + ~HelpResult() override; 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/htmlresult.cpp b/src/lib/htmlresult.cpp index 631f4892..18b8cf0e 100644 --- a/src/lib/htmlresult.cpp +++ b/src/lib/htmlresult.cpp @@ -1,182 +1,184 @@ /* 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) 2019 Sirgienko Nikita */ #include "htmlresult.h" #include #include #include #include #include #include "jupyterutils.h" using namespace Cantor; class Cantor::HtmlResultPrivate { public: QString html; QString plain; std::map alternatives; // Useful only for Jupyter, it think Cantor::HtmlResult::Format format{Cantor::HtmlResult::Html}; }; HtmlResult::HtmlResult(const QString& html, const QString& plain, const std::map& alternatives) : d(new HtmlResultPrivate()) { d->html = html; d->plain = plain; d->alternatives = alternatives; } HtmlResult::~HtmlResult() { delete d; } QString HtmlResult::toHtml() { switch(d->format) { case HtmlResult::Html: return d->html; case HtmlResult::HtmlSource: return QStringLiteral("
    ") + d->html.toHtmlEscaped() + QStringLiteral("
    "); case HtmlResult::PlainAlternative: return QStringLiteral("
    ") + d->plain.toHtmlEscaped() + QStringLiteral("
    "); default: return QString(); } } QVariant Cantor::HtmlResult::data() { return d->html; } QString Cantor::HtmlResult::plain() { return d->plain; } void Cantor::HtmlResult::setFormat(HtmlResult::Format format) { d->format = format; } HtmlResult::Format Cantor::HtmlResult::format() { return d->format; } int Cantor::HtmlResult::type() { return HtmlResult::Type; } QString Cantor::HtmlResult::mimeType() { return QStringLiteral("text/html"); } QDomElement Cantor::HtmlResult::toXml(QDomDocument& doc) { QDomElement e=doc.createElement(QStringLiteral("Result")); e.setAttribute(QStringLiteral("type"), QStringLiteral("html")); switch(d->format) { case HtmlResult::HtmlSource: e.setAttribute(QStringLiteral("format"), QStringLiteral("htmlSource")); + break; case HtmlResult::PlainAlternative: e.setAttribute(QStringLiteral("format"), QStringLiteral("plain")); + break; // Html format used by default, so don't set it default: break; } QDomElement plainE = doc.createElement(QStringLiteral("Plain")); plainE.appendChild(doc.createTextNode(d->plain)); e.appendChild(plainE); QDomElement htmlE = doc.createElement(QStringLiteral("Html")); htmlE.appendChild(doc.createTextNode(d->html)); e.appendChild(htmlE); for (auto iter = d->alternatives.begin(); iter != d->alternatives.end(); iter++) { QJsonDocument jsonDoc; QJsonObject obj; obj.insert(QLatin1String("root"), iter->second); jsonDoc.setObject(obj); QDomElement content = doc.createElement(QStringLiteral("Alternative")); content.setAttribute(QStringLiteral("key"), iter->first); content.appendChild(doc.createTextNode(QString::fromUtf8(jsonDoc.toJson()))); e.appendChild(content); } return e; } QJsonValue Cantor::HtmlResult::toJupyterJson() { QJsonObject root; if (executionIndex() != -1) { root.insert(QLatin1String("output_type"), QLatin1String("execute_result")); root.insert(QLatin1String("execution_count"), executionIndex()); } else root.insert(QLatin1String("output_type"), QLatin1String("display_data")); QJsonObject data; data.insert(QLatin1String("text/html"), JupyterUtils::toJupyterMultiline(d->html)); if (!d->plain.isEmpty()) data.insert(QLatin1String("text/plain"), JupyterUtils::toJupyterMultiline(d->plain)); for (auto iter = d->alternatives.begin(); iter != d->alternatives.end(); iter++) data.insert(iter->first, iter->second); root.insert(QLatin1String("data"), data); root.insert(QLatin1String("metadata"), jupyterMetadata()); return root; } void Cantor::HtmlResult::save(const QString& filename) { QFile file(filename); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) return; QTextStream stream(&file); stream<html; file.close(); } diff --git a/src/lib/textresult.cpp b/src/lib/textresult.cpp index a8efa855..79a0ab73 100644 --- a/src/lib/textresult.cpp +++ b/src/lib/textresult.cpp @@ -1,232 +1,232 @@ /* 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; - bool isStderr; + bool isStderr{false}; }; 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 "<isStderr); if (d->format == LatexFormat) e.setAttribute(QStringLiteral("format"), QStringLiteral("latex")); QDomText txt=doc.createTextNode(data().toString()); e.appendChild(txt); return e; } QJsonValue Cantor::TextResult::toJupyterJson() { QJsonObject root; switch (d->format) { case PlainTextFormat: { if (executionIndex() != -1) { root.insert(QLatin1String("output_type"), QLatin1String("execute_result")); root.insert(QLatin1String("execution_count"), executionIndex()); QJsonObject data; data.insert(QLatin1String("text/plain"), jupyterText(d->data)); root.insert(QLatin1String("data"), data); root.insert(QLatin1String("metadata"), jupyterMetadata()); } else { root.insert(QLatin1String("output_type"), QLatin1String("stream")); if (d->isStderr) root.insert(QLatin1String("name"), QLatin1String("stderr")); else root.insert(QLatin1String("name"), QLatin1String("stdout")); // Jupyter don't support a few text result (it merges them into one text), // so add additional \n to end // See https://github.com/jupyter/notebook/issues/4699 root.insert(QLatin1String("text"), jupyterText(d->data, true)); } break; } case LatexFormat: { if (executionIndex() != -1) { root.insert(QLatin1String("output_type"), QLatin1String("execute_result")); root.insert(QLatin1String("execution_count"), executionIndex()); } else root.insert(QLatin1String("output_type"), QLatin1String("display_data")); QJsonObject data; data.insert(QLatin1String("text/latex"), jupyterText(d->data)); data.insert(QLatin1String("text/plain"), jupyterText(d->plain)); root.insert(QLatin1String("data"), data); root.insert(QLatin1String("metadata"), jupyterMetadata()); break; } } 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(); } QJsonArray TextResult::jupyterText(const QString& text, bool addEndNewLine) { QJsonArray array; const QStringList& lines = text.split(QLatin1Char('\n')); for (int i = 0; i < lines.size(); i++) { QString line = lines[i]; if (i != lines.size() - 1 || addEndNewLine) line.append(QLatin1Char('\n')); array.append(line); } return array; } bool Cantor::TextResult::isStderr() const { return d->isStderr; } void Cantor::TextResult::setStdErr(bool value) { d->isStderr = value; } diff --git a/src/panelplugins/variablemgr/variablemanagerwidget.cpp b/src/panelplugins/variablemgr/variablemanagerwidget.cpp index e7e38c39..9fb8bb45 100644 --- a/src/panelplugins/variablemgr/variablemanagerwidget.cpp +++ b/src/panelplugins/variablemgr/variablemanagerwidget.cpp @@ -1,186 +1,201 @@ /* 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) 2010 Alexander Rieder */ #include "variablemanagerwidget.h" #include #include #include #include #include #include #include #include "session.h" #include "extension.h" #include "backend.h" #include "ui_newvardlg.h" VariableManagerWidget::VariableManagerWidget(Cantor::Session* session, QWidget* parent) : QWidget(parent), m_session(nullptr), m_model(nullptr), m_table(new QTreeView(this)) { QVBoxLayout* layout=new QVBoxLayout(this); layout->addWidget(m_table, 1); m_table->setRootIsDecorated(false); QHBoxLayout* btnLayout=new QHBoxLayout(); int size=KIconLoader::global()->currentSize(KIconLoader::MainToolbar); QToolButton* m_newBtn=new QToolButton(this); m_newBtn->setIcon(QIcon::fromTheme(QLatin1String("document-new"))); m_newBtn->setToolTip(i18n("Add new variable")); m_newBtn->setIconSize(QSize(size, size)); connect(m_newBtn, &QToolButton::clicked, this, &VariableManagerWidget::newVariable); btnLayout->addWidget(m_newBtn); QToolButton* m_loadBtn=new QToolButton(this); m_loadBtn->setIcon(QIcon::fromTheme(QLatin1String("document-open"))); m_loadBtn->setToolTip(i18n("Load Variables")); m_loadBtn->setIconSize(QSize(size, size)); connect(m_loadBtn, &QToolButton::clicked, this, &VariableManagerWidget::load); btnLayout->addWidget(m_loadBtn); QToolButton* m_saveBtn=new QToolButton(this); m_saveBtn->setIcon(QIcon::fromTheme(QLatin1String("document-save"))); m_saveBtn->setToolTip(i18n("Store Variables")); m_saveBtn->setIconSize(QSize(size, size)); connect(m_saveBtn, &QToolButton::clicked, this, &VariableManagerWidget::save); btnLayout->addWidget(m_saveBtn); QToolButton* m_clearBtn=new QToolButton(this); m_clearBtn->setIcon(QIcon::fromTheme(QLatin1String("edit-clear"))); m_clearBtn->setToolTip(i18n("Clear Variables")); m_clearBtn->setIconSize(QSize(size, size)); connect(m_clearBtn, &QToolButton::clicked, this, &VariableManagerWidget::clearVariables); btnLayout->addWidget(m_clearBtn); layout->addLayout(btnLayout); setSession(session); //check for the methods the backend actually supports, and disable the buttons accordingly Cantor::VariableManagementExtension* ext= dynamic_cast(m_session->backend()->extension(QLatin1String("VariableManagementExtension"))); - if(ext->loadVariables(QString()).isNull()) - m_loadBtn->setDisabled(true); - if(ext->saveVariables(QString()).isNull()) - m_saveBtn->setDisabled(true); - if(ext->addVariable(QString(), QString()).isNull()) - m_newBtn->setDisabled(true); - if(ext->clearVariables().isNull()) - m_clearBtn->setDisabled(true); + if (ext) + { + if(ext->loadVariables(QString()).isNull()) + m_loadBtn->setDisabled(true); + if(ext->saveVariables(QString()).isNull()) + m_saveBtn->setDisabled(true); + if(ext->addVariable(QString(), QString()).isNull()) + m_newBtn->setDisabled(true); + if(ext->clearVariables().isNull()) + m_clearBtn->setDisabled(true); + } } void VariableManagerWidget::setSession(Cantor::Session* session) { m_session=session; if(session) { m_model=session->variableDataModel(); if(m_table) m_table->setModel(m_model); } } void VariableManagerWidget::clearVariables() { int btn=KMessageBox::questionYesNo(this, i18n("Are you sure you want to remove all variables?"), i18n("Confirmation - Cantor")); if(btn==KMessageBox::Yes) { m_model->removeRows(0, m_model->rowCount()); //evaluate the "clear" command Cantor::VariableManagementExtension* ext= dynamic_cast(m_session->backend()->extension(QLatin1String("VariableManagementExtension"))); - const QString& cmd=ext->clearVariables(); - emit runCommand(cmd); + + if (ext) + { + const QString& cmd=ext->clearVariables(); + emit runCommand(cmd); + } //HACK? should the model detect that this happened on its own? //inform the model that all variables have been removed. //Do so by trying to evaluate the clearVariables slot of //DefaultVariableModel. If our model isn't one of those, //this call will just do nothing. QMetaObject::invokeMethod(m_model, "clearVariables", Qt::QueuedConnection); } } void VariableManagerWidget::save() { const QString file=QFileDialog::getSaveFileName(this, i18n("Save"), QString(), QString()); if (file.trimmed().isEmpty()) return; Cantor::VariableManagementExtension* ext= dynamic_cast(m_session->backend()->extension(QLatin1String("VariableManagementExtension"))); - const QString& cmd=ext->saveVariables(file); - emit runCommand(cmd); + if (ext) + { + const QString& cmd=ext->saveVariables(file); + emit runCommand(cmd); + } } void VariableManagerWidget::load() { const QString file=QFileDialog::getOpenFileName(this, i18n("Load file"), QString(), QString()); if (file.trimmed().isEmpty()) return; Cantor::VariableManagementExtension* ext= dynamic_cast(m_session->backend()->extension(QLatin1String("VariableManagementExtension"))); - const QString& cmd=ext->loadVariables(file); - emit runCommand(cmd); + if (ext) + { + const QString& cmd=ext->loadVariables(file); + emit runCommand(cmd); + } } void VariableManagerWidget::newVariable() { QPointer dlg=new QDialog(this); QWidget *widget=new QWidget(dlg); Ui::NewVariableDialogBase base; base.setupUi(widget); QVBoxLayout *mainLayout = new QVBoxLayout; dlg->setLayout(mainLayout); base.buttonBox->button(QDialogButtonBox::Ok)->setIcon(QApplication::style()->standardIcon(QStyle::SP_DialogOkButton)); base.buttonBox->button(QDialogButtonBox::Cancel)->setIcon(QApplication::style()->standardIcon(QStyle::SP_DialogCancelButton)); connect(base.buttonBox, SIGNAL(accepted()), dlg, SLOT(accept()) ); connect(base.buttonBox, SIGNAL(rejected()), dlg, SLOT(reject()) ); mainLayout->addWidget(widget); if( dlg->exec()) { const QString& name=base.name->text(); const QString& val=base.value->text(); Cantor::VariableManagementExtension* ext= dynamic_cast(m_session->backend()->extension(QLatin1String("VariableManagementExtension"))); - const QString& cmd=ext->addVariable(name, val); - - emit runCommand(cmd); + if (ext) + { + const QString& cmd=ext->addVariable(name, val); + emit runCommand(cmd); + } } delete dlg; } diff --git a/src/textresultitem.cpp b/src/textresultitem.cpp index be93f21d..d1370c7e 100644 --- a/src/textresultitem.cpp +++ b/src/textresultitem.cpp @@ -1,247 +1,247 @@ /* 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 "textresultitem.h" #include "commandentry.h" #include "lib/result.h" #include "lib/textresult.h" #include "lib/latexresult.h" #include "lib/epsrenderer.h" #include "lib/mimeresult.h" #include "lib/htmlresult.h" #include "mathrendertask.h" #include "config-cantor.h" #include #include #include #include #include TextResultItem::TextResultItem(QGraphicsObject* parent, Cantor::Result* result) : WorksheetTextItem(parent), ResultItem(result) { setTextInteractionFlags(Qt::TextSelectableByMouse); update(); // So useful behaviour: // If we have HtmlResult, but after setting we have empty document // So show Plain version - it more useful // We do it here, because we need it one if (document()->characterCount() && document()->characterAt(0) == QChar::ParagraphSeparator) { Cantor::HtmlResult* hr = static_cast(m_result); hr->setFormat(Cantor::HtmlResult::PlainAlternative); setHtml(hr->toHtml()); } } double TextResultItem::setGeometry(double x, double y, double w) { return WorksheetTextItem::setGeometry(x, y, w); } void TextResultItem::populateMenu(QMenu* menu, QPointF pos) { QAction * copy = KStandardAction::copy(this, SLOT(copy()), menu); if (!textCursor().hasSelection()) copy->setEnabled(false); menu->addAction(copy); ResultItem::addCommonActions(this, menu); Cantor::Result* res = result(); if (res->type() == Cantor::LatexResult::Type) { QAction* showCodeAction = nullptr; - Cantor::LatexResult* lres = dynamic_cast(res); + Cantor::LatexResult* lres = static_cast(res); if (lres->isCodeShown()) showCodeAction = menu->addAction(i18n("Show Rendered")); else showCodeAction = menu->addAction(i18n("Show Code")); connect(showCodeAction, &QAction::triggered, this, &TextResultItem::toggleLatexCode); } else if (res->type() == Cantor::HtmlResult::Type) { Cantor::HtmlResult* hres = static_cast(res); switch (hres->format()) { case Cantor::HtmlResult::Html: connect(menu->addAction(i18n("Show HTML Code")), &QAction::triggered, this, &TextResultItem::showHtmlSource); if (!hres->plain().isEmpty()) connect(menu->addAction(i18n("Show Plain Alternative")), &QAction::triggered, this, &TextResultItem::showPlain); break; case Cantor::HtmlResult::HtmlSource: connect(menu->addAction(i18n("Show Html")), &QAction::triggered, this, &TextResultItem::showHtml); if (!hres->plain().isEmpty()) connect(menu->addAction(i18n("Show Plain Alternative")), &QAction::triggered, this, &TextResultItem::showPlain); break; case Cantor::HtmlResult::PlainAlternative: connect(menu->addAction(i18n("Show HTML")), &QAction::triggered, this, &TextResultItem::showHtml); connect(menu->addAction(i18n("Show HTML Code")), &QAction::triggered, this, &TextResultItem::showHtmlSource); break; } } menu->addSeparator(); qDebug() << "populate Menu"; emit menuCreated(menu, mapToParent(pos)); } void TextResultItem::update() { Q_ASSERT( m_result->type() == Cantor::TextResult::Type || m_result->type() == Cantor::LatexResult::Type || m_result->type() == Cantor::MimeResult::Type || m_result->type() == Cantor::HtmlResult::Type ); switch(m_result->type()) { case Cantor::TextResult::Type: case Cantor::MimeResult::Type: case Cantor::HtmlResult::Type: setHtml(m_result->toHtml()); break; case Cantor::LatexResult::Type: - setLatex(dynamic_cast(m_result)); + setLatex(static_cast(m_result)); break; default: break; } } void TextResultItem::setLatex(Cantor::LatexResult* result) { QTextCursor cursor = textCursor(); cursor.movePosition(QTextCursor::Start); cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); QString latex = result->toLatex().trimmed(); if (latex.startsWith(QLatin1String("\\begin{eqnarray*}")) && latex.endsWith(QLatin1String("\\end{eqnarray*}"))) { latex = latex.mid(17); latex = latex.left(latex.size() - 15); } #ifdef WITH_EPS if (result->isCodeShown()) { if (latex.isEmpty()) cursor.removeSelectedText(); else cursor.insertText(latex); } else { QTextImageFormat format; if (!result->image().isNull() && worksheet()->epsRenderer()->scale() == 1.0) { cursor.insertText(QString(QChar::ObjectReplacementCharacter), toFormat(result->image(), latex)); } else { Cantor::EpsRenderer* renderer = qobject_cast(scene())->epsRenderer();; format = renderer->render(cursor.document(), result->url()); format.setProperty(Cantor::EpsRenderer::CantorFormula, Cantor::EpsRenderer::LatexFormula); format.setProperty(Cantor::EpsRenderer::Code, latex); format.setProperty(Cantor::EpsRenderer::Delimiter, QLatin1String("$$")); if(format.isValid()) cursor.insertText(QString(QChar::ObjectReplacementCharacter), format); else cursor.insertText(i18n("Cannot render Eps file. You may need additional packages")); } } #else cursor.insertText(QString(QChar::ObjectReplacementCharacter), toFormat(result->image(), latex)); #endif } double TextResultItem::width() const { return WorksheetTextItem::width(); } double TextResultItem::height() const { return WorksheetTextItem::height(); } void TextResultItem::toggleLatexCode() { - Cantor::LatexResult* lr = dynamic_cast(result()); + Cantor::LatexResult* lr = static_cast(result()); if(lr->isCodeShown()) lr->showRendered(); else lr->showCode(); parentEntry()->updateEntry(); } void TextResultItem::showHtml() { Cantor::HtmlResult* hr = static_cast(result()); hr->setFormat(Cantor::HtmlResult::Html); parentEntry()->updateEntry(); } void TextResultItem::showHtmlSource() { Cantor::HtmlResult* hr = static_cast(result()); hr->setFormat(Cantor::HtmlResult::HtmlSource); parentEntry()->updateEntry(); } void TextResultItem::showPlain() { Cantor::HtmlResult* hr = static_cast(result()); hr->setFormat(Cantor::HtmlResult::PlainAlternative); parentEntry()->updateEntry(); } void TextResultItem::saveResult() { Cantor::Result* res = result(); const QString& filename = QFileDialog::getSaveFileName(worksheet()->worksheetView(), i18n("Save result"), QString(), res->mimeType()); qDebug() << "saving result to " << filename; res->save(filename); } void TextResultItem::deleteLater() { WorksheetTextItem::deleteLater(); } QTextImageFormat TextResultItem::toFormat(const QImage& image, const QString& latex) { QTextImageFormat format; QUrl internal; internal.setScheme(QLatin1String("internal")); internal.setPath(MathRenderTask::genUuid()); document()->addResource(QTextDocument::ImageResource, internal, QVariant(image) ); format.setName(internal.url()); format.setProperty(Cantor::EpsRenderer::CantorFormula, Cantor::EpsRenderer::LatexFormula); //format.setProperty(Cantor::EpsRenderer::ImagePath, filename); format.setProperty(Cantor::EpsRenderer::Code, latex); format.setProperty(Cantor::EpsRenderer::Delimiter, QLatin1String("$$")); return format; } diff --git a/src/worksheet.cpp b/src/worksheet.cpp index 1893c996..c137dd4d 100644 --- a/src/worksheet.cpp +++ b/src/worksheet.cpp @@ -1,2253 +1,2256 @@ /* 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/jupyterutils.h" #include "lib/backend.h" #include "lib/extension.h" #include "lib/helpresult.h" #include "lib/session.h" #include "lib/defaulthighlighter.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; m_jupyterMetadata = nullptr; enableHighlighting(Settings::self()->highlightDefault()); enableCompletion(Settings::self()->completionDefault()); enableExpressionNumbering(Settings::self()->expressionNumberingDefault()); enableAnimations(Settings::self()->animationDefault()); enableEmbeddedMath(Settings::self()->embeddedMathDefault()); } 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; } if (m_jupyterMetadata) delete m_jupyterMetadata; } 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_mathRenderer.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_mathRenderer.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); m_mathRenderer.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() ? lastEntry()->size().height() + lastEntry()->y() : 0; 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() { if (!m_isLoadingFromFile) 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); setModified(); } 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(); if (current) current->showCompletion(); } WorksheetEntry* Worksheet::appendEntry(const int type, bool focus) { 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(); if (focus) { makeVisible(entry); focusEntry(entry); } setModified(); } 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(); setModified(); } 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(); setModified(); } 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; } bool Worksheet::embeddedMathEnabled() { return m_embeddedMathEnabled && m_mathRenderer.mathRenderAvailable(); } void Worksheet::enableEmbeddedMath(bool enable) { m_embeddedMathEnabled = 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(m_jupyterMetadata ? *m_jupyterMetadata : QJsonObject()); QJsonObject kernalInfo; if (m_session && m_session->backend()) kernalInfo = Cantor::JupyterUtils::getKernelspec(m_session->backend()); else kernalInfo.insert(QLatin1String("name"), m_backendName); metadata.insert(QLatin1String("kernelspec"), kernalInfo); root.insert(QLatin1String("metadata"), metadata); // Not sure, but it looks like we support nbformat version 4.5 root.insert(QLatin1String("nbformat"), 4); root.insert(QLatin1String("nbformat_minor"), 5); 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"; switch (m_type) { case CantorWorksheet: { KZip zipFile( device ); 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; } 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(); + if (e) + { + 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 ) { qDebug() << "loading worksheet" << 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(); // Don't add focus on load if (tag == QLatin1String("Expression")) { entry = appendEntry(CommandEntry::Type, false); entry->setContent(expressionChild, archive); } else if (tag == QLatin1String("Text")) { entry = appendEntry(TextEntry::Type, false); entry->setContent(expressionChild, archive); } else if (tag == QLatin1String("Markdown")) { entry = appendEntry(MarkdownEntry::Type, false); entry->setContent(expressionChild, archive); } else if (tag == QLatin1String("Latex")) { entry = appendEntry(LatexEntry::Type, false); entry->setContent(expressionChild, archive); } else if (tag == QLatin1String("PageBreak")) { entry = appendEntry(PageBreakEntry::Type, false); entry->setContent(expressionChild, archive); } else if (tag == QLatin1String("Image")) { entry = appendEntry(ImageEntry::Type, false); 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; int nbformatMajor, nbformatMinor; if (!Cantor::JupyterUtils::isJupyterNotebook(doc)) { // Two possiblities: old jupyter notebook (with another scheme) or just not a notebook at AlignLeft std::tie(nbformatMajor, nbformatMinor) = Cantor::JupyterUtils::getNbformatVersion(doc.object()); if (nbformatMajor == 0 && nbformatMinor == 0) { QApplication::restoreOverrideCursor(); showInvalidNotebookSchemeError(); } else { KMessageBox::error(worksheetView(), i18n("The file is old Jupyter notebook (found version %1.%2), which isn't supported by Cantor",nbformatMajor, nbformatMinor ), i18n("Cantor")); } return false; } QJsonObject notebookObject = doc.object(); std::tie(nbformatMajor, nbformatMinor) = Cantor::JupyterUtils::getNbformatVersion(notebookObject); if (QT_VERSION_CHECK(nbformatMajor, nbformatMinor, 0) < QT_VERSION_CHECK(4,0,0)) { QApplication::restoreOverrideCursor(); KMessageBox::error(worksheetView(), i18n("Cantor doesn't support import Jupyter notebooks with version lower that 4.0 (detected %1.%2)",nbformatMajor, nbformatMinor), i18n("Cantor")); return false; } const QJsonArray& cells = Cantor::JupyterUtils::getCells(notebookObject); const QJsonObject& metadata = Cantor::JupyterUtils::getMetadata(notebookObject); if (m_jupyterMetadata) delete m_jupyterMetadata; m_jupyterMetadata = new QJsonObject(metadata); const QJsonObject& kernalspec = metadata.value(QLatin1String("kernelspec")).toObject(); m_backendName = Cantor::JupyterUtils::getKernelName(kernalspec); if (kernalspec.isEmpty() || m_backendName.isEmpty()) { QApplication::restoreOverrideCursor(); showInvalidNotebookSchemeError(); return false; } 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) { 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"; WorksheetEntry* entry = nullptr; for (QJsonArray::const_iterator iter = cells.begin(); iter != cells.end(); iter++) { if (!Cantor::JupyterUtils::isJupyterCell(*iter)) { QApplication::restoreOverrideCursor(); QString explanation; if (iter->isObject()) explanation = i18n("an object with keys: %1", iter->toObject().keys().join(QLatin1String(", "))); else explanation = i18n("non object JSON value"); m_isLoadingFromFile = false; showInvalidNotebookSchemeError(i18n("found incorrect data (%1) that is not Jupyter cell", explanation)); return false; } const QJsonObject& cell = iter->toObject(); QString cellType = Cantor::JupyterUtils::getCellType(cell); if (cellType == QLatin1String("code")) { if (LatexEntry::isConvertableToLatexEntry(cell)) { entry = appendEntry(LatexEntry::Type, false); entry->setContentFromJupyter(cell); entry->evaluate(WorksheetEntry::InternalEvaluation); } else { entry = appendEntry(CommandEntry::Type, false); entry->setContentFromJupyter(cell); } } else if (cellType == QLatin1String("markdown")) { if (TextEntry::isConvertableToTextEntry(cell)) { entry = appendEntry(TextEntry::Type, false); entry->setContentFromJupyter(cell); } else { entry = appendEntry(MarkdownEntry::Type, false); entry->setContentFromJupyter(cell); entry->evaluate(WorksheetEntry::InternalEvaluation); } } else if (cellType == QLatin1String("raw")) { if (PageBreakEntry::isConvertableToPageBreakEntry(cell)) entry = appendEntry(PageBreakEntry::Type, false); else entry = appendEntry(TextEntry::Type, false); entry->setContentFromJupyter(cell); } if (m_readOnly && entry) { entry->setAcceptHoverEvents(false); entry = nullptr; } } if (m_readOnly) clearFocus(); m_isLoadingFromFile = false; enableHighlighting( m_highlighter!=nullptr || Settings::highlightDefault() ); emit loaded(); return true; } void Worksheet::showInvalidNotebookSchemeError(QString additionalInfo) { if (additionalInfo.isEmpty()) KMessageBox::error(worksheetView(), i18n("The file is not valid Jupyter notebook"), i18n("Cantor")); else KMessageBox::error(worksheetView(), i18n("Invalid Jupyter notebook scheme: %1", additionalInfo), 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(); } Cantor::EpsRenderer* Worksheet::epsRenderer() { return &m_epsRenderer; } MathRenderer* Worksheet::mathRenderer() { return &m_mathRenderer; } 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/worksheettextitem.cpp b/src/worksheettextitem.cpp index 2f5acd91..79e4a327 100644 --- a/src/worksheettextitem.cpp +++ b/src/worksheettextitem.cpp @@ -1,928 +1,928 @@ /* 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 "worksheettextitem.h" #include "worksheet.h" #include "worksheetentry.h" #include "lib/epsrenderer.h" #include "worksheetcursor.h" #include "extended_document.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include WorksheetTextItem::WorksheetTextItem(QGraphicsObject* parent, Qt::TextInteractionFlags ti) : QGraphicsTextItem(parent) { setDocument(new ExtendedDocument(this)); setTextInteractionFlags(ti); if (ti & Qt::TextEditable) { setCursor(Qt::IBeamCursor); connect(this, SIGNAL(sizeChanged()), parent, SLOT(recalculateSize())); } m_completionEnabled = false; m_completionActive = false; m_itemDragable = false; m_richTextEnabled = false; m_size = document()->size();; m_maxWidth = -1; setAcceptDrops(true); setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); connect(document(), SIGNAL(contentsChanged()), this, SLOT(testSize())); connect(this, SIGNAL(menuCreated(QMenu*,QPointF)), parent, SLOT(populateMenu(QMenu*,QPointF)), Qt::DirectConnection); connect(this, SIGNAL(deleteEntry()), parent, SLOT(startRemoving())); connect(this, &WorksheetTextItem::cursorPositionChanged, this, &WorksheetTextItem::updateRichTextActions); connect(document(), SIGNAL(undoAvailable(bool)), this, SIGNAL(undoAvailable(bool))); connect(document(), SIGNAL(redoAvailable(bool)), this, SIGNAL(redoAvailable(bool))); } WorksheetTextItem::~WorksheetTextItem() { if (worksheet() && this == worksheet()->lastFocusedTextItem()) worksheet()->updateFocusedTextItem(nullptr); if (worksheet() && m_maxWidth > 0 && width() > m_maxWidth) worksheet()->removeProtrusion(width() - m_maxWidth); } int WorksheetTextItem::type() const { return Type; } /* void WorksheetTextItem::setHeight() { m_height = height(); } */ void WorksheetTextItem::testSize() { qreal h = document()->size().height(); if (h != m_size.height()) { emit sizeChanged(); m_size.setHeight(h); } qreal w = document()->size().width(); if (w != m_size.width()) { if (m_maxWidth > 0) { qreal oldDiff = m_size.width() - m_maxWidth; qreal newDiff = w - m_maxWidth; if (w > m_maxWidth) { if (m_size.width() > m_maxWidth) worksheet()->updateProtrusion(oldDiff, newDiff); else worksheet()->addProtrusion(newDiff); } else if (m_size.width() > m_maxWidth) { worksheet()->removeProtrusion(oldDiff); } } m_size.setWidth(w); } } qreal WorksheetTextItem::setGeometry(qreal x, qreal y, qreal w, bool centered) { if (m_size.width() < w && centered) setPos(x + w/2 - m_size.width()/2, y); else setPos(x,y); qreal oldDiff = 0; if (m_maxWidth > 0 && width() > m_maxWidth) oldDiff = width() - m_maxWidth; m_maxWidth = w; setTextWidth(w); m_size = document()->size(); if (oldDiff) { if (m_size.width() > m_maxWidth) { qreal newDiff = m_size.width() - m_maxWidth; worksheet()->updateProtrusion(oldDiff, newDiff); } else { worksheet()->removeProtrusion(oldDiff); } } else if (m_size.width() > m_maxWidth) { qreal newDiff = m_size.width() - m_maxWidth; worksheet()->addProtrusion(newDiff); } return m_size.height(); } void WorksheetTextItem::populateMenu(QMenu* menu, QPointF pos) { qDebug() << "populate Menu"; QAction * cut = KStandardAction::cut(this, SLOT(cut()), menu); QAction * copy = KStandardAction::copy(this, SLOT(copy()), menu); QAction * paste = KStandardAction::paste(this, SLOT(paste()), menu); if (!textCursor().hasSelection()) { cut->setEnabled(false); copy->setEnabled(false); } if (QApplication::clipboard()->text().isEmpty()) { paste->setEnabled(false); } bool actionAdded = false; if (isEditable()) { menu->addAction(cut); actionAdded = true; } if (!m_itemDragable && (flags() & Qt::TextSelectableByMouse)) { menu->addAction(copy); actionAdded = true; } if (isEditable()) { menu->addAction(paste); actionAdded = true; } if (actionAdded) menu->addSeparator(); emit menuCreated(menu, mapToParent(pos)); } QKeyEvent* WorksheetTextItem::eventForStandardAction(KStandardAction::StandardAction actionID) { // there must be a better way to get the shortcut... QAction * action = KStandardAction::create(actionID, this, SLOT(copy()), this); QKeySequence keySeq = action->shortcut(); // we do not support key sequences with multiple keys here int code = keySeq[0]; const int ModMask = Qt::ShiftModifier | Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier; const int KeyMask = ~ModMask; QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, code & KeyMask, QFlags(code & ModMask)); delete action; return event; } void WorksheetTextItem::cut() { if (richTextEnabled()) { QKeyEvent* event = eventForStandardAction(KStandardAction::Cut); QApplication::sendEvent(worksheet(), event); delete event; } else { copy(); textCursor().removeSelectedText(); } } void WorksheetTextItem::paste() { if (richTextEnabled()) { QKeyEvent* event = eventForStandardAction(KStandardAction::Paste); QApplication::sendEvent(worksheet(), event); delete event; } else { textCursor().insertText(QApplication::clipboard()->text()); } } void WorksheetTextItem::copy() { if (richTextEnabled()) { QKeyEvent* event = eventForStandardAction(KStandardAction::Copy); QApplication::sendEvent(worksheet(), event); delete event; } else { if (!textCursor().hasSelection()) return; QString text = resolveImages(textCursor()); text.replace(QChar::ParagraphSeparator, QLatin1Char('\n')); text.replace(QChar::LineSeparator, QLatin1Char('\n')); QApplication::clipboard()->setText(text); } } void WorksheetTextItem::undo() { document()->undo(); } void WorksheetTextItem::redo() { document()->redo(); } void WorksheetTextItem::clipboardChanged() { if (isEditable()) emit pasteAvailable(!QApplication::clipboard()->text().isEmpty()); } void WorksheetTextItem::selectionChanged() { emit copyAvailable(textCursor().hasSelection()); if (isEditable()) emit cutAvailable(textCursor().hasSelection()); } QString WorksheetTextItem::resolveImages(const QTextCursor& cursor) { int start = cursor.selectionStart(); int end = cursor.selectionEnd(); const QString repl = QString(QChar::ObjectReplacementCharacter); QString result; QTextCursor cursor1 = textCursor(); cursor1.setPosition(start); QTextCursor cursor2 = document()->find(repl, cursor1); for (; !cursor2.isNull() && cursor2.selectionEnd() <= end; cursor2 = document()->find(repl, cursor1)) { cursor1.setPosition(cursor2.selectionStart(), QTextCursor::KeepAnchor); result += cursor1.selectedText(); QVariant var = cursor2.charFormat().property(Cantor::EpsRenderer::Delimiter); QString delim; if (var.isValid()) delim = var.value(); else delim = QLatin1String(""); result += delim + cursor2.charFormat().property(Cantor::EpsRenderer::Code).value() + delim; cursor1.setPosition(cursor2.selectionEnd()); } cursor1.setPosition(end, QTextCursor::KeepAnchor); result += cursor1.selectedText(); return result; } void WorksheetTextItem::setCursorPosition(const QPointF& pos) { QTextCursor cursor = cursorForPosition(pos); setTextCursor(cursor); emit cursorPositionChanged(cursor); //setLocalCursorPosition(mapFromParent(pos)); } QPointF WorksheetTextItem::cursorPosition() const { return mapToParent(localCursorPosition()); } void WorksheetTextItem::setLocalCursorPosition(const QPointF& pos) { int p = document()->documentLayout()->hitTest(pos, Qt::FuzzyHit); QTextCursor cursor = textCursor(); cursor.setPosition(p); setTextCursor(cursor); emit cursorPositionChanged(cursor); } QPointF WorksheetTextItem::localCursorPosition() const { QTextCursor cursor = textCursor(); QTextBlock block = cursor.block(); int p = cursor.position() - block.position(); QTextLine line = block.layout()->lineForTextPosition(p); if (!line.isValid()) // can this happen? return block.layout()->position(); return QPointF(line.cursorToX(p), line.y() + line.height()); } QRectF WorksheetTextItem::sceneCursorRect(QTextCursor cursor) const { return mapRectToScene(cursorRect(cursor)); } QRectF WorksheetTextItem::cursorRect(QTextCursor cursor) const { if (cursor.isNull()) cursor = textCursor(); QTextCursor startCursor = cursor; startCursor.setPosition(cursor.selectionStart()); QTextBlock block = startCursor.block(); if (!block.layout()) return mapRectToScene(boundingRect()); int p = startCursor.position() - block.position(); QTextLine line = block.layout()->lineForTextPosition(p); QRectF r1(line.cursorToX(p), line.y(), 1, line.height()+line.leading()); if (!cursor.hasSelection()) return r1; QTextCursor endCursor = cursor; endCursor.setPosition(cursor.selectionEnd()); block = endCursor.block(); p = endCursor.position() - block.position(); line = block.layout()->lineForTextPosition(p); QRectF r2(line.cursorToX(p), line.y(), 1, line.height()+line.leading()); if (r1.y() == r2.y()) return r1.united(r2); else return QRectF(x(), qMin(r1.y(), r2.y()), boundingRect().width(), qMax(r1.y() + r1.height(), r2.y() + r2.height())); } QTextCursor WorksheetTextItem::cursorForPosition(const QPointF& pos) const { QPointF lpos = mapFromParent(pos); int p = document()->documentLayout()->hitTest(lpos, Qt::FuzzyHit); QTextCursor cursor = textCursor(); cursor.setPosition(p); return cursor; } bool WorksheetTextItem::isEditable() { return textInteractionFlags() & Qt::TextEditable; } void WorksheetTextItem::setBackgroundColor(const QColor& color) { m_backgroundColor = color; } const QColor& WorksheetTextItem::backgroundColor() const { return m_backgroundColor; } bool WorksheetTextItem::richTextEnabled() { return m_richTextEnabled; } void WorksheetTextItem::enableCompletion(bool b) { m_completionEnabled = b; } void WorksheetTextItem::activateCompletion(bool b) { m_completionActive = b; } void WorksheetTextItem::setItemDragable(bool b) { m_itemDragable = b; } void WorksheetTextItem::enableRichText(bool b) { m_richTextEnabled = b; } void WorksheetTextItem::setFocusAt(int pos, qreal xCoord) { QTextCursor cursor = textCursor(); if (pos == TopLeft) { cursor.movePosition(QTextCursor::Start); } else if (pos == BottomRight) { cursor.movePosition(QTextCursor::End); } else { QTextLine line; if (pos == TopCoord) { line = document()->firstBlock().layout()->lineAt(0); } else { QTextLayout* layout = document()->lastBlock().layout(); qDebug() << document()->blockCount() << "blocks"; qDebug() << document()->lastBlock().lineCount() << "lines in last block"; line = layout->lineAt(document()->lastBlock().lineCount()-1); } qreal x = mapFromScene(xCoord, 0).x(); int p = line.xToCursor(x); cursor.setPosition(p); // Hack: The code for selecting the last line above does not work. // This is a workaround if (pos == BottomCoord) while (cursor.movePosition(QTextCursor::Down)) ; } setTextCursor(cursor); emit cursorPositionChanged(cursor); setFocus(); } Cantor::Session* WorksheetTextItem::session() { return worksheet()->session(); } void WorksheetTextItem::keyPressEvent(QKeyEvent *event) { switch (event->key()) { case Qt::Key_Left: if (event->modifiers() == Qt::NoModifier && textCursor().atStart()) { emit moveToPrevious(BottomRight, 0); qDebug()<<"Reached leftmost valid position"; return; } break; case Qt::Key_Right: if (event->modifiers() == Qt::NoModifier && textCursor().atEnd()) { emit moveToNext(TopLeft, 0); qDebug()<<"Reached rightmost valid position"; return; } break; case Qt::Key_Up: if (event->modifiers() == Qt::NoModifier && !textCursor().movePosition(QTextCursor::Up)) { qreal x = mapToScene(localCursorPosition()).x(); emit moveToPrevious(BottomCoord, x); qDebug()<<"Reached topmost valid position" << localCursorPosition().x(); return; } break; case Qt::Key_Down: if (event->modifiers() == Qt::NoModifier && !textCursor().movePosition(QTextCursor::Down)) { qreal x = mapToScene(localCursorPosition()).x(); emit moveToNext(TopCoord, x); qDebug()<<"Reached bottommost valid position" << localCursorPosition().x(); return; } break; case Qt::Key_Enter: case Qt::Key_Return: if (event->modifiers() == Qt::NoModifier && m_completionActive) { emit applyCompletion(); return; } break; case Qt::Key_Tab: qDebug() << "Tab"; break; default: break; } int p = textCursor().position(); bool b = textCursor().hasSelection(); QGraphicsTextItem::keyPressEvent(event); if (p != textCursor().position()) emit cursorPositionChanged(textCursor()); if (b != textCursor().hasSelection()) selectionChanged(); } bool WorksheetTextItem::sceneEvent(QEvent *event) { if (event->type() == QEvent::KeyPress) { // QGraphicsTextItem's TabChangesFocus feature prevents calls to // keyPressEvent for Tab, even when it's turned off. So we got to catch // that here. - QKeyEvent* kev = dynamic_cast(event); + QKeyEvent* kev = static_cast(event); if (kev->key() == Qt::Key_Tab && kev->modifiers() == Qt::NoModifier) { emit tabPressed(); return true; } else if ((kev->key() == Qt::Key_Tab && kev->modifiers() == Qt::ShiftModifier) || kev->key() == Qt::Key_Backtab) { emit backtabPressed(); return true; } } else if (event->type() == QEvent::ShortcutOverride) { - QKeyEvent* kev = dynamic_cast(event); + QKeyEvent* kev = static_cast(event); QKeySequence seq(kev->key() + kev->modifiers()); if (worksheet()->isShortcut(seq)) { qDebug() << "ShortcutOverride" << kev->key() << kev->modifiers(); kev->ignore(); return false; } } return QGraphicsTextItem::sceneEvent(event); } void WorksheetTextItem::focusInEvent(QFocusEvent *event) { QGraphicsTextItem::focusInEvent(event); //parentItem()->ensureVisible(QRectF(), 0, 0); WorksheetEntry* entry = qobject_cast(parentObject()); WorksheetCursor c(entry, this, textCursor()); // No need make the text item visible // if we just hide/show window, it it not necessary if (event->reason() != Qt::ActiveWindowFocusReason) worksheet()->makeVisible(c); worksheet()->updateFocusedTextItem(this); connect(QApplication::clipboard(), SIGNAL(dataChanged()), this, SLOT(clipboardChanged())); emit receivedFocus(this); emit cursorPositionChanged(textCursor()); } void WorksheetTextItem::focusOutEvent(QFocusEvent *event) { QGraphicsTextItem::focusOutEvent(event); emit cursorPositionChanged(QTextCursor()); } void WorksheetTextItem::mousePressEvent(QGraphicsSceneMouseEvent *event) { int p = textCursor().position(); bool b = textCursor().hasSelection(); QGraphicsTextItem::mousePressEvent(event); if (isEditable() && event->button() == Qt::MiddleButton && QApplication::clipboard()->supportsSelection() && !event->isAccepted()) event->accept(); if (m_itemDragable && event->button() == Qt::LeftButton) event->accept(); if (p != textCursor().position()) emit cursorPositionChanged(textCursor()); if (b != textCursor().hasSelection()) selectionChanged(); } void WorksheetTextItem::mouseMoveEvent(QGraphicsSceneMouseEvent* event) { const QPointF buttonDownPos = event->buttonDownPos(Qt::LeftButton); if (m_itemDragable && event->buttons() == Qt::LeftButton && contains(buttonDownPos) && (event->pos() - buttonDownPos).manhattanLength() >= QApplication::startDragDistance()) { ungrabMouse(); emit drag(mapToParent(buttonDownPos), mapToParent(event->pos())); event->accept(); } else { bool b = textCursor().hasSelection(); QGraphicsTextItem::mouseMoveEvent(event); if (b != textCursor().hasSelection()) selectionChanged(); } } void WorksheetTextItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { int p = textCursor().position(); // custom middle-click paste that does not copy rich text if (isEditable() && event->button() == Qt::MiddleButton && QApplication::clipboard()->supportsSelection() && !richTextEnabled()) { setLocalCursorPosition(mapFromScene(event->scenePos())); const QString& text = QApplication::clipboard()->text(QClipboard::Selection); textCursor().insertText(text); } else { QGraphicsTextItem::mouseReleaseEvent(event); } if (p != textCursor().position()) emit cursorPositionChanged(textCursor()); } void WorksheetTextItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) { QTextCursor cursor = textCursor(); const QChar repl = QChar::ObjectReplacementCharacter; if (!cursor.hasSelection()) { // We look at the current cursor and the next cursor for a // ObjectReplacementCharacter for (int i = 2; i; --i) { if (document()->characterAt(cursor.position()-1) == repl) { setTextCursor(cursor); emit doubleClick(); return; } cursor.movePosition(QTextCursor::NextCharacter); } } else if (cursor.selectedText().contains(repl)) { emit doubleClick(); return; } QGraphicsTextItem::mouseDoubleClickEvent(event); } void WorksheetTextItem::dragEnterEvent(QGraphicsSceneDragDropEvent* event) { if (isEditable() && event->mimeData()->hasFormat(QLatin1String("text/plain"))) { if (event->proposedAction() & (Qt::CopyAction | Qt::MoveAction)) { event->acceptProposedAction(); } else if (event->possibleActions() & Qt::CopyAction) { event->setDropAction(Qt::CopyAction); event->accept(); } else if (event->possibleActions() & Qt::MoveAction) { event->setDropAction(Qt::MoveAction); event->accept(); } else { event->ignore(); } } else { event->ignore(); } } void WorksheetTextItem::dragMoveEvent(QGraphicsSceneDragDropEvent* event) { if (isEditable() && event->mimeData()->hasFormat(QLatin1String("text/plain"))) setLocalCursorPosition(mapFromScene(event->scenePos())); } void WorksheetTextItem::dropEvent(QGraphicsSceneDragDropEvent* event) { if (isEditable()) { if (richTextEnabled() && event->mimeData()->hasFormat(QLatin1String("text/html"))) textCursor().insertHtml(event->mimeData()->html()); else textCursor().insertText(event->mimeData()->text()); event->accept(); } } void WorksheetTextItem::contextMenuEvent(QGraphicsSceneContextMenuEvent *event) { QMenu *menu = worksheet()->createContextMenu(); populateMenu(menu, event->pos()); menu->popup(event->screenPos()); } void WorksheetTextItem::insertTab() { QTextCursor cursor = textCursor(); cursor.clearSelection(); cursor.movePosition(QTextCursor::StartOfLine, QTextCursor::KeepAnchor); QString sel = cursor.selectedText(); bool spacesOnly = true; qDebug() << sel; for (QString::iterator it = sel.begin(); it != sel.end(); ++it) { if (! it->isSpace()) { spacesOnly = false; break; } } cursor.setPosition(cursor.selectionEnd()); if (spacesOnly) { while (document()->characterAt(cursor.position()) == QLatin1Char(' ')) cursor.movePosition(QTextCursor::NextCharacter); } QTextLayout *layout = textCursor().block().layout(); if (!layout) { cursor.insertText(QLatin1String(" ")); } else { cursor.movePosition(QTextCursor::StartOfLine, QTextCursor::KeepAnchor); int i = cursor.selectionEnd() - cursor.selectionStart(); i = ((i+4) & (~3)) - i; cursor.setPosition(cursor.selectionEnd()); QString insertBlankSpace = QLatin1String(" "); cursor.insertText(insertBlankSpace.repeated(i)); } setTextCursor(cursor); emit cursorPositionChanged(textCursor()); } void WorksheetTextItem::paint(QPainter* painter, const QStyleOptionGraphicsItem* o, QWidget* w) { if (m_backgroundColor.isValid()) { painter->setPen(QPen(Qt::NoPen)); painter->setBrush(m_backgroundColor); painter->drawRect(boundingRect()); } QGraphicsTextItem::paint(painter, o, w); } double WorksheetTextItem::width() const { return m_size.width(); } double WorksheetTextItem::height() const { return m_size.height(); } Worksheet* WorksheetTextItem::worksheet() { return qobject_cast(scene()); } WorksheetView* WorksheetTextItem::worksheetView() { return worksheet()->worksheetView(); } void WorksheetTextItem::clearSelection() { QTextCursor cursor = textCursor(); cursor.clearSelection(); setTextCursor(cursor); selectionChanged(); } bool WorksheetTextItem::isUndoAvailable() { return document()->isUndoAvailable(); } bool WorksheetTextItem::isRedoAvailable() { return document()->isRedoAvailable(); } bool WorksheetTextItem::isCutAvailable() { return isEditable() && textCursor().hasSelection(); } bool WorksheetTextItem::isCopyAvailable() { return !m_itemDragable && textCursor().hasSelection(); } bool WorksheetTextItem::isPasteAvailable() { return isEditable() && !QApplication::clipboard()->text().isEmpty(); } QTextCursor WorksheetTextItem::search(QString pattern, QTextDocument::FindFlags qt_flags, const WorksheetCursor& pos) { if (pos.isValid() && pos.textItem() != this) return QTextCursor(); QTextDocument* doc = document(); QTextCursor cursor; if (pos.isValid()) { cursor = doc->find(pattern, pos.textCursor(), qt_flags); } else { cursor = textCursor(); if (qt_flags & QTextDocument::FindBackward) cursor.movePosition(QTextCursor::End); else cursor.movePosition(QTextCursor::Start); cursor = doc->find(pattern, cursor, qt_flags); } return cursor; } // RichText void WorksheetTextItem::updateRichTextActions(QTextCursor cursor) { if (cursor.isNull()) return; Worksheet::RichTextInfo info; QTextCharFormat fmt = cursor.charFormat(); info.bold = (fmt.fontWeight() == QFont::Bold); info.italic = fmt.fontItalic(); info.underline = fmt.fontUnderline(); info.strikeOut = fmt.fontStrikeOut(); info.font = fmt.fontFamily(); info.fontSize = fmt.font().pointSize(); QTextBlockFormat bfmt = cursor.blockFormat(); info.align = bfmt.alignment(); worksheet()->setRichTextInformation(info); } void WorksheetTextItem::mergeFormatOnWordOrSelection(const QTextCharFormat &format) { qDebug() << format; QTextCursor cursor = textCursor(); QTextCursor wordStart(cursor); QTextCursor wordEnd(cursor); wordStart.movePosition(QTextCursor::StartOfWord); wordEnd.movePosition(QTextCursor::EndOfWord); //cursor.beginEditBlock(); if (!cursor.hasSelection() && cursor.position() != wordStart.position() && cursor.position() != wordEnd.position()) cursor.select(QTextCursor::WordUnderCursor); cursor.mergeCharFormat(format); //q->mergeCurrentCharFormat(format); //cursor.endEditBlock(); setTextCursor(cursor); } void WorksheetTextItem::setTextForegroundColor() { QTextCharFormat fmt = textCursor().charFormat(); QColor color = fmt.foreground().color(); color = QColorDialog::getColor(color, worksheetView()); if (!color.isValid()) color = KColorScheme(QPalette::Active, KColorScheme::View).foreground().color(); QTextCharFormat newFmt; newFmt.setForeground(color); mergeFormatOnWordOrSelection(newFmt); } void WorksheetTextItem::setTextBackgroundColor() { QTextCharFormat fmt = textCursor().charFormat(); QColor color = fmt.background().color(); color = QColorDialog::getColor(color, worksheetView()); if (!color.isValid()) color = KColorScheme(QPalette::Active, KColorScheme::View).background().color(); QTextCharFormat newFmt; newFmt.setBackground(color); mergeFormatOnWordOrSelection(newFmt); } void WorksheetTextItem::setTextBold(bool b) { QTextCharFormat fmt; fmt.setFontWeight(b ? QFont::Bold : QFont::Normal); mergeFormatOnWordOrSelection(fmt); } void WorksheetTextItem::setTextItalic(bool b) { QTextCharFormat fmt; fmt.setFontItalic(b); mergeFormatOnWordOrSelection(fmt); } void WorksheetTextItem::setTextUnderline(bool b) { QTextCharFormat fmt; fmt.setFontUnderline(b); mergeFormatOnWordOrSelection(fmt); } void WorksheetTextItem::setTextStrikeOut(bool b) { QTextCharFormat fmt; fmt.setFontStrikeOut(b); mergeFormatOnWordOrSelection(fmt); } void WorksheetTextItem::setAlignment(Qt::Alignment a) { QTextBlockFormat fmt; fmt.setAlignment(a); QTextCursor cursor = textCursor(); cursor.mergeBlockFormat(fmt); setTextCursor(cursor); } void WorksheetTextItem::setFontFamily(const QString& font) { if (!richTextEnabled()) return; QTextCharFormat fmt; fmt.setFontFamily(font); mergeFormatOnWordOrSelection(fmt); } void WorksheetTextItem::setFontSize(int size) { if (!richTextEnabled()) return; QTextCharFormat fmt; fmt.setFontPointSize(size); mergeFormatOnWordOrSelection(fmt); } void WorksheetTextItem::allowEditing() { setTextInteractionFlags(Qt::TextEditorInteraction); } void WorksheetTextItem::denyEditing() { setTextInteractionFlags(Qt::TextBrowserInteraction | Qt::TextSelectableByKeyboard); }