* feat: refactor settings class
pull/779/head
Maxim Medvedev 2025-02-01 16:06:48 +01:00 zatwierdzone przez GitHub
rodzic e0da70f7e5
commit e9a85b665f
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: B5690EEEBB952194
7 zmienionych plików z 141 dodań i 96 usunięć

Wyświetl plik

@ -24,7 +24,7 @@ from PySide6 import QtCore, QtGui, QtWidgets
from PySide6.QtCore import Qt, Signal
from PySide6.QtGui import QAction, QColor, QColorConstants
from ..Defaults import app_config
from ..Defaults import get_app_config
from ..Marker.Widget import Marker
from ..RFTools import Datapoint
@ -92,6 +92,7 @@ class ChartMarker(QtWidgets.QWidget):
self.qp = qp
def draw(self, x: int, y: int, color: QtGui.QColor, text: str = ""):
app_config = get_app_config()
offset = int(app_config.chart.marker_size // 2)
if app_config.chart.marker_at_tip:
y -= offset
@ -179,7 +180,7 @@ class Chart(QtWidgets.QWidget):
self.update()
def setMarkerSize(self, size) -> None:
app_config.chart.marker_size = size
get_app_config().chart.marker_size = size
self.update()
def setSweepTitle(self, title) -> None:

Wyświetl plik

@ -23,7 +23,7 @@ from PySide6.QtWidgets import QCheckBox, QSizePolicy
from NanoVNASaver import NanoVNASaver
from ..Defaults import app_config
from ..Defaults import get_app_config
from ..Marker.Widget import Marker
from .Control import Control
@ -32,6 +32,7 @@ logger = logging.getLogger(__name__)
class ShowButton(QtWidgets.QPushButton):
def setText(self, text: str = ""):
app_config = get_app_config()
if not text:
text = (
"Show data" if app_config.gui.markers_hidden else "Hide data"
@ -44,6 +45,7 @@ class MarkerControl(Control):
def __init__(self, app: NanoVNASaver):
super().__init__(app, "Markers")
app_config = get_app_config()
for i in range(app_config.chart.marker_count):
marker = Marker("", self.app.settings)
# marker.setFixedHeight(20)
@ -86,6 +88,7 @@ class MarkerControl(Control):
def toggle_frame(self):
def settings(hidden: bool):
app_config = get_app_config()
app_config.gui.markers_hidden = not hidden
self.app.marker_frame.setHidden(app_config.gui.markers_hidden)
self.showMarkerButton.setText()

Wyświetl plik

@ -22,6 +22,7 @@ from PySide6 import QtCore, QtWidgets
from NanoVNASaver import NanoVNASaver
from ..Defaults import SweepConfig, get_app_config
from ..Formatting import (
format_frequency_short,
format_frequency_sweep,
@ -37,6 +38,7 @@ class SweepControl(Control):
def __init__(self, app: NanoVNASaver):
super().__init__(app, "Sweep control")
sweep_settings = self.get_settings()
line = QtWidgets.QFrame()
line.setFrameShape(QtWidgets.QFrame.Shape.VLine)
@ -48,7 +50,7 @@ class SweepControl(Control):
input_layout.addLayout(input_right_layout)
self.layout.addRow(input_layout)
self.input_start = FrequencyInputWidget()
self.input_start = FrequencyInputWidget(sweep_settings.start)
self.input_start.setFixedHeight(20)
self.input_start.setMinimumWidth(60)
self.input_start.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight)
@ -56,14 +58,14 @@ class SweepControl(Control):
self.input_start.textChanged.connect(self.update_step_size)
input_left_layout.addRow(QtWidgets.QLabel("Start"), self.input_start)
self.input_end = FrequencyInputWidget()
self.input_end = FrequencyInputWidget(sweep_settings.end)
self.input_end.setFixedHeight(20)
self.input_end.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight)
self.input_end.textEdited.connect(self.update_center_span)
self.input_end.textChanged.connect(self.update_step_size)
input_left_layout.addRow(QtWidgets.QLabel("Stop"), self.input_end)
self.input_center = FrequencyInputWidget()
self.input_center = FrequencyInputWidget(sweep_settings.center)
self.input_center.setFixedHeight(20)
self.input_center.setMinimumWidth(60)
self.input_center.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight)
@ -71,16 +73,14 @@ class SweepControl(Control):
input_right_layout.addRow(QtWidgets.QLabel("Center"), self.input_center)
self.input_span = FrequencyInputWidget()
self.input_span = FrequencyInputWidget(sweep_settings.span)
self.input_span.setFixedHeight(20)
self.input_span.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight)
self.input_span.textEdited.connect(self.update_start_end)
input_right_layout.addRow(QtWidgets.QLabel("Span"), self.input_span)
self.input_segments = QtWidgets.QLineEdit(
self.app.settings.value("Segments", "1")
)
self.input_segments = QtWidgets.QLineEdit(sweep_settings.segments)
self.input_segments.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight)
self.input_segments.setFixedHeight(20)
self.input_segments.setFixedWidth(60)
@ -229,3 +229,14 @@ class SweepControl(Control):
def update_sweep_btn(self, enabled: bool) -> None:
self.btn_start.setEnabled(enabled)
def get_settings(self) -> SweepConfig:
return get_app_config().sweep_settings
def store_settings(self) -> None:
settings = self.get_settings()
settings.start = self.input_start.text()
settings.end = self.input_end.text()
settings.center = self.input_center.text()
settings.span = self.input_span.text()
settings.segments = self.input_segments.text()

Wyświetl plik

@ -123,6 +123,13 @@ class MarkersConfig:
default_factory=lambda: QColor(QColorConstants.LightGray)
)
@dataclass
class SweepConfig:
start: str = ""
end: str = ""
center: str = ""
span: str = ""
segments: str = "1"
@dataclass
class AppConfig:
@ -131,60 +138,25 @@ class AppConfig:
chart: ChartConfig = field(default_factory=ChartConfig)
chart_colors: ChartColorsConfig = field(default_factory=ChartColorsConfig)
markers: MarkersConfig = field(default_factory=MarkersConfig)
app_config = AppConfig()
def restore_config(settings: "AppSettings") -> AppConfig:
result = AppConfig()
for field_it in fields(result):
value = settings.restore_dataclass(
field_it.name.upper(), getattr(result, field_it.name)
)
setattr(result, field_it.name, value)
logger.debug("restored\n(\n%s\n)", result)
return result
def store_config(settings: "AppSettings", data: AppConfig | None = None) -> None:
data = data or app_config
logger.debug("storing\n(\n%s\n)", data)
assert isinstance(data, AppConfig)
for field_it in fields(data):
data_class = getattr(data, field_it.name)
assert is_dataclass(data_class)
settings.store_dataclass(field_it.name.upper(), data_class)
def _from_type(data) -> str:
type_map = {
bytearray: bytearray.hex,
QColor: QColor.getRgb,
QByteArray: QByteArray.toHex,
}
return (
f"{type_map[type(data)](data)}" if type(data) in type_map else f"{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)),
}
return (
type_map[data_type](data) if data_type in type_map else data_type(data)
)
sweep_settings: SweepConfig = field(default_factory=SweepConfig)
# noinspection PyDataclass
class AppSettings(QSettings):
def store_dataclass(self, name: str, data: object) -> None:
def __init__(self, organization: str = "NanoVNASaver", application: str = "NanoVNASaver") -> None:
super().__init__(
QSettings.Format.IniFormat,
QSettings.Scope.UserScope,
organization,
application)
self._app_config = AppConfig()
def get_app_config(self) -> AppConfig:
return _app_config
def _store_dataclass(self, name: str, data: object) -> None:
assert is_dataclass(data)
self.beginGroup(name)
for field_it in fields(data):
@ -200,10 +172,10 @@ class AppSettings(QSettings):
field_it.type,
)
raise TypeError from exc
self.setValue(field_it.name, _from_type(value))
self.setValue(field_it.name, AppSettings._from_type(value))
self.endGroup()
def restore_dataclass(self, name: str, data: object) -> object:
def _restore_dataclass(self, name: str, data: object) -> object:
assert is_dataclass(data)
result = replace(data)
@ -215,8 +187,64 @@ class AppSettings(QSettings):
setattr(result, field_it.name, default)
continue
try:
setattr(result, field_it.name, _to_type(value, field_it.type))
setattr(result, field_it.name, AppSettings._to_type(value, field_it.type))
except TypeError:
setattr(result, field_it.name, default)
self.endGroup()
return result
def restore_config(self) -> AppConfig:
logger.info("Loading settings from: %s", self.fileName())
result = AppConfig()
for field_it in fields(result):
value = self._restore_dataclass(
field_it.name.upper(), getattr(result, field_it.name)
)
setattr(result, field_it.name, value)
logger.debug("restored\n(\n%s\n)", result)
self._app_config = result
return get_app_config()
def store_config(self) -> None:
logger.info("Saving settings to: %s", self.fileName())
logger.debug("storing\n(\n%s\n)", _app_config)
for field_it in fields(_app_config):
data_class = getattr(_app_config, field_it.name)
assert is_dataclass(data_class)
self._store_dataclass(field_it.name.upper(), data_class)
@staticmethod
def _from_type(data) -> str:
type_map = {
bytearray: bytearray.hex,
QColor: QColor.getRgb,
QByteArray: QByteArray.toHex,
}
return (
f"{type_map[type(data)](data)}" if type(data) in type_map else f"{data}"
)
@staticmethod
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)),
}
return (
type_map[data_type](data) if data_type in type_map else data_type(data)
)
APP_SETTINGS = AppSettings()
_app_config = AppConfig()
def get_app_config() -> AppConfig:
return APP_SETTINGS.get_app_config()

Wyświetl plik

@ -18,7 +18,6 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>.
import contextlib
import logging
import sys
import threading
from time import localtime, strftime
@ -55,7 +54,7 @@ from .Charts.Chart import Chart
from .Controls.MarkerControl import MarkerControl
from .Controls.SerialControl import SerialControl
from .Controls.SweepControl import SweepControl
from .Defaults import AppSettings, app_config, restore_config, store_config
from .Defaults import AppSettings, get_app_config
from .Formatting import format_frequency, format_gain, format_vswr
from .Hardware.Hardware import Interface
from .Hardware.VNA import VNA
@ -94,14 +93,8 @@ class NanoVNASaver(QWidget):
self.communicate = Communicate()
self.s21att = 0.0
self.setWindowIcon(get_window_icon())
self.settings = AppSettings(
QtCore.QSettings.Format.IniFormat,
QtCore.QSettings.Scope.UserScope,
"NanoVNASaver",
"NanoVNASaver",
)
logger.info("Settings from: %s", self.settings.fileName())
app_config = restore_config(self.settings)
self.settings = AppSettings()
app_config = self.settings.restore_config()
self.threadpool = QtCore.QThreadPool()
self.sweep = Sweep()
self.worker = SweepWorker(self)
@ -492,8 +485,6 @@ class NanoVNASaver(QWidget):
self.s21_max_gain_label.setText("")
self.tdr_result_label.setText("")
self.settings.setValue("Segments", self.sweep_control.get_segments())
logger.debug("Starting worker thread")
self.threadpool.start(self.worker)
@ -690,17 +681,20 @@ class NanoVNASaver(QWidget):
self.bands.saveSettings()
self.threadpool.waitForDone(2500)
app_config = get_app_config()
app_config.chart.marker_count = Marker.count()
app_config.gui.window_width = self.width()
app_config.gui.window_height = self.height()
app_config.gui.splitter_sizes = self.splitter.saveState()
store_config(self.settings, app_config)
self.sweep_control.store_settings()
self.settings.store_config()
# Dosconnect connected devices and release serial port
self.serial_control.disconnect_device()
a0.accept()
sys.exit()
def changeFont(self, font: QtGui.QFont) -> None:
qf_new = QtGui.QFontMetricsF(font)

Wyświetl plik

@ -24,7 +24,7 @@ from PySide6.QtGui import QColor, QColorConstants, QPalette, QShortcut
from NanoVNASaver import NanoVNASaver
from ..Charts.Chart import Chart, ChartColors
from ..Defaults import app_config, store_config
from ..Defaults import get_app_config
from ..Marker.Widget import Marker
from .Bands import BandsWindow
from .Defaults import make_scrollable
@ -68,6 +68,7 @@ class DisplaySettingsWindow(QtWidgets.QWidget):
)
display_options_layout.addRow("", self.returnloss_is_positive)
app_config = get_app_config()
self.returnloss_is_positive.setChecked(
app_config.chart.returnloss_is_positive
)
@ -185,7 +186,7 @@ class DisplaySettingsWindow(QtWidgets.QWidget):
self.font_dropdown.setMinimumHeight(20)
self.font_dropdown.addItems(["7", "8", "9", "10", "11", "12"])
self.font_dropdown.setCurrentText(str(app_config.gui.font_size))
self.changeFont()
self.changeFont(str(app_config.gui.font_size))
self.font_dropdown.currentTextChanged.connect(self.changeFont)
font_options_layout.addRow("Font size", self.font_dropdown)
@ -422,6 +423,7 @@ class DisplaySettingsWindow(QtWidgets.QWidget):
def changeReturnLoss(self) -> None:
state = self.returnloss_is_positive.isChecked()
app_config = get_app_config()
app_config.chart.returnloss_is_positive = bool(state)
for m in self.app.markers:
m.returnloss_is_positive = state
@ -433,43 +435,51 @@ class DisplaySettingsWindow(QtWidgets.QWidget):
def changeShowLines(self) -> None:
state = self.show_lines_option.isChecked()
app_config = get_app_config()
app_config.chart.show_lines = bool(state)
for c in self.app.subscribing_charts:
c.setDrawLines(state)
def changeShowMarkerNumber(self) -> None:
app_config = get_app_config()
app_config.chart.marker_label = bool(
self.show_marker_number_option.isChecked()
)
self.updateCharts()
def changeFilledMarkers(self):
app_config = get_app_config()
app_config.chart.marker_filled = bool(
self.filled_marker_option.isChecked()
)
self.updateCharts()
def changeMarkerAtTip(self) -> None:
app_config = get_app_config()
app_config.chart.marker_at_tip = bool(self.marker_at_tip.isChecked())
self.updateCharts()
def changePointSize(self, size: int) -> None:
app_config = get_app_config()
app_config.chart.point_size = size
for c in self.app.subscribing_charts:
c.setPointSize(size)
def changeLineThickness(self, size: int) -> None:
app_config = get_app_config()
app_config.chart.line_thickness = size
for c in self.app.subscribing_charts:
c.setLineThickness(size)
def changeMarkerSize(self, size: int) -> None:
app_config = get_app_config()
app_config.chart.marker_size = size
self.markerSizeInput.setValue(size)
self.updateCharts()
def changeDarkMode(self) -> None:
state = self.dark_mode_option.isChecked()
app_config = get_app_config()
app_config.gui.dark_mode = bool(state)
Chart.color.foreground = QColor(QColorConstants.LightGray)
if state:
@ -516,8 +526,9 @@ class DisplaySettingsWindow(QtWidgets.QWidget):
for c in self.app.subscribing_charts:
c.update()
def changeFont(self) -> None:
font_size = int(self.font_dropdown.currentText())
def changeFont(self, new_font_size: str) -> None:
font_size = int(new_font_size)
app_config = get_app_config()
app_config.gui.font_size = font_size
app: QtWidgets.QApplication = QtWidgets.QApplication.instance()
font = app.font()
@ -607,4 +618,4 @@ class DisplaySettingsWindow(QtWidgets.QWidget):
def updateCharts(self) -> None:
for c in self.app.subscribing_charts:
c.update()
store_config(self.app.settings, app_config)
self.app.settings.sync()

Wyświetl plik

@ -19,7 +19,7 @@
import unittest
from dataclasses import dataclass, field
from NanoVNASaver.Defaults import AppConfig, AppSettings, GuiConfig, QSettings, restore_config, store_config
from NanoVNASaver.Defaults import AppSettings, GuiConfig
@dataclass
@ -38,14 +38,10 @@ class TestCases(unittest.TestCase):
def setUp(self) -> None:
self.settings_1 = AppSettings(
QSettings.Format.IniFormat,
QSettings.Scope.UserScope,
"NanoVNASaver",
"Test_1",
)
self.settings_2 = AppSettings(
QSettings.Format.IniFormat,
QSettings.Scope.UserScope,
"NanoVNASaver",
"Test_2",
)
@ -59,8 +55,8 @@ class TestCases(unittest.TestCase):
)
def test_store_dataclass(self):
self.settings_1.store_dataclass("Section1", self.config_1)
self.settings_1.store_dataclass("Section2", self.config_2)
self.settings_1._store_dataclass("Section1", self.config_1)
self.settings_1._store_dataclass("Section2", self.config_2)
illegal_config = TConfig(
my_int=4,
my_float=3.0,
@ -69,11 +65,11 @@ class TestCases(unittest.TestCase):
my_list=(4, 5, 6),
)
with self.assertRaises(TypeError):
self.settings_1.store_dataclass("SectionX", illegal_config)
self.settings_1._store_dataclass("SectionX", illegal_config)
def test_restore_dataclass(self):
tc_1 = self.settings_1.restore_dataclass("Section1", self.config_1)
tc_2 = self.settings_1.restore_dataclass("Section2", self.config_2)
tc_1 = self.settings_1._restore_dataclass("Section1", self.config_1)
tc_2 = self.settings_1._restore_dataclass("Section2", self.config_2)
self.assertNotEqual(tc_1, tc_2)
self.assertEqual(tc_1, self.config_1)
self.assertEqual(tc_2, self.config_2)
@ -86,14 +82,15 @@ class TestCases(unittest.TestCase):
self.assertIsInstance(tc_2.my_float, float)
def test_restore_empty(self):
tc_3 = self.settings_1.restore_dataclass("Section3", TConfig())
tc_3 = self.settings_1._restore_dataclass("Section3", TConfig())
self.assertEqual(tc_3, TConfig())
def test_store(self):
tc_1 = AppConfig()
tc_1 = self.settings_2.get_app_config()
tc_1.gui.dark_mode = not tc_1.gui.dark_mode
store_config(self.settings_2, tc_1)
tc_2 = restore_config(self.settings_2)
self.settings_2.store_config()
tc_2 = self.settings_2.restore_config()
print(f"\n{tc_1}\n{tc_2}\n")
self.assertEqual(tc_1, tc_2)
self.assertNotEqual(tc_2.gui, GuiConfig())