diff --git a/Version.py b/Version.py new file mode 100644 index 0000000..305429b --- /dev/null +++ b/Version.py @@ -0,0 +1,219 @@ +# NanoVNASaver +# +# A python program to view and export Touchstone data from a NanoVNA +# Copyright (C) 2019, 2020 Rune B. Broberg +# Copyright (C) 2020 NanoVNA-Saver Authors +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +import logging +import re +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()]) + if role == QtCore.Qt.TextAlignmentRole: + if index.column() == 0: + return QtCore.QVariant(QtCore.Qt.AlignCenter) + return QtCore.QVariant(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) + 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)" + return "Invalid" + 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) + super().flags(index) + + def setColor(self, color): + self.color = color + + +class Version: + RXP = re.compile(r"""^ + \D* + (?P\d+)\. + (?P\d+)\. + (?P\d+) + (?P.*) + $""", re.VERBOSE) + + def __init__(self, vstring: str = "0.0.0"): + self.data = { + "major": 0, + "minor": 0, + "revision": 0, + "note": "", + } + try: + self.data = Version.RXP.search(vstring).groupdict() + for name in ("major", "minor", "revision"): + self.data[name] = int(self.data[name]) + except AttributeError: + logger.error("Unable to parse version: %s", vstring) + + def __gt__(self, other: "Version") -> bool: + l, r = self.data, other.data + for name in ("major", "minor", "revision"): + if l[name] > r[name]: + return True + if l[name] < r[name]: + return False + return False + + def __lt__(self, other: "Version") -> bool: + return other > self + + def __ge__(self, other: "Version") -> bool: + return self > other or self == other + + def __le__(self, other: "Version") -> bool: + return self < other or self == other + + def __eq__(self, other: "Version") -> bool: + return self.data == other.data + + def __str__(self) -> str: + return (f'{self.data["major"]}.{self.data["minor"]}' + f'.{self.data["revision"]}{self.data["note"]}') + + @property + def major(self) -> int: + return self.data["major"] + + @property + def minor(self) -> int: + return self.data["minor"] + + @property + def revision(self) -> int: + return self.data["revision"] + + @property + def note(self) -> str: + return self.data["note"]