diff --git a/NanoVNASaver/Charts/Capacitance.py b/NanoVNASaver/Charts/Capacitance.py
index b0647b0..68829d2 100644
--- a/NanoVNASaver/Charts/Capacitance.py
+++ b/NanoVNASaver/Charts/Capacitance.py
@@ -16,16 +16,7 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-import math
import logging
-from decimal import InvalidOperation
-from typing import List
-
-from PyQt5 import QtWidgets, QtGui
-
-from NanoVNASaver.RFTools import Datapoint
-from NanoVNASaver.SITools import Format, Value
-from NanoVNASaver.Charts.Chart import Chart
from NanoVNASaver.Charts.Frequency import FrequencyChart
logger = logging.getLogger(__name__)
@@ -34,264 +25,7 @@ logger = logging.getLogger(__name__)
class CapacitanceChart(FrequencyChart):
def __init__(self, name=""):
super().__init__(name)
- self.leftMargin = 45
- self.dim.width = 250
- self.dim.height = 250
self.minDisplayValue = 0
self.maxDisplayValue = 100
-
- self.minValue = -1
- self.maxValue = 1
- self.span = 1
-
- self.setMinimumSize(self.dim.width + self.rightMargin + self.leftMargin,
- self.dim.height + self.topMargin + self.bottomMargin)
- self.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding,
- QtWidgets.QSizePolicy.MinimumExpanding))
- pal = QtGui.QPalette()
- pal.setColor(QtGui.QPalette.Background, Chart.color.background)
- self.setPalette(pal)
- self.setAutoFillBackground(True)
-
- def drawChart(self, qp: QtGui.QPainter):
- qp.setPen(QtGui.QPen(Chart.color.text))
- qp.drawText(3, 15, self.name + " (F)")
- qp.setPen(QtGui.QPen(Chart.color.foreground))
- qp.drawLine(self.leftMargin, 20, self.leftMargin, self.topMargin+self.dim.height+5)
- qp.drawLine(self.leftMargin-5, self.topMargin+self.dim.height,
- self.leftMargin+self.dim.width, self.topMargin + self.dim.height)
- self.drawTitle(qp)
-
- def drawValues(self, qp: QtGui.QPainter):
- if len(self.data) == 0 and len(self.reference) == 0:
- return
- pen = QtGui.QPen(Chart.color.sweep)
- pen.setWidth(self.dim.point)
- line_pen = QtGui.QPen(Chart.color.sweep)
- line_pen.setWidth(self.dim.line)
- highlighter = QtGui.QPen(QtGui.QColor(20, 0, 255))
- highlighter.setWidth(1)
- if not self.fixedSpan:
- if len(self.data) > 0:
- fstart = self.data[0].freq
- fstop = self.data[len(self.data)-1].freq
- else:
- fstart = self.reference[0].freq
- fstop = self.reference[len(self.reference) - 1].freq
- self.fstart = fstart
- self.fstop = fstop
- else:
- fstart = self.fstart = self.minFrequency
- fstop = self.fstop = self.maxFrequency
-
- # Draw bands if required
- if self.bands.enabled:
- self.drawBands(qp, fstart, fstop)
-
- if self.fixedValues:
- maxValue = self.maxDisplayValue / 10e11
- minValue = self.minDisplayValue / 10e11
- self.maxValue = maxValue
- self.minValue = minValue
- else:
- # Find scaling
- minValue = 1
- maxValue = -1
- for d in self.data:
- val = d.capacitiveEquivalent()
- if val > maxValue:
- maxValue = val
- if val < minValue:
- minValue = val
- for d in self.reference: # Also check min/max for the reference sweep
- if d.freq < self.fstart or d.freq > self.fstop:
- continue
- val = d.capacitiveEquivalent()
- if val > maxValue:
- maxValue = val
- if val < minValue:
- minValue = val
- self.maxValue = maxValue
- self.minValue = minValue
-
- span = maxValue - minValue
- if span == 0:
- logger.info("Span is zero for CapacitanceChart, setting to a small value.")
- span = 1e-15
- self.span = span
-
- target_ticks = math.floor(self.dim.height / 60)
- fmt = Format(max_nr_digits=3)
- for i in range(target_ticks):
- val = minValue + (i / target_ticks) * span
- try:
- y = self.topMargin + round((self.maxValue - val) / self.span * self.dim.height)
- qp.setPen(Chart.color.text)
- if val != minValue:
- valstr = str(Value(val, fmt=fmt))
- qp.drawText(3, y + 3, valstr)
- except (ValueError, InvalidOperation) as exc:
- logger.debug("Drawing wrent wrong: %s", exc)
- y = self.topMargin
- qp.setPen(QtGui.QPen(Chart.color.foreground))
- qp.drawLine(self.leftMargin - 5, y, self.leftMargin + self.dim.width, y)
-
- qp.setPen(QtGui.QPen(Chart.color.foreground))
- qp.drawLine(self.leftMargin - 5, self.topMargin,
- self.leftMargin + self.dim.width, self.topMargin)
- qp.setPen(Chart.color.text)
- qp.drawText(3, self.topMargin + 4, str(Value(maxValue, fmt=fmt)))
- qp.drawText(3, self.dim.height+self.topMargin, str(Value(minValue, fmt=fmt)))
- self.drawFrequencyTicks(qp)
-
- self.drawData(qp, self.data, Chart.color.sweep)
- self.drawData(qp, self.reference, Chart.color.reference)
- self.drawMarkers(qp)
-
- def getYPosition(self, d: Datapoint) -> int:
- try:
- return (
- self.topMargin +
- round((self.maxValue - d.capacitiveEquivalent()) /
- self.span * self.dim.height))
- except ValueError:
- return self.topMargin
-
- def valueAtPosition(self, y) -> List[float]:
- absy = y - self.topMargin
- val = -1 * ((absy / self.dim.height * self.span) - self.maxValue)
- return [val * 10e11]
-
- def copy(self):
- new_chart: CapacitanceChart = super().copy()
- new_chart.span = self.span
- return new_chart
-
-
-class InductanceChart(FrequencyChart):
- def __init__(self, name=""):
- super().__init__(name)
- self.leftMargin = 30
- self.dim.width = 250
- self.dim.height = 250
- self.minDisplayValue = 0
- self.maxDisplayValue = 100
-
- self.minValue = -1
- self.maxValue = 1
- self.span = 1
-
- self.setMinimumSize(self.dim.width + self.rightMargin + self.leftMargin,
- self.dim.height + self.topMargin + self.bottomMargin)
- self.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding,
- QtWidgets.QSizePolicy.MinimumExpanding))
- pal = QtGui.QPalette()
- pal.setColor(QtGui.QPalette.Background, Chart.color.background)
- self.setPalette(pal)
- self.setAutoFillBackground(True)
-
- def drawChart(self, qp: QtGui.QPainter):
- qp.setPen(QtGui.QPen(Chart.color.text))
- qp.drawText(3, 15, self.name + " (H)")
- qp.setPen(QtGui.QPen(Chart.color.foreground))
- qp.drawLine(self.leftMargin, 20, self.leftMargin, self.topMargin+self.dim.height+5)
- qp.drawLine(self.leftMargin-5, self.topMargin+self.dim.height,
- self.leftMargin+self.dim.width, self.topMargin + self.dim.height)
- self.drawTitle(qp)
-
- def drawValues(self, qp: QtGui.QPainter):
- if len(self.data) == 0 and len(self.reference) == 0:
- return
- pen = QtGui.QPen(Chart.color.sweep)
- pen.setWidth(self.dim.point)
- line_pen = QtGui.QPen(Chart.color.sweep)
- line_pen.setWidth(self.dim.line)
- highlighter = QtGui.QPen(QtGui.QColor(20, 0, 255))
- highlighter.setWidth(1)
- if not self.fixedSpan:
- if len(self.data) > 0:
- fstart = self.data[0].freq
- fstop = self.data[len(self.data)-1].freq
- else:
- fstart = self.reference[0].freq
- fstop = self.reference[len(self.reference) - 1].freq
- self.fstart = fstart
- self.fstop = fstop
- else:
- fstart = self.fstart = self.minFrequency
- fstop = self.fstop = self.maxFrequency
-
- # Draw bands if required
- if self.bands.enabled:
- self.drawBands(qp, fstart, fstop)
-
- if self.fixedValues:
- maxValue = self.maxDisplayValue / 10e11
- minValue = self.minDisplayValue / 10e11
- self.maxValue = maxValue
- self.minValue = minValue
- else:
- # Find scaling
- minValue = 1
- maxValue = -1
- for d in self.data:
- val = d.inductiveEquivalent()
- if val > maxValue:
- maxValue = val
- if val < minValue:
- minValue = val
- for d in self.reference: # Also check min/max for the reference sweep
- if d.freq < self.fstart or d.freq > self.fstop:
- continue
- val = d.inductiveEquivalent()
- if val > maxValue:
- maxValue = val
- if val < minValue:
- minValue = val
- self.maxValue = maxValue
- self.minValue = minValue
-
- span = maxValue - minValue
- if span == 0:
- logger.info("Span is zero for CapacitanceChart, setting to a small value.")
- span = 1e-15
- self.span = span
-
- target_ticks = math.floor(self.dim.height / 60)
- fmt = Format(max_nr_digits=3)
- for i in range(target_ticks):
- val = minValue + (i / target_ticks) * span
- y = self.topMargin + round((self.maxValue - val) / self.span * self.dim.height)
- qp.setPen(Chart.color.text)
- if val != minValue:
- valstr = str(Value(val, fmt=fmt))
- qp.drawText(3, y + 3, valstr)
- qp.setPen(QtGui.QPen(Chart.color.foreground))
- qp.drawLine(self.leftMargin - 5, y, self.leftMargin + self.dim.width, y)
-
- qp.setPen(QtGui.QPen(Chart.color.foreground))
- qp.drawLine(self.leftMargin - 5, self.topMargin,
- self.leftMargin + self.dim.width, self.topMargin)
- qp.setPen(Chart.color.text)
- qp.drawText(3, self.topMargin + 4, str(Value(maxValue, fmt=fmt)))
- qp.drawText(3, self.dim.height+self.topMargin, str(Value(minValue, fmt=fmt)))
- self.drawFrequencyTicks(qp)
-
- self.drawData(qp, self.data, Chart.color.sweep)
- self.drawData(qp, self.reference, Chart.color.reference)
- self.drawMarkers(qp)
-
- def getYPosition(self, d: Datapoint) -> int:
- return (self.topMargin +
- round((self.maxValue - d.inductiveEquivalent()) /
- self.span * self.dim.height))
-
- def valueAtPosition(self, y) -> List[float]:
- absy = y - self.topMargin
- val = -1 * ((absy / self.dim.height * self.span) - self.maxValue)
- return [val * 10e11]
-
- def copy(self):
- new_chart: InductanceChart = super().copy()
- new_chart.span = self.span
- return new_chart
+ self.name_unit = "F"
+ self.value_function = lambda x: x.capacitiveEquivalent()
diff --git a/NanoVNASaver/Charts/Chart.py b/NanoVNASaver/Charts/Chart.py
index a5cfb33..312176e 100644
--- a/NanoVNASaver/Charts/Chart.py
+++ b/NanoVNASaver/Charts/Chart.py
@@ -24,6 +24,7 @@ from typing import List, Set, Tuple, ClassVar, Any
from PyQt5 import QtWidgets, QtGui, QtCore
from PyQt5.QtCore import pyqtSignal
+from NanoVNASaver import Defaults
from NanoVNASaver.RFTools import Datapoint
from NanoVNASaver.Marker import Marker
@@ -64,23 +65,15 @@ class ChartFlags:
draw_lines: bool = False
is_popout: bool = False
-@dataclass
-class ChartMarkerConfig:
- draw_label: bool = False
- fill: bool = False
- at_tip: bool = False
- size: int = 3
class ChartMarker(QtWidgets.QWidget):
- cfg: ClassVar[ChartMarkerConfig] = ChartMarkerConfig()
-
def __init__(self, qp: QtGui.QPaintDevice):
super().__init__()
self.qp = qp
def draw(self, x: int, y: int, color: QtGui.QColor, text: str = ""):
- offset = self.cfg.size // 2
- if self.cfg.at_tip:
+ offset = Defaults.cfg.chart.marker_size // 2
+ if Defaults.cfg.chart.marker_at_tip:
y -= offset
pen = QtGui.QPen(color)
self.qp.setPen(pen)
@@ -90,12 +83,12 @@ class ChartMarker(QtWidgets.QWidget):
qpp.lineTo(x + offset, y - offset)
qpp.lineTo(x, y + offset)
- if self.cfg.fill:
+ if Defaults.cfg.chart.marker_filled:
self.qp.fillPath(qpp, color)
else:
self.qp.drawPath(qpp)
- if text and self.cfg.draw_label:
+ if text and Defaults.cfg.chart.marker_label:
text_width = self.qp.fontMetrics().horizontalAdvance(text)
self.qp.drawText(x - text_width // 2, y - 3 - offset, text)
@@ -104,7 +97,6 @@ class Chart(QtWidgets.QWidget):
bands: ClassVar[Any] = None
popoutRequested: ClassVar[Any] = pyqtSignal(object)
color: ClassVar[ChartColors] = ChartColors()
- marker_cfg: ClassVar[ChartMarkerConfig] = ChartMarkerConfig()
def __init__(self, name):
super().__init__()
@@ -160,7 +152,7 @@ class Chart(QtWidgets.QWidget):
self.update()
def setMarkerSize(self, size):
- ChartMarker.cfg.size = size
+ Defaults.cfg.chart.marker_size = size
self.update()
def setSweepTitle(self, title):
@@ -249,7 +241,6 @@ class Chart(QtWidgets.QWidget):
new_chart.reference = self.reference
new_chart.dim = replace(self.dim)
new_chart.flag = replace(self.flag)
- new_chart.marker_cfg = replace(self.marker_cfg)
new_chart.markers = self.markers
new_chart.swrMarkers = self.swrMarkers
new_chart.bands = self.bands
diff --git a/NanoVNASaver/Charts/Frequency.py b/NanoVNASaver/Charts/Frequency.py
index e9ae60b..107c5f2 100644
--- a/NanoVNASaver/Charts/Frequency.py
+++ b/NanoVNASaver/Charts/Frequency.py
@@ -2,7 +2,7 @@
#
# A python program to view and export Touchstone data from a NanoVNA
# Copyright (C) 2019, 2020 Rune B. Broberg
-# Copyright (C) 2020,2021 NanoVNA-Saver Authors
+# Copyright (C) 2020ff NanoVNA-Saver Authors
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -35,30 +35,22 @@ logger = logging.getLogger(__name__)
class FrequencyChart(Chart):
- fstart = 0
- fstop = 0
- maxFrequency = 100000000
- minFrequency = 1000000
-
- # TODO: use unscaled values instead of unit dependend ones
- minDisplayValue = -1
- maxDisplayValue = 1
-
- fixedSpan = False
- fixedValues = False
-
- logarithmicX = False
- logarithmicY = False
-
- leftMargin = 30
- rightMargin = 20
- bottomMargin = 20
- topMargin = 30
def __init__(self, name):
super().__init__(name)
+ self.maxFrequency = 100000000
+ self.minFrequency = 1000000
+ self.fixedSpan = False
+ self.fixedValues = False
+ self.logarithmicX = False
+ self.logarithmicY = False
+
self.leftMargin = 30
+ self.rightMargin = 20
+ self.bottomMargin = 20
+ self.topMargin = 30
+
self.dim.width = 250
self.dim.height = 250
self.fstart = 0
@@ -67,6 +59,10 @@ class FrequencyChart(Chart):
self.name_unit = ""
self.value_function = lambda x: 0.0
+ # TODO: use unscaled values instead of unit dependend ones
+ self.minDisplayValue = -1
+ self.maxDisplayValue = 1
+
self.minValue = -1
self.maxValue = 1
self.span = 1
@@ -189,17 +185,15 @@ class FrequencyChart(Chart):
def _set_start_stop(self):
if self.fixedSpan:
- fstart = self.minFrequency
- fstop = self.maxFrequency
- else:
- if len(self.data) > 0:
- fstart = self.data[0].freq
- fstop = self.data[len(self.data) - 1].freq
- else:
- fstart = self.reference[0].freq
- fstop = self.reference[len(self.reference) - 1].freq
- self.fstart = fstart
- self.fstop = fstop
+ self.fstart = self.minFrequency
+ self.fstop = self.maxFrequency
+ return
+ if self.data:
+ self.fstart = self.data[0].freq
+ self.fstop = self.data[len(self.data) - 1].freq
+ return
+ self.fstart = self.reference[0].freq
+ self.fstop = self.reference[len(self.reference) - 1].freq
def contextMenuEvent(self, event):
self.action_set_fixed_start.setText(
@@ -336,10 +330,13 @@ class FrequencyChart(Chart):
return math.floor(self.width() / 2)
def getYPosition(self, d: Datapoint) -> int:
- return (
- self.topMargin +
- round((self.maxValue - d.capacitiveEquivalent()) /
- self.span * self.dim.height))
+ try:
+ return (
+ self.topMargin +
+ round((self.maxValue - self.value_function(d) /
+ self.span * self.dim.height)))
+ except ValueError:
+ return self.topMargin
def frequencyAtPosition(self, x, limit=True) -> int:
"""
@@ -352,20 +349,21 @@ class FrequencyChart(Chart):
and the value is before or after the chart,
returns minimum or maximum frequencies.
"""
- if self.fstop - self.fstart > 0:
- absx = x - self.leftMargin
- if limit and absx < 0:
+ if self.fstop - self.fstart <= 0:
+ return -1
+ absx = x - self.leftMargin
+ if limit:
+ if absx < 0:
return self.fstart
- if limit and absx > self.dim.width:
+ if absx > self.dim.width:
return self.fstop
- if self.logarithmicX:
- span = math.log(self.fstop) - math.log(self.fstart)
- step = span / self.dim.width
- return round(math.exp(math.log(self.fstart) + absx * step))
- span = self.fstop - self.fstart
+ if self.logarithmicX:
+ span = math.log(self.fstop) - math.log(self.fstart)
step = span / self.dim.width
- return round(self.fstart + absx * step)
- return -1
+ return round(math.exp(math.log(self.fstart) + absx * step))
+ span = self.fstop - self.fstart
+ step = span / self.dim.width
+ return round(self.fstart + absx * step)
def valueAtPosition(self, y) -> List[float]:
"""
@@ -574,26 +572,21 @@ class FrequencyChart(Chart):
self.drawData(qp, self.reference, Chart.color.reference)
self.drawMarkers(qp)
- def _find_scaling(self) -> Tuple[int, int]:
+ def _find_scaling(self) -> Tuple[float, float]:
+ min_value = self.minDisplayValue / 10e11
+ max_value = self.maxDisplayValue / 10e11
if self.fixedValues:
- return (self.minDisplayValue / 10e11,
- self.maxDisplayValue / 10e11)
- min_value = 1
- max_value = -1
+ return (min_value, max_value)
for d in self.data:
val = self.value_function(d)
- if val > max_value:
- max_value = val
- if val < min_value:
- min_value = val
+ min_value = min(min_value, val)
+ max_value = max(max_value, val)
for d in self.reference: # Also check min/max for the reference sweep
if d.freq < self.fstart or d.freq > self.fstop:
continue
val = self.value_function(d)
- if val > max_value:
- max_value = val
- if val < min_value:
- min_value = val
+ min_value = min(min_value, val)
+ max_value = max(max_value, val)
return (min_value, max_value)
def drawFrequencyTicks(self, qp):
diff --git a/NanoVNASaver/Charts/Inductance.py b/NanoVNASaver/Charts/Inductance.py
index 4b56c45..85d131e 100644
--- a/NanoVNASaver/Charts/Inductance.py
+++ b/NanoVNASaver/Charts/Inductance.py
@@ -2,7 +2,7 @@
#
# A python program to view and export Touchstone data from a NanoVNA
# Copyright (C) 2019, 2020 Rune B. Broberg
-# Copyright (C) 2020 NanoVNA-Saver Authors
+# Copyright (C) 2020ff NanoVNA-Saver Authors
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -16,15 +16,8 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-import math
import logging
-from typing import List
-from PyQt5 import QtWidgets, QtGui
-
-from NanoVNASaver.RFTools import Datapoint
-from NanoVNASaver.SITools import Format, Value
-from NanoVNASaver.Charts.Chart import Chart
from NanoVNASaver.Charts.Frequency import FrequencyChart
logger = logging.getLogger(__name__)
@@ -33,127 +26,7 @@ logger = logging.getLogger(__name__)
class InductanceChart(FrequencyChart):
def __init__(self, name=""):
super().__init__(name)
- self.leftMargin = 45
- self.dim.width = 250
- self.dim.height = 250
self.minDisplayValue = 0
self.maxDisplayValue = 100
-
- self.minValue = -1
- self.maxValue = 1
- self.span = 1
-
- self.setMinimumSize(self.dim.width + self.rightMargin + self.leftMargin,
- self.dim.height + self.topMargin + self.bottomMargin)
- self.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding,
- QtWidgets.QSizePolicy.MinimumExpanding))
- pal = QtGui.QPalette()
- pal.setColor(QtGui.QPalette.Background, Chart.color.background)
- self.setPalette(pal)
- self.setAutoFillBackground(True)
-
- def drawChart(self, qp: QtGui.QPainter):
- qp.setPen(QtGui.QPen(Chart.color.text))
- qp.drawText(3, 15, self.name + " (H)")
- qp.setPen(QtGui.QPen(Chart.color.foreground))
- qp.drawLine(self.leftMargin, 20, self.leftMargin, self.topMargin+self.dim.height+5)
- qp.drawLine(self.leftMargin-5, self.topMargin+self.dim.height,
- self.leftMargin+self.dim.width, self.topMargin + self.dim.height)
- self.drawTitle(qp)
-
- def drawValues(self, qp: QtGui.QPainter):
- if len(self.data) == 0 and len(self.reference) == 0:
- return
- pen = QtGui.QPen(Chart.color.sweep)
- pen.setWidth(self.dim.point)
- line_pen = QtGui.QPen(Chart.color.sweep)
- line_pen.setWidth(self.dim.line)
- highlighter = QtGui.QPen(QtGui.QColor(20, 0, 255))
- highlighter.setWidth(1)
- if not self.fixedSpan:
- if len(self.data) > 0:
- fstart = self.data[0].freq
- fstop = self.data[len(self.data)-1].freq
- else:
- fstart = self.reference[0].freq
- fstop = self.reference[len(self.reference) - 1].freq
- self.fstart = fstart
- self.fstop = fstop
- else:
- fstart = self.fstart = self.minFrequency
- fstop = self.fstop = self.maxFrequency
-
- # Draw bands if required
- if self.bands.enabled:
- self.drawBands(qp, fstart, fstop)
-
- if self.fixedValues:
- maxValue = self.maxDisplayValue / 10e11
- minValue = self.minDisplayValue / 10e11
- self.maxValue = maxValue
- self.minValue = minValue
- else:
- # Find scaling
- minValue = 1
- maxValue = -1
- for d in self.data:
- val = d.inductiveEquivalent()
- if val > maxValue:
- maxValue = val
- if val < minValue:
- minValue = val
- for d in self.reference: # Also check min/max for the reference sweep
- if d.freq < self.fstart or d.freq > self.fstop:
- continue
- val = d.inductiveEquivalent()
- if val > maxValue:
- maxValue = val
- if val < minValue:
- minValue = val
- self.maxValue = maxValue
- self.minValue = minValue
-
- span = maxValue - minValue
- if span == 0:
- logger.info("Span is zero for CapacitanceChart, setting to a small value.")
- span = 1e-15
- self.span = span
-
- target_ticks = math.floor(self.dim.height / 60)
- fmt = Format(max_nr_digits=3)
- for i in range(target_ticks):
- val = minValue + (i / target_ticks) * span
- y = self.topMargin + round((self.maxValue - val) / self.span * self.dim.height)
- qp.setPen(Chart.color.text)
- if val != minValue:
- valstr = str(Value(val, fmt=fmt))
- qp.drawText(3, y + 3, valstr)
- qp.setPen(QtGui.QPen(Chart.color.foreground))
- qp.drawLine(self.leftMargin - 5, y, self.leftMargin + self.dim.width, y)
-
- qp.setPen(QtGui.QPen(Chart.color.foreground))
- qp.drawLine(self.leftMargin - 5, self.topMargin,
- self.leftMargin + self.dim.width, self.topMargin)
- qp.setPen(Chart.color.text)
- qp.drawText(3, self.topMargin + 4, str(Value(maxValue, fmt=fmt)))
- qp.drawText(3, self.dim.height+self.topMargin, str(Value(minValue, fmt=fmt)))
- self.drawFrequencyTicks(qp)
-
- self.drawData(qp, self.data, Chart.color.sweep)
- self.drawData(qp, self.reference, Chart.color.reference)
- self.drawMarkers(qp)
-
- def getYPosition(self, d: Datapoint) -> int:
- return (self.topMargin +
- round((self.maxValue - d.inductiveEquivalent()) /
- self.span * self.dim.height))
-
- def valueAtPosition(self, y) -> List[float]:
- absy = y - self.topMargin
- val = -1 * ((absy / self.dim.height * self.span) - self.maxValue)
- return [val * 10e11]
-
- def copy(self):
- new_chart: InductanceChart = super().copy()
- new_chart.span = self.span
- return new_chart
+ self.name_unit = "H"
+ self.value_function = lambda x: x.inductiveEquivalent()
diff --git a/NanoVNASaver/Charts/MagnitudeZ.py b/NanoVNASaver/Charts/MagnitudeZ.py
index 90a073a..d325b12 100644
--- a/NanoVNASaver/Charts/MagnitudeZ.py
+++ b/NanoVNASaver/Charts/MagnitudeZ.py
@@ -23,7 +23,8 @@ from typing import List
from PyQt5 import QtGui
from NanoVNASaver.RFTools import Datapoint
-from NanoVNASaver.SITools import Format, Value
+from NanoVNASaver.SITools import (
+ Format, Value, round_ceil, round_floor)
from NanoVNASaver.Charts.Chart import Chart
from NanoVNASaver.Charts.Frequency import FrequencyChart
from NanoVNASaver.Charts.LogMag import LogMagChart
@@ -54,48 +55,34 @@ class MagnitudeZChart(FrequencyChart):
self.drawBands(qp, self.fstart, self.fstop)
if self.fixedValues:
- maxValue = self.maxDisplayValue
- minValue = self.minDisplayValue
- self.maxValue = maxValue
- if self.logarithmicY and minValue <= 0:
- self.minValue = 0.01
- else:
- self.minValue = minValue
+ self.maxValue = self.maxDisplayValue
+ self.minValue = max(
+ self.minDisplayValue, 0.01) if self.logarithmicY else self.minDisplayValue
else:
# Find scaling
- minValue = 100
- maxValue = 0
+ self.minValue = 100
+ self.maxValue = 0
for d in self.data:
mag = self.magnitude(d)
- if math.isinf(mag): # Avoid infinite scales
+ if math.isinf(mag): # Avoid infinite scales
continue
- if mag > maxValue:
- maxValue = mag
- if mag < minValue:
- minValue = mag
+ self.maxValue = max(self.maxValue, mag)
+ self.minValue = min(self.minValue, mag)
for d in self.reference: # Also check min/max for the reference sweep
if d.freq < self.fstart or d.freq > self.fstop:
continue
mag = self.magnitude(d)
- if math.isinf(mag): # Avoid infinite scales
+ if math.isinf(mag): # Avoid infinite scales
continue
- if mag > maxValue:
- maxValue = mag
- if mag < minValue:
- minValue = mag
+ self.maxValue = max(self.maxValue, mag)
+ self.minValue = min(self.minValue, mag)
- minValue = 10*math.floor(minValue/10)
- if self.logarithmicY and minValue <= 0:
- minValue = 0.01
- self.minValue = minValue
+ self.minValue = round_floor(self.minValue, 2)
+ if self.logarithmicY and self.minValue <= 0:
+ self.minValue = 0.01
+ self.maxValue = round_ceil(self.maxValue, 2)
- maxValue = 10*math.ceil(maxValue/10)
- self.maxValue = maxValue
-
- span = maxValue-minValue
- if span == 0:
- span = 0.01
- self.span = span
+ self.span = (self.maxValue - self.minValue) or 0.01
# We want one horizontal tick per 50 pixels, at most
horizontal_ticks = math.floor(self.dim.height/50)
diff --git a/NanoVNASaver/Controls/MarkerControl.py b/NanoVNASaver/Controls/MarkerControl.py
index 8f6fafe..86f0508 100644
--- a/NanoVNASaver/Controls/MarkerControl.py
+++ b/NanoVNASaver/Controls/MarkerControl.py
@@ -21,6 +21,7 @@ import logging
from PyQt5 import QtWidgets, QtCore
from PyQt5.QtWidgets import QCheckBox
+from NanoVNASaver import Defaults
from NanoVNASaver.Marker import Marker
from NanoVNASaver.Controls.Control import Control
@@ -28,13 +29,21 @@ from NanoVNASaver.Controls.Control import Control
logger = logging.getLogger(__name__)
+class ShowButton(QtWidgets.QPushButton):
+ def setText(self, text: str=''):
+ if not text:
+ text = ("Show data"
+ if Defaults.cfg.gui.markers_hidden else "Hide data")
+ super().setText(text)
+ self.setToolTip("Toggle visibility of marker readings area")
+
+
class MarkerControl(Control):
def __init__(self, app: QtWidgets.QWidget):
super().__init__(app, "Markers")
- marker_count = max(self.app.settings.value("MarkerCount", 3, int), 1)
- for i in range(marker_count):
+ for i in range(Defaults.cfg.chart.marker_count):
marker = Marker("", self.app.settings)
# marker.setFixedHeight(20)
marker.updated.connect(self.app.markerUpdated)
@@ -56,12 +65,9 @@ class MarkerControl(Control):
self.layout.addRow(layout2)
- self.showMarkerButton = QtWidgets.QPushButton()
+ self.showMarkerButton = ShowButton()
self.showMarkerButton.setFixedHeight(20)
- if self.app.marker_frame.isHidden():
- self.showMarkerButton.setText("Show data")
- else:
- self.showMarkerButton.setText("Hide data")
+ self.showMarkerButton.setText()
self.showMarkerButton.clicked.connect(self.toggle_frame)
lock_radiobutton = QtWidgets.QRadioButton("Locked")
@@ -76,10 +82,10 @@ class MarkerControl(Control):
def toggle_frame(self):
def settings(hidden: bool):
- self.app.marker_frame.setHidden(not hidden)
- self.app.settings.setValue("MarkersVisible", hidden)
- self.showMarkerButton.setText(
- "Hide data" if hidden else "Show data")
+ Defaults.cfg.gui.markers_hidden = not hidden
+ self.app.marker_frame.setHidden(
+ Defaults.cfg.gui.markers_hidden)
+ self.showMarkerButton.setText()
self.showMarkerButton.repaint()
settings(self.app.marker_frame.isHidden())
diff --git a/NanoVNASaver/Defaults.py b/NanoVNASaver/Defaults.py
new file mode 100644
index 0000000..ed6cc2f
--- /dev/null
+++ b/NanoVNASaver/Defaults.py
@@ -0,0 +1,125 @@
+# NanoVNASaver
+#
+# A python program to view and export Touchstone data from a NanoVNA
+# Copyright (C) 2019, 2020 Rune B. Broberg
+# Copyright (C) 2020ff NanoVNA-Saver Authors
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+import dataclasses as DC
+import logging
+import json
+
+from PyQt5.QtCore import QSettings
+
+
+logger = logging.getLogger(__name__)
+
+# pylint: disable=too-few-public-methods
+# pylint: disable=too-many-instance-attributes
+@DC.dataclass
+class GUI:
+ window_height: int = 950
+ window_width: int = 1433
+ font_size: int = 8
+ dark_mode: bool = False
+ # TODO: implement QByteArray
+ splitter_sizes: bytearray = DC.field(default_factory=bytearray)
+ markers_hidden: bool = False
+
+
+@DC.dataclass
+class Chart:
+ point_size: int = 2
+ show_lines: bool = False
+ line_thickness: int = 1
+ marker_count: int = 3
+ marker_label: bool = False
+ marker_filled: bool = False
+ marker_at_tip: bool = False
+ marker_size: int = 8
+ returnloss_is_positive: bool = False
+
+
+@DC.dataclass
+class CFG:
+ gui: object = GUI()
+ chart: object = Chart()
+
+
+cfg = CFG()
+
+def restore(settings: 'AppSettings') -> CFG:
+ result = CFG()
+ for field in DC.fields(result):
+ value = settings.restore_dataclass(field.name.upper(),
+ getattr(result, field.name))
+ setattr(result, field.name, value)
+ logger.debug("restored\n(\n%s\n)", result)
+ return result
+
+
+def store(settings: 'AppSettings', data: CFG) -> None:
+ logger.debug("storing\n(\n%s\n)", data)
+ assert isinstance(data, CFG)
+ for field in DC.fields(data):
+ data_class = getattr(data, field.name)
+ assert DC.is_dataclass(data_class)
+ settings.store_dataclass(field.name.upper(), data_class)
+
+
+class AppSettings(QSettings):
+ def store_dataclass(self, name: str, data: object) -> None:
+ assert DC.is_dataclass(data)
+ self.beginGroup(name)
+ for field in DC.fields(data):
+ 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)
+ self.endGroup()
+
+ def restore_dataclass(self, name: str, data: object) -> object:
+ assert DC.is_dataclass(data)
+
+ 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))
+ self.endGroup()
+
+ return result
diff --git a/NanoVNASaver/NanoVNASaver.py b/NanoVNASaver/NanoVNASaver.py
index a6a3317..5b535c5 100644
--- a/NanoVNASaver/NanoVNASaver.py
+++ b/NanoVNASaver/NanoVNASaver.py
@@ -24,6 +24,7 @@ from time import strftime, localtime
from PyQt5 import QtWidgets, QtCore, QtGui
+from NanoVNASaver import Defaults
from .Windows import (
AboutWindow, AnalysisWindow, CalibrationWindow,
DeviceSettingsWindow, DisplaySettingsWindow, SweepSettingsWindow,
@@ -69,10 +70,11 @@ class NanoVNASaver(QtWidgets.QWidget):
else:
self.icon = QtGui.QIcon("icon_48x48.png")
self.setWindowIcon(self.icon)
- self.settings = QtCore.QSettings(QtCore.QSettings.IniFormat,
+ self.settings = Defaults.AppSettings(QtCore.QSettings.IniFormat,
QtCore.QSettings.UserScope,
"NanoVNASaver", "NanoVNASaver")
logger.info("Settings from: %s", self.settings.fileName())
+ Defaults.cfg = Defaults.restore(self.settings)
self.threadpool = QtCore.QThreadPool()
self.sweep = Sweep()
self.worker = SweepWorker(self)
@@ -121,9 +123,8 @@ class NanoVNASaver(QtWidgets.QWidget):
outer.addWidget(scrollarea)
self.setLayout(outer)
scrollarea.setWidgetResizable(True)
- window_width = self.settings.value("WindowWidth", 1350, type=int)
- window_height = self.settings.value("WindowHeight", 950, type=int)
- self.resize(window_width, window_height)
+ self.resize(Defaults.cfg.gui.window_width,
+ Defaults.cfg.gui.window_height)
scrollarea.setSizePolicy(
QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)
self.setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding,
@@ -203,18 +204,14 @@ class NanoVNASaver(QtWidgets.QWidget):
left_column = QtWidgets.QVBoxLayout()
right_column = QtWidgets.QVBoxLayout()
right_column.addLayout(self.charts_layout)
- self.marker_frame.setHidden(
- not self.settings.value("MarkersVisible", True, bool))
+ self.marker_frame.setHidden(Defaults.cfg.gui.markers_hidden)
chart_widget = QtWidgets.QWidget()
chart_widget.setLayout(right_column)
self.splitter = QtWidgets.QSplitter()
self.splitter.addWidget(self.marker_frame)
self.splitter.addWidget(chart_widget)
- try:
- self.splitter.restoreState(self.settings.value("SplitterSizes"))
- except TypeError:
- pass
+ self.splitter.restoreState(Defaults.cfg.gui.splitter_sizes)
layout.addLayout(left_column)
layout.addWidget(self.splitter, 2)
@@ -638,17 +635,18 @@ class NanoVNASaver(QtWidgets.QWidget):
def closeEvent(self, a0: QtGui.QCloseEvent) -> None:
self.worker.stopped = True
- self.settings.setValue("MarkerCount", Marker.count())
for marker in self.markers:
marker.update_settings()
-
- self.settings.setValue("WindowHeight", self.height())
- self.settings.setValue("WindowWidth", self.width())
- self.settings.setValue("SplitterSizes", self.splitter.saveState())
-
self.settings.sync()
self.bands.saveSettings()
self.threadpool.waitForDone(2500)
+
+ Defaults.cfg.chart.marker_count = Marker.count()
+ Defaults.cfg.gui.window_width = self.width()
+ Defaults.cfg.gui.window_height = self.height()
+ Defaults.cfg.gui.splitter_sizes = bytearray(self.splitter.saveState())
+ Defaults.store(self.settings, Defaults.cfg)
+
a0.accept()
sys.exit()
diff --git a/NanoVNASaver/SITools.py b/NanoVNASaver/SITools.py
index b952e05..5bd03f9 100644
--- a/NanoVNASaver/SITools.py
+++ b/NanoVNASaver/SITools.py
@@ -34,6 +34,13 @@ def clamp_value(value: Real, rmin: Real, rmax: Real) -> Real:
return rmax
return value
+def round_ceil(value: Real, digits: int=0) -> Real:
+ factor = 10 ** digits
+ return factor * math.ceil(value / factor)
+
+def round_floor(value: Real, digits: int=0) -> Real:
+ factor = 10 ** digits
+ return factor * math.floor(value / factor)
class Format(NamedTuple):
max_nr_digits: int = 6
@@ -74,8 +81,8 @@ class Value:
self._value = decimal.Decimal(value, context=Value.CTX)
def __repr__(self) -> str:
- return (f"{self.__class__.__name__}(" + repr(self._value) +
- f", '{self._unit}', {self.fmt})")
+ return (f"{self.__class__.__name__}("
+ f"{repr(self._value)}, '{self._unit}', {self.fmt})")
def __str__(self) -> str:
fmt = self.fmt
@@ -100,10 +107,10 @@ class Value:
max_digits = fmt.max_nr_digits + (
(1 if not fmt.fix_decimals and abs(real) < 10 else 0) +
(1 if not fmt.fix_decimals and abs(real) < 100 else 0))
- formstr = "." + str(max_digits - 3) + "f"
+ formstr = f".{max_digits - 3}f"
if self.fmt.allways_signed:
- formstr = "+" + formstr
+ formstr = f"+{formstr}"
result = format(real, formstr)
if float(result) == 0.0:
diff --git a/NanoVNASaver/Windows/About.py b/NanoVNASaver/Windows/About.py
index f269a96..0d8f30f 100644
--- a/NanoVNASaver/Windows/About.py
+++ b/NanoVNASaver/Windows/About.py
@@ -16,6 +16,7 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+import contextlib
import logging
from time import strftime, localtime
from urllib import request, error
@@ -54,7 +55,7 @@ class AboutWindow(QtWidgets.QWidget):
layout.addWidget(QtWidgets.QLabel(""))
layout.addWidget(QtWidgets.QLabel(
"\N{COPYRIGHT SIGN} Copyright 2019, 2020 Rune B. Broberg\n"
- "\N{COPYRIGHT SIGN} Copyright 2020 NanoVNA-Saver Authors"
+ "\N{COPYRIGHT SIGN} Copyright 2020ff NanoVNA-Saver Authors"
))
layout.addWidget(QtWidgets.QLabel(
"This program comes with ABSOLUTELY NO WARRANTY"))
@@ -79,21 +80,6 @@ class AboutWindow(QtWidgets.QWidget):
btn_check_version.clicked.connect(self.findUpdates)
self.updateLabel = QtWidgets.QLabel("Last checked: ")
- self.updateCheckBox = QtWidgets.QCheckBox(
- "Check for updates on startup")
-
- self.updateCheckBox.toggled.connect(self.updateSettings)
-
- check_for_updates = self.app.settings.value(
- "CheckForUpdates", "Ask")
- if check_for_updates == "Yes":
- self.updateCheckBox.setChecked(True)
- self.findUpdates(automatic=True)
- elif check_for_updates == "No":
- self.updateCheckBox.setChecked(False)
- else:
- logger.debug("Starting timer")
- QtCore.QTimer.singleShot(2000, self.askAboutUpdates)
update_hbox = QtWidgets.QHBoxLayout()
update_hbox.addWidget(btn_check_version)
@@ -101,7 +87,6 @@ class AboutWindow(QtWidgets.QWidget):
update_hbox.addLayout(update_form)
update_hbox.addStretch()
update_form.addRow(self.updateLabel)
- update_form.addRow(self.updateCheckBox)
layout.addLayout(update_hbox)
layout.addStretch()
@@ -115,39 +100,10 @@ class AboutWindow(QtWidgets.QWidget):
self.updateLabels()
def updateLabels(self):
- try:
+ with contextlib.suppress(IOError, AttributeError):
self.versionLabel.setText(
f"NanoVNA Firmware Version: {self.app.vna.name} "
f"v{self.app.vna.version}")
- except (IOError, AttributeError):
- pass
-
- def updateSettings(self):
- if self.updateCheckBox.isChecked():
- self.app.settings.setValue("CheckForUpdates", "Yes")
- else:
- self.app.settings.setValue("CheckForUpdates", "No")
-
- def askAboutUpdates(self):
- logger.debug("Asking about automatic update checks")
- selection = QtWidgets.QMessageBox.question(
- self.app,
- "Enable checking for updates?",
- "Would you like NanoVNA-Saver to"
- " check for updates automatically?")
- if selection == QtWidgets.QMessageBox.Yes:
- self.updateCheckBox.setChecked(True)
- self.app.settings.setValue("CheckForUpdates", "Yes")
- self.findUpdates()
- elif selection == QtWidgets.QMessageBox.No:
- self.updateCheckBox.setChecked(False)
- self.app.settings.setValue("CheckForUpdates", "No")
- QtWidgets.QMessageBox.information(
- self.app,
- "Checking for updates disabled",
- 'You can check for updates using the "About" window.')
- else:
- self.app.settings.setValue("CheckForUpdates", "Ask")
def findUpdates(self, automatic=False):
latest_version = Version()
diff --git a/NanoVNASaver/Windows/DisplaySettings.py b/NanoVNASaver/Windows/DisplaySettings.py
index e681906..27c892f 100644
--- a/NanoVNASaver/Windows/DisplaySettings.py
+++ b/NanoVNASaver/Windows/DisplaySettings.py
@@ -2,7 +2,7 @@
#
# A python program to view and export Touchstone data from a NanoVNA
# Copyright (C) 2019, 2020 Rune B. Broberg
-# Copyright (C) 2020,2021 NanoVNA-Saver Authors
+# Copyright (C) 2020ff NanoVNA-Saver Authors
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -21,8 +21,9 @@ from typing import List
from PyQt5 import QtWidgets, QtCore, QtGui
+from NanoVNASaver import Defaults
from NanoVNASaver.Charts.Chart import (
- Chart, ChartColors, ChartMarker, ChartMarkerConfig)
+ Chart, ChartColors)
from NanoVNASaver.Windows.Bands import BandsWindow
from NanoVNASaver.Windows.MarkerSettings import MarkerSettingsWindow
from NanoVNASaver.Marker import Marker
@@ -37,7 +38,6 @@ class DisplaySettingsWindow(QtWidgets.QWidget):
self.app = app
self.setWindowTitle("Display settings")
self.setWindowIcon(self.app.icon)
- self.marker_cfg = ChartMarkerConfig()
self.marker_window = MarkerSettingsWindow(self.app)
self.callback_params = {}
@@ -61,10 +61,10 @@ class DisplaySettingsWindow(QtWidgets.QWidget):
display_options_layout.addRow("Return loss is:", self.returnloss_is_negative)
display_options_layout.addRow("", self.returnloss_is_positive)
- if self.app.settings.value("ReturnLossPositive", False, bool):
- self.returnloss_is_positive.setChecked(True)
- else:
- self.returnloss_is_negative.setChecked(True)
+ self.returnloss_is_positive.setChecked(
+ Defaults.cfg.chart.returnloss_is_positive)
+ self.returnloss_is_negative.setChecked(
+ not Defaults.cfg.chart.returnloss_is_positive)
self.returnloss_is_positive.toggled.connect(self.changeReturnLoss)
self.changeReturnLoss()
@@ -83,7 +83,7 @@ class DisplaySettingsWindow(QtWidgets.QWidget):
self.pointSizeInput = QtWidgets.QSpinBox()
self.pointSizeInput.setMinimumHeight(20)
- pointsize = self.app.settings.value("PointSize", 2, int)
+ pointsize = Defaults.cfg.chart.point_size
self.pointSizeInput.setValue(pointsize)
self.changePointSize(pointsize)
self.pointSizeInput.setMinimum(1)
@@ -95,7 +95,7 @@ class DisplaySettingsWindow(QtWidgets.QWidget):
self.lineThicknessInput = QtWidgets.QSpinBox()
self.lineThicknessInput.setMinimumHeight(20)
- linethickness = self.app.settings.value("LineThickness", 1, int)
+ linethickness = Defaults.cfg.chart.line_thickness
self.lineThicknessInput.setValue(linethickness)
self.changeLineThickness(linethickness)
self.lineThicknessInput.setMinimum(1)
@@ -107,10 +107,9 @@ class DisplaySettingsWindow(QtWidgets.QWidget):
self.markerSizeInput = QtWidgets.QSpinBox()
self.markerSizeInput.setMinimumHeight(20)
- markersize = self.app.settings.value("MarkerSize", 6, int)
+ markersize = Defaults.cfg.chart.marker_size
self.markerSizeInput.setValue(markersize)
self.markerSizeInput.setMinimum(4)
- self.markerSizeInput.setMinimum(4)
self.markerSizeInput.setMaximum(20)
self.markerSizeInput.setSingleStep(2)
self.markerSizeInput.setSuffix(" px")
@@ -137,10 +136,8 @@ class DisplaySettingsWindow(QtWidgets.QWidget):
display_options_layout.addRow("Data point is:", self.marker_at_center)
display_options_layout.addRow("", self.marker_at_tip)
- if self.app.settings.value("MarkerAtTip", False, bool):
- self.marker_at_tip.setChecked(True)
- else:
- self.marker_at_center.setChecked(True)
+ self.marker_at_tip.setChecked(Defaults.cfg.chart.marker_at_tip)
+ self.marker_at_center.setChecked(not Defaults.cfg.chart.marker_at_tip)
self.marker_at_tip.toggled.connect(self.changeMarkerAtTip)
self.changeMarkerAtTip()
@@ -162,10 +159,7 @@ class DisplaySettingsWindow(QtWidgets.QWidget):
self.font_dropdown = QtWidgets.QComboBox()
self.font_dropdown.setMinimumHeight(20)
self.font_dropdown.addItems(["7", "8", "9", "10", "11", "12"])
- font_size = self.app.settings.value("FontSize",
- defaultValue="8",
- type=str)
- self.font_dropdown.setCurrentText(font_size)
+ self.font_dropdown.setCurrentText(str(Defaults.cfg.gui.font_size))
self.changeFont()
self.font_dropdown.currentTextChanged.connect(self.changeFont)
@@ -196,11 +190,8 @@ class DisplaySettingsWindow(QtWidgets.QWidget):
self.vswrMarkers: List[float] = self.app.settings.value("VSWRMarkers", [], float)
if isinstance(self.vswrMarkers, float):
- if self.vswrMarkers == 0:
- self.vswrMarkers = []
- else:
- # Single values from the .ini become floats rather than lists. Convert them.
- self.vswrMarkers = [self.vswrMarkers]
+ # Single values from the .ini become floats rather than lists. Convert them.
+ self.vswrMarkers = [] if self.vswrMarkers == 0.0 else [self.vswrMarkers]
vswr_marker_layout.addRow(
"VSWR Markers",self.color_picker("VSWRColor", "swr"))
@@ -209,7 +200,7 @@ class DisplaySettingsWindow(QtWidgets.QWidget):
self.vswr_marker_dropdown.setMinimumHeight(20)
vswr_marker_layout.addRow(self.vswr_marker_dropdown)
- if len(self.vswrMarkers) == 0:
+ if not self.vswrMarkers:
self.vswr_marker_dropdown.addItem("None")
else:
for m in self.vswrMarkers:
@@ -262,10 +253,7 @@ class DisplaySettingsWindow(QtWidgets.QWidget):
# "S21 Phase",
# "None"]
- selections = []
-
- for c in self.app.selectable_charts:
- selections.append(c.name)
+ selections = [c.name for c in self.app.selectable_charts]
selections.append("None")
chart00_selection = QtWidgets.QComboBox()
@@ -364,14 +352,11 @@ class DisplaySettingsWindow(QtWidgets.QWidget):
"VSWRColor", defaultValue=ChartColors.swr,
type=QtGui.QColor)
- self.dark_mode_option.setChecked(
- self.app.settings.value("DarkMode", False, bool))
- self.show_lines_option.setChecked(
- self.app.settings.value("ShowLines", False, bool))
+ self.dark_mode_option.setChecked(Defaults.cfg.gui.dark_mode)
+ self.show_lines_option.setChecked(Defaults.cfg.chart.show_lines)
self.show_marker_number_option.setChecked(
- self.app.settings.value("ShowMarkerNumbers", ChartMarkerConfig.draw_label, bool))
- self.filled_marker_option.setChecked(
- self.app.settings.value("FilledMarkers", ChartMarkerConfig.fill, bool))
+ Defaults.cfg.chart.marker_label)
+ self.filled_marker_option.setChecked(Defaults.cfg.chart.marker_filled)
if self.app.settings.value("UseCustomColors",
defaultValue=False, type=bool):
@@ -450,8 +435,7 @@ class DisplaySettingsWindow(QtWidgets.QWidget):
def changeReturnLoss(self):
state = self.returnloss_is_positive.isChecked()
- self.app.settings.setValue("ReturnLossPositive", state)
-
+ Defaults.cfg.chart.returnloss_is_positive = bool(state)
for m in self.app.markers:
m.returnloss_is_positive = state
m.updateLabels(self.app.data.s11, self.app.data.s21)
@@ -462,56 +446,51 @@ class DisplaySettingsWindow(QtWidgets.QWidget):
def changeShowLines(self):
state = self.show_lines_option.isChecked()
- self.app.settings.setValue("ShowLines", state)
+ Defaults.cfg.chart.show_lines = bool(state)
for c in self.app.subscribing_charts:
c.setDrawLines(state)
def changeShowMarkerNumber(self):
- state = self.show_marker_number_option.isChecked()
- self.app.settings.setValue("ShowMarkerNumbers", state)
- ChartMarker.cfg.draw_label = state
+ Defaults.cfg.chart.marker_label = bool(
+ self.show_marker_number_option.isChecked())
self.updateCharts()
def changeFilledMarkers(self):
- state = self.filled_marker_option.isChecked()
- self.app.settings.setValue("FilledMarkers", state)
- ChartMarker.cfg.fill = state
+ Defaults.cfg.chart.marker_filled = bool(
+ self.filled_marker_option.isChecked())
self.updateCharts()
def changeMarkerAtTip(self):
- state = self.marker_at_tip.isChecked()
- self.app.settings.setValue("MarkerAtTip", state)
- ChartMarker.cfg.at_tip = state
+ Defaults.cfg.chart.marker_at_tip = bool(
+ self.marker_at_tip.isChecked())
self.updateCharts()
def changePointSize(self, size: int):
- self.app.settings.setValue("PointSize", size)
+ Defaults.cfg.chart.point_size = size
for c in self.app.subscribing_charts:
c.setPointSize(size)
def changeLineThickness(self, size: int):
- self.app.settings.setValue("LineThickness", size)
+ Defaults.cfg.chart.line_thickness = int(size)
for c in self.app.subscribing_charts:
c.setLineThickness(size)
def changeMarkerSize(self, size: int):
- self.app.settings.setValue("MarkerSize", size)
- ChartMarker.cfg.size = size
+ Defaults.cfg.chart.marker_size = size
self.markerSizeInput.setValue(size)
self.updateCharts()
def changeDarkMode(self):
state = self.dark_mode_option.isChecked()
- self.app.settings.setValue("DarkMode", state)
+ Defaults.cfg.gui.dark_mode = bool(state)
Chart.color.foreground = QtGui.QColor(QtCore.Qt.lightGray)
if state:
Chart.color.background = QtGui.QColor(QtCore.Qt.black)
Chart.color.text = QtGui.QColor(QtCore.Qt.white)
- Chart.color.swr = Chart.color.swr
else:
Chart.color.background = QtGui.QColor(QtCore.Qt.white)
Chart.color.text = QtGui.QColor(QtCore.Qt.black)
- Chart.color.swr = Chart.color.swr
+ Chart.color.swr = Chart.color.swr
self.updateCharts()
def changeSetting(self, setting: str, value: str):
@@ -547,11 +526,11 @@ class DisplaySettingsWindow(QtWidgets.QWidget):
c.update()
def changeFont(self):
- font_size = self.font_dropdown.currentText()
- self.app.settings.setValue("FontSize", font_size)
+ font_size = int(self.font_dropdown.currentText())
+ Defaults.cfg.gui.font_size = font_size
app: QtWidgets.QApplication = QtWidgets.QApplication.instance()
font = app.font()
- font.setPointSize(int(font_size))
+ font.setPointSize(font_size)
app.setFont(font)
self.app.changeFont(font)
@@ -628,3 +607,4 @@ class DisplaySettingsWindow(QtWidgets.QWidget):
def updateCharts(self):
for c in self.app.subscribing_charts:
c.update()
+ Defaults.store(self.app.settings, Defaults.cfg)
\ No newline at end of file
diff --git a/debug.sh b/debug.sh
new file mode 100755
index 0000000..9385eb2
--- /dev/null
+++ b/debug.sh
@@ -0,0 +1,2 @@
+#!/bin/sh
+exec python -m debugpy --listen 5678 --wait-for-client $@
diff --git a/nanovna-saver.py b/nanovna-saver.py
index f76c875..44adaa6 100755
--- a/nanovna-saver.py
+++ b/nanovna-saver.py
@@ -15,10 +15,13 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-try:
+from contextlib import suppress
+
+with suppress(ImportError):
+ # pylint: disable=no-name-in-module,import-error,unused-import
+ # pyright: reportMissingImports=false
import pkg_resources.py2_warn
-except ImportError:
- pass
+
from NanoVNASaver.__main__ import main
if __name__ == '__main__':
diff --git a/test/test_settings.py b/test/test_settings.py
new file mode 100644
index 0000000..d6f0f08
--- /dev/null
+++ b/test/test_settings.py
@@ -0,0 +1,88 @@
+# NanoVNASaver
+#
+# A python program to view and export Touchstone data from a NanoVNA
+# Copyright (C) 2019, 2020 Rune B. Broberg
+# Copyright (C) 2020,2021 NanoVNA-Saver Authors
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+import unittest
+from dataclasses import dataclass, field
+
+import NanoVNASaver.Defaults as CFG
+
+
+@dataclass
+class TConfig:
+ my_int: int = 3
+ my_float: float = 3.14
+ my_str: str = "Hello World"
+ my_bool: bool = True
+ my_list: list = field(default_factory=lambda: [1, 2, 3])
+ my_bytearray: bytearray = field(default_factory=lambda: bytearray((1,2,3)))
+
+
+class TestCases(unittest.TestCase):
+
+ def setUp(self) -> None:
+ self.settings_1 = CFG.AppSettings(
+ CFG.QSettings.IniFormat,
+ CFG.QSettings.UserScope,
+ "NanoVNASaver", "Test_1")
+ self.settings_2 = CFG.AppSettings(
+ CFG.QSettings.IniFormat,
+ CFG.QSettings.UserScope,
+ "NanoVNASaver", "Test_2")
+ self.config_1 = TConfig()
+ self.config_2 = TConfig(
+ my_int=4,
+ my_float=3.0,
+ my_str="Goodbye World",
+ my_bool=False,
+ my_list=[4, 5, 6])
+
+ def test_store_dataclass(self):
+ 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, my_str="Goodbye World",
+ my_bool="False", my_list=(4, 5, 6))
+ with self.assertRaises(AssertionError):
+ self.settings_1.store_dataclass("SectionX", illegal_config)
+
+ def test_restore_dataclass(self):
+ tc_1 = self.settings_1.restore_dataclass("Section1", TConfig())
+ tc_2 = self.settings_1.restore_dataclass("Section2", TConfig())
+ self.assertNotEqual(tc_1, tc_2)
+ self.assertEqual(tc_1, self.config_1)
+ self.assertEqual(tc_2, self.config_2)
+ self.assertEqual(tc_2.my_int, 4)
+ self.assertEqual(tc_2.my_float, 3.0)
+ self.assertEqual(tc_2.my_str, "Goodbye World")
+ self.assertEqual(tc_2.my_bool, False)
+ self.assertEqual(tc_2.my_list, [4, 5, 6])
+ self.assertIsInstance(tc_2.my_int, int)
+ self.assertIsInstance(tc_2.my_float, float)
+
+ def test_restore_empty(self):
+ tc_3 = self.settings_1.restore_dataclass("Section3", TConfig())
+ self.assertEqual(tc_3, TConfig())
+
+ def test_store(self):
+ tc_1 = CFG.CFG()
+ tc_1.gui.dark_mode = not tc_1.gui.dark_mode
+ CFG.store(self.settings_2, tc_1)
+ tc_2 = CFG.restore(self.settings_2)
+ print(f"\n{tc_1}\n{tc_2}\n")
+ self.assertEqual(tc_1, tc_2)
+ self.assertNotEqual(tc_2.gui, CFG.GUI())
\ No newline at end of file