kopia lustrzana https://github.com/NanoVNA-Saver/nanovna-saver
Merge branch 'feature/refactor_windows' into development
commit
d0b91dbfd1
Plik diff jest za duży
Load Diff
|
@ -0,0 +1,154 @@
|
|||
# NanoVNASaver
|
||||
# A python program to view and export Touchstone data from a NanoVNA
|
||||
# Copyright (C) 2019. Rune B. Broberg
|
||||
#
|
||||
# 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 logging
|
||||
import typing
|
||||
from typing import List, Tuple
|
||||
|
||||
from PyQt5 import QtCore, QtGui
|
||||
from PyQt5.QtCore import QModelIndex
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BandsModel(QtCore.QAbstractTableModel):
|
||||
bands: List[Tuple[str, int, int]] = []
|
||||
enabled = False
|
||||
color = QtGui.QColor(128, 128, 128, 48)
|
||||
|
||||
# These bands correspond broadly to the Danish Amateur Radio allocation
|
||||
default_bands = ["2200 m;135700;137800",
|
||||
"630 m;472000;479000",
|
||||
"160 m;1800000;2000000",
|
||||
"80 m;3500000;3800000",
|
||||
"60 m;5250000;5450000",
|
||||
"40 m;7000000;7200000",
|
||||
"30 m;10100000;10150000",
|
||||
"20 m;14000000;14350000",
|
||||
"17 m;18068000;18168000",
|
||||
"15 m;21000000;21450000",
|
||||
"12 m;24890000;24990000",
|
||||
"10 m;28000000;29700000",
|
||||
"6 m;50000000;52000000",
|
||||
"4 m;69887500;70512500",
|
||||
"2 m;144000000;146000000",
|
||||
"70 cm;432000000;438000000",
|
||||
"23 cm;1240000000;1300000000",
|
||||
"13 cm;2320000000;2450000000"]
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.settings = QtCore.QSettings(QtCore.QSettings.IniFormat,
|
||||
QtCore.QSettings.UserScope,
|
||||
"NanoVNASaver", "Bands")
|
||||
self.settings.setIniCodec("UTF-8")
|
||||
self.enabled = self.settings.value("ShowBands", False, bool)
|
||||
|
||||
stored_bands: List[str] = self.settings.value("bands", self.default_bands)
|
||||
if stored_bands:
|
||||
for b in stored_bands:
|
||||
(name, start, end) = b.split(";")
|
||||
self.bands.append((name, int(start), int(end)))
|
||||
|
||||
def saveSettings(self):
|
||||
stored_bands = []
|
||||
for b in self.bands:
|
||||
stored_bands.append(b[0] + ";" + str(b[1]) + ";" + str(b[2]))
|
||||
self.settings.setValue("bands", stored_bands)
|
||||
self.settings.sync()
|
||||
|
||||
def resetBands(self):
|
||||
self.bands = []
|
||||
for b in self.default_bands:
|
||||
(name, start, end) = b.split(";")
|
||||
self.bands.append((name, int(start), int(end)))
|
||||
self.layoutChanged.emit()
|
||||
self.saveSettings()
|
||||
|
||||
def columnCount(self, parent: QModelIndex = ...) -> int:
|
||||
return 3
|
||||
|
||||
def rowCount(self, parent: QModelIndex = ...) -> int:
|
||||
return len(self.bands)
|
||||
|
||||
def data(self, index: QModelIndex, role: int = ...) -> QtCore.QVariant:
|
||||
if (role == QtCore.Qt.DisplayRole or
|
||||
role == QtCore.Qt.ItemDataRole or role == QtCore.Qt.EditRole):
|
||||
return QtCore.QVariant(self.bands[index.row()][index.column()])
|
||||
elif role == QtCore.Qt.TextAlignmentRole:
|
||||
if index.column() == 0:
|
||||
return QtCore.QVariant(QtCore.Qt.AlignCenter)
|
||||
else:
|
||||
return QtCore.QVariant(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
else:
|
||||
return QtCore.QVariant()
|
||||
|
||||
def setData(self, index: QModelIndex, value: typing.Any, role: int = ...) -> bool:
|
||||
if role == QtCore.Qt.EditRole and index.isValid():
|
||||
t = self.bands[index.row()]
|
||||
name = t[0]
|
||||
start = t[1]
|
||||
end = t[2]
|
||||
if index.column() == 0:
|
||||
name = value
|
||||
elif index.column() == 1:
|
||||
start = value
|
||||
elif index.column() == 2:
|
||||
end = value
|
||||
self.bands[index.row()] = (name, start, end)
|
||||
self.dataChanged.emit(index, index)
|
||||
self.saveSettings()
|
||||
return True
|
||||
return False
|
||||
|
||||
def index(self, row: int, column: int, parent: QModelIndex = ...) -> QModelIndex:
|
||||
return self.createIndex(row, column)
|
||||
|
||||
def addRow(self):
|
||||
self.bands.append(("New", 0, 0))
|
||||
self.dataChanged.emit(self.index(len(self.bands), 0), self.index(len(self.bands), 2))
|
||||
self.layoutChanged.emit()
|
||||
|
||||
def removeRow(self, row: int, parent: QModelIndex = ...) -> bool:
|
||||
self.bands.remove(self.bands[row])
|
||||
self.layoutChanged.emit()
|
||||
self.saveSettings()
|
||||
return True
|
||||
|
||||
def headerData(self, section: int, orientation: QtCore.Qt.Orientation, role: int = ...):
|
||||
if role == QtCore.Qt.DisplayRole and orientation == QtCore.Qt.Horizontal:
|
||||
if section == 0:
|
||||
return "Band"
|
||||
if section == 1:
|
||||
return "Start (Hz)"
|
||||
if section == 2:
|
||||
return "End (Hz)"
|
||||
else:
|
||||
return "Invalid"
|
||||
else:
|
||||
super().headerData(section, orientation, role)
|
||||
|
||||
def flags(self, index: QModelIndex) -> QtCore.Qt.ItemFlags:
|
||||
if index.isValid():
|
||||
return QtCore.Qt.ItemFlags(
|
||||
QtCore.Qt.ItemIsEditable |
|
||||
QtCore.Qt.ItemIsEnabled |
|
||||
QtCore.Qt.ItemIsSelectable)
|
||||
else:
|
||||
super().flags(index)
|
||||
|
||||
def setColor(self, color):
|
||||
self.color = color
|
|
@ -0,0 +1,189 @@
|
|||
# NanoVNASaver
|
||||
# A python program to view and export Touchstone data from a NanoVNA
|
||||
# Copyright (C) 2019. Rune B. Broberg
|
||||
#
|
||||
# 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 logging
|
||||
import json
|
||||
from time import strftime, localtime
|
||||
from urllib import request, error
|
||||
|
||||
from PyQt5 import QtWidgets, QtCore
|
||||
|
||||
from NanoVNASaver.Hardware import Version
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class AboutWindow(QtWidgets.QWidget):
|
||||
def __init__(self, app: QtWidgets.QWidget):
|
||||
super().__init__()
|
||||
self.app = app
|
||||
|
||||
self.setWindowTitle("About NanoVNASaver")
|
||||
self.setWindowIcon(self.app.icon)
|
||||
top_layout = QtWidgets.QHBoxLayout()
|
||||
self.setLayout(top_layout)
|
||||
QtWidgets.QShortcut(QtCore.Qt.Key_Escape, self, self.hide)
|
||||
|
||||
icon_layout = QtWidgets.QVBoxLayout()
|
||||
top_layout.addLayout(icon_layout)
|
||||
icon = QtWidgets.QLabel()
|
||||
icon.setPixmap(self.app.icon.pixmap(128, 128))
|
||||
icon_layout.addWidget(icon)
|
||||
icon_layout.addStretch()
|
||||
|
||||
layout = QtWidgets.QVBoxLayout()
|
||||
top_layout.addLayout(layout)
|
||||
|
||||
layout.addWidget(QtWidgets.QLabel(
|
||||
f"NanoVNASaver version {self.app.version}"))
|
||||
layout.addWidget(QtWidgets.QLabel(""))
|
||||
layout.addWidget(QtWidgets.QLabel(
|
||||
"\N{COPYRIGHT SIGN} Copyright 2019 Rune B. Broberg"))
|
||||
layout.addWidget(QtWidgets.QLabel(
|
||||
"This program comes with ABSOLUTELY NO WARRANTY"))
|
||||
layout.addWidget(QtWidgets.QLabel(
|
||||
"This program is licensed under the GNU General Public License version 3"))
|
||||
layout.addWidget(QtWidgets.QLabel(""))
|
||||
link_label = QtWidgets.QLabel(
|
||||
"For further details, see: <a href=\"https://mihtjel.github.io/nanovna-saver/\">"
|
||||
"https://mihtjel.github.io/nanovna-saver/</a>")
|
||||
link_label.setOpenExternalLinks(True)
|
||||
layout.addWidget(link_label)
|
||||
layout.addWidget(QtWidgets.QLabel(""))
|
||||
|
||||
self.versionLabel = QtWidgets.QLabel("NanoVNA Firmware Version: Not connected.")
|
||||
layout.addWidget(self.versionLabel)
|
||||
|
||||
layout.addStretch()
|
||||
|
||||
btn_check_version = QtWidgets.QPushButton("Check for updates")
|
||||
btn_check_version.clicked.connect(self.findUpdates)
|
||||
|
||||
self.updateLabel = QtWidgets.QLabel("Last checked: ")
|
||||
self.updateCheckBox = QtWidgets.QCheckBox("Check for updates on startup")
|
||||
|
||||
self.updateCheckBox.toggled.connect(self.updateSettings)
|
||||
|
||||
check_for_updates = self.app.settings.value("CheckForUpdates", "Ask")
|
||||
if check_for_updates == "Yes":
|
||||
self.updateCheckBox.setChecked(True)
|
||||
self.findUpdates(automatic=True)
|
||||
elif check_for_updates == "No":
|
||||
self.updateCheckBox.setChecked(False)
|
||||
else:
|
||||
logger.debug("Starting timer")
|
||||
QtCore.QTimer.singleShot(2000, self.askAboutUpdates)
|
||||
|
||||
update_hbox = QtWidgets.QHBoxLayout()
|
||||
update_hbox.addWidget(btn_check_version)
|
||||
update_form = QtWidgets.QFormLayout()
|
||||
update_hbox.addLayout(update_form)
|
||||
update_hbox.addStretch()
|
||||
update_form.addRow(self.updateLabel)
|
||||
update_form.addRow(self.updateCheckBox)
|
||||
layout.addLayout(update_hbox)
|
||||
|
||||
layout.addStretch()
|
||||
|
||||
btn_ok = QtWidgets.QPushButton("Ok")
|
||||
btn_ok.clicked.connect(lambda: self.close()) # noqa
|
||||
layout.addWidget(btn_ok)
|
||||
|
||||
def show(self):
|
||||
super().show()
|
||||
self.updateLabels()
|
||||
|
||||
def updateLabels(self):
|
||||
if self.app.vna.isValid():
|
||||
logger.debug("Valid VNA")
|
||||
v: Version = self.app.vna.version
|
||||
self.versionLabel.setText(
|
||||
f"NanoVNA Firmware Version: {self.app.vna.name}"
|
||||
f"{v.version_string}")
|
||||
|
||||
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):
|
||||
update_url = "http://mihtjel.dk/nanovna-saver/latest.json"
|
||||
|
||||
try:
|
||||
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:
|
||||
logger.exception("Checking for updates produced an HTTP exception: %s", e)
|
||||
self.updateLabel.setText("Connection error.")
|
||||
return
|
||||
except json.JSONDecodeError as e:
|
||||
logger.exception("Checking for updates provided an unparseable file: %s", e)
|
||||
self.updateLabel.setText("Data error reading versions.")
|
||||
return
|
||||
except error.URLError as e:
|
||||
logger.exception("Checking for updates produced a URL exception: %s", e)
|
||||
self.updateLabel.setText("Connection error.")
|
||||
return
|
||||
|
||||
logger.info("Latest version is %s", latest_version.version_string)
|
||||
this_version = Version(self.app.version)
|
||||
logger.info("This is %s", this_version)
|
||||
if latest_version > this_version:
|
||||
logger.info("New update available: %s!", latest_version)
|
||||
if automatic:
|
||||
QtWidgets.QMessageBox.information(
|
||||
self,
|
||||
"Updates available",
|
||||
"There is a new update for NanoVNA-Saver available!\n" +
|
||||
"Version " + latest_version.version_string + "\n\n" +
|
||||
"Press \"About\" to find the update.")
|
||||
else:
|
||||
QtWidgets.QMessageBox.information(
|
||||
self, "Updates available",
|
||||
"There is a new update for NanoVNA-Saver available!")
|
||||
self.updateLabel.setText(
|
||||
f'<a href="{latest_url}">New version available</a>.')
|
||||
self.updateLabel.setOpenExternalLinks(True)
|
||||
else:
|
||||
# Probably don't show a message box, just update the screen?
|
||||
# Maybe consider showing it if not an automatic update.
|
||||
#
|
||||
self.updateLabel.setText(
|
||||
f"Last checked: {strftime('%Y-%m-%d %H:%M:%S', localtime())}")
|
||||
return
|
|
@ -0,0 +1,100 @@
|
|||
# NanoVNASaver
|
||||
# A python program to view and export Touchstone data from a NanoVNA
|
||||
# Copyright (C) 2019. Rune B. Broberg
|
||||
#
|
||||
# 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 logging
|
||||
|
||||
from PyQt5 import QtWidgets, QtCore
|
||||
|
||||
from NanoVNASaver.Analysis import Analysis, LowPassAnalysis, HighPassAnalysis, \
|
||||
BandPassAnalysis, BandStopAnalysis, VSWRAnalysis, \
|
||||
SimplePeakSearchAnalysis
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AnalysisWindow(QtWidgets.QWidget):
|
||||
analyses = []
|
||||
analysis: Analysis = None
|
||||
|
||||
def __init__(self, app: QtWidgets.QWidget):
|
||||
super().__init__()
|
||||
|
||||
self.app = app
|
||||
self.setWindowTitle("Sweep analysis")
|
||||
self.setWindowIcon(self.app.icon)
|
||||
|
||||
QtWidgets.QShortcut(QtCore.Qt.Key_Escape, self, self.hide)
|
||||
|
||||
layout = QtWidgets.QVBoxLayout()
|
||||
self.setLayout(layout)
|
||||
|
||||
select_analysis_box = QtWidgets.QGroupBox("Select analysis")
|
||||
select_analysis_layout = QtWidgets.QFormLayout(select_analysis_box)
|
||||
self.analysis_list = QtWidgets.QComboBox()
|
||||
self.analysis_list.addItem("Low-pass filter", LowPassAnalysis(self.app))
|
||||
self.analysis_list.addItem("Band-pass filter", BandPassAnalysis(self.app))
|
||||
self.analysis_list.addItem("High-pass filter", HighPassAnalysis(self.app))
|
||||
self.analysis_list.addItem("Band-stop filter", BandStopAnalysis(self.app))
|
||||
# self.analysis_list.addItem("Peak search", PeakSearchAnalysis(self.app))
|
||||
self.analysis_list.addItem("Peak search", SimplePeakSearchAnalysis(self.app))
|
||||
self.analysis_list.addItem("VSWR analysis", VSWRAnalysis(self.app))
|
||||
select_analysis_layout.addRow("Analysis type", self.analysis_list)
|
||||
self.analysis_list.currentIndexChanged.connect(self.updateSelection)
|
||||
|
||||
btn_run_analysis = QtWidgets.QPushButton("Run analysis")
|
||||
btn_run_analysis.clicked.connect(self.runAnalysis)
|
||||
select_analysis_layout.addRow(btn_run_analysis)
|
||||
|
||||
self.checkbox_run_automatically = QtWidgets.QCheckBox("Run automatically")
|
||||
self.checkbox_run_automatically.stateChanged.connect(self.toggleAutomaticRun)
|
||||
select_analysis_layout.addRow(self.checkbox_run_automatically)
|
||||
|
||||
analysis_box = QtWidgets.QGroupBox("Analysis")
|
||||
analysis_box.setSizePolicy(
|
||||
QtWidgets.QSizePolicy.MinimumExpanding,
|
||||
QtWidgets.QSizePolicy.MinimumExpanding)
|
||||
|
||||
self.analysis_layout = QtWidgets.QVBoxLayout(analysis_box)
|
||||
self.analysis_layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
layout.addWidget(select_analysis_box)
|
||||
layout.addWidget(analysis_box)
|
||||
|
||||
self.updateSelection()
|
||||
|
||||
def runAnalysis(self):
|
||||
if self.analysis is not None:
|
||||
self.analysis.runAnalysis()
|
||||
|
||||
def updateSelection(self):
|
||||
self.analysis = self.analysis_list.currentData()
|
||||
old_item = self.analysis_layout.itemAt(0)
|
||||
if old_item is not None:
|
||||
old_widget = self.analysis_layout.itemAt(0).widget()
|
||||
self.analysis_layout.replaceWidget(old_widget, self.analysis.widget())
|
||||
old_widget.hide()
|
||||
else:
|
||||
self.analysis_layout.addWidget(self.analysis.widget())
|
||||
self.analysis.widget().show()
|
||||
self.update()
|
||||
|
||||
def toggleAutomaticRun(self, state: QtCore.Qt.CheckState):
|
||||
if state == QtCore.Qt.Checked:
|
||||
self.analysis_list.setDisabled(True)
|
||||
self.app.dataAvailable.connect(self.runAnalysis)
|
||||
else:
|
||||
self.analysis_list.setDisabled(False)
|
||||
self.app.dataAvailable.disconnect(self.runAnalysis)
|
|
@ -0,0 +1,69 @@
|
|||
# NanoVNASaver
|
||||
# A python program to view and export Touchstone data from a NanoVNA
|
||||
# Copyright (C) 2019. Rune B. Broberg
|
||||
#
|
||||
# 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 logging
|
||||
|
||||
from PyQt5 import QtWidgets, QtCore
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BandsWindow(QtWidgets.QWidget):
|
||||
def __init__(self, app: QtWidgets.QWidget):
|
||||
super().__init__()
|
||||
|
||||
self.app = app
|
||||
self.setWindowTitle("Manage bands")
|
||||
self.setWindowIcon(self.app.icon)
|
||||
|
||||
QtWidgets.QShortcut(QtCore.Qt.Key_Escape, self, self.hide)
|
||||
|
||||
layout = QtWidgets.QVBoxLayout()
|
||||
self.setLayout(layout)
|
||||
self.setMinimumSize(500, 300)
|
||||
|
||||
self.bands_table = QtWidgets.QTableView()
|
||||
self.bands_table.setModel(self.app.bands)
|
||||
self.bands_table.horizontalHeader().setStretchLastSection(True)
|
||||
|
||||
layout.addWidget(self.bands_table)
|
||||
|
||||
btn_add_row = QtWidgets.QPushButton("Add row")
|
||||
btn_delete_row = QtWidgets.QPushButton("Delete row")
|
||||
btn_reset_bands = QtWidgets.QPushButton("Reset bands")
|
||||
btn_layout = QtWidgets.QHBoxLayout()
|
||||
btn_layout.addWidget(btn_add_row)
|
||||
btn_layout.addWidget(btn_delete_row)
|
||||
btn_layout.addWidget(btn_reset_bands)
|
||||
layout.addLayout(btn_layout)
|
||||
|
||||
btn_add_row.clicked.connect(self.app.bands.addRow)
|
||||
btn_delete_row.clicked.connect(self.deleteRows)
|
||||
btn_reset_bands.clicked.connect(self.resetBands)
|
||||
|
||||
def deleteRows(self):
|
||||
rows = self.bands_table.selectedIndexes()
|
||||
for row in rows:
|
||||
self.app.bands.removeRow(row.row())
|
||||
|
||||
def resetBands(self):
|
||||
confirm = QtWidgets.QMessageBox(
|
||||
QtWidgets.QMessageBox.Warning,
|
||||
"Confirm reset",
|
||||
"Are you sure you want to reset the bands to default?",
|
||||
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.Cancel).exec()
|
||||
if confirm == QtWidgets.QMessageBox.Yes:
|
||||
self.app.bands.resetBands()
|
|
@ -0,0 +1,115 @@
|
|||
# NanoVNASaver
|
||||
# A python program to view and export Touchstone data from a NanoVNA
|
||||
# Copyright (C) 2019. Rune B. Broberg
|
||||
#
|
||||
# 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 logging
|
||||
|
||||
from PyQt5 import QtWidgets, QtCore
|
||||
|
||||
from NanoVNASaver.Windows.Screenshot import ScreenshotWindow
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class DeviceSettingsWindow(QtWidgets.QWidget):
|
||||
def __init__(self, app: QtWidgets.QWidget):
|
||||
super().__init__()
|
||||
|
||||
self.app = app
|
||||
self.setWindowTitle("Device settings")
|
||||
self.setWindowIcon(self.app.icon)
|
||||
|
||||
QtWidgets.QShortcut(QtCore.Qt.Key_Escape, self, self.hide)
|
||||
|
||||
layout = QtWidgets.QVBoxLayout()
|
||||
self.setLayout(layout)
|
||||
|
||||
status_box = QtWidgets.QGroupBox("Status")
|
||||
status_layout = QtWidgets.QFormLayout(status_box)
|
||||
self.statusLabel = QtWidgets.QLabel("Not connected.")
|
||||
status_layout.addRow("Status:", self.statusLabel)
|
||||
|
||||
self.calibrationStatusLabel = QtWidgets.QLabel("Not connected.")
|
||||
status_layout.addRow("Calibration:", self.calibrationStatusLabel)
|
||||
|
||||
status_layout.addRow(QtWidgets.QLabel("Features:"))
|
||||
self.featureList = QtWidgets.QListWidget()
|
||||
status_layout.addRow(self.featureList)
|
||||
|
||||
settings_box = QtWidgets.QGroupBox("Settings")
|
||||
settings_layout = QtWidgets.QFormLayout(settings_box)
|
||||
|
||||
self.chkValidateInputData = QtWidgets.QCheckBox("Validate received data")
|
||||
validate_input = self.app.settings.value("SerialInputValidation", True, bool)
|
||||
self.chkValidateInputData.setChecked(validate_input)
|
||||
self.chkValidateInputData.stateChanged.connect(self.updateValidation)
|
||||
settings_layout.addRow("Validation", self.chkValidateInputData)
|
||||
|
||||
control_layout = QtWidgets.QHBoxLayout()
|
||||
self.btnRefresh = QtWidgets.QPushButton("Refresh")
|
||||
self.btnRefresh.clicked.connect(self.updateFields)
|
||||
control_layout.addWidget(self.btnRefresh)
|
||||
|
||||
self.screenshotWindow = ScreenshotWindow()
|
||||
self.btnCaptureScreenshot = QtWidgets.QPushButton("Screenshot")
|
||||
self.btnCaptureScreenshot.clicked.connect(self.captureScreenshot)
|
||||
control_layout.addWidget(self.btnCaptureScreenshot)
|
||||
|
||||
layout.addWidget(status_box)
|
||||
layout.addWidget(settings_box)
|
||||
layout.addLayout(control_layout)
|
||||
|
||||
def show(self):
|
||||
super().show()
|
||||
self.updateFields()
|
||||
|
||||
def updateFields(self):
|
||||
if self.app.vna.isValid():
|
||||
self.statusLabel.setText("Connected to " + self.app.vna.name + ".")
|
||||
if self.app.worker.running:
|
||||
self.calibrationStatusLabel.setText("(Sweep running)")
|
||||
else:
|
||||
self.calibrationStatusLabel.setText(self.app.vna.getCalibration())
|
||||
|
||||
self.featureList.clear()
|
||||
self.featureList.addItem(self.app.vna.name + " v" + str(self.app.vna.version))
|
||||
features = self.app.vna.getFeatures()
|
||||
for item in features:
|
||||
self.featureList.addItem(item)
|
||||
|
||||
if "Screenshots" in features:
|
||||
self.btnCaptureScreenshot.setDisabled(False)
|
||||
else:
|
||||
self.btnCaptureScreenshot.setDisabled(True)
|
||||
else:
|
||||
self.statusLabel.setText("Not connected.")
|
||||
self.calibrationStatusLabel.setText("Not connected.")
|
||||
self.featureList.clear()
|
||||
self.featureList.addItem("Not connected.")
|
||||
self.btnCaptureScreenshot.setDisabled(True)
|
||||
|
||||
def updateValidation(self, validate_data: bool):
|
||||
self.app.vna.validateInput = validate_data
|
||||
self.app.settings.setValue("SerialInputValidation", validate_data)
|
||||
|
||||
def captureScreenshot(self):
|
||||
if not self.app.worker.running:
|
||||
pixmap = self.app.vna.getScreenshot()
|
||||
self.screenshotWindow.setScreenshot(pixmap)
|
||||
self.screenshotWindow.show()
|
||||
else:
|
||||
# TODO: Tell the user no screenshots while sweep is running?
|
||||
# TODO: Consider having a list of widgets that want to be
|
||||
# disabled when a sweep is running?
|
||||
pass
|
|
@ -0,0 +1,741 @@
|
|||
# NanoVNASaver - a python program to view and export Touchstone data from a NanoVNA
|
||||
# Copyright (C) 2019. Rune B. Broberg
|
||||
#
|
||||
# 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 logging
|
||||
from typing import List
|
||||
|
||||
from PyQt5 import QtWidgets, QtCore, QtGui
|
||||
|
||||
from NanoVNASaver.Windows.Bands import BandsWindow
|
||||
from NanoVNASaver.Windows.MarkerSettings import MarkerSettingsWindow
|
||||
from NanoVNASaver.Marker import Marker
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class DisplaySettingsWindow(QtWidgets.QWidget):
|
||||
def __init__(self, app: QtWidgets.QWidget):
|
||||
super().__init__()
|
||||
|
||||
self.app = app
|
||||
self.setWindowTitle("Display settings")
|
||||
self.setWindowIcon(self.app.icon)
|
||||
|
||||
self.marker_window = MarkerSettingsWindow(self.app)
|
||||
|
||||
QtWidgets.QShortcut(QtCore.Qt.Key_Escape, self, self.hide)
|
||||
|
||||
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)
|
||||
|
||||
self.returnloss_group = QtWidgets.QButtonGroup()
|
||||
self.returnloss_is_negative = QtWidgets.QRadioButton("Negative")
|
||||
self.returnloss_is_positive = QtWidgets.QRadioButton("Positive")
|
||||
self.returnloss_group.addButton(self.returnloss_is_positive)
|
||||
self.returnloss_group.addButton(self.returnloss_is_negative)
|
||||
|
||||
display_options_layout.addRow("Return loss is:", self.returnloss_is_negative)
|
||||
display_options_layout.addRow("", self.returnloss_is_positive)
|
||||
|
||||
if self.app.settings.value("ReturnLossPositive", False, bool):
|
||||
self.returnloss_is_positive.setChecked(True)
|
||||
else:
|
||||
self.returnloss_is_negative.setChecked(True)
|
||||
|
||||
self.returnloss_is_positive.toggled.connect(self.changeReturnLoss)
|
||||
self.changeReturnLoss()
|
||||
|
||||
self.show_lines_option = QtWidgets.QCheckBox("Show lines")
|
||||
show_lines_label = QtWidgets.QLabel("Displays a thin line between data points")
|
||||
self.show_lines_option.stateChanged.connect(self.changeShowLines)
|
||||
display_options_layout.addRow(self.show_lines_option, show_lines_label)
|
||||
|
||||
self.dark_mode_option = QtWidgets.QCheckBox("Dark mode")
|
||||
dark_mode_label = QtWidgets.QLabel("Black background with white text")
|
||||
self.dark_mode_option.stateChanged.connect(self.changeDarkMode)
|
||||
display_options_layout.addRow(self.dark_mode_option, dark_mode_label)
|
||||
|
||||
self.btnColorPicker = QtWidgets.QPushButton("█")
|
||||
self.btnColorPicker.setFixedWidth(20)
|
||||
self.sweepColor = self.app.settings.value("SweepColor", defaultValue=QtGui.QColor(160, 140, 20, 128),
|
||||
type=QtGui.QColor)
|
||||
self.setSweepColor(self.sweepColor)
|
||||
self.btnColorPicker.clicked.connect(lambda: self.setSweepColor(
|
||||
QtWidgets.QColorDialog.getColor(self.sweepColor, options=QtWidgets.QColorDialog.ShowAlphaChannel)))
|
||||
|
||||
display_options_layout.addRow("Sweep color", self.btnColorPicker)
|
||||
|
||||
self.btnSecondaryColorPicker = QtWidgets.QPushButton("█")
|
||||
self.btnSecondaryColorPicker.setFixedWidth(20)
|
||||
self.secondarySweepColor = self.app.settings.value("SecondarySweepColor",
|
||||
defaultValue=QtGui.QColor(20, 160, 140, 128),
|
||||
type=QtGui.QColor)
|
||||
self.setSecondarySweepColor(self.secondarySweepColor)
|
||||
self.btnSecondaryColorPicker.clicked.connect(lambda: self.setSecondarySweepColor(
|
||||
QtWidgets.QColorDialog.getColor(self.secondarySweepColor,
|
||||
options=QtWidgets.QColorDialog.ShowAlphaChannel)))
|
||||
|
||||
display_options_layout.addRow("Second sweep color", self.btnSecondaryColorPicker)
|
||||
|
||||
self.btnReferenceColorPicker = QtWidgets.QPushButton("█")
|
||||
self.btnReferenceColorPicker.setFixedWidth(20)
|
||||
self.referenceColor = self.app.settings.value("ReferenceColor", defaultValue=QtGui.QColor(0, 0, 255, 48),
|
||||
type=QtGui.QColor)
|
||||
self.setReferenceColor(self.referenceColor)
|
||||
self.btnReferenceColorPicker.clicked.connect(lambda: self.setReferenceColor(
|
||||
QtWidgets.QColorDialog.getColor(self.referenceColor, options=QtWidgets.QColorDialog.ShowAlphaChannel)))
|
||||
|
||||
display_options_layout.addRow("Reference color", self.btnReferenceColorPicker)
|
||||
|
||||
self.btnSecondaryReferenceColorPicker = QtWidgets.QPushButton("█")
|
||||
self.btnSecondaryReferenceColorPicker.setFixedWidth(20)
|
||||
self.secondaryReferenceColor = self.app.settings.value("SecondaryReferenceColor",
|
||||
defaultValue=QtGui.QColor(0, 0, 255, 48),
|
||||
type=QtGui.QColor)
|
||||
self.setSecondaryReferenceColor(self.secondaryReferenceColor)
|
||||
self.btnSecondaryReferenceColorPicker.clicked.connect(lambda: self.setSecondaryReferenceColor(
|
||||
QtWidgets.QColorDialog.getColor(self.secondaryReferenceColor,
|
||||
options=QtWidgets.QColorDialog.ShowAlphaChannel)))
|
||||
|
||||
display_options_layout.addRow("Second reference color", self.btnSecondaryReferenceColorPicker)
|
||||
|
||||
self.pointSizeInput = QtWidgets.QSpinBox()
|
||||
pointsize = self.app.settings.value("PointSize", 2, int)
|
||||
self.pointSizeInput.setValue(pointsize)
|
||||
self.changePointSize(pointsize)
|
||||
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()
|
||||
linethickness = self.app.settings.value("LineThickness", 1, int)
|
||||
self.lineThicknessInput.setValue(linethickness)
|
||||
self.changeLineThickness(linethickness)
|
||||
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)
|
||||
|
||||
self.markerSizeInput = QtWidgets.QSpinBox()
|
||||
markersize = self.app.settings.value("MarkerSize", 6, int)
|
||||
self.markerSizeInput.setValue(markersize)
|
||||
self.changeMarkerSize(markersize)
|
||||
self.markerSizeInput.setMinimum(4)
|
||||
self.markerSizeInput.setMaximum(20)
|
||||
self.markerSizeInput.setSingleStep(2)
|
||||
self.markerSizeInput.setSuffix(" px")
|
||||
self.markerSizeInput.setAlignment(QtCore.Qt.AlignRight)
|
||||
self.markerSizeInput.valueChanged.connect(self.changeMarkerSize)
|
||||
self.markerSizeInput.editingFinished.connect(self.validateMarkerSize)
|
||||
display_options_layout.addRow("Marker size", self.markerSizeInput)
|
||||
|
||||
self.show_marker_number_option = QtWidgets.QCheckBox("Show marker numbers")
|
||||
show_marker_number_label = QtWidgets.QLabel("Displays the marker number next to the marker")
|
||||
self.show_marker_number_option.stateChanged.connect(self.changeShowMarkerNumber)
|
||||
display_options_layout.addRow(self.show_marker_number_option, show_marker_number_label)
|
||||
|
||||
self.filled_marker_option = QtWidgets.QCheckBox("Filled markers")
|
||||
filled_marker_label = QtWidgets.QLabel("Shows the marker as a filled triangle")
|
||||
self.filled_marker_option.stateChanged.connect(self.changeFilledMarkers)
|
||||
display_options_layout.addRow(self.filled_marker_option, filled_marker_label)
|
||||
|
||||
self.marker_tip_group = QtWidgets.QButtonGroup()
|
||||
self.marker_at_center = QtWidgets.QRadioButton("At the center of the marker")
|
||||
self.marker_at_tip = QtWidgets.QRadioButton("At the tip of the marker")
|
||||
self.marker_tip_group.addButton(self.marker_at_center)
|
||||
self.marker_tip_group.addButton(self.marker_at_tip)
|
||||
|
||||
display_options_layout.addRow("Data point is:", self.marker_at_center)
|
||||
display_options_layout.addRow("", self.marker_at_tip)
|
||||
|
||||
if self.app.settings.value("MarkerAtTip", False, bool):
|
||||
self.marker_at_tip.setChecked(True)
|
||||
else:
|
||||
self.marker_at_center.setChecked(True)
|
||||
|
||||
self.marker_at_tip.toggled.connect(self.changeMarkerAtTip)
|
||||
self.changeMarkerAtTip()
|
||||
|
||||
color_options_box = QtWidgets.QGroupBox("Chart colors")
|
||||
color_options_layout = QtWidgets.QFormLayout(color_options_box)
|
||||
|
||||
self.use_custom_colors = QtWidgets.QCheckBox("Use custom chart colors")
|
||||
self.use_custom_colors.stateChanged.connect(self.changeCustomColors)
|
||||
color_options_layout.addRow(self.use_custom_colors)
|
||||
|
||||
self.btn_background_picker = QtWidgets.QPushButton("█")
|
||||
self.btn_background_picker.setFixedWidth(20)
|
||||
self.btn_background_picker.clicked.connect(
|
||||
lambda: self.setColor(
|
||||
"background",
|
||||
QtWidgets.QColorDialog.getColor(
|
||||
self.backgroundColor,
|
||||
options=QtWidgets.QColorDialog.ShowAlphaChannel)))
|
||||
|
||||
color_options_layout.addRow(
|
||||
"Chart background", self.btn_background_picker)
|
||||
|
||||
self.btn_foreground_picker = QtWidgets.QPushButton("█")
|
||||
self.btn_foreground_picker.setFixedWidth(20)
|
||||
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)
|
||||
|
||||
right_layout = QtWidgets.QVBoxLayout()
|
||||
layout.addLayout(right_layout)
|
||||
|
||||
font_options_box = QtWidgets.QGroupBox("Font")
|
||||
font_options_layout = QtWidgets.QFormLayout(font_options_box)
|
||||
self.font_dropdown = QtWidgets.QComboBox()
|
||||
self.font_dropdown.addItems(["7", "8", "9", "10", "11", "12"])
|
||||
font_size = self.app.settings.value("FontSize",
|
||||
defaultValue="8",
|
||||
type=str)
|
||||
self.font_dropdown.setCurrentText(font_size)
|
||||
self.changeFont()
|
||||
|
||||
self.font_dropdown.currentTextChanged.connect(self.changeFont)
|
||||
font_options_layout.addRow("Font size", self.font_dropdown)
|
||||
|
||||
bands_box = QtWidgets.QGroupBox("Bands")
|
||||
bands_layout = QtWidgets.QFormLayout(bands_box)
|
||||
|
||||
self.show_bands = QtWidgets.QCheckBox("Show bands")
|
||||
self.show_bands.setChecked(self.app.bands.enabled)
|
||||
self.show_bands.stateChanged.connect(lambda: self.setShowBands(self.show_bands.isChecked()))
|
||||
bands_layout.addRow(self.show_bands)
|
||||
|
||||
self.btn_bands_picker = QtWidgets.QPushButton("█")
|
||||
self.btn_bands_picker.setFixedWidth(20)
|
||||
self.btn_bands_picker.clicked.connect(
|
||||
lambda: self.setColor(
|
||||
"bands",
|
||||
QtWidgets.QColorDialog.getColor(
|
||||
self.bandsColor,
|
||||
options=QtWidgets.QColorDialog.ShowAlphaChannel)))
|
||||
|
||||
bands_layout.addRow("Chart bands", self.btn_bands_picker)
|
||||
|
||||
self.btn_manage_bands = QtWidgets.QPushButton("Manage bands")
|
||||
|
||||
self.bandsWindow = BandsWindow(self.app)
|
||||
self.btn_manage_bands.clicked.connect(self.displayBandsWindow)
|
||||
|
||||
bands_layout.addRow(self.btn_manage_bands)
|
||||
|
||||
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_vswr_marker = QtWidgets.QPushButton("Add ...")
|
||||
btn_remove_vswr_marker = QtWidgets.QPushButton("Remove")
|
||||
vswr_marker_btn_layout = QtWidgets.QHBoxLayout()
|
||||
vswr_marker_btn_layout.addWidget(btn_add_vswr_marker)
|
||||
vswr_marker_btn_layout.addWidget(btn_remove_vswr_marker)
|
||||
vswr_marker_layout.addRow(vswr_marker_btn_layout)
|
||||
|
||||
btn_add_vswr_marker.clicked.connect(self.addVSWRMarker)
|
||||
btn_remove_vswr_marker.clicked.connect(self.removeVSWRMarker)
|
||||
|
||||
markers_box = QtWidgets.QGroupBox("Markers")
|
||||
markers_layout = QtWidgets.QFormLayout(markers_box)
|
||||
|
||||
btn_add_marker = QtWidgets.QPushButton("Add")
|
||||
btn_add_marker.clicked.connect(self.addMarker)
|
||||
self.btn_remove_marker = QtWidgets.QPushButton("Remove")
|
||||
self.btn_remove_marker.clicked.connect(self.removeMarker)
|
||||
btn_marker_settings = QtWidgets.QPushButton("Settings ...")
|
||||
btn_marker_settings.clicked.connect(self.displayMarkerWindow)
|
||||
|
||||
marker_btn_layout = QtWidgets.QHBoxLayout()
|
||||
marker_btn_layout.addWidget(btn_add_marker)
|
||||
marker_btn_layout.addWidget(self.btn_remove_marker)
|
||||
marker_btn_layout.addWidget(btn_marker_settings)
|
||||
|
||||
markers_layout.addRow(marker_btn_layout)
|
||||
|
||||
charts_box = QtWidgets.QGroupBox("Displayed charts")
|
||||
charts_layout = QtWidgets.QGridLayout(charts_box)
|
||||
|
||||
# selections = ["S11 Smith chart",
|
||||
# "S11 LogMag",
|
||||
# "S11 VSWR",
|
||||
# "S11 Phase",
|
||||
# "S21 Smith chart",
|
||||
# "S21 LogMag",
|
||||
# "S21 Phase",
|
||||
# "None"]
|
||||
|
||||
selections = []
|
||||
|
||||
for c in self.app.selectable_charts:
|
||||
selections.append(c.name)
|
||||
|
||||
selections.append("None")
|
||||
chart00_selection = QtWidgets.QComboBox()
|
||||
chart00_selection.addItems(selections)
|
||||
chart00 = self.app.settings.value("Chart00", "S11 Smith Chart")
|
||||
if chart00_selection.findText(chart00) > -1:
|
||||
chart00_selection.setCurrentText(chart00)
|
||||
else:
|
||||
chart00_selection.setCurrentText("S11 Smith Chart")
|
||||
chart00_selection.currentTextChanged.connect(lambda: self.changeChart(0, 0, chart00_selection.currentText()))
|
||||
charts_layout.addWidget(chart00_selection, 0, 0)
|
||||
|
||||
chart01_selection = QtWidgets.QComboBox()
|
||||
chart01_selection.addItems(selections)
|
||||
chart01 = self.app.settings.value("Chart01", "S11 Return Loss")
|
||||
if chart01_selection.findText(chart01) > -1:
|
||||
chart01_selection.setCurrentText(chart01)
|
||||
else:
|
||||
chart01_selection.setCurrentText("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 = self.app.settings.value("Chart02", "None")
|
||||
if chart02_selection.findText(chart02) > -1:
|
||||
chart02_selection.setCurrentText(chart02)
|
||||
else:
|
||||
chart02_selection.setCurrentText("None")
|
||||
chart02_selection.currentTextChanged.connect(lambda: self.changeChart(0, 2, chart02_selection.currentText()))
|
||||
charts_layout.addWidget(chart02_selection, 0, 2)
|
||||
|
||||
chart10_selection = QtWidgets.QComboBox()
|
||||
chart10_selection.addItems(selections)
|
||||
chart10 = self.app.settings.value("Chart10", "S21 Polar Plot")
|
||||
if chart10_selection.findText(chart10) > -1:
|
||||
chart10_selection.setCurrentText(chart10)
|
||||
else:
|
||||
chart10_selection.setCurrentText("S21 Polar Plot")
|
||||
chart10_selection.currentTextChanged.connect(lambda: self.changeChart(1, 0, chart10_selection.currentText()))
|
||||
charts_layout.addWidget(chart10_selection, 1, 0)
|
||||
|
||||
chart11_selection = QtWidgets.QComboBox()
|
||||
chart11_selection.addItems(selections)
|
||||
chart11 = self.app.settings.value("Chart11", "S21 Gain")
|
||||
if chart11_selection.findText(chart11) > -1:
|
||||
chart11_selection.setCurrentText(chart11)
|
||||
else:
|
||||
chart11_selection.setCurrentText("S21 Gain")
|
||||
chart11_selection.currentTextChanged.connect(lambda: self.changeChart(1, 1, chart11_selection.currentText()))
|
||||
charts_layout.addWidget(chart11_selection, 1, 1)
|
||||
|
||||
chart12_selection = QtWidgets.QComboBox()
|
||||
chart12_selection.addItems(selections)
|
||||
chart12 = self.app.settings.value("Chart12", "None")
|
||||
if chart12_selection.findText(chart12) > -1:
|
||||
chart12_selection.setCurrentText(chart12)
|
||||
else:
|
||||
chart12_selection.setCurrentText("None")
|
||||
chart12_selection.currentTextChanged.connect(lambda: self.changeChart(1, 2, chart12_selection.currentText()))
|
||||
charts_layout.addWidget(chart12_selection, 1, 2)
|
||||
|
||||
self.changeChart(0, 0, chart00_selection.currentText())
|
||||
self.changeChart(0, 1, chart01_selection.currentText())
|
||||
self.changeChart(0, 2, chart02_selection.currentText())
|
||||
self.changeChart(1, 0, chart10_selection.currentText())
|
||||
self.changeChart(1, 1, chart11_selection.currentText())
|
||||
self.changeChart(1, 2, chart12_selection.currentText())
|
||||
|
||||
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"),
|
||||
type=QtGui.QColor)
|
||||
self.textColor = self.app.settings.value("TextColor", defaultValue=QtGui.QColor("black"),
|
||||
type=QtGui.QColor)
|
||||
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))
|
||||
self.show_marker_number_option.setChecked(self.app.settings.value("ShowMarkerNumbers", False, bool))
|
||||
self.filled_marker_option.setChecked(self.app.settings.value("FilledMarkers", False, bool))
|
||||
|
||||
if self.app.settings.value("UseCustomColors", defaultValue=False, type=bool):
|
||||
self.dark_mode_option.setDisabled(True)
|
||||
self.dark_mode_option.setChecked(False)
|
||||
self.use_custom_colors.setChecked(True)
|
||||
else:
|
||||
self.btn_background_picker.setDisabled(True)
|
||||
self.btn_foreground_picker.setDisabled(True)
|
||||
self.btn_text_picker.setDisabled(True)
|
||||
|
||||
self.changeCustomColors() # Update all the colours of all the charts
|
||||
|
||||
p = self.btn_background_picker.palette()
|
||||
p.setColor(QtGui.QPalette.ButtonText, self.backgroundColor)
|
||||
self.btn_background_picker.setPalette(p)
|
||||
|
||||
p = self.btn_foreground_picker.palette()
|
||||
p.setColor(QtGui.QPalette.ButtonText, self.foregroundColor)
|
||||
self.btn_foreground_picker.setPalette(p)
|
||||
|
||||
p = self.btn_text_picker.palette()
|
||||
p.setColor(QtGui.QPalette.ButtonText, self.textColor)
|
||||
self.btn_text_picker.setPalette(p)
|
||||
|
||||
p = self.btn_bands_picker.palette()
|
||||
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.addWidget(markers_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)
|
||||
right_layout.addStretch(1)
|
||||
|
||||
def changeChart(self, x, y, chart):
|
||||
found = None
|
||||
for c in self.app.selectable_charts:
|
||||
if c.name == chart:
|
||||
found = c
|
||||
|
||||
self.app.settings.setValue("Chart" + str(x) + str(y), chart)
|
||||
|
||||
old_widget = self.app.charts_layout.itemAtPosition(x, y)
|
||||
if old_widget is not None:
|
||||
w = old_widget.widget()
|
||||
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()
|
||||
|
||||
def changeReturnLoss(self):
|
||||
state = self.returnloss_is_positive.isChecked()
|
||||
self.app.settings.setValue("ReturnLossPositive", state)
|
||||
|
||||
for m in self.app.markers:
|
||||
m.returnloss_is_positive = state
|
||||
m.updateLabels(self.app.data, self.app.data21)
|
||||
self.marker_window.exampleMarker.returnloss_is_positive = state
|
||||
self.marker_window.updateMarker()
|
||||
self.app.s11LogMag.isInverted = state
|
||||
self.app.s11LogMag.update()
|
||||
|
||||
def changeShowLines(self):
|
||||
state = self.show_lines_option.isChecked()
|
||||
self.app.settings.setValue("ShowLines", state)
|
||||
for c in self.app.subscribing_charts:
|
||||
c.setDrawLines(state)
|
||||
|
||||
def changeShowMarkerNumber(self):
|
||||
state = self.show_marker_number_option.isChecked()
|
||||
self.app.settings.setValue("ShowMarkerNumbers", state)
|
||||
for c in self.app.subscribing_charts:
|
||||
c.setDrawMarkerNumbers(state)
|
||||
|
||||
def changeFilledMarkers(self):
|
||||
state = self.filled_marker_option.isChecked()
|
||||
self.app.settings.setValue("FilledMarkers", state)
|
||||
for c in self.app.subscribing_charts:
|
||||
c.setFilledMarkers(state)
|
||||
|
||||
def changeMarkerAtTip(self):
|
||||
state = self.marker_at_tip.isChecked()
|
||||
self.app.settings.setValue("MarkerAtTip", state)
|
||||
for c in self.app.subscribing_charts:
|
||||
c.setMarkerAtTip(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 changeMarkerSize(self, size: int):
|
||||
if size % 2 == 0:
|
||||
self.app.settings.setValue("MarkerSize", size)
|
||||
for c in self.app.subscribing_charts:
|
||||
c.setMarkerSize(int(size / 2))
|
||||
|
||||
def validateMarkerSize(self):
|
||||
size = self.markerSizeInput.value()
|
||||
if size % 2 != 0:
|
||||
self.markerSizeInput.setValue(size + 1)
|
||||
|
||||
def changeDarkMode(self):
|
||||
state = self.dark_mode_option.isChecked()
|
||||
self.app.settings.setValue("DarkMode", state)
|
||||
if state:
|
||||
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.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())
|
||||
if self.use_custom_colors.isChecked():
|
||||
self.dark_mode_option.setDisabled(True)
|
||||
self.dark_mode_option.setChecked(False)
|
||||
self.btn_background_picker.setDisabled(False)
|
||||
self.btn_foreground_picker.setDisabled(False)
|
||||
self.btn_text_picker.setDisabled(False)
|
||||
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)
|
||||
self.btn_foreground_picker.setDisabled(True)
|
||||
self.btn_text_picker.setDisabled(True)
|
||||
self.changeDarkMode() # Reset to the default colors depending on Dark Mode setting
|
||||
|
||||
def setColor(self, name: str, color: QtGui.QColor):
|
||||
if name == "background":
|
||||
p = self.btn_background_picker.palette()
|
||||
p.setColor(QtGui.QPalette.ButtonText, color)
|
||||
self.btn_background_picker.setPalette(p)
|
||||
self.backgroundColor = color
|
||||
self.app.settings.setValue("BackgroundColor", color)
|
||||
elif name == "foreground":
|
||||
p = self.btn_foreground_picker.palette()
|
||||
p.setColor(QtGui.QPalette.ButtonText, color)
|
||||
self.btn_foreground_picker.setPalette(p)
|
||||
self.foregroundColor = color
|
||||
self.app.settings.setValue("ForegroundColor", color)
|
||||
elif name == "text":
|
||||
p = self.btn_text_picker.palette()
|
||||
p.setColor(QtGui.QPalette.ButtonText, color)
|
||||
self.btn_text_picker.setPalette(p)
|
||||
self.textColor = color
|
||||
self.app.settings.setValue("TextColor", color)
|
||||
elif name == "bands":
|
||||
p = self.btn_bands_picker.palette()
|
||||
p.setColor(QtGui.QPalette.ButtonText, color)
|
||||
self.btn_bands_picker.setPalette(p)
|
||||
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):
|
||||
if color.isValid():
|
||||
self.sweepColor = color
|
||||
p = self.btnColorPicker.palette()
|
||||
p.setColor(QtGui.QPalette.ButtonText, color)
|
||||
self.btnColorPicker.setPalette(p)
|
||||
self.app.settings.setValue("SweepColor", color)
|
||||
self.app.settings.sync()
|
||||
for c in self.app.subscribing_charts:
|
||||
c.setSweepColor(color)
|
||||
|
||||
def setSecondarySweepColor(self, color: QtGui.QColor):
|
||||
if color.isValid():
|
||||
self.secondarySweepColor = color
|
||||
p = self.btnSecondaryColorPicker.palette()
|
||||
p.setColor(QtGui.QPalette.ButtonText, color)
|
||||
self.btnSecondaryColorPicker.setPalette(p)
|
||||
self.app.settings.setValue("SecondarySweepColor", color)
|
||||
self.app.settings.sync()
|
||||
for c in self.app.subscribing_charts:
|
||||
c.setSecondarySweepColor(color)
|
||||
|
||||
def setReferenceColor(self, color):
|
||||
if color.isValid():
|
||||
self.referenceColor = color
|
||||
p = self.btnReferenceColorPicker.palette()
|
||||
p.setColor(QtGui.QPalette.ButtonText, color)
|
||||
self.btnReferenceColorPicker.setPalette(p)
|
||||
self.app.settings.setValue("ReferenceColor", color)
|
||||
self.app.settings.sync()
|
||||
|
||||
for c in self.app.subscribing_charts:
|
||||
c.setReferenceColor(color)
|
||||
|
||||
def setSecondaryReferenceColor(self, color):
|
||||
if color.isValid():
|
||||
self.secondaryReferenceColor = color
|
||||
p = self.btnSecondaryReferenceColorPicker.palette()
|
||||
p.setColor(QtGui.QPalette.ButtonText, color)
|
||||
self.btnSecondaryReferenceColorPicker.setPalette(p)
|
||||
self.app.settings.setValue("SecondaryReferenceColor", color)
|
||||
self.app.settings.sync()
|
||||
|
||||
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.subscribing_charts:
|
||||
c.update()
|
||||
|
||||
def changeFont(self):
|
||||
font_size = self.font_dropdown.currentText()
|
||||
self.app.settings.setValue("FontSize", font_size)
|
||||
app: QtWidgets.QApplication = QtWidgets.QApplication.instance()
|
||||
font = app.font()
|
||||
font.setPointSize(int(font_size))
|
||||
app.setFont(font)
|
||||
self.app.changeFont(font)
|
||||
|
||||
def displayBandsWindow(self):
|
||||
self.bandsWindow.show()
|
||||
QtWidgets.QApplication.setActiveWindow(self.bandsWindow)
|
||||
|
||||
def displayMarkerWindow(self):
|
||||
self.marker_window.show()
|
||||
QtWidgets.QApplication.setActiveWindow(self.marker_window)
|
||||
|
||||
def addMarker(self):
|
||||
new_marker = Marker("", self.app.settings)
|
||||
new_marker.setScale(self.app.scaleFactor)
|
||||
self.app.markers.append(new_marker)
|
||||
self.app.marker_data_layout.addWidget(new_marker.getGroupBox())
|
||||
|
||||
new_marker.updated.connect(self.app.markerUpdated)
|
||||
label, layout = new_marker.getRow()
|
||||
self.app.marker_control_layout.insertRow(Marker.count() - 1, label, layout)
|
||||
self.btn_remove_marker.setDisabled(False)
|
||||
|
||||
def removeMarker(self):
|
||||
# keep at least one marker
|
||||
if Marker.count() <= 1:
|
||||
return
|
||||
if Marker.count() == 2:
|
||||
self.btn_remove_marker.setDisabled(True)
|
||||
last_marker = self.app.markers.pop()
|
||||
|
||||
last_marker.updated.disconnect(self.app.markerUpdated)
|
||||
self.app.marker_data_layout.removeWidget(last_marker.getGroupBox())
|
||||
self.app.marker_control_layout.removeRow(Marker.count()-1)
|
||||
last_marker.getGroupBox().hide()
|
||||
last_marker.getGroupBox().destroy()
|
||||
label, _ = last_marker.getRow()
|
||||
label.hide()
|
||||
|
||||
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)
|
||||
|
|
@ -0,0 +1,154 @@
|
|||
# NanoVNASaver
|
||||
# A python program to view and export Touchstone data from a NanoVNA
|
||||
# Copyright (C) 2019. Rune B. Broberg
|
||||
#
|
||||
# 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 logging
|
||||
|
||||
from PyQt5 import QtWidgets, QtCore, QtGui
|
||||
|
||||
from NanoVNASaver.RFTools import Datapoint
|
||||
from NanoVNASaver.Marker import Marker
|
||||
from NanoVNASaver.Marker.Values import TYPES, default_label_ids
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MarkerSettingsWindow(QtWidgets.QWidget):
|
||||
exampleData11 = [Datapoint(123000000, 0.89, -0.11),
|
||||
Datapoint(123500000, 0.9, -0.1),
|
||||
Datapoint(124000000, 0.91, -0.95)]
|
||||
exampleData21 = [Datapoint(123000000, -0.25, 0.49),
|
||||
Datapoint(123456000, -0.3, 0.5),
|
||||
Datapoint(124000000, -0.2, 0.5)]
|
||||
|
||||
def __init__(self, app: QtWidgets.QWidget):
|
||||
super().__init__()
|
||||
self.app = app
|
||||
|
||||
self.setWindowTitle("Marker settings")
|
||||
self.setWindowIcon(self.app.icon)
|
||||
|
||||
QtWidgets.QShortcut(QtCore.Qt.Key_Escape, self, self.cancelButtonClick)
|
||||
|
||||
self.exampleMarker = Marker("Example marker")
|
||||
layout = QtWidgets.QVBoxLayout()
|
||||
self.setLayout(layout)
|
||||
|
||||
settings_group_box = QtWidgets.QGroupBox("Settings")
|
||||
settings_group_box_layout = QtWidgets.QFormLayout(settings_group_box)
|
||||
self.checkboxColouredMarker = QtWidgets.QCheckBox("Colored marker name")
|
||||
self.checkboxColouredMarker.setChecked(self.app.settings.value("ColoredMarkerNames", True, bool))
|
||||
self.checkboxColouredMarker.stateChanged.connect(self.updateMarker)
|
||||
settings_group_box_layout.addRow(self.checkboxColouredMarker)
|
||||
|
||||
fields_group_box = QtWidgets.QGroupBox("Displayed data")
|
||||
fields_group_box_layout = QtWidgets.QFormLayout(fields_group_box)
|
||||
|
||||
self.savedFieldSelection = self.app.settings.value(
|
||||
"MarkerFields", defaultValue=default_label_ids()
|
||||
)
|
||||
|
||||
if self.savedFieldSelection == "":
|
||||
self.savedFieldSelection = []
|
||||
|
||||
self.currentFieldSelection = self.savedFieldSelection[:]
|
||||
|
||||
self.active_labels_view = QtWidgets.QListView()
|
||||
self.update_displayed_data_form()
|
||||
|
||||
fields_group_box_layout.addRow(self.active_labels_view)
|
||||
|
||||
layout.addWidget(settings_group_box)
|
||||
layout.addWidget(fields_group_box)
|
||||
layout.addWidget(self.exampleMarker.getGroupBox())
|
||||
|
||||
btn_layout = QtWidgets.QHBoxLayout()
|
||||
layout.addLayout(btn_layout)
|
||||
btn_ok = QtWidgets.QPushButton("OK")
|
||||
btn_apply = QtWidgets.QPushButton("Apply")
|
||||
btn_default = QtWidgets.QPushButton("Defaults")
|
||||
btn_cancel = QtWidgets.QPushButton("Cancel")
|
||||
|
||||
btn_ok.clicked.connect(self.okButtonClick)
|
||||
btn_apply.clicked.connect(self.applyButtonClick)
|
||||
btn_default.clicked.connect(self.defaultButtonClick)
|
||||
btn_cancel.clicked.connect(self.cancelButtonClick)
|
||||
|
||||
btn_layout.addWidget(btn_ok)
|
||||
btn_layout.addWidget(btn_apply)
|
||||
btn_layout.addWidget(btn_default)
|
||||
btn_layout.addWidget(btn_cancel)
|
||||
|
||||
self.updateMarker()
|
||||
for m in self.app.markers:
|
||||
m.setFieldSelection(self.currentFieldSelection)
|
||||
m.setColoredText(self.checkboxColouredMarker.isChecked())
|
||||
|
||||
def updateMarker(self):
|
||||
self.exampleMarker.setFrequency(123456000)
|
||||
self.exampleMarker.setColoredText(self.checkboxColouredMarker.isChecked())
|
||||
self.exampleMarker.setFieldSelection(self.currentFieldSelection)
|
||||
self.exampleMarker.findLocation(self.exampleData11)
|
||||
self.exampleMarker.resetLabels()
|
||||
self.exampleMarker.updateLabels(self.exampleData11, self.exampleData21)
|
||||
|
||||
def updateField(self, field: QtGui.QStandardItem):
|
||||
if field.checkState() == QtCore.Qt.Checked:
|
||||
if not field.data() in self.currentFieldSelection:
|
||||
self.currentFieldSelection = []
|
||||
for i in range(self.model.rowCount()):
|
||||
field = self.model.item(i, 0)
|
||||
if field.checkState() == QtCore.Qt.Checked:
|
||||
self.currentFieldSelection.append(field.data())
|
||||
else:
|
||||
if field.data() in self.currentFieldSelection:
|
||||
self.currentFieldSelection.remove(field.data())
|
||||
self.updateMarker()
|
||||
|
||||
def applyButtonClick(self):
|
||||
self.savedFieldSelection = self.currentFieldSelection[:]
|
||||
self.app.settings.setValue("MarkerFields", self.savedFieldSelection)
|
||||
self.app.settings.setValue("ColoredMarkerNames", self.checkboxColouredMarker.isChecked())
|
||||
for m in self.app.markers:
|
||||
m.setFieldSelection(self.savedFieldSelection)
|
||||
m.setColoredText(self.checkboxColouredMarker.isChecked())
|
||||
|
||||
def okButtonClick(self):
|
||||
self.applyButtonClick()
|
||||
self.close()
|
||||
|
||||
def cancelButtonClick(self):
|
||||
self.currentFieldSelection = self.savedFieldSelection[:]
|
||||
self.update_displayed_data_form()
|
||||
self.updateMarker()
|
||||
self.close()
|
||||
|
||||
def defaultButtonClick(self):
|
||||
self.currentFieldSelection = default_label_ids()
|
||||
self.update_displayed_data_form()
|
||||
self.updateMarker()
|
||||
|
||||
def update_displayed_data_form(self):
|
||||
self.model = QtGui.QStandardItemModel()
|
||||
for label in TYPES:
|
||||
item = QtGui.QStandardItem(label.description)
|
||||
item.setData(label.label_id)
|
||||
item.setCheckable(True)
|
||||
item.setEditable(False)
|
||||
if label.label_id in self.currentFieldSelection:
|
||||
item.setCheckState(QtCore.Qt.Checked)
|
||||
self.model.appendRow(item)
|
||||
self.active_labels_view.setModel(self.model)
|
||||
self.model.itemChanged.connect(self.updateField)
|
Plik diff jest za duży
Load Diff
|
@ -0,0 +1,99 @@
|
|||
# NanoVNASaver
|
||||
# A python program to view and export Touchstone data from a NanoVNA
|
||||
# Copyright (C) 2019. Rune B. Broberg
|
||||
#
|
||||
# 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 logging
|
||||
from PyQt5 import QtWidgets, QtCore, QtGui
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ScreenshotWindow(QtWidgets.QLabel):
|
||||
pix = None
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.setWindowTitle("Screenshot")
|
||||
# TODO : self.setWindowIcon(self.app.icon)
|
||||
|
||||
QtWidgets.QShortcut(QtCore.Qt.Key_Escape, self, self.hide)
|
||||
self.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu)
|
||||
|
||||
self.action_original_size = QtWidgets.QAction("Original size")
|
||||
self.action_original_size.triggered.connect(lambda: self.setScale(1))
|
||||
self.action_2x_size = QtWidgets.QAction("2x size")
|
||||
self.action_2x_size.triggered.connect(lambda: self.setScale(2))
|
||||
self.action_3x_size = QtWidgets.QAction("3x size")
|
||||
self.action_3x_size.triggered.connect(lambda: self.setScale(3))
|
||||
self.action_4x_size = QtWidgets.QAction("4x size")
|
||||
self.action_4x_size.triggered.connect(lambda: self.setScale(4))
|
||||
self.action_5x_size = QtWidgets.QAction("5x size")
|
||||
self.action_5x_size.triggered.connect(lambda: self.setScale(5))
|
||||
|
||||
self.addAction(self.action_original_size)
|
||||
self.addAction(self.action_2x_size)
|
||||
self.addAction(self.action_3x_size)
|
||||
self.addAction(self.action_4x_size)
|
||||
self.addAction(self.action_5x_size)
|
||||
self.action_save_screenshot = QtWidgets.QAction("Save image")
|
||||
self.action_save_screenshot.triggered.connect(self.saveScreenshot)
|
||||
self.addAction(self.action_save_screenshot)
|
||||
|
||||
def setScreenshot(self, pixmap: QtGui.QPixmap):
|
||||
if self.pix is None:
|
||||
self.resize(pixmap.size())
|
||||
self.pix = pixmap
|
||||
self.setPixmap(
|
||||
self.pix.scaled(
|
||||
self.size(),
|
||||
QtCore.Qt.KeepAspectRatio,
|
||||
QtCore.Qt.FastTransformation))
|
||||
w, h = pixmap.width(), pixmap.height()
|
||||
self.action_original_size.setText(
|
||||
"Original size (" + str(w) + "x" + str(h) + ")")
|
||||
self.action_2x_size.setText(
|
||||
"2x size (" + str(w * 2) + "x" + str(h * 2) + ")")
|
||||
self.action_3x_size.setText(
|
||||
"3x size (" + str(w * 3) + "x" + str(h * 3) + ")")
|
||||
self.action_4x_size.setText(
|
||||
"4x size (" + str(w * 4) + "x" + str(h * 4) + ")")
|
||||
self.action_5x_size.setText(
|
||||
"5x size (" + str(w * 5) + "x" + str(h * 5) + ")")
|
||||
|
||||
def saveScreenshot(self):
|
||||
if self.pix is not None:
|
||||
logger.info("Saving screenshot to file...")
|
||||
filename, _ = QtWidgets.QFileDialog.getSaveFileName(
|
||||
parent=self, caption="Save image",
|
||||
filter="PNG (*.png);;All files (*.*)")
|
||||
|
||||
logger.debug("Filename: %s", filename)
|
||||
if filename != "":
|
||||
self.pixmap().save(filename)
|
||||
else:
|
||||
logger.warning("The user got shown an empty screenshot window?")
|
||||
|
||||
def resizeEvent(self, a0: QtGui.QResizeEvent) -> None:
|
||||
super().resizeEvent(a0)
|
||||
if self.pixmap() is not None:
|
||||
self.setPixmap(
|
||||
self.pix.scaled(
|
||||
self.size(),
|
||||
QtCore.Qt.KeepAspectRatio,
|
||||
QtCore.Qt.FastTransformation))
|
||||
|
||||
def setScale(self, scale):
|
||||
width, height = self.pix.size().width() * scale, self.pix.size().height() * scale
|
||||
self.resize(width, height)
|
|
@ -0,0 +1,176 @@
|
|||
# NanoVNASaver
|
||||
# A python program to view and export Touchstone data from a NanoVNA
|
||||
# Copyright (C) 2019. Rune B. Broberg
|
||||
#
|
||||
# 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 logging
|
||||
|
||||
from PyQt5 import QtWidgets, QtCore
|
||||
|
||||
from NanoVNASaver.RFTools import RFTools
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SweepSettingsWindow(QtWidgets.QWidget):
|
||||
def __init__(self, app: QtWidgets.QWidget):
|
||||
super().__init__()
|
||||
|
||||
self.app = app
|
||||
self.setWindowTitle("Sweep settings")
|
||||
self.setWindowIcon(self.app.icon)
|
||||
|
||||
QtWidgets.QShortcut(QtCore.Qt.Key_Escape, self, self.hide)
|
||||
|
||||
layout = QtWidgets.QVBoxLayout()
|
||||
self.setLayout(layout)
|
||||
|
||||
title_box = QtWidgets.QGroupBox("Sweep name")
|
||||
title_layout = QtWidgets.QFormLayout(title_box)
|
||||
self.sweep_title_input = QtWidgets.QLineEdit()
|
||||
title_layout.addRow("Sweep name", self.sweep_title_input)
|
||||
title_button_layout = QtWidgets.QHBoxLayout()
|
||||
btn_set_sweep_title = QtWidgets.QPushButton("Set")
|
||||
btn_set_sweep_title.clicked.connect(
|
||||
lambda: self.app.setSweepTitle(self.sweep_title_input.text()))
|
||||
btn_reset_sweep_title = QtWidgets.QPushButton("Reset")
|
||||
btn_reset_sweep_title.clicked.connect(lambda: self.app.setSweepTitle(""))
|
||||
title_button_layout.addWidget(btn_set_sweep_title)
|
||||
title_button_layout.addWidget(btn_reset_sweep_title)
|
||||
title_layout.addRow(title_button_layout)
|
||||
layout.addWidget(title_box)
|
||||
|
||||
settings_box = QtWidgets.QGroupBox("Settings")
|
||||
settings_layout = QtWidgets.QFormLayout(settings_box)
|
||||
|
||||
self.single_sweep_radiobutton = QtWidgets.QRadioButton("Single sweep")
|
||||
self.continuous_sweep_radiobutton = QtWidgets.QRadioButton("Continuous sweep")
|
||||
self.averaged_sweep_radiobutton = QtWidgets.QRadioButton("Averaged sweep")
|
||||
|
||||
settings_layout.addWidget(self.single_sweep_radiobutton)
|
||||
self.single_sweep_radiobutton.setChecked(True)
|
||||
settings_layout.addWidget(self.continuous_sweep_radiobutton)
|
||||
settings_layout.addWidget(self.averaged_sweep_radiobutton)
|
||||
|
||||
self.averages = QtWidgets.QLineEdit("3")
|
||||
self.truncates = QtWidgets.QLineEdit("0")
|
||||
|
||||
settings_layout.addRow("Number of measurements to average", self.averages)
|
||||
settings_layout.addRow("Number to discard", self.truncates)
|
||||
settings_layout.addRow(
|
||||
QtWidgets.QLabel(
|
||||
"Averaging allows discarding outlying samples to get better averages."))
|
||||
settings_layout.addRow(
|
||||
QtWidgets.QLabel("Common values are 3/0, 5/2, 9/4 and 25/6."))
|
||||
|
||||
self.continuous_sweep_radiobutton.toggled.connect(
|
||||
lambda: self.app.worker.setContinuousSweep(
|
||||
self.continuous_sweep_radiobutton.isChecked()))
|
||||
self.averaged_sweep_radiobutton.toggled.connect(self.updateAveraging)
|
||||
self.averages.textEdited.connect(self.updateAveraging)
|
||||
self.truncates.textEdited.connect(self.updateAveraging)
|
||||
|
||||
layout.addWidget(settings_box)
|
||||
|
||||
band_sweep_box = QtWidgets.QGroupBox("Sweep band")
|
||||
band_sweep_layout = QtWidgets.QFormLayout(band_sweep_box)
|
||||
|
||||
self.band_list = QtWidgets.QComboBox()
|
||||
self.band_list.setModel(self.app.bands)
|
||||
self.band_list.currentIndexChanged.connect(self.updateCurrentBand)
|
||||
|
||||
band_sweep_layout.addRow("Select band", self.band_list)
|
||||
|
||||
self.band_pad_group = QtWidgets.QButtonGroup()
|
||||
self.band_pad_0 = QtWidgets.QRadioButton("None")
|
||||
self.band_pad_10 = QtWidgets.QRadioButton("10%")
|
||||
self.band_pad_25 = QtWidgets.QRadioButton("25%")
|
||||
self.band_pad_100 = QtWidgets.QRadioButton("100%")
|
||||
self.band_pad_0.setChecked(True)
|
||||
self.band_pad_group.addButton(self.band_pad_0)
|
||||
self.band_pad_group.addButton(self.band_pad_10)
|
||||
self.band_pad_group.addButton(self.band_pad_25)
|
||||
self.band_pad_group.addButton(self.band_pad_100)
|
||||
self.band_pad_group.buttonClicked.connect(self.updateCurrentBand)
|
||||
band_sweep_layout.addRow("Pad band limits", self.band_pad_0)
|
||||
band_sweep_layout.addRow("", self.band_pad_10)
|
||||
band_sweep_layout.addRow("", self.band_pad_25)
|
||||
band_sweep_layout.addRow("", self.band_pad_100)
|
||||
|
||||
self.band_limit_label = QtWidgets.QLabel()
|
||||
|
||||
band_sweep_layout.addRow(self.band_limit_label)
|
||||
|
||||
btn_set_band_sweep = QtWidgets.QPushButton("Set band sweep")
|
||||
btn_set_band_sweep.clicked.connect(self.setBandSweep)
|
||||
band_sweep_layout.addRow(btn_set_band_sweep)
|
||||
|
||||
self.updateCurrentBand()
|
||||
|
||||
layout.addWidget(band_sweep_box)
|
||||
|
||||
def updateCurrentBand(self):
|
||||
index_start = self.band_list.model().index(self.band_list.currentIndex(), 1)
|
||||
index_stop = self.band_list.model().index(self.band_list.currentIndex(), 2)
|
||||
start = int(self.band_list.model().data(index_start, QtCore.Qt.ItemDataRole).value())
|
||||
stop = int(self.band_list.model().data(index_stop, QtCore.Qt.ItemDataRole).value())
|
||||
|
||||
if self.band_pad_10.isChecked():
|
||||
padding = 10
|
||||
elif self.band_pad_25.isChecked():
|
||||
padding = 25
|
||||
elif self.band_pad_100.isChecked():
|
||||
padding = 100
|
||||
else:
|
||||
padding = 0
|
||||
|
||||
if padding > 0:
|
||||
span = stop - start
|
||||
start -= round(span * padding / 100)
|
||||
start = max(1, start)
|
||||
stop += round(span * padding / 100)
|
||||
|
||||
self.band_limit_label.setText(
|
||||
f"Sweep span: {RFTools.formatShortFrequency(start)}"
|
||||
f" to {RFTools.formatShortFrequency(stop)}")
|
||||
|
||||
def setBandSweep(self):
|
||||
index_start = self.band_list.model().index(self.band_list.currentIndex(), 1)
|
||||
index_stop = self.band_list.model().index(self.band_list.currentIndex(), 2)
|
||||
start = int(self.band_list.model().data(index_start, QtCore.Qt.ItemDataRole).value())
|
||||
stop = int(self.band_list.model().data(index_stop, QtCore.Qt.ItemDataRole).value())
|
||||
|
||||
if self.band_pad_10.isChecked():
|
||||
padding = 10
|
||||
elif self.band_pad_25.isChecked():
|
||||
padding = 25
|
||||
elif self.band_pad_100.isChecked():
|
||||
padding = 100
|
||||
else:
|
||||
padding = 0
|
||||
|
||||
if padding > 0:
|
||||
span = stop - start
|
||||
start -= round(span * padding / 100)
|
||||
start = max(1, start)
|
||||
stop += round(span * padding / 100)
|
||||
|
||||
self.app.sweepStartInput.setText(RFTools.formatSweepFrequency(start))
|
||||
self.app.sweepEndInput.setText(RFTools.formatSweepFrequency(stop))
|
||||
self.app.sweepEndInput.textEdited.emit(self.app.sweepEndInput.text())
|
||||
|
||||
def updateAveraging(self):
|
||||
self.app.worker.setAveraging(self.averaged_sweep_radiobutton.isChecked(),
|
||||
self.averages.text(),
|
||||
self.truncates.text())
|
|
@ -0,0 +1,152 @@
|
|||
# NanoVNASaver - a python program to view and export Touchstone data from a NanoVNA
|
||||
# Copyright (C) 2019. Rune B. Broberg
|
||||
#
|
||||
# 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 logging
|
||||
import math
|
||||
|
||||
import numpy as np
|
||||
import scipy.signal as signal
|
||||
from PyQt5 import QtWidgets, QtCore
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TDRWindow(QtWidgets.QWidget):
|
||||
updated = QtCore.pyqtSignal()
|
||||
|
||||
def __init__(self, app: QtWidgets.QWidget):
|
||||
super().__init__()
|
||||
self.app = app
|
||||
|
||||
self.td = []
|
||||
self.distance_axis = []
|
||||
self.step_response = []
|
||||
self.step_response_Z = []
|
||||
|
||||
self.setWindowTitle("TDR")
|
||||
self.setWindowIcon(self.app.icon)
|
||||
|
||||
QtWidgets.QShortcut(QtCore.Qt.Key_Escape, self, self.hide)
|
||||
|
||||
layout = QtWidgets.QFormLayout()
|
||||
self.setLayout(layout)
|
||||
|
||||
self.tdr_velocity_dropdown = QtWidgets.QComboBox()
|
||||
self.tdr_velocity_dropdown.addItem("Jelly filled (0.64)", 0.64)
|
||||
self.tdr_velocity_dropdown.addItem("Polyethylene (0.66)", 0.66)
|
||||
self.tdr_velocity_dropdown.addItem("PTFE (Teflon) (0.70)", 0.70)
|
||||
self.tdr_velocity_dropdown.addItem("Pulp Insulation (0.72)", 0.72)
|
||||
self.tdr_velocity_dropdown.addItem("Foam or Cellular PE (0.78)", 0.78)
|
||||
self.tdr_velocity_dropdown.addItem("Semi-solid PE (SSPE) (0.84)", 0.84)
|
||||
self.tdr_velocity_dropdown.addItem("Air (Helical spacers) (0.94)", 0.94)
|
||||
self.tdr_velocity_dropdown.insertSeparator(self.tdr_velocity_dropdown.count())
|
||||
# Lots of cable types added by Larry Goga, AE5CZ
|
||||
self.tdr_velocity_dropdown.addItem("RG-6/U PE 75\N{OHM SIGN} (Belden 8215) (0.66)", 0.66)
|
||||
self.tdr_velocity_dropdown.addItem("RG-6/U Foam 75\N{OHM SIGN} (Belden 9290) (0.81)", 0.81)
|
||||
self.tdr_velocity_dropdown.addItem("RG-8/U PE 50\N{OHM SIGN} (Belden 8237) (0.66)", 0.66)
|
||||
self.tdr_velocity_dropdown.addItem("RG-8/U Foam (Belden 8214) (0.78)", 0.78)
|
||||
self.tdr_velocity_dropdown.addItem("RG-8/U (Belden 9913) (0.84)", 0.84)
|
||||
self.tdr_velocity_dropdown.addItem("RG-8X (Belden 9258) (0.82)", 0.82)
|
||||
self.tdr_velocity_dropdown.addItem(
|
||||
"RG-11/U 75\N{OHM SIGN} Foam HDPE (Belden 9292) (0.84)", 0.84)
|
||||
self.tdr_velocity_dropdown.addItem("RG-58/U 52\N{OHM SIGN} PE (Belden 9201) (0.66)", 0.66)
|
||||
self.tdr_velocity_dropdown.addItem(
|
||||
"RG-58A/U 54\N{OHM SIGN} Foam (Belden 8219) (0.73)", 0.73)
|
||||
self.tdr_velocity_dropdown.addItem("RG-59A/U PE 75\N{OHM SIGN} (Belden 8241) (0.66)", 0.66)
|
||||
self.tdr_velocity_dropdown.addItem(
|
||||
"RG-59A/U Foam 75\N{OHM SIGN} (Belden 8241F) (0.78)", 0.78)
|
||||
self.tdr_velocity_dropdown.addItem("RG-174 PE (Belden 8216)(0.66)", 0.66)
|
||||
self.tdr_velocity_dropdown.addItem("RG-174 Foam (Belden 7805R) (0.735)", 0.735)
|
||||
self.tdr_velocity_dropdown.addItem("RG-213/U PE (Belden 8267) (0.66)", 0.66)
|
||||
self.tdr_velocity_dropdown.addItem("RG316 (0.695)", 0.695)
|
||||
self.tdr_velocity_dropdown.addItem("RG402 (0.695)", 0.695)
|
||||
self.tdr_velocity_dropdown.addItem("LMR-240 (0.84)", 0.84)
|
||||
self.tdr_velocity_dropdown.addItem("LMR-240UF (0.80)", 0.80)
|
||||
self.tdr_velocity_dropdown.addItem("LMR-400 (0.85)", 0.85)
|
||||
self.tdr_velocity_dropdown.addItem("LMR400UF (0.83)", 0.83)
|
||||
self.tdr_velocity_dropdown.addItem("Davis Bury-FLEX (0.82)", 0.82)
|
||||
self.tdr_velocity_dropdown.insertSeparator(self.tdr_velocity_dropdown.count())
|
||||
self.tdr_velocity_dropdown.addItem("Custom", -1)
|
||||
|
||||
self.tdr_velocity_dropdown.setCurrentIndex(1) # Default to PE (0.66)
|
||||
|
||||
self.tdr_velocity_dropdown.currentIndexChanged.connect(self.updateTDR)
|
||||
|
||||
layout.addRow(self.tdr_velocity_dropdown)
|
||||
|
||||
self.tdr_velocity_input = QtWidgets.QLineEdit()
|
||||
self.tdr_velocity_input.setDisabled(True)
|
||||
self.tdr_velocity_input.setText("0.66")
|
||||
self.tdr_velocity_input.textChanged.connect(self.app.dataUpdated)
|
||||
|
||||
layout.addRow("Velocity factor", self.tdr_velocity_input)
|
||||
|
||||
self.tdr_result_label = QtWidgets.QLabel()
|
||||
layout.addRow("Estimated cable length:", self.tdr_result_label)
|
||||
|
||||
layout.addRow(self.app.tdr_chart)
|
||||
|
||||
def updateTDR(self):
|
||||
c = 299792458
|
||||
# TODO: Let the user select whether to use high or low resolution TDR?
|
||||
FFT_POINTS = 2**14
|
||||
|
||||
if len(self.app.data) < 2:
|
||||
return
|
||||
|
||||
if self.tdr_velocity_dropdown.currentData() == -1:
|
||||
self.tdr_velocity_input.setDisabled(False)
|
||||
else:
|
||||
self.tdr_velocity_input.setDisabled(True)
|
||||
self.tdr_velocity_input.setText(str(self.tdr_velocity_dropdown.currentData()))
|
||||
|
||||
try:
|
||||
v = float(self.tdr_velocity_input.text())
|
||||
except ValueError:
|
||||
return
|
||||
|
||||
step_size = self.app.data[1].freq - self.app.data[0].freq
|
||||
if step_size == 0:
|
||||
self.tdr_result_label.setText("")
|
||||
logger.info("Cannot compute cable length at 0 span")
|
||||
return
|
||||
|
||||
s11 = []
|
||||
for d in self.app.data:
|
||||
s11.append(np.complex(d.re, d.im))
|
||||
|
||||
window = np.blackman(len(self.app.data))
|
||||
|
||||
windowed_s11 = window * s11
|
||||
self.td = np.abs(np.fft.ifft(windowed_s11, FFT_POINTS))
|
||||
step = np.ones(FFT_POINTS)
|
||||
self.step_response = signal.convolve(self.td, step)
|
||||
|
||||
self.step_response_Z = 50 * (1 + self.step_response) / (1 - self.step_response)
|
||||
|
||||
time_axis = np.linspace(0, 1/step_size, FFT_POINTS)
|
||||
self.distance_axis = time_axis * v * c
|
||||
# peak = np.max(td)
|
||||
# We should check that this is an actual *peak*, and not just a vague maximum
|
||||
index_peak = np.argmax(self.td)
|
||||
|
||||
cable_len = round(self.distance_axis[index_peak]/2, 3)
|
||||
feet = math.floor(cable_len / 0.3048)
|
||||
inches = round(((cable_len / 0.3048) - feet)*12, 1)
|
||||
|
||||
self.tdr_result_label.setText(f"{cable_len}m ({feet}ft {inches}in)")
|
||||
self.app.tdr_result_label.setText(str(cable_len) + " m")
|
||||
self.updated.emit()
|
|
@ -0,0 +1,189 @@
|
|||
# NanoVNASaver
|
||||
# A python program to view and export Touchstone data from a NanoVNA
|
||||
# Copyright (C) 2019. Rune B. Broberg
|
||||
#
|
||||
# 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 logging
|
||||
import json
|
||||
from time import strftime, localtime
|
||||
from urllib import request, error
|
||||
|
||||
from PyQt5 import QtWidgets, QtCore
|
||||
|
||||
from NanoVNASaver.Hardware import Version
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class AboutWindow(QtWidgets.QWidget):
|
||||
def __init__(self, app: QtWidgets.QWidget):
|
||||
super().__init__()
|
||||
self.app = app
|
||||
|
||||
self.setWindowTitle("About NanoVNASaver")
|
||||
self.setWindowIcon(self.app.icon)
|
||||
top_layout = QtWidgets.QHBoxLayout()
|
||||
self.setLayout(top_layout)
|
||||
QtWidgets.QShortcut(QtCore.Qt.Key_Escape, self, self.hide)
|
||||
|
||||
icon_layout = QtWidgets.QVBoxLayout()
|
||||
top_layout.addLayout(icon_layout)
|
||||
icon = QtWidgets.QLabel()
|
||||
icon.setPixmap(self.app.icon.pixmap(128, 128))
|
||||
icon_layout.addWidget(icon)
|
||||
icon_layout.addStretch()
|
||||
|
||||
layout = QtWidgets.QVBoxLayout()
|
||||
top_layout.addLayout(layout)
|
||||
|
||||
layout.addWidget(QtWidgets.QLabel(
|
||||
f"NanoVNASaver version {self.app.version}"))
|
||||
layout.addWidget(QtWidgets.QLabel(""))
|
||||
layout.addWidget(QtWidgets.QLabel(
|
||||
"\N{COPYRIGHT SIGN} Copyright 2019 Rune B. Broberg"))
|
||||
layout.addWidget(QtWidgets.QLabel(
|
||||
"This program comes with ABSOLUTELY NO WARRANTY"))
|
||||
layout.addWidget(QtWidgets.QLabel(
|
||||
"This program is licensed under the GNU General Public License version 3"))
|
||||
layout.addWidget(QtWidgets.QLabel(""))
|
||||
link_label = QtWidgets.QLabel(
|
||||
"For further details, see: <a href=\"https://mihtjel.github.io/nanovna-saver/\">"
|
||||
"https://mihtjel.github.io/nanovna-saver/</a>")
|
||||
link_label.setOpenExternalLinks(True)
|
||||
layout.addWidget(link_label)
|
||||
layout.addWidget(QtWidgets.QLabel(""))
|
||||
|
||||
self.versionLabel = QtWidgets.QLabel("NanoVNA Firmware Version: Not connected.")
|
||||
layout.addWidget(self.versionLabel)
|
||||
|
||||
layout.addStretch()
|
||||
|
||||
btn_check_version = QtWidgets.QPushButton("Check for updates")
|
||||
btn_check_version.clicked.connect(self.findUpdates)
|
||||
|
||||
self.updateLabel = QtWidgets.QLabel("Last checked: ")
|
||||
self.updateCheckBox = QtWidgets.QCheckBox("Check for updates on startup")
|
||||
|
||||
self.updateCheckBox.toggled.connect(self.updateSettings)
|
||||
|
||||
check_for_updates = self.app.settings.value("CheckForUpdates", "Ask")
|
||||
if check_for_updates == "Yes":
|
||||
self.updateCheckBox.setChecked(True)
|
||||
self.findUpdates(automatic=True)
|
||||
elif check_for_updates == "No":
|
||||
self.updateCheckBox.setChecked(False)
|
||||
else:
|
||||
logger.debug("Starting timer")
|
||||
QtCore.QTimer.singleShot(2000, self.askAboutUpdates)
|
||||
|
||||
update_hbox = QtWidgets.QHBoxLayout()
|
||||
update_hbox.addWidget(btn_check_version)
|
||||
update_form = QtWidgets.QFormLayout()
|
||||
update_hbox.addLayout(update_form)
|
||||
update_hbox.addStretch()
|
||||
update_form.addRow(self.updateLabel)
|
||||
update_form.addRow(self.updateCheckBox)
|
||||
layout.addLayout(update_hbox)
|
||||
|
||||
layout.addStretch()
|
||||
|
||||
btn_ok = QtWidgets.QPushButton("Ok")
|
||||
btn_ok.clicked.connect(lambda: self.close()) # noqa
|
||||
layout.addWidget(btn_ok)
|
||||
|
||||
def show(self):
|
||||
super().show()
|
||||
self.updateLabels()
|
||||
|
||||
def updateLabels(self):
|
||||
if self.app.vna.isValid():
|
||||
logger.debug("Valid VNA")
|
||||
v: Version = self.app.vna.version
|
||||
self.versionLabel.setText(
|
||||
f"NanoVNA Firmware Version: {self.app.vna.name}"
|
||||
f"{v.version_string}")
|
||||
|
||||
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):
|
||||
update_url = "http://mihtjel.dk/nanovna-saver/latest.json"
|
||||
|
||||
try:
|
||||
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:
|
||||
logger.exception("Checking for updates produced an HTTP exception: %s", e)
|
||||
self.updateLabel.setText("Connection error.")
|
||||
return
|
||||
except json.JSONDecodeError as e:
|
||||
logger.exception("Checking for updates provided an unparseable file: %s", e)
|
||||
self.updateLabel.setText("Data error reading versions.")
|
||||
return
|
||||
except error.URLError as e:
|
||||
logger.exception("Checking for updates produced a URL exception: %s", e)
|
||||
self.updateLabel.setText("Connection error.")
|
||||
return
|
||||
|
||||
logger.info("Latest version is %s", latest_version.version_string)
|
||||
this_version = Version(self.app.version)
|
||||
logger.info("This is %s", this_version)
|
||||
if latest_version > this_version:
|
||||
logger.info("New update available: %s!", latest_version)
|
||||
if automatic:
|
||||
QtWidgets.QMessageBox.information(
|
||||
self,
|
||||
"Updates available",
|
||||
"There is a new update for NanoVNA-Saver available!\n" +
|
||||
"Version " + latest_version.version_string + "\n\n" +
|
||||
"Press \"About\" to find the update.")
|
||||
else:
|
||||
QtWidgets.QMessageBox.information(
|
||||
self, "Updates available",
|
||||
"There is a new update for NanoVNA-Saver available!")
|
||||
self.updateLabel.setText(
|
||||
f'<a href="{latest_url}">New version available</a>.')
|
||||
self.updateLabel.setOpenExternalLinks(True)
|
||||
else:
|
||||
# Probably don't show a message box, just update the screen?
|
||||
# Maybe consider showing it if not an automatic update.
|
||||
#
|
||||
self.updateLabel.setText(
|
||||
f"Last checked: {strftime('%Y-%m-%d %H:%M:%S', localtime())}")
|
||||
return
|
|
@ -0,0 +1,100 @@
|
|||
# NanoVNASaver
|
||||
# A python program to view and export Touchstone data from a NanoVNA
|
||||
# Copyright (C) 2019. Rune B. Broberg
|
||||
#
|
||||
# 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 logging
|
||||
|
||||
from PyQt5 import QtWidgets, QtCore
|
||||
|
||||
from NanoVNASaver.Analysis import Analysis, LowPassAnalysis, HighPassAnalysis, \
|
||||
BandPassAnalysis, BandStopAnalysis, VSWRAnalysis, \
|
||||
SimplePeakSearchAnalysis
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AnalysisWindow(QtWidgets.QWidget):
|
||||
analyses = []
|
||||
analysis: Analysis = None
|
||||
|
||||
def __init__(self, app: QtWidgets.QWidget):
|
||||
super().__init__()
|
||||
|
||||
self.app = app
|
||||
self.setWindowTitle("Sweep analysis")
|
||||
self.setWindowIcon(self.app.icon)
|
||||
|
||||
QtWidgets.QShortcut(QtCore.Qt.Key_Escape, self, self.hide)
|
||||
|
||||
layout = QtWidgets.QVBoxLayout()
|
||||
self.setLayout(layout)
|
||||
|
||||
select_analysis_box = QtWidgets.QGroupBox("Select analysis")
|
||||
select_analysis_layout = QtWidgets.QFormLayout(select_analysis_box)
|
||||
self.analysis_list = QtWidgets.QComboBox()
|
||||
self.analysis_list.addItem("Low-pass filter", LowPassAnalysis(self.app))
|
||||
self.analysis_list.addItem("Band-pass filter", BandPassAnalysis(self.app))
|
||||
self.analysis_list.addItem("High-pass filter", HighPassAnalysis(self.app))
|
||||
self.analysis_list.addItem("Band-stop filter", BandStopAnalysis(self.app))
|
||||
# self.analysis_list.addItem("Peak search", PeakSearchAnalysis(self.app))
|
||||
self.analysis_list.addItem("Peak search", SimplePeakSearchAnalysis(self.app))
|
||||
self.analysis_list.addItem("VSWR analysis", VSWRAnalysis(self.app))
|
||||
select_analysis_layout.addRow("Analysis type", self.analysis_list)
|
||||
self.analysis_list.currentIndexChanged.connect(self.updateSelection)
|
||||
|
||||
btn_run_analysis = QtWidgets.QPushButton("Run analysis")
|
||||
btn_run_analysis.clicked.connect(self.runAnalysis)
|
||||
select_analysis_layout.addRow(btn_run_analysis)
|
||||
|
||||
self.checkbox_run_automatically = QtWidgets.QCheckBox("Run automatically")
|
||||
self.checkbox_run_automatically.stateChanged.connect(self.toggleAutomaticRun)
|
||||
select_analysis_layout.addRow(self.checkbox_run_automatically)
|
||||
|
||||
analysis_box = QtWidgets.QGroupBox("Analysis")
|
||||
analysis_box.setSizePolicy(
|
||||
QtWidgets.QSizePolicy.MinimumExpanding,
|
||||
QtWidgets.QSizePolicy.MinimumExpanding)
|
||||
|
||||
self.analysis_layout = QtWidgets.QVBoxLayout(analysis_box)
|
||||
self.analysis_layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
layout.addWidget(select_analysis_box)
|
||||
layout.addWidget(analysis_box)
|
||||
|
||||
self.updateSelection()
|
||||
|
||||
def runAnalysis(self):
|
||||
if self.analysis is not None:
|
||||
self.analysis.runAnalysis()
|
||||
|
||||
def updateSelection(self):
|
||||
self.analysis = self.analysis_list.currentData()
|
||||
old_item = self.analysis_layout.itemAt(0)
|
||||
if old_item is not None:
|
||||
old_widget = self.analysis_layout.itemAt(0).widget()
|
||||
self.analysis_layout.replaceWidget(old_widget, self.analysis.widget())
|
||||
old_widget.hide()
|
||||
else:
|
||||
self.analysis_layout.addWidget(self.analysis.widget())
|
||||
self.analysis.widget().show()
|
||||
self.update()
|
||||
|
||||
def toggleAutomaticRun(self, state: QtCore.Qt.CheckState):
|
||||
if state == QtCore.Qt.Checked:
|
||||
self.analysis_list.setDisabled(True)
|
||||
self.app.dataAvailable.connect(self.runAnalysis)
|
||||
else:
|
||||
self.analysis_list.setDisabled(False)
|
||||
self.app.dataAvailable.disconnect(self.runAnalysis)
|
|
@ -0,0 +1,69 @@
|
|||
# NanoVNASaver
|
||||
# A python program to view and export Touchstone data from a NanoVNA
|
||||
# Copyright (C) 2019. Rune B. Broberg
|
||||
#
|
||||
# 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 logging
|
||||
|
||||
from PyQt5 import QtWidgets, QtCore
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BandsWindow(QtWidgets.QWidget):
|
||||
def __init__(self, app: QtWidgets.QWidget):
|
||||
super().__init__()
|
||||
|
||||
self.app = app
|
||||
self.setWindowTitle("Manage bands")
|
||||
self.setWindowIcon(self.app.icon)
|
||||
|
||||
QtWidgets.QShortcut(QtCore.Qt.Key_Escape, self, self.hide)
|
||||
|
||||
layout = QtWidgets.QVBoxLayout()
|
||||
self.setLayout(layout)
|
||||
self.setMinimumSize(500, 300)
|
||||
|
||||
self.bands_table = QtWidgets.QTableView()
|
||||
self.bands_table.setModel(self.app.bands)
|
||||
self.bands_table.horizontalHeader().setStretchLastSection(True)
|
||||
|
||||
layout.addWidget(self.bands_table)
|
||||
|
||||
btn_add_row = QtWidgets.QPushButton("Add row")
|
||||
btn_delete_row = QtWidgets.QPushButton("Delete row")
|
||||
btn_reset_bands = QtWidgets.QPushButton("Reset bands")
|
||||
btn_layout = QtWidgets.QHBoxLayout()
|
||||
btn_layout.addWidget(btn_add_row)
|
||||
btn_layout.addWidget(btn_delete_row)
|
||||
btn_layout.addWidget(btn_reset_bands)
|
||||
layout.addLayout(btn_layout)
|
||||
|
||||
btn_add_row.clicked.connect(self.app.bands.addRow)
|
||||
btn_delete_row.clicked.connect(self.deleteRows)
|
||||
btn_reset_bands.clicked.connect(self.resetBands)
|
||||
|
||||
def deleteRows(self):
|
||||
rows = self.bands_table.selectedIndexes()
|
||||
for row in rows:
|
||||
self.app.bands.removeRow(row.row())
|
||||
|
||||
def resetBands(self):
|
||||
confirm = QtWidgets.QMessageBox(
|
||||
QtWidgets.QMessageBox.Warning,
|
||||
"Confirm reset",
|
||||
"Are you sure you want to reset the bands to default?",
|
||||
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.Cancel).exec()
|
||||
if confirm == QtWidgets.QMessageBox.Yes:
|
||||
self.app.bands.resetBands()
|
|
@ -0,0 +1,115 @@
|
|||
# NanoVNASaver
|
||||
# A python program to view and export Touchstone data from a NanoVNA
|
||||
# Copyright (C) 2019. Rune B. Broberg
|
||||
#
|
||||
# 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 logging
|
||||
|
||||
from PyQt5 import QtWidgets, QtCore
|
||||
|
||||
from NanoVNASaver.Windows.Screenshot import ScreenshotWindow
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class DeviceSettingsWindow(QtWidgets.QWidget):
|
||||
def __init__(self, app: QtWidgets.QWidget):
|
||||
super().__init__()
|
||||
|
||||
self.app = app
|
||||
self.setWindowTitle("Device settings")
|
||||
self.setWindowIcon(self.app.icon)
|
||||
|
||||
QtWidgets.QShortcut(QtCore.Qt.Key_Escape, self, self.hide)
|
||||
|
||||
layout = QtWidgets.QVBoxLayout()
|
||||
self.setLayout(layout)
|
||||
|
||||
status_box = QtWidgets.QGroupBox("Status")
|
||||
status_layout = QtWidgets.QFormLayout(status_box)
|
||||
self.statusLabel = QtWidgets.QLabel("Not connected.")
|
||||
status_layout.addRow("Status:", self.statusLabel)
|
||||
|
||||
self.calibrationStatusLabel = QtWidgets.QLabel("Not connected.")
|
||||
status_layout.addRow("Calibration:", self.calibrationStatusLabel)
|
||||
|
||||
status_layout.addRow(QtWidgets.QLabel("Features:"))
|
||||
self.featureList = QtWidgets.QListWidget()
|
||||
status_layout.addRow(self.featureList)
|
||||
|
||||
settings_box = QtWidgets.QGroupBox("Settings")
|
||||
settings_layout = QtWidgets.QFormLayout(settings_box)
|
||||
|
||||
self.chkValidateInputData = QtWidgets.QCheckBox("Validate received data")
|
||||
validate_input = self.app.settings.value("SerialInputValidation", True, bool)
|
||||
self.chkValidateInputData.setChecked(validate_input)
|
||||
self.chkValidateInputData.stateChanged.connect(self.updateValidation)
|
||||
settings_layout.addRow("Validation", self.chkValidateInputData)
|
||||
|
||||
control_layout = QtWidgets.QHBoxLayout()
|
||||
self.btnRefresh = QtWidgets.QPushButton("Refresh")
|
||||
self.btnRefresh.clicked.connect(self.updateFields)
|
||||
control_layout.addWidget(self.btnRefresh)
|
||||
|
||||
self.screenshotWindow = ScreenshotWindow()
|
||||
self.btnCaptureScreenshot = QtWidgets.QPushButton("Screenshot")
|
||||
self.btnCaptureScreenshot.clicked.connect(self.captureScreenshot)
|
||||
control_layout.addWidget(self.btnCaptureScreenshot)
|
||||
|
||||
layout.addWidget(status_box)
|
||||
layout.addWidget(settings_box)
|
||||
layout.addLayout(control_layout)
|
||||
|
||||
def show(self):
|
||||
super().show()
|
||||
self.updateFields()
|
||||
|
||||
def updateFields(self):
|
||||
if self.app.vna.isValid():
|
||||
self.statusLabel.setText("Connected to " + self.app.vna.name + ".")
|
||||
if self.app.worker.running:
|
||||
self.calibrationStatusLabel.setText("(Sweep running)")
|
||||
else:
|
||||
self.calibrationStatusLabel.setText(self.app.vna.getCalibration())
|
||||
|
||||
self.featureList.clear()
|
||||
self.featureList.addItem(self.app.vna.name + " v" + str(self.app.vna.version))
|
||||
features = self.app.vna.getFeatures()
|
||||
for item in features:
|
||||
self.featureList.addItem(item)
|
||||
|
||||
if "Screenshots" in features:
|
||||
self.btnCaptureScreenshot.setDisabled(False)
|
||||
else:
|
||||
self.btnCaptureScreenshot.setDisabled(True)
|
||||
else:
|
||||
self.statusLabel.setText("Not connected.")
|
||||
self.calibrationStatusLabel.setText("Not connected.")
|
||||
self.featureList.clear()
|
||||
self.featureList.addItem("Not connected.")
|
||||
self.btnCaptureScreenshot.setDisabled(True)
|
||||
|
||||
def updateValidation(self, validate_data: bool):
|
||||
self.app.vna.validateInput = validate_data
|
||||
self.app.settings.setValue("SerialInputValidation", validate_data)
|
||||
|
||||
def captureScreenshot(self):
|
||||
if not self.app.worker.running:
|
||||
pixmap = self.app.vna.getScreenshot()
|
||||
self.screenshotWindow.setScreenshot(pixmap)
|
||||
self.screenshotWindow.show()
|
||||
else:
|
||||
# TODO: Tell the user no screenshots while sweep is running?
|
||||
# TODO: Consider having a list of widgets that want to be
|
||||
# disabled when a sweep is running?
|
||||
pass
|
|
@ -0,0 +1,741 @@
|
|||
# NanoVNASaver - a python program to view and export Touchstone data from a NanoVNA
|
||||
# Copyright (C) 2019. Rune B. Broberg
|
||||
#
|
||||
# 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 logging
|
||||
from typing import List
|
||||
|
||||
from PyQt5 import QtWidgets, QtCore, QtGui
|
||||
|
||||
from NanoVNASaver.Windows.Bands import BandsWindow
|
||||
from NanoVNASaver.Windows.MarkerSettings import MarkerSettingsWindow
|
||||
from NanoVNASaver.Marker import Marker
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class DisplaySettingsWindow(QtWidgets.QWidget):
|
||||
def __init__(self, app: QtWidgets.QWidget):
|
||||
super().__init__()
|
||||
|
||||
self.app = app
|
||||
self.setWindowTitle("Display settings")
|
||||
self.setWindowIcon(self.app.icon)
|
||||
|
||||
self.marker_window = MarkerSettingsWindow(self.app)
|
||||
|
||||
QtWidgets.QShortcut(QtCore.Qt.Key_Escape, self, self.hide)
|
||||
|
||||
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)
|
||||
|
||||
self.returnloss_group = QtWidgets.QButtonGroup()
|
||||
self.returnloss_is_negative = QtWidgets.QRadioButton("Negative")
|
||||
self.returnloss_is_positive = QtWidgets.QRadioButton("Positive")
|
||||
self.returnloss_group.addButton(self.returnloss_is_positive)
|
||||
self.returnloss_group.addButton(self.returnloss_is_negative)
|
||||
|
||||
display_options_layout.addRow("Return loss is:", self.returnloss_is_negative)
|
||||
display_options_layout.addRow("", self.returnloss_is_positive)
|
||||
|
||||
if self.app.settings.value("ReturnLossPositive", False, bool):
|
||||
self.returnloss_is_positive.setChecked(True)
|
||||
else:
|
||||
self.returnloss_is_negative.setChecked(True)
|
||||
|
||||
self.returnloss_is_positive.toggled.connect(self.changeReturnLoss)
|
||||
self.changeReturnLoss()
|
||||
|
||||
self.show_lines_option = QtWidgets.QCheckBox("Show lines")
|
||||
show_lines_label = QtWidgets.QLabel("Displays a thin line between data points")
|
||||
self.show_lines_option.stateChanged.connect(self.changeShowLines)
|
||||
display_options_layout.addRow(self.show_lines_option, show_lines_label)
|
||||
|
||||
self.dark_mode_option = QtWidgets.QCheckBox("Dark mode")
|
||||
dark_mode_label = QtWidgets.QLabel("Black background with white text")
|
||||
self.dark_mode_option.stateChanged.connect(self.changeDarkMode)
|
||||
display_options_layout.addRow(self.dark_mode_option, dark_mode_label)
|
||||
|
||||
self.btnColorPicker = QtWidgets.QPushButton("█")
|
||||
self.btnColorPicker.setFixedWidth(20)
|
||||
self.sweepColor = self.app.settings.value("SweepColor", defaultValue=QtGui.QColor(160, 140, 20, 128),
|
||||
type=QtGui.QColor)
|
||||
self.setSweepColor(self.sweepColor)
|
||||
self.btnColorPicker.clicked.connect(lambda: self.setSweepColor(
|
||||
QtWidgets.QColorDialog.getColor(self.sweepColor, options=QtWidgets.QColorDialog.ShowAlphaChannel)))
|
||||
|
||||
display_options_layout.addRow("Sweep color", self.btnColorPicker)
|
||||
|
||||
self.btnSecondaryColorPicker = QtWidgets.QPushButton("█")
|
||||
self.btnSecondaryColorPicker.setFixedWidth(20)
|
||||
self.secondarySweepColor = self.app.settings.value("SecondarySweepColor",
|
||||
defaultValue=QtGui.QColor(20, 160, 140, 128),
|
||||
type=QtGui.QColor)
|
||||
self.setSecondarySweepColor(self.secondarySweepColor)
|
||||
self.btnSecondaryColorPicker.clicked.connect(lambda: self.setSecondarySweepColor(
|
||||
QtWidgets.QColorDialog.getColor(self.secondarySweepColor,
|
||||
options=QtWidgets.QColorDialog.ShowAlphaChannel)))
|
||||
|
||||
display_options_layout.addRow("Second sweep color", self.btnSecondaryColorPicker)
|
||||
|
||||
self.btnReferenceColorPicker = QtWidgets.QPushButton("█")
|
||||
self.btnReferenceColorPicker.setFixedWidth(20)
|
||||
self.referenceColor = self.app.settings.value("ReferenceColor", defaultValue=QtGui.QColor(0, 0, 255, 48),
|
||||
type=QtGui.QColor)
|
||||
self.setReferenceColor(self.referenceColor)
|
||||
self.btnReferenceColorPicker.clicked.connect(lambda: self.setReferenceColor(
|
||||
QtWidgets.QColorDialog.getColor(self.referenceColor, options=QtWidgets.QColorDialog.ShowAlphaChannel)))
|
||||
|
||||
display_options_layout.addRow("Reference color", self.btnReferenceColorPicker)
|
||||
|
||||
self.btnSecondaryReferenceColorPicker = QtWidgets.QPushButton("█")
|
||||
self.btnSecondaryReferenceColorPicker.setFixedWidth(20)
|
||||
self.secondaryReferenceColor = self.app.settings.value("SecondaryReferenceColor",
|
||||
defaultValue=QtGui.QColor(0, 0, 255, 48),
|
||||
type=QtGui.QColor)
|
||||
self.setSecondaryReferenceColor(self.secondaryReferenceColor)
|
||||
self.btnSecondaryReferenceColorPicker.clicked.connect(lambda: self.setSecondaryReferenceColor(
|
||||
QtWidgets.QColorDialog.getColor(self.secondaryReferenceColor,
|
||||
options=QtWidgets.QColorDialog.ShowAlphaChannel)))
|
||||
|
||||
display_options_layout.addRow("Second reference color", self.btnSecondaryReferenceColorPicker)
|
||||
|
||||
self.pointSizeInput = QtWidgets.QSpinBox()
|
||||
pointsize = self.app.settings.value("PointSize", 2, int)
|
||||
self.pointSizeInput.setValue(pointsize)
|
||||
self.changePointSize(pointsize)
|
||||
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()
|
||||
linethickness = self.app.settings.value("LineThickness", 1, int)
|
||||
self.lineThicknessInput.setValue(linethickness)
|
||||
self.changeLineThickness(linethickness)
|
||||
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)
|
||||
|
||||
self.markerSizeInput = QtWidgets.QSpinBox()
|
||||
markersize = self.app.settings.value("MarkerSize", 6, int)
|
||||
self.markerSizeInput.setValue(markersize)
|
||||
self.changeMarkerSize(markersize)
|
||||
self.markerSizeInput.setMinimum(4)
|
||||
self.markerSizeInput.setMaximum(20)
|
||||
self.markerSizeInput.setSingleStep(2)
|
||||
self.markerSizeInput.setSuffix(" px")
|
||||
self.markerSizeInput.setAlignment(QtCore.Qt.AlignRight)
|
||||
self.markerSizeInput.valueChanged.connect(self.changeMarkerSize)
|
||||
self.markerSizeInput.editingFinished.connect(self.validateMarkerSize)
|
||||
display_options_layout.addRow("Marker size", self.markerSizeInput)
|
||||
|
||||
self.show_marker_number_option = QtWidgets.QCheckBox("Show marker numbers")
|
||||
show_marker_number_label = QtWidgets.QLabel("Displays the marker number next to the marker")
|
||||
self.show_marker_number_option.stateChanged.connect(self.changeShowMarkerNumber)
|
||||
display_options_layout.addRow(self.show_marker_number_option, show_marker_number_label)
|
||||
|
||||
self.filled_marker_option = QtWidgets.QCheckBox("Filled markers")
|
||||
filled_marker_label = QtWidgets.QLabel("Shows the marker as a filled triangle")
|
||||
self.filled_marker_option.stateChanged.connect(self.changeFilledMarkers)
|
||||
display_options_layout.addRow(self.filled_marker_option, filled_marker_label)
|
||||
|
||||
self.marker_tip_group = QtWidgets.QButtonGroup()
|
||||
self.marker_at_center = QtWidgets.QRadioButton("At the center of the marker")
|
||||
self.marker_at_tip = QtWidgets.QRadioButton("At the tip of the marker")
|
||||
self.marker_tip_group.addButton(self.marker_at_center)
|
||||
self.marker_tip_group.addButton(self.marker_at_tip)
|
||||
|
||||
display_options_layout.addRow("Data point is:", self.marker_at_center)
|
||||
display_options_layout.addRow("", self.marker_at_tip)
|
||||
|
||||
if self.app.settings.value("MarkerAtTip", False, bool):
|
||||
self.marker_at_tip.setChecked(True)
|
||||
else:
|
||||
self.marker_at_center.setChecked(True)
|
||||
|
||||
self.marker_at_tip.toggled.connect(self.changeMarkerAtTip)
|
||||
self.changeMarkerAtTip()
|
||||
|
||||
color_options_box = QtWidgets.QGroupBox("Chart colors")
|
||||
color_options_layout = QtWidgets.QFormLayout(color_options_box)
|
||||
|
||||
self.use_custom_colors = QtWidgets.QCheckBox("Use custom chart colors")
|
||||
self.use_custom_colors.stateChanged.connect(self.changeCustomColors)
|
||||
color_options_layout.addRow(self.use_custom_colors)
|
||||
|
||||
self.btn_background_picker = QtWidgets.QPushButton("█")
|
||||
self.btn_background_picker.setFixedWidth(20)
|
||||
self.btn_background_picker.clicked.connect(
|
||||
lambda: self.setColor(
|
||||
"background",
|
||||
QtWidgets.QColorDialog.getColor(
|
||||
self.backgroundColor,
|
||||
options=QtWidgets.QColorDialog.ShowAlphaChannel)))
|
||||
|
||||
color_options_layout.addRow(
|
||||
"Chart background", self.btn_background_picker)
|
||||
|
||||
self.btn_foreground_picker = QtWidgets.QPushButton("█")
|
||||
self.btn_foreground_picker.setFixedWidth(20)
|
||||
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)
|
||||
|
||||
right_layout = QtWidgets.QVBoxLayout()
|
||||
layout.addLayout(right_layout)
|
||||
|
||||
font_options_box = QtWidgets.QGroupBox("Font")
|
||||
font_options_layout = QtWidgets.QFormLayout(font_options_box)
|
||||
self.font_dropdown = QtWidgets.QComboBox()
|
||||
self.font_dropdown.addItems(["7", "8", "9", "10", "11", "12"])
|
||||
font_size = self.app.settings.value("FontSize",
|
||||
defaultValue="8",
|
||||
type=str)
|
||||
self.font_dropdown.setCurrentText(font_size)
|
||||
self.changeFont()
|
||||
|
||||
self.font_dropdown.currentTextChanged.connect(self.changeFont)
|
||||
font_options_layout.addRow("Font size", self.font_dropdown)
|
||||
|
||||
bands_box = QtWidgets.QGroupBox("Bands")
|
||||
bands_layout = QtWidgets.QFormLayout(bands_box)
|
||||
|
||||
self.show_bands = QtWidgets.QCheckBox("Show bands")
|
||||
self.show_bands.setChecked(self.app.bands.enabled)
|
||||
self.show_bands.stateChanged.connect(lambda: self.setShowBands(self.show_bands.isChecked()))
|
||||
bands_layout.addRow(self.show_bands)
|
||||
|
||||
self.btn_bands_picker = QtWidgets.QPushButton("█")
|
||||
self.btn_bands_picker.setFixedWidth(20)
|
||||
self.btn_bands_picker.clicked.connect(
|
||||
lambda: self.setColor(
|
||||
"bands",
|
||||
QtWidgets.QColorDialog.getColor(
|
||||
self.bandsColor,
|
||||
options=QtWidgets.QColorDialog.ShowAlphaChannel)))
|
||||
|
||||
bands_layout.addRow("Chart bands", self.btn_bands_picker)
|
||||
|
||||
self.btn_manage_bands = QtWidgets.QPushButton("Manage bands")
|
||||
|
||||
self.bandsWindow = BandsWindow(self.app)
|
||||
self.btn_manage_bands.clicked.connect(self.displayBandsWindow)
|
||||
|
||||
bands_layout.addRow(self.btn_manage_bands)
|
||||
|
||||
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_vswr_marker = QtWidgets.QPushButton("Add ...")
|
||||
btn_remove_vswr_marker = QtWidgets.QPushButton("Remove")
|
||||
vswr_marker_btn_layout = QtWidgets.QHBoxLayout()
|
||||
vswr_marker_btn_layout.addWidget(btn_add_vswr_marker)
|
||||
vswr_marker_btn_layout.addWidget(btn_remove_vswr_marker)
|
||||
vswr_marker_layout.addRow(vswr_marker_btn_layout)
|
||||
|
||||
btn_add_vswr_marker.clicked.connect(self.addVSWRMarker)
|
||||
btn_remove_vswr_marker.clicked.connect(self.removeVSWRMarker)
|
||||
|
||||
markers_box = QtWidgets.QGroupBox("Markers")
|
||||
markers_layout = QtWidgets.QFormLayout(markers_box)
|
||||
|
||||
btn_add_marker = QtWidgets.QPushButton("Add")
|
||||
btn_add_marker.clicked.connect(self.addMarker)
|
||||
self.btn_remove_marker = QtWidgets.QPushButton("Remove")
|
||||
self.btn_remove_marker.clicked.connect(self.removeMarker)
|
||||
btn_marker_settings = QtWidgets.QPushButton("Settings ...")
|
||||
btn_marker_settings.clicked.connect(self.displayMarkerWindow)
|
||||
|
||||
marker_btn_layout = QtWidgets.QHBoxLayout()
|
||||
marker_btn_layout.addWidget(btn_add_marker)
|
||||
marker_btn_layout.addWidget(self.btn_remove_marker)
|
||||
marker_btn_layout.addWidget(btn_marker_settings)
|
||||
|
||||
markers_layout.addRow(marker_btn_layout)
|
||||
|
||||
charts_box = QtWidgets.QGroupBox("Displayed charts")
|
||||
charts_layout = QtWidgets.QGridLayout(charts_box)
|
||||
|
||||
# selections = ["S11 Smith chart",
|
||||
# "S11 LogMag",
|
||||
# "S11 VSWR",
|
||||
# "S11 Phase",
|
||||
# "S21 Smith chart",
|
||||
# "S21 LogMag",
|
||||
# "S21 Phase",
|
||||
# "None"]
|
||||
|
||||
selections = []
|
||||
|
||||
for c in self.app.selectable_charts:
|
||||
selections.append(c.name)
|
||||
|
||||
selections.append("None")
|
||||
chart00_selection = QtWidgets.QComboBox()
|
||||
chart00_selection.addItems(selections)
|
||||
chart00 = self.app.settings.value("Chart00", "S11 Smith Chart")
|
||||
if chart00_selection.findText(chart00) > -1:
|
||||
chart00_selection.setCurrentText(chart00)
|
||||
else:
|
||||
chart00_selection.setCurrentText("S11 Smith Chart")
|
||||
chart00_selection.currentTextChanged.connect(lambda: self.changeChart(0, 0, chart00_selection.currentText()))
|
||||
charts_layout.addWidget(chart00_selection, 0, 0)
|
||||
|
||||
chart01_selection = QtWidgets.QComboBox()
|
||||
chart01_selection.addItems(selections)
|
||||
chart01 = self.app.settings.value("Chart01", "S11 Return Loss")
|
||||
if chart01_selection.findText(chart01) > -1:
|
||||
chart01_selection.setCurrentText(chart01)
|
||||
else:
|
||||
chart01_selection.setCurrentText("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 = self.app.settings.value("Chart02", "None")
|
||||
if chart02_selection.findText(chart02) > -1:
|
||||
chart02_selection.setCurrentText(chart02)
|
||||
else:
|
||||
chart02_selection.setCurrentText("None")
|
||||
chart02_selection.currentTextChanged.connect(lambda: self.changeChart(0, 2, chart02_selection.currentText()))
|
||||
charts_layout.addWidget(chart02_selection, 0, 2)
|
||||
|
||||
chart10_selection = QtWidgets.QComboBox()
|
||||
chart10_selection.addItems(selections)
|
||||
chart10 = self.app.settings.value("Chart10", "S21 Polar Plot")
|
||||
if chart10_selection.findText(chart10) > -1:
|
||||
chart10_selection.setCurrentText(chart10)
|
||||
else:
|
||||
chart10_selection.setCurrentText("S21 Polar Plot")
|
||||
chart10_selection.currentTextChanged.connect(lambda: self.changeChart(1, 0, chart10_selection.currentText()))
|
||||
charts_layout.addWidget(chart10_selection, 1, 0)
|
||||
|
||||
chart11_selection = QtWidgets.QComboBox()
|
||||
chart11_selection.addItems(selections)
|
||||
chart11 = self.app.settings.value("Chart11", "S21 Gain")
|
||||
if chart11_selection.findText(chart11) > -1:
|
||||
chart11_selection.setCurrentText(chart11)
|
||||
else:
|
||||
chart11_selection.setCurrentText("S21 Gain")
|
||||
chart11_selection.currentTextChanged.connect(lambda: self.changeChart(1, 1, chart11_selection.currentText()))
|
||||
charts_layout.addWidget(chart11_selection, 1, 1)
|
||||
|
||||
chart12_selection = QtWidgets.QComboBox()
|
||||
chart12_selection.addItems(selections)
|
||||
chart12 = self.app.settings.value("Chart12", "None")
|
||||
if chart12_selection.findText(chart12) > -1:
|
||||
chart12_selection.setCurrentText(chart12)
|
||||
else:
|
||||
chart12_selection.setCurrentText("None")
|
||||
chart12_selection.currentTextChanged.connect(lambda: self.changeChart(1, 2, chart12_selection.currentText()))
|
||||
charts_layout.addWidget(chart12_selection, 1, 2)
|
||||
|
||||
self.changeChart(0, 0, chart00_selection.currentText())
|
||||
self.changeChart(0, 1, chart01_selection.currentText())
|
||||
self.changeChart(0, 2, chart02_selection.currentText())
|
||||
self.changeChart(1, 0, chart10_selection.currentText())
|
||||
self.changeChart(1, 1, chart11_selection.currentText())
|
||||
self.changeChart(1, 2, chart12_selection.currentText())
|
||||
|
||||
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"),
|
||||
type=QtGui.QColor)
|
||||
self.textColor = self.app.settings.value("TextColor", defaultValue=QtGui.QColor("black"),
|
||||
type=QtGui.QColor)
|
||||
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))
|
||||
self.show_marker_number_option.setChecked(self.app.settings.value("ShowMarkerNumbers", False, bool))
|
||||
self.filled_marker_option.setChecked(self.app.settings.value("FilledMarkers", False, bool))
|
||||
|
||||
if self.app.settings.value("UseCustomColors", defaultValue=False, type=bool):
|
||||
self.dark_mode_option.setDisabled(True)
|
||||
self.dark_mode_option.setChecked(False)
|
||||
self.use_custom_colors.setChecked(True)
|
||||
else:
|
||||
self.btn_background_picker.setDisabled(True)
|
||||
self.btn_foreground_picker.setDisabled(True)
|
||||
self.btn_text_picker.setDisabled(True)
|
||||
|
||||
self.changeCustomColors() # Update all the colours of all the charts
|
||||
|
||||
p = self.btn_background_picker.palette()
|
||||
p.setColor(QtGui.QPalette.ButtonText, self.backgroundColor)
|
||||
self.btn_background_picker.setPalette(p)
|
||||
|
||||
p = self.btn_foreground_picker.palette()
|
||||
p.setColor(QtGui.QPalette.ButtonText, self.foregroundColor)
|
||||
self.btn_foreground_picker.setPalette(p)
|
||||
|
||||
p = self.btn_text_picker.palette()
|
||||
p.setColor(QtGui.QPalette.ButtonText, self.textColor)
|
||||
self.btn_text_picker.setPalette(p)
|
||||
|
||||
p = self.btn_bands_picker.palette()
|
||||
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.addWidget(markers_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)
|
||||
right_layout.addStretch(1)
|
||||
|
||||
def changeChart(self, x, y, chart):
|
||||
found = None
|
||||
for c in self.app.selectable_charts:
|
||||
if c.name == chart:
|
||||
found = c
|
||||
|
||||
self.app.settings.setValue("Chart" + str(x) + str(y), chart)
|
||||
|
||||
old_widget = self.app.charts_layout.itemAtPosition(x, y)
|
||||
if old_widget is not None:
|
||||
w = old_widget.widget()
|
||||
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()
|
||||
|
||||
def changeReturnLoss(self):
|
||||
state = self.returnloss_is_positive.isChecked()
|
||||
self.app.settings.setValue("ReturnLossPositive", state)
|
||||
|
||||
for m in self.app.markers:
|
||||
m.returnloss_is_positive = state
|
||||
m.updateLabels(self.app.data, self.app.data21)
|
||||
self.marker_window.exampleMarker.returnloss_is_positive = state
|
||||
self.marker_window.updateMarker()
|
||||
self.app.s11LogMag.isInverted = state
|
||||
self.app.s11LogMag.update()
|
||||
|
||||
def changeShowLines(self):
|
||||
state = self.show_lines_option.isChecked()
|
||||
self.app.settings.setValue("ShowLines", state)
|
||||
for c in self.app.subscribing_charts:
|
||||
c.setDrawLines(state)
|
||||
|
||||
def changeShowMarkerNumber(self):
|
||||
state = self.show_marker_number_option.isChecked()
|
||||
self.app.settings.setValue("ShowMarkerNumbers", state)
|
||||
for c in self.app.subscribing_charts:
|
||||
c.setDrawMarkerNumbers(state)
|
||||
|
||||
def changeFilledMarkers(self):
|
||||
state = self.filled_marker_option.isChecked()
|
||||
self.app.settings.setValue("FilledMarkers", state)
|
||||
for c in self.app.subscribing_charts:
|
||||
c.setFilledMarkers(state)
|
||||
|
||||
def changeMarkerAtTip(self):
|
||||
state = self.marker_at_tip.isChecked()
|
||||
self.app.settings.setValue("MarkerAtTip", state)
|
||||
for c in self.app.subscribing_charts:
|
||||
c.setMarkerAtTip(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 changeMarkerSize(self, size: int):
|
||||
if size % 2 == 0:
|
||||
self.app.settings.setValue("MarkerSize", size)
|
||||
for c in self.app.subscribing_charts:
|
||||
c.setMarkerSize(int(size / 2))
|
||||
|
||||
def validateMarkerSize(self):
|
||||
size = self.markerSizeInput.value()
|
||||
if size % 2 != 0:
|
||||
self.markerSizeInput.setValue(size + 1)
|
||||
|
||||
def changeDarkMode(self):
|
||||
state = self.dark_mode_option.isChecked()
|
||||
self.app.settings.setValue("DarkMode", state)
|
||||
if state:
|
||||
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.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())
|
||||
if self.use_custom_colors.isChecked():
|
||||
self.dark_mode_option.setDisabled(True)
|
||||
self.dark_mode_option.setChecked(False)
|
||||
self.btn_background_picker.setDisabled(False)
|
||||
self.btn_foreground_picker.setDisabled(False)
|
||||
self.btn_text_picker.setDisabled(False)
|
||||
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)
|
||||
self.btn_foreground_picker.setDisabled(True)
|
||||
self.btn_text_picker.setDisabled(True)
|
||||
self.changeDarkMode() # Reset to the default colors depending on Dark Mode setting
|
||||
|
||||
def setColor(self, name: str, color: QtGui.QColor):
|
||||
if name == "background":
|
||||
p = self.btn_background_picker.palette()
|
||||
p.setColor(QtGui.QPalette.ButtonText, color)
|
||||
self.btn_background_picker.setPalette(p)
|
||||
self.backgroundColor = color
|
||||
self.app.settings.setValue("BackgroundColor", color)
|
||||
elif name == "foreground":
|
||||
p = self.btn_foreground_picker.palette()
|
||||
p.setColor(QtGui.QPalette.ButtonText, color)
|
||||
self.btn_foreground_picker.setPalette(p)
|
||||
self.foregroundColor = color
|
||||
self.app.settings.setValue("ForegroundColor", color)
|
||||
elif name == "text":
|
||||
p = self.btn_text_picker.palette()
|
||||
p.setColor(QtGui.QPalette.ButtonText, color)
|
||||
self.btn_text_picker.setPalette(p)
|
||||
self.textColor = color
|
||||
self.app.settings.setValue("TextColor", color)
|
||||
elif name == "bands":
|
||||
p = self.btn_bands_picker.palette()
|
||||
p.setColor(QtGui.QPalette.ButtonText, color)
|
||||
self.btn_bands_picker.setPalette(p)
|
||||
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):
|
||||
if color.isValid():
|
||||
self.sweepColor = color
|
||||
p = self.btnColorPicker.palette()
|
||||
p.setColor(QtGui.QPalette.ButtonText, color)
|
||||
self.btnColorPicker.setPalette(p)
|
||||
self.app.settings.setValue("SweepColor", color)
|
||||
self.app.settings.sync()
|
||||
for c in self.app.subscribing_charts:
|
||||
c.setSweepColor(color)
|
||||
|
||||
def setSecondarySweepColor(self, color: QtGui.QColor):
|
||||
if color.isValid():
|
||||
self.secondarySweepColor = color
|
||||
p = self.btnSecondaryColorPicker.palette()
|
||||
p.setColor(QtGui.QPalette.ButtonText, color)
|
||||
self.btnSecondaryColorPicker.setPalette(p)
|
||||
self.app.settings.setValue("SecondarySweepColor", color)
|
||||
self.app.settings.sync()
|
||||
for c in self.app.subscribing_charts:
|
||||
c.setSecondarySweepColor(color)
|
||||
|
||||
def setReferenceColor(self, color):
|
||||
if color.isValid():
|
||||
self.referenceColor = color
|
||||
p = self.btnReferenceColorPicker.palette()
|
||||
p.setColor(QtGui.QPalette.ButtonText, color)
|
||||
self.btnReferenceColorPicker.setPalette(p)
|
||||
self.app.settings.setValue("ReferenceColor", color)
|
||||
self.app.settings.sync()
|
||||
|
||||
for c in self.app.subscribing_charts:
|
||||
c.setReferenceColor(color)
|
||||
|
||||
def setSecondaryReferenceColor(self, color):
|
||||
if color.isValid():
|
||||
self.secondaryReferenceColor = color
|
||||
p = self.btnSecondaryReferenceColorPicker.palette()
|
||||
p.setColor(QtGui.QPalette.ButtonText, color)
|
||||
self.btnSecondaryReferenceColorPicker.setPalette(p)
|
||||
self.app.settings.setValue("SecondaryReferenceColor", color)
|
||||
self.app.settings.sync()
|
||||
|
||||
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.subscribing_charts:
|
||||
c.update()
|
||||
|
||||
def changeFont(self):
|
||||
font_size = self.font_dropdown.currentText()
|
||||
self.app.settings.setValue("FontSize", font_size)
|
||||
app: QtWidgets.QApplication = QtWidgets.QApplication.instance()
|
||||
font = app.font()
|
||||
font.setPointSize(int(font_size))
|
||||
app.setFont(font)
|
||||
self.app.changeFont(font)
|
||||
|
||||
def displayBandsWindow(self):
|
||||
self.bandsWindow.show()
|
||||
QtWidgets.QApplication.setActiveWindow(self.bandsWindow)
|
||||
|
||||
def displayMarkerWindow(self):
|
||||
self.marker_window.show()
|
||||
QtWidgets.QApplication.setActiveWindow(self.marker_window)
|
||||
|
||||
def addMarker(self):
|
||||
new_marker = Marker("", self.app.settings)
|
||||
new_marker.setScale(self.app.scaleFactor)
|
||||
self.app.markers.append(new_marker)
|
||||
self.app.marker_data_layout.addWidget(new_marker.getGroupBox())
|
||||
|
||||
new_marker.updated.connect(self.app.markerUpdated)
|
||||
label, layout = new_marker.getRow()
|
||||
self.app.marker_control_layout.insertRow(Marker.count() - 1, label, layout)
|
||||
self.btn_remove_marker.setDisabled(False)
|
||||
|
||||
def removeMarker(self):
|
||||
# keep at least one marker
|
||||
if Marker.count() <= 1:
|
||||
return
|
||||
if Marker.count() == 2:
|
||||
self.btn_remove_marker.setDisabled(True)
|
||||
last_marker = self.app.markers.pop()
|
||||
|
||||
last_marker.updated.disconnect(self.app.markerUpdated)
|
||||
self.app.marker_data_layout.removeWidget(last_marker.getGroupBox())
|
||||
self.app.marker_control_layout.removeRow(Marker.count()-1)
|
||||
last_marker.getGroupBox().hide()
|
||||
last_marker.getGroupBox().destroy()
|
||||
label, _ = last_marker.getRow()
|
||||
label.hide()
|
||||
|
||||
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)
|
||||
|
|
@ -0,0 +1,154 @@
|
|||
# NanoVNASaver
|
||||
# A python program to view and export Touchstone data from a NanoVNA
|
||||
# Copyright (C) 2019. Rune B. Broberg
|
||||
#
|
||||
# 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 logging
|
||||
|
||||
from PyQt5 import QtWidgets, QtCore, QtGui
|
||||
|
||||
from NanoVNASaver.RFTools import Datapoint
|
||||
from NanoVNASaver.Marker import Marker
|
||||
from NanoVNASaver.Marker.Values import TYPES, default_label_ids
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MarkerSettingsWindow(QtWidgets.QWidget):
|
||||
exampleData11 = [Datapoint(123000000, 0.89, -0.11),
|
||||
Datapoint(123500000, 0.9, -0.1),
|
||||
Datapoint(124000000, 0.91, -0.95)]
|
||||
exampleData21 = [Datapoint(123000000, -0.25, 0.49),
|
||||
Datapoint(123456000, -0.3, 0.5),
|
||||
Datapoint(124000000, -0.2, 0.5)]
|
||||
|
||||
def __init__(self, app: QtWidgets.QWidget):
|
||||
super().__init__()
|
||||
self.app = app
|
||||
|
||||
self.setWindowTitle("Marker settings")
|
||||
self.setWindowIcon(self.app.icon)
|
||||
|
||||
QtWidgets.QShortcut(QtCore.Qt.Key_Escape, self, self.cancelButtonClick)
|
||||
|
||||
self.exampleMarker = Marker("Example marker")
|
||||
layout = QtWidgets.QVBoxLayout()
|
||||
self.setLayout(layout)
|
||||
|
||||
settings_group_box = QtWidgets.QGroupBox("Settings")
|
||||
settings_group_box_layout = QtWidgets.QFormLayout(settings_group_box)
|
||||
self.checkboxColouredMarker = QtWidgets.QCheckBox("Colored marker name")
|
||||
self.checkboxColouredMarker.setChecked(self.app.settings.value("ColoredMarkerNames", True, bool))
|
||||
self.checkboxColouredMarker.stateChanged.connect(self.updateMarker)
|
||||
settings_group_box_layout.addRow(self.checkboxColouredMarker)
|
||||
|
||||
fields_group_box = QtWidgets.QGroupBox("Displayed data")
|
||||
fields_group_box_layout = QtWidgets.QFormLayout(fields_group_box)
|
||||
|
||||
self.savedFieldSelection = self.app.settings.value(
|
||||
"MarkerFields", defaultValue=default_label_ids()
|
||||
)
|
||||
|
||||
if self.savedFieldSelection == "":
|
||||
self.savedFieldSelection = []
|
||||
|
||||
self.currentFieldSelection = self.savedFieldSelection[:]
|
||||
|
||||
self.active_labels_view = QtWidgets.QListView()
|
||||
self.update_displayed_data_form()
|
||||
|
||||
fields_group_box_layout.addRow(self.active_labels_view)
|
||||
|
||||
layout.addWidget(settings_group_box)
|
||||
layout.addWidget(fields_group_box)
|
||||
layout.addWidget(self.exampleMarker.getGroupBox())
|
||||
|
||||
btn_layout = QtWidgets.QHBoxLayout()
|
||||
layout.addLayout(btn_layout)
|
||||
btn_ok = QtWidgets.QPushButton("OK")
|
||||
btn_apply = QtWidgets.QPushButton("Apply")
|
||||
btn_default = QtWidgets.QPushButton("Defaults")
|
||||
btn_cancel = QtWidgets.QPushButton("Cancel")
|
||||
|
||||
btn_ok.clicked.connect(self.okButtonClick)
|
||||
btn_apply.clicked.connect(self.applyButtonClick)
|
||||
btn_default.clicked.connect(self.defaultButtonClick)
|
||||
btn_cancel.clicked.connect(self.cancelButtonClick)
|
||||
|
||||
btn_layout.addWidget(btn_ok)
|
||||
btn_layout.addWidget(btn_apply)
|
||||
btn_layout.addWidget(btn_default)
|
||||
btn_layout.addWidget(btn_cancel)
|
||||
|
||||
self.updateMarker()
|
||||
for m in self.app.markers:
|
||||
m.setFieldSelection(self.currentFieldSelection)
|
||||
m.setColoredText(self.checkboxColouredMarker.isChecked())
|
||||
|
||||
def updateMarker(self):
|
||||
self.exampleMarker.setFrequency(123456000)
|
||||
self.exampleMarker.setColoredText(self.checkboxColouredMarker.isChecked())
|
||||
self.exampleMarker.setFieldSelection(self.currentFieldSelection)
|
||||
self.exampleMarker.findLocation(self.exampleData11)
|
||||
self.exampleMarker.resetLabels()
|
||||
self.exampleMarker.updateLabels(self.exampleData11, self.exampleData21)
|
||||
|
||||
def updateField(self, field: QtGui.QStandardItem):
|
||||
if field.checkState() == QtCore.Qt.Checked:
|
||||
if not field.data() in self.currentFieldSelection:
|
||||
self.currentFieldSelection = []
|
||||
for i in range(self.model.rowCount()):
|
||||
field = self.model.item(i, 0)
|
||||
if field.checkState() == QtCore.Qt.Checked:
|
||||
self.currentFieldSelection.append(field.data())
|
||||
else:
|
||||
if field.data() in self.currentFieldSelection:
|
||||
self.currentFieldSelection.remove(field.data())
|
||||
self.updateMarker()
|
||||
|
||||
def applyButtonClick(self):
|
||||
self.savedFieldSelection = self.currentFieldSelection[:]
|
||||
self.app.settings.setValue("MarkerFields", self.savedFieldSelection)
|
||||
self.app.settings.setValue("ColoredMarkerNames", self.checkboxColouredMarker.isChecked())
|
||||
for m in self.app.markers:
|
||||
m.setFieldSelection(self.savedFieldSelection)
|
||||
m.setColoredText(self.checkboxColouredMarker.isChecked())
|
||||
|
||||
def okButtonClick(self):
|
||||
self.applyButtonClick()
|
||||
self.close()
|
||||
|
||||
def cancelButtonClick(self):
|
||||
self.currentFieldSelection = self.savedFieldSelection[:]
|
||||
self.update_displayed_data_form()
|
||||
self.updateMarker()
|
||||
self.close()
|
||||
|
||||
def defaultButtonClick(self):
|
||||
self.currentFieldSelection = default_label_ids()
|
||||
self.update_displayed_data_form()
|
||||
self.updateMarker()
|
||||
|
||||
def update_displayed_data_form(self):
|
||||
self.model = QtGui.QStandardItemModel()
|
||||
for label in TYPES:
|
||||
item = QtGui.QStandardItem(label.description)
|
||||
item.setData(label.label_id)
|
||||
item.setCheckable(True)
|
||||
item.setEditable(False)
|
||||
if label.label_id in self.currentFieldSelection:
|
||||
item.setCheckState(QtCore.Qt.Checked)
|
||||
self.model.appendRow(item)
|
||||
self.active_labels_view.setModel(self.model)
|
||||
self.model.itemChanged.connect(self.updateField)
|
|
@ -0,0 +1,99 @@
|
|||
# NanoVNASaver
|
||||
# A python program to view and export Touchstone data from a NanoVNA
|
||||
# Copyright (C) 2019. Rune B. Broberg
|
||||
#
|
||||
# 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 logging
|
||||
from PyQt5 import QtWidgets, QtCore, QtGui
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ScreenshotWindow(QtWidgets.QLabel):
|
||||
pix = None
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.setWindowTitle("Screenshot")
|
||||
# TODO : self.setWindowIcon(self.app.icon)
|
||||
|
||||
QtWidgets.QShortcut(QtCore.Qt.Key_Escape, self, self.hide)
|
||||
self.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu)
|
||||
|
||||
self.action_original_size = QtWidgets.QAction("Original size")
|
||||
self.action_original_size.triggered.connect(lambda: self.setScale(1))
|
||||
self.action_2x_size = QtWidgets.QAction("2x size")
|
||||
self.action_2x_size.triggered.connect(lambda: self.setScale(2))
|
||||
self.action_3x_size = QtWidgets.QAction("3x size")
|
||||
self.action_3x_size.triggered.connect(lambda: self.setScale(3))
|
||||
self.action_4x_size = QtWidgets.QAction("4x size")
|
||||
self.action_4x_size.triggered.connect(lambda: self.setScale(4))
|
||||
self.action_5x_size = QtWidgets.QAction("5x size")
|
||||
self.action_5x_size.triggered.connect(lambda: self.setScale(5))
|
||||
|
||||
self.addAction(self.action_original_size)
|
||||
self.addAction(self.action_2x_size)
|
||||
self.addAction(self.action_3x_size)
|
||||
self.addAction(self.action_4x_size)
|
||||
self.addAction(self.action_5x_size)
|
||||
self.action_save_screenshot = QtWidgets.QAction("Save image")
|
||||
self.action_save_screenshot.triggered.connect(self.saveScreenshot)
|
||||
self.addAction(self.action_save_screenshot)
|
||||
|
||||
def setScreenshot(self, pixmap: QtGui.QPixmap):
|
||||
if self.pix is None:
|
||||
self.resize(pixmap.size())
|
||||
self.pix = pixmap
|
||||
self.setPixmap(
|
||||
self.pix.scaled(
|
||||
self.size(),
|
||||
QtCore.Qt.KeepAspectRatio,
|
||||
QtCore.Qt.FastTransformation))
|
||||
w, h = pixmap.width(), pixmap.height()
|
||||
self.action_original_size.setText(
|
||||
"Original size (" + str(w) + "x" + str(h) + ")")
|
||||
self.action_2x_size.setText(
|
||||
"2x size (" + str(w * 2) + "x" + str(h * 2) + ")")
|
||||
self.action_3x_size.setText(
|
||||
"3x size (" + str(w * 3) + "x" + str(h * 3) + ")")
|
||||
self.action_4x_size.setText(
|
||||
"4x size (" + str(w * 4) + "x" + str(h * 4) + ")")
|
||||
self.action_5x_size.setText(
|
||||
"5x size (" + str(w * 5) + "x" + str(h * 5) + ")")
|
||||
|
||||
def saveScreenshot(self):
|
||||
if self.pix is not None:
|
||||
logger.info("Saving screenshot to file...")
|
||||
filename, _ = QtWidgets.QFileDialog.getSaveFileName(
|
||||
parent=self, caption="Save image",
|
||||
filter="PNG (*.png);;All files (*.*)")
|
||||
|
||||
logger.debug("Filename: %s", filename)
|
||||
if filename != "":
|
||||
self.pixmap().save(filename)
|
||||
else:
|
||||
logger.warning("The user got shown an empty screenshot window?")
|
||||
|
||||
def resizeEvent(self, a0: QtGui.QResizeEvent) -> None:
|
||||
super().resizeEvent(a0)
|
||||
if self.pixmap() is not None:
|
||||
self.setPixmap(
|
||||
self.pix.scaled(
|
||||
self.size(),
|
||||
QtCore.Qt.KeepAspectRatio,
|
||||
QtCore.Qt.FastTransformation))
|
||||
|
||||
def setScale(self, scale):
|
||||
width, height = self.pix.size().width() * scale, self.pix.size().height() * scale
|
||||
self.resize(width, height)
|
|
@ -0,0 +1,176 @@
|
|||
# NanoVNASaver
|
||||
# A python program to view and export Touchstone data from a NanoVNA
|
||||
# Copyright (C) 2019. Rune B. Broberg
|
||||
#
|
||||
# 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 logging
|
||||
|
||||
from PyQt5 import QtWidgets, QtCore
|
||||
|
||||
from NanoVNASaver.RFTools import RFTools
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SweepSettingsWindow(QtWidgets.QWidget):
|
||||
def __init__(self, app: QtWidgets.QWidget):
|
||||
super().__init__()
|
||||
|
||||
self.app = app
|
||||
self.setWindowTitle("Sweep settings")
|
||||
self.setWindowIcon(self.app.icon)
|
||||
|
||||
QtWidgets.QShortcut(QtCore.Qt.Key_Escape, self, self.hide)
|
||||
|
||||
layout = QtWidgets.QVBoxLayout()
|
||||
self.setLayout(layout)
|
||||
|
||||
title_box = QtWidgets.QGroupBox("Sweep name")
|
||||
title_layout = QtWidgets.QFormLayout(title_box)
|
||||
self.sweep_title_input = QtWidgets.QLineEdit()
|
||||
title_layout.addRow("Sweep name", self.sweep_title_input)
|
||||
title_button_layout = QtWidgets.QHBoxLayout()
|
||||
btn_set_sweep_title = QtWidgets.QPushButton("Set")
|
||||
btn_set_sweep_title.clicked.connect(
|
||||
lambda: self.app.setSweepTitle(self.sweep_title_input.text()))
|
||||
btn_reset_sweep_title = QtWidgets.QPushButton("Reset")
|
||||
btn_reset_sweep_title.clicked.connect(lambda: self.app.setSweepTitle(""))
|
||||
title_button_layout.addWidget(btn_set_sweep_title)
|
||||
title_button_layout.addWidget(btn_reset_sweep_title)
|
||||
title_layout.addRow(title_button_layout)
|
||||
layout.addWidget(title_box)
|
||||
|
||||
settings_box = QtWidgets.QGroupBox("Settings")
|
||||
settings_layout = QtWidgets.QFormLayout(settings_box)
|
||||
|
||||
self.single_sweep_radiobutton = QtWidgets.QRadioButton("Single sweep")
|
||||
self.continuous_sweep_radiobutton = QtWidgets.QRadioButton("Continuous sweep")
|
||||
self.averaged_sweep_radiobutton = QtWidgets.QRadioButton("Averaged sweep")
|
||||
|
||||
settings_layout.addWidget(self.single_sweep_radiobutton)
|
||||
self.single_sweep_radiobutton.setChecked(True)
|
||||
settings_layout.addWidget(self.continuous_sweep_radiobutton)
|
||||
settings_layout.addWidget(self.averaged_sweep_radiobutton)
|
||||
|
||||
self.averages = QtWidgets.QLineEdit("3")
|
||||
self.truncates = QtWidgets.QLineEdit("0")
|
||||
|
||||
settings_layout.addRow("Number of measurements to average", self.averages)
|
||||
settings_layout.addRow("Number to discard", self.truncates)
|
||||
settings_layout.addRow(
|
||||
QtWidgets.QLabel(
|
||||
"Averaging allows discarding outlying samples to get better averages."))
|
||||
settings_layout.addRow(
|
||||
QtWidgets.QLabel("Common values are 3/0, 5/2, 9/4 and 25/6."))
|
||||
|
||||
self.continuous_sweep_radiobutton.toggled.connect(
|
||||
lambda: self.app.worker.setContinuousSweep(
|
||||
self.continuous_sweep_radiobutton.isChecked()))
|
||||
self.averaged_sweep_radiobutton.toggled.connect(self.updateAveraging)
|
||||
self.averages.textEdited.connect(self.updateAveraging)
|
||||
self.truncates.textEdited.connect(self.updateAveraging)
|
||||
|
||||
layout.addWidget(settings_box)
|
||||
|
||||
band_sweep_box = QtWidgets.QGroupBox("Sweep band")
|
||||
band_sweep_layout = QtWidgets.QFormLayout(band_sweep_box)
|
||||
|
||||
self.band_list = QtWidgets.QComboBox()
|
||||
self.band_list.setModel(self.app.bands)
|
||||
self.band_list.currentIndexChanged.connect(self.updateCurrentBand)
|
||||
|
||||
band_sweep_layout.addRow("Select band", self.band_list)
|
||||
|
||||
self.band_pad_group = QtWidgets.QButtonGroup()
|
||||
self.band_pad_0 = QtWidgets.QRadioButton("None")
|
||||
self.band_pad_10 = QtWidgets.QRadioButton("10%")
|
||||
self.band_pad_25 = QtWidgets.QRadioButton("25%")
|
||||
self.band_pad_100 = QtWidgets.QRadioButton("100%")
|
||||
self.band_pad_0.setChecked(True)
|
||||
self.band_pad_group.addButton(self.band_pad_0)
|
||||
self.band_pad_group.addButton(self.band_pad_10)
|
||||
self.band_pad_group.addButton(self.band_pad_25)
|
||||
self.band_pad_group.addButton(self.band_pad_100)
|
||||
self.band_pad_group.buttonClicked.connect(self.updateCurrentBand)
|
||||
band_sweep_layout.addRow("Pad band limits", self.band_pad_0)
|
||||
band_sweep_layout.addRow("", self.band_pad_10)
|
||||
band_sweep_layout.addRow("", self.band_pad_25)
|
||||
band_sweep_layout.addRow("", self.band_pad_100)
|
||||
|
||||
self.band_limit_label = QtWidgets.QLabel()
|
||||
|
||||
band_sweep_layout.addRow(self.band_limit_label)
|
||||
|
||||
btn_set_band_sweep = QtWidgets.QPushButton("Set band sweep")
|
||||
btn_set_band_sweep.clicked.connect(self.setBandSweep)
|
||||
band_sweep_layout.addRow(btn_set_band_sweep)
|
||||
|
||||
self.updateCurrentBand()
|
||||
|
||||
layout.addWidget(band_sweep_box)
|
||||
|
||||
def updateCurrentBand(self):
|
||||
index_start = self.band_list.model().index(self.band_list.currentIndex(), 1)
|
||||
index_stop = self.band_list.model().index(self.band_list.currentIndex(), 2)
|
||||
start = int(self.band_list.model().data(index_start, QtCore.Qt.ItemDataRole).value())
|
||||
stop = int(self.band_list.model().data(index_stop, QtCore.Qt.ItemDataRole).value())
|
||||
|
||||
if self.band_pad_10.isChecked():
|
||||
padding = 10
|
||||
elif self.band_pad_25.isChecked():
|
||||
padding = 25
|
||||
elif self.band_pad_100.isChecked():
|
||||
padding = 100
|
||||
else:
|
||||
padding = 0
|
||||
|
||||
if padding > 0:
|
||||
span = stop - start
|
||||
start -= round(span * padding / 100)
|
||||
start = max(1, start)
|
||||
stop += round(span * padding / 100)
|
||||
|
||||
self.band_limit_label.setText(
|
||||
f"Sweep span: {RFTools.formatShortFrequency(start)}"
|
||||
f" to {RFTools.formatShortFrequency(stop)}")
|
||||
|
||||
def setBandSweep(self):
|
||||
index_start = self.band_list.model().index(self.band_list.currentIndex(), 1)
|
||||
index_stop = self.band_list.model().index(self.band_list.currentIndex(), 2)
|
||||
start = int(self.band_list.model().data(index_start, QtCore.Qt.ItemDataRole).value())
|
||||
stop = int(self.band_list.model().data(index_stop, QtCore.Qt.ItemDataRole).value())
|
||||
|
||||
if self.band_pad_10.isChecked():
|
||||
padding = 10
|
||||
elif self.band_pad_25.isChecked():
|
||||
padding = 25
|
||||
elif self.band_pad_100.isChecked():
|
||||
padding = 100
|
||||
else:
|
||||
padding = 0
|
||||
|
||||
if padding > 0:
|
||||
span = stop - start
|
||||
start -= round(span * padding / 100)
|
||||
start = max(1, start)
|
||||
stop += round(span * padding / 100)
|
||||
|
||||
self.app.sweepStartInput.setText(RFTools.formatSweepFrequency(start))
|
||||
self.app.sweepEndInput.setText(RFTools.formatSweepFrequency(stop))
|
||||
self.app.sweepEndInput.textEdited.emit(self.app.sweepEndInput.text())
|
||||
|
||||
def updateAveraging(self):
|
||||
self.app.worker.setAveraging(self.averaged_sweep_radiobutton.isChecked(),
|
||||
self.averages.text(),
|
||||
self.truncates.text())
|
|
@ -0,0 +1,152 @@
|
|||
# NanoVNASaver - a python program to view and export Touchstone data from a NanoVNA
|
||||
# Copyright (C) 2019. Rune B. Broberg
|
||||
#
|
||||
# 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 logging
|
||||
import math
|
||||
|
||||
import numpy as np
|
||||
import scipy.signal as signal
|
||||
from PyQt5 import QtWidgets, QtCore
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TDRWindow(QtWidgets.QWidget):
|
||||
updated = QtCore.pyqtSignal()
|
||||
|
||||
def __init__(self, app: QtWidgets.QWidget):
|
||||
super().__init__()
|
||||
self.app = app
|
||||
|
||||
self.td = []
|
||||
self.distance_axis = []
|
||||
self.step_response = []
|
||||
self.step_response_Z = []
|
||||
|
||||
self.setWindowTitle("TDR")
|
||||
self.setWindowIcon(self.app.icon)
|
||||
|
||||
QtWidgets.QShortcut(QtCore.Qt.Key_Escape, self, self.hide)
|
||||
|
||||
layout = QtWidgets.QFormLayout()
|
||||
self.setLayout(layout)
|
||||
|
||||
self.tdr_velocity_dropdown = QtWidgets.QComboBox()
|
||||
self.tdr_velocity_dropdown.addItem("Jelly filled (0.64)", 0.64)
|
||||
self.tdr_velocity_dropdown.addItem("Polyethylene (0.66)", 0.66)
|
||||
self.tdr_velocity_dropdown.addItem("PTFE (Teflon) (0.70)", 0.70)
|
||||
self.tdr_velocity_dropdown.addItem("Pulp Insulation (0.72)", 0.72)
|
||||
self.tdr_velocity_dropdown.addItem("Foam or Cellular PE (0.78)", 0.78)
|
||||
self.tdr_velocity_dropdown.addItem("Semi-solid PE (SSPE) (0.84)", 0.84)
|
||||
self.tdr_velocity_dropdown.addItem("Air (Helical spacers) (0.94)", 0.94)
|
||||
self.tdr_velocity_dropdown.insertSeparator(self.tdr_velocity_dropdown.count())
|
||||
# Lots of cable types added by Larry Goga, AE5CZ
|
||||
self.tdr_velocity_dropdown.addItem("RG-6/U PE 75\N{OHM SIGN} (Belden 8215) (0.66)", 0.66)
|
||||
self.tdr_velocity_dropdown.addItem("RG-6/U Foam 75\N{OHM SIGN} (Belden 9290) (0.81)", 0.81)
|
||||
self.tdr_velocity_dropdown.addItem("RG-8/U PE 50\N{OHM SIGN} (Belden 8237) (0.66)", 0.66)
|
||||
self.tdr_velocity_dropdown.addItem("RG-8/U Foam (Belden 8214) (0.78)", 0.78)
|
||||
self.tdr_velocity_dropdown.addItem("RG-8/U (Belden 9913) (0.84)", 0.84)
|
||||
self.tdr_velocity_dropdown.addItem("RG-8X (Belden 9258) (0.82)", 0.82)
|
||||
self.tdr_velocity_dropdown.addItem(
|
||||
"RG-11/U 75\N{OHM SIGN} Foam HDPE (Belden 9292) (0.84)", 0.84)
|
||||
self.tdr_velocity_dropdown.addItem("RG-58/U 52\N{OHM SIGN} PE (Belden 9201) (0.66)", 0.66)
|
||||
self.tdr_velocity_dropdown.addItem(
|
||||
"RG-58A/U 54\N{OHM SIGN} Foam (Belden 8219) (0.73)", 0.73)
|
||||
self.tdr_velocity_dropdown.addItem("RG-59A/U PE 75\N{OHM SIGN} (Belden 8241) (0.66)", 0.66)
|
||||
self.tdr_velocity_dropdown.addItem(
|
||||
"RG-59A/U Foam 75\N{OHM SIGN} (Belden 8241F) (0.78)", 0.78)
|
||||
self.tdr_velocity_dropdown.addItem("RG-174 PE (Belden 8216)(0.66)", 0.66)
|
||||
self.tdr_velocity_dropdown.addItem("RG-174 Foam (Belden 7805R) (0.735)", 0.735)
|
||||
self.tdr_velocity_dropdown.addItem("RG-213/U PE (Belden 8267) (0.66)", 0.66)
|
||||
self.tdr_velocity_dropdown.addItem("RG316 (0.695)", 0.695)
|
||||
self.tdr_velocity_dropdown.addItem("RG402 (0.695)", 0.695)
|
||||
self.tdr_velocity_dropdown.addItem("LMR-240 (0.84)", 0.84)
|
||||
self.tdr_velocity_dropdown.addItem("LMR-240UF (0.80)", 0.80)
|
||||
self.tdr_velocity_dropdown.addItem("LMR-400 (0.85)", 0.85)
|
||||
self.tdr_velocity_dropdown.addItem("LMR400UF (0.83)", 0.83)
|
||||
self.tdr_velocity_dropdown.addItem("Davis Bury-FLEX (0.82)", 0.82)
|
||||
self.tdr_velocity_dropdown.insertSeparator(self.tdr_velocity_dropdown.count())
|
||||
self.tdr_velocity_dropdown.addItem("Custom", -1)
|
||||
|
||||
self.tdr_velocity_dropdown.setCurrentIndex(1) # Default to PE (0.66)
|
||||
|
||||
self.tdr_velocity_dropdown.currentIndexChanged.connect(self.updateTDR)
|
||||
|
||||
layout.addRow(self.tdr_velocity_dropdown)
|
||||
|
||||
self.tdr_velocity_input = QtWidgets.QLineEdit()
|
||||
self.tdr_velocity_input.setDisabled(True)
|
||||
self.tdr_velocity_input.setText("0.66")
|
||||
self.tdr_velocity_input.textChanged.connect(self.app.dataUpdated)
|
||||
|
||||
layout.addRow("Velocity factor", self.tdr_velocity_input)
|
||||
|
||||
self.tdr_result_label = QtWidgets.QLabel()
|
||||
layout.addRow("Estimated cable length:", self.tdr_result_label)
|
||||
|
||||
layout.addRow(self.app.tdr_chart)
|
||||
|
||||
def updateTDR(self):
|
||||
c = 299792458
|
||||
# TODO: Let the user select whether to use high or low resolution TDR?
|
||||
FFT_POINTS = 2**14
|
||||
|
||||
if len(self.app.data) < 2:
|
||||
return
|
||||
|
||||
if self.tdr_velocity_dropdown.currentData() == -1:
|
||||
self.tdr_velocity_input.setDisabled(False)
|
||||
else:
|
||||
self.tdr_velocity_input.setDisabled(True)
|
||||
self.tdr_velocity_input.setText(str(self.tdr_velocity_dropdown.currentData()))
|
||||
|
||||
try:
|
||||
v = float(self.tdr_velocity_input.text())
|
||||
except ValueError:
|
||||
return
|
||||
|
||||
step_size = self.app.data[1].freq - self.app.data[0].freq
|
||||
if step_size == 0:
|
||||
self.tdr_result_label.setText("")
|
||||
logger.info("Cannot compute cable length at 0 span")
|
||||
return
|
||||
|
||||
s11 = []
|
||||
for d in self.app.data:
|
||||
s11.append(np.complex(d.re, d.im))
|
||||
|
||||
window = np.blackman(len(self.app.data))
|
||||
|
||||
windowed_s11 = window * s11
|
||||
self.td = np.abs(np.fft.ifft(windowed_s11, FFT_POINTS))
|
||||
step = np.ones(FFT_POINTS)
|
||||
self.step_response = signal.convolve(self.td, step)
|
||||
|
||||
self.step_response_Z = 50 * (1 + self.step_response) / (1 - self.step_response)
|
||||
|
||||
time_axis = np.linspace(0, 1/step_size, FFT_POINTS)
|
||||
self.distance_axis = time_axis * v * c
|
||||
# peak = np.max(td)
|
||||
# We should check that this is an actual *peak*, and not just a vague maximum
|
||||
index_peak = np.argmax(self.td)
|
||||
|
||||
cable_len = round(self.distance_axis[index_peak]/2, 3)
|
||||
feet = math.floor(cable_len / 0.3048)
|
||||
inches = round(((cable_len / 0.3048) - feet)*12, 1)
|
||||
|
||||
self.tdr_result_label.setText(f"{cable_len}m ({feet}ft {inches}in)")
|
||||
self.app.tdr_result_label.setText(str(cable_len) + " m")
|
||||
self.updated.emit()
|
|
@ -0,0 +1,9 @@
|
|||
from .About import AboutWindow
|
||||
from .AnalysisWindow import AnalysisWindow
|
||||
from .Bands import BandsWindow
|
||||
from .DeviceSettings import DeviceSettingsWindow
|
||||
from .DisplaySettings import DisplaySettingsWindow
|
||||
from .MarkerSettings import MarkerSettingsWindow
|
||||
from .Screenshot import ScreenshotWindow
|
||||
from .SweepSettings import SweepSettingsWindow
|
||||
from .TDR import TDRWindow
|
Ładowanie…
Reference in New Issue