diff --git a/src/Cutter.pro b/src/Cutter.pro index d494633d..8c943494 100644 --- a/src/Cutter.pro +++ b/src/Cutter.pro @@ -228,7 +228,9 @@ SOURCES += \ RunScriptTask.cpp \ dialogs/EditMethodDialog.cpp \ dialogs/LoadNewTypesDialog.cpp \ - widgets/SdbWidget.cpp + widgets/SdbWidget.cpp \ + plugins/CutterPythonPlugin.cpp \ + common/PythonManager.cpp HEADERS += \ Cutter.h \ @@ -339,7 +341,9 @@ HEADERS += \ common/Json.h \ dialogs/EditMethodDialog.h \ dialogs/LoadNewTypesDialog.h \ - widgets/SdbWidget.h + widgets/SdbWidget.h \ + plugins/CutterPythonPlugin.h \ + common/PythonManager.h FORMS += \ dialogs/AboutDialog.ui \ diff --git a/src/CutterApplication.cpp b/src/CutterApplication.cpp index 9ec1fe4b..f24bc0f0 100644 --- a/src/CutterApplication.cpp +++ b/src/CutterApplication.cpp @@ -1,4 +1,9 @@ +#include "common/PythonManager.h" #include "CutterApplication.h" +#ifdef CUTTER_ENABLE_JUPYTER +#include "common/JupyterConnection.h" +#endif +#include "plugins/CutterPythonPlugin.h" #include #include @@ -14,11 +19,6 @@ #include #include -#ifdef CUTTER_ENABLE_JUPYTER -#include "common/JupyterConnection.h" -#endif -#include "plugins/CutterPlugin.h" - #include "CutterConfig.h" #include @@ -72,11 +72,9 @@ CutterApplication::CutterApplication(int &argc, char **argv) : QApplication(argc QObject::tr("file")); cmd_parser.addOption(scriptOption); -#ifdef CUTTER_ENABLE_JUPYTER - QCommandLineOption pythonHomeOption("pythonhome", QObject::tr("PYTHONHOME to use for Jupyter"), + QCommandLineOption pythonHomeOption("pythonhome", QObject::tr("PYTHONHOME to use for embeded python interpreter"), "PYTHONHOME"); cmd_parser.addOption(pythonHomeOption); -#endif cmd_parser.process(*this); @@ -98,11 +96,12 @@ CutterApplication::CutterApplication(int &argc, char **argv) : QApplication(argc } } -#ifdef CUTTER_ENABLE_JUPYTER + // Init python if (cmd_parser.isSet(pythonHomeOption)) { - Jupyter()->setPythonHome(cmd_parser.value(pythonHomeOption)); + Python()->setPythonHome(cmd_parser.value(pythonHomeOption)); } -#endif + Python()->initialize(); + bool analLevelSpecified = false; int analLevel = 0; @@ -219,18 +218,29 @@ void CutterApplication::loadPlugins() return; } + Python()->addPythonPath(pluginsDir.absolutePath().toLatin1().data()); + + CutterPlugin *cutterPlugin = nullptr; foreach (QString fileName, pluginsDir.entryList(QDir::Files)) { - QPluginLoader pluginLoader(pluginsDir.absoluteFilePath(fileName)); - QObject *plugin = pluginLoader.instance(); - if (plugin) { - CutterPlugin *cutterPlugin = qobject_cast(plugin); - if (cutterPlugin) { - cutterPlugin->setupPlugin(Core()); - plugins.append(cutterPlugin); + if (fileName.endsWith(".py")) { + // Load python plugins + cutterPlugin = Python()->loadPlugin(fileName.toLatin1().data()); + } else { + // Load C++ plugins + QPluginLoader pluginLoader(pluginsDir.absoluteFilePath(fileName)); + QObject *plugin = pluginLoader.instance(); + if (plugin) { + cutterPlugin = qobject_cast(plugin); } } + + if (cutterPlugin) { + cutterPlugin->setupPlugin(Core()); + plugins.append(cutterPlugin); + } } + qDebug() << "Loaded" << plugins.length() << "plugins."; Core()->setCutterPlugins(plugins); } diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 5f9c8eed..b269bdc3 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -1,3 +1,4 @@ +#include "common/PythonManager.h" #include "MainWindow.h" #include "ui_MainWindow.h" #include "common/Helpers.h" diff --git a/src/common/JupyterConnection.cpp b/src/common/JupyterConnection.cpp index dd5dd197..9f7ac383 100644 --- a/src/common/JupyterConnection.cpp +++ b/src/common/JupyterConnection.cpp @@ -2,18 +2,12 @@ #include -#include -#include -#include -#include -#include -#include -#include - #include "JupyterConnection.h" #include "NestedIPyKernel.h" -#include "QtResImporter.h" -#include "PythonAPI.h" +#include "PythonManager.h" + +#include +#include Q_GLOBAL_STATIC(JupyterConnection, uniqueInstance) @@ -24,130 +18,32 @@ JupyterConnection *JupyterConnection::getInstance() JupyterConnection::JupyterConnection(QObject *parent) : QObject(parent) { - /* Will be removed/reworked with python plugins PR */ - initPythonHome(); - initPython(); - qDebug() << "Python init"; } JupyterConnection::~JupyterConnection() { - if (pyThreadState) { - PyEval_RestoreThread(pyThreadState); - - if (cutterNotebookAppInstance) { - auto stopFunc = PyObject_GetAttrString(cutterNotebookAppInstance, "stop"); - PyObject_CallObject(stopFunc, nullptr); - Py_DECREF(cutterNotebookAppInstance); - } - - Py_Finalize(); - } - - if (pythonHome) { - PyMem_RawFree(pythonHome); - } } -void JupyterConnection::initPythonHome() -{ -#if defined(APPIMAGE) || defined(MACOS_PYTHON_FRAMEWORK_BUNDLED) - if (customPythonHome.isNull()) { - auto pythonHomeDir = QDir(QCoreApplication::applicationDirPath()); -#ifdef APPIMAGE - // Executable is in appdir/bin - pythonHomeDir.cdUp(); - qInfo() << "Setting PYTHONHOME =" << pythonHomeDir.absolutePath() << " for AppImage."; -#else // MACOS_PYTHON_FRAMEWORK_BUNDLED - // @executable_path/../Frameworks/Python.framework/Versions/Current - pythonHomeDir.cd("../Frameworks/Python.framework/Versions/Current"); - qInfo() << "Setting PYTHONHOME =" << pythonHomeDir.absolutePath() << - " for macOS Application Bundle."; -#endif - customPythonHome = pythonHomeDir.absolutePath(); - } -#endif - - if (!customPythonHome.isNull()) { - qInfo() << "PYTHONHOME =" << customPythonHome; - pythonHome = Py_DecodeLocale(customPythonHome.toLocal8Bit().constData(), nullptr); - Py_SetPythonHome(pythonHome); - } - -} - -void JupyterConnection::initPython() -{ - PyImport_AppendInittab("_cutter", &PyInit_api); - PyImport_AppendInittab("cutter_internal", &PyInit_api_internal); - PyImport_AppendInittab("_qtres", &PyInit_qtres); - Py_Initialize(); - PyEval_InitThreads(); - - pyThreadState = PyEval_SaveThread(); -} - -void JupyterConnection::createCutterJupyterModule() -{ - if (pyThreadState) { - PyEval_RestoreThread(pyThreadState); - } - - cutterJupyterModule = QtResImport("cutter_jupyter"); - if (!cutterJupyterModule) { - PyErr_Print(); - qWarning() << "Could not import cutter_jupyter."; - emit creationFailed(); - pyThreadState = PyEval_SaveThread(); - return; - } - - pyThreadState = PyEval_SaveThread(); -} void JupyterConnection::start() { - if (cutterNotebookAppInstance) { + if (notebookInstanceExists) { return; } - if (!Py_IsInitialized()) { - initPython(); - } - - if (!cutterJupyterModule) { - createCutterJupyterModule(); - - if (!cutterJupyterModule) { - return; - } - } - - PyEval_RestoreThread(pyThreadState); - auto startFunc = PyObject_GetAttrString(cutterJupyterModule, "start_jupyter"); - cutterNotebookAppInstance = PyObject_CallObject(startFunc, nullptr); - pyThreadState = PyEval_SaveThread(); + notebookInstanceExists = Python()->startJupyterNotebook(); emit urlReceived(getUrl()); } QString JupyterConnection::getUrl() { - if (!cutterNotebookAppInstance) { + if (!notebookInstanceExists) { return nullptr; } - PyEval_RestoreThread(pyThreadState); - - auto urlWithToken = PyObject_GetAttrString(cutterNotebookAppInstance, "url_with_token"); - auto asciiBytes = PyUnicode_AsASCIIString(urlWithToken); - auto urlWithTokenString = QString::fromUtf8(PyBytes_AsString(asciiBytes)); - Py_DECREF(asciiBytes); - Py_DECREF(urlWithToken); - - pyThreadState = PyEval_SaveThread(); - - return urlWithTokenString; + QString url = Python()->getJupyterUrl(); + return url; } long JupyterConnection::startNestedIPyKernel(const QStringList &argv) diff --git a/src/common/JupyterConnection.h b/src/common/JupyterConnection.h index 3848488d..445b1296 100644 --- a/src/common/JupyterConnection.h +++ b/src/common/JupyterConnection.h @@ -5,16 +5,9 @@ #include #include -#include class NestedIPyKernel; -struct _object; -typedef _object PyObject; - -struct _ts; -typedef _ts PyThreadState; - class JupyterConnection : public QObject { Q_OBJECT @@ -25,11 +18,6 @@ public: JupyterConnection(QObject *parent = nullptr); ~JupyterConnection(); - void setPythonHome(const QString pythonHome) - { - customPythonHome = pythonHome; - } - void start(); QString getUrl(); @@ -42,26 +30,15 @@ signals: void creationFailed(); private: - PyObject *cutterJupyterModule = nullptr; - PyObject *cutterNotebookAppInstance = nullptr; - - PyThreadState *pyThreadState = nullptr; - QMap kernels; long nextKernelId = 1; - QString customPythonHome; - - wchar_t *pythonHome = nullptr; - - void initPythonHome(); - void initPython(); - void createCutterJupyterModule(); + bool notebookInstanceExists = false; }; #define Jupyter() (JupyterConnection::getInstance()) -#endif +#endif // CUTTER_ENABLE_JUPYTER -#endif //JUPYTERCONNECTION_H +#endif // JUPYTERCONNECTION_H diff --git a/src/common/PythonAPI.cpp b/src/common/PythonAPI.cpp index 79e8c808..1dc18768 100644 --- a/src/common/PythonAPI.cpp +++ b/src/common/PythonAPI.cpp @@ -1,10 +1,6 @@ -#ifdef CUTTER_ENABLE_JUPYTER - #include "PythonAPI.h" #include "Cutter.h" -#include "JupyterConnection.h" -#include "NestedIPyKernel.h" #include "CutterConfig.h" @@ -88,6 +84,9 @@ PyObject *PyInit_api() // ----------------------------- +#ifdef CUTTER_ENABLE_JUPYTER +#include "JupyterConnection.h" +#include "NestedIPyKernel.h" PyObject *api_internal_launch_ipykernel(PyObject *self, PyObject *args, PyObject *kw) { @@ -195,4 +194,4 @@ PyObject *PyInit_api_internal() return PyModule_Create(&CutterInternalModule); } -#endif +#endif // CUTTER_ENABLE_JUPYTER diff --git a/src/common/PythonManager.cpp b/src/common/PythonManager.cpp new file mode 100644 index 00000000..970cc65b --- /dev/null +++ b/src/common/PythonManager.cpp @@ -0,0 +1,170 @@ +#include "PythonAPI.h" +#include "PythonManager.h" + +#include +#include +#include + +#include "QtResImporter.h" +#include "plugins/CutterPythonPlugin.h" + +Q_GLOBAL_STATIC(PythonManager, uniqueInstance) + +PythonManager *PythonManager::getInstance() +{ + return uniqueInstance; +} + +PythonManager::PythonManager() +{ +} + +PythonManager::~PythonManager() +{ + if (pyThreadState) { + PyEval_RestoreThread(pyThreadState); + + if (cutterNotebookAppInstance) { + auto stopFunc = PyObject_GetAttrString(cutterNotebookAppInstance, "stop"); + PyObject_CallObject(stopFunc, nullptr); + Py_DECREF(cutterNotebookAppInstance); + } + + Py_Finalize(); + } + + if (pythonHome) { + PyMem_RawFree(pythonHome); + } +} + +void PythonManager::initPythonHome() +{ +#if defined(APPIMAGE) || defined(MACOS_PYTHON_FRAMEWORK_BUNDLED) + if (customPythonHome.isNull()) { + auto pythonHomeDir = QDir(QCoreApplication::applicationDirPath()); +# ifdef APPIMAGE + // Executable is in appdir/bin + pythonHomeDir.cdUp(); + qInfo() << "Setting PYTHONHOME =" << pythonHomeDir.absolutePath() << " for AppImage."; +# else // MACOS_PYTHON_FRAMEWORK_BUNDLED + // @executable_path/../Frameworks/Python.framework/Versions/Current + pythonHomeDir.cd("../Frameworks/Python.framework/Versions/Current"); + qInfo() << "Setting PYTHONHOME =" << pythonHomeDir.absolutePath() << + " for macOS Application Bundle."; +# endif + customPythonHome = pythonHomeDir.absolutePath(); + } +#endif + + if (!customPythonHome.isNull()) { + qInfo() << "PYTHONHOME =" << customPythonHome; + pythonHome = Py_DecodeLocale(customPythonHome.toLocal8Bit().constData(), nullptr); + Py_SetPythonHome(pythonHome); + } +} + +void PythonManager::initialize() +{ + initPythonHome(); + + PyImport_AppendInittab("_cutter", &PyInit_api); + PyImport_AppendInittab("cutter_internal", &PyInit_api_internal); + PyImport_AppendInittab("_qtres", &PyInit_qtres); + Py_Initialize(); + PyEval_InitThreads(); + + // Import other modules + cutterJupyterModule = QtResImport("cutter_jupyter"); + cutterPluginModule = QtResImport("cutter_plugin"); + + pyThreadState = PyEval_SaveThread(); +} + +void PythonManager::addPythonPath(char *path) { + if (pyThreadState) { + PyEval_RestoreThread(pyThreadState); + } + + PyObject *sysModule = PyImport_ImportModule("sys"); + if (!sysModule) { + return; + } + PyObject *pythonPath = PyObject_GetAttrString(sysModule, "path"); + if (!pythonPath) { + return; + } + PyObject *append = PyObject_GetAttrString(pythonPath, "append"); + if (!append) { + return; + } + PyEval_CallFunction(append, "(s)", path); + + pyThreadState = PyEval_SaveThread(); +} + +bool PythonManager::startJupyterNotebook() +{ + PyEval_RestoreThread(pyThreadState); + + PyObject* startFunc = PyObject_GetAttrString(cutterJupyterModule, "start_jupyter"); + if (!startFunc) { + qWarning() << "Couldn't get attribute start_jupyter."; + return false; + } + + cutterNotebookAppInstance = PyObject_CallObject(startFunc, nullptr); + pyThreadState = PyEval_SaveThread(); + + return cutterNotebookAppInstance != nullptr; +} + +QString PythonManager::getJupyterUrl() +{ + PyEval_RestoreThread(pyThreadState); + + auto urlWithToken = PyObject_GetAttrString(cutterNotebookAppInstance, "url_with_token"); + auto asciiBytes = PyUnicode_AsASCIIString(urlWithToken); + auto urlWithTokenString = QString::fromUtf8(PyBytes_AsString(asciiBytes)); + Py_DECREF(asciiBytes); + Py_DECREF(urlWithToken); + + pyThreadState = PyEval_SaveThread(); + + return urlWithTokenString; +} + +CutterPythonPlugin* PythonManager::loadPlugin(char *pluginName) { + CutterPythonPlugin *plugin = nullptr; + if (!cutterPluginModule) { + return plugin; + } + + if (pyThreadState) { + PyEval_RestoreThread(pyThreadState); + } + + PyObject *pluginModule = PyImport_ImportModule(pluginName); + if (!pluginModule) { + qWarning() << "Couldn't import the plugin" << QString(pluginName); + } + plugin = new CutterPythonPlugin(pluginModule); + + pyThreadState = PyEval_SaveThread(); + + return plugin; +} + +PyObject *PythonManager::getAttrStringSafe(PyObject *object, const char* attribute) +{ + PyObject *result = nullptr; + if (pyThreadState) { + PyEval_RestoreThread(pyThreadState); + } + + result = PyObject_GetAttrString(object, attribute); + + pyThreadState = PyEval_SaveThread(); + + return result; +} diff --git a/src/common/PythonManager.h b/src/common/PythonManager.h new file mode 100644 index 00000000..4c048a50 --- /dev/null +++ b/src/common/PythonManager.h @@ -0,0 +1,48 @@ +#ifndef PYTHONMANAGER_H +#define PYTHONMANAGER_H + +#include + +class CutterPythonPlugin; +typedef struct _ts PyThreadState; +typedef struct _object PyObject; + +class PythonManager : public QObject +{ + Q_OBJECT + +public: + static PythonManager *getInstance(); + + PythonManager(); + ~PythonManager(); + + void setPythonHome(const QString pythonHome) + { + customPythonHome = pythonHome; + } + + void initPythonHome(); + void initialize(); + void addPythonPath(char *path); + + bool startJupyterNotebook(); + QString getJupyterUrl(); + + CutterPythonPlugin *loadPlugin(char *pluginName); + + PyObject *getAttrStringSafe(PyObject *object, const char* attribute); + +private: + QString customPythonHome; + wchar_t *pythonHome = nullptr; + PyThreadState *pyThreadState = nullptr; + + PyObject *cutterJupyterModule; + PyObject *cutterPluginModule; + PyObject *cutterNotebookAppInstance = nullptr; +}; + +#define Python() (PythonManager::getInstance()) + +#endif // PYTHONMANAGER_H diff --git a/src/plugins/CutterPythonPlugin.cpp b/src/plugins/CutterPythonPlugin.cpp new file mode 100644 index 00000000..1e1cf446 --- /dev/null +++ b/src/plugins/CutterPythonPlugin.cpp @@ -0,0 +1,36 @@ + +#include + +#include "CutterPythonPlugin.h" + +CutterPythonPlugin::CutterPythonPlugin(PyObject* pluginModule) +{ + this->pluginModule = pluginModule; +} + +void CutterPythonPlugin::setupPlugin(CutterCore *core) +{ + Q_UNUSED(core) + + //PyObject *pInstance = PyObject_GetAttrString(pluginModule, "plugin"); + PyObject *pInstance = Python()->getAttrStringSafe(pluginModule, "plugin"); + if (!pInstance) { + qWarning() << "Cannot find plugin instance."; + return; + } + + PyObject *pName = PyObject_GetAttrString(pInstance, "name"); + qDebug() << "pname" << pName; + if (pName) { + this->name = QString(PyUnicode_AS_DATA(pName)); + qDebug() << "OK COOL" << this->name; + } +} + +CutterDockWidget* CutterPythonPlugin::setupInterface(MainWindow *main, QAction *action) +{ + Q_UNUSED(main) + Q_UNUSED(action) + + return nullptr; +} diff --git a/src/plugins/CutterPythonPlugin.h b/src/plugins/CutterPythonPlugin.h new file mode 100644 index 00000000..cb8356a5 --- /dev/null +++ b/src/plugins/CutterPythonPlugin.h @@ -0,0 +1,18 @@ +#ifndef CUTTERPYTHONPLUGIN_H +#define CUTTERPYTHONPLUGIN_H + +#include "common/PythonManager.h" +#include "CutterPlugin.h" + +class CutterPythonPlugin : public CutterPlugin +{ +public: + CutterPythonPlugin(PyObject* pluginModule); + void setupPlugin(CutterCore *core); + CutterDockWidget* setupInterface(MainWindow *main, QAction *action); + +private: + PyObject *pluginModule; +}; + +#endif // CUTTERPYTHONPLUGIN_H diff --git a/src/python/cutter_plugin.py b/src/python/cutter_plugin.py new file mode 100644 index 00000000..1019cdb9 --- /dev/null +++ b/src/python/cutter_plugin.py @@ -0,0 +1,21 @@ +from abc import ABC, abstractmethod + +class CutterPlugin(ABC): + name = '' + description = '' + version = '' + author = '' + core = None + dockable = None + + @abstractmethod + def __init__(self): + pass + + @abstractmethod + def setupPlugin(self): + pass + + @abstractmethod + def setupInterface(self, main, action): + pass diff --git a/src/resources.qrc b/src/resources.qrc index 4d44e914..60e65405 100644 --- a/src/resources.qrc +++ b/src/resources.qrc @@ -86,5 +86,6 @@ img/icons/fork.svg python/cutter.py python/reg_qtres_importer.py + python/cutter_plugin.py diff --git a/src/widgets/JupyterWidget.cpp b/src/widgets/JupyterWidget.cpp index 498d0fda..f02aba68 100644 --- a/src/widgets/JupyterWidget.cpp +++ b/src/widgets/JupyterWidget.cpp @@ -1,9 +1,8 @@ - #ifdef CUTTER_ENABLE_JUPYTER -#include "ui_JupyterWidget.h" - +#include "common/JupyterConnection.h" #include "JupyterWidget.h" +#include "ui_JupyterWidget.h" #include #include diff --git a/src/widgets/JupyterWidget.h b/src/widgets/JupyterWidget.h index bcb211c2..c7c9264e 100644 --- a/src/widgets/JupyterWidget.h +++ b/src/widgets/JupyterWidget.h @@ -4,12 +4,10 @@ #ifdef CUTTER_ENABLE_JUPYTER -#include - -#include - #include "CutterDockWidget.h" -#include "common/JupyterConnection.h" + +#include +#include namespace Ui { class JupyterWidget;