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