##// END OF EJS Templates
Merge pull request #8238 from ipython/revert-8235-testpath...
Merge pull request #8238 from ipython/revert-8235-testpath Revert "use testpath.tempdir for utils.tempdir"

File last commit:

r21081:5104f42c
r21110:ce85574b merge
Show More
widget_selection.py
238 lines | 8.8 KiB | text/x-python | PythonLexer
"""Selection classes.
Represents an enumeration using a widget.
"""
# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
from collections import OrderedDict
from threading import Lock
from .widget import DOMWidget, register
from IPython.utils.traitlets import (
Unicode, Bool, Any, Dict, TraitError, CaselessStrEnum, Tuple, List
)
from IPython.utils.py3compat import unicode_type
from .deprecated import DeprecatedClass
class _Selection(DOMWidget):
"""Base class for Selection widgets
``options`` can be specified as a list or dict. If given as a list,
it will be transformed to a dict of the form ``{str(value):value}``.
When programmatically setting the value, a reverse lookup is performed
among the options to set the value of ``selected_label`` accordingly. The
reverse lookup uses the equality operator by default, but an other
predicate may be provided via the ``equals`` argument. For example, when
dealing with numpy arrays, one may set equals=np.array_equal.
"""
value = Any(help="Selected value")
selected_label = Unicode(help="The label of the selected value", sync=True)
options = Any(help="""List of (key, value) tuples or dict of values that the
user can select.
The keys of this list are the strings that will be displayed in the UI,
representing the actual Python choices.
The keys of this list are also available as _options_labels.
""")
_options_dict = Dict()
_options_labels = Tuple(sync=True)
_options_values = Tuple()
disabled = Bool(False, help="Enable or disable user changes", sync=True)
description = Unicode(help="Description of the value this widget represents", sync=True)
def __init__(self, *args, **kwargs):
self.value_lock = Lock()
self.options_lock = Lock()
self.equals = kwargs.pop('equals', lambda x, y: x == y)
self.on_trait_change(self._options_readonly_changed, ['_options_dict', '_options_labels', '_options_values', '_options'])
if 'options' in kwargs:
self.options = kwargs.pop('options')
DOMWidget.__init__(self, *args, **kwargs)
self._value_in_options()
def _make_options(self, x):
# If x is a dict, convert it to list format.
if isinstance(x, (OrderedDict, dict)):
return [(k, v) for k, v in x.items()]
# Make sure x is a list or tuple.
if not isinstance(x, (list, tuple)):
raise ValueError('x')
# If x is an ordinary list, use the option values as names.
for y in x:
if not isinstance(y, (list, tuple)) or len(y) < 2:
return [(i, i) for i in x]
# Value is already in the correct format.
return x
def _options_changed(self, name, old, new):
"""Handles when the options tuple has been changed.
Setting options implies setting option labels from the keys of the dict.
"""
if self.options_lock.acquire(False):
try:
self.options = new
options = self._make_options(new)
self._options_dict = {i[0]: i[1] for i in options}
self._options_labels = [i[0] for i in options]
self._options_values = [i[1] for i in options]
self._value_in_options()
finally:
self.options_lock.release()
def _value_in_options(self):
# ensure that the chosen value is one of the choices
if self._options_values:
if self.value not in self._options_values:
self.value = next(iter(self._options_values))
def _options_readonly_changed(self, name, old, new):
if not self.options_lock.locked():
raise TraitError("`.%s` is a read-only trait. Use the `.options` tuple instead." % name)
def _value_changed(self, name, old, new):
"""Called when value has been changed"""
if self.value_lock.acquire(False):
try:
# Reverse dictionary lookup for the value name
for k, v in self._options_dict.items():
if self.equals(new, v):
# set the selected value name
self.selected_label = k
return
# undo the change, and raise KeyError
self.value = old
raise KeyError(new)
finally:
self.value_lock.release()
def _selected_label_changed(self, name, old, new):
"""Called when the value name has been changed (typically by the frontend)."""
if self.value_lock.acquire(False):
try:
self.value = self._options_dict[new]
finally:
self.value_lock.release()
class _MultipleSelection(_Selection):
"""Base class for MultipleSelection widgets.
As with ``_Selection``, ``options`` can be specified as a list or dict. If
given as a list, it will be transformed to a dict of the form
``{str(value): value}``.
Despite their names, ``value`` (and ``selected_label``) will be tuples, even
if only a single option is selected.
"""
value = Tuple(help="Selected values")
selected_labels = Tuple(help="The labels of the selected options",
sync=True)
@property
def selected_label(self):
raise AttributeError(
"Does not support selected_label, use selected_labels")
def _value_in_options(self):
# ensure that the chosen value is one of the choices
if self.options:
old_value = self.value or []
new_value = []
for value in old_value:
if value in self._options_dict.values():
new_value.append(value)
if new_value:
self.value = new_value
else:
self.value = [next(iter(self._options_dict.values()))]
def _value_changed(self, name, old, new):
"""Called when value has been changed"""
if self.value_lock.acquire(False):
try:
self.selected_labels = [
self._options_labels[self._options_values.index(v)]
for v in new
]
except:
self.value = old
raise KeyError(new)
finally:
self.value_lock.release()
def _selected_labels_changed(self, name, old, new):
"""Called when the selected label has been changed (typically by the
frontend)."""
if self.value_lock.acquire(False):
try:
self.value = [self._options_dict[name] for name in new]
finally:
self.value_lock.release()
@register('IPython.ToggleButtons')
class ToggleButtons(_Selection):
"""Group of toggle buttons that represent an enumeration. Only one toggle
button can be toggled at any point in time."""
_view_name = Unicode('ToggleButtonsView', sync=True)
tooltips = List(Unicode(), sync=True)
icons = List(Unicode(), sync=True)
button_style = CaselessStrEnum(
values=['primary', 'success', 'info', 'warning', 'danger', ''],
default_value='', allow_none=True, sync=True, help="""Use a
predefined styling for the buttons.""")
@register('IPython.Dropdown')
class Dropdown(_Selection):
"""Allows you to select a single item from a dropdown."""
_view_name = Unicode('DropdownView', sync=True)
button_style = CaselessStrEnum(
values=['primary', 'success', 'info', 'warning', 'danger', ''],
default_value='', allow_none=True, sync=True, help="""Use a
predefined styling for the buttons.""")
@register('IPython.RadioButtons')
class RadioButtons(_Selection):
"""Group of radio buttons that represent an enumeration. Only one radio
button can be toggled at any point in time."""
_view_name = Unicode('RadioButtonsView', sync=True)
@register('IPython.Select')
class Select(_Selection):
"""Listbox that only allows one item to be selected at any given time."""
_view_name = Unicode('SelectView', sync=True)
@register('IPython.SelectMultiple')
class SelectMultiple(_MultipleSelection):
"""Listbox that allows many items to be selected at any given time.
Despite their names, inherited from ``_Selection``, the currently chosen
option values, ``value``, or their labels, ``selected_labels`` must both be
updated with a list-like object."""
_view_name = Unicode('SelectMultipleView', sync=True)
# Remove in IPython 4.0
ToggleButtonsWidget = DeprecatedClass(ToggleButtons, 'ToggleButtonsWidget')
DropdownWidget = DeprecatedClass(Dropdown, 'DropdownWidget')
RadioButtonsWidget = DeprecatedClass(RadioButtons, 'RadioButtonsWidget')
SelectWidget = DeprecatedClass(Select, 'SelectWidget')