diff --git a/src/backends/CMakeLists.txt b/src/backends/CMakeLists.txt index d318781c..709847da 100644 --- a/src/backends/CMakeLists.txt +++ b/src/backends/CMakeLists.txt @@ -1,102 +1,82 @@ function(add_backend name) kcoreaddons_add_plugin("cantor_${name}" SOURCES ${ARGN} JSON "${name}.json" INSTALL_NAMESPACE "cantor/backends") target_link_libraries("cantor_${name}" cantorlibs) endfunction() add_subdirectory(maxima) add_subdirectory(octave) add_subdirectory(scilab) if(NOT WIN32) add_subdirectory(sage) endif(NOT WIN32) set_package_properties(Analitza5 PROPERTIES DESCRIPTION "A library provided by KAlgebra." URL "https://edu.kde.org/kalgebra/" TYPE OPTIONAL PURPOSE "Backend to use KAlgebra with Cantor.") find_package(Analitza5) if(Analitza5_FOUND) add_subdirectory(kalgebra) endif(Analitza5_FOUND) set_package_properties(R PROPERTIES DESCRIPTION "A free software environment for statistical computing and graphics." URL "https://www.r-project.org/" TYPE OPTIONAL PURPOSE "Backend to use R with Cantor.") find_package(R) if(R_FOUND) add_subdirectory(R) endif(R_FOUND) set_package_properties(Qalculate PROPERTIES DESCRIPTION "A multi-purpose desktop calculator with support for customizable functions, units and arbitrary precision." URL "https://qalculate.github.io/" TYPE OPTIONAL PURPOSE "Backend to use Qalculate with Cantor.") find_package(Qalculate) if(QALCULATE_FOUND) add_subdirectory(qalculate) endif(QALCULATE_FOUND) -set_package_properties(PythonLibs PROPERTIES DESCRIPTION "A powerful dynamic programming language." - URL "https://www.python.org/" - TYPE OPTIONAL - PURPOSE "Backend to use Python 2 with Cantor.") - -# If PythonInterp has been found (which it is indirectly by KF5I18n), -# find_package(PythonLibs) will prefer that version over whatever -# version information you pass the find_package call. Set a special -# variable to override that: -set(Python_ADDITIONAL_VERSIONS 2.7) -find_package(PythonLibs 2.7) - set_package_properties(PythonLibs3 PROPERTIES DESCRIPTION "A powerful dynamic programming language." URL "https://www.python.org/" TYPE OPTIONAL - PURPOSE "Backend to use Python 3 with Cantor.") + PURPOSE "Backend to use Python with Cantor.") find_package(PythonLibs3) - -if(PYTHONLIBS_FOUND OR PYTHONLIBS3_FOUND) - add_subdirectory(python) -endif(PYTHONLIBS_FOUND OR PYTHONLIBS3_FOUND) - -if(PYTHONLIBS_FOUND) - add_subdirectory(python2) -endif(PYTHONLIBS_FOUND) - if(PYTHONLIBS3_FOUND) + add_subdirectory(python) add_subdirectory(python3) endif(PYTHONLIBS3_FOUND) set_package_properties(LuaJIT PROPERTIES DESCRIPTION "A lightweight, extensible programming language (luajit implementation)." URL "https://www.lua.org/" TYPE OPTIONAL PURPOSE "Backend to use Lua (luajit2) with Cantor.") find_package(LuaJIT) if(LUAJIT_FOUND) add_subdirectory(lua) endif(LUAJIT_FOUND) set_package_properties( Julia PROPERTIES DESCRIPTION "A high-level, high-performance dynamic programming language for technical computing" URL "https://julialang.org/" TYPE OPTIONAL PURPOSE "Backend to use Julia with Cantor.") find_package(Julia) if(Julia_FOUND) add_subdirectory(julia) endif() diff --git a/src/backends/python2/CMakeLists.txt b/src/backends/python2/CMakeLists.txt deleted file mode 100644 index 109f4715..00000000 --- a/src/backends/python2/CMakeLists.txt +++ /dev/null @@ -1,36 +0,0 @@ -set( Python2Backend_SRCS - python2backend.cpp - python2session.cpp -) - -set(Python2Server_SRCS - ../python/pythonservermain.cpp - ../python/pythonserver.cpp -) - -kconfig_add_kcfg_files(Python2Backend_SRCS settings.kcfgc) - -if(MSVC) - # ssize_t is typedef'd in both kdewin and python headers, this prevents using the kdewin one - add_definitions(-D_SSIZE_T_DEFINED) -endif(MSVC) - -include_directories(${PYTHON_LIBRARIES_DIR}) -include_directories(${PYTHON_INCLUDE_DIR}) -add_backend(python2backend ${Python2Backend_SRCS}) -target_link_libraries(cantor_python2backend ${PYTHON_LIBRARIES} cantor_pythonbackend) - -add_executable(cantor_python2server ${Python2Server_SRCS}) -set_target_properties(cantor_python2server PROPERTIES INSTALL_RPATH_USE_LINK_PATH false) -target_link_libraries(cantor_python2server ${PYTHON_LIBRARIES}) - -if(BUILD_TESTING) - add_executable(testpython2 testpython2.cpp settings.cpp) - target_link_libraries(testpython2 ${QT_QTTEST_LIBRARY} cantorlibs cantortest) - add_test(NAME testpython2 COMMAND testpython2) -endif() - -install(FILES cantor_python2.knsrc DESTINATION ${KDE_INSTALL_CONFDIR}) -install(FILES python2backend.kcfg DESTINATION ${KDE_INSTALL_KCFGDIR}) - -install(TARGETS cantor_python2server ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/src/backends/python2/cantor_python2.knsrc b/src/backends/python2/cantor_python2.knsrc deleted file mode 100644 index aef132c5..00000000 --- a/src/backends/python2/cantor_python2.knsrc +++ /dev/null @@ -1,7 +0,0 @@ -[KNewStuff3] -ProvidersUrl=https://autoconfig.kde.org/ocs/providers.xml -Categories=Cantor (Maxima),Cantor (Sage),Cantor (KAlgebra),Cantor (Qalculate),Cantor (Python 2),Cantor (Python 3),Cantor (Scilab),Cantor (Octave),Cantor (R),Cantor (Lua) -UploadCategories=Cantor (Python2) -TargetDir=cantor/examples -Uncompress=never -CustomName=true diff --git a/src/backends/python2/python2backend.cpp b/src/backends/python2/python2backend.cpp deleted file mode 100644 index bb76944f..00000000 --- a/src/backends/python2/python2backend.cpp +++ /dev/null @@ -1,96 +0,0 @@ -/* - 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) 2014, 2015 Minh Ngo - Copyright (C) 2019 Alexander Semke - */ - -#include "python2backend.h" -#include "python2session.h" -#include "settings.h" - -#include - -Python2Backend::Python2Backend(QObject* parent, const QList args) - : PythonBackend(parent, args) -{ - // Because the plugin may not have been loaded with - // ExportExternalSymbols, we load the python symbols again - // to make sure that python modules such as numpy see them - // (see bug #330032) - QLibrary pythonLib(QLatin1String("python2.7")); - pythonLib.setLoadHints(QLibrary::ExportExternalSymbolsHint); - pythonLib.load(); -} - -Cantor::Session* Python2Backend::createSession() -{ - return new Python2Session(this); -} - -QString Python2Backend::id() const -{ - return QLatin1String("python2"); -} - -QString Python2Backend::version() const -{ - return QLatin1String("2.7"); -} - -Cantor::Backend::Capabilities Python2Backend::capabilities() const -{ - Backend::Capabilities cap = - Cantor::Backend::SyntaxHighlighting | - Cantor::Backend::Completion | - Cantor::Backend::SyntaxHelp; - - if(PythonSettings::variableManagement()) - cap |= Cantor::Backend::VariableManagement; - - return cap; -} - -QUrl Python2Backend::helpUrl() const -{ - const QUrl& localDoc = PythonSettings::self()->localDoc(); - if (!localDoc.isEmpty()) - return localDoc; - else - return QUrl(i18nc("The url to the documentation Python 2", "https://docs.python.org/2/")); -} - -QString Python2Backend::description() const -{ - return i18n("Python is a remarkably powerful dynamic programming language that is used in a wide variety of application domains. " \ - "There are several Python packages to scientific programming. " \ - "This backend supports Python 2."); -} - -KConfigSkeleton* Python2Backend::config() const -{ - return PythonSettings::self(); -} - -bool Python2Backend::requirementsFullfilled(QString* const reason) const -{ - const QString& path = PythonSettings::pythonServerPath().toLocalFile(); - return Cantor::Backend::checkExecutable(QLatin1String("Cantor Python2 Server"), path, reason); -} - -K_PLUGIN_FACTORY_WITH_JSON(python2backend, "python2backend.json", registerPlugin();) -#include "python2backend.moc" diff --git a/src/backends/python2/python2backend.h b/src/backends/python2/python2backend.h deleted file mode 100644 index 1d807455..00000000 --- a/src/backends/python2/python2backend.h +++ /dev/null @@ -1,43 +0,0 @@ - /* - 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) 2014, 2015 Minh Ngo - */ - -#ifndef _PYTHON2BACKEND_H -#define _PYTHON2BACKEND_H - -#include "../python/pythonbackend.h" - -class Python2Backend : public PythonBackend -{ - Q_OBJECT - public: - explicit Python2Backend(QObject* parent = nullptr, const QList args = QList()); - - Cantor::Session* createSession() override; - - QString id() const override; - QString version() const override; - Cantor::Backend::Capabilities capabilities() const override; - QUrl helpUrl() const override; - QString description() const override; - bool requirementsFullfilled(QString* const reason = nullptr) const override; - KConfigSkeleton* config() const override; -}; - -#endif diff --git a/src/backends/python2/python2backend.json b/src/backends/python2/python2backend.json deleted file mode 100644 index 16424d74..00000000 --- a/src/backends/python2/python2backend.json +++ /dev/null @@ -1,67 +0,0 @@ -{ - "KPlugin": { - "Dependencies": [], - "Description": "Backend for Python 2 Scientific Programming", - "Description[ca@valencia]": "Dorsal per a la programació científica Python2", - "Description[ca]": "Dorsal per a la programació científica Python2", - "Description[cs]": "Podpůrná vrstva pro vědecké programovací v Python 2", - "Description[de]": "Modul für die wissenschaftliche Programmierumgebung Python 2", - "Description[el]": "Σύστημα υποστήριξης για το Python 2 Scientific Programming", - "Description[en_GB]": "Backend for Python 2 Scientific Programming", - "Description[es]": "Motor para programación científica en Python 2", - "Description[et]": "Python 2 teadusliku programmeerimise taustaprogramm", - "Description[fi]": "Python 2 -pohjaisen tieteellisen ohjelmointiympäristön taustajärjestelmä", - "Description[fr]": "Moteur pour la programmation scientifique Python 2", - "Description[gl]": "Infraestrutura para o programación científica con Python 2.", - "Description[it]": "Backend per l'ambiente scientifico di programmazione Python 2", - "Description[nl]": "Backend voor wetenschappelijke programmeeromgeving Python 2", - "Description[nn]": "Motor for Python 2 «Scientific Programming»", - "Description[pl]": "Silnika dla naukowego środowiska programistycznego Python 2", - "Description[pt]": "Infra-Estrutura de Programação Científica com Python 2", - "Description[pt_BR]": "Infraestrutura de programação científica Python 2", - "Description[ru]": "Поддержка языка Python 2 и инженерных и научных расчётов на нём", - "Description[sk]": "Backend pre vedecké programovanie Python 2", - "Description[sl]": "Zaledje za znanstveno programersko okolje Python 2", - "Description[sv]": "Bakgrundsprogram för Python 2 vetenskaplig programmeringsmiljö", - "Description[tr]": "Python 2 Bilimsel Programlama için arka uç", - "Description[uk]": "Модуль наукового програмування мовою Python 2", - "Description[x-test]": "xxBackend for Python 2 Scientific Programmingxx", - "Description[zh_CN]": "Python 2 科学编程环境的后端", - "Description[zh_TW]": "Python 2 科學程式環境的後端介面", - "Icon": "pythonbackend", - "Id": "Python 2", - "License": "GPL", - "Name": "Python 2", - "Name[ca@valencia]": "Python 2", - "Name[ca]": "Python 2", - "Name[cs]": "Python 2", - "Name[da]": "Python 2", - "Name[de]": "Python 2", - "Name[el]": "Python 2", - "Name[en_GB]": "Python 2", - "Name[es]": "Python 2", - "Name[et]": "Python 2", - "Name[fi]": "Python 2", - "Name[fr]": "Python 2", - "Name[gl]": "Python 2", - "Name[it]": "Python 2", - "Name[nl]": "Python 2", - "Name[nn]": "Python 2", - "Name[pl]": "Python 2", - "Name[pt]": "Python 2", - "Name[pt_BR]": "Python 2", - "Name[ru]": "Python 2", - "Name[sk]": "Python 2", - "Name[sl]": "Python 2", - "Name[sv]": "Python 2", - "Name[tr]": "Python 2", - "Name[uk]": "Python 2", - "Name[x-test]": "xxPython 2xx", - "Name[zh_CN]": "Python 2", - "Name[zh_TW]": "Python 2", - "ServiceTypes": [ - "Cantor/Backend" - ], - "Website": "https://python.org/" - } -} diff --git a/src/backends/python2/python2backend.kcfg b/src/backends/python2/python2backend.kcfg deleted file mode 100644 index 1d3e1485..00000000 --- a/src/backends/python2/python2backend.kcfg +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - QUrl::fromLocalFile(QStandardPaths::findExecutable(QLatin1String("cantor_python2server"))) - - - - - - - false - - - - true - - - - - - diff --git a/src/backends/python2/python2session.cpp b/src/backends/python2/python2session.cpp deleted file mode 100644 index 63a51857..00000000 --- a/src/backends/python2/python2session.cpp +++ /dev/null @@ -1,51 +0,0 @@ -/* - 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 - */ - -#include "python2session.h" -#include "settings.h" -#include "../python/pythonexpression.h" -#include "../python/pythonkeywords.h" - -#include -#include -#include -#include - -#include - -Python2Session::Python2Session(Cantor::Backend* backend) - : PythonSession(backend, 2, PythonSettings::pythonServerPath()) -{ -} - -bool Python2Session::integratePlots() const -{ - return PythonSettings::integratePlots(); -} - -QStringList Python2Session::autorunScripts() const -{ - return PythonSettings::autorunScripts(); -} - -bool Python2Session::variableManagement() const -{ - return PythonSettings::variableManagement(); -} diff --git a/src/backends/python2/python2session.h b/src/backends/python2/python2session.h deleted file mode 100644 index 3a54af93..00000000 --- a/src/backends/python2/python2session.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - 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 _PYTHON2SESSION_H -#define _PYTHON2SESSION_H - -#include "../python/pythonsession.h" - -class Python2Session : public PythonSession -{ - public: - explicit Python2Session(Cantor::Backend* backend); - - bool integratePlots() const override; - QStringList autorunScripts() const override; - bool variableManagement() const override; -}; - -#endif diff --git a/src/backends/python2/settings.kcfgc b/src/backends/python2/settings.kcfgc deleted file mode 100644 index df849074..00000000 --- a/src/backends/python2/settings.kcfgc +++ /dev/null @@ -1,3 +0,0 @@ -File=python2backend.kcfg -ClassName=PythonSettings -Singleton=true diff --git a/src/backends/python2/testpython2.cpp b/src/backends/python2/testpython2.cpp deleted file mode 100644 index ac44611a..00000000 --- a/src/backends/python2/testpython2.cpp +++ /dev/null @@ -1,323 +0,0 @@ -/* - 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) 2013 Tuukka Verho - */ - -#include "testpython2.h" - -#include "session.h" -#include "backend.h" -#include "expression.h" -#include "result.h" -#include "defaultvariablemodel.h" -#include "imageresult.h" -#include "completionobject.h" - -#include "settings.h" - -QString TestPython2::backendName() -{ - return QLatin1String("python2"); -} - -void TestPython2::testCommandQueue() -{ - Cantor::Expression* e1=session()->evaluateExpression(QLatin1String("0+1")); - Cantor::Expression* e2=session()->evaluateExpression(QLatin1String("1+1")); - Cantor::Expression* e3=evalExp(QLatin1String("1+2")); - - QVERIFY(e1!=nullptr); - QVERIFY(e2!=nullptr); - QVERIFY(e3!=nullptr); - - QVERIFY(e1->result()); - QVERIFY(e2->result()); - QVERIFY(e3->result()); - - QCOMPARE(cleanOutput(e1->result()->data().toString()), QLatin1String("1")); - QCOMPARE(cleanOutput(e2->result()->data().toString()), QLatin1String("2")); - QCOMPARE(cleanOutput(e3->result()->data().toString()), QLatin1String("3")); -} - -void TestPython2::testImportStatement() -{ - Cantor::Expression* e = evalExp(QLatin1String("import sys")); - - QVERIFY(e != nullptr); - QCOMPARE(e->status(), Cantor::Expression::Done); -} - -void TestPython2::testCodeWithComments() -{ - { - Cantor::Expression* e = evalExp(QLatin1String("#comment\n1+2")); - - QVERIFY(e != nullptr); - QVERIFY(e->result()); - QVERIFY(e->result()->data().toString() == QLatin1String("3")); - } - - { - Cantor::Expression* e = evalExp(QLatin1String(" #comment\n1+2")); - - QVERIFY(e != nullptr); - QVERIFY(e->result()); - QVERIFY(e->result()->data().toString() == QLatin1String("3")); - } -} - -void TestPython2::testSimpleCode() -{ - Cantor::Expression* e=evalExp( QLatin1String("2+2")); - - QVERIFY( e!=nullptr ); - QVERIFY( e->result()!=nullptr ); - - QCOMPARE( cleanOutput(e->result()->data().toString()), QLatin1String("4") ); -} - -void TestPython2::testMultilineCode() -{ - Cantor::Expression* e=evalExp(QLatin1String( - "a = 2+2\n" - "b = 3+3\n" - "print a,b" - )); - - QVERIFY( e!=nullptr ); - QVERIFY( e->result()!=nullptr ); - - QCOMPARE( cleanOutput(e->result()->data().toString()), QLatin1String("4 6") ); - - evalExp(QLatin1String("del a; del b")); -} - -void TestPython2::testVariablesCreatingFromCode() -{ - if (!PythonSettings::variableManagement()) - QSKIP("This test needs enabled variable management in Python2 settings", SkipSingle); - - QAbstractItemModel* model = session()->variableModel(); - QVERIFY(model != nullptr); - - Cantor::Expression* e=evalExp(QLatin1String("a = 15; b = 'S';")); - QVERIFY(e!=nullptr); - - if(session()->status()==Cantor::Session::Running) - waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status))); - - QCOMPARE(2, model->rowCount()); - - QCOMPARE(model->index(0,0).data().toString(), QLatin1String("a")); - QCOMPARE(model->index(0,1).data().toString(), QLatin1String("15")); - - QCOMPARE(model->index(1,0).data().toString(), QLatin1String("b")); - QCOMPARE(model->index(1,1).data().toString(), QLatin1String("'S'")); - - evalExp(QLatin1String("del a; del b")); -} - -void TestPython2::testVariableCleanupAfterRestart() -{ - Cantor::DefaultVariableModel* model = session()->variableModel(); - QVERIFY(model != nullptr); - - Cantor::Expression* e=evalExp(QLatin1String("a = 15; b = 'S';")); - QVERIFY(e!=nullptr); - - if(session()->status()==Cantor::Session::Running) - waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status))); - - QCOMPARE(2, static_cast(model)->rowCount()); - - session()->logout(); - session()->login(); - - QCOMPARE(0, static_cast(model)->rowCount()); -} - -void TestPython2::testDictVariable() -{ - if (!PythonSettings::variableManagement()) - QSKIP("This test needs enabled variable management in Python2 settings", SkipSingle); - - Cantor::DefaultVariableModel* model = session()->variableModel(); - QVERIFY(model != nullptr); - - Cantor::Expression* e=evalExp(QLatin1String("d = {'value': 33}")); - - QVERIFY(e!=nullptr); - - if(session()->status()==Cantor::Session::Running) - waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status))); - - QCOMPARE(1, static_cast(model)->rowCount()); - QCOMPARE(model->index(0,0).data().toString(), QLatin1String("d")); - QCOMPARE(model->index(0,1).data().toString(), QLatin1String("{'value': 33}")); - - evalExp(QLatin1String("del d")); -} - -void TestPython2::testCommentExpression() -{ - Cantor::Expression* e = evalExp(QLatin1String("#only comment")); - - QVERIFY(e != nullptr); - QCOMPARE(e->status(), Cantor::Expression::Status::Done); - QCOMPARE(e->results().size(), 0); -} - -void TestPython2::testInvalidSyntax() -{ - Cantor::Expression* e=evalExp( QLatin1String("2+2*+.") ); - - QVERIFY( e!=nullptr ); - QCOMPARE( e->status(), Cantor::Expression::Error ); -} - -void TestPython2::testSimplePlot() -{ - if (!PythonSettings::integratePlots()) - QSKIP("This test needs enabled plots integration in Python2 settings", SkipSingle); - - Cantor::Expression* e = evalExp(QLatin1String( - "import matplotlib\n" - "import matplotlib.pyplot as plt\n" - "import numpy as np" - )); - QVERIFY(e != nullptr); - QVERIFY(e->errorMessage().isEmpty() == true); - - //the variable model shouldn't have any entries after the module imports - QAbstractItemModel* model = session()->variableModel(); - QVERIFY(model != nullptr); - if(session()->status()==Cantor::Session::Running) - waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status))); - QVERIFY(model->rowCount() == 0); - - //create data for plotting - e = evalExp(QLatin1String( - "t = np.arange(0.0, 2.0, 0.01)\n" - "s = 1 + np.sin(2 * np.pi * t)" - )); - QVERIFY(e != nullptr); - QVERIFY(e->errorMessage().isEmpty() == true); - - if(session()->status()==Cantor::Session::Running) - waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status))); - //the variable model should have two entries now - QVERIFY(model->rowCount() == 2); - - //plot the data and check the results - e = evalExp(QLatin1String( - "plt.plot(t,s)\n" - "plt.show()" - )); - - QVERIFY(e != nullptr); - if (e->result() == nullptr) - waitForSignal(e, SIGNAL(gotResult())); - QVERIFY(e->errorMessage().isEmpty() == true); - QVERIFY(model->rowCount() == 2); //still only two variables - - //there must be one single image result - QVERIFY(e->results().size() == 1); - const Cantor::ImageResult* result = dynamic_cast(e->result()); - QVERIFY(result != nullptr); - - evalExp(QLatin1String("del t; del s")); -} - -void TestPython2::testSimpleExpressionWithComment() -{ - Cantor::Expression* e = evalExp(QLatin1String("2+2 # comment")); - - QVERIFY(e != nullptr); - QVERIFY(e->result()); - QVERIFY(e->result()->data().toString() == QLatin1String("4")); -} - -void TestPython2::testMultilineCommandWithComment() -{ - Cantor::Expression* e = evalExp(QLatin1String( - "print 2+2 \n" - "#comment in middle \n" - "print 7*5")); - - QVERIFY(e != nullptr); - QVERIFY(e->result()); - QVERIFY(e->result()->data().toString() == QLatin1String("4\n35")); -} - -void TestPython2::testCompletion() -{ - if(session()->status()==Cantor::Session::Running) - waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status))); - - Cantor::CompletionObject* help = session()->completionFor(QLatin1String("ma"), 2); - waitForSignal(help, SIGNAL(fetchingDone())); - - // Checks all completions for this request - // This correct for Python 2.7.15 - const QStringList& completions = help->completions(); - qDebug() << completions; - QCOMPARE(completions.size(), 2); - QVERIFY(completions.contains(QLatin1String("map"))); - QVERIFY(completions.contains(QLatin1String("max"))); -} - -void TestPython2::testInterrupt() -{ - Cantor::Expression* e1=session()->evaluateExpression(QLatin1String("import time; time.sleep(45)")); - Cantor::Expression* e2=session()->evaluateExpression(QLatin1String("2")); - - // Wait, because server need time to read input - QTest::qWait(100); - - QCOMPARE(e1->status(), Cantor::Expression::Computing); - QCOMPARE(e2->status(), Cantor::Expression::Queued); - - while(session()->status() != Cantor::Session::Running) - waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status))); - - session()->interrupt(); - - while(session()->status() != Cantor::Session::Done) - waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status))); - - QCOMPARE(e1->status(), Cantor::Expression::Interrupted); - QCOMPARE(e2->status(), Cantor::Expression::Interrupted); - - Cantor::Expression* e = evalExp(QLatin1String("2+2")); - - QVERIFY(e != nullptr); - QVERIFY(e->result()); - QCOMPARE(e->result()->data().toString(), QLatin1String("4")); -} - -void TestPython2::testWarning() -{ - Cantor::Expression* e = evalExp(QLatin1String("import warnings; warnings.warn('Test')")); - - QVERIFY(e != nullptr); - QCOMPARE(e->status(), Cantor::Expression::Status::Done); - QCOMPARE(e->results().size(), 1); -} - -QTEST_MAIN(TestPython2) - diff --git a/src/backends/python2/testpython2.h b/src/backends/python2/testpython2.h deleted file mode 100644 index a15608f3..00000000 --- a/src/backends/python2/testpython2.h +++ /dev/null @@ -1,57 +0,0 @@ -/* - 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) 2013 Tuukka Verho - */ - -#ifndef _TESTPYTHON2_H -#define _TESTPYTHON2_H - -#include "backendtest.h" - - -class TestPython2 : public BackendTest -{ - Q_OBJECT - private Q_SLOTS: - void testCodeWithComments(); - void testSimpleCode(); - void testMultilineCode(); - void testCommandQueue(); - - void testSimplePlot(); - - void testImportStatement(); - void testInvalidSyntax(); - - void testSimpleExpressionWithComment(); - void testCommentExpression(); - void testMultilineCommandWithComment(); - - void testVariablesCreatingFromCode(); - void testVariableCleanupAfterRestart(); - void testDictVariable(); - - void testCompletion(); - void testInterrupt(); - - void testWarning(); - private: - QString backendName() override; -}; - -#endif /* _TESTPYTHON2_H */ diff --git a/src/backends/python3/python3backend.cpp b/src/backends/python3/python3backend.cpp index efd5210b..12aaa19e 100644 --- a/src/backends/python3/python3backend.cpp +++ b/src/backends/python3/python3backend.cpp @@ -1,92 +1,91 @@ /* 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) 2014, 2015 Minh Ngo Copyright (C) 2019 Alexander Semke */ #include "python3backend.h" #include "python3session.h" #include "settings.h" #include Python3Backend::Python3Backend(QObject* parent, const QList& args) : PythonBackend(parent, args) { setObjectName(QLatin1String("python3backend")); } Cantor::Session* Python3Backend::createSession() { return new Python3Session(this); } QString Python3Backend::id() const { return QLatin1String("python3"); } QString Python3Backend::version() const { return QLatin1String("3.6"); } Cantor::Backend::Capabilities Python3Backend::capabilities() const { qDebug()<<"Requesting capabilities of Python3Session"; Backend::Capabilities cap = Cantor::Backend::SyntaxHighlighting | Cantor::Backend::Completion | Cantor::Backend::SyntaxHelp; if(PythonSettings::variableManagement()) cap |= Cantor::Backend::VariableManagement; return cap; } QUrl Python3Backend::helpUrl() const { const QUrl& localDoc = PythonSettings::self()->localDoc(); if (!localDoc.isEmpty()) return localDoc; else - return QUrl(i18nc("The url to the documentation Python 3", "https://docs.python.org/3/")); + return QUrl(i18nc("The url to the documentation Python", "https://docs.python.org/3/")); } QString Python3Backend::description() const { return i18n("Python is a remarkably powerful dynamic programming language that is used in a wide variety of application domains. " \ - "There are several Python packages to scientific programming. " \ - "This backend supports Python 3."); + "There are several Python packages to scientific programming."); } KConfigSkeleton* Python3Backend::config() const { return PythonSettings::self(); } bool Python3Backend::requirementsFullfilled(QString* const reason) const { const QString& path = PythonSettings::pythonServerPath().toLocalFile(); return Cantor::Backend::checkExecutable(QLatin1String("Cantor Python3 Server"), path, reason); } K_PLUGIN_FACTORY_WITH_JSON(python3backend, "python3backend.json", registerPlugin();) #include "python3backend.moc" diff --git a/src/backends/python3/python3backend.json b/src/backends/python3/python3backend.json index efaec55b..c5cd6c1f 100644 --- a/src/backends/python3/python3backend.json +++ b/src/backends/python3/python3backend.json @@ -1,67 +1,67 @@ { "KPlugin": { "Dependencies": [], - "Description": "Backend for Python 3 Scientific Programming", - "Description[ca@valencia]": "Dorsal per a la programació científica Python 3", - "Description[ca]": "Dorsal per a la programació científica Python 3", - "Description[cs]": "Podpůrná vrstva pro vědecké programovací v Python 3", - "Description[de]": "Modul für die wissenschaftliche Programmierumgebung Python 3", - "Description[el]": "Σύστημα υποστήριξης για το Python 3 Scientific Programming", - "Description[en_GB]": "Backend for Python 3 Scientific Programming", - "Description[es]": "Motor para programación científica en Python 3", - "Description[et]": "Python 3 teadusliku programmeerimise taustaprogramm", - "Description[fi]": "Python 3 -pohjaisen tieteellisen ohjelmointiympäristön taustajärjestelmä", - "Description[fr]": "Moteur pour la programmation scientifique Python 3", - "Description[gl]": "Infraestrutura para o programación científica con Python 3.", - "Description[it]": "Backend per l'ambiente scientifico di programmazione Python 3", - "Description[nl]": "Backend voor wetenschappelijke programmeeromgeving Python 3", - "Description[nn]": "Motor for Python 3 «Scientific Programming»", - "Description[pl]": "Silnika dla naukowego środowiska programistycznego Python 3", - "Description[pt]": "Infra-Estrutura de Programação Científica com Python 3", - "Description[pt_BR]": "Infraestrutura de programação científica Python 3", - "Description[ru]": "Поддержка языка Python 3 и инженерных и научных расчётов на нём", - "Description[sk]": "Backend pre vedecké programovanie Python 3", - "Description[sl]": "Zaledje za znanstveno programersko okolje Python 3", - "Description[sv]": "Bakgrundsprogram för Python 3 vetenskaplig programmeringsmiljö", - "Description[tr]": "Python 3 Bilimsel Programlama için arka uç", - "Description[uk]": "Модуль наукового програмування мовою Python 3", - "Description[x-test]": "xxBackend for Python 3 Scientific Programmingxx", - "Description[zh_CN]": "Python 3 科学编程环境的后端", - "Description[zh_TW]": "Python 3 科學程式環境的後端介面", + "Description": "Backend for Python Scientific Programming", + "Description[ca@valencia]": "Dorsal per a la programació científica Python", + "Description[ca]": "Dorsal per a la programació científica Python", + "Description[cs]": "Podpůrná vrstva pro vědecké programovací v Python", + "Description[de]": "Modul für die wissenschaftliche Programmierumgebung Python", + "Description[el]": "Σύστημα υποστήριξης για το Python Scientific Programming", + "Description[en_GB]": "Backend for Python Scientific Programming", + "Description[es]": "Motor para programación científica en Python", + "Description[et]": "Python teadusliku programmeerimise taustaprogramm", + "Description[fi]": "Python -pohjaisen tieteellisen ohjelmointiympäristön taustajärjestelmä", + "Description[fr]": "Moteur pour la programmation scientifique Python", + "Description[gl]": "Infraestrutura para o programación científica con Python.", + "Description[it]": "Backend per l'ambiente scientifico di programmazione Python", + "Description[nl]": "Backend voor wetenschappelijke programmeeromgeving Python", + "Description[nn]": "Motor for Python «Scientific Programming»", + "Description[pl]": "Silnika dla naukowego środowiska programistycznego Python", + "Description[pt]": "Infra-Estrutura de Programação Científica com Python", + "Description[pt_BR]": "Infraestrutura de programação científica Python", + "Description[ru]": "Поддержка языка Python и инженерных и научных расчётов на нём", + "Description[sk]": "Backend pre vedecké programovanie Python", + "Description[sl]": "Zaledje za znanstveno programersko okolje Python", + "Description[sv]": "Bakgrundsprogram för Python vetenskaplig programmeringsmiljö", + "Description[tr]": "Python Bilimsel Programlama için arka uç", + "Description[uk]": "Модуль наукового програмування мовою Python", + "Description[x-test]": "xxBackend for Python Scientific Programmingxx", + "Description[zh_CN]": "Python 科学编程环境的后端", + "Description[zh_TW]": "Python 科學程式環境的後端介面", "Icon": "pythonbackend", - "Id": "Python 3", + "Id": "Python", "License": "GPL", - "Name": "Python 3", - "Name[ca@valencia]": "Python 3", - "Name[ca]": "Python 3", - "Name[cs]": "Python 3", - "Name[da]": "Python 3", - "Name[de]": "Python 3", - "Name[el]": "Python 3", - "Name[en_GB]": "Python 3", - "Name[es]": "Python 3", - "Name[et]": "Python 3", - "Name[fi]": "Python 3", - "Name[fr]": "Python 3", - "Name[gl]": "Python 3", - "Name[it]": "Python 3", - "Name[nl]": "Python 3", - "Name[nn]": "Python 3", - "Name[pl]": "Python 3", - "Name[pt]": "Python 3", - "Name[pt_BR]": "Python 3", - "Name[ru]": "Python 3", - "Name[sk]": "Python 3", - "Name[sl]": "Python 3", - "Name[sv]": "Python 3", - "Name[tr]": "Python 3", - "Name[uk]": "Python 3", - "Name[x-test]": "xxPython 3xx", - "Name[zh_CN]": "Python 3", - "Name[zh_TW]": "Python 3", + "Name": "Python", + "Name[ca@valencia]": "Python", + "Name[ca]": "Python", + "Name[cs]": "Python", + "Name[da]": "Python", + "Name[de]": "Python", + "Name[el]": "Python", + "Name[en_GB]": "Python", + "Name[es]": "Python", + "Name[et]": "Python", + "Name[fi]": "Python", + "Name[fr]": "Python", + "Name[gl]": "Python", + "Name[it]": "Python", + "Name[nl]": "Python", + "Name[nn]": "Python", + "Name[pl]": "Python", + "Name[pt]": "Python", + "Name[pt_BR]": "Python", + "Name[ru]": "Python", + "Name[sk]": "Python", + "Name[sl]": "Python", + "Name[sv]": "Python", + "Name[tr]": "Python", + "Name[uk]": "Python", + "Name[x-test]": "xxPythonxx", + "Name[zh_CN]": "Python", + "Name[zh_TW]": "Python", "ServiceTypes": [ "Cantor/Backend" ], "Website": "https://python.org/" } } diff --git a/src/worksheet.cpp b/src/worksheet.cpp index 3f403678..631b3dca 100644 --- a/src/worksheet.cpp +++ b/src/worksheet.cpp @@ -1,2480 +1,2511 @@ /* 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, bool useDeafultWorksheetParameters) : QGraphicsScene(parent) { m_session = nullptr; m_highlighter = nullptr; m_firstEntry = nullptr; m_lastEntry = nullptr; m_lastFocusedTextItem = nullptr; m_dragEntry = nullptr; m_placeholderEntry = nullptr; m_dragScrollTimer = nullptr; m_choosenCursorEntry = nullptr; m_isCursorEntryAfterLastEntry = false; m_useDefaultWorksheetParameters = useDeafultWorksheetParameters; m_viewWidth = 0; m_maxWidth = 0; 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_jupyterMetadata = nullptr; if (backend) initSession(backend); } Worksheet::~Worksheet() { // This is necessary, because a SearchBar 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; //disconnect from everything, no need to react on session status changes //in the logout() when deleting the worksheet disconnect(m_session, nullptr, nullptr, nullptr); if (m_session && m_session->status() != Cantor::Session::Disable) m_session->logout(); if (m_session) { disconnect(m_session, nullptr, nullptr, nullptr); if (m_session->status() != Cantor::Session::Disable) m_session->logout(); m_session->deleteLater(); } if (m_jupyterMetadata) delete m_jupyterMetadata; } void Worksheet::loginToSession() { m_session->login(); #ifdef WITH_EPS if (Cantor::LatexRenderer::isLatexAvailable()) session()->setTypesettingEnabled(Settings::self()->typesetDefault()); else session()->setTypesettingEnabled(false); #else session()->setTypesettingEnabled(false); #endif } 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, sceneRect().width(), 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, sceneRect().width(), y)); if (cursorRectVisible) makeVisible(worksheetCursor()); else if (atEnd) worksheetView()->scrollToEnd(); drawEntryCursor(); } void Worksheet::setRequestedWidth(QGraphicsObject* object, qreal width) { qreal oldWidth = m_itemWidths[object]; m_itemWidths[object] = width; if (width > m_maxWidth || oldWidth == m_maxWidth) { m_maxWidth = width; qreal y = lastEntry() ? lastEntry()->size().height() + lastEntry()->y() : 0; setSceneRect(QRectF(0, 0, m_maxWidth + LeftMargin + RightMargin, y)); } } void Worksheet::removeRequestedWidth(QGraphicsObject* object) { if (!m_itemWidths.contains(object)) return; qreal width = m_itemWidths[object]; m_itemWidths.remove(object); if (width == m_maxWidth) { m_maxWidth = 0; for (qreal width : m_itemWidths.values()) if (width > m_maxWidth) m_maxWidth = width; qreal y = lastEntry() ? lastEntry()->size().height() + lastEntry()->y() : 0; setSceneRect(QRectF(0, 0, m_maxWidth + LeftMargin + RightMargin, 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() { // Entry cursor activate if (m_choosenCursorEntry || m_isCursorEntryAfterLastEntry) return nullptr; QGraphicsItem* item = focusItem(); 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_readOnly && m_session && m_session->status() == Cantor::Session::Disable) loginToSession(); firstEntry()->evaluate(WorksheetEntry::EvaluateNext); setModified(); } void Worksheet::evaluateCurrentEntry() { if (!m_readOnly && m_session && m_session->status() == Cantor::Session::Disable) 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); if (!m_isLoadingFromFile) { 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()->formats()); } } // 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()->setFormats(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()->clearFormats(); } 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(); zipFile.writeFile( QLatin1String("content.xml"), content.data()); break; } case JupyterNotebook: { if (!device->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")))); 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("Open File")); return false; } bool rc = load(&file); if (rc && !m_readOnly) m_session->setWorksheetPath(filename); return rc; } void Worksheet::load(QByteArray* data) { QBuffer buf(data); buf.open(QIODevice::ReadOnly); load(&buf); } bool Worksheet::load(QIODevice* device) { if (!device->isReadable()) { QApplication::restoreOverrideCursor(); KMessageBox::error(worksheetView(), i18n("Couldn't open the selected file for reading."), i18n("Open File")); 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("Open File")); 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("Open File")); return false; } const KArchiveFile* content = static_cast(contentEntry); QByteArray data = content->data(); - QDomDocument doc; doc.setContent(data); - QDomElement root=doc.documentElement(); + QDomElement root = doc.documentElement(); + + m_backendName = root.attribute(QLatin1String("backend")); + + //There is "Python" only now, replace "Python 3" by "Python" + if (m_backendName == QLatin1String("Python 3")) + m_backendName = QLatin1String("Python"); + + //"Python 2" in older projects not supported anymore, switch to Python (=Python3) + if (m_backendName == QLatin1String("Python 2")) + { + QApplication::restoreOverrideCursor(); + KMessageBox::information(worksheetView(), + i18n("This worksheet was created using Python2 which is not supported anymore. Python3 will be used."), + i18n("Python2 not supported anymore")); + QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); + m_backendName = QLatin1String("Python"); + } - m_backendName=root.attribute(QLatin1String("backend")); - Cantor::Backend* b=Cantor::Backend::getBackend(m_backendName); + auto* b = Cantor::Backend::getBackend(m_backendName); if (!b) { QApplication::restoreOverrideCursor(); KMessageBox::information(worksheetView(), i18n("%1 backend was not found. Editing and executing entries is not possible.", m_backendName), i18n("Open File")); m_readOnly = true; } else m_readOnly = false; if(!m_readOnly && !b->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("Open File")); 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; //file can only be loaded in a worksheet that was not edited/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(); m_itemWidths.clear(); m_maxWidth = 0; if (!m_readOnly) initSession(b); 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; updateLayout(); //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; } void Worksheet::initSession(Cantor::Backend* backend) { m_session = backend->createSession(); if (m_useDefaultWorksheetParameters) { enableHighlighting(Settings::self()->highlightDefault()); enableCompletion(Settings::self()->completionDefault()); enableExpressionNumbering(Settings::self()->expressionNumberingDefault()); enableAnimations(Settings::self()->animationDefault()); enableEmbeddedMath(Settings::self()->embeddedMathDefault()); } } bool Worksheet::loadJupyterNotebook(const QJsonDocument& doc) { m_type = Type::JupyterNotebook; int nbformatMajor, nbformatMinor; if (!Cantor::JupyterUtils::isJupyterNotebook(doc)) { // Two possibilities: old jupyter notebook (version <= 4.0.0 and a another scheme) or just not a notebook at all std::tie(nbformatMajor, nbformatMinor) = Cantor::JupyterUtils::getNbformatVersion(doc.object()); if (nbformatMajor == 0 && nbformatMinor == 0) { QApplication::restoreOverrideCursor(); showInvalidNotebookSchemeError(); } else { KMessageBox::error(worksheetView(), i18n("Jupyter notebooks with versions lower than 4.5 (detected version %1.%2) are not supported.", nbformatMajor, nbformatMinor ), i18n("Open File")); } 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,5,0)) { QApplication::restoreOverrideCursor(); KMessageBox::error( worksheetView(), i18n("Jupyter notebooks with versions higher than 4.5 (detected version %1.%2) are not supported.", nbformatMajor, nbformatMinor), i18n("Open File") ); 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); + + //There is "Python" only now, replace "python3" by "Python" + if (m_backendName == QLatin1String("python3")) + m_backendName = QLatin1String("Python"); + + //"python 2" in older projects not supported anymore, switch to Python (=Python3) + if (m_backendName == QLatin1String("python2")) + { + QApplication::restoreOverrideCursor(); + KMessageBox::information(worksheetView(), + i18n("This notebook was created using Python2 which is not supported anymore. Python3 will be used."), + i18n("Python2 not supported anymore")); + QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); + m_backendName = QLatin1String("Python"); + } + 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("Open File")); 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("Open File")); 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; if (m_firstEntry) { delete m_firstEntry; m_firstEntry = nullptr; } resetEntryCursor(); m_itemWidths.clear(); m_maxWidth = 0; if (!m_readOnly) initSession(backend); 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; updateLayout(); 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("Open File")); else KMessageBox::error(worksheetView(), i18n("Invalid Jupyter notebook scheme: %1", additionalInfo), i18n("Open File")); } 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::Renderer* Worksheet::renderer() { 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) { // Two menu: for particular entry and for selection (multiple entry) if (m_selectedEntries.isEmpty()) { 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* convertTo = new QMenu(menu); QMenu* insert = new QMenu(menu); QMenu* insertBefore = new QMenu(menu); convertTo->addAction(QIcon::fromTheme(QLatin1String("run-build")), i18n("Command Entry"), entry, &WorksheetEntry::convertToCommandEntry); convertTo->addAction(QIcon::fromTheme(QLatin1String("draw-text")), i18n("Text Entry"), entry, &WorksheetEntry::convertToTextEntry); #ifdef Discount_FOUND convertTo->addAction(QIcon::fromTheme(QLatin1String("text-x-markdown")), i18n("Markdown Entry"), entry, &WorksheetEntry::convertToMarkdownEntry); #endif #ifdef WITH_EPS convertTo->addAction(QIcon::fromTheme(QLatin1String("text-x-tex")), i18n("LaTeX Entry"), entry, &WorksheetEntry::convertToLatexEntry); #endif convertTo->addAction(QIcon::fromTheme(QLatin1String("image-x-generic")), i18n("Image"), entry, &WorksheetEntry::convertToImageEntry); convertTo->addAction(QIcon::fromTheme(QLatin1String("go-next-view-page")), i18n("Page Break"), entry, &WorksheetEntry::converToPageBreakEntry); 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())); convertTo->setTitle(i18n("Convert Entry To")); convertTo->setIcon(QIcon::fromTheme(QLatin1String("gtk-convert"))); 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(convertTo); 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())); } } else { menu->clear(); menu->addAction(QIcon::fromTheme(QLatin1String("go-up")), i18n("Move Entries Up"), this, SLOT(selectionMoveUp()), 0); menu->addAction(QIcon::fromTheme(QLatin1String("go-down")), i18n("Move Entries Down"), this, SLOT(selectionMoveDown()), 0); menu->addAction(QIcon::fromTheme(QLatin1String("media-playback-start")), i18n("Evaluate Entries"), this, SLOT(selectionEvaluate()), 0); menu->addAction(QIcon::fromTheme(QLatin1String("edit-delete")), i18n("Remove Entries"), this, SLOT(selectionRemove()), 0); } } 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) { /* if (event->button() == Qt::LeftButton && !focusItem() && lastEntry() && event->scenePos().y() > lastEntry()->y() + lastEntry()->size().height()) lastEntry()->focusEntry(WorksheetTextItem::BottomRight); */ if (!m_readOnly && event->buttons() & Qt::LeftButton) { WorksheetEntry* selectedEntry = entryAt(event->scenePos()); if (event->modifiers() & Qt::ControlModifier) { clearFocus(); resetEntryCursor(); if (selectedEntry) { selectedEntry->setCellSelected(!selectedEntry->isCellSelected()); selectedEntry->update(); WorksheetEntry* lastSelectedEntry = m_circularFocusBuffer.size() > 0 ? m_circularFocusBuffer.last() : nullptr; if (lastSelectedEntry) { lastSelectedEntry->setCellSelected(!lastSelectedEntry->isCellSelected()); lastSelectedEntry->update(); m_circularFocusBuffer.clear(); } for (WorksheetEntry* entry : {selectedEntry, lastSelectedEntry}) if (entry) { if (entry->isCellSelected()) m_selectedEntries.append(entry); else if (!entry->isCellSelected()) m_selectedEntries.removeOne(entry); } } } else { for (WorksheetEntry* entry : m_selectedEntries) { if(isValidEntry(entry)) { entry->setCellSelected(false); entry->update(); } } m_selectedEntries.clear(); if (selectedEntry) notifyEntryFocus(selectedEntry); updateEntryCursor(event); } } QGraphicsScene::mousePressEvent(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; } void Worksheet::changeEntryType(WorksheetEntry* target, int newType) { if (target && target->type() != newType) { bool animation_state = m_animationsEnabled; m_animationsEnabled = false; QString content; switch(target->type()) { case CommandEntry::Type: content = static_cast(target)->command(); break; case MarkdownEntry::Type: content = static_cast(target)->plainText(); break; case TextEntry::Type: content = static_cast(target)->text(); break; case LatexEntry::Type: content = static_cast(target)->plain(); } WorksheetEntry* newEntry = WorksheetEntry::create(newType, this); newEntry->setContent(content); if (newEntry) { WorksheetEntry* tmp = target; newEntry->setPrevious(tmp->previous()); newEntry->setNext(tmp->next()); tmp->setPrevious(nullptr); tmp->setNext(nullptr); tmp->clearFocus(); tmp->forceRemove(); if (newEntry->previous()) newEntry->previous()->setNext(newEntry); else setFirstEntry(newEntry); if (newEntry->next()) newEntry->next()->setPrevious(newEntry); else setLastEntry(newEntry); updateLayout(); makeVisible(newEntry); focusEntry(newEntry); setModified(); newEntry->focusEntry(); } m_animationsEnabled = animation_state; } } bool Worksheet::isValidEntry(WorksheetEntry* entry) { for (WorksheetEntry* iter = firstEntry(); iter; iter = iter->next()) if (entry == iter) return true; return false; } void Worksheet::selectionRemove() { for (WorksheetEntry* entry : m_selectedEntries) if (isValidEntry(entry)) entry->startRemoving(); m_selectedEntries.clear(); } void Worksheet::selectionEvaluate() { // run entries in worksheet order: from top to down for (WorksheetEntry* entry = firstEntry(); entry; entry = entry->next()) if (m_selectedEntries.indexOf(entry) != -1) entry->evaluate(); } void Worksheet::selectionMoveUp() { // movement up should have an order from top to down. for(WorksheetEntry* entry = firstEntry(); entry; entry = entry->next()) if(m_selectedEntries.indexOf(entry) != -1) if (entry->previous() && m_selectedEntries.indexOf(entry->previous()) == -1) entry->moveToPrevious(false); updateLayout(); } void Worksheet::selectionMoveDown() { // movement up should have an order from down to top. for(WorksheetEntry* entry = lastEntry(); entry; entry = entry->previous()) if(m_selectedEntries.indexOf(entry) != -1) if (entry->next() && m_selectedEntries.indexOf(entry->next()) == -1) entry->moveToNext(false); updateLayout(); } void Worksheet::notifyEntryFocus(WorksheetEntry* entry) { if (entry) { m_circularFocusBuffer.enqueue(entry); if (m_circularFocusBuffer.size() > 2) m_circularFocusBuffer.dequeue(); } else m_circularFocusBuffer.clear(); }