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