|
|
"""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')
|
|
|
|