This repository has been archived on 2024-05-09. You can view files and clone it, but cannot push or open issues/pull-requests.
ipodderx-core/objc/_convenience.py

603 lines
18 KiB
Python

"""
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__),
)