Feature/linting 220514 (#503)

* Unified Chart Code
* New Defauls class for persistance of settings - fixes #491
* Removed non-interactive update checks
pull/504/head
Holger Müller 2022-05-24 17:05:59 +02:00 zatwierdzone przez GitHub
rodzic 6aa7aaa051
commit 3f4a262abe
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
14 zmienionych plików z 385 dodań i 642 usunięć

Wyświetl plik

@ -16,16 +16,7 @@
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>. # along with this program. If not, see <https://www.gnu.org/licenses/>.
import math
import logging 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 from NanoVNASaver.Charts.Frequency import FrequencyChart
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -34,264 +25,7 @@ logger = logging.getLogger(__name__)
class CapacitanceChart(FrequencyChart): class CapacitanceChart(FrequencyChart):
def __init__(self, name=""): def __init__(self, name=""):
super().__init__(name) super().__init__(name)
self.leftMargin = 45
self.dim.width = 250
self.dim.height = 250
self.minDisplayValue = 0 self.minDisplayValue = 0
self.maxDisplayValue = 100 self.maxDisplayValue = 100
self.name_unit = "F"
self.minValue = -1 self.value_function = lambda x: x.capacitiveEquivalent()
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

Wyświetl plik

@ -24,6 +24,7 @@ from typing import List, Set, Tuple, ClassVar, Any
from PyQt5 import QtWidgets, QtGui, QtCore from PyQt5 import QtWidgets, QtGui, QtCore
from PyQt5.QtCore import pyqtSignal from PyQt5.QtCore import pyqtSignal
from NanoVNASaver import Defaults
from NanoVNASaver.RFTools import Datapoint from NanoVNASaver.RFTools import Datapoint
from NanoVNASaver.Marker import Marker from NanoVNASaver.Marker import Marker
@ -64,23 +65,15 @@ class ChartFlags:
draw_lines: bool = False draw_lines: bool = False
is_popout: 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): class ChartMarker(QtWidgets.QWidget):
cfg: ClassVar[ChartMarkerConfig] = ChartMarkerConfig()
def __init__(self, qp: QtGui.QPaintDevice): def __init__(self, qp: QtGui.QPaintDevice):
super().__init__() super().__init__()
self.qp = qp self.qp = qp
def draw(self, x: int, y: int, color: QtGui.QColor, text: str = ""): def draw(self, x: int, y: int, color: QtGui.QColor, text: str = ""):
offset = self.cfg.size // 2 offset = Defaults.cfg.chart.marker_size // 2
if self.cfg.at_tip: if Defaults.cfg.chart.marker_at_tip:
y -= offset y -= offset
pen = QtGui.QPen(color) pen = QtGui.QPen(color)
self.qp.setPen(pen) self.qp.setPen(pen)
@ -90,12 +83,12 @@ class ChartMarker(QtWidgets.QWidget):
qpp.lineTo(x + offset, y - offset) qpp.lineTo(x + offset, y - offset)
qpp.lineTo(x, y + offset) qpp.lineTo(x, y + offset)
if self.cfg.fill: if Defaults.cfg.chart.marker_filled:
self.qp.fillPath(qpp, color) self.qp.fillPath(qpp, color)
else: else:
self.qp.drawPath(qpp) 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) text_width = self.qp.fontMetrics().horizontalAdvance(text)
self.qp.drawText(x - text_width // 2, y - 3 - offset, text) self.qp.drawText(x - text_width // 2, y - 3 - offset, text)
@ -104,7 +97,6 @@ class Chart(QtWidgets.QWidget):
bands: ClassVar[Any] = None bands: ClassVar[Any] = None
popoutRequested: ClassVar[Any] = pyqtSignal(object) popoutRequested: ClassVar[Any] = pyqtSignal(object)
color: ClassVar[ChartColors] = ChartColors() color: ClassVar[ChartColors] = ChartColors()
marker_cfg: ClassVar[ChartMarkerConfig] = ChartMarkerConfig()
def __init__(self, name): def __init__(self, name):
super().__init__() super().__init__()
@ -160,7 +152,7 @@ class Chart(QtWidgets.QWidget):
self.update() self.update()
def setMarkerSize(self, size): def setMarkerSize(self, size):
ChartMarker.cfg.size = size Defaults.cfg.chart.marker_size = size
self.update() self.update()
def setSweepTitle(self, title): def setSweepTitle(self, title):
@ -249,7 +241,6 @@ class Chart(QtWidgets.QWidget):
new_chart.reference = self.reference new_chart.reference = self.reference
new_chart.dim = replace(self.dim) new_chart.dim = replace(self.dim)
new_chart.flag = replace(self.flag) new_chart.flag = replace(self.flag)
new_chart.marker_cfg = replace(self.marker_cfg)
new_chart.markers = self.markers new_chart.markers = self.markers
new_chart.swrMarkers = self.swrMarkers new_chart.swrMarkers = self.swrMarkers
new_chart.bands = self.bands new_chart.bands = self.bands

Wyświetl plik

@ -2,7 +2,7 @@
# #
# A python program to view and export Touchstone data from a NanoVNA # A python program to view and export Touchstone data from a NanoVNA
# Copyright (C) 2019, 2020 Rune B. Broberg # 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 # 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 # it under the terms of the GNU General Public License as published by
@ -35,30 +35,22 @@ logger = logging.getLogger(__name__)
class FrequencyChart(Chart): 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): def __init__(self, name):
super().__init__(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.leftMargin = 30
self.rightMargin = 20
self.bottomMargin = 20
self.topMargin = 30
self.dim.width = 250 self.dim.width = 250
self.dim.height = 250 self.dim.height = 250
self.fstart = 0 self.fstart = 0
@ -67,6 +59,10 @@ class FrequencyChart(Chart):
self.name_unit = "" self.name_unit = ""
self.value_function = lambda x: 0.0 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.minValue = -1
self.maxValue = 1 self.maxValue = 1
self.span = 1 self.span = 1
@ -189,17 +185,15 @@ class FrequencyChart(Chart):
def _set_start_stop(self): def _set_start_stop(self):
if self.fixedSpan: if self.fixedSpan:
fstart = self.minFrequency self.fstart = self.minFrequency
fstop = self.maxFrequency self.fstop = self.maxFrequency
else: return
if len(self.data) > 0: if self.data:
fstart = self.data[0].freq self.fstart = self.data[0].freq
fstop = self.data[len(self.data) - 1].freq self.fstop = self.data[len(self.data) - 1].freq
else: return
fstart = self.reference[0].freq self.fstart = self.reference[0].freq
fstop = self.reference[len(self.reference) - 1].freq self.fstop = self.reference[len(self.reference) - 1].freq
self.fstart = fstart
self.fstop = fstop
def contextMenuEvent(self, event): def contextMenuEvent(self, event):
self.action_set_fixed_start.setText( self.action_set_fixed_start.setText(
@ -336,10 +330,13 @@ class FrequencyChart(Chart):
return math.floor(self.width() / 2) return math.floor(self.width() / 2)
def getYPosition(self, d: Datapoint) -> int: def getYPosition(self, d: Datapoint) -> int:
return ( try:
self.topMargin + return (
round((self.maxValue - d.capacitiveEquivalent()) / self.topMargin +
self.span * self.dim.height)) round((self.maxValue - self.value_function(d) /
self.span * self.dim.height)))
except ValueError:
return self.topMargin
def frequencyAtPosition(self, x, limit=True) -> int: def frequencyAtPosition(self, x, limit=True) -> int:
""" """
@ -352,20 +349,21 @@ class FrequencyChart(Chart):
and the value is before or after the chart, and the value is before or after the chart,
returns minimum or maximum frequencies. returns minimum or maximum frequencies.
""" """
if self.fstop - self.fstart > 0: if self.fstop - self.fstart <= 0:
absx = x - self.leftMargin return -1
if limit and absx < 0: absx = x - self.leftMargin
if limit:
if absx < 0:
return self.fstart return self.fstart
if limit and absx > self.dim.width: if absx > self.dim.width:
return self.fstop return self.fstop
if self.logarithmicX: if self.logarithmicX:
span = math.log(self.fstop) - math.log(self.fstart) 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
step = span / self.dim.width step = span / self.dim.width
return round(self.fstart + absx * step) return round(math.exp(math.log(self.fstart) + absx * step))
return -1 span = self.fstop - self.fstart
step = span / self.dim.width
return round(self.fstart + absx * step)
def valueAtPosition(self, y) -> List[float]: def valueAtPosition(self, y) -> List[float]:
""" """
@ -574,26 +572,21 @@ class FrequencyChart(Chart):
self.drawData(qp, self.reference, Chart.color.reference) self.drawData(qp, self.reference, Chart.color.reference)
self.drawMarkers(qp) 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: if self.fixedValues:
return (self.minDisplayValue / 10e11, return (min_value, max_value)
self.maxDisplayValue / 10e11)
min_value = 1
max_value = -1
for d in self.data: for d in self.data:
val = self.value_function(d) val = self.value_function(d)
if val > max_value: min_value = min(min_value, val)
max_value = val max_value = max(max_value, val)
if val < min_value:
min_value = val
for d in self.reference: # Also check min/max for the reference sweep for d in self.reference: # Also check min/max for the reference sweep
if d.freq < self.fstart or d.freq > self.fstop: if d.freq < self.fstart or d.freq > self.fstop:
continue continue
val = self.value_function(d) val = self.value_function(d)
if val > max_value: min_value = min(min_value, val)
max_value = val max_value = max(max_value, val)
if val < min_value:
min_value = val
return (min_value, max_value) return (min_value, max_value)
def drawFrequencyTicks(self, qp): def drawFrequencyTicks(self, qp):

Wyświetl plik

@ -2,7 +2,7 @@
# #
# A python program to view and export Touchstone data from a NanoVNA # A python program to view and export Touchstone data from a NanoVNA
# Copyright (C) 2019, 2020 Rune B. Broberg # 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 # 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 # 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 # You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>. # along with this program. If not, see <https://www.gnu.org/licenses/>.
import math
import logging 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 from NanoVNASaver.Charts.Frequency import FrequencyChart
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -33,127 +26,7 @@ logger = logging.getLogger(__name__)
class InductanceChart(FrequencyChart): class InductanceChart(FrequencyChart):
def __init__(self, name=""): def __init__(self, name=""):
super().__init__(name) super().__init__(name)
self.leftMargin = 45
self.dim.width = 250
self.dim.height = 250
self.minDisplayValue = 0 self.minDisplayValue = 0
self.maxDisplayValue = 100 self.maxDisplayValue = 100
self.name_unit = "H"
self.minValue = -1 self.value_function = lambda x: x.inductiveEquivalent()
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

Wyświetl plik

@ -23,7 +23,8 @@ from typing import List
from PyQt5 import QtGui from PyQt5 import QtGui
from NanoVNASaver.RFTools import Datapoint 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.Chart import Chart
from NanoVNASaver.Charts.Frequency import FrequencyChart from NanoVNASaver.Charts.Frequency import FrequencyChart
from NanoVNASaver.Charts.LogMag import LogMagChart from NanoVNASaver.Charts.LogMag import LogMagChart
@ -54,48 +55,34 @@ class MagnitudeZChart(FrequencyChart):
self.drawBands(qp, self.fstart, self.fstop) self.drawBands(qp, self.fstart, self.fstop)
if self.fixedValues: if self.fixedValues:
maxValue = self.maxDisplayValue self.maxValue = self.maxDisplayValue
minValue = self.minDisplayValue self.minValue = max(
self.maxValue = maxValue self.minDisplayValue, 0.01) if self.logarithmicY else self.minDisplayValue
if self.logarithmicY and minValue <= 0:
self.minValue = 0.01
else:
self.minValue = minValue
else: else:
# Find scaling # Find scaling
minValue = 100 self.minValue = 100
maxValue = 0 self.maxValue = 0
for d in self.data: for d in self.data:
mag = self.magnitude(d) mag = self.magnitude(d)
if math.isinf(mag): # Avoid infinite scales if math.isinf(mag): # Avoid infinite scales
continue continue
if mag > maxValue: self.maxValue = max(self.maxValue, mag)
maxValue = mag self.minValue = min(self.minValue, mag)
if mag < minValue:
minValue = mag
for d in self.reference: # Also check min/max for the reference sweep for d in self.reference: # Also check min/max for the reference sweep
if d.freq < self.fstart or d.freq > self.fstop: if d.freq < self.fstart or d.freq > self.fstop:
continue continue
mag = self.magnitude(d) mag = self.magnitude(d)
if math.isinf(mag): # Avoid infinite scales if math.isinf(mag): # Avoid infinite scales
continue continue
if mag > maxValue: self.maxValue = max(self.maxValue, mag)
maxValue = mag self.minValue = min(self.minValue, mag)
if mag < minValue:
minValue = mag
minValue = 10*math.floor(minValue/10) self.minValue = round_floor(self.minValue, 2)
if self.logarithmicY and minValue <= 0: if self.logarithmicY and self.minValue <= 0:
minValue = 0.01 self.minValue = 0.01
self.minValue = minValue self.maxValue = round_ceil(self.maxValue, 2)
maxValue = 10*math.ceil(maxValue/10) self.span = (self.maxValue - self.minValue) or 0.01
self.maxValue = maxValue
span = maxValue-minValue
if span == 0:
span = 0.01
self.span = span
# We want one horizontal tick per 50 pixels, at most # We want one horizontal tick per 50 pixels, at most
horizontal_ticks = math.floor(self.dim.height/50) horizontal_ticks = math.floor(self.dim.height/50)

Wyświetl plik

@ -21,6 +21,7 @@ import logging
from PyQt5 import QtWidgets, QtCore from PyQt5 import QtWidgets, QtCore
from PyQt5.QtWidgets import QCheckBox from PyQt5.QtWidgets import QCheckBox
from NanoVNASaver import Defaults
from NanoVNASaver.Marker import Marker from NanoVNASaver.Marker import Marker
from NanoVNASaver.Controls.Control import Control from NanoVNASaver.Controls.Control import Control
@ -28,13 +29,21 @@ from NanoVNASaver.Controls.Control import Control
logger = logging.getLogger(__name__) 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): class MarkerControl(Control):
def __init__(self, app: QtWidgets.QWidget): def __init__(self, app: QtWidgets.QWidget):
super().__init__(app, "Markers") super().__init__(app, "Markers")
marker_count = max(self.app.settings.value("MarkerCount", 3, int), 1) for i in range(Defaults.cfg.chart.marker_count):
for i in range(marker_count):
marker = Marker("", self.app.settings) marker = Marker("", self.app.settings)
# marker.setFixedHeight(20) # marker.setFixedHeight(20)
marker.updated.connect(self.app.markerUpdated) marker.updated.connect(self.app.markerUpdated)
@ -56,12 +65,9 @@ class MarkerControl(Control):
self.layout.addRow(layout2) self.layout.addRow(layout2)
self.showMarkerButton = QtWidgets.QPushButton() self.showMarkerButton = ShowButton()
self.showMarkerButton.setFixedHeight(20) self.showMarkerButton.setFixedHeight(20)
if self.app.marker_frame.isHidden(): self.showMarkerButton.setText()
self.showMarkerButton.setText("Show data")
else:
self.showMarkerButton.setText("Hide data")
self.showMarkerButton.clicked.connect(self.toggle_frame) self.showMarkerButton.clicked.connect(self.toggle_frame)
lock_radiobutton = QtWidgets.QRadioButton("Locked") lock_radiobutton = QtWidgets.QRadioButton("Locked")
@ -76,10 +82,10 @@ class MarkerControl(Control):
def toggle_frame(self): def toggle_frame(self):
def settings(hidden: bool): def settings(hidden: bool):
self.app.marker_frame.setHidden(not hidden) Defaults.cfg.gui.markers_hidden = not hidden
self.app.settings.setValue("MarkersVisible", hidden) self.app.marker_frame.setHidden(
self.showMarkerButton.setText( Defaults.cfg.gui.markers_hidden)
"Hide data" if hidden else "Show data") self.showMarkerButton.setText()
self.showMarkerButton.repaint() self.showMarkerButton.repaint()
settings(self.app.marker_frame.isHidden()) settings(self.app.marker_frame.isHidden())

Wyświetl plik

@ -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 <https://www.gnu.org/licenses/>.
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

Wyświetl plik

@ -24,6 +24,7 @@ from time import strftime, localtime
from PyQt5 import QtWidgets, QtCore, QtGui from PyQt5 import QtWidgets, QtCore, QtGui
from NanoVNASaver import Defaults
from .Windows import ( from .Windows import (
AboutWindow, AnalysisWindow, CalibrationWindow, AboutWindow, AnalysisWindow, CalibrationWindow,
DeviceSettingsWindow, DisplaySettingsWindow, SweepSettingsWindow, DeviceSettingsWindow, DisplaySettingsWindow, SweepSettingsWindow,
@ -69,10 +70,11 @@ class NanoVNASaver(QtWidgets.QWidget):
else: else:
self.icon = QtGui.QIcon("icon_48x48.png") self.icon = QtGui.QIcon("icon_48x48.png")
self.setWindowIcon(self.icon) self.setWindowIcon(self.icon)
self.settings = QtCore.QSettings(QtCore.QSettings.IniFormat, self.settings = Defaults.AppSettings(QtCore.QSettings.IniFormat,
QtCore.QSettings.UserScope, QtCore.QSettings.UserScope,
"NanoVNASaver", "NanoVNASaver") "NanoVNASaver", "NanoVNASaver")
logger.info("Settings from: %s", self.settings.fileName()) logger.info("Settings from: %s", self.settings.fileName())
Defaults.cfg = Defaults.restore(self.settings)
self.threadpool = QtCore.QThreadPool() self.threadpool = QtCore.QThreadPool()
self.sweep = Sweep() self.sweep = Sweep()
self.worker = SweepWorker(self) self.worker = SweepWorker(self)
@ -121,9 +123,8 @@ class NanoVNASaver(QtWidgets.QWidget):
outer.addWidget(scrollarea) outer.addWidget(scrollarea)
self.setLayout(outer) self.setLayout(outer)
scrollarea.setWidgetResizable(True) scrollarea.setWidgetResizable(True)
window_width = self.settings.value("WindowWidth", 1350, type=int) self.resize(Defaults.cfg.gui.window_width,
window_height = self.settings.value("WindowHeight", 950, type=int) Defaults.cfg.gui.window_height)
self.resize(window_width, window_height)
scrollarea.setSizePolicy( scrollarea.setSizePolicy(
QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding) QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)
self.setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, self.setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding,
@ -203,18 +204,14 @@ class NanoVNASaver(QtWidgets.QWidget):
left_column = QtWidgets.QVBoxLayout() left_column = QtWidgets.QVBoxLayout()
right_column = QtWidgets.QVBoxLayout() right_column = QtWidgets.QVBoxLayout()
right_column.addLayout(self.charts_layout) right_column.addLayout(self.charts_layout)
self.marker_frame.setHidden( self.marker_frame.setHidden(Defaults.cfg.gui.markers_hidden)
not self.settings.value("MarkersVisible", True, bool))
chart_widget = QtWidgets.QWidget() chart_widget = QtWidgets.QWidget()
chart_widget.setLayout(right_column) chart_widget.setLayout(right_column)
self.splitter = QtWidgets.QSplitter() self.splitter = QtWidgets.QSplitter()
self.splitter.addWidget(self.marker_frame) self.splitter.addWidget(self.marker_frame)
self.splitter.addWidget(chart_widget) self.splitter.addWidget(chart_widget)
try: self.splitter.restoreState(Defaults.cfg.gui.splitter_sizes)
self.splitter.restoreState(self.settings.value("SplitterSizes"))
except TypeError:
pass
layout.addLayout(left_column) layout.addLayout(left_column)
layout.addWidget(self.splitter, 2) layout.addWidget(self.splitter, 2)
@ -638,17 +635,18 @@ class NanoVNASaver(QtWidgets.QWidget):
def closeEvent(self, a0: QtGui.QCloseEvent) -> None: def closeEvent(self, a0: QtGui.QCloseEvent) -> None:
self.worker.stopped = True self.worker.stopped = True
self.settings.setValue("MarkerCount", Marker.count())
for marker in self.markers: for marker in self.markers:
marker.update_settings() 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.settings.sync()
self.bands.saveSettings() self.bands.saveSettings()
self.threadpool.waitForDone(2500) 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() a0.accept()
sys.exit() sys.exit()

Wyświetl plik

@ -34,6 +34,13 @@ def clamp_value(value: Real, rmin: Real, rmax: Real) -> Real:
return rmax return rmax
return value 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): class Format(NamedTuple):
max_nr_digits: int = 6 max_nr_digits: int = 6
@ -74,8 +81,8 @@ class Value:
self._value = decimal.Decimal(value, context=Value.CTX) self._value = decimal.Decimal(value, context=Value.CTX)
def __repr__(self) -> str: def __repr__(self) -> str:
return (f"{self.__class__.__name__}(" + repr(self._value) + return (f"{self.__class__.__name__}("
f", '{self._unit}', {self.fmt})") f"{repr(self._value)}, '{self._unit}', {self.fmt})")
def __str__(self) -> str: def __str__(self) -> str:
fmt = self.fmt fmt = self.fmt
@ -100,10 +107,10 @@ class Value:
max_digits = fmt.max_nr_digits + ( 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) < 10 else 0) +
(1 if not fmt.fix_decimals and abs(real) < 100 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: if self.fmt.allways_signed:
formstr = "+" + formstr formstr = f"+{formstr}"
result = format(real, formstr) result = format(real, formstr)
if float(result) == 0.0: if float(result) == 0.0:

Wyświetl plik

@ -16,6 +16,7 @@
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>. # along with this program. If not, see <https://www.gnu.org/licenses/>.
import contextlib
import logging import logging
from time import strftime, localtime from time import strftime, localtime
from urllib import request, error from urllib import request, error
@ -54,7 +55,7 @@ class AboutWindow(QtWidgets.QWidget):
layout.addWidget(QtWidgets.QLabel("")) layout.addWidget(QtWidgets.QLabel(""))
layout.addWidget(QtWidgets.QLabel( layout.addWidget(QtWidgets.QLabel(
"\N{COPYRIGHT SIGN} Copyright 2019, 2020 Rune B. Broberg\n" "\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( layout.addWidget(QtWidgets.QLabel(
"This program comes with ABSOLUTELY NO WARRANTY")) "This program comes with ABSOLUTELY NO WARRANTY"))
@ -79,21 +80,6 @@ class AboutWindow(QtWidgets.QWidget):
btn_check_version.clicked.connect(self.findUpdates) btn_check_version.clicked.connect(self.findUpdates)
self.updateLabel = QtWidgets.QLabel("Last checked: ") 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 = QtWidgets.QHBoxLayout()
update_hbox.addWidget(btn_check_version) update_hbox.addWidget(btn_check_version)
@ -101,7 +87,6 @@ class AboutWindow(QtWidgets.QWidget):
update_hbox.addLayout(update_form) update_hbox.addLayout(update_form)
update_hbox.addStretch() update_hbox.addStretch()
update_form.addRow(self.updateLabel) update_form.addRow(self.updateLabel)
update_form.addRow(self.updateCheckBox)
layout.addLayout(update_hbox) layout.addLayout(update_hbox)
layout.addStretch() layout.addStretch()
@ -115,39 +100,10 @@ class AboutWindow(QtWidgets.QWidget):
self.updateLabels() self.updateLabels()
def updateLabels(self): def updateLabels(self):
try: with contextlib.suppress(IOError, AttributeError):
self.versionLabel.setText( self.versionLabel.setText(
f"NanoVNA Firmware Version: {self.app.vna.name} " f"NanoVNA Firmware Version: {self.app.vna.name} "
f"v{self.app.vna.version}") 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): def findUpdates(self, automatic=False):
latest_version = Version() latest_version = Version()

Wyświetl plik

@ -2,7 +2,7 @@
# #
# A python program to view and export Touchstone data from a NanoVNA # A python program to view and export Touchstone data from a NanoVNA
# Copyright (C) 2019, 2020 Rune B. Broberg # 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 # 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 # 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 PyQt5 import QtWidgets, QtCore, QtGui
from NanoVNASaver import Defaults
from NanoVNASaver.Charts.Chart import ( from NanoVNASaver.Charts.Chart import (
Chart, ChartColors, ChartMarker, ChartMarkerConfig) Chart, ChartColors)
from NanoVNASaver.Windows.Bands import BandsWindow from NanoVNASaver.Windows.Bands import BandsWindow
from NanoVNASaver.Windows.MarkerSettings import MarkerSettingsWindow from NanoVNASaver.Windows.MarkerSettings import MarkerSettingsWindow
from NanoVNASaver.Marker import Marker from NanoVNASaver.Marker import Marker
@ -37,7 +38,6 @@ class DisplaySettingsWindow(QtWidgets.QWidget):
self.app = app self.app = app
self.setWindowTitle("Display settings") self.setWindowTitle("Display settings")
self.setWindowIcon(self.app.icon) self.setWindowIcon(self.app.icon)
self.marker_cfg = ChartMarkerConfig()
self.marker_window = MarkerSettingsWindow(self.app) self.marker_window = MarkerSettingsWindow(self.app)
self.callback_params = {} 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("Return loss is:", self.returnloss_is_negative)
display_options_layout.addRow("", self.returnloss_is_positive) display_options_layout.addRow("", self.returnloss_is_positive)
if self.app.settings.value("ReturnLossPositive", False, bool): self.returnloss_is_positive.setChecked(
self.returnloss_is_positive.setChecked(True) Defaults.cfg.chart.returnloss_is_positive)
else: self.returnloss_is_negative.setChecked(
self.returnloss_is_negative.setChecked(True) not Defaults.cfg.chart.returnloss_is_positive)
self.returnloss_is_positive.toggled.connect(self.changeReturnLoss) self.returnloss_is_positive.toggled.connect(self.changeReturnLoss)
self.changeReturnLoss() self.changeReturnLoss()
@ -83,7 +83,7 @@ class DisplaySettingsWindow(QtWidgets.QWidget):
self.pointSizeInput = QtWidgets.QSpinBox() self.pointSizeInput = QtWidgets.QSpinBox()
self.pointSizeInput.setMinimumHeight(20) self.pointSizeInput.setMinimumHeight(20)
pointsize = self.app.settings.value("PointSize", 2, int) pointsize = Defaults.cfg.chart.point_size
self.pointSizeInput.setValue(pointsize) self.pointSizeInput.setValue(pointsize)
self.changePointSize(pointsize) self.changePointSize(pointsize)
self.pointSizeInput.setMinimum(1) self.pointSizeInput.setMinimum(1)
@ -95,7 +95,7 @@ class DisplaySettingsWindow(QtWidgets.QWidget):
self.lineThicknessInput = QtWidgets.QSpinBox() self.lineThicknessInput = QtWidgets.QSpinBox()
self.lineThicknessInput.setMinimumHeight(20) self.lineThicknessInput.setMinimumHeight(20)
linethickness = self.app.settings.value("LineThickness", 1, int) linethickness = Defaults.cfg.chart.line_thickness
self.lineThicknessInput.setValue(linethickness) self.lineThicknessInput.setValue(linethickness)
self.changeLineThickness(linethickness) self.changeLineThickness(linethickness)
self.lineThicknessInput.setMinimum(1) self.lineThicknessInput.setMinimum(1)
@ -107,10 +107,9 @@ class DisplaySettingsWindow(QtWidgets.QWidget):
self.markerSizeInput = QtWidgets.QSpinBox() self.markerSizeInput = QtWidgets.QSpinBox()
self.markerSizeInput.setMinimumHeight(20) self.markerSizeInput.setMinimumHeight(20)
markersize = self.app.settings.value("MarkerSize", 6, int) markersize = Defaults.cfg.chart.marker_size
self.markerSizeInput.setValue(markersize) self.markerSizeInput.setValue(markersize)
self.markerSizeInput.setMinimum(4) self.markerSizeInput.setMinimum(4)
self.markerSizeInput.setMinimum(4)
self.markerSizeInput.setMaximum(20) self.markerSizeInput.setMaximum(20)
self.markerSizeInput.setSingleStep(2) self.markerSizeInput.setSingleStep(2)
self.markerSizeInput.setSuffix(" px") 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("Data point is:", self.marker_at_center)
display_options_layout.addRow("", self.marker_at_tip) display_options_layout.addRow("", self.marker_at_tip)
if self.app.settings.value("MarkerAtTip", False, bool): self.marker_at_tip.setChecked(Defaults.cfg.chart.marker_at_tip)
self.marker_at_tip.setChecked(True) self.marker_at_center.setChecked(not Defaults.cfg.chart.marker_at_tip)
else:
self.marker_at_center.setChecked(True)
self.marker_at_tip.toggled.connect(self.changeMarkerAtTip) self.marker_at_tip.toggled.connect(self.changeMarkerAtTip)
self.changeMarkerAtTip() self.changeMarkerAtTip()
@ -162,10 +159,7 @@ class DisplaySettingsWindow(QtWidgets.QWidget):
self.font_dropdown = QtWidgets.QComboBox() self.font_dropdown = QtWidgets.QComboBox()
self.font_dropdown.setMinimumHeight(20) self.font_dropdown.setMinimumHeight(20)
self.font_dropdown.addItems(["7", "8", "9", "10", "11", "12"]) self.font_dropdown.addItems(["7", "8", "9", "10", "11", "12"])
font_size = self.app.settings.value("FontSize", self.font_dropdown.setCurrentText(str(Defaults.cfg.gui.font_size))
defaultValue="8",
type=str)
self.font_dropdown.setCurrentText(font_size)
self.changeFont() self.changeFont()
self.font_dropdown.currentTextChanged.connect(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) self.vswrMarkers: List[float] = self.app.settings.value("VSWRMarkers", [], float)
if isinstance(self.vswrMarkers, float): if isinstance(self.vswrMarkers, float):
if self.vswrMarkers == 0: # Single values from the .ini become floats rather than lists. Convert them.
self.vswrMarkers = [] self.vswrMarkers = [] if self.vswrMarkers == 0.0 else [self.vswrMarkers]
else:
# Single values from the .ini become floats rather than lists. Convert them.
self.vswrMarkers = [self.vswrMarkers]
vswr_marker_layout.addRow( vswr_marker_layout.addRow(
"VSWR Markers",self.color_picker("VSWRColor", "swr")) "VSWR Markers",self.color_picker("VSWRColor", "swr"))
@ -209,7 +200,7 @@ class DisplaySettingsWindow(QtWidgets.QWidget):
self.vswr_marker_dropdown.setMinimumHeight(20) self.vswr_marker_dropdown.setMinimumHeight(20)
vswr_marker_layout.addRow(self.vswr_marker_dropdown) vswr_marker_layout.addRow(self.vswr_marker_dropdown)
if len(self.vswrMarkers) == 0: if not self.vswrMarkers:
self.vswr_marker_dropdown.addItem("None") self.vswr_marker_dropdown.addItem("None")
else: else:
for m in self.vswrMarkers: for m in self.vswrMarkers:
@ -262,10 +253,7 @@ class DisplaySettingsWindow(QtWidgets.QWidget):
# "S21 Phase", # "S21 Phase",
# "None"] # "None"]
selections = [] selections = [c.name for c in self.app.selectable_charts]
for c in self.app.selectable_charts:
selections.append(c.name)
selections.append("None") selections.append("None")
chart00_selection = QtWidgets.QComboBox() chart00_selection = QtWidgets.QComboBox()
@ -364,14 +352,11 @@ class DisplaySettingsWindow(QtWidgets.QWidget):
"VSWRColor", defaultValue=ChartColors.swr, "VSWRColor", defaultValue=ChartColors.swr,
type=QtGui.QColor) type=QtGui.QColor)
self.dark_mode_option.setChecked( self.dark_mode_option.setChecked(Defaults.cfg.gui.dark_mode)
self.app.settings.value("DarkMode", False, bool)) self.show_lines_option.setChecked(Defaults.cfg.chart.show_lines)
self.show_lines_option.setChecked(
self.app.settings.value("ShowLines", False, bool))
self.show_marker_number_option.setChecked( self.show_marker_number_option.setChecked(
self.app.settings.value("ShowMarkerNumbers", ChartMarkerConfig.draw_label, bool)) Defaults.cfg.chart.marker_label)
self.filled_marker_option.setChecked( self.filled_marker_option.setChecked(Defaults.cfg.chart.marker_filled)
self.app.settings.value("FilledMarkers", ChartMarkerConfig.fill, bool))
if self.app.settings.value("UseCustomColors", if self.app.settings.value("UseCustomColors",
defaultValue=False, type=bool): defaultValue=False, type=bool):
@ -450,8 +435,7 @@ class DisplaySettingsWindow(QtWidgets.QWidget):
def changeReturnLoss(self): def changeReturnLoss(self):
state = self.returnloss_is_positive.isChecked() 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: for m in self.app.markers:
m.returnloss_is_positive = state m.returnloss_is_positive = state
m.updateLabels(self.app.data.s11, self.app.data.s21) m.updateLabels(self.app.data.s11, self.app.data.s21)
@ -462,56 +446,51 @@ class DisplaySettingsWindow(QtWidgets.QWidget):
def changeShowLines(self): def changeShowLines(self):
state = self.show_lines_option.isChecked() 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: for c in self.app.subscribing_charts:
c.setDrawLines(state) c.setDrawLines(state)
def changeShowMarkerNumber(self): def changeShowMarkerNumber(self):
state = self.show_marker_number_option.isChecked() Defaults.cfg.chart.marker_label = bool(
self.app.settings.setValue("ShowMarkerNumbers", state) self.show_marker_number_option.isChecked())
ChartMarker.cfg.draw_label = state
self.updateCharts() self.updateCharts()
def changeFilledMarkers(self): def changeFilledMarkers(self):
state = self.filled_marker_option.isChecked() Defaults.cfg.chart.marker_filled = bool(
self.app.settings.setValue("FilledMarkers", state) self.filled_marker_option.isChecked())
ChartMarker.cfg.fill = state
self.updateCharts() self.updateCharts()
def changeMarkerAtTip(self): def changeMarkerAtTip(self):
state = self.marker_at_tip.isChecked() Defaults.cfg.chart.marker_at_tip = bool(
self.app.settings.setValue("MarkerAtTip", state) self.marker_at_tip.isChecked())
ChartMarker.cfg.at_tip = state
self.updateCharts() self.updateCharts()
def changePointSize(self, size: int): def changePointSize(self, size: int):
self.app.settings.setValue("PointSize", size) Defaults.cfg.chart.point_size = size
for c in self.app.subscribing_charts: for c in self.app.subscribing_charts:
c.setPointSize(size) c.setPointSize(size)
def changeLineThickness(self, size: int): 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: for c in self.app.subscribing_charts:
c.setLineThickness(size) c.setLineThickness(size)
def changeMarkerSize(self, size: int): def changeMarkerSize(self, size: int):
self.app.settings.setValue("MarkerSize", size) Defaults.cfg.chart.marker_size = size
ChartMarker.cfg.size = size
self.markerSizeInput.setValue(size) self.markerSizeInput.setValue(size)
self.updateCharts() self.updateCharts()
def changeDarkMode(self): def changeDarkMode(self):
state = self.dark_mode_option.isChecked() 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) Chart.color.foreground = QtGui.QColor(QtCore.Qt.lightGray)
if state: if state:
Chart.color.background = QtGui.QColor(QtCore.Qt.black) Chart.color.background = QtGui.QColor(QtCore.Qt.black)
Chart.color.text = QtGui.QColor(QtCore.Qt.white) Chart.color.text = QtGui.QColor(QtCore.Qt.white)
Chart.color.swr = Chart.color.swr
else: else:
Chart.color.background = QtGui.QColor(QtCore.Qt.white) Chart.color.background = QtGui.QColor(QtCore.Qt.white)
Chart.color.text = QtGui.QColor(QtCore.Qt.black) Chart.color.text = QtGui.QColor(QtCore.Qt.black)
Chart.color.swr = Chart.color.swr Chart.color.swr = Chart.color.swr
self.updateCharts() self.updateCharts()
def changeSetting(self, setting: str, value: str): def changeSetting(self, setting: str, value: str):
@ -547,11 +526,11 @@ class DisplaySettingsWindow(QtWidgets.QWidget):
c.update() c.update()
def changeFont(self): def changeFont(self):
font_size = self.font_dropdown.currentText() font_size = int(self.font_dropdown.currentText())
self.app.settings.setValue("FontSize", font_size) Defaults.cfg.gui.font_size = font_size
app: QtWidgets.QApplication = QtWidgets.QApplication.instance() app: QtWidgets.QApplication = QtWidgets.QApplication.instance()
font = app.font() font = app.font()
font.setPointSize(int(font_size)) font.setPointSize(font_size)
app.setFont(font) app.setFont(font)
self.app.changeFont(font) self.app.changeFont(font)
@ -628,3 +607,4 @@ class DisplaySettingsWindow(QtWidgets.QWidget):
def updateCharts(self): def updateCharts(self):
for c in self.app.subscribing_charts: for c in self.app.subscribing_charts:
c.update() c.update()
Defaults.store(self.app.settings, Defaults.cfg)

2
debug.sh 100755
Wyświetl plik

@ -0,0 +1,2 @@
#!/bin/sh
exec python -m debugpy --listen 5678 --wait-for-client $@

Wyświetl plik

@ -15,10 +15,13 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>. # along with this program. If not, see <https://www.gnu.org/licenses/>.
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 import pkg_resources.py2_warn
except ImportError:
pass
from NanoVNASaver.__main__ import main from NanoVNASaver.__main__ import main
if __name__ == '__main__': if __name__ == '__main__':

Wyświetl plik

@ -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 <https://www.gnu.org/licenses/>.
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())