Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- diff --git a/plugins/extensions/pykrita/kritarunner/CMakeLists.txt b/plugins/extensions/pykrita/kritarunner/CMakeLists.txt
- --- a/plugins/extensions/pykrita/kritarunner/CMakeLists.txt
- +++ b/plugins/extensions/pykrita/kritarunner/CMakeLists.txt
- @@ -5,10 +5,12 @@
- )
- set(kritarunner_SRCS main.cpp
- - ../plugin/engine.cpp
- ../plugin/plugin.cpp
- ../plugin/pyqtpluginsettings.cpp
- ../plugin/utilities.cpp
- + ../plugin/PykritaModule.cpp
- + ../plugin/PythonPluginManager.cpp
- + ../plugin/PythonPluginsModel.cpp
- )
- add_executable(kritarunner ${kritarunner_SRCS})
- diff --git a/plugins/extensions/pykrita/kritarunner/main.cpp b/plugins/extensions/pykrita/kritarunner/main.cpp
- --- a/plugins/extensions/pykrita/kritarunner/main.cpp
- +++ b/plugins/extensions/pykrita/kritarunner/main.cpp
- @@ -26,11 +26,9 @@
- #include <KoGlobal.h>
- #include <resources/KoHashGeneratorProvider.h>
- #include "kis_md5_generator.h"
- +#include "PythonPluginManager.h"
- #include <opengl/kis_opengl.h>
- -#include <engine.h>
- -#include <utilities.h>
- -
- extern "C" int main(int argc, char **argv)
- {
- // The global initialization of the random generator
- @@ -80,12 +78,26 @@
- qDebug() << "\tPython path:" << pythonPath;
- qDebug() << "Creating engine";
- - PyKrita::Engine engine;
- - QString r = engine.tryInitializeGetFailureReason();
- - if (!r.isEmpty()) {
- - qDebug("Could not initialize the Python engine");
- - return 1;
- + // TODO: refactor to share common parts with plugin.cpp
- +
- + PyKrita::InitResult initResult = PyKrita::initialize();
- +
- + switch (initResult) {
- + case PyKrita::INIT_OK:
- + break;
- + case PyKrita::INIT_CANNOT_LOAD_PYTHON_LIBRARY:
- + qWarning() << i18n("Cannot load Python library");
- + return 1;
- + case PyKrita::INIT_CANNOT_SET_PYTHON_PATHS:
- + qWarning() << i18n("Cannot set Python paths");
- + return 1;
- + case PyKrita::INIT_CANNOT_LOAD_PYKRITA_MODULE:
- + qWarning() << i18n("Cannot load built-in pykrita module");
- + return 1;
- + default:
- + qWarning() << i18n("Unexpected error initializing python plugin.");
- + return 1;
- }
- qDebug() << "Try to import the pykrita module";
- diff --git a/plugins/extensions/pykrita/plugin/CMakeLists.txt b/plugins/extensions/pykrita/plugin/CMakeLists.txt
- --- a/plugins/extensions/pykrita/plugin/CMakeLists.txt
- +++ b/plugins/extensions/pykrita/plugin/CMakeLists.txt
- @@ -7,7 +7,9 @@
- plugin.cpp
- pyqtpluginsettings.cpp
- utilities.cpp
- - engine.cpp
- + PykritaModule.cpp
- + PythonPluginManager.cpp
- + PythonPluginsModel.cpp
- )
- ki18n_wrap_ui(SOURCES
- diff --git a/plugins/extensions/pykrita/plugin/PykritaModule.h b/plugins/extensions/pykrita/plugin/PykritaModule.h
- new file mode 100644
- --- /dev/null
- +++ b/plugins/extensions/pykrita/plugin/PykritaModule.h
- @@ -0,0 +1,33 @@
- +/*
- + * This file is part of PyKrita, Krita' Python scripting plugin.
- + *
- + * Copyright (C) 2013 Alex Turbov <i.zaufi@gmail.com>
- + * Copyright (C) 2014-2016 Boudewijn Rempt <boud@valdyas.org>
- + *
- + * This library is free software; you can redistribute it and/or
- + * modify it under the terms of the GNU Library General Public
- + * License as published by the Free Software Foundation; either
- + * version 2 of the License, or (at your option) version 3.
- + *
- + * This library 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
- + * Library General Public License for more details.
- + *
- + * You should have received a copy of the GNU Library General Public License
- + * along with this library; see the file COPYING.LIB. If not, write to
- + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- + * Boston, MA 02110-1301, USA.
- + */
- +
- +#ifndef __PYKRITA_MODULE_H__
- +#define __PYKRITA_MODULE_H__
- +
- +#include <Python.h>
- +
- +/**
- + * Initializer for the built-in Python module.
- + */
- +PyMODINIT_FUNC PyInit_pykrita();
- +
- +#endif
- diff --git a/plugins/extensions/pykrita/plugin/PykritaModule.cpp b/plugins/extensions/pykrita/plugin/PykritaModule.cpp
- new file mode 100644
- --- /dev/null
- +++ b/plugins/extensions/pykrita/plugin/PykritaModule.cpp
- @@ -0,0 +1,79 @@
- +// This file is part of PyKrita, Krita' Python scripting plugin.
- +//
- +// Copyright (C) 2006 Paul Giannaros <paul@giannaros.org>
- +// Copyright (C) 2012, 2013 Shaheed Haque <srhaque@theiet.org>
- +// Copyright (C) 2013 Alex Turbov <i.zaufi@gmail.com>
- +//
- +// This library is free software; you can redistribute it and/or
- +// modify it under the terms of the GNU Lesser General Public
- +// License as published by the Free Software Foundation; either
- +// version 2.1 of the License, or (at your option) version 3, or any
- +// later version accepted by the membership of KDE e.V. (or its
- +// successor approved by the membership of KDE e.V.), which shall
- +// act as a proxy defined in Section 6 of version 3 of the license.
- +//
- +// This library 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
- +// Lesser General Public License for more details.
- +//
- +// You should have received a copy of the GNU Lesser General Public
- +// License along with this library. If not, see <http://www.gnu.org/licenses/>.
- +//
- +
- +#include "PykritaModule.h"
- +
- +#include "kis_debug.h"
- +
- +#define PYKRITA_INIT PyInit_pykrita
- +
- +/// \note Namespace name written in uppercase intentionally!
- +/// It will appear in debug output from Python plugins...
- +namespace PYKRITA
- +{
- + PyObject* debug(PyObject* /*self*/, PyObject* args)
- + {
- + const char* text;
- +
- + if (PyArg_ParseTuple(args, "s", &text))
- + dbgScript << text;
- + Py_INCREF(Py_None);
- + return Py_None;
- + }
- +} // namespace PYKRITA
- +
- +namespace
- +{
- + PyMethodDef pykritaMethods[] = {
- + {
- + "qDebug"
- + , &PYKRITA::debug
- + , METH_VARARGS
- + , "True KDE way to show debug info"
- + }
- + , { 0, 0, 0, 0 }
- + };
- +} // anonymous namespace
- +
- +//BEGIN Python module registration
- +PyMODINIT_FUNC PyInit_pykrita()
- +{
- + static struct PyModuleDef moduledef = {
- + PyModuleDef_HEAD_INIT
- + , "pykrita"
- + , "The pykrita module"
- + , -1
- + , pykritaMethods
- + , 0
- + , 0
- + , 0
- + , 0
- + };
- + PyObject *pykritaModule = PyModule_Create(&moduledef);
- + PyModule_AddStringConstant(pykritaModule, "__file__", __FILE__);
- + return pykritaModule;
- +}
- +//END Python module registration
- +
- +// krita: space-indent on; indent-width 4;
- +#undef PYKRITA_INIT
- diff --git a/plugins/extensions/pykrita/plugin/PythonPluginManager.h b/plugins/extensions/pykrita/plugin/PythonPluginManager.h
- new file mode 100644
- --- /dev/null
- +++ b/plugins/extensions/pykrita/plugin/PythonPluginManager.h
- @@ -0,0 +1,142 @@
- +/*
- + * This file is part of PyKrita, Krita' Python scripting plugin.
- + *
- + * Copyright (C) 2013 Alex Turbov <i.zaufi@gmail.com>
- + * Copyright (C) 2014-2016 Boudewijn Rempt <boud@valdyas.org>
- + * Copyright (C) 2017 Jouni Pentikäinen (joupent@gmail.com)
- + *
- + * This library is free software; you can redistribute it and/or
- + * modify it under the terms of the GNU Library General Public
- + * License as published by the Free Software Foundation; either
- + * version 2 of the License, or (at your option) any later version.
- + *
- + * This library 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
- + * Library General Public License for more details.
- + *
- + * You should have received a copy of the GNU Library General Public License
- + * along with this library; see the file COPYING.LIB. If not, write to
- + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- + * Boston, MA 02110-1301, USA.
- + */
- +#ifndef PYTHONMODULEMANAGER_H
- +#define PYTHONMODULEMANAGER_H
- +
- +#include <QObject>
- +#include "version_checker.h"
- +#include "PythonPluginsModel.h"
- +
- +class PythonPluginsModel;
- +
- +/**
- + * Represents a Python described in the plugin's .desktop file.
- + */
- +class PythonPlugin
- +{
- +public:
- + /**
- + * Transforms the Python module name into a file path part
- + */
- + QString moduleFilePathPart() const;
- +
- + bool isValid() const;
- +
- + inline const QString& errorReason() const
- + {
- + return m_errorReason;
- + }
- +
- + inline bool isEnabled() const
- + {
- + return m_enabled;
- + }
- +
- + inline bool isBroken() const
- + {
- + return m_broken;
- + }
- +
- + inline bool isUnstable() const
- + {
- + return m_unstable;
- + }
- +
- + QString name() const
- + {
- + return m_name;
- + }
- +
- + QString moduleName() const
- + {
- + return m_moduleName;
- + }
- +
- + QVariant property(const QString &name) const
- + {
- + return m_properties.value(name, "");
- + }
- +
- + QString comment() const
- + {
- + return m_comment;
- + }
- +
- +private:
- + friend class PythonPluginManager;
- +
- + PythonPlugin() {
- + m_properties["X-Python-Dependencies"] = QStringList();
- + m_properties["X-Python-2-Dependencies"] = QStringList();
- + }
- +
- + QString m_errorReason;
- + bool m_enabled{false};
- + bool m_broken{false};
- + bool m_unstable{false};
- + bool m_loaded{false};
- +
- + QString m_name;
- + QString m_moduleName;
- + QString m_comment;
- + QMap<QString, QVariant> m_properties;
- +};
- +
- +/**
- + * The Python plugin manager handles discovery, loading and unloading of Python plugins.
- + * To get a reference to the manager, use PyKrita::pluginManager().
- + */
- +class PythonPluginManager : public QObject
- +{
- + Q_OBJECT
- +
- +public:
- + PythonPluginManager();
- + ~PythonPluginManager() override;
- +
- + const QList<PythonPlugin>& plugins() const;
- + PythonPlugin *plugin(int index);
- +
- + void scanPlugins();
- + void tryLoadEnabledPlugins();
- + void setPluginEnabled(PythonPlugin &plugin, bool enabled);
- +
- + PythonPluginsModel *model();
- +
- +public Q_SLOTS:
- + void unloadAllModules();
- +
- +private:
- + void loadModule(PythonPlugin &plugin);
- + void unloadModule(PythonPlugin &plugin);
- +
- + static bool verifyModuleExists(PythonPlugin &);
- + static void verifyDependenciesSetStatus(PythonPlugin&);
- + static QPair<QString, PyKrita::version_checker> parseDependency(const QString&);
- +
- + QList<PythonPlugin> m_plugins;
- +
- + PythonPluginsModel m_model;
- +};
- +
- +#endif //PYTHONMODULEMANAGER_H
- diff --git a/plugins/extensions/pykrita/plugin/PythonPluginManager.cpp b/plugins/extensions/pykrita/plugin/PythonPluginManager.cpp
- new file mode 100644
- --- /dev/null
- +++ b/plugins/extensions/pykrita/plugin/PythonPluginManager.cpp
- @@ -0,0 +1,416 @@
- +/*
- + * This file is part of PyKrita, Krita' Python scripting plugin.
- + *
- + * Copyright (C) 2013 Alex Turbov <i.zaufi@gmail.com>
- + * Copyright (C) 2014-2016 Boudewijn Rempt <boud@valdyas.org>
- + * Copyright (C) 2017 Jouni Pentikäinen (joupent@gmail.com)
- + *
- + * This library is free software; you can redistribute it and/or
- + * modify it under the terms of the GNU Library General Public
- + * License as published by the Free Software Foundation; either
- + * version 2 of the License, or (at your option) any later version.
- + *
- + * This library 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
- + * Library General Public License for more details.
- + *
- + * You should have received a copy of the GNU Library General Public License
- + * along with this library; see the file COPYING.LIB. If not, write to
- + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- + * Boston, MA 02110-1301, USA.
- + */
- +
- +#include "PythonPluginManager.h"
- +
- +#include <QtCore/QSettings>
- +#include <KoResourcePaths.h>
- +#include <KConfigCore/KConfig>
- +#include <KI18n/KLocalizedString>
- +#include <KConfigCore/KSharedConfig>
- +#include <KConfigCore/KConfigGroup>
- +
- +#include "config.h"
- +#include "version_checker.h"
- +
- +PythonPluginManager* instance = 0;
- +
- +// PythonPlugin implementation
- +
- +QString PythonPlugin::moduleFilePathPart() const
- +{
- + QString filePath = m_moduleName;
- + return filePath.replace(".", "/");
- +}
- +
- +bool PythonPlugin::isValid() const
- +{
- + dbgScript << "Got Krita/PythonPlugin: " << name()
- + << ", module-path=" << moduleName()
- + ;
- + // Make sure mandatory properties are here
- + if (m_name.isEmpty()) {
- + dbgScript << "Ignore desktop file w/o a name";
- + return false;
- + }
- + if (m_moduleName.isEmpty()) {
- + dbgScript << "Ignore desktop file w/o a module to import";
- + return false;
- + }
- +
- + return true;
- +}
- +
- +// PythonPluginManager implementation
- +
- +PythonPluginManager::PythonPluginManager()
- + : QObject(0)
- + , m_model(0, this)
- +{}
- +
- +/// \todo More accurate shutdown required:
- +/// need to keep track what exactly was broken on
- +/// initialize attempt...
- +PythonPluginManager::~PythonPluginManager()
- +{
- + dbgScript << "Going to destroy the Python engine";
- +
- + // Notify Python that engine going to die
- + {
- + PyKrita::Python py = PyKrita::Python();
- + py.functionCall("_pykritaUnloading");
- + }
- + unloadAllModules();
- +
- + PyKrita::Python::maybeFinalize();
- + PyKrita::Python::libraryUnload();
- +}
- +
- +const QList<PythonPlugin>& PythonPluginManager::plugins() const
- +{
- + return m_plugins;
- +}
- +
- +PythonPlugin * PythonPluginManager::plugin(int index) {
- + if (index >= 0 && index < m_plugins.count()) {
- + return &m_plugins[index];
- + }
- +
- + return nullptr;
- +}
- +
- +PythonPluginsModel * PythonPluginManager::model()
- +{
- + return &m_model;
- +}
- +
- +void PythonPluginManager::unloadAllModules()
- +{
- + Q_FOREACH(PythonPlugin plugin, m_plugins) {
- + unloadModule(plugin);
- + }
- +}
- +
- +bool PythonPluginManager::verifyModuleExists(PythonPlugin &plugin)
- +{
- + // Find the module:
- + // 0) try to locate directory based plugin first
- + QString rel_path = plugin.moduleFilePathPart();
- + rel_path = rel_path + "/" + "__init__.py";
- + dbgScript << "Finding Python module with rel_path:" << rel_path;
- +
- + QString module_path = KoResourcePaths::findResource("pythonscripts", rel_path);
- +
- + dbgScript << "module_path:" << module_path;
- +
- + if (module_path.isEmpty()) {
- + // 1) Nothing found, then try file based plugin
- + rel_path = plugin.moduleFilePathPart() + ".py";
- + dbgScript << "Finding Python module with rel_path:" << rel_path;
- + module_path = KoResourcePaths::findResource("pythonscripts", rel_path);
- + dbgScript << "module_path:" << module_path;
- + }
- +
- + // Is anything found at all?
- + if (module_path.isEmpty()) {
- + plugin.m_broken = true;
- + plugin.m_errorReason = i18nc(
- + "@info:tooltip"
- + , "Unable to find the module specified <application>%1</application>"
- + , plugin.moduleName()
- + );
- + dbgScript << "Cannot load module:" << plugin.m_errorReason;
- + return false;
- + }
- + dbgScript << "Found module path:" << module_path;
- + return true;
- +}
- +
- +QPair<QString, PyKrita::version_checker> PythonPluginManager::parseDependency(const QString& d)
- +{
- + // Check if dependency has package info attached
- + const int pnfo = d.indexOf('(');
- + if (pnfo != -1) {
- + QString dependency = d.mid(0, pnfo);
- + QString version_str = d.mid(pnfo + 1, d.size() - pnfo - 2).trimmed();
- + dbgScript << "Desired version spec [" << dependency << "]:" << version_str;
- + PyKrita::version_checker checker = PyKrita::version_checker::fromString(version_str);
- + if (!(checker.isValid() && d.endsWith(')'))) {
- + dbgScript << "Invalid version spec " << d;
- + QString reason = i18nc(
- + "@info:tooltip"
- + , "<p>Specified version has invalid format for dependency <application>%1</application>: "
- + "<icode>%2</icode>. Skipped</p>"
- + , dependency
- + , version_str
- + );
- + return qMakePair(reason, PyKrita::version_checker());
- + }
- + return qMakePair(dependency, checker);
- + }
- + return qMakePair(d, PyKrita::version_checker(PyKrita::version_checker::undefined));
- +}
- +
- +/**
- + * Collect dependencies and check them. To do it
- + * just try to import a module... when unload it ;)
- + *
- + * \c X-Python-Dependencies property of \c .desktop file has the following format:
- + * <tt>python-module(version-info)</tt>, where <tt>python-module</tt>
- + * a python module name to be imported, <tt>version-spec</tt>
- + * is a version triplet delimited by dots, possible w/ leading compare
- + * operator: \c =, \c <, \c >, \c <=, \c >=
- + */
- +void PythonPluginManager::verifyDependenciesSetStatus(PythonPlugin& plugin)
- +{
- + QStringList dependencies = plugin.property("X-Python-Dependencies").toStringList();
- +
- + PyKrita::Python py = PyKrita::Python();
- + QString reason = i18nc("@info:tooltip", "<title>Dependency check</title>");
- + Q_FOREACH(const QString & d, dependencies) {
- + QPair<QString, PyKrita::version_checker> info_pair = parseDependency(d);
- + PyKrita::version_checker& checker = info_pair.second;
- + if (!checker.isValid()) {
- + plugin.m_broken = true;
- + reason += info_pair.first;
- + continue;
- + }
- +
- + dbgScript << "Try to import dependency module/package:" << d;
- +
- + // Try to import a module
- + const QString& dependency = info_pair.first;
- + PyObject* module = py.moduleImport(PQ(dependency));
- + if (module) {
- + if (checker.isEmpty()) { // Need to check smth?
- + dbgScript << "No version to check, just make sure it's loaded:" << dependency;
- + Py_DECREF(module);
- + continue;
- + }
- + // Try to get __version__ from module
- + // See PEP396: http://www.python.org/dev/peps/pep-0396/
- + PyObject* version_obj = py.itemString("__version__", PQ(dependency));
- + if (!version_obj) {
- + dbgScript << "No __version__ for " << dependency
- + << "[" << plugin.name() << "]:\n" << py.lastTraceback()
- + ;
- + plugin.m_unstable = true;
- + reason += i18nc(
- + "@info:tooltip"
- + , "<p>Failed to check version of dependency <application>%1</application>: "
- + "Module do not have PEP396 <code>__version__</code> attribute. "
- + "It is not disabled, but behaviour is unpredictable...</p>"
- + , dependency
- + );
- + }
- + PyKrita::version dep_version = PyKrita::version::fromPythonObject(version_obj);
- +
- + if (!dep_version.isValid()) {
- + // Dunno what is this... Giving up!
- + dbgScript << "***: Can't parse module version for" << dependency;
- + plugin.m_unstable = true;
- + reason += i18nc(
- + "@info:tooltip"
- + , "<p><application>%1</application>: Unexpected module's version format"
- + , dependency
- + );
- + } else if (!checker(dep_version)) {
- + dbgScript << "Version requirement check failed ["
- + << plugin.name() << "] for "
- + << dependency << ": wanted " << checker.operationToString()
- + << QString(checker.required())
- + << ", but found" << QString(dep_version)
- + ;
- + plugin.m_broken = true;
- + reason += i18nc(
- + "@info:tooltip"
- + , "<p><application>%1</application>: No suitable version found. "
- + "Required version %2 %3, but found %4</p>"
- + , dependency
- + , checker.operationToString()
- + , QString(checker.required())
- + , QString(dep_version)
- + );
- + }
- + // Do not need this module anymore...
- + Py_DECREF(module);
- + } else {
- + dbgScript << "Load failure [" << plugin.name() << "]:\n" << py.lastTraceback();
- + plugin.m_broken = true;
- + reason += i18nc(
- + "@info:tooltip"
- + , "<p>Failure on module load <application>%1</application>:</p><pre>%2</pre>"
- + , dependency
- + , py.lastTraceback()
- + );
- + }
- + }
- +
- + if (plugin.isBroken() || plugin.isUnstable()) {
- + plugin.m_errorReason = reason;
- + }
- +}
- +
- +void PythonPluginManager::scanPlugins()
- +{
- + m_plugins.clear();
- +
- + KConfigGroup pluginSettings(KSharedConfig::openConfig(), "python");
- +
- + QStringList desktopFiles = KoResourcePaths::findAllResources("data", "pykrita/*desktop");
- + qDebug() << desktopFiles;
- +
- + Q_FOREACH(const QString &desktopFile, desktopFiles) {
- +
- + QSettings s(desktopFile, QSettings::IniFormat);
- + s.beginGroup("Desktop Entry");
- + if (s.value("ServiceTypes").toString() == "Krita/PythonPlugin") {
- + PythonPlugin plugin;
- + plugin.m_comment = s.value("Comment").toString();
- + plugin.m_name = s.value("Name").toString();
- + plugin.m_moduleName = s.value("X-KDE-Library").toString();
- + plugin.m_properties["X-Python-2-Compatible"] = s.value("X-Python-2-Compatible", false).toBool();
- +
- + if (!plugin.isValid()) {
- + dbgScript << plugin.name() << "is not usable";
- + continue;
- + }
- +
- + if (!verifyModuleExists(plugin)) {
- + dbgScript << "Cannot load" << plugin.name() << ": broken"
- + << plugin.isBroken()
- + << "because:" << plugin.errorReason();
- + continue;
- + }
- +
- + verifyDependenciesSetStatus(plugin);
- +
- + plugin.m_enabled = pluginSettings.readEntry(QString("enable_") + plugin.moduleName(), false);
- +
- + m_plugins.append(plugin);
- + }
- + }
- +}
- +
- +void PythonPluginManager::tryLoadEnabledPlugins()
- +{
- + for (PythonPlugin &plugin : m_plugins) {
- + dbgScript << "Trying to load plugin" << plugin.moduleName()
- + << ". Enabled:" << plugin.isEnabled()
- + << ". Broken: " << plugin.isBroken();
- +
- + if (plugin.m_enabled && !plugin.isBroken()) {
- + loadModule(plugin);
- + }
- + }
- +}
- +
- +void PythonPluginManager::loadModule(PythonPlugin &plugin)
- +{
- + KIS_SAFE_ASSERT_RECOVER_RETURN(plugin.isEnabled() && !plugin.isBroken());
- +
- + QString module_name = plugin.moduleName();
- + dbgScript << "Loading module: " << module_name;
- +
- + PyKrita::Python py = PyKrita::Python();
- +
- + // Get 'plugins' key from 'pykrita' module dictionary.
- + // Every entry has a module name as a key and 2 elements tuple as a value
- + PyObject* plugins = py.itemString("plugins");
- + KIS_SAFE_ASSERT_RECOVER_RETURN(plugins);
- +
- + PyObject* module = py.moduleImport(PQ(module_name));
- + if (module) {
- + // Move just loaded module to the dict
- + const int ins_result = PyDict_SetItemString(plugins, PQ(module_name), module);
- + KIS_SAFE_ASSERT_RECOVER_NOOP(ins_result == 0);
- + Py_DECREF(module);
- + // Handle failure in release mode.
- + if (ins_result == 0) {
- + // Initialize the module from Python's side
- + PyObject* const args = Py_BuildValue("(s)", PQ(module_name));
- + PyObject* result = py.functionCall("_pluginLoaded", PyKrita::Python::PYKRITA_ENGINE, args);
- + Py_DECREF(args);
- + if (result) {
- + dbgScript << "\t" << "success!";
- + plugin.m_loaded = true;
- + return;
- + }
- + }
- + plugin.m_errorReason = i18nc("@info:tooltip", "Internal engine failure");
- + } else {
- + plugin.m_errorReason = i18nc(
- + "@info:tooltip"
- + , "Module not loaded:<br/>%1"
- + , py.lastTraceback().replace("\n", "<br/>")
- + );
- + }
- + plugin.m_broken = true;
- + warnScript << "Error loading plugin" << module_name;
- +}
- +
- +void PythonPluginManager::unloadModule(PythonPlugin &plugin)
- +{
- + KIS_SAFE_ASSERT_RECOVER_RETURN(!plugin.m_loaded);
- + KIS_SAFE_ASSERT_RECOVER_RETURN(!plugin.isBroken());
- +
- + dbgScript << "Unloading module: " << plugin.moduleName();
- +
- + PyKrita::Python py = PyKrita::Python();
- +
- + // Get 'plugins' key from 'pykrita' module dictionary
- + PyObject* plugins = py.itemString("plugins");
- + KIS_SAFE_ASSERT_RECOVER_RETURN(plugins);
- +
- + PyObject* const args = Py_BuildValue("(s)", PQ(plugin.moduleName()));
- + py.functionCall("_pluginUnloading", PyKrita::Python::PYKRITA_ENGINE, args);
- + Py_DECREF(args);
- +
- + // This will just decrement a reference count for module instance
- + PyDict_DelItemString(plugins, PQ(plugin.moduleName()));
- +
- + // Remove the module also from 'sys.modules' dict to really unload it,
- + // so if reloaded all @init actions will work again!
- + PyObject* sys_modules = py.itemString("modules", "sys");
- + KIS_SAFE_ASSERT_RECOVER_RETURN(sys_modules);
- + PyDict_DelItemString(sys_modules, PQ(plugin.moduleName()));
- +
- + plugin.m_loaded = false;
- +}
- +
- +void PythonPluginManager::setPluginEnabled(PythonPlugin &plugin, bool enabled)
- +{
- + bool wasEnabled = plugin.isEnabled();
- +
- + if (wasEnabled && !enabled) {
- + unloadModule(plugin);
- + }
- +
- + plugin.m_enabled = enabled;
- + KConfigGroup pluginSettings(KSharedConfig::openConfig(), "python");
- + pluginSettings.writeEntry(QString("enable_") + plugin.moduleName(), enabled);
- +
- + if (!wasEnabled && enabled) {
- + unloadModule(plugin);
- + }
- +}
- diff --git a/plugins/extensions/pykrita/plugin/PythonPluginsModel.h b/plugins/extensions/pykrita/plugin/PythonPluginsModel.h
- new file mode 100644
- --- /dev/null
- +++ b/plugins/extensions/pykrita/plugin/PythonPluginsModel.h
- @@ -0,0 +1,51 @@
- +/*
- + * This file is part of PyKrita, Krita' Python scripting plugin.
- + *
- + * Copyright (C) 2013 Alex Turbov <i.zaufi@gmail.com>
- + * Copyright (C) 2014-2016 Boudewijn Rempt <boud@valdyas.org>
- + * Copyright (C) 2017 Jouni Pentikäinen (joupent@gmail.com)
- + *
- + * This library is free software; you can redistribute it and/or
- + * modify it under the terms of the GNU Library General Public
- + * License as published by the Free Software Foundation; either
- + * version 2 of the License, or (at your option) any later version.
- + *
- + * This library 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
- + * Library General Public License for more details.
- + *
- + * You should have received a copy of the GNU Library General Public License
- + * along with this library; see the file COPYING.LIB. If not, write to
- + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- + * Boston, MA 02110-1301, USA.
- + */
- +
- +#ifndef KRITA_PYTHONPLUGINSMODEL_H
- +#define KRITA_PYTHONPLUGINSMODEL_H
- +
- +#include <QtCore/QAbstractTableModel>
- +
- +class PythonPluginManager;
- +
- +class PythonPluginsModel : public QAbstractTableModel
- +{
- +public:
- + PythonPluginsModel(QObject *parent, PythonPluginManager *pluginManager);
- +
- +private:
- + enum Column {COl_NAME, COL_COMMENT, COLUMN_COUNT};
- +
- + int columnCount(const QModelIndex&) const override;
- + int rowCount(const QModelIndex&) const override;
- + QModelIndex index(int row, int column, const QModelIndex& parent) const override;
- + QVariant headerData(int, Qt::Orientation, int) const override;
- + QVariant data(const QModelIndex&, int) const override;
- + Qt::ItemFlags flags(const QModelIndex&) const override;
- + bool setData(const QModelIndex&, const QVariant&, int) override;
- +
- +private:
- + PythonPluginManager *m_pluginManager;
- +};
- +
- +#endif //KRITA_PYTHONPLUGINSMODEL_H
- diff --git a/plugins/extensions/pykrita/plugin/PythonPluginsModel.cpp b/plugins/extensions/pykrita/plugin/PythonPluginsModel.cpp
- new file mode 100644
- --- /dev/null
- +++ b/plugins/extensions/pykrita/plugin/PythonPluginsModel.cpp
- @@ -0,0 +1,149 @@
- +/*
- + * This file is part of PyKrita, Krita' Python scripting plugin.
- + *
- + * Copyright (C) 2013 Alex Turbov <i.zaufi@gmail.com>
- + * Copyright (C) 2014-2016 Boudewijn Rempt <boud@valdyas.org>
- + * Copyright (C) 2017 Jouni Pentikäinen (joupent@gmail.com)
- + *
- + * This library is free software; you can redistribute it and/or
- + * modify it under the terms of the GNU Library General Public
- + * License as published by the Free Software Foundation; either
- + * version 2 of the License, or (at your option) any later version.
- + *
- + * This library 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
- + * Library General Public License for more details.
- + *
- + * You should have received a copy of the GNU Library General Public License
- + * along with this library; see the file COPYING.LIB. If not, write to
- + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- + * Boston, MA 02110-1301, USA.
- + */
- +
- +#include "PythonPluginsModel.h"
- +
- +#include <kcolorscheme.h>
- +#include <KI18n/KLocalizedString>
- +
- +#include "PythonPluginManager.h"
- +
- +PythonPluginsModel::PythonPluginsModel(QObject *parent, PythonPluginManager *pluginManager)
- + : QAbstractTableModel(parent)
- + , m_pluginManager(pluginManager)
- +{
- +}
- +
- +int PythonPluginsModel::columnCount(const QModelIndex&) const
- +{
- + return COLUMN_COUNT;
- +}
- +
- +int PythonPluginsModel::rowCount(const QModelIndex&) const
- +{
- + return m_pluginManager->plugins().size();
- +}
- +
- +QModelIndex PythonPluginsModel::index(const int row, const int column, const QModelIndex& parent) const
- +{
- + if (!parent.isValid() && column < COLUMN_COUNT) {
- + auto *plugin = m_pluginManager->plugin(row);
- + if (plugin) {
- + return createIndex(row, column, plugin);
- + }
- + }
- +
- + return QModelIndex();
- +}
- +
- +QVariant PythonPluginsModel::headerData(const int section, const Qt::Orientation orientation, const int role) const
- +{
- + if (role == Qt::DisplayRole && orientation == Qt::Horizontal) {
- + switch (section) {
- + case COl_NAME:
- + return i18nc("@title:column", "Name");
- + case COL_COMMENT:
- + return i18nc("@title:column", "Comment");
- + default:
- + break;
- + }
- + }
- + return QVariant();
- +}
- +
- +QVariant PythonPluginsModel::data(const QModelIndex& index, const int role) const
- +{
- + if (index.isValid()) {
- + PythonPlugin *plugin = static_cast<PythonPlugin*>(index.internalPointer());
- + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(plugin, QVariant());
- +
- + switch (role) {
- + case Qt::DisplayRole:
- + switch (index.column()) {
- + case COl_NAME:
- + return plugin->name();
- + case COL_COMMENT:
- + return plugin->comment();
- + default:
- + break;
- + }
- + break;
- + case Qt::CheckStateRole:
- + if (index.column() == COl_NAME) {
- + const bool checked = plugin->isEnabled();
- + return checked ? Qt::Checked : Qt::Unchecked;
- + }
- + break;
- + case Qt::ToolTipRole:
- + {
- + auto error = plugin->errorReason();
- + if (!error.isEmpty()) {
- + return error;
- + }
- + }
- + break;
- + case Qt::ForegroundRole:
- + if (plugin->isUnstable()) {
- + KColorScheme scheme(QPalette::Inactive, KColorScheme::View);
- + return scheme.foreground(KColorScheme::NegativeText).color();
- + }
- + break;
- + default:
- + break;
- + }
- + }
- +
- + return QVariant();
- +}
- +
- +Qt::ItemFlags PythonPluginsModel::flags(const QModelIndex& index) const
- +{
- + PythonPlugin *plugin = static_cast<PythonPlugin*>(index.internalPointer());
- + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(plugin, Qt::ItemIsSelectable);
- +
- + int result = Qt::ItemIsSelectable;
- + if (index.column() == COl_NAME) {
- + result |= Qt::ItemIsUserCheckable;
- + }
- +
- + // Disable UI for broken modules
- + if (!plugin->isBroken()) {
- + result |= Qt::ItemIsEnabled;
- + }
- +
- + return static_cast<Qt::ItemFlag>(result);
- +}
- +
- +bool PythonPluginsModel::setData(const QModelIndex& index, const QVariant& value, const int role)
- +{
- + PythonPlugin *plugin = static_cast<PythonPlugin*>(index.internalPointer());
- + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(plugin, false);
- +
- + if (role == Qt::CheckStateRole) {
- + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!plugin->isBroken(), false);
- +
- + const bool enabled = value.toBool();
- + m_pluginManager->setPluginEnabled(*plugin, enabled);
- + }
- + return true;
- +}
- diff --git a/plugins/extensions/pykrita/plugin/config.h.cmake b/plugins/extensions/pykrita/plugin/config.h.cmake
- --- a/plugins/extensions/pykrita/plugin/config.h.cmake
- +++ b/plugins/extensions/pykrita/plugin/config.h.cmake
- @@ -19,3 +19,6 @@
- #define PYKRITA_PYTHON_LIBRARY "${PYTHON_LIBRARY}"
- #define PYKRITA_PYTHON_SITE_PACKAGES_INSTALL_DIR "${PYTHON_SITE_PACKAGES_INSTALL_DIR}"
- +
- +/// Name of the file where per-plugin configuration is stored
- +#define CONFIG_FILE "kritapykritarc"
- diff --git a/plugins/extensions/pykrita/plugin/engine.h b/plugins/extensions/pykrita/plugin/engine.h
- deleted file mode 100644
- --- a/plugins/extensions/pykrita/plugin/engine.h
- +++ /dev/null
- @@ -1,234 +0,0 @@
- -/*
- - * This file is part of PyKrita, Krita' Python scripting plugin.
- - *
- - * Copyright (C) 2013 Alex Turbov <i.zaufi@gmail.com>
- - * Copyright (C) 2014-2016 Boudewijn Rempt <boud@valdyas.org>
- - *
- - * This library is free software; you can redistribute it and/or
- - * modify it under the terms of the GNU Library General Public
- - * License as published by the Free Software Foundation; either
- - * version 2 of the License, or (at your option) version 3.
- - *
- - * This library 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
- - * Library General Public License for more details.
- - *
- - * You should have received a copy of the GNU Library General Public License
- - * along with this library; see the file COPYING.LIB. If not, write to
- - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- - * Boston, MA 02110-1301, USA.
- - */
- -
- -#ifndef __PYKRITA_ENGINE_H__
- -# define __PYKRITA_ENGINE_H__
- -
- -#include <cmath>
- -#include <Python.h>
- -
- -#include "version_checker.h"
- -
- -#include <QAbstractItemModel>
- -#include <QList>
- -#include <QStringList>
- -
- -namespace PyKrita
- -{
- -
- -/**
- - * @brief The PyPlugin class describes a plugin written in Python and loaded into the system
- - */
- -class PyPlugin {
- -
- -public:
- -
- - PyPlugin()
- - {
- - m_properties["X-Python-Dependencies"] = QStringList();
- - m_properties["X-Python-2-Dependencies"] = QStringList();
- - }
- -
- - QString name() const
- - {
- - return m_name;
- - }
- -
- - QString library() const
- - {
- - return m_libraryPath;
- - }
- -
- - QVariant property(const QString &name) const
- - {
- - return m_properties.value(name, "");
- - }
- -
- - QString comment() const
- - {
- - return m_comment;
- - }
- -
- - QString m_name;
- - QString m_libraryPath;
- - QMap<QString, QVariant> m_properties;
- - QString m_comment;
- -};
- -
- -class Python; // fwd decl
- -
- -/**
- - * The Engine class hosts the Python interpreter, loading
- - * it into memory within Krita, and then with finding and
- - * loading all of the PyKrita plugins.
- - *
- - * \attention Qt/KDE does not use exceptions (unfortunately),
- - * so this class must be initialized in two steps:
- - * - create an instance (via constructor)
- - * - try to initialize the rest (via \c Engine::tryInitializeGetFailureReason())
- - * If latter returns a non empty (failure reason) string, the only member
- - * can be called is conversion to boolean! (which is implemented as safe-bool idiom [1])
- - * Calling others leads to UB!
- - *
- - * \sa [1] http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Safe_bool
- - */
- -class Engine : public QAbstractItemModel
- -{
- - Q_OBJECT
- -
- - typedef void (Engine::*bool_type)() const;
- - void unspecified_true_bool_type() const {}
- -
- -public:
- - /// \todo Turn into a class w/ accessors
- - class PluginState
- - {
- - public:
- - /// \name Immutable accessors
- - //@{
- - QString pythonModuleName() const;
- - const QString& errorReason() const;
- - bool isEnabled() const;
- - bool isBroken() const;
- - bool isUnstable() const;
- - //@}
- -
- - private:
- - friend class Engine;
- -
- - PluginState();
- - /// Transfort Python module name into a file path part
- - QString moduleFilePathPart() const;
- -
- - PyPlugin m_pythonPlugin;
- - QString m_pythonModule;
- - QString m_errorReason;
- - bool m_enabled;
- - bool m_broken;
- - bool m_unstable;
- - bool m_isDir;
- - };
- -
- - /// Default constructor: initialize Python interpreter
- - Engine();
- - /// Cleanup everything on unload
- - ~Engine();
- -
- - //BEGIN QAbstractItemModel interface
- - virtual int columnCount(const QModelIndex&) const /*override*/;
- - virtual int rowCount(const QModelIndex&) const /*override*/;
- - virtual QModelIndex index(int, int, const QModelIndex&) const /*override*/;
- - virtual QModelIndex parent(const QModelIndex&) const /*override*/;
- - virtual QVariant headerData(int, Qt::Orientation, int) const /*override*/;
- - virtual QVariant data(const QModelIndex&, int) const /*override*/;
- - virtual Qt::ItemFlags flags(const QModelIndex&) const /*override*/;
- - virtual bool setData(const QModelIndex&, const QVariant&, int) /*override*/;
- - //END QAbstractItemModel interface
- -
- - void setEnabledPlugins(const QStringList&); ///< Set enabled plugins to the model
- - void tryLoadEnabledPlugins(); ///< Try to load enabled plugins
- - QStringList enabledPlugins() const; ///< Form a list of enabled plugins
- - const QList<PluginState>& plugins() const; ///< Provide immutable access to found plugins
- - QString tryInitializeGetFailureReason(); ///< Try to initialize Python interpreter
- - operator bool_type() const; ///< Check if instance is usable
- - void setBroken(); ///< Make it broken by some external reason
- -
- -public Q_SLOTS:
- - void readGlobalPluginsConfiguration(); ///< Load plugins' configuration.
- - void saveGlobalPluginsConfiguration(); ///< Write out plugins' configuration.
- - void unloadAllModules();
- -
- -protected:
- - void scanPlugins(); ///< Search for available plugins
- - void loadModule(int); ///< Load module by index in \c m_plugins
- - void unloadModule(int); ///< Unload module by index in \c m_plugins
- -
- -private:
- - // Simulate strong typed enums from C++11
- - struct Column {
- - enum type {
- - NAME
- - , COMMENT
- - , LAST__
- - };
- - };
- -
- - static bool isPythonPluginUsable(const PyPlugin *pyPlugin); ///< Make sure that service is usable
- - static bool setModuleProperties(PluginState&);
- - static void verifyDependenciesSetStatus(PluginState&);
- - static QPair<QString, version_checker> parseDependency(const QString&);
- - static version tryObtainVersionFromTuple(PyObject*);
- - static version tryObtainVersionFromString(PyObject*);
- -
- - PyObject* m_configuration; ///< Application-wide configuration data
- - PyObject* m_sessionConfiguration; ///< Session-wide configuration data
- - QList<PluginState> m_plugins; ///< List of available plugins
- - bool m_engineIsUsable; ///< Is engine loaded Ok?
- -};
- -
- -inline QString Engine::PluginState::pythonModuleName() const
- -{
- - return m_pythonPlugin.library();
- -}
- -
- -inline QString PyKrita::Engine::PluginState::moduleFilePathPart() const
- -{
- - return m_pythonPlugin.library().replace(".", "/");
- -}
- -
- -inline const QString& Engine::PluginState::errorReason() const
- -{
- - return m_errorReason;
- -}
- -
- -inline bool Engine::PluginState::isEnabled() const
- -{
- - return m_enabled;
- -}
- -
- -inline bool Engine::PluginState::isBroken() const
- -{
- - return m_broken;
- -}
- -
- -inline bool Engine::PluginState::isUnstable() const
- -{
- - return m_unstable;
- -}
- -
- -inline const QList<Engine::PluginState>& Engine::plugins() const
- -{
- - return m_plugins;
- -}
- -
- -inline Engine::operator bool_type() const
- -{
- - return m_engineIsUsable ? &Engine::unspecified_true_bool_type : 0;
- -}
- -
- -inline void Engine::setBroken()
- -{
- - m_engineIsUsable = false;
- -}
- -
- -} // namespace PyKrita
- -#endif // __PYKRITA_ENGINE_H__
- diff --git a/plugins/extensions/pykrita/plugin/engine.cpp b/plugins/extensions/pykrita/plugin/engine.cpp
- deleted file mode 100644
- --- a/plugins/extensions/pykrita/plugin/engine.cpp
- +++ /dev/null
- @@ -1,799 +0,0 @@
- -// This file is part of PyKrita, Krita' Python scripting plugin.
- -//
- -// Copyright (C) 2006 Paul Giannaros <paul@giannaros.org>
- -// Copyright (C) 2012, 2013 Shaheed Haque <srhaque@theiet.org>
- -// Copyright (C) 2013 Alex Turbov <i.zaufi@gmail.com>
- -//
- -// This library is free software; you can redistribute it and/or
- -// modify it under the terms of the GNU Lesser General Public
- -// License as published by the Free Software Foundation; either
- -// version 2.1 of the License, or (at your option) version 3, or any
- -// later version accepted by the membership of KDE e.V. (or its
- -// successor approved by the membership of KDE e.V.), which shall
- -// act as a proxy defined in Section 6 of version 3 of the license.
- -//
- -// This library 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
- -// Lesser General Public License for more details.
- -//
- -// You should have received a copy of the GNU Lesser General Public
- -// License along with this library. If not, see <http://www.gnu.org/licenses/>.
- -//
- -
- -#include "engine.h"
- -
- -// config.h defines PYKRITA_PYTHON_LIBRARY, the path to libpython.so
- -// on the build system
- -
- -#include "config.h"
- -#include "utilities.h"
- -
- -#include <cmath>
- -#include <Python.h>
- -
- -#include <QSettings>
- -#include <QLibrary>
- -#include <QFileInfo>
- -
- -#include <kconfig.h>
- -#include <klocalizedstring.h>
- -#include <kcolorscheme.h>
- -
- -//#include <KoServiceLocator.h>
- -#include <KoResourcePaths.h>
- -
- -#include <kis_debug.h>
- -
- -/// Name of the file where per-plugin configuration is stored.
- -#define CONFIG_FILE "kritapykritarc"
- -
- -#if PY_MAJOR_VERSION < 3
- -# define PYKRITA_INIT initpykrita
- -#else
- -# define PYKRITA_INIT PyInit_pykrita
- -#endif
- -
- -PyMODINIT_FUNC PYKRITA_INIT(); // fwd decl
- -
- -/// \note Namespace name written in uppercase intentionally!
- -/// It will appear in debug output from Python plugins...
- -namespace PYKRITA
- -{
- -PyObject* debug(PyObject* /*self*/, PyObject* args)
- -{
- - const char* text;
- -
- - if (PyArg_ParseTuple(args, "s", &text))
- - dbgScript << text;
- - Py_INCREF(Py_None);
- - return Py_None;
- -}
- -} // namespace PYKRITA
- -
- -namespace
- -{
- -PyObject* s_pykrita;
- -/**
- - * \attention Krita has embedded Python, so init function \b never will be called
- - * automatically! We can use this fact to initialize a pointer to an instance
- - * of the \c Engine class (which is a part of the \c Plugin), so exported
- - * functions will know it (yep, from Python's side they should be static).
- - */
- -PyKrita::Engine* s_engine_instance = 0;
- -
- -/**
- - * Wrapper function, called explicitly from \c Engine::Engine
- - * to initialize pointer to the only (by design) instance of the engine,
- - * so exported (to Python) functions get know it... Then invoke
- - * a real initialization sequence...
- - */
- -void pythonInitwrapper(PyKrita::Engine* const engine)
- -{
- - Q_ASSERT("Sanity check" && !s_engine_instance);
- - s_engine_instance = engine;
- - // Call initialize explicitly to initialize embedded interpreter.
- - PYKRITA_INIT();
- -}
- -
- -/**
- - * Functions for the Python module called pykrita.
- - * \todo Does it \b REALLY needed? Configuration data will be flushed
- - * on exit anyway! Why to write it (and even allow to plugins to call this)
- - * \b before krita really going to exit? It would be better to \b deprecate
- - * this (harmful) function!
- - */
- -PyObject* pykritaSaveConfiguration(PyObject* /*self*/, PyObject* /*unused*/)
- -{
- - if (s_engine_instance)
- - s_engine_instance->saveGlobalPluginsConfiguration();
- - Py_INCREF(Py_None);
- - return Py_None;
- -}
- -
- -PyMethodDef pykritaMethods[] = {
- - {
- - "saveConfiguration"
- - , &pykritaSaveConfiguration
- - , METH_NOARGS
- - , "Save the configuration of the plugin into " CONFIG_FILE
- - }
- - , {
- - "qDebug"
- - , &PYKRITA::debug
- - , METH_VARARGS
- - , "True KDE way to show debug info"
- - }
- - , { 0, 0, 0, 0 }
- -};
- -
- -} // anonymous namespace
- -
- -//BEGIN Python module registration
- -PyMODINIT_FUNC PYKRITA_INIT()
- -{
- -#if PY_MAJOR_VERSION < 3
- - s_pykrita = Py_InitModule3("pykrita", pykritaMethods, "The pykrita module");
- - PyModule_AddStringConstant(s_pykrita, "__file__", __FILE__);
- -#else
- - static struct PyModuleDef moduledef = {
- - PyModuleDef_HEAD_INIT
- - , "pykrita"
- - , "The pykrita module"
- - , -1
- - , pykritaMethods
- - , 0
- - , 0
- - , 0
- - , 0
- - };
- - s_pykrita = PyModule_Create(&moduledef);
- - PyModule_AddStringConstant(s_pykrita, "__file__", __FILE__);
- - return s_pykrita;
- -#endif
- -}
- -//END Python module registration
- -
- -
- -//BEGIN PyKrita::Engine::PluginState
- -PyKrita::Engine::PluginState::PluginState()
- - : m_enabled(false)
- - , m_broken(false)
- - , m_unstable(false)
- - , m_isDir(false)
- -{
- -}
- -//END PyKrita::Engine::PluginState
- -
- -
- -/**
- - * Just initialize some members. The second (most important) part
- - * is to call \c Engine::tryInitializeGetFailureReason()!
- - * W/o that call instance is invalid and using it lead to UB!
- - */
- -PyKrita::Engine::Engine()
- - : m_configuration(0)
- - , m_sessionConfiguration(0)
- - , m_engineIsUsable(false)
- -{
- -}
- -
- -/// \todo More accurate shutdown required:
- -/// need to keep track what exactly was broken on
- -/// initialize attempt...
- -PyKrita::Engine::~Engine()
- -{
- - dbgScript << "Going to destroy the Python engine";
- -
- - // Notify Python that engine going to die
- - {
- - Python py = Python();
- - py.functionCall("_pykritaUnloading");
- - }
- - unloadAllModules();
- -
- - // Clean internal configuration dicts
- - // NOTE Do not need to save anything! It's already done!
- - if (m_configuration) {
- - Py_DECREF(m_configuration);
- - }
- - if (m_sessionConfiguration) {
- - Py_DECREF(m_sessionConfiguration);
- - }
- -
- - Python::maybeFinalize();
- - Python::libraryUnload();
- - s_engine_instance = 0;
- -}
- -
- -void PyKrita::Engine::unloadAllModules()
- -{
- - // Unload all modules
- - for (int i = 0; i < m_plugins.size(); ++i) {
- - if (m_plugins[i].isEnabled() && !m_plugins[i].isBroken()) {
- - unloadModule(i);
- - }
- - }
- -}
- -
- -/**
- - * \todo Make sure noone tries to use uninitialized engine!
- - * (Or enable exceptions for this module, so this case wouldn't even araise?)
- - */
- -QString PyKrita::Engine::tryInitializeGetFailureReason()
- -{
- - dbgScript << "Construct the Python engine for Python" << PY_MAJOR_VERSION << "," << PY_MINOR_VERSION;
- -
- - if (!Python::libraryLoad()) {
- - return i18nc("@info:tooltip ", "Cannot load Python library");
- - }
- -
- - // Update PYTHONPATH
- - // 0) custom plugin directories (prefer local dir over systems')
- - // 1) shipped krita module's dir
- - QStringList pluginDirectories = KoResourcePaths::findDirs("pythonscripts");
- - dbgScript << "Plugin Directories: " << pluginDirectories;
- - if (!Python::setPath(pluginDirectories)) {
- - return i18nc("@info:tooltip ", "Cannot set Python paths");
- - }
- -
- - if (0 != PyImport_AppendInittab(Python::PYKRITA_ENGINE, PYKRITA_INIT)) {
- - return i18nc("@info:tooltip ", "Cannot load built-in <icode>pykrita</icode> module");
- - }
- -
- - Python::ensureInitialized();
- - Python py = Python();
- -
- - PyRun_SimpleString(
- - "import sip\n"
- - "sip.setapi('QDate', 2)\n"
- - "sip.setapi('QTime', 2)\n"
- - "sip.setapi('QDateTime', 2)\n"
- - "sip.setapi('QUrl', 2)\n"
- - "sip.setapi('QTextStream', 2)\n"
- - "sip.setapi('QString', 2)\n"
- - "sip.setapi('QVariant', 2)\n"
- - );
- -
- - // Initialize our built-in module.
- - pythonInitwrapper(this);
- - if (!s_pykrita) {
- - return i18nc("@info:tooltip ", "No <icode>pykrita</icode> built-in module");
- - }
- -
- - // Setup global configuration
- - m_configuration = PyDict_New();
- - /// \todo Check \c m_configuration ?
- - // Host the configuration dictionary.
- - py.itemStringSet("configuration", m_configuration);
- -
- - // Setup per session configuration
- - m_sessionConfiguration = PyDict_New();
- - py.itemStringSet("sessionConfiguration", m_sessionConfiguration);
- -
- - // Initialize 'plugins' dict of module 'pykrita'
- - PyObject* plugins = PyDict_New();
- - py.itemStringSet("plugins", plugins);
- -
- - // Get plugins available
- - scanPlugins();
- -
- - // NOTE Empty failure reson string indicates success!
- - m_engineIsUsable = true;
- - return QString();
- -}
- -
- -int PyKrita::Engine::columnCount(const QModelIndex&) const
- -{
- - return Column::LAST__;
- -}
- -
- -int PyKrita::Engine::rowCount(const QModelIndex&) const
- -{
- - return m_plugins.size();
- -}
- -
- -QModelIndex PyKrita::Engine::index(const int row, const int column, const QModelIndex& parent) const
- -{
- - if (!parent.isValid() && row < m_plugins.size() && column < Column::LAST__)
- - return createIndex(row, column);
- - return QModelIndex();
- -}
- -
- -QModelIndex PyKrita::Engine::parent(const QModelIndex&) const
- -{
- - return QModelIndex();
- -}
- -
- -QVariant PyKrita::Engine::headerData(
- - const int section
- - , const Qt::Orientation orientation
- - , const int role
- -) const
- -{
- - if (role == Qt::DisplayRole && orientation == Qt::Horizontal) {
- - switch (section) {
- - case Column::NAME:
- - return i18nc("@title:column", "Name");
- - case Column::COMMENT:
- - return i18nc("@title:column", "Comment");
- - default:
- - break;
- - }
- - }
- - return QVariant();
- -}
- -
- -QVariant PyKrita::Engine::data(const QModelIndex& index, const int role) const
- -{
- - Q_ASSERT("Sanity check" && index.row() < m_plugins.size());
- - Q_ASSERT("Sanity check" && index.column() < Column::LAST__);
- - switch (role) {
- - case Qt::DisplayRole:
- - switch (index.column()) {
- - case Column::NAME:
- - return m_plugins[index.row()].m_pythonPlugin.name();
- - case Column::COMMENT:
- - return m_plugins[index.row()].m_pythonPlugin.comment();
- - default:
- - break;
- - }
- - break;
- - case Qt::CheckStateRole: {
- - if (index.column() == Column::NAME) {
- - const bool checked = m_plugins[index.row()].isEnabled();
- - return checked ? Qt::Checked : Qt::Unchecked;
- - }
- - break;
- - }
- - case Qt::ToolTipRole:
- - if (!m_plugins[index.row()].m_errorReason.isEmpty())
- - return m_plugins[index.row()].m_errorReason;
- - break;
- - case Qt::ForegroundRole:
- - if (m_plugins[index.row()].isUnstable()) {
- - KColorScheme scheme(QPalette::Inactive, KColorScheme::View);
- - return scheme.foreground(KColorScheme::NegativeText).color();
- - }
- - default:
- - break;
- - }
- - return QVariant();
- -}
- -
- -Qt::ItemFlags PyKrita::Engine::flags(const QModelIndex& index) const
- -{
- - Q_ASSERT("Sanity check" && index.row() < m_plugins.size());
- - Q_ASSERT("Sanity check" && index.column() < Column::LAST__);
- -
- - int result = Qt::ItemIsSelectable;
- - if (index.column() == Column::NAME)
- - result |= Qt::ItemIsUserCheckable;
- - // Disable to select/check broken modules
- - if (!m_plugins[index.row()].isBroken())
- - result |= Qt::ItemIsEnabled;
- - return static_cast<Qt::ItemFlag>(result);
- -}
- -
- -bool PyKrita::Engine::setData(const QModelIndex& index, const QVariant& value, const int role)
- -{
- - Q_ASSERT("Sanity check" && index.row() < m_plugins.size());
- -
- - if (role == Qt::CheckStateRole) {
- - Q_ASSERT("Sanity check" && !m_plugins[index.row()].isBroken());
- -
- - const bool enabled = value.toBool();
- - m_plugins[index.row()].m_enabled = enabled;
- - if (enabled)
- - loadModule(index.row());
- - else
- - unloadModule(index.row());
- - }
- - return true;
- -}
- -
- -QStringList PyKrita::Engine::enabledPlugins() const
- -{
- - /// \todo \c std::transform + lambda or even better to use
- - /// filtered and transformed view from boost
- - QStringList result;
- - Q_FOREACH(const PluginState & plugin, m_plugins)
- - if (plugin.isEnabled()) {
- - result.append(plugin.m_pythonPlugin.name());
- - }
- - return result;
- -}
- -
- -void PyKrita::Engine::readGlobalPluginsConfiguration()
- -{
- - Python py = Python();
- - PyDict_Clear(m_configuration);
- - KConfig config(CONFIG_FILE, KConfig::SimpleConfig);
- - config.sync();
- - py.updateDictionaryFromConfiguration(m_configuration, &config);
- -}
- -
- -void PyKrita::Engine::saveGlobalPluginsConfiguration()
- -{
- - Python py = Python();
- - KConfig config(CONFIG_FILE, KConfig::SimpleConfig);
- - py.updateConfigurationFromDictionary(&config, m_configuration);
- - config.sync();
- -}
- -
- -bool PyKrita::Engine::isPythonPluginUsable(const PyPlugin *pythonPlugin)
- -{
- - dbgScript << "Got Krita/PythonPlugin: " << pythonPlugin->name()
- - << ", module-path=" << pythonPlugin->library()
- - ;
- - // Make sure mandatory properties are here
- - if (pythonPlugin->name().isEmpty()) {
- - dbgScript << "Ignore desktop file w/o a name";
- - return false;
- - }
- - if (pythonPlugin->library().isEmpty()) {
- - dbgScript << "Ignore desktop file w/o a module to import";
- - return false;
- - }
- - return true;
- -}
- -
- -bool PyKrita::Engine::setModuleProperties(PluginState& plugin)
- -{
- - // Find the module:
- - // 0) try to locate directory based plugin first
- - QString rel_path = plugin.moduleFilePathPart();
- - rel_path = rel_path + "/" + "__init__.py";
- - dbgScript << "Finding Pyrhon module with rel_path:" << rel_path;
- -
- - QString module_path = KoResourcePaths::findResource("pythonscripts", rel_path);
- -
- - dbgScript << "module_path:" << module_path;
- -
- - if (module_path.isEmpty()) {
- - // 1) Nothing found, then try file based plugin
- - rel_path = plugin.moduleFilePathPart() + ".py";
- - dbgScript << "Finding Pyrhon module with rel_path:" << rel_path;
- - module_path = KoResourcePaths::findResource("pythonscripts", rel_path);
- - dbgScript << "module_path:" << module_path;
- - } else {
- - plugin.m_isDir = true;
- - }
- -
- - // Is anything found at all?
- - if (module_path.isEmpty()) {
- - plugin.m_broken = true;
- - plugin.m_errorReason = i18nc(
- - "@info:tooltip"
- - , "Unable to find the module specified <application>%1</application>"
- - , plugin.m_pythonPlugin.library()
- - );
- - dbgScript << "Cannot load module:" << plugin.m_errorReason;
- - return false;
- - }
- - dbgScript << "Found module path:" << module_path;
- - return true;
- -}
- -
- -QPair<QString, PyKrita::version_checker> PyKrita::Engine::parseDependency(const QString& d)
- -{
- - // Check if dependency has package info attached
- - const int pnfo = d.indexOf('(');
- - if (pnfo != -1) {
- - QString dependency = d.mid(0, pnfo);
- - QString version_str = d.mid(pnfo + 1, d.size() - pnfo - 2).trimmed();
- - dbgScript << "Desired version spec [" << dependency << "]:" << version_str;
- - version_checker checker = version_checker::fromString(version_str);
- - if (!(checker.isValid() && d.endsWith(')'))) {
- - dbgScript << "Invalid version spec " << d;
- - QString reason = i18nc(
- - "@info:tooltip"
- - , "<p>Specified version has invalid format for dependency <application>%1</application>: "
- - "<icode>%2</icode>. Skipped</p>"
- - , dependency
- - , version_str
- - );
- - return qMakePair(reason, version_checker());
- - }
- - return qMakePair(dependency, checker);
- - }
- - return qMakePair(d, version_checker(version_checker::undefined));
- -}
- -
- -PyKrita::version PyKrita::Engine::tryObtainVersionFromTuple(PyObject* version_obj)
- -{
- - Q_ASSERT("Sanity check" && version_obj);
- -
- - if (PyTuple_Check(version_obj) == 0)
- - return version::invalid();
- -
- - int version_info[3] = {0, 0, 0};
- - for (unsigned i = 0; i < PyTuple_Size(version_obj); ++i) {
- - PyObject* v = PyTuple_GetItem(version_obj, i);
- - if (v && PyLong_Check(v))
- - version_info[i] = PyLong_AsLong(v);
- - else
- - version_info[i] = -1;
- - }
- - if (version_info[0] != -1 && version_info[1] != -1 && version_info[2] != -1)
- - return version(version_info[0], version_info[1], version_info[2]);
- -
- - return version::invalid();
- -}
- -
- -/**
- - * Try to parse version string as a simple triplet X.Y.Z.
- - *
- - * \todo Some modules has letters in a version string...
- - * For example current \c pytz version is \e "2013d".
- - */
- -PyKrita::version PyKrita::Engine::tryObtainVersionFromString(PyObject* version_obj)
- -{
- - Q_ASSERT("Sanity check" && version_obj);
- -
- - if (!Python::isUnicode(version_obj))
- - return version::invalid();
- -
- - QString version_str = Python::unicode(version_obj);
- - if (version_str.isEmpty())
- - return version::invalid();
- -
- - return version::fromString(version_str);
- -}
- -
- -/**
- - * Collect dependencies and check them. To do it
- - * just try to import a module... when unload it ;)
- - *
- - * \c X-Python-Dependencies property of \c .desktop file has the following format:
- - * <tt>python-module(version-info)</tt>, where <tt>python-module</tt>
- - * a python module name to be imported, <tt>version-spec</tt>
- - * is a version triplet delimited by dots, possible w/ leading compare
- - * operator: \c =, \c <, \c >, \c <=, \c >=
- - */
- -void PyKrita::Engine::verifyDependenciesSetStatus(PluginState& plugin)
- -{
- - QStringList dependencies = plugin.m_pythonPlugin.property("X-Python-Dependencies").toStringList();
- -#if PY_MAJOR_VERSION < 3
- - {
- - // Try to get Py2 only dependencies
- - QStringList py2_dependencies = plugin.m_service->property("X-Python-2-Dependencies").toStringList();
- - dependencies.append(py2_dependencies);
- - }
- -#endif
- -
- - Python py = Python();
- - QString reason = i18nc("@info:tooltip", "<title>Dependency check</title>");
- - Q_FOREACH(const QString & d, dependencies) {
- - QPair<QString, version_checker> info_pair = parseDependency(d);
- - version_checker& checker = info_pair.second;
- - if (!checker.isValid()) {
- - plugin.m_broken = true;
- - reason += info_pair.first;
- - continue;
- - }
- -
- - dbgScript << "Try to import dependency module/package:" << d;
- -
- - // Try to import a module
- - const QString& dependency = info_pair.first;
- - PyObject* module = py.moduleImport(PQ(dependency));
- - if (module) {
- - if (checker.isEmpty()) { // Need to check smth?
- - dbgScript << "No version to check, just make sure it's loaded:" << dependency;
- - Py_DECREF(module);
- - continue;
- - }
- - // Try to get __version__ from module
- - // See PEP396: http://www.python.org/dev/peps/pep-0396/
- - PyObject* version_obj = py.itemString("__version__", PQ(dependency));
- - if (!version_obj) {
- - dbgScript << "No __version__ for " << dependency
- - << "[" << plugin.m_pythonPlugin.name() << "]:\n" << py.lastTraceback()
- - ;
- - plugin.m_unstable = true;
- - reason += i18nc(
- - "@info:tooltip"
- - , "<p>Failed to check version of dependency <application>%1</application>: "
- - "Module do not have PEP396 <code>__version__</code> attribute. "
- - "It is not disabled, but behaviour is unpredictable...</p>"
- - , dependency
- - );
- - }
- - // PEP396 require __version__ to tuple of integers... try it!
- - version dep_version = tryObtainVersionFromTuple(version_obj);
- - if (!dep_version.isValid())
- - // Second attempt: some "bad" modules have it as a string
- - dep_version = tryObtainVersionFromString(version_obj);
- -
- - // Did we get it?
- - if (!dep_version.isValid()) {
- - // Dunno what is this... Giving up!
- - dbgScript << "***: Can't parse module version for" << dependency;
- - plugin.m_unstable = true;
- - reason += i18nc(
- - "@info:tooltip"
- - , "<p><application>%1</application>: Unexpected module's version format"
- - , dependency
- - );
- - } else if (!checker(dep_version)) {
- - dbgScript << "Version requirement check failed ["
- - << plugin.m_pythonPlugin.name() << "] for "
- - << dependency << ": wanted " << checker.operationToString()
- - << QString(checker.required())
- - << ", but found" << QString(dep_version)
- - ;
- - plugin.m_broken = true;
- - reason += i18nc(
- - "@info:tooltip"
- - , "<p><application>%1</application>: No suitable version found. "
- - "Required version %2 %3, but found %4</p>"
- - , dependency
- - , checker.operationToString()
- - , QString(checker.required())
- - , QString(dep_version)
- - );
- - }
- - // Do not need this module anymore...
- - Py_DECREF(module);
- - } else {
- - dbgScript << "Load failure [" << plugin.m_pythonPlugin.name() << "]:\n" << py.lastTraceback();
- - plugin.m_broken = true;
- - reason += i18nc(
- - "@info:tooltip"
- - , "<p>Failure on module load <application>%1</application>:</p><pre>%2</pre>"
- - , dependency
- - , py.lastTraceback()
- - );
- - }
- - }
- -
- - if (plugin.isBroken() || plugin.isUnstable()) {
- - plugin.m_errorReason = reason;
- - }
- -}
- -
- -void PyKrita::Engine::scanPlugins()
- -{
- - m_plugins.clear(); // Clear current state.
- -
- - QStringList desktopFiles = KoResourcePaths::findAllResources("data", "pykrita/*desktop");
- - qDebug() << desktopFiles;
- -
- - Q_FOREACH(const QString &desktopFile, desktopFiles) {
- -
- - QSettings s(desktopFile, QSettings::IniFormat);
- - s.beginGroup("Desktop Entry");
- - if (s.value("ServiceTypes").toString() == "Krita/PythonPlugin") {
- - PyPlugin pyplugin;
- - pyplugin.m_comment = s.value("Comment").toString();
- - pyplugin.m_name = s.value("Name").toString();
- - pyplugin.m_libraryPath = s.value("X-KDE-Library").toString();
- - pyplugin.m_properties["X-Python-2-Compatible"] = s.value("X-Python-2-Compatible", false).toBool();
- -
- - if (!isPythonPluginUsable(&pyplugin)) {
- - dbgScript << pyplugin.name() << "is not usable";
- - continue;
- - }
- -
- - PluginState pluginState;
- - pluginState.m_pythonPlugin = pyplugin;
- -
- - if (!setModuleProperties(pluginState)) {
- - dbgScript << "Cannot load" << pyplugin.name() << ": broken"
- - << pluginState.isBroken()
- - << "because:" << pluginState.errorReason();
- - continue;
- - }
- -
- - verifyDependenciesSetStatus(pluginState);
- - m_plugins.append(pluginState);
- - }
- - }
- -}
- -
- -void PyKrita::Engine::setEnabledPlugins(const QStringList& enabled_plugins)
- -{
- - for (int i = 0; i < m_plugins.size(); ++i) {
- - m_plugins[i].m_enabled = enabled_plugins.indexOf(m_plugins[i].m_pythonPlugin.name()) != -1;
- - }
- -}
- -
- -void PyKrita::Engine::tryLoadEnabledPlugins()
- -{
- - for (int i = 0; i < m_plugins.size(); ++i) {
- - dbgScript << "Trying to load plugin" << m_plugins[i].pythonModuleName()
- - << ". Enabled:" << m_plugins[i].isEnabled()
- - << ". Broken: " << m_plugins[i].isBroken();
- - if (!m_plugins[i].isBroken()) {
- - m_plugins[i].m_enabled = true;
- - loadModule(i);
- - }
- - }
- -}
- -
- -void PyKrita::Engine::loadModule(const int idx)
- -{
- - Q_ASSERT("Plugin index is out of range!" && 0 <= idx && idx < m_plugins.size());
- - PluginState& plugin = m_plugins[idx];
- - Q_ASSERT(
- - "Why to call loadModule() for disabled/broken plugin?"
- - && plugin.isEnabled()
- - && !plugin.isBroken()
- - );
- -
- - QString module_name = plugin.pythonModuleName();
- - dbgScript << "Loading module: " << module_name;
- -
- - Python py = Python();
- -
- - // Get 'plugins' key from 'pykrita' module dictionary.
- - // Every entry has a module name as a key and 2 elements tuple as a value
- - PyObject* plugins = py.itemString("plugins");
- - Q_ASSERT(
- - "'plugins' dict expected to be alive, otherwise code review required!"
- - && plugins
- - );
- -
- - PyObject* module = py.moduleImport(PQ(module_name));
- - if (module) {
- - // Move just loaded module to the dict
- - const int ins_result = PyDict_SetItemString(plugins, PQ(module_name), module);
- - Q_ASSERT("expected successful insertion" && ins_result == 0);
- - Py_DECREF(module);
- - // Handle failure in release mode.
- - if (ins_result == 0) {
- - // Initialize the module from Python's side
- - PyObject* const args = Py_BuildValue("(s)", PQ(module_name));
- - PyObject* result = py.functionCall("_pluginLoaded", Python::PYKRITA_ENGINE, args);
- - Py_DECREF(args);
- - if (result) {
- - dbgScript << "\t" << "success!";
- - return;
- - }
- - }
- - plugin.m_errorReason = i18nc("@info:tooltip", "Internal engine failure");
- - } else {
- - plugin.m_errorReason = i18nc(
- - "@info:tooltip"
- - , "Module not loaded:<br/>%1"
- - , py.lastTraceback().replace("\n", "<br/>")
- - );
- - }
- - plugin.m_broken = true;
- - warnScript << "Error loading plugin" << module_name;
- -}
- -
- -void PyKrita::Engine::unloadModule(int idx)
- -{
- - Q_ASSERT("Plugin index is out of range!" && 0 <= idx && idx < m_plugins.size());
- - PluginState& plugin = m_plugins[idx];
- - Q_ASSERT("Why to call unloadModule() for broken plugin?" && !plugin.isBroken());
- -
- - dbgScript << "Unloading module: " << plugin.pythonModuleName();
- -
- - Python py = Python();
- -
- - // Get 'plugins' key from 'pykrita' module dictionary
- - PyObject* plugins = py.itemString("plugins");
- - Q_ASSERT(
- - "'plugins' dict expected to be alive, otherwise code review required!"
- - && plugins
- - );
- -
- - PyObject* const args = Py_BuildValue("(s)", PQ(plugin.pythonModuleName()));
- - py.functionCall("_pluginUnloading", Python::PYKRITA_ENGINE, args);
- - Py_DECREF(args);
- -
- - // This will just decrement a reference count for module instance
- - PyDict_DelItemString(plugins, PQ(plugin.pythonModuleName()));
- -
- - // Remove the module also from 'sys.modules' dict to really unload it,
- - // so if reloaded all @init actions will work again!
- - PyObject* sys_modules = py.itemString("modules", "sys");
- - Q_ASSERT("Sanity check" && sys_modules);
- - PyDict_DelItemString(sys_modules, PQ(plugin.pythonModuleName()));
- -}
- -
- -// krita: space-indent on; indent-width 4;
- -#undef PYKRITA_INIT
- diff --git a/plugins/extensions/pykrita/plugin/plugin.h b/plugins/extensions/pykrita/plugin/plugin.h
- --- a/plugins/extensions/pykrita/plugin/plugin.h
- +++ b/plugins/extensions/pykrita/plugin/plugin.h
- @@ -25,7 +25,7 @@
- #include <QObject>
- #include <kis_view_plugin.h>
- -#include "engine.h"
- +#include "PythonPluginManager.h"
- class KritaPyQtPlugin : public KisViewPlugin
- {
- @@ -34,8 +34,7 @@
- KritaPyQtPlugin(QObject *parent, const QVariantList &);
- virtual ~KritaPyQtPlugin();
- private:
- - PyKrita::Engine m_engine;
- - QString m_engineFailureReason;
- + PythonPluginManager *pluginManager;
- bool m_autoReload;
- };
- diff --git a/plugins/extensions/pykrita/plugin/plugin.cpp b/plugins/extensions/pykrita/plugin/plugin.cpp
- --- a/plugins/extensions/pykrita/plugin/plugin.cpp
- +++ b/plugins/extensions/pykrita/plugin/plugin.cpp
- @@ -16,40 +16,52 @@
- */
- #include "plugin.h"
- -#include "engine.h"
- -#include "utilities.h"
- #include <klocalizedstring.h>
- #include <kis_debug.h>
- #include <kpluginfactory.h>
- -#include <KoResourcePaths.h>
- #include <kis_preference_set_registry.h>
- #include "pyqtpluginsettings.h"
- #include <Krita.h>
- -#include <Extension.h>
- K_PLUGIN_FACTORY_WITH_JSON(KritaPyQtPluginFactory, "kritapykrita.json", registerPlugin<KritaPyQtPlugin>();)
- KritaPyQtPlugin::KritaPyQtPlugin(QObject *parent, const QVariantList &)
- : KisViewPlugin(parent)
- - , m_engineFailureReason(m_engine.tryInitializeGetFailureReason())
- , m_autoReload(false)
- {
- -
- qDebug() << "Loading Python plugin";
- - KisPreferenceSetRegistry *preferenceSetRegistry = KisPreferenceSetRegistry::instance();
- - PyQtPluginSettingsFactory* settingsFactory = new PyQtPluginSettingsFactory(&m_engine);
- + PyKrita::InitResult initResult = PyKrita::initialize();
- + switch (initResult) {
- + case PyKrita::INIT_OK:
- + break;
- + case PyKrita::INIT_CANNOT_LOAD_PYTHON_LIBRARY:
- + qWarning() << i18n("Cannot load Python library");
- + return;
- + case PyKrita::INIT_CANNOT_SET_PYTHON_PATHS:
- + qWarning() << i18n("Cannot set Python paths");
- + return;
- + case PyKrita::INIT_CANNOT_LOAD_PYKRITA_MODULE:
- + qWarning() << i18n("Cannot load built-in pykrita module");
- + return;
- + default:
- + qWarning() << i18n("Unexpected error initializing python plugin.");
- + return;
- + }
- + pluginManager = PyKrita::pluginManager();
- + KisPreferenceSetRegistry *preferenceSetRegistry = KisPreferenceSetRegistry::instance();
- + PyQtPluginSettingsFactory* settingsFactory = new PyQtPluginSettingsFactory(pluginManager);
- //load and save preferences
- //if something in kritarc is missing, then the default from this load function will be used and saved back to kconfig.
- //this way, cfg.readEntry() in any part won't be able to set its own default
- KisPreferenceSet* settings = settingsFactory->createPreferenceSet();
- - Q_ASSERT(settings);
- + KIS_SAFE_ASSERT_RECOVER_RETURN(settings);
- settings->loadPreferences();
- settings->savePreferences();
- delete settings;
- @@ -61,17 +73,16 @@
- PyObject* pykritaPackage = py.moduleImport("pykrita");
- pykritaPackage = py.moduleImport("krita");
- - if (pykritaPackage) {
- + if (pykritaPackage) {
- dbgScript << "Loaded pykrita, now load plugins";
- - m_engine.tryLoadEnabledPlugins();
- + pluginManager->scanPlugins();
- + pluginManager->tryLoadEnabledPlugins();
- //py.functionCall("_pykritaLoaded", PyKrita::Python::PYKRITA_ENGINE);
- - }
- - else {
- + } else {
- dbgScript << "Cannot load pykrita module";
- - m_engine.setBroken();
- }
- - Q_FOREACH (Extension* ext, Krita::instance()->extensions())
- - {
- +
- + Q_FOREACH (Extension* ext, Krita::instance()->extensions()) {
- ext->setup();
- }
- }
- diff --git a/plugins/extensions/pykrita/plugin/pyqtpluginsettings.h b/plugins/extensions/pykrita/plugin/pyqtpluginsettings.h
- --- a/plugins/extensions/pykrita/plugin/pyqtpluginsettings.h
- +++ b/plugins/extensions/pykrita/plugin/pyqtpluginsettings.h
- @@ -18,7 +18,6 @@
- #define PYQTPLUGINSETTINGS_H
- #include "kis_preference_set_registry.h"
- -#include "engine.h"
- namespace Ui
- {
- @@ -26,13 +25,14 @@
- }
- class QIcon;
- +class PythonPluginManager;
- class PyQtPluginSettings : public KisPreferenceSet
- {
- Q_OBJECT
- public:
- - PyQtPluginSettings(PyKrita::Engine *engine, QWidget *parent = 0);
- + PyQtPluginSettings(PythonPluginManager *pluginManager, QWidget *parent = 0);
- ~PyQtPluginSettings();
- virtual QString id();
- @@ -71,12 +71,12 @@
- {
- public:
- - PyQtPluginSettingsFactory(PyKrita::Engine *engine) {
- - m_engine = engine;
- + PyQtPluginSettingsFactory(PythonPluginManager *engine) {
- + m_pluginManager = engine;
- }
- KisPreferenceSet* createPreferenceSet() {
- - PyQtPluginSettings* ps = new PyQtPluginSettings(m_engine);
- + PyQtPluginSettings* ps = new PyQtPluginSettings(m_pluginManager);
- QObject::connect(ps, SIGNAL(settingsChanged()), &repeater, SLOT(updateSettings()), Qt::UniqueConnection);
- return ps;
- }
- @@ -84,7 +84,7 @@
- return "PyQtSettings";
- }
- PyQtPluginSettingsUpdateRepeater repeater;
- - PyKrita::Engine *m_engine;
- + PythonPluginManager *m_pluginManager;
- };
- diff --git a/plugins/extensions/pykrita/plugin/pyqtpluginsettings.cpp b/plugins/extensions/pykrita/plugin/pyqtpluginsettings.cpp
- --- a/plugins/extensions/pykrita/plugin/pyqtpluginsettings.cpp
- +++ b/plugins/extensions/pykrita/plugin/pyqtpluginsettings.cpp
- @@ -26,23 +26,23 @@
- #include <KoIcon.h>
- -
- #include "kis_config.h"
- +#include "PythonPluginManager.h"
- -PyQtPluginSettings::PyQtPluginSettings(PyKrita::Engine *engine, QWidget *parent) :
- +PyQtPluginSettings::PyQtPluginSettings(PythonPluginManager *pluginManager, QWidget *parent) :
- KisPreferenceSet(parent),
- m_manager(new Ui::ManagerPage)
- {
- m_manager->setupUi(this);
- QSortFilterProxyModel* const proxy_model = new QSortFilterProxyModel(this);
- - proxy_model->setSourceModel(engine);
- + proxy_model->setSourceModel(pluginManager->model());
- m_manager->pluginsList->setModel(proxy_model);
- m_manager->pluginsList->resizeColumnToContents(0);
- m_manager->pluginsList->sortByColumn(0, Qt::AscendingOrder);
- - const bool is_enabled = bool(engine);
- + const bool is_enabled = bool(pluginManager);
- const bool is_visible = !is_enabled;
- m_manager->errorLabel->setVisible(is_visible);
- m_manager->pluginsList->setEnabled(is_enabled);
- diff --git a/plugins/extensions/pykrita/plugin/utilities.h b/plugins/extensions/pykrita/plugin/utilities.h
- --- a/plugins/extensions/pykrita/plugin/utilities.h
- +++ b/plugins/extensions/pykrita/plugin/utilities.h
- @@ -34,8 +34,31 @@
- /// Save us some ruddy time when printing out QStrings with UTF-8
- # define PQ(x) x.toUtf8().constData()
- +class PythonPluginManager;
- +
- namespace PyKrita
- {
- + enum InitResult {
- + INIT_UNINITIALIZED,
- + INIT_OK,
- + INIT_CANNOT_LOAD_PYTHON_LIBRARY,
- + INIT_CANNOT_SET_PYTHON_PATHS,
- + INIT_CANNOT_LOAD_PYKRITA_MODULE,
- + };
- +
- + /**
- + * Initialize the Python environment and plugin manager.
- + * This should be called first before using the manager
- + * or the Python class.
- + */
- + InitResult initialize();
- +
- + /**
- + * Gets the instance of the plugin manager.
- + * Note: PyKrita::initialize() must be called
- + * before using this function.
- + */
- + PythonPluginManager *pluginManager();
- /**
- * Instantiate this class on the stack to automatically get and release the
- @@ -115,18 +138,6 @@
- QString lastTraceback(void) const;
- /**
- - * Create a Python dictionary from a KConfigBase instance, writing the
- - * string representation of the values.
- - */
- - void updateDictionaryFromConfiguration(PyObject* dictionary, const KConfigBase* config);
- -
- - /**
- - * Write a Python dictionary to a configuration object, converting objects
- - * to their string representation along the way.
- - */
- - void updateConfigurationFromDictionary(KConfigBase* config, PyObject* dictionary);
- -
- - /**
- * Call the named module's named entry point.
- */
- bool functionCall(const char* functionName, const char* moduleName = PYKRITA_ENGINE);
- diff --git a/plugins/extensions/pykrita/plugin/utilities.cpp b/plugins/extensions/pykrita/plugin/utilities.cpp
- --- a/plugins/extensions/pykrita/plugin/utilities.cpp
- +++ b/plugins/extensions/pykrita/plugin/utilities.cpp
- @@ -25,13 +25,13 @@
- #include "config.h"
- #include "utilities.h"
- +#include "PythonPluginManager.h"
- #include <algorithm>
- #include <cmath>
- #include <Python.h>
- -
- #include <QDir>
- #include <QLibrary>
- #include <QString>
- @@ -45,11 +45,82 @@
- #include <kis_debug.h>
- +#include "PykritaModule.h"
- +
- #define THREADED 1
- namespace PyKrita
- {
- -namespace
- + static InitResult initStatus = INIT_UNINITIALIZED;
- + static QScopedPointer<PythonPluginManager> pluginManagerInstance;
- +
- + InitResult initialize()
- + {
- + // Already initialized?
- + if (initStatus == INIT_OK) return INIT_OK;
- +
- + dbgScript << "Initializing Python plugin for Python" << PY_MAJOR_VERSION << "," << PY_MINOR_VERSION;
- +
- + if (!Python::libraryLoad()) {
- + return INIT_CANNOT_LOAD_PYTHON_LIBRARY;
- + }
- +
- + // Update PYTHONPATH
- + // 0) custom plugin directories (prefer local dir over systems')
- + // 1) shipped krita module's dir
- + QStringList pluginDirectories = KoResourcePaths::findDirs("pythonscripts");
- + dbgScript << "Plugin Directories: " << pluginDirectories;
- + if (!Python::setPath(pluginDirectories)) {
- + initStatus = INIT_CANNOT_SET_PYTHON_PATHS;
- + return initStatus;
- + }
- +
- + if (0 != PyImport_AppendInittab(Python::PYKRITA_ENGINE, PyInit_pykrita)) {
- + initStatus = INIT_CANNOT_LOAD_PYKRITA_MODULE;
- + return initStatus;
- + }
- +
- + Python::ensureInitialized();
- + Python py = Python();
- +
- + PyRun_SimpleString(
- + "import sip\n"
- + "sip.setapi('QDate', 2)\n"
- + "sip.setapi('QTime', 2)\n"
- + "sip.setapi('QDateTime', 2)\n"
- + "sip.setapi('QUrl', 2)\n"
- + "sip.setapi('QTextStream', 2)\n"
- + "sip.setapi('QString', 2)\n"
- + "sip.setapi('QVariant', 2)\n"
- + );
- +
- + // Initialize 'plugins' dict of module 'pykrita'
- + PyObject* plugins = PyDict_New();
- + py.itemStringSet("plugins", plugins);
- +
- + pluginManagerInstance.reset(new PythonPluginManager());
- +
- + // Initialize our built-in module.
- + auto pykritaModule = PyInit_pykrita();
- +
- + if (!pykritaModule) {
- + initStatus = INIT_CANNOT_LOAD_PYKRITA_MODULE;
- + return initStatus;
- + //return i18nc("@info:tooltip ", "No <icode>pykrita</icode> built-in module");
- + }
- +
- + initStatus = INIT_OK;
- + return initStatus;
- + }
- +
- + PythonPluginManager *pluginManager()
- + {
- + auto pluginManager = pluginManagerInstance.data();
- + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(pluginManager, nullptr);
- + return pluginManager;
- + }
- +
- + namespace
- {
- #ifndef Q_OS_WIN
- QLibrary* s_pythonLibrary = 0;
- @@ -500,75 +571,6 @@
- #endif
- }
- -void Python::updateConfigurationFromDictionary(KConfigBase* const config, PyObject* const dictionary)
- -{
- - PyObject* groupKey;
- - PyObject* groupDictionary;
- - Py_ssize_t position = 0;
- - while (PyDict_Next(dictionary, &position, &groupKey, &groupDictionary)) {
- - if (!isUnicode(groupKey)) {
- - traceback(QString("Configuration group name not a string"));
- - continue;
- - }
- - QString groupName = unicode(groupKey);
- - if (!PyDict_Check(groupDictionary)) {
- - traceback(QString("Configuration group %1 top level key not a dictionary").arg(groupName));
- - continue;
- - }
- -
- - // There is a group per module.
- - KConfigGroup group = config->group(groupName);
- - PyObject* key;
- - PyObject* value;
- - Py_ssize_t x = 0;
- - while (PyDict_Next(groupDictionary, &x, &key, &value)) {
- - if (!isUnicode(key)) {
- - traceback(QString("Configuration group %1 itemKey not a string").arg(groupName));
- - continue;
- - }
- - PyObject* arguments = Py_BuildValue("(Oi)", value, 0);
- - PyObject* pickled = functionCall("dumps", "pickle", arguments);
- - if (pickled) {
- -#if PY_MAJOR_VERSION < 3
- - QString ascii(unicode(pickled));
- -#else
- - QString ascii(PyBytes_AsString(pickled));
- -#endif
- - group.writeEntry(unicode(key), ascii);
- - Py_DECREF(pickled);
- - } else {
- - errScript << "Cannot write" << groupName << unicode(key) << unicode(PyObject_Str(value));
- - }
- - }
- - }
- -}
- -
- -void Python::updateDictionaryFromConfiguration(PyObject* const dictionary, const KConfigBase* const config)
- -{
- - qDebug() << config->groupList();
- - Q_FOREACH(QString groupName, config->groupList()) {
- - KConfigGroup group = config->group(groupName);
- - PyObject* groupDictionary = PyDict_New();
- - PyDict_SetItemString(dictionary, PQ(groupName), groupDictionary);
- - Q_FOREACH(QString key, group.keyList()) {
- - QString pickled = group.readEntry(key);
- -#if PY_MAJOR_VERSION < 3
- - PyObject* arguments = Py_BuildValue("(s)", PQ(pickled));
- -#else
- - PyObject* arguments = Py_BuildValue("(y)", PQ(pickled));
- -#endif
- - PyObject* value = functionCall("loads", "pickle", arguments);
- - if (value) {
- - PyDict_SetItemString(groupDictionary, PQ(key), value);
- - Py_DECREF(value);
- - } else {
- - errScript << "Cannot read" << groupName << key << pickled;
- - }
- - }
- - Py_DECREF(groupDictionary);
- - }
- -}
- -
- bool Python::prependPythonPaths(const QString& path)
- {
- PyObject* sys_path = itemString("path", "sys");
- diff --git a/plugins/extensions/pykrita/plugin/version_checker.h b/plugins/extensions/pykrita/plugin/version_checker.h
- --- a/plugins/extensions/pykrita/plugin/version_checker.h
- +++ b/plugins/extensions/pykrita/plugin/version_checker.h
- @@ -20,6 +20,8 @@
- #ifndef __VERSION_CHECKER_H__
- # define __VERSION_CHECKER_H__
- +#include <libs/global/kis_debug.h>
- +#include "utilities.h"
- # include <QtCore/QString>
- # include <QtCore/QStringList>
- # include <QtCore/QtGlobal>
- @@ -33,17 +35,13 @@
- class version
- {
- enum type {
- - undefined = -1
- - , zero = 0
- + undefined = -1,
- + zero = 0
- };
- public:
- /// Default constructor
- - explicit version(
- - const int major = zero
- - , const int minor = zero
- - , const int patch = zero
- - )
- + explicit version(const int major = zero, const int minor = zero, const int patch = zero)
- : m_major(major)
- , m_minor(minor)
- , m_patch(patch) {
- @@ -87,6 +85,17 @@
- return version(tmp[0], tmp[1], tmp[2]);
- };
- + static version fromPythonObject(PyObject* version_obj)
- + {
- + version v = tryObtainVersionFromTuple(version_obj);
- + if (!v.isValid()) {
- + // PEP396 requires __version__ to be a tuple of integers,
- + // but some modules use a string instead.
- + v = tryObtainVersionFromString(version_obj);
- + }
- + return v;
- + }
- +
- static version invalid() {
- static version s_bad(undefined, undefined, undefined);
- return s_bad;
- @@ -96,6 +105,50 @@
- int m_major;
- int m_minor;
- int m_patch;
- +
- +
- + static version tryObtainVersionFromTuple(PyObject* version_obj)
- + {
- + Q_ASSERT("Sanity check" && version_obj);
- +
- + if (PyTuple_Check(version_obj) == 0)
- + return version::invalid();
- +
- + int version_info[3] = {0, 0, 0};
- + for (unsigned i = 0; i < PyTuple_Size(version_obj); ++i) {
- + PyObject* v = PyTuple_GetItem(version_obj, i);
- + if (v && PyLong_Check(v))
- + version_info[i] = PyLong_AsLong(v);
- + else
- + version_info[i] = -1;
- + }
- + if (version_info[0] != -1 && version_info[1] != -1 && version_info[2] != -1)
- + return ::PyKrita::version(version_info[0], version_info[1], version_info[2]);
- +
- + return version::invalid();
- + }
- +
- +/**
- + * Try to parse version string as a simple triplet X.Y.Z.
- + *
- + * \todo Some modules has letters in a version string...
- + * For example current \c pytz version is \e "2013d".
- + */
- + static version tryObtainVersionFromString(PyObject* version_obj)
- + {
- + Q_ASSERT("Sanity check" && version_obj);
- +
- + if (!Python::isUnicode(version_obj))
- + return version::invalid();
- +
- + QString version_str = Python::unicode(version_obj);
- + if (version_str.isEmpty())
- + return version::invalid();
- +
- + return version::fromString(version_str);
- + }
- +
- +
- };
- inline bool operator==(const version& left, const version& right)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement