# -*- coding: utf-8 -*-
#
# Copyright © 2009-2010 CEA
# Pierre Raybaut
# Licensed under the terms of the CECILL License
# (see guidata/__init__.py for details)
"""
dataset.dataitems
=================
The ``guidata.dataset.dataitems`` module contains implementation for
concrete DataItems.
"""
from __future__ import division, unicode_literals
import os
import re
import datetime
import collections
from guidata.dataset.datatypes import DataItem, ItemProperty
from guidata.utils import utf8_to_unicode, add_extension
from guidata.config import _
from guidata.py3compat import to_text_string, is_text_string, TEXT_TYPES
[docs]class NumericTypeItem(DataItem):
"""
Numeric data item
"""
type = None
def __init__(self, label, default=None, min=None, max=None,
nonzero=None, unit='', help=''):
DataItem.__init__(self, label, default=default, help=help)
self.set_prop("data", min=min, max=max, nonzero=nonzero)
self.set_prop("display", unit=unit)
[docs] def get_auto_help(self, instance):
"""Override DataItem method"""
auto_help = {int: _('integer'), float: _('float')}[self.type]
_min = self.get_prop_value("data", instance, "min")
_max = self.get_prop_value("data", instance, "max")
nonzero = self.get_prop_value("data", instance, "nonzero")
unit = self.get_prop_value("display", instance, "unit")
if _min is not None and _max is not None:
auto_help += _(" between ")+str(_min)+ _(" and ")+str(_max)
elif _min is not None:
auto_help += _(" higher than ")+str(_min)
elif _max is not None:
auto_help += _(" lower than ")+str(_max)
if nonzero:
auto_help += ", "+_("non zero")
if unit:
auto_help += (", %s %s" % (_("unit:"), unit))
return auto_help
[docs] def check_value(self, value):
"""Override DataItem method"""
if not isinstance(value, self.type):
return False
if self.get_prop("data", "nonzero") and value == 0:
return False
_min = self.get_prop("data", "min")
if _min is not None:
if value < _min:
return False
_max = self.get_prop("data", "max")
if _max is not None:
if value > _max:
return False
return True
[docs] def from_string(self, value):
"""Override DataItem method"""
value = to_text_string(value) # necessary if value is a QString
# String may contains numerical operands:
if re.match(r'^([\d\(\)\+/\-\*.]|e)+$', value):
try:
return self.type(eval(value))
except:
pass
return None
[docs]class FloatItem(NumericTypeItem):
"""
Construct a float data item
* label [string]: name
* default [float]: default value (optional)
* min [float]: minimum value (optional)
* max [float]: maximum value (optional)
* slider [bool]: if True, shows a slider widget right after the line
edit widget (default is False)
* step [float]: step between tick values with a slider widget (optional)
* nonzero [bool]: if True, zero is not a valid value (optional)
* unit [string]: physical unit (optional)
* help [string]: text shown in tooltip (optional)
"""
type = float
def __init__(self, label, default=None, min=None, max=None,
nonzero=None, unit='', step=0.1, slider=False, help=''):
super(FloatItem, self).__init__(label, default=default, min=min,
max=max, nonzero=nonzero, unit=unit, help=help)
self.set_prop("display", slider=slider)
self.set_prop("data", step=step)
[docs] def get_value_from_reader(self, reader):
"""Reads value from the reader object, inside the try...except
statement defined in the base item `deserialize` method"""
return reader.read_float()
[docs]class IntItem(NumericTypeItem):
"""
Construct an integer data item
* label [string]: name
* default [int]: default value (optional)
* min [int]: minimum value (optional)
* max [int]: maximum value (optional)
* nonzero [bool]: if True, zero is not a valid value (optional)
* unit [string]: physical unit (optional)
* even [bool]: if True, even values are valid, if False,
odd values are valid if None (default), ignored (optional)
* slider [bool]: if True, shows a slider widget right after the line
edit widget (default is False)
* help [string]: text shown in tooltip (optional)
"""
type = int
def __init__(self, label, default=None, min=None, max=None,
nonzero=None, unit='', even=None, slider=False, help=''):
super(IntItem, self).__init__(label, default=default, min=min, max=max,
nonzero=nonzero, unit=unit, help=help)
self.set_prop("data", even=even)
self.set_prop("display", slider=slider)
[docs] def get_auto_help(self, instance):
"""Override DataItem method"""
auto_help = super(IntItem, self).get_auto_help(instance)
even = self.get_prop_value("data", instance, "even")
if even is not None:
if even:
auto_help += ", "+_("even")
else:
auto_help += ", "+_("odd")
return auto_help
[docs] def check_value(self, value):
"""Override DataItem method"""
valid = super(IntItem, self).check_value(value)
if not valid:
return False
even = self.get_prop("data", "even")
if even is not None:
is_even = value//2 == value/2.
if (even and not is_even) or (not even and is_even):
return False
return True
[docs] def get_value_from_reader(self, reader):
"""Reads value from the reader object, inside the try...except
statement defined in the base item `deserialize` method"""
return reader.read_int()
[docs]class StringItem(DataItem):
"""
Construct a string data item
* label [string]: name
* default [string]: default value (optional)
* help [string]: text shown in tooltip (optional)
* notempty [bool]: if True, empty string is not a valid value (opt.)
* wordwrap [bool]: toggle word wrapping (optional)
"""
type = TEXT_TYPES
def __init__(self, label, default=None, notempty=None,
wordwrap=False, help=''):
DataItem.__init__(self, label, default=default, help=help)
self.set_prop("data", notempty=notempty)
self.set_prop("display", wordwrap=wordwrap)
[docs] def check_value(self, value):
"""Override DataItem method"""
notempty = self.get_prop("data", "notempty")
if notempty and not value:
return False
return True
[docs] def from_string(self, value):
"""Override DataItem method"""
# QString -> str
return to_text_string(value)
[docs] def get_value_from_reader(self, reader):
"""Reads value from the reader object, inside the try...except
statement defined in the base item `deserialize` method"""
return reader.read_unicode()
[docs]class TextItem(StringItem):
"""
Construct a text data item (multiline string)
* label [string]: name
* default [string]: default value (optional)
* help [string]: text shown in tooltip (optional)
* notempty [bool]: if True, empty string is not a valid value (opt.)
* wordwrap [bool]: toggle word wrapping (optional)
"""
def __init__(self, label, default=None, notempty=None,
wordwrap=True, help=''):
StringItem.__init__(self, label, default=default, notempty=notempty,
wordwrap=wordwrap, help=help)
[docs]class BoolItem(DataItem):
"""
Construct a boolean data item
* text [string]: form's field name (optional)
* label [string]: name
* default [string]: default value (optional)
* help [string]: text shown in tooltip (optional)
"""
type = bool
def __init__(self, text='', label='', default=None, help=''):
DataItem.__init__(self, label, default=default, help=help)
self.set_prop("display", text=text)
[docs] def get_value_from_reader(self, reader):
"""Reads value from the reader object, inside the try...except
statement defined in the base item `deserialize` method"""
return reader.read_bool()
[docs]class DateItem(DataItem):
"""
Construct a date data item.
* text [string]: form's field name (optional)
* label [string]: name
* default [datetime.date]: default value (optional)
* help [string]: text shown in tooltip (optional)
"""
type = datetime.date
class DateTimeItem(DateItem):
pass
[docs]class ColorItem(StringItem):
"""
Construct a color data item
* label [string]: name
* default [string]: default value (optional)
* help [string]: text shown in tooltip (optional)
Color values are encoded as hexadecimal strings or Qt color names
"""
[docs] def check_value(self, value):
"""Override DataItem method"""
if not isinstance(value, self.type):
return False
from guidata.qthelpers import text_to_qcolor
return text_to_qcolor(value).isValid()
[docs] def get_value_from_reader(self, reader):
"""Reads value from the reader object, inside the try...except
statement defined in the base item `deserialize` method"""
# Using read_str converts `numpy.string_` to `str` -- otherwise,
# when passing the string to a QColor Qt object, any numpy.string_ will
# be interpreted as no color (black)
return reader.read_str()
[docs]class FileSaveItem(StringItem):
"""
Construct a path data item for a file to be saved
* label [string]: name
* formats [string (or string list)]: wildcard filter
* default [string]: default value (optional)
* basedir [string]: default base directory (optional)
* help [string]: text shown in tooltip (optional)
"""
def __init__(self, label, formats='*', default=None,
basedir=None, all_files_first=False, help=''):
DataItem.__init__(self, label, default=default, help=help)
if isinstance(formats, str):
formats = [formats]
self.set_prop("data", formats=formats)
self.set_prop("data", basedir=basedir)
self.set_prop("data", all_files_first=all_files_first)
[docs] def get_auto_help(self, instance):
"""Override DataItem method"""
formats = self.get_prop("data", "formats")
return _("all file types") if formats == ['*'] \
else _("supported file types:") + " *.%s" % ", *.".join(formats)
[docs] def check_value(self, value):
"""Override DataItem method"""
if not isinstance(value, self.type):
return False
return len(value)>0
[docs] def from_string(self, value):
"""Override DataItem method"""
return add_extension(self, value)
[docs]class FileOpenItem(FileSaveItem):
"""
Construct a path data item for a file to be opened
* label [string]: name
* formats [string (or string list)]: wildcard filter
* default [string]: default value (optional)
* basedir [string]: default base directory (optional)
* help [string]: text shown in tooltip (optional)
"""
[docs] def check_value(self, value):
"""Override DataItem method"""
if not isinstance(value, self.type):
return False
return os.path.exists(value) and os.path.isfile(value)
[docs]class FilesOpenItem(FileSaveItem):
"""
Construct a path data item for multiple files to be opened.
* label [string]: name
* formats [string (or string list)]: wildcard filter
* default [string]: default value (optional)
* basedir [string]: default base directory (optional)
* help [string]: text shown in tooltip (optional)
"""
type = list
def __init__(self, label, formats='*', default=None,
basedir=None, all_files_first=False, help=''):
if is_text_string(default):
default = [default]
FileSaveItem.__init__(self, label, formats=formats,
default=default, basedir=basedir,
all_files_first=all_files_first, help=help)
[docs] def check_value(self, value):
"""Override DataItem method"""
allexist = True
for path in value:
allexist = allexist and os.path.exists(path) \
and os.path.isfile(path)
return allexist
[docs] def from_string(self, value):
"""Override DataItem method"""
value = to_text_string(value)
if value.endswith("']"):
value = eval(value)
else:
value = [value]
return [add_extension(self, path) for path in value]
[docs] def serialize(self, instance, writer):
"""Serialize this item"""
value = self.get_value(instance)
writer.write_sequence([fname.encode("utf-8") for fname in value])
[docs] def get_value_from_reader(self, reader):
"""Reads value from the reader object, inside the try...except
statement defined in the base item `deserialize` method"""
return [to_text_string(fname, "utf-8")
for fname in reader.read_sequence()]
[docs]class DirectoryItem(StringItem):
"""
Construct a path data item for a directory.
* label [string]: name
* default [string]: default value (optional)
* help [string]: text shown in tooltip (optional)
"""
[docs] def check_value(self, value):
"""Override DataItem method"""
if not isinstance(value, self.type):
return False
return os.path.exists(value) and os.path.isdir(value)
class FirstChoice(object):
pass
[docs]class ChoiceItem(DataItem):
"""
Construct a data item for a list of choices.
* label [string]: name
* choices [list, tuple or function]: string list or (key, label) list
function of two arguments (item, value) returning a list of tuples
(key, label, image) where image is an icon path, a QIcon instance
or a function of one argument (key) returning a QIcon instance
* default [-]: default label or default key (optional)
* help [string]: text shown in tooltip (optional)
"""
def __init__(self, label, choices, default=FirstChoice, help=''):
if isinstance(choices, collections.Callable):
_choices_data = ItemProperty(choices)
else:
_choices_data = []
for idx, choice in enumerate(choices):
_choices_data.append( self._normalize_choice(idx, choice) )
if default is FirstChoice and\
not isinstance(choices, collections.Callable):
default = _choices_data[0][0]
elif default is FirstChoice:
default = None
DataItem.__init__(self, label, default=default, help=help)
self.set_prop("data", choices=_choices_data )
def _normalize_choice(self, idx, choice_tuple):
if isinstance(choice_tuple, tuple):
key, value = choice_tuple
else:
key = idx
value = choice_tuple
if isinstance(value, str):
value = utf8_to_unicode(value)
return (key, value, None)
# def _choices(self, item):
# _choices_data = self.get_prop("data", "choices")
# if callable(_choices_data):
# return _choices_data(self, item)
# return _choices_data
[docs] def get_string_value(self, instance):
"""Override DataItem method"""
value = self.get_value(instance)
choices = self.get_prop_value("data", instance, "choices")
#print "ShowChoiceWidget:", choices, value
for choice in choices:
if choice[0] == value:
return to_text_string(choice[1])
else:
return DataItem.get_string_value(self, instance)
[docs]class MultipleChoiceItem(ChoiceItem):
"""
Construct a data item for a list of choices -- multiple choices can be selected
* label [string]: name
* choices [list or tuple]: string list or (key, label) list
* default [-]: default label or default key (optional)
* help [string]: text shown in tooltip (optional)
"""
def __init__(self, label, choices, default=(), help=''):
ChoiceItem.__init__(self, label, choices, default, help)
self.set_prop("display", shape = (1, -1))
[docs] def horizontal(self, row_nb=1):
"""
Method to arange choice list horizontally on `n` rows
Example:
nb = MultipleChoiceItem("Number", ['1', '2', '3'] ).horizontal(2)
"""
self.set_prop("display", shape = (row_nb, -1))
return self
[docs] def vertical(self, col_nb=1):
"""
Method to arange choice list vertically on `n` columns
Example:
nb = MultipleChoiceItem("Number", ['1', '2', '3'] ).vertical(2)
"""
self.set_prop("display", shape = (-1, col_nb))
return self
[docs] def serialize(self, instance, writer):
"""Serialize this item"""
value = self.get_value(instance)
seq = []
_choices = self.get_prop_value("data", instance, "choices")
for key, _label, _img in _choices:
seq.append( key in value )
writer.write_sequence( seq )
[docs] def deserialize(self, instance, reader):
"""Deserialize this item"""
flags = reader.read_sequence()
# We could have trouble with objects providing their own choice
# function which depend on not yet deserialized values
_choices = self.get_prop_value("data", instance, "choices")
value = []
for idx, flag in enumerate(flags):
if flag:
value.append( _choices[idx][0] )
self.__set__(instance, value)
[docs]class ImageChoiceItem(ChoiceItem):
"""
Construct a data item for a list of choices with images
* label [string]: name
* choices [list, tuple or function]: (label, image) list or
(key, label, image) list function of two arguments (item, value)
returning a list of tuples (key, label, image) where image is an
icon path, a QIcon instance or a function of one argument (key)
returning a QIcon instance
* default [-]: default label or default key (optional)
* help [string]: text shown in tooltip (optional)
"""
def _normalize_choice(self, idx, choice_tuple):
assert isinstance(choice_tuple, tuple)
if len(choice_tuple) == 3:
key, value, img = choice_tuple
else:
key = idx
value, img = choice_tuple
if isinstance(value, str):
value = utf8_to_unicode(value)
return (key, value, img)
[docs]class FloatArrayItem(DataItem):
"""
Construct a float array data item
* label [string]: name
* default [numpy.ndarray]: default value (optional)
* help [string]: text shown in tooltip (optional)
* format [string]: formatting string (example: '%.3f') (optional)
* transpose [bool]: transpose matrix (display only)
* large [bool]: view all float of the array
* minmax [string]: "all" (default), "columns", "rows"
"""
def __init__(self, label, default=None, help='', format='%.3f',
transpose=False, minmax="all"):
DataItem.__init__(self, label, default=default, help=help)
self.set_prop("display", format=format, transpose=transpose,
minmax=minmax)
[docs] def serialize(self, instance, writer):
"""Serialize this item"""
value = self.get_value(instance)
writer.write_array(value)
[docs] def get_value_from_reader(self, reader):
"""Reads value from the reader object, inside the try...except
statement defined in the base item `deserialize` method"""
return reader.read_array()
[docs]class DictItem(ButtonItem):
"""
Construct a dictionary data item
* label [string]: name
* default [dict]: default value (optional)
* help [string]: text shown in tooltip (optional)
"""
def __init__(self, label, default=None, help=''):
def dictedit(instance, item, value, parent):
from spyderlib.widgets.dicteditor import DictEditor
editor = DictEditor(parent)
value_was_none = value is None
if value_was_none:
value = {}
editor.setup(value)
if editor.exec_():
return editor.get_value()
else:
if value_was_none:
return
return value
ButtonItem.__init__(self, label, dictedit,
icon='dictedit.png', default=default, help=help)
[docs]class FontFamilyItem(StringItem):
"""
Construct a font family name item
* label [string]: name
* default [string]: default value (optional)
* help [string]: text shown in tooltip (optional)
"""
pass