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