diff --git a/changelog.md b/changelog.md index 8eb68aa0..535dc50e 100644 --- a/changelog.md +++ b/changelog.md @@ -18,6 +18,7 @@ * **[Waypoints]** Allow user to add navigation waypoints where possible without degrading to a custom flight-plan * **[Campaign Management]** Improve squadron retreat logic to account for parking-slot sizes * **[Autoplanner]** Support for auto-planning Air Assaults +* **[UI]** Improved frequency selector to support all modeled bands for every aircraft's intra-flight radio ## Fixes * **[Mission Generation]** Anti-ship strikes should use "group attack" in their attack-task diff --git a/game/radio/radios.py b/game/radio/radios.py index a0f2e37c..cf02f040 100644 --- a/game/radio/radios.py +++ b/game/radio/radios.py @@ -190,8 +190,8 @@ RADIOS: List[Radio] = [ Radio( "AN/ARC-222", ( - RadioRange(MHz(30), MHz(88), kHz(25), Modulation.FM), RadioRange(MHz(116), MHz(152), kHz(25), Modulation.AM), + RadioRange(MHz(30), MHz(88), kHz(25), Modulation.FM), ), ), Radio("SCR-522", (RadioRange(MHz(100), MHz(156), kHz(25), Modulation.AM),)), @@ -200,8 +200,8 @@ RADIOS: List[Radio] = [ Radio( "TRT ERA 7000 V/UHF", ( - RadioRange(MHz(118), MHz(150), kHz(25), Modulation.AM), RadioRange(MHz(225), MHz(400), kHz(25), Modulation.AM), + RadioRange(MHz(118), MHz(150), kHz(25), Modulation.AM), ), ), Radio( @@ -247,10 +247,10 @@ RADIOS: List[Radio] = [ Radio( "R-863", ( - RadioRange(MHz(100), MHz(150), kHz(25), Modulation.AM), RadioRange(MHz(220), MHz(400), kHz(25), Modulation.AM), - RadioRange(MHz(100), MHz(150), kHz(25), Modulation.FM), + RadioRange(MHz(100), MHz(150), kHz(25), Modulation.AM), RadioRange(MHz(220), MHz(400), kHz(25), Modulation.FM), + RadioRange(MHz(100), MHz(150), kHz(25), Modulation.FM), ), ), # UH-1H @@ -263,8 +263,8 @@ RADIOS: List[Radio] = [ Radio( "V/UHF TRAP 136", ( - RadioRange(MHz(118), MHz(144), kHz(25), Modulation.AM), RadioRange(MHz(225), MHz(400), kHz(25), Modulation.AM), + RadioRange(MHz(118), MHz(144), kHz(25), Modulation.AM), ), ), Radio("UHF TRAP 137B", (RadioRange(MHz(225), MHz(400), kHz(25), Modulation.AM),)), @@ -272,9 +272,9 @@ RADIOS: List[Radio] = [ Radio( "R-800", ( - RadioRange(MHz(30), MHz(88), kHz(25), Modulation.AM), - RadioRange(MHz(108), MHz(174), kHz(25), Modulation.AM), RadioRange(MHz(225), MHz(400), kHz(25), Modulation.AM), + RadioRange(MHz(108), MHz(174), kHz(25), Modulation.AM), + RadioRange(MHz(30), MHz(88), kHz(25), Modulation.AM), ), ), # MB-339A @@ -294,18 +294,11 @@ RADIOS: List[Radio] = [ "SRT-651/N", ( RadioRange( - MHz(30), - MHz(88), + MHz(225), + MHz(400), kHz(25), - Modulation.FM, - frozenset((MHz(40, 500),)), - ), - RadioRange( - MHz(108), - MHz(156), - kHz(25), - Modulation.AM, - frozenset((MHz(121, 500),)), + Modulation.AM, # Actually AM/FM, but we can't represent that. + frozenset((MHz(243),)), ), RadioRange( MHz(156), @@ -315,11 +308,18 @@ RADIOS: List[Radio] = [ frozenset((MHz(156, 800),)), ), RadioRange( - MHz(225), - MHz(400), + MHz(108), + MHz(156), kHz(25), - Modulation.AM, # Actually AM/FM, but we can't represent that. - frozenset((MHz(243),)), + Modulation.AM, + frozenset((MHz(121, 500),)), + ), + RadioRange( + MHz(30), + MHz(88), + kHz(25), + Modulation.FM, + frozenset((MHz(40, 500),)), ), ), ), diff --git a/qt_ui/widgets/QFrequencyWidget.py b/qt_ui/widgets/QFrequencyWidget.py index 148b2c3a..0e57dddc 100644 --- a/qt_ui/widgets/QFrequencyWidget.py +++ b/qt_ui/widgets/QFrequencyWidget.py @@ -49,22 +49,18 @@ class QFrequencyWidget(QWidget): return f"FREQ: {freq}" def open_freq_dialog(self) -> None: - range = RadioRange(MHz(100), MHz(400), kHz(25)) + ranges = [RadioRange(MHz(30), MHz(400), kHz(25))] if isinstance(self.ct, Flight): if self.ct.unit_type.intra_flight_radio is not None: - range = self.ct.unit_type.intra_flight_radio.ranges[0] - self.frequency_dialog = QRadioFrequencyDialog(self, self.ct, range) + ranges = self.ct.unit_type.intra_flight_radio.ranges + self.frequency_dialog = QRadioFrequencyDialog(self, self.ct, ranges) self.frequency_dialog.accepted.connect(self.assign_frequency) self.frequency_dialog.show() def assign_frequency(self) -> None: hz = round(self.frequency_dialog.frequency_input.value() * 10**6) self._try_remove() - mod = RadioFrequency.modulation - if isinstance(self.ct, Flight): - if self.ct.unit_type.intra_flight_radio is not None: - range = self.ct.unit_type.intra_flight_radio.ranges[0] - mod = range.modulation + mod = self.frequency_dialog.frequency_modulation.currentData() self.ct.frequency = RadioFrequency(hertz=hz, modulation=mod) self.gm.allocated_freqs.append(self.ct.frequency) self.freq.setText(self._get_label_text()) diff --git a/qt_ui/widgets/QLink4Widget.py b/qt_ui/widgets/QLink4Widget.py index dc0647ed..bbced2c2 100644 --- a/qt_ui/widgets/QLink4Widget.py +++ b/qt_ui/widgets/QLink4Widget.py @@ -46,8 +46,8 @@ class QLink4Widget(QWidget): return f"LINK4: {freq}" def open_freq_dialog(self) -> None: - range = RadioRange(MHz(225), MHz(400), kHz(25)) - self.frequency_dialog = QRadioFrequencyDialog(self, self.cp, range, link4=True) + ranges = [RadioRange(MHz(225), MHz(400), kHz(25))] + self.frequency_dialog = QRadioFrequencyDialog(self, self.cp, ranges, link4=True) self.frequency_dialog.accepted.connect(self.assign_frequency) self.frequency_dialog.show() diff --git a/qt_ui/windows/QRadioFrequencyDialog.py b/qt_ui/windows/QRadioFrequencyDialog.py index 3c69a212..c35c274a 100644 --- a/qt_ui/windows/QRadioFrequencyDialog.py +++ b/qt_ui/windows/QRadioFrequencyDialog.py @@ -1,12 +1,14 @@ -from typing import Optional +from typing import Optional, Iterable from PySide2.QtCore import Qt, QLocale +from PySide2.QtGui import QIcon from PySide2.QtWidgets import ( QDialog, QPushButton, QLabel, QHBoxLayout, QDoubleSpinBox, + QComboBox, ) from game.radio.Link4Container import Link4Container @@ -15,12 +17,80 @@ from game.radio.radios import RadioRange, kHz, MHz from qt_ui.uiconstants import EVENT_ICONS +class QFrequencySpinbox(QDoubleSpinBox): + def __init__(self, ranges: Iterable[RadioRange]) -> None: + super().__init__() + self.setLocale(QLocale(QLocale.Language.English)) + self.setDecimals(3) + self.ranges = list(ranges) + first = True + for r in ranges: + if r.minimum.mhz < self.minimum(): + self.setMinimum(r.minimum.mhz) + if self.maximum() < r.maximum.mhz: + self.setMaximum(r.maximum.mhz) + if first: + self.setSingleStep(r.step.mhz) + self.setValue(r.minimum.mhz) + first = False + + def correct_value(self, value: float) -> None: + for r in self.ranges: + if r.maximum.mhz == value: + self.setValue(value - r.step.mhz) + return + + def stepBy(self, steps: int) -> None: + new_value = self.check_value(self.value() + (steps * self.singleStep())) + self.setValue(new_value) + + def check_value(self, value: float) -> float: + for r in self.ranges: + if r.minimum.mhz <= value < r.maximum.mhz: + self.setSingleStep(r.step.mhz) + return value + minimums = [m for m in set(r.minimum.mhz for r in self.ranges) if m > value] + maximums = [m for m in set(r.maximum.mhz for r in self.ranges) if m <= value] + if not minimums or not maximums: + return self.value() + smallest_min = min(minimums) + largest_max = max(maximums) + if largest_max <= value < smallest_min: + if value < self.value(): + rs = [r for r in self.ranges if r.maximum.mhz == largest_max] + value = largest_max - rs[0].step.mhz + else: + rs = [r for r in self.ranges if r.minimum.mhz == smallest_min] + value = smallest_min + r = rs[0] + self.setSingleStep(r.step.mhz) + return value + raise RuntimeError() + + +class QFrequencyModulationBox(QComboBox): + def __init__(self, ranges: Iterable[RadioRange], freq: float) -> None: + super().__init__() + self.setMaximumWidth(60) + self.ranges = list(ranges) + self.update_modulations(freq) + + def update_modulations(self, freq: float) -> None: + self.modulations = set( + r.modulation for r in self.ranges if r.minimum.mhz <= freq < r.maximum.mhz + ) + self.setEnabled(len(self.modulations) > 1) + self.clear() + for i, m in enumerate(sorted(self.modulations, key=lambda x: x.name)): + self.addItem(QIcon(), m.name, m) + + class QRadioFrequencyDialog(QDialog): def __init__( self, parent=None, container: Optional[RadioFrequencyContainer] = None, - range: RadioRange = RadioRange(MHz(225), MHz(400), kHz(25)), + ranges: Iterable[RadioRange] = tuple([RadioRange(MHz(225), MHz(400), kHz(25))]), link4: bool = False, ) -> None: super().__init__(parent=parent) @@ -36,20 +106,18 @@ class QRadioFrequencyDialog(QDialog): layout = QHBoxLayout() self.frequency_label = QLabel("FREQ (MHz):") - self.frequency_input = QDoubleSpinBox() - self.frequency_input.setRange( - range.minimum.mhz, range.maximum.mhz - range.step.mhz + self.frequency_input = QFrequencySpinbox(ranges) + self.frequency_modulation = QFrequencyModulationBox( + ranges, self.frequency_input.value() ) - self.frequency_input.setSingleStep(range.step.mhz) - self.frequency_input.setDecimals(3) - self.frequency_input.setLocale(QLocale(QLocale.Language.English)) - if range.minimum.mhz <= 225.0 < range.maximum.mhz: - self.frequency_input.setValue(225.0) - else: - mid = range.minimum.mhz + (range.maximum.mhz - range.minimum.mhz) / 2 - self.frequency_input.setValue(mid) + self.frequency_input.valueChanged.connect( + self.frequency_modulation.update_modulations + ) + self.frequency_input.valueChanged.connect(self.frequency_input.correct_value) + layout.addWidget(self.frequency_label) layout.addWidget(self.frequency_input) + layout.addWidget(self.frequency_modulation) self.create_button = QPushButton("Save") self.create_button.clicked.connect(self.accept)