diff --git a/NanoVNASaver/Analysis.py b/NanoVNASaver/Analysis.py index ad895420..b8a8c83d 100644 --- a/NanoVNASaver/Analysis.py +++ b/NanoVNASaver/Analysis.py @@ -199,11 +199,12 @@ def runAnalysis(self): if sixty_db_location > 0: sixty_db_cutoff_frequency = self.app.data21[sixty_db_location].freq self.sixty_db_label.setText(NanoVNASaver.formatFrequency(sixty_db_cutoff_frequency)) + elif ten_db_location != -1 and twenty_db_location != -1: + ten = self.app.data21[ten_db_location].freq + twenty = self.app.data21[twenty_db_location].freq + sixty_db_frequency = ten * 10 ** (5 * (math.log10(twenty) - math.log10(ten))) + self.sixty_db_label.setText(NanoVNASaver.formatFrequency(sixty_db_frequency) + " (derived)") else: - # # We derive 60 dB instead - # factor = 10 * (-54 / decade_attenuation) - # sixty_db_cutoff_frequency = round(six_db_cutoff_frequency + six_db_cutoff_frequency * factor) - # self.sixty_db_label.setText(NanoVNASaver.formatFrequency(sixty_db_cutoff_frequency) + " (derived)") self.sixty_db_label.setText("Not calculated") if ten_db_location > 0 and twenty_db_location > 0 and ten_db_location != twenty_db_location: @@ -358,14 +359,16 @@ def runAnalysis(self): break if sixty_db_location > 0: - sixty_db_cutoff_frequency = self.app.data21[sixty_db_location].freq - self.sixty_db_label.setText(NanoVNASaver.formatFrequency(sixty_db_cutoff_frequency)) - else: - # # We derive 60 dB instead - # factor = 10 * (-54 / decade_attenuation) - # sixty_db_cutoff_frequency = round(six_db_cutoff_frequency + six_db_cutoff_frequency * factor) - # self.sixty_db_label.setText(NanoVNASaver.formatFrequency(sixty_db_cutoff_frequency) + " (derived)") - self.sixty_db_label.setText("Not calculated") + if sixty_db_location > 0: + sixty_db_cutoff_frequency = self.app.data21[sixty_db_location].freq + self.sixty_db_label.setText(NanoVNASaver.formatFrequency(sixty_db_cutoff_frequency)) + elif ten_db_location != -1 and twenty_db_location != -1: + ten = self.app.data21[ten_db_location].freq + twenty = self.app.data21[twenty_db_location].freq + sixty_db_frequency = ten * 10 ** (5 * (math.log10(twenty) - math.log10(ten))) + self.sixty_db_label.setText(NanoVNASaver.formatFrequency(sixty_db_frequency) + " (derived)") + else: + self.sixty_db_label.setText("Not calculated") if ten_db_location > 0 and twenty_db_location > 0 and ten_db_location != twenty_db_location: octave_attenuation, decade_attenuation = self.calculateRolloff(ten_db_location, twenty_db_location) @@ -610,14 +613,16 @@ def runAnalysis(self): break if sixty_db_location > 0: - sixty_db_cutoff_frequency = self.app.data21[sixty_db_location].freq - self.lower_sixty_db_label.setText(NanoVNASaver.formatFrequency(sixty_db_cutoff_frequency)) - else: - # # We derive 60 dB instead - # factor = 10 * (-54 / decade_attenuation) - # sixty_db_cutoff_frequency = round(six_db_cutoff_frequency + six_db_cutoff_frequency * factor) - # self.sixty_db_label.setText(NanoVNASaver.formatFrequency(sixty_db_cutoff_frequency) + " (derived)") - self.lower_sixty_db_label.setText("Not calculated") + if sixty_db_location > 0: + sixty_db_cutoff_frequency = self.app.data21[sixty_db_location].freq + self.lower_sixty_db_label.setText(NanoVNASaver.formatFrequency(sixty_db_cutoff_frequency)) + elif ten_db_location != -1 and twenty_db_location != -1: + ten = self.app.data21[ten_db_location].freq + twenty = self.app.data21[twenty_db_location].freq + sixty_db_frequency = ten * 10 ** (5 * (math.log10(twenty) - math.log10(ten))) + self.lower_sixty_db_label.setText(NanoVNASaver.formatFrequency(sixty_db_frequency) + " (derived)") + else: + self.lower_sixty_db_label.setText("Not calculated") if ten_db_location > 0 and twenty_db_location > 0 and ten_db_location != twenty_db_location: octave_attenuation, decade_attenuation = self.calculateRolloff(ten_db_location, twenty_db_location) @@ -674,11 +679,12 @@ def runAnalysis(self): if sixty_db_location > 0: sixty_db_cutoff_frequency = self.app.data21[sixty_db_location].freq self.upper_sixty_db_label.setText(NanoVNASaver.formatFrequency(sixty_db_cutoff_frequency)) + elif ten_db_location != -1 and twenty_db_location != -1: + ten = self.app.data21[ten_db_location].freq + twenty = self.app.data21[twenty_db_location].freq + sixty_db_frequency = ten * 10 ** (5 * (math.log10(twenty) - math.log10(ten))) + self.upper_sixty_db_label.setText(NanoVNASaver.formatFrequency(sixty_db_frequency) + " (derived)") else: - # # We derive 60 dB instead - # factor = 10 * (-54 / decade_attenuation) - # sixty_db_cutoff_frequency = round(six_db_cutoff_frequency + six_db_cutoff_frequency * factor) - # self.sixty_db_label.setText(NanoVNASaver.formatFrequency(sixty_db_cutoff_frequency) + " (derived)") self.upper_sixty_db_label.setText("Not calculated") if ten_db_location > 0 and twenty_db_location > 0 and ten_db_location != twenty_db_location: @@ -885,11 +891,12 @@ def runAnalysis(self): if sixty_db_location > 0: sixty_db_cutoff_frequency = self.app.data21[sixty_db_location].freq self.lower_sixty_db_label.setText(NanoVNASaver.formatFrequency(sixty_db_cutoff_frequency)) + elif ten_db_location != -1 and twenty_db_location != -1: + ten = self.app.data21[ten_db_location].freq + twenty = self.app.data21[twenty_db_location].freq + sixty_db_frequency = ten * 10 ** (5 * (math.log10(twenty) - math.log10(ten))) + self.lower_sixty_db_label.setText(NanoVNASaver.formatFrequency(sixty_db_frequency) + " (derived)") else: - # # We derive 60 dB instead - # factor = 10 * (-54 / decade_attenuation) - # sixty_db_cutoff_frequency = round(six_db_cutoff_frequency + six_db_cutoff_frequency * factor) - # self.sixty_db_label.setText(NanoVNASaver.formatFrequency(sixty_db_cutoff_frequency) + " (derived)") self.lower_sixty_db_label.setText("Not calculated") if ten_db_location > 0 and twenty_db_location > 0 and ten_db_location != twenty_db_location: @@ -947,11 +954,12 @@ def runAnalysis(self): if sixty_db_location > 0: sixty_db_cutoff_frequency = self.app.data21[sixty_db_location].freq self.upper_sixty_db_label.setText(NanoVNASaver.formatFrequency(sixty_db_cutoff_frequency)) + elif ten_db_location != -1 and twenty_db_location != -1: + ten = self.app.data21[ten_db_location].freq + twenty = self.app.data21[twenty_db_location].freq + sixty_db_frequency = ten * 10 ** (5 * (math.log10(twenty) - math.log10(ten))) + self.upper_sixty_db_label.setText(NanoVNASaver.formatFrequency(sixty_db_frequency) + " (derived)") else: - # # We derive 60 dB instead - # factor = 10 * (-54 / decade_attenuation) - # sixty_db_cutoff_frequency = round(six_db_cutoff_frequency + six_db_cutoff_frequency * factor) - # self.sixty_db_label.setText(NanoVNASaver.formatFrequency(sixty_db_cutoff_frequency) + " (derived)") self.upper_sixty_db_label.setText("Not calculated") if ten_db_location > 0 and twenty_db_location > 0 and ten_db_location != twenty_db_location: diff --git a/NanoVNASaver/Calibration.py b/NanoVNASaver/Calibration.py index 8574c2cc..83f65b8d 100644 --- a/NanoVNASaver/Calibration.py +++ b/NanoVNASaver/Calibration.py @@ -138,10 +138,10 @@ def __init__(self, app): self.short_l2_input = QtWidgets.QLineEdit("0") self.short_l3_input = QtWidgets.QLineEdit("0") self.short_length = QtWidgets.QLineEdit("0") - cal_short_form.addRow("L0 (F(e-12))", self.short_l0_input) - cal_short_form.addRow("L1 (F(e-24))", self.short_l1_input) - cal_short_form.addRow("L2 (F(e-33))", self.short_l2_input) - cal_short_form.addRow("L3 (F(e-42))", self.short_l3_input) + cal_short_form.addRow("L0 (H(e-12))", self.short_l0_input) + cal_short_form.addRow("L1 (H(e-24))", self.short_l1_input) + cal_short_form.addRow("L2 (H(e-33))", self.short_l2_input) + cal_short_form.addRow("L3 (H(e-42))", self.short_l3_input) cal_short_form.addRow("Offset Delay (ps)", self.short_length) self.cal_open_box = QtWidgets.QGroupBox("Open") @@ -152,10 +152,10 @@ def __init__(self, app): self.open_c2_input = QtWidgets.QLineEdit("0") self.open_c3_input = QtWidgets.QLineEdit("0") self.open_length = QtWidgets.QLineEdit("0") - cal_open_form.addRow("C0 (H(e-15))", self.open_c0_input) - cal_open_form.addRow("C1 (H(e-27))", self.open_c1_input) - cal_open_form.addRow("C2 (H(e-36))", self.open_c2_input) - cal_open_form.addRow("C3 (H(e-45))", self.open_c3_input) + cal_open_form.addRow("C0 (F(e-15))", self.open_c0_input) + cal_open_form.addRow("C1 (F(e-27))", self.open_c1_input) + cal_open_form.addRow("C2 (F(e-36))", self.open_c2_input) + cal_open_form.addRow("C3 (F(e-45))", self.open_c3_input) cal_open_form.addRow("Offset Delay (ps)", self.open_length) self.cal_load_box = QtWidgets.QGroupBox("Load") @@ -510,15 +510,15 @@ def automaticCalibration(self): "Calibration assistant", "This calibration assistant will help you create a calibration in the " + "NanoVNASaver application. It will sweep the standards for you, and "+ - "guide you through the process.\n\n" + + "guide you through the process.

" + "Before starting, ensure you have Open, Short and Load standards " + "available, and the cables you wish to have calibrated with the device " + - "connected.\n\n" + + "connected.

" + "If you want a 2-port calibration, also have a \"through\" connector " + - "to hand.\n\n" + - "The best results are achieved by having the NanoVNA calibrated " + + "to hand.

" + + "The best results are achieved by having the NanoVNA calibrated " + "on-device for the full span of interest and saved to save slot 0 " + - "before starting.\n\n" + + "before starting.

" + "Once you are ready to proceed, press Ok", QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel) response = introduction.exec() @@ -533,6 +533,12 @@ def automaticCalibration(self): self.btn_automatic.setDisabled(False) return + if self.app.sweepSettingsWindow.continuous_sweep_radiobutton.isChecked(): + QtWidgets.QMessageBox(QtWidgets.QMessageBox.Information, "Continuous sweep enabled", + "Please disable continuous sweeping before attempting calibration.").exec() + self.btn_automatic.setDisabled(False) + return + short_step = QtWidgets.QMessageBox(QtWidgets.QMessageBox.Information, "Calibrate short", "Please connect the \"short\" standard to port 0 of the NanoVNA.\n\n" + diff --git a/NanoVNASaver/Chart.py b/NanoVNASaver/Chart.py index 87d42ef8..d40aab40 100644 --- a/NanoVNASaver/Chart.py +++ b/NanoVNASaver/Chart.py @@ -15,11 +15,12 @@ # along with this program. If not, see . import collections import math -from typing import List +from typing import List, Set import numpy as np import logging from PyQt5 import QtWidgets, QtGui, QtCore +from PyQt5.QtCore import pyqtSignal from .Marker import Marker logger = logging.getLogger(__name__) @@ -36,15 +37,24 @@ class Chart(QtWidgets.QWidget): backgroundColor: QtGui.QColor = QtGui.QColor(QtCore.Qt.white) foregroundColor: QtGui.QColor = QtGui.QColor(QtCore.Qt.lightGray) textColor: QtGui.QColor = QtGui.QColor(QtCore.Qt.black) + swrColor: QtGui.QColor = QtGui.QColor(QtCore.Qt.red) + swrColor.setAlpha(128) data: List[Datapoint] = [] reference: List[Datapoint] = [] markers: List[Marker] = [] + swrMarkers: Set[float] = set() bands = None draggedMarker: Marker = None name = "" drawLines = False minChartHeight = 200 minChartWidth = 200 + lineThickness = 1 + pointSize = 2 + + + isPopout = False + popoutRequested = pyqtSignal(object) def __init__(self, name): super().__init__() @@ -54,6 +64,11 @@ def __init__(self, name): self.action_save_screenshot = QtWidgets.QAction("Save image") self.action_save_screenshot.triggered.connect(self.saveScreenshot) self.addAction(self.action_save_screenshot) + self.action_popout = QtWidgets.QAction("Popout chart") + self.action_popout.triggered.connect(lambda: self.popoutRequested.emit(self)) + self.addAction(self.action_popout) + + self.swrMarkers = set() def setSweepColor(self, color : QtGui.QColor): self.sweepColor = color @@ -104,6 +119,14 @@ def setMarkers(self, markers): def setBands(self, bands): self.bands = bands + def setLineThickness(self, thickness): + self.lineThickness = thickness + self.update() + + def setPointSize(self, size): + self.pointSize = size + self.update() + def getActiveMarker(self, event: QtGui.QMouseEvent) -> Marker: if self.draggedMarker is not None: return self.draggedMarker @@ -171,6 +194,46 @@ def saveScreenshot(self): if filename != "": self.grab().save(filename) + def copy(self): + new_chart = self.__class__(self.name) + new_chart.data = self.data + new_chart.reference = self.reference + new_chart.sweepColor = self.sweepColor + new_chart.secondarySweepColor = self.secondarySweepColor + new_chart.referenceColor = self.referenceColor + new_chart.secondaryReferenceColor = self.secondaryReferenceColor + new_chart.setBackgroundColor(self.backgroundColor) + new_chart.textColor = self.textColor + new_chart.foregroundColor = self.foregroundColor + new_chart.swrColor = self.swrColor + new_chart.markers = self.markers + new_chart.swrMarkers = self.swrMarkers + new_chart.bands = self.bands + new_chart.drawLines = self.drawLines + new_chart.resize(self.width(), self.height()) + return new_chart + + def addSWRMarker(self, swr: float): + self.swrMarkers.add(swr) + self.update() + + def removeSWRMarker(self, swr: float): + try: + self.swrMarkers.remove(swr) + except KeyError: + logger.debug("KeyError from %s", self.name) + return + finally: + self.update() + + def clearSWRMarkers(self): + self.swrMarkers.clear() + self.update() + + def setSWRColor(self, color: QtGui.QColor): + self.swrColor = color + self.update() + class FrequencyChart(Chart): fstart = 0 @@ -185,8 +248,7 @@ class FrequencyChart(Chart): fixedSpan = False fixedValues = False - linear = True - logarithmic = False + logarithmicX = False chartWidth = Chart.minChartWidth chartHeight = Chart.minChartHeight @@ -229,6 +291,20 @@ def __init__(self, name): self.x_menu.addAction(self.action_set_fixed_start) self.x_menu.addAction(self.action_set_fixed_stop) + + self.x_menu.addSeparator() + frequency_mode_group = QtWidgets.QActionGroup(self.x_menu) + self.action_set_linear_x = QtWidgets.QAction("Linear") + self.action_set_linear_x.setCheckable(True) + self.action_set_logarithmic_x = QtWidgets.QAction("Logarithmic") + self.action_set_logarithmic_x.setCheckable(True) + frequency_mode_group.addAction(self.action_set_linear_x) + frequency_mode_group.addAction(self.action_set_logarithmic_x) + self.action_set_linear_x.triggered.connect(lambda: self.setLogarithmicX(False)) + self.action_set_logarithmic_x.triggered.connect(lambda: self.setLogarithmicX(True)) + self.action_set_linear_x.setChecked(True) + self.x_menu.addAction(self.action_set_linear_x) + self.x_menu.addAction(self.action_set_logarithmic_x) self.y_menu = QtWidgets.QMenu("Data axis") self.y_action_automatic = QtWidgets.QAction("Automatic") @@ -258,6 +334,9 @@ def __init__(self, name): self.menu.addMenu(self.y_menu) self.menu.addSeparator() self.menu.addAction(self.action_save_screenshot) + self.action_popout = QtWidgets.QAction("Popout chart") + self.action_popout.triggered.connect(lambda: self.popoutRequested.emit(self)) + self.menu.addAction(self.action_popout) def contextMenuEvent(self, event): self.action_set_fixed_start.setText("Start (" + Chart.shortenFrequency(self.minFrequency) + ")") @@ -283,6 +362,10 @@ def setFixedValues(self, fixed_values: bool): self.y_action_fixed_span.setChecked(False) self.update() + def setLogarithmicX(self, logarithmic: bool): + self.logarithmicX = logarithmic + self.update() + def setMinimumFrequency(self): from NanoVNASaver.NanoVNASaver import NanoVNASaver min_freq_str, selected = QtWidgets.QInputDialog.getText(self, "Start frequency", @@ -332,12 +415,18 @@ def resetDisplayLimits(self): self.y_action_automatic.setChecked(True) self.fixedSpan = False self.action_automatic.setChecked(True) + self.logarithmicX = False self.update() def getXPosition(self, d: Datapoint) -> int: span = self.fstop - self.fstart if span > 0: - return self.leftMargin + 1 + round(self.chartWidth * (d.freq - self.fstart) / span) + if self.logarithmicX: + span = math.log(self.fstop) - math.log(self.fstart) + return self.leftMargin + 1 +\ + round(self.chartWidth * (math.log(d.freq) - math.log(self.fstart)) / span) + else: + return self.leftMargin + 1 + round(self.chartWidth * (d.freq - self.fstart) / span) else: return math.floor(self.width()/2) @@ -353,9 +442,14 @@ def mouseMoveEvent(self, a0: QtGui.QMouseEvent) -> None: a0.accept() if self.fstop - self.fstart > 0: m = self.getActiveMarker(a0) - span = self.fstop - self.fstart - step = span/self.chartWidth - f = self.fstart + absx * step + if self.logarithmicX: + span = math.log(self.fstop) - math.log(self.fstart) + step = span/self.chartWidth + f = math.exp(math.log(self.fstart) + absx * step) + else: + span = self.fstop - self.fstart + step = span/self.chartWidth + f = self.fstart + absx * step m.setFrequency(str(round(f))) m.frequencyInput.setText(str(round(f))) return @@ -380,6 +474,23 @@ def paintEvent(self, a0: QtGui.QPaintEvent) -> None: "Data outside frequency span") qp.end() + def drawFrequencyTicks(self, qp): + fspan = self.fstop - self.fstart + qp.setPen(self.textColor) + qp.drawText(self.leftMargin - 20, self.topMargin + self.chartHeight + 15, Chart.shortenFrequency(self.fstart)) + ticks = math.floor(self.chartWidth / 100) # Number of ticks does not include the origin + for i in range(ticks): + x = self.leftMargin + round((i + 1) * self.chartWidth / ticks) + if self.logarithmicX: + fspan = math.log(self.fstop) - math.log(self.fstart) + freq = round(math.exp(((i + 1) * fspan / ticks) + math.log(self.fstart))) + else: + freq = round(fspan / ticks * (i + 1) + self.fstart) + qp.setPen(QtGui.QPen(self.foregroundColor)) + qp.drawLine(x, self.topMargin, x, self.topMargin + self.chartHeight + 5) + qp.setPen(self.textColor) + qp.drawText(x - 20, self.topMargin + self.chartHeight + 15, Chart.shortenFrequency(freq)) + def drawBands(self, qp, fstart, fstop): qp.setBrush(self.bands.color) qp.setPen(QtGui.QColor(128, 128, 128, 0)) # Don't outline the bands @@ -403,9 +514,9 @@ def drawBands(self, qp, fstart, fstop): def drawData(self, qp: QtGui.QPainter, data: List[Datapoint], color: QtGui.QColor): pen = QtGui.QPen(color) - pen.setWidth(2) + pen.setWidth(self.pointSize) line_pen = QtGui.QPen(color) - line_pen.setWidth(1) + line_pen.setWidth(self.lineThickness) qp.setPen(pen) for i in range(len(data)): x, y = self.getPosition(data[i]) @@ -465,17 +576,47 @@ def getPlotable(self, x, y, distantx, distanty): else: return x, y + def copy(self): + new_chart: FrequencyChart = super().copy() + new_chart.fstart = self.fstart + new_chart.fstop = self.fstop + new_chart.maxFrequency = self.maxFrequency + new_chart.minFrequency = self.minFrequency + new_chart.minDisplayValue = self.minDisplayValue + new_chart.maxDisplayValue = self.maxDisplayValue + new_chart.pointSize = self.pointSize + new_chart.lineThickness = self.lineThickness + + new_chart.setFixedSpan(self.fixedSpan) + new_chart.action_automatic.setChecked(not self.fixedSpan) + new_chart.action_fixed_span.setChecked(self.fixedSpan) + + new_chart.setFixedValues(self.fixedValues) + new_chart.y_action_automatic.setChecked(not self.fixedValues) + new_chart.y_action_fixed_span.setChecked(self.fixedValues) + + new_chart.setLogarithmicX(self.logarithmicX) + new_chart.action_set_logarithmic_x.setChecked(self.logarithmicX) + new_chart.action_set_linear_x.setChecked(not self.logarithmicX) + return new_chart + class SquareChart(Chart): def __init__(self, name): super().__init__(name) sizepolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.MinimumExpanding) self.setSizePolicy(sizepolicy) + self.chartWidth = self.width()-40 + self.chartHeight = self.height()-40 def resizeEvent(self, a0: QtGui.QResizeEvent) -> None: - self.setFixedWidth(a0.size().height()) - self.chartWidth = a0.size().height()-40 - self.chartHeight = a0.size().height()-40 + if not self.isPopout: + self.setFixedWidth(a0.size().height()) + self.chartWidth = a0.size().height()-40 + self.chartHeight = a0.size().height()-40 + else: + min_dimension = min(a0.size().height(), a0.size().width()) + self.chartWidth = self.chartHeight = min_dimension - 40 self.update() @@ -511,6 +652,12 @@ def __init__(self, name=""): self.action_unwrap.triggered.connect(lambda: self.setUnwrap(self.action_unwrap.isChecked())) self.y_menu.addAction(self.action_unwrap) + def copy(self): + new_chart: PhaseChart = super().copy() + new_chart.setUnwrap(self.unwrap) + new_chart.action_unwrap.setChecked(self.unwrap) + return new_chart + def setUnwrap(self, unwrap: bool): self.unwrap = unwrap self.update() @@ -526,9 +673,9 @@ def drawValues(self, qp: QtGui.QPainter): if len(self.data) == 0 and len(self.reference) == 0: return pen = QtGui.QPen(self.sweepColor) - pen.setWidth(2) + pen.setWidth(self.pointSize) line_pen = QtGui.QPen(self.sweepColor) - line_pen.setWidth(1) + line_pen.setWidth(self.lineThickness) if self.unwrap: rawData = [] @@ -601,15 +748,7 @@ def drawValues(self, qp: QtGui.QPainter): if self.bands.enabled: self.drawBands(qp, fstart, fstop) - qp.setPen(self.textColor) - qp.drawText(self.leftMargin-20, self.topMargin + self.chartHeight + 15, Chart.shortenFrequency(self.fstart)) - ticks = math.floor(self.chartWidth/100) # Number of ticks does not include the origin - for i in range(ticks): - x = self.leftMargin + round((i+1)*self.chartWidth/ticks) - qp.setPen(QtGui.QPen(self.foregroundColor)) - qp.drawLine(x, 20, x, 20+self.chartHeight+5) - qp.setPen(self.textColor) - qp.drawText(x-20, self.topMargin+self.chartHeight+15, Chart.shortenFrequency(round(fspan/ticks*(i+1) + self.fstart))) + self.drawFrequencyTicks(qp) self.drawData(qp, self.data, self.sweepColor) self.drawData(qp, self.reference, self.referenceColor) @@ -668,9 +807,9 @@ def drawValues(self, qp: QtGui.QPainter): if len(self.data) == 0 and len(self.reference) == 0: return pen = QtGui.QPen(self.sweepColor) - pen.setWidth(2) + pen.setWidth(self.pointSize) line_pen = QtGui.QPen(self.sweepColor) - line_pen.setWidth(1) + line_pen.setWidth(self.lineThickness) highlighter = QtGui.QPen(QtGui.QColor(20, 0, 255)) highlighter.setWidth(1) if self.fixedSpan: @@ -718,7 +857,7 @@ def drawValues(self, qp: QtGui.QPainter): vswrstr = str(round(vswr)) else: vswrstr = str(round(vswr, digits)) - qp.drawText(3, y+3, vswrstr) + qp.drawText(3, y+3, vswrstr) qp.setPen(QtGui.QPen(self.foregroundColor)) qp.drawLine(self.leftMargin-5, y, self.leftMargin+self.chartWidth, y) qp.drawLine(self.leftMargin - 5, self.topMargin, self.leftMargin + self.chartWidth, self.topMargin) @@ -729,17 +868,14 @@ def drawValues(self, qp: QtGui.QPainter): else: vswrstr = str(round(maxVSWR, digits)) qp.drawText(3, 35, vswrstr) - # qp.drawText(3, self.chartHeight + self.topMargin, str(minVSWR)) - # At least 100 px between ticks - qp.drawText(self.leftMargin-20, self.topMargin + self.chartHeight + 15, Chart.shortenFrequency(fstart)) - ticks = math.floor(self.chartWidth/100) # Number of ticks does not include the origin - for i in range(ticks): - x = self.leftMargin + round((i+1)*self.chartWidth/ticks) - qp.setPen(QtGui.QPen(self.foregroundColor)) - qp.drawLine(x, self.topMargin, x, self.topMargin + self.chartHeight + 5) - qp.setPen(self.textColor) - qp.drawText(x-20, self.topMargin + self.chartHeight + 15, Chart.shortenFrequency(round(fspan/ticks*(i+1) + fstart))) + self.drawFrequencyTicks(qp) + + qp.setPen(self.swrColor) + for vswr in self.swrMarkers: + y = self.topMargin + round((self.maxVSWR - vswr) / self.span * self.chartHeight) + qp.drawLine(self.leftMargin, y, self.leftMargin + self.chartWidth, y) + qp.drawText(self.leftMargin + 3, y - 1, str(vswr)) self.drawData(qp, self.data, self.sweepColor) self.drawData(qp, self.reference, self.referenceColor) @@ -798,9 +934,9 @@ def drawValues(self, qp: QtGui.QPainter): if len(self.data) == 0 and len(self.reference) == 0: return pen = QtGui.QPen(self.sweepColor) - pen.setWidth(2) + pen.setWidth(self.pointSize) line_pen = QtGui.QPen(self.sweepColor) - line_pen.setWidth(1) + line_pen.setWidth(self.lineThickness) highlighter = QtGui.QPen(QtGui.QColor(20, 0, 255)) highlighter.setWidth(1) qp.setPen(pen) @@ -932,13 +1068,22 @@ def drawSmithChart(self, qp: QtGui.QPainter): qp.drawArc(centerX - self.chartWidth*2, centerY, self.chartWidth*5, self.chartHeight*5, int(93.85*16), int(18.85*16)) # Im(Z) = -0.2 qp.drawArc(centerX - self.chartWidth*2, centerY, self.chartWidth*5, -self.chartHeight*5, int(-93.85 * 16), int(-18.85 * 16)) # Im(Z) = 0.2 + qp.setPen(self.swrColor) + for swr in self.swrMarkers: + if swr <= 1: + continue + gamma = (swr - 1)/(swr + 1) + r = round(gamma * self.chartWidth/2) + qp.drawEllipse(QtCore.QPoint(centerX, centerY), r, r) + qp.drawText(QtCore.QRect(centerX - 50, centerY - 4 + r, 100, 20), QtCore.Qt.AlignCenter, str(swr)) + def drawValues(self, qp: QtGui.QPainter): if len(self.data) == 0 and len(self.reference) == 0: return pen = QtGui.QPen(self.sweepColor) - pen.setWidth(2) + pen.setWidth(self.pointSize) line_pen = QtGui.QPen(self.sweepColor) - line_pen.setWidth(1) + line_pen.setWidth(self.lineThickness) highlighter = QtGui.QPen(QtGui.QColor(20, 0, 255)) highlighter.setWidth(1) qp.setPen(pen) @@ -1058,9 +1203,9 @@ def drawValues(self, qp: QtGui.QPainter): if len(self.data) == 0 and len(self.reference) == 0: return pen = QtGui.QPen(self.sweepColor) - pen.setWidth(2) + pen.setWidth(self.pointSize) line_pen = QtGui.QPen(self.sweepColor) - line_pen.setWidth(1) + line_pen.setWidth(self.lineThickness) highlighter = QtGui.QPen(QtGui.QColor(20, 0, 255)) highlighter.setWidth(1) if not self.fixedSpan: @@ -1175,15 +1320,18 @@ def drawValues(self, qp: QtGui.QPainter): qp.setPen(self.textColor) qp.drawText(3, self.topMargin + 4, str(maxValue)) qp.drawText(3, self.chartHeight+self.topMargin, str(minValue)) - # Frequency ticks - qp.drawText(self.leftMargin-20, self.topMargin + self.chartHeight + 15, Chart.shortenFrequency(self.fstart)) - ticks = math.floor(self.chartWidth/100) # Number of ticks does not include the origin - for i in range(ticks): - x = self.leftMargin + round((i+1)*self.chartWidth/ticks) - qp.setPen(QtGui.QPen(self.foregroundColor)) - qp.drawLine(x, 20, x, self.topMargin+self.chartHeight+5) - qp.setPen(self.textColor) - qp.drawText(x-20, self.topMargin+self.chartHeight+15, LogMagChart.shortenFrequency(round(fspan/ticks*(i+1) + self.fstart))) + self.drawFrequencyTicks(qp) + + qp.setPen(self.swrColor) + for vswr in self.swrMarkers: + if vswr <= 1: + continue + logMag = 20 * math.log10((vswr-1)/(vswr+1)) + if self.isInverted: + logMag = logMag * -1 + y = self.topMargin + round((self.maxValue - logMag) / self.span * self.chartHeight) + qp.drawLine(self.leftMargin, y, self.leftMargin + self.chartWidth, y) + qp.drawText(self.leftMargin + 3, y - 1, "VSWR: " + str(vswr)) self.drawData(qp, self.data, self.sweepColor) self.drawData(qp, self.reference, self.referenceColor) @@ -1200,6 +1348,12 @@ def logMag(self, p: Datapoint) -> float: else: return NanoVNASaver.gain(p) + def copy(self): + new_chart: LogMagChart = super().copy() + new_chart.isInverted = self.isInverted + new_chart.span = self.span + return new_chart + class QualityFactorChart(FrequencyChart): def __init__(self, name=""): @@ -1285,9 +1439,9 @@ def drawValues(self, qp: QtGui.QPainter): if self.span == 0: return pen = QtGui.QPen(self.sweepColor) - pen.setWidth(2) + pen.setWidth(self.pointSize) line_pen = QtGui.QPen(self.sweepColor) - line_pen.setWidth(1) + line_pen.setWidth(self.lineThickness) highlighter = QtGui.QPen(QtGui.QColor(20, 0, 255)) highlighter.setWidth(1) if self.fixedSpan: @@ -1308,17 +1462,7 @@ def drawValues(self, qp: QtGui.QPainter): if self.bands.enabled: self.drawBands(qp, fstart, fstop) - qp.setPen(self.textColor) - qp.drawText(self.leftMargin-20, self.topMargin + self.chartHeight + 15, Chart.shortenFrequency(fstart)) - ticks = math.floor(self.chartWidth/100) # Number of ticks does not include the origin - for i in range(ticks): - x = self.leftMargin + round((i+1)*self.chartWidth/ticks) - qp.setPen(QtGui.QPen(self.foregroundColor)) - qp.drawLine(x, self.topMargin - 5, x, self.topMargin + self.chartHeight + 5) - qp.setPen(self.textColor) - qp.drawText(x - 20, self.topMargin + self.chartHeight + 15, - Chart.shortenFrequency(round(fspan/ticks*(i+1) + fstart))) - + self.drawFrequencyTicks(qp) self.drawData(qp, self.data, self.sweepColor) self.drawData(qp, self.reference, self.referenceColor) self.drawMarkers(qp) @@ -1337,12 +1481,19 @@ def __init__(self, name): self.rightMargin = 20 self.bottomMargin = 35 self.setMinimumSize(250, 250) - self.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)) + self.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, + QtWidgets.QSizePolicy.MinimumExpanding)) pal = QtGui.QPalette() pal.setColor(QtGui.QPalette.Background, self.backgroundColor) self.setPalette(pal) self.setAutoFillBackground(True) + def copy(self): + new_chart = super().copy() + new_chart.tdrWindow = self.tdrWindow + self.tdrWindow.updated.connect(new_chart.update) + return new_chart + def paintEvent(self, a0: QtGui.QPaintEvent) -> None: qp = QtGui.QPainter(self) qp.setPen(QtGui.QPen(self.textColor)) @@ -1359,7 +1510,7 @@ def paintEvent(self, a0: QtGui.QPaintEvent) -> None: ticks = math.floor((self.width() - self.leftMargin)/100) # Number of ticks does not include the origin if len(self.tdrWindow.td) > 0: - x_step = len(self.tdrWindow.distance_axis) / width + x_step = len(self.tdrWindow.distance_axis) / (width * 2) y_step = np.max(self.tdrWindow.td)*1.1 / height for i in range(ticks): @@ -1370,7 +1521,9 @@ def paintEvent(self, a0: QtGui.QPaintEvent) -> None: qp.drawText(x - 20, 20 + height, str(round(self.tdrWindow.distance_axis[int((x - self.leftMargin) * x_step) - 1]/2, 1)) + "m") - qp.setPen(self.sweepColor) + pen = QtGui.QPen(self.sweepColor) + pen.setWidth(self.pointSize) + qp.setPen(pen) for i in range(len(self.tdrWindow.distance_axis)): qp.drawPoint(self.leftMargin + int(i / x_step), height - int(self.tdrWindow.td[i] / y_step)) id_max = np.argmax(self.tdrWindow.td) @@ -1451,6 +1604,15 @@ def __init__(self, name=""): self.setPalette(pal) self.setAutoFillBackground(True) + def copy(self): + new_chart: RealImaginaryChart = super().copy() + + new_chart.maxDisplayReal = self.maxDisplayReal + new_chart.maxDisplayImag = self.maxDisplayImag + new_chart.minDisplayReal = self.minDisplayReal + new_chart.minDisplayImag = self.minDisplayImag + return new_chart + def drawChart(self, qp: QtGui.QPainter): qp.setPen(QtGui.QPen(self.textColor)) qp.drawText(self.leftMargin + 5, 15, self.name + " (\N{OHM SIGN})") @@ -1466,9 +1628,9 @@ def drawValues(self, qp: QtGui.QPainter): if len(self.data) == 0 and len(self.reference) == 0: return pen = QtGui.QPen(self.sweepColor) - pen.setWidth(2) + pen.setWidth(self.pointSize) line_pen = QtGui.QPen(self.sweepColor) - line_pen.setWidth(1) + line_pen.setWidth(self.lineThickness) highlighter = QtGui.QPen(QtGui.QColor(20, 0, 255)) highlighter.setWidth(1) if self.fixedSpan: @@ -1579,18 +1741,10 @@ def drawValues(self, qp: QtGui.QPainter): qp.drawText(3, self.chartHeight + self.topMargin, str(round(min_real, 1))) qp.drawText(self.leftMargin + self.chartWidth + 8, self.chartHeight + self.topMargin, str(round(min_imag, 1))) - qp.drawText(self.leftMargin-20, self.topMargin + self.chartHeight + 15, Chart.shortenFrequency(fstart)) - ticks = math.floor(self.chartWidth/100) # Number of ticks does not include the origin - for i in range(ticks): - x = self.leftMargin + round((i+1)*self.chartWidth/ticks) - qp.setPen(QtGui.QPen(self.foregroundColor)) - qp.drawLine(x, self.topMargin - 5, x, self.topMargin + self.chartHeight + 5) - qp.setPen(self.textColor) - qp.drawText(x-20, self.topMargin + self.chartHeight + 15, Chart.shortenFrequency(round(fspan/ticks*(i+1) + fstart))) + self.drawFrequencyTicks(qp) primary_pen = pen secondary_pen = QtGui.QPen(self.secondarySweepColor) - secondary_pen.setWidth(2) if len(self.data) > 0: c = QtGui.QColor(self.sweepColor) c.setAlpha(255) @@ -1604,6 +1758,10 @@ def drawValues(self, qp: QtGui.QPainter): qp.setPen(pen) qp.drawLine(self.leftMargin + self.chartWidth, 9, self.leftMargin + self.chartWidth + 5, 9) + primary_pen.setWidth(self.pointSize) + secondary_pen.setWidth(self.pointSize) + line_pen.setWidth(self.lineThickness) + for i in range(len(self.data)): x = self.getXPosition(self.data[i]) y_re = self.getReYPosition(self.data[i]) @@ -1678,7 +1836,7 @@ def drawValues(self, qp: QtGui.QPainter): prev_y_re = self.getReYPosition(self.reference[i-1]) prev_y_im = self.getImYPosition(self.reference[i-1]) - line_pen.setColor(self.secondaryReferenceColor) + line_pen.setColor(self.referenceColor) qp.setPen(line_pen) # Real part first if self.isPlotable(x, y_re) and self.isPlotable(prev_x, prev_y_re): diff --git a/NanoVNASaver/Hardware.py b/NanoVNASaver/Hardware.py index c03d974d..26281910 100644 --- a/NanoVNASaver/Hardware.py +++ b/NanoVNASaver/Hardware.py @@ -47,7 +47,9 @@ def getVNA(app, serialPort: serial.Serial) -> 'VNA': return NanoVNA_F(app, serialPort) elif firmware.find("NanoVNA") > 0: return NanoVNA(app, serialPort) - return InvalidVNA(app, serialPort) + else: + logger.warning("Did not recognize NanoVNA type from firmware.") + return NanoVNA(app, serialPort) def readFrequencies(self) -> List[str]: pass @@ -259,13 +261,13 @@ class Version: def __init__(self, version_string): self.version_string = version_string - results = re.match(r"(.*\D+)?(\d+)\.(\d+)\.(\d+)(.*)", version_string) + results = re.match(r"(.*\s+)?(\d+)\.(\d+)\.(\d+)(.*)", version_string) if results: self.major = int(results.group(2)) self.minor = int(results.group(3)) self.revision = int(results.group(4)) self.note = results.group(5) - logger.debug("Parsed version as %d.%d.%d%s", self.major, self.minor, self.revision, self.note) + logger.debug("Parsed version as \"%d.%d.%d%s\"", self.major, self.minor, self.revision, self.note) @staticmethod def getVersion(major: int, minor: int, revision: int, note=""): diff --git a/NanoVNASaver/NanoVNASaver.py b/NanoVNASaver/NanoVNASaver.py index 76c49a96..f5d7f079 100644 --- a/NanoVNASaver/NanoVNASaver.py +++ b/NanoVNASaver/NanoVNASaver.py @@ -57,7 +57,6 @@ def __init__(self): else: self.icon = QtGui.QIcon("icon_48x48.png") self.setWindowIcon(self.icon) - self.settings = QtCore.QSettings(QtCore.QSettings.IniFormat, QtCore.QSettings.UserScope, "NanoVNASaver", "NanoVNASaver") @@ -111,6 +110,8 @@ def __init__(self): widget.setLayout(layout) scrollarea.setWidget(widget) + # outer.setContentsMargins(2, 2, 2, 2) # Small screen mode, reduce margins? + self.s11SmithChart = SmithChart("S11 Smith Chart") self.s21PolarChart = PolarChart("S21 Polar Plot") self.s11LogMag = LogMagChart("S11 Return Loss") @@ -120,7 +121,10 @@ def __init__(self): self.s11VSWR = VSWRChart("S11 VSWR") self.s11QualityFactor = QualityFactorChart("S11 Quality Factor") self.s11RealImaginary = RealImaginaryChart("S11 R+jX") + self.tdr_chart = TDRChart("TDR") + self.tdr_mainwindow_chart = TDRChart("TDR") + # List of all the S11 charts, for selecting self.s11charts: List[Chart] = [] self.s11charts.append(self.s11SmithChart) self.s11charts.append(self.s11LogMag) @@ -129,15 +133,23 @@ def __init__(self): self.s11charts.append(self.s11RealImaginary) self.s11charts.append(self.s11QualityFactor) + # List of all the S21 charts, for selecting self.s21charts: List[Chart] = [] self.s21charts.append(self.s21PolarChart) self.s21charts.append(self.s21LogMag) self.s21charts.append(self.s21Phase) - self.charts = self.s11charts + self.s21charts + # List of all charts that can be selected for display + self.selectable_charts = self.s11charts + self.s21charts + self.selectable_charts.append(self.tdr_mainwindow_chart) - self.tdr_chart = TDRChart("TDR") - self.charts.append(self.tdr_chart) + # List of all charts that subscribe to updates (including duplicates!) + self.subscribing_charts = [] + self.subscribing_charts.extend(self.selectable_charts) + self.subscribing_charts.append(self.tdr_chart) + + for c in self.subscribing_charts: + c.popoutRequested.connect(self.popoutChart) self.charts_layout = QtWidgets.QGridLayout() @@ -178,13 +190,13 @@ def __init__(self): self.sweepStartInput.setMinimumWidth(60) self.sweepStartInput.setAlignment(QtCore.Qt.AlignRight) self.sweepStartInput.textEdited.connect(self.updateCenterSpan) - + self.sweepStartInput.textChanged.connect(self.updateStepSize) sweep_input_left_layout.addRow(QtWidgets.QLabel("Start"), self.sweepStartInput) self.sweepEndInput = QtWidgets.QLineEdit("") self.sweepEndInput.setAlignment(QtCore.Qt.AlignRight) self.sweepEndInput.textEdited.connect(self.updateCenterSpan) - + self.sweepEndInput.textChanged.connect(self.updateStepSize) sweep_input_left_layout.addRow(QtWidgets.QLabel("Stop"), self.sweepEndInput) self.sweepCenterInput = QtWidgets.QLineEdit("") @@ -226,8 +238,10 @@ def __init__(self): self.btnSweep = QtWidgets.QPushButton("Sweep") self.btnSweep.clicked.connect(self.sweep) + self.btnSweep.setShortcut(QtCore.Qt.Key_W | QtCore.Qt.CTRL) self.btnStopSweep = QtWidgets.QPushButton("Stop") self.btnStopSweep.clicked.connect(self.stopSweep) + self.btnStopSweep.setShortcut(QtCore.Qt.Key_Escape) self.btnStopSweep.setDisabled(True) btn_layout = QtWidgets.QHBoxLayout() btn_layout.addWidget(self.btnSweep) @@ -279,7 +293,7 @@ def __init__(self): self.showMarkerButton.clicked.connect(self.toggleMarkerFrame) marker_control_layout.addRow(self.showMarkerButton) - for c in self.charts: + for c in self.subscribing_charts: c.setMarkers(self.markers) c.setBands(self.bands) left_column.addWidget(marker_control_box) @@ -331,6 +345,9 @@ def __init__(self): self.tdr_window = TDRWindow(self) self.tdr_chart.tdrWindow = self.tdr_window + self.tdr_mainwindow_chart.tdrWindow = self.tdr_window + self.tdr_window.updated.connect(self.tdr_chart.update) + self.tdr_window.updated.connect(self.tdr_mainwindow_chart.update) tdr_control_box = QtWidgets.QGroupBox() tdr_control_box.setTitle("TDR") @@ -799,7 +816,6 @@ def updateCenterSpan(self): return self.sweepSpanInput.setText(str(fspan)) self.sweepCenterInput.setText(str(fcenter)) - self.updateStepSize() def updateStartEnd(self): fcenter = self.parseFrequency(self.sweepCenterInput.text()) @@ -812,7 +828,6 @@ def updateStartEnd(self): return self.sweepStartInput.setText(str(fstart)) self.sweepEndInput.setText(str(fstop)) - self.updateStepSize() def updateStepSize(self): fspan = self.parseFrequency(self.sweepSpanInput.text()) @@ -931,7 +946,7 @@ def resetReference(self): self.referenceS21data = [] self.referenceSource = "" self.updateTitle() - for c in self.charts: + for c in self.subscribing_charts: c.resetReference() self.btnResetReference.setDisabled(True) @@ -991,6 +1006,23 @@ def showSweepError(self): self.showError(self.worker.error_message) self.stopSerial() + def popoutChart(self, chart: Chart): + logger.debug("Requested popout for chart: %s", chart.name) + new_chart = self.copyChart(chart) + new_chart.isPopout = True + new_chart.show() + new_chart.setWindowTitle(new_chart.name) + + def copyChart(self, chart: Chart): + new_chart = chart.copy() + self.subscribing_charts.append(new_chart) + if chart in self.s11charts: + self.s11charts.append(new_chart) + if chart in self.s21charts: + self.s21charts.append(new_chart) + new_chart.popoutRequested.connect(self.popoutChart) + return new_chart + def closeEvent(self, a0: QtGui.QCloseEvent) -> None: self.worker.stopped = True self.settings.setValue("Marker1Color", self.markers[0].color) @@ -1016,9 +1048,12 @@ def __init__(self, app: NanoVNASaver): shortcut = QtWidgets.QShortcut(QtCore.Qt.Key_Escape, self, self.hide) - layout = QtWidgets.QVBoxLayout() + layout = QtWidgets.QHBoxLayout() self.setLayout(layout) + left_layout = QtWidgets.QVBoxLayout() + layout.addLayout(left_layout) + display_options_box = QtWidgets.QGroupBox("Options") display_options_layout = QtWidgets.QFormLayout(display_options_box) @@ -1092,7 +1127,23 @@ def __init__(self, app: NanoVNASaver): display_options_layout.addRow("Second reference color", self.btnSecondaryReferenceColorPicker) - layout.addWidget(display_options_box) + self.pointSizeInput = QtWidgets.QSpinBox() + self.pointSizeInput.setValue(self.app.settings.value("PointSize", 2, int)) + self.pointSizeInput.setMinimum(1) + self.pointSizeInput.setMaximum(10) + self.pointSizeInput.setSuffix(" px") + self.pointSizeInput.setAlignment(QtCore.Qt.AlignRight) + self.pointSizeInput.valueChanged.connect(self.changePointSize) + display_options_layout.addRow("Point size", self.pointSizeInput) + + self.lineThicknessInput = QtWidgets.QSpinBox() + self.lineThicknessInput.setValue(self.app.settings.value("LineThickness", 1, int)) + self.lineThicknessInput.setMinimum(1) + self.lineThicknessInput.setMaximum(10) + self.lineThicknessInput.setSuffix(" px") + self.lineThicknessInput.setAlignment(QtCore.Qt.AlignRight) + self.lineThicknessInput.valueChanged.connect(self.changeLineThickness) + display_options_layout.addRow("Line thickness", self.lineThicknessInput) color_options_box = QtWidgets.QGroupBox("Chart colors") color_options_layout = QtWidgets.QFormLayout(color_options_box) @@ -1112,14 +1163,15 @@ def __init__(self, app: NanoVNASaver): self.btn_foreground_picker.clicked.connect(lambda: self.setColor("foreground", QtWidgets.QColorDialog.getColor(self.foregroundColor, options=QtWidgets.QColorDialog.ShowAlphaChannel))) color_options_layout.addRow("Chart foreground", self.btn_foreground_picker) - + self.btn_text_picker = QtWidgets.QPushButton("█") self.btn_text_picker.setFixedWidth(20) self.btn_text_picker.clicked.connect(lambda: self.setColor("text", QtWidgets.QColorDialog.getColor(self.textColor, options=QtWidgets.QColorDialog.ShowAlphaChannel))) color_options_layout.addRow("Chart text", self.btn_text_picker) - layout.addWidget(color_options_box) + right_layout = QtWidgets.QVBoxLayout() + layout.addLayout(right_layout) font_options_box = QtWidgets.QGroupBox("Font") font_options_layout = QtWidgets.QFormLayout(font_options_box) @@ -1134,8 +1186,6 @@ def __init__(self, app: NanoVNASaver): self.font_dropdown.currentTextChanged.connect(self.changeFont) font_options_layout.addRow("Font size", self.font_dropdown) - layout.addWidget(font_options_box) - bands_box = QtWidgets.QGroupBox("Bands") bands_layout = QtWidgets.QFormLayout(bands_box) @@ -1157,7 +1207,45 @@ def __init__(self, app: NanoVNASaver): bands_layout.addRow(self.btn_manage_bands) - layout.addWidget(bands_box) + vswr_marker_box = QtWidgets.QGroupBox("VSWR Markers") + vswr_marker_layout = QtWidgets.QFormLayout(vswr_marker_box) + + 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] + + self.btn_vswr_picker = QtWidgets.QPushButton("█") + self.btn_vswr_picker.setFixedWidth(20) + self.btn_vswr_picker.clicked.connect(lambda: self.setColor("vswr", QtWidgets.QColorDialog.getColor(self.vswrColor, options=QtWidgets.QColorDialog.ShowAlphaChannel))) + + vswr_marker_layout.addRow("VSWR Markers", self.btn_vswr_picker) + + self.vswr_marker_dropdown = QtWidgets.QComboBox() + vswr_marker_layout.addRow(self.vswr_marker_dropdown) + + if len(self.vswrMarkers) == 0: + self.vswr_marker_dropdown.addItem("None") + else: + for m in self.vswrMarkers: + self.vswr_marker_dropdown.addItem(str(m)) + for c in self.app.s11charts: + c.addSWRMarker(m) + + self.vswr_marker_dropdown.setCurrentIndex(0) + btn_add_marker = QtWidgets.QPushButton("Add ...") + btn_remove_marker = QtWidgets.QPushButton("Remove") + vswr_marker_btn_layout = QtWidgets.QHBoxLayout() + vswr_marker_btn_layout.addWidget(btn_add_marker) + vswr_marker_btn_layout.addWidget(btn_remove_marker) + vswr_marker_layout.addRow(vswr_marker_btn_layout) + + btn_add_marker.clicked.connect(self.addVSWRMarker) + btn_remove_marker.clicked.connect(self.removeVSWRMarker) charts_box = QtWidgets.QGroupBox("Displayed charts") charts_layout = QtWidgets.QGridLayout(charts_box) @@ -1173,7 +1261,7 @@ def __init__(self, app: NanoVNASaver): selections = [] - for c in self.app.charts: + for c in self.app.selectable_charts: selections.append(c.name) selections.append("None") @@ -1189,7 +1277,7 @@ def __init__(self, app: NanoVNASaver): chart01_selection.setCurrentIndex(selections.index(self.app.settings.value("Chart01", "S11 Return Loss"))) chart01_selection.currentTextChanged.connect(lambda: self.changeChart(0, 1, chart01_selection.currentText())) charts_layout.addWidget(chart01_selection, 0, 1) - + chart02_selection = QtWidgets.QComboBox() chart02_selection.addItems(selections) chart02_selection.setCurrentIndex(selections.index(self.app.settings.value("Chart02", "None"))) @@ -1221,10 +1309,6 @@ def __init__(self, app: NanoVNASaver): self.changeChart(1, 1, chart11_selection.currentText()) self.changeChart(1, 2, chart12_selection.currentText()) - layout.addWidget(charts_box) - 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.backgroundColor = self.app.settings.value("BackgroundColor", defaultValue=QtGui.QColor("white"), type=QtGui.QColor) self.foregroundColor = self.app.settings.value("ForegroundColor", defaultValue=QtGui.QColor("lightgray"), @@ -1234,6 +1318,11 @@ def __init__(self, app: NanoVNASaver): self.bandsColor = self.app.settings.value("BandsColor", defaultValue=QtGui.QColor(128, 128, 128, 48), type=QtGui.QColor) self.app.bands.color = self.bandsColor + self.vswrColor = self.app.settings.value("VSWRColor", defaultValue=QtGui.QColor(192, 0, 0, 128), + 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)) if self.app.settings.value("UseCustomColors", defaultValue=False, type=bool): self.dark_mode_option.setDisabled(True) @@ -1260,9 +1349,22 @@ def __init__(self, app: NanoVNASaver): p.setColor(QtGui.QPalette.ButtonText, self.bandsColor) self.btn_bands_picker.setPalette(p) + p = self.btn_vswr_picker.palette() + p.setColor(QtGui.QPalette.ButtonText, self.vswrColor) + self.btn_vswr_picker.setPalette(p) + + left_layout.addWidget(display_options_box) + left_layout.addWidget(charts_box) + left_layout.addStretch(1) + + right_layout.addWidget(color_options_box) + right_layout.addWidget(font_options_box) + right_layout.addWidget(bands_box) + right_layout.addWidget(vswr_marker_box) + def changeChart(self, x, y, chart): found = None - for c in self.app.charts: + for c in self.app.selectable_charts: if c.name == chart: found = c @@ -1274,6 +1376,10 @@ def changeChart(self, x, y, chart): self.app.charts_layout.removeWidget(w) w.hide() if found is not None: + if self.app.charts_layout.indexOf(found) > -1: + logger.debug("%s is already shown, duplicating.", found.name) + found = self.app.copyChart(found) + self.app.charts_layout.addWidget(found, x, y) if found.isHidden(): found.show() @@ -1291,22 +1397,34 @@ def changeReturnLoss(self): def changeShowLines(self): state = self.show_lines_option.isChecked() self.app.settings.setValue("ShowLines", state) - for c in self.app.charts: + for c in self.app.subscribing_charts: c.setDrawLines(state) + def changePointSize(self, size: int): + self.app.settings.setValue("PointSize", size) + for c in self.app.subscribing_charts: + c.setPointSize(size) + + def changeLineThickness(self, size: int): + self.app.settings.setValue("LineThickness", size) + for c in self.app.subscribing_charts: + c.setLineThickness(size) + def changeDarkMode(self): state = self.dark_mode_option.isChecked() self.app.settings.setValue("DarkMode", state) if state: - for c in self.app.charts: + for c in self.app.subscribing_charts: c.setBackgroundColor(QtGui.QColor(QtCore.Qt.black)) c.setForegroundColor(QtGui.QColor(QtCore.Qt.lightGray)) c.setTextColor(QtGui.QColor(QtCore.Qt.white)) + c.setSWRColor(self.vswrColor) else: - for c in self.app.charts: + for c in self.app.subscribing_charts: c.setBackgroundColor(QtGui.QColor(QtCore.Qt.white)) c.setForegroundColor(QtGui.QColor(QtCore.Qt.lightGray)) c.setTextColor(QtGui.QColor(QtCore.Qt.black)) + c.setSWRColor(self.vswrColor) def changeCustomColors(self): self.app.settings.setValue("UseCustomColors", self.use_custom_colors.isChecked()) @@ -1316,10 +1434,11 @@ def changeCustomColors(self): self.btn_background_picker.setDisabled(False) self.btn_foreground_picker.setDisabled(False) self.btn_text_picker.setDisabled(False) - for c in self.app.charts: + for c in self.app.subscribing_charts: c.setBackgroundColor(self.backgroundColor) c.setForegroundColor(self.foregroundColor) c.setTextColor(self.textColor) + c.setSWRColor(self.vswrColor) else: self.dark_mode_option.setDisabled(False) self.btn_background_picker.setDisabled(True) @@ -1353,6 +1472,12 @@ def setColor(self, name: str, color: QtGui.QColor): self.bandsColor = color self.app.settings.setValue("BandsColor", color) self.app.bands.setColor(color) + elif name == "vswr": + p = self.btn_vswr_picker.palette() + p.setColor(QtGui.QPalette.ButtonText, color) + self.btn_vswr_picker.setPalette(p) + self.vswrColor = color + self.app.settings.setValue("VSWRColor", color) self.changeCustomColors() def setSweepColor(self, color: QtGui.QColor): @@ -1363,7 +1488,7 @@ def setSweepColor(self, color: QtGui.QColor): self.btnColorPicker.setPalette(p) self.app.settings.setValue("SweepColor", color) self.app.settings.sync() - for c in self.app.charts: + for c in self.app.subscribing_charts: c.setSweepColor(color) def setSecondarySweepColor(self, color: QtGui.QColor): @@ -1374,7 +1499,7 @@ def setSecondarySweepColor(self, color: QtGui.QColor): self.btnSecondaryColorPicker.setPalette(p) self.app.settings.setValue("SecondarySweepColor", color) self.app.settings.sync() - for c in self.app.charts: + for c in self.app.subscribing_charts: c.setSecondarySweepColor(color) def setReferenceColor(self, color): @@ -1386,7 +1511,7 @@ def setReferenceColor(self, color): self.app.settings.setValue("ReferenceColor", color) self.app.settings.sync() - for c in self.app.charts: + for c in self.app.subscribing_charts: c.setReferenceColor(color) def setSecondaryReferenceColor(self, color): @@ -1398,14 +1523,14 @@ def setSecondaryReferenceColor(self, color): self.app.settings.setValue("SecondaryReferenceColor", color) self.app.settings.sync() - for c in self.app.charts: + for c in self.app.subscribing_charts: c.setSecondaryReferenceColor(color) def setShowBands(self, show_bands): self.app.bands.enabled = show_bands self.app.bands.settings.setValue("ShowBands", show_bands) self.app.bands.settings.sync() - for c in self.app.charts: + for c in self.app.subscribing_charts: c.update() def changeFont(self): @@ -1420,6 +1545,33 @@ def displayBandsWindow(self): self.bandsWindow.show() QtWidgets.QApplication.setActiveWindow(self.bandsWindow) + def addVSWRMarker(self): + value, selected = QtWidgets.QInputDialog.getDouble(self, "Add VSWR Marker", + "VSWR value to show:", min=1.001, decimals=3) + if selected: + self.vswrMarkers.append(value) + if self.vswr_marker_dropdown.itemText(0) == "None": + self.vswr_marker_dropdown.removeItem(0) + self.vswr_marker_dropdown.addItem(str(value)) + self.vswr_marker_dropdown.setCurrentText(str(value)) + for c in self.app.s11charts: + c.addSWRMarker(value) + self.app.settings.setValue("VSWRMarkers", self.vswrMarkers) + + def removeVSWRMarker(self): + value_str = self.vswr_marker_dropdown.currentText() + if value_str != "None": + value = float(value_str) + self.vswrMarkers.remove(value) + self.vswr_marker_dropdown.removeItem(self.vswr_marker_dropdown.currentIndex()) + if self.vswr_marker_dropdown.count() == 0: + self.vswr_marker_dropdown.addItem("None") + self.app.settings.remove("VSWRMarkers") + else: + self.app.settings.setValue("VSWRMarkers", self.vswrMarkers) + for c in self.app.s11charts: + c.removeSWRMarker(value) + class AboutWindow(QtWidgets.QWidget): def __init__(self, app: NanoVNASaver): @@ -1475,7 +1627,7 @@ def __init__(self, app: NanoVNASaver): check_for_updates = self.app.settings.value("CheckForUpdates", "Ask") if check_for_updates == "Yes": self.updateCheckBox.setChecked(True) - self.findUpdates(automatic = True) + self.findUpdates(automatic=True) elif check_for_updates == "No": self.updateCheckBox.setChecked(False) else: @@ -1535,7 +1687,9 @@ def findUpdates(self, automatic=False): update_url = "http://mihtjel.dk/nanovna-saver/latest.json" try: - updates = json.load(request.urlopen(update_url, timeout=3)) + req = request.Request(update_url) + req.add_header('User-Agent', "NanoVNA-Saver/" + self.app.version) + updates = json.load(request.urlopen(req, timeout=3)) latest_version = Version(updates['version']) latest_url = updates['url'] except error.HTTPError as e: @@ -1571,6 +1725,8 @@ def findUpdates(self, automatic=False): class TDRWindow(QtWidgets.QWidget): + updated = QtCore.pyqtSignal() + def __init__(self, app: NanoVNASaver): super().__init__() self.app = app @@ -1682,7 +1838,7 @@ def updateTDR(self): self.tdr_result_label.setText(str(cable_len) + " m (" + str(feet) + "ft " + str(inches) + "in)") self.app.tdr_result_label.setText(str(cable_len) + " m") - self.app.tdr_chart.update() + self.updated.emit() class SweepSettingsWindow(QtWidgets.QWidget): diff --git a/NanoVNASaver/about.py b/NanoVNASaver/about.py index c7da89dd..10ea0f83 100644 --- a/NanoVNASaver/about.py +++ b/NanoVNASaver/about.py @@ -14,5 +14,5 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -version = '0.1.2' +version = '0.1.3' debug = False