##// END OF EJS Templates
Correct documentation
Jason Grout -
Show More
@@ -1,543 +1,539 b''
1 """Base Widget class. Allows user to create widgets in the back-end that render
1 """Base Widget class. Allows user to create widgets in the back-end that render
2 in the IPython notebook front-end.
2 in the IPython notebook front-end.
3 """
3 """
4 #-----------------------------------------------------------------------------
4 #-----------------------------------------------------------------------------
5 # Copyright (c) 2013, the IPython Development Team.
5 # Copyright (c) 2013, the IPython Development Team.
6 #
6 #
7 # Distributed under the terms of the Modified BSD License.
7 # Distributed under the terms of the Modified BSD License.
8 #
8 #
9 # The full license is in the file COPYING.txt, distributed with this software.
9 # The full license is in the file COPYING.txt, distributed with this software.
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11
11
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13 # Imports
13 # Imports
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 from contextlib import contextmanager
15 from contextlib import contextmanager
16 import collections
16 import collections
17
17
18 from IPython.core.getipython import get_ipython
18 from IPython.core.getipython import get_ipython
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.importstring import import_item
21 from IPython.utils.importstring import import_item
22 from IPython.utils.traitlets import Unicode, Dict, Instance, Bool, List, \
22 from IPython.utils.traitlets import Unicode, Dict, Instance, Bool, List, \
23 CaselessStrEnum, Tuple, CUnicode, Int, Set
23 CaselessStrEnum, Tuple, CUnicode, Int, Set
24 from IPython.utils.py3compat import string_types
24 from IPython.utils.py3compat import string_types
25 from .trait_types import Color
25 from .trait_types import Color
26
26
27 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
28 # Classes
28 # Classes
29 #-----------------------------------------------------------------------------
29 #-----------------------------------------------------------------------------
30 class CallbackDispatcher(LoggingConfigurable):
30 class CallbackDispatcher(LoggingConfigurable):
31 """A structure for registering and running callbacks"""
31 """A structure for registering and running callbacks"""
32 callbacks = List()
32 callbacks = List()
33
33
34 def __call__(self, *args, **kwargs):
34 def __call__(self, *args, **kwargs):
35 """Call all of the registered callbacks."""
35 """Call all of the registered callbacks."""
36 value = None
36 value = None
37 for callback in self.callbacks:
37 for callback in self.callbacks:
38 try:
38 try:
39 local_value = callback(*args, **kwargs)
39 local_value = callback(*args, **kwargs)
40 except Exception as e:
40 except Exception as e:
41 ip = get_ipython()
41 ip = get_ipython()
42 if ip is None:
42 if ip is None:
43 self.log.warn("Exception in callback %s: %s", callback, e, exc_info=True)
43 self.log.warn("Exception in callback %s: %s", callback, e, exc_info=True)
44 else:
44 else:
45 ip.showtraceback()
45 ip.showtraceback()
46 else:
46 else:
47 value = local_value if local_value is not None else value
47 value = local_value if local_value is not None else value
48 return value
48 return value
49
49
50 def register_callback(self, callback, remove=False):
50 def register_callback(self, callback, remove=False):
51 """(Un)Register a callback
51 """(Un)Register a callback
52
52
53 Parameters
53 Parameters
54 ----------
54 ----------
55 callback: method handle
55 callback: method handle
56 Method to be registered or unregistered.
56 Method to be registered or unregistered.
57 remove=False: bool
57 remove=False: bool
58 Whether to unregister the callback."""
58 Whether to unregister the callback."""
59
59
60 # (Un)Register the callback.
60 # (Un)Register the callback.
61 if remove and callback in self.callbacks:
61 if remove and callback in self.callbacks:
62 self.callbacks.remove(callback)
62 self.callbacks.remove(callback)
63 elif not remove and callback not in self.callbacks:
63 elif not remove and callback not in self.callbacks:
64 self.callbacks.append(callback)
64 self.callbacks.append(callback)
65
65
66 def _show_traceback(method):
66 def _show_traceback(method):
67 """decorator for showing tracebacks in IPython"""
67 """decorator for showing tracebacks in IPython"""
68 def m(self, *args, **kwargs):
68 def m(self, *args, **kwargs):
69 try:
69 try:
70 return(method(self, *args, **kwargs))
70 return(method(self, *args, **kwargs))
71 except Exception as e:
71 except Exception as e:
72 ip = get_ipython()
72 ip = get_ipython()
73 if ip is None:
73 if ip is None:
74 self.log.warn("Exception in widget method %s: %s", method, e, exc_info=True)
74 self.log.warn("Exception in widget method %s: %s", method, e, exc_info=True)
75 else:
75 else:
76 ip.showtraceback()
76 ip.showtraceback()
77 return m
77 return m
78
78
79
79
80 def register(key=None):
80 def register(key=None):
81 """Returns a decorator registering a widget class in the widget registry.
81 """Returns a decorator registering a widget class in the widget registry.
82 If no key is provided, the class name is used as a key. A key is
82 If no key is provided, the class name is used as a key. A key is
83 provided for each core IPython widget so that the frontend can use
83 provided for each core IPython widget so that the frontend can use
84 this key regardless of the language of the kernel"""
84 this key regardless of the language of the kernel"""
85 def wrap(widget):
85 def wrap(widget):
86 l = key if key is not None else widget.__module__ + widget.__name__
86 l = key if key is not None else widget.__module__ + widget.__name__
87 Widget.widget_types[l] = widget
87 Widget.widget_types[l] = widget
88 return widget
88 return widget
89 return wrap
89 return wrap
90
90
91
91
92 def _widget_to_json(x):
92 def _widget_to_json(x):
93 if isinstance(x, dict):
93 if isinstance(x, dict):
94 return {k: _widget_to_json(v) for k, v in x.items()}
94 return {k: _widget_to_json(v) for k, v in x.items()}
95 elif isinstance(x, (list, tuple)):
95 elif isinstance(x, (list, tuple)):
96 return [_widget_to_json(v) for v in x]
96 return [_widget_to_json(v) for v in x]
97 elif isinstance(x, Widget):
97 elif isinstance(x, Widget):
98 return "IPY_MODEL_" + x.model_id
98 return "IPY_MODEL_" + x.model_id
99 else:
99 else:
100 return x
100 return x
101
101
102 def _json_to_widget(x):
102 def _json_to_widget(x):
103 if isinstance(x, dict):
103 if isinstance(x, dict):
104 return {k: _json_to_widget(v) for k, v in x.items()}
104 return {k: _json_to_widget(v) for k, v in x.items()}
105 elif isinstance(x, (list, tuple)):
105 elif isinstance(x, (list, tuple)):
106 return [_json_to_widget(v) for v in x]
106 return [_json_to_widget(v) for v in x]
107 elif isinstance(x, string_types) and x.startswith('IPY_MODEL_') and x[10:] in Widget.widgets:
107 elif isinstance(x, string_types) and x.startswith('IPY_MODEL_') and x[10:] in Widget.widgets:
108 return Widget.widgets[x[10:]]
108 return Widget.widgets[x[10:]]
109 else:
109 else:
110 return x
110 return x
111
111
112 widget_serialization = {
112 widget_serialization = {
113 'from_json': _json_to_widget,
113 'from_json': _json_to_widget,
114 'to_json': lambda x: (_widget_to_json(x), {'serialization': ('widget_serialization', 'widgets/js/types')})
114 'to_json': lambda x: (_widget_to_json(x), {'serialization': ('widget_serialization', 'widgets/js/types')})
115 }
115 }
116
116
117 def _to_binary_list(x):
117 def _to_binary_list(x):
118 import numpy
118 import numpy
119 return memoryview(numpy.array(x, dtype=float)), {'serialization': ('list_of_numbers', 'widgets/js/types')}
119 return memoryview(numpy.array(x, dtype=float)), {'serialization': ('list_of_numbers', 'widgets/js/types')}
120
120
121 def _from_binary_list(x):
121 def _from_binary_list(x):
122 import numpy
122 import numpy
123 a = numpy.frombuffer(x.tobytes(), dtype=float)
123 a = numpy.frombuffer(x.tobytes(), dtype=float)
124 return list(a)
124 return list(a)
125
125
126 list_of_numbers = {
126 list_of_numbers = {
127 'from_json': _from_binary_list,
127 'from_json': _from_binary_list,
128 'to_json': _to_binary_list
128 'to_json': _to_binary_list
129 }
129 }
130
130
131
131
132
132
133 class Widget(LoggingConfigurable):
133 class Widget(LoggingConfigurable):
134 #-------------------------------------------------------------------------
134 #-------------------------------------------------------------------------
135 # Class attributes
135 # Class attributes
136 #-------------------------------------------------------------------------
136 #-------------------------------------------------------------------------
137 _widget_construction_callback = None
137 _widget_construction_callback = None
138 widgets = {}
138 widgets = {}
139 widget_types = {}
139 widget_types = {}
140
140
141 @staticmethod
141 @staticmethod
142 def on_widget_constructed(callback):
142 def on_widget_constructed(callback):
143 """Registers a callback to be called when a widget is constructed.
143 """Registers a callback to be called when a widget is constructed.
144
144
145 The callback must have the following signature:
145 The callback must have the following signature:
146 callback(widget)"""
146 callback(widget)"""
147 Widget._widget_construction_callback = callback
147 Widget._widget_construction_callback = callback
148
148
149 @staticmethod
149 @staticmethod
150 def _call_widget_constructed(widget):
150 def _call_widget_constructed(widget):
151 """Static method, called when a widget is constructed."""
151 """Static method, called when a widget is constructed."""
152 if Widget._widget_construction_callback is not None and callable(Widget._widget_construction_callback):
152 if Widget._widget_construction_callback is not None and callable(Widget._widget_construction_callback):
153 Widget._widget_construction_callback(widget)
153 Widget._widget_construction_callback(widget)
154
154
155 @staticmethod
155 @staticmethod
156 def handle_comm_opened(comm, msg):
156 def handle_comm_opened(comm, msg):
157 """Static method, called when a widget is constructed."""
157 """Static method, called when a widget is constructed."""
158 widget_class = import_item(msg['content']['data']['widget_class'])
158 widget_class = import_item(msg['content']['data']['widget_class'])
159 widget = widget_class(comm=comm)
159 widget = widget_class(comm=comm)
160
160
161
161
162 #-------------------------------------------------------------------------
162 #-------------------------------------------------------------------------
163 # Traits
163 # Traits
164 #-------------------------------------------------------------------------
164 #-------------------------------------------------------------------------
165 _model_module = Unicode(None, allow_none=True, help="""A requirejs module name
165 _model_module = Unicode(None, allow_none=True, help="""A requirejs module name
166 in which to find _model_name. If empty, look in the global registry.""")
166 in which to find _model_name. If empty, look in the global registry.""")
167 _model_name = Unicode('WidgetModel', help="""Name of the backbone model
167 _model_name = Unicode('WidgetModel', help="""Name of the backbone model
168 registered in the front-end to create and sync this widget with.""")
168 registered in the front-end to create and sync this widget with.""")
169 _view_module = Unicode(help="""A requirejs module in which to find _view_name.
169 _view_module = Unicode(help="""A requirejs module in which to find _view_name.
170 If empty, look in the global registry.""", sync=True)
170 If empty, look in the global registry.""", sync=True)
171 _view_name = Unicode(None, allow_none=True, help="""Default view registered in the front-end
171 _view_name = Unicode(None, allow_none=True, help="""Default view registered in the front-end
172 to use to represent the widget.""", sync=True)
172 to use to represent the widget.""", sync=True)
173 comm = Instance('IPython.kernel.comm.Comm')
173 comm = Instance('IPython.kernel.comm.Comm')
174
174
175 msg_throttle = Int(3, sync=True, help="""Maximum number of msgs the
175 msg_throttle = Int(3, sync=True, help="""Maximum number of msgs the
176 front-end can send before receiving an idle msg from the back-end.""")
176 front-end can send before receiving an idle msg from the back-end.""")
177
177
178 version = Int(0, sync=True, help="""Widget's version""")
178 version = Int(0, sync=True, help="""Widget's version""")
179 keys = List()
179 keys = List()
180 def _keys_default(self):
180 def _keys_default(self):
181 return [name for name in self.traits(sync=True)]
181 return [name for name in self.traits(sync=True)]
182
182
183 _property_lock = Tuple((None, None))
183 _property_lock = Tuple((None, None))
184 _send_state_lock = Int(0)
184 _send_state_lock = Int(0)
185 _states_to_send = Set()
185 _states_to_send = Set()
186 _display_callbacks = Instance(CallbackDispatcher, ())
186 _display_callbacks = Instance(CallbackDispatcher, ())
187 _msg_callbacks = Instance(CallbackDispatcher, ())
187 _msg_callbacks = Instance(CallbackDispatcher, ())
188
188
189 #-------------------------------------------------------------------------
189 #-------------------------------------------------------------------------
190 # (Con/de)structor
190 # (Con/de)structor
191 #-------------------------------------------------------------------------
191 #-------------------------------------------------------------------------
192 def __init__(self, **kwargs):
192 def __init__(self, **kwargs):
193 """Public constructor"""
193 """Public constructor"""
194 self._model_id = kwargs.pop('model_id', None)
194 self._model_id = kwargs.pop('model_id', None)
195 super(Widget, self).__init__(**kwargs)
195 super(Widget, self).__init__(**kwargs)
196
196
197 Widget._call_widget_constructed(self)
197 Widget._call_widget_constructed(self)
198 self.open()
198 self.open()
199
199
200 def __del__(self):
200 def __del__(self):
201 """Object disposal"""
201 """Object disposal"""
202 self.close()
202 self.close()
203
203
204 #-------------------------------------------------------------------------
204 #-------------------------------------------------------------------------
205 # Properties
205 # Properties
206 #-------------------------------------------------------------------------
206 #-------------------------------------------------------------------------
207
207
208 def open(self):
208 def open(self):
209 """Open a comm to the frontend if one isn't already open."""
209 """Open a comm to the frontend if one isn't already open."""
210 if self.comm is None:
210 if self.comm is None:
211 args = dict(target_name='ipython.widget',
211 args = dict(target_name='ipython.widget',
212 data={'model_name': self._model_name,
212 data={'model_name': self._model_name,
213 'model_module': self._model_module})
213 'model_module': self._model_module})
214 if self._model_id is not None:
214 if self._model_id is not None:
215 args['comm_id'] = self._model_id
215 args['comm_id'] = self._model_id
216 self.comm = Comm(**args)
216 self.comm = Comm(**args)
217
217
218 def _comm_changed(self, name, new):
218 def _comm_changed(self, name, new):
219 """Called when the comm is changed."""
219 """Called when the comm is changed."""
220 if new is None:
220 if new is None:
221 return
221 return
222 self._model_id = self.model_id
222 self._model_id = self.model_id
223
223
224 self.comm.on_msg(self._handle_msg)
224 self.comm.on_msg(self._handle_msg)
225 Widget.widgets[self.model_id] = self
225 Widget.widgets[self.model_id] = self
226
226
227 # first update
227 # first update
228 self.send_state()
228 self.send_state()
229
229
230 @property
230 @property
231 def model_id(self):
231 def model_id(self):
232 """Gets the model id of this widget.
232 """Gets the model id of this widget.
233
233
234 If a Comm doesn't exist yet, a Comm will be created automagically."""
234 If a Comm doesn't exist yet, a Comm will be created automagically."""
235 return self.comm.comm_id
235 return self.comm.comm_id
236
236
237 #-------------------------------------------------------------------------
237 #-------------------------------------------------------------------------
238 # Methods
238 # Methods
239 #-------------------------------------------------------------------------
239 #-------------------------------------------------------------------------
240
240
241 def close(self):
241 def close(self):
242 """Close method.
242 """Close method.
243
243
244 Closes the underlying comm.
244 Closes the underlying comm.
245 When the comm is closed, all of the widget views are automatically
245 When the comm is closed, all of the widget views are automatically
246 removed from the front-end."""
246 removed from the front-end."""
247 if self.comm is not None:
247 if self.comm is not None:
248 Widget.widgets.pop(self.model_id, None)
248 Widget.widgets.pop(self.model_id, None)
249 self.comm.close()
249 self.comm.close()
250 self.comm = None
250 self.comm = None
251
251
252 def send_state(self, key=None):
252 def send_state(self, key=None):
253 """Sends the widget state, or a piece of it, to the front-end.
253 """Sends the widget state, or a piece of it, to the front-end.
254
254
255 Parameters
255 Parameters
256 ----------
256 ----------
257 key : unicode, or iterable (optional)
257 key : unicode, or iterable (optional)
258 A single property's name or iterable of property names to sync with the front-end.
258 A single property's name or iterable of property names to sync with the front-end.
259 """
259 """
260 state, buffer_keys, buffers, metadata = self.get_state(key=key)
260 state, buffer_keys, buffers, metadata = self.get_state(key=key)
261 msg = {"method": "update", "state": state}
261 msg = {"method": "update", "state": state}
262 if buffer_keys:
262 if buffer_keys:
263 msg['buffers'] = buffer_keys
263 msg['buffers'] = buffer_keys
264 if metadata:
264 if metadata:
265 msg['metadata'] = metadata
265 msg['metadata'] = metadata
266 self._send(msg, buffers=buffers)
266 self._send(msg, buffers=buffers)
267
267
268 def get_state(self, key=None):
268 def get_state(self, key=None):
269 """Gets the widget state, or a piece of it.
269 """Gets the widget state, or a piece of it.
270
270
271 Parameters
271 Parameters
272 ----------
272 ----------
273 key : unicode or iterable (optional)
273 key : unicode or iterable (optional)
274 A single property's name or iterable of property names to get.
274 A single property's name or iterable of property names to get.
275
275
276 Returns
276 Returns
277 -------
277 -------
278 state : dict of states
278 state : dict of states
279 buffer_keys : list of strings
279 buffer_keys : list of strings
280 the values that are stored in buffers
280 the values that are stored in buffers
281 buffers : list of binary memoryviews
281 buffers : list of binary memoryviews
282 values to transmit in binary
282 values to transmit in binary
283 metadata : dict
283 metadata : dict
284 metadata for each field: {key: metadata}
284 metadata for each field: {key: metadata}
285 """
285 """
286 if key is None:
286 if key is None:
287 keys = self.keys
287 keys = self.keys
288 elif isinstance(key, string_types):
288 elif isinstance(key, string_types):
289 keys = [key]
289 keys = [key]
290 elif isinstance(key, collections.Iterable):
290 elif isinstance(key, collections.Iterable):
291 keys = key
291 keys = key
292 else:
292 else:
293 raise ValueError("key must be a string, an iterable of keys, or None")
293 raise ValueError("key must be a string, an iterable of keys, or None")
294 state = {}
294 state = {}
295 buffers = []
295 buffers = []
296 buffer_keys = []
296 buffer_keys = []
297 metadata = {}
297 metadata = {}
298 for k in keys:
298 for k in keys:
299 f = self.trait_metadata(k, 'to_json', self._trait_to_json)
299 f = self.trait_metadata(k, 'to_json', self._trait_to_json)
300 value = getattr(self, k)
300 value = getattr(self, k)
301 serialized, md = f(value)
301 serialized, md = f(value)
302 if isinstance(serialized, memoryview):
302 if isinstance(serialized, memoryview):
303 buffers.append(serialized)
303 buffers.append(serialized)
304 buffer_keys.append(k)
304 buffer_keys.append(k)
305 else:
305 else:
306 state[k] = serialized
306 state[k] = serialized
307 if md is not None:
307 if md is not None:
308 metadata[k] = md
308 metadata[k] = md
309 return state, buffer_keys, buffers, metadata
309 return state, buffer_keys, buffers, metadata
310
310
311 def set_state(self, sync_data):
311 def set_state(self, sync_data):
312 """Called when a state is received from the front-end."""
312 """Called when a state is received from the front-end."""
313 for name in self.keys:
313 for name in self.keys:
314 if name in sync_data:
314 if name in sync_data:
315 json_value = sync_data[name]
315 json_value = sync_data[name]
316 from_json = self.trait_metadata(name, 'from_json', self._trait_from_json)
316 from_json = self.trait_metadata(name, 'from_json', self._trait_from_json)
317 with self._lock_property(name, json_value):
317 with self._lock_property(name, json_value):
318 setattr(self, name, from_json(json_value))
318 setattr(self, name, from_json(json_value))
319
319
320 def send(self, content, buffers=None):
320 def send(self, content, buffers=None):
321 """Sends a custom msg to the widget model in the front-end.
321 """Sends a custom msg to the widget model in the front-end.
322
322
323 Parameters
323 Parameters
324 ----------
324 ----------
325 content : dict
325 content : dict
326 Content of the message to send.
326 Content of the message to send.
327 buffers : list of binary buffers
327 buffers : list of binary buffers
328 Binary buffers to send with message
328 Binary buffers to send with message
329 """
329 """
330 self._send({"method": "custom", "content": content}, buffers=buffers)
330 self._send({"method": "custom", "content": content}, buffers=buffers)
331
331
332 def on_msg(self, callback, remove=False):
332 def on_msg(self, callback, remove=False):
333 """(Un)Register a custom msg receive callback.
333 """(Un)Register a custom msg receive callback.
334
334
335 Parameters
335 Parameters
336 ----------
336 ----------
337 callback: callable
337 callback: callable
338 callback will be passed three arguments when a message arrives::
338 callback will be passed three arguments when a message arrives::
339
339
340 callback(widget, content, buffers)
340 callback(widget, content, buffers)
341
341
342 remove: bool
342 remove: bool
343 True if the callback should be unregistered."""
343 True if the callback should be unregistered."""
344 self._msg_callbacks.register_callback(callback, remove=remove)
344 self._msg_callbacks.register_callback(callback, remove=remove)
345
345
346 def on_displayed(self, callback, remove=False):
346 def on_displayed(self, callback, remove=False):
347 """(Un)Register a widget displayed callback.
347 """(Un)Register a widget displayed callback.
348
348
349 Parameters
349 Parameters
350 ----------
350 ----------
351 callback: method handler
351 callback: method handler
352 Must have a signature of::
352 Must have a signature of::
353
353
354 callback(widget, **kwargs)
354 callback(widget, **kwargs)
355
355
356 kwargs from display are passed through without modification.
356 kwargs from display are passed through without modification.
357 remove: bool
357 remove: bool
358 True if the callback should be unregistered."""
358 True if the callback should be unregistered."""
359 self._display_callbacks.register_callback(callback, remove=remove)
359 self._display_callbacks.register_callback(callback, remove=remove)
360
360
361 #-------------------------------------------------------------------------
361 #-------------------------------------------------------------------------
362 # Support methods
362 # Support methods
363 #-------------------------------------------------------------------------
363 #-------------------------------------------------------------------------
364 @contextmanager
364 @contextmanager
365 def _lock_property(self, key, value):
365 def _lock_property(self, key, value):
366 """Lock a property-value pair.
366 """Lock a property-value pair.
367
367
368 The value should be the JSON state of the property.
368 The value should be the JSON state of the property.
369
369
370 NOTE: This, in addition to the single lock for all state changes, is
370 NOTE: This, in addition to the single lock for all state changes, is
371 flawed. In the future we may want to look into buffering state changes
371 flawed. In the future we may want to look into buffering state changes
372 back to the front-end."""
372 back to the front-end."""
373 self._property_lock = (key, value)
373 self._property_lock = (key, value)
374 try:
374 try:
375 yield
375 yield
376 finally:
376 finally:
377 self._property_lock = (None, None)
377 self._property_lock = (None, None)
378
378
379 @contextmanager
379 @contextmanager
380 def hold_sync(self):
380 def hold_sync(self):
381 """Hold syncing any state until the context manager is released"""
381 """Hold syncing any state until the context manager is released"""
382 # We increment a value so that this can be nested. Syncing will happen when
382 # We increment a value so that this can be nested. Syncing will happen when
383 # all levels have been released.
383 # all levels have been released.
384 self._send_state_lock += 1
384 self._send_state_lock += 1
385 try:
385 try:
386 yield
386 yield
387 finally:
387 finally:
388 self._send_state_lock -=1
388 self._send_state_lock -=1
389 if self._send_state_lock == 0:
389 if self._send_state_lock == 0:
390 self.send_state(self._states_to_send)
390 self.send_state(self._states_to_send)
391 self._states_to_send.clear()
391 self._states_to_send.clear()
392
392
393 def _should_send_property(self, key, value):
393 def _should_send_property(self, key, value):
394 """Check the property lock (property_lock)"""
394 """Check the property lock (property_lock)"""
395 to_json = self.trait_metadata(key, 'to_json', self._trait_to_json)
395 to_json = self.trait_metadata(key, 'to_json', self._trait_to_json)
396 if (key == self._property_lock[0]
396 if (key == self._property_lock[0]
397 and to_json(value) == self._property_lock[1]):
397 and to_json(value) == self._property_lock[1]):
398 return False
398 return False
399 elif self._send_state_lock > 0:
399 elif self._send_state_lock > 0:
400 self._states_to_send.add(key)
400 self._states_to_send.add(key)
401 return False
401 return False
402 else:
402 else:
403 return True
403 return True
404
404
405 # Event handlers
405 # Event handlers
406 @_show_traceback
406 @_show_traceback
407 def _handle_msg(self, msg):
407 def _handle_msg(self, msg):
408 """Called when a msg is received from the front-end"""
408 """Called when a msg is received from the front-end"""
409 data = msg['content']['data']
409 data = msg['content']['data']
410 method = data['method']
410 method = data['method']
411
411
412 # Handle backbone sync methods CREATE, PATCH, and UPDATE all in one.
412 # Handle backbone sync methods CREATE, PATCH, and UPDATE all in one.
413 if method == 'backbone':
413 if method == 'backbone':
414 if 'sync_data' in data:
414 if 'sync_data' in data:
415 # get binary buffers too
415 # get binary buffers too
416 sync_data = data['sync_data']
416 sync_data = data['sync_data']
417 for i,k in enumerate(data.get('buffer_keys', [])):
417 for i,k in enumerate(data.get('buffer_keys', [])):
418 sync_data[k] = msg['buffers'][i]
418 sync_data[k] = msg['buffers'][i]
419 self.set_state(sync_data) # handles all methods
419 self.set_state(sync_data) # handles all methods
420
420
421 # Handle a state request.
421 # Handle a state request.
422 elif method == 'request_state':
422 elif method == 'request_state':
423 self.send_state()
423 self.send_state()
424
424
425 # Handle a custom msg from the front-end.
425 # Handle a custom msg from the front-end.
426 elif method == 'custom':
426 elif method == 'custom':
427 if 'content' in data:
427 if 'content' in data:
428 self._handle_custom_msg(data['content'], msg['buffers'])
428 self._handle_custom_msg(data['content'], msg['buffers'])
429
429
430 # Catch remainder.
430 # Catch remainder.
431 else:
431 else:
432 self.log.error('Unknown front-end to back-end widget msg with method "%s"' % method)
432 self.log.error('Unknown front-end to back-end widget msg with method "%s"' % method)
433
433
434 def _handle_custom_msg(self, content, buffers):
434 def _handle_custom_msg(self, content, buffers):
435 """Called when a custom msg is received."""
435 """Called when a custom msg is received."""
436 self._msg_callbacks(self, content, buffers)
436 self._msg_callbacks(self, content, buffers)
437
437
438 def _notify_trait(self, name, old_value, new_value):
438 def _notify_trait(self, name, old_value, new_value):
439 """Called when a property has been changed."""
439 """Called when a property has been changed."""
440 # Trigger default traitlet callback machinery. This allows any user
440 # Trigger default traitlet callback machinery. This allows any user
441 # registered validation to be processed prior to allowing the widget
441 # registered validation to be processed prior to allowing the widget
442 # machinery to handle the state.
442 # machinery to handle the state.
443 LoggingConfigurable._notify_trait(self, name, old_value, new_value)
443 LoggingConfigurable._notify_trait(self, name, old_value, new_value)
444
444
445 # Send the state after the user registered callbacks for trait changes
445 # Send the state after the user registered callbacks for trait changes
446 # have all fired (allows for user to validate values).
446 # have all fired (allows for user to validate values).
447 if self.comm is not None and name in self.keys:
447 if self.comm is not None and name in self.keys:
448 # Make sure this isn't information that the front-end just sent us.
448 # Make sure this isn't information that the front-end just sent us.
449 if self._should_send_property(name, new_value):
449 if self._should_send_property(name, new_value):
450 # Send new state to front-end
450 # Send new state to front-end
451 self.send_state(key=name)
451 self.send_state(key=name)
452
452
453 def _handle_displayed(self, **kwargs):
453 def _handle_displayed(self, **kwargs):
454 """Called when a view has been displayed for this widget instance"""
454 """Called when a view has been displayed for this widget instance"""
455 self._display_callbacks(self, **kwargs)
455 self._display_callbacks(self, **kwargs)
456
456
457 def _trait_to_json(self, x):
457 def _trait_to_json(self, x):
458 """Convert a trait value to json
458 """Convert a trait value to json.
459
459
460 Traverse lists/tuples and dicts and serialize their values as well.
460 Metadata (the second return value) is not sent
461 Replace any widgets with their model_id
462 """
461 """
463 return x, None
462 return x, None
464
463
465 def _trait_from_json(self, x):
464 def _trait_from_json(self, x):
466 """Convert json values to objects
465 """Convert json values to objects."""
467
468 Replace any strings representing valid model id values to Widget references.
469 """
470 return x
466 return x
471
467
472 def _ipython_display_(self, **kwargs):
468 def _ipython_display_(self, **kwargs):
473 """Called when `IPython.display.display` is called on the widget."""
469 """Called when `IPython.display.display` is called on the widget."""
474 # Show view.
470 # Show view.
475 if self._view_name is not None:
471 if self._view_name is not None:
476 self._send({"method": "display"})
472 self._send({"method": "display"})
477 self._handle_displayed(**kwargs)
473 self._handle_displayed(**kwargs)
478
474
479 def _send(self, msg, buffers=None):
475 def _send(self, msg, buffers=None):
480 """Sends a message to the model in the front-end."""
476 """Sends a message to the model in the front-end."""
481 self.comm.send(data=msg, buffers=buffers)
477 self.comm.send(data=msg, buffers=buffers)
482
478
483
479
484 class DOMWidget(Widget):
480 class DOMWidget(Widget):
485 visible = Bool(True, allow_none=True, help="Whether the widget is visible. False collapses the empty space, while None preserves the empty space.", sync=True)
481 visible = Bool(True, allow_none=True, help="Whether the widget is visible. False collapses the empty space, while None preserves the empty space.", sync=True)
486 _css = Tuple(sync=True, help="CSS property list: (selector, key, value)")
482 _css = Tuple(sync=True, help="CSS property list: (selector, key, value)")
487 _dom_classes = Tuple(sync=True, help="DOM classes applied to widget.$el.")
483 _dom_classes = Tuple(sync=True, help="DOM classes applied to widget.$el.")
488
484
489 width = CUnicode(sync=True)
485 width = CUnicode(sync=True)
490 height = CUnicode(sync=True)
486 height = CUnicode(sync=True)
491 # A default padding of 2.5 px makes the widgets look nice when displayed inline.
487 # A default padding of 2.5 px makes the widgets look nice when displayed inline.
492 padding = CUnicode(sync=True)
488 padding = CUnicode(sync=True)
493 margin = CUnicode(sync=True)
489 margin = CUnicode(sync=True)
494
490
495 color = Color(None, allow_none=True, sync=True)
491 color = Color(None, allow_none=True, sync=True)
496 background_color = Color(None, allow_none=True, sync=True)
492 background_color = Color(None, allow_none=True, sync=True)
497 border_color = Color(None, allow_none=True, sync=True)
493 border_color = Color(None, allow_none=True, sync=True)
498
494
499 border_width = CUnicode(sync=True)
495 border_width = CUnicode(sync=True)
500 border_radius = CUnicode(sync=True)
496 border_radius = CUnicode(sync=True)
501 border_style = CaselessStrEnum(values=[ # http://www.w3schools.com/cssref/pr_border-style.asp
497 border_style = CaselessStrEnum(values=[ # http://www.w3schools.com/cssref/pr_border-style.asp
502 'none',
498 'none',
503 'hidden',
499 'hidden',
504 'dotted',
500 'dotted',
505 'dashed',
501 'dashed',
506 'solid',
502 'solid',
507 'double',
503 'double',
508 'groove',
504 'groove',
509 'ridge',
505 'ridge',
510 'inset',
506 'inset',
511 'outset',
507 'outset',
512 'initial',
508 'initial',
513 'inherit', ''],
509 'inherit', ''],
514 default_value='', sync=True)
510 default_value='', sync=True)
515
511
516 font_style = CaselessStrEnum(values=[ # http://www.w3schools.com/cssref/pr_font_font-style.asp
512 font_style = CaselessStrEnum(values=[ # http://www.w3schools.com/cssref/pr_font_font-style.asp
517 'normal',
513 'normal',
518 'italic',
514 'italic',
519 'oblique',
515 'oblique',
520 'initial',
516 'initial',
521 'inherit', ''],
517 'inherit', ''],
522 default_value='', sync=True)
518 default_value='', sync=True)
523 font_weight = CaselessStrEnum(values=[ # http://www.w3schools.com/cssref/pr_font_weight.asp
519 font_weight = CaselessStrEnum(values=[ # http://www.w3schools.com/cssref/pr_font_weight.asp
524 'normal',
520 'normal',
525 'bold',
521 'bold',
526 'bolder',
522 'bolder',
527 'lighter',
523 'lighter',
528 'initial',
524 'initial',
529 'inherit', ''] + list(map(str, range(100,1000,100))),
525 'inherit', ''] + list(map(str, range(100,1000,100))),
530 default_value='', sync=True)
526 default_value='', sync=True)
531 font_size = CUnicode(sync=True)
527 font_size = CUnicode(sync=True)
532 font_family = Unicode(sync=True)
528 font_family = Unicode(sync=True)
533
529
534 def __init__(self, *pargs, **kwargs):
530 def __init__(self, *pargs, **kwargs):
535 super(DOMWidget, self).__init__(*pargs, **kwargs)
531 super(DOMWidget, self).__init__(*pargs, **kwargs)
536
532
537 def _validate_border(name, old, new):
533 def _validate_border(name, old, new):
538 if new is not None and new != '':
534 if new is not None and new != '':
539 if name != 'border_width' and not self.border_width:
535 if name != 'border_width' and not self.border_width:
540 self.border_width = 1
536 self.border_width = 1
541 if name != 'border_style' and self.border_style == '':
537 if name != 'border_style' and self.border_style == '':
542 self.border_style = 'solid'
538 self.border_style = 'solid'
543 self.on_trait_change(_validate_border, ['border_width', 'border_style', 'border_color'])
539 self.on_trait_change(_validate_border, ['border_width', 'border_style', 'border_color'])
General Comments 0
You need to be logged in to leave comments. Login now