Show More
@@ -18,14 +18,79 b' import types' | |||||
18 |
|
18 | |||
19 | from IPython.kernel.comm import Comm |
|
19 | from IPython.kernel.comm import Comm | |
20 | from IPython.config import LoggingConfigurable |
|
20 | from IPython.config import LoggingConfigurable | |
21 | from IPython.utils.traitlets import Unicode, Dict, Instance, Bool |
|
21 | from IPython.utils.traitlets import Unicode, Dict, Instance, Bool, List | |
22 | from IPython.utils.py3compat import string_types |
|
22 | from IPython.utils.py3compat import string_types | |
23 |
|
23 | |||
24 | #----------------------------------------------------------------------------- |
|
24 | #----------------------------------------------------------------------------- | |
25 | # Classes |
|
25 | # Classes | |
26 | #----------------------------------------------------------------------------- |
|
26 | #----------------------------------------------------------------------------- | |
27 |
class |
|
27 | class CallbackDispatcher(LoggingConfigurable): | |
|
28 | acceptable_nargs = List([], help="""List of integers. | |||
|
29 | The number of arguments in the callbacks registered must match one of | |||
|
30 | the integers in this list. If this list is empty or None, it will be | |||
|
31 | ignored.""") | |||
|
32 | ||||
|
33 | def __init__(self, *pargs, **kwargs): | |||
|
34 | """Constructor""" | |||
|
35 | LoggingConfigurable.__init__(self, *pargs, **kwargs) | |||
|
36 | self.callbacks = {} | |||
|
37 | ||||
|
38 | def __call__(self, *pargs, **kwargs): | |||
|
39 | """Call all of the registered callbacks that have the same number of | |||
|
40 | positional arguments.""" | |||
|
41 | nargs = len(pargs) | |||
|
42 | self._validate_nargs(nargs) | |||
|
43 | if nargs in self.callbacks: | |||
|
44 | for callback in self.callbacks[nargs]: | |||
|
45 | callback(*pargs, **kwargs) | |||
|
46 | ||||
|
47 | def register_callback(self, callback, remove=False): | |||
|
48 | """(Un)Register a callback | |||
|
49 | ||||
|
50 | Parameters | |||
|
51 | ---------- | |||
|
52 | callback: method handle | |||
|
53 | Method to be registered or unregisted. | |||
|
54 | remove=False: bool | |||
|
55 | Whether or not to unregister the callback.""" | |||
|
56 | ||||
|
57 | # Validate the number of arguments that the callback accepts. | |||
|
58 | nargs = self._get_nargs(callback) | |||
|
59 | self._validate_nargs(nargs) | |||
|
60 | ||||
|
61 | # Get/create the appropriate list of callbacks. | |||
|
62 | if nargs not in self.callbacks: | |||
|
63 | self.callbacks[nargs] = [] | |||
|
64 | callback_list = self.callbacks[nargs] | |||
|
65 | ||||
|
66 | # (Un)Register the callback. | |||
|
67 | if remove and callback in callback_list: | |||
|
68 | callback_list.remove(callback) | |||
|
69 | else not remove and callback not in callback_list: | |||
|
70 | callback_list.append(callback) | |||
|
71 | ||||
|
72 | def _validate_nargs(self, nargs): | |||
|
73 | if self.acceptable_nargs is not None and \ | |||
|
74 | len(self.acceptable_nargs) > 0 and \ | |||
|
75 | nargs not in self.acceptable_nargs: | |||
|
76 | ||||
|
77 | raise TypeError('Invalid number of positional arguments. See acceptable_nargs list.') | |||
|
78 | ||||
|
79 | def _get_nargs(self, callback): | |||
|
80 | """Gets the number of arguments in a callback""" | |||
|
81 | if callable(callback): | |||
|
82 | argspec = inspect.getargspec(callback) | |||
|
83 | nargs = len(argspec[1]) # Only count vargs! | |||
|
84 | ||||
|
85 | # Bound methods have an additional 'self' argument | |||
|
86 | if isinstance(callback, types.MethodType): | |||
|
87 | nargs -= 1 | |||
|
88 | return nargs | |||
|
89 | else: | |||
|
90 | raise TypeError('Callback must be callable.') | |||
|
91 | ||||
28 |
|
92 | |||
|
93 | class Widget(LoggingConfigurable): | |||
29 | #------------------------------------------------------------------------- |
|
94 | #------------------------------------------------------------------------- | |
30 | # Class attributes |
|
95 | # Class attributes | |
31 | #------------------------------------------------------------------------- |
|
96 | #------------------------------------------------------------------------- | |
@@ -59,10 +124,13 b' class Widget(LoggingConfigurable):' | |||||
59 | def __init__(self, **kwargs): |
|
124 | def __init__(self, **kwargs): | |
60 | """Public constructor""" |
|
125 | """Public constructor""" | |
61 | self.closed = False |
|
126 | self.closed = False | |
|
127 | ||||
62 | self._property_lock = (None, None) |
|
128 | self._property_lock = (None, None) | |
63 | self._display_callbacks = [] |
|
|||
64 | self._msg_callbacks = [] |
|
|||
65 | self._keys = None |
|
129 | self._keys = None | |
|
130 | ||||
|
131 | self._display_callbacks = CallbackDispatcher(acceptable_nargs=[0]) | |||
|
132 | self._msg_callbacks = CallbackDispatcher(acceptable_nargs=[1, 2]) | |||
|
133 | ||||
66 | super(Widget, self).__init__(**kwargs) |
|
134 | super(Widget, self).__init__(**kwargs) | |
67 |
|
135 | |||
68 | self.on_trait_change(self._handle_property_changed, self.keys) |
|
136 | self.on_trait_change(self._handle_property_changed, self.keys) | |
@@ -162,31 +230,11 b' class Widget(LoggingConfigurable):' | |||||
162 | ---------- |
|
230 | ---------- | |
163 | callback: method handler |
|
231 | callback: method handler | |
164 | Can have a signature of: |
|
232 | Can have a signature of: | |
165 | - callback(content) |
|
233 | - callback(content) Signature 1 | |
166 | - callback(sender, content) |
|
234 | - callback(sender, content) Signature 2 | |
167 | remove: bool |
|
235 | remove: bool | |
168 | True if the callback should be unregistered.""" |
|
236 | True if the callback should be unregistered.""" | |
169 | if remove and callback in self._msg_callbacks: |
|
237 | self._msg_callbacks.register_callback(callback, remove=remove) | |
170 | self._msg_callbacks.remove(callback) |
|
|||
171 | elif not remove and not callback in self._msg_callbacks: |
|
|||
172 | if callable(callback): |
|
|||
173 | argspec = inspect.getargspec(callback) |
|
|||
174 | nargs = len(argspec[0]) |
|
|||
175 |
|
||||
176 | # Bound methods have an additional 'self' argument |
|
|||
177 | if isinstance(callback, types.MethodType): |
|
|||
178 | nargs -= 1 |
|
|||
179 |
|
||||
180 | # Call the callback |
|
|||
181 | if nargs == 1: |
|
|||
182 | self._msg_callbacks.append(lambda sender, content: callback(content)) |
|
|||
183 | elif nargs == 2: |
|
|||
184 | self._msg_callbacks.append(callback) |
|
|||
185 | else: |
|
|||
186 | raise TypeError('Widget msg callback must ' \ |
|
|||
187 | 'accept 1 or 2 arguments, not %d.' % nargs) |
|
|||
188 | else: |
|
|||
189 | raise Exception('Callback must be callable.') |
|
|||
190 |
|
238 | |||
191 | def on_displayed(self, callback, remove=False): |
|
239 | def on_displayed(self, callback, remove=False): | |
192 | """(Un)Register a widget displayed callback. |
|
240 | """(Un)Register a widget displayed callback. | |
@@ -199,13 +247,7 b' class Widget(LoggingConfigurable):' | |||||
199 | kwargs from display call passed through without modification. |
|
247 | kwargs from display call passed through without modification. | |
200 | remove: bool |
|
248 | remove: bool | |
201 | True if the callback should be unregistered.""" |
|
249 | True if the callback should be unregistered.""" | |
202 | if remove and callback in self._display_callbacks: |
|
250 | self._display_callbacks.register_callback(callback, remove=remove) | |
203 | self._display_callbacks.remove(callback) |
|
|||
204 | elif not remove and not callback in self._display_callbacks: |
|
|||
205 | if callable(handler): |
|
|||
206 | self._display_callbacks.append(callback) |
|
|||
207 | else: |
|
|||
208 | raise Exception('Callback must be callable.') |
|
|||
209 |
|
251 | |||
210 | #------------------------------------------------------------------------- |
|
252 | #------------------------------------------------------------------------- | |
211 | # Support methods |
|
253 | # Support methods | |
@@ -262,8 +304,8 b' class Widget(LoggingConfigurable):' | |||||
262 |
|
304 | |||
263 | def _handle_custom_msg(self, content): |
|
305 | def _handle_custom_msg(self, content): | |
264 | """Called when a custom msg is received.""" |
|
306 | """Called when a custom msg is received.""" | |
265 | for handler in self._msg_callbacks: |
|
307 | self._msg_callbacks(content) # Signature 1 | |
266 | handler(self, content) |
|
308 | self._msg_callbacks(self, content) # Signature 2 | |
267 |
|
309 | |||
268 | def _handle_property_changed(self, name, old, new): |
|
310 | def _handle_property_changed(self, name, old, new): | |
269 | """Called when a property has been changed.""" |
|
311 | """Called when a property has been changed.""" | |
@@ -274,8 +316,7 b' class Widget(LoggingConfigurable):' | |||||
274 |
|
316 | |||
275 | def _handle_displayed(self, **kwargs): |
|
317 | def _handle_displayed(self, **kwargs): | |
276 | """Called when a view has been displayed for this widget instance""" |
|
318 | """Called when a view has been displayed for this widget instance""" | |
277 |
|
|
319 | self._display_callbacks(**kwargs) | |
278 | handler(self, **kwargs) |
|
|||
279 |
|
320 | |||
280 | def _pack_widgets(self, x): |
|
321 | def _pack_widgets(self, x): | |
281 | """Recursively converts all widget instances to model id strings. |
|
322 | """Recursively converts all widget instances to model id strings. |
@@ -14,10 +14,7 b' click events on the button and trigger backend code when the clicks are fired.' | |||||
14 | #----------------------------------------------------------------------------- |
|
14 | #----------------------------------------------------------------------------- | |
15 | # Imports |
|
15 | # Imports | |
16 | #----------------------------------------------------------------------------- |
|
16 | #----------------------------------------------------------------------------- | |
17 | import inspect |
|
17 | from .widget import DOMWidget, CallbackDispatcher | |
18 | import types |
|
|||
19 |
|
||||
20 | from .widget import DOMWidget |
|
|||
21 | from IPython.utils.traitlets import Unicode, Bool |
|
18 | from IPython.utils.traitlets import Unicode, Bool | |
22 |
|
19 | |||
23 | #----------------------------------------------------------------------------- |
|
20 | #----------------------------------------------------------------------------- | |
@@ -33,8 +30,7 b' class ButtonWidget(DOMWidget):' | |||||
33 | def __init__(self, **kwargs): |
|
30 | def __init__(self, **kwargs): | |
34 | """Constructor""" |
|
31 | """Constructor""" | |
35 | super(ButtonWidget, self).__init__(**kwargs) |
|
32 | super(ButtonWidget, self).__init__(**kwargs) | |
36 |
|
33 | self._click_handlers = CallbackDispatcher(acceptable_nargs=[0, 1]) | ||
37 | self._click_handlers = [] |
|
|||
38 | self.on_msg(self._handle_button_msg) |
|
34 | self.on_msg(self._handle_button_msg) | |
39 |
|
35 | |||
40 | def on_click(self, callback, remove=False): |
|
36 | def on_click(self, callback, remove=False): | |
@@ -50,10 +46,7 b' class ButtonWidget(DOMWidget):' | |||||
50 | ---------- |
|
46 | ---------- | |
51 | remove : bool (optional) |
|
47 | remove : bool (optional) | |
52 | Set to true to remove the callback from the list of callbacks.""" |
|
48 | Set to true to remove the callback from the list of callbacks.""" | |
53 | if remove: |
|
49 | self._click_handlers.register_callback(callback, remove=remove) | |
54 | self._click_handlers.remove(callback) |
|
|||
55 | elif not callback in self._click_handlers: |
|
|||
56 | self._click_handlers.append(callback) |
|
|||
57 |
|
50 | |||
58 | def _handle_button_msg(self, content): |
|
51 | def _handle_button_msg(self, content): | |
59 | """Handle a msg from the front-end. |
|
52 | """Handle a msg from the front-end. | |
@@ -63,26 +56,5 b' class ButtonWidget(DOMWidget):' | |||||
63 | content: dict |
|
56 | content: dict | |
64 | Content of the msg.""" |
|
57 | Content of the msg.""" | |
65 | if 'event' in content and content['event'] == 'click': |
|
58 | if 'event' in content and content['event'] == 'click': | |
66 |
self._handle |
|
59 | self._click_handlers() | |
67 |
|
60 | self._click_handlers(self) | ||
68 | def _handle_click(self): |
|
|||
69 | """Handles when the button has been clicked. |
|
|||
70 |
|
||||
71 | Fires on_click callbacks when appropriate.""" |
|
|||
72 | for handler in self._click_handlers: |
|
|||
73 | if callable(handler): |
|
|||
74 | argspec = inspect.getargspec(handler) |
|
|||
75 | nargs = len(argspec[0]) |
|
|||
76 |
|
||||
77 | # Bound methods have an additional 'self' argument |
|
|||
78 | if isinstance(handler, types.MethodType): |
|
|||
79 | nargs -= 1 |
|
|||
80 |
|
||||
81 | # Call the callback |
|
|||
82 | if nargs == 0: |
|
|||
83 | handler() |
|
|||
84 | elif nargs == 1: |
|
|||
85 | handler(self) |
|
|||
86 | else: |
|
|||
87 | raise TypeError('ButtonWidget click callback must ' \ |
|
|||
88 | 'accept 0 or 1 arguments.') |
|
@@ -13,10 +13,7 b' Represents a unicode string using a widget.' | |||||
13 | #----------------------------------------------------------------------------- |
|
13 | #----------------------------------------------------------------------------- | |
14 | # Imports |
|
14 | # Imports | |
15 | #----------------------------------------------------------------------------- |
|
15 | #----------------------------------------------------------------------------- | |
16 | import inspect |
|
16 | from .widget import DOMWidget, CallbackDispatcher | |
17 | import types |
|
|||
18 |
|
||||
19 | from .widget import DOMWidget |
|
|||
20 | from IPython.utils.traitlets import Unicode, Bool, List |
|
17 | from IPython.utils.traitlets import Unicode, Bool, List | |
21 |
|
18 | |||
22 | #----------------------------------------------------------------------------- |
|
19 | #----------------------------------------------------------------------------- | |
@@ -47,7 +44,7 b' class TextBoxWidget(HTMLWidget):' | |||||
47 |
|
44 | |||
48 | def __init__(self, **kwargs): |
|
45 | def __init__(self, **kwargs): | |
49 | super(TextBoxWidget, self).__init__(**kwargs) |
|
46 | super(TextBoxWidget, self).__init__(**kwargs) | |
50 |
self._submission_callbacks = [] |
|
47 | self._submission_callbacks = CallbackDispatcher(acceptable_nargs=[0, 1]) | |
51 | self.on_msg(self._handle_string_msg) |
|
48 | self.on_msg(self._handle_string_msg) | |
52 |
|
49 | |||
53 | def _handle_string_msg(self, content): |
|
50 | def _handle_string_msg(self, content): | |
@@ -58,8 +55,8 b' class TextBoxWidget(HTMLWidget):' | |||||
58 | content: dict |
|
55 | content: dict | |
59 | Content of the msg.""" |
|
56 | Content of the msg.""" | |
60 | if 'event' in content and content['event'] == 'submit': |
|
57 | if 'event' in content and content['event'] == 'submit': | |
61 |
|
|
58 | self._submission_callbacks() | |
62 | handler(self) |
|
59 | self._submission_callbacks(self) | |
63 |
|
60 | |||
64 | def on_submit(self, callback, remove=False): |
|
61 | def on_submit(self, callback, remove=False): | |
65 | """(Un)Register a callback to handle text submission. |
|
62 | """(Un)Register a callback to handle text submission. | |
@@ -74,22 +71,4 b' class TextBoxWidget(HTMLWidget):' | |||||
74 | callback(sender) |
|
71 | callback(sender) | |
75 | remove: bool (optional) |
|
72 | remove: bool (optional) | |
76 | Whether or not to unregister the callback""" |
|
73 | Whether or not to unregister the callback""" | |
77 | if remove and callback in self._submission_callbacks: |
|
74 | self._submission_callbacks.register_callback(callback, remove=remove) | |
78 | self._submission_callbacks.remove(callback) |
|
|||
79 | elif not remove and not callback in self._submission_callbacks: |
|
|||
80 | if callable(callback): |
|
|||
81 | argspec = inspect.getargspec(callback) |
|
|||
82 | nargs = len(argspec[0]) |
|
|||
83 |
|
||||
84 | # Bound methods have an additional 'self' argument |
|
|||
85 | if isinstance(callback, types.MethodType): |
|
|||
86 | nargs -= 1 |
|
|||
87 |
|
||||
88 | # Call the callback |
|
|||
89 | if nargs == 0: |
|
|||
90 | self._submission_callbacks.append(lambda sender: callback()) |
|
|||
91 | elif nargs == 1: |
|
|||
92 | self._submission_callbacks.append(callback) |
|
|||
93 | else: |
|
|||
94 | raise TypeError('TextBoxWidget submit callback must ' \ |
|
|||
95 | 'accept 0 or 1 arguments.') |
|
General Comments 0
You need to be logged in to leave comments.
Login now