[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