widget_selection.py
238 lines
| 8.8 KiB
| text/x-python
|
PythonLexer
Jonathan Frederic
|
r17598 | """Selection classes. | ||
Jonathan Frederic
|
r14283 | |||
Represents an enumeration using a widget. | ||||
""" | ||||
Min RK
|
r21081 | # Copyright (c) IPython Development Team. | ||
# Distributed under the terms of the Modified BSD License. | ||||
MinRK
|
r15024 | |||
from collections import OrderedDict | ||||
Jonathan Frederic
|
r14698 | from threading import Lock | ||
Sylvain Corlay
|
r18531 | from .widget import DOMWidget, register | ||
Thomas Kluyver
|
r19060 | from IPython.utils.traitlets import ( | ||
Sylvain Corlay
|
r20445 | Unicode, Bool, Any, Dict, TraitError, CaselessStrEnum, Tuple, List | ||
Thomas Kluyver
|
r19060 | ) | ||
MinRK
|
r15024 | from IPython.utils.py3compat import unicode_type | ||
Min RK
|
r21081 | from .deprecated import DeprecatedClass | ||
Jonathan Frederic
|
r14242 | |||
Jonathan Frederic
|
r17598 | class _Selection(DOMWidget): | ||
MinRK
|
r15024 | """Base class for Selection widgets | ||
Nicholas Bollweg
|
r20287 | ``options`` can be specified as a list or dict. If given as a list, | ||
MinRK
|
r15024 | it will be transformed to a dict of the form ``{str(value):value}``. | ||
Sylvain Corlay
|
r20445 | |||
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. | ||||
MinRK
|
r15024 | """ | ||
value = Any(help="Selected value") | ||||
Nicholas Bollweg
|
r20287 | 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 | ||||
Jonathan Frederic
|
r19134 | user can select. | ||
MinRK
|
r15024 | |||
Jonathan Frederic
|
r19058 | The keys of this list are the strings that will be displayed in the UI, | ||
MinRK
|
r15024 | representing the actual Python choices. | ||
Nicholas Bollweg
|
r20287 | The keys of this list are also available as _options_labels. | ||
MinRK
|
r15024 | """) | ||
Jonathan Frederic
|
r19058 | |||
Nicholas Bollweg
|
r20287 | _options_dict = Dict() | ||
_options_labels = Tuple(sync=True) | ||||
_options_values = Tuple() | ||||
Jonathan Frederic
|
r19058 | |||
Jonathan Frederic
|
r14588 | disabled = Bool(False, help="Enable or disable user changes", sync=True) | ||
description = Unicode(help="Description of the value this widget represents", sync=True) | ||||
Jonathan Frederic
|
r19058 | |||
MinRK
|
r15024 | def __init__(self, *args, **kwargs): | ||
Jonathan Frederic
|
r14698 | self.value_lock = Lock() | ||
Nicholas Bollweg
|
r20287 | self.options_lock = Lock() | ||
Sylvain Corlay
|
r20323 | self.equals = kwargs.pop('equals', lambda x, y: x == y) | ||
Nicholas Bollweg
|
r20287 | self.on_trait_change(self._options_readonly_changed, ['_options_dict', '_options_labels', '_options_values', '_options']) | ||
if 'options' in kwargs: | ||||
self.options = kwargs.pop('options') | ||||
MinRK
|
r15024 | DOMWidget.__init__(self, *args, **kwargs) | ||
Nicholas Bollweg
|
r20287 | self._value_in_options() | ||
MinRK
|
r15024 | |||
Nicholas Bollweg
|
r20287 | def _make_options(self, x): | ||
Jonathan Frederic
|
r19058 | # 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') | ||||
Nicholas Bollweg
|
r20287 | # If x is an ordinary list, use the option values as names. | ||
Jonathan Frederic
|
r19058 | 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 | ||||
Nicholas Bollweg
|
r20287 | def _options_changed(self, name, old, new): | ||
"""Handles when the options tuple has been changed. | ||||
Jonathan Frederic
|
r14698 | |||
Nicholas Bollweg
|
r20287 | Setting options implies setting option labels from the keys of the dict. | ||
""" | ||||
if self.options_lock.acquire(False): | ||||
Jonathan Frederic
|
r19058 | try: | ||
Nicholas Bollweg
|
r20287 | self.options = new | ||
Jonathan Frederic
|
r19157 | |||
Nicholas Bollweg
|
r20287 | 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() | ||||
Jonathan Frederic
|
r19059 | finally: | ||
Nicholas Bollweg
|
r20287 | self.options_lock.release() | ||
Jonathan Frederic
|
r19058 | |||
Nicholas Bollweg
|
r20287 | def _value_in_options(self): | ||
MinRK
|
r15046 | # ensure that the chosen value is one of the choices | ||
MinRK
|
r15024 | |||
Nicholas Bollweg
|
r20287 | if self._options_values: | ||
if self.value not in self._options_values: | ||||
self.value = next(iter(self._options_values)) | ||||
Jonathan Frederic
|
r14698 | |||
Nicholas Bollweg
|
r20287 | 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) | ||||
Sylvain Corlay
|
r20323 | |||
Jonathan Frederic
|
r14698 | def _value_changed(self, name, old, new): | ||
"""Called when value has been changed""" | ||||
if self.value_lock.acquire(False): | ||||
try: | ||||
MinRK
|
r15046 | # Reverse dictionary lookup for the value name | ||
Nicholas Bollweg
|
r20287 | for k, v in self._options_dict.items(): | ||
Sylvain Corlay
|
r20323 | if self.equals(new, v): | ||
MinRK
|
r15024 | # set the selected value name | ||
Nicholas Bollweg
|
r20287 | self.selected_label = k | ||
MinRK
|
r15024 | return | ||
MinRK
|
r15401 | # undo the change, and raise KeyError | ||
self.value = old | ||||
MinRK
|
r15046 | raise KeyError(new) | ||
Jonathan Frederic
|
r14698 | finally: | ||
self.value_lock.release() | ||||
Nicholas Bollweg
|
r20287 | def _selected_label_changed(self, name, old, new): | ||
MinRK
|
r15024 | """Called when the value name has been changed (typically by the frontend).""" | ||
Jonathan Frederic
|
r14698 | if self.value_lock.acquire(False): | ||
try: | ||||
Nicholas Bollweg
|
r20287 | 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] | ||||
Jonathan Frederic
|
r14698 | finally: | ||
self.value_lock.release() | ||||
Jonathan Frederic
|
r14592 | |||
Sylvain Corlay
|
r18533 | @register('IPython.ToggleButtons') | ||
Jonathan Frederic
|
r17598 | class ToggleButtons(_Selection): | ||
Jonathan Frederic
|
r17602 | """Group of toggle buttons that represent an enumeration. Only one toggle | ||
button can be toggled at any point in time.""" | ||||
Jonathan Frederic
|
r14701 | _view_name = Unicode('ToggleButtonsView', sync=True) | ||
Sylvain Corlay
|
r20445 | tooltips = List(Unicode(), sync=True) | ||
Sylvain Corlay
|
r20446 | icons = List(Unicode(), sync=True) | ||
Jonathan Frederic
|
r14670 | |||
Jonathan Frederic
|
r17728 | button_style = CaselessStrEnum( | ||
values=['primary', 'success', 'info', 'warning', 'danger', ''], | ||||
default_value='', allow_none=True, sync=True, help="""Use a | ||||
predefined styling for the buttons.""") | ||||
Sylvain Corlay
|
r18533 | @register('IPython.Dropdown') | ||
Jonathan Frederic
|
r17598 | class Dropdown(_Selection): | ||
Jonathan Frederic
|
r17602 | """Allows you to select a single item from a dropdown.""" | ||
Jonathan Frederic
|
r14701 | _view_name = Unicode('DropdownView', sync=True) | ||
Jonathan Frederic
|
r14592 | |||
Jonathan Frederic
|
r17729 | button_style = CaselessStrEnum( | ||
values=['primary', 'success', 'info', 'warning', 'danger', ''], | ||||
default_value='', allow_none=True, sync=True, help="""Use a | ||||
predefined styling for the buttons.""") | ||||
Sylvain Corlay
|
r18533 | @register('IPython.RadioButtons') | ||
Jonathan Frederic
|
r17598 | class RadioButtons(_Selection): | ||
Jonathan Frederic
|
r17602 | """Group of radio buttons that represent an enumeration. Only one radio | ||
button can be toggled at any point in time.""" | ||||
Jonathan Frederic
|
r14701 | _view_name = Unicode('RadioButtonsView', sync=True) | ||
Jonathan Frederic
|
r14592 | |||
Sylvain Corlay
|
r18531 | |||
Sylvain Corlay
|
r18533 | @register('IPython.Select') | ||
Jonathan Frederic
|
r17598 | class Select(_Selection): | ||
Jonathan Frederic
|
r17602 | """Listbox that only allows one item to be selected at any given time.""" | ||
Jonathan Frederic
|
r14834 | _view_name = Unicode('SelectView', sync=True) | ||
Jonathan Frederic
|
r17598 | |||
Jonathan Frederic
|
r17602 | |||
Nicholas Bollweg
|
r20287 | @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) | ||||
Jonathan Frederic
|
r17602 | # Remove in IPython 4.0 | ||
Jonathan Frederic
|
r17598 | ToggleButtonsWidget = DeprecatedClass(ToggleButtons, 'ToggleButtonsWidget') | ||
DropdownWidget = DeprecatedClass(Dropdown, 'DropdownWidget') | ||||
RadioButtonsWidget = DeprecatedClass(RadioButtons, 'RadioButtonsWidget') | ||||
SelectWidget = DeprecatedClass(Select, 'SelectWidget') | ||||