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' ]
8
9from _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="v@:@")
31
32def instancemethod(func):
33    return selector(func, isClassMethod=False)
34
35def accessor(func, typeSignature='@'):
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='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='Z@:N^@o^@')
62
63        if funcName.startswith('insertObject_in') and funcName.endswith('AtIndex_'):
64            return selector(func, signature='v@:@i')
65        elif funcName.startswith('replaceObjectIn') and funcName.endswith('AtIndex_withObject_'):
66            return selector(func, signature='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='@@:i')
73        elif funcName.startswith('removeObjectFrom') and funcName.endswith('AtIndex_'):
74            return selector(func, signature='v@:i')
75        elif funcName.startswith('get') and funcName.endswith('_range_'):
76            return selector(func, signature='@@:{_NSRange=ii}')
77
78        return selector(func, signature="v@:" + typeSignature)
79
80    elif selArgs == 1:
81        if typeSignature == '@' and func.func_name.startswith('countOf'):
82            typeSignature = 'i'
83
84        return selector(func, signature=typeSignature + "@:")
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 typedAccessor(typeSignature):
112    """
113    Python 2.4 decorator for creating a typed accessor, usage:
114
115        @typedAccessor('i')
116        def someIntegerAccessor(self):
117            return self.someInteger
118
119        @typedAccessor('i')
120        def setSomeIntegerAccessor_(self, anInteger):
121            self.someInteger = anInteger
122    """
123    def _typedAccessor(func):
124        return accessor(func, typeSignature)
125    return _typedAccessor
126
127def Accessor(func):
128    import warnings
129    warnings.warn(
130        "Use objc.accessor instead of objc.Accessor", DeprecationWarning)
131    return accessor(func)
132
133#
134# Callback support
135#
136def callbackFor(callable, argIndex=-1):
137    """
138    Decorator for converting a function into an object that can be used
139    as a callback function for (Objective-)C API's that take such a beast
140    as one of their arguments.
141
142    Note that using this decorator for methods is unsupported and that this
143    decorator is optional when the callback isn't stored by the called function
144
145    Usage::
146
147        @objc.callbackFor(NSArray.sortedArrayUsingFunction_context_)
148        def compare(left, right, context):
149            return 1
150    """
151    def addClosure(function):
152        closure = _makeClosure(function, callable, argIndex)
153        function.pyobjc_closure = closure
154        return function
155
156    return addClosure
157
158def selectorFor(callable, argIndex=-1):
159    """
160    Decorator that makes sure that the method has the right signature to be
161    used as the selector argument to the specified method.
162
163    Usage::
164
165        @objc.selectorFor(NSApplication.beginSheet_modalForWindow_modalDelegate_didEndSelector_contextInfo_)
166        def sheetDidEnd_returnCode_contextInfo_(self, sheet, returnCode, info):
167            pass
168    """
169    if argIndex == -1:
170        for arg in callable.__metadata__()['arguments']:
171            if arg['type'] == _C_SEL and 'sel_of_type' in arg:
172                signature = arg['sel_of_type']
173                break
174        else:
175            raise ValueError("No selector argument with type information")
176
177    else:
178        signature = callable.__metadata__().arguments[idx]['type_of_sel']
179
180    def addSignature(function):
181        return selector(function, signature=signature)
182
183    return addSignature
184
185
186def synthesize(name, copy=False, readwrite=True, type=_C_ID, ivarName=None):
187    """
188    Use this in a class dictionary to syntheze simple setting/setter methods.
189
190    Note: this is only necessary to get propper behaviour when Key-Value coding
191    is used and special features (like copying) are needed
192
193    usage::
194
195        class MyClass (NSObject):
196            objc.synthesize('someTitle', copy=True)
197
198    """
199    if ivarName is None:
200        ivarName = '_' + name
201
202    classDict = sys._getframe(1).f_locals
203
204    if copy:
205        setter = textwrap.dedent('''
206            def set%(name)s_(self, value):
207                self.%(ivar)s = value.copy()
208            ''' % dict(name=name.capitalize(), ivar=ivarName))
209
210    else:
211        setter = textwrap.dedent('''
212            def set%(name)s_(self, value):
213                self.%(ivar)s = value
214            ''' % dict(name=name.capitalize(), ivar=ivarName))
215
216    getter = textwrap.dedent('''
217            def %(name)s(self):
218                return self.%(ivar)s
219            ''' % dict(name=name, ivar=ivarName))
220
221    if readwrite:
222        exec setter in classDict
223
224    exec getter in classDict
225
226    classDict[ivarName] = ivar(type=type)
227
228
229