[prev in list] [next in list] [prev in thread] [next in thread] 

List:       kde-commits
Subject:    [calligra/krita-scripting-rempt] krita/plugins/extensions: Initial version of a scripting plugin
From:       Boudewijn Rempt <boud () valdyas ! org>
Date:       2014-07-10 10:15:17
Message-ID: E1X5BNl-00043r-6O () scm ! kde ! org
[Download RAW message or body]

Git commit b034586a1d4cc2f270640e5fc099c4f27d625eb1 by Boudewijn Rempt.
Committed on 10/07/2014 at 10:13.
Pushed by rempt into branch 'krita-scripting-rempt'.

Initial version of a scripting plugin

This is a copy of Pate, which uses PyQt. The scripting management page
is loaded into our settings dialog.

Nothing works yet!

M  +1    -0    krita/plugins/extensions/CMakeLists.txt
A  +18   -0    krita/plugins/extensions/pyqt/CMakeLists.txt
A  +19   -0    krita/plugins/extensions/pyqt/sip/CMakeLists.txt
A  +0    -0    krita/plugins/extensions/pyqt/sip/__init__.py
A  +87   -0    krita/plugins/extensions/pyqt/sip/krita/application.sip
A  +57   -0    krita/plugins/extensions/pyqt/sip/krita/documentmanager.sip
A  +51   -0    krita/plugins/extensions/pyqt/sip/krita/kritamod.sip
A  +71   -0    krita/plugins/extensions/pyqt/sip/krita/mainwindow.sip
A  +62   -0    krita/plugins/extensions/pyqt/sip/krita/plugin.sip
A  +75   -0    krita/plugins/extensions/pyqt/sip/krita/pluginconfigpageinterface.sip
A  +47   -0    krita/plugins/extensions/pyqt/sip/krita/pluginmanager.sip
A  +39   -0    krita/plugins/extensions/pyqt/src/CMakeLists.txt
A  +2    -0    krita/plugins/extensions/pyqt/src/config.h.cmake
A  +601  -0    krita/plugins/extensions/pyqt/src/engine.cpp     [License: LGPL (v2/3)]
A  +178  -0    krita/plugins/extensions/pyqt/src/engine.h     [License: LGPL (v2/3)]
A  +298  -0    krita/plugins/extensions/pyqt/src/info.ui
A  +532  -0    krita/plugins/extensions/pyqt/src/krita/__init__.py
A  +248  -0    krita/plugins/extensions/pyqt/src/krita/gui.py
A  +7    -0    krita/plugins/extensions/pyqt/src/kritapyqt.desktop
A  +11   -0    krita/plugins/extensions/pyqt/src/kritapyqtplugin.desktop
A  +5    -0    krita/plugins/extensions/pyqt/src/kritapyqtplugin.rc
A  +104  -0    krita/plugins/extensions/pyqt/src/manager.ui
A  +54   -0    krita/plugins/extensions/pyqt/src/plugin.cpp     [License: LGPL (v2)]
A  +33   -0    krita/plugins/extensions/pyqt/src/plugin.h     [License: LGPL (v2)]
A  +75   -0    krita/plugins/extensions/pyqt/src/plugins/CMakeLists.txt
A  +78   -0    krita/plugins/extensions/pyqt/src/pyqtpluginsettings.cpp     [License: LGPL \
(v2)] A  +80   -0    krita/plugins/extensions/pyqt/src/pyqtpluginsettings.h     [License: LGPL \
(v2)] A  +543  -0    krita/plugins/extensions/pyqt/src/utilities.cpp     [License: LGPL (v2/3)]
A  +224  -0    krita/plugins/extensions/pyqt/src/utilities.h     [License: LGPL (v2/3)]

http://commits.kde.org/calligra/b034586a1d4cc2f270640e5fc099c4f27d625eb1

diff --git a/krita/plugins/extensions/CMakeLists.txt b/krita/plugins/extensions/CMakeLists.txt
index 4e1c195..583089a 100644
--- a/krita/plugins/extensions/CMakeLists.txt
+++ b/krita/plugins/extensions/CMakeLists.txt
@@ -14,6 +14,7 @@ add_subdirectory( separate_channels )
 add_subdirectory( shearimage )
 add_subdirectory( dockers )
 add_subdirectory( layergroupswitcher )
+add_subdirectory( pyqt )
 
 # Allow to skip building GMIC plugin
 option(WITH_GMIC "Build the G'Mic plugin" ON)
diff --git a/krita/plugins/extensions/pyqt/CMakeLists.txt \
b/krita/plugins/extensions/pyqt/CMakeLists.txt new file mode 100644
index 0000000..8b67475
--- /dev/null
+++ b/krita/plugins/extensions/pyqt/CMakeLists.txt
@@ -0,0 +1,18 @@
+macro_optional_find_package(PythonLibrary)
+macro_log_feature(PYTHON_LIBRARY "PythonLibrary" "Python Library" FALSE "" "Required by the \
Krita PyQt plugin") +
+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_optional_find_package(PyQt4 4.3.1)
+macro_log_feature(PYQT4_FOUND "PyQt4" "Python bindings for Qt4" FALSE "" "Required by the \
Krita PyQt plugin") +
+if (NOT EXISTS ${PYQT4_SIP_DIR}/QtCore/QtCoremod.sip)
+    message(WARNING "krita-pyqt 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(sip)
+add_subdirectory(src)
diff --git a/krita/plugins/extensions/pyqt/sip/CMakeLists.txt \
b/krita/plugins/extensions/pyqt/sip/CMakeLists.txt new file mode 100644
index 0000000..fbe486e
--- /dev/null
+++ b/krita/plugins/extensions/pyqt/sip/CMakeLists.txt
@@ -0,0 +1,19 @@
+include(SIPMacros)
+
+set(SIP_INCLUDES ${PYQT4_SIP_DIR} ${PYKDE4_SIP_DIR} ./kate)
+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 komain kritaimage kritaui \
kritalibbrush) +
+install(FILES
+    ./__init__.py
+    DESTINATION ${PYTHON_SITE_PACKAGES_INSTALL_DIR}/PyKrita4/)
+
diff --git a/krita/plugins/extensions/pyqt/sip/__init__.py \
b/krita/plugins/extensions/pyqt/sip/__init__.py new file mode 100644
index 0000000..e69de29
diff --git a/krita/plugins/extensions/pyqt/sip/krita/application.sip \
b/krita/plugins/extensions/pyqt/sip/krita/application.sip new file mode 100644
index 0000000..db9c96a
--- /dev/null
+++ b/krita/plugins/extensions/pyqt/sip/krita/application.sip
@@ -0,0 +1,87 @@
+//
+//     Copyright 2007 Jim Bublitz <jbublitz@nwinternet.com>
+//     Earlier copyrights 1998 - 2006 Jim Bublitz also apply
+
+
+//                 Generated by preSip
+//            PyKDE4 module kate  version KDE 3.92.0
+
+
+// This file is part of PyKDE4.
+
+// PyKDE4 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) any later version.
+
+// PyKDE4 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 General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+%ModuleHeaderCode
+//ctscc
+#include <application.h>
+#include <documentmanager.h>
+#include <mainwindow.h>
+#include <plugin.h>
+#include <pluginconfigpageinterface.h>
+#include <pluginmanager.h>
+%End
+
+%ModuleHeaderCode
+#include <application.h>
+%End
+
+namespace Kate
+{
+
+class Application : QObject
+{
+%TypeHeaderCode
+#include <application.h>
+%End
+
+
+public:
+                         Application (void* /In/);
+
+public:
+    Kate::DocumentManager* documentManager ();
+    Kate::PluginManager* pluginManager ();
+    KTextEditor::Editor* editor ();
+    Kate::MainWindow*    activeMainWindow ();
+    const QList<Kate::MainWindow*>& mainWindows () const;
+
+// Subclasses for QObject
+
+public:
+%ConvertToSubClassCode
+
+    if (dynamic_cast<Kate::Application*>(sipCpp))
+        sipClass = sipClass_Kate_Application;
+    else if (dynamic_cast<Kate::DocumentManager*>(sipCpp))
+        sipClass = sipClass_Kate_DocumentManager;
+    else if (dynamic_cast<Kate::MainWindow*>(sipCpp))
+        sipClass = sipClass_Kate_MainWindow;
+    else if (dynamic_cast<Kate::Plugin*>(sipCpp))
+        sipClass = sipClass_Kate_Plugin;
+    else if (dynamic_cast<Kate::PluginConfigPage*>(sipCpp))
+        sipClass = sipClass_Kate_PluginConfigPage;
+    else if (dynamic_cast<Kate::PluginManager*>(sipCpp))
+        sipClass = sipClass_Kate_PluginManager;
+    else if (dynamic_cast<Kate::PluginView*>(sipCpp))
+        sipClass = sipClass_Kate_PluginView;
+    else
+        sipClass = NULL;
+%End
+
+
+};  // class Application
+
+Kate::Application*   application ();
+};  // namespace Kate
+
diff --git a/krita/plugins/extensions/pyqt/sip/krita/documentmanager.sip \
b/krita/plugins/extensions/pyqt/sip/krita/documentmanager.sip new file mode 100644
index 0000000..b66d9a5
--- /dev/null
+++ b/krita/plugins/extensions/pyqt/sip/krita/documentmanager.sip
@@ -0,0 +1,57 @@
+//
+//     Copyright 2007 Jim Bublitz <jbublitz@nwinternet.com>
+//     Earlier copyrights 1998 - 2006 Jim Bublitz also apply
+
+
+//                 Generated by preSip
+//            PyKDE4 module kate  version KDE 3.92.0
+
+
+// This file is part of PyKDE4.
+
+// PyKDE4 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) any later version.
+
+// PyKDE4 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 General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+%ModuleHeaderCode
+#include <documentmanager.h>
+%End
+
+namespace Kate
+{
+
+class DocumentManager : QObject
+{
+%TypeHeaderCode
+#include <documentmanager.h>
+%End
+
+
+public:
+                         DocumentManager (void* /In/);
+
+public:
+    const QList<KTextEditor::Document*>& documents () const;
+    KTextEditor::Document* findUrl (const KUrl&) const;
+    KTextEditor::Document* openUrl (const KUrl&, const QString& = QString ());
+    bool                 closeDocument (KTextEditor::Document*);
+
+signals:
+    void                 documentCreated (KTextEditor::Document*);
+    void                 documentWillBeDeleted (KTextEditor::Document*);
+    void                 documentDeleted (KTextEditor::Document*);
+
+};  // class DocumentManager
+
+Kate::DocumentManager* documentManager ();
+};  // namespace Kate
+
diff --git a/krita/plugins/extensions/pyqt/sip/krita/kritamod.sip \
b/krita/plugins/extensions/pyqt/sip/krita/kritamod.sip new file mode 100644
index 0000000..32a4be7
--- /dev/null
+++ b/krita/plugins/extensions/pyqt/sip/krita/kritamod.sip
@@ -0,0 +1,51 @@
+//
+//     Copyright 2007 Jim Bublitz <jbublitz@nwinternet.com>
+//     Earlier copyrights 1998 - 2006 Jim Bublitz also apply
+
+
+//                 Generated by preSip
+//            PyKDE4 module kate  version KDE_3_92_0
+
+%Copying
+
+ This file is part of PyKDE4.
+
+ PyKDE4 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) any later version.
+
+ PyKDE4 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 General Public License
+ along with this program.  If not, see <http:www.gnu.org/licenses/>.
+
+%End
+
+%Module PyKate4.kate
+
+%ModuleHeaderCode
+#pragma GCC visibility push(default)
+%End
+
+%Import QtCore/QtCoremod.sip
+%Import QtGui/QtGuimod.sip
+%Import QtXml/QtXmlmod.sip
+%Import kdecore/kdecoremod.sip
+//%Import kdefx/kdefxmod.sip
+%Import kdeui/kdeuimod.sip
+%Import kio/kiomod.sip
+// %Import kutils/kutilsmod.sip
+%Import kparts/kpartsmod.sip
+%Import ktexteditor/ktexteditormod.sip
+
+%Include application.sip
+%Include documentmanager.sip
+%Include mainwindow.sip
+%Include plugin.sip
+%Include pluginconfigpageinterface.sip
+%Include pluginmanager.sip
+
diff --git a/krita/plugins/extensions/pyqt/sip/krita/mainwindow.sip \
b/krita/plugins/extensions/pyqt/sip/krita/mainwindow.sip new file mode 100644
index 0000000..55121b5
--- /dev/null
+++ b/krita/plugins/extensions/pyqt/sip/krita/mainwindow.sip
@@ -0,0 +1,71 @@
+//
+//     Copyright 2007 Jim Bublitz <jbublitz@nwinternet.com>
+//     Earlier copyrights 1998 - 2006 Jim Bublitz also apply
+
+
+//                 Generated by preSip
+//            PyKDE4 module kate  version KDE 3.92.0
+
+
+// This file is part of PyKDE4.
+
+// PyKDE4 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) any later version.
+
+// PyKDE4 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 General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+namespace Kate
+{
+
+class MainWindow : QObject
+{
+%TypeHeaderCode
+#include <mainwindow.h>
+%End
+
+
+public:
+                         MainWindow (void* /In/);
+
+public:
+    KXMLGUIFactory*      guiFactory () const;
+    QWidget*             window () const;
+    QWidget*             centralWidget () const;
+
+public:
+    KTextEditor::View*   activeView ();
+    KTextEditor::View*   activateView (KTextEditor::Document*);
+    KTextEditor::View*   openUrl (const KUrl&, const QString& = QString ());
+    // http://kde.6490.n7.nabble.com/Can-I-know-the-project-configuration-from-python-plugin-td1523455.html
 +    Kate::PluginView*    pluginView (const QString&);
+
+signals:
+    void                 viewChanged ();
+
+public:
+
+    enum Position
+    {
+        Left, 
+        Right, 
+        Top, 
+        Bottom
+    };
+
+    QWidget*             createToolView (const QString&, Kate::MainWindow::Position, const \
QPixmap&, const QString&); +    bool                 moveToolView (QWidget*, \
Kate::MainWindow::Position); +    bool                 showToolView (QWidget*);
+    bool                 hideToolView (QWidget*);
+
+};  // class MainWindow
+
+};  // namespace Kate
+
diff --git a/krita/plugins/extensions/pyqt/sip/krita/plugin.sip \
b/krita/plugins/extensions/pyqt/sip/krita/plugin.sip new file mode 100644
index 0000000..b16a4de
--- /dev/null
+++ b/krita/plugins/extensions/pyqt/sip/krita/plugin.sip
@@ -0,0 +1,62 @@
+//
+//     Copyright 2007 Jim Bublitz <jbublitz@nwinternet.com>
+//     Earlier copyrights 1998 - 2006 Jim Bublitz also apply
+
+
+//                 Generated by preSip
+//            PyKDE4 module kate  version KDE 3.92.0
+
+
+// This file is part of PyKDE4.
+
+// PyKDE4 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) any later version.
+
+// PyKDE4 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 General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+
+namespace Kate
+{
+
+class Plugin : QObject
+{
+%TypeHeaderCode
+#include <plugin.h>
+%End
+
+public:
+                         Plugin (Kate::Application* = 0, const char* = 0);
+    Kate::Application*   application () const;
+    virtual Kate::PluginView* createView (Kate::MainWindow*);
+    virtual void         readSessionConfig (KConfigBase*, const QString&);
+    virtual void         writeSessionConfig (KConfigBase*, const QString&);
+
+};  // class Plugin
+
+Kate::Plugin*        createPlugin (const char*, Kate::Application* = 0, const QStringList& = \
QStringList ()); +
+class PluginView : QObject
+{
+%TypeHeaderCode
+#include <plugin.h>
+%End
+
+public:
+                         PluginView (Kate::MainWindow*);
+    Kate::MainWindow*    mainWindow () const;
+    virtual void         readSessionConfig (KConfigBase*, const QString&);
+    virtual void         writeSessionConfig (KConfigBase*, const QString&);
+
+};  // class PluginView
+
+};  // namespace Kate
+
+
diff --git a/krita/plugins/extensions/pyqt/sip/krita/pluginconfigpageinterface.sip \
b/krita/plugins/extensions/pyqt/sip/krita/pluginconfigpageinterface.sip new file mode 100644
index 0000000..8328dbc
--- /dev/null
+++ b/krita/plugins/extensions/pyqt/sip/krita/pluginconfigpageinterface.sip
@@ -0,0 +1,75 @@
+//
+//     Copyright 2007 Jim Bublitz <jbublitz@nwinternet.com>
+//     Earlier copyrights 1998 - 2006 Jim Bublitz also apply
+
+
+//                 Generated by preSip
+//            PyKDE4 module kate  version KDE 3.92.0
+
+
+// This file is part of PyKDE4.
+
+// PyKDE4 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) any later version.
+
+// PyKDE4 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 General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+%ModuleHeaderCode
+#include <pluginconfigpageinterface.h>
+%End
+
+namespace Kate
+{
+
+class PluginConfigPage : QWidget
+{
+%TypeHeaderCode
+#include <pluginconfigpageinterface.h>
+%End
+
+
+public:
+                         PluginConfigPage (QWidget* = 0, const char* = 0);
+
+public:
+    virtual void         apply ()  = 0;
+    virtual void         reset ()  = 0;
+    virtual void         defaults ()  = 0;
+
+signals:
+    void                 changed ();
+
+};  // class PluginConfigPage
+
+
+class PluginConfigPageInterface
+{
+%TypeHeaderCode
+#include <pluginconfigpageinterface.h>
+%End
+
+
+public:
+                         PluginConfigPageInterface ();
+    uint                 pluginConfigPageInterfaceNumber () const;
+
+public:
+    virtual uint         configPages () const = 0;
+    virtual Kate::PluginConfigPage* configPage (uint = 0, QWidget* = 0, const char* = 0)  = 0;
+    virtual QString      configPageName (uint = 0) const = 0;
+    virtual QString      configPageFullName (uint = 0) const = 0;
+    virtual KIcon        configPageIcon (uint = 0) const = 0;
+
+};  // class PluginConfigPageInterface
+
+Kate::PluginConfigPageInterface* pluginConfigPageInterface (Kate::Plugin*);
+};  // namespace Kate
+
diff --git a/krita/plugins/extensions/pyqt/sip/krita/pluginmanager.sip \
b/krita/plugins/extensions/pyqt/sip/krita/pluginmanager.sip new file mode 100644
index 0000000..cc09c87
--- /dev/null
+++ b/krita/plugins/extensions/pyqt/sip/krita/pluginmanager.sip
@@ -0,0 +1,47 @@
+//
+//     Copyright 2007 Jim Bublitz <jbublitz@nwinternet.com>
+//     Earlier copyrights 1998 - 2006 Jim Bublitz also apply
+
+
+//                 Generated by preSip
+//            PyKDE4 module kate  version KDE 3.92.0
+
+
+// This file is part of PyKDE4.
+
+// PyKDE4 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) any later version.
+
+// PyKDE4 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 General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+namespace Kate
+{
+
+class PluginManager : QObject
+{
+%TypeHeaderCode
+#include <pluginmanager.h>
+%End
+
+
+public:
+                         PluginManager (void* /In/);
+
+public:
+    Kate::Plugin*        plugin (const QString&);
+    bool                 pluginAvailable (const QString&);
+    Kate::Plugin*        loadPlugin (const QString&, bool = 1);
+    void                 unloadPlugin (const QString&, bool = 1);
+
+};  // class PluginManager
+
+};  // namespace Kate
+
diff --git a/krita/plugins/extensions/pyqt/src/CMakeLists.txt \
b/krita/plugins/extensions/pyqt/src/CMakeLists.txt new file mode 100644
index 0000000..de2c31a
--- /dev/null
+++ b/krita/plugins/extensions/pyqt/src/CMakeLists.txt
@@ -0,0 +1,39 @@
+# 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
+)
+
+kde4_add_ui_files(SOURCES 
+    info.ui 
+    manager.ui
+)
+
+kde4_add_plugin(kritapyqt ${SOURCES})
+
+target_link_libraries(
+    kritapyqt
+    ${PYTHON_LIBRARY}
+    kritaui
+    kritalibbrush
+  )
+
+install(TARGETS kritapyqt 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/pyqt
+    FILES_MATCHING PATTERN "*.py"
+)
+
+# Dive into plugins crowd
+#add_subdirectory(plugins)
+
diff --git a/krita/plugins/extensions/pyqt/src/config.h.cmake \
b/krita/plugins/extensions/pyqt/src/config.h.cmake new file mode 100644
index 0000000..4c7bc72
--- /dev/null
+++ b/krita/plugins/extensions/pyqt/src/config.h.cmake
@@ -0,0 +1,2 @@
+#define KRITA_PYTHON_LIBRARY "${PYTHON_LIBRARY}"
+#define KRITA_PYTHON_SITE_PACKAGES_INSTALL_DIR "${PYTHON_SITE_PACKAGES_INSTALL_DIR}"
diff --git a/krita/plugins/extensions/pyqt/src/engine.cpp \
b/krita/plugins/extensions/pyqt/src/engine.cpp new file mode 100644
index 0000000..5d73ac9
--- /dev/null
+++ b/krita/plugins/extensions/pyqt/src/engine.cpp
@@ -0,0 +1,601 @@
+// This file is part of Pate, Kate' Python scripting plugin.
+//
+// Copyright (C) 2006 Paul Giannaros <paul@giannaros.org>
+// Copyright (C) 2012 Shaheed Haque <srhaque@theiet.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.
+
+#include "engine.h"
+
+// config.h defines PATE_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>
+#include <KDebug>
+#include <KGlobal>
+#include <KLocalizedString>
+#include <KServiceTypeTrader>
+#include <KStandardDirs>
+
+#include <kate/application.h>
+
+/// Name of the file where per-plugin configuration is stored.
+#define CONFIG_FILE "katepaterc"
+
+#if PY_MAJOR_VERSION < 3
+# define PATE_INIT initpate
+#else
+# define PATE_INIT PyInit_pate
+#endif
+
+PyMODINIT_FUNC PATE_INIT();                                 // fwd decl
+
+namespace {
+PyObject* s_pate;
+/**
+ * \attention Kate 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).
+ */
+Pate::Engine* s_engine_instance = 0;
+
+/**
+ * Wrapper function, called exolicitly 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(Pate::Engine* const engine)
+{
+    assert("Sanity check" && !s_engine_instance);
+    s_engine_instance = engine;
+    // Call initialize explicitly to initialize embedded interpreter.
+    PATE_INIT();
+}
+
+/**
+ * Functions for the Python module called pate.
+ * \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 kate really going to exit? It would be better to \b deprecate
+ * this (harmful) function!
+ */
+PyObject* pateSaveConfiguration(PyObject* /*self*/, PyObject* /*unused*/)
+{
+    if (s_engine_instance)
+        s_engine_instance->saveGlobalPluginsConfiguration();
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+PyMethodDef pateMethods[] =
+{
+    {
+        "saveConfiguration"
+      , &pateSaveConfiguration
+      , METH_NOARGS
+      , "Save the configuration of the plugin into " CONFIG_FILE
+    }
+  , { 0, 0, 0, 0 }
+};
+
+}                                                           // anonymous namespace
+
+//BEGIN Python module registration
+PyMODINIT_FUNC PATE_INIT()
+{
+#if PY_MAJOR_VERSION < 3
+    s_pate = Py_InitModule3("pate", pateMethods, "The pate module");
+    PyModule_AddStringConstant(s_pate, "__file__", __FILE__);
+#else
+    static struct PyModuleDef moduledef =
+    {
+        PyModuleDef_HEAD_INIT
+      , "pate"
+      , "The pate module"
+      , -1
+      , pateMethods
+      , 0
+      , 0
+      , 0
+      , 0
+    };
+    s_pate = PyModule_Create(&moduledef);
+    PyModule_AddStringConstant(s_pate, "__file__", __FILE__);
+    return s_pate;
+#endif
+}
+//END Python module registration
+
+
+//BEGIN Pate::Engine::PluginState
+Pate::Engine::PluginState::PluginState()
+  : m_enabled(false)
+  , m_broken(false)
+  , m_isDir(false)
+{
+}
+//END Pate::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!
+ */
+Pate::Engine::Engine()
+  : m_configuration(0)
+  , m_sessionConfiguration(0)
+  , m_engineIsUsable(false)
+  , m_pluginsLoaded(false)
+{
+}
+
+Pate::Engine::~Engine()
+{
+    kDebug() << "Destroy the Python engine";
+    if (m_configuration)
+    {
+        /// \todo Do we \b really need to write smth??
+        /// Isn't it already written??
+        saveGlobalPluginsConfiguration();
+        Py_DECREF(m_configuration);
+    }
+    if (m_sessionConfiguration)
+        Py_DECREF(m_sessionConfiguration);
+
+    Python::libraryUnload();
+    s_engine_instance = 0;
+}
+
+/**
+ * \todo Make sure noone tries to use uninitialized engine!
+ * (Or enable exceptions for this module, so this case wouldn't even araise?)
+ */
+QString Pate::Engine::tryInitializeGetFailureReason()
+{
+    kDebug() << "Construct the Python engine for Python" << PY_MAJOR_VERSION << \
PY_MINOR_VERSION; +    if (0 != PyImport_AppendInittab(Python::PATE_ENGINE, PATE_INIT))
+        return i18nc("@info:tooltip ", "Cannot load built-in <icode>pate</icode> module");
+
+    Python::libraryLoad();
+    Python py = Python();
+
+    // Update PYTHONPATH
+    // 0) custom plugin directories (prefer local dir over systems')
+    // 1) shipped kate module's dir
+    // 2) w/ site_packages/ dir of the Python
+    QStringList pluginDirectories = KGlobal::dirs()->findDirs("appdata", "pate/");
+    pluginDirectories
+      << KStandardDirs::locate("appdata", "plugins/pate/")
+      << QLatin1String(PATE_PYTHON_SITE_PACKAGES_INSTALL_DIR)
+      ;
+    kDebug() << "Plugin Directories: " << pluginDirectories;
+    if (!py.prependPythonPaths(pluginDirectories))
+        return i18nc("@info:tooltip ", "Can not update Python paths");
+
+    // Show some SPAM
+    /// \todo Add <em>"About Python"</em> to Help menu or <em>System Info</em> tab
+    /// to the plugin configuration, so users (and plugin authors) can get a path
+    /// list (and probably other (interestring) system details) w/o reading a lot
+    /// of SPAM from terminal or \c ~/.xsession_errors file.
+    if (PyObject* sysPath = py.itemString("path", "sys"))
+    {
+        Py_ssize_t len = PyList_Size(sysPath);
+        for (Py_ssize_t i = 0; i < len; i++)
+        {
+            PyObject* path = PyList_GetItem(sysPath, i);
+            kDebug() << "sys.path" << i << Python::unicode(path);
+        }
+    }
+
+    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"
+    );
+
+    // Initialise our built-in module.
+    pythonInitwrapper(this);
+    if (!s_pate)
+        return i18nc("@info:tooltip ", "No <icode>pate</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);
+
+    // Load the kate module, but find it first, and verify it loads.
+    PyObject* katePackage = py.moduleImport("kate");
+    if (!katePackage)
+        return i18nc("@info:tooltip ", "Cannot load <icode>kate</icode> module");
+
+    // NOTE Empty failure reson string indicates success!
+    m_engineIsUsable = true;
+    return QString();
+}
+
+int Pate::Engine::columnCount(const QModelIndex&) const
+{
+    return Column::LAST__;
+}
+
+int Pate::Engine::rowCount(const QModelIndex&) const
+{
+    return m_plugins.size();
+}
+
+QModelIndex Pate::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 Pate::Engine::parent(const QModelIndex&) const
+{
+    return QModelIndex();
+}
+
+QVariant Pate::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 Pate::Engine::data(const QModelIndex& index, const int role) const
+{
+    assert("Sanity check" && index.row() < m_plugins.size());
+    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()].m_enabled
+                  && !m_plugins[index.row()].m_broken
+                  ;
+                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;
+        default:
+            break;
+    }
+    return QVariant();
+}
+
+Qt::ItemFlags Pate::Engine::flags(const QModelIndex& index) const
+{
+    assert("Sanity check" && index.row() < m_plugins.size());
+    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()].m_broken)
+        result |= Qt::ItemIsEnabled;
+    return static_cast<Qt::ItemFlag>(result);
+}
+
+bool Pate::Engine::setData(const QModelIndex& index, const QVariant& value, const int role)
+{
+    assert("Sanity check" && index.row() < m_plugins.size());
+
+    if (role == Qt::CheckStateRole)
+    {
+        kDebug() << "value.toBool()=" << value.toBool();
+        m_plugins[index.row()].m_enabled = value.toBool();
+    }
+    return true;
+}
+
+QStringList Pate::Engine::enabledPlugins() const
+{
+    QStringList result;
+    Q_FOREACH(const PluginState& plugin, m_plugins)
+        if (plugin.m_enabled)
+            result.append(plugin.m_service->name());
+    return result;
+}
+
+void Pate::Engine::readGlobalPluginsConfiguration()
+{
+    Python py = Python();
+    PyDict_Clear(m_configuration);
+    KConfig config(CONFIG_FILE, KConfig::SimpleConfig);
+    py.updateDictionaryFromConfiguration(m_configuration, &config);
+}
+
+void Pate::Engine::saveGlobalPluginsConfiguration()
+{
+    Python py = Python();
+    KGlobal::config()->sync();
+    KConfig config(CONFIG_FILE, KConfig::SimpleConfig);
+    py.updateConfigurationFromDictionary(&config, m_configuration);
+    config.sync();
+}
+
+void Pate::Engine::readSessionPluginsConfiguration(KConfigBase* const config)
+{
+    PyDict_Clear(m_sessionConfiguration);
+    Python().updateDictionaryFromConfiguration(m_sessionConfiguration, config);
+}
+
+void Pate::Engine::writeSessionPluginsConfiguration(KConfigBase* const config)
+{
+    Python().updateConfigurationFromDictionary(config, m_sessionConfiguration);
+}
+
+void Pate::Engine::tryLoadEnabledPlugins(const QStringList& enabled_plugins)
+{
+    m_plugins.clear();                                      // Clear current state.
+
+    // Collect plugins available
+    KService::List services;
+    KServiceTypeTrader* trader = KServiceTypeTrader::self();
+
+    kDebug() << "Seeking for installed plugins...";
+    services = trader->query("Kate/PythonPlugin");
+    Q_FOREACH(KService::Ptr service, services)
+    {
+        kDebug() << "Got Kate/PythonPlugin: " << service->name()
+          << ", module-path=" << service->library()
+          ;
+        // Make sure mandatory properties are here
+        if (service->name().isEmpty())
+        {
+            kDebug() << "Ignore desktop file w/o a name";
+            continue;
+        }
+        if (service->library().isEmpty())
+        {
+            kDebug() << "Ignore desktop file w/o a module to import";
+            continue;
+        }
+
+        // Check Python compatibility
+        const bool is_compatible = service->property(
+#if PY_MAJOR_VERSION < 3
+            "X-Python-2-Compatible"
+#else
+            "X-Python-3-Compatible"
+#endif
+          , QVariant::Bool
+          ).toBool();
+
+        if (!is_compatible)
+        {
+            kDebug() << service->name() << "is incompatible w/ embedded Python version";
+            continue;
+        }
+
+        // Make a new state
+        PluginState plugin;
+        plugin.m_service = service;
+        plugin.m_enabled = enabled_plugins.indexOf(service->name()) != -1;
+
+        // Find the module:
+        // 0) try to locate directory based plugin first
+        KUrl rel_path = QString(Python::PATE_ENGINE);
+        rel_path.addPath(plugin.moduleFilePathPart());
+        rel_path.addPath("__init__.py");
+        QString module_path = KGlobal::dirs()->findResource("appdata", \
rel_path.toLocalFile()); +        if (module_path.isEmpty())
+        {
+            // 1) Nothing found, then try file based plugin
+            rel_path = QString(Python::PATE_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>"
+              , service->library()
+              );
+        }
+        else kDebug() << "Found module path:" << module_path;
+
+        // Try to check dependencies. To do it
+        // just try to import a module... when unload it ;)
+        /// \todo Full featured dependencies checker can be implemented
+        /// as a Python module (special named or listed as a property in
+        /// a \c .desktop file ;-)
+        const QStringList dependencies = service->property(
+            "X-Python-Dependencies"
+          , QVariant::StringList
+          ).toStringList();
+        Python py = Python();
+        Q_FOREACH(const QString& dep, dependencies)
+        {
+            kDebug() << "Try import dependency module/package:" << dep;
+            PyObject* module = py.moduleImport(PQ(dep));
+            if (module)
+                Py_DECREF(module);
+            else
+            {
+                plugin.m_broken = true;
+                plugin.m_errorReason = i18nc(
+                    "@info:tooltip"
+                  , "Failure on checking dependency <application>%1</application>:<nl/>%2"
+                  , dep
+                  , py.lastTraceback()
+                  );
+            }
+        }
+
+        m_plugins.append(plugin);
+    }
+
+    reloadEnabledPlugins();
+}
+
+void Pate::Engine::reloadEnabledPlugins()
+{
+    unloadModules();
+    loadModules();
+}
+
+/**
+ * Walk over the model, loading all usable plugins into a PyObject module
+ * dictionary.
+ */
+void Pate::Engine::loadModules()
+{
+    if (m_pluginsLoaded)
+        return;
+
+    kDebug() << "Loading enabled python modules";
+
+    // Add two lists to the module: pluginDirectories and plugins.
+    Python py = Python();
+    PyObject* pluginDirectories = PyList_New(0);
+    Py_INCREF(pluginDirectories);
+    py.itemStringSet("pluginDirectories", pluginDirectories);
+    PyObject* plugins = PyList_New(0);
+    Py_INCREF(plugins);
+    py.itemStringSet("plugins", plugins);
+
+    for (
+        QList<PluginState>::iterator it = m_plugins.begin()
+      , last = m_plugins.end()
+      ; it != last
+      ; ++it
+      )
+    {
+        PluginState& plugin = *it;
+        QString module_name = plugin.pythonModuleName();
+        kDebug() << "Loading module: " << module_name;
+
+        // Were we asked to load this plugin?
+        if (plugin.m_enabled)
+        {
+            // Import and add to pate.plugins
+            PyObject* module = py.moduleImport(PQ(module_name));
+            if (module)
+            {
+                PyList_Append(plugins, module);
+                Py_DECREF(module);
+            }
+            else
+            {
+                plugin.m_broken = true;
+                plugin.m_errorReason = i18nc(
+                    "@info:tooltip"
+                  , "Module not loaded:<nl/>%1"
+                  , py.lastTraceback()
+                  );
+            }
+        }
+    }
+
+    m_pluginsLoaded = true;
+
+    // everything is loaded and started. Call the module's init callback
+    py.functionCall("_pluginsLoaded");
+}
+
+void Pate::Engine::unloadModules()
+{
+    // We don't have the luxury of being able to unload Python easily while
+    // Kate is running. If anyone can find a way, feel free to tell me and
+    // I'll patch it in. Calling Py_Finalize crashes.
+    // So, clean up the best that we can.
+    if (!m_pluginsLoaded)
+        return;
+
+    kDebug() << "Unloading python modules";
+
+    // Remove each plugin from sys.modules
+    Python py = Python();
+    PyObject* modules = PyImport_GetModuleDict();
+    PyObject* plugins = py.itemString("plugins");
+    if (plugins)
+    {
+        for (Py_ssize_t i = 0, j = PyList_Size(plugins); i < j; ++i)
+        {
+            PyObject* pluginName = py.itemString("__name__", \
PyModule_GetDict(PyList_GetItem(plugins, i))); +            if (pluginName && \
PyDict_Contains(modules, pluginName)) +            {
+                PyDict_DelItem(modules, pluginName);
+                kDebug() << "Deleted" << Python::unicode(pluginName) << "from sys.modules";
+            }
+        }
+        py.itemStringDel("plugins");
+        Py_DECREF(plugins);
+    }
+    m_pluginsLoaded = false;
+    py.functionCall("_pluginsUnloaded");
+}
+
+// kate: space-indent on; indent-width 4;
+#undef PATE_INIT
diff --git a/krita/plugins/extensions/pyqt/src/engine.h \
b/krita/plugins/extensions/pyqt/src/engine.h new file mode 100644
index 0000000..f32893b
--- /dev/null
+++ b/krita/plugins/extensions/pyqt/src/engine.h
@@ -0,0 +1,178 @@
+// This file is part of Pate, Kate' Python scripting plugin.
+//
+// Copyright (C) 2006 Paul Giannaros <paul@giannaros.org>
+// Copyright (C) 2012 Shaheed Haque <srhaque@theiet.org>
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) version 3.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public License
+// along with this library; see the file COPYING.LIB.  If not, write to
+// the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+// Boston, MA 02110-1301, USA.
+
+#ifndef __PATE_ENGINE_H__
+# define  __PATE_ENGINE_H__
+
+# include <KService>
+# include <KUrl>
+# include <Python.h>
+# include <QAbstractItemModel>
+# include <QList>
+# include <QStringList>
+
+namespace Pate {
+class Python;                                               // fwd decl
+
+/**
+ * The Engine class hosts the Python interpreter, loading
+ * it into memory within Kate, and then with finding and
+ * loading all of the Pate 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;
+        //@}
+
+    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_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*);
+
+    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 +
+public Q_SLOTS:
+    void readGlobalPluginsConfiguration();                  ///< Load plugins' configuration.
+    void saveGlobalPluginsConfiguration();                  ///< Write out plugins' \
configuration. +    /// Scan for plugins and load only explicitly enabled
+    void tryLoadEnabledPlugins(const QStringList&);
+    void reloadEnabledPlugins();                            ///< (re)Load the configured \
modules. +
+protected:
+    /// Load enabled modules (a real implementation)
+    void loadModules();
+    /// Unload enabled modules
+    void unloadModules();
+
+private:
+    // Simulate strong typed enums from C++11
+    struct Column
+    {
+        enum type
+        {
+            NAME
+          , COMMENT
+          , LAST__
+        };
+    };
+
+    /**
+     * The root configuration used by Pate's Python objects. It is a Python
+     * dictionary.
+     */
+    PyObject* m_configuration;
+    PyObject* m_sessionConfiguration;
+    QList<PluginState> m_plugins;
+    bool m_engineIsUsable;
+    bool m_pluginsLoaded;
+};
+
+inline QString Engine::PluginState::pythonModuleName() const
+{
+    return m_service->library();
+}
+inline QString Pate::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 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;
+}
+
+}                                                           // namespace Pate
+#endif                                                      //  __PATE_ENGINE_H__
+// kate: indent-width 4;
diff --git a/krita/plugins/extensions/pyqt/src/info.ui \
b/krita/plugins/extensions/pyqt/src/info.ui new file mode 100644
index 0000000..2edc253
--- /dev/null
+++ b/krita/plugins/extensions/pyqt/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/pyqt/src/krita/__init__.py \
b/krita/plugins/extensions/pyqt/src/krita/__init__.py new file mode 100644
index 0000000..a8f2eb1
--- /dev/null
+++ b/krita/plugins/extensions/pyqt/src/krita/__init__.py
@@ -0,0 +1,532 @@
+# -*- encoding: utf-8 -*-
+# This file is part of Pate, Kate' Python scripting plugin.
+#
+# Copyright (C) 2006 Paul Giannaros <paul@giannaros.org>
+# Copyright (C) 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 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.
+
+"""Namespace for Kate application interfaces.
+
+This package provides support for Python plugin developers for the Kate
+editor. In addition to the C++ APIs exposed via PyKDE4.ktexteditor and
+PyKate4.kate, Python plugins can:
+
+    - Hook into Kate's menus and shortcuts.
+    - Have Kate configuration pages.
+    - Use Python pickling to save configuration data, so arbitrary Python
+      objects can be part of a plugin's configuration.
+    - Use threaded Python.
+    - Use Python2 or Python3 (from Kate 4.11 onwards), depending on how Kate
+      was built.
+    - Support i18n.
+
+Also, plugins are found using a search path. This is intended to encourage
+user-hacking by allowing a user to simply copy a system-installed plugin to
+a directory under $HOME and then modify it.
+"""
+
+from __future__ import print_function
+import sys
+import os
+import traceback
+import functools
+import pydoc
+from inspect import getmembers, isfunction
+from PyKDE4.kdecore import i18nc
+from PyKDE4.kdeui import KMessageBox
+
+import pate
+import kate.gui
+
+from PyQt4 import QtCore, QtGui
+from PyKDE4 import kdecore, kdeui
+# kate namespace
+from PyKate4.kate import Kate
+from PyKDE4.ktexteditor import KTextEditor
+
+class Configuration:
+    '''A Configuration object provides a plugin-specific persistent
+    configuration dictionary. The configuration is saved and loaded from disk
+    automatically.
+
+    Do not instantiate your own Configuration object; a plugin simply uses
+    kate.configuration and the class automatically creates a plugin-specific
+    dictionary to support it.
+
+    Use a string key. Any Python type that can be pickled is can be used as a
+    value -- dictionaries, lists, numbers, strings, sets, and so on.
+    '''
+
+    def __init__(self, root):
+        self.root = root
+
+    def __getitem__(self, key):
+        plugin = sys._getframe(1).f_globals['__name__']
+        return self.root.get(plugin, {})[key]
+
+    def __setitem__(self, key, value):
+        plugin = sys._getframe(1).f_globals['__name__']
+        if plugin not in self.root:
+            self.root[plugin] = {}
+        self.root[plugin][key] = value
+
+    def __delitem__(self, key):
+        plugin = sys._getframe(1).f_globals['__name__']
+        del self.root.get(plugin, {})[key]
+
+    def __contains__(self, key):
+        plugin = sys._getframe(1).f_globals['__name__']
+        return key in self.root.get(plugin, {})
+
+    def __len__(self):
+        plugin = sys._getframe(1).f_globals['__name__']
+        return len(self.root.get(plugin, {}))
+
+    def __iter__(self):
+        plugin = sys._getframe(1).f_globals['__name__']
+        return iter(self.root.get(plugin, {}))
+
+    def __str__(self):
+        plugin = sys._getframe(1).f_globals['__name__']
+        return str(self.root.get(plugin, {}))
+
+    def __repr__(self):
+        plugin = sys._getframe(1).f_globals['__name__']
+        return repr(self.root.get(plugin, {}))
+
+    def keys(self):
+        """Return the keys from the configuration dictionary."""
+        plugin = sys._getframe(1).f_globals['__name__']
+        return self.root.get(plugin, {}).keys()
+
+    def values(self):
+        """Return the values from the configuration dictionary."""
+        plugin = sys._getframe(1).f_globals['__name__']
+        return self.root.get(plugin, {}).values()
+
+    def items(self):
+        """Return the items from the configuration dictionary."""
+        plugin = sys._getframe(1).f_globals['__name__']
+        return self.root.get(plugin, {}).items()
+
+    def get(self, key, default=None):
+        """Fetch a configuration item using it's string key, returning
+        a given default if not found.
+
+        Parameters:
+            * key -             String key for item.
+            * default -         Value to return if key is not found.
+
+        Returns:
+            The item value for key, or the given default if not found.
+        """
+        plugin = sys._getframe(1).f_globals['__name__']
+        try:
+            return self[plugin][key]
+        except KeyError:
+            return default
+
+    def pop(self, key):
+        """Delete a configuration item using it's string key.
+
+        Parameters:
+            * key -             String key for item.
+        """
+        value = self[key]
+        del self[key]
+        return value
+
+    def save(self):
+        pate.saveConfiguration()
+
+    def _name(self):
+        return sys._getframe(1).f_globals['__name__']
+
+globalConfiguration = pate.configuration
+"""Configuration for all plugins.
+
+This can also be used by one plugin to access another plugin's configurations.
+"""
+
+configuration = Configuration(pate.configuration)
+"""Persistent configuration dictionary for this plugin."""
+
+sessionConfiguration = Configuration(pate.sessionConfiguration)
+"""Per session persistent configuration dictionary for this plugin."""
+
+def plugins():
+    """ The list of available plugins."""
+    return pate.plugins
+
+def pluginDirectories():
+    """ The search path for Python plugin directories."""
+    return pate.pluginDirectories
+
+def moduleGetHelp(module):
+    """Fetch HTML documentation on some module."""
+    return pydoc.html.page(pydoc.describe(module), pydoc.html.document(module, \
module.__name__)) +
+def _moduleActionDecompile(action):
+    """Deconstruct an @action."""
+    if len(action.text()) > 0:
+        text = action.text()
+    else:
+        text = None
+
+    if len(action.icon().name()) > 0:
+        icon = action.icon().name()
+    elif action.icon().isNull():
+        icon = None
+    else:
+        icon = action.icon()
+
+    if 'menu' in action.__dict__:
+        menu = action.__dict__['menu']
+    else:
+        menu = None
+
+    if len(action.shortcut().toString()) > 0:
+        shortcut = action.shortcut().toString()
+    else:
+        shortcut = None
+    return (text, icon, shortcut, menu)
+
+def moduleGetActions(module):
+    """Return a list of each module function decorated with @action.
+
+    The returned object is [ { function, ( text, icon, shortcut, menu), help }... ].
+    """
+    try:
+        result = []
+        for k, v in module.__dict__.items():
+            if isinstance(v, kate.catchAllHandler):
+                result.append((k, _moduleActionDecompile(v.action), getattr(v.f, "__doc__", \
None))) +        return result
+    except IndexError:
+        # Python3 throws this for modules, though Python 2 does not.
+        sys.stderr.write("IndexError getting actions for " + str(module) + "\n")
+        return []
+
+def moduleGetConfigPages(module):
+    """Return a list of each module function decorated with @configPage.
+
+    The returned object is [ { function, callable, ( name, fullName, icon ) }... ].
+    """
+    result = []
+    for k, v in module.__dict__.items():
+        if isfunction(v) and hasattr(v, "configPage"):
+            result.append((k, v, v.configPage))
+    return result
+
+def _callAll(l, *args, **kwargs):
+    for f in l:
+        try:
+            f(*args, **kwargs)
+        except:
+            traceback.print_exc()
+            sys.stderr.write('\n')
+            continue
+
+def _attribute(**attributes):
+    # utility decorator that we wrap events in. Simply initialises
+    # attributes on the function object to make code nicer.
+    def decorator(func):
+        for key, value in attributes.items():
+            setattr(func, key, value)
+        return func
+    return decorator
+
+def _simpleEventListener(func):
+    # automates the most common decorator pattern: calling a bunch
+    # of functions when an event has occurred
+    func.functions = set()
+    func.fire = functools.partial(_callAll, func.functions)
+    func.clear = func.functions.clear
+    return func
+
+# Decorator event listeners
+
+@_simpleEventListener
+def init(func):
+    ''' The function will be called when Kate has loaded completely: when all
+    other enabled plugins have been loaded into memory, the configuration has
+    been initiated, etc. '''
+    init.functions.add(func)
+    return func
+
+@_simpleEventListener
+def unload(func):
+    ''' The function will be called when Pate is being unloaded from memory.
+    Clean up any widgets that you have added to the interface (toolviews
+    etc). '''
+    unload.functions.add(func)
+    return func
+
+@_simpleEventListener
+def viewChanged(func):
+    ''' Calls the function when the view changes. To access the new active view,
+    use kate.activeView() '''
+    viewChanged.functions.add(func)
+    return func
+
+@_simpleEventListener
+def viewCreated(func):
+    ''' Calls the function when a new view is created, passing the view as a
+    parameter '''
+    viewCreated.functions.add(func)
+    return func
+
+class _HandledException(Exception):
+    def __init__(self, message):
+        super(_HandledException, self).__init__(message)
+
+class catchAllHandler(object):
+    '''Standard error handling for plugin actions.'''
+    def __init__(self, f):
+        self.f = f
+
+    def __call__(self):
+        try:
+            return self.f()
+        except _HandledException:
+            raise
+        except Exception as e:
+            txt = ''.join(traceback.format_exception(*sys.exc_info()))
+            KMessageBox.error(
+                None
+              , txt
+              , i18nc('@title:window', 'Error in action <icode>%1</icode>', self.f.__name__)
+              )
+            raise _HandledException(txt)
+
+@_attribute(actions=set())
+def action(text, icon=None, shortcut=None, menu=None):
+    ''' Decorator that adds an action to the menu bar. When the item is fired,
+    your function is called. Optional shortcuts, menu to place the action in,
+    and icon can be specified.
+
+    Parameters:
+        * text -        The text associated with the action (used as the menu
+                        item label, etc).
+        * shortcut -    The shortcut to fire this action or None if there is no
+                        shortcut. Must be a string such as 'Ctrl+1' or a
+                        QKeySequence instance. By default no shortcut is set (by
+                        passing None)
+        * icon -        An icon to associate with this action. It is shown
+                        alongside text in the menu bar and in toolbars as
+                        required. Pass a string to use KDE's image loading
+                        system or a QPixmap or QIcon to use any custom icon.
+                        None (the default) sets no icon.
+        * menu -        The menu under which to place this item. Must be a
+                        string such as 'tools' or 'settings', or None to not
+                        place it in any menu.
+
+    The docstring for your action will be used to provide "help" text.
+
+    NOTE: Kate may need to be restarted for this decorator to take effect, or
+    to remove all traces of the plugin on removal.
+    '''
+    def decorator(func):
+        a = kdeui.KAction(text, None)
+        if shortcut is not None:
+            if isinstance(shortcut, str):
+                a.setShortcut(QtGui.QKeySequence(shortcut))
+            else:
+                a.setShortcut(shortcut)
+        if icon is not None:
+            a.setIcon(kdeui.KIcon(icon))
+        a.menu = menu
+        func = catchAllHandler(func)
+        a.connect(a, QtCore.SIGNAL('triggered()'), func)
+        # delay till everything has been initialised
+        action.actions.add(a)
+        func.action = a
+        return func
+    return decorator
+
+@_attribute(actions=set())
+def configPage(name, fullName, icon):
+    ''' Decorator that adds a configPage into Kate's settings dialog. When the
+    item is fired, your function is called.
+
+    Parameters:
+        * name -        The text associated with the configPage in the list of
+                        config pages.
+        * fullName -    The title of the configPage when selected.
+        * icon -        An icon to associate with this configPage. Pass a string
+                        to use KDE's image loading system or a QPixmap or
+                        QIcon to use any custom icon.
+
+    NOTE: Kate may need to be restarted for this decorator to take effect, or
+    to remove all traces of the plugin on removal.
+    '''
+    def decorator(func):
+        a = name, fullName, kdeui.KIcon(icon)
+        configPage.actions.add(a)
+        func.configPage = a
+        return func
+    return decorator
+
+# End decorators
+
+
+# API functions and objects
+
+class NoActiveView(Exception):
+    pass
+
+application = Kate.application()
+"""Global accessor to the application object. Equivalent to Kate::application().
+
+Returns: application object.
+"""
+
+documentManager = application.documentManager()
+"""Global accessor to the document manager object. Equivalent to Kate::documentManager().
+
+Returns: document manager object.
+"""
+
+def mainWindow():
+    ''' The QWidget-derived main Kate window currently showing. A
+    shortcut around kate.application.activeMainWindow().window().
+
+    The Kate API differentiates between the interface main window and
+    the actual widget main window. If you need to access the
+    Kate.MainWindow for the methods it provides (e.g createToolView),
+    then use the mainInterfaceWindow function '''
+    return application.activeMainWindow().window()
+
+def mainInterfaceWindow():
+    ''' The interface to the main window currently showing. Calling
+    window() on the interface window gives you the actual
+    QWidget-derived main window, which is what the mainWindow()
+    function returns '''
+    return application.activeMainWindow()
+
+def activeView():
+    ''' The currently active view. Access its KTextEditor.Document
+    by calling document() on it (or by using kate.activeDocument()).
+    This is a shortcut for kate.application.activeMainWindow().activeView()'''
+    return application.activeMainWindow().activeView()
+
+def activeDocument():
+    ''' The document for the currently active view.
+    Throws NoActiveView if no active view available.'''
+    view = activeView()
+    if view:
+        return view.document()
+    raise NoActiveView
+
+def centralWidget():
+    ''' The central widget that holds the tab bar and the editor.
+    This is a shortcut for kate.application.activeMainWindow().centralWidget() '''
+    return application.activeMainWindow().centralWidget()
+
+def focusEditor():
+    ''' Give the editing section focus '''
+    print('dumping tree....')
+    for x in mainWindow().findChildren(QtGui.QWidget):
+        print(x.__class__.__name__, x.objectName())
+
+def applicationDirectories(*path):
+    path = os.path.join('pate', *path)
+    return kdecore.KGlobal.dirs().findDirs('appdata', path)
+
+def objectIsAlive(obj):
+    ''' Test whether an object is alive; that is, whether the pointer
+    to the object still exists. '''
+    import sip
+    try:
+       sip.unwrapinstance(obj)
+    except RuntimeError:
+       return False
+    return True
+
+
+# Initialisation
+
+def pateInit():
+    # wait for the configuration to be read
+    def _initPhase2():
+        global initialized
+        initialized = True
+        # set up actions -- plug them into the window's action collection
+        windowInterface = application.activeMainWindow()
+        window = windowInterface.window()
+        nameToMenu = {} # e.g "help": KMenu
+        for menu in window.findChildren(QtGui.QMenu):
+            name = str(menu.objectName())
+            if name:
+                nameToMenu[name] = menu
+        collection = window.actionCollection()
+        for a in action.actions:
+            # allow a configurable name so that built-in actions can be
+            # overriden?
+            collection.addAction(a.text(), a)
+            if a.menu is not None:
+                # '&Blah' => 'blah'
+                menuName = a.menu.lower().replace('&', '')
+                if menuName not in nameToMenu:
+                    #
+                    # Plugin wants to create an item in a.menu which does not
+                    # exist, so we need to create it.
+                    #
+                    before = nameToMenu['help'].menuAction()
+                    menu = kdeui.KMenu(a.menu, window.menuBar())
+                    menu.setObjectName(menuName)
+                    window.menuBar().insertMenu(before, menu)
+                    nameToMenu[menuName] = menu
+                nameToMenu[menuName].addAction(a)
+        # print 'init:', Kate.application(), application.activeMainWindow()
+        windowInterface.connect(windowInterface, QtCore.SIGNAL('viewChanged()'), \
viewChanged.fire) +        windowInterface.connect(windowInterface, \
QtCore.SIGNAL('viewCreated(KTextEditor::View*)'), viewCreated.fire) +        \
_callAll(init.functions) +    QtCore.QTimer.singleShot(0, _initPhase2)
+
+# called by pate on initialisation
+pate._pluginsLoaded = pateInit
+del pateInit
+
+
+def pateDie():
+    # Unload actions or things will crash
+    for a in action.actions:
+        for w in a.associatedWidgets():
+            w.removeAction(a)
+    # clear up
+    unload.fire()
+
+    action.actions.clear()
+    init.clear()
+    unload.clear()
+    viewChanged.clear()
+    viewCreated.clear()
+
+pate._pluginsUnloaded = pateDie
+del pateDie
+
+def pateSessionInit():
+    pass
+    # print 'new session:', Kate.application(), application.activeMainWindow()
+
+pate._sessionCreated = pateSessionInit
+del pateSessionInit
+
+kate.gui.loadIcon = lambda s: kdeui.KIcon(s).pixmap(32, 32)
+
+# kate: space-indent on; indent-width 4;
diff --git a/krita/plugins/extensions/pyqt/src/krita/gui.py \
b/krita/plugins/extensions/pyqt/src/krita/gui.py new file mode 100644
index 0000000..48ef40d
--- /dev/null
+++ b/krita/plugins/extensions/pyqt/src/krita/gui.py
@@ -0,0 +1,248 @@
+# This file is part of Pate, Kate' 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.
+
+''' Useful widgets '''
+
+from PyQt4.QtCore import *
+from PyQt4.QtGui import *
+
+
+def loadIcon(iconName):
+    # overwrite this with your own icon loading function
+    # in your project. 
+    return QPixmap(iconName)
+
+
+def setBackgroundColor(widget, color):
+    ''' Utility function to set the background color of a QWidget '''
+    p = widget.palette()
+    p.setColor(QPalette.Active, QPalette.Window, color)
+    p.setColor(QPalette.Inactive, QPalette.Window, color)
+    widget.setPalette(p)
+    widget.setAutoFillBackground(True)
+
+
+class FunctionIntervalTimer(QTimer):
+    def __init__(self, parent, interval, func, *args):
+        QTimer.__init__(self, parent)
+        self.func = func
+        self.args = args
+        self.connect(self, SIGNAL('timeout()'), self.timeOut)
+        self.start(interval)
+
+    def timeOut(self):
+        self.func(*self.args)
+
+    def stop(self):
+        QTimer.stop(self)
+        self.deleteLater()
+
+
+def slideInFromBottomRight(widget, step=5, interval=20, offsetRight=0, offsetBottom=0):
+    parent = widget.parent()
+    x = parent.width() - (widget.width() + offsetRight)
+    # shpeed
+    widget.move(x, parent.height())
+    originalHeight = widget.height()
+    widget.setFixedHeight(0)
+    def slideInFromBottomLeftInner():
+        if (widget.height() + step) > originalHeight:
+            slideInTimer.stop()
+            widget.setFixedHeight(originalHeight)
+            widget.move(x, parent.height() - originalHeight - offsetBottom)
+            try:
+                widget.effectFinished('slideInFromBottomLeft')
+            except AttributeError:
+                pass
+        else:
+            widget.setFixedHeight(widget.height() + step)
+            widget.move(x, parent.height() - widget.height() - offsetBottom)
+            #timer(widget, interval, slideInFromBottomLeftInner)
+    slideInTimer = FunctionIntervalTimer(widget, interval, slideInFromBottomLeftInner)
+
+
+def slideOutFromBottomRight(widget, step=5, interval=20, offsetRight=0, offsetBottom=0):
+    parent = widget.parent()
+    x = parent.width() - (widget.width() + offsetRight)
+    # shpeed
+    originalHeight = widget.height()
+    widget.move(x, parent.height() - originalHeight - offsetBottom)
+    def slideOutFromBottomLeftInner():
+        if (widget.height() - step) < 0:
+            slideOutTimer.stop()
+            widget.setFixedHeight(0)
+            widget.move(x, parent.height() - offsetBottom)
+            try:
+                widget.effectFinished('slideOutFromBottomLeft')
+            except AttributeError:
+                pass
+        else:
+            widget.setFixedHeight(widget.height() - step)
+            widget.move(x, parent.height() - widget.height() - offsetBottom)
+    slideOutTimer = FunctionIntervalTimer(widget, interval, slideOutFromBottomLeftInner)
+
+
+class VerticalProgressWidget(QFrame):
+    Brush = QBrush(QColor(200, 210, 240))
+    def __init__(self, parent):
+        QFrame.__init__(self, parent)
+        self.setFixedWidth(10)
+        setBackgroundColor(self, Qt.white)
+        palette = self.palette()
+        palette.setColor(QPalette.Foreground, QColor(180, 180, 180))
+        self.setPalette(palette)
+        self.setFrameStyle(QFrame.Box | QFrame.Plain)
+        self.percent = 100
+        self.oldHeight = self.height()
+
+    def paintEvent(self, e):
+        painter = QPainter()
+        painter.begin(self)
+        self_height = self.height()
+        self_width = self.width()
+        height = int(self_height * (self.percent / 100.0))
+#         painter.fillRect(0, 0, self_width, self_height, QColor(255, 255, 255
+        painter.fillRect(0, self_height - height, self_width, height, self.Brush)
+        self.oldHeight = height
+        painter.end()
+        QFrame.paintEvent(self, e)
+
+    def increasedDrawnPercentage(self, p):
+        return int(self.height() * p / 100.0) > self.oldPercent
+
+    def decreaseDrawnPercentage(self, p):
+        return int(self.height() * p / 100.0) < self.oldHeight
+
+
+class PassivePopupLabel(QLabel):
+    def __init__(self, parent, message, maxTextWidth=None, minTextWidth=None):
+        QLabel.__init__(self, parent)
+        # if '<' in message and '>' in message:
+            # self.setTextFormat(Qt.RichText)
+        self.setTextInteractionFlags(Qt.TextSelectableByMouse | Qt.LinksAccessibleByMouse)
+        if maxTextWidth is not None:
+            self.setMaximumWidth(maxTextWidth)
+        if minTextWidth is not None:
+            self.setMinimumWidth(minTextWidth)
+        self.setWordWrap(True)
+        self.setText(message)
+
+
+class TimeoutPassivePopup(QFrame):
+    popups = {}
+    def __init__(self, parent, message, timeout=5, icon=None, maxTextWidth=200, \
minTextWidth=None): +        QFrame.__init__(self, parent)
+        setBackgroundColor(self, QColor(255, 255, 255, 200))
+        palette = self.palette()
+        originalWindowText = palette.color(QPalette.WindowText)
+        # why does the style use WindowText!?
+        palette.setColor(QPalette.WindowText, QColor(80, 80, 80))
+        self.setPalette(palette)
+        self.setFrameStyle(QFrame.Plain | QFrame.Box)
+        self.timeout = timeout
+        self.progress = 0
+        self.interval = None
+        self.hasMouseOver = False
+        self.timer = QTimer(self)
+        self.connect(self.timer, SIGNAL('timeout()'), self.updateProgress)
+        layout = QVBoxLayout(self)
+        layout.setMargin(self.frameWidth() + 7)
+        layout.setSpacing(0)
+        grid = QGridLayout()
+        grid.setMargin(4)
+        grid.setSpacing(5)
+        self.message = PassivePopupLabel(self, message, maxTextWidth, minTextWidth)
+        self.message.palette().setColor(QPalette.WindowText, originalWindowText)
+        self.icon = QLabel(self)
+        self.icon.setAlignment(Qt.AlignHCenter | Qt.AlignTop)
+        self.timerWidget = VerticalProgressWidget(self)
+        if icon is not None:
+            pixmap = loadIcon(icon)
+            self.icon.setPixmap(pixmap)
+            self.icon.setMargin(3)
+        grid.addWidget(self.timerWidget, 0, 0)
+        grid.addWidget(self.icon, 0, 1)
+        grid.addWidget(self.message, 0, 2, Qt.AlignVCenter)
+        layout.addLayout(grid, 1)
+        # start further up if there is another popup
+        self.stackList = TimeoutPassivePopup.popups.setdefault(parent, [])
+        self.stackHeight = len(self.stackList)
+        # resize according to the layout
+        self.adjustSize()
+        self.originalHeight = self.height()
+        self.offsetBottom = 0
+        self.move(0, 0)
+
+    def updateProgress(self):
+        if self.progress >= 100:
+            self.timer.stop()
+            self.hide()
+        else:
+            # if drawing at this percentage won't cause a visible difference to the widget, \
then don't draw +            if self.timerWidget.decreaseDrawnPercentage(100 - self.progress):
+                self.timerWidget.percent = (100 - self.progress)
+                #print self.timerWidget.percent
+                self.timerWidget.repaint()
+            self.progress += 1
+
+    def enterEvent(self, e):
+        self.hasMouseOver = True
+        self.timer.stop()
+    def leaveEvent(self, e):
+        self.hasMouseOver = False
+        if self.interval is not None:
+            self.timer.start(self.interval)
+
+    def effectFinished(self, name):
+        if name == 'slideInFromBottomLeft':
+            # kickstart timeout
+            interval = int(self.timeout * 1000) / 100
+            self.interval = interval
+            if not self.hasMouseOver:
+                self.timer.start(interval)
+        elif name == 'slideOutFromBottomLeft':
+            self.stackList.remove(self)
+            self.deleteLater()
+
+    def show(self):
+        if self.stackList:
+            maxOffsetBottomWidget = self.stackList[0]
+            for x in self.stackList[1:]:
+                if x.offsetBottom > maxOffsetBottomWidget.offsetBottom:
+                    maxOffsetBottomWidget = x
+            self.offsetBottom = maxOffsetBottomWidget.originalHeight + \
maxOffsetBottomWidget.offsetBottom + 2 +        else:
+            self.offsetBottom = 0
+        self.stackList.append(self)
+        QFrame.show(self)
+        slideInFromBottomRight(self, offsetRight=21, offsetBottom=self.offsetBottom)
+
+    def hide(self):
+        slideOutFromBottomRight(self, offsetRight=21, offsetBottom=self.offsetBottom)
+
+
+def popup(message, timeout, icon=None, maxTextWidth=None, minTextWidth=None, parent=None):
+    if parent is None:
+        import kate
+        parent = kate.mainWindow()
+    popup = TimeoutPassivePopup(parent, message, timeout, icon, maxTextWidth, minTextWidth)
+    popup.show()
+    return popup
+
+# kate: space-indent on; indent-width 4;
diff --git a/krita/plugins/extensions/pyqt/src/kritapyqt.desktop \
b/krita/plugins/extensions/pyqt/src/kritapyqt.desktop new file mode 100644
index 0000000..bf34dbd
--- /dev/null
+++ b/krita/plugins/extensions/pyqt/src/kritapyqt.desktop
@@ -0,0 +1,7 @@
+[Desktop Entry]
+Name=Python Plugin Manager
+X-KDE-ServiceTypes=Krita/ViewPlugin
+Type=Service
+X-KDE-Library=kritapyqt
+X-Krita-Version=28
+
diff --git a/krita/plugins/extensions/pyqt/src/kritapyqtplugin.desktop \
b/krita/plugins/extensions/pyqt/src/kritapyqtplugin.desktop new file mode 100644
index 0000000..36ddfde
--- /dev/null
+++ b/krita/plugins/extensions/pyqt/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/pyqt/src/kritapyqtplugin.rc \
b/krita/plugins/extensions/pyqt/src/kritapyqtplugin.rc new file mode 100644
index 0000000..18f97a1
--- /dev/null
+++ b/krita/plugins/extensions/pyqt/src/kritapyqtplugin.rc
@@ -0,0 +1,5 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+<kpartgui library="kritapyqtplugin" version="7">
+<MenuBar>
+</MenuBar>
+</kpartgui>
diff --git a/krita/plugins/extensions/pyqt/src/manager.ui \
b/krita/plugins/extensions/pyqt/src/manager.ui new file mode 100644
index 0000000..abaff84
--- /dev/null
+++ b/krita/plugins/extensions/pyqt/src/manager.ui
@@ -0,0 +1,104 @@
+<?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="KTabWidget" name="tabWidget">
+     <widget class="QWidget" name="manageTab">
+      <attribute name="title">
+       <string>Manager</string>
+      </attribute>
+      <layout class="QVBoxLayout" name="verticalLayout_2">
+       <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>true</bool>
+         </property>
+         <property name="expandsOnDoubleClick">
+          <bool>false</bool>
+         </property>
+         <attribute name="headerShowSortIndicator" stdset="0">
+          <bool>false</bool>
+         </attribute>
+        </widget>
+       </item>
+       <item>
+        <layout class="QHBoxLayout" name="horizontalLayout">
+         <item>
+          <spacer name="horizontalSpacer">
+           <property name="orientation">
+            <enum>Qt::Horizontal</enum>
+           </property>
+           <property name="sizeHint" stdset="0">
+            <size>
+             <width>40</width>
+             <height>20</height>
+            </size>
+           </property>
+          </spacer>
+         </item>
+         <item>
+          <widget class="KPushButton" name="reload">
+           <property name="toolTip">
+            <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; \
font-weight:600;&quot;&gt;Caution:&lt;/span&gt; Reloading a running plugin may have \
unpredictable effects. Consider saving your work before using this \
command.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> +           </property>
+           <property name="text">
+            <string>Reload Plugins</string>
+           </property>
+           <property name="icon">
+            <iconset theme="system-reboot">
+             <normaloff/>
+            </iconset>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </item>
+      </layout>
+     </widget>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <customwidgets>
+  <customwidget>
+   <class>KPushButton</class>
+   <extends>QPushButton</extends>
+   <header>kpushbutton.h</header>
+  </customwidget>
+  <customwidget>
+   <class>KTabWidget</class>
+   <extends>QTabWidget</extends>
+   <header>ktabwidget.h</header>
+   <container>1</container>
+  </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/krita/plugins/extensions/pyqt/src/plugin.cpp \
b/krita/plugins/extensions/pyqt/src/plugin.cpp new file mode 100644
index 0000000..635390e
--- /dev/null
+++ b/krita/plugins/extensions/pyqt/src/plugin.cpp
@@ -0,0 +1,54 @@
+/*
+ * 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 <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/kritapyqtplugin.rc")
+{
+    KisPreferenceSetRegistry *preferenceSetRegistry = KisPreferenceSetRegistry::instance();
+
+    PyQtPluginSettingsFactory* settingsFactory = new PyQtPluginSettingsFactory();
+
+    //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);
+}
+
+KritaPyQtPlugin::~KritaPyQtPlugin()
+{
+
+}
+
diff --git a/krita/plugins/extensions/pyqt/src/plugin.h \
b/krita/plugins/extensions/pyqt/src/plugin.h new file mode 100644
index 0000000..1b8e4c1
--- /dev/null
+++ b/krita/plugins/extensions/pyqt/src/plugin.h
@@ -0,0 +1,33 @@
+/*
+ *  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 <kis_view_plugin.h>
+
+class KritaPyQtPlugin : public KisViewPlugin
+{
+    Q_OBJECT
+public:
+    KritaPyQtPlugin(QObject *parent, const QVariantList &);
+    virtual ~KritaPyQtPlugin();
+};
+
+#endif
diff --git a/krita/plugins/extensions/pyqt/src/plugins/CMakeLists.txt \
b/krita/plugins/extensions/pyqt/src/plugins/CMakeLists.txt new file mode 100644
index 0000000..2e44365
--- /dev/null
+++ b/krita/plugins/extensions/pyqt/src/plugins/CMakeLists.txt
@@ -0,0 +1,75 @@
+#
+# Install .desktop files for indivudual plugins
+#
+install(
+    FILES
+        cmake_utils/katepate_cmake_utils.desktop
+        katepate_block.desktop
+        katepate_color_tools.desktop
+        katepate_commentar.desktop
+        katepate_expand.desktop
+        katepate_format.desktop
+        python_autocomplete/katepate_python_autocomplete.desktop
+        python_console_classic/katepate_python_console_classic.desktop
+        python_console_ipython/katepate_python_console_ipython.desktop
+    DESTINATION ${SERVICES_INSTALL_DIR}
+  )
+
+#
+# Install individual Pate plugins.
+#
+install(
+    FILES
+        block.py
+        color_tools.py
+        color_tools_toolview.ui
+        commentar.py
+        commentar_config.ui
+        expand.py
+        format.py
+        xml_pretty.py
+        xml_pretty.ui
+    DESTINATION ${DATA_INSTALL_DIR}/kate/pate
+  )
+
+#
+# Install various directories w/ plugins
+#
+install(
+    DIRECTORY
+        django_utils
+        expand
+        gid
+        libkatepate
+        python_console_classic
+        python_utils
+        python_autocomplete
+    DESTINATION ${DATA_INSTALL_DIR}/kate/pate
+    FILES_MATCHING PATTERN "*.py" PATTERN "*.ui"
+  )
+
+install(
+    DIRECTORY expand
+    DESTINATION ${DATA_INSTALL_DIR}/kate/pate
+    FILES_MATCHING PATTERN "*.expand"
+  )
+
+install(
+    DIRECTORY js_utils
+    DESTINATION ${DATA_INSTALL_DIR}/kate/pate
+    FILES_MATCHING PATTERN "*.py" PATTERN "*.ui" PATTERN "*.json"
+    )
+
+install(
+    DIRECTORY python_console_ipython
+    DESTINATION ${DATA_INSTALL_DIR}/kate/pate
+    FILES_MATCHING PATTERN "*.py" PATTERN "*.ui" PATTERN "*.css"
+  )
+
+install(
+    DIRECTORY cmake_utils
+    DESTINATION ${DATA_INSTALL_DIR}/kate/pate
+    FILES_MATCHING PATTERN "*.py" PATTERN "*.ui"
+  )
+
+# kate: indent-width 4;
diff --git a/krita/plugins/extensions/pyqt/src/pyqtpluginsettings.cpp \
b/krita/plugins/extensions/pyqt/src/pyqtpluginsettings.cpp new file mode 100644
index 0000000..01cc74d
--- /dev/null
+++ b/krita/plugins/extensions/pyqt/src/pyqtpluginsettings.cpp
@@ -0,0 +1,78 @@
+/*
+ *  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 <kconfiggroup.h>
+
+#include <KoIcon.h>
+
+
+#include "kis_config.h"
+
+
+PyQtPluginSettings::PyQtPluginSettings(QWidget *parent) :
+    KisPreferenceSet(parent),
+    ui(new Ui::ManagerPage)
+{
+    ui->setupUi(this);
+}
+
+PyQtPluginSettings::~PyQtPluginSettings()
+{
+    delete ui;
+}
+
+QString PyQtPluginSettings::id()
+{
+    return QString("pyqtpluginmanager");
+}
+
+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/pyqt/src/pyqtpluginsettings.h \
b/krita/plugins/extensions/pyqt/src/pyqtpluginsettings.h new file mode 100644
index 0000000..697ee45
--- /dev/null
+++ b/krita/plugins/extensions/pyqt/src/pyqtpluginsettings.h
@@ -0,0 +1,80 @@
+/*
+ *  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"
+
+namespace Ui {
+    class ManagerPage;
+}
+
+class KIcon;
+
+class PyQtPluginSettings : public KisPreferenceSet {
+    Q_OBJECT
+public:
+
+    PyQtPluginSettings(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 *ui;
+};
+
+
+class PyQtPluginSettingsUpdateRepeater : public QObject {
+    Q_OBJECT
+
+Q_SIGNALS:
+    void settingsUpdated();
+
+public Q_SLOTS:
+    void updateSettings() {
+        Q_EMIT settingsUpdated();
+    }
+};
+
+
+class PyQtPluginSettingsFactory : public KisAbstractPreferenceSetFactory {
+public:
+    KisPreferenceSet* createPreferenceSet() {
+        PyQtPluginSettings* ps = new PyQtPluginSettings();
+        QObject::connect(ps, SIGNAL(settingsChanged()), &repeater, SLOT(updateSettings()), \
Qt::UniqueConnection); +        return ps;
+    }
+    virtual QString id() const { return "ColorSelectorSettings"; }
+    PyQtPluginSettingsUpdateRepeater repeater;
+};
+
+
+
+
+#endif // PYQTPLUGINSETTINGS_H
diff --git a/krita/plugins/extensions/pyqt/src/utilities.cpp \
b/krita/plugins/extensions/pyqt/src/utilities.cpp new file mode 100644
index 0000000..b4d6c21
--- /dev/null
+++ b/krita/plugins/extensions/pyqt/src/utilities.cpp
@@ -0,0 +1,543 @@
+// This file is part of Pate, Kate' 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.
+
+// config.h defines PATE_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 <kdebug.h>
+#include <KLocale>
+
+#define THREADED 1
+
+namespace Pate { namespace {
+QLibrary* s_pythonLibrary = 0;
+PyThreadState* s_pythonThreadState = 0;
+}                                                           // anonymous namespace
+
+const char* Python::PATE_ENGINE = "pate";
+
+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)
+    {
+        kError() << "Missing arguments for" << moduleName << functionName;
+        return 0;
+    }
+    PyObject* const func = itemString(functionName, moduleName);
+    if (!func)
+    {
+        kError() << "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;
+
+    kError() << "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::kateHandler(const char* const moduleName, const char* const handler)
+{
+    if (PyObject* const module = moduleImport(moduleName))
+        return functionCall(handler, "kate", 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(PATE_PYTHON_LIBRARY);
+        if (!s_pythonLibrary)
+            kError() << "Could not create" << PATE_PYTHON_LIBRARY;
+
+        s_pythonLibrary->setLoadHints(QLibrary::ExportExternalSymbolsHint);
+        if (!s_pythonLibrary->load())
+            kError() << "Could not load" << PATE_PYTHON_LIBRARY;
+
+        Py_InitializeEx(0);
+        if (!Py_IsInitialized())
+            kError() << "Could not initialise" << PATE_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 kateHandler(moduleName, "moduleGetActions");
+}
+
+PyObject* Python::moduleConfigPages(const char* const moduleName)
+{
+    return kateHandler(moduleName, "moduleGetConfigPages");
+}
+
+QString Python::moduleHelp(const char* moduleName)
+{
+    QString r;
+    PyObject* const result = kateHandler(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;
+    kError() << 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
+            {
+                kError() << "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
+            {
+                kError() << "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 Pate
+
+// kate: indent-width 4;
diff --git a/krita/plugins/extensions/pyqt/src/utilities.h \
b/krita/plugins/extensions/pyqt/src/utilities.h new file mode 100644
index 0000000..a138ed8
--- /dev/null
+++ b/krita/plugins/extensions/pyqt/src/utilities.h
@@ -0,0 +1,224 @@
+// This file is part of Pate, Kate' Python scripting plugin.
+//
+// A couple of useful macros and functions used inside of pate_engine.cpp and pate_plugin.cpp.
+//
+// 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.
+
+#ifndef __PATE_UTILITIES_H__
+# define  __PATE_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 Pate {
+
+/**
+ * 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/kate/pate/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 = PATE_ENGINE);
+
+    /**
+     * Delete the item from the named module's dictionary.
+     */
+    bool itemStringDel(const char* item, const char* moduleName = PATE_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 = PATE_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 = \
PATE_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 = PATE_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* PATE_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 kate module on another module.
+     *
+     * @return 0 or a new reference to the result.
+     */
+    PyObject* kateHandler(const char* moduleName, const char* handler);
+    PyObject* functionCall(const char* functionName, const char* moduleName, PyObject* \
arguments); +};
+
+}                                                           // namespace Pate
+#endif                                                      //  __PATE_UTILITIES_H__
+// kate: indent-width 4;


[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic