# -*- 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.
"""
import os
import re
import datetime
import collections.abc
from guidata.dataset.datatypes import DataItem, ItemProperty
from guidata.utils import utf8_to_unicode, add_extension
from guidata.config import _
[docs]class NumericTypeItem(DataItem):
"""
Numeric data item
"""
type = None
def __init__(
self,
label,
default=None,
min=None,
max=None,
nonzero=None,
unit="",
help="",
check=True,
):
DataItem.__init__(self, label, default=default, help=help)
self.set_prop("data", min=min, max=max, nonzero=nonzero, check_value=check)
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 self.get_prop("data", "check_value", True):
return True
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"""
# 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)
* check [bool]: if False, value is not checked (optional, default=True)
"""
type = float
def __init__(
self,
label,
default=None,
min=None,
max=None,
nonzero=None,
unit="",
step=0.1,
slider=False,
help="",
check=True,
):
super(FloatItem, self).__init__(
label,
default=default,
min=min,
max=max,
nonzero=nonzero,
unit=unit,
help=help,
check=check,
)
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)
* check [bool]: if False, value is not checked (optional, default=True)
"""
type = int
def __init__(
self,
label,
default=None,
min=None,
max=None,
nonzero=None,
unit="",
even=None,
slider=False,
help="",
check=True,
):
super(IntItem, self).__init__(
label,
default=default,
min=min,
max=max,
nonzero=nonzero,
unit=unit,
help=help,
check=check,
)
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"""
if not self.get_prop("data", "check_value", True):
return True
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.0
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 = (str,)
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"""
return 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)
* check [bool]: if False, value is not checked (optional, default=True)
"""
type = bool
def __init__(self, text="", label="", default=None, help="", check=True):
DataItem.__init__(self, label, default=default, help=help, check=check)
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
[docs]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)
* check [bool]: if False, value is not checked (optional, default=True)
Color values are encoded as hexadecimal strings or Qt color names
"""
[docs] def check_value(self, value):
"""Override DataItem method"""
if not self.get_prop("data", "check_value", True):
return True
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)
* check [bool]: if False, value is not checked (optional, default=True)
"""
def __init__(
self,
label,
formats="*",
default=None,
basedir=None,
all_files_first=False,
help="",
check=True,
):
DataItem.__init__(self, label, default=default, help=help, check=check)
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 self.get_prop("data", "check_value", True):
return True
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)
* check [bool]: if False, value is not checked (optional, default=True)
"""
[docs] def check_value(self, value):
"""Override DataItem method"""
if not self.get_prop("data", "check_value", True):
return True
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)
* check [bool]: if False, value is not checked (optional, default=True)
"""
type = list
def __init__(
self,
label,
formats="*",
default=None,
basedir=None,
all_files_first=False,
help="",
check=True,
):
if isinstance(default, str):
default = [default]
FileSaveItem.__init__(
self,
label,
formats=formats,
default=default,
basedir=basedir,
all_files_first=all_files_first,
help=help,
check=check,
)
[docs] def check_value(self, value):
"""Override DataItem method"""
if not self.get_prop("data", "check_value", True):
return True
if value is None:
return False
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"""
if value.endswith("']") or 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 [fname 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)
* check [bool]: if False, value is not checked (optional, default=True)
"""
[docs] def check_value(self, value):
"""Override DataItem method"""
if not self.get_prop("data", "check_value", True):
return True
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)
* check [bool]: if False, value is not checked (optional, default=True)
* radio [bool]: if True, shows radio buttons instead of a combo box
(default is False)
"""
def __init__(
self, label, choices, default=FirstChoice, help="", check=True, radio=False
):
if isinstance(choices, collections.abc.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.abc.Callable):
default = _choices_data[0][0]
elif default is FirstChoice:
default = None
DataItem.__init__(self, label, default=default, help=help, check=check)
self.set_prop("data", choices=_choices_data)
self.set_prop("display", radio=radio)
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 str(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)
* check [bool]: if False, value is not checked (optional, default=True)
"""
def __init__(self, label, choices, default=(), help="", check=True):
ChoiceItem.__init__(self, label, choices, default, help, check=check)
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)
* check [bool]: if False, value is not checked (optional, default=True)
"""
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"
* check [bool]: if False, value is not checked (optional, default=True)
"""
def __init__(
self,
label,
default=None,
help="",
format="%.3f",
transpose=False,
minmax="all",
check=True,
):
DataItem.__init__(self, label, default=default, help=help, check=check)
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)
* check [bool]: if False, value is not checked (optional, default=True)
"""
def __init__(self, label, default=None, help="", check=True):
def dictedit(instance, item, value, parent):
from guidata.widgets.collectionseditor import CollectionsEditor
editor = CollectionsEditor(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,
check=check,
)
[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