Skip to content

Commit

Permalink
Fix/settings (#778)
Browse files Browse the repository at this point in the history
* feat: refactor settings class
  • Loading branch information
redrathnure authored Feb 1, 2025
1 parent e0da70f commit e9a85b6
Show file tree
Hide file tree
Showing 7 changed files with 139 additions and 94 deletions.
5 changes: 3 additions & 2 deletions src/NanoVNASaver/Charts/Chart.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
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

Expand Down Expand Up @@ -92,6 +92,7 @@ def __init__(self, qp: QtGui.QPaintDevice):
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
Expand Down Expand Up @@ -179,7 +180,7 @@ def setPointSize(self, size) -> None:
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:
Expand Down
5 changes: 4 additions & 1 deletion src/NanoVNASaver/Controls/MarkerControl.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

from NanoVNASaver import NanoVNASaver

from ..Defaults import app_config
from ..Defaults import get_app_config
from ..Marker.Widget import Marker
from .Control import Control

Expand All @@ -32,6 +32,7 @@

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"
Expand All @@ -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)
Expand Down Expand Up @@ -86,6 +88,7 @@ def __init__(self, app: NanoVNASaver):

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()
Expand Down
25 changes: 18 additions & 7 deletions src/NanoVNASaver/Controls/SweepControl.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

from NanoVNASaver import NanoVNASaver

from ..Defaults import SweepConfig, get_app_config
from ..Formatting import (
format_frequency_short,
format_frequency_sweep,
Expand All @@ -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)

Expand All @@ -48,39 +50,37 @@ def __init__(self, app: NanoVNASaver):
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)
self.input_start.textEdited.connect(self.update_center_span)
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)
self.input_center.textEdited.connect(self.update_start_end)

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)
Expand Down Expand Up @@ -229,3 +229,14 @@ def update_sweep(self):

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()
130 changes: 79 additions & 51 deletions src/NanoVNASaver/Defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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)
sweep_settings: SweepConfig = field(default_factory=SweepConfig)


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}"
)
# noinspection PyDataclass
class AppSettings(QSettings):

def __init__(self, organization: str = "NanoVNASaver", application: str = "NanoVNASaver") -> None:
super().__init__(
QSettings.Format.IniFormat,
QSettings.Scope.UserScope,
organization,
application)

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)
)
self._app_config = AppConfig()

def get_app_config(self) -> AppConfig:
return _app_config

# noinspection PyDataclass
class AppSettings(QSettings):
def store_dataclass(self, name: str, data: object) -> None:
def _store_dataclass(self, name: str, data: object) -> None:
assert is_dataclass(data)
self.beginGroup(name)
for field_it in fields(data):
Expand All @@ -200,10 +172,10 @@ def store_dataclass(self, name: str, data: object) -> None:
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)
Expand All @@ -215,8 +187,64 @@ def restore_dataclass(self, name: str, data: object) -> object:
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()
22 changes: 8 additions & 14 deletions src/NanoVNASaver/NanoVNASaver.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -55,7 +54,7 @@
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
Expand Down Expand Up @@ -94,14 +93,8 @@ def __init__(self) -> None:
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)
Expand Down Expand Up @@ -492,8 +485,6 @@ def sweep_start(self):
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)

Expand Down Expand Up @@ -690,17 +681,20 @@ def closeEvent(self, a0: QtGui.QCloseEvent) -> None:
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)
Expand Down
Loading

0 comments on commit e9a85b6

Please sign in to comment.