##// END OF EJS Templates
jupyter -> IPython
Sylvain Corlay -
Show More
@@ -1,480 +1,480
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 23 CaselessStrEnum, Tuple, CUnicode, Int, Set
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 provided for each core Jupyter widget so that the frontend can use
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(allow_none=False)
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 self.comm = new
179 179 self._model_id = self.model_id
180 180
181 181 self.comm.on_msg(self._handle_msg)
182 182 Widget.widgets[self.model_id] = self
183 183
184 184 # first update
185 185 self.send_state()
186 186
187 187 @property
188 188 def model_id(self):
189 189 """Gets the model id of this widget.
190 190
191 191 If a Comm doesn't exist yet, a Comm will be created automagically."""
192 192 return self.comm.comm_id
193 193
194 194 #-------------------------------------------------------------------------
195 195 # Methods
196 196 #-------------------------------------------------------------------------
197 197
198 198 def close(self):
199 199 """Close method.
200 200
201 201 Closes the underlying comm.
202 202 When the comm is closed, all of the widget views are automatically
203 203 removed from the front-end."""
204 204 if self.comm is not None:
205 205 Widget.widgets.pop(self.model_id, None)
206 206 self.comm.close()
207 207 self.comm = None
208 208
209 209 def send_state(self, key=None):
210 210 """Sends the widget state, or a piece of it, to the front-end.
211 211
212 212 Parameters
213 213 ----------
214 214 key : unicode, or iterable (optional)
215 215 A single property's name or iterable of property names to sync with the front-end.
216 216 """
217 217 self._send({
218 218 "method" : "update",
219 219 "state" : self.get_state(key=key)
220 220 })
221 221
222 222 def get_state(self, key=None):
223 223 """Gets the widget state, or a piece of it.
224 224
225 225 Parameters
226 226 ----------
227 227 key : unicode or iterable (optional)
228 228 A single property's name or iterable of property names to get.
229 229 """
230 230 if key is None:
231 231 keys = self.keys
232 232 elif isinstance(key, string_types):
233 233 keys = [key]
234 234 elif isinstance(key, collections.Iterable):
235 235 keys = key
236 236 else:
237 237 raise ValueError("key must be a string, an iterable of keys, or None")
238 238 state = {}
239 239 for k in keys:
240 240 f = self.trait_metadata(k, 'to_json', self._trait_to_json)
241 241 value = getattr(self, k)
242 242 state[k] = f(value)
243 243 return state
244 244
245 245 def set_state(self, sync_data):
246 246 """Called when a state is received from the front-end."""
247 247 for name in self.keys:
248 248 if name in sync_data:
249 249 json_value = sync_data[name]
250 250 from_json = self.trait_metadata(name, 'from_json', self._trait_from_json)
251 251 with self._lock_property(name, json_value):
252 252 setattr(self, name, from_json(json_value))
253 253
254 254 def send(self, content):
255 255 """Sends a custom msg to the widget model in the front-end.
256 256
257 257 Parameters
258 258 ----------
259 259 content : dict
260 260 Content of the message to send.
261 261 """
262 262 self._send({"method": "custom", "content": content})
263 263
264 264 def on_msg(self, callback, remove=False):
265 265 """(Un)Register a custom msg receive callback.
266 266
267 267 Parameters
268 268 ----------
269 269 callback: callable
270 270 callback will be passed two arguments when a message arrives::
271 271
272 272 callback(widget, content)
273 273
274 274 remove: bool
275 275 True if the callback should be unregistered."""
276 276 self._msg_callbacks.register_callback(callback, remove=remove)
277 277
278 278 def on_displayed(self, callback, remove=False):
279 279 """(Un)Register a widget displayed callback.
280 280
281 281 Parameters
282 282 ----------
283 283 callback: method handler
284 284 Must have a signature of::
285 285
286 286 callback(widget, **kwargs)
287 287
288 288 kwargs from display are passed through without modification.
289 289 remove: bool
290 290 True if the callback should be unregistered."""
291 291 self._display_callbacks.register_callback(callback, remove=remove)
292 292
293 293 #-------------------------------------------------------------------------
294 294 # Support methods
295 295 #-------------------------------------------------------------------------
296 296 @contextmanager
297 297 def _lock_property(self, key, value):
298 298 """Lock a property-value pair.
299 299
300 300 The value should be the JSON state of the property.
301 301
302 302 NOTE: This, in addition to the single lock for all state changes, is
303 303 flawed. In the future we may want to look into buffering state changes
304 304 back to the front-end."""
305 305 self._property_lock = (key, value)
306 306 try:
307 307 yield
308 308 finally:
309 309 self._property_lock = (None, None)
310 310
311 311 @contextmanager
312 312 def hold_sync(self):
313 313 """Hold syncing any state until the context manager is released"""
314 314 # We increment a value so that this can be nested. Syncing will happen when
315 315 # all levels have been released.
316 316 self._send_state_lock += 1
317 317 try:
318 318 yield
319 319 finally:
320 320 self._send_state_lock -=1
321 321 if self._send_state_lock == 0:
322 322 self.send_state(self._states_to_send)
323 323 self._states_to_send.clear()
324 324
325 325 def _should_send_property(self, key, value):
326 326 """Check the property lock (property_lock)"""
327 327 to_json = self.trait_metadata(key, 'to_json', self._trait_to_json)
328 328 if (key == self._property_lock[0]
329 329 and to_json(value) == self._property_lock[1]):
330 330 return False
331 331 elif self._send_state_lock > 0:
332 332 self._states_to_send.add(key)
333 333 return False
334 334 else:
335 335 return True
336 336
337 337 # Event handlers
338 338 @_show_traceback
339 339 def _handle_msg(self, msg):
340 340 """Called when a msg is received from the front-end"""
341 341 data = msg['content']['data']
342 342 method = data['method']
343 343 if not method in ['backbone', 'custom']:
344 344 self.log.error('Unknown front-end to back-end widget msg with method "%s"' % method)
345 345
346 346 # Handle backbone sync methods CREATE, PATCH, and UPDATE all in one.
347 347 if method == 'backbone' and '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 custom msg from the front-end
352 352 elif method == 'custom':
353 353 if 'content' in data:
354 354 self._handle_custom_msg(data['content'])
355 355
356 356 def _handle_custom_msg(self, content):
357 357 """Called when a custom msg is received."""
358 358 self._msg_callbacks(self, content)
359 359
360 360 def _notify_trait(self, name, old_value, new_value):
361 361 """Called when a property has been changed."""
362 362 # Trigger default traitlet callback machinery. This allows any user
363 363 # registered validation to be processed prior to allowing the widget
364 364 # machinery to handle the state.
365 365 LoggingConfigurable._notify_trait(self, name, old_value, new_value)
366 366
367 367 # Send the state after the user registered callbacks for trait changes
368 368 # have all fired (allows for user to validate values).
369 369 if self.comm is not None and name in self.keys:
370 370 # Make sure this isn't information that the front-end just sent us.
371 371 if self._should_send_property(name, new_value):
372 372 # Send new state to front-end
373 373 self.send_state(key=name)
374 374
375 375 def _handle_displayed(self, **kwargs):
376 376 """Called when a view has been displayed for this widget instance"""
377 377 self._display_callbacks(self, **kwargs)
378 378
379 379 def _trait_to_json(self, x):
380 380 """Convert a trait value to json
381 381
382 382 Traverse lists/tuples and dicts and serialize their values as well.
383 383 Replace any widgets with their model_id
384 384 """
385 385 if isinstance(x, dict):
386 386 return {k: self._trait_to_json(v) for k, v in x.items()}
387 387 elif isinstance(x, (list, tuple)):
388 388 return [self._trait_to_json(v) for v in x]
389 389 elif isinstance(x, Widget):
390 390 return "IPY_MODEL_" + x.model_id
391 391 else:
392 392 return x # Value must be JSON-able
393 393
394 394 def _trait_from_json(self, x):
395 395 """Convert json values to objects
396 396
397 397 Replace any strings representing valid model id values to Widget references.
398 398 """
399 399 if isinstance(x, dict):
400 400 return {k: self._trait_from_json(v) for k, v in x.items()}
401 401 elif isinstance(x, (list, tuple)):
402 402 return [self._trait_from_json(v) for v in x]
403 403 elif isinstance(x, string_types) and x.startswith('IPY_MODEL_') and x[10:] in Widget.widgets:
404 404 # we want to support having child widgets at any level in a hierarchy
405 405 # trusting that a widget UUID will not appear out in the wild
406 406 return Widget.widgets[x[10:]]
407 407 else:
408 408 return x
409 409
410 410 def _ipython_display_(self, **kwargs):
411 411 """Called when `IPython.display.display` is called on the widget."""
412 412 # Show view.
413 413 if self._view_name is not None:
414 414 self._send({"method": "display"})
415 415 self._handle_displayed(**kwargs)
416 416
417 417 def _send(self, msg):
418 418 """Sends a message to the model in the front-end."""
419 419 self.comm.send(msg)
420 420
421 421
422 422 class DOMWidget(Widget):
423 423 visible = Bool(True, help="Whether the widget is visible.", sync=True)
424 424 _css = Tuple(sync=True, help="CSS property list: (selector, key, value)")
425 425 _dom_classes = Tuple(sync=True, help="DOM classes applied to widget.$el.")
426 426
427 427 width = CUnicode(sync=True)
428 428 height = CUnicode(sync=True)
429 429 padding = CUnicode(sync=True)
430 430 margin = CUnicode(sync=True)
431 431
432 432 color = Unicode(sync=True)
433 433 background_color = Unicode(sync=True)
434 434 border_color = Unicode(sync=True)
435 435
436 436 border_width = CUnicode(sync=True)
437 437 border_radius = CUnicode(sync=True)
438 438 border_style = CaselessStrEnum(values=[ # http://www.w3schools.com/cssref/pr_border-style.asp
439 439 'none',
440 440 'hidden',
441 441 'dotted',
442 442 'dashed',
443 443 'solid',
444 444 'double',
445 445 'groove',
446 446 'ridge',
447 447 'inset',
448 448 'outset',
449 449 'initial',
450 450 'inherit', ''],
451 451 default_value='', sync=True)
452 452
453 453 font_style = CaselessStrEnum(values=[ # http://www.w3schools.com/cssref/pr_font_font-style.asp
454 454 'normal',
455 455 'italic',
456 456 'oblique',
457 457 'initial',
458 458 'inherit', ''],
459 459 default_value='', sync=True)
460 460 font_weight = CaselessStrEnum(values=[ # http://www.w3schools.com/cssref/pr_font_weight.asp
461 461 'normal',
462 462 'bold',
463 463 'bolder',
464 464 'lighter',
465 465 'initial',
466 466 'inherit', ''] + [str(100 * (i+1)) for i in range(9)],
467 467 default_value='', sync=True)
468 468 font_size = CUnicode(sync=True)
469 469 font_family = Unicode(sync=True)
470 470
471 471 def __init__(self, *pargs, **kwargs):
472 472 super(DOMWidget, self).__init__(*pargs, **kwargs)
473 473
474 474 def _validate_border(name, old, new):
475 475 if new is not None and new != '':
476 476 if name != 'border_width' and not self.border_width:
477 477 self.border_width = 1
478 478 if name != 'border_style' and self.border_style == '':
479 479 self.border_style = 'solid'
480 480 self.on_trait_change(_validate_border, ['border_width', 'border_style', 'border_color'])
@@ -1,50 +1,50
1 1 """Bool class.
2 2
3 3 Represents a boolean 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 17 from IPython.utils.traitlets import Unicode, Bool, CaselessStrEnum
18 18 from IPython.utils.warn import DeprecatedClass
19 19
20 20 #-----------------------------------------------------------------------------
21 21 # Classes
22 22 #-----------------------------------------------------------------------------
23 23 class _Bool(DOMWidget):
24 24 """A base class for creating widgets that represent booleans."""
25 25 value = Bool(False, help="Bool value", sync=True)
26 26 description = Unicode('', help="Description of the boolean (label).", sync=True)
27 27 disabled = Bool(False, help="Enable or disable user changes.", sync=True)
28 28
29 29
30 @register('jupyter.Checkbox')
30 @register('IPython.Checkbox')
31 31 class Checkbox(_Bool):
32 32 """Displays a boolean `value`."""
33 33 _view_name = Unicode('CheckboxView', sync=True)
34 34
35 35
36 @register('jupyter.ToggleButton')
36 @register('IPython.ToggleButton')
37 37 class ToggleButton(_Bool):
38 38 """Displays a boolean `value`."""
39 39
40 40 _view_name = Unicode('ToggleButtonView', sync=True)
41 41
42 42 button_style = CaselessStrEnum(
43 43 values=['primary', 'success', 'info', 'warning', 'danger', ''],
44 44 default_value='', allow_none=True, sync=True, help="""Use a
45 45 predefined styling for the button.""")
46 46
47 47
48 48 # Remove in IPython 4.0
49 49 CheckboxWidget = DeprecatedClass(Checkbox, 'CheckboxWidget')
50 50 ToggleButtonWidget = DeprecatedClass(ToggleButton, 'ToggleButtonWidget')
@@ -1,91 +1,91
1 1 """Box class.
2 2
3 3 Represents a container that can be used to group other widgets.
4 4 """
5 5
6 6 # Copyright (c) IPython Development Team.
7 7 # Distributed under the terms of the Modified BSD License.
8 8
9 9 from .widget import DOMWidget, register
10 10 from IPython.utils.traitlets import Unicode, Tuple, TraitError, Int, CaselessStrEnum
11 11 from IPython.utils.warn import DeprecatedClass
12 12
13 @register('jupyter.Box')
13 @register('IPython.Box')
14 14 class Box(DOMWidget):
15 15 """Displays multiple widgets in a group."""
16 16 _view_name = Unicode('BoxView', sync=True)
17 17
18 18 # Child widgets in the container.
19 19 # Using a tuple here to force reassignment to update the list.
20 20 # When a proper notifying-list trait exists, that is what should be used here.
21 21 children = Tuple(sync=True, allow_none=False)
22 22
23 23 _overflow_values = ['visible', 'hidden', 'scroll', 'auto', 'initial', 'inherit', '']
24 24 overflow_x = CaselessStrEnum(
25 25 values=_overflow_values,
26 26 default_value='', allow_none=False, sync=True, help="""Specifies what
27 27 happens to content that is too large for the rendered region.""")
28 28 overflow_y = CaselessStrEnum(
29 29 values=_overflow_values,
30 30 default_value='', allow_none=False, sync=True, help="""Specifies what
31 31 happens to content that is too large for the rendered region.""")
32 32
33 33 box_style = CaselessStrEnum(
34 34 values=['success', 'info', 'warning', 'danger', ''],
35 35 default_value='', allow_none=True, sync=True, help="""Use a
36 36 predefined styling for the box.""")
37 37
38 38 def __init__(self, children = (), **kwargs):
39 39 kwargs['children'] = children
40 40 super(Box, self).__init__(**kwargs)
41 41 self.on_displayed(Box._fire_children_displayed)
42 42
43 43 def _fire_children_displayed(self):
44 44 for child in self.children:
45 45 child._handle_displayed()
46 46
47 47
48 @register('jupyter.Popup')
48 @register('IPython.Popup')
49 49 class Popup(Box):
50 50 """Displays multiple widgets in an in page popup div."""
51 51 _view_name = Unicode('PopupView', sync=True)
52 52
53 53 description = Unicode(sync=True)
54 54 button_text = Unicode(sync=True)
55 55
56 56
57 @register('jupyter.FlexBox')
57 @register('IPython.FlexBox')
58 58 class FlexBox(Box):
59 59 """Displays multiple widgets using the flexible box model."""
60 60 _view_name = Unicode('FlexBoxView', sync=True)
61 61 orientation = CaselessStrEnum(values=['vertical', 'horizontal'], default_value='vertical', sync=True)
62 62 flex = Int(0, sync=True, help="""Specify the flexible-ness of the model.""")
63 63 def _flex_changed(self, name, old, new):
64 64 new = min(max(0, new), 2)
65 65 if self.flex != new:
66 66 self.flex = new
67 67
68 68 _locations = ['start', 'center', 'end', 'baseline', 'stretch']
69 69 pack = CaselessStrEnum(
70 70 values=_locations,
71 71 default_value='start', allow_none=False, sync=True)
72 72 align = CaselessStrEnum(
73 73 values=_locations,
74 74 default_value='start', allow_none=False, sync=True)
75 75
76 76
77 77 def VBox(*pargs, **kwargs):
78 78 """Displays multiple widgets vertically using the flexible box model."""
79 79 kwargs['orientation'] = 'vertical'
80 80 return FlexBox(*pargs, **kwargs)
81 81
82 82 def HBox(*pargs, **kwargs):
83 83 """Displays multiple widgets horizontally using the flexible box model."""
84 84 kwargs['orientation'] = 'horizontal'
85 85 return FlexBox(*pargs, **kwargs)
86 86
87 87
88 88 # Remove in IPython 4.0
89 89 ContainerWidget = DeprecatedClass(Box, 'ContainerWidget')
90 90 PopupWidget = DeprecatedClass(Popup, 'PopupWidget')
91 91
@@ -1,72 +1,72
1 1 """Button class.
2 2
3 3 Represents a button in the frontend using a widget. Allows user to listen for
4 4 click events on the button and trigger backend code when the clicks are fired.
5 5 """
6 6 #-----------------------------------------------------------------------------
7 7 # Copyright (c) 2013, the IPython Development Team.
8 8 #
9 9 # Distributed under the terms of the Modified BSD License.
10 10 #
11 11 # The full license is in the file COPYING.txt, distributed with this software.
12 12 #-----------------------------------------------------------------------------
13 13
14 14 #-----------------------------------------------------------------------------
15 15 # Imports
16 16 #-----------------------------------------------------------------------------
17 17 from .widget import DOMWidget, CallbackDispatcher, register
18 18 from IPython.utils.traitlets import Unicode, Bool, CaselessStrEnum
19 19 from IPython.utils.warn import DeprecatedClass
20 20
21 21 #-----------------------------------------------------------------------------
22 22 # Classes
23 23 #-----------------------------------------------------------------------------
24 @register('jupyter.Button')
24 @register('IPython.Button')
25 25 class Button(DOMWidget):
26 26 """Button widget.
27 27
28 28 This widget has an `on_click` method that allows you to listen for the
29 29 user clicking on the button. The click event itself is stateless."""
30 30 _view_name = Unicode('ButtonView', sync=True)
31 31
32 32 # Keys
33 33 description = Unicode('', help="Button label.", sync=True)
34 34 tooltip = Unicode(help="Tooltip caption of the button.", sync=True)
35 35 disabled = Bool(False, help="Enable or disable user changes.", sync=True)
36 36
37 37 button_style = CaselessStrEnum(
38 38 values=['primary', 'success', 'info', 'warning', 'danger', ''],
39 39 default_value='', allow_none=True, sync=True, help="""Use a
40 40 predefined styling for the button.""")
41 41
42 42 def __init__(self, **kwargs):
43 43 """Constructor"""
44 44 super(Button, self).__init__(**kwargs)
45 45 self._click_handlers = CallbackDispatcher()
46 46 self.on_msg(self._handle_button_msg)
47 47
48 48 def on_click(self, callback, remove=False):
49 49 """Register a callback to execute when the button is clicked.
50 50
51 51 The callback will be called with one argument,
52 52 the clicked button widget instance.
53 53
54 54 Parameters
55 55 ----------
56 56 remove : bool (optional)
57 57 Set to true to remove the callback from the list of callbacks."""
58 58 self._click_handlers.register_callback(callback, remove=remove)
59 59
60 60 def _handle_button_msg(self, _, content):
61 61 """Handle a msg from the front-end.
62 62
63 63 Parameters
64 64 ----------
65 65 content: dict
66 66 Content of the msg."""
67 67 if content.get('event', '') == 'click':
68 68 self._click_handlers(self)
69 69
70 70
71 71 # Remove in IPython 4.0
72 72 ButtonWidget = DeprecatedClass(Button, 'ButtonWidget')
@@ -1,184 +1,184
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 17 from IPython.utils.traitlets import Unicode, CFloat, Bool, CaselessStrEnum, Tuple
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
29 29 class _BoundedFloat(_Float):
30 30 max = CFloat(100.0, help="Max value", sync=True)
31 31 min = CFloat(0.0, help="Min value", sync=True)
32 32 step = CFloat(0.1, help="Minimum step that the value can take (ignored by some views)", sync=True)
33 33
34 34 def __init__(self, *pargs, **kwargs):
35 35 """Constructor"""
36 36 DOMWidget.__init__(self, *pargs, **kwargs)
37 37 self._validate('value', None, self.value)
38 38 self.on_trait_change(self._validate, ['value', 'min', 'max'])
39 39
40 40 def _validate(self, name, old, new):
41 41 """Validate value, max, min."""
42 42 if self.min > new or new > self.max:
43 43 self.value = min(max(new, self.min), self.max)
44 44
45 45
46 @register('jupyter.FloatText')
46 @register('IPython.FloatText')
47 47 class FloatText(_Float):
48 48 _view_name = Unicode('FloatTextView', sync=True)
49 49
50 50
51 @register('jupyter.BoundedFloatText')
51 @register('IPython.BoundedFloatText')
52 52 class BoundedFloatText(_BoundedFloat):
53 53 _view_name = Unicode('FloatTextView', sync=True)
54 54
55 55
56 @register('jupyter.FloatSlider')
56 @register('IPython.FloatSlider')
57 57 class FloatSlider(_BoundedFloat):
58 58 _view_name = Unicode('FloatSliderView', sync=True)
59 59 orientation = CaselessStrEnum(values=['horizontal', 'vertical'],
60 60 default_value='horizontal',
61 61 help="Vertical or horizontal.", allow_none=False, sync=True)
62 62 _range = Bool(False, help="Display a range selector", sync=True)
63 63 readout = Bool(True, help="Display the current value of the slider next to it.", sync=True)
64 64 slider_color = Unicode(sync=True)
65 65
66 66
67 @register('jupyter.FloatProgress')
67 @register('IPython.FloatProgress')
68 68 class FloatProgress(_BoundedFloat):
69 69 _view_name = Unicode('ProgressView', sync=True)
70 70
71 71 bar_style = CaselessStrEnum(
72 72 values=['success', 'info', 'warning', 'danger', ''],
73 73 default_value='', allow_none=True, sync=True, help="""Use a
74 74 predefined styling for the progess bar.""")
75 75
76 76 class _FloatRange(_Float):
77 77 value = Tuple(CFloat, CFloat, default_value=(0.0, 1.0), help="Tuple of (lower, upper) bounds", sync=True)
78 78 lower = CFloat(0.0, help="Lower bound", sync=False)
79 79 upper = CFloat(1.0, help="Upper bound", sync=False)
80 80
81 81 def __init__(self, *pargs, **kwargs):
82 82 value_given = 'value' in kwargs
83 83 lower_given = 'lower' in kwargs
84 84 upper_given = 'upper' in kwargs
85 85 if value_given and (lower_given or upper_given):
86 86 raise ValueError("Cannot specify both 'value' and 'lower'/'upper' for range widget")
87 87 if lower_given != upper_given:
88 88 raise ValueError("Must specify both 'lower' and 'upper' for range widget")
89 89
90 90 DOMWidget.__init__(self, *pargs, **kwargs)
91 91
92 92 # ensure the traits match, preferring whichever (if any) was given in kwargs
93 93 if value_given:
94 94 self.lower, self.upper = self.value
95 95 else:
96 96 self.value = (self.lower, self.upper)
97 97
98 98 self.on_trait_change(self._validate, ['value', 'upper', 'lower'])
99 99
100 100 def _validate(self, name, old, new):
101 101 if name == 'value':
102 102 self.lower, self.upper = min(new), max(new)
103 103 elif name == 'lower':
104 104 self.value = (new, self.value[1])
105 105 elif name == 'upper':
106 106 self.value = (self.value[0], new)
107 107
108 108 class _BoundedFloatRange(_FloatRange):
109 109 step = CFloat(1.0, help="Minimum step that the value can take (ignored by some views)", sync=True)
110 110 max = CFloat(100.0, help="Max value", sync=True)
111 111 min = CFloat(0.0, help="Min value", sync=True)
112 112
113 113 def __init__(self, *pargs, **kwargs):
114 114 any_value_given = 'value' in kwargs or 'upper' in kwargs or 'lower' in kwargs
115 115 _FloatRange.__init__(self, *pargs, **kwargs)
116 116
117 117 # ensure a minimal amount of sanity
118 118 if self.min > self.max:
119 119 raise ValueError("min must be <= max")
120 120
121 121 if any_value_given:
122 122 # if a value was given, clamp it within (min, max)
123 123 self._validate("value", None, self.value)
124 124 else:
125 125 # otherwise, set it to 25-75% to avoid the handles overlapping
126 126 self.value = (0.75*self.min + 0.25*self.max,
127 127 0.25*self.min + 0.75*self.max)
128 128 # callback already set for 'value', 'lower', 'upper'
129 129 self.on_trait_change(self._validate, ['min', 'max'])
130 130
131 131
132 132 def _validate(self, name, old, new):
133 133 if name == "min":
134 134 if new > self.max:
135 135 raise ValueError("setting min > max")
136 136 self.min = new
137 137 elif name == "max":
138 138 if new < self.min:
139 139 raise ValueError("setting max < min")
140 140 self.max = new
141 141
142 142 low, high = self.value
143 143 if name == "value":
144 144 low, high = min(new), max(new)
145 145 elif name == "upper":
146 146 if new < self.lower:
147 147 raise ValueError("setting upper < lower")
148 148 high = new
149 149 elif name == "lower":
150 150 if new > self.upper:
151 151 raise ValueError("setting lower > upper")
152 152 low = new
153 153
154 154 low = max(self.min, min(low, self.max))
155 155 high = min(self.max, max(high, self.min))
156 156
157 157 # determine the order in which we should update the
158 158 # lower, upper traits to avoid a temporary inverted overlap
159 159 lower_first = high < self.lower
160 160
161 161 self.value = (low, high)
162 162 if lower_first:
163 163 self.lower = low
164 164 self.upper = high
165 165 else:
166 166 self.upper = high
167 167 self.lower = low
168 168
169 169
170 @register('jupyter.FloatRangeSlider')
170 @register('IPython.FloatRangeSlider')
171 171 class FloatRangeSlider(_BoundedFloatRange):
172 172 _view_name = Unicode('FloatSliderView', sync=True)
173 173 orientation = CaselessStrEnum(values=['horizontal', 'vertical'],
174 174 default_value='horizontal', allow_none=False,
175 175 help="Vertical or horizontal.", sync=True)
176 176 _range = Bool(True, help="Display a range selector", sync=True)
177 177 readout = Bool(True, help="Display the current value of the slider next to it.", sync=True)
178 178 slider_color = Unicode(sync=True)
179 179
180 180 # Remove in IPython 4.0
181 181 FloatTextWidget = DeprecatedClass(FloatText, 'FloatTextWidget')
182 182 BoundedFloatTextWidget = DeprecatedClass(BoundedFloatText, 'BoundedFloatTextWidget')
183 183 FloatSliderWidget = DeprecatedClass(FloatSlider, 'FloatSliderWidget')
184 184 FloatProgressWidget = DeprecatedClass(FloatProgress, 'FloatProgressWidget')
@@ -1,47 +1,47
1 1 """Image class.
2 2
3 3 Represents an image in the frontend 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 import base64
17 17
18 18 from .widget import DOMWidget, register
19 19 from IPython.utils.traitlets import Unicode, CUnicode, Bytes
20 20 from IPython.utils.warn import DeprecatedClass
21 21
22 22 #-----------------------------------------------------------------------------
23 23 # Classes
24 24 #-----------------------------------------------------------------------------
25 @register('jupyter.Image')
25 @register('IPython.Image')
26 26 class Image(DOMWidget):
27 27 """Displays an image as a widget.
28 28
29 29 The `value` of this widget accepts a byte string. The byte string is the raw
30 30 image data that you want the browser to display. You can explicitly define
31 31 the format of the byte string using the `format` trait (which defaults to
32 32 "png")."""
33 33 _view_name = Unicode('ImageView', sync=True)
34 34
35 35 # Define the custom state properties to sync with the front-end
36 36 format = Unicode('png', sync=True)
37 37 width = CUnicode(sync=True)
38 38 height = CUnicode(sync=True)
39 39 _b64value = Unicode(sync=True)
40 40
41 41 value = Bytes()
42 42 def _value_changed(self, name, old, new):
43 43 self._b64value = base64.b64encode(new)
44 44
45 45
46 46 # Remove in IPython 4.0
47 47 ImageWidget = DeprecatedClass(Image, 'ImageWidget')
@@ -1,197 +1,197
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 17 from IPython.utils.traitlets import Unicode, CInt, Bool, CaselessStrEnum, Tuple
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
30 30 class _BoundedInt(_Int):
31 31 """Base class used to create widgets that represent a int that is bounded
32 32 by a minium and maximum."""
33 33 step = CInt(1, help="Minimum step that the value can take (ignored by some views)", sync=True)
34 34 max = CInt(100, help="Max value", sync=True)
35 35 min = CInt(0, help="Min value", sync=True)
36 36
37 37 def __init__(self, *pargs, **kwargs):
38 38 """Constructor"""
39 39 DOMWidget.__init__(self, *pargs, **kwargs)
40 40 self.on_trait_change(self._validate_value, ['value'])
41 41 self.on_trait_change(self._handle_max_changed, ['max'])
42 42 self.on_trait_change(self._handle_min_changed, ['min'])
43 43
44 44 def _validate_value(self, name, old, new):
45 45 """Validate value."""
46 46 if self.min > new or new > self.max:
47 47 self.value = min(max(new, self.min), self.max)
48 48
49 49 def _handle_max_changed(self, name, old, new):
50 50 """Make sure the min is always <= the max."""
51 51 if new < self.min:
52 52 raise ValueError("setting max < min")
53 53
54 54 def _handle_min_changed(self, name, old, new):
55 55 """Make sure the max is always >= the min."""
56 56 if new > self.max:
57 57 raise ValueError("setting min > max")
58 58
59 @register('jupyter.IntText')
59 @register('IPython.IntText')
60 60 class IntText(_Int):
61 61 """Textbox widget that represents a int."""
62 62 _view_name = Unicode('IntTextView', sync=True)
63 63
64 64
65 @register('jupyter.BoundedIntText')
65 @register('IPython.BoundedIntText')
66 66 class BoundedIntText(_BoundedInt):
67 67 """Textbox widget that represents a int bounded by a minimum and maximum value."""
68 68 _view_name = Unicode('IntTextView', sync=True)
69 69
70 70
71 @register('jupyter.IntSlider')
71 @register('IPython.IntSlider')
72 72 class IntSlider(_BoundedInt):
73 73 """Slider widget that represents a int bounded by a minimum and maximum value."""
74 74 _view_name = Unicode('IntSliderView', sync=True)
75 75 orientation = CaselessStrEnum(values=['horizontal', 'vertical'],
76 76 default_value='horizontal', allow_none=False,
77 77 help="Vertical or horizontal.", sync=True)
78 78 _range = Bool(False, help="Display a range selector", sync=True)
79 79 readout = Bool(True, help="Display the current value of the slider next to it.", sync=True)
80 80 slider_color = Unicode(sync=True)
81 81
82 82
83 @register('jupyter.IntProgress')
83 @register('IPython.IntProgress')
84 84 class IntProgress(_BoundedInt):
85 85 """Progress bar that represents a int bounded by a minimum and maximum value."""
86 86 _view_name = Unicode('ProgressView', sync=True)
87 87
88 88 bar_style = CaselessStrEnum(
89 89 values=['success', 'info', 'warning', 'danger', ''],
90 90 default_value='', allow_none=True, sync=True, help="""Use a
91 91 predefined styling for the progess bar.""")
92 92
93 93 class _IntRange(_Int):
94 94 value = Tuple(CInt, CInt, default_value=(0, 1), help="Tuple of (lower, upper) bounds", sync=True)
95 95 lower = CInt(0, help="Lower bound", sync=False)
96 96 upper = CInt(1, help="Upper bound", sync=False)
97 97
98 98 def __init__(self, *pargs, **kwargs):
99 99 value_given = 'value' in kwargs
100 100 lower_given = 'lower' in kwargs
101 101 upper_given = 'upper' in kwargs
102 102 if value_given and (lower_given or upper_given):
103 103 raise ValueError("Cannot specify both 'value' and 'lower'/'upper' for range widget")
104 104 if lower_given != upper_given:
105 105 raise ValueError("Must specify both 'lower' and 'upper' for range widget")
106 106
107 107 DOMWidget.__init__(self, *pargs, **kwargs)
108 108
109 109 # ensure the traits match, preferring whichever (if any) was given in kwargs
110 110 if value_given:
111 111 self.lower, self.upper = self.value
112 112 else:
113 113 self.value = (self.lower, self.upper)
114 114
115 115 self.on_trait_change(self._validate, ['value', 'upper', 'lower'])
116 116
117 117 def _validate(self, name, old, new):
118 118 if name == 'value':
119 119 self.lower, self.upper = min(new), max(new)
120 120 elif name == 'lower':
121 121 self.value = (new, self.value[1])
122 122 elif name == 'upper':
123 123 self.value = (self.value[0], new)
124 124
125 125 class _BoundedIntRange(_IntRange):
126 126 step = CInt(1, help="Minimum step that the value can take (ignored by some views)", sync=True)
127 127 max = CInt(100, help="Max value", sync=True)
128 128 min = CInt(0, help="Min value", sync=True)
129 129
130 130 def __init__(self, *pargs, **kwargs):
131 131 any_value_given = 'value' in kwargs or 'upper' in kwargs or 'lower' in kwargs
132 132 _IntRange.__init__(self, *pargs, **kwargs)
133 133
134 134 # ensure a minimal amount of sanity
135 135 if self.min > self.max:
136 136 raise ValueError("min must be <= max")
137 137
138 138 if any_value_given:
139 139 # if a value was given, clamp it within (min, max)
140 140 self._validate("value", None, self.value)
141 141 else:
142 142 # otherwise, set it to 25-75% to avoid the handles overlapping
143 143 self.value = (0.75*self.min + 0.25*self.max,
144 144 0.25*self.min + 0.75*self.max)
145 145 # callback already set for 'value', 'lower', 'upper'
146 146 self.on_trait_change(self._validate, ['min', 'max'])
147 147
148 148 def _validate(self, name, old, new):
149 149 if name == "min":
150 150 if new > self.max:
151 151 raise ValueError("setting min > max")
152 152 elif name == "max":
153 153 if new < self.min:
154 154 raise ValueError("setting max < min")
155 155
156 156 low, high = self.value
157 157 if name == "value":
158 158 low, high = min(new), max(new)
159 159 elif name == "upper":
160 160 if new < self.lower:
161 161 raise ValueError("setting upper < lower")
162 162 high = new
163 163 elif name == "lower":
164 164 if new > self.upper:
165 165 raise ValueError("setting lower > upper")
166 166 low = new
167 167
168 168 low = max(self.min, min(low, self.max))
169 169 high = min(self.max, max(high, self.min))
170 170
171 171 # determine the order in which we should update the
172 172 # lower, upper traits to avoid a temporary inverted overlap
173 173 lower_first = high < self.lower
174 174
175 175 self.value = (low, high)
176 176 if lower_first:
177 177 self.lower = low
178 178 self.upper = high
179 179 else:
180 180 self.upper = high
181 181 self.lower = low
182 182
183 @register('jupyter.IntRangeSlider')
183 @register('IPython.IntRangeSlider')
184 184 class IntRangeSlider(_BoundedIntRange):
185 185 _view_name = Unicode('IntSliderView', sync=True)
186 186 orientation = CaselessStrEnum(values=['horizontal', 'vertical'],
187 187 default_value='horizontal', allow_none=False,
188 188 help="Vertical or horizontal.", sync=True)
189 189 _range = Bool(True, help="Display a range selector", sync=True)
190 190 readout = Bool(True, help="Display the current value of the slider next to it.", sync=True)
191 191 slider_color = Unicode(sync=True)
192 192
193 193 # Remove in IPython 4.0
194 194 IntTextWidget = DeprecatedClass(IntText, 'IntTextWidget')
195 195 BoundedIntTextWidget = DeprecatedClass(BoundedIntText, 'BoundedIntTextWidget')
196 196 IntSliderWidget = DeprecatedClass(IntSlider, 'IntSliderWidget')
197 197 IntProgressWidget = DeprecatedClass(IntProgress, 'IntProgressWidget')
@@ -1,156 +1,156
1 1 """Selection classes.
2 2
3 3 Represents an enumeration 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
17 17 from collections import OrderedDict
18 18 from threading import Lock
19 19
20 20 from .widget import DOMWidget, register
21 21 from IPython.utils.traitlets import Unicode, List, Bool, Any, Dict, TraitError, CaselessStrEnum
22 22 from IPython.utils.py3compat import unicode_type
23 23 from IPython.utils.warn import DeprecatedClass
24 24
25 25 #-----------------------------------------------------------------------------
26 26 # SelectionWidget
27 27 #-----------------------------------------------------------------------------
28 28 class _Selection(DOMWidget):
29 29 """Base class for Selection widgets
30 30
31 31 ``values`` can be specified as a list or dict. If given as a list,
32 32 it will be transformed to a dict of the form ``{str(value):value}``.
33 33 """
34 34
35 35 value = Any(help="Selected value")
36 36 values = Dict(help="""Dictionary of {name: value} the user can select.
37 37
38 38 The keys of this dictionary are the strings that will be displayed in the UI,
39 39 representing the actual Python choices.
40 40
41 41 The keys of this dictionary are also available as value_names.
42 42 """)
43 43 value_name = Unicode(help="The name of the selected value", sync=True)
44 44 value_names = List(Unicode, help="""Read-only list of names for each value.
45 45
46 46 If values is specified as a list, this is the string representation of each element.
47 47 Otherwise, it is the keys of the values dictionary.
48 48
49 49 These strings are used to display the choices in the front-end.""", sync=True)
50 50 disabled = Bool(False, help="Enable or disable user changes", sync=True)
51 51 description = Unicode(help="Description of the value this widget represents", sync=True)
52 52
53 53
54 54 def __init__(self, *args, **kwargs):
55 55 self.value_lock = Lock()
56 56 self._in_values_changed = False
57 57 if 'values' in kwargs:
58 58 values = kwargs['values']
59 59 # convert list values to an dict of {str(v):v}
60 60 if isinstance(values, list):
61 61 # preserve list order with an OrderedDict
62 62 kwargs['values'] = OrderedDict((unicode_type(v), v) for v in values)
63 63 # python3.3 turned on hash randomization by default - this means that sometimes, randomly
64 64 # we try to set value before setting values, due to dictionary ordering. To fix this, force
65 65 # the setting of self.values right now, before anything else runs
66 66 self.values = kwargs.pop('values')
67 67 DOMWidget.__init__(self, *args, **kwargs)
68 68 self._value_in_values()
69 69
70 70 def _values_changed(self, name, old, new):
71 71 """Handles when the values dict has been changed.
72 72
73 73 Setting values implies setting value names from the keys of the dict.
74 74 """
75 75 self._in_values_changed = True
76 76 try:
77 77 self.value_names = list(new.keys())
78 78 finally:
79 79 self._in_values_changed = False
80 80 self._value_in_values()
81 81
82 82 def _value_in_values(self):
83 83 # ensure that the chosen value is one of the choices
84 84 if self.values:
85 85 if self.value not in self.values.values():
86 86 self.value = next(iter(self.values.values()))
87 87
88 88 def _value_names_changed(self, name, old, new):
89 89 if not self._in_values_changed:
90 90 raise TraitError("value_names is a read-only proxy to values.keys(). Use the values dict instead.")
91 91
92 92 def _value_changed(self, name, old, new):
93 93 """Called when value has been changed"""
94 94 if self.value_lock.acquire(False):
95 95 try:
96 96 # Reverse dictionary lookup for the value name
97 97 for k,v in self.values.items():
98 98 if new == v:
99 99 # set the selected value name
100 100 self.value_name = k
101 101 return
102 102 # undo the change, and raise KeyError
103 103 self.value = old
104 104 raise KeyError(new)
105 105 finally:
106 106 self.value_lock.release()
107 107
108 108 def _value_name_changed(self, name, old, new):
109 109 """Called when the value name has been changed (typically by the frontend)."""
110 110 if self.value_lock.acquire(False):
111 111 try:
112 112 self.value = self.values[new]
113 113 finally:
114 114 self.value_lock.release()
115 115
116 116
117 @register('jupyter.ToggleButtons')
117 @register('IPython.ToggleButtons')
118 118 class ToggleButtons(_Selection):
119 119 """Group of toggle buttons that represent an enumeration. Only one toggle
120 120 button can be toggled at any point in time."""
121 121 _view_name = Unicode('ToggleButtonsView', sync=True)
122 122
123 123 button_style = CaselessStrEnum(
124 124 values=['primary', 'success', 'info', 'warning', 'danger', ''],
125 125 default_value='', allow_none=True, sync=True, help="""Use a
126 126 predefined styling for the buttons.""")
127 127
128 @register('jupyter.Dropdown')
128 @register('IPython.Dropdown')
129 129 class Dropdown(_Selection):
130 130 """Allows you to select a single item from a dropdown."""
131 131 _view_name = Unicode('DropdownView', sync=True)
132 132
133 133 button_style = CaselessStrEnum(
134 134 values=['primary', 'success', 'info', 'warning', 'danger', ''],
135 135 default_value='', allow_none=True, sync=True, help="""Use a
136 136 predefined styling for the buttons.""")
137 137
138 @register('jupyter.RadioButtons')
138 @register('IPython.RadioButtons')
139 139 class RadioButtons(_Selection):
140 140 """Group of radio buttons that represent an enumeration. Only one radio
141 141 button can be toggled at any point in time."""
142 142 _view_name = Unicode('RadioButtonsView', sync=True)
143 143
144 144
145 145
146 @register('jupyter.Select')
146 @register('IPython.Select')
147 147 class Select(_Selection):
148 148 """Listbox that only allows one item to be selected at any given time."""
149 149 _view_name = Unicode('SelectView', sync=True)
150 150
151 151
152 152 # Remove in IPython 4.0
153 153 ToggleButtonsWidget = DeprecatedClass(ToggleButtons, 'ToggleButtonsWidget')
154 154 DropdownWidget = DeprecatedClass(Dropdown, 'DropdownWidget')
155 155 RadioButtonsWidget = DeprecatedClass(RadioButtons, 'RadioButtonsWidget')
156 156 SelectWidget = DeprecatedClass(Select, 'SelectWidget')
@@ -1,68 +1,68
1 1 """SelectionContainer class.
2 2
3 3 Represents a multipage container that can be used to group other widgets into
4 4 pages.
5 5 """
6 6 #-----------------------------------------------------------------------------
7 7 # Copyright (c) 2013, the IPython Development Team.
8 8 #
9 9 # Distributed under the terms of the Modified BSD License.
10 10 #
11 11 # The full license is in the file COPYING.txt, distributed with this software.
12 12 #-----------------------------------------------------------------------------
13 13
14 14 #-----------------------------------------------------------------------------
15 15 # Imports
16 16 #-----------------------------------------------------------------------------
17 17 from .widget_box import Box, register
18 18 from IPython.utils.traitlets import Unicode, Dict, CInt
19 19 from IPython.utils.warn import DeprecatedClass
20 20
21 21 #-----------------------------------------------------------------------------
22 22 # Classes
23 23 #-----------------------------------------------------------------------------
24 24 class _SelectionContainer(Box):
25 25 """Base class used to display multiple child widgets."""
26 26 _titles = Dict(help="Titles of the pages", sync=True)
27 27 selected_index = CInt(0, sync=True)
28 28
29 29 # Public methods
30 30 def set_title(self, index, title):
31 31 """Sets the title of a container page.
32 32
33 33 Parameters
34 34 ----------
35 35 index : int
36 36 Index of the container page
37 37 title : unicode
38 38 New title"""
39 39 self._titles[index] = title
40 40 self.send_state('_titles')
41 41
42 42 def get_title(self, index):
43 43 """Gets the title of a container pages.
44 44
45 45 Parameters
46 46 ----------
47 47 index : int
48 48 Index of the container page"""
49 49 if index in self._titles:
50 50 return self._titles[index]
51 51 else:
52 52 return None
53 53
54 @register('jupyter.Accordion')
54 @register('IPython.Accordion')
55 55 class Accordion(_SelectionContainer):
56 56 """Displays children each on a separate accordion page."""
57 57 _view_name = Unicode('AccordionView', sync=True)
58 58
59 59
60 @register('jupyter.Tab')
60 @register('IPython.Tab')
61 61 class Tab(_SelectionContainer):
62 62 """Displays children each on a separate accordion tab."""
63 63 _view_name = Unicode('TabView', sync=True)
64 64
65 65
66 66 # Remove in IPython 4.0
67 67 AccordionWidget = DeprecatedClass(Accordion, 'AccordionWidget')
68 68 TabWidget = DeprecatedClass(Tab, 'TabWidget')
@@ -1,91 +1,91
1 1 """String class.
2 2
3 3 Represents a unicode string 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, CallbackDispatcher, register
17 17 from IPython.utils.traitlets import Unicode, Bool
18 18 from IPython.utils.warn import DeprecatedClass
19 19
20 20 #-----------------------------------------------------------------------------
21 21 # Classes
22 22 #-----------------------------------------------------------------------------
23 23 class _String(DOMWidget):
24 24 """Base class used to create widgets that represent a string."""
25 25 value = Unicode(help="String 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 placeholder = Unicode("", help="Placeholder text to display when nothing has been typed", sync=True)
29 29
30 30
31 @register('jupyter.HTML')
31 @register('IPython.HTML')
32 32 class HTML(_String):
33 33 """Renders the string `value` as HTML."""
34 34 _view_name = Unicode('HTMLView', sync=True)
35 35
36 36
37 @register('jupyter.Latex')
37 @register('IPython.Latex')
38 38 class Latex(_String):
39 39 """Renders math inside the string `value` as Latex (requires $ $ or $$ $$
40 40 and similar latex tags)."""
41 41 _view_name = Unicode('LatexView', sync=True)
42 42
43 43
44 @register('jupyter.Textarea')
44 @register('IPython.Textarea')
45 45 class Textarea(_String):
46 46 """Multiline text area widget."""
47 47 _view_name = Unicode('TextareaView', sync=True)
48 48
49 49 def scroll_to_bottom(self):
50 50 self.send({"method": "scroll_to_bottom"})
51 51
52 52
53 @register('jupyter.Text')
53 @register('IPython.Text')
54 54 class Text(_String):
55 55 """Single line textbox widget."""
56 56 _view_name = Unicode('TextView', sync=True)
57 57
58 58 def __init__(self, **kwargs):
59 59 super(Text, self).__init__(**kwargs)
60 60 self._submission_callbacks = CallbackDispatcher()
61 61 self.on_msg(self._handle_string_msg)
62 62
63 63 def _handle_string_msg(self, _, content):
64 64 """Handle a msg from the front-end.
65 65
66 66 Parameters
67 67 ----------
68 68 content: dict
69 69 Content of the msg."""
70 70 if content.get('event', '') == 'submit':
71 71 self._submission_callbacks(self)
72 72
73 73 def on_submit(self, callback, remove=False):
74 74 """(Un)Register a callback to handle text submission.
75 75
76 76 Triggered when the user clicks enter.
77 77
78 78 Parameters
79 79 ----------
80 80 callback: callable
81 81 Will be called with exactly one argument: the Widget instance
82 82 remove: bool (optional)
83 83 Whether to unregister the callback"""
84 84 self._submission_callbacks.register_callback(callback, remove=remove)
85 85
86 86
87 87 # Remove in IPython 4.0
88 88 HTMLWidget = DeprecatedClass(HTML, 'HTMLWidget')
89 89 LatexWidget = DeprecatedClass(Latex, 'LatexWidget')
90 90 TextareaWidget = DeprecatedClass(Textarea, 'TextareaWidget')
91 91 TextWidget = DeprecatedClass(Text, 'TextWidget')
General Comments 0
You need to be logged in to leave comments. Login now