1"""
2Python <-> Objective-C bridge (PyObjC)
3
4This module defines the core interfaces of the Python<->Objective-C bridge.
5"""
6
7__all__ = ['IBOutlet', 'IBAction', 'accessor', 'Accessor', 'typedAccessor', 'callbackFor', 'selectorFor', 'synthesize', 'namedselector', 'typedSelector', 'namedSelector', 'instancemethod', 'signature' ]
8
9from objc._objc import ivar, selector, _makeClosure, selector, _C_SEL, _C_ID, _C_NSUInteger, _C_NSBOOL
10import sys, textwrap
11import warnings
12from inspect import getargspec
13
14_C_NSRange = [b"{_NSRange=II}", b"{_NSRange=QQ}"][sys.maxsize > 2**32]
15
16#
17# Interface builder support.
18#
19def IBOutlet(name=None):
20    """
21    Create an instance variable that can be used as an outlet in
22    Interface Builder.
23    """
24    if name is None:
25        return ivar(isOutlet=1)
26    else:
27        return ivar(name, isOutlet=1)
28
29def IBAction(func):
30    """
31    Return an Objective-C method object that can be used as an action
32    in Interface Builder.
33    """
34    if func is None:
35        raise TypeError("IBAction argument must be a callable")
36    return selector(func, signature=b"v@:@")
37
38def instancemethod(func):
39    if func is None:
40        raise TypeError("instancemethod argument must be a callable")
41    return selector(func, isClassMethod=False)
42
43def accessor(func, typeSignature=b'@'):
44    """
45    Return an Objective-C method object that is conformant with key-value coding
46    and key-value observing.
47    """
48    args, varargs, varkw, defaults = getargspec(func)
49    funcName = func.__name__
50    maxArgs = len(args)
51    minArgs = maxArgs - len(defaults or ())
52    # implicit self
53    selArgs = 1 + funcName.count('_')
54    if varargs is not None or varkw is not None:
55        raise TypeError('%s can not be an accessor because it accepts varargs or varkw' % (funcName,))
56
57    if not (minArgs <= selArgs <= maxArgs):
58        if minArgs == maxArgs:
59            raise TypeError('%s expected to take %d args, but must accept %d from Objective-C (implicit self plus count of underscores)' % (funcName, maxArgs, selArgs))
60        else:
61            raise TypeError('%s expected to take between %d and %d args, but must accept %d from Objective-C (implicit self plus count of underscores)' % (funcName, minArgs, maxArgs, selArgs))
62
63    if selArgs == 3:
64        if funcName.startswith('validate') and funcName.endswith('_error_'):
65            return selector(func, signature=_C_NSBOOL + b'@:N^@o^@')
66
67        if funcName.startswith('insertObject_in') and funcName.endswith('AtIndex_'):
68            return selector(func, signature=b'v@:' + typeSignature + _C_NSUInteger)
69        elif funcName.startswith('replaceObjectIn') and funcName.endswith('AtIndex_withObject_'):
70            return selector(func, signature=b'v@:' + _C_NSUInteger + typeSignature)
71
72        elif funcName.startswith('get') and funcName.endswith('_range_'):
73            return selector(func, signature=b'v@:o^@' + _C_NSRange)
74
75        elif funcName.startswith('insert') and funcName.endswith('_atIndexes_'):
76            return selector(func, signature=b'v@:@@')
77
78        elif funcName.startswith('replace') and 'AtIndexes_with' in funcName:
79            return selector(func, signature=b'v@:@@')
80
81        # pass through to "too many arguments"
82
83    elif selArgs == 2:
84        if funcName.startswith('objectIn') and funcName.endswith('AtIndex_'):
85            return selector(func, signature=typeSignature + b'@:' + _C_NSUInteger)
86        elif funcName.startswith('removeObjectFrom') and funcName.endswith('AtIndex_'):
87            return selector(func, signature=b'v@:' + _C_NSUInteger)
88        elif funcName.startswith('remove') and funcName.endswith('AtIndexes_'):
89            return selector(func, signature=b"v@:@")
90        elif funcName.endswith('AtIndexes_'):
91            return selector(func, signature=b"@@:@")
92        elif funcName.startswith('memberOf'):
93            return selector(func, signature=_C_NSBOOL + b"@:" + typeSignature)
94        elif funcName.startswith('add') and funcName.endswith('Object_'):
95            return selector(func, signature=b"v@:" + typeSignature)
96        elif funcName.startswith('add'):
97            return selector(func, signature=b"v@:@")
98        elif funcName.startswith('intersect'):
99            return selector(func, signature=b"v@:@")
100
101        return selector(func, signature=b"v@:" + typeSignature)
102
103    elif selArgs == 1:
104        if funcName.startswith('countOf'):
105            typeSignature = _C_NSUInteger
106        elif funcName.startswith('enumerator'):
107            typeSignature = b"@"
108
109
110        return selector(func, signature=typeSignature + b"@:")
111
112    raise TypeError("%s not recognized as an accessor" % (funcName,))
113
114
115def typedSelector(signature):
116    def _typedSelector(func):
117        if func is None:
118            raise TypeError("typedSelector() function argument must be a callable")
119        return selector(func, signature=signature)
120    return _typedSelector
121
122def namedSelector(name, signature=None):
123    """
124    Decorator for overriding the Objective-C SEL for a method, usage:
125
126        @namedSelector("foo:bar:")
127        def foobar(self, foo, bar):
128            return foo + bar
129    """
130    if signature is not None:
131        def _namedselector(func):
132            if func is None:
133                raise TypeError("IBAction argument must be a callable")
134            return selector(func, selector=name, signature=signature)
135    else:
136        def _namedselector(func):
137            if func is None:
138                raise TypeError("IBAction argument must be a callable")
139            return selector(func, selector=name)
140
141    return _namedselector
142
143def namedselector(name, signature=None):
144    warnings.warn("use objc.namedSelector instead of objc.namedselector", DeprecationWarning, stacklevel=2)
145    return namedSelector(name, signature)
146
147def typedAccessor(typeSignature):
148    """
149    Decorator for creating a typed accessor, usage:
150
151        @typedAccessor('i')
152        def someIntegerAccessor(self):
153            return self.someInteger
154
155        @typedAccessor('i')
156        def setSomeIntegerAccessor_(self, anInteger):
157            self.someInteger = anInteger
158    """
159    def _typedAccessor(func):
160        return accessor(func, typeSignature)
161    return _typedAccessor
162
163def Accessor(func):
164    warnings.warn(
165        "Use objc.accessor instead of objc.Accessor", DeprecationWarning)
166    return accessor(func)
167
168#
169# Callback support
170#
171def callbackFor(callable, argIndex=-1):
172    """
173    Decorator for converting a function into an object that can be used
174    as a callback function for (Objective-)C API's that take such a beast
175    as one of their arguments.
176
177    Note that using this decorator for methods is unsupported and that this
178    decorator is optional when the callback isn't stored by the called function
179
180    Usage::
181
182        @objc.callbackFor(NSArray.sortedArrayUsingFunction_context_)
183        def compare(left, right, context):
184            return 1
185    """
186    def addClosure(function):
187        closure = _makeClosure(function, callable, argIndex)
188        function.pyobjc_closure = closure
189        return function
190
191    return addClosure
192
193def selectorFor(callable, argIndex=-1):
194    """
195    Decorator that makes sure that the method has the right signature to be
196    used as the selector argument to the specified method.
197
198    Usage::
199
200        @objc.selectorFor(NSApplication.beginSheet_modalForWindow_modalDelegate_didEndSelector_contextInfo_)
201        def sheetDidEnd_returnCode_contextInfo_(self, sheet, returnCode, info):
202            pass
203    """
204    if argIndex == -1:
205        for arg in callable.__metadata__()['arguments']:
206            if arg['type'] == _C_SEL and 'sel_of_type' in arg:
207                signature = arg['sel_of_type']
208                break
209        else:
210            raise ValueError("No selector argument with type information")
211
212    else:
213        try:
214            signature = callable.__metadata__()['arguments'][argIndex]['sel_of_type']
215        except (IndexError, KeyError):
216            raise ValueError("Not a selector argument with type information")
217
218    def addSignature(function):
219        return selector(function, signature=signature)
220
221    return addSignature
222
223
224def synthesize(name, copy=False, readwrite=True, type=_C_ID, ivarName=None):
225    """
226    Use this in a class dictionary to syntheze simple setting/setter methods.
227
228    Note: this is only necessary to get propper behaviour when Key-Value coding
229    is used and special features (like copying) are needed
230
231    usage::
232
233        class MyClass (NSObject):
234            objc.synthesize('someTitle', copy=True)
235
236    """
237    if not name:
238        raise ValueError("Empty property name")
239
240    if ivarName is None:
241        ivarName = '_' + name
242
243    classDict = sys._getframe(1).f_locals
244
245    setterName = 'set%s%s_'%(name[0].upper(), name[1:])
246
247    if copy:
248        setter = textwrap.dedent('''
249            def %(name)s(self, value):
250                self.%(ivar)s = value.copy()
251            ''' % dict(name=setterName, ivar=ivarName))
252
253    else:
254        setter = textwrap.dedent('''
255            def %(name)s(self, value):
256                self.%(ivar)s = value
257            ''' % dict(name=setterName, ivar=ivarName))
258
259    getter = textwrap.dedent('''
260            def %(name)s(self):
261                return self.%(ivar)s
262            ''' % dict(name=name, ivar=ivarName))
263
264    if readwrite:
265        exec(setter, globals(), classDict)
266
267    exec(getter, globals(), classDict)
268
269    classDict[ivarName] = ivar(type=type)
270
271
272def signature(signature, **kw):
273    """
274    A Python method decorator that allows easy specification
275    of Objective-C selectors.
276
277    Usage::
278
279        @objc.signature('i@:if')
280        def methodWithX_andY_(self, x, y):
281            return 0
282    """
283    warnings.warn("Usage objc.typedSelector instead of objc.signature", DeprecationWarning)
284    kw['signature'] = signature
285    def makeSignature(func):
286        return selector(func, **kw)
287    return makeSignature
288