diff --git a/CHANGELOG.md b/CHANGELOG.md index 5582560..eeefa45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,15 @@ Changelog ========= +0.5.0 pre +--------- + + - Fix crash on open in use serial device + - Use a Defaults module for all settings - + ignores old .ini settings + - Refactoring and unifying Chart classes + - No more automatic update checks (more privacy) + 0.4.0 ----- diff --git a/NanoVNASaver/Defaults.py b/NanoVNASaver/Defaults.py index ed6cc2f..51fa66a 100644 --- a/NanoVNASaver/Defaults.py +++ b/NanoVNASaver/Defaults.py @@ -19,13 +19,15 @@ import dataclasses as DC import logging -import json - -from PyQt5.QtCore import QSettings +from ast import literal_eval +from PyQt5 import QtCore +from PyQt5.QtCore import QSettings, QByteArray +from PyQt5.QtGui import QColor logger = logging.getLogger(__name__) + # pylint: disable=too-few-public-methods # pylint: disable=too-many-instance-attributes @DC.dataclass @@ -33,12 +35,22 @@ class GUI: window_height: int = 950 window_width: int = 1433 font_size: int = 8 + custom_colors: bool = False dark_mode: bool = False - # TODO: implement QByteArray - splitter_sizes: bytearray = DC.field(default_factory=bytearray) + splitter_sizes: QByteArray = DC.field(default_factory=QByteArray) markers_hidden: bool = False +@DC.dataclass +class ChartsSelected: + chart_00: str = 'S11 Smith Chart' + chart_01: str = 'S11 Return Loss' + chart_02: str = 'None' + chart_10: str = 'S21 Polar Plot' + chart_11: str = 'S21 Gain' + chart_12: str = 'None' + + @DC.dataclass class Chart: point_size: int = 2 @@ -50,16 +62,51 @@ class Chart: marker_at_tip: bool = False marker_size: int = 8 returnloss_is_positive: bool = False + show_bands: bool = False + vswr_lines: list = DC.field(default_factory=lambda: []) + +@DC.dataclass +class ChartColors: # pylint: disable=too-many-instance-attributes + background: QColor = QColor(QtCore.Qt.white) + foreground: QColor = QColor(QtCore.Qt.lightGray) + reference: QColor = QColor(0, 0, 255, 64) + reference_secondary: QColor = QColor(0, 0, 192, 48) + sweep: QColor = QColor(QtCore.Qt.darkYellow) + sweep_secondary: QColor = QColor(QtCore.Qt.darkMagenta) + swr: QColor = QColor(255, 0, 0, 128) + text: QColor = QColor(QtCore.Qt.black) + bands: QColor = QColor(128, 128, 128, 48) + + +@DC.dataclass +class Markers: + active_labels: list = DC.field(default_factory=lambda: [ + "actualfreq", "impedance", "serr", "serl", "serc", "parr", "parlc", + "vswr", "returnloss", "s11q", "s11phase", "s21gain", "s21phase", + ]) + colored_names: bool = True + color_0: QColor = QColor(QtCore.Qt.darkGray) + color_1: QColor = QColor(255, 0, 0) + color_2: QColor = QColor(0, 255, 0) + color_3: QColor = QColor(0, 0, 255) + color_4: QColor = QColor(0, 255, 255) + color_5: QColor = QColor(255, 0, 255) + color_6: QColor = QColor(255, 255, 0) + color_7: QColor = QColor(QtCore.Qt.lightGray) @DC.dataclass class CFG: gui: object = GUI() + charts_selected: object = ChartsSelected() chart: object = Chart() + chart_colors: object = ChartColors() + markers: object = Markers() cfg = CFG() + def restore(settings: 'AppSettings') -> CFG: result = CFG() for field in DC.fields(result): @@ -70,15 +117,42 @@ def restore(settings: 'AppSettings') -> CFG: return result -def store(settings: 'AppSettings', data: CFG) -> None: +def store(settings: 'AppSettings', data: CFG = None) -> None: + data = data or cfg logger.debug("storing\n(\n%s\n)", data) assert isinstance(data, CFG) for field in DC.fields(data): - data_class = getattr(data, field.name) + data_class = getattr(data, field.name) assert DC.is_dataclass(data_class) settings.store_dataclass(field.name.upper(), data_class) +def from_type(data) -> str: + type_map = { + bytearray: lambda x: x.hex(), + QColor: lambda x: x.getRgb(), + QByteArray: lambda x: x.toHex(), + } + if type(data) in type_map: + return str(type_map[type(data)](data)) + return str(data) + + +def to_type(data: object, data_type: type) -> object: + type_map = { + bool: lambda x: x.lower() == 'true', + bytearray: bytearray.fromhex, + list: literal_eval, + tuple: literal_eval, + QColor: lambda x: QColor.fromRgb(*literal_eval(x)), + QByteArray: lambda x: QByteArray.fromHex(literal_eval(x)), + } + if data_type in type_map: + return type_map[data_type](data) + return data_type(data) + + +# noinspection PyDataclass class AppSettings(QSettings): def store_dataclass(self, name: str, data: object) -> None: assert DC.is_dataclass(data) @@ -87,16 +161,11 @@ class AppSettings(QSettings): value = getattr(data, field.name) try: assert isinstance(value, field.type) - except AssertionError: - logger.error("%s: %s is not a %s", name, field.name, - field.type) - continue - if field.type not in (int, float, str, bool): - try: - value = json.dumps(value) - except TypeError: - value = field.type(value).hex() - self.setValue(field.name, value) + except AssertionError as exc: + logger.error("%s: %s of type %s is not a %s", + name, field.name, type(value), field.type) + raise TypeError from exc + self.setValue(field.name, from_type(value)) self.endGroup() def restore_dataclass(self, name: str, data: object) -> object: @@ -105,21 +174,14 @@ class AppSettings(QSettings): result = DC.replace(data) self.beginGroup(name) for field in DC.fields(data): - value = None - if field.type in (int, float, str, bool): - value = self.value(field.name, - type=field.type, - defaultValue=field.default) - else: - default = getattr(data, field.name) - try: - value = json.loads( - self.value(field.name, type=str, - defaultValue=json.dumps(default))) - except TypeError: - value = self.value(field.name) - value = bytes.fromhex(value) if value is str else default - setattr(result, field.name, field.type(value)) + default = getattr(data, field.name) + value = self.value(field.name, type=str, defaultValue="") + if not value: + setattr(result, field.name, default) + continue + try: + setattr(result, field.name, to_type(value, field.type)) + except TypeError: + setattr(result, field.name, default) self.endGroup() - return result diff --git a/README.md b/README.md index 0e277a6..d4c5dc0 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,14 @@ points, and generally display and analyze the resulting data. Latest Changes -------------- +### Changes in 0.5.0 pre + + - Fix crash on open in use serial device + - Use a Defaults module for all settings - + ignores old .ini settings + - Refactoring and unifying Chart classes + - No more automatic update checks (more privacy) + ### Changes in 0.4.0 - PA0JOZ Enhanced Response Correction @@ -36,12 +44,6 @@ Latest Changes - Reference plane applied after calibration - Calibration fixes by DiSlord -### Changes in v0.3.9 - -- TX Power on V2 -- New analysis -- Magnitude Z Chart -- VSWR Chart improvements Introduction ------------