Advertisement
Guest User

Untitled

a guest
Dec 7th, 2017
345
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 89.14 KB | None | 0 0
  1. diff --git a/plugins/extensions/pykrita/kritarunner/CMakeLists.txt b/plugins/extensions/pykrita/kritarunner/CMakeLists.txt
  2. --- a/plugins/extensions/pykrita/kritarunner/CMakeLists.txt
  3. +++ b/plugins/extensions/pykrita/kritarunner/CMakeLists.txt
  4. @@ -5,10 +5,12 @@
  5. )
  6.  
  7. set(kritarunner_SRCS main.cpp
  8. - ../plugin/engine.cpp
  9. ../plugin/plugin.cpp
  10. ../plugin/pyqtpluginsettings.cpp
  11. ../plugin/utilities.cpp
  12. + ../plugin/PykritaModule.cpp
  13. + ../plugin/PythonPluginManager.cpp
  14. + ../plugin/PythonPluginsModel.cpp
  15. )
  16.  
  17. add_executable(kritarunner ${kritarunner_SRCS})
  18. diff --git a/plugins/extensions/pykrita/kritarunner/main.cpp b/plugins/extensions/pykrita/kritarunner/main.cpp
  19. --- a/plugins/extensions/pykrita/kritarunner/main.cpp
  20. +++ b/plugins/extensions/pykrita/kritarunner/main.cpp
  21. @@ -26,11 +26,9 @@
  22. #include <KoGlobal.h>
  23. #include <resources/KoHashGeneratorProvider.h>
  24. #include "kis_md5_generator.h"
  25. +#include "PythonPluginManager.h"
  26. #include <opengl/kis_opengl.h>
  27.  
  28. -#include <engine.h>
  29. -#include <utilities.h>
  30. -
  31. extern "C" int main(int argc, char **argv)
  32. {
  33. // The global initialization of the random generator
  34. @@ -80,12 +78,26 @@
  35. qDebug() << "\tPython path:" << pythonPath;
  36.  
  37. qDebug() << "Creating engine";
  38. - PyKrita::Engine engine;
  39. - QString r = engine.tryInitializeGetFailureReason();
  40.  
  41. - if (!r.isEmpty()) {
  42. - qDebug("Could not initialize the Python engine");
  43. - return 1;
  44. + // TODO: refactor to share common parts with plugin.cpp
  45. +
  46. + PyKrita::InitResult initResult = PyKrita::initialize();
  47. +
  48. + switch (initResult) {
  49. + case PyKrita::INIT_OK:
  50. + break;
  51. + case PyKrita::INIT_CANNOT_LOAD_PYTHON_LIBRARY:
  52. + qWarning() << i18n("Cannot load Python library");
  53. + return 1;
  54. + case PyKrita::INIT_CANNOT_SET_PYTHON_PATHS:
  55. + qWarning() << i18n("Cannot set Python paths");
  56. + return 1;
  57. + case PyKrita::INIT_CANNOT_LOAD_PYKRITA_MODULE:
  58. + qWarning() << i18n("Cannot load built-in pykrita module");
  59. + return 1;
  60. + default:
  61. + qWarning() << i18n("Unexpected error initializing python plugin.");
  62. + return 1;
  63. }
  64.  
  65. qDebug() << "Try to import the pykrita module";
  66. diff --git a/plugins/extensions/pykrita/plugin/CMakeLists.txt b/plugins/extensions/pykrita/plugin/CMakeLists.txt
  67. --- a/plugins/extensions/pykrita/plugin/CMakeLists.txt
  68. +++ b/plugins/extensions/pykrita/plugin/CMakeLists.txt
  69. @@ -7,7 +7,9 @@
  70. plugin.cpp
  71. pyqtpluginsettings.cpp
  72. utilities.cpp
  73. - engine.cpp
  74. + PykritaModule.cpp
  75. + PythonPluginManager.cpp
  76. + PythonPluginsModel.cpp
  77. )
  78.  
  79. ki18n_wrap_ui(SOURCES
  80. diff --git a/plugins/extensions/pykrita/plugin/PykritaModule.h b/plugins/extensions/pykrita/plugin/PykritaModule.h
  81. new file mode 100644
  82. --- /dev/null
  83. +++ b/plugins/extensions/pykrita/plugin/PykritaModule.h
  84. @@ -0,0 +1,33 @@
  85. +/*
  86. + * This file is part of PyKrita, Krita' Python scripting plugin.
  87. + *
  88. + * Copyright (C) 2013 Alex Turbov <i.zaufi@gmail.com>
  89. + * Copyright (C) 2014-2016 Boudewijn Rempt <boud@valdyas.org>
  90. + *
  91. + * This library is free software; you can redistribute it and/or
  92. + * modify it under the terms of the GNU Library General Public
  93. + * License as published by the Free Software Foundation; either
  94. + * version 2 of the License, or (at your option) version 3.
  95. + *
  96. + * This library is distributed in the hope that it will be useful,
  97. + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  98. + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  99. + * Library General Public License for more details.
  100. + *
  101. + * You should have received a copy of the GNU Library General Public License
  102. + * along with this library; see the file COPYING.LIB. If not, write to
  103. + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
  104. + * Boston, MA 02110-1301, USA.
  105. + */
  106. +
  107. +#ifndef __PYKRITA_MODULE_H__
  108. +#define __PYKRITA_MODULE_H__
  109. +
  110. +#include <Python.h>
  111. +
  112. +/**
  113. + * Initializer for the built-in Python module.
  114. + */
  115. +PyMODINIT_FUNC PyInit_pykrita();
  116. +
  117. +#endif
  118. diff --git a/plugins/extensions/pykrita/plugin/PykritaModule.cpp b/plugins/extensions/pykrita/plugin/PykritaModule.cpp
  119. new file mode 100644
  120. --- /dev/null
  121. +++ b/plugins/extensions/pykrita/plugin/PykritaModule.cpp
  122. @@ -0,0 +1,79 @@
  123. +// This file is part of PyKrita, Krita' Python scripting plugin.
  124. +//
  125. +// Copyright (C) 2006 Paul Giannaros <paul@giannaros.org>
  126. +// Copyright (C) 2012, 2013 Shaheed Haque <srhaque@theiet.org>
  127. +// Copyright (C) 2013 Alex Turbov <i.zaufi@gmail.com>
  128. +//
  129. +// This library is free software; you can redistribute it and/or
  130. +// modify it under the terms of the GNU Lesser General Public
  131. +// License as published by the Free Software Foundation; either
  132. +// version 2.1 of the License, or (at your option) version 3, or any
  133. +// later version accepted by the membership of KDE e.V. (or its
  134. +// successor approved by the membership of KDE e.V.), which shall
  135. +// act as a proxy defined in Section 6 of version 3 of the license.
  136. +//
  137. +// This library is distributed in the hope that it will be useful,
  138. +// but WITHOUT ANY WARRANTY; without even the implied warranty of
  139. +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  140. +// Lesser General Public License for more details.
  141. +//
  142. +// You should have received a copy of the GNU Lesser General Public
  143. +// License along with this library. If not, see <http://www.gnu.org/licenses/>.
  144. +//
  145. +
  146. +#include "PykritaModule.h"
  147. +
  148. +#include "kis_debug.h"
  149. +
  150. +#define PYKRITA_INIT PyInit_pykrita
  151. +
  152. +/// \note Namespace name written in uppercase intentionally!
  153. +/// It will appear in debug output from Python plugins...
  154. +namespace PYKRITA
  155. +{
  156. + PyObject* debug(PyObject* /*self*/, PyObject* args)
  157. + {
  158. + const char* text;
  159. +
  160. + if (PyArg_ParseTuple(args, "s", &text))
  161. + dbgScript << text;
  162. + Py_INCREF(Py_None);
  163. + return Py_None;
  164. + }
  165. +} // namespace PYKRITA
  166. +
  167. +namespace
  168. +{
  169. + PyMethodDef pykritaMethods[] = {
  170. + {
  171. + "qDebug"
  172. + , &PYKRITA::debug
  173. + , METH_VARARGS
  174. + , "True KDE way to show debug info"
  175. + }
  176. + , { 0, 0, 0, 0 }
  177. + };
  178. +} // anonymous namespace
  179. +
  180. +//BEGIN Python module registration
  181. +PyMODINIT_FUNC PyInit_pykrita()
  182. +{
  183. + static struct PyModuleDef moduledef = {
  184. + PyModuleDef_HEAD_INIT
  185. + , "pykrita"
  186. + , "The pykrita module"
  187. + , -1
  188. + , pykritaMethods
  189. + , 0
  190. + , 0
  191. + , 0
  192. + , 0
  193. + };
  194. + PyObject *pykritaModule = PyModule_Create(&moduledef);
  195. + PyModule_AddStringConstant(pykritaModule, "__file__", __FILE__);
  196. + return pykritaModule;
  197. +}
  198. +//END Python module registration
  199. +
  200. +// krita: space-indent on; indent-width 4;
  201. +#undef PYKRITA_INIT
  202. diff --git a/plugins/extensions/pykrita/plugin/PythonPluginManager.h b/plugins/extensions/pykrita/plugin/PythonPluginManager.h
  203. new file mode 100644
  204. --- /dev/null
  205. +++ b/plugins/extensions/pykrita/plugin/PythonPluginManager.h
  206. @@ -0,0 +1,142 @@
  207. +/*
  208. + * This file is part of PyKrita, Krita' Python scripting plugin.
  209. + *
  210. + * Copyright (C) 2013 Alex Turbov <i.zaufi@gmail.com>
  211. + * Copyright (C) 2014-2016 Boudewijn Rempt <boud@valdyas.org>
  212. + * Copyright (C) 2017 Jouni Pentikäinen (joupent@gmail.com)
  213. +​ *
  214. +​ * This library is free software; you can redistribute it and/or
  215. +​ * modify it under the terms of the GNU Library General Public
  216. +​ * License as published by the Free Software Foundation; either
  217. +​ * version 2 of the License, or (at your option) any later version.
  218. +​ *
  219. +​ * This library is distributed in the hope that it will be useful,
  220. +​ * but WITHOUT ANY WARRANTY; without even the implied warranty of
  221. +​ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  222. +​ * Library General Public License for more details.
  223. +​ *
  224. +​ * You should have received a copy of the GNU Library General Public License
  225. +​ * along with this library; see the file COPYING.LIB. If not, write to
  226. +​ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
  227. +​ * Boston, MA 02110-1301, USA.
  228. +​ */
  229. +#ifndef PYTHONMODULEMANAGER_H
  230. +#define PYTHONMODULEMANAGER_H
  231. +
  232. +#include <QObject>
  233. +#include "version_checker.h"
  234. +#include "PythonPluginsModel.h"
  235. +
  236. +class PythonPluginsModel;
  237. +
  238. +/**
  239. + * Represents a Python described in the plugin's .desktop file.
  240. + */
  241. +class PythonPlugin
  242. +{
  243. +public:
  244. + /**
  245. + * Transforms the Python module name into a file path part
  246. + */
  247. + QString moduleFilePathPart() const;
  248. +
  249. + bool isValid() const;
  250. +
  251. + inline const QString& errorReason() const
  252. + {
  253. + return m_errorReason;
  254. + }
  255. +
  256. + inline bool isEnabled() const
  257. + {
  258. + return m_enabled;
  259. + }
  260. +
  261. + inline bool isBroken() const
  262. + {
  263. + return m_broken;
  264. + }
  265. +
  266. + inline bool isUnstable() const
  267. + {
  268. + return m_unstable;
  269. + }
  270. +
  271. + QString name() const
  272. + {
  273. + return m_name;
  274. + }
  275. +
  276. + QString moduleName() const
  277. + {
  278. + return m_moduleName;
  279. + }
  280. +
  281. + QVariant property(const QString &name) const
  282. + {
  283. + return m_properties.value(name, "");
  284. + }
  285. +
  286. + QString comment() const
  287. + {
  288. + return m_comment;
  289. + }
  290. +
  291. +private:
  292. + friend class PythonPluginManager;
  293. +
  294. + PythonPlugin() {
  295. + m_properties["X-Python-Dependencies"] = QStringList();
  296. + m_properties["X-Python-2-Dependencies"] = QStringList();
  297. + }
  298. +
  299. + QString m_errorReason;
  300. + bool m_enabled{false};
  301. + bool m_broken{false};
  302. + bool m_unstable{false};
  303. + bool m_loaded{false};
  304. +
  305. + QString m_name;
  306. + QString m_moduleName;
  307. + QString m_comment;
  308. + QMap<QString, QVariant> m_properties;
  309. +};
  310. +
  311. +/**
  312. + * The Python plugin manager handles discovery, loading and unloading of Python plugins.
  313. + * To get a reference to the manager, use PyKrita::pluginManager().
  314. + */
  315. +class PythonPluginManager : public QObject
  316. +{
  317. + Q_OBJECT
  318. +
  319. +public:
  320. + PythonPluginManager();
  321. + ~PythonPluginManager() override;
  322. +
  323. + const QList<PythonPlugin>& plugins() const;
  324. + PythonPlugin *plugin(int index);
  325. +
  326. + void scanPlugins();
  327. + void tryLoadEnabledPlugins();
  328. + void setPluginEnabled(PythonPlugin &plugin, bool enabled);
  329. +
  330. + PythonPluginsModel *model();
  331. +
  332. +public Q_SLOTS:
  333. + void unloadAllModules();
  334. +
  335. +private:
  336. + void loadModule(PythonPlugin &plugin);
  337. + void unloadModule(PythonPlugin &plugin);
  338. +
  339. + static bool verifyModuleExists(PythonPlugin &);
  340. + static void verifyDependenciesSetStatus(PythonPlugin&);
  341. + static QPair<QString, PyKrita::version_checker> parseDependency(const QString&);
  342. +
  343. + QList<PythonPlugin> m_plugins;
  344. +
  345. + PythonPluginsModel m_model;
  346. +};
  347. +
  348. +#endif //PYTHONMODULEMANAGER_H
  349. diff --git a/plugins/extensions/pykrita/plugin/PythonPluginManager.cpp b/plugins/extensions/pykrita/plugin/PythonPluginManager.cpp
  350. new file mode 100644
  351. --- /dev/null
  352. +++ b/plugins/extensions/pykrita/plugin/PythonPluginManager.cpp
  353. @@ -0,0 +1,416 @@
  354. +/*
  355. + * This file is part of PyKrita, Krita' Python scripting plugin.
  356. + *
  357. + * Copyright (C) 2013 Alex Turbov <i.zaufi@gmail.com>
  358. + * Copyright (C) 2014-2016 Boudewijn Rempt <boud@valdyas.org>
  359. + * Copyright (C) 2017 Jouni Pentikäinen (joupent@gmail.com)
  360. +​ *
  361. +​ * This library is free software; you can redistribute it and/or
  362. +​ * modify it under the terms of the GNU Library General Public
  363. +​ * License as published by the Free Software Foundation; either
  364. +​ * version 2 of the License, or (at your option) any later version.
  365. +​ *
  366. +​ * This library is distributed in the hope that it will be useful,
  367. +​ * but WITHOUT ANY WARRANTY; without even the implied warranty of
  368. +​ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  369. +​ * Library General Public License for more details.
  370. +​ *
  371. +​ * You should have received a copy of the GNU Library General Public License
  372. +​ * along with this library; see the file COPYING.LIB. If not, write to
  373. +​ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
  374. +​ * Boston, MA 02110-1301, USA.
  375. +​ */
  376. +
  377. +#include "PythonPluginManager.h"
  378. +
  379. +#include <QtCore/QSettings>
  380. +#include <KoResourcePaths.h>
  381. +#include <KConfigCore/KConfig>
  382. +#include <KI18n/KLocalizedString>
  383. +#include <KConfigCore/KSharedConfig>
  384. +#include <KConfigCore/KConfigGroup>
  385. +
  386. +#include "config.h"
  387. +#include "version_checker.h"
  388. +
  389. +PythonPluginManager* instance = 0;
  390. +
  391. +// PythonPlugin implementation
  392. +
  393. +QString PythonPlugin::moduleFilePathPart() const
  394. +{
  395. + QString filePath = m_moduleName;
  396. + return filePath.replace(".", "/");
  397. +}
  398. +
  399. +bool PythonPlugin::isValid() const
  400. +{
  401. + dbgScript << "Got Krita/PythonPlugin: " << name()
  402. + << ", module-path=" << moduleName()
  403. + ;
  404. + // Make sure mandatory properties are here
  405. + if (m_name.isEmpty()) {
  406. + dbgScript << "Ignore desktop file w/o a name";
  407. + return false;
  408. + }
  409. + if (m_moduleName.isEmpty()) {
  410. + dbgScript << "Ignore desktop file w/o a module to import";
  411. + return false;
  412. + }
  413. +
  414. + return true;
  415. +}
  416. +
  417. +// PythonPluginManager implementation
  418. +
  419. +PythonPluginManager::PythonPluginManager()
  420. + : QObject(0)
  421. + , m_model(0, this)
  422. +{}
  423. +
  424. +/// \todo More accurate shutdown required:
  425. +/// need to keep track what exactly was broken on
  426. +/// initialize attempt...
  427. +PythonPluginManager::~PythonPluginManager()
  428. +{
  429. + dbgScript << "Going to destroy the Python engine";
  430. +
  431. + // Notify Python that engine going to die
  432. + {
  433. + PyKrita::Python py = PyKrita::Python();
  434. + py.functionCall("_pykritaUnloading");
  435. + }
  436. + unloadAllModules();
  437. +
  438. + PyKrita::Python::maybeFinalize();
  439. + PyKrita::Python::libraryUnload();
  440. +}
  441. +
  442. +const QList<PythonPlugin>& PythonPluginManager::plugins() const
  443. +{
  444. + return m_plugins;
  445. +}
  446. +
  447. +PythonPlugin * PythonPluginManager::plugin(int index) {
  448. + if (index >= 0 && index < m_plugins.count()) {
  449. + return &m_plugins[index];
  450. + }
  451. +
  452. + return nullptr;
  453. +}
  454. +
  455. +PythonPluginsModel * PythonPluginManager::model()
  456. +{
  457. + return &m_model;
  458. +}
  459. +
  460. +void PythonPluginManager::unloadAllModules()
  461. +{
  462. + Q_FOREACH(PythonPlugin plugin, m_plugins) {
  463. + unloadModule(plugin);
  464. + }
  465. +}
  466. +
  467. +bool PythonPluginManager::verifyModuleExists(PythonPlugin &plugin)
  468. +{
  469. + // Find the module:
  470. + // 0) try to locate directory based plugin first
  471. + QString rel_path = plugin.moduleFilePathPart();
  472. + rel_path = rel_path + "/" + "__init__.py";
  473. + dbgScript << "Finding Python module with rel_path:" << rel_path;
  474. +
  475. + QString module_path = KoResourcePaths::findResource("pythonscripts", rel_path);
  476. +
  477. + dbgScript << "module_path:" << module_path;
  478. +
  479. + if (module_path.isEmpty()) {
  480. + // 1) Nothing found, then try file based plugin
  481. + rel_path = plugin.moduleFilePathPart() + ".py";
  482. + dbgScript << "Finding Python module with rel_path:" << rel_path;
  483. + module_path = KoResourcePaths::findResource("pythonscripts", rel_path);
  484. + dbgScript << "module_path:" << module_path;
  485. + }
  486. +
  487. + // Is anything found at all?
  488. + if (module_path.isEmpty()) {
  489. + plugin.m_broken = true;
  490. + plugin.m_errorReason = i18nc(
  491. + "@info:tooltip"
  492. + , "Unable to find the module specified <application>%1</application>"
  493. + , plugin.moduleName()
  494. + );
  495. + dbgScript << "Cannot load module:" << plugin.m_errorReason;
  496. + return false;
  497. + }
  498. + dbgScript << "Found module path:" << module_path;
  499. + return true;
  500. +}
  501. +
  502. +QPair<QString, PyKrita::version_checker> PythonPluginManager::parseDependency(const QString& d)
  503. +{
  504. + // Check if dependency has package info attached
  505. + const int pnfo = d.indexOf('(');
  506. + if (pnfo != -1) {
  507. + QString dependency = d.mid(0, pnfo);
  508. + QString version_str = d.mid(pnfo + 1, d.size() - pnfo - 2).trimmed();
  509. + dbgScript << "Desired version spec [" << dependency << "]:" << version_str;
  510. + PyKrita::version_checker checker = PyKrita::version_checker::fromString(version_str);
  511. + if (!(checker.isValid() && d.endsWith(')'))) {
  512. + dbgScript << "Invalid version spec " << d;
  513. + QString reason = i18nc(
  514. + "@info:tooltip"
  515. + , "<p>Specified version has invalid format for dependency <application>%1</application>: "
  516. + "<icode>%2</icode>. Skipped</p>"
  517. + , dependency
  518. + , version_str
  519. + );
  520. + return qMakePair(reason, PyKrita::version_checker());
  521. + }
  522. + return qMakePair(dependency, checker);
  523. + }
  524. + return qMakePair(d, PyKrita::version_checker(PyKrita::version_checker::undefined));
  525. +}
  526. +
  527. +/**
  528. + * Collect dependencies and check them. To do it
  529. + * just try to import a module... when unload it ;)
  530. + *
  531. + * \c X-Python-Dependencies property of \c .desktop file has the following format:
  532. + * <tt>python-module(version-info)</tt>, where <tt>python-module</tt>
  533. + * a python module name to be imported, <tt>version-spec</tt>
  534. + * is a version triplet delimited by dots, possible w/ leading compare
  535. + * operator: \c =, \c <, \c >, \c <=, \c >=
  536. + */
  537. +void PythonPluginManager::verifyDependenciesSetStatus(PythonPlugin& plugin)
  538. +{
  539. + QStringList dependencies = plugin.property("X-Python-Dependencies").toStringList();
  540. +
  541. + PyKrita::Python py = PyKrita::Python();
  542. + QString reason = i18nc("@info:tooltip", "<title>Dependency check</title>");
  543. + Q_FOREACH(const QString & d, dependencies) {
  544. + QPair<QString, PyKrita::version_checker> info_pair = parseDependency(d);
  545. + PyKrita::version_checker& checker = info_pair.second;
  546. + if (!checker.isValid()) {
  547. + plugin.m_broken = true;
  548. + reason += info_pair.first;
  549. + continue;
  550. + }
  551. +
  552. + dbgScript << "Try to import dependency module/package:" << d;
  553. +
  554. + // Try to import a module
  555. + const QString& dependency = info_pair.first;
  556. + PyObject* module = py.moduleImport(PQ(dependency));
  557. + if (module) {
  558. + if (checker.isEmpty()) { // Need to check smth?
  559. + dbgScript << "No version to check, just make sure it's loaded:" << dependency;
  560. + Py_DECREF(module);
  561. + continue;
  562. + }
  563. + // Try to get __version__ from module
  564. + // See PEP396: http://www.python.org/dev/peps/pep-0396/
  565. + PyObject* version_obj = py.itemString("__version__", PQ(dependency));
  566. + if (!version_obj) {
  567. + dbgScript << "No __version__ for " << dependency
  568. + << "[" << plugin.name() << "]:\n" << py.lastTraceback()
  569. + ;
  570. + plugin.m_unstable = true;
  571. + reason += i18nc(
  572. + "@info:tooltip"
  573. + , "<p>Failed to check version of dependency <application>%1</application>: "
  574. + "Module do not have PEP396 <code>__version__</code> attribute. "
  575. + "It is not disabled, but behaviour is unpredictable...</p>"
  576. + , dependency
  577. + );
  578. + }
  579. + PyKrita::version dep_version = PyKrita::version::fromPythonObject(version_obj);
  580. +
  581. + if (!dep_version.isValid()) {
  582. + // Dunno what is this... Giving up!
  583. + dbgScript << "***: Can't parse module version for" << dependency;
  584. + plugin.m_unstable = true;
  585. + reason += i18nc(
  586. + "@info:tooltip"
  587. + , "<p><application>%1</application>: Unexpected module's version format"
  588. + , dependency
  589. + );
  590. + } else if (!checker(dep_version)) {
  591. + dbgScript << "Version requirement check failed ["
  592. + << plugin.name() << "] for "
  593. + << dependency << ": wanted " << checker.operationToString()
  594. + << QString(checker.required())
  595. + << ", but found" << QString(dep_version)
  596. + ;
  597. + plugin.m_broken = true;
  598. + reason += i18nc(
  599. + "@info:tooltip"
  600. + , "<p><application>%1</application>: No suitable version found. "
  601. + "Required version %2 %3, but found %4</p>"
  602. + , dependency
  603. + , checker.operationToString()
  604. + , QString(checker.required())
  605. + , QString(dep_version)
  606. + );
  607. + }
  608. + // Do not need this module anymore...
  609. + Py_DECREF(module);
  610. + } else {
  611. + dbgScript << "Load failure [" << plugin.name() << "]:\n" << py.lastTraceback();
  612. + plugin.m_broken = true;
  613. + reason += i18nc(
  614. + "@info:tooltip"
  615. + , "<p>Failure on module load <application>%1</application>:</p><pre>%2</pre>"
  616. + , dependency
  617. + , py.lastTraceback()
  618. + );
  619. + }
  620. + }
  621. +
  622. + if (plugin.isBroken() || plugin.isUnstable()) {
  623. + plugin.m_errorReason = reason;
  624. + }
  625. +}
  626. +
  627. +void PythonPluginManager::scanPlugins()
  628. +{
  629. + m_plugins.clear();
  630. +
  631. + KConfigGroup pluginSettings(KSharedConfig::openConfig(), "python");
  632. +
  633. + QStringList desktopFiles = KoResourcePaths::findAllResources("data", "pykrita/*desktop");
  634. + qDebug() << desktopFiles;
  635. +
  636. + Q_FOREACH(const QString &desktopFile, desktopFiles) {
  637. +
  638. + QSettings s(desktopFile, QSettings::IniFormat);
  639. + s.beginGroup("Desktop Entry");
  640. + if (s.value("ServiceTypes").toString() == "Krita/PythonPlugin") {
  641. + PythonPlugin plugin;
  642. + plugin.m_comment = s.value("Comment").toString();
  643. + plugin.m_name = s.value("Name").toString();
  644. + plugin.m_moduleName = s.value("X-KDE-Library").toString();
  645. + plugin.m_properties["X-Python-2-Compatible"] = s.value("X-Python-2-Compatible", false).toBool();
  646. +
  647. + if (!plugin.isValid()) {
  648. + dbgScript << plugin.name() << "is not usable";
  649. + continue;
  650. + }
  651. +
  652. + if (!verifyModuleExists(plugin)) {
  653. + dbgScript << "Cannot load" << plugin.name() << ": broken"
  654. + << plugin.isBroken()
  655. + << "because:" << plugin.errorReason();
  656. + continue;
  657. + }
  658. +
  659. + verifyDependenciesSetStatus(plugin);
  660. +
  661. + plugin.m_enabled = pluginSettings.readEntry(QString("enable_") + plugin.moduleName(), false);
  662. +
  663. + m_plugins.append(plugin);
  664. + }
  665. + }
  666. +}
  667. +
  668. +void PythonPluginManager::tryLoadEnabledPlugins()
  669. +{
  670. + for (PythonPlugin &plugin : m_plugins) {
  671. + dbgScript << "Trying to load plugin" << plugin.moduleName()
  672. + << ". Enabled:" << plugin.isEnabled()
  673. + << ". Broken: " << plugin.isBroken();
  674. +
  675. + if (plugin.m_enabled && !plugin.isBroken()) {
  676. + loadModule(plugin);
  677. + }
  678. + }
  679. +}
  680. +
  681. +void PythonPluginManager::loadModule(PythonPlugin &plugin)
  682. +{
  683. + KIS_SAFE_ASSERT_RECOVER_RETURN(plugin.isEnabled() && !plugin.isBroken());
  684. +
  685. + QString module_name = plugin.moduleName();
  686. + dbgScript << "Loading module: " << module_name;
  687. +
  688. + PyKrita::Python py = PyKrita::Python();
  689. +
  690. + // Get 'plugins' key from 'pykrita' module dictionary.
  691. + // Every entry has a module name as a key and 2 elements tuple as a value
  692. + PyObject* plugins = py.itemString("plugins");
  693. + KIS_SAFE_ASSERT_RECOVER_RETURN(plugins);
  694. +
  695. + PyObject* module = py.moduleImport(PQ(module_name));
  696. + if (module) {
  697. + // Move just loaded module to the dict
  698. + const int ins_result = PyDict_SetItemString(plugins, PQ(module_name), module);
  699. + KIS_SAFE_ASSERT_RECOVER_NOOP(ins_result == 0);
  700. + Py_DECREF(module);
  701. + // Handle failure in release mode.
  702. + if (ins_result == 0) {
  703. + // Initialize the module from Python's side
  704. + PyObject* const args = Py_BuildValue("(s)", PQ(module_name));
  705. + PyObject* result = py.functionCall("_pluginLoaded", PyKrita::Python::PYKRITA_ENGINE, args);
  706. + Py_DECREF(args);
  707. + if (result) {
  708. + dbgScript << "\t" << "success!";
  709. + plugin.m_loaded = true;
  710. + return;
  711. + }
  712. + }
  713. + plugin.m_errorReason = i18nc("@info:tooltip", "Internal engine failure");
  714. + } else {
  715. + plugin.m_errorReason = i18nc(
  716. + "@info:tooltip"
  717. + , "Module not loaded:<br/>%1"
  718. + , py.lastTraceback().replace("\n", "<br/>")
  719. + );
  720. + }
  721. + plugin.m_broken = true;
  722. + warnScript << "Error loading plugin" << module_name;
  723. +}
  724. +
  725. +void PythonPluginManager::unloadModule(PythonPlugin &plugin)
  726. +{
  727. + KIS_SAFE_ASSERT_RECOVER_RETURN(!plugin.m_loaded);
  728. + KIS_SAFE_ASSERT_RECOVER_RETURN(!plugin.isBroken());
  729. +
  730. + dbgScript << "Unloading module: " << plugin.moduleName();
  731. +
  732. + PyKrita::Python py = PyKrita::Python();
  733. +
  734. + // Get 'plugins' key from 'pykrita' module dictionary
  735. + PyObject* plugins = py.itemString("plugins");
  736. + KIS_SAFE_ASSERT_RECOVER_RETURN(plugins);
  737. +
  738. + PyObject* const args = Py_BuildValue("(s)", PQ(plugin.moduleName()));
  739. + py.functionCall("_pluginUnloading", PyKrita::Python::PYKRITA_ENGINE, args);
  740. + Py_DECREF(args);
  741. +
  742. + // This will just decrement a reference count for module instance
  743. + PyDict_DelItemString(plugins, PQ(plugin.moduleName()));
  744. +
  745. + // Remove the module also from 'sys.modules' dict to really unload it,
  746. + // so if reloaded all @init actions will work again!
  747. + PyObject* sys_modules = py.itemString("modules", "sys");
  748. + KIS_SAFE_ASSERT_RECOVER_RETURN(sys_modules);
  749. + PyDict_DelItemString(sys_modules, PQ(plugin.moduleName()));
  750. +
  751. + plugin.m_loaded = false;
  752. +}
  753. +
  754. +void PythonPluginManager::setPluginEnabled(PythonPlugin &plugin, bool enabled)
  755. +{
  756. + bool wasEnabled = plugin.isEnabled();
  757. +
  758. + if (wasEnabled && !enabled) {
  759. + unloadModule(plugin);
  760. + }
  761. +
  762. + plugin.m_enabled = enabled;
  763. + KConfigGroup pluginSettings(KSharedConfig::openConfig(), "python");
  764. + pluginSettings.writeEntry(QString("enable_") + plugin.moduleName(), enabled);
  765. +
  766. + if (!wasEnabled && enabled) {
  767. + unloadModule(plugin);
  768. + }
  769. +}
  770. diff --git a/plugins/extensions/pykrita/plugin/PythonPluginsModel.h b/plugins/extensions/pykrita/plugin/PythonPluginsModel.h
  771. new file mode 100644
  772. --- /dev/null
  773. +++ b/plugins/extensions/pykrita/plugin/PythonPluginsModel.h
  774. @@ -0,0 +1,51 @@
  775. +/*
  776. + * This file is part of PyKrita, Krita' Python scripting plugin.
  777. + *
  778. + * Copyright (C) 2013 Alex Turbov <i.zaufi@gmail.com>
  779. + * Copyright (C) 2014-2016 Boudewijn Rempt <boud@valdyas.org>
  780. + * Copyright (C) 2017 Jouni Pentikäinen (joupent@gmail.com)
  781. +​ *
  782. +​ * This library is free software; you can redistribute it and/or
  783. +​ * modify it under the terms of the GNU Library General Public
  784. +​ * License as published by the Free Software Foundation; either
  785. +​ * version 2 of the License, or (at your option) any later version.
  786. +​ *
  787. +​ * This library is distributed in the hope that it will be useful,
  788. +​ * but WITHOUT ANY WARRANTY; without even the implied warranty of
  789. +​ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  790. +​ * Library General Public License for more details.
  791. +​ *
  792. +​ * You should have received a copy of the GNU Library General Public License
  793. +​ * along with this library; see the file COPYING.LIB. If not, write to
  794. +​ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
  795. +​ * Boston, MA 02110-1301, USA.
  796. +​ */
  797. +
  798. +#ifndef KRITA_PYTHONPLUGINSMODEL_H
  799. +#define KRITA_PYTHONPLUGINSMODEL_H
  800. +
  801. +#include <QtCore/QAbstractTableModel>
  802. +
  803. +class PythonPluginManager;
  804. +
  805. +class PythonPluginsModel : public QAbstractTableModel
  806. +{
  807. +public:
  808. + PythonPluginsModel(QObject *parent, PythonPluginManager *pluginManager);
  809. +
  810. +private:
  811. + enum Column {COl_NAME, COL_COMMENT, COLUMN_COUNT};
  812. +
  813. + int columnCount(const QModelIndex&) const override;
  814. + int rowCount(const QModelIndex&) const override;
  815. + QModelIndex index(int row, int column, const QModelIndex& parent) const override;
  816. + QVariant headerData(int, Qt::Orientation, int) const override;
  817. + QVariant data(const QModelIndex&, int) const override;
  818. + Qt::ItemFlags flags(const QModelIndex&) const override;
  819. + bool setData(const QModelIndex&, const QVariant&, int) override;
  820. +
  821. +private:
  822. + PythonPluginManager *m_pluginManager;
  823. +};
  824. +
  825. +#endif //KRITA_PYTHONPLUGINSMODEL_H
  826. diff --git a/plugins/extensions/pykrita/plugin/PythonPluginsModel.cpp b/plugins/extensions/pykrita/plugin/PythonPluginsModel.cpp
  827. new file mode 100644
  828. --- /dev/null
  829. +++ b/plugins/extensions/pykrita/plugin/PythonPluginsModel.cpp
  830. @@ -0,0 +1,149 @@
  831. +/*
  832. + * This file is part of PyKrita, Krita' Python scripting plugin.
  833. + *
  834. + * Copyright (C) 2013 Alex Turbov <i.zaufi@gmail.com>
  835. + * Copyright (C) 2014-2016 Boudewijn Rempt <boud@valdyas.org>
  836. + * Copyright (C) 2017 Jouni Pentikäinen (joupent@gmail.com)
  837. +​ *
  838. +​ * This library is free software; you can redistribute it and/or
  839. +​ * modify it under the terms of the GNU Library General Public
  840. +​ * License as published by the Free Software Foundation; either
  841. +​ * version 2 of the License, or (at your option) any later version.
  842. +​ *
  843. +​ * This library is distributed in the hope that it will be useful,
  844. +​ * but WITHOUT ANY WARRANTY; without even the implied warranty of
  845. +​ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  846. +​ * Library General Public License for more details.
  847. +​ *
  848. +​ * You should have received a copy of the GNU Library General Public License
  849. +​ * along with this library; see the file COPYING.LIB. If not, write to
  850. +​ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
  851. +​ * Boston, MA 02110-1301, USA.
  852. +​ */
  853. +
  854. +#include "PythonPluginsModel.h"
  855. +
  856. +#include <kcolorscheme.h>
  857. +#include <KI18n/KLocalizedString>
  858. +
  859. +#include "PythonPluginManager.h"
  860. +
  861. +PythonPluginsModel::PythonPluginsModel(QObject *parent, PythonPluginManager *pluginManager)
  862. + : QAbstractTableModel(parent)
  863. + , m_pluginManager(pluginManager)
  864. +{
  865. +}
  866. +
  867. +int PythonPluginsModel::columnCount(const QModelIndex&) const
  868. +{
  869. + return COLUMN_COUNT;
  870. +}
  871. +
  872. +int PythonPluginsModel::rowCount(const QModelIndex&) const
  873. +{
  874. + return m_pluginManager->plugins().size();
  875. +}
  876. +
  877. +QModelIndex PythonPluginsModel::index(const int row, const int column, const QModelIndex& parent) const
  878. +{
  879. + if (!parent.isValid() && column < COLUMN_COUNT) {
  880. + auto *plugin = m_pluginManager->plugin(row);
  881. + if (plugin) {
  882. + return createIndex(row, column, plugin);
  883. + }
  884. + }
  885. +
  886. + return QModelIndex();
  887. +}
  888. +
  889. +QVariant PythonPluginsModel::headerData(const int section, const Qt::Orientation orientation, const int role) const
  890. +{
  891. + if (role == Qt::DisplayRole && orientation == Qt::Horizontal) {
  892. + switch (section) {
  893. + case COl_NAME:
  894. + return i18nc("@title:column", "Name");
  895. + case COL_COMMENT:
  896. + return i18nc("@title:column", "Comment");
  897. + default:
  898. + break;
  899. + }
  900. + }
  901. + return QVariant();
  902. +}
  903. +
  904. +QVariant PythonPluginsModel::data(const QModelIndex& index, const int role) const
  905. +{
  906. + if (index.isValid()) {
  907. + PythonPlugin *plugin = static_cast<PythonPlugin*>(index.internalPointer());
  908. + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(plugin, QVariant());
  909. +
  910. + switch (role) {
  911. + case Qt::DisplayRole:
  912. + switch (index.column()) {
  913. + case COl_NAME:
  914. + return plugin->name();
  915. + case COL_COMMENT:
  916. + return plugin->comment();
  917. + default:
  918. + break;
  919. + }
  920. + break;
  921. + case Qt::CheckStateRole:
  922. + if (index.column() == COl_NAME) {
  923. + const bool checked = plugin->isEnabled();
  924. + return checked ? Qt::Checked : Qt::Unchecked;
  925. + }
  926. + break;
  927. + case Qt::ToolTipRole:
  928. + {
  929. + auto error = plugin->errorReason();
  930. + if (!error.isEmpty()) {
  931. + return error;
  932. + }
  933. + }
  934. + break;
  935. + case Qt::ForegroundRole:
  936. + if (plugin->isUnstable()) {
  937. + KColorScheme scheme(QPalette::Inactive, KColorScheme::View);
  938. + return scheme.foreground(KColorScheme::NegativeText).color();
  939. + }
  940. + break;
  941. + default:
  942. + break;
  943. + }
  944. + }
  945. +
  946. + return QVariant();
  947. +}
  948. +
  949. +Qt::ItemFlags PythonPluginsModel::flags(const QModelIndex& index) const
  950. +{
  951. + PythonPlugin *plugin = static_cast<PythonPlugin*>(index.internalPointer());
  952. + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(plugin, Qt::ItemIsSelectable);
  953. +
  954. + int result = Qt::ItemIsSelectable;
  955. + if (index.column() == COl_NAME) {
  956. + result |= Qt::ItemIsUserCheckable;
  957. + }
  958. +
  959. + // Disable UI for broken modules
  960. + if (!plugin->isBroken()) {
  961. + result |= Qt::ItemIsEnabled;
  962. + }
  963. +
  964. + return static_cast<Qt::ItemFlag>(result);
  965. +}
  966. +
  967. +bool PythonPluginsModel::setData(const QModelIndex& index, const QVariant& value, const int role)
  968. +{
  969. + PythonPlugin *plugin = static_cast<PythonPlugin*>(index.internalPointer());
  970. + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(plugin, false);
  971. +
  972. + if (role == Qt::CheckStateRole) {
  973. + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!plugin->isBroken(), false);
  974. +
  975. + const bool enabled = value.toBool();
  976. + m_pluginManager->setPluginEnabled(*plugin, enabled);
  977. + }
  978. + return true;
  979. +}
  980. diff --git a/plugins/extensions/pykrita/plugin/config.h.cmake b/plugins/extensions/pykrita/plugin/config.h.cmake
  981. --- a/plugins/extensions/pykrita/plugin/config.h.cmake
  982. +++ b/plugins/extensions/pykrita/plugin/config.h.cmake
  983. @@ -19,3 +19,6 @@
  984.  
  985. #define PYKRITA_PYTHON_LIBRARY "${PYTHON_LIBRARY}"
  986. #define PYKRITA_PYTHON_SITE_PACKAGES_INSTALL_DIR "${PYTHON_SITE_PACKAGES_INSTALL_DIR}"
  987. +
  988. +/// Name of the file where per-plugin configuration is stored
  989. +#define CONFIG_FILE "kritapykritarc"
  990. diff --git a/plugins/extensions/pykrita/plugin/engine.h b/plugins/extensions/pykrita/plugin/engine.h
  991. deleted file mode 100644
  992. --- a/plugins/extensions/pykrita/plugin/engine.h
  993. +++ /dev/null
  994. @@ -1,234 +0,0 @@
  995. -/*
  996. - * This file is part of PyKrita, Krita' Python scripting plugin.
  997. - *
  998. - * Copyright (C) 2013 Alex Turbov <i.zaufi@gmail.com>
  999. - * Copyright (C) 2014-2016 Boudewijn Rempt <boud@valdyas.org>
  1000. - *
  1001. - * This library is free software; you can redistribute it and/or
  1002. - * modify it under the terms of the GNU Library General Public
  1003. - * License as published by the Free Software Foundation; either
  1004. - * version 2 of the License, or (at your option) version 3.
  1005. - *
  1006. - * This library is distributed in the hope that it will be useful,
  1007. - * but WITHOUT ANY WARRANTY; without even the implied warranty of
  1008. - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  1009. - * Library General Public License for more details.
  1010. - *
  1011. - * You should have received a copy of the GNU Library General Public License
  1012. - * along with this library; see the file COPYING.LIB. If not, write to
  1013. - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
  1014. - * Boston, MA 02110-1301, USA.
  1015. - */
  1016. -
  1017. -#ifndef __PYKRITA_ENGINE_H__
  1018. -# define __PYKRITA_ENGINE_H__
  1019. -
  1020. -#include <cmath>
  1021. -#include <Python.h>
  1022. -
  1023. -#include "version_checker.h"
  1024. -
  1025. -#include <QAbstractItemModel>
  1026. -#include <QList>
  1027. -#include <QStringList>
  1028. -
  1029. -namespace PyKrita
  1030. -{
  1031. -
  1032. -/**
  1033. - * @brief The PyPlugin class describes a plugin written in Python and loaded into the system
  1034. - */
  1035. -class PyPlugin {
  1036. -
  1037. -public:
  1038. -
  1039. - PyPlugin()
  1040. - {
  1041. - m_properties["X-Python-Dependencies"] = QStringList();
  1042. - m_properties["X-Python-2-Dependencies"] = QStringList();
  1043. - }
  1044. -
  1045. - QString name() const
  1046. - {
  1047. - return m_name;
  1048. - }
  1049. -
  1050. - QString library() const
  1051. - {
  1052. - return m_libraryPath;
  1053. - }
  1054. -
  1055. - QVariant property(const QString &name) const
  1056. - {
  1057. - return m_properties.value(name, "");
  1058. - }
  1059. -
  1060. - QString comment() const
  1061. - {
  1062. - return m_comment;
  1063. - }
  1064. -
  1065. - QString m_name;
  1066. - QString m_libraryPath;
  1067. - QMap<QString, QVariant> m_properties;
  1068. - QString m_comment;
  1069. -};
  1070. -
  1071. -class Python; // fwd decl
  1072. -
  1073. -/**
  1074. - * The Engine class hosts the Python interpreter, loading
  1075. - * it into memory within Krita, and then with finding and
  1076. - * loading all of the PyKrita plugins.
  1077. - *
  1078. - * \attention Qt/KDE does not use exceptions (unfortunately),
  1079. - * so this class must be initialized in two steps:
  1080. - * - create an instance (via constructor)
  1081. - * - try to initialize the rest (via \c Engine::tryInitializeGetFailureReason())
  1082. - * If latter returns a non empty (failure reason) string, the only member
  1083. - * can be called is conversion to boolean! (which is implemented as safe-bool idiom [1])
  1084. - * Calling others leads to UB!
  1085. - *
  1086. - * \sa [1] http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Safe_bool
  1087. - */
  1088. -class Engine : public QAbstractItemModel
  1089. -{
  1090. - Q_OBJECT
  1091. -
  1092. - typedef void (Engine::*bool_type)() const;
  1093. - void unspecified_true_bool_type() const {}
  1094. -
  1095. -public:
  1096. - /// \todo Turn into a class w/ accessors
  1097. - class PluginState
  1098. - {
  1099. - public:
  1100. - /// \name Immutable accessors
  1101. - //@{
  1102. - QString pythonModuleName() const;
  1103. - const QString& errorReason() const;
  1104. - bool isEnabled() const;
  1105. - bool isBroken() const;
  1106. - bool isUnstable() const;
  1107. - //@}
  1108. -
  1109. - private:
  1110. - friend class Engine;
  1111. -
  1112. - PluginState();
  1113. - /// Transfort Python module name into a file path part
  1114. - QString moduleFilePathPart() const;
  1115. -
  1116. - PyPlugin m_pythonPlugin;
  1117. - QString m_pythonModule;
  1118. - QString m_errorReason;
  1119. - bool m_enabled;
  1120. - bool m_broken;
  1121. - bool m_unstable;
  1122. - bool m_isDir;
  1123. - };
  1124. -
  1125. - /// Default constructor: initialize Python interpreter
  1126. - Engine();
  1127. - /// Cleanup everything on unload
  1128. - ~Engine();
  1129. -
  1130. - //BEGIN QAbstractItemModel interface
  1131. - virtual int columnCount(const QModelIndex&) const /*override*/;
  1132. - virtual int rowCount(const QModelIndex&) const /*override*/;
  1133. - virtual QModelIndex index(int, int, const QModelIndex&) const /*override*/;
  1134. - virtual QModelIndex parent(const QModelIndex&) const /*override*/;
  1135. - virtual QVariant headerData(int, Qt::Orientation, int) const /*override*/;
  1136. - virtual QVariant data(const QModelIndex&, int) const /*override*/;
  1137. - virtual Qt::ItemFlags flags(const QModelIndex&) const /*override*/;
  1138. - virtual bool setData(const QModelIndex&, const QVariant&, int) /*override*/;
  1139. - //END QAbstractItemModel interface
  1140. -
  1141. - void setEnabledPlugins(const QStringList&); ///< Set enabled plugins to the model
  1142. - void tryLoadEnabledPlugins(); ///< Try to load enabled plugins
  1143. - QStringList enabledPlugins() const; ///< Form a list of enabled plugins
  1144. - const QList<PluginState>& plugins() const; ///< Provide immutable access to found plugins
  1145. - QString tryInitializeGetFailureReason(); ///< Try to initialize Python interpreter
  1146. - operator bool_type() const; ///< Check if instance is usable
  1147. - void setBroken(); ///< Make it broken by some external reason
  1148. -
  1149. -public Q_SLOTS:
  1150. - void readGlobalPluginsConfiguration(); ///< Load plugins' configuration.
  1151. - void saveGlobalPluginsConfiguration(); ///< Write out plugins' configuration.
  1152. - void unloadAllModules();
  1153. -
  1154. -protected:
  1155. - void scanPlugins(); ///< Search for available plugins
  1156. - void loadModule(int); ///< Load module by index in \c m_plugins
  1157. - void unloadModule(int); ///< Unload module by index in \c m_plugins
  1158. -
  1159. -private:
  1160. - // Simulate strong typed enums from C++11
  1161. - struct Column {
  1162. - enum type {
  1163. - NAME
  1164. - , COMMENT
  1165. - , LAST__
  1166. - };
  1167. - };
  1168. -
  1169. - static bool isPythonPluginUsable(const PyPlugin *pyPlugin); ///< Make sure that service is usable
  1170. - static bool setModuleProperties(PluginState&);
  1171. - static void verifyDependenciesSetStatus(PluginState&);
  1172. - static QPair<QString, version_checker> parseDependency(const QString&);
  1173. - static version tryObtainVersionFromTuple(PyObject*);
  1174. - static version tryObtainVersionFromString(PyObject*);
  1175. -
  1176. - PyObject* m_configuration; ///< Application-wide configuration data
  1177. - PyObject* m_sessionConfiguration; ///< Session-wide configuration data
  1178. - QList<PluginState> m_plugins; ///< List of available plugins
  1179. - bool m_engineIsUsable; ///< Is engine loaded Ok?
  1180. -};
  1181. -
  1182. -inline QString Engine::PluginState::pythonModuleName() const
  1183. -{
  1184. - return m_pythonPlugin.library();
  1185. -}
  1186. -
  1187. -inline QString PyKrita::Engine::PluginState::moduleFilePathPart() const
  1188. -{
  1189. - return m_pythonPlugin.library().replace(".", "/");
  1190. -}
  1191. -
  1192. -inline const QString& Engine::PluginState::errorReason() const
  1193. -{
  1194. - return m_errorReason;
  1195. -}
  1196. -
  1197. -inline bool Engine::PluginState::isEnabled() const
  1198. -{
  1199. - return m_enabled;
  1200. -}
  1201. -
  1202. -inline bool Engine::PluginState::isBroken() const
  1203. -{
  1204. - return m_broken;
  1205. -}
  1206. -
  1207. -inline bool Engine::PluginState::isUnstable() const
  1208. -{
  1209. - return m_unstable;
  1210. -}
  1211. -
  1212. -inline const QList<Engine::PluginState>& Engine::plugins() const
  1213. -{
  1214. - return m_plugins;
  1215. -}
  1216. -
  1217. -inline Engine::operator bool_type() const
  1218. -{
  1219. - return m_engineIsUsable ? &Engine::unspecified_true_bool_type : 0;
  1220. -}
  1221. -
  1222. -inline void Engine::setBroken()
  1223. -{
  1224. - m_engineIsUsable = false;
  1225. -}
  1226. -
  1227. -} // namespace PyKrita
  1228. -#endif // __PYKRITA_ENGINE_H__
  1229. diff --git a/plugins/extensions/pykrita/plugin/engine.cpp b/plugins/extensions/pykrita/plugin/engine.cpp
  1230. deleted file mode 100644
  1231. --- a/plugins/extensions/pykrita/plugin/engine.cpp
  1232. +++ /dev/null
  1233. @@ -1,799 +0,0 @@
  1234. -// This file is part of PyKrita, Krita' Python scripting plugin.
  1235. -//
  1236. -// Copyright (C) 2006 Paul Giannaros <paul@giannaros.org>
  1237. -// Copyright (C) 2012, 2013 Shaheed Haque <srhaque@theiet.org>
  1238. -// Copyright (C) 2013 Alex Turbov <i.zaufi@gmail.com>
  1239. -//
  1240. -// This library is free software; you can redistribute it and/or
  1241. -// modify it under the terms of the GNU Lesser General Public
  1242. -// License as published by the Free Software Foundation; either
  1243. -// version 2.1 of the License, or (at your option) version 3, or any
  1244. -// later version accepted by the membership of KDE e.V. (or its
  1245. -// successor approved by the membership of KDE e.V.), which shall
  1246. -// act as a proxy defined in Section 6 of version 3 of the license.
  1247. -//
  1248. -// This library is distributed in the hope that it will be useful,
  1249. -// but WITHOUT ANY WARRANTY; without even the implied warranty of
  1250. -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  1251. -// Lesser General Public License for more details.
  1252. -//
  1253. -// You should have received a copy of the GNU Lesser General Public
  1254. -// License along with this library. If not, see <http://www.gnu.org/licenses/>.
  1255. -//
  1256. -
  1257. -#include "engine.h"
  1258. -
  1259. -// config.h defines PYKRITA_PYTHON_LIBRARY, the path to libpython.so
  1260. -// on the build system
  1261. -
  1262. -#include "config.h"
  1263. -#include "utilities.h"
  1264. -
  1265. -#include <cmath>
  1266. -#include <Python.h>
  1267. -
  1268. -#include <QSettings>
  1269. -#include <QLibrary>
  1270. -#include <QFileInfo>
  1271. -
  1272. -#include <kconfig.h>
  1273. -#include <klocalizedstring.h>
  1274. -#include <kcolorscheme.h>
  1275. -
  1276. -//#include <KoServiceLocator.h>
  1277. -#include <KoResourcePaths.h>
  1278. -
  1279. -#include <kis_debug.h>
  1280. -
  1281. -/// Name of the file where per-plugin configuration is stored.
  1282. -#define CONFIG_FILE "kritapykritarc"
  1283. -
  1284. -#if PY_MAJOR_VERSION < 3
  1285. -# define PYKRITA_INIT initpykrita
  1286. -#else
  1287. -# define PYKRITA_INIT PyInit_pykrita
  1288. -#endif
  1289. -
  1290. -PyMODINIT_FUNC PYKRITA_INIT(); // fwd decl
  1291. -
  1292. -/// \note Namespace name written in uppercase intentionally!
  1293. -/// It will appear in debug output from Python plugins...
  1294. -namespace PYKRITA
  1295. -{
  1296. -PyObject* debug(PyObject* /*self*/, PyObject* args)
  1297. -{
  1298. - const char* text;
  1299. -
  1300. - if (PyArg_ParseTuple(args, "s", &text))
  1301. - dbgScript << text;
  1302. - Py_INCREF(Py_None);
  1303. - return Py_None;
  1304. -}
  1305. -} // namespace PYKRITA
  1306. -
  1307. -namespace
  1308. -{
  1309. -PyObject* s_pykrita;
  1310. -/**
  1311. - * \attention Krita has embedded Python, so init function \b never will be called
  1312. - * automatically! We can use this fact to initialize a pointer to an instance
  1313. - * of the \c Engine class (which is a part of the \c Plugin), so exported
  1314. - * functions will know it (yep, from Python's side they should be static).
  1315. - */
  1316. -PyKrita::Engine* s_engine_instance = 0;
  1317. -
  1318. -/**
  1319. - * Wrapper function, called explicitly from \c Engine::Engine
  1320. - * to initialize pointer to the only (by design) instance of the engine,
  1321. - * so exported (to Python) functions get know it... Then invoke
  1322. - * a real initialization sequence...
  1323. - */
  1324. -void pythonInitwrapper(PyKrita::Engine* const engine)
  1325. -{
  1326. - Q_ASSERT("Sanity check" && !s_engine_instance);
  1327. - s_engine_instance = engine;
  1328. - // Call initialize explicitly to initialize embedded interpreter.
  1329. - PYKRITA_INIT();
  1330. -}
  1331. -
  1332. -/**
  1333. - * Functions for the Python module called pykrita.
  1334. - * \todo Does it \b REALLY needed? Configuration data will be flushed
  1335. - * on exit anyway! Why to write it (and even allow to plugins to call this)
  1336. - * \b before krita really going to exit? It would be better to \b deprecate
  1337. - * this (harmful) function!
  1338. - */
  1339. -PyObject* pykritaSaveConfiguration(PyObject* /*self*/, PyObject* /*unused*/)
  1340. -{
  1341. - if (s_engine_instance)
  1342. - s_engine_instance->saveGlobalPluginsConfiguration();
  1343. - Py_INCREF(Py_None);
  1344. - return Py_None;
  1345. -}
  1346. -
  1347. -PyMethodDef pykritaMethods[] = {
  1348. - {
  1349. - "saveConfiguration"
  1350. - , &pykritaSaveConfiguration
  1351. - , METH_NOARGS
  1352. - , "Save the configuration of the plugin into " CONFIG_FILE
  1353. - }
  1354. - , {
  1355. - "qDebug"
  1356. - , &PYKRITA::debug
  1357. - , METH_VARARGS
  1358. - , "True KDE way to show debug info"
  1359. - }
  1360. - , { 0, 0, 0, 0 }
  1361. -};
  1362. -
  1363. -} // anonymous namespace
  1364. -
  1365. -//BEGIN Python module registration
  1366. -PyMODINIT_FUNC PYKRITA_INIT()
  1367. -{
  1368. -#if PY_MAJOR_VERSION < 3
  1369. - s_pykrita = Py_InitModule3("pykrita", pykritaMethods, "The pykrita module");
  1370. - PyModule_AddStringConstant(s_pykrita, "__file__", __FILE__);
  1371. -#else
  1372. - static struct PyModuleDef moduledef = {
  1373. - PyModuleDef_HEAD_INIT
  1374. - , "pykrita"
  1375. - , "The pykrita module"
  1376. - , -1
  1377. - , pykritaMethods
  1378. - , 0
  1379. - , 0
  1380. - , 0
  1381. - , 0
  1382. - };
  1383. - s_pykrita = PyModule_Create(&moduledef);
  1384. - PyModule_AddStringConstant(s_pykrita, "__file__", __FILE__);
  1385. - return s_pykrita;
  1386. -#endif
  1387. -}
  1388. -//END Python module registration
  1389. -
  1390. -
  1391. -//BEGIN PyKrita::Engine::PluginState
  1392. -PyKrita::Engine::PluginState::PluginState()
  1393. - : m_enabled(false)
  1394. - , m_broken(false)
  1395. - , m_unstable(false)
  1396. - , m_isDir(false)
  1397. -{
  1398. -}
  1399. -//END PyKrita::Engine::PluginState
  1400. -
  1401. -
  1402. -/**
  1403. - * Just initialize some members. The second (most important) part
  1404. - * is to call \c Engine::tryInitializeGetFailureReason()!
  1405. - * W/o that call instance is invalid and using it lead to UB!
  1406. - */
  1407. -PyKrita::Engine::Engine()
  1408. - : m_configuration(0)
  1409. - , m_sessionConfiguration(0)
  1410. - , m_engineIsUsable(false)
  1411. -{
  1412. -}
  1413. -
  1414. -/// \todo More accurate shutdown required:
  1415. -/// need to keep track what exactly was broken on
  1416. -/// initialize attempt...
  1417. -PyKrita::Engine::~Engine()
  1418. -{
  1419. - dbgScript << "Going to destroy the Python engine";
  1420. -
  1421. - // Notify Python that engine going to die
  1422. - {
  1423. - Python py = Python();
  1424. - py.functionCall("_pykritaUnloading");
  1425. - }
  1426. - unloadAllModules();
  1427. -
  1428. - // Clean internal configuration dicts
  1429. - // NOTE Do not need to save anything! It's already done!
  1430. - if (m_configuration) {
  1431. - Py_DECREF(m_configuration);
  1432. - }
  1433. - if (m_sessionConfiguration) {
  1434. - Py_DECREF(m_sessionConfiguration);
  1435. - }
  1436. -
  1437. - Python::maybeFinalize();
  1438. - Python::libraryUnload();
  1439. - s_engine_instance = 0;
  1440. -}
  1441. -
  1442. -void PyKrita::Engine::unloadAllModules()
  1443. -{
  1444. - // Unload all modules
  1445. - for (int i = 0; i < m_plugins.size(); ++i) {
  1446. - if (m_plugins[i].isEnabled() && !m_plugins[i].isBroken()) {
  1447. - unloadModule(i);
  1448. - }
  1449. - }
  1450. -}
  1451. -
  1452. -/**
  1453. - * \todo Make sure noone tries to use uninitialized engine!
  1454. - * (Or enable exceptions for this module, so this case wouldn't even araise?)
  1455. - */
  1456. -QString PyKrita::Engine::tryInitializeGetFailureReason()
  1457. -{
  1458. - dbgScript << "Construct the Python engine for Python" << PY_MAJOR_VERSION << "," << PY_MINOR_VERSION;
  1459. -
  1460. - if (!Python::libraryLoad()) {
  1461. - return i18nc("@info:tooltip ", "Cannot load Python library");
  1462. - }
  1463. -
  1464. - // Update PYTHONPATH
  1465. - // 0) custom plugin directories (prefer local dir over systems')
  1466. - // 1) shipped krita module's dir
  1467. - QStringList pluginDirectories = KoResourcePaths::findDirs("pythonscripts");
  1468. - dbgScript << "Plugin Directories: " << pluginDirectories;
  1469. - if (!Python::setPath(pluginDirectories)) {
  1470. - return i18nc("@info:tooltip ", "Cannot set Python paths");
  1471. - }
  1472. -
  1473. - if (0 != PyImport_AppendInittab(Python::PYKRITA_ENGINE, PYKRITA_INIT)) {
  1474. - return i18nc("@info:tooltip ", "Cannot load built-in <icode>pykrita</icode> module");
  1475. - }
  1476. -
  1477. - Python::ensureInitialized();
  1478. - Python py = Python();
  1479. -
  1480. - PyRun_SimpleString(
  1481. - "import sip\n"
  1482. - "sip.setapi('QDate', 2)\n"
  1483. - "sip.setapi('QTime', 2)\n"
  1484. - "sip.setapi('QDateTime', 2)\n"
  1485. - "sip.setapi('QUrl', 2)\n"
  1486. - "sip.setapi('QTextStream', 2)\n"
  1487. - "sip.setapi('QString', 2)\n"
  1488. - "sip.setapi('QVariant', 2)\n"
  1489. - );
  1490. -
  1491. - // Initialize our built-in module.
  1492. - pythonInitwrapper(this);
  1493. - if (!s_pykrita) {
  1494. - return i18nc("@info:tooltip ", "No <icode>pykrita</icode> built-in module");
  1495. - }
  1496. -
  1497. - // Setup global configuration
  1498. - m_configuration = PyDict_New();
  1499. - /// \todo Check \c m_configuration ?
  1500. - // Host the configuration dictionary.
  1501. - py.itemStringSet("configuration", m_configuration);
  1502. -
  1503. - // Setup per session configuration
  1504. - m_sessionConfiguration = PyDict_New();
  1505. - py.itemStringSet("sessionConfiguration", m_sessionConfiguration);
  1506. -
  1507. - // Initialize 'plugins' dict of module 'pykrita'
  1508. - PyObject* plugins = PyDict_New();
  1509. - py.itemStringSet("plugins", plugins);
  1510. -
  1511. - // Get plugins available
  1512. - scanPlugins();
  1513. -
  1514. - // NOTE Empty failure reson string indicates success!
  1515. - m_engineIsUsable = true;
  1516. - return QString();
  1517. -}
  1518. -
  1519. -int PyKrita::Engine::columnCount(const QModelIndex&) const
  1520. -{
  1521. - return Column::LAST__;
  1522. -}
  1523. -
  1524. -int PyKrita::Engine::rowCount(const QModelIndex&) const
  1525. -{
  1526. - return m_plugins.size();
  1527. -}
  1528. -
  1529. -QModelIndex PyKrita::Engine::index(const int row, const int column, const QModelIndex& parent) const
  1530. -{
  1531. - if (!parent.isValid() && row < m_plugins.size() && column < Column::LAST__)
  1532. - return createIndex(row, column);
  1533. - return QModelIndex();
  1534. -}
  1535. -
  1536. -QModelIndex PyKrita::Engine::parent(const QModelIndex&) const
  1537. -{
  1538. - return QModelIndex();
  1539. -}
  1540. -
  1541. -QVariant PyKrita::Engine::headerData(
  1542. - const int section
  1543. - , const Qt::Orientation orientation
  1544. - , const int role
  1545. -) const
  1546. -{
  1547. - if (role == Qt::DisplayRole && orientation == Qt::Horizontal) {
  1548. - switch (section) {
  1549. - case Column::NAME:
  1550. - return i18nc("@title:column", "Name");
  1551. - case Column::COMMENT:
  1552. - return i18nc("@title:column", "Comment");
  1553. - default:
  1554. - break;
  1555. - }
  1556. - }
  1557. - return QVariant();
  1558. -}
  1559. -
  1560. -QVariant PyKrita::Engine::data(const QModelIndex& index, const int role) const
  1561. -{
  1562. - Q_ASSERT("Sanity check" && index.row() < m_plugins.size());
  1563. - Q_ASSERT("Sanity check" && index.column() < Column::LAST__);
  1564. - switch (role) {
  1565. - case Qt::DisplayRole:
  1566. - switch (index.column()) {
  1567. - case Column::NAME:
  1568. - return m_plugins[index.row()].m_pythonPlugin.name();
  1569. - case Column::COMMENT:
  1570. - return m_plugins[index.row()].m_pythonPlugin.comment();
  1571. - default:
  1572. - break;
  1573. - }
  1574. - break;
  1575. - case Qt::CheckStateRole: {
  1576. - if (index.column() == Column::NAME) {
  1577. - const bool checked = m_plugins[index.row()].isEnabled();
  1578. - return checked ? Qt::Checked : Qt::Unchecked;
  1579. - }
  1580. - break;
  1581. - }
  1582. - case Qt::ToolTipRole:
  1583. - if (!m_plugins[index.row()].m_errorReason.isEmpty())
  1584. - return m_plugins[index.row()].m_errorReason;
  1585. - break;
  1586. - case Qt::ForegroundRole:
  1587. - if (m_plugins[index.row()].isUnstable()) {
  1588. - KColorScheme scheme(QPalette::Inactive, KColorScheme::View);
  1589. - return scheme.foreground(KColorScheme::NegativeText).color();
  1590. - }
  1591. - default:
  1592. - break;
  1593. - }
  1594. - return QVariant();
  1595. -}
  1596. -
  1597. -Qt::ItemFlags PyKrita::Engine::flags(const QModelIndex& index) const
  1598. -{
  1599. - Q_ASSERT("Sanity check" && index.row() < m_plugins.size());
  1600. - Q_ASSERT("Sanity check" && index.column() < Column::LAST__);
  1601. -
  1602. - int result = Qt::ItemIsSelectable;
  1603. - if (index.column() == Column::NAME)
  1604. - result |= Qt::ItemIsUserCheckable;
  1605. - // Disable to select/check broken modules
  1606. - if (!m_plugins[index.row()].isBroken())
  1607. - result |= Qt::ItemIsEnabled;
  1608. - return static_cast<Qt::ItemFlag>(result);
  1609. -}
  1610. -
  1611. -bool PyKrita::Engine::setData(const QModelIndex& index, const QVariant& value, const int role)
  1612. -{
  1613. - Q_ASSERT("Sanity check" && index.row() < m_plugins.size());
  1614. -
  1615. - if (role == Qt::CheckStateRole) {
  1616. - Q_ASSERT("Sanity check" && !m_plugins[index.row()].isBroken());
  1617. -
  1618. - const bool enabled = value.toBool();
  1619. - m_plugins[index.row()].m_enabled = enabled;
  1620. - if (enabled)
  1621. - loadModule(index.row());
  1622. - else
  1623. - unloadModule(index.row());
  1624. - }
  1625. - return true;
  1626. -}
  1627. -
  1628. -QStringList PyKrita::Engine::enabledPlugins() const
  1629. -{
  1630. - /// \todo \c std::transform + lambda or even better to use
  1631. - /// filtered and transformed view from boost
  1632. - QStringList result;
  1633. - Q_FOREACH(const PluginState & plugin, m_plugins)
  1634. - if (plugin.isEnabled()) {
  1635. - result.append(plugin.m_pythonPlugin.name());
  1636. - }
  1637. - return result;
  1638. -}
  1639. -
  1640. -void PyKrita::Engine::readGlobalPluginsConfiguration()
  1641. -{
  1642. - Python py = Python();
  1643. - PyDict_Clear(m_configuration);
  1644. - KConfig config(CONFIG_FILE, KConfig::SimpleConfig);
  1645. - config.sync();
  1646. - py.updateDictionaryFromConfiguration(m_configuration, &config);
  1647. -}
  1648. -
  1649. -void PyKrita::Engine::saveGlobalPluginsConfiguration()
  1650. -{
  1651. - Python py = Python();
  1652. - KConfig config(CONFIG_FILE, KConfig::SimpleConfig);
  1653. - py.updateConfigurationFromDictionary(&config, m_configuration);
  1654. - config.sync();
  1655. -}
  1656. -
  1657. -bool PyKrita::Engine::isPythonPluginUsable(const PyPlugin *pythonPlugin)
  1658. -{
  1659. - dbgScript << "Got Krita/PythonPlugin: " << pythonPlugin->name()
  1660. - << ", module-path=" << pythonPlugin->library()
  1661. - ;
  1662. - // Make sure mandatory properties are here
  1663. - if (pythonPlugin->name().isEmpty()) {
  1664. - dbgScript << "Ignore desktop file w/o a name";
  1665. - return false;
  1666. - }
  1667. - if (pythonPlugin->library().isEmpty()) {
  1668. - dbgScript << "Ignore desktop file w/o a module to import";
  1669. - return false;
  1670. - }
  1671. - return true;
  1672. -}
  1673. -
  1674. -bool PyKrita::Engine::setModuleProperties(PluginState& plugin)
  1675. -{
  1676. - // Find the module:
  1677. - // 0) try to locate directory based plugin first
  1678. - QString rel_path = plugin.moduleFilePathPart();
  1679. - rel_path = rel_path + "/" + "__init__.py";
  1680. - dbgScript << "Finding Pyrhon module with rel_path:" << rel_path;
  1681. -
  1682. - QString module_path = KoResourcePaths::findResource("pythonscripts", rel_path);
  1683. -
  1684. - dbgScript << "module_path:" << module_path;
  1685. -
  1686. - if (module_path.isEmpty()) {
  1687. - // 1) Nothing found, then try file based plugin
  1688. - rel_path = plugin.moduleFilePathPart() + ".py";
  1689. - dbgScript << "Finding Pyrhon module with rel_path:" << rel_path;
  1690. - module_path = KoResourcePaths::findResource("pythonscripts", rel_path);
  1691. - dbgScript << "module_path:" << module_path;
  1692. - } else {
  1693. - plugin.m_isDir = true;
  1694. - }
  1695. -
  1696. - // Is anything found at all?
  1697. - if (module_path.isEmpty()) {
  1698. - plugin.m_broken = true;
  1699. - plugin.m_errorReason = i18nc(
  1700. - "@info:tooltip"
  1701. - , "Unable to find the module specified <application>%1</application>"
  1702. - , plugin.m_pythonPlugin.library()
  1703. - );
  1704. - dbgScript << "Cannot load module:" << plugin.m_errorReason;
  1705. - return false;
  1706. - }
  1707. - dbgScript << "Found module path:" << module_path;
  1708. - return true;
  1709. -}
  1710. -
  1711. -QPair<QString, PyKrita::version_checker> PyKrita::Engine::parseDependency(const QString& d)
  1712. -{
  1713. - // Check if dependency has package info attached
  1714. - const int pnfo = d.indexOf('(');
  1715. - if (pnfo != -1) {
  1716. - QString dependency = d.mid(0, pnfo);
  1717. - QString version_str = d.mid(pnfo + 1, d.size() - pnfo - 2).trimmed();
  1718. - dbgScript << "Desired version spec [" << dependency << "]:" << version_str;
  1719. - version_checker checker = version_checker::fromString(version_str);
  1720. - if (!(checker.isValid() && d.endsWith(')'))) {
  1721. - dbgScript << "Invalid version spec " << d;
  1722. - QString reason = i18nc(
  1723. - "@info:tooltip"
  1724. - , "<p>Specified version has invalid format for dependency <application>%1</application>: "
  1725. - "<icode>%2</icode>. Skipped</p>"
  1726. - , dependency
  1727. - , version_str
  1728. - );
  1729. - return qMakePair(reason, version_checker());
  1730. - }
  1731. - return qMakePair(dependency, checker);
  1732. - }
  1733. - return qMakePair(d, version_checker(version_checker::undefined));
  1734. -}
  1735. -
  1736. -PyKrita::version PyKrita::Engine::tryObtainVersionFromTuple(PyObject* version_obj)
  1737. -{
  1738. - Q_ASSERT("Sanity check" && version_obj);
  1739. -
  1740. - if (PyTuple_Check(version_obj) == 0)
  1741. - return version::invalid();
  1742. -
  1743. - int version_info[3] = {0, 0, 0};
  1744. - for (unsigned i = 0; i < PyTuple_Size(version_obj); ++i) {
  1745. - PyObject* v = PyTuple_GetItem(version_obj, i);
  1746. - if (v && PyLong_Check(v))
  1747. - version_info[i] = PyLong_AsLong(v);
  1748. - else
  1749. - version_info[i] = -1;
  1750. - }
  1751. - if (version_info[0] != -1 && version_info[1] != -1 && version_info[2] != -1)
  1752. - return version(version_info[0], version_info[1], version_info[2]);
  1753. -
  1754. - return version::invalid();
  1755. -}
  1756. -
  1757. -/**
  1758. - * Try to parse version string as a simple triplet X.Y.Z.
  1759. - *
  1760. - * \todo Some modules has letters in a version string...
  1761. - * For example current \c pytz version is \e "2013d".
  1762. - */
  1763. -PyKrita::version PyKrita::Engine::tryObtainVersionFromString(PyObject* version_obj)
  1764. -{
  1765. - Q_ASSERT("Sanity check" && version_obj);
  1766. -
  1767. - if (!Python::isUnicode(version_obj))
  1768. - return version::invalid();
  1769. -
  1770. - QString version_str = Python::unicode(version_obj);
  1771. - if (version_str.isEmpty())
  1772. - return version::invalid();
  1773. -
  1774. - return version::fromString(version_str);
  1775. -}
  1776. -
  1777. -/**
  1778. - * Collect dependencies and check them. To do it
  1779. - * just try to import a module... when unload it ;)
  1780. - *
  1781. - * \c X-Python-Dependencies property of \c .desktop file has the following format:
  1782. - * <tt>python-module(version-info)</tt>, where <tt>python-module</tt>
  1783. - * a python module name to be imported, <tt>version-spec</tt>
  1784. - * is a version triplet delimited by dots, possible w/ leading compare
  1785. - * operator: \c =, \c <, \c >, \c <=, \c >=
  1786. - */
  1787. -void PyKrita::Engine::verifyDependenciesSetStatus(PluginState& plugin)
  1788. -{
  1789. - QStringList dependencies = plugin.m_pythonPlugin.property("X-Python-Dependencies").toStringList();
  1790. -#if PY_MAJOR_VERSION < 3
  1791. - {
  1792. - // Try to get Py2 only dependencies
  1793. - QStringList py2_dependencies = plugin.m_service->property("X-Python-2-Dependencies").toStringList();
  1794. - dependencies.append(py2_dependencies);
  1795. - }
  1796. -#endif
  1797. -
  1798. - Python py = Python();
  1799. - QString reason = i18nc("@info:tooltip", "<title>Dependency check</title>");
  1800. - Q_FOREACH(const QString & d, dependencies) {
  1801. - QPair<QString, version_checker> info_pair = parseDependency(d);
  1802. - version_checker& checker = info_pair.second;
  1803. - if (!checker.isValid()) {
  1804. - plugin.m_broken = true;
  1805. - reason += info_pair.first;
  1806. - continue;
  1807. - }
  1808. -
  1809. - dbgScript << "Try to import dependency module/package:" << d;
  1810. -
  1811. - // Try to import a module
  1812. - const QString& dependency = info_pair.first;
  1813. - PyObject* module = py.moduleImport(PQ(dependency));
  1814. - if (module) {
  1815. - if (checker.isEmpty()) { // Need to check smth?
  1816. - dbgScript << "No version to check, just make sure it's loaded:" << dependency;
  1817. - Py_DECREF(module);
  1818. - continue;
  1819. - }
  1820. - // Try to get __version__ from module
  1821. - // See PEP396: http://www.python.org/dev/peps/pep-0396/
  1822. - PyObject* version_obj = py.itemString("__version__", PQ(dependency));
  1823. - if (!version_obj) {
  1824. - dbgScript << "No __version__ for " << dependency
  1825. - << "[" << plugin.m_pythonPlugin.name() << "]:\n" << py.lastTraceback()
  1826. - ;
  1827. - plugin.m_unstable = true;
  1828. - reason += i18nc(
  1829. - "@info:tooltip"
  1830. - , "<p>Failed to check version of dependency <application>%1</application>: "
  1831. - "Module do not have PEP396 <code>__version__</code> attribute. "
  1832. - "It is not disabled, but behaviour is unpredictable...</p>"
  1833. - , dependency
  1834. - );
  1835. - }
  1836. - // PEP396 require __version__ to tuple of integers... try it!
  1837. - version dep_version = tryObtainVersionFromTuple(version_obj);
  1838. - if (!dep_version.isValid())
  1839. - // Second attempt: some "bad" modules have it as a string
  1840. - dep_version = tryObtainVersionFromString(version_obj);
  1841. -
  1842. - // Did we get it?
  1843. - if (!dep_version.isValid()) {
  1844. - // Dunno what is this... Giving up!
  1845. - dbgScript << "***: Can't parse module version for" << dependency;
  1846. - plugin.m_unstable = true;
  1847. - reason += i18nc(
  1848. - "@info:tooltip"
  1849. - , "<p><application>%1</application>: Unexpected module's version format"
  1850. - , dependency
  1851. - );
  1852. - } else if (!checker(dep_version)) {
  1853. - dbgScript << "Version requirement check failed ["
  1854. - << plugin.m_pythonPlugin.name() << "] for "
  1855. - << dependency << ": wanted " << checker.operationToString()
  1856. - << QString(checker.required())
  1857. - << ", but found" << QString(dep_version)
  1858. - ;
  1859. - plugin.m_broken = true;
  1860. - reason += i18nc(
  1861. - "@info:tooltip"
  1862. - , "<p><application>%1</application>: No suitable version found. "
  1863. - "Required version %2 %3, but found %4</p>"
  1864. - , dependency
  1865. - , checker.operationToString()
  1866. - , QString(checker.required())
  1867. - , QString(dep_version)
  1868. - );
  1869. - }
  1870. - // Do not need this module anymore...
  1871. - Py_DECREF(module);
  1872. - } else {
  1873. - dbgScript << "Load failure [" << plugin.m_pythonPlugin.name() << "]:\n" << py.lastTraceback();
  1874. - plugin.m_broken = true;
  1875. - reason += i18nc(
  1876. - "@info:tooltip"
  1877. - , "<p>Failure on module load <application>%1</application>:</p><pre>%2</pre>"
  1878. - , dependency
  1879. - , py.lastTraceback()
  1880. - );
  1881. - }
  1882. - }
  1883. -
  1884. - if (plugin.isBroken() || plugin.isUnstable()) {
  1885. - plugin.m_errorReason = reason;
  1886. - }
  1887. -}
  1888. -
  1889. -void PyKrita::Engine::scanPlugins()
  1890. -{
  1891. - m_plugins.clear(); // Clear current state.
  1892. -
  1893. - QStringList desktopFiles = KoResourcePaths::findAllResources("data", "pykrita/*desktop");
  1894. - qDebug() << desktopFiles;
  1895. -
  1896. - Q_FOREACH(const QString &desktopFile, desktopFiles) {
  1897. -
  1898. - QSettings s(desktopFile, QSettings::IniFormat);
  1899. - s.beginGroup("Desktop Entry");
  1900. - if (s.value("ServiceTypes").toString() == "Krita/PythonPlugin") {
  1901. - PyPlugin pyplugin;
  1902. - pyplugin.m_comment = s.value("Comment").toString();
  1903. - pyplugin.m_name = s.value("Name").toString();
  1904. - pyplugin.m_libraryPath = s.value("X-KDE-Library").toString();
  1905. - pyplugin.m_properties["X-Python-2-Compatible"] = s.value("X-Python-2-Compatible", false).toBool();
  1906. -
  1907. - if (!isPythonPluginUsable(&pyplugin)) {
  1908. - dbgScript << pyplugin.name() << "is not usable";
  1909. - continue;
  1910. - }
  1911. -
  1912. - PluginState pluginState;
  1913. - pluginState.m_pythonPlugin = pyplugin;
  1914. -
  1915. - if (!setModuleProperties(pluginState)) {
  1916. - dbgScript << "Cannot load" << pyplugin.name() << ": broken"
  1917. - << pluginState.isBroken()
  1918. - << "because:" << pluginState.errorReason();
  1919. - continue;
  1920. - }
  1921. -
  1922. - verifyDependenciesSetStatus(pluginState);
  1923. - m_plugins.append(pluginState);
  1924. - }
  1925. - }
  1926. -}
  1927. -
  1928. -void PyKrita::Engine::setEnabledPlugins(const QStringList& enabled_plugins)
  1929. -{
  1930. - for (int i = 0; i < m_plugins.size(); ++i) {
  1931. - m_plugins[i].m_enabled = enabled_plugins.indexOf(m_plugins[i].m_pythonPlugin.name()) != -1;
  1932. - }
  1933. -}
  1934. -
  1935. -void PyKrita::Engine::tryLoadEnabledPlugins()
  1936. -{
  1937. - for (int i = 0; i < m_plugins.size(); ++i) {
  1938. - dbgScript << "Trying to load plugin" << m_plugins[i].pythonModuleName()
  1939. - << ". Enabled:" << m_plugins[i].isEnabled()
  1940. - << ". Broken: " << m_plugins[i].isBroken();
  1941. - if (!m_plugins[i].isBroken()) {
  1942. - m_plugins[i].m_enabled = true;
  1943. - loadModule(i);
  1944. - }
  1945. - }
  1946. -}
  1947. -
  1948. -void PyKrita::Engine::loadModule(const int idx)
  1949. -{
  1950. - Q_ASSERT("Plugin index is out of range!" && 0 <= idx && idx < m_plugins.size());
  1951. - PluginState& plugin = m_plugins[idx];
  1952. - Q_ASSERT(
  1953. - "Why to call loadModule() for disabled/broken plugin?"
  1954. - && plugin.isEnabled()
  1955. - && !plugin.isBroken()
  1956. - );
  1957. -
  1958. - QString module_name = plugin.pythonModuleName();
  1959. - dbgScript << "Loading module: " << module_name;
  1960. -
  1961. - Python py = Python();
  1962. -
  1963. - // Get 'plugins' key from 'pykrita' module dictionary.
  1964. - // Every entry has a module name as a key and 2 elements tuple as a value
  1965. - PyObject* plugins = py.itemString("plugins");
  1966. - Q_ASSERT(
  1967. - "'plugins' dict expected to be alive, otherwise code review required!"
  1968. - && plugins
  1969. - );
  1970. -
  1971. - PyObject* module = py.moduleImport(PQ(module_name));
  1972. - if (module) {
  1973. - // Move just loaded module to the dict
  1974. - const int ins_result = PyDict_SetItemString(plugins, PQ(module_name), module);
  1975. - Q_ASSERT("expected successful insertion" && ins_result == 0);
  1976. - Py_DECREF(module);
  1977. - // Handle failure in release mode.
  1978. - if (ins_result == 0) {
  1979. - // Initialize the module from Python's side
  1980. - PyObject* const args = Py_BuildValue("(s)", PQ(module_name));
  1981. - PyObject* result = py.functionCall("_pluginLoaded", Python::PYKRITA_ENGINE, args);
  1982. - Py_DECREF(args);
  1983. - if (result) {
  1984. - dbgScript << "\t" << "success!";
  1985. - return;
  1986. - }
  1987. - }
  1988. - plugin.m_errorReason = i18nc("@info:tooltip", "Internal engine failure");
  1989. - } else {
  1990. - plugin.m_errorReason = i18nc(
  1991. - "@info:tooltip"
  1992. - , "Module not loaded:<br/>%1"
  1993. - , py.lastTraceback().replace("\n", "<br/>")
  1994. - );
  1995. - }
  1996. - plugin.m_broken = true;
  1997. - warnScript << "Error loading plugin" << module_name;
  1998. -}
  1999. -
  2000. -void PyKrita::Engine::unloadModule(int idx)
  2001. -{
  2002. - Q_ASSERT("Plugin index is out of range!" && 0 <= idx && idx < m_plugins.size());
  2003. - PluginState& plugin = m_plugins[idx];
  2004. - Q_ASSERT("Why to call unloadModule() for broken plugin?" && !plugin.isBroken());
  2005. -
  2006. - dbgScript << "Unloading module: " << plugin.pythonModuleName();
  2007. -
  2008. - Python py = Python();
  2009. -
  2010. - // Get 'plugins' key from 'pykrita' module dictionary
  2011. - PyObject* plugins = py.itemString("plugins");
  2012. - Q_ASSERT(
  2013. - "'plugins' dict expected to be alive, otherwise code review required!"
  2014. - && plugins
  2015. - );
  2016. -
  2017. - PyObject* const args = Py_BuildValue("(s)", PQ(plugin.pythonModuleName()));
  2018. - py.functionCall("_pluginUnloading", Python::PYKRITA_ENGINE, args);
  2019. - Py_DECREF(args);
  2020. -
  2021. - // This will just decrement a reference count for module instance
  2022. - PyDict_DelItemString(plugins, PQ(plugin.pythonModuleName()));
  2023. -
  2024. - // Remove the module also from 'sys.modules' dict to really unload it,
  2025. - // so if reloaded all @init actions will work again!
  2026. - PyObject* sys_modules = py.itemString("modules", "sys");
  2027. - Q_ASSERT("Sanity check" && sys_modules);
  2028. - PyDict_DelItemString(sys_modules, PQ(plugin.pythonModuleName()));
  2029. -}
  2030. -
  2031. -// krita: space-indent on; indent-width 4;
  2032. -#undef PYKRITA_INIT
  2033. diff --git a/plugins/extensions/pykrita/plugin/plugin.h b/plugins/extensions/pykrita/plugin/plugin.h
  2034. --- a/plugins/extensions/pykrita/plugin/plugin.h
  2035. +++ b/plugins/extensions/pykrita/plugin/plugin.h
  2036. @@ -25,7 +25,7 @@
  2037. #include <QObject>
  2038.  
  2039. #include <kis_view_plugin.h>
  2040. -#include "engine.h"
  2041. +#include "PythonPluginManager.h"
  2042.  
  2043. class KritaPyQtPlugin : public KisViewPlugin
  2044. {
  2045. @@ -34,8 +34,7 @@
  2046. KritaPyQtPlugin(QObject *parent, const QVariantList &);
  2047. virtual ~KritaPyQtPlugin();
  2048. private:
  2049. - PyKrita::Engine m_engine;
  2050. - QString m_engineFailureReason;
  2051. + PythonPluginManager *pluginManager;
  2052. bool m_autoReload;
  2053. };
  2054.  
  2055. diff --git a/plugins/extensions/pykrita/plugin/plugin.cpp b/plugins/extensions/pykrita/plugin/plugin.cpp
  2056. --- a/plugins/extensions/pykrita/plugin/plugin.cpp
  2057. +++ b/plugins/extensions/pykrita/plugin/plugin.cpp
  2058. @@ -16,40 +16,52 @@
  2059. */
  2060.  
  2061. #include "plugin.h"
  2062. -#include "engine.h"
  2063. -#include "utilities.h"
  2064.  
  2065. #include <klocalizedstring.h>
  2066. #include <kis_debug.h>
  2067. #include <kpluginfactory.h>
  2068. -#include <KoResourcePaths.h>
  2069.  
  2070. #include <kis_preference_set_registry.h>
  2071. #include "pyqtpluginsettings.h"
  2072.  
  2073. #include <Krita.h>
  2074. -#include <Extension.h>
  2075.  
  2076. K_PLUGIN_FACTORY_WITH_JSON(KritaPyQtPluginFactory, "kritapykrita.json", registerPlugin<KritaPyQtPlugin>();)
  2077.  
  2078. KritaPyQtPlugin::KritaPyQtPlugin(QObject *parent, const QVariantList &)
  2079. : KisViewPlugin(parent)
  2080. - , m_engineFailureReason(m_engine.tryInitializeGetFailureReason())
  2081. , m_autoReload(false)
  2082. {
  2083. -
  2084. qDebug() << "Loading Python plugin";
  2085.  
  2086. - KisPreferenceSetRegistry *preferenceSetRegistry = KisPreferenceSetRegistry::instance();
  2087. - PyQtPluginSettingsFactory* settingsFactory = new PyQtPluginSettingsFactory(&m_engine);
  2088. + PyKrita::InitResult initResult = PyKrita::initialize();
  2089. + switch (initResult) {
  2090. + case PyKrita::INIT_OK:
  2091. + break;
  2092. + case PyKrita::INIT_CANNOT_LOAD_PYTHON_LIBRARY:
  2093. + qWarning() << i18n("Cannot load Python library");
  2094. + return;
  2095. + case PyKrita::INIT_CANNOT_SET_PYTHON_PATHS:
  2096. + qWarning() << i18n("Cannot set Python paths");
  2097. + return;
  2098. + case PyKrita::INIT_CANNOT_LOAD_PYKRITA_MODULE:
  2099. + qWarning() << i18n("Cannot load built-in pykrita module");
  2100. + return;
  2101. + default:
  2102. + qWarning() << i18n("Unexpected error initializing python plugin.");
  2103. + return;
  2104. + }
  2105.  
  2106. + pluginManager = PyKrita::pluginManager();
  2107.  
  2108. + KisPreferenceSetRegistry *preferenceSetRegistry = KisPreferenceSetRegistry::instance();
  2109. + PyQtPluginSettingsFactory* settingsFactory = new PyQtPluginSettingsFactory(pluginManager);
  2110.  
  2111. //load and save preferences
  2112. //if something in kritarc is missing, then the default from this load function will be used and saved back to kconfig.
  2113. //this way, cfg.readEntry() in any part won't be able to set its own default
  2114. KisPreferenceSet* settings = settingsFactory->createPreferenceSet();
  2115. - Q_ASSERT(settings);
  2116. + KIS_SAFE_ASSERT_RECOVER_RETURN(settings);
  2117. settings->loadPreferences();
  2118. settings->savePreferences();
  2119. delete settings;
  2120. @@ -61,17 +73,16 @@
  2121. PyObject* pykritaPackage = py.moduleImport("pykrita");
  2122. pykritaPackage = py.moduleImport("krita");
  2123.  
  2124. - if (pykritaPackage) {
  2125. + if (pykritaPackage) {
  2126. dbgScript << "Loaded pykrita, now load plugins";
  2127. - m_engine.tryLoadEnabledPlugins();
  2128. + pluginManager->scanPlugins();
  2129. + pluginManager->tryLoadEnabledPlugins();
  2130. //py.functionCall("_pykritaLoaded", PyKrita::Python::PYKRITA_ENGINE);
  2131. - }
  2132. - else {
  2133. + } else {
  2134. dbgScript << "Cannot load pykrita module";
  2135. - m_engine.setBroken();
  2136. }
  2137. - Q_FOREACH (Extension* ext, Krita::instance()->extensions())
  2138. - {
  2139. +
  2140. + Q_FOREACH (Extension* ext, Krita::instance()->extensions()) {
  2141. ext->setup();
  2142. }
  2143. }
  2144. diff --git a/plugins/extensions/pykrita/plugin/pyqtpluginsettings.h b/plugins/extensions/pykrita/plugin/pyqtpluginsettings.h
  2145. --- a/plugins/extensions/pykrita/plugin/pyqtpluginsettings.h
  2146. +++ b/plugins/extensions/pykrita/plugin/pyqtpluginsettings.h
  2147. @@ -18,7 +18,6 @@
  2148. #define PYQTPLUGINSETTINGS_H
  2149.  
  2150. #include "kis_preference_set_registry.h"
  2151. -#include "engine.h"
  2152.  
  2153. namespace Ui
  2154. {
  2155. @@ -26,13 +25,14 @@
  2156. }
  2157.  
  2158. class QIcon;
  2159. +class PythonPluginManager;
  2160.  
  2161. class PyQtPluginSettings : public KisPreferenceSet
  2162. {
  2163. Q_OBJECT
  2164. public:
  2165.  
  2166. - PyQtPluginSettings(PyKrita::Engine *engine, QWidget *parent = 0);
  2167. + PyQtPluginSettings(PythonPluginManager *pluginManager, QWidget *parent = 0);
  2168. ~PyQtPluginSettings();
  2169.  
  2170. virtual QString id();
  2171. @@ -71,12 +71,12 @@
  2172. {
  2173. public:
  2174.  
  2175. - PyQtPluginSettingsFactory(PyKrita::Engine *engine) {
  2176. - m_engine = engine;
  2177. + PyQtPluginSettingsFactory(PythonPluginManager *engine) {
  2178. + m_pluginManager = engine;
  2179. }
  2180.  
  2181. KisPreferenceSet* createPreferenceSet() {
  2182. - PyQtPluginSettings* ps = new PyQtPluginSettings(m_engine);
  2183. + PyQtPluginSettings* ps = new PyQtPluginSettings(m_pluginManager);
  2184. QObject::connect(ps, SIGNAL(settingsChanged()), &repeater, SLOT(updateSettings()), Qt::UniqueConnection);
  2185. return ps;
  2186. }
  2187. @@ -84,7 +84,7 @@
  2188. return "PyQtSettings";
  2189. }
  2190. PyQtPluginSettingsUpdateRepeater repeater;
  2191. - PyKrita::Engine *m_engine;
  2192. + PythonPluginManager *m_pluginManager;
  2193. };
  2194.  
  2195.  
  2196. diff --git a/plugins/extensions/pykrita/plugin/pyqtpluginsettings.cpp b/plugins/extensions/pykrita/plugin/pyqtpluginsettings.cpp
  2197. --- a/plugins/extensions/pykrita/plugin/pyqtpluginsettings.cpp
  2198. +++ b/plugins/extensions/pykrita/plugin/pyqtpluginsettings.cpp
  2199. @@ -26,23 +26,23 @@
  2200.  
  2201. #include <KoIcon.h>
  2202.  
  2203. -
  2204. #include "kis_config.h"
  2205. +#include "PythonPluginManager.h"
  2206.  
  2207.  
  2208. -PyQtPluginSettings::PyQtPluginSettings(PyKrita::Engine *engine, QWidget *parent) :
  2209. +PyQtPluginSettings::PyQtPluginSettings(PythonPluginManager *pluginManager, QWidget *parent) :
  2210. KisPreferenceSet(parent),
  2211. m_manager(new Ui::ManagerPage)
  2212. {
  2213. m_manager->setupUi(this);
  2214.  
  2215. QSortFilterProxyModel* const proxy_model = new QSortFilterProxyModel(this);
  2216. - proxy_model->setSourceModel(engine);
  2217. + proxy_model->setSourceModel(pluginManager->model());
  2218. m_manager->pluginsList->setModel(proxy_model);
  2219. m_manager->pluginsList->resizeColumnToContents(0);
  2220. m_manager->pluginsList->sortByColumn(0, Qt::AscendingOrder);
  2221.  
  2222. - const bool is_enabled = bool(engine);
  2223. + const bool is_enabled = bool(pluginManager);
  2224. const bool is_visible = !is_enabled;
  2225. m_manager->errorLabel->setVisible(is_visible);
  2226. m_manager->pluginsList->setEnabled(is_enabled);
  2227. diff --git a/plugins/extensions/pykrita/plugin/utilities.h b/plugins/extensions/pykrita/plugin/utilities.h
  2228. --- a/plugins/extensions/pykrita/plugin/utilities.h
  2229. +++ b/plugins/extensions/pykrita/plugin/utilities.h
  2230. @@ -34,8 +34,31 @@
  2231. /// Save us some ruddy time when printing out QStrings with UTF-8
  2232. # define PQ(x) x.toUtf8().constData()
  2233.  
  2234. +class PythonPluginManager;
  2235. +
  2236. namespace PyKrita
  2237. {
  2238. + enum InitResult {
  2239. + INIT_UNINITIALIZED,
  2240. + INIT_OK,
  2241. + INIT_CANNOT_LOAD_PYTHON_LIBRARY,
  2242. + INIT_CANNOT_SET_PYTHON_PATHS,
  2243. + INIT_CANNOT_LOAD_PYKRITA_MODULE,
  2244. + };
  2245. +
  2246. + /**
  2247. + * Initialize the Python environment and plugin manager.
  2248. + * This should be called first before using the manager
  2249. + * or the Python class.
  2250. + */
  2251. + InitResult initialize();
  2252. +
  2253. + /**
  2254. + * Gets the instance of the plugin manager.
  2255. + * Note: PyKrita::initialize() must be called
  2256. + * before using this function.
  2257. + */
  2258. + PythonPluginManager *pluginManager();
  2259.  
  2260. /**
  2261. * Instantiate this class on the stack to automatically get and release the
  2262. @@ -115,18 +138,6 @@
  2263. QString lastTraceback(void) const;
  2264.  
  2265. /**
  2266. - * Create a Python dictionary from a KConfigBase instance, writing the
  2267. - * string representation of the values.
  2268. - */
  2269. - void updateDictionaryFromConfiguration(PyObject* dictionary, const KConfigBase* config);
  2270. -
  2271. - /**
  2272. - * Write a Python dictionary to a configuration object, converting objects
  2273. - * to their string representation along the way.
  2274. - */
  2275. - void updateConfigurationFromDictionary(KConfigBase* config, PyObject* dictionary);
  2276. -
  2277. - /**
  2278. * Call the named module's named entry point.
  2279. */
  2280. bool functionCall(const char* functionName, const char* moduleName = PYKRITA_ENGINE);
  2281. diff --git a/plugins/extensions/pykrita/plugin/utilities.cpp b/plugins/extensions/pykrita/plugin/utilities.cpp
  2282. --- a/plugins/extensions/pykrita/plugin/utilities.cpp
  2283. +++ b/plugins/extensions/pykrita/plugin/utilities.cpp
  2284. @@ -25,13 +25,13 @@
  2285.  
  2286. #include "config.h"
  2287. #include "utilities.h"
  2288. +#include "PythonPluginManager.h"
  2289.  
  2290. #include <algorithm>
  2291.  
  2292. #include <cmath>
  2293. #include <Python.h>
  2294.  
  2295. -
  2296. #include <QDir>
  2297. #include <QLibrary>
  2298. #include <QString>
  2299. @@ -45,11 +45,82 @@
  2300.  
  2301. #include <kis_debug.h>
  2302.  
  2303. +#include "PykritaModule.h"
  2304. +
  2305. #define THREADED 1
  2306.  
  2307. namespace PyKrita
  2308. {
  2309. -namespace
  2310. + static InitResult initStatus = INIT_UNINITIALIZED;
  2311. + static QScopedPointer<PythonPluginManager> pluginManagerInstance;
  2312. +
  2313. + InitResult initialize()
  2314. + {
  2315. + // Already initialized?
  2316. + if (initStatus == INIT_OK) return INIT_OK;
  2317. +
  2318. + dbgScript << "Initializing Python plugin for Python" << PY_MAJOR_VERSION << "," << PY_MINOR_VERSION;
  2319. +
  2320. + if (!Python::libraryLoad()) {
  2321. + return INIT_CANNOT_LOAD_PYTHON_LIBRARY;
  2322. + }
  2323. +
  2324. + // Update PYTHONPATH
  2325. + // 0) custom plugin directories (prefer local dir over systems')
  2326. + // 1) shipped krita module's dir
  2327. + QStringList pluginDirectories = KoResourcePaths::findDirs("pythonscripts");
  2328. + dbgScript << "Plugin Directories: " << pluginDirectories;
  2329. + if (!Python::setPath(pluginDirectories)) {
  2330. + initStatus = INIT_CANNOT_SET_PYTHON_PATHS;
  2331. + return initStatus;
  2332. + }
  2333. +
  2334. + if (0 != PyImport_AppendInittab(Python::PYKRITA_ENGINE, PyInit_pykrita)) {
  2335. + initStatus = INIT_CANNOT_LOAD_PYKRITA_MODULE;
  2336. + return initStatus;
  2337. + }
  2338. +
  2339. + Python::ensureInitialized();
  2340. + Python py = Python();
  2341. +
  2342. + PyRun_SimpleString(
  2343. + "import sip\n"
  2344. + "sip.setapi('QDate', 2)\n"
  2345. + "sip.setapi('QTime', 2)\n"
  2346. + "sip.setapi('QDateTime', 2)\n"
  2347. + "sip.setapi('QUrl', 2)\n"
  2348. + "sip.setapi('QTextStream', 2)\n"
  2349. + "sip.setapi('QString', 2)\n"
  2350. + "sip.setapi('QVariant', 2)\n"
  2351. + );
  2352. +
  2353. + // Initialize 'plugins' dict of module 'pykrita'
  2354. + PyObject* plugins = PyDict_New();
  2355. + py.itemStringSet("plugins", plugins);
  2356. +
  2357. + pluginManagerInstance.reset(new PythonPluginManager());
  2358. +
  2359. + // Initialize our built-in module.
  2360. + auto pykritaModule = PyInit_pykrita();
  2361. +
  2362. + if (!pykritaModule) {
  2363. + initStatus = INIT_CANNOT_LOAD_PYKRITA_MODULE;
  2364. + return initStatus;
  2365. + //return i18nc("@info:tooltip ", "No <icode>pykrita</icode> built-in module");
  2366. + }
  2367. +
  2368. + initStatus = INIT_OK;
  2369. + return initStatus;
  2370. + }
  2371. +
  2372. + PythonPluginManager *pluginManager()
  2373. + {
  2374. + auto pluginManager = pluginManagerInstance.data();
  2375. + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(pluginManager, nullptr);
  2376. + return pluginManager;
  2377. + }
  2378. +
  2379. + namespace
  2380. {
  2381. #ifndef Q_OS_WIN
  2382. QLibrary* s_pythonLibrary = 0;
  2383. @@ -500,75 +571,6 @@
  2384. #endif
  2385. }
  2386.  
  2387. -void Python::updateConfigurationFromDictionary(KConfigBase* const config, PyObject* const dictionary)
  2388. -{
  2389. - PyObject* groupKey;
  2390. - PyObject* groupDictionary;
  2391. - Py_ssize_t position = 0;
  2392. - while (PyDict_Next(dictionary, &position, &groupKey, &groupDictionary)) {
  2393. - if (!isUnicode(groupKey)) {
  2394. - traceback(QString("Configuration group name not a string"));
  2395. - continue;
  2396. - }
  2397. - QString groupName = unicode(groupKey);
  2398. - if (!PyDict_Check(groupDictionary)) {
  2399. - traceback(QString("Configuration group %1 top level key not a dictionary").arg(groupName));
  2400. - continue;
  2401. - }
  2402. -
  2403. - // There is a group per module.
  2404. - KConfigGroup group = config->group(groupName);
  2405. - PyObject* key;
  2406. - PyObject* value;
  2407. - Py_ssize_t x = 0;
  2408. - while (PyDict_Next(groupDictionary, &x, &key, &value)) {
  2409. - if (!isUnicode(key)) {
  2410. - traceback(QString("Configuration group %1 itemKey not a string").arg(groupName));
  2411. - continue;
  2412. - }
  2413. - PyObject* arguments = Py_BuildValue("(Oi)", value, 0);
  2414. - PyObject* pickled = functionCall("dumps", "pickle", arguments);
  2415. - if (pickled) {
  2416. -#if PY_MAJOR_VERSION < 3
  2417. - QString ascii(unicode(pickled));
  2418. -#else
  2419. - QString ascii(PyBytes_AsString(pickled));
  2420. -#endif
  2421. - group.writeEntry(unicode(key), ascii);
  2422. - Py_DECREF(pickled);
  2423. - } else {
  2424. - errScript << "Cannot write" << groupName << unicode(key) << unicode(PyObject_Str(value));
  2425. - }
  2426. - }
  2427. - }
  2428. -}
  2429. -
  2430. -void Python::updateDictionaryFromConfiguration(PyObject* const dictionary, const KConfigBase* const config)
  2431. -{
  2432. - qDebug() << config->groupList();
  2433. - Q_FOREACH(QString groupName, config->groupList()) {
  2434. - KConfigGroup group = config->group(groupName);
  2435. - PyObject* groupDictionary = PyDict_New();
  2436. - PyDict_SetItemString(dictionary, PQ(groupName), groupDictionary);
  2437. - Q_FOREACH(QString key, group.keyList()) {
  2438. - QString pickled = group.readEntry(key);
  2439. -#if PY_MAJOR_VERSION < 3
  2440. - PyObject* arguments = Py_BuildValue("(s)", PQ(pickled));
  2441. -#else
  2442. - PyObject* arguments = Py_BuildValue("(y)", PQ(pickled));
  2443. -#endif
  2444. - PyObject* value = functionCall("loads", "pickle", arguments);
  2445. - if (value) {
  2446. - PyDict_SetItemString(groupDictionary, PQ(key), value);
  2447. - Py_DECREF(value);
  2448. - } else {
  2449. - errScript << "Cannot read" << groupName << key << pickled;
  2450. - }
  2451. - }
  2452. - Py_DECREF(groupDictionary);
  2453. - }
  2454. -}
  2455. -
  2456. bool Python::prependPythonPaths(const QString& path)
  2457. {
  2458. PyObject* sys_path = itemString("path", "sys");
  2459. diff --git a/plugins/extensions/pykrita/plugin/version_checker.h b/plugins/extensions/pykrita/plugin/version_checker.h
  2460. --- a/plugins/extensions/pykrita/plugin/version_checker.h
  2461. +++ b/plugins/extensions/pykrita/plugin/version_checker.h
  2462. @@ -20,6 +20,8 @@
  2463. #ifndef __VERSION_CHECKER_H__
  2464. # define __VERSION_CHECKER_H__
  2465.  
  2466. +#include <libs/global/kis_debug.h>
  2467. +#include "utilities.h"
  2468. # include <QtCore/QString>
  2469. # include <QtCore/QStringList>
  2470. # include <QtCore/QtGlobal>
  2471. @@ -33,17 +35,13 @@
  2472. class version
  2473. {
  2474. enum type {
  2475. - undefined = -1
  2476. - , zero = 0
  2477. + undefined = -1,
  2478. + zero = 0
  2479. };
  2480.  
  2481. public:
  2482. /// Default constructor
  2483. - explicit version(
  2484. - const int major = zero
  2485. - , const int minor = zero
  2486. - , const int patch = zero
  2487. - )
  2488. + explicit version(const int major = zero, const int minor = zero, const int patch = zero)
  2489. : m_major(major)
  2490. , m_minor(minor)
  2491. , m_patch(patch) {
  2492. @@ -87,6 +85,17 @@
  2493. return version(tmp[0], tmp[1], tmp[2]);
  2494. };
  2495.  
  2496. + static version fromPythonObject(PyObject* version_obj)
  2497. + {
  2498. + version v = tryObtainVersionFromTuple(version_obj);
  2499. + if (!v.isValid()) {
  2500. + // PEP396 requires __version__ to be a tuple of integers,
  2501. + // but some modules use a string instead.
  2502. + v = tryObtainVersionFromString(version_obj);
  2503. + }
  2504. + return v;
  2505. + }
  2506. +
  2507. static version invalid() {
  2508. static version s_bad(undefined, undefined, undefined);
  2509. return s_bad;
  2510. @@ -96,6 +105,50 @@
  2511. int m_major;
  2512. int m_minor;
  2513. int m_patch;
  2514. +
  2515. +
  2516. + static version tryObtainVersionFromTuple(PyObject* version_obj)
  2517. + {
  2518. + Q_ASSERT("Sanity check" && version_obj);
  2519. +
  2520. + if (PyTuple_Check(version_obj) == 0)
  2521. + return version::invalid();
  2522. +
  2523. + int version_info[3] = {0, 0, 0};
  2524. + for (unsigned i = 0; i < PyTuple_Size(version_obj); ++i) {
  2525. + PyObject* v = PyTuple_GetItem(version_obj, i);
  2526. + if (v && PyLong_Check(v))
  2527. + version_info[i] = PyLong_AsLong(v);
  2528. + else
  2529. + version_info[i] = -1;
  2530. + }
  2531. + if (version_info[0] != -1 && version_info[1] != -1 && version_info[2] != -1)
  2532. + return ::PyKrita::version(version_info[0], version_info[1], version_info[2]);
  2533. +
  2534. + return version::invalid();
  2535. + }
  2536. +
  2537. +/**
  2538. + * Try to parse version string as a simple triplet X.Y.Z.
  2539. + *
  2540. + * \todo Some modules has letters in a version string...
  2541. + * For example current \c pytz version is \e "2013d".
  2542. + */
  2543. + static version tryObtainVersionFromString(PyObject* version_obj)
  2544. + {
  2545. + Q_ASSERT("Sanity check" && version_obj);
  2546. +
  2547. + if (!Python::isUnicode(version_obj))
  2548. + return version::invalid();
  2549. +
  2550. + QString version_str = Python::unicode(version_obj);
  2551. + if (version_str.isEmpty())
  2552. + return version::invalid();
  2553. +
  2554. + return version::fromString(version_str);
  2555. + }
  2556. +
  2557. +
  2558. };
  2559.  
  2560. inline bool operator==(const version& left, const version& right)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement