1#  NanoVNASaver
2#
3#  A python program to view and export Touchstone data from a NanoVNA
4#  Copyright (C) 2019, 2020  Rune B. Broberg
5#  Copyright (C) 2020 NanoVNA-Saver Authors
6#
7#  This program is free software: you can redistribute it and/or modify
8#  it under the terms of the GNU General Public License as published by
9#  the Free Software Foundation, either version 3 of the License, or
10#  (at your option) any later version.
11#
12#  This program is distributed in the hope that it will be useful,
13#  but WITHOUT ANY WARRANTY; without even the implied warranty of
14#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15#  GNU General Public License for more details.
16#
17#  You should have received a copy of the GNU General Public License
18#  along with this program.  If not, see <https://www.gnu.org/licenses/>.
19import logging
20from functools import partial
21from PyQt5 import QtWidgets, QtCore
22
23from NanoVNASaver.Formatting import (
24    format_frequency_short, format_frequency_sweep,
25)
26from NanoVNASaver.Settings.Sweep import SweepMode
27
28logger = logging.getLogger(__name__)
29
30
31class SweepSettingsWindow(QtWidgets.QWidget):
32    def __init__(self, app: QtWidgets.QWidget):
33        super().__init__()
34        self.app = app
35        self.padding = 0
36
37        self.setWindowTitle("Sweep settings")
38        self.setWindowIcon(self.app.icon)
39
40        QtWidgets.QShortcut(QtCore.Qt.Key_Escape, self, self.hide)
41
42        layout = QtWidgets.QVBoxLayout()
43        self.setLayout(layout)
44
45        layout.addWidget(self.title_box())
46        layout.addWidget(self.settings_box())
47        layout.addWidget(self.sweep_box())
48        self.update_band()
49
50    def title_box(self):
51        box = QtWidgets.QGroupBox("Sweep name")
52        layout = QtWidgets.QFormLayout(box)
53
54        input_title = QtWidgets.QLineEdit(self.app.sweep.properties.name)
55        input_title.editingFinished.connect(
56            lambda: self.update_title(input_title.text()))
57        layout.addRow(input_title)
58        return box
59
60    def settings_box(self) -> 'QtWidgets.QWidget':
61        box = QtWidgets.QGroupBox("Settings")
62        layout = QtWidgets.QFormLayout(box)
63
64        # Sweep Mode
65        radio_button = QtWidgets.QRadioButton("Single sweep")
66        radio_button.setChecked(
67            self.app.sweep.properties.mode == SweepMode.SINGLE)
68        radio_button.clicked.connect(
69            lambda: self.update_mode(SweepMode.SINGLE))
70        layout.addWidget(radio_button)
71
72        radio_button = QtWidgets.QRadioButton("Continous sweep")
73        radio_button.setChecked(
74            self.app.sweep.properties.mode == SweepMode.CONTINOUS)
75        radio_button.clicked.connect(
76            lambda: self.update_mode(SweepMode.CONTINOUS))
77        layout.addWidget(radio_button)
78
79        radio_button = QtWidgets.QRadioButton("Averaged sweep")
80        radio_button.setChecked(
81            self.app.sweep.properties.mode == SweepMode.AVERAGE)
82        radio_button.clicked.connect(
83            lambda: self.update_mode(SweepMode.AVERAGE))
84        layout.addWidget(radio_button)
85
86        # Log sweep
87        label = QtWidgets.QLabel(
88            "Logarithmic sweeping changes the step width in each segment"
89            " in logarithmical manner. Useful in conjunction with small"
90            " amount of datapoints and many segments. Step display in"
91            " SweepControl cannot reflect this currently.")
92        label.setWordWrap(True)
93        layout.addRow(label)
94        checkbox = QtWidgets.QCheckBox("Logarithmic sweep")
95        checkbox.setCheckState(self.app.sweep.properties.logarithmic)
96        checkbox.toggled.connect(
97            lambda: self.update_logarithmic(checkbox.isChecked()))
98        layout.addWidget(checkbox)
99
100        # Averaging
101        label = QtWidgets.QLabel(
102            "Averaging allows discarding outlying samples to get better"
103            " averages. Common values are 3/0, 5/2, 9/4 and 25/6.")
104        label.setWordWrap(True)
105        layout.addRow(label)
106        averages = QtWidgets.QLineEdit(
107            str(self.app.sweep.properties.averages[0]))
108        truncates = QtWidgets.QLineEdit(
109            str(self.app.sweep.properties.averages[1]))
110        averages.editingFinished.connect(
111            lambda: self.update_averaging(averages, truncates))
112        truncates.editingFinished.connect(
113            lambda: self.update_averaging(averages, truncates))
114        layout.addRow("Number of measurements to average", averages)
115        layout.addRow("Number to discard", truncates)
116
117        # TODO: is this more a device than a sweep property?
118        label = QtWidgets.QLabel(
119            "Some times when you measure amplifiers you need to use an"
120            " attenuator in line with  the S21 input (CH1) here you can"
121            " specify it.")
122        label.setWordWrap(True)
123        layout.addRow(label)
124
125        input_att = QtWidgets.QLineEdit(str(self.app.s21att))
126        input_att.editingFinished.connect(
127            lambda: self.update_attenuator(input_att))
128        layout.addRow("Attenuator in port CH1 (s21) in dB", input_att)
129        return box
130
131    def sweep_box(self) -> 'QtWidgets.QWidget':
132        box = QtWidgets.QGroupBox("Sweep band")
133        layout = QtWidgets.QFormLayout(box)
134
135        self.band_list = QtWidgets.QComboBox()
136        self.band_list.setModel(self.app.bands)
137        # pylint: disable=unnecessary-lambda
138        self.band_list.currentIndexChanged.connect(lambda: self.update_band())
139        layout.addRow("Select band", self.band_list)
140
141        for raw_label, btn_label, value in (("Pad band limits", "None", 0),
142                                            ("", "10%", 10),
143                                            ("", "25%", 25),
144                                            ("", "100%", 100),):
145            radio_button = QtWidgets.QRadioButton(btn_label)
146            radio_button.setChecked(self.padding == value)
147            radio_button.clicked.connect(partial(self.update_padding, value))
148            layout.addRow(raw_label, radio_button)
149
150        self.band_label = QtWidgets.QLabel()
151        layout.addRow(self.band_label)
152
153        btn_set_band_sweep = QtWidgets.QPushButton("Set band sweep")
154        btn_set_band_sweep.clicked.connect(lambda: self.update_band(True))
155        layout.addRow(btn_set_band_sweep)
156        return box
157
158    def update_band(self, apply: bool = False):
159        logger.debug("update_band(%s)", apply)
160        index_start = self.band_list.model().index(self.band_list.currentIndex(), 1)
161        index_stop = self.band_list.model().index(self.band_list.currentIndex(), 2)
162        start = int(self.band_list.model().data(index_start, QtCore.Qt.ItemDataRole).value())
163        stop = int(self.band_list.model().data(index_stop, QtCore.Qt.ItemDataRole).value())
164
165        if self.padding > 0:
166            span = stop - start
167            start -= round(span * self.padding / 100)
168            start = max(1, start)
169            stop += round(span * self.padding / 100)
170
171        self.band_label.setText(
172            f"Sweep span: {format_frequency_short(start)}"
173            f" to {format_frequency_short(stop)}")
174
175        if not apply:
176            return
177
178        self.app.sweep_control.input_start.setText(
179            format_frequency_sweep(start))
180        self.app.sweep_control.input_end.setText(
181            format_frequency_sweep(stop))
182        self.app.sweep_control.input_end.textEdited.emit(
183            self.app.sweep_control.input_end.text())
184
185    def update_attenuator(self, value: 'QtWidgets.QLineEdit'):
186        try:
187            att = float(value.text())
188            assert att >= 0
189        except (ValueError, AssertionError):
190            logger.warning("Values for attenuator are absolute and with no"
191                           " minus sign, resetting.")
192            att = 0
193        logger.debug("Attenuator %sdB inline with S21 input", att)
194        value.setText(str(att))
195        self.app.s21att = att
196
197    def update_averaging(self,
198                         averages: 'QtWidgets.QLineEdit',
199                         truncs: 'QtWidgets.QLineEdit'):
200        try:
201            amount = int(averages.text())
202            truncates = int(truncs.text())
203            assert amount > 0
204            assert truncates >= 0
205            assert amount > truncates
206        except (AssertionError, ValueError):
207            logger.warning("Illegal averaging values, set default")
208            amount = 3
209            truncates = 0
210        logger.debug("update_averaging(%s, %s)", amount, truncates)
211        averages.setText(str(amount))
212        truncs.setText(str(truncates))
213        with self.app.sweep.lock:
214            self.app.sweep.properties.averages = (amount, truncates)
215
216    def update_logarithmic(self, logarithmic: bool):
217        logger.debug("update_logarithmic(%s)", logarithmic)
218        with self.app.sweep.lock:
219            self.app.sweep.properties.logarithmic = logarithmic
220
221    def update_mode(self, mode: 'SweepMode'):
222        logger.debug("update_mode(%s)", mode)
223        with self.app.sweep.lock:
224            self.app.sweep.properties.mode = mode
225
226    def update_padding(self, padding: int):
227        logger.debug("update_padding(%s)", padding)
228        self.padding = padding
229        self.update_band()
230
231    def update_title(self, title: str = ""):
232        logger.debug("update_title(%s)", title)
233        with self.app.sweep.lock:
234            self.app.sweep.properties.name = title
235        self.app.update_sweep_title()
236