Module mikro
[hide private]
[frames] | no frames]

Source Code for Module mikro

  1  # -*- coding: utf-8 -*- 
  2  """ 
  3  Mini Kross - a scripting solution inspired by Kross (http://kross.dipe.org/) 
  4  Technically this is one of the most important modules in ScripterNG. 
  5  Via the Qt meta object system it provides access to unwrapped objects.  
  6  This code uses a lot of metaprogramming magic. To fully understand it, 
  7  you have to know about metaclasses in Python 
  8  """ 
  9  from __future__ import with_statement 
 10  import sip 
 11  from PyQt4.QtCore import ( 
 12      QMetaObject, Q_RETURN_ARG, QString, Q_ARG,   
 13      QObject, QVariant, Qt, SIGNAL, QMetaMethod) 
 14  from PyQt4.QtGui import QBrush, QFont, QPixmap, qApp, QImage, QPalette 
 15   
 16   
 17  variant_converter = { 
 18    # XXX QList<type>, QMap<*>, longlong 
 19    "QVariantList": lambda v: from_variantlist(v), 
 20    "QList<QVariant>": lambda v: v.toList(), 
 21    "int": lambda v: v.toInt()[0], 
 22    "double": lambda v: v.toDouble()[0], 
 23    "char": lambda v: v.toChar(), 
 24    "QByteArray": lambda v: v.toByteArray(), 
 25    "QString": lambda v: unicode(v.toString()), 
 26    "QPoint": lambda v: v.toPoint(), 
 27    "QPointF": lambda v: v.toPointF(), 
 28    "QSize": lambda v: v.toSize(), 
 29    "QLine": lambda v: v.toLine(), 
 30    "QStringList": lambda v: v.toStringList(), 
 31    "QTime": lambda v: v.toTime(), 
 32    "QDateTime": lambda v: v.toDateTime(), 
 33    "QDate": lambda v: v.toDate(), 
 34    "QLocale": lambda v: v.toLocale(), 
 35    "QUrl": lambda v: v.toUrl(), 
 36    "QRect": lambda v: v.toRect(), 
 37    "QBrush": lambda v: QBrush(v), 
 38    "QFont": lambda v: QFont(v), 
 39    "QPalette": lambda v: QPalette(v), 
 40    "QPixmap": lambda v: QPixmap(v), 
 41    "QImage": lambda v: QImage(v), 
 42    "bool": lambda v: v.toBool(), 
 43    "QObject*": lambda v: ScripterNG.fromVariant(v), 
 44    "QWidget*": lambda v: ScripterNG.fromVariant(v), 
 45  } 
46 47 48 49 -def from_variantlist(variantlist):
50 """ 51 convert QList<QVariant> to a normal Python list 52 """ 53 return [from_variant(variant) for variant in variantlist.toList()]
54
55 56 57 -def classname(obj):
58 """ 59 return real class name 60 Unwrapped classes will be represended in PyQt by a known base class. 61 So obj.__class__.__name__ will not return the desired class name 62 """ 63 return obj.metaObject().className()
64
65 66 67 -def from_variant(variant):
68 """ 69 convert a QVariant to a Python value 70 """ 71 typeName = variant.typeName() 72 convert = variant_converter.get(typeName) 73 if not convert: 74 raise ValueError, "Could not convert value to %s" % typeName 75 else: 76 return convert(variant)
77 78 79 80 qtclasses = {}
81 82 -def supercast(obj):
83 """ 84 cast a QObject subclass to the best known wrapped super class 85 """ 86 if not qtclasses: 87 # To get really all Qt classes I would have to 88 # import QtNetwork, QtXml, QtSvg and QtScript, too. 89 import PyQt4 90 qtclasses.update( 91 dict([(key, value) \ 92 for key, value in PyQt4.QtCore.__dict__.items() + PyQt4.QtGui.__dict__.items() \ 93 if hasattr(value, "__subclasses__") and issubclass(value, QObject)]) 94 ) 95 try: 96 if not issubclass(value, QObject): 97 return obj 98 except TypeError: 99 # no class - no cast... 100 return obj 101 mo = obj.metaObject() 102 while mo: 103 cls = qtclasses.get(str(mo.className())) 104 if cls: 105 return sip.cast(obj, cls) 106 mo = mo.superClass() 107 # This should never be reached 108 return obj
109
110 111 112 -def wrap(obj, force=False):
113 """ 114 If a class is not known by PyQt it will be automatically 115 casted to a known wrapped super class. 116 But that limits access to methods and propperties of this super class. 117 So instead this functions returns a wrapper class (PyQtClass) 118 which queries the metaObject and provides access to 119 all slots and all properties. 120 """ 121 if isinstance(obj, QString): 122 # prefer Python strings 123 return unicode(obj) 124 elif isinstance(obj, PyQtClass): 125 # already wrapped 126 return obj 127 if obj and isinstance(obj, QObject): 128 if force or obj.__class__.__name__ != obj.metaObject().className(): 129 # Ah this is an unwrapped class 130 obj = create_pyqt_object(obj) 131 return obj
132
133 134 135 -def is_wrapped(obj):
136 """ 137 checks if a object is wrapped by PyQtClass 138 """ 139 # XXX: Any better/faster check? 140 return hasattr(obj, "qt")
141
142 143 144 -def unwrap(obj):
145 """ 146 if wrapped returns the wrapped object 147 """ 148 if is_wrapped(obj): 149 obj = obj.qt 150 return obj
151
152 153 154 -def is_qobject(obj):
155 """ 156 checks if class or wrapped class is a subclass of QObject 157 """ 158 if hasattr(obj, "__bases__") and issubclass(unwrap(obj), QObject): 159 return True 160 else: 161 return False
162
163 164 -def is_scripterng_child(qobj):
165 """ 166 walk up the object tree until ScripterNG or the root is found 167 """ 168 found = False 169 p = qobj.parent() 170 while p and not found: 171 if str(p.objectName()) == "ScripterNG": 172 found = True 173 break 174 else: 175 p = p.parent() 176 return found
177
178 179 180 -class Error(Exception):
181 """ 182 Base error classed. Catch this to handle exceptions comming from C++ 183 """
184
185 186 187 -class PyQtClass(object):
188 """ 189 Base class 190 """ 191
192 - def __init__(self, instance):
193 self._instance = instance
194 195
196 - def __del__(self):
197 """ 198 If this object is deleted it should also delete the wrapped object 199 if it was created explicitly for this use. 200 """ 201 qobj = self._instance 202 if is_scripterng_child(qobj): 203 if len(qobj.children()): 204 print "Cannot delete", qobj, "because it has child objects" 205 #else: 206 # print "* deleting", qobj 207 # XXX: or better setdeleted ? 208 sip.delete(qobj)
209 #else: 210 # print "* NOT deleting", qobj 211 212
213 - def setProperty(self, name, value):
214 self._instance.setProperty(name, QVariant(value))
215 216
217 - def getProperty(self, name):
218 return wrap(self._instance.property(name))
219 220
221 - def propertyNames(self):
222 return self.__class__.__properties__.keys()
223 224
225 - def dynamicPropertyNames(self):
226 return self._instance.dynamicPropertyNames()
227 228
229 - def metaObject(self):
230 return self._instance.metaObject()
231 232
233 - def connect(self, signal, slot):
234 self._instance.connect(self._instance, SIGNAL(signal), slot)
235 236
237 - def disconnect(self, signal, slot):
238 self._instance.disconnect(self._instance, SIGNAL(signal), slot)
239 240
241 - def parent(self):
242 return wrap(self._instance.parent())
243 244
245 - def children(self):
246 return [wrap(c) for c in self._instance.children()]
247 248 249 @property
250 - def qt(self):
251 return self._instance
252 253
254 - def __getitem__(self, key):
255 if isinstance(key, int): 256 length = getattr(self, "length", None) 257 if length is not None: 258 # array protocol 259 try: 260 return getattr(self, str(key)) 261 except AttributeError, e: 262 raise IndexError, key 263 else: 264 return self.children()[key] 265 else: 266 return getattr(self, key)
267 268
269 - def __getattr__(self, name):
270 # Make named child objects available as attributes like QtScript 271 for child in self._instance.children(): 272 if str(child.objectName()) == name: 273 obj = wrap(child) 274 # save found object for faster lookup 275 setattr(self, name, obj) 276 return obj 277 # Dynamic object property? 278 variant = self._instance.property(name) 279 if variant.type() != 0: 280 return from_variant(variant) 281 raise AttributeError, name
282 283 284 @property
285 - def __members__(self):
286 """ 287 This method is for introspection. 288 Using dir(thispyqtclass_object) returns a list of 289 all children, methods, properties and dynamic properties. 290 """ 291 names = self.__dict__.keys() 292 for c in self._instance.children(): 293 child_name = str(c.objectName()) 294 if child_name: 295 names.append(child_name) 296 # XXX: add unnamed childs? 297 for pn in self._instance.dynamicPropertyNames(): 298 names.append(str(pn)) 299 return names
300 301
302 - def __enter__(self):
303 print "__enter__", self
304 305
306 - def __exit__(self, exc_type, exc_value, traceback):
307 print "__exit__", self, exc_type, exc_value, traceback
308
309 310 311 312 -class PyQtProperty(object):
313 314 # slots for more speed 315 __slots__ = ["meta_property", "name", "__doc__", "read_only"] 316 317
318 - def __init__(self, meta_property):
319 self.meta_property = meta_property 320 self.name = meta_property.name() 321 self.read_only = not meta_property.isWritable() 322 self.__doc__ = "%s is a %s%s" % ( 323 self.name, meta_property.typeName(), 324 self.read_only and " (read-only)" or "" 325 )
326 327
328 - def get(self, obj):
329 return from_variant(self.meta_property.read(obj._instance))
330 331
332 - def set(self, obj, value):
333 self.meta_property.write(obj._instance, QVariant(value))
334
335 336 337 338 -class PyQtMethod(object):
339 340 __slots__ = ["meta_method", "name", "args", "returnType", "__doc__"] 341 342
343 - def __init__(self, meta_method):
344 self.meta_method = meta_method 345 self.name, args = str(meta_method.signature()).split("(", 1) 346 self.args = args[:-1].split(",") 347 self.returnType = str(meta_method.typeName()) 348 349 types = [str(t) for t in meta_method.parameterTypes()] 350 names = [str(n) or "arg%i" % (i+1) \ 351 for i, n in enumerate(meta_method.parameterNames())] 352 params = ", ".join("%s %s" % (t, n) for n, t in zip(types, names)) 353 354 self.__doc__ = "%s(%s)%s" % ( 355 self.name, params, 356 self.returnType and (" -> %s" % self.returnType) or "" 357 )
358 359
360 - def instancemethod(self):
361 def wrapper(obj, *args): 362 # XXX: support kwargs? 363 qargs = [Q_ARG(t, v) for t, v in zip(self.args, args)] 364 invoke_args = [obj._instance, self.name] 365 invoke_args.append(Qt.DirectConnection) 366 rtype = self.returnType 367 if rtype: 368 invoke_args.append(Q_RETURN_ARG(rtype)) 369 invoke_args.extend(qargs) 370 try: 371 result = QMetaObject.invokeMethod(*invoke_args) 372 error_msg = str(qApp.property("MIKRO_EXCEPTION").toString()) 373 if error_msg: 374 # clear message 375 qApp.setProperty("MIKRO_EXCEPTION", QVariant()) 376 raise Error(error_msg) 377 except RuntimeError, e: 378 raise TypeError, \ 379 "%s.%s(%r) call failed: %s" % (obj, self.name, args, e) 380 return wrap(result)
381 wrapper.__doc__ = self.__doc__ 382 return wrapper
383
384 385 386 387 # Cache on-the-fly-created classes for better speed 388 # XXX Should I use weak references? 389 pyqt_classes = {} 390 391 -def create_pyqt_class(metaobject):
392 class_name = str(metaobject.className()) 393 cls = pyqt_classes.get(class_name) 394 if cls: 395 return cls 396 attrs = {} 397 398 properties = attrs["__properties__"] = {} 399 for i in range(metaobject.propertyCount()): 400 prop = PyQtProperty(metaobject.property(i)) 401 prop_name = str(prop.name) 402 #prop_name = prop_name[0].upper() + prop_name[1:] 403 if prop.read_only: 404 # XXX: write set-method which raises an error 405 properties[prop_name] = attrs[prop_name] = property(prop.get, doc=prop.__doc__) 406 else: 407 properties[prop_name] = attrs[prop_name] = property( 408 prop.get, prop.set, doc=prop.__doc__) 409 410 methods = attrs["__methods__"] = {} 411 for i in range(metaobject.methodCount()): 412 meta_method = metaobject.method(i) 413 if meta_method.methodType() != QMetaMethod.Signal: 414 method = PyQtMethod(meta_method) 415 method_name = method.name 416 if method_name in attrs: 417 # There is already a property with this name 418 # So append an underscore 419 method_name += "_" 420 instance_method = method.instancemethod() 421 instance_method.__doc__ = method.__doc__ 422 methods[method_name] = attrs[method_name] = instance_method 423 424 # Python is great :) 425 # It can dynamically create a class with a base class and a dictionary 426 cls = type(class_name, (PyQtClass,), attrs) 427 pyqt_classes[class_name] = cls 428 return cls
429
430 431 432 -def create_pyqt_object(obj):
433 """ 434 Wrap a QObject and make all slots and properties dynamically available. 435 @type obj: QObject 436 @param obj: an unwrapped QObject 437 @rtype: PyQtClass object 438 @return: dynamicaly created object with all available properties and slots 439 440 This is probably the only function you need from this module. 441 Everything else are helper functions and classes. 442 """ 443 cls = create_pyqt_class(obj.metaObject()) 444 return cls(obj)
445