diff --git a/IPython/html/widgets/widget_selection.py b/IPython/html/widgets/widget_selection.py
index 79e61e280..da55afdae 100644
--- a/IPython/html/widgets/widget_selection.py
+++ b/IPython/html/widgets/widget_selection.py
@@ -33,68 +33,81 @@ class _Selection(DOMWidget):
"""
value = Any(help="Selected value")
- values = Dict(help="""Dictionary of {name: value} the user can select.
+ value_name = Unicode(help="The name of the selected value", sync=True)
+ values = Any(sync=True, help="""List of (key, value) tuples the user can select.
- The keys of this dictionary are the strings that will be displayed in the UI,
+ The keys of this list are the strings that will be displayed in the UI,
representing the actual Python choices.
- The keys of this dictionary are also available as value_names.
+ The keys of this list are also available as value_names.
""")
- value_name = Unicode(help="The name of the selected value", sync=True)
- value_names = List(Unicode, help="""Read-only list of names for each value.
-
- If values is specified as a list, this is the string representation of each element.
- Otherwise, it is the keys of the values dictionary.
-
- These strings are used to display the choices in the front-end.""", sync=True)
+
+ values_dict = Dict()
+ values_names = Tuple()
+ values_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._in_values_changed = False
+ self.values_lock = Lock()
+ self.on_trait_change(self._values_readonly_changed, ['values_dict', 'values_names', 'values_values', '_values'])
if 'values' in kwargs:
- values = kwargs['values']
- # convert list values to an dict of {str(v):v}
- if isinstance(values, list):
- # preserve list order with an OrderedDict
- kwargs['values'] = OrderedDict((unicode_type(v), v) for v in values)
- # python3.3 turned on hash randomization by default - this means that sometimes, randomly
- # we try to set value before setting values, due to dictionary ordering. To fix this, force
- # the setting of self.values right now, before anything else runs
self.values = kwargs.pop('values')
DOMWidget.__init__(self, *args, **kwargs)
self._value_in_values()
+ def _make_values(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 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 _values_changed(self, name, old, new):
- """Handles when the values dict has been changed.
+ """Handles when the values tuple has been changed.
Setting values implies setting value names from the keys of the dict.
- """
- self._in_values_changed = True
- try:
- self.value_names = list(new.keys())
- finally:
- self._in_values_changed = False
- self._value_in_values()
+ """
+ if self.values_lock.acquire(False):
+ try:
+
+ self.values = self._make_values(x)
+ self.values_dict = {i[0]: i[1] for i in self.values}
+ self.values_names = [i[0] for i in self.values]
+ self.values_values = [i[1] for i in self.values]
+ self._value_in_values()
+
def _value_in_values(self):
# ensure that the chosen value is one of the choices
- if self.values:
- if self.value not in self.values.values():
- self.value = next(iter(self.values.values()))
+ if self.values_values:
+ if self.value not in self.values_values:
+ self.value = next(iter(self.values_values))
- def _value_names_changed(self, name, old, new):
- if not self._in_values_changed:
- raise TraitError("value_names is a read-only proxy to values.keys(). Use the values dict instead.")
+ def _values_readonly_changed(self, name, old, new):
+ if not self.values_lock.acquire(False):
+ raise TraitError("`.%s` is a read-only trait. Use the `.values` tuple instead." % name)
+ else:
+ self.values_lock.release()
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.values.items():
+ for k,v in self.values_dict.items():
if new == v:
# set the selected value name
self.value_name = k
@@ -109,7 +122,7 @@ class _Selection(DOMWidget):
"""Called when the value name has been changed (typically by the frontend)."""
if self.value_lock.acquire(False):
try:
- self.value = self.values[new]
+ self.value = self.values_dict[new]
finally:
self.value_lock.release()