Configurable RF/TCN/ICLS/LINK4 with UI feedback

Resolves #70

Freq/Channel will turn orange when double booked.
Freq will turn red if GUARD freq was assigned.
This commit is contained in:
Raffson
2023-01-14 15:42:13 +01:00
parent ddb9d6968b
commit 88f984b0a8
37 changed files with 992 additions and 177 deletions

View File

@@ -17,9 +17,13 @@ from game.ato.flight import Flight
from game.ato.flighttype import FlightType
from game.ato.package import Package
from game.game import Game
from game.radio.RadioFrequencyContainer import RadioFrequencyContainer
from game.radio.radios import RadioFrequency
from game.radio.tacan import TacanChannel
from game.server import EventStream
from game.sim.gameupdateevents import GameUpdateEvents
from game.squadrons.squadron import Pilot, Squadron
from game.theater import NavalControlPoint
from game.theater.missiontarget import MissionTarget
from game.transfers import PendingTransfers, TransferOrder
from qt_ui.simcontroller import SimController
@@ -179,6 +183,8 @@ class PackageModel(QAbstractListModel):
self.package.remove_flight(flight)
self.endRemoveRows()
self.update_tot()
self.game_model.release_freq(flight.frequency)
self.game_model.release_tacan(flight.tacan)
def flight_at_index(self, index: QModelIndex) -> Flight:
"""Returns the flight located at the given index."""
@@ -294,6 +300,7 @@ class AtoModel(QAbstractListModel):
# noinspection PyUnresolvedReferences
self.client_slots_changed.emit()
self.on_packages_changed()
self.release_comms_for_package(package)
def on_packages_changed(self) -> None:
if self.game is not None:
@@ -342,6 +349,12 @@ class AtoModel(QAbstractListModel):
def on_sim_update(self, _events: GameUpdateEvents) -> None:
self.dataChanged.emit(self.index(0), self.index(self.rowCount()))
def release_comms_for_package(self, package: Package):
self.game_model.release_freq(package.frequency)
for f in package.flights:
self.game_model.release_freq(f.frequency)
self.game_model.release_tacan(f.tacan)
class TransferModel(QAbstractListModel):
"""The model for a ground unit transfer."""
@@ -532,6 +545,12 @@ class GameModel:
self.ato_model = AtoModel(self, self.game.blue.ato)
self.red_ato_model = AtoModel(self, self.game.red.ato)
# For UI purposes
self.allocated_freqs: list[RadioFrequency] = list()
self.allocated_tacan: list[TacanChannel] = list()
self.allocated_icls: list[int] = list()
self.init_comms_registry()
def ato_model_for(self, player: bool) -> AtoModel:
if player:
return self.ato_model
@@ -553,3 +572,43 @@ class GameModel:
if self.game is None:
raise RuntimeError("GameModel has no Game set")
return self.game
def init_comms_registry(self) -> None:
if self.game is None:
return
allocated_freqs = {None}
allocated_tacan = {None}
allocated_icls = {None}
for p in self.ato_model.ato.packages:
allocated_freqs.add(p.frequency)
for f in p.flights:
allocated_freqs.add(f.frequency)
allocated_tacan.add(f.tacan)
for cp in self.game.theater.control_points_for(True):
if isinstance(cp, NavalControlPoint):
allocated_freqs.add(cp.frequency)
allocated_freqs.add(cp.link4)
allocated_tacan.add(cp.tacan)
allocated_icls.add(cp.icls_channel)
elif isinstance(cp, RadioFrequencyContainer):
allocated_freqs.add(cp.frequency)
allocated_freqs.remove(None)
allocated_tacan.remove(None)
allocated_icls.remove(None)
self.allocated_freqs = list(allocated_freqs)
self.allocated_tacan = list(allocated_tacan)
self.allocated_icls = list(allocated_icls)
def release_freq(self, freq: RadioFrequency):
if freq:
try:
self.allocated_freqs.remove(freq)
except ValueError:
pass
def release_tacan(self, tacan: TacanChannel):
if tacan:
try:
self.allocated_tacan.remove(tacan)
except ValueError:
pass

View File

@@ -0,0 +1,101 @@
from PySide2.QtCore import Signal
from PySide2.QtWidgets import (
QHBoxLayout,
QLabel,
QPushButton,
QWidget,
)
from game.ato import Flight
from game.radio.RadioFrequencyContainer import RadioFrequencyContainer
from game.radio.radios import RadioFrequency, RadioRange, MHz, kHz
from qt_ui.models import GameModel
from qt_ui.windows.QRadioFrequencyDialog import QRadioFrequencyDialog
class QFrequencyWidget(QWidget):
freq_changed = Signal(QWidget)
def __init__(
self, container: RadioFrequencyContainer, game_model: GameModel
) -> None:
super().__init__()
self.ct = container
self.gm = game_model
columns = QHBoxLayout()
self.setLayout(columns)
self.freq = QLabel(self._get_label_text())
self.check_freq()
columns.addWidget(self.freq)
columns.addStretch()
self.set_freq_btn = QPushButton("Set FREQ")
self.set_freq_btn.setProperty("class", "comms")
self.set_freq_btn.setFixedWidth(100)
columns.addWidget(self.set_freq_btn)
self.set_freq_btn.clicked.connect(self.open_freq_dialog)
self.reset_freq_btn = QPushButton("Reset FREQ")
self.reset_freq_btn.setProperty("class", "btn-danger comms")
self.reset_freq_btn.setFixedWidth(100)
columns.addWidget(self.reset_freq_btn)
self.reset_freq_btn.clicked.connect(self.reset_freq)
def _get_label_text(self) -> str:
freq = "AUTO" if self.ct.frequency is None else self.ct.frequency
return f"<b>FREQ: {freq}</b>"
def open_freq_dialog(self) -> None:
range = 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)
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()
self.ct.frequency = RadioFrequency(hertz=hz)
self.gm.allocated_freqs.append(self.ct.frequency)
self.freq.setText(self._get_label_text())
self.check_freq()
self.freq_changed.emit(self)
def reset_freq(self) -> None:
self._try_remove()
self.ct.frequency = None
self.freq.setText(self._get_label_text())
self._reset_color_and_tooltip()
self.freq_changed.emit(self)
def check_freq(self) -> None:
if self.ct.frequency is None:
return
if self.gm.allocated_freqs.count(self.ct.frequency) > 1:
self.freq.setStyleSheet("color: orange")
self.freq.setToolTip(
"Double booked frequency, verify that this was your intention."
)
elif self.gm.allocated_freqs.count(self.ct.frequency) == 1:
self._reset_color_and_tooltip()
if self.ct.frequency == MHz(243) or self.ct.frequency == MHz(121, 500):
self.freq.setStyleSheet("color: red")
self.freq.setToolTip(
"GUARD Freq. was assigned, verify that this was your intention."
)
def _reset_color_and_tooltip(self):
self.freq.setStyleSheet("color: white")
self.freq.setToolTip(None)
def _try_remove(self) -> None:
try:
self.gm.allocated_freqs.remove(self.ct.frequency)
except ValueError:
pass

View File

@@ -0,0 +1,86 @@
from PySide2.QtWidgets import (
QHBoxLayout,
QLabel,
QPushButton,
QWidget,
)
from game.radio.ICLSContainer import ICLSContainer
from qt_ui.models import GameModel
from qt_ui.windows.QICLSDialog import QICLSDialog
class QICLSWidget(QWidget):
def __init__(self, container: ICLSContainer, game_model: GameModel) -> None:
super().__init__()
self.ct = container
self.gm = game_model
columns = QHBoxLayout()
self.setLayout(columns)
self.channel = QLabel(self._get_label_text())
self.check_channel()
columns.addWidget(self.channel)
columns.addStretch()
self.set_icls_btn = QPushButton("Set ICLS")
self.set_icls_btn.setProperty("class", "comms")
self.set_icls_btn.setFixedWidth(100)
columns.addWidget(self.set_icls_btn)
self.set_icls_btn.clicked.connect(self.open_icls_dialog)
self.reset_icls_btn = QPushButton("Reset ICLS")
self.reset_icls_btn.setProperty("class", "btn-danger comms")
self.reset_icls_btn.setFixedWidth(100)
columns.addWidget(self.reset_icls_btn)
self.reset_icls_btn.clicked.connect(self.reset_icls)
def _get_label_text(self) -> str:
c = "AUTO" if self.ct.icls_channel is None else self.ct.icls_channel
cs = self.ct.icls_name
cs = "" if cs is None else f" ({cs})"
return f"<b>ICLS: {c}{cs}</b>"
def open_icls_dialog(self) -> None:
self.icls_dialog = QICLSDialog(self, self.ct)
self.icls_dialog.accepted.connect(self.assign_icls)
self.icls_dialog.show()
def assign_icls(self) -> None:
self._try_remove()
self.ct.icls_channel = self.icls_dialog.icls_input.value()
self.gm.allocated_icls.append(self.ct.icls_channel)
if cs := self.icls_dialog.callsign_input.text():
self.ct.icls_name = cs.upper()
self.channel.setText(self._get_label_text())
self.check_channel()
def reset_icls(self) -> None:
self._try_remove()
self.ct.icls_channel = None
self.ct.icls_name = None
self.channel.setText(self._get_label_text())
self._reset_color_and_tooltip()
def check_channel(self) -> None:
if self.ct.icls_channel is None:
return
if self.gm.allocated_icls.count(self.ct.icls_channel) > 1:
self.channel.setStyleSheet("color: orange")
self.channel.setToolTip(
"Double booked ICLS channel, verify that this was your intention."
)
elif self.gm.allocated_icls.count(self.ct.icls_channel) == 1:
self._reset_color_and_tooltip()
def _reset_color_and_tooltip(self):
self.channel.setStyleSheet("color: white")
self.channel.setToolTip(None)
def _try_remove(self) -> None:
try:
self.gm.allocated_icls.remove(self.ct.icls_channel)
except ValueError:
pass

View File

@@ -0,0 +1,95 @@
from PySide2.QtCore import Signal
from PySide2.QtWidgets import (
QHBoxLayout,
QLabel,
QPushButton,
QWidget,
)
from game.radio.radios import RadioFrequency, RadioRange, MHz, kHz
from game.theater import NavalControlPoint
from qt_ui.models import GameModel
from qt_ui.windows.QRadioFrequencyDialog import QRadioFrequencyDialog
class QLink4Widget(QWidget):
freq_changed = Signal(QWidget)
def __init__(self, cp: NavalControlPoint, game_model: GameModel) -> None:
super().__init__()
self.cp = cp
self.gm = game_model
columns = QHBoxLayout()
self.setLayout(columns)
self.freq = QLabel(self._get_label_text())
self.check_freq()
columns.addWidget(self.freq)
columns.addStretch()
self.set_freq_btn = QPushButton("Set LINK4")
self.set_freq_btn.setProperty("class", "comms")
self.set_freq_btn.setFixedWidth(100)
columns.addWidget(self.set_freq_btn)
self.set_freq_btn.clicked.connect(self.open_freq_dialog)
self.reset_freq_btn = QPushButton("Reset LINK4")
self.reset_freq_btn.setProperty("class", "btn-danger comms")
self.reset_freq_btn.setFixedWidth(100)
columns.addWidget(self.reset_freq_btn)
self.reset_freq_btn.clicked.connect(self.reset_freq)
def _get_label_text(self) -> str:
freq = "AUTO" if self.cp.link4 is None else self.cp.link4
return f"<b>LINK4: {freq}</b>"
def open_freq_dialog(self) -> None:
range = RadioRange(MHz(225), MHz(400), kHz(25))
self.frequency_dialog = QRadioFrequencyDialog(self, self.cp, range, link4=True)
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()
self.cp.link4 = RadioFrequency(hertz=hz)
self.gm.allocated_freqs.append(self.cp.link4)
self.freq.setText(self._get_label_text())
self.check_freq()
self.freq_changed.emit(self)
def reset_freq(self):
self._try_remove()
self.cp.link4 = None
self.freq.setText(self._get_label_text())
self._reset_color_and_tooltip()
self.freq_changed.emit(self)
def check_freq(self):
if self.cp.link4 is None:
return
if self.gm.allocated_freqs.count(self.cp.link4) > 1:
self.freq.setStyleSheet("color: orange")
self.freq.setToolTip(
"Double booked frequency, verify that this was your intention."
)
elif self.gm.allocated_freqs.count(self.cp.link4) == 1:
self._reset_color_and_tooltip()
if self.cp.link4 == MHz(243) or self.cp.link4 == MHz(121, 500):
self.freq.setStyleSheet("color: red")
self.freq.setToolTip(
"GUARD Freq. was assigned, verify that this was your intention."
)
def _reset_color_and_tooltip(self):
self.freq.setStyleSheet("color: white")
self.freq.setToolTip(None)
def _try_remove(self):
try:
self.gm.allocated_freqs.remove(self.cp.link4)
except ValueError:
pass

View File

@@ -0,0 +1,90 @@
from PySide2.QtWidgets import (
QHBoxLayout,
QLabel,
QPushButton,
QWidget,
)
from game.radio.TacanContainer import TacanContainer
from game.radio.tacan import TacanChannel, TacanBand
from qt_ui.models import GameModel
from qt_ui.windows.QTacanDialog import QTacanDialog
class QTacanWidget(QWidget):
def __init__(self, container: TacanContainer, game_model: GameModel) -> None:
super().__init__()
self.ct = container
self.gm = game_model
columns = QHBoxLayout()
self.setLayout(columns)
self.channel = QLabel(self._get_label_text())
self.check_channel()
columns.addWidget(self.channel)
columns.addStretch()
self.set_tacan_btn = QPushButton("Set TACAN")
self.set_tacan_btn.setProperty("class", "comms")
self.set_tacan_btn.setFixedWidth(100)
columns.addWidget(self.set_tacan_btn)
self.set_tacan_btn.clicked.connect(self.open_tacan_dialog)
self.reset_tacan_btn = QPushButton("Reset TACAN")
self.reset_tacan_btn.setProperty("class", "btn-danger comms")
self.reset_tacan_btn.setFixedWidth(100)
columns.addWidget(self.reset_tacan_btn)
self.reset_tacan_btn.clicked.connect(self.reset_tacan)
def _get_label_text(self) -> str:
c = "AUTO" if self.ct.tacan is None else self.ct.tacan
cs = self.ct.tcn_name
cs = "" if cs is None else f" ({cs})"
return f"<b>TACAN: {c}{cs}</b>"
def open_tacan_dialog(self) -> None:
self.tacan_dialog = QTacanDialog(self, self.ct)
self.tacan_dialog.accepted.connect(self.assign_tacan)
self.tacan_dialog.show()
def assign_tacan(self) -> None:
channel = self.tacan_dialog.tacan_input.value()
band = self.tacan_dialog.band_input.currentText()
band = TacanBand.X if band == "X" else TacanBand.Y
self._try_remove()
self.ct.tacan = TacanChannel(number=channel, band=band)
self.gm.allocated_tacan.append(self.ct.tacan)
if cs := self.tacan_dialog.callsign_input.text():
self.ct.tcn_name = cs.upper()
self.channel.setText(self._get_label_text())
self.check_channel()
def reset_tacan(self) -> None:
self._try_remove()
self.ct.tacan = None
self.ct.tcn_name = None
self.channel.setText(self._get_label_text())
self._reset_color_and_tooltip()
def check_channel(self) -> None:
if self.ct.tacan is None:
return
if self.gm.allocated_tacan.count(self.ct.tacan) > 1:
self.channel.setStyleSheet("color: orange")
self.channel.setToolTip(
"Double booked TACAN channel, verify that this was your intention."
)
elif self.gm.allocated_tacan.count(self.ct.tacan) == 1:
self._reset_color_and_tooltip()
def _reset_color_and_tooltip(self):
self.channel.setStyleSheet("color: white")
self.channel.setToolTip(None)
def _try_remove(self) -> None:
try:
self.gm.allocated_tacan.remove(self.ct.tacan)
except ValueError:
pass

View File

@@ -0,0 +1,54 @@
from typing import Optional
from PySide2.QtCore import Qt
from PySide2.QtWidgets import (
QDialog,
QPushButton,
QLabel,
QHBoxLayout,
QSpinBox,
QLineEdit,
)
from game.radio.ICLSContainer import ICLSContainer
from qt_ui.uiconstants import EVENT_ICONS
class QICLSDialog(QDialog):
def __init__(self, parent=None, container: Optional[ICLSContainer] = None) -> None:
super().__init__(parent=parent)
self.container = container
self.setMinimumWidth(400)
# Make dialog modal to prevent background windows to close unexpectedly.
self.setModal(True)
self.setWindowTitle("Assign ICLS")
self.setWindowIcon(EVENT_ICONS["strike"])
layout = QHBoxLayout()
self.icls_label = QLabel("ICLS:")
self.icls_input = QSpinBox()
self.icls_input.setRange(1, 20)
self.icls_input.setSingleStep(1)
layout.addWidget(self.icls_label)
layout.addStretch()
layout.addWidget(self.icls_input)
self.callsign_input = QLineEdit()
self.callsign_input.setMaxLength(3)
self.callsign_input.setMaximumWidth(50)
layout.addWidget(self.callsign_input)
layout.addStretch()
self.create_button = QPushButton("Save")
self.create_button.clicked.connect(self.accept)
layout.addWidget(self.create_button, alignment=Qt.AlignRight)
self.setLayout(layout)
if container is not None:
if container.icls_name is not None:
self.callsign_input.setText(container.icls_name)
if container.icls_channel is not None:
self.icls_input.setValue(container.icls_channel)

View File

@@ -404,6 +404,7 @@ class QLiberationWindow(QMainWindow):
self.info_panel.setGame(game)
self.sim_controller.set_game(game)
self.game_model.set(self.game)
self.game_model.init_comms_registry()
except AttributeError:
logging.exception("Incompatible save game")
QMessageBox.critical(
@@ -530,6 +531,7 @@ class QLiberationWindow(QMainWindow):
logging.info("On Debriefing")
self.debriefing = QDebriefingWindow(debrief)
self.debriefing.show()
self.game_model.init_comms_registry()
def open_tgo_info_dialog(self, tgo: TheaterGroundObject) -> None:
QGroundObjectMenu(self, tgo, tgo.control_point, self.game).show()

View File

@@ -0,0 +1,60 @@
from typing import Optional
from PySide2.QtCore import Qt, QLocale
from PySide2.QtWidgets import (
QDialog,
QPushButton,
QLabel,
QHBoxLayout,
QDoubleSpinBox,
)
from game.radio.Link4Container import Link4Container
from game.radio.RadioFrequencyContainer import RadioFrequencyContainer
from game.radio.radios import RadioRange, kHz, MHz
from qt_ui.uiconstants import EVENT_ICONS
class QRadioFrequencyDialog(QDialog):
def __init__(
self,
parent=None,
container: Optional[RadioFrequencyContainer] = None,
range: RadioRange = RadioRange(MHz(225), MHz(400), kHz(25)),
link4: bool = False,
) -> None:
super().__init__(parent=parent)
self.container = container
self.setMinimumWidth(400)
# Make dialog modal to prevent background windows to close unexpectedly.
self.setModal(True)
self.setWindowTitle("Assign frequency")
self.setWindowIcon(EVENT_ICONS["strike"])
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.setSingleStep(range.step.mhz)
self.frequency_input.setDecimals(3)
self.frequency_input.setLocale(QLocale(QLocale.Language.English))
self.frequency_input.setValue(225.0)
layout.addWidget(self.frequency_label)
layout.addWidget(self.frequency_input)
self.create_button = QPushButton("Save")
self.create_button.clicked.connect(self.accept)
layout.addWidget(self.create_button, alignment=Qt.AlignRight)
self.setLayout(layout)
if link4 and isinstance(container, Link4Container):
if container.link4:
self.frequency_input.setValue(container.link4.mhz)
elif container.frequency:
self.frequency_input.setValue(container.frequency.mhz)

View File

@@ -0,0 +1,59 @@
from typing import Optional
from PySide2.QtCore import Qt
from PySide2.QtWidgets import (
QDialog,
QPushButton,
QLabel,
QHBoxLayout,
QSpinBox,
QComboBox,
QLineEdit,
)
from game.radio.TacanContainer import TacanContainer
from qt_ui.uiconstants import EVENT_ICONS
class QTacanDialog(QDialog):
def __init__(self, parent=None, container: Optional[TacanContainer] = None) -> None:
super().__init__(parent=parent)
self.container = container
self.setMinimumWidth(400)
# Make dialog modal to prevent background windows to close unexpectedly.
self.setModal(True)
self.setWindowTitle("Assign TACAN")
self.setWindowIcon(EVENT_ICONS["strike"])
layout = QHBoxLayout()
self.tacan_label = QLabel("TACAN:")
self.tacan_input = QSpinBox()
self.tacan_input.setRange(1, 126)
self.tacan_input.setSingleStep(1)
layout.addWidget(self.tacan_label)
layout.addStretch()
layout.addWidget(self.tacan_input)
self.band_input = QComboBox()
self.band_input.addItems(["X", "Y"])
layout.addWidget(self.band_input)
self.callsign_input = QLineEdit()
self.callsign_input.setMaxLength(3)
self.callsign_input.setMaximumWidth(50)
layout.addWidget(self.callsign_input)
layout.addStretch()
self.create_button = QPushButton("Save")
self.create_button.clicked.connect(self.accept)
layout.addWidget(self.create_button, alignment=Qt.AlignRight)
self.setLayout(layout)
if container is not None:
if container.tcn_name is not None:
self.callsign_input.setText(container.tcn_name)
if container.tacan is not None:
self.tacan_input.setValue(container.tacan.number)
self.band_input.setCurrentText(container.tacan.band.value)

View File

@@ -8,11 +8,15 @@ from PySide2.QtWidgets import (
QPushButton,
QVBoxLayout,
QWidget,
QGridLayout,
)
from game import Game
from game.ato.flighttype import FlightType
from game.config import RUNWAY_REPAIR_COST
from game.radio.ICLSContainer import ICLSContainer
from game.radio.RadioFrequencyContainer import RadioFrequencyContainer
from game.radio.TacanContainer import TacanContainer
from game.server import EventStream
from game.sim import GameUpdateEvents
from game.theater import (
@@ -20,10 +24,15 @@ from game.theater import (
ControlPoint,
ControlPointType,
FREE_FRONTLINE_UNIT_SUPPLY,
NavalControlPoint,
)
from qt_ui.dialogs import Dialog
from qt_ui.models import GameModel
from qt_ui.uiconstants import EVENT_ICONS
from qt_ui.widgets.QFrequencyWidget import QFrequencyWidget
from qt_ui.widgets.QICLSWidget import QICLSWidget
from qt_ui.widgets.QLink4Widget import QLink4Widget
from qt_ui.widgets.QTacanWidget import QTacanWidget
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
from qt_ui.windows.basemenu.NewUnitTransferDialog import NewUnitTransferDialog
from qt_ui.windows.basemenu.QBaseMenuTabs import QBaseMenuTabs
@@ -60,13 +69,49 @@ class QBaseMenu2(QDialog):
pixmap = QPixmap(self.get_base_image())
header.setPixmap(pixmap)
cp_settings = QGridLayout()
top_layout.addLayout(cp_settings)
title = QLabel("<b>" + self.cp.name + "</b>")
title.setAlignment(Qt.AlignLeft | Qt.AlignTop)
title.setProperty("style", "base-title")
cp_settings.addWidget(title, 0, 0, 1, 2)
cp_settings.setHorizontalSpacing(20)
counter = 2
self.freq_widget = None
self.link4_widget = None
is_friendly = cp.is_friendly(True)
if is_friendly and isinstance(cp, RadioFrequencyContainer):
self.freq_widget = QFrequencyWidget(cp, self.game_model)
cp_settings.addWidget(self.freq_widget, counter // 2, counter % 2)
counter += 1
if is_friendly and isinstance(cp, TacanContainer):
self.tacan_widget = QTacanWidget(cp, self.game_model)
cp_settings.addWidget(self.tacan_widget, counter // 2, counter % 2)
counter += 1
if is_friendly and isinstance(cp, ICLSContainer):
self.icls_widget = QICLSWidget(cp, self.game_model)
cp_settings.addWidget(self.icls_widget, counter // 2, counter % 2)
counter += 1
if is_friendly and isinstance(cp, NavalControlPoint):
self.link4_widget = QLink4Widget(cp, self.game_model)
cp_settings.addWidget(self.link4_widget, counter // 2, counter % 2)
counter += 1
if self.freq_widget and self.link4_widget:
# link them so on change they check freq
self.freq_widget.freq_changed.connect(self.link4_widget.check_freq)
self.link4_widget.freq_changed.connect(self.freq_widget.check_freq)
self.intel_summary = QLabel()
self.intel_summary.setToolTip(self.generate_intel_tooltip())
self.update_intel_summary()
top_layout.addWidget(title)
top_layout.addWidget(self.intel_summary)
top_layout.setAlignment(Qt.AlignTop)
@@ -127,6 +172,7 @@ class QBaseMenu2(QDialog):
GameUpdateSignal.get_instance().updateGame(self.game_model.game)
state = self.game_model.game.check_win_loss()
GameUpdateSignal.get_instance().gameStateChanged(state)
self.close()
@property
def has_transfer_destinations(self) -> bool:

View File

@@ -32,7 +32,7 @@ class QEditFlightDialog(QDialog):
layout = QVBoxLayout()
self.flight_planner = QFlightPlanner(package_model, flight, game_model.game)
self.flight_planner = QFlightPlanner(package_model, flight, game_model)
layout.addWidget(self.flight_planner)
self.setLayout(layout)

View File

@@ -26,9 +26,10 @@ from game.sim import GameUpdateEvents
from game.theater.missiontarget import MissionTarget
from qt_ui.models import AtoModel, GameModel, PackageModel
from qt_ui.uiconstants import EVENT_ICONS
from qt_ui.widgets.QFrequencyWidget import QFrequencyWidget
from qt_ui.widgets.ato import QFlightList
from qt_ui.windows.QRadioFrequencyDialog import QRadioFrequencyDialog
from qt_ui.windows.mission.flight.QFlightCreator import QFlightCreator
from qt_ui.windows.mission.package.QPackageFrequency import QPackageFrequency
class QPackageDialog(QDialog):
@@ -70,22 +71,9 @@ class QPackageDialog(QDialog):
package_type_row.addWidget(self.package_type_text)
self.package_type_column.addLayout(package_type_row)
# TODO: make freq red if used by another package
package_freq_row = QHBoxLayout()
self.package_freq_label = QLabel("Package FREQ:")
freq = (
self.package_model.package.frequency.mhz
if self.package_model.package.frequency is not None
else ""
)
self.package_freq_text = QLabel(f"{freq}")
package_freq_row.addWidget(self.package_freq_label)
package_freq_row.addWidget(self.package_freq_text)
self.package_type_column.addLayout(package_freq_row)
self.summary_row.addStretch(1)
self.package_name_column = QVBoxLayout()
self.package_name_column = QHBoxLayout()
self.summary_row.addLayout(self.package_name_column)
self.package_name_label = QLabel("Package Name:")
self.package_name_label.setAlignment(Qt.AlignCenter)
@@ -145,16 +133,17 @@ class QPackageDialog(QDialog):
self.delete_flight_button.setEnabled(model.rowCount() > 0)
self.button_layout.addWidget(self.delete_flight_button)
self.open_radio_button = QPushButton("Set Package FREQ")
self.open_radio_button.clicked.connect(self.on_open_radio)
self.button_layout.addWidget(self.open_radio_button)
self.button_layout.addStretch()
self.package_model.tot_changed.connect(self.update_tot)
self.freq_widget = QFrequencyWidget(self.package_model.package, game_model)
self.button_layout.addWidget(self.freq_widget)
self.button_layout.addStretch()
self.setLayout(self.layout)
self.package_model.tot_changed.connect(self.update_tot)
self.accepted.connect(self.on_save)
self.finished.connect(self.on_close)
self.rejected.connect(self.on_cancel)
@@ -238,8 +227,8 @@ class QPackageDialog(QDialog):
self.package_model.package.custom_name = self.package_name_text.text()
def on_open_radio(self) -> None:
self.package_frequency_dialog = QPackageFrequency(
self.game, self.package_model.package, parent=self.window()
self.package_frequency_dialog = QRadioFrequencyDialog(
parent=self.window(), container=self.package_model.package
)
self.package_frequency_dialog.accepted.connect(self.assign_frequency)
self.package_frequency_dialog.show()
@@ -251,8 +240,11 @@ class QPackageDialog(QDialog):
def on_package_changed(self):
self.package_type_text.setText(self.package_model.description)
if (freq := self.package_model.package.frequency) is not None:
self.package_freq_text.setText(f"{freq.mhz} MHz")
self.freq_widget.check_freq()
def on_reset_radio(self):
self.package_model.package.frequency = None
self.package_freq_text.setText("AUTO")
class QNewPackageDialog(QPackageDialog):

View File

@@ -1,8 +1,7 @@
from PySide2.QtWidgets import QTabWidget
from game import Game
from game.ato.flight import Flight
from qt_ui.models import PackageModel
from qt_ui.models import PackageModel, GameModel
from qt_ui.windows.mission.flight.payload.QFlightPayloadTab import QFlightPayloadTab
from qt_ui.windows.mission.flight.settings.QGeneralFlightSettingsTab import (
QGeneralFlightSettingsTab,
@@ -11,14 +10,12 @@ from qt_ui.windows.mission.flight.waypoints.QFlightWaypointTab import QFlightWay
class QFlightPlanner(QTabWidget):
def __init__(self, package_model: PackageModel, flight: Flight, game: Game):
def __init__(self, package_model: PackageModel, flight: Flight, gm: GameModel):
super().__init__()
self.general_settings_tab = QGeneralFlightSettingsTab(
game, package_model, flight
)
self.payload_tab = QFlightPayloadTab(flight, game)
self.waypoint_tab = QFlightWaypointTab(game, package_model.package, flight)
self.general_settings_tab = QGeneralFlightSettingsTab(gm, package_model, flight)
self.payload_tab = QFlightPayloadTab(flight, gm.game)
self.waypoint_tab = QFlightWaypointTab(gm.game, package_model.package, flight)
self.waypoint_tab.loadout_changed.connect(self.payload_tab.reload_from_flight)
self.addTab(self.general_settings_tab, "General Flight settings")
self.addTab(self.payload_tab, "Payload")

View File

@@ -4,6 +4,8 @@ from PySide2.QtWidgets import (
QFrame,
QLabel,
QVBoxLayout,
QScrollArea,
QWidget,
)
from game import Game
@@ -42,7 +44,12 @@ class QFlightPayloadTab(QFrame):
docsText.setAlignment(Qt.AlignCenter)
docsText.setOpenExternalLinks(True)
layout.addLayout(PropertyEditor(self.flight))
self.scroll_area = QScrollArea()
self.property_editor = QWidget()
self.property_editor.setLayout(PropertyEditor(self.flight))
self.scroll_area.setWidget(self.property_editor)
layout.addWidget(self.scroll_area)
self.loadout_selector = DcsLoadoutSelector(flight)
self.loadout_selector.currentIndexChanged.connect(self.on_new_loadout)
layout.addWidget(self.loadout_selector)

View File

@@ -30,7 +30,6 @@ class QLoadoutEditor(QGroupBox):
layout.addWidget(QPylonEditor(game, flight, pylon), i, 1)
vbox.addLayout(layout)
vbox.addStretch()
self.setLayout(vbox)
for i in self.findChildren(QPylonEditor):

View File

@@ -0,0 +1,25 @@
from PySide2.QtWidgets import QGroupBox, QVBoxLayout
from game.ato import Flight, FlightType
from qt_ui.models import GameModel
from qt_ui.widgets.QFrequencyWidget import QFrequencyWidget
from qt_ui.widgets.QTacanWidget import QTacanWidget
class QCommsEditor(QGroupBox):
def __init__(self, flight: Flight, game: GameModel):
title = "Intra-Flight Frequency"
layout = QVBoxLayout()
is_refuel = flight.flight_type == FlightType.REFUELING
has_tacan = flight.unit_type.dcs_unit_type.tacan
layout.addWidget(QFrequencyWidget(flight, game))
if is_refuel and has_tacan:
layout.addWidget(QTacanWidget(flight, game))
title = title + " / TACAN"
super(QCommsEditor, self).__init__(title)
self.flight = flight
self.setLayout(layout)

View File

@@ -1,6 +1,6 @@
from typing import Optional
from PySide2.QtWidgets import QGroupBox, QVBoxLayout, QLineEdit, QLabel, QMessageBox
from PySide2.QtWidgets import QGroupBox, QHBoxLayout, QLineEdit, QLabel, QMessageBox
from game.ato.flight import Flight
@@ -10,7 +10,7 @@ class QFlightCustomName(QGroupBox):
self.flight = flight
self.layout = QVBoxLayout()
self.layout = QHBoxLayout()
self.custom_name_label = QLabel(f"Custom Name:")
self.custom_name_input = QLineEdit(flight.custom_name)
self.custom_name_input.textChanged.connect(self.on_change)

View File

@@ -3,10 +3,11 @@ from PySide2.QtWidgets import QFrame, QGridLayout, QVBoxLayout
from game import Game
from game.ato.flight import Flight
from qt_ui.models import PackageModel
from qt_ui.models import PackageModel, GameModel
from qt_ui.windows.mission.flight.settings.FlightAirfieldDisplay import (
FlightAirfieldDisplay,
)
from qt_ui.windows.mission.flight.settings.QCommsEditor import QCommsEditor
from qt_ui.windows.mission.flight.settings.QCustomName import QFlightCustomName
from qt_ui.windows.mission.flight.settings.QFlightSlotEditor import QFlightSlotEditor
from qt_ui.windows.mission.flight.settings.QFlightStartType import QFlightStartType
@@ -18,16 +19,23 @@ from qt_ui.windows.mission.flight.settings.QFlightTypeTaskInfo import (
class QGeneralFlightSettingsTab(QFrame):
on_flight_settings_changed = Signal()
def __init__(self, game: Game, package_model: PackageModel, flight: Flight):
def __init__(self, game: GameModel, package_model: PackageModel, flight: Flight):
super().__init__()
widgets = [
QFlightTypeTaskInfo(flight),
QCommsEditor(flight, game),
FlightAirfieldDisplay(game.game, package_model, flight),
QFlightSlotEditor(package_model, flight, game.game),
QFlightStartType(package_model, flight),
QFlightCustomName(flight),
]
layout = QGridLayout()
layout.addWidget(QFlightTypeTaskInfo(flight), 0, 0)
layout.addWidget(FlightAirfieldDisplay(game, package_model, flight), 1, 0)
layout.addWidget(QFlightSlotEditor(package_model, flight, game), 2, 0)
layout.addWidget(QFlightStartType(package_model, flight), 3, 0)
layout.addWidget(QFlightCustomName(flight), 4, 0)
row = 0
for w in widgets:
layout.addWidget(w, row, 0)
row += 1
vstretch = QVBoxLayout()
vstretch.addStretch()
layout.addLayout(vstretch, 5, 0)
layout.addLayout(vstretch, row, 0)
self.setLayout(layout)

View File

@@ -1,47 +0,0 @@
from typing import Optional, Type
from PySide2.QtCore import Qt
from PySide2.QtWidgets import (
QDialog,
QPushButton,
QVBoxLayout,
QLabel,
QHBoxLayout,
QDoubleSpinBox,
)
from game import Game
from game.ato.package import Package
from game.radio.radios import RadioRegistry
from qt_ui.uiconstants import EVENT_ICONS
class QPackageFrequency(QDialog):
def __init__(self, game: Game, package: Package, parent=None) -> None:
super().__init__(parent=parent)
self.setMinimumWidth(400)
self.game = game
self.package = package
# Make dialog modal to prevent background windows to close unexpectedly.
self.setModal(True)
self.setWindowTitle("Assign frequency")
self.setWindowIcon(EVENT_ICONS["strike"])
layout = QHBoxLayout()
self.frequency_label = QLabel("FREQ (Mhz):")
self.frequency_input = QDoubleSpinBox()
self.frequency_input.setRange(225, 399.975)
self.frequency_input.setSingleStep(0.025)
self.frequency_input.setDecimals(3)
layout.addWidget(self.frequency_label)
layout.addWidget(self.frequency_input)
self.create_button = QPushButton("Save")
self.create_button.clicked.connect(self.accept)
layout.addWidget(self.create_button, alignment=Qt.AlignRight)
self.setLayout(layout)