1""" 2This module implements a callback function that is used by the C code to 3add Python special methods to Objective-C classes with a suitable interface. 4 5This module contains no user callable code. 6 7TODO: 8- Add external interface: Framework specific modules may want to add to this. 9 10- These are candidates for implementation: 11 12 >>> from Foundation import * 13 >>> set(dir(list)) - set(dir(NSMutableArray)) 14 set(['__delslice__', '__imul__', '__getslice__', '__setslice__', 15 '__iadd__', '__mul__', '__add__', '__rmul__']) 16 >>> set(dir(dict)) - set(dir(NSMutableDictionary)) 17 set(['__cmp__']) 18 19""" 20from _objc import setClassExtender, selector, lookUpClass, currentBundle, repythonify, splitSignature, _block_call 21from itertools import imap 22import sys 23 24__all__ = ( 'addConvenienceForSelector', 'addConvenienceForClass' ) 25 26 27# 12041508: NSDictionary now defines both objectForKey: and containsObject:, 28# so that the defining of __contains__ is order dependent (and often wrong). 29# So we define an OVERRIDE dict, whose keys are pipe-separated, triple strings: 30# 31# 1) the python method in question 32# 2) the first selector 33# 3) the overriding selector 34# 35# So "__contains__|containsObject:|objectForKey:" means that if containsObject: 36# was first, and has already defined __contains__, the objectForKey: can 37# later override the definition of __contains__ (but not in the reverse order). 38OVERRIDE = {'__contains__|containsObject:|objectForKey:': 1} 39def _canOverride(meth, owner, sel): 40 if not meth in owner: 41 return False 42 k = meth + '|' + owner[meth] + '|' + sel 43 return k in OVERRIDE 44 45CONVENIENCE_METHODS = {} 46CLASS_METHODS = {} 47 48def addConvenienceForSelector(selector, methods): 49 """ 50 Add the list with methods to every class that has a selector with the 51 given name. 52 """ 53 CONVENIENCE_METHODS[selector] = methods 54 55def addConvenienceForClass(classname, methods): 56 """ 57 Add the list with methods to the class with the specified name 58 """ 59 CLASS_METHODS[classname] = methods 60 61NSObject = lookUpClass('NSObject') 62 63def isNative(sel): 64 return not hasattr(sel, 'callable') 65 66def add_convenience_methods(super_class, name, type_dict): 67 try: 68 return _add_convenience_methods(super_class, name, type_dict) 69 except: 70 import traceback 71 traceback.print_exc() 72 raise 73 74def _add_convenience_methods(super_class, name, type_dict): 75 """ 76 Add additional methods to the type-dict of subclass 'name' of 77 'super_class'. 78 79 CONVENIENCE_METHODS is a global variable containing a mapping from 80 an Objective-C selector to a Python method name and implementation. 81 82 CLASS_METHODS is a global variable containing a mapping from 83 class name to a list of Python method names and implementation. 84 85 Matching entries from both mappings are added to the 'type_dict'. 86 """ 87 if type_dict.get('__objc_python_subclass__'): 88 if 'bundleForClass' not in type_dict: 89 cb = currentBundle() 90 def bundleForClass(cls): 91 return cb 92 type_dict['bundleForClass'] = selector(bundleForClass, isClassMethod=True) 93 if ('__useKVO__' not in type_dict and 94 isNative(type_dict.get('willChangeValueForKey_')) and 95 isNative(type_dict.get('didChangeValueForKey_'))): 96 useKVO = issubclass(super_class, NSObject) 97 type_dict['__useKVO__'] = useKVO 98 if '__bundle_hack__' in type_dict: 99 import warnings 100 warnings.warn( 101 "__bundle_hack__ is not necessary in PyObjC 1.3+ / py2app 0.1.8+", 102 DeprecationWarning) 103 104 owner = {} 105 for k, sel in type_dict.items(): 106 if not isinstance(sel, selector): 107 continue 108 109 # 110 # Handle some common exceptions to the usual rules: 111 # 112 113 sel = sel.selector 114 115 if sel in CONVENIENCE_METHODS: 116 v = CONVENIENCE_METHODS[sel] 117 for nm, value in v: 118 if nm in type_dict and isinstance(type_dict[nm], selector): 119 120 # Clone attributes of already existing version 121 122 t = type_dict[nm] 123 v = selector(value, selector=t.selector, 124 signature=t.signature, isClassMethod=t.isClassMethod) 125 126 type_dict[nm] = v 127 elif nm not in type_dict or _canOverride(nm, owner, sel): 128 type_dict[nm] = value 129 owner[nm] = sel 130 131 if name in CLASS_METHODS: 132 for nm, value in CLASS_METHODS[name]: 133 type_dict[nm] = value 134 135 136 if name == 'NSObject': 137 class kvc (object): 138 """ 139 Key-Value-Coding accessor for Cocoa objects. 140 141 Both attribute access and dict-like indexing will attempt to 142 access the requested item through Key-Value-Coding. 143 """ 144 __slots__ = ('__object',) 145 def __init__(self, value): 146 self.__object = value 147 148 def __repr__(self): 149 return "<KVC accessor for %r>"%(self.__object,) 150 151 def __getattr__(self, key): 152 try: 153 return self.__object.valueForKey_(key) 154 except KeyError, msg: 155 if hasattr(msg, '_pyobjc_info_') and msg._pyobjc_info_['name'] == 'NSUnknownKeyException': 156 raise AttributeError(key) 157 158 raise 159 def __setattr__(self, key, value): 160 if not key.startswith('_'): 161 return self.__object.setValue_forKey_(value, key) 162 else: 163 super(kvc, self).__setattr__(key, value) 164 165 def __getitem__(self, key): 166 if not isinstance(key, (str, unicode)): 167 raise TypeError("Key must be string") 168 return self.__object.valueForKey_(key) 169 170 def __setitem__(self, key, value): 171 if not isinstance(key, (str, unicode)): 172 raise TypeError("Key must be string") 173 return self.__object.setValue_forKey_(value, key) 174 175 type_dict['_'] = property(kvc) 176 177setClassExtender(add_convenience_methods) 178 179 180# 181# The following conveniences should strictly speaking be in 182# in pyobjc-framework-Foundation, but as they are very fundamental 183# we're keeping them here. 184# 185 186def __getitem__objectForKey_(self, key): 187 res = self.objectForKey_(container_wrap(key)) 188 return container_unwrap(res, KeyError, key) 189 190def has_key_objectForKey_(self, key): 191 res = self.objectForKey_(container_wrap(key)) 192 return res is not None 193 194def get_objectForKey_(self, key, dflt=None): 195 res = self.objectForKey_(container_wrap(key)) 196 if res is None: 197 res = dflt 198 return res 199 200CONVENIENCE_METHODS['objectForKey:'] = ( 201 ('__getitem__', __getitem__objectForKey_), 202 ('has_key', has_key_objectForKey_), 203 ('get', get_objectForKey_), 204 ('__contains__', has_key_objectForKey_), 205) 206 207def __delitem__removeObjectForKey_(self, key): 208 self.removeObjectForKey_(container_wrap(key)) 209 210CONVENIENCE_METHODS['removeObjectForKey:'] = ( 211 ('__delitem__', __delitem__removeObjectForKey_), 212) 213 214def update_setObject_forKey_(self, other): 215 # XXX - should this be more flexible? 216 for key, value in other.items(): 217 self[key] = value 218 219def setdefault_setObject_forKey_(self, key, dflt=None): 220 try: 221 return self[key] 222 except KeyError: 223 self[key] = dflt 224 return dflt 225 226def __setitem__setObject_forKey_(self, key, value): 227 self.setObject_forKey_(container_wrap(value), container_wrap(key)) 228 229def pop_setObject_forKey_(self, key, dflt=None): 230 try: 231 res = self[key] 232 except KeyError: 233 res = dflt 234 else: 235 del self[key] 236 return res 237 238def popitem_setObject_forKey_(self): 239 try: 240 k = self[iter(self).next()] 241 except StopIteration: 242 raise KeyError, "popitem on an empty %s" % (type(self).__name__,) 243 else: 244 return (k, self[k]) 245 246CONVENIENCE_METHODS['setObject:forKey:'] = ( 247 ('__setitem__', __setitem__setObject_forKey_), 248 ('update', update_setObject_forKey_), 249 ('setdefault', setdefault_setObject_forKey_), 250 ('pop', pop_setObject_forKey_), 251 ('popitem', popitem_setObject_forKey_), 252) 253 254 255CONVENIENCE_METHODS['count'] = ( 256 ('__len__', lambda self: self.count()), 257) 258 259CONVENIENCE_METHODS['containsObject:'] = ( 260 ('__contains__', lambda self, elem: bool(self.containsObject_(container_wrap(elem)))), 261) 262 263 264 265def objc_hash(self, _max=sys.maxint, _const=((sys.maxint + 1L) * 2L)): 266 rval = self.hash() 267 if rval > _max: 268 rval -= _const 269 # -1 is not a valid hash in Python and hash(x) will 270 # translate a hash of -1 to -2, so we might as well 271 # do it here so that it's not too surprising.. 272 if rval == -1: 273 rval = -2 274 return int(rval) 275CONVENIENCE_METHODS['hash'] = ( 276 ('__hash__', objc_hash), 277) 278 279CONVENIENCE_METHODS['isEqualTo:'] = ( 280 ('__eq__', lambda self, other: bool(self.isEqualTo_(other))), 281) 282 283CONVENIENCE_METHODS['isEqual:'] = ( 284 ('__eq__', lambda self, other: bool(self.isEqual_(other))), 285) 286 287CONVENIENCE_METHODS['isGreaterThan:'] = ( 288 ('__gt__', lambda self, other: bool(self.isGreaterThan_(other))), 289) 290 291CONVENIENCE_METHODS['isGreaterThanOrEqualTo:'] = ( 292 ('__ge__', lambda self, other: bool(self.isGreaterThanOrEqualTo_(other))), 293) 294 295CONVENIENCE_METHODS['isLessThan:'] = ( 296 ('__lt__', lambda self, other: bool(self.isLessThan_(other))), 297) 298 299CONVENIENCE_METHODS['isLessThanOrEqualTo:'] = ( 300 ('__le__', lambda self, other: bool(self.isLessThanOrEqualTo_(other))), 301) 302 303CONVENIENCE_METHODS['isNotEqualTo:'] = ( 304 ('__ne__', lambda self, other: bool(self.isNotEqualTo_(other))), 305) 306 307CONVENIENCE_METHODS['length'] = ( 308 ('__len__', lambda self: self.length()), 309) 310 311CONVENIENCE_METHODS['addObject:'] = ( 312 ('append', lambda self, item: self.addObject_(container_wrap(item))), 313) 314 315def reverse_exchangeObjectAtIndex_withObjectAtIndex_(self): 316 begin = 0 317 end = len(self) - 1 318 while begin < end: 319 self.exchangeObjectAtIndex_withObjectAtIndex_(begin, end) 320 begin += 1 321 end -= 1 322 323CONVENIENCE_METHODS['exchangeObjectAtIndex:withObjectAtIndex:'] = ( 324 ('reverse', reverse_exchangeObjectAtIndex_withObjectAtIndex_), 325) 326 327def ensureArray(anArray): 328 if not isinstance(anArray, (NSArray, list, tuple)): 329 anArray = list(anArray) 330 return anArray 331 332 333def extend_addObjectsFromArray_(self, anArray): 334 self.addObjectsFromArray_(ensureArray(anArray)) 335 336CONVENIENCE_METHODS['addObjectsFromArray:'] = ( 337 ('extend', extend_addObjectsFromArray_), 338) 339 340def index_indexOfObject_(self, item): 341 from Foundation import NSNotFound 342 res = self.indexOfObject_(container_wrap(item)) 343 if res == NSNotFound: 344 raise ValueError, "%s.index(x): x not in list" % (type(self).__name__,) 345 return res 346 347CONVENIENCE_METHODS['indexOfObject:'] = ( 348 ('index', index_indexOfObject_), 349) 350 351def insert_insertObject_atIndex_(self, idx, item): 352 if idx < 0: 353 idx += len(self) 354 if idx < 0: 355 raise IndexError("list index out of range") 356 self.insertObject_atIndex_(container_wrap(item), idx) 357 358CONVENIENCE_METHODS['insertObject:atIndex:'] = ( 359 ( 'insert', insert_insertObject_atIndex_), 360) 361 362def __getitem__objectAtIndex_(self, idx): 363 if isinstance(idx, slice): 364 start, stop, step = idx.indices(len(self)) 365 #if step == 1: 366 # m = getattr(self, 'subarrayWithRange_', None) 367 # if m is not None: 368 # return m((start, stop - start)) 369 return [self[i] for i in xrange(start, stop, step)] 370 if idx < 0: 371 idx += len(self) 372 if idx < 0: 373 raise IndexError("list index out of range") 374 375 return container_unwrap(self.objectAtIndex_(idx), RuntimeError) 376 377CONVENIENCE_METHODS['objectAtIndex:'] = ( 378 ('__getitem__', __getitem__objectAtIndex_), 379) 380 381def __delitem__removeObjectAtIndex_(self, idx): 382 if isinstance(idx, slice): 383 start, stop, step = idx.indices(len(self)) 384 if step == 1: 385 if start > stop: 386 start, stop = stop, start 387 m = getattr(self, 'removeObjectsInRange_', None) 388 if m is not None: 389 m((start, stop - start)) 390 return 391 r = range(start, stop, step) 392 r.sort() 393 r.reverse() 394 for i in r: 395 self.removeObjectAtIndex_(i) 396 return 397 if idx < 0: 398 idx += len(self) 399 if idx < 0: 400 raise IndexError("list index out of range") 401 402 self.removeObjectAtIndex_(idx) 403 404def pop_removeObjectAtIndex_(self, idx=-1): 405 length = len(self) 406 if length <= 0: 407 raise IndexError("pop from empty list") 408 elif idx >= length or (idx + length) < 0: 409 raise IndexError("pop index out of range") 410 elif idx < 0: 411 idx += len(self) 412 if idx < 0: 413 raise IndexError("list index out of range") 414 rval = self[idx] 415 self.removeObjectAtIndex_(idx) 416 return rval 417 418def remove_removeObjectAtIndex_(self, obj): 419 idx = self.index(obj) 420 self.removeObjectAtIndex_(idx) 421 422CONVENIENCE_METHODS['removeObjectAtIndex:'] = ( 423 ('remove', remove_removeObjectAtIndex_), 424 ('pop', pop_removeObjectAtIndex_), 425 ('__delitem__', __delitem__removeObjectAtIndex_), 426) 427 428def __setitem__replaceObjectAtIndex_withObject_(self, idx, anObject): 429 if isinstance(idx, slice): 430 start, stop, step = idx.indices(len(self)) 431 if step == 1: 432 m = getattr(self, 'replaceObjectsInRange_withObjectsFromArray_', None) 433 if m is not None: 434 m((start, stop - start), ensureArray(anObject)) 435 return 436 # XXX - implement this.. 437 raise NotImplementedError 438 if idx < 0: 439 idx += len(self) 440 if idx < 0: 441 raise IndexError("list index out of range") 442 443 self.replaceObjectAtIndex_withObject_(idx, anObject) 444 445CONVENIENCE_METHODS['replaceObjectAtIndex:withObject:'] = ( 446 ('__setitem__', __setitem__replaceObjectAtIndex_withObject_), 447) 448 449def enumeratorGenerator(anEnumerator): 450 while True: 451 yield container_unwrap(anEnumerator.nextObject(), StopIteration) 452 453def dictItems(aDict): 454 """ 455 NSDictionary.items() 456 """ 457 keys = aDict.allKeys() 458 return zip(keys, imap(aDict.__getitem__, keys)) 459 460CONVENIENCE_METHODS['allKeys'] = ( 461 ('keys', lambda self: self.allKeys()), 462 ('items', lambda self: dictItems(self)), 463) 464 465CONVENIENCE_METHODS['allValues'] = ( 466 ('values', lambda self: self.allValues()), 467) 468 469def itemsGenerator(aDict): 470 for key in aDict: 471 yield (key, aDict[key]) 472 473def __iter__objectEnumerator_keyEnumerator(self): 474 meth = getattr(self, 'keyEnumerator', None) 475 if meth is None: 476 meth = self.objectEnumerator 477 return iter(meth()) 478 479CONVENIENCE_METHODS['keyEnumerator'] = ( 480 ('__iter__', __iter__objectEnumerator_keyEnumerator), 481 ('iterkeys', lambda self: iter(self.keyEnumerator())), 482 ('iteritems', lambda self: itemsGenerator(self)), 483) 484 485CONVENIENCE_METHODS['objectEnumerator'] = ( 486 ('__iter__', __iter__objectEnumerator_keyEnumerator), 487 ('itervalues', lambda self: iter(self.objectEnumerator())), 488) 489 490CONVENIENCE_METHODS['reverseObjectEnumerator'] = ( 491 ('__reversed__', lambda self: iter(self.reverseObjectEnumerator())), 492) 493 494CONVENIENCE_METHODS['removeAllObjects'] = ( 495 ('clear', lambda self: self.removeAllObjects()), 496) 497 498CONVENIENCE_METHODS['dictionaryWithDictionary:'] = ( 499 ('copy', lambda self: type(self).dictionaryWithDictionary_(self)), 500) 501 502CONVENIENCE_METHODS['nextObject'] = ( 503 ('__iter__', enumeratorGenerator), 504) 505 506# 507# NSNumber seems to be and abstract base-class that is implemented using 508# NSCFNumber, a CoreFoundation 'proxy'. 509# 510NSNull = lookUpClass('NSNull') 511NSArray = lookUpClass('NSArray') 512#null = NSNull.null() 513 514number_wrap = repythonify 515 516def container_wrap(v): 517 if v is None: 518 return NSNull.null() 519 return v 520 521def container_unwrap(v, exc_type, *exc_args): 522 if v is None: 523 raise exc_type(*exc_args) 524 elif v is NSNull.null(): 525 return None 526 return v 527 528 529def fromkeys_dictionaryWithObjects_forKeys_(cls, keys, values=None): 530 if not isinstance(keys, (list, tuple)): 531 keys = list(keys) 532 if values is None: 533 values = (None,) * len(keys) 534 elif not isinstance(values, (list, tuple)): 535 values = list(values) 536 return cls.dictionaryWithObjects_forKeys_(values, keys) 537 538CONVENIENCE_METHODS['dictionaryWithObjects:forKeys:'] = ( 539 ('fromkeys', 540 classmethod(fromkeys_dictionaryWithObjects_forKeys_)), 541) 542 543 544def sort(self, key=None, reverse=False, cmpfunc=cmp): 545 # NOTE: cmpfunc argument is for backward compatibility. 546 if key is None: 547 if reverse: 548 def doCmp(a, b, cmpfunc): 549 return -cmpfunc(a, b) 550 else: 551 def doCmp(a, b, cmpfunc): 552 return cmpfunc(a, b) 553 else: 554 # This is (a lot) slower than the algoritm used for 555 # list.sort, but so be it. 556 if reverse: 557 def doCmp(a, b, cmpfunc): 558 return -cmpfunc(key(a), key(b)) 559 else: 560 def doCmp(a, b, cmpfunc): 561 return cmpfunc(key(a), key(b)) 562 563 self.sortUsingFunction_context_(doCmp, cmpfunc) 564 565 566 567CONVENIENCE_METHODS['sortUsingFunction:context:'] = ( 568 ('sort', sort), 569) 570 571CONVENIENCE_METHODS['hasPrefix:'] = ( 572 ('startswith', lambda self, pfx: self.hasPrefix_(pfx)), 573) 574 575CONVENIENCE_METHODS['hasSuffix:'] = ( 576 ('endswith', lambda self, pfx: self.hasSuffix_(pfx)), 577) 578 579 580CONVENIENCE_METHODS['copyWithZone:'] = ( 581 ('__copy__', lambda self: self.copyWithZone_(None)), 582) 583 584# This won't work: 585#NSKeyedArchiver = lookUpClass('NSKeyedArchiver') 586#NSKeyedUnarchiver = lookUpClass('NSKeyedUnarchiver') 587#def coder_deepcopy(self, memo): 588# buf = NSKeyedArchiver.archivedDataWithRootObject_(self) 589# result = NSKeyedUnarchiver.unarchiveObjectWithData_(buf) 590# return result 591# 592#CONVENIENCE_METHODS['encodeWithCoder:'] = ( 593# ('__deepcopy__', coder_deepcopy ), 594#) 595 596CLASS_METHODS['NSNull'] = ( 597 ('__nonzero__', lambda self: False ), 598) 599 600NSDecimalNumber = lookUpClass('NSDecimalNumber') 601def _makeD(v): 602 if isinstance(v, NSDecimalNumber): 603 return v 604 return NSDecimalNumber.decimalNumberWithDecimal_(v) 605 606CLASS_METHODS['NSDecimalNumber'] = ( 607 ('__add__', lambda self, other: _makeD(self.decimalValue() + other)), 608 ('__radd__', lambda self, other: _makeD(other + self.decimalValue())), 609 ('__sub__', lambda self, other: _makeD(self.decimalValue() - other)), 610 ('__rsub__', lambda self, other: _makeD(other - self.decimalValue())), 611 ('__mul__', lambda self, other: _makeD(self.decimalValue() * other)), 612 ('__rmul__', lambda self, other: _makeD(other * self.decimalValue())), 613 ('__div__', lambda self, other: _makeD(self.decimalValue() / other)), 614 ('__rdiv__', lambda self, other: _makeD(other / self.decimalValue())), 615 ('__mod__', lambda self, other: _makeD(self.decimalValue() % other)), 616 ('__rmod__', lambda self, other: _makeD(other % self.decimalValue())), 617 ('__neg__', lambda self: _makeD(-(self.decimalValue()))), 618 ('__pos__', lambda self: _makeD(+(self.decimalValue()))), 619 ('__abs__', lambda self: _makeD(abs(self.decimalValue()))), 620) 621 622def NSData__getslice__(self, i, j): 623 return self.bytes()[i:j] 624 625def NSData__getitem__(self, item): 626 buff = self.bytes() 627 try: 628 return buff[item] 629 except TypeError: 630 return buff[:][item] 631 632CLASS_METHODS['NSData'] = ( 633 ('__str__', lambda self: self.bytes()[:]), 634 ('__getitem__', NSData__getitem__), 635 ('__getslice__', NSData__getslice__), 636) 637 638def NSMutableData__setslice__(self, i, j, sequence): 639 # XXX - could use replaceBytes:inRange:, etc. 640 self.mutableBytes()[i:j] = sequence 641 642def NSMutableData__setitem__(self, item, value): 643 self.mutableBytes()[item] = value 644 645CLASS_METHODS['NSMutableData'] = ( 646 ('__setslice__', NSMutableData__setslice__), 647 ('__setitem__', NSMutableData__setitem__), 648) 649 650 651def __call__(self, *args, **kwds): 652 return _block_call(self, self.__block_signature__, args, kwds) 653 654CLASS_METHODS['NSBlock'] = ( 655 ('__call__', __call__), 656) 657