diff --git a/plugins/extensions/pykrita/libkis/Krita.cpp b/plugins/extensions/pykrita/libkis/Krita.cpp index 638c5de861..cd7b13ea4c 100644 --- a/plugins/extensions/pykrita/libkis/Krita.cpp +++ b/plugins/extensions/pykrita/libkis/Krita.cpp @@ -1,256 +1,256 @@ /* * Copyright (c) 2016 Boudewijn Rempt * * 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; either version 2 of the License, or * (at your option) any later version. * * 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 "Krita.h" #include #include #include #include #include #include #include #include #include #include #include "View.h" #include "Document.h" #include "Window.h" #include "ViewExtension.h" #include "DockWidgetFactoryBase.h" #include #include Krita* Krita::s_instance = 0; struct Krita::Private { Private() {} QList viewExtensions; bool batchMode {false}; Notifier *notifier{new Notifier()}; }; Krita::Krita(QObject *parent) : QObject(parent) , d(new Private) { qRegisterMetaType(); } Krita::~Krita() { qDeleteAll(d->viewExtensions); delete d->notifier; delete d; } QVariantMap Krita::actions() const { QVariantMap actionList; KisMainWindow *mainWindow = KisPart::instance()->currentMainwindow(); KActionCollection *actionCollection = mainWindow->actionCollection(); Q_FOREACH(QAction *action, actionCollection->actions()) { actionList.insert(action->objectName(), QVariant::fromValue(new Action(action->objectName(), action))); } return actionList; } Document* Krita::activeDocument() const { return 0; } void Krita::setActiveDocument(Document* value) { } bool Krita::batchmode() const { return d->batchMode; } void Krita::setBatchmode(bool value) { d->batchMode = value; } -QList Krita::documents() const +QVariantList Krita::documents() const { - QList ret; + QVariantList ret; foreach(QPointer doc, KisPart::instance()->documents()) { - ret << new Document(doc); + ret << QVariant::fromValue(new Document(doc)); } return ret; } -QList Krita::exporters() const +QVariantList Krita::exporters() const { - return QList(); + return QVariantList(); } -QList Krita::filters() const +QVariantList Krita::filters() const { - return QList(); + return QVariantList(); } -QList Krita::generators() const +QVariantList Krita::generators() const { - return QList(); + return QVariantList(); } -QList Krita::importers() const +QVariantList Krita::importers() const { - return QList(); + return QVariantList(); } Notifier* Krita::notifier() const { return d->notifier; } InfoObject* Krita::preferences() const { return 0; } void Krita::setPreferences(InfoObject* value) { } QString Krita::version() const { return QString(); } -QList Krita::views() const +QVariantList Krita::views() const { - QList ret; + QVariantList ret; foreach(QPointer view, KisPart::instance()->views()) { - ret << new View(view); + ret << QVariant::fromValue(new View(view)); } return ret; } -QList Krita::windows() const +QVariantList Krita::windows() const { - QList ret; + QVariantList ret; foreach(QPointer mainWin, KisPart::instance()->mainWindows()) { - ret << new Window(mainWin); + ret << QVariant::fromValue(new Window(mainWin)); } return ret; } -QList Krita::resources() const +QVariantList Krita::resources() const { - return QList(); + return QVariantList(); } void Krita::addDockWidget(DockWidget *dockWidget) { } void Krita::addAction(Action *action) { } bool Krita::closeApplication() { qDebug() << "closeApplication called"; return false; } Document* Krita::createDocument() { return 0; } Document* Krita::openDocument() { return 0; } Window* Krita::openWindow() { return 0; } QAction *Krita::createAction(const QString &text) { KisAction *action = new KisAction(text, this); KisPart::instance()->addScriptAction(action); return action; } void Krita::addViewExtension(ViewExtension* viewExtension) { d->viewExtensions.append(viewExtension); } QList< ViewExtension* > Krita::viewExtensions() { return d->viewExtensions; } void Krita::addDockWidgetFactory(DockWidgetFactoryBase* factory) { KoDockRegistry::instance()->add(factory); } Krita* Krita::instance() { if (!s_instance) { s_instance = new Krita; } return s_instance; } /** * Scripter.fromVariant(variant) * variant is a QVariant * returns instance of QObject-subclass * * This is a helper method for PyQt * Because PyQt cannot cast a variant to a QObject or QWidget * I hope that will change some time. */ QObject *Krita::fromVariant(const QVariant& v) { if (v.canConvert< QWidget* >()) { QObject* obj = qvariant_cast< QWidget* >(v); return obj; } else if (v.canConvert< QObject* >()) { QObject* obj = qvariant_cast< QObject* >(v); return obj; } else return 0; } diff --git a/plugins/extensions/pykrita/libkis/Krita.h b/plugins/extensions/pykrita/libkis/Krita.h index 35c96f18fd..8237844cd7 100644 --- a/plugins/extensions/pykrita/libkis/Krita.h +++ b/plugins/extensions/pykrita/libkis/Krita.h @@ -1,145 +1,145 @@ /* * Copyright (c) 2016 Boudewijn Rempt * * 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; either version 2 of the License, or * (at your option) any later version. * * 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 LIBKIS_KRITA_H #define LIBKIS_KRITA_H #include #include "kritalibkis_export.h" #include "libkis.h" #include "ViewExtension.h" #include "Document.h" #include "Window.h" #include "View.h" #include "Action.h" #include "Notifier.h" class QAction; /** * Krita is a singleton class that offers the root access to the Krita object hierarchy. */ class KRITALIBKIS_EXPORT Krita : public QObject { Q_OBJECT /** * The actions list returns a map of Action objects that belong to the currently * active Window. You can retrieve a certain action by name from the map. * * This code will retrieve all actions, and first use the Select All action to * select everything, and the Deselect action to deselect everything *in the * current window, the current view, the current image, on the current layer*. * * @code - * actions = Krita.instance().actions() + * actions = Krita.Actions * print(actions.keys()) * actions["select_all"].trigger() * actions["deselect"].trigger() * @endcode */ Q_PROPERTY(QVariantMap Actions READ actions) Q_PROPERTY(Document* ActiveDocument READ activeDocument WRITE setActiveDocument) Q_PROPERTY(bool Batchmode READ batchmode WRITE setBatchmode) - Q_PROPERTY(QList Documents READ documents) - Q_PROPERTY(QList Exporters READ exporters) - Q_PROPERTY(QList Filters READ filters) - Q_PROPERTY(QList Generators READ generators) - Q_PROPERTY(QList Importers READ importers) + Q_PROPERTY(QVariantList Documents READ documents) + Q_PROPERTY(QVariantList Exporters READ exporters) + Q_PROPERTY(QVariantList Filters READ filters) + Q_PROPERTY(QVariantList Generators READ generators) + Q_PROPERTY(QVariantList Importers READ importers) Q_PROPERTY(Notifier* Notifier READ notifier) Q_PROPERTY(InfoObject* Preferences READ preferences WRITE setPreferences) Q_PROPERTY(QString Version READ version) - Q_PROPERTY(QList Views READ views) - Q_PROPERTY(QList Windows READ windows) - Q_PROPERTY(QList Resources READ resources) + Q_PROPERTY(QVariantList Views READ views) + Q_PROPERTY(QVariantList Windows READ windows) + Q_PROPERTY(QVariantList Resources READ resources) public: explicit Krita(QObject *parent = 0); virtual ~Krita(); Document* activeDocument() const; void setActiveDocument(Document* value); bool batchmode() const; void setBatchmode(bool value); QVariantMap actions() const; - QList documents() const; + QVariantList documents() const; - QList exporters() const; + QVariantList exporters() const; - QList filters() const; + QVariantList filters() const; - QList generators() const; + QVariantList generators() const; - QList importers() const; + QVariantList importers() const; Notifier* notifier() const; InfoObject* preferences() const; void setPreferences(InfoObject* value); QString version() const; - QList views() const; + QVariantList views() const; - QList windows() const; + QVariantList windows() const; - QList resources() const; + QVariantList resources() const; void setResources(QList value); public Q_SLOTS: void addDockWidget(DockWidget *dockWidget); void addAction(Action *action); bool closeApplication(); Document* createDocument(); Document* openDocument(); Window* openWindow(); QAction *createAction(const QString &text); void addViewExtension(ViewExtension* viewExtension); QList viewExtensions(); void addDockWidgetFactory(DockWidgetFactoryBase* factory ); static Krita* instance(); static QObject *fromVariant(const QVariant& v); private: struct Private; Private *const d; static Krita* s_instance; }; Q_DECLARE_METATYPE(Notifier*); #endif // LIBKIS_KRITA_H diff --git a/plugins/extensions/pykrita/plugin/krita/mikro.py b/plugins/extensions/pykrita/plugin/krita/mikro.py index 2fa23295da..eb1b3cec23 100644 --- a/plugins/extensions/pykrita/plugin/krita/mikro.py +++ b/plugins/extensions/pykrita/plugin/krita/mikro.py @@ -1,437 +1,438 @@ # -*- coding: utf-8 -*- """ Copyright (c) 2016 Boudewijn Rempt 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; either version 2 of the License, or (at your option) any later version. 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. """ """ Mini Kross - a scripting solution inspired by Kross (http://kross.dipe.org/) Technically this is one of the most important modules in Scripter. Via the Qt meta object system it provides access to unwrapped objects. This code uses a lot of metaprogramming magic. To fully understand it, you have to know about metaclasses in Python """ import sys import sip from PyQt5.QtCore import QVariant, QMetaObject, Q_RETURN_ARG, Q_ARG, QObject, Qt, QMetaMethod, pyqtSignal from PyQt5.QtGui import QBrush, QFont, QImage, QPalette, QPixmap from PyQt5.QtWidgets import qApp variant_converter = { "QVariantList": lambda v: v.toList(v), "QVariantMap": lambda v: toPyObject(v), "QPoint": lambda v: v.toPoint(), "str": lambda v: v.toString(), "int": lambda v: v.toInt()[0], "double": lambda v: v.toDouble()[0], "char": lambda v: v.toChar(), "QByteArray": lambda v: v.toByteArray(), "QPoint": lambda v: v.toPoint(), "QPointF": lambda v: v.toPointF(), "QSize": lambda v: v.toSize(), "QLine": lambda v: v.toLine(), "QStringList": lambda v: v.toStringList(), "QTime": lambda v: v.toTime(), "QDateTime": lambda v: v.toDateTime(), "QDate": lambda v: v.toDate(), "QLocale": lambda v: v.toLocale(), "QUrl": lambda v: v.toUrl(), "QRect": lambda v: v.toRect(), "QBrush": lambda v: QBrush(v), "QFont": lambda v: QFont(v), "QPalette": lambda v: QPalette(v), "QPixmap": lambda v: QPixmap(v), "QImage": lambda v: QImage(v), "bool": lambda v: v.toBool(), "QObject*": lambda v: wrap_variant_object(v), "QWidget*": lambda v: wrap_variant_object(v), "ActionMap": lambda v: int(v.count()) } def wrap_variant_object(variant): """ convert a QObject or a QWidget to its wrapped superclass """ o = Krita.fromVariant(variant) return wrap(o, True) def from_variant(variant): """ convert a QVariant to a Python value """ # Check whether it's really a QVariant if hasattr(variant, '__type__') and not (variant is None or variant.type() is None): typeName = variant.typeName() convert = variant_converter.get(typeName) if not convert: raise ValueError("Could not convert value to %s" % typeName) else: v = convert(variant) return v # Give up and return return variant def convert_value(value): """ Convert a given value, upcasting to the highest QObject-based class if possible, unpacking lists and dicts. """ # Check whether it's a dict: if so, convert the keys/values if hasattr(value, '__class__') and issubclass(value.__class__, dict) and len(value) > 0: return {convert_value(k): convert_value(v) for k, v in value.items()} # Check whether it's a list: if so, convert the values if hasattr(value, '__class__') and issubclass(value.__class__, list) and len(value) > 0: return [convert_value(v) for v in value] if isinstance(value, str): # prefer Python strings return str(value) elif isinstance(value, PyQtClass): # already wrapped return value # Check whether it's a QObject if hasattr(value, '__class__') and issubclass(value.__class__, QObject): return wrap(value, True) if hasattr(value, '__type__') and not (value is None or value.type() is None) : return from_variant(value); return value qtclasses = {} def wrap(obj, force=False): """ If a class is not known by PyQt it will be automatically casted to a known wrapped super class. But that limits access to methods and propperties of this super class. So instead this functions returns a wrapper class (PyQtClass) which queries the metaObject and provides access to all slots and all properties. """ if isinstance(obj, str): # prefer Python strings return str(obj) elif isinstance(obj, PyQtClass): # already wrapped return obj elif obj and isinstance(obj, QObject): if force or obj.__class__.__name__ != obj.metaObject().className(): # Ah this is an unwrapped class obj = create_pyqt_object(obj) return obj def unwrap(obj): """ if wrapped returns the wrapped object """ if hasattr(obj, "qt"): obj = obj.qt return obj def is_qobject(obj): """ checks if class or wrapped class is a subclass of QObject """ if hasattr(obj, "__bases__") and issubclass(unwrap(obj), QObject): return True else: return False def is_scripter_child(qobj): """ walk up the object tree until Scripter or the root is found """ found = False p = qobj.parent() while p and not found: if str(p.objectName()) == "Krita": found = True break else: p = p.parent() return found class Error(Exception): """ Base error classed. Catch this to handle exceptions comming from C++ """ class PyQtClass(object): """ Base class """ def __init__(self, instance): self._instance = instance def __del__(self): """ If this object is deleted it should also delete the wrapped object if it was created explicitly for this use. """ qobj = self._instance if is_scripter_child(qobj): if len(qobj.children()): print("Cannot delete", qobj, "because it has child objects") sip.delete(qobj) + def setProperty(self, name, value): self._instance.setProperty(name, value) def getProperty(self, name): return wrap(self._instance.property(name)) def propertyNames(self): return list(self.__class__.__properties__.keys()) def dynamicPropertyNames(self): return self._instance.dynamicPropertyNames() def metaObject(self): return self._instance.metaObject() def connect(self, signal, slot): getattr(self._instance, signal).connect(slot) def disconnect(self, signal, slot): getattr(self._instance, signal).disconnect(slot) def parent(self): return wrap(self._instance.parent()) def children(self): return [wrap(c) for c in self._instance.children()] @property def qt(self): return self._instance def __getitem__(self, key): if isinstance(key, int): length = getattr(self, "length", None) if length is not None: # array protocol try: return getattr(self, str(key)) except AttributeError as e: raise IndexError(key) else: return self.children()[key] else: return getattr(self, key) def __getattr__(self, name): # Make named child objects available as attributes like QtQml # Check whether the object is in the QObject hierarchy for child in self._instance.children(): if str(child.objectName()) == name: obj = wrap(child) # Save found object for faster lookup setattr(self, name, obj) return obj # Check whether it's a property v = self._instance.property(name) return convert_value(v) @property def __members__(self): """ This method is for introspection. Using dir(thispyqtclass_object) returns a list of all children, methods, properties and dynamic properties. """ names = list(self.__dict__.keys()) for c in self._instance.children(): child_name = str(c.objectName()) if child_name: names.append(child_name) for pn in self._instance.dynamicPropertyNames(): names.append(str(pn)) return names def __enter__(self): print("__enter__", self) def __exit__(self, exc_type, exc_value, traceback): print("__exit__", self, exc_type, exc_value, traceback) class PyQtProperty(object): # slots for more speed __slots__ = ["meta_property", "name", "__doc__", "read_only"] def __init__(self, meta_property): self.meta_property = meta_property self.name = meta_property.name() self.read_only = not meta_property.isWritable() self.__doc__ = "%s is a %s%s" % ( self.name, meta_property.typeName(), self.read_only and " (read-only)" or "" ) def get(self, obj): return convert_value(self.meta_property.read(obj._instance)) def set(self, obj, value): self.meta_property.write(obj._instance, value) class PyQtMethod(object): __slots__ = ["meta_method", "name", "args", "returnType", "__doc__"] def __init__(self, meta_method): self.meta_method = meta_method self.name, args = str(meta_method.methodSignature(), encoding="utf-8").split("(", 1) self.args = args[:-1].split(",") self.returnType = str(meta_method.typeName()) types = [str(t, encoding="utf-8") for t in meta_method.parameterTypes()] names = [str(n, encoding="utf-8") or "arg%i" % (i+1) \ for i, n in enumerate(meta_method.parameterNames())] params = ", ".join("%s %s" % (t, n) for n, t in zip(types, names)) self.__doc__ = "%s(%s)%s" % ( self.name, params, self.returnType and (" -> %s" % self.returnType) or "" ) def instancemethod(self): def wrapper(obj, *args): qargs = [Q_ARG(t, v) for t, v in zip(self.args, args)] invoke_args = [obj._instance, self.name] invoke_args.append(Qt.DirectConnection) rtype = self.returnType if rtype: invoke_args.append(Q_RETURN_ARG(rtype)) invoke_args.extend(qargs) try: result = QMetaObject.invokeMethod(*invoke_args) except RuntimeError as e: raise TypeError( "%s.%s(%r) call failed: %s" % (obj, self.name, args, e)) return wrap(result) wrapper.__doc__ = self.__doc__ return wrapper # Cache on-the-fly-created classes for better speed pyqt_classes = {} def create_pyqt_class(metaobject): class_name = str(metaobject.className()) cls = pyqt_classes.get(class_name) if cls: return cls attrs = {} properties = attrs["__properties__"] = {} for i in range(metaobject.propertyCount()): prop = PyQtProperty(metaobject.property(i)) prop_name = str(prop.name) if prop.read_only: properties[prop_name] = attrs[prop_name] = property(prop.get, doc=prop.__doc__) else: properties[prop_name] = attrs[prop_name] = property( prop.get, prop.set, doc=prop.__doc__) methods = attrs["__methods__"] = {} signals = attrs["__signals__"] = {} for i in range(metaobject.methodCount()): meta_method = metaobject.method(i) if meta_method.methodType() != QMetaMethod.Signal : method = PyQtMethod(meta_method) method_name = method.name if method_name in attrs: # There is already a property with this name # So append an underscore method_name += "_" instance_method = method.instancemethod() instance_method.__doc__ = method.__doc__ methods[method_name] = attrs[method_name] = instance_method else : method_name = meta_method.name() signal_attrs = [] properties[bytes(method_name).decode('ascii')] = pyqtSignal(meta_method.parameterTypes()) # Dynamically create a class with a base class and a dictionary cls = type(class_name, (PyQtClass,), attrs) pyqt_classes[class_name] = cls return cls def create_pyqt_object(obj): """ Wrap a QObject and make all slots and properties dynamically available. @type obj: QObject @param obj: an unwrapped QObject @rtype: PyQtClass object @return: dynamically created object with all available properties and slots This is probably the only function you need from this module. Everything else are helper functions and classes. """ cls = create_pyqt_class(obj.metaObject()) return cls(obj)