1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387
|
"""This modules defines CSVListEditor.
A CSVListEditor provides an editor for lists of simple data types.
It allows the user to edit the list in a text field, using commas
(or optionally some other character) to separate the elements.
"""
#------------------------------------------------------------------------------
#
# Copyright (c) 2011, Enthought, Inc.
# All rights reserved.
#
# This software is provided without warranty under the terms of the BSD
# license included in enthought/LICENSE.txt and may be redistributed only
# under the conditions described in the aforementioned license. The license
# is also available online at http://www.enthought.com/licenses/BSD.txt
#
# Author: Warren Weckesser
# Date: July 2011
#
#------------------------------------------------------------------------------
from traits.api import Str, Int, Float, Enum, Range, Bool, Trait, TraitError
from traits.trait_handlers import RangeTypes
from .text_editor import TextEditor
from ..helper import enum_values_changed
def _eval_list_str(s, sep=',', item_eval=None,
ignore_trailing_sep=True):
"""Convert a string into a list.
Parameters
----------
s : str
The string to be converted.
sep : str or None
`sep` is the text separator of list items. If `sep` is None,
each contiguous stretch of whitespace is a separator.
item_eval : callable or None
`item_eval` is used to evaluate the list elements. If `item_eval`
is None, the list will be a list substrings of `s`.
ignore_trailing_sep : bool
If `ignore_trailing_sep` is False, it is an error to have a separator
at the end of the list (i.e. 'foo, bar,' is invalid).
If `ignore_trailing_sep` is True, a separator at the end of the
string `s` is ignored.
Returns
-------
values : list
List of converted values from the string.
"""
if item_eval is None:
item_eval = lambda x: x
s = s.strip()
if sep is not None and ignore_trailing_sep and s.endswith(sep):
s = s[:-len(sep)]
s = s.rstrip()
if s == '':
values = []
else:
values = [item_eval(x.strip()) for x in s.split(sep)]
return values
def _format_list_str(values, sep=',', item_format=str):
"""Convert a list to a string.
Each item in the list `values` is converted to a string with the
function `item_format`, and these are joined with `sep` plus a space.
If `sep` is None, a single space is used to join the items.
Parameters
----------
values : list
The list of values to be represented as a string.
sep : str
String used to join the items. A space is also added after
`sep`.
item_format : callable
Converts its single argument to a string.
Returns
-------
s : str
The result of converting the list to a string.
"""
if sep is None:
joiner = ' '
else:
joiner = sep + ' '
s = joiner.join(item_format(x) for x in values)
return s
def _validate_range_value(range_object, object, name, value):
"""Validate a Range value.
This function is used by the CSVListEditor to validate a value
when editing a list of ranges where the Range is dynamic (that
is, one or both of the 'low' and 'high' values are strings that
refer to other traits in `object`.
The function implements the same validation logic as in the method
traits.trait_types.BaseRange._set(), but does not call the
set_value() method; instead it simply returns the valid value.
If the value is not valid, range_object.error(...) is called.
Parameters
----------
range_object : instance of traits.trait_types.Range
object : instance of HasTraits
This is the HasTraits object that holds the traits
to which the one or both of range_object.low and
range_object.high refer.
name : str
The name of the List trait in `object`.
value : object (e.g. int, float, str)
The value to be validated.
Returns
-------
value : object
The validated value. It might not be the same
type as the input argument (e.g. if the range type
is float and the input value is an int, the return
value will be a float).
"""
low = eval(range_object._low)
high = eval(range_object._high)
if low is None and high is None:
if isinstance(value, RangeTypes):
return value
else:
new_value = range_object._typed_value(value, low, high)
satisfies_low = (low is None or low < new_value or
((not range_object._exclude_low) and (low == new_value)))
satisfies_high = (high is None or high > new_value or
((not range_object._exclude_high) and (high == new_value)))
if satisfies_low and satisfies_high:
return value
# Note: this is the only explicit use of 'object' and 'name'.
range_object.error(object, name, value)
def _prepare_method(cls, parent):
""" Unbound implementation of the prepare editor method to add a
change notification hook in the items of the list before calling
the parent prepare method of the parent class.
"""
name = cls.extended_name
if name != 'None':
cls.context_object.on_trait_change(cls._update_editor,
name + '[]',
dispatch='ui')
super(cls.__class__, cls).prepare(parent)
def _dispose_method(cls):
""" Unbound implementation of the dispose editor method to remove
the change notification hook in the items of the list before calling
the parent dispose method of the parent class.
"""
if cls.ui is None:
return
name = cls.extended_name
if name != 'None':
cls.context_object.on_trait_change(cls._update_editor,
name + '[]',
remove=True)
super(cls.__class__, cls).dispose()
class CSVListEditor(TextEditor):
"""A text editor for a List.
This editor provides a single line of input text of comma separated
values. (Actually, the default separator is a comma, but this can
changed.) The editor can only be used with List traits whose inner
trait is one of Int, Float, Str, Enum, or Range.
The 'simple', 'text', 'custom' and readonly styles are based on
TextEditor. The 'readonly' style provides the same formatting in the
text field as the other editors, but the user cannot change the value.
Like other Traits editors, the background of the text field will turn
red if the user enters an incorrectly formatted list or if the values
do not match the type of the inner trait. This validation only occurs
while editing the text field. If, for example, the inner trait is
Range(low='lower', high='upper'), a change in 'upper' will not trigger
the validation code of the editor.
The editor removes whitespace of entered items with strip(), so for
Str types, the editor should not be used if whitespace at the beginning
or end of the string must be preserved.
Constructor Keyword Arguments
-----------------------------
sep : str or None, optional
The separator of the values in the list. If None, each contiguous
sequence of whitespace is a separator.
Default is ','.
ignore_trailing_sep : bool, optional
If this is False, a line containing a trailing separator is invalid.
Default is True.
auto_set : bool
If True, then every keystroke sets the value of the trait.
enter_set : bool
If True, the user input sets the value when the Enter key is pressed.
Example
-------
The following will display a window containing a single input field.
Entering, say, '0, .5, 1' in this field will result in the list
x = [0.0, 0.5, 1.0].
>>> class Foo(HasTraits):
.... x = List(Range(low=0.0, high=1.0))
.... traits_view = View(Item('x', editor=CSVListEditor()))
.... def _x_changed(self):
.... print self.x
....
>>> f = Foo()
>>> f.configure_traits()
"""
# The separator of the element in the list.
sep = Trait(',', None, Str)
# If False, it is an error to have a trailing separator.
ignore_trailing_sep = Bool(True)
# Include some of the TextEditor API:
# Is user input set on every keystroke?
auto_set = Bool(True)
# Is user input set when the Enter key is pressed?
enter_set = Bool(False)
def _funcs(self, object, name):
"""Create the evalution and formatting functions for the editor.
Parameters
----------
object : instance of HasTraits
This is the object that has the List trait for which we are
creating an editor.
name : str
Name of the List trait on `object`.
Returns
-------
evaluate, fmt_func : callable, callable
The functions for converting a string to a list (`evaluate`)
and a list to a string (`fmt_func`). These are the functions
that are ultimately given as the keyword arguments 'evaluate'
and 'format_func' of the TextEditor that will be generated by
the CSVListEditor editor factory functions.
"""
t = getattr(object, name)
# Get the list of inner traits. Only a single inner trait is allowed.
it_list = t.trait.inner_traits()
if len(it_list) > 1:
raise TraitError("Only one inner trait may be specified when "
"using a CSVListEditor.")
# `it` is the single inner trait. This will be an instance of
# traits.traits.CTrait.
it = it_list[0]
# The following 'if' statement figures out the appropriate evaluation
# function (evaluate) and formatting function (fmt_func) for the
# given inner trait.
if it.is_trait_type(Int) or it.is_trait_type(Float) or \
it.is_trait_type(Str):
evaluate = \
lambda s: _eval_list_str(s, sep=self.sep,
item_eval=it.trait_type.evaluate,
ignore_trailing_sep=self.ignore_trailing_sep)
fmt_func = lambda vals: _format_list_str(vals,
sep=self.sep)
elif it.is_trait_type(Enum):
values, mapping, inverse_mapping = enum_values_changed(it)
evaluate = \
lambda s: _eval_list_str(s, sep=self.sep,
item_eval=mapping.__getitem__,
ignore_trailing_sep=self.ignore_trailing_sep)
fmt_func = \
lambda vals: \
_format_list_str(vals, sep=self.sep,
item_format=inverse_mapping.__getitem__)
elif it.is_trait_type(Range):
# Get the type of the values from the default value.
# range_object will be an instance of traits.trait_types.Range.
range_object = it.handler
if range_object.default_value_type == 8:
# range_object.default_value is callable.
defval = range_object.default_value(object)
else:
# range_object.default_value *is* the default value.
defval = range_object.default_value
typ = type(defval)
if range_object.validate is None:
# This will be the case for dynamic ranges.
item_eval = lambda s: _validate_range_value(
range_object, object, name, typ(s))
else:
# Static ranges have a validate method.
item_eval = lambda s: range_object.validate(
object, name, typ(s))
evaluate = \
lambda s: _eval_list_str(s, sep=self.sep,
item_eval=item_eval,
ignore_trailing_sep=self.ignore_trailing_sep)
fmt_func = lambda vals: _format_list_str(vals,
sep=self.sep)
else:
raise TraitError("To use a CSVListEditor, the inner trait of the "
"List must be Int, Float, Range, Str or Enum.")
return evaluate, fmt_func
#---------------------------------------------------------------------------
# Methods that generate backend toolkit-specific editors.
#---------------------------------------------------------------------------
def simple_editor ( self, ui, object, name, description, parent ):
""" Generates an editor using the "simple" style.
"""
self.evaluate, self.format_func = self._funcs(object, name)
return self.simple_editor_class( parent,
factory = self,
ui = ui,
object = object,
name = name,
description = description )
def custom_editor ( self, ui, object, name, description, parent ):
""" Generates an editor using the "custom" style.
"""
self.evaluate, self.format_func = self._funcs(object, name)
return self.custom_editor_class( parent,
factory = self,
ui = ui,
object = object,
name = name,
description = description )
def text_editor ( self, ui, object, name, description, parent ):
""" Generates an editor using the "text" style.
"""
self.evaluate, self.format_func = self._funcs(object, name)
return self.text_editor_class( parent,
factory = self,
ui = ui,
object = object,
name = name,
description = description )
def readonly_editor ( self, ui, object, name, description, parent ):
""" Generates an "editor" that is read-only.
"""
self.evaluate, self.format_func = self._funcs(object, name)
return self.readonly_editor_class( parent,
factory = self,
ui = ui,
object = object,
name = name,
description = description )
|