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