603 lines
18 KiB
Python
Executable File
603 lines
18 KiB
Python
Executable File
"""
|
|
This module implements a callback function that is used by the C code to
|
|
add Python special methods to Objective-C classes with a suitable interface.
|
|
|
|
This module contains no user callable code.
|
|
|
|
TODO:
|
|
- Add external interface: Framework specific modules may want to add to this.
|
|
|
|
- These are candidates for implementation:
|
|
|
|
>>> from Foundation import *
|
|
>>> set(dir(list)) - set(dir(NSMutableArray))
|
|
set(['__delslice__', '__imul__', '__getslice__', '__setslice__',
|
|
'__iadd__', '__mul__', '__add__', '__rmul__'])
|
|
>>> set(dir(dict)) - set(dir(NSMutableDictionary))
|
|
set(['__cmp__'])
|
|
|
|
"""
|
|
from _objc import setClassExtender, selector, lookUpClass, currentBundle, repythonify, splitSignature
|
|
from itertools import imap
|
|
|
|
__all__ = ['CONVENIENCE_METHODS', 'CLASS_METHODS']
|
|
|
|
CONVENIENCE_METHODS = {}
|
|
CLASS_METHODS = {}
|
|
|
|
NSObject = lookUpClass('NSObject')
|
|
|
|
HAS_KVO = NSObject.instancesRespondToSelector_('willChangeValueForKey:')
|
|
|
|
def isNative(sel):
|
|
return not hasattr(sel, 'callable')
|
|
|
|
def add_convenience_methods(super_class, name, type_dict):
|
|
try:
|
|
return _add_convenience_methods(super_class, name, type_dict)
|
|
except:
|
|
import traceback
|
|
traceback.print_exc()
|
|
raise
|
|
|
|
|
|
def _add_convenience_methods(super_class, name, type_dict):
|
|
"""
|
|
Add additional methods to the type-dict of subclass 'name' of
|
|
'super_class'.
|
|
|
|
CONVENIENCE_METHODS is a global variable containing a mapping from
|
|
an Objective-C selector to a Python method name and implementation.
|
|
|
|
CLASS_METHODS is a global variable containing a mapping from
|
|
class name to a list of Python method names and implementation.
|
|
|
|
Matching entries from both mappings are added to the 'type_dict'.
|
|
"""
|
|
if type_dict.get('__objc_python_subclass__'):
|
|
if 'bundleForClass' not in type_dict:
|
|
cb = currentBundle()
|
|
def bundleForClass(cls):
|
|
return cb
|
|
type_dict['bundleForClass'] = selector(bundleForClass, isClassMethod=True)
|
|
if (HAS_KVO and
|
|
'__useKVO__' not in type_dict and
|
|
isNative(type_dict.get('willChangeValueForKey_')) and
|
|
isNative(type_dict.get('didChangeValueForKey_'))):
|
|
useKVO = issubclass(super_class, NSObject)
|
|
type_dict['__useKVO__'] = useKVO
|
|
if '__bundle_hack__' in type_dict:
|
|
import warnings
|
|
warnings.warn(
|
|
"__bundle_hack__ is not necessary in PyObjC 1.3+ / py2app 0.1.8+",
|
|
DeprecationWarning)
|
|
|
|
for k, sel in type_dict.items():
|
|
if not isinstance(sel, selector):
|
|
continue
|
|
|
|
if sel.selector == "alloc" or sel.selector == "allocWithZone:":
|
|
sel.isAlloc = 1
|
|
|
|
if sel.selector.endswith(':error:'):
|
|
sigParts = splitSignature(sel.signature)
|
|
if sigParts[-1] == '^@':
|
|
sigParts = sigParts[:-1] + ('o^@',)
|
|
sel.signature = ''.join(sigParts)
|
|
|
|
if sel.selector in ( 'copy', 'copyWithZone:',
|
|
'mutableCopy', 'mutableCopyWithZone:'):
|
|
# These methods transfer ownership to the caller, the runtime uses
|
|
# this information to adjust the reference count.
|
|
sel.doesDonateReference = 1
|
|
|
|
sel = sel.selector
|
|
|
|
if sel in CONVENIENCE_METHODS:
|
|
v = CONVENIENCE_METHODS[sel]
|
|
for nm, value in v:
|
|
if nm in type_dict and isinstance(type_dict[nm], selector):
|
|
|
|
# Clone attributes of already existing version
|
|
|
|
t = type_dict[nm]
|
|
v = selector(value, selector=t.selector,
|
|
signature=t.signature, isClassMethod=t.isClassMethod)
|
|
v.isAlloc = t.isAlloc
|
|
|
|
type_dict[nm] = v
|
|
else:
|
|
type_dict[nm] = value
|
|
|
|
if name in CLASS_METHODS:
|
|
for nm, value in CLASS_METHODS[name]:
|
|
type_dict[nm] = value
|
|
|
|
setClassExtender(add_convenience_methods)
|
|
|
|
def __getitem__objectForKey_(self, key):
|
|
res = self.objectForKey_(container_wrap(key))
|
|
return container_unwrap(res, KeyError, key)
|
|
|
|
def has_key_objectForKey_(self, key):
|
|
res = self.objectForKey_(container_wrap(key))
|
|
return res is not None
|
|
|
|
def get_objectForKey_(self, key, dflt=None):
|
|
res = self.objectForKey_(container_wrap(key))
|
|
if res is None:
|
|
res = dflt
|
|
return res
|
|
|
|
CONVENIENCE_METHODS['objectForKey:'] = (
|
|
('__getitem__', __getitem__objectForKey_),
|
|
('has_key', has_key_objectForKey_),
|
|
('get', get_objectForKey_),
|
|
('__contains__', has_key_objectForKey_),
|
|
)
|
|
|
|
def __delitem__removeObjectForKey_(self, key):
|
|
self.removeObjectForKey_(container_wrap(key))
|
|
|
|
CONVENIENCE_METHODS['removeObjectForKey:'] = (
|
|
('__delitem__', __delitem__removeObjectForKey_),
|
|
)
|
|
|
|
def update_setObject_forKey_(self, other):
|
|
# XXX - should this be more flexible?
|
|
for key, value in other.items():
|
|
self[key] = value
|
|
|
|
def setdefault_setObject_forKey_(self, key, dflt=None):
|
|
try:
|
|
return self[key]
|
|
except KeyError:
|
|
self[key] = dflt
|
|
return dflt
|
|
|
|
def __setitem__setObject_forKey_(self, key, value):
|
|
self.setObject_forKey_(container_wrap(value), container_wrap(key))
|
|
|
|
def pop_setObject_forKey_(self, key, dflt=None):
|
|
try:
|
|
res = self[key]
|
|
except KeyError:
|
|
res = dflt
|
|
else:
|
|
del self[key]
|
|
return res
|
|
|
|
def popitem_setObject_forKey_(self):
|
|
try:
|
|
k = self[iter(self).next()]
|
|
except StopIteration:
|
|
raise KeyError, "popitem on an empty %s" % (type(self).__name__,)
|
|
else:
|
|
return (k, self[k])
|
|
|
|
CONVENIENCE_METHODS['setObject:forKey:'] = (
|
|
('__setitem__', __setitem__setObject_forKey_),
|
|
('update', update_setObject_forKey_),
|
|
('setdefault', setdefault_setObject_forKey_),
|
|
('pop', pop_setObject_forKey_),
|
|
('popitem', popitem_setObject_forKey_),
|
|
)
|
|
|
|
|
|
CONVENIENCE_METHODS['count'] = (
|
|
('__len__', lambda self: self.count()),
|
|
)
|
|
|
|
CONVENIENCE_METHODS['containsObject:'] = (
|
|
('__contains__', lambda self, elem: bool(self.containsObject_(container_wrap(elem)))),
|
|
)
|
|
|
|
CONVENIENCE_METHODS['hash'] = (
|
|
('__hash__', lambda self: self.hash()),
|
|
)
|
|
|
|
CONVENIENCE_METHODS['isEqualTo:'] = (
|
|
('__eq__', lambda self, other: bool(self.isEqualTo_(other))),
|
|
)
|
|
|
|
CONVENIENCE_METHODS['isEqual:'] = (
|
|
('__eq__', lambda self, other: bool(self.isEqual_(other))),
|
|
)
|
|
|
|
CONVENIENCE_METHODS['isGreaterThan:'] = (
|
|
('__gt__', lambda self, other: bool(self.isGreaterThan_(other))),
|
|
)
|
|
|
|
CONVENIENCE_METHODS['isGreaterThanOrEqualTo:'] = (
|
|
('__ge__', lambda self, other: bool(self.isGreaterThanOrEqualTo_(other))),
|
|
)
|
|
|
|
CONVENIENCE_METHODS['isLessThan:'] = (
|
|
('__lt__', lambda self, other: bool(self.isLessThan_(other))),
|
|
)
|
|
|
|
CONVENIENCE_METHODS['isLessThanOrEqualTo:'] = (
|
|
('__le__', lambda self, other: bool(self.isLessThanOrEqualTo_(other))),
|
|
)
|
|
|
|
CONVENIENCE_METHODS['isNotEqualTo:'] = (
|
|
('__ne__', lambda self, other: bool(self.isNotEqualTo_(other))),
|
|
)
|
|
|
|
CONVENIENCE_METHODS['length'] = (
|
|
('__len__', lambda self: self.length()),
|
|
)
|
|
|
|
CONVENIENCE_METHODS['addObject:'] = (
|
|
('append', lambda self, item: self.addObject_(container_wrap(item))),
|
|
)
|
|
|
|
def reverse_exchangeObjectAtIndex_withObjectAtIndex_(self):
|
|
begin = 0
|
|
end = len(self) - 1
|
|
while begin < end:
|
|
self.exchangeObjectAtIndex_withObjectAtIndex_(begin, end)
|
|
begin += 1
|
|
end -= 1
|
|
|
|
CONVENIENCE_METHODS['exchangeObjectAtIndex:withObjectAtIndex:'] = (
|
|
('reverse', reverse_exchangeObjectAtIndex_withObjectAtIndex_),
|
|
)
|
|
|
|
def ensureArray(anArray):
|
|
if not isinstance(anArray, (NSArray, list, tuple)):
|
|
anArray = list(anArray)
|
|
return anArray
|
|
|
|
|
|
def extend_addObjectsFromArray_(self, anArray):
|
|
self.addObjectsFromArray_(ensureArray(anArray))
|
|
|
|
CONVENIENCE_METHODS['addObjectsFromArray:'] = (
|
|
('extend', extend_addObjectsFromArray_),
|
|
)
|
|
|
|
def index_indexOfObject_(self, item):
|
|
from Foundation import NSNotFound
|
|
res = self.indexOfObject_(container_wrap(item))
|
|
if res == NSNotFound:
|
|
raise ValueError, "%s.index(x): x not in list" % (type(self).__name__,)
|
|
return res
|
|
|
|
CONVENIENCE_METHODS['indexOfObject:'] = (
|
|
('index', index_indexOfObject_),
|
|
)
|
|
|
|
def insert_insertObject_atIndex_(self, idx, item):
|
|
idx = slice(idx, None, None).indices(len(self)).start
|
|
self.insertObject_atIndex_(container_wrap(item), idx)
|
|
|
|
CONVENIENCE_METHODS['insertObject:atIndex:'] = (
|
|
( 'insert', insert_insertObject_atIndex_),
|
|
)
|
|
|
|
def __getitem__objectAtIndex_(self, idx):
|
|
length = len(self)
|
|
if isinstance(idx, slice):
|
|
start, stop, step = idx.indices(length)
|
|
#if step == 1:
|
|
# m = getattr(self, 'subarrayWithRange_', None)
|
|
# if m is not None:
|
|
# return m((start, stop - start))
|
|
return [self[i] for i in xrange(start, stop, step)]
|
|
if idx < 0:
|
|
idx += length
|
|
if idx < 0:
|
|
raise IndexError, "index out of range"
|
|
elif idx >= length:
|
|
raise IndexError, "index out of range"
|
|
return container_unwrap(self.objectAtIndex_(idx), RuntimeError)
|
|
|
|
CONVENIENCE_METHODS['objectAtIndex:'] = (
|
|
('__getitem__', __getitem__objectAtIndex_),
|
|
)
|
|
|
|
def __delitem__removeObjectAtIndex_(self, idx):
|
|
length = len(self)
|
|
if isinstance(idx, slice):
|
|
start, stop, step = idx.indices(length)
|
|
if step == 1:
|
|
if start > stop:
|
|
start, stop = stop, start
|
|
m = getattr(self, 'removeObjectsInRange_', None)
|
|
if m is not None:
|
|
m((start, stop - start))
|
|
return
|
|
r = range(start, stop, step)
|
|
r.sort()
|
|
r.reverse()
|
|
for i in r:
|
|
self.removeObjectAtIndex_(i)
|
|
return
|
|
if idx < 0:
|
|
idx += length
|
|
if idx < 0:
|
|
raise IndexError, "index out of range"
|
|
elif idx >= length:
|
|
raise IndexError, "index out of range"
|
|
self.removeObjectAtIndex_(idx)
|
|
|
|
def pop_removeObjectAtIndex_(self, idx=-1):
|
|
length = len(self)
|
|
if length <= 0:
|
|
raise IndexError("pop from empty list")
|
|
elif idx >= length or (idx + length) < 0:
|
|
raise IndexError("pop index out of range")
|
|
elif idx < 0:
|
|
idx += length
|
|
rval = self[idx]
|
|
self.removeObjectAtIndex_(idx)
|
|
return rval
|
|
|
|
def remove_removeObjectAtIndex_(self, obj):
|
|
idx = self.index(obj)
|
|
self.removeObjectAtIndex_(idx)
|
|
|
|
CONVENIENCE_METHODS['removeObjectAtIndex:'] = (
|
|
('remove', remove_removeObjectAtIndex_),
|
|
('pop', pop_removeObjectAtIndex_),
|
|
('__delitem__', __delitem__removeObjectAtIndex_),
|
|
)
|
|
|
|
def __setitem__replaceObjectAtIndex_withObject_(self, idx, anObject):
|
|
length = len(self)
|
|
if isinstance(idx, slice):
|
|
start, stop, step = idx.indices(length)
|
|
if step == 1:
|
|
m = getattr(self, 'replaceObjectsInRange_withObjectsFromArray_', None)
|
|
if m is not None:
|
|
m((start, stop - start), ensureArray(anObject))
|
|
return
|
|
# XXX - implement this..
|
|
raise NotImplementedError
|
|
if idx < 0:
|
|
idx += length
|
|
if idx < 0:
|
|
raise IndexError, "index out of range"
|
|
elif idx >= length:
|
|
raise IndexError, "index out of range"
|
|
self.replaceObjectAtIndex_withObject_(idx, anObject)
|
|
|
|
CONVENIENCE_METHODS['replaceObjectAtIndex:withObject:'] = (
|
|
('__setitem__', __setitem__replaceObjectAtIndex_withObject_),
|
|
)
|
|
|
|
def enumeratorGenerator(anEnumerator):
|
|
while True:
|
|
yield container_unwrap(anEnumerator.nextObject(), StopIteration)
|
|
|
|
def dictItems(aDict):
|
|
"""
|
|
NSDictionary.items()
|
|
"""
|
|
keys = aDict.allKeys()
|
|
return zip(keys, imap(aDict.__getitem__, keys))
|
|
|
|
CONVENIENCE_METHODS['allKeys'] = (
|
|
('keys', lambda self: self.allKeys()),
|
|
('items', lambda self: dictItems(self)),
|
|
)
|
|
|
|
CONVENIENCE_METHODS['allValues'] = (
|
|
('values', lambda self: self.allValues()),
|
|
)
|
|
|
|
def itemsGenerator(aDict):
|
|
for key in aDict:
|
|
yield (key, aDict[key])
|
|
|
|
def __iter__objectEnumerator_keyEnumerator(self):
|
|
meth = getattr(self, 'keyEnumerator', None)
|
|
if meth is None:
|
|
meth = self.objectEnumerator
|
|
return iter(meth())
|
|
|
|
CONVENIENCE_METHODS['keyEnumerator'] = (
|
|
('__iter__', __iter__objectEnumerator_keyEnumerator),
|
|
('iterkeys', lambda self: iter(self.keyEnumerator())),
|
|
('iteritems', lambda self: itemsGenerator(self)),
|
|
)
|
|
|
|
CONVENIENCE_METHODS['objectEnumerator'] = (
|
|
('__iter__', __iter__objectEnumerator_keyEnumerator),
|
|
('itervalues', lambda self: iter(self.objectEnumerator())),
|
|
)
|
|
|
|
CONVENIENCE_METHODS['reverseObjectEnumerator'] = (
|
|
('__reversed__', lambda self: iter(self.reverseObjectEnumerator())),
|
|
)
|
|
|
|
CONVENIENCE_METHODS['removeAllObjects'] = (
|
|
('clear', lambda self: self.removeAllObjects()),
|
|
)
|
|
|
|
CONVENIENCE_METHODS['dictionaryWithDictionary:'] = (
|
|
('copy', lambda self: type(self).dictionaryWithDictionary_(self)),
|
|
)
|
|
|
|
CONVENIENCE_METHODS['nextObject'] = (
|
|
('__iter__', enumeratorGenerator),
|
|
)
|
|
|
|
#
|
|
# NSNumber seems to be and abstract base-class that is implemented using
|
|
# NSCFNumber, a CoreFoundation 'proxy'.
|
|
#
|
|
NSNull = lookUpClass('NSNull')
|
|
NSArray = lookUpClass('NSArray')
|
|
null = NSNull.null()
|
|
|
|
number_wrap = repythonify
|
|
|
|
def container_wrap(v):
|
|
if v is None:
|
|
return null
|
|
return v
|
|
|
|
def container_unwrap(v, exc_type, *exc_args):
|
|
if v is None:
|
|
raise exc_type(*exc_args)
|
|
elif v is null:
|
|
return None
|
|
return v
|
|
|
|
#
|
|
# Special wrappers for a number of varargs functions (constructors)
|
|
#
|
|
|
|
def initWithObjects_(self, *args):
|
|
if args[-1] is not None:
|
|
raise ValueError, "Need None as the last argument"
|
|
return self.initWithArray_(args[:-1])
|
|
|
|
CONVENIENCE_METHODS['initWithObjects:'] = (
|
|
('initWithObjects_', initWithObjects_),
|
|
)
|
|
|
|
def arrayWithObjects_(self, *args):
|
|
if args[-1] is not None:
|
|
raise ValueError, "Need None as the last argument"
|
|
return self.arrayWithArray_(args[:-1])
|
|
|
|
CONVENIENCE_METHODS['arrayWithObjects:'] = (
|
|
('arrayWithObjects_', selector(arrayWithObjects_, signature='@@:@', isClassMethod=1)),
|
|
)
|
|
|
|
|
|
def setWithObjects_(self, *args):
|
|
if args[-1] is not None:
|
|
raise ValueError, "Need None as the last argument"
|
|
return self.setWithArray_(args[:-1])
|
|
|
|
CONVENIENCE_METHODS['setWithObjects:'] = (
|
|
('setWithObjects_', selector(setWithObjects_, signature='@@:@', isClassMethod=1)),
|
|
)
|
|
|
|
def setWithObjects_count_(self, args, count):
|
|
return self.setWithArray_(args[:count])
|
|
|
|
CONVENIENCE_METHODS['setWithObjects:count:'] = (
|
|
('setWithObjects_count_', selector(setWithObjects_count_, signature='@@:^@i', isClassMethod=1)),
|
|
)
|
|
|
|
def splitObjectsAndKeys(values):
|
|
if values[-1] is not None:
|
|
raise ValueError, "Need None as the last argument"
|
|
if len(values) % 2 != 1:
|
|
raise ValueError, "Odd number of arguments"
|
|
|
|
objects = []
|
|
keys = []
|
|
for i in range(0, len(values)-1, 2):
|
|
objects.append(container_wrap(values[i]))
|
|
keys.append(container_wrap(values[i+1]))
|
|
return objects, keys
|
|
|
|
def dictionaryWithObjectsAndKeys_(self, *values):
|
|
objects, keys = splitObjectsAndKeys(values)
|
|
return self.dictionaryWithObjects_forKeys_(objects, keys)
|
|
|
|
CONVENIENCE_METHODS['dictionaryWithObjectsAndKeys:'] = (
|
|
('dictionaryWithObjectsAndKeys_',
|
|
selector(dictionaryWithObjectsAndKeys_, signature='@@:@',isClassMethod=1)),
|
|
)
|
|
|
|
def fromkeys_dictionaryWithObjects_forKeys_(cls, keys, values=None):
|
|
if not isinstance(keys, (list, tuple)):
|
|
keys = list(keys)
|
|
if values is None:
|
|
values = (None,) * len(keys)
|
|
elif not isinstance(values, (list, tuple)):
|
|
values = list(values)
|
|
return cls.dictionaryWithObjects_forKeys_(values, keys)
|
|
|
|
CONVENIENCE_METHODS['dictionaryWithObjects:forKeys:'] = (
|
|
('fromkeys',
|
|
classmethod(fromkeys_dictionaryWithObjects_forKeys_)),
|
|
)
|
|
|
|
def initWithObjectsAndKeys_(self, *values):
|
|
objects, keys = splitObjectsAndKeys(values)
|
|
return self.initWithObjects_forKeys_(objects, keys)
|
|
|
|
CONVENIENCE_METHODS['initWithObjectsAndKeys:'] = (
|
|
( 'initWithObjectsAndKeys_', initWithObjectsAndKeys_ ),
|
|
)
|
|
|
|
def sort(self, cmpfunc=cmp):
|
|
def doCmp(a, b, cmpfunc):
|
|
return cmpfunc(a, b)
|
|
|
|
self.sortUsingFunction_context_(doCmp, cmpfunc)
|
|
|
|
CONVENIENCE_METHODS['sortUsingFunction:context:'] = (
|
|
('sort', sort),
|
|
)
|
|
|
|
CONVENIENCE_METHODS['hasPrefix:'] = (
|
|
('startswith', lambda self, pfx: self.hasPrefix_(pfx)),
|
|
)
|
|
|
|
CONVENIENCE_METHODS['hasSuffix:'] = (
|
|
('endswith', lambda self, pfx: self.hasSuffix_(pfx)),
|
|
)
|
|
|
|
CLASS_METHODS['NSNull'] = (
|
|
('__nonzero__', lambda self: False ),
|
|
)
|
|
|
|
NSDecimalNumber = lookUpClass('NSDecimalNumber')
|
|
def _makeD(v):
|
|
if isinstance(v, NSDecimalNumber):
|
|
return v
|
|
return NSDecimalNumber.decimalNumberWithDecimal_(v)
|
|
|
|
CLASS_METHODS['NSDecimalNumber'] = (
|
|
('__add__', lambda self, other: _makeD(self.decimalValue() + other)),
|
|
('__radd__', lambda self, other: _makeD(other + self.decimalValue())),
|
|
('__sub__', lambda self, other: _makeD(self.decimalValue() - other)),
|
|
('__rsub__', lambda self, other: _makeD(other - self.decimalValue())),
|
|
('__mul__', lambda self, other: _makeD(self.decimalValue() * other)),
|
|
('__rmul__', lambda self, other: _makeD(other * self.decimalValue())),
|
|
('__div__', lambda self, other: _makeD(self.decimalValue() / other)),
|
|
('__rdiv__', lambda self, other: _makeD(other / self.decimalValue())),
|
|
('__mod__', lambda self, other: _makeD(self.decimalValue() % other)),
|
|
('__rmod__', lambda self, other: _makeD(other % self.decimalValue())),
|
|
('__neg__', lambda self: _makeD(-(self.decimalValue()))),
|
|
('__pos__', lambda self: _makeD(+(self.decimalValue()))),
|
|
('__abs__', lambda self: _makeD(abs(self.decimalValue()))),
|
|
)
|
|
|
|
def NSData__getslice__(self, i, j):
|
|
return self.bytes()[i:j]
|
|
|
|
def NSData__getitem__(self, item):
|
|
buff = self.bytes()
|
|
try:
|
|
return buff[item]
|
|
except TypeError:
|
|
return buff[:][item]
|
|
|
|
CLASS_METHODS['NSData'] = (
|
|
('__str__', lambda self: self.bytes()[:]),
|
|
('__getitem__', NSData__getitem__),
|
|
('__getslice__', NSData__getslice__),
|
|
)
|
|
|
|
def NSMutableData__setslice__(self, i, j, sequence):
|
|
# XXX - could use replaceBytes:inRange:, etc.
|
|
self.mutableBytes()[i:j] = sequence
|
|
|
|
def NSMutableData__setitem__(self, item, value):
|
|
self.mutableBytes()[item] = value
|
|
|
|
CLASS_METHODS['NSMutableData'] = (
|
|
('__setslice__', NSMutableData__setslice__),
|
|
('__setitem__', NSMutableData__setitem__),
|
|
)
|