[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><html><head/><body><p><span \
style=" font-weight:600;">Caution:</span> Reloading a running \
plugin may have unpredictable effects. Consider saving your work before using this \
command.</p></body></html></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