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