3SPDX-FileCopyrightText: 2016 Boudewijn Rempt <boud@valdyas.org>
5SPDX-License-Identifier: LGPL-2.0-or-later
9Mini Kross - a scripting solution inspired by Kross (http://kross.dipe.org/)
11Technically this is one of the most important modules in Scripter.
12Via the Qt meta object system it provides access to unwrapped objects.
13This code uses a lot of metaprogramming magic. To fully understand it,
14you have to know about metaclasses in Python
21 from PyQt6.QtCore import QVariant, QMetaObject, Q_RETURN_ARG, Q_ARG, QObject, Qt, QMetaMethod, pyqtSignal
22 from PyQt6.QtGui import QBrush, QFont, QImage, QPalette, QPixmap
29 from PyQt5.QtCore import QVariant, QMetaObject, Q_RETURN_ARG, Q_ARG, QObject, Qt, QMetaMethod, pyqtSignal
30 from PyQt5.QtGui import QBrush, QFont, QImage, QPalette, QPixmap
35 "QVariantList":
lambda v: v.toList(v),
36 "QVariantMap":
lambda v: toPyObject(v),
37 "QPoint":
lambda v: v.toPoint(),
38 "str":
lambda v: v.toString(),
39 "int":
lambda v: v.toInt()[0],
40 "double":
lambda v: v.toDouble()[0],
41 "char":
lambda v: v.toChar(),
42 "QByteArray":
lambda v: v.toByteArray(),
43 "QPoint":
lambda v: v.toPoint(),
44 "QPointF":
lambda v: v.toPointF(),
45 "QSize":
lambda v: v.toSize(),
46 "QLine":
lambda v: v.toLine(),
47 "QStringList":
lambda v: v.toStringList(),
48 "QTime":
lambda v: v.toTime(),
49 "QDateTime":
lambda v: v.toDateTime(),
50 "QDate":
lambda v: v.toDate(),
51 "QLocale":
lambda v: v.toLocale(),
52 "QUrl":
lambda v: v.toUrl(),
53 "QRect":
lambda v: v.toRect(),
54 "QBrush":
lambda v: QBrush(v),
55 "QFont":
lambda v: QFont(v),
56 "QPalette":
lambda v: QPalette(v),
57 "QPixmap":
lambda v: QPixmap(v),
58 "QImage":
lambda v: QImage(v),
59 "bool":
lambda v: v.toBool(),
62 "ActionMap":
lambda v: int(v.count())
68 convert a QObject or a QWidget to its wrapped superclass
76 convert a QVariant to a Python value
79 if hasattr(variant,
'__type__')
and not (variant
is None or variant.type()
is None):
80 typeName = variant.typeName()
81 convert = variant_converter.get(typeName)
83 raise ValueError(
"Could not convert value to %s" % typeName)
94 Convert a given value, upcasting to the highest QObject-based class if possible,
95 unpacking lists and dicts.
99 if hasattr(value,
'__class__')
and issubclass(value.__class__, dict)
and len(value) > 0:
103 if hasattr(value,
'__class__')
and issubclass(value.__class__, list)
and len(value) > 0:
106 if isinstance(value, str):
110 elif isinstance(value, PyQtClass):
115 if hasattr(value,
'__class__')
and issubclass(value.__class__, QObject):
116 return wrap(value,
True)
118 if hasattr(value,
'__type__')
and not (value
is None or value.type()
is None):
128 If a class is not known by PyQt it will be automatically
129 casted to a known wrapped super class.
131 But that limits access to methods and properties of this super class.
132 So instead this functions returns a wrapper class (PyQtClass)
133 which queries the metaObject and provides access to
134 all slots and all properties.
136 if isinstance(obj, str):
139 elif isinstance(obj, PyQtClass):
142 elif obj
and isinstance(obj, QObject):
143 if force
or obj.__class__.__name__ != obj.metaObject().className():
151 if wrapped returns the wrapped object
153 if hasattr(obj,
"qt"):
160 checks if class or wrapped class is a subclass of QObject
162 if hasattr(obj,
"__bases__")
and issubclass(
unwrap(obj), QObject):
170 walk up the object tree until Scripter or the root is found
174 while p
and not found:
175 if str(p.objectName()) ==
"Krita":
186 Base error classed. Catch this to handle exceptions coming from C++
190class PyQtClass(object):
201 If this object is deleted it should also delete the wrapped object
202 if it was created explicitly for this use.
206 if len(qobj.children()):
207 print(
"Cannot delete", qobj,
"because it has child objects")
217 return list(self.__class__.__properties__.keys())
242 if isinstance(key, int):
243 length = getattr(self,
"length",
None)
244 if length
is not None:
247 return getattr(self, str(key))
248 except AttributeError
as e:
249 raise IndexError(key)
253 return getattr(self, key)
259 if str(child.objectName()) == name:
262 setattr(self, name, obj)
272 This method is for introspection.
273 Using dir(thispyqtclass_object) returns a list of
274 all children, methods, properties and dynamic properties.
276 names = list(self.__dict__.keys())
278 child_name = str(c.objectName())
280 names.append(child_name)
282 names.append(str(pn))
286 print(
"__enter__", self)
288 def __exit__(self, exc_type, exc_value, traceback):
289 print(
"__exit__", self, exc_type, exc_value, traceback)
295 __slots__ = [
"meta_property",
"name",
"__doc__",
"read_only"]
299 self.
name = meta_property.name()
302 self.
name, meta_property.typeName(),
309 def set(self, obj, value):
315 __slots__ = [
"meta_method",
"name",
"args",
"returnType",
"__doc__"]
319 self.
name, args = str(meta_method.methodSignature(), encoding=
"utf-8").split(
"(", 1)
320 self.
args = args[:-1].split(
",")
323 types = [str(t, encoding=
"utf-8")
for t
in meta_method.parameterTypes()]
324 names = [str(n, encoding=
"utf-8")
or "arg%i" % (i + 1)
325 for i, n
in enumerate(meta_method.parameterNames())]
326 params =
", ".join(
"%s %s" % (t, n)
for n, t
in zip(types, names))
334 def wrapper(obj, *args):
335 qargs = [Q_ARG(t, v)
for t, v
in zip(self.
args, args)]
336 invoke_args = [obj._instance, self.
name]
337 invoke_args.append(Qt.ConnectionType.DirectConnection)
340 invoke_args.append(Q_RETURN_ARG(rtype))
341 invoke_args.extend(qargs)
343 result = QMetaObject.invokeMethod(*invoke_args)
344 except RuntimeError
as e:
346 "%s.%s(%r) call failed: %s" % (obj, self.
name, args, e))
357 class_name = str(metaobject.className())
358 cls = pyqt_classes.get(class_name)
363 properties = attrs[
"__properties__"] = {}
364 for i
in range(metaobject.propertyCount()):
366 prop_name = str(prop.name)
368 properties[prop_name] = attrs[prop_name] = property(prop.get, doc=prop.__doc__)
370 properties[prop_name] = attrs[prop_name] = property(
371 prop.get, prop.set, doc=prop.__doc__)
373 methods = attrs[
"__methods__"] = {}
374 signals = attrs[
"__signals__"] = {}
375 for i
in range(metaobject.methodCount()):
376 meta_method = metaobject.method(i)
377 if meta_method.methodType() != QMetaMethod.MethodType.Signal:
379 method_name = method.name
380 if method_name
in attrs:
384 instance_method = method.instancemethod()
385 instance_method.__doc__ = method.__doc__
386 methods[method_name] = attrs[method_name] = instance_method
388 method_name = meta_method.name()
390 properties[bytes(method_name).decode(
'ascii')] = pyqtSignal(meta_method.parameterTypes())
393 cls = type(class_name, (PyQtClass,), attrs)
394 pyqt_classes[class_name] = cls
400 Wrap a QObject and make all slots and properties dynamically available.
402 @param obj: an unwrapped QObject
403 @rtype: PyQtClass object
404 @return: dynamically created object with all available properties and slots
406 This is probably the only function you need from this module.
407 Everything else are helper functions and classes.
static QObject * fromVariant(const QVariant &v)
connect(self, signal, slot)
dynamicPropertyNames(self)
disconnect(self, signal, slot)
setProperty(self, name, value)
__exit__(self, exc_type, exc_value, traceback)
__init__(self, meta_method)
__init__(self, meta_property)
wrap_variant_object(variant)
create_pyqt_class(metaobject)