##// END OF EJS Templates
Using the Color trait type for styling widgets
Sylvain Corlay -
Show More
@@ -1,489 +1,489
1 1 """Base Widget class. Allows user to create widgets in the back-end that render
2 2 in the IPython notebook front-end.
3 3 """
4 4 #-----------------------------------------------------------------------------
5 5 # Copyright (c) 2013, the IPython Development Team.
6 6 #
7 7 # Distributed under the terms of the Modified BSD License.
8 8 #
9 9 # The full license is in the file COPYING.txt, distributed with this software.
10 10 #-----------------------------------------------------------------------------
11 11
12 12 #-----------------------------------------------------------------------------
13 13 # Imports
14 14 #-----------------------------------------------------------------------------
15 15 from contextlib import contextmanager
16 16 import collections
17 17
18 18 from IPython.core.getipython import get_ipython
19 19 from IPython.kernel.comm import Comm
20 20 from IPython.config import LoggingConfigurable
21 21 from IPython.utils.importstring import import_item
22 22 from IPython.utils.traitlets import Unicode, Dict, Instance, Bool, List, \
23 CaselessStrEnum, Tuple, CUnicode, Int, Set
23 CaselessStrEnum, Tuple, CUnicode, Int, Set, Color
24 24 from IPython.utils.py3compat import string_types
25 25
26 26 #-----------------------------------------------------------------------------
27 27 # Classes
28 28 #-----------------------------------------------------------------------------
29 29 class CallbackDispatcher(LoggingConfigurable):
30 30 """A structure for registering and running callbacks"""
31 31 callbacks = List()
32 32
33 33 def __call__(self, *args, **kwargs):
34 34 """Call all of the registered callbacks."""
35 35 value = None
36 36 for callback in self.callbacks:
37 37 try:
38 38 local_value = callback(*args, **kwargs)
39 39 except Exception as e:
40 40 ip = get_ipython()
41 41 if ip is None:
42 42 self.log.warn("Exception in callback %s: %s", callback, e, exc_info=True)
43 43 else:
44 44 ip.showtraceback()
45 45 else:
46 46 value = local_value if local_value is not None else value
47 47 return value
48 48
49 49 def register_callback(self, callback, remove=False):
50 50 """(Un)Register a callback
51 51
52 52 Parameters
53 53 ----------
54 54 callback: method handle
55 55 Method to be registered or unregistered.
56 56 remove=False: bool
57 57 Whether to unregister the callback."""
58 58
59 59 # (Un)Register the callback.
60 60 if remove and callback in self.callbacks:
61 61 self.callbacks.remove(callback)
62 62 elif not remove and callback not in self.callbacks:
63 63 self.callbacks.append(callback)
64 64
65 65 def _show_traceback(method):
66 66 """decorator for showing tracebacks in IPython"""
67 67 def m(self, *args, **kwargs):
68 68 try:
69 69 return(method(self, *args, **kwargs))
70 70 except Exception as e:
71 71 ip = get_ipython()
72 72 if ip is None:
73 73 self.log.warn("Exception in widget method %s: %s", method, e, exc_info=True)
74 74 else:
75 75 ip.showtraceback()
76 76 return m
77 77
78 78
79 79 def register(key=None):
80 80 """Returns a decorator registering a widget class in the widget registry.
81 81 If no key is provided, the class name is used as a key. A key is
82 82 provided for each core IPython widget so that the frontend can use
83 83 this key regardless of the language of the kernel"""
84 84 def wrap(widget):
85 85 l = key if key is not None else widget.__module__ + widget.__name__
86 86 Widget.widget_types[l] = widget
87 87 return widget
88 88 return wrap
89 89
90 90
91 91 class Widget(LoggingConfigurable):
92 92 #-------------------------------------------------------------------------
93 93 # Class attributes
94 94 #-------------------------------------------------------------------------
95 95 _widget_construction_callback = None
96 96 widgets = {}
97 97 widget_types = {}
98 98
99 99 @staticmethod
100 100 def on_widget_constructed(callback):
101 101 """Registers a callback to be called when a widget is constructed.
102 102
103 103 The callback must have the following signature:
104 104 callback(widget)"""
105 105 Widget._widget_construction_callback = callback
106 106
107 107 @staticmethod
108 108 def _call_widget_constructed(widget):
109 109 """Static method, called when a widget is constructed."""
110 110 if Widget._widget_construction_callback is not None and callable(Widget._widget_construction_callback):
111 111 Widget._widget_construction_callback(widget)
112 112
113 113 @staticmethod
114 114 def handle_comm_opened(comm, msg):
115 115 """Static method, called when a widget is constructed."""
116 116 widget_class = import_item(msg['content']['data']['widget_class'])
117 117 widget = widget_class(comm=comm)
118 118
119 119
120 120 #-------------------------------------------------------------------------
121 121 # Traits
122 122 #-------------------------------------------------------------------------
123 123 _model_module = Unicode(None, allow_none=True, help="""A requirejs module name
124 124 in which to find _model_name. If empty, look in the global registry.""")
125 125 _model_name = Unicode('WidgetModel', help="""Name of the backbone model
126 126 registered in the front-end to create and sync this widget with.""")
127 127 _view_module = Unicode(help="""A requirejs module in which to find _view_name.
128 128 If empty, look in the global registry.""", sync=True)
129 129 _view_name = Unicode(None, allow_none=True, help="""Default view registered in the front-end
130 130 to use to represent the widget.""", sync=True)
131 131 comm = Instance('IPython.kernel.comm.Comm')
132 132
133 133 msg_throttle = Int(3, sync=True, help="""Maximum number of msgs the
134 134 front-end can send before receiving an idle msg from the back-end.""")
135 135
136 136 version = Int(0, sync=True, help="""Widget's version""")
137 137 keys = List()
138 138 def _keys_default(self):
139 139 return [name for name in self.traits(sync=True)]
140 140
141 141 _property_lock = Tuple((None, None))
142 142 _send_state_lock = Int(0)
143 143 _states_to_send = Set()
144 144 _display_callbacks = Instance(CallbackDispatcher, ())
145 145 _msg_callbacks = Instance(CallbackDispatcher, ())
146 146
147 147 #-------------------------------------------------------------------------
148 148 # (Con/de)structor
149 149 #-------------------------------------------------------------------------
150 150 def __init__(self, **kwargs):
151 151 """Public constructor"""
152 152 self._model_id = kwargs.pop('model_id', None)
153 153 super(Widget, self).__init__(**kwargs)
154 154
155 155 Widget._call_widget_constructed(self)
156 156 self.open()
157 157
158 158 def __del__(self):
159 159 """Object disposal"""
160 160 self.close()
161 161
162 162 #-------------------------------------------------------------------------
163 163 # Properties
164 164 #-------------------------------------------------------------------------
165 165
166 166 def open(self):
167 167 """Open a comm to the frontend if one isn't already open."""
168 168 if self.comm is None:
169 169 args = dict(target_name='ipython.widget',
170 170 data={'model_name': self._model_name,
171 171 'model_module': self._model_module})
172 172 if self._model_id is not None:
173 173 args['comm_id'] = self._model_id
174 174 self.comm = Comm(**args)
175 175
176 176 def _comm_changed(self, name, new):
177 177 """Called when the comm is changed."""
178 178 if new is None:
179 179 return
180 180 self._model_id = self.model_id
181 181
182 182 self.comm.on_msg(self._handle_msg)
183 183 Widget.widgets[self.model_id] = self
184 184
185 185 # first update
186 186 self.send_state()
187 187
188 188 @property
189 189 def model_id(self):
190 190 """Gets the model id of this widget.
191 191
192 192 If a Comm doesn't exist yet, a Comm will be created automagically."""
193 193 return self.comm.comm_id
194 194
195 195 #-------------------------------------------------------------------------
196 196 # Methods
197 197 #-------------------------------------------------------------------------
198 198
199 199 def close(self):
200 200 """Close method.
201 201
202 202 Closes the underlying comm.
203 203 When the comm is closed, all of the widget views are automatically
204 204 removed from the front-end."""
205 205 if self.comm is not None:
206 206 Widget.widgets.pop(self.model_id, None)
207 207 self.comm.close()
208 208 self.comm = None
209 209
210 210 def send_state(self, key=None):
211 211 """Sends the widget state, or a piece of it, to the front-end.
212 212
213 213 Parameters
214 214 ----------
215 215 key : unicode, or iterable (optional)
216 216 A single property's name or iterable of property names to sync with the front-end.
217 217 """
218 218 self._send({
219 219 "method" : "update",
220 220 "state" : self.get_state(key=key)
221 221 })
222 222
223 223 def get_state(self, key=None):
224 224 """Gets the widget state, or a piece of it.
225 225
226 226 Parameters
227 227 ----------
228 228 key : unicode or iterable (optional)
229 229 A single property's name or iterable of property names to get.
230 230 """
231 231 if key is None:
232 232 keys = self.keys
233 233 elif isinstance(key, string_types):
234 234 keys = [key]
235 235 elif isinstance(key, collections.Iterable):
236 236 keys = key
237 237 else:
238 238 raise ValueError("key must be a string, an iterable of keys, or None")
239 239 state = {}
240 240 for k in keys:
241 241 f = self.trait_metadata(k, 'to_json', self._trait_to_json)
242 242 value = getattr(self, k)
243 243 state[k] = f(value)
244 244 return state
245 245
246 246 def set_state(self, sync_data):
247 247 """Called when a state is received from the front-end."""
248 248 for name in self.keys:
249 249 if name in sync_data:
250 250 json_value = sync_data[name]
251 251 from_json = self.trait_metadata(name, 'from_json', self._trait_from_json)
252 252 with self._lock_property(name, json_value):
253 253 setattr(self, name, from_json(json_value))
254 254
255 255 def send(self, content):
256 256 """Sends a custom msg to the widget model in the front-end.
257 257
258 258 Parameters
259 259 ----------
260 260 content : dict
261 261 Content of the message to send.
262 262 """
263 263 self._send({"method": "custom", "content": content})
264 264
265 265 def on_msg(self, callback, remove=False):
266 266 """(Un)Register a custom msg receive callback.
267 267
268 268 Parameters
269 269 ----------
270 270 callback: callable
271 271 callback will be passed two arguments when a message arrives::
272 272
273 273 callback(widget, content)
274 274
275 275 remove: bool
276 276 True if the callback should be unregistered."""
277 277 self._msg_callbacks.register_callback(callback, remove=remove)
278 278
279 279 def on_displayed(self, callback, remove=False):
280 280 """(Un)Register a widget displayed callback.
281 281
282 282 Parameters
283 283 ----------
284 284 callback: method handler
285 285 Must have a signature of::
286 286
287 287 callback(widget, **kwargs)
288 288
289 289 kwargs from display are passed through without modification.
290 290 remove: bool
291 291 True if the callback should be unregistered."""
292 292 self._display_callbacks.register_callback(callback, remove=remove)
293 293
294 294 #-------------------------------------------------------------------------
295 295 # Support methods
296 296 #-------------------------------------------------------------------------
297 297 @contextmanager
298 298 def _lock_property(self, key, value):
299 299 """Lock a property-value pair.
300 300
301 301 The value should be the JSON state of the property.
302 302
303 303 NOTE: This, in addition to the single lock for all state changes, is
304 304 flawed. In the future we may want to look into buffering state changes
305 305 back to the front-end."""
306 306 self._property_lock = (key, value)
307 307 try:
308 308 yield
309 309 finally:
310 310 self._property_lock = (None, None)
311 311
312 312 @contextmanager
313 313 def hold_sync(self):
314 314 """Hold syncing any state until the context manager is released"""
315 315 # We increment a value so that this can be nested. Syncing will happen when
316 316 # all levels have been released.
317 317 self._send_state_lock += 1
318 318 try:
319 319 yield
320 320 finally:
321 321 self._send_state_lock -=1
322 322 if self._send_state_lock == 0:
323 323 self.send_state(self._states_to_send)
324 324 self._states_to_send.clear()
325 325
326 326 def _should_send_property(self, key, value):
327 327 """Check the property lock (property_lock)"""
328 328 to_json = self.trait_metadata(key, 'to_json', self._trait_to_json)
329 329 if (key == self._property_lock[0]
330 330 and to_json(value) == self._property_lock[1]):
331 331 return False
332 332 elif self._send_state_lock > 0:
333 333 self._states_to_send.add(key)
334 334 return False
335 335 else:
336 336 return True
337 337
338 338 # Event handlers
339 339 @_show_traceback
340 340 def _handle_msg(self, msg):
341 341 """Called when a msg is received from the front-end"""
342 342 data = msg['content']['data']
343 343 method = data['method']
344 344
345 345 # Handle backbone sync methods CREATE, PATCH, and UPDATE all in one.
346 346 if method == 'backbone':
347 347 if 'sync_data' in data:
348 348 sync_data = data['sync_data']
349 349 self.set_state(sync_data) # handles all methods
350 350
351 351 # Handle a state request.
352 352 elif method == 'request_state':
353 353 self.send_state()
354 354
355 355 # Handle a custom msg from the front-end.
356 356 elif method == 'custom':
357 357 if 'content' in data:
358 358 self._handle_custom_msg(data['content'])
359 359
360 360 # Catch remainder.
361 361 else:
362 362 self.log.error('Unknown front-end to back-end widget msg with method "%s"' % method)
363 363
364 364 def _handle_custom_msg(self, content):
365 365 """Called when a custom msg is received."""
366 366 self._msg_callbacks(self, content)
367 367
368 368 def _notify_trait(self, name, old_value, new_value):
369 369 """Called when a property has been changed."""
370 370 # Trigger default traitlet callback machinery. This allows any user
371 371 # registered validation to be processed prior to allowing the widget
372 372 # machinery to handle the state.
373 373 LoggingConfigurable._notify_trait(self, name, old_value, new_value)
374 374
375 375 # Send the state after the user registered callbacks for trait changes
376 376 # have all fired (allows for user to validate values).
377 377 if self.comm is not None and name in self.keys:
378 378 # Make sure this isn't information that the front-end just sent us.
379 379 if self._should_send_property(name, new_value):
380 380 # Send new state to front-end
381 381 self.send_state(key=name)
382 382
383 383 def _handle_displayed(self, **kwargs):
384 384 """Called when a view has been displayed for this widget instance"""
385 385 self._display_callbacks(self, **kwargs)
386 386
387 387 def _trait_to_json(self, x):
388 388 """Convert a trait value to json
389 389
390 390 Traverse lists/tuples and dicts and serialize their values as well.
391 391 Replace any widgets with their model_id
392 392 """
393 393 if isinstance(x, dict):
394 394 return {k: self._trait_to_json(v) for k, v in x.items()}
395 395 elif isinstance(x, (list, tuple)):
396 396 return [self._trait_to_json(v) for v in x]
397 397 elif isinstance(x, Widget):
398 398 return "IPY_MODEL_" + x.model_id
399 399 else:
400 400 return x # Value must be JSON-able
401 401
402 402 def _trait_from_json(self, x):
403 403 """Convert json values to objects
404 404
405 405 Replace any strings representing valid model id values to Widget references.
406 406 """
407 407 if isinstance(x, dict):
408 408 return {k: self._trait_from_json(v) for k, v in x.items()}
409 409 elif isinstance(x, (list, tuple)):
410 410 return [self._trait_from_json(v) for v in x]
411 411 elif isinstance(x, string_types) and x.startswith('IPY_MODEL_') and x[10:] in Widget.widgets:
412 412 # we want to support having child widgets at any level in a hierarchy
413 413 # trusting that a widget UUID will not appear out in the wild
414 414 return Widget.widgets[x[10:]]
415 415 else:
416 416 return x
417 417
418 418 def _ipython_display_(self, **kwargs):
419 419 """Called when `IPython.display.display` is called on the widget."""
420 420 # Show view.
421 421 if self._view_name is not None:
422 422 self._send({"method": "display"})
423 423 self._handle_displayed(**kwargs)
424 424
425 425 def _send(self, msg):
426 426 """Sends a message to the model in the front-end."""
427 427 self.comm.send(msg)
428 428
429 429
430 430 class DOMWidget(Widget):
431 431 visible = Bool(True, allow_none=True, help="Whether the widget is visible. False collapses the empty space, while None preserves the empty space.", sync=True)
432 432 _css = Tuple(sync=True, help="CSS property list: (selector, key, value)")
433 433 _dom_classes = Tuple(sync=True, help="DOM classes applied to widget.$el.")
434 434
435 435 width = CUnicode(sync=True)
436 436 height = CUnicode(sync=True)
437 437 # A default padding of 2.5 px makes the widgets look nice when displayed inline.
438 438 padding = CUnicode(sync=True)
439 439 margin = CUnicode(sync=True)
440 440
441 color = Unicode(sync=True)
442 background_color = Unicode(sync=True)
443 border_color = Unicode(sync=True)
441 color = Color(None, allow_none=True, sync=True)
442 background_color = Color(None, allow_none=True, sync=True)
443 border_color = Color(None, allow_none=True, sync=True)
444 444
445 445 border_width = CUnicode(sync=True)
446 446 border_radius = CUnicode(sync=True)
447 447 border_style = CaselessStrEnum(values=[ # http://www.w3schools.com/cssref/pr_border-style.asp
448 448 'none',
449 449 'hidden',
450 450 'dotted',
451 451 'dashed',
452 452 'solid',
453 453 'double',
454 454 'groove',
455 455 'ridge',
456 456 'inset',
457 457 'outset',
458 458 'initial',
459 459 'inherit', ''],
460 460 default_value='', sync=True)
461 461
462 462 font_style = CaselessStrEnum(values=[ # http://www.w3schools.com/cssref/pr_font_font-style.asp
463 463 'normal',
464 464 'italic',
465 465 'oblique',
466 466 'initial',
467 467 'inherit', ''],
468 468 default_value='', sync=True)
469 469 font_weight = CaselessStrEnum(values=[ # http://www.w3schools.com/cssref/pr_font_weight.asp
470 470 'normal',
471 471 'bold',
472 472 'bolder',
473 473 'lighter',
474 474 'initial',
475 475 'inherit', ''] + list(map(str, range(100,1000,100))),
476 476 default_value='', sync=True)
477 477 font_size = CUnicode(sync=True)
478 478 font_family = Unicode(sync=True)
479 479
480 480 def __init__(self, *pargs, **kwargs):
481 481 super(DOMWidget, self).__init__(*pargs, **kwargs)
482 482
483 483 def _validate_border(name, old, new):
484 484 if new is not None and new != '':
485 485 if name != 'border_width' and not self.border_width:
486 486 self.border_width = 1
487 487 if name != 'border_style' and self.border_style == '':
488 488 self.border_style = 'solid'
489 489 self.on_trait_change(_validate_border, ['border_width', 'border_style', 'border_color'])
@@ -1,296 +1,296
1 1 """Float class.
2 2
3 3 Represents an unbounded float using a widget.
4 4 """
5 5 #-----------------------------------------------------------------------------
6 6 # Copyright (c) 2013, the IPython Development Team.
7 7 #
8 8 # Distributed under the terms of the Modified BSD License.
9 9 #
10 10 # The full license is in the file COPYING.txt, distributed with this software.
11 11 #-----------------------------------------------------------------------------
12 12
13 13 #-----------------------------------------------------------------------------
14 14 # Imports
15 15 #-----------------------------------------------------------------------------
16 16 from .widget import DOMWidget, register
17 from IPython.utils.traitlets import Unicode, CFloat, Bool, CaselessStrEnum, Tuple
17 from IPython.utils.traitlets import Unicode, CFloat, Bool, CaselessStrEnum, Tuple, Color
18 18 from IPython.utils.warn import DeprecatedClass
19 19
20 20 #-----------------------------------------------------------------------------
21 21 # Classes
22 22 #-----------------------------------------------------------------------------
23 23 class _Float(DOMWidget):
24 24 value = CFloat(0.0, help="Float value", sync=True)
25 25 disabled = Bool(False, help="Enable or disable user changes", sync=True)
26 26 description = Unicode(help="Description of the value this widget represents", sync=True)
27 27
28 28 def __init__(self, value=None, **kwargs):
29 29 if value is not None:
30 30 kwargs['value'] = value
31 31 super(_Float, self).__init__(**kwargs)
32 32
33 33 class _BoundedFloat(_Float):
34 34 max = CFloat(100.0, help="Max value", sync=True)
35 35 min = CFloat(0.0, help="Min value", sync=True)
36 36 step = CFloat(0.1, help="Minimum step that the value can take (ignored by some views)", sync=True)
37 37
38 38 def __init__(self, *pargs, **kwargs):
39 39 """Constructor"""
40 40 super(_BoundedFloat, self).__init__(*pargs, **kwargs)
41 41 self._handle_value_changed('value', None, self.value)
42 42 self._handle_max_changed('max', None, self.max)
43 43 self._handle_min_changed('min', None, self.min)
44 44 self.on_trait_change(self._handle_value_changed, 'value')
45 45 self.on_trait_change(self._handle_max_changed, 'max')
46 46 self.on_trait_change(self._handle_min_changed, 'min')
47 47
48 48 def _handle_value_changed(self, name, old, new):
49 49 """Validate value."""
50 50 if self.min > new or new > self.max:
51 51 self.value = min(max(new, self.min), self.max)
52 52
53 53 def _handle_max_changed(self, name, old, new):
54 54 """Make sure the min is always <= the max."""
55 55 if new < self.min:
56 56 raise ValueError("setting max < min")
57 57 if new < self.value:
58 58 self.value = new
59 59
60 60 def _handle_min_changed(self, name, old, new):
61 61 """Make sure the max is always >= the min."""
62 62 if new > self.max:
63 63 raise ValueError("setting min > max")
64 64 if new > self.value:
65 65 self.value = new
66 66
67 67
68 68 @register('IPython.FloatText')
69 69 class FloatText(_Float):
70 70 """ Displays a float value within a textbox. For a textbox in
71 71 which the value must be within a specific range, use BoundedFloatText.
72 72
73 73 Parameters
74 74 ----------
75 75 value : float
76 76 value displayed
77 77 description : str
78 78 description displayed next to the textbox
79 79 color : str Unicode color code (eg. '#C13535'), optional
80 80 color of the value displayed
81 81 """
82 82 _view_name = Unicode('FloatTextView', sync=True)
83 83
84 84
85 85 @register('IPython.BoundedFloatText')
86 86 class BoundedFloatText(_BoundedFloat):
87 87 """ Displays a float value within a textbox. Value must be within the range specified.
88 88 For a textbox in which the value doesn't need to be within a specific range, use FloatText.
89 89
90 90 Parameters
91 91 ----------
92 92 value : float
93 93 value displayed
94 94 min : float
95 95 minimal value of the range of possible values displayed
96 96 max : float
97 97 maximal value of the range of possible values displayed
98 98 description : str
99 99 description displayed next to the textbox
100 100 color : str Unicode color code (eg. '#C13535'), optional
101 101 color of the value displayed
102 102 """
103 103 _view_name = Unicode('FloatTextView', sync=True)
104 104
105 105
106 106 @register('IPython.FloatSlider')
107 107 class FloatSlider(_BoundedFloat):
108 108 """ Slider/trackbar of floating values with the specified range.
109 109
110 110 Parameters
111 111 ----------
112 112 value : float
113 113 position of the slider
114 114 min : float
115 115 minimal position of the slider
116 116 max : float
117 117 maximal position of the slider
118 118 step : float
119 119 step of the trackbar
120 120 description : str
121 121 name of the slider
122 122 orientation : {'vertical', 'horizontal}, optional
123 123 default is horizontal
124 124 readout : {True, False}, optional
125 125 default is True, display the current value of the slider next to it
126 126 slider_color : str Unicode color code (eg. '#C13535'), optional
127 127 color of the slider
128 128 color : str Unicode color code (eg. '#C13535'), optional
129 129 color of the value displayed (if readout == True)
130 130 """
131 131 _view_name = Unicode('FloatSliderView', sync=True)
132 132 orientation = CaselessStrEnum(values=['horizontal', 'vertical'],
133 133 default_value='horizontal', help="Vertical or horizontal.", sync=True)
134 134 _range = Bool(False, help="Display a range selector", sync=True)
135 135 readout = Bool(True, help="Display the current value of the slider next to it.", sync=True)
136 slider_color = Unicode(sync=True)
136 slider_color = Color(None, allow_none=True, sync=True)
137 137
138 138
139 139 @register('IPython.FloatProgress')
140 140 class FloatProgress(_BoundedFloat):
141 141 """ Displays a progress bar.
142 142
143 143 Parameters
144 144 -----------
145 145 value : float
146 146 position within the range of the progress bar
147 147 min : float
148 148 minimal position of the slider
149 149 max : float
150 150 maximal position of the slider
151 151 step : float
152 152 step of the progress bar
153 153 description : str
154 154 name of the progress bar
155 155 bar_style: {'success', 'info', 'warning', 'danger', ''}, optional
156 156 color of the progress bar, default is '' (blue)
157 157 colors are: 'success'-green, 'info'-light blue, 'warning'-orange, 'danger'-red
158 158 """
159 159 _view_name = Unicode('ProgressView', sync=True)
160 160
161 161 bar_style = CaselessStrEnum(
162 162 values=['success', 'info', 'warning', 'danger', ''],
163 163 default_value='', allow_none=True, sync=True, help="""Use a
164 164 predefined styling for the progess bar.""")
165 165
166 166 class _FloatRange(_Float):
167 167 value = Tuple(CFloat, CFloat, default_value=(0.0, 1.0), help="Tuple of (lower, upper) bounds", sync=True)
168 168 lower = CFloat(0.0, help="Lower bound", sync=False)
169 169 upper = CFloat(1.0, help="Upper bound", sync=False)
170 170
171 171 def __init__(self, *pargs, **kwargs):
172 172 value_given = 'value' in kwargs
173 173 lower_given = 'lower' in kwargs
174 174 upper_given = 'upper' in kwargs
175 175 if value_given and (lower_given or upper_given):
176 176 raise ValueError("Cannot specify both 'value' and 'lower'/'upper' for range widget")
177 177 if lower_given != upper_given:
178 178 raise ValueError("Must specify both 'lower' and 'upper' for range widget")
179 179
180 180 DOMWidget.__init__(self, *pargs, **kwargs)
181 181
182 182 # ensure the traits match, preferring whichever (if any) was given in kwargs
183 183 if value_given:
184 184 self.lower, self.upper = self.value
185 185 else:
186 186 self.value = (self.lower, self.upper)
187 187
188 188 self.on_trait_change(self._validate, ['value', 'upper', 'lower'])
189 189
190 190 def _validate(self, name, old, new):
191 191 if name == 'value':
192 192 self.lower, self.upper = min(new), max(new)
193 193 elif name == 'lower':
194 194 self.value = (new, self.value[1])
195 195 elif name == 'upper':
196 196 self.value = (self.value[0], new)
197 197
198 198 class _BoundedFloatRange(_FloatRange):
199 199 step = CFloat(1.0, help="Minimum step that the value can take (ignored by some views)", sync=True)
200 200 max = CFloat(100.0, help="Max value", sync=True)
201 201 min = CFloat(0.0, help="Min value", sync=True)
202 202
203 203 def __init__(self, *pargs, **kwargs):
204 204 any_value_given = 'value' in kwargs or 'upper' in kwargs or 'lower' in kwargs
205 205 _FloatRange.__init__(self, *pargs, **kwargs)
206 206
207 207 # ensure a minimal amount of sanity
208 208 if self.min > self.max:
209 209 raise ValueError("min must be <= max")
210 210
211 211 if any_value_given:
212 212 # if a value was given, clamp it within (min, max)
213 213 self._validate("value", None, self.value)
214 214 else:
215 215 # otherwise, set it to 25-75% to avoid the handles overlapping
216 216 self.value = (0.75*self.min + 0.25*self.max,
217 217 0.25*self.min + 0.75*self.max)
218 218 # callback already set for 'value', 'lower', 'upper'
219 219 self.on_trait_change(self._validate, ['min', 'max'])
220 220
221 221
222 222 def _validate(self, name, old, new):
223 223 if name == "min":
224 224 if new > self.max:
225 225 raise ValueError("setting min > max")
226 226 self.min = new
227 227 elif name == "max":
228 228 if new < self.min:
229 229 raise ValueError("setting max < min")
230 230 self.max = new
231 231
232 232 low, high = self.value
233 233 if name == "value":
234 234 low, high = min(new), max(new)
235 235 elif name == "upper":
236 236 if new < self.lower:
237 237 raise ValueError("setting upper < lower")
238 238 high = new
239 239 elif name == "lower":
240 240 if new > self.upper:
241 241 raise ValueError("setting lower > upper")
242 242 low = new
243 243
244 244 low = max(self.min, min(low, self.max))
245 245 high = min(self.max, max(high, self.min))
246 246
247 247 # determine the order in which we should update the
248 248 # lower, upper traits to avoid a temporary inverted overlap
249 249 lower_first = high < self.lower
250 250
251 251 self.value = (low, high)
252 252 if lower_first:
253 253 self.lower = low
254 254 self.upper = high
255 255 else:
256 256 self.upper = high
257 257 self.lower = low
258 258
259 259
260 260 @register('IPython.FloatRangeSlider')
261 261 class FloatRangeSlider(_BoundedFloatRange):
262 262 """ Slider/trackbar for displaying a floating value range (within the specified range of values).
263 263
264 264 Parameters
265 265 ----------
266 266 value : float tuple
267 267 range of the slider displayed
268 268 min : float
269 269 minimal position of the slider
270 270 max : float
271 271 maximal position of the slider
272 272 step : float
273 273 step of the trackbar
274 274 description : str
275 275 name of the slider
276 276 orientation : {'vertical', 'horizontal}, optional
277 277 default is horizontal
278 278 readout : {True, False}, optional
279 279 default is True, display the current value of the slider next to it
280 280 slider_color : str Unicode color code (eg. '#C13535'), optional
281 281 color of the slider
282 282 color : str Unicode color code (eg. '#C13535'), optional
283 283 color of the value displayed (if readout == True)
284 284 """
285 285 _view_name = Unicode('FloatSliderView', sync=True)
286 286 orientation = CaselessStrEnum(values=['horizontal', 'vertical'],
287 287 default_value='horizontal', help="Vertical or horizontal.", sync=True)
288 288 _range = Bool(True, help="Display a range selector", sync=True)
289 289 readout = Bool(True, help="Display the current value of the slider next to it.", sync=True)
290 slider_color = Unicode(sync=True)
290 slider_color = Color(None, allow_none=True, sync=True)
291 291
292 292 # Remove in IPython 4.0
293 293 FloatTextWidget = DeprecatedClass(FloatText, 'FloatTextWidget')
294 294 BoundedFloatTextWidget = DeprecatedClass(BoundedFloatText, 'BoundedFloatTextWidget')
295 295 FloatSliderWidget = DeprecatedClass(FloatSlider, 'FloatSliderWidget')
296 296 FloatProgressWidget = DeprecatedClass(FloatProgress, 'FloatProgressWidget')
@@ -1,207 +1,207
1 1 """Int class.
2 2
3 3 Represents an unbounded int using a widget.
4 4 """
5 5 #-----------------------------------------------------------------------------
6 6 # Copyright (c) 2013, the IPython Development Team.
7 7 #
8 8 # Distributed under the terms of the Modified BSD License.
9 9 #
10 10 # The full license is in the file COPYING.txt, distributed with this software.
11 11 #-----------------------------------------------------------------------------
12 12
13 13 #-----------------------------------------------------------------------------
14 14 # Imports
15 15 #-----------------------------------------------------------------------------
16 16 from .widget import DOMWidget, register
17 from IPython.utils.traitlets import Unicode, CInt, Bool, CaselessStrEnum, Tuple
17 from IPython.utils.traitlets import Unicode, CInt, Bool, CaselessStrEnum, Tuple, Color
18 18 from IPython.utils.warn import DeprecatedClass
19 19
20 20 #-----------------------------------------------------------------------------
21 21 # Classes
22 22 #-----------------------------------------------------------------------------
23 23 class _Int(DOMWidget):
24 24 """Base class used to create widgets that represent an int."""
25 25 value = CInt(0, help="Int value", sync=True)
26 26 disabled = Bool(False, help="Enable or disable user changes", sync=True)
27 27 description = Unicode(help="Description of the value this widget represents", sync=True)
28 28
29 29 def __init__(self, value=None, **kwargs):
30 30 if value is not None:
31 31 kwargs['value'] = value
32 32 super(_Int, self).__init__(**kwargs)
33 33
34 34 class _BoundedInt(_Int):
35 35 """Base class used to create widgets that represent a int that is bounded
36 36 by a minium and maximum."""
37 37 step = CInt(1, help="Minimum step that the value can take (ignored by some views)", sync=True)
38 38 max = CInt(100, help="Max value", sync=True)
39 39 min = CInt(0, help="Min value", sync=True)
40 40
41 41 def __init__(self, *pargs, **kwargs):
42 42 """Constructor"""
43 43 super(_BoundedInt, self).__init__(*pargs, **kwargs)
44 44 self._handle_value_changed('value', None, self.value)
45 45 self._handle_max_changed('max', None, self.max)
46 46 self._handle_min_changed('min', None, self.min)
47 47 self.on_trait_change(self._handle_value_changed, 'value')
48 48 self.on_trait_change(self._handle_max_changed, 'max')
49 49 self.on_trait_change(self._handle_min_changed, 'min')
50 50
51 51 def _handle_value_changed(self, name, old, new):
52 52 """Validate value."""
53 53 if self.min > new or new > self.max:
54 54 self.value = min(max(new, self.min), self.max)
55 55
56 56 def _handle_max_changed(self, name, old, new):
57 57 """Make sure the min is always <= the max."""
58 58 if new < self.min:
59 59 raise ValueError("setting max < min")
60 60 if new < self.value:
61 61 self.value = new
62 62
63 63 def _handle_min_changed(self, name, old, new):
64 64 """Make sure the max is always >= the min."""
65 65 if new > self.max:
66 66 raise ValueError("setting min > max")
67 67 if new > self.value:
68 68 self.value = new
69 69
70 70 @register('IPython.IntText')
71 71 class IntText(_Int):
72 72 """Textbox widget that represents a int."""
73 73 _view_name = Unicode('IntTextView', sync=True)
74 74
75 75
76 76 @register('IPython.BoundedIntText')
77 77 class BoundedIntText(_BoundedInt):
78 78 """Textbox widget that represents a int bounded by a minimum and maximum value."""
79 79 _view_name = Unicode('IntTextView', sync=True)
80 80
81 81
82 82 @register('IPython.IntSlider')
83 83 class IntSlider(_BoundedInt):
84 84 """Slider widget that represents a int bounded by a minimum and maximum value."""
85 85 _view_name = Unicode('IntSliderView', sync=True)
86 86 orientation = CaselessStrEnum(values=['horizontal', 'vertical'],
87 87 default_value='horizontal', help="Vertical or horizontal.", sync=True)
88 88 _range = Bool(False, help="Display a range selector", sync=True)
89 89 readout = Bool(True, help="Display the current value of the slider next to it.", sync=True)
90 slider_color = Unicode(sync=True)
90 slider_color = Color(None, allow_none=True, sync=True)
91 91
92 92
93 93 @register('IPython.IntProgress')
94 94 class IntProgress(_BoundedInt):
95 95 """Progress bar that represents a int bounded by a minimum and maximum value."""
96 96 _view_name = Unicode('ProgressView', sync=True)
97 97
98 98 bar_style = CaselessStrEnum(
99 99 values=['success', 'info', 'warning', 'danger', ''],
100 100 default_value='', allow_none=True, sync=True, help="""Use a
101 101 predefined styling for the progess bar.""")
102 102
103 103 class _IntRange(_Int):
104 104 value = Tuple(CInt, CInt, default_value=(0, 1), help="Tuple of (lower, upper) bounds", sync=True)
105 105 lower = CInt(0, help="Lower bound", sync=False)
106 106 upper = CInt(1, help="Upper bound", sync=False)
107 107
108 108 def __init__(self, *pargs, **kwargs):
109 109 value_given = 'value' in kwargs
110 110 lower_given = 'lower' in kwargs
111 111 upper_given = 'upper' in kwargs
112 112 if value_given and (lower_given or upper_given):
113 113 raise ValueError("Cannot specify both 'value' and 'lower'/'upper' for range widget")
114 114 if lower_given != upper_given:
115 115 raise ValueError("Must specify both 'lower' and 'upper' for range widget")
116 116
117 117 super(_IntRange, self).__init__(*pargs, **kwargs)
118 118
119 119 # ensure the traits match, preferring whichever (if any) was given in kwargs
120 120 if value_given:
121 121 self.lower, self.upper = self.value
122 122 else:
123 123 self.value = (self.lower, self.upper)
124 124
125 125 self.on_trait_change(self._validate, ['value', 'upper', 'lower'])
126 126
127 127 def _validate(self, name, old, new):
128 128 if name == 'value':
129 129 self.lower, self.upper = min(new), max(new)
130 130 elif name == 'lower':
131 131 self.value = (new, self.value[1])
132 132 elif name == 'upper':
133 133 self.value = (self.value[0], new)
134 134
135 135 class _BoundedIntRange(_IntRange):
136 136 step = CInt(1, help="Minimum step that the value can take (ignored by some views)", sync=True)
137 137 max = CInt(100, help="Max value", sync=True)
138 138 min = CInt(0, help="Min value", sync=True)
139 139
140 140 def __init__(self, *pargs, **kwargs):
141 141 any_value_given = 'value' in kwargs or 'upper' in kwargs or 'lower' in kwargs
142 142 _IntRange.__init__(self, *pargs, **kwargs)
143 143
144 144 # ensure a minimal amount of sanity
145 145 if self.min > self.max:
146 146 raise ValueError("min must be <= max")
147 147
148 148 if any_value_given:
149 149 # if a value was given, clamp it within (min, max)
150 150 self._validate("value", None, self.value)
151 151 else:
152 152 # otherwise, set it to 25-75% to avoid the handles overlapping
153 153 self.value = (0.75*self.min + 0.25*self.max,
154 154 0.25*self.min + 0.75*self.max)
155 155 # callback already set for 'value', 'lower', 'upper'
156 156 self.on_trait_change(self._validate, ['min', 'max'])
157 157
158 158 def _validate(self, name, old, new):
159 159 if name == "min":
160 160 if new > self.max:
161 161 raise ValueError("setting min > max")
162 162 elif name == "max":
163 163 if new < self.min:
164 164 raise ValueError("setting max < min")
165 165
166 166 low, high = self.value
167 167 if name == "value":
168 168 low, high = min(new), max(new)
169 169 elif name == "upper":
170 170 if new < self.lower:
171 171 raise ValueError("setting upper < lower")
172 172 high = new
173 173 elif name == "lower":
174 174 if new > self.upper:
175 175 raise ValueError("setting lower > upper")
176 176 low = new
177 177
178 178 low = max(self.min, min(low, self.max))
179 179 high = min(self.max, max(high, self.min))
180 180
181 181 # determine the order in which we should update the
182 182 # lower, upper traits to avoid a temporary inverted overlap
183 183 lower_first = high < self.lower
184 184
185 185 self.value = (low, high)
186 186 if lower_first:
187 187 self.lower = low
188 188 self.upper = high
189 189 else:
190 190 self.upper = high
191 191 self.lower = low
192 192
193 193 @register('IPython.IntRangeSlider')
194 194 class IntRangeSlider(_BoundedIntRange):
195 195 """Slider widget that represents a pair of ints between a minimum and maximum value."""
196 196 _view_name = Unicode('IntSliderView', sync=True)
197 197 orientation = CaselessStrEnum(values=['horizontal', 'vertical'],
198 198 default_value='horizontal', help="Vertical or horizontal.", sync=True)
199 199 _range = Bool(True, help="Display a range selector", sync=True)
200 200 readout = Bool(True, help="Display the current value of the slider next to it.", sync=True)
201 slider_color = Unicode(sync=True)
201 slider_color = Color(None, allow_none=True, sync=True)
202 202
203 203 # Remove in IPython 4.0
204 204 IntTextWidget = DeprecatedClass(IntText, 'IntTextWidget')
205 205 BoundedIntTextWidget = DeprecatedClass(BoundedIntText, 'BoundedIntTextWidget')
206 206 IntSliderWidget = DeprecatedClass(IntSlider, 'IntSliderWidget')
207 207 IntProgressWidget = DeprecatedClass(IntProgress, 'IntProgressWidget')
General Comments 0
You need to be logged in to leave comments. Login now