##// END OF EJS Templates
Added new CallbackDispatcher class
Jonathan Frederic -
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 Widget(LoggingConfigurable):
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 for handler in self._display_callbacks:
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_click()
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 for handler in self._submission_callbacks:
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