diff --git a/krita/plugins/extensions/pykrita/src/engine.cpp b/krita/plugins/extensions/pykrita/src/engine.cpp index 86c9fff4f8..6cabaaade6 100644 --- a/krita/plugins/extensions/pykrita/src/engine.cpp +++ b/krita/plugins/extensions/pykrita/src/engine.cpp @@ -1,806 +1,809 @@ // This file is part of PyKrita, Krita' Python scripting plugin. // // Copyright (C) 2006 Paul Giannaros // Copyright (C) 2012, 2013 Shaheed Haque // Copyright (C) 2013 Alex Turbov // // 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 . // #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 #include #include #include #include #include #include #include #include #include /// 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 } , { "kDebug" , &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::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 (0 != PyImport_AppendInittab(Python::PYKRITA_ENGINE, PYKRITA_INIT)) { return i18nc("@info:tooltip ", "Cannot load built-in pykrita module"); } Python::libraryLoad(); Python py = Python(); // Update PYTHONPATH // 0) custom plugin directories (prefer local dir over systems') // 1) shipped krita module's dir // 2) w/ site_packages/ dir of the Python QStringList pluginDirectories = KGlobal::dirs()->findDirs("appdata", "pykrita/"); pluginDirectories << KStandardDirs::locate("appdata", "plugins/pykrita/") << QLatin1String(PYKRITA_PYTHON_SITE_PACKAGES_INSTALL_DIR) ; dbgScript << "Plugin Directories: " << pluginDirectories; if (!py.prependPythonPaths(pluginDirectories)) { return i18nc("@info:tooltip ", "Cannot update Python paths"); } 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 pykrita 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, 0); 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_service->name(); case Column::COMMENT: return m_plugins[index.row()].m_service->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(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_service->name()); return result; } void PyKrita::Engine::readGlobalPluginsConfiguration() { Python py = Python(); PyDict_Clear(m_configuration); KConfig config(CONFIG_FILE, KConfig::SimpleConfig); py.updateDictionaryFromConfiguration(m_configuration, &config); } void PyKrita::Engine::saveGlobalPluginsConfiguration() { Python py = Python(); KGlobal::config()->sync(); KConfig config(CONFIG_FILE, KConfig::SimpleConfig); py.updateConfigurationFromDictionary(&config, m_configuration); config.sync(); } void PyKrita::Engine::readSessionPluginsConfiguration(KConfigBase* const config) { PyDict_Clear(m_sessionConfiguration); Python().updateDictionaryFromConfiguration(m_sessionConfiguration, config); } void PyKrita::Engine::writeSessionPluginsConfiguration(KConfigBase* const config) { Python().updateConfigurationFromDictionary(config, m_sessionConfiguration); } bool PyKrita::Engine::isServiceUsable(const KService::Ptr& service) { dbgScript << "Got Krita/PythonPlugin: " << service->name() << ", module-path=" << service->library() ; // Make sure mandatory properties are here if (service->name().isEmpty()) { dbgScript << "Ignore desktop file w/o a name"; return false; } if (service->library().isEmpty()) { dbgScript << "Ignore desktop file w/o a module to import"; return false; } // Check Python compatibility // ATTENTION Python 3 is a default platform! Assume all modules are // compatible! Do checks only if someone tries to build krita w/ Python 2. // So, Python 2 modules must be marked explicitly! #if PY_MAJOR_VERSION < 3 const QVariant is_compatible = service->property("X-Python-2-Compatible", QVariant::Bool); if (!(is_compatible.isValid() && is_compatible.toBool())) { dbgScript << service->name() << "is incompatible w/ embedded Python version"; // Do not even show incompatible modules in the manager... return false; } #endif // ATTENTION If some module is Python 2 only, it must be marked w/ // the property 'X-Python-2-Only' of type bool and ANY (valid) value... const QVariant is_py2_only = service->property("X-Python-2-Only", QVariant::Bool); if (is_py2_only.isValid()) { dbgScript << service->name() << "is marked as Python 2 ONLY... >/dev/null"; // Do not even show incompatible modules in the manager... return false; } return true; } bool PyKrita::Engine::setModuleProperties(PluginState& plugin) { // Find the module: // 0) try to locate directory based plugin first KUrl rel_path = QString(Python::PYKRITA_ENGINE); dbgScript << rel_path; rel_path.addPath(plugin.moduleFilePathPart()); dbgScript << rel_path; rel_path.addPath("__init__.py"); dbgScript << rel_path; QString module_path = KGlobal::dirs()->findResource("appdata", rel_path.toLocalFile()); dbgScript << module_path; if (module_path.isEmpty()) { // 1) Nothing found, then try file based plugin rel_path = QString(Python::PYKRITA_ENGINE); rel_path.addPath(plugin.moduleFilePathPart() + ".py"); module_path = KGlobal::dirs()->findResource("appdata", rel_path.toLocalFile()); } 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 %1" , plugin.m_service->library() ); dbgScript << "Plugin is " << plugin.m_errorReason; return false; } dbgScript << "Found module path:" << module_path; return true; } QPair 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" , "

Specified version has invalid format for dependency %1: " "%2. Skipped

" , 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: * python-module(version-info), where python-module * a python module name to be imported, version-spec * 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_service ->property("X-Python-Dependencies", QVariant::StringList) .toStringList(); #if PY_MAJOR_VERSION < 3 { // Try to get Py2 only dependencies QStringList py2_dependencies = plugin.m_service ->property("X-Python-2-Dependencies", QVariant::StringList) .toStringList(); dependencies.append(py2_dependencies); } #endif Python py = Python(); QString reason = i18nc("@info:tooltip", "Dependency check"); Q_FOREACH(const QString & d, dependencies) { QPair 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_service->name() << "]:\n" << py.lastTraceback() ; plugin.m_unstable = true; reason += i18nc( "@info:tooltip" , "

Failed to check version of dependency %1: " "Module do not have PEP396 __version__ attribute. " "It is not disabled, but behaviour is unpredictable...

" , 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" , "

%1: Unexpected module's version format" , dependency ); } else if (!checker(dep_version)) { dbgScript << "Version requerement check failed [" << plugin.m_service->name() << "] for " << dependency << ": wanted " << checker.operationToString() << QString(checker.required()) << ", but found" << QString(dep_version) ; plugin.m_broken = true; reason += i18nc( "@info:tooltip" , "

%1: No suitable version found. " "Required version %2 %3, but found %4

" , dependency , checker.operationToString() , QString(checker.required()) , QString(dep_version) ); } // Do not need this module anymore... Py_DECREF(module); } else { dbgScript << "Load failure [" << plugin.m_service->name() << "]:\n" << py.lastTraceback(); plugin.m_broken = true; reason += i18nc( "@info:tooltip" , "

Failure on module load %1:

%2
" , dependency , py.lastTraceback() ); } } if (plugin.isBroken() || plugin.isUnstable()) plugin.m_errorReason = reason; } void PyKrita::Engine::scanPlugins() { m_plugins.clear(); // Clear current state. dbgScript << "Seeking for installed plugins..."; KService::List services = KoServiceLocator::instance()->entries("Krita/PythonPlugin"); Q_FOREACH(KService::Ptr service, services) { if (!isServiceUsable(service)) { dbgScript << service->name() << "is not usable"; continue; } // Make a new state PluginState plugin; plugin.m_service = service; if (!setModuleProperties(plugin)) { dbgScript << "Cannot load" << service->name() << ": broken"<< plugin.isBroken() << "because:" << plugin.errorReason(); continue; } verifyDependenciesSetStatus(plugin); m_plugins.append(plugin); } } 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_service->name()) != -1; } void PyKrita::Engine::tryLoadEnabledPlugins() { for (int i = 0; i < m_plugins.size(); ++i) if (m_plugins[i].isEnabled() && ! m_plugins[i].isBroken()) 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) - return; // Success! + if (result) { + return; + // Success! + } } plugin.m_errorReason = i18nc("@info:tooltip", "Internal engine failure"); } else { plugin.m_errorReason = i18nc( "@info:tooltip" , "Module not loaded:%1" , py.lastTraceback() ); } plugin.m_broken = true; + warnScript << "Error loading plugin" << module_name << ":" << plugin.m_errorReason; } 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/krita/plugins/extensions/pykrita/src/krita/__init__.py b/krita/plugins/extensions/pykrita/src/krita/__init__.py index b846e251fa..c10c8db3f7 100644 --- a/krita/plugins/extensions/pykrita/src/krita/__init__.py +++ b/krita/plugins/extensions/pykrita/src/krita/__init__.py @@ -1,46 +1,45 @@ import pykrita import os import sys from .api import * -from .configuration import * from .decorators import * def kDebug(text): '''Use KDE way to show debug info TODO Add a way to control debug output from partucular plugins (?) ''' plugin = sys._getframe(1).f_globals['__name__'] pykrita.kDebug('{}: {}'.format(plugin, text)) @pykritaEventHandler('_pluginLoaded') def on_load(plugin): if plugin in init.functions: # Call registered init functions for the plugin init.fire(plugin=plugin) del init.functions[plugin] return True @pykritaEventHandler('_pluginUnloading') def on_unload(plugin): if plugin in unload.functions: # Deinitialize plugin unload.fire(plugin=plugin) del unload.functions[plugin] return True @pykritaEventHandler('_pykritaLoaded') def on_pykrita_loaded(): kDebug('PYKRITA LOADED') return True @pykritaEventHandler('_pykritaUnloading') def on_pykrita_unloading(): kDebug('UNLOADING PYKRITA') return True diff --git a/krita/plugins/extensions/pykrita/src/krita/configuration.py b/krita/plugins/extensions/pykrita/src/krita/configuration.py deleted file mode 100644 index 9e94abcb08..0000000000 --- a/krita/plugins/extensions/pykrita/src/krita/configuration.py +++ /dev/null @@ -1,142 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (C) 2006 Paul Giannaros -# Copyright (C) 2013 Shaheed Haque -# Copyright (C) 2013 Alex Turbov -# -# 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. - -'''Configuration related stuff''' - -import pykrita # Built-in module -import sys - - -class Configuration: - '''A Configuration object provides a plugin-specific persistent - configuration dictionary. The configuration is saved and loaded from disk - automatically. - - Do not instantiate your own Configuration object; a plugin simply uses - pykrita.configuration and the class automatically creates a plugin-specific - dictionary to support it. - - Use a string key. Any Python type that can be pickled is can be used as a - value -- dictionaries, lists, numbers, strings, sets, and so on. - ''' - - def __init__(self, root): - self.root = root - - def __getitem__(self, key): - plugin = sys._getframe(1).f_globals['__name__'] - return self.root.get(plugin, {})[key] - - def __setitem__(self, key, value): - plugin = sys._getframe(1).f_globals['__name__'] - if plugin not in self.root: - self.root[plugin] = {} - self.root[plugin][key] = value - - def __delitem__(self, key): - plugin = sys._getframe(1).f_globals['__name__'] - del self.root.get(plugin, {})[key] - - def __contains__(self, key): - plugin = sys._getframe(1).f_globals['__name__'] - return key in self.root.get(plugin, {}) - - def __len__(self): - plugin = sys._getframe(1).f_globals['__name__'] - return len(self.root.get(plugin, {})) - - def __iter__(self): - plugin = sys._getframe(1).f_globals['__name__'] - return iter(self.root.get(plugin, {})) - - def __str__(self): - plugin = sys._getframe(1).f_globals['__name__'] - return str(self.root.get(plugin, {})) - - def __repr__(self): - plugin = sys._getframe(1).f_globals['__name__'] - return repr(self.root.get(plugin, {})) - - def keys(self): - '''Return the keys from the configuration dictionary.''' - plugin = sys._getframe(1).f_globals['__name__'] - return self.root.get(plugin, {}).keys() - - def values(self): - '''Return the values from the configuration dictionary.''' - plugin = sys._getframe(1).f_globals['__name__'] - return self.root.get(plugin, {}).values() - - def items(self): - '''Return the items from the configuration dictionary.''' - plugin = sys._getframe(1).f_globals['__name__'] - return self.root.get(plugin, {}).items() - - def get(self, key, default=None): - '''Fetch a configuration item using it's string key, returning - a given default if not found. - - Parameters: - * key - String key for item. - * default - Value to return if key is not found. - - Returns: - The item value for key, or the given default if not found. - ''' - plugin = sys._getframe(1).f_globals['__name__'] - try: - return self.root.get(plugin, {})[key] - except KeyError: - return default - - def pop(self, key): - '''Delete a configuration item using it's string key. - - Parameters: - * key - String key for item. - Returns: - The value of the removed item. - Throws: - KeyError if key doesn't exist. - ''' - plugin = sys._getframe(1).f_globals['__name__'] - value = self.root.get(plugin, {})[key] - del self.root.get(plugin, {})[key] - return value - - def save(self): - pykrita.saveConfiguration() - - def _name(self): - return sys._getframe(1).f_globals['__name__'] - - -globalConfiguration = pykrita.configuration -'''Configuration for all plugins. - -This can also be used by one plugin to access another plugin's configurations. -''' - -configuration = Configuration(pykrita.configuration) -'''Persistent configuration dictionary for this plugin.''' - - -sessionConfiguration = Configuration(pykrita.sessionConfiguration) -'''Per session persistent configuration dictionary for this plugin.''' diff --git a/krita/plugins/extensions/pykrita/src/plugins/hello/hello.py b/krita/plugins/extensions/pykrita/src/plugins/hello/hello.py index 712673c59e..65768b46ac 100644 --- a/krita/plugins/extensions/pykrita/src/plugins/hello/hello.py +++ b/krita/plugins/extensions/pykrita/src/plugins/hello/hello.py @@ -1,7 +1,9 @@ from PyQt4.QtGui import * from PyKrita4.krita import * +from krita import * def hello(): - QMessageBox.information(QWidget(), "Test", "Hello World") +QAction ac = Krita.createAction("Hello") +ac.triggered.conect(hello)