import random
from weakref import WeakKeyDictionary
import numpy as np
from .core import CallbackProperty
__all__ = ['ChoiceSeparator', 'SelectionCallbackProperty']
[docs]class ChoiceSeparator(str):
pass
[docs]class SelectionCallbackProperty(CallbackProperty):
def __init__(self, default_index=0, choices=None, display_func=None, comparison_type=None, **kwargs):
if choices is not None and 'default' not in kwargs:
kwargs['default'] = choices[default_index]
super(SelectionCallbackProperty, self).__init__(**kwargs)
self.default_index = default_index
self.default_choices = choices or []
self.comparison_type = comparison_type
self._default_display_func = display_func
self._choices = WeakKeyDictionary()
self._display = WeakKeyDictionary()
self._force_next_sync = WeakKeyDictionary()
def __set__(self, instance, value):
if value is not None:
choices = self.get_choices(instance)
# For built-in scalar types we use ==, and for other types we use
# is, otherwise e.g. ComponentID returns something that evaluates
# to true when using ==.
if self.comparison_type == 'equality' or (self.comparison_type is None and np.isscalar(value)):
if not any(value == x for x in choices):
raise ValueError('value {0} is not in valid choices: {1}'.format(value, choices))
if self.comparison_type == 'identity' or (self.comparison_type is None and not np.isscalar(value)):
if not any(value is x for x in choices):
raise ValueError('value {0} is not in valid choices: {1}'.format(value, choices))
super(SelectionCallbackProperty, self).__set__(instance, value)
[docs] def force_next_sync(self, instance):
self._force_next_sync[instance] = True
def _get_full_info(self, instance):
if self._force_next_sync.get(instance, False):
try:
return self.__get__(instance), random.random()
finally:
self._force_next_sync[instance] = False
else:
return self.__get__(instance), self.get_choices(instance), self.get_choice_labels(instance)
[docs] def get_display_func(self, instance):
return self._display.get(instance, self._default_display_func)
[docs] def set_display_func(self, instance, display):
self._display[instance] = display
# selection = self.__get__(instance)
# self.notify(instance, selection, selection)
[docs] def get_choices(self, instance):
return self._choices.get(instance, self.default_choices)
[docs] def get_choice_labels(self, instance):
display = self._display.get(instance, str)
labels = []
for choice in self.get_choices(instance):
if isinstance(choice, ChoiceSeparator):
labels.append(str(choice))
else:
labels.append(display(choice))
return labels
[docs] def set_choices(self, instance, choices):
self._choices[instance] = choices
self._choices_updated(instance, choices)
selection = self.__get__(instance)
self.notify(instance, selection, selection)
def _choices_updated(self, instance, choices):
if not choices:
self.__set__(instance, None)
return
selection = self.__get__(instance)
# We do the following because 'selection in choice' actually compares
# equality not identity (and we really just care about identity here)
# However, for simple Python types, we also need to check ==.
for choice in choices:
if selection is choice or (np.isscalar(choice) and
(np.isreal(choice) or isinstance(choice, str)) and
selection == choice):
return
choices_without_separators = [choice for choice in choices
if not isinstance(choice, ChoiceSeparator)]
if choices_without_separators:
try:
selection = choices_without_separators[self.default_index]
except IndexError:
if self.default_index > 0:
selection = choices_without_separators[-1]
else:
selection = choices_without_separators[0]
else:
selection = None
self.__set__(instance, selection)