Commit 0812079f authored by Shubham  .'s avatar Shubham .
Browse files

Merge branch 'master' into gsoc20_documentation

parents f341d993 fc391344
......@@ -37,4 +37,4 @@ default cmake install directory (`/usr/local/` usually).
To uninstall the project:
```make uninstall or su -c 'make uninstall'```
`make uninstall` or `su -c 'make uninstall'`
......@@ -54,4 +54,6 @@ install(FILES pythonbackend.kcfg DESTINATION ${KDE_INSTALL_KCFGDIR})
install(TARGETS cantor_pythonbackend DESTINATION ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
install(TARGETS cantor_pythonserver DESTINATION ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
install(FILES graphic_packages.xml DESTINATION ${KDE_INSTALL_DATADIR}/cantor/python)
#install(DIRECTORY . DESTINATION ${KDE_INSTALL_DATADIR}/cantor/pythonbackend FILES_MATCHING PATTERN "*.py")
<GraphicPackages>
<GraphicPackage>
<Id>matplotlib</Id>
<Name>Matplotlib</Name>
<TestPresenceCommand>
try:
import matplotlib
print(1)
except ModuleNotFoundError:
print(0)
</TestPresenceCommand>
<EnableCommand>
from matplotlib import pyplot as __cantor_plt__;
__cantor_plot_prefix__ = '%1'
def __cantor_matplotlib_show__():
global __cantor_plot_global_counter__
__cantor_plt_filename__ = __cantor_plot_prefix__ + str(__cantor_plot_global_counter__)
__cantor_plot_global_counter__ += 1
__cantor_plt__.savefig(__cantor_plt_filename__)
print('\nINNER PLOT INFO CANTOR: ', __cantor_plt_filename__, sep='')
__cantor_plt__.clf();
__cantor_matplotlib_original_show__ = matplotlib.pyplot.show
__cantor_plt__.show = __cantor_matplotlib_show__
</EnableCommand>
<DisableCommand>
__cantor_plt__.show = __cantor_matplotlib_original_show__;
</DisableCommand>
<ToFileCommandTemplate></ToFileCommandTemplate>
</GraphicPackage>
<GraphicPackage>
<Id>plotly</Id>
<Name>Plot.ly</Name>
<TestPresenceCommand>
def __cantor_plotly_check_presence__():
import matplotlib;
import sys
import subprocess
output = subprocess.check_output(['orca', '--help'], shell=True)
output_str = output.decode(sys.stdout.encoding)
is_needed_orca = output_str.find('Plotly\'s image-exporting utilities') != -1
print(1 if is_needed_orca else 0)
try:
__cantor_plotly_check_presence__()
except ModuleNotFoundError:
print(0)
</TestPresenceCommand>
<EnableCommand>
import plotly as __cantor_plotly__
__cantor_plot_prefix__ = '%1'
def __cantor_plotly_show__(figure):
global __cantor_plot_global_counter__
__cantor_plt_filename__ = __cantor_plot_prefix__ + str(__cantor_plot_global_counter__) + '.png'
__cantor_plot_global_counter__ += 1
figure.write_image(__cantor_plt_filename__, scale=2)
print('\nINNER PLOT INFO CANTOR: ', __cantor_plt_filename__, sep='')
__cantor_plotly_original_show__ = __cantor_plotly__.basedatatypes.BaseFigure.show
__cantor_plotly__.basedatatypes.BaseFigure.show = __cantor_plotly_show__
</EnableCommand>
<DisableCommand>
__cantor_plotly__.basedatatypes.BaseFigure.show = __cantor_plotly_original_show__;
</DisableCommand>
<ToFileCommandTemplate></ToFileCommandTemplate>
</GraphicPackage>
</GraphicPackages>
......@@ -68,7 +68,8 @@ Cantor::Backend::Capabilities PythonBackend::capabilities() const
Backend::Capabilities cap =
Cantor::Backend::SyntaxHighlighting |
Cantor::Backend::Completion |
Cantor::Backend::SyntaxHelp;
Cantor::Backend::SyntaxHelp |
Cantor::Backend::IntegratedPlots;
if(PythonSettings::variableManagement())
cap |= Cantor::Backend::VariableManagement;
......
......@@ -24,5 +24,14 @@
<entry name="autorunScripts" type="StringList">
<label>List of scripts to autorun at the beginning of session</label>
</entry>
<entry name="choosenGraphicPackage" type="Enum">
<choices>
<choice name="matplotlib"/>
<choice name="plotly"/>
<choice name="all"/>
</choices>
<label>Graphical package, for which will be enable support for embedding plots</label>
<default>0</default>
</entry>
</group>
</kcfg>
......@@ -35,23 +35,17 @@
#include <QFile>
#include <QDebug>
PythonExpression::PythonExpression(Cantor::Session* session, bool internal) : Cantor::Expression(session, internal),
m_tempFile(nullptr)
#include "pythonsession.h"
PythonExpression::PythonExpression(Cantor::Session* session, bool internal) : Cantor::Expression(session, internal)
{
}
PythonExpression::~PythonExpression() {
if(m_tempFile)
delete m_tempFile;
}
void PythonExpression::evaluate()
{
if(m_tempFile) {
delete m_tempFile;
m_tempFile = nullptr;
}
session()->enqueueExpression(this);
}
......@@ -59,16 +53,19 @@ QString PythonExpression::internalCommand()
{
QString cmd = command();
if((PythonSettings::integratePlots()) && (command().contains(QLatin1String("show()")))){
m_tempFile = new QTemporaryFile(QDir::tempPath() + QLatin1String("/cantor_python-XXXXXX.png"));
m_tempFile->open();
QString saveFigCommand = QLatin1String("savefig('%1')");
cmd.replace(QLatin1String("show()"), saveFigCommand.arg(m_tempFile->fileName()));
if (PythonSettings::integratePlots())
{
PythonSession* pySession = static_cast<PythonSession*>(session());
const QString& filepath = pySession->plotFilePrefixPath() + QString::number(pySession->plotFileCounter()) + QLatin1String(".png");
QFileSystemWatcher* watcher = fileWatcher();
watcher->removePaths(watcher->files());
watcher->addPath(m_tempFile->fileName());
connect(watcher, &QFileSystemWatcher::fileChanged, this, &PythonExpression::imageChanged, Qt::UniqueConnection);
for(const Cantor::GraphicPackage& package : session()->enabledGraphicPackages())
{
if (package.isHavePlotCommand())
{
cmd.append(QLatin1String("\n"));
cmd.append(package.savePlotCommand(filepath, pySession->plotFileCounter()));
}
}
}
QStringList commandLine = cmd.split(QLatin1String("\n"));
......@@ -102,16 +99,38 @@ QString PythonExpression::internalCommand()
void PythonExpression::parseOutput(QString output)
{
qDebug() << "expression output: " << output;
if(command().simplified().startsWith(QLatin1String("help("))){
if(command().simplified().startsWith(QLatin1String("help(")))
{
setResult(new Cantor::HelpResult(output.remove(output.lastIndexOf(QLatin1String("None")), 4)));
} else {
if (!output.isEmpty())
addResult(new Cantor::TextResult(output));
}
else if (!output.isEmpty())
{
PythonSession* pySession = static_cast<PythonSession*>(session());
const QString& plotFilePrefixPath = pySession->plotFilePrefixPath();
const QString& searchPrefixPath = QLatin1String("INNER PLOT INFO CANTOR: ") + plotFilePrefixPath;
QStringList buffer;
const QStringList& lines = output.split(QLatin1String("\n"));
for (const QString& line : lines)
{
if (line.startsWith(searchPrefixPath))
{
if (!buffer.isEmpty() && !(buffer.size() == 1 && buffer[0].isEmpty()))
addResult(new Cantor::TextResult(buffer.join(QLatin1String("\n"))));
if (m_tempFile == nullptr || result() != nullptr) // not plot expression
setStatus(Cantor::Expression::Done);
QString filepath = plotFilePrefixPath + QString::number(pySession->plotFileCounter()) + QLatin1String(".png");
pySession->plotFileCounter()++;
addResult(new Cantor::ImageResult(QUrl::fromLocalFile(filepath)));
buffer.clear();
}
else
buffer.append(line);
}
if (!buffer.isEmpty() && !(buffer.size() == 1 && buffer[0].isEmpty()))
addResult(new Cantor::TextResult(buffer.join(QLatin1String("\n"))));
}
setStatus(Cantor::Expression::Done);
}
void PythonExpression::parseError(QString error)
......@@ -132,29 +151,6 @@ void PythonExpression::parseWarning(QString warning)
}
}
void PythonExpression::imageChanged()
{
if(m_tempFile->size() <= 0)
return;
Cantor::ImageResult* newResult = new Cantor::ImageResult(QUrl::fromLocalFile(m_tempFile->fileName()));
if (result() == nullptr)
setResult(newResult);
else
{
bool found = false;
for (int i = 0; i < results().size(); i++)
if (results()[i]->type() == newResult->type())
{
replaceResult(i, newResult);
found = true;
}
if (!found)
addResult(newResult);
}
setStatus(Done);
}
void PythonExpression::interrupt()
{
qDebug()<<"interruptinging command";
......
......@@ -39,10 +39,6 @@ class PythonExpression : public Cantor::Expression
void parseOutput(QString output);
void parseWarning(QString warning);
void parseError(QString error);
private:
void imageChanged();
QTemporaryFile* m_tempFile;
};
#endif /* _PYTHONEXPRESSION_H */
......@@ -60,12 +60,13 @@ void PythonServer::runPythonCommand(const string& command)
const char* prepareCommand =
"import sys;\n"\
"class CatchOutPythonBackend:\n"\
" def __init__(self):\n"\
" def __init__(self, std_stream):\n"\
" self.value = ''\n"\
" self.encoding = std_stream.encoding\n"\
" def write(self, txt):\n"\
" self.value += txt\n"\
"outputPythonBackend = CatchOutPythonBackend()\n"\
"errorPythonBackend = CatchOutPythonBackend()\n"\
"outputPythonBackend = CatchOutPythonBackend(sys.stdout)\n"\
"errorPythonBackend = CatchOutPythonBackend(sys.stderr)\n"\
"sys.stdout = outputPythonBackend\n"\
"sys.stderr = errorPythonBackend\n";
PyRun_SimpleString(prepareCommand);
......
......@@ -20,6 +20,7 @@
*/
#include <defaultvariablemodel.h>
#include <backend.h>
#include "pythonsession.h"
#include "pythonexpression.h"
#include "pythonvariablemodel.h"
......@@ -29,15 +30,16 @@
#include "pythonutils.h"
#include "settings.h"
#include <random>
#include <QDebug>
#include <QDir>
#include <QStandardPaths>
#include <QProcess>
#include <QFileInfo>
#include <KDirWatch>
#include <KLocalizedString>
#include <KMessageBox>
#ifndef Q_OS_WIN
#include <signal.h>
......@@ -51,6 +53,7 @@ const QChar messageEnd = 29;
PythonSession::PythonSession(Cantor::Backend* backend)
: Session(backend)
, m_process(nullptr)
, m_plotFileCounter(0)
{
setVariableModel(new PythonVariableModel(this));
}
......@@ -104,6 +107,20 @@ void PythonSession::login()
dir = QFileInfo(m_worksheetPath).absoluteDir().absolutePath();
sendCommand(QLatin1String("setFilePath"), QStringList() << m_worksheetPath << dir);
std::random_device rd;
std::mt19937 mt(rd());
std::uniform_int_distribution<int> rand_dist(0, 999999999);
m_plotFilePrefixPath =
QDir::tempPath()
+ QLatin1String("/cantor_python_")
+ QString::number(m_process->pid())
+ QLatin1String("_")
+ QString::number(rand_dist(mt))
+ QLatin1String("_");
m_plotFileCounter = 0;
evaluateExpression(QLatin1String("__cantor_plot_global_counter__ = 0"), Cantor::Expression::DeleteOnFinish, true);
const QStringList& scripts = PythonSettings::autorunScripts();
if(!scripts.isEmpty()){
QString autorunScripts = scripts.join(QLatin1String("\n"));
......@@ -131,6 +148,14 @@ void PythonSession::logout()
m_process->deleteLater();
m_process = nullptr;
if (!m_plotFilePrefixPath.isEmpty())
{
for (int i = 0; i < m_plotFileCounter; i++)
QFile::remove(m_plotFilePrefixPath + QString::number(i) + QLatin1String(".png"));
m_plotFilePrefixPath.clear();
m_plotFileCounter = 0;
}
qDebug()<<"logout";
Session::logout();
}
......@@ -163,6 +188,12 @@ void PythonSession::interrupt()
Cantor::Expression* PythonSession::evaluateExpression(const QString& cmd, Cantor::Expression::FinishingBehavior behave, bool internal)
{
// We ignore here internal because of two reasons
// 1. The function uses internal expressions itself
// 2. Internal commands don't come from user and don't produce plots, so don't need to check graphic packages
if (!internal)
updateGraphicPackagesFromSettings();
qDebug() << "evaluating: " << cmd;
PythonExpression* expr = new PythonExpression(this, internal);
......@@ -277,3 +308,67 @@ void PythonSession::reportServerProcessError(QProcess::ProcessError serverError)
}
reportSessionCrash();
}
int& PythonSession::plotFileCounter()
{
return m_plotFileCounter;
}
QString PythonSession::plotFilePrefixPath()
{
return m_plotFilePrefixPath;
}
void PythonSession::updateGraphicPackagesFromSettings()
{
QList<Cantor::GraphicPackage> enabledInSettingPackages;
if (PythonSettings::integratePlots())
{
int val = PythonSettings::choosenGraphicPackage();
if (val != PythonSettings::EnumChoosenGraphicPackage::all)
{
QString searchId;
Q_ASSERT(PythonSettings::EnumChoosenGraphicPackage::COUNT == 3);
if (val == PythonSettings::EnumChoosenGraphicPackage::matplotlib)
searchId = QLatin1String("matplotlib");
else if (val == PythonSettings::EnumChoosenGraphicPackage::plotly)
searchId = QLatin1String("plotly");
for (Cantor::GraphicPackage& package : backend()->availableGraphicPackages())
{
if (package.id() == searchId)
{
enabledInSettingPackages = QList<Cantor::GraphicPackage>{package};
break;
}
}
}
else
{
enabledInSettingPackages = backend()->availableGraphicPackages();
}
}
updateEnabledGraphicPackages(enabledInSettingPackages, m_plotFilePrefixPath);
}
QString PythonSession::graphicPackageErrorMessage(QString packageId) const
{
Q_ASSERT(PythonSettings::EnumChoosenGraphicPackage::COUNT == 3);
if (packageId == QLatin1String("matplotlib"))
{
return i18n(
"For using integrated graphics with Matplotlib package you need install \"matplotlib\" python package first."
);
}
else if (packageId == QLatin1String("plotly"))
{
return i18n(
"For using integrated graphic with Plot.ly you need install \"plotly\" python package and special Plot'ly compatible "
"\"orca\" executable. See \"Static Image Export\" article in Plot.ly documentation for details."
);
}
return QString();
}
......@@ -44,12 +44,15 @@ class PythonSession : public Cantor::Session
QSyntaxHighlighter* syntaxHighlighter(QObject* parent) override;
void setWorksheetPath(const QString& path) override;
QString plotFilePrefixPath();
int& plotFileCounter();
private:
QProcess* m_process;
QString m_worksheetPath;
QString m_output;
QString m_plotFilePrefixPath;
int m_plotFileCounter;
private Q_SLOT:
void readOutput();
......@@ -57,6 +60,8 @@ class PythonSession : public Cantor::Session
private:
void runFirstExpression() override;
void updateGraphicPackagesFromSettings();
QString graphicPackageErrorMessage(QString packageId) const override;
void sendCommand(const QString& command, const QStringList arguments = QStringList()) const;
};
......
......@@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<width>660</width>
<height>300</height>
</rect>
</property>
......@@ -66,6 +66,45 @@
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout3">
<item>
<widget class="QLabel" name="label">
<property name="toolTip">
<string>Graphic package, used for plotting. When integrated plots option is enable, then plots from the package will be integrated to worksheet</string>
</property>
<property name="text">
<string>Used Graphic Package: </string>
</property>
</widget>
</item>
<item>
<widget class="KComboBox" name="kcfg_choosenGraphicPackage">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<item>
<property name="text">
<string>Matplotlib (with seaborn, ggplot support too)</string>
</property>
</item>
<item>
<property name="text">
<string>Plot.ly</string>
</property>
</item>
<item>
<property name="text">
<string>All supported</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="toolTip">
......@@ -98,16 +137,21 @@
</widget>
<customwidgets>
<customwidget>
<class>KUrlRequester</class>
<extends>QFrame</extends>
<header>kurlrequester.h</header>
<container>1</container>
<class>KComboBox</class>
<extends>QComboBox</extends>
<header>kcombobox.h</header>
</customwidget>
<customwidget>
<class>KEditListWidget</class>
<extends>QWidget</extends>
<header>keditlistwidget.h</header>
</customwidget>
<customwidget>
<class>KUrlRequester</class>
<extends>QFrame</extends>
<header>kurlrequester.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
......
......@@ -28,6 +28,7 @@ set( cantor_LIB_SRCS
worksheetaccess.cpp
directives/plotdirectives.cpp
jupyterutils.cpp
graphicpackage.cpp
)
Set( cantor_LIB_HDRS
......@@ -55,6 +56,7 @@ Set( cantor_LIB_HDRS
defaultvariablemodel.h
worksheetaccess.h
jupyterutils.h
graphicpackage.h
)
ki18n_wrap_ui(cantor_LIB_SRCS directives/axisrange.ui directives/plottitle.ui)
......@@ -92,7 +94,7 @@ if(LIBSPECTRE_FOUND)
target_link_libraries(cantorlibs ${LIBSPECTRE_LIBRARY})
endif(LIBSPECTRE_FOUND)
set (CANTORLIBS_SOVERSION 25)
set (CANTORLIBS_SOVERSION 26)
set_target_properties( cantorlibs PROPERTIES VERSION ${RELEASE_SERVICE_VERSION} SOVERSION ${CANTORLIBS_SOVERSION})
ecm_setup_version(${RELEASE_SERVICE_VERSION}
......
......@@ -18,6 +18,8 @@
Copyright (C) 2009 Alexander Rieder <alexanderrieder@gmail.com>
*/
#include <vector>
#include "backend.h"
#include "extension.h"
......@@ -25,6 +27,7 @@
#include <QRegularExpression>
#include <QUrl>
#include <QProcess>
#include <QStandardPaths>
#include <KPluginMetaData>
#include <KLocalizedString>
......@@ -39,6 +42,7 @@ class Cantor::BackendPrivate
QString icon;
QString url;
bool enabled{true};
QList<GraphicPackage> supportedGraphicPackagesCache;
};
Backend::Backend(QObject* parent, const QList<QVariant>& args) : QObject(parent),
......@@ -248,3 +252,25 @@ bool Cantor::Backend::testProgramWritable(const QString& program, const QStringL
return true;
}
QList<GraphicPackage> Backend::availableGraphicPackages() const
{
if (d->supportedGraphicPackagesCache.size() != 0)
return d->supportedGraphicPackagesCache;
if (!(capabilities() & Capability::IntegratedPlots))
return QList<GraphicPackage>(); // because this cache is empty
QString packagesFile = id() + QLatin1String("/graphic_packages.xml");
QString filename = QStandardPaths::locate(QStandardPaths::AppDataLocation, packagesFile, QStandardPaths::LocateFile);
if (filename.isEmpty())
filename = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("cantor/") + packagesFile, QStandardPaths::LocateFile);
if (filename.isEmpty())
return QList<GraphicPackage>();
d->supportedGraphicPackagesCache = GraphicPackage::loadFromFile(filename);
return d->supportedGraphicPackagesCache;
}
......@@ -26,6 +26,8 @@
#include <KXMLGUIClient>
#include <KPluginInfo>
#include <graphicpackage.h>
#include "cantor_export.h"
class KConfigSkeleton;
......@@ -60,14 +62,15 @@ class CANTOR_EXPORT Backend : public QObject, public KXMLGUIClient
Nothing = 0x0, ///< the Backend doesn't support any of the optional features
LaTexOutput = 0x1, ///< it can output results as LaTeX code
InteractiveMode = 0x2, /**< it supports an interactive workflow. (It means a command
can ask for additional input while running
*/