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