[prev in list] [next in list] [prev in thread] [next in thread]
List: kde-commits
Subject: [krita/berger-scripting] krita/plugins/extensions/pykrita: rename to pykrita
From: Cyrille Berger <cberger () cberger ! net>
Date: 2015-12-09 21:13:27
Message-ID: E1a6m3D-0005XC-FJ () scm ! kde ! org
[Download RAW message or body]
Git commit 29eb6fb2287e95ca26032471cbc4b2e59463b50f by Cyrille Berger, on behalf of \
Boudewijn Rempt. Committed on 09/12/2015 at 19:42.
Pushed by berger into branch 'berger-scripting'.
rename to pykrita
A +27 -0 krita/plugins/extensions/pykrita/CMakeLists.txt
A +30 -0 krita/plugins/extensions/pykrita/libkis/CMakeLists.txt
A +6 -0 krita/plugins/extensions/pykrita/libkis/application.cpp [License: \
UNKNOWN] * A +18 -0 krita/plugins/extensions/pykrita/libkis/application.h \
[License: UNKNOWN] * A +15 -0 \
krita/plugins/extensions/pykrita/libkis/document.cpp [License: UNKNOWN] * A +26 \
-0 krita/plugins/extensions/pykrita/libkis/document.h [License: UNKNOWN] * A \
+7 -0 krita/plugins/extensions/pykrita/libkis/image.cpp [License: UNKNOWN] \
* A +21 -0 krita/plugins/extensions/pykrita/libkis/image.h [License: \
UNKNOWN] * A +22 -0 krita/plugins/extensions/pykrita/libkis/mainwindow.cpp \
[License: UNKNOWN] * A +27 -0 \
krita/plugins/extensions/pykrita/libkis/mainwindow.h [License: UNKNOWN] * A +60 \
-0 krita/plugins/extensions/pykrita/libkis/module.cpp [License: UNKNOWN] * A \
+33 -0 krita/plugins/extensions/pykrita/libkis/module.h [License: UNKNOWN] \
* A +6 -0 krita/plugins/extensions/pykrita/libkis/node.cpp [License: \
UNKNOWN] * A +20 -0 krita/plugins/extensions/pykrita/libkis/node.h \
[License: UNKNOWN] * A +7 -0 krita/plugins/extensions/pykrita/libkis/view.cpp \
[License: UNKNOWN] * A +26 -0 krita/plugins/extensions/pykrita/libkis/view.h \
[License: UNKNOWN] * A +19 -0 \
krita/plugins/extensions/pykrita/sip/CMakeLists.txt A +0 -0 \
krita/plugins/extensions/pykrita/sip/__init__.py A +14 -0 \
krita/plugins/extensions/pykrita/sip/krita/application.sip A +16 -0 \
krita/plugins/extensions/pykrita/sip/krita/document.sip A +15 -0 \
krita/plugins/extensions/pykrita/sip/krita/image.sip A +20 -0 \
krita/plugins/extensions/pykrita/sip/krita/kritamod.sip A +16 -0 \
krita/plugins/extensions/pykrita/sip/krita/mainwindow.sip A +21 -0 \
krita/plugins/extensions/pykrita/sip/krita/module.sip A +16 -0 \
krita/plugins/extensions/pykrita/sip/krita/node.sip A +15 -0 \
krita/plugins/extensions/pykrita/sip/krita/view.sip A +42 -0 \
krita/plugins/extensions/pykrita/src/CMakeLists.txt A +21 -0 \
krita/plugins/extensions/pykrita/src/config.h.cmake A +806 -0 \
krita/plugins/extensions/pykrita/src/engine.cpp [License: LGPL] A +197 -0 \
krita/plugins/extensions/pykrita/src/engine.h [License: LGPL] A +298 -0 \
krita/plugins/extensions/pykrita/src/info.ui A +39 -0 \
krita/plugins/extensions/pykrita/src/krita/__init__.py A +7 -0 \
krita/plugins/extensions/pykrita/src/kritapyqt.desktop A +11 -0 \
krita/plugins/extensions/pykrita/src/kritapyqtplugin.desktop A +5 -0 \
krita/plugins/extensions/pykrita/src/kritapyqtplugin.rc A +48 -0 \
krita/plugins/extensions/pykrita/src/manager.ui A +79 -0 \
krita/plugins/extensions/pykrita/src/plugin.cpp [License: LGPL (v2)] A +40 -0 \
krita/plugins/extensions/pykrita/src/plugin.h [License: LGPL (v2)] A +89 -0 \
krita/plugins/extensions/pykrita/src/plugins/CMakeLists.txt A +1 -0 \
krita/plugins/extensions/pykrita/src/plugins/hello/__init__.py A +5 -0 \
krita/plugins/extensions/pykrita/src/plugins/hello/hello.py A +8 -0 \
krita/plugins/extensions/pykrita/src/plugins/hello/hello_ui.rc A +7 -0 \
krita/plugins/extensions/pykrita/src/plugins/hello/kritapykrita_hello.desktop A +90 \
-0 krita/plugins/extensions/pykrita/src/pyqtpluginsettings.cpp [License: LGPL \
(v2)] A +93 -0 krita/plugins/extensions/pykrita/src/pyqtpluginsettings.h \
[License: LGPL (v2)] A +13 -0 \
krita/plugins/extensions/pykrita/src/test/CMakeLists.txt A +181 -0 \
krita/plugins/extensions/pykrita/src/test/version_checker_test.cpp [License: LGPL \
(v2/3)] A +38 -0 \
krita/plugins/extensions/pykrita/src/test/version_checker_test.h [License: LGPL \
(v2/3)] A +519 -0 krita/plugins/extensions/pykrita/src/utilities.cpp \
[License: LGPL] A +228 -0 krita/plugins/extensions/pykrita/src/utilities.h \
[License: LGPL] A +279 -0 krita/plugins/extensions/pykrita/src/version_checker.h \
[License: LGPL (v2/3)]
The files marked with a * at the end have a non valid license. Please read: \
http://techbase.kde.org/Policies/Licensing_Policy and use the headers which are \
listed at that page.
http://commits.kde.org/krita/29eb6fb2287e95ca26032471cbc4b2e59463b50f
diff --git a/krita/plugins/extensions/pykrita/CMakeLists.txt \
b/krita/plugins/extensions/pykrita/CMakeLists.txt new file mode 100644
index 0000000..45b6329
--- /dev/null
+++ b/krita/plugins/extensions/pykrita/CMakeLists.txt
@@ -0,0 +1,27 @@
+macro_optional_find_package(PythonLibrary)
+macro_log_feature(PYTHON_LIBRARY "PythonLibrary" "Python Library" FALSE "" "Required \
by the Krita PyQt plugin") +macro_bool_to_01(PYTHONLIBS_FOUND HAVE_PYTHONLIBS)
+
+macro_optional_find_package(SIP 4.7.1)
+macro_log_feature(SIP_FOUND "SIP" "Support for SIP Python bindings" FALSE "" \
"Required by the Krita PyQt plugin") +macro_bool_to_01(SIP_FOUND HAVE_SIP)
+
+macro_optional_find_package(PyQt4 4.3.1)
+macro_log_feature(PYQT4_FOUND "PyQt4" "Python bindings for Qt4" FALSE "" "Required \
by the Krita PyQt plugin") +macro_bool_to_01(PYQT4_FOUND HAVE_PYQT4)
+
+if (HAVE_PYQT4 AND HAVE_SIP AND HAVE_PYTHONLIBS)
+
+if (NOT EXISTS ${PYQT4_SIP_DIR}/QtCore/QtCoremod.sip)
+ message(WARNING "krita-pykrita plugin needs the PyQt4 development sip file \
QtCoremod.sip") + return()
+endif()
+
+include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${SIP_INCLUDE_DIR} \
${PYTHON_INCLUDE_PATH}) +
+add_subdirectory(libkis)
+add_subdirectory(sip)
+add_subdirectory(src)
+
+
+endif (HAVE_PYQT4 AND HAVE_SIP AND HAVE_PYTHONLIBS)
diff --git a/krita/plugins/extensions/pykrita/libkis/CMakeLists.txt \
b/krita/plugins/extensions/pykrita/libkis/CMakeLists.txt new file mode 100644
index 0000000..b9c25a1
--- /dev/null
+++ b/krita/plugins/extensions/pykrita/libkis/CMakeLists.txt
@@ -0,0 +1,30 @@
+include_directories(${KOMAIN_INCLUDES} ${KRITA_INCLUDES})
+
+
+set(kritalibkis_LIB_SRCS
+ application.cpp
+ document.cpp
+ image.cpp
+ mainwindow.cpp
+ module.cpp
+ node.cpp
+ view.cpp
+)
+
+kde4_add_library(kritalibkis SHARED ${kritalibkis_LIB_SRCS} )
+
+if (WIN32)
+ target_link_libraries(kritalibkis kritaui kritaimage ${WIN32_PLATFORM_NET_LIBS})
+else (WIN32)
+ target_link_libraries(kritalibkis kritaui kritaimage)
+endif (WIN32)
+
+
+target_link_libraries(kritalibkis LINK_INTERFACE_LIBRARIES kritaimage kritaui)
+
+set_target_properties(kritalibkis PROPERTIES
+ VERSION ${GENERIC_CALLIGRA_LIB_VERSION} SOVERSION \
${GENERIC_CALLIGRA_LIB_SOVERSION} +)
+
+install(TARGETS kritalibkis ${INSTALL_TARGETS_DEFAULT_ARGS})
+
diff --git a/krita/plugins/extensions/pykrita/libkis/application.cpp \
b/krita/plugins/extensions/pykrita/libkis/application.cpp new file mode 100644
index 0000000..bc646d1
--- /dev/null
+++ b/krita/plugins/extensions/pykrita/libkis/application.cpp
@@ -0,0 +1,6 @@
+#include "application.h"
+
+Application::Application(QObject *parent) :
+ QObject(parent)
+{
+}
diff --git a/krita/plugins/extensions/pykrita/libkis/application.h \
b/krita/plugins/extensions/pykrita/libkis/application.h new file mode 100644
index 0000000..55346ab
--- /dev/null
+++ b/krita/plugins/extensions/pykrita/libkis/application.h
@@ -0,0 +1,18 @@
+#ifndef LIBKIS_APPLICATION_H
+#define LIBKIS_APPLICATION_H
+
+#include <QObject>
+#include <krita_export.h>
+class LIBKIS_EXPORT Application : public QObject
+{
+ Q_OBJECT
+public:
+ explicit Application(QObject *parent = 0);
+
+signals:
+
+public slots:
+
+};
+
+#endif // LIBKIS_APPLICATION_H
diff --git a/krita/plugins/extensions/pykrita/libkis/document.cpp \
b/krita/plugins/extensions/pykrita/libkis/document.cpp new file mode 100644
index 0000000..1e6e3f9
--- /dev/null
+++ b/krita/plugins/extensions/pykrita/libkis/document.cpp
@@ -0,0 +1,15 @@
+#include "document.h"
+#include "image.h"
+
+#include <kis_doc2.h>
+
+Document::Document(KisDoc2 *document, QObject *parent)
+ : QObject(parent)
+ , m_document(document)
+{
+}
+
+Image *Document::image()
+{
+ return new Image(m_document->image(), this);
+}
diff --git a/krita/plugins/extensions/pykrita/libkis/document.h \
b/krita/plugins/extensions/pykrita/libkis/document.h new file mode 100644
index 0000000..d85ea1e
--- /dev/null
+++ b/krita/plugins/extensions/pykrita/libkis/document.h
@@ -0,0 +1,26 @@
+#ifndef LIBKIS_DOCUMENT_H
+#define LIBKIS_DOCUMENT_H
+
+#include <QObject>
+#include <krita_export.h>
+
+class KisDoc2;
+class Image;
+
+class LIBKIS_EXPORT Document : public QObject
+{
+ Q_OBJECT
+public:
+ explicit Document(KisDoc2 *document, QObject *parent = 0);
+
+ Image *image();
+
+signals:
+
+public slots:
+private:
+
+ KisDoc2 *m_document;
+};
+
+#endif // LIBKIS_DOCUMENT_H
diff --git a/krita/plugins/extensions/pykrita/libkis/image.cpp \
b/krita/plugins/extensions/pykrita/libkis/image.cpp new file mode 100644
index 0000000..2a85ed6
--- /dev/null
+++ b/krita/plugins/extensions/pykrita/libkis/image.cpp
@@ -0,0 +1,7 @@
+#include "image.h"
+
+Image::Image(KisImageWSP image, QObject *parent)
+ : QObject(parent)
+ , m_image(image)
+{
+}
diff --git a/krita/plugins/extensions/pykrita/libkis/image.h \
b/krita/plugins/extensions/pykrita/libkis/image.h new file mode 100644
index 0000000..9a877e6
--- /dev/null
+++ b/krita/plugins/extensions/pykrita/libkis/image.h
@@ -0,0 +1,21 @@
+#ifndef LIBKIS_IMAGE_H
+#define LIBKIS_IMAGE_H
+
+#include <QObject>
+#include <kis_image.h>
+#include <krita_export.h>
+
+class LIBKIS_EXPORT Image : public QObject
+{
+ Q_OBJECT
+public:
+ explicit Image(KisImageWSP image, QObject *parent = 0);
+
+signals:
+
+public slots:
+private:
+ KisImageWSP m_image;
+};
+
+#endif // LIBKIS_IMAGE_H
diff --git a/krita/plugins/extensions/pykrita/libkis/mainwindow.cpp \
b/krita/plugins/extensions/pykrita/libkis/mainwindow.cpp new file mode 100644
index 0000000..cc330f2
--- /dev/null
+++ b/krita/plugins/extensions/pykrita/libkis/mainwindow.cpp
@@ -0,0 +1,22 @@
+#include "mainwindow.h"
+
+#include <KoMainWindow.h>
+#include <kis_view2.h>
+
+#include "view.h"
+
+MainWindow::MainWindow(KoMainWindow *mainWin, QObject *parent)
+ : QObject(parent)
+ , m_mainWindow(mainWin)
+{
+}
+
+QList<View *> MainWindow::views()
+{
+ QList<View*> ret;
+ KisView2 *view = qobject_cast<KisView2*>(m_mainWindow->rootView());
+ if (view) {
+ ret << new View(view, this);
+ }
+ return ret;
+}
diff --git a/krita/plugins/extensions/pykrita/libkis/mainwindow.h \
b/krita/plugins/extensions/pykrita/libkis/mainwindow.h new file mode 100644
index 0000000..66efc89
--- /dev/null
+++ b/krita/plugins/extensions/pykrita/libkis/mainwindow.h
@@ -0,0 +1,27 @@
+#ifndef LIBKIS_MAINWINDOW_H
+#define LIBKIS_MAINWINDOW_H
+
+#include <QObject>
+#include <krita_export.h>
+class KoMainWindow;
+
+#include <view.h>
+
+class LIBKIS_EXPORT MainWindow : public QObject
+{
+ Q_OBJECT
+public:
+ explicit MainWindow(KoMainWindow *mainWin, QObject *parent = 0);
+
+ QList<View*> views();
+signals:
+
+public slots:
+
+
+private:
+
+ KoMainWindow *m_mainWindow;
+};
+
+#endif // MAINWINDOW_H
diff --git a/krita/plugins/extensions/pykrita/libkis/module.cpp \
b/krita/plugins/extensions/pykrita/libkis/module.cpp new file mode 100644
index 0000000..c9d15e4
--- /dev/null
+++ b/krita/plugins/extensions/pykrita/libkis/module.cpp
@@ -0,0 +1,60 @@
+#include "module.h"
+
+#include <KoApplication.h>
+#include <KoPart.h>
+#include <KoMainWindow.h>
+
+#include "kis_view2.h"
+#include "kis_doc2.h"
+#include "kis_image.h"
+
+Module::Module(QObject *parent) :
+ QObject(parent)
+{
+}
+
+QList<MainWindow *> Module::mainWindows()
+{
+ QList<MainWindow *> ret;
+ foreach(KoPart *part, koApp->partList()) {
+ if (part) {
+ foreach(KoMainWindow *mainWin, part->mainWindows()) {
+ ret << new MainWindow(mainWin, this);
+ }
+ }
+ }
+ return ret;
+}
+
+QList<View *> Module::views()
+{
+ QList<View *> ret;
+ foreach(MainWindow *mainWin, mainWindows()) {
+ ret << mainWin->views();
+ }
+ return ret;
+}
+
+QList<Document *> Module::documents()
+{
+ QList<Document *> ret;
+ foreach(KoPart *part, koApp->partList()) {
+ if (part) {
+ KisDoc2 *doc = qobject_cast<KisDoc2*>(part->document());
+ if (doc) {
+ ret << new Document(doc, this);
+ }
+ }
+ }
+ return ret;
+
+}
+
+QList<Image *> Module::images()
+{
+ QList<Image *> ret;
+ foreach(Document *doc, documents()) {
+ ret << doc->image();
+ }
+ return ret;
+}
diff --git a/krita/plugins/extensions/pykrita/libkis/module.h \
b/krita/plugins/extensions/pykrita/libkis/module.h new file mode 100644
index 0000000..489882a
--- /dev/null
+++ b/krita/plugins/extensions/pykrita/libkis/module.h
@@ -0,0 +1,33 @@
+#ifndef LIBKIS_MODULE_H
+#define LIBKIS_MODULE_H
+
+#include <QObject>
+#include <QPointer>
+
+#include "application.h"
+#include "mainwindow.h"
+#include "view.h"
+#include "document.h"
+#include "image.h"
+
+#include <krita_export.h>
+
+class LIBKIS_EXPORT Module : public QObject
+{
+ Q_OBJECT
+public:
+ explicit Module(QObject *parent = 0);
+
+ QList<MainWindow*> mainWindows();
+ QList<View*> views();
+ QList<Document*> documents();
+ QList<Image*> images();
+
+signals:
+
+public slots:
+
+private:
+};
+
+#endif // LIBKIS_MODULE_H
diff --git a/krita/plugins/extensions/pykrita/libkis/node.cpp \
b/krita/plugins/extensions/pykrita/libkis/node.cpp new file mode 100644
index 0000000..d46273d
--- /dev/null
+++ b/krita/plugins/extensions/pykrita/libkis/node.cpp
@@ -0,0 +1,6 @@
+#include "node.h"
+
+Node::Node(QObject *parent) :
+ QObject(parent)
+{
+}
diff --git a/krita/plugins/extensions/pykrita/libkis/node.h \
b/krita/plugins/extensions/pykrita/libkis/node.h new file mode 100644
index 0000000..d5d7054
--- /dev/null
+++ b/krita/plugins/extensions/pykrita/libkis/node.h
@@ -0,0 +1,20 @@
+#ifndef LIBKIS_NODE_H
+#define LIBKIS_NODE_H
+
+#include <QObject>
+
+#include <krita_export.h>
+
+class LIBKIS_EXPORT Node : public QObject
+{
+ Q_OBJECT
+public:
+ explicit Node(QObject *parent = 0);
+
+signals:
+
+public slots:
+
+};
+
+#endif // LIBKIS_NODE_H
diff --git a/krita/plugins/extensions/pykrita/libkis/view.cpp \
b/krita/plugins/extensions/pykrita/libkis/view.cpp new file mode 100644
index 0000000..61cf272
--- /dev/null
+++ b/krita/plugins/extensions/pykrita/libkis/view.cpp
@@ -0,0 +1,7 @@
+#include "view.h"
+
+View::View(KisView2 *view, QObject *parent)
+ : QObject(parent)
+ , m_view(view)
+{
+}
diff --git a/krita/plugins/extensions/pykrita/libkis/view.h \
b/krita/plugins/extensions/pykrita/libkis/view.h new file mode 100644
index 0000000..37bc158
--- /dev/null
+++ b/krita/plugins/extensions/pykrita/libkis/view.h
@@ -0,0 +1,26 @@
+#ifndef LIBKIS_VIEW_H
+#define LIBKIS_VIEW_H
+
+#include <QObject>
+
+#include <krita_export.h>
+
+class KisView2;
+
+class LIBKIS_EXPORT View : public QObject
+{
+ Q_OBJECT
+public:
+ explicit View(KisView2 *view, QObject *parent = 0);
+
+signals:
+
+public slots:
+
+private:
+
+ KisView2 *m_view;
+
+};
+
+#endif // LIBKIS_VIEW_H
diff --git a/krita/plugins/extensions/pykrita/sip/CMakeLists.txt \
b/krita/plugins/extensions/pykrita/sip/CMakeLists.txt new file mode 100644
index 0000000..f7cc864
--- /dev/null
+++ b/krita/plugins/extensions/pykrita/sip/CMakeLists.txt
@@ -0,0 +1,19 @@
+include(SIPMacros)
+
+set(SIP_INCLUDES ${PYQT4_SIP_DIR} ./krita)
+set(SIP_CONCAT_PARTS 1)
+set(SIP_TAGS ALL WS_X11 ${PYQT4_VERSION_TAG})
+
+if(PYQT4_VERSION STRLESS "040905")
+ set(SIP_EXTRA_OPTIONS -g)
+else (PYQT4_VERSION STRLESS "040905")
+ # Disable QVector<int> for newer PyQt
+ set(SIP_EXTRA_OPTIONS -g -x PyKDE_QVector)
+endif(PYQT4_VERSION STRLESS "040905")
+
+add_sip_python_module(PyKrita4.krita ./krita/kritamod.sip kritalibkis kritaui \
kritaimage kritalibbrush) +
+install(FILES
+ ./__init__.py
+ DESTINATION ${PYTHON_SITE_PACKAGES_INSTALL_DIR}/PyKrita4/)
+
diff --git a/krita/plugins/extensions/pykrita/sip/__init__.py \
b/krita/plugins/extensions/pykrita/sip/__init__.py new file mode 100644
index 0000000..e69de29
diff --git a/krita/plugins/extensions/pykrita/sip/krita/application.sip \
b/krita/plugins/extensions/pykrita/sip/krita/application.sip new file mode 100644
index 0000000..e8a24ca
--- /dev/null
+++ b/krita/plugins/extensions/pykrita/sip/krita/application.sip
@@ -0,0 +1,14 @@
+%Module PyKrita4.Application
+
+%Import QtCore/QtCoremod.sip
+%Import QtGui/QtGuimod.sip
+
+class Application : public QObject
+{
+%TypeHeaderCode
+#include <../../libkis/application.h>
+%End
+
+public:
+ explicit Application(QObject *parent /TransferThis/ = 0);
+};
diff --git a/krita/plugins/extensions/pykrita/sip/krita/document.sip \
b/krita/plugins/extensions/pykrita/sip/krita/document.sip new file mode 100644
index 0000000..9b3281b
--- /dev/null
+++ b/krita/plugins/extensions/pykrita/sip/krita/document.sip
@@ -0,0 +1,16 @@
+%Module PyKrita4.Document
+
+%Import QtCore/QtCoremod.sip
+%Import QtGui/QtGuimod.sip
+
+class Document : public QObject
+{
+%TypeHeaderCode
+#include <../../libkis/document.h>
+%End
+
+public:
+ explicit Document(void */In/, QObject *parent /TransferThis/ = 0);
+
+ Image *image();
+};
diff --git a/krita/plugins/extensions/pykrita/sip/krita/image.sip \
b/krita/plugins/extensions/pykrita/sip/krita/image.sip new file mode 100644
index 0000000..5c18f4d
--- /dev/null
+++ b/krita/plugins/extensions/pykrita/sip/krita/image.sip
@@ -0,0 +1,15 @@
+%Module PyKrita4.Image
+
+%Import QtCore/QtCoremod.sip
+%Import QtGui/QtGuimod.sip
+
+class Image : public QObject
+{
+%TypeHeaderCode
+#include <../../libkis/image.h>
+%End
+
+public:
+ explicit Image(void */In/, QObject *parent /TransferThis/ = 0);
+
+};
\ No newline at end of file
diff --git a/krita/plugins/extensions/pykrita/sip/krita/kritamod.sip \
b/krita/plugins/extensions/pykrita/sip/krita/kritamod.sip new file mode 100644
index 0000000..94256ec
--- /dev/null
+++ b/krita/plugins/extensions/pykrita/sip/krita/kritamod.sip
@@ -0,0 +1,20 @@
+%Module PyKrita4.krita
+
+%Import QtCore/QtCoremod.sip
+%Import QtGui/QtGuimod.sip
+
+%ModuleHeaderCode
+#pragma GCC visibility push(default)
+%End
+
+%Import QtCore/QtCoremod.sip
+%Import QtGui/QtGuimod.sip
+%Import QtXml/QtXmlmod.sip
+
+%Include application.sip
+%Include document.sip
+%Include image.sip
+%Include mainwindow.sip
+%Include module.sip
+%Include node.sip
+%Include view.sip
\ No newline at end of file
diff --git a/krita/plugins/extensions/pykrita/sip/krita/mainwindow.sip \
b/krita/plugins/extensions/pykrita/sip/krita/mainwindow.sip new file mode 100644
index 0000000..413998b
--- /dev/null
+++ b/krita/plugins/extensions/pykrita/sip/krita/mainwindow.sip
@@ -0,0 +1,16 @@
+%Module PyKrita4.MainWindow
+
+%Import QtCore/QtCoremod.sip
+%Import QtGui/QtGuimod.sip
+
+
+class MainWindow : public QObject
+{
+%TypeHeaderCode
+#include <mainwindow.h>
+%End
+
+public:
+ MainWindow(void */In/, QObject *parent /TransferThis/ = 0);
+ QList<View*> views();
+};
diff --git a/krita/plugins/extensions/pykrita/sip/krita/module.sip \
b/krita/plugins/extensions/pykrita/sip/krita/module.sip new file mode 100644
index 0000000..5303086
--- /dev/null
+++ b/krita/plugins/extensions/pykrita/sip/krita/module.sip
@@ -0,0 +1,21 @@
+%Module PyKrita4.Module
+
+%Import QtCore/QtCoremod.sip
+%Import QtGui/QtGuimod.sip
+
+class Module : public QObject
+{
+%TypeHeaderCode
+#include <module.h>
+%End
+
+public:
+ Module(QObject *parent /TransferThis/ = 0);
+
+ QList<MainWindow*> mainWindows();
+ QList<View*> views();
+ QList<Document*> documents();
+ QList<Image*> images();
+
+};
+
diff --git a/krita/plugins/extensions/pykrita/sip/krita/node.sip \
b/krita/plugins/extensions/pykrita/sip/krita/node.sip new file mode 100644
index 0000000..d3465c5
--- /dev/null
+++ b/krita/plugins/extensions/pykrita/sip/krita/node.sip
@@ -0,0 +1,16 @@
+%Module PyKrita4.Node
+
+%Import QtCore/QtCoremod.sip
+%Import QtGui/QtGuimod.sip
+
+class Node : public QObject
+{
+
+%TypeHeaderCode
+#include <../../libkis/node.h>
+%End
+
+public:
+ explicit Node(QObject *parent /TransferThis/ = 0);
+
+};
diff --git a/krita/plugins/extensions/pykrita/sip/krita/view.sip \
b/krita/plugins/extensions/pykrita/sip/krita/view.sip new file mode 100644
index 0000000..78d915d
--- /dev/null
+++ b/krita/plugins/extensions/pykrita/sip/krita/view.sip
@@ -0,0 +1,15 @@
+%Module PyKrita4.View
+
+%Import QtCore/QtCoremod.sip
+%Import QtGui/QtGuimod.sip
+
+class View : public QObject
+{
+%TypeHeaderCode
+#include <../../libkis/view.h>
+%End
+
+public:
+ explicit View(void */In/, QObject *parent /TransferThis/ = 0);
+
+};
diff --git a/krita/plugins/extensions/pykrita/src/CMakeLists.txt \
b/krita/plugins/extensions/pykrita/src/CMakeLists.txt new file mode 100644
index 0000000..a1c7b57
--- /dev/null
+++ b/krita/plugins/extensions/pykrita/src/CMakeLists.txt
@@ -0,0 +1,42 @@
+# NOTE Disable trivial Qt keywords due conflicts w/ some Python.h header
+# (at least version 3.3 of it has a member PyType_Spec::slots)
+#add_definitions(-DQT_NO_KEYWORDS)
+configure_file(config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config.h)
+
+set(SOURCES
+ plugin.cpp
+ pyqtpluginsettings.cpp
+ utilities.cpp
+ engine.cpp
+)
+
+kde4_add_ui_files(SOURCES
+ info.ui
+ manager.ui
+)
+
+kde4_add_plugin(kritapykrita ${SOURCES})
+
+target_link_libraries(
+ kritapykrita
+ ${PYTHON_LIBRARY}
+ kritaui
+ kritalibbrush
+ )
+
+install(TARGETS kritapykrita DESTINATION ${PLUGIN_INSTALL_DIR})
+install(FILES kritapyqt.desktop DESTINATION ${SERVICES_INSTALL_DIR}/calligra)
+install(FILES kritapyqtplugin.rc DESTINATION ${DATA_INSTALL_DIR}/kritaplugins)
+
+install(FILES kritapyqtplugin.desktop DESTINATION ${SERVICETYPES_INSTALL_DIR})
+
+# Install "built-in" plugins
+install(
+ DIRECTORY krita
+ DESTINATION ${DATA_INSTALL_DIR}/krita/plugins/pykrita
+ FILES_MATCHING PATTERN "*.py"
+)
+
+add_subdirectory(plugins)
+add_subdirectory(test)
+
diff --git a/krita/plugins/extensions/pykrita/src/config.h.cmake \
b/krita/plugins/extensions/pykrita/src/config.h.cmake new file mode 100644
index 0000000..ec07c47
--- /dev/null
+++ b/krita/plugins/extensions/pykrita/src/config.h.cmake
@@ -0,0 +1,21 @@
+// This file is part of PyKrita, Krita' Python scripting plugin.
+//
+// Copyright (C) 2006 Paul Giannaros <paul@giannaros.org>
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) version 3.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public License
+// along with this library; see the file COPYING.LIB. If not, write to
+// the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+// Boston, MA 02110-1301, USA.
+
+#define PYKRITA_PYTHON_LIBRARY "${PYTHON_LIBRARY}"
+#define PYKRITA_PYTHON_SITE_PACKAGES_INSTALL_DIR \
"${PYTHON_SITE_PACKAGES_INSTALL_DIR}"
diff --git a/krita/plugins/extensions/pykrita/src/engine.cpp \
b/krita/plugins/extensions/pykrita/src/engine.cpp new file mode 100644
index 0000000..fd43174
--- /dev/null
+++ b/krita/plugins/extensions/pykrita/src/engine.cpp
@@ -0,0 +1,806 @@
+// This file is part of PyKrita, Krita' Python scripting plugin.
+//
+// Copyright (C) 2006 Paul Giannaros <paul@giannaros.org>
+// Copyright (C) 2012, 2013 Shaheed Haque <srhaque@theiet.org>
+// Copyright (C) 2013 Alex Turbov <i.zaufi@gmail.com>
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) version 3, or any
+// later version accepted by the membership of KDE e.V. (or its
+// successor approved by the membership of KDE e.V.), which shall
+// act as a proxy defined in Section 6 of version 3 of the license.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library. If not, see <http://www.gnu.org/licenses/>.
+//
+
+#include "engine.h"
+
+// config.h defines PYKRITA_PYTHON_LIBRARY, the path to libpython.so
+// on the build system
+
+#include "config.h"
+#include "utilities.h"
+
+#include <Python.h>
+
+#include <QLibrary>
+#include <QFileInfo>
+
+#include <kconfig.h>
+#include <kglobal.h>
+#include <klocalizedstring.h>
+#include <kcolorscheme.h>
+#include <kstandarddirs.h>
+
+#include <KoServiceLocator.h>
+
+#include <kis_debug.h>
+
+/// Name of the file where per-plugin configuration is stored.
+#define CONFIG_FILE "kritapykritarc"
+
+#if PY_MAJOR_VERSION < 3
+# define PYKRITA_INIT initpykrita
+#else
+# define PYKRITA_INIT PyInit_pykrita
+#endif
+
+PyMODINIT_FUNC PYKRITA_INIT(); // fwd decl
+
+/// \note Namespace name written in uppercase intentionally!
+/// It will appear in debug output from Python plugins...
+namespace PYKRITA
+{
+PyObject* debug(PyObject* /*self*/, PyObject* args)
+{
+ const char* text;
+
+ if (PyArg_ParseTuple(args, "s", &text))
+ dbgScript << text;
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+} // namespace PYKRITA
+
+namespace
+{
+PyObject* s_pykrita;
+/**
+ * \attention Krita has embedded Python, so init function \b never will be called
+ * automatically! We can use this fact to initialize a pointer to an instance
+ * of the \c Engine class (which is a part of the \c Plugin), so exported
+ * functions will know it (yep, from Python's side they should be static).
+ */
+PyKrita::Engine* s_engine_instance = 0;
+
+/**
+ * Wrapper function, called explicitly from \c Engine::Engine
+ * to initialize pointer to the only (by design) instance of the engine,
+ * so exported (to Python) functions get know it... Then invoke
+ * a real initialization sequence...
+ */
+void pythonInitwrapper(PyKrita::Engine* const engine)
+{
+ Q_ASSERT("Sanity check" && !s_engine_instance);
+ s_engine_instance = engine;
+ // Call initialize explicitly to initialize embedded interpreter.
+ PYKRITA_INIT();
+}
+
+/**
+ * Functions for the Python module called pykrita.
+ * \todo Does it \b REALLY needed? Configuration data will be flushed
+ * on exit anyway! Why to write it (and even allow to plugins to call this)
+ * \b before krita really going to exit? It would be better to \b deprecate
+ * this (harmful) function!
+ */
+PyObject* pykritaSaveConfiguration(PyObject* /*self*/, PyObject* /*unused*/)
+{
+ if (s_engine_instance)
+ s_engine_instance->saveGlobalPluginsConfiguration();
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+PyMethodDef pykritaMethods[] = {
+ {
+ "saveConfiguration"
+ , &pykritaSaveConfiguration
+ , METH_NOARGS
+ , "Save the configuration of the plugin into " CONFIG_FILE
+ }
+ , {
+ "kDebug"
+ , &PYKRITA::debug
+ , METH_VARARGS
+ , "True KDE way to show debug info"
+ }
+ , { 0, 0, 0, 0 }
+};
+
+} // anonymous namespace
+
+//BEGIN Python module registration
+PyMODINIT_FUNC PYKRITA_INIT()
+{
+#if PY_MAJOR_VERSION < 3
+ s_pykrita = Py_InitModule3("pykrita", pykritaMethods, "The pykrita module");
+ PyModule_AddStringConstant(s_pykrita, "__file__", __FILE__);
+#else
+ static struct PyModuleDef moduledef = {
+ PyModuleDef_HEAD_INIT
+ , "pykrita"
+ , "The pykrita module"
+ , -1
+ , pykritaMethods
+ , 0
+ , 0
+ , 0
+ , 0
+ };
+ s_pykrita = PyModule_Create(&moduledef);
+ PyModule_AddStringConstant(s_pykrita, "__file__", __FILE__);
+ return s_pykrita;
+#endif
+}
+//END Python module registration
+
+
+//BEGIN PyKrita::Engine::PluginState
+PyKrita::Engine::PluginState::PluginState()
+ : m_enabled(false)
+ , m_broken(false)
+ , m_unstable(false)
+ , m_isDir(false)
+{
+}
+//END PyKrita::Engine::PluginState
+
+
+/**
+ * Just initialize some members. The second (most important) part
+ * is to call \c Engine::tryInitializeGetFailureReason()!
+ * W/o that call instance is invalid and using it lead to UB!
+ */
+PyKrita::Engine::Engine()
+ : m_configuration(0)
+ , m_sessionConfiguration(0)
+ , m_engineIsUsable(false)
+{
+}
+
+/// \todo More accurate shutdown required:
+/// need to keep track what exactly was broken on
+/// initialize attempt...
+PyKrita::Engine::~Engine()
+{
+ dbgScript << "Going to destroy the Python engine";
+
+ // Notify Python that engine going to die
+ {
+ Python py = Python();
+ py.functionCall("_pykritaUnloading");
+ }
+ unloadAllModules();
+
+ // Clean internal configuration dicts
+ // NOTE Do not need to save anything! It's already done!
+ if (m_configuration)
+ Py_DECREF(m_configuration);
+ if (m_sessionConfiguration)
+ Py_DECREF(m_sessionConfiguration);
+
+ Python::libraryUnload();
+ s_engine_instance = 0;
+}
+
+void PyKrita::Engine::unloadAllModules()
+{
+ // Unload all modules
+ for (int i = 0; i < m_plugins.size(); ++i)
+ if (m_plugins[i].isEnabled() && !m_plugins[i].isBroken())
+ unloadModule(i);
+}
+
+/**
+ * \todo Make sure noone tries to use uninitialized engine!
+ * (Or enable exceptions for this module, so this case wouldn't even araise?)
+ */
+QString PyKrita::Engine::tryInitializeGetFailureReason()
+{
+ dbgScript << "Construct the Python engine for Python" << PY_MAJOR_VERSION << \
PY_MINOR_VERSION; + if (0 != PyImport_AppendInittab(Python::PYKRITA_ENGINE, \
PYKRITA_INIT)) { + return i18nc("@info:tooltip ", "Cannot load built-in \
<icode>pykrita</icode> module"); + }
+
+ Python::libraryLoad();
+ Python py = Python();
+
+ // Update PYTHONPATH
+ // 0) custom plugin directories (prefer local dir over systems')
+ // 1) shipped krita module's dir
+ // 2) w/ site_packages/ dir of the Python
+ QStringList pluginDirectories = KGlobal::dirs()->findDirs("appdata", \
"pykrita/"); + pluginDirectories
+ << KStandardDirs::locate("appdata", "plugins/pykrita/")
+ << QLatin1String(PYKRITA_PYTHON_SITE_PACKAGES_INSTALL_DIR)
+ ;
+ dbgScript << "Plugin Directories: " << pluginDirectories;
+ if (!py.prependPythonPaths(pluginDirectories)) {
+ return i18nc("@info:tooltip ", "Cannot update Python paths");
+ }
+
+ PyRun_SimpleString(
+ "import sip\n"
+ "sip.setapi('QDate', 2)\n"
+ "sip.setapi('QTime', 2)\n"
+ "sip.setapi('QDateTime', 2)\n"
+ "sip.setapi('QUrl', 2)\n"
+ "sip.setapi('QTextStream', 2)\n"
+ "sip.setapi('QString', 2)\n"
+ "sip.setapi('QVariant', 2)\n"
+ );
+
+ // Initialize our built-in module.
+ pythonInitwrapper(this);
+ if (!s_pykrita) {
+ return i18nc("@info:tooltip ", "No <icode>pykrita</icode> built-in module");
+ }
+
+ // Setup global configuration
+ m_configuration = PyDict_New();
+ /// \todo Check \c m_configuration ?
+ // Host the configuration dictionary.
+ py.itemStringSet("configuration", m_configuration);
+
+ // Setup per session configuration
+ m_sessionConfiguration = PyDict_New();
+ py.itemStringSet("sessionConfiguration", m_sessionConfiguration);
+
+ // Initialize 'plugins' dict of module 'pykrita'
+ PyObject* plugins = PyDict_New();
+ py.itemStringSet("plugins", plugins);
+
+ // Get plugins available
+ scanPlugins();
+ // NOTE Empty failure reson string indicates success!
+ m_engineIsUsable = true;
+ return QString();
+}
+
+int PyKrita::Engine::columnCount(const QModelIndex&) const
+{
+ return Column::LAST__;
+}
+
+int PyKrita::Engine::rowCount(const QModelIndex&) const
+{
+ return m_plugins.size();
+}
+
+QModelIndex PyKrita::Engine::index(const int row, const int column, const \
QModelIndex& parent) const +{
+ if (!parent.isValid() && row < m_plugins.size() && column < Column::LAST__)
+ return createIndex(row, column, 0);
+ return QModelIndex();
+}
+
+QModelIndex PyKrita::Engine::parent(const QModelIndex&) const
+{
+ return QModelIndex();
+}
+
+QVariant PyKrita::Engine::headerData(
+ const int section
+ , const Qt::Orientation orientation
+ , const int role
+) const
+{
+ if (role == Qt::DisplayRole && orientation == Qt::Horizontal) {
+ switch (section) {
+ case Column::NAME:
+ return i18nc("@title:column", "Name");
+ case Column::COMMENT:
+ return i18nc("@title:column", "Comment");
+ default:
+ break;
+ }
+ }
+ return QVariant();
+}
+
+QVariant PyKrita::Engine::data(const QModelIndex& index, const int role) const
+{
+ Q_ASSERT("Sanity check" && index.row() < m_plugins.size());
+ Q_ASSERT("Sanity check" && index.column() < Column::LAST__);
+ switch (role) {
+ case Qt::DisplayRole:
+ switch (index.column()) {
+ case Column::NAME:
+ return m_plugins[index.row()].m_service->name();
+ case Column::COMMENT:
+ return m_plugins[index.row()].m_service->comment();
+ default:
+ break;
+ }
+ break;
+ case Qt::CheckStateRole: {
+ if (index.column() == Column::NAME) {
+ const bool checked = m_plugins[index.row()].isEnabled();
+ return checked ? Qt::Checked : Qt::Unchecked;
+ }
+ break;
+ }
+ case Qt::ToolTipRole:
+ if (!m_plugins[index.row()].m_errorReason.isEmpty())
+ return m_plugins[index.row()].m_errorReason;
+ break;
+ case Qt::ForegroundRole:
+ if (m_plugins[index.row()].isUnstable()) {
+ KColorScheme scheme(QPalette::Inactive, KColorScheme::View);
+ return scheme.foreground(KColorScheme::NegativeText).color();
+ }
+ default:
+ break;
+ }
+ return QVariant();
+}
+
+Qt::ItemFlags PyKrita::Engine::flags(const QModelIndex& index) const
+{
+ Q_ASSERT("Sanity check" && index.row() < m_plugins.size());
+ Q_ASSERT("Sanity check" && index.column() < Column::LAST__);
+
+ int result = Qt::ItemIsSelectable;
+ if (index.column() == Column::NAME)
+ result |= Qt::ItemIsUserCheckable;
+ // Disable to select/check broken modules
+ if (!m_plugins[index.row()].isBroken())
+ result |= Qt::ItemIsEnabled;
+ return static_cast<Qt::ItemFlag>(result);
+}
+
+bool PyKrita::Engine::setData(const QModelIndex& index, const QVariant& value, const \
int role) +{
+ Q_ASSERT("Sanity check" && index.row() < m_plugins.size());
+
+ if (role == Qt::CheckStateRole) {
+ Q_ASSERT("Sanity check" && !m_plugins[index.row()].isBroken());
+
+ const bool enabled = value.toBool();
+ m_plugins[index.row()].m_enabled = enabled;
+ if (enabled)
+ loadModule(index.row());
+ else
+ unloadModule(index.row());
+ }
+ return true;
+}
+
+QStringList PyKrita::Engine::enabledPlugins() const
+{
+ /// \todo \c std::transform + lambda or even better to use
+ /// filtered and transformed view from boost
+ QStringList result;
+ Q_FOREACH(const PluginState & plugin, m_plugins)
+ if (plugin.isEnabled())
+ result.append(plugin.m_service->name());
+ return result;
+}
+
+void PyKrita::Engine::readGlobalPluginsConfiguration()
+{
+ Python py = Python();
+ PyDict_Clear(m_configuration);
+ KConfig config(CONFIG_FILE, KConfig::SimpleConfig);
+ py.updateDictionaryFromConfiguration(m_configuration, &config);
+}
+
+void PyKrita::Engine::saveGlobalPluginsConfiguration()
+{
+ Python py = Python();
+ KGlobal::config()->sync();
+ KConfig config(CONFIG_FILE, KConfig::SimpleConfig);
+ py.updateConfigurationFromDictionary(&config, m_configuration);
+ config.sync();
+}
+
+void PyKrita::Engine::readSessionPluginsConfiguration(KConfigBase* const config)
+{
+ PyDict_Clear(m_sessionConfiguration);
+ Python().updateDictionaryFromConfiguration(m_sessionConfiguration, config);
+}
+
+void PyKrita::Engine::writeSessionPluginsConfiguration(KConfigBase* const config)
+{
+ Python().updateConfigurationFromDictionary(config, m_sessionConfiguration);
+}
+
+bool PyKrita::Engine::isServiceUsable(const KService::Ptr& service)
+{
+ dbgScript << "Got Krita/PythonPlugin: " << service->name()
+ << ", module-path=" << service->library()
+ ;
+ // Make sure mandatory properties are here
+ if (service->name().isEmpty()) {
+ dbgScript << "Ignore desktop file w/o a name";
+ return false;
+ }
+ if (service->library().isEmpty()) {
+ dbgScript << "Ignore desktop file w/o a module to import";
+ return false;
+ }
+ // Check Python compatibility
+ // ATTENTION Python 3 is a default platform! Assume all modules are
+ // compatible! Do checks only if someone tries to build krita w/ Python 2.
+ // So, Python 2 modules must be marked explicitly!
+#if PY_MAJOR_VERSION < 3
+ const QVariant is_compatible = service->property("X-Python-2-Compatible", \
QVariant::Bool); + if (!(is_compatible.isValid() && is_compatible.toBool())) {
+ dbgScript << service->name() << "is incompatible w/ embedded Python \
version"; + // Do not even show incompatible modules in the manager...
+ return false;
+ }
+#endif
+ // ATTENTION If some module is Python 2 only, it must be marked w/
+ // the property 'X-Python-2-Only' of type bool and ANY (valid) value...
+ const QVariant is_py2_only = service->property("X-Python-2-Only", \
QVariant::Bool); + if (is_py2_only.isValid()) {
+ dbgScript << service->name() << "is marked as Python 2 ONLY... >/dev/null";
+ // Do not even show incompatible modules in the manager...
+ return false;
+ }
+
+ return true;
+}
+
+bool PyKrita::Engine::setModuleProperties(PluginState& plugin)
+{
+ // Find the module:
+ // 0) try to locate directory based plugin first
+ KUrl rel_path = QString(Python::PYKRITA_ENGINE);
+ dbgScript << rel_path;
+ rel_path.addPath(plugin.moduleFilePathPart());
+ dbgScript << rel_path;
+ rel_path.addPath("__init__.py");
+ dbgScript << rel_path;
+
+ QString module_path = KGlobal::dirs()->findResource("appdata", \
rel_path.toLocalFile()); +
+ dbgScript << module_path;
+
+ if (module_path.isEmpty()) {
+ // 1) Nothing found, then try file based plugin
+ rel_path = QString(Python::PYKRITA_ENGINE);
+ rel_path.addPath(plugin.moduleFilePathPart() + ".py");
+ module_path = KGlobal::dirs()->findResource("appdata", \
rel_path.toLocalFile()); + } else {
+ plugin.m_isDir = true;
+ }
+
+ // Is anything found at all?
+ if (module_path.isEmpty()) {
+ plugin.m_broken = true;
+ plugin.m_errorReason = i18nc(
+ "@info:tooltip"
+ , "Unable to find the module specified \
<application>%1</application>" + , \
plugin.m_service->library() + );
+ dbgScript << "Plugin is " << plugin.m_errorReason;
+ return false;
+ }
+ dbgScript << "Found module path:" << module_path;
+ return true;
+}
+
+QPair<QString, PyKrita::version_checker> PyKrita::Engine::parseDependency(const \
QString& d) +{
+ // Check if dependency has package info attached
+ const int pnfo = d.indexOf('(');
+ if (pnfo != -1) {
+ QString dependency = d.mid(0, pnfo);
+ QString version_str = d.mid(pnfo + 1, d.size() - pnfo - 2).trimmed();
+ dbgScript << "Desired version spec [" << dependency << "]:" << version_str;
+ version_checker checker = version_checker::fromString(version_str);
+ if (!(checker.isValid() && d.endsWith(')'))) {
+ dbgScript << "Invalid version spec " << d;
+ QString reason = i18nc(
+ "@info:tooltip"
+ , "<p>Specified version has invalid format for \
dependency <application>%1</application>: " + \
"<icode>%2</icode>. Skipped</p>" + , dependency
+ , version_str
+ );
+ return qMakePair(reason, version_checker());
+ }
+ return qMakePair(dependency, checker);
+ }
+ return qMakePair(d, version_checker(version_checker::undefined));
+}
+
+PyKrita::version PyKrita::Engine::tryObtainVersionFromTuple(PyObject* version_obj)
+{
+ Q_ASSERT("Sanity check" && version_obj);
+
+ if (PyTuple_Check(version_obj) == 0)
+ return version::invalid();
+
+ int version_info[3] = {0, 0, 0};
+ for (unsigned i = 0; i < PyTuple_Size(version_obj); ++i) {
+ PyObject* v = PyTuple_GetItem(version_obj, i);
+ if (v && PyLong_Check(v))
+ version_info[i] = PyLong_AsLong(v);
+ else
+ version_info[i] = -1;
+ }
+ if (version_info[0] != -1 && version_info[1] != -1 && version_info[2] != -1)
+ return version(version_info[0], version_info[1], version_info[2]);
+
+ return version::invalid();
+}
+
+/**
+ * Try to parse version string as a simple triplet X.Y.Z.
+ *
+ * \todo Some modules has letters in a version string...
+ * For example current \c pytz version is \e "2013d".
+ */
+PyKrita::version PyKrita::Engine::tryObtainVersionFromString(PyObject* version_obj)
+{
+ Q_ASSERT("Sanity check" && version_obj);
+
+ if (!Python::isUnicode(version_obj))
+ return version::invalid();
+
+ QString version_str = Python::unicode(version_obj);
+ if (version_str.isEmpty())
+ return version::invalid();
+
+ return version::fromString(version_str);
+}
+
+/**
+ * Collect dependencies and check them. To do it
+ * just try to import a module... when unload it ;)
+ *
+ * \c X-Python-Dependencies property of \c .desktop file has the following format:
+ * <tt>python-module(version-info)</tt>, where <tt>python-module</tt>
+ * a python module name to be imported, <tt>version-spec</tt>
+ * is a version triplet delimited by dots, possible w/ leading compare
+ * operator: \c =, \c <, \c >, \c <=, \c >=
+ */
+void PyKrita::Engine::verifyDependenciesSetStatus(PluginState& plugin)
+{
+ QStringList dependencies = plugin.m_service
+ ->property("X-Python-Dependencies", \
QVariant::StringList) + .toStringList();
+#if PY_MAJOR_VERSION < 3
+ {
+ // Try to get Py2 only dependencies
+ QStringList py2_dependencies = plugin.m_service
+ ->property("X-Python-2-Dependencies", \
QVariant::StringList) + .toStringList();
+ dependencies.append(py2_dependencies);
+ }
+#endif
+
+ Python py = Python();
+ QString reason = i18nc("@info:tooltip", "<title>Dependency check</title>");
+ Q_FOREACH(const QString & d, dependencies) {
+ QPair<QString, version_checker> info_pair = parseDependency(d);
+ version_checker& checker = info_pair.second;
+ if (!checker.isValid()) {
+ plugin.m_broken = true;
+ reason += info_pair.first;
+ continue;
+ }
+
+ dbgScript << "Try to import dependency module/package:" << d;
+
+ // Try to import a module
+ const QString& dependency = info_pair.first;
+ PyObject* module = py.moduleImport(PQ(dependency));
+ if (module) {
+ if (checker.isEmpty()) { // Need to check smth?
+ dbgScript << "No version to check, just make sure it's loaded:" << \
dependency; + Py_DECREF(module);
+ continue;
+ }
+ // Try to get __version__ from module
+ // See PEP396: http://www.python.org/dev/peps/pep-0396/
+ PyObject* version_obj = py.itemString("__version__", PQ(dependency));
+ if (!version_obj) {
+ dbgScript << "No __version__ for " << dependency
+ << "[" << plugin.m_service->name() << "]:\n" << \
py.lastTraceback() + ;
+ plugin.m_unstable = true;
+ reason += i18nc(
+ "@info:tooltip"
+ , "<p>Failed to check version of dependency \
<application>%1</application>: " + "Module do not have \
PEP396 <code>__version__</code> attribute. " + "It is \
not disabled, but behaviour is unpredictable...</p>" + , \
dependency + );
+ }
+ // PEP396 require __version__ to tuple of integers... try it!
+ version dep_version = tryObtainVersionFromTuple(version_obj);
+ if (!dep_version.isValid())
+ // Second attempt: some "bad" modules have it as a string
+ dep_version = tryObtainVersionFromString(version_obj);
+
+ // Did we get it?
+ if (!dep_version.isValid()) {
+ // Dunno what is this... Giving up!
+ dbgScript << "***: Can't parse module version for" << dependency;
+ plugin.m_unstable = true;
+ reason += i18nc(
+ "@info:tooltip"
+ , "<p><application>%1</application>: Unexpected \
module's version format" + , dependency
+ );
+ } else if (!checker(dep_version)) {
+ dbgScript << "Version requerement check failed ["
+ << plugin.m_service->name() << "] for "
+ << dependency << ": wanted " << \
checker.operationToString() + << QString(checker.required())
+ << ", but found" << QString(dep_version)
+ ;
+ plugin.m_broken = true;
+ reason += i18nc(
+ "@info:tooltip"
+ , "<p><application>%1</application>: No suitable \
version found. " + "Required version %2 %3, but found \
%4</p>" + , dependency
+ , checker.operationToString()
+ , QString(checker.required())
+ , QString(dep_version)
+ );
+ }
+ // Do not need this module anymore...
+ Py_DECREF(module);
+ } else {
+ dbgScript << "Load failure [" << plugin.m_service->name() << "]:\n" << \
py.lastTraceback(); + plugin.m_broken = true;
+ reason += i18nc(
+ "@info:tooltip"
+ , "<p>Failure on module load \
<application>%1</application>:</p><pre>%2</pre>" + , \
dependency + , py.lastTraceback()
+ );
+ }
+ }
+
+ if (plugin.isBroken() || plugin.isUnstable())
+ plugin.m_errorReason = reason;
+}
+
+void PyKrita::Engine::scanPlugins()
+{
+ m_plugins.clear(); // Clear current state.
+
+ dbgScript << "Seeking for installed plugins...";
+ KService::List services = \
KoServiceLocator::instance()->entries("Krita/PythonPlugin"); + \
Q_FOREACH(KService::Ptr service, services) { +
+ if (!isServiceUsable(service)) {
+ dbgScript << service->name() << "is not usable";
+ continue;
+ }
+
+ // Make a new state
+ PluginState plugin;
+ plugin.m_service = service;
+
+ if (!setModuleProperties(plugin)) {
+ dbgScript << "Cannot load" << service->name() << ": broken"<< \
plugin.isBroken() << "because:" << plugin.errorReason(); + continue;
+ }
+
+ verifyDependenciesSetStatus(plugin);
+ m_plugins.append(plugin);
+ }
+}
+
+void PyKrita::Engine::setEnabledPlugins(const QStringList& enabled_plugins)
+{
+ for (int i = 0; i < m_plugins.size(); ++i)
+ m_plugins[i].m_enabled = \
enabled_plugins.indexOf(m_plugins[i].m_service->name()) != -1; +}
+
+void PyKrita::Engine::tryLoadEnabledPlugins()
+{
+ for (int i = 0; i < m_plugins.size(); ++i)
+ if (m_plugins[i].isEnabled() && ! m_plugins[i].isBroken())
+ loadModule(i);
+}
+
+void PyKrita::Engine::loadModule(const int idx)
+{
+ Q_ASSERT("Plugin index is out of range!" && 0 <= idx && idx < m_plugins.size());
+ PluginState& plugin = m_plugins[idx];
+ Q_ASSERT(
+ "Why to call loadModule() for disabled/broken plugin?"
+ && plugin.isEnabled()
+ && !plugin.isBroken()
+ );
+
+ QString module_name = plugin.pythonModuleName();
+ dbgScript << "Loading module: " << module_name;
+
+ Python py = Python();
+
+ // Get 'plugins' key from 'pykrita' module dictionary.
+ // Every entry has a module name as a key and 2 elements tuple as a value
+ PyObject* plugins = py.itemString("plugins");
+ Q_ASSERT(
+ "'plugins' dict expected to be alive, otherwise code review required!"
+ && plugins
+ );
+
+ PyObject* module = py.moduleImport(PQ(module_name));
+ if (module) {
+ // Move just loaded module to the dict
+ const int ins_result = PyDict_SetItemString(plugins, PQ(module_name), \
module); + Q_ASSERT("expected successful insertion" && ins_result == 0);
+ Py_DECREF(module);
+ // Handle failure in release mode.
+ if (ins_result == 0) {
+ // Initialize the module from Python's side
+ PyObject* const args = Py_BuildValue("(s)", PQ(module_name));
+ PyObject* result = py.functionCall("_pluginLoaded", \
"Python::PYKRITA_ENGINE", args); + Py_DECREF(args);
+ if (result)
+ return; // Success!
+ }
+ plugin.m_errorReason = i18nc("@info:tooltip", "Internal engine failure");
+ } else {
+ plugin.m_errorReason = i18nc(
+ "@info:tooltip"
+ , "Module not loaded:<nl/>%1"
+ , py.lastTraceback()
+ );
+ }
+ plugin.m_broken = true;
+}
+
+void PyKrita::Engine::unloadModule(int idx)
+{
+ Q_ASSERT("Plugin index is out of range!" && 0 <= idx && idx < m_plugins.size());
+ PluginState& plugin = m_plugins[idx];
+ Q_ASSERT("Why to call unloadModule() for broken plugin?" && !plugin.isBroken());
+
+ dbgScript << "Unloading module: " << plugin.pythonModuleName();
+
+ Python py = Python();
+
+ // Get 'plugins' key from 'pykrita' module dictionary
+ PyObject* plugins = py.itemString("plugins");
+ Q_ASSERT(
+ "'plugins' dict expected to be alive, otherwise code review required!"
+ && plugins
+ );
+
+ PyObject* const args = Py_BuildValue("(s)", PQ(plugin.pythonModuleName()));
+ py.functionCall("_pluginUnloading", Python::PYKRITA_ENGINE, args);
+ Py_DECREF(args);
+
+ // This will just decrement a reference count for module instance
+ PyDict_DelItemString(plugins, PQ(plugin.pythonModuleName()));
+
+ // Remove the module also from 'sys.modules' dict to really unload it,
+ // so if reloaded all @init actions will work again!
+ PyObject* sys_modules = py.itemString("modules", "sys");
+ Q_ASSERT("Sanity check" && sys_modules);
+ PyDict_DelItemString(sys_modules, PQ(plugin.pythonModuleName()));
+}
+
+// krita: space-indent on; indent-width 4;
+#undef PYKRITA_INIT
diff --git a/krita/plugins/extensions/pykrita/src/engine.h \
b/krita/plugins/extensions/pykrita/src/engine.h new file mode 100644
index 0000000..4de0e51
--- /dev/null
+++ b/krita/plugins/extensions/pykrita/src/engine.h
@@ -0,0 +1,197 @@
+// This file is part of PyKrita, Krita' Python scripting plugin.
+//
+// Copyright (C) 2006 Paul Giannaros <paul@giannaros.org>
+// Copyright (C) 2012, 2013 Shaheed Haque <srhaque@theiet.org>
+// Copyright (C) 2013 Alex Turbov <i.zaufi@gmail.com>
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) version 3, or any
+// later version accepted by the membership of KDE e.V. (or its
+// successor approved by the membership of KDE e.V.), which shall
+// act as a proxy defined in Section 6 of version 3 of the license.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library. If not, see <http://www.gnu.org/licenses/>.
+//
+
+#ifndef __PYKRITA_ENGINE_H__
+# define __PYKRITA_ENGINE_H__
+
+# include "version_checker.h"
+
+# include <kservice.h>
+# include <kurl.h>
+
+# include <Python.h>
+
+# include <QAbstractItemModel>
+# include <QList>
+# include <QStringList>
+
+namespace PyKrita
+{
+class Python; // fwd decl
+
+/**
+ * The Engine class hosts the Python interpreter, loading
+ * it into memory within Krita, and then with finding and
+ * loading all of the PyKrita plugins.
+ *
+ * \attention Qt/KDE do not use exceptions (unfortunately),
+ * so this class must be initialized in two steps:
+ * - create an instance (via constructor)
+ * - try to initialize the rest (via \c Engine::tryInitializeGetFailureReason())
+ * If latter returns a non empty (failure reason) string, the only member
+ * can be called is conversion to boolean! (which is implemented as safe-bool idiom \
[1]) + * Calling others leads to UB!
+ *
+ * \sa [1] http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Safe_bool
+ */
+class Engine : public QAbstractItemModel
+{
+ Q_OBJECT
+
+ typedef void (Engine::*bool_type)() const;
+ void unspecified_true_bool_type() const {}
+
+public:
+ /// \todo Turn into a class w/ accessors
+ class PluginState
+ {
+ public:
+ /// \name Immutable accessors
+ //@{
+ QString pythonModuleName() const;
+ const QString& errorReason() const;
+ bool isEnabled() const;
+ bool isBroken() const;
+ bool isUnstable() const;
+ //@}
+
+ private:
+ friend class Engine;
+
+ PluginState();
+ /// Transfort Python module name into a file path part
+ QString moduleFilePathPart() const;
+
+ KService::Ptr m_service;
+ QString m_pythonModule;
+ QString m_errorReason;
+ bool m_enabled;
+ bool m_broken;
+ bool m_unstable;
+ bool m_isDir;
+ };
+
+ /// Default constructor: initialize Python interpreter
+ Engine();
+ /// Cleanup everything on unload
+ ~Engine();
+
+ //BEGIN QAbstractItemModel interface
+ virtual int columnCount(const QModelIndex&) const /*override*/;
+ virtual int rowCount(const QModelIndex&) const /*override*/;
+ virtual QModelIndex index(int, int, const QModelIndex&) const /*override*/;
+ virtual QModelIndex parent(const QModelIndex&) const /*override*/;
+ virtual QVariant headerData(int, Qt::Orientation, int) const /*override*/;
+ virtual QVariant data(const QModelIndex&, int) const /*override*/;
+ virtual Qt::ItemFlags flags(const QModelIndex&) const /*override*/;
+ virtual bool setData(const QModelIndex&, const QVariant&, int) /*override*/;
+ //END QAbstractItemModel interface
+
+ void readSessionPluginsConfiguration(KConfigBase*);
+ void writeSessionPluginsConfiguration(KConfigBase*);
+
+ void setEnabledPlugins(const QStringList&); ///< Set enabled plugins \
to the model + void tryLoadEnabledPlugins(); ///< Try to \
load enabled plugins + QStringList enabledPlugins() const; \
///< Form a list of enabled plugins + const QList<PluginState>& plugins() const; \
///< Provide immutable access to found plugins + QString \
tryInitializeGetFailureReason(); ///< Try to initialize Python \
interpreter + operator bool_type() const; ///< Check \
if instance is usable + void setBroken(); \
///< Make it broken by some external reason +
+public Q_SLOTS:
+ void readGlobalPluginsConfiguration(); ///< Load plugins' \
configuration. + void saveGlobalPluginsConfiguration(); ///< \
Write out plugins' configuration. + void unloadAllModules();
+
+protected:
+ void scanPlugins(); ///< Search for \
available plugins + void loadModule(int); ///< \
Load module by index in \c m_plugins + void unloadModule(int); \
///< Unload module by index in \c m_plugins +
+private:
+ // Simulate strong typed enums from C++11
+ struct Column {
+ enum type {
+ NAME
+ , COMMENT
+ , LAST__
+ };
+ };
+
+ static bool isServiceUsable(const KService::Ptr&); ///< Make sure that \
service is usable + static bool setModuleProperties(PluginState&);
+ static void verifyDependenciesSetStatus(PluginState&);
+ static QPair<QString, version_checker> parseDependency(const QString&);
+ static version tryObtainVersionFromTuple(PyObject*);
+ static version tryObtainVersionFromString(PyObject*);
+
+ PyObject* m_configuration; ///< Application-wide \
configuration data + PyObject* m_sessionConfiguration; ///< \
Session-wide configuration data + QList<PluginState> m_plugins; \
///< List of available plugins + bool m_engineIsUsable; \
///< Is engine loaded Ok? +};
+
+inline QString Engine::PluginState::pythonModuleName() const
+{
+ return m_service->library();
+}
+inline QString PyKrita::Engine::PluginState::moduleFilePathPart() const
+{
+ /// \todo Use \c QString::split() and \c KUrl to form a valid path
+ return m_service->library().replace(".", "/");
+}
+inline const QString& Engine::PluginState::errorReason() const
+{
+ return m_errorReason;
+}
+inline bool Engine::PluginState::isEnabled() const
+{
+ return m_enabled;
+}
+inline bool Engine::PluginState::isBroken() const
+{
+ return m_broken;
+}
+inline bool Engine::PluginState::isUnstable() const
+{
+ return m_unstable;
+}
+
+inline const QList<Engine::PluginState>& Engine::plugins() const
+{
+ return m_plugins;
+}
+
+inline Engine::operator bool_type() const
+{
+ return m_engineIsUsable ? &Engine::unspecified_true_bool_type : 0;
+}
+
+inline void Engine::setBroken()
+{
+ m_engineIsUsable = false;
+}
+
+} // namespace PyKrita
+#endif // __PYKRITA_ENGINE_H__
+// krita: indent-width 4;
diff --git a/krita/plugins/extensions/pykrita/src/info.ui \
b/krita/plugins/extensions/pykrita/src/info.ui new file mode 100644
index 0000000..2edc253
--- /dev/null
+++ b/krita/plugins/extensions/pykrita/src/info.ui
@@ -0,0 +1,298 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>InfoPage</class>
+ <widget class="QWidget" name="InfoPage">
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="1" column="0" colspan="2">
+ <widget class="KTabWidget" name="tabs">
+ <property name="currentIndex">
+ <number>0</number>
+ </property>
+ <widget class="QWidget" name="tab2">
+ <attribute name="title">
+ <string>Actions</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="tab2lo">
+ <item>
+ <layout class="QGridLayout" name="tab2_1lo">
+ <item row="1" column="3" rowspan="8">
+ <widget class="KIconButton" name="actionIcon">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>64</height>
+ </size>
+ </property>
+ <property name="whatsThis">
+ <string>The icon associated with this action. It is shown alongside text \
in the menu bar and in toolbars as required. A string to use KDE's image loading \
system, or a custom QPixmap or QIcon, or None.</string> + </property>
+ <property name="flat">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="1" rowspan="5">
+ <widget class="QLabel" name="shortcut">
+ <property name="whatsThis">
+ <string>The shortcut to fire this action, such as 'Ctrl+1', or a \
QKeySequence instance, or None.</string> + </property>
+ </widget>
+ </item>
+ <item row="9" column="0">
+ <widget class="QLabel" name="labelTopics">
+ <property name="text">
+ <string>Description:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <property name="buddy">
+ <cstring>topics</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="9" column="1" colspan="3">
+ <widget class="QTextEdit" name="description">
+ <property name="textInteractionFlags">
+ <set>Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QLabel" name="labelText">
+ <property name="text">
+ <string>Menu Item:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <property name="buddy">
+ <cstring>name</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QLabel" name="text">
+ <property name="whatsThis">
+ <string>The text associated with the action (used as the menu item \
label, etc), or None.</string> + </property>
+ </widget>
+ </item>
+ <item row="1" column="1" rowspan="2">
+ <widget class="QLabel" name="menu">
+ <property name="whatsThis">
+ <string>The menu under which to place this item, such as 'tools' or \
'settings', or None.</string> + </property>
+ </widget>
+ </item>
+ <item row="4" column="0" rowspan="5">
+ <widget class="QLabel" name="labelShortcut">
+ <property name="text">
+ <string>Shortcut:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <property name="buddy">
+ <cstring>fullName</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0" rowspan="2">
+ <widget class="QLabel" name="labelMenu">
+ <property name="text">
+ <string>Menu:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <property name="buddy">
+ <cstring>menu</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="2" rowspan="8">
+ <widget class="QLabel" name="labelActionIcon">
+ <property name="text">
+ <string>Icon:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <property name="buddy">
+ <cstring>configPageIcon</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1" colspan="3">
+ <widget class="KComboBox" name="actions"/>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>Action:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <spacer name="verticalSpacer2">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="tab3">
+ <attribute name="title">
+ <string>Configuration Pages</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="tab3lo">
+ <item>
+ <layout class="QGridLayout" name="tab3_1lo">
+ <item row="1" column="1" colspan="3">
+ <widget class="QLabel" name="fullName">
+ <property name="whatsThis">
+ <string>The shortcut to fire this action such as 'Ctrl+1', or a \
QKeySequence instance, or None.</string> + </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Title:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="2" rowspan="5">
+ <widget class="QLabel" name="labelPageIcon">
+ <property name="text">
+ <string>Icon:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <property name="buddy">
+ <cstring>configPageIcon</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="3" rowspan="5">
+ <widget class="KIconButton" name="configPageIcon">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>64</height>
+ </size>
+ </property>
+ <property name="whatsThis">
+ <string>The icon associated with this action. It is shown alongside text \
in the menu bar and in toolbars as required. A string to use KDE's image loading \
system, or a custom QPixmap or QIcon, or None.</string> + </property>
+ <property name="flat">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0" rowspan="5">
+ <widget class="QLabel" name="labelName">
+ <property name="text">
+ <string>Name:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <property name="buddy">
+ <cstring>name</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1" rowspan="5">
+ <widget class="QLabel" name="name">
+ <property name="whatsThis">
+ <string>The text associated with the action (used as the menu item \
label, etc), or None.</string> + </property>
+ </widget>
+ </item>
+ <item row="0" column="1" colspan="3">
+ <widget class="KComboBox" name="configPages"/>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>Page:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <spacer name="verticalSpacer3">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ <item row="0" column="0" colspan="2">
+ <widget class="KComboBox" name="topics">
+ <property name="whatsThis">
+ <string>Select a Plugin or Built-in Module</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>KIconButton</class>
+ <extends>QPushButton</extends>
+ <header>kicondialog.h</header>
+ </customwidget>
+ <customwidget>
+ <class>KComboBox</class>
+ <extends>QComboBox</extends>
+ <header>kcombobox.h</header>
+ </customwidget>
+ <customwidget>
+ <class>KTabWidget</class>
+ <extends>QTabWidget</extends>
+ <header>ktabwidget.h</header>
+ <container>1</container>
+ </customwidget>
+ </customwidgets>
+ <tabstops>
+ <tabstop>topics</tabstop>
+ <tabstop>tabs</tabstop>
+ <tabstop>actions</tabstop>
+ <tabstop>configPages</tabstop>
+ <tabstop>fullName</tabstop>
+ </tabstops>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/krita/plugins/extensions/pykrita/src/krita/__init__.py \
b/krita/plugins/extensions/pykrita/src/krita/__init__.py new file mode 100644
index 0000000..07cffa0
--- /dev/null
+++ b/krita/plugins/extensions/pykrita/src/krita/__init__.py
@@ -0,0 +1,39 @@
+import pykrita
+
+def pykritaEventHandler(event):
+ def _decorator(func):
+ setattr(pykrita, event, func)
+ del func
+ return _decorator
+
+
+
+@pykritaEventHandler('_pluginLoaded')
+def on_load(plugin):
+ if plugin in init.functions:
+ # Call registered init functions for the plugin
+ init.fire(plugin=plugin)
+ del init.functions[plugin]
+ return True
+
+
+@pykritaEventHandler('_pluginUnloading')
+def on_unload(plugin):
+ if plugin in unload.functions:
+ # Deinitialize plugin
+ unload.fire(plugin=plugin)
+ del unload.functions[plugin]
+ return True
+
+
+@pykritaEventHandler('_pykritaLoaded')
+def on_pykrita_loaded():
+ kDebug('PYKRITA LOADED')
+ return True
+
+
+@pykritaEventHandler('_pykritaUnloading')
+def on_pykrita_unloading():
+ kDebug('UNLOADING PYKRITA')
+ return True
+
diff --git a/krita/plugins/extensions/pykrita/src/kritapyqt.desktop \
b/krita/plugins/extensions/pykrita/src/kritapyqt.desktop new file mode 100644
index 0000000..28a61f0
--- /dev/null
+++ b/krita/plugins/extensions/pykrita/src/kritapyqt.desktop
@@ -0,0 +1,7 @@
+[Desktop Entry]
+Name=Python Plugin Manager
+X-KDE-ServiceTypes=Krita/ViewPlugin
+Type=Service
+X-KDE-Library=kritapykrita
+X-Krita-Version=28
+
diff --git a/krita/plugins/extensions/pykrita/src/kritapyqtplugin.desktop \
b/krita/plugins/extensions/pykrita/src/kritapyqtplugin.desktop new file mode 100644
index 0000000..36ddfde
--- /dev/null
+++ b/krita/plugins/extensions/pykrita/src/kritapyqtplugin.desktop
@@ -0,0 +1,11 @@
+[Desktop Entry]
+Type=ServiceType
+X-KDE-ServiceType=Krita/PythonPlugin
+X-KDE-Derived=
+Comment=Krita Python Plugin
+
+[PropertyDef::X-Python-2-Compatible]
+Type=bool
+
+[PropertyDef::X-Python-3-Compatible]
+Type=bool
diff --git a/krita/plugins/extensions/pykrita/src/kritapyqtplugin.rc \
b/krita/plugins/extensions/pykrita/src/kritapyqtplugin.rc new file mode 100644
index 0000000..c0b4399
--- /dev/null
+++ b/krita/plugins/extensions/pykrita/src/kritapyqtplugin.rc
@@ -0,0 +1,5 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+<kpartgui library="kritapykritaplugin" version="7">
+<MenuBar>
+</MenuBar>
+</kpartgui>
diff --git a/krita/plugins/extensions/pykrita/src/manager.ui \
b/krita/plugins/extensions/pykrita/src/manager.ui new file mode 100644
index 0000000..71e4cfe
--- /dev/null
+++ b/krita/plugins/extensions/pykrita/src/manager.ui
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ManagerPage</class>
+ <widget class="QWidget" name="ManagerPage">
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QLabel" name="errorLabel">
+ <property name="font">
+ <font>
+ <weight>75</weight>
+ <bold>true</bold>
+ </font>
+ </property>
+ <property name="text">
+ <string>Error: The Python engine could not be initialized</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QTreeView" name="pluginsList">
+ <property name="selectionMode">
+ <enum>QAbstractItemView::SingleSelection</enum>
+ </property>
+ <property name="selectionBehavior">
+ <enum>QAbstractItemView::SelectRows</enum>
+ </property>
+ <property name="rootIsDecorated">
+ <bool>false</bool>
+ </property>
+ <property name="itemsExpandable">
+ <bool>false</bool>
+ </property>
+ <property name="sortingEnabled">
+ <bool>false</bool>
+ </property>
+ <property name="expandsOnDoubleClick">
+ <bool>false</bool>
+ </property>
+ <attribute name="headerShowSortIndicator" stdset="0">
+ <bool>false</bool>
+ </attribute>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/krita/plugins/extensions/pykrita/src/plugin.cpp \
b/krita/plugins/extensions/pykrita/src/plugin.cpp new file mode 100644
index 0000000..f25b3f0
--- /dev/null
+++ b/krita/plugins/extensions/pykrita/src/plugin.cpp
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2014 Boudewijn Rempt (boud@valdyas.org)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "plugin.h"
+#include "engine.h"
+#include "utilities.h"
+
+#include <klocale.h>
+#include <kis_debug.h>
+#include <kpluginfactory.h>
+
+#include <kis_view2.h>
+#include <kis_preference_set_registry.h>
+#include <pyqtpluginsettings.h>
+
+K_PLUGIN_FACTORY(KritaPyQtPluginFactory, registerPlugin<KritaPyQtPlugin>();)
+K_EXPORT_PLUGIN(KritaPyQtPluginFactory("krita"))
+
+KritaPyQtPlugin::KritaPyQtPlugin(QObject *parent, const QVariantList &)
+ : KisViewPlugin(parent, "kritaplugins/kritapykritaplugin.rc")
+ , m_engineFailureReason(m_engine.tryInitializeGetFailureReason())
+ , m_autoReload(false)
+{
+ qDebug() << ">>>>>>>>>>>>>>>" << m_engineFailureReason;
+
+ KisPreferenceSetRegistry *preferenceSetRegistry = \
KisPreferenceSetRegistry::instance(); +
+ PyQtPluginSettingsFactory* settingsFactory = new \
PyQtPluginSettingsFactory(&m_engine); +
+ //load and save preferences
+ //if something in kritarc is missing, then the default from this load function \
will be used and saved back to kconfig. + //this way, cfg.readEntry() in any part \
won't be able to set its own default + KisPreferenceSet* settings = \
settingsFactory->createPreferenceSet(); + Q_ASSERT(settings);
+ settings->loadPreferences();
+ settings->savePreferences();
+ delete settings;
+
+ preferenceSetRegistry->add("PyQtPluginSettingsFactory", settingsFactory);
+
+ // Try to import the `pykrita` module
+ PyKrita::Python py = PyKrita::Python();
+ PyObject* pykritaPackage = py.moduleImport("pykrita");
+ pykritaPackage = py.moduleImport("krita");
+ if (pykritaPackage)
+ {
+ dbgKrita << "Loaded pykrita, now load plugins";
+ m_engine.tryLoadEnabledPlugins();
+ py.functionCall("_pykritaLoaded", "krita");
+ }
+ else
+ {
+ dbgScript << "Cannot load pykrita module";
+ m_engine.setBroken();
+ }
+
+
+
+}
+
+KritaPyQtPlugin::~KritaPyQtPlugin()
+{
+
+}
+
diff --git a/krita/plugins/extensions/pykrita/src/plugin.h \
b/krita/plugins/extensions/pykrita/src/plugin.h new file mode 100644
index 0000000..8613769
--- /dev/null
+++ b/krita/plugins/extensions/pykrita/src/plugin.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2014 Boudewijn Rempt <boud@kogmbh.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef _PYQT_PLUGIN_H_
+#define _PYQT_PLUGIN_H_
+
+#include <QObject>
+
+#include <Python.h>
+
+#include <kis_view_plugin.h>
+#include "engine.h"
+
+class KritaPyQtPlugin : public KisViewPlugin
+{
+ Q_OBJECT
+public:
+ KritaPyQtPlugin(QObject *parent, const QVariantList &);
+ virtual ~KritaPyQtPlugin();
+private:
+ PyKrita::Engine m_engine;
+ QString m_engineFailureReason;
+ bool m_autoReload;
+};
+
+#endif
diff --git a/krita/plugins/extensions/pykrita/src/plugins/CMakeLists.txt \
b/krita/plugins/extensions/pykrita/src/plugins/CMakeLists.txt new file mode 100644
index 0000000..0b4de3f
--- /dev/null
+++ b/krita/plugins/extensions/pykrita/src/plugins/CMakeLists.txt
@@ -0,0 +1,89 @@
+# Copyright (C) 2012, 2013 Shaheed Haque <srhaque@theiet.org>
+# Copyright (C) 2013 Alex Turbov <i.zaufi@gmail.com>
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+include(CMakeParseArguments)
+
+#
+# Simple helper function to install plugin and related files
+# having only a name of the plugin...
+# (just to reduce syntactic noise when a lot of plugins get installed)
+#
+function(install_pykrita_plugin name)
+ set(_options)
+ set(_one_value_args)
+ set(_multi_value_args PATTERNS FILE)
+ cmake_parse_arguments(install_pykrita_plugin "${_options}" "${_one_value_args}" \
"${_multi_value_args}" ${ARGN}) + if(NOT name)
+ message(FATAL_ERROR "Plugin filename is not given")
+ endif()
+ if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${name}.py)
+ install(FILES kritapykrita_${name}.desktop DESTINATION \
${SERVICES_INSTALL_DIR}/calligra) + foreach(_f ${name}.py ${name}.ui \
${name}_ui.rc ${install_pykrita_plugin_FILE}) + if(EXISTS \
${CMAKE_CURRENT_SOURCE_DIR}/${_f}) + install(FILES \
${CMAKE_CURRENT_SOURCE_DIR}/${_f} DESTINATION ${DATA_INSTALL_DIR}/krita/pykrita) + \
endif() + endforeach()
+ elseif(IS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/${name})
+ install(FILES ${name}/kritapykrita_${name}.desktop DESTINATION \
${SERVICES_INSTALL_DIR}/calligra) + install(
+ DIRECTORY ${name}
+ DESTINATION ${DATA_INSTALL_DIR}/krita/pykrita
+ FILES_MATCHING
+ PATTERN "*.py"
+ PATTERN "*.ui"
+ PATTERN "*_ui.rc"
+ PATTERN "__pycache__*" EXCLUDE
+ )
+ # TODO Is there any way to form a long PATTERN options string
+ # and use it in a single install() call?
+ # NOTE Install specified patterns one-by-one...
+ foreach(_pattern ${install_pykrita_plugin_PATTERNS})
+ install(
+ DIRECTORY ${name}
+ DESTINATION ${DATA_INSTALL_DIR}/krita/pykrita
+ FILES_MATCHING
+ PATTERN "${_pattern}"
+ PATTERN "__pycache__*" EXCLUDE
+ )
+ endforeach()
+ else()
+ message(FATAL_ERROR "Do not know what to do with ${name}")
+ endif()
+endfunction()
+
+install_pykrita_plugin(hello)
+
+if(PYTHON_VERSION_MAJOR VERSION_EQUAL 3)
+ install_pykrita_plugin(cmake_utils)
+ install_pykrita_plugin(js_utils PATTERNS "*.json")
+ install_pykrita_plugin(expand PATTERNS "*.expand" "templates/*.tpl")
+endif()
+
+install(
+ DIRECTORY libkritapykrita
+ DESTINATION ${DATA_INSTALL_DIR}/krita/pykrita
+ FILES_MATCHING
+ PATTERN "*.py"
+ PATTERN "__pycache__*" EXCLUDE
+ )
diff --git a/krita/plugins/extensions/pykrita/src/plugins/hello/__init__.py \
b/krita/plugins/extensions/pykrita/src/plugins/hello/__init__.py new file mode 100644
index 0000000..43dc36c
--- /dev/null
+++ b/krita/plugins/extensions/pykrita/src/plugins/hello/__init__.py
@@ -0,0 +1 @@
+# let's make a module
diff --git a/krita/plugins/extensions/pykrita/src/plugins/hello/hello.py \
b/krita/plugins/extensions/pykrita/src/plugins/hello/hello.py new file mode 100644
index 0000000..da21e93
--- /dev/null
+++ b/krita/plugins/extensions/pykrita/src/plugins/hello/hello.py
@@ -0,0 +1,5 @@
+from PyQt4.QtGui import *
+
+def hello():
+ QMessageBox.information(QWidget(), "Test", "Hello World")
+
diff --git a/krita/plugins/extensions/pykrita/src/plugins/hello/hello_ui.rc \
b/krita/plugins/extensions/pykrita/src/plugins/hello/hello_ui.rc new file mode 100644
index 0000000..fe5338e
--- /dev/null
+++ b/krita/plugins/extensions/pykrita/src/plugins/hello/hello_ui.rc
@@ -0,0 +1,8 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+<kpartgui library="hello" version="7">
+<MenuBar>
+ <Menu name="Scripts"><text>Scripts</text>
+ <Action name="hello"/>
+ </Menu>
+</MenuBar>
+</kpartgui>
diff --git a/krita/plugins/extensions/pykrita/src/plugins/hello/kritapykrita_hello.desktop \
b/krita/plugins/extensions/pykrita/src/plugins/hello/kritapykrita_hello.desktop new \
file mode 100644 index 0000000..30b4fe5
--- /dev/null
+++ b/krita/plugins/extensions/pykrita/src/plugins/hello/kritapykrita_hello.desktop
@@ -0,0 +1,7 @@
+[Desktop Entry]
+Type=Service
+ServiceTypes=Krita/PythonPlugin
+X-KDE-Library=hello
+X-Python-2-Compatible=true
+Name=Hello World
+Comment=Basic plugin to test PyKrita
diff --git a/krita/plugins/extensions/pykrita/src/pyqtpluginsettings.cpp \
b/krita/plugins/extensions/pykrita/src/pyqtpluginsettings.cpp new file mode 100644
index 0000000..589b2a4
--- /dev/null
+++ b/krita/plugins/extensions/pykrita/src/pyqtpluginsettings.cpp
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2014 Boudewijn Rempt <boud@kogmbh.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+#include "pyqtpluginsettings.h"
+
+#include "ui_manager.h"
+
+#include <QVBoxLayout>
+#include <QDialogButtonBox>
+#include <QPushButton>
+#include <QSortFilterProxyModel>
+#include <kconfiggroup.h>
+
+#include <KoIcon.h>
+
+
+#include "kis_config.h"
+
+
+PyQtPluginSettings::PyQtPluginSettings(PyKrita::Engine *engine, QWidget *parent) :
+ KisPreferenceSet(parent),
+ m_manager(new Ui::ManagerPage)
+{
+ m_manager->setupUi(this);
+
+ QSortFilterProxyModel* const proxy_model = new QSortFilterProxyModel(this);
+ proxy_model->setSourceModel(engine);
+ m_manager->pluginsList->setModel(proxy_model);
+ m_manager->pluginsList->resizeColumnToContents(0);
+ m_manager->pluginsList->sortByColumn(0, Qt::AscendingOrder);
+
+ const bool is_enabled = bool(engine);
+ const bool is_visible = !is_enabled;
+ m_manager->errorLabel->setVisible(is_visible);
+ m_manager->pluginsList->setEnabled(is_enabled);
+
+}
+
+PyQtPluginSettings::~PyQtPluginSettings()
+{
+ delete m_manager;
+}
+
+QString PyQtPluginSettings::id()
+{
+ return QString("pykritapluginmanager");
+}
+
+QString PyQtPluginSettings::name()
+{
+ return header();
+}
+
+QString PyQtPluginSettings::header()
+{
+ return QString(i18n("Python Plugin Manager"));
+}
+
+
+KIcon PyQtPluginSettings::icon()
+{
+ return koIcon("applications-development");
+}
+
+
+void PyQtPluginSettings::savePreferences() const
+{
+ emit settingsChanged();
+}
+
+void PyQtPluginSettings::loadPreferences()
+{
+}
+
+void PyQtPluginSettings::loadDefaultPreferences()
+{
+}
diff --git a/krita/plugins/extensions/pykrita/src/pyqtpluginsettings.h \
b/krita/plugins/extensions/pykrita/src/pyqtpluginsettings.h new file mode 100644
index 0000000..214f5bd
--- /dev/null
+++ b/krita/plugins/extensions/pykrita/src/pyqtpluginsettings.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2014 Boudewijn Rempt <boud@kogmbh.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+#ifndef PYQTPLUGINSETTINGS_H
+#define PYQTPLUGINSETTINGS_H
+
+#include "kis_preference_set_registry.h"
+#include "engine.h"
+
+namespace Ui
+{
+class ManagerPage;
+}
+
+class KIcon;
+
+class PyQtPluginSettings : public KisPreferenceSet
+{
+ Q_OBJECT
+public:
+
+ PyQtPluginSettings(PyKrita::Engine *engine, QWidget *parent = 0);
+ ~PyQtPluginSettings();
+
+ virtual QString id();
+ virtual QString name();
+ virtual QString header();
+ virtual KIcon icon();
+
+public Q_SLOTS:
+ void savePreferences() const;
+ void loadPreferences();
+ void loadDefaultPreferences();
+
+Q_SIGNALS:
+ void settingsChanged() const;
+
+private:
+ Ui::ManagerPage *m_manager;
+};
+
+
+class PyQtPluginSettingsUpdateRepeater : public QObject
+{
+ Q_OBJECT
+
+Q_SIGNALS:
+ void settingsUpdated();
+
+public Q_SLOTS:
+ void updateSettings() {
+ Q_EMIT settingsUpdated();
+ }
+};
+
+
+class PyQtPluginSettingsFactory : public KisAbstractPreferenceSetFactory
+{
+public:
+
+ PyQtPluginSettingsFactory(PyKrita::Engine *engine) {
+ m_engine = engine;
+ }
+
+ KisPreferenceSet* createPreferenceSet() {
+ PyQtPluginSettings* ps = new PyQtPluginSettings(m_engine);
+ QObject::connect(ps, SIGNAL(settingsChanged()), &repeater, \
SLOT(updateSettings()), Qt::UniqueConnection); + return ps;
+ }
+ virtual QString id() const {
+ return "ColorSelectorSettings";
+ }
+ PyQtPluginSettingsUpdateRepeater repeater;
+ PyKrita::Engine *m_engine;
+};
+
+
+
+
+#endif // PYQTPLUGINSETTINGS_H
diff --git a/krita/plugins/extensions/pykrita/src/test/CMakeLists.txt \
b/krita/plugins/extensions/pykrita/src/test/CMakeLists.txt new file mode 100644
index 0000000..becc691
--- /dev/null
+++ b/krita/plugins/extensions/pykrita/src/test/CMakeLists.txt
@@ -0,0 +1,13 @@
+qt4_wrap_cpp(UNIT_TESTS_HEADERS_MOC version_checker_test.h)
+
+add_executable(
+ unit_tests
+ version_checker_test.cpp
+ ${UNIT_TESTS_HEADERS_MOC}
+ )
+
+target_link_libraries(
+ unit_tests
+ ${QT_QTTEST_LIBRARY_RELEASE}
+ ${QT_QTCORE_LIBRARY_RELEASE}
+ )
diff --git a/krita/plugins/extensions/pykrita/src/test/version_checker_test.cpp \
b/krita/plugins/extensions/pykrita/src/test/version_checker_test.cpp new file mode \
100644 index 0000000..b6c8a66
--- /dev/null
+++ b/krita/plugins/extensions/pykrita/src/test/version_checker_test.cpp
@@ -0,0 +1,181 @@
+// This file is part of PyKrita, Krita' Python scripting plugin.
+//
+// Copyright (C) 2013 Alex Turbov <i.zaufi@gmail.com>
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) version 3.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public License
+// along with this library; see the file COPYING.LIB. If not, write to
+// the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+// Boston, MA 02110-1301, USA.
+
+#include "version_checker_test.h"
+#include "../version_checker.h"
+
+using namespace PyKrita;
+
+void version_checker_tests::version_ctor_test()
+{
+ {
+ version v;
+ QVERIFY(v.isValid());
+ }
+ {
+ version v(0);
+ QVERIFY(v.isValid());
+ }
+ {
+ version v(0, 0);
+ QVERIFY(v.isValid());
+ }
+ {
+ version v(0, 0, 0);
+ QVERIFY(v.isValid());
+ }
+ {
+ version v(1);
+ QVERIFY(v.isValid());
+ }
+ {
+ version v(1, 2);
+ QVERIFY(v.isValid());
+ }
+ {
+ version v(1, 2, 3);
+ QVERIFY(v.isValid());
+ }
+}
+
+void version_checker_tests::version_ops_test()
+{
+ {
+ version v1;
+ version v2;
+ QVERIFY(v1 == v2);
+ }
+ {
+ version v1;
+ version v2(1);
+ QVERIFY(v1 < v2);
+ QVERIFY(v2 > v1);
+ QVERIFY(v1 != v2);
+ QVERIFY(v1 <= v2);
+ QVERIFY(v2 >= v1);
+ }
+ {
+ version v1(0, 0, 1);
+ version v2(0, 0, 2);
+ QVERIFY(v1 < v2);
+ QVERIFY(v2 > v1);
+ QVERIFY(v1 != v2);
+ QVERIFY(v1 <= v2);
+ QVERIFY(v2 >= v1);
+ }
+ {
+ version v1(0, 0, 1);
+ version v2(0, 1, 0);
+ QVERIFY(v1 < v2);
+ QVERIFY(v2 > v1);
+ QVERIFY(v1 != v2);
+ QVERIFY(v1 <= v2);
+ QVERIFY(v2 >= v1);
+ }
+ {
+ version v1(0, 0, 1);
+ version v2(1, 0, 0);
+ QVERIFY(v1 < v2);
+ QVERIFY(v2 > v1);
+ QVERIFY(v1 != v2);
+ QVERIFY(v1 <= v2);
+ QVERIFY(v2 >= v1);
+ }
+ {
+ version v1(0, 1, 0);
+ version v2(1, 0, 0);
+ QVERIFY(v1 < v2);
+ QVERIFY(v2 > v1);
+ QVERIFY(v1 != v2);
+ QVERIFY(v1 <= v2);
+ QVERIFY(v2 >= v1);
+ }
+}
+
+void version_checker_tests::version_string_test()
+{
+ QCOMPARE(QString(version(10)), QString("10.0.0"));
+ QCOMPARE(QString(version(10, 200)), QString("10.200.0"));
+ QCOMPARE(QString(version(1, 2, 3)), QString("1.2.3"));
+ QCOMPARE(QString(version(11, 222, 3333)), QString("11.222.3333"));
+}
+
+void version_checker_tests::version_checker_test()
+{
+ {
+ version rhs;
+ version_checker chkr(version_checker::equal);
+ QVERIFY(chkr.isValid());
+ chkr.bind_second(rhs);
+ QVERIFY(chkr.isValid());
+ QVERIFY(chkr(version()));
+ }
+ {
+ version rhs(1);
+ version_checker chkr(version_checker::less);
+ QVERIFY(chkr.isValid());
+ chkr.bind_second(rhs);
+ QVERIFY(chkr.isValid());
+ QVERIFY(chkr(version(0, 1)));
+ }
+ {
+ version rhs(1);
+ version_checker chkr(version_checker::greather);
+ QVERIFY(chkr.isValid());
+ chkr.bind_second(rhs);
+ QVERIFY(chkr.isValid());
+ QVERIFY(chkr(version(1, 1)));
+ }
+}
+
+void version_checker_tests::version_checker_string_test()
+{
+ {
+ version_checker chkr = version_checker::fromString(">0.0.1");
+ QVERIFY(chkr.isValid());
+ QCOMPARE(QString(chkr.required()), QString("0.0.1"));
+ QVERIFY(chkr(version(1)));
+ }
+ {
+ version_checker chkr = version_checker::fromString(">=0.0.1");
+ QVERIFY(chkr.isValid());
+ QCOMPARE(QString(chkr.required()), QString("0.0.1"));
+ QVERIFY(chkr(version(0, 0, 1)));
+ }
+ {
+ version_checker chkr = version_checker::fromString("<=1.0.1");
+ QVERIFY(chkr.isValid());
+ QCOMPARE(QString(chkr.required()), QString("1.0.1"));
+ QVERIFY(chkr(version(0, 0, 1)));
+ }
+ {
+ version_checker chkr = version_checker::fromString("=1.0.1");
+ QVERIFY(chkr.isValid());
+ QCOMPARE(QString(chkr.required()), QString("1.0.1"));
+ QVERIFY(chkr(version(1, 0, 1)));
+ }
+ {
+ version_checker chkr = version_checker::fromString("1.0.1");
+ QVERIFY(chkr.isValid());
+ QCOMPARE(QString(chkr.required()), QString("1.0.1"));
+ QVERIFY(chkr(version(1, 0, 1)));
+ }
+}
+
+QTEST_MAIN(version_checker_tests)
diff --git a/krita/plugins/extensions/pykrita/src/test/version_checker_test.h \
b/krita/plugins/extensions/pykrita/src/test/version_checker_test.h new file mode \
100644 index 0000000..da8b3a8
--- /dev/null
+++ b/krita/plugins/extensions/pykrita/src/test/version_checker_test.h
@@ -0,0 +1,38 @@
+// This file is part of PyKrita, Krita' Python scripting plugin.
+//
+// Copyright (C) 2013 Alex Turbov <i.zaufi@gmail.com>
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) version 3.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public License
+// along with this library; see the file COPYING.LIB. If not, write to
+// the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+// Boston, MA 02110-1301, USA.
+
+#ifndef __VERSION_CHECKER_TEST_H__
+# define __VERSION_CHECKER_TEST_H__
+
+#include <QtTest/QtTest>
+
+class version_checker_tests : public QObject
+{
+ Q_OBJECT
+
+private Q_SLOTS:
+ void version_ctor_test();
+ void version_ops_test();
+ void version_string_test();
+
+ void version_checker_test();
+ void version_checker_string_test();
+};
+
+#endif // \
__VERSION_CHECKER_TEST_H__
diff --git a/krita/plugins/extensions/pykrita/src/utilities.cpp \
b/krita/plugins/extensions/pykrita/src/utilities.cpp new file mode 100644
index 0000000..b3bf79d
--- /dev/null
+++ b/krita/plugins/extensions/pykrita/src/utilities.cpp
@@ -0,0 +1,519 @@
+// This file is part of PyKrita, Krita' Python scripting plugin.
+//
+// Copyright (C) 2006 Paul Giannaros <paul@giannaros.org>
+// Copyright (C) 2012, 2013 Shaheed Haque <srhaque@theiet.org>
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) version 3, or any
+// later version accepted by the membership of KDE e.V. (or its
+// successor approved by the membership of KDE e.V.), which shall
+// act as a proxy defined in Section 6 of version 3 of the license.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library. If not, see <http://www.gnu.org/licenses/>.
+//
+
+// config.h defines PYKRITA_PYTHON_LIBRARY, the path to libpython.so
+// on the build system
+
+#include "config.h"
+#include "utilities.h"
+
+#include <algorithm>
+
+#include <Python.h>
+
+#include <QLibrary>
+#include <QString>
+#include <QStringList>
+
+#include <kconfigbase.h>
+#include <kconfiggroup.h>
+#include <klocale.h>
+
+#include <kis_debug.h>
+
+#define THREADED 1
+
+namespace PyKrita
+{
+namespace
+{
+QLibrary* s_pythonLibrary = 0;
+PyThreadState* s_pythonThreadState = 0;
+} // anonymous namespace
+
+const char* Python::PYKRITA_ENGINE = "pykrita";
+
+Python::Python()
+{
+#if THREADED
+ m_state = PyGILState_Ensure();
+#endif
+}
+
+Python::~Python()
+{
+#if THREADED
+ PyGILState_Release(m_state);
+#endif
+}
+
+bool Python::prependStringToList(PyObject* const list, const QString& value)
+{
+ PyObject* const u = unicode(value);
+ bool result = !PyList_Insert(list, 0, u);
+ Py_DECREF(u);
+ if (!result)
+ traceback(QString("Failed to prepend %1").arg(value));
+ return result;
+}
+
+bool Python::functionCall(const char* const functionName, const char* const \
moduleName) +{
+ PyObject* const result = functionCall(functionName, moduleName, PyTuple_New(0));
+ if (result)
+ Py_DECREF(result);
+ return bool(result);
+}
+
+PyObject* Python::functionCall(
+ const char* const functionName
+ , const char* const moduleName
+ , PyObject* const arguments
+)
+{
+ if (!arguments) {
+ errScript << "Missing arguments for" << moduleName << functionName;
+ return 0;
+ }
+ PyObject* const func = itemString(functionName, moduleName);
+ if (!func) {
+ errScript << "Failed to resolve" << moduleName << functionName;
+ return 0;
+ }
+ if (!PyCallable_Check(func)) {
+ traceback(QString("Not callable %1.%2").arg(moduleName).arg(functionName));
+ return 0;
+ }
+ PyObject* const result = PyObject_CallObject(func, arguments);
+ Py_DECREF(arguments);
+ if (!result)
+ traceback(QString("No result from \
%1.%2").arg(moduleName).arg(functionName)); +
+ return result;
+}
+
+bool Python::itemStringDel(const char* const item, const char* const moduleName)
+{
+ PyObject* const dict = moduleDict(moduleName);
+ const bool result = dict && PyDict_DelItemString(dict, item);
+ if (!result)
+ traceback(QString("Could not delete item string \
%1.%2").arg(moduleName).arg(item)); + return result;
+}
+
+PyObject* Python::itemString(const char* const item, const char* const moduleName)
+{
+ if (PyObject* const value = itemString(item, moduleDict(moduleName)))
+ return value;
+
+ errScript << "Could not get item string" << moduleName << item;
+ return 0;
+}
+
+PyObject* Python::itemString(const char* item, PyObject* dict)
+{
+ if (dict)
+ if (PyObject* const value = PyDict_GetItemString(dict, item))
+ return value;
+ traceback(QString("Could not get item string %1").arg(item));
+ return 0;
+}
+
+bool Python::itemStringSet(const char* const item, PyObject* const value, const \
char* const moduleName) +{
+ PyObject* const dict = moduleDict(moduleName);
+ const bool result = dict && !PyDict_SetItemString(dict, item, value);
+ if (!result)
+ traceback(QString("Could not set item string \
%1.%2").arg(moduleName).arg(item)); + return result;
+}
+
+PyObject* Python::kritaHandler(const char* const moduleName, const char* const \
handler) +{
+ if (PyObject* const module = moduleImport(moduleName))
+ return functionCall(handler, "krita", Py_BuildValue("(O)", module));
+ return 0;
+}
+
+QString Python::lastTraceback() const
+{
+ QString result;
+ result.swap(m_traceback);
+ return result;
+}
+
+void Python::libraryLoad()
+{
+ if (!s_pythonLibrary) {
+ kDebug() << "Creating s_pythonLibrary";
+ s_pythonLibrary = new QLibrary(PYKRITA_PYTHON_LIBRARY);
+ if (!s_pythonLibrary)
+ errScript << "Could not create" << PYKRITA_PYTHON_LIBRARY;
+
+ s_pythonLibrary->setLoadHints(QLibrary::ExportExternalSymbolsHint);
+ if (!s_pythonLibrary->load())
+ errScript << "Could not load" << PYKRITA_PYTHON_LIBRARY;
+
+ Py_InitializeEx(0);
+ if (!Py_IsInitialized())
+ errScript << "Could not initialise" << PYKRITA_PYTHON_LIBRARY;
+#if THREADED
+ PyEval_InitThreads();
+ s_pythonThreadState = PyGILState_GetThisThreadState();
+ PyEval_ReleaseThread(s_pythonThreadState);
+#endif
+ }
+}
+
+void Python::libraryUnload()
+{
+ if (s_pythonLibrary) {
+ // Shut the interpreter down if it has been started.
+ if (Py_IsInitialized()) {
+#if THREADED
+ PyEval_AcquireThread(s_pythonThreadState);
+#endif
+ //Py_Finalize();
+ }
+ if (s_pythonLibrary->isLoaded()) {
+ s_pythonLibrary->unload();
+ }
+ delete s_pythonLibrary;
+ s_pythonLibrary = 0;
+ }
+}
+
+PyObject* Python::moduleActions(const char* moduleName)
+{
+ return kritaHandler(moduleName, "moduleGetActions");
+}
+
+PyObject* Python::moduleConfigPages(const char* const moduleName)
+{
+ return kritaHandler(moduleName, "moduleGetConfigPages");
+}
+
+QString Python::moduleHelp(const char* moduleName)
+{
+ QString r;
+ PyObject* const result = kritaHandler(moduleName, "moduleGetHelp");
+ if (result) {
+ r = unicode(result);
+ Py_DECREF(result);
+ }
+ return r;
+}
+
+PyObject* Python::moduleDict(const char* const moduleName)
+{
+ PyObject* const module = moduleImport(moduleName);
+ if (module)
+ if (PyObject* const dictionary = PyModule_GetDict(module))
+ return dictionary;
+
+ traceback(QString("Could not get dict %1").arg(moduleName));
+ return 0;
+}
+
+PyObject* Python::moduleImport(const char* const moduleName)
+{
+ PyObject* const module = PyImport_ImportModule(moduleName);
+ if (module)
+ return module;
+
+ traceback(QString("Could not import %1").arg(moduleName));
+ return 0;
+}
+
+void* Python::objectUnwrap(PyObject* o)
+{
+ PyObject* const arguments = Py_BuildValue("(O)", o);
+ PyObject* const result = functionCall("unwrapinstance", "sip", arguments);
+ if (!result)
+ return 0;
+
+ void* const r = reinterpret_cast<void*>(ptrdiff_t(PyLong_AsLongLong(result)));
+ Py_DECREF(result);
+ return r;
+}
+
+PyObject* Python::objectWrap(void* const o, const QString& fullClassName)
+{
+ const QString classModuleName = fullClassName.section('.', 0, -2);
+ const QString className = fullClassName.section('.', -1);
+ PyObject* const classObject = itemString(PQ(className), PQ(classModuleName));
+ if (!classObject)
+ return 0;
+
+ PyObject* const arguments = Py_BuildValue("NO", PyLong_FromVoidPtr(o), \
classObject); + PyObject* const result = functionCall("wrapinstance", "sip", \
arguments); +
+ return result;
+}
+
+// Inspired by http://www.gossamer-threads.com/lists/python/python/150924.
+void Python::traceback(const QString& description)
+{
+ m_traceback.clear();
+ if (!PyErr_Occurred())
+ // Return an empty string on no error.
+ // NOTE "Return a string?" really??
+ return;
+
+ PyObject* exc_typ;
+ PyObject* exc_val;
+ PyObject* exc_tb;
+ PyErr_Fetch(&exc_typ, &exc_val, &exc_tb);
+ PyErr_NormalizeException(&exc_typ, &exc_val, &exc_tb);
+
+ // Include the traceback.
+ if (exc_tb) {
+ m_traceback = "Traceback (most recent call last):\n";
+ PyObject* const arguments = PyTuple_New(1);
+ PyTuple_SetItem(arguments, 0, exc_tb);
+ PyObject* const result = functionCall("format_tb", "traceback", arguments);
+ if (result) {
+ for (int i = 0, j = PyList_Size(result); i < j; i++) {
+ PyObject* const tt = PyList_GetItem(result, i);
+ PyObject* const t = Py_BuildValue("(O)", tt);
+ char* buffer;
+ if (!PyArg_ParseTuple(t, "s", &buffer))
+ break;
+ m_traceback += buffer;
+ }
+ Py_DECREF(result);
+ }
+ Py_DECREF(exc_tb);
+ }
+
+ // Include the exception type and value.
+ if (exc_typ) {
+ PyObject* const temp = PyObject_GetAttrString(exc_typ, "__name__");
+ if (temp) {
+ m_traceback += unicode(temp);
+ m_traceback += ": ";
+ }
+ Py_DECREF(exc_typ);
+ }
+
+ if (exc_val) {
+ PyObject* const temp = PyObject_Str(exc_val);
+ if (temp) {
+ m_traceback += unicode(temp);
+ m_traceback += "\n";
+ }
+ Py_DECREF(exc_val);
+ }
+ m_traceback += description;
+ errScript << m_traceback;
+ /// \todo How about to show it somewhere else than "console output"?
+}
+
+PyObject* Python::unicode(const QString& string)
+{
+#if PY_MAJOR_VERSION < 3
+ /* Python 2.x. http://docs.python.org/2/c-api/unicode.html */
+ PyObject* s = PyString_FromString(PQ(string));
+ PyObject* u = PyUnicode_FromEncodedObject(s, "utf-8", "strict");
+ Py_DECREF(s);
+ return u;
+#elif PY_MINOR_VERSION < 3
+ /* Python 3.2 or less. \
http://docs.python.org/3.2/c-api/unicode.html#unicode-objects */ +# ifdef \
Py_UNICODE_WIDE + return PyUnicode_DecodeUTF16((const char*)string.constData(), \
string.length() * 2, 0, 0); +# else
+ return PyUnicode_FromUnicode(string.constData(), string.length());
+# endif
+#else /* Python 3.3 or greater. \
http://docs.python.org/3.3/c-api/unicode.html#unicode-objects */ + return \
PyUnicode_FromKindAndData(PyUnicode_2BYTE_KIND, string.constData(), string.length()); \
+#endif +}
+
+QString Python::unicode(PyObject* const string)
+{
+#if PY_MAJOR_VERSION < 3
+ /* Python 2.x. http://docs.python.org/2/c-api/unicode.html */
+ if (PyString_Check(string))
+ return QString(PyString_AsString(string));
+ else if (PyUnicode_Check(string)) {
+ const int unichars = PyUnicode_GetSize(string);
+# ifdef HAVE_USABLE_WCHAR_T
+ return QString::fromWCharArray(PyUnicode_AsUnicode(string), unichars);
+# else
+# ifdef Py_UNICODE_WIDE
+ return QString::fromUcs4((const unsigned int*)PyUnicode_AsUnicode(string), \
unichars); +# else
+ return QString::fromUtf16(PyUnicode_AsUnicode(string), unichars);
+# endif
+# endif
+ } else return QString();
+#elif PY_MINOR_VERSION < 3
+ /* Python 3.2 or less. \
http://docs.python.org/3.2/c-api/unicode.html#unicode-objects */ + if \
(!PyUnicode_Check(string)) + return QString();
+
+ const int unichars = PyUnicode_GetSize(string);
+# ifdef HAVE_USABLE_WCHAR_T
+ return QString::fromWCharArray(PyUnicode_AsUnicode(string), unichars);
+# else
+# ifdef Py_UNICODE_WIDE
+ return QString::fromUcs4(PyUnicode_AsUnicode(string), unichars);
+# else
+ return QString::fromUtf16(PyUnicode_AsUnicode(string), unichars);
+# endif
+# endif
+#else /* Python 3.3 or greater. \
http://docs.python.org/3.3/c-api/unicode.html#unicode-objects */ + if \
(!PyUnicode_Check(string)) + return QString();
+
+ const int unichars = PyUnicode_GetLength(string);
+ if (0 != PyUnicode_READY(string))
+ return QString();
+
+ switch (PyUnicode_KIND(string)) {
+ case PyUnicode_1BYTE_KIND:
+ return QString::fromLatin1((const char*)PyUnicode_1BYTE_DATA(string), \
unichars); + case PyUnicode_2BYTE_KIND:
+ return QString::fromUtf16(PyUnicode_2BYTE_DATA(string), unichars);
+ case PyUnicode_4BYTE_KIND:
+ return QString::fromUcs4(PyUnicode_4BYTE_DATA(string), unichars);
+ default:
+ break;
+ }
+ return QString();
+#endif
+}
+
+bool Python::isUnicode(PyObject* const string)
+{
+#if PY_MAJOR_VERSION < 3
+ return PyString_Check(string) || PyUnicode_Check(string);
+#else
+ return PyUnicode_Check(string);
+#endif
+}
+
+void Python::updateConfigurationFromDictionary(KConfigBase* const config, PyObject* \
const dictionary) +{
+ PyObject* groupKey;
+ PyObject* groupDictionary;
+ Py_ssize_t position = 0;
+ while (PyDict_Next(dictionary, &position, &groupKey, &groupDictionary)) {
+ if (!isUnicode(groupKey)) {
+ traceback(QString("Configuration group name not a string"));
+ continue;
+ }
+ QString groupName = unicode(groupKey);
+ if (!PyDict_Check(groupDictionary)) {
+ traceback(QString("Configuration group %1 top level key not a \
dictionary").arg(groupName)); + continue;
+ }
+
+ // There is a group per module.
+ KConfigGroup group = config->group(groupName);
+ PyObject* key;
+ PyObject* value;
+ Py_ssize_t x = 0;
+ while (PyDict_Next(groupDictionary, &x, &key, &value)) {
+ if (!isUnicode(key)) {
+ traceback(QString("Configuration group %1 itemKey not a \
string").arg(groupName)); + continue;
+ }
+ PyObject* arguments = Py_BuildValue("(Oi)", value, 0);
+ PyObject* pickled = functionCall("dumps", "pickle", arguments);
+ if (pickled) {
+#if PY_MAJOR_VERSION < 3
+ QString ascii(unicode(pickled));
+#else
+ QString ascii(PyBytes_AsString(pickled));
+#endif
+ group.writeEntry(unicode(key), ascii);
+ Py_DECREF(pickled);
+ } else {
+ errScript << "Cannot write" << groupName << unicode(key) << \
unicode(PyObject_Str(value)); + }
+ }
+ }
+}
+
+void Python::updateDictionaryFromConfiguration(PyObject* const dictionary, const \
KConfigBase* const config) +{
+ kDebug() << config->groupList();
+ Q_FOREACH(QString groupName, config->groupList()) {
+ KConfigGroup group = config->group(groupName);
+ PyObject* groupDictionary = PyDict_New();
+ PyDict_SetItemString(dictionary, PQ(groupName), groupDictionary);
+ Q_FOREACH(QString key, group.keyList()) {
+ QString pickled = group.readEntry(key);
+#if PY_MAJOR_VERSION < 3
+ PyObject* arguments = Py_BuildValue("(s)", PQ(pickled));
+#else
+ PyObject* arguments = Py_BuildValue("(y)", PQ(pickled));
+#endif
+ PyObject* value = functionCall("loads", "pickle", arguments);
+ if (value) {
+ PyDict_SetItemString(groupDictionary, PQ(key), value);
+ Py_DECREF(value);
+ } else {
+ errScript << "Cannot read" << groupName << key << pickled;
+ }
+ }
+ Py_DECREF(groupDictionary);
+ }
+}
+
+bool Python::prependPythonPaths(const QString& path)
+{
+ PyObject* sys_path = itemString("path", "sys");
+ return bool(sys_path) && prependPythonPaths(path, sys_path);
+}
+
+bool Python::prependPythonPaths(const QStringList& paths)
+{
+ PyObject* sys_path = itemString("path", "sys");
+ if (!sys_path)
+ return false;
+
+ /// \todo Heh, boosts' range adaptors would be good here!
+ QStringList reversed_paths;
+ std::reverse_copy(
+ paths.begin()
+ , paths.end()
+ , std::back_inserter(reversed_paths)
+ );
+
+ Q_FOREACH(const QString & path, reversed_paths)
+ if (!prependPythonPaths(path, sys_path))
+ return false;
+
+ return true;
+}
+
+bool Python::prependPythonPaths(const QString& path, PyObject* sys_path)
+{
+ Q_ASSERT("Dir entry expected to be valid" && sys_path);
+ return bool(prependStringToList(sys_path, path));
+}
+
+} // namespace PyKrita
+
+// krita: indent-width 4;
diff --git a/krita/plugins/extensions/pykrita/src/utilities.h \
b/krita/plugins/extensions/pykrita/src/utilities.h new file mode 100644
index 0000000..5cd4a88
--- /dev/null
+++ b/krita/plugins/extensions/pykrita/src/utilities.h
@@ -0,0 +1,228 @@
+// This file is part of PyKrita, Krita' Python scripting plugin.
+//
+// A couple of useful macros and functions used inside of pykrita_engine.cpp and \
pykrita_plugin.cpp. +//
+// Copyright (C) 2006 Paul Giannaros <paul@giannaros.org>
+// Copyright (C) 2012, 2013 Shaheed Haque <srhaque@theiet.org>
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) version 3, or any
+// later version accepted by the membership of KDE e.V. (or its
+// successor approved by the membership of KDE e.V.), which shall
+// act as a proxy defined in Section 6 of version 3 of the license.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library. If not, see <http://www.gnu.org/licenses/>.
+//
+
+#ifndef __PYKRITA_UTILITIES_H__
+# define __PYKRITA_UTILITIES_H__
+
+# include <Python.h>
+# include <QString>
+
+class KConfigBase;
+
+/// Save us some ruddy time when printing out QStrings with UTF-8
+# define PQ(x) x.toUtf8().constData()
+
+namespace PyKrita
+{
+
+/**
+ * Instantiate this class on the stack to automatically get and release the
+ * GIL.
+ *
+ * Also, making all the utility functions members of this class means that in
+ * many cases the compiler tells us where the class in needed. In the remaining
+ * cases (i.e. bare calls to the Python C API), inspection is used to needed
+ * to add the requisite Python() object. To prevent this object being optimised
+ * away in these cases due to lack of use, all instances have the form of an
+ * assignment, e.g.:
+ *
+ * Python py = Python()
+ *
+ * This adds a little overhead, but this is a small price for consistency.
+ */
+class Python
+{
+public:
+ Python();
+ ~Python();
+
+ /**
+ * Load the Python interpreter.
+ */
+ static void libraryLoad();
+
+ /**
+ * Unload the Python interpreter.
+ */
+ static void libraryUnload();
+
+ /// Convert a QString to a Python unicode object.
+ static PyObject* unicode(const QString& string);
+
+ /// Convert a Python unicode object to a QString.
+ static QString unicode(PyObject* string);
+
+ /// Test if a Python object is compatible with a QString.
+ static bool isUnicode(PyObject* string);
+
+ /// Prepend a QString to a list as a Python unicode object
+ bool prependStringToList(PyObject* list, const QString& value);
+
+ /**
+ * Print and save (see @ref lastTraceback()) the current traceback in a
+ * form approximating what Python would print:
+ *
+ * Traceback (most recent call last):
+ * File "/home/shahhaqu/.kde/share/apps/krita/pykrita/pluginmgr.py", line 13, \
in <module> + * import kdeui
+ * ImportError: No module named kdeui
+ * Could not import pluginmgr.
+ */
+ void traceback(const QString& description);
+
+ /**
+ * Store the last traceback we handled using @ref traceback().
+ */
+ QString lastTraceback(void) const;
+
+ /**
+ * Create a Python dictionary from a KConfigBase instance, writing the
+ * string representation of the values.
+ */
+ void updateDictionaryFromConfiguration(PyObject* dictionary, const KConfigBase* \
config); +
+ /**
+ * Write a Python dictionary to a configuration object, converting objects
+ * to their string representation along the way.
+ */
+ void updateConfigurationFromDictionary(KConfigBase* config, PyObject* \
dictionary); +
+ /**
+ * Call the named module's named entry point.
+ */
+ bool functionCall(const char* functionName, const char* moduleName = \
PYKRITA_ENGINE); + PyObject* functionCall(const char* functionName, const char* \
moduleName, PyObject* arguments); +
+ /**
+ * Delete the item from the named module's dictionary.
+ */
+ bool itemStringDel(const char* item, const char* moduleName = PYKRITA_ENGINE);
+
+ /**
+ * Get the item from the named module's dictionary.
+ *
+ * @return 0 or a borrowed reference to the item.
+ */
+ PyObject* itemString(const char* item, const char* moduleName = PYKRITA_ENGINE);
+
+ /**
+ * Get the item from the given dictionary.
+ *
+ * @return 0 or a borrowed reference to the item.
+ */
+ PyObject* itemString(const char* item, PyObject* dict);
+
+ /**
+ * Set the item in the named module's dictionary.
+ */
+ bool itemStringSet(const char* item, PyObject* value, const char* moduleName = \
PYKRITA_ENGINE); +
+ /**
+ * Get the Actions defined by a module. The returned object is
+ * [ { function, ( text, icon, shortcut, menu ) }... ] for each module
+ * function decorated with @action.
+ *
+ * @return 0 or a new reference to the result.
+ */
+ PyObject* moduleActions(const char* moduleName);
+
+ /**
+ * Get the ConfigPages defined by a module. The returned object is
+ * [ { function, callable, ( name, fullName, icon ) }... ] for each module
+ * function decorated with @configPage.
+ *
+ * @return 0 or a new reference to the result.
+ */
+ PyObject* moduleConfigPages(const char* moduleName);
+
+ /**
+ * Get the named module's dictionary.
+ *
+ * @return 0 or a borrowed reference to the dictionary.
+ */
+ PyObject* moduleDict(const char* moduleName = PYKRITA_ENGINE);
+
+ /**
+ * Get the help text defined by a module.
+ */
+ QString moduleHelp(const char* moduleName);
+
+ /**
+ * Import the named module.
+ *
+ * @return 0 or a borrowed reference to the module.
+ */
+ PyObject* moduleImport(const char* moduleName);
+
+ /**
+ * A void * for an arbitrary Qt/KDE object that has been wrapped by SIP. Nifty.
+ *
+ * @param o The object to be unwrapped. The reference is borrowed.
+ */
+ void* objectUnwrap(PyObject* o);
+
+ /**
+ * A PyObject * for an arbitrary Qt/KDE object using SIP wrapping. Nifty.
+ *
+ * @param o The object to be wrapped.
+ * @param className The full class name of o, e.g. "PyQt4.QtGui.QWidget".
+ * @return @c 0 or a new reference to the object.
+ */
+ PyObject* objectWrap(void* o, const QString& className);
+
+ /**
+ * Add a given path to to the front of \c PYTHONPATH
+ *
+ * @param path A string (path) to be added
+ * @return @c true on success, @c false otherwise.
+ */
+ bool prependPythonPaths(const QString& path);
+
+ /**
+ * Add listed paths to to the front of \c PYTHONPATH
+ *
+ * @param paths A string list (paths) to be added
+ * @return @c true on success, @c false otherwise.
+ */
+ bool prependPythonPaths(const QStringList& paths);
+
+ static const char* PYKRITA_ENGINE;
+
+private:
+ /// @internal Helper function for @c prependPythonPaths overloads
+ bool prependPythonPaths(const QString&, PyObject*);
+ PyGILState_STATE m_state;
+ mutable QString m_traceback;
+
+ /**
+ * Run a handler function supplied by the krita module on another module.
+ *
+ * @return 0 or a new reference to the result.
+ */
+ PyObject* kritaHandler(const char* moduleName, const char* handler);
+};
+
+} // namespace PyKrita
+#endif // \
__PYKRITA_UTILITIES_H__ +// krita: indent-width 4;
diff --git a/krita/plugins/extensions/pykrita/src/version_checker.h \
b/krita/plugins/extensions/pykrita/src/version_checker.h new file mode 100644
index 0000000..bfb9c2c
--- /dev/null
+++ b/krita/plugins/extensions/pykrita/src/version_checker.h
@@ -0,0 +1,279 @@
+// This file is part of PyKrita, Krita' Python scripting plugin.
+//
+// Copyright (C) 2013 Alex Turbov <i.zaufi@gmail.com>
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) version 3.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public License
+// along with this library; see the file COPYING.LIB. If not, write to
+// the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+// Boston, MA 02110-1301, USA.
+
+#ifndef __VERSION_CHECKER_H__
+# define __VERSION_CHECKER_H__
+
+# include <QtCore/QString>
+# include <QtCore/QStringList>
+# include <QtCore/QtGlobal>
+
+namespace PyKrita
+{
+
+/**
+ * \brief Class \c version
+ */
+class version
+{
+ enum type {
+ undefined = -1
+ , zero = 0
+ };
+
+public:
+ /// Default constructor
+ explicit version(
+ const int major = zero
+ , const int minor = zero
+ , const int patch = zero
+ )
+ : m_major(major)
+ , m_minor(minor)
+ , m_patch(patch) {
+ }
+
+ int major() const {
+ return m_major;
+ }
+ int minor() const {
+ return m_minor;
+ }
+ int patch() const {
+ return m_patch;
+ }
+
+ bool isValid() const {
+ return major() != undefined && minor() != undefined && patch() != undefined;
+ }
+
+ operator QString() const {
+ return QString("%1.%2.%3").arg(major()).arg(minor()).arg(patch());
+ }
+
+ static version fromString(const QString& version_str) {
+ int tmp[3] = {zero, zero, zero};
+ QStringList parts = version_str.split('.');
+ for (
+ unsigned long i = 0
+ ; i < qMin(static_cast<unsigned long>(sizeof(tmp) / \
sizeof(int)), static_cast<unsigned long>(parts.size())) + ; ++i
+ ) {
+ bool ok;
+ const int num = parts[i].toInt(&ok);
+ if (ok)
+ tmp[i] = num;
+ else {
+ tmp[i] = undefined;
+ break;
+ }
+ }
+ return version(tmp[0], tmp[1], tmp[2]);
+ };
+
+ static version invalid() {
+ static version s_bad(undefined, undefined, undefined);
+ return s_bad;
+ }
+
+private:
+ int m_major;
+ int m_minor;
+ int m_patch;
+};
+
+inline bool operator==(const version& left, const version& right)
+{
+ return left.major() == right.major()
+ && left.minor() == right.minor()
+ && left.patch() == right.patch()
+ ;
+}
+
+inline bool operator!=(const version& left, const version& right)
+{
+ return !(left == right);
+}
+
+inline bool operator<(const version& left, const version& right)
+{
+ return left.major() < right.major()
+ || (left.major() == right.major() && left.minor() < right.minor())
+ || (left.major() == right.major() && left.minor() == right.minor() && \
left.patch() < right.patch()) + ;
+}
+
+inline bool operator>(const version& left, const version& right)
+{
+ return left.major() > right.major()
+ || (left.major() == right.major() && left.minor() > right.minor())
+ || (left.major() == right.major() && left.minor() == right.minor() && \
left.patch() > right.patch()) + ;
+}
+
+inline bool operator<=(const version& left, const version& right)
+{
+ return left == right || left < right;
+}
+
+inline bool operator>=(const version& left, const version& right)
+{
+ return left == right || left > right;
+}
+
+
+/**
+* \brief Class \c version_checker
+*/
+class version_checker
+{
+public:
+ enum operation {
+ invalid
+ , undefined
+ , less
+ , less_or_equal
+ , greather
+ , greather_or_equal
+ , not_equal
+ , equal
+ , last__
+ };
+
+ /// Default constructor
+ explicit version_checker(const operation op = invalid)
+ : m_op(op) {
+ }
+
+ bool isValid() const {
+ return m_op != invalid;
+ }
+
+ bool isEmpty() const {
+ return m_op == undefined;
+ }
+
+ void bind_second(const version& rhs) {
+ m_rhs = rhs;
+ }
+
+ bool operator()(const version& left) {
+ switch (m_op) {
+ case less:
+ return left < m_rhs;
+ case greather:
+ return left > m_rhs;
+ case equal:
+ return left == m_rhs;
+ case not_equal:
+ return left != m_rhs;
+ case less_or_equal:
+ return left <= m_rhs;
+ case greather_or_equal:
+ return left >= m_rhs;
+ default:
+ Q_ASSERT(!"Sanity check");
+ break;
+ }
+ return false;
+ }
+
+ version required() const {
+ return m_rhs;
+ }
+
+ QString operationToString() const {
+ QString result;
+ switch (m_op) {
+ case less:
+ result = " < ";
+ break;
+ case greather:
+ result = " > ";
+ break;
+ case equal:
+ result = " = ";
+ break;
+ case not_equal:
+ result = " != ";
+ break;
+ case less_or_equal:
+ result = " <= ";
+ break;
+ case greather_or_equal:
+ result = " >= ";
+ break;
+ default:
+ Q_ASSERT(!"Sanity check");
+ break;
+ }
+ return result;
+ }
+
+ static version_checker fromString(const QString& version_info) {
+ version_checker checker(invalid);
+ if (version_info.isEmpty())
+ return checker;
+
+ bool lookup_next_char = false;
+ int strip_lead_pos = 0;
+ switch (version_info.at(0).toAscii()) {
+ case '<':
+ checker.m_op = less;
+ lookup_next_char = true;
+ break;
+ case '>':
+ checker.m_op = greather;
+ lookup_next_char = true;
+ break;
+ case '=':
+ strip_lead_pos = 1;
+ checker.m_op = equal;
+ break;
+ default:
+ strip_lead_pos = 0;
+ checker.m_op = equal;
+ break;
+ }
+ if (lookup_next_char) {
+ if (version_info.at(1).toAscii() == '=') {
+ // NOTE Shift state
+ checker.m_op = operation(int(checker.m_op) + 1);
+ strip_lead_pos = 2;
+ } else {
+ strip_lead_pos = 1;
+ }
+ }
+ //
+ QString rhs_str = version_info.mid(strip_lead_pos).trimmed();
+ version rhs = version::fromString(rhs_str);
+ if (rhs.isValid())
+ checker.bind_second(rhs);
+ else
+ checker.m_op = invalid;
+ return checker;
+ }
+
+private:
+ operation m_op;
+ version m_rhs;
+};
+
+} // namespace PyKrita
+#endif // \
__VERSION_CHECKER_H__
[prev in list] [next in list] [prev in thread] [next in thread]
Configure |
About |
News |
Add a list |
Sponsored by KoreLogic