dcs-retribution/qt_ui/windows/AirWingDialog.py
2024-05-25 02:18:43 +02:00

310 lines
9.9 KiB
Python

from __future__ import annotations
from dataclasses import dataclass
from typing import Iterator, Optional
from PySide6.QtCore import QItemSelectionModel, QModelIndex, QSize
from PySide6.QtWidgets import (
QAbstractItemView,
QCheckBox,
QDialog,
QHBoxLayout,
QListView,
QTabWidget,
QTableWidget,
QTableWidgetItem,
QVBoxLayout,
QWidget,
QPushButton,
)
from game.ato.flight import Flight
from game.server import EventStream
from game.sim import GameUpdateEvents
from game.squadrons import Squadron
from game.theater import ConflictTheater
from qt_ui.delegates import TwoColumnRowDelegate
from qt_ui.models import AirWingModel, AtoModel, GameModel, SquadronModel
from qt_ui.simcontroller import SimController
from qt_ui.windows.AirWingConfigurationDialog import AirWingConfigurationDialog
from qt_ui.windows.SquadronDialog import SquadronDialog
from qt_ui.windows.newgame.WizardPages.QFactionSelection import QFactionUnits
class SquadronDelegate(TwoColumnRowDelegate):
def __init__(self, air_wing_model: AirWingModel) -> None:
super().__init__(rows=2, columns=2, font_size=12)
self.air_wing_model = air_wing_model
@staticmethod
def squadron(index: QModelIndex) -> Squadron:
return index.data(AirWingModel.SquadronRole)
def text_for(self, index: QModelIndex, row: int, column: int) -> str:
squadron = self.squadron(index)
if (row, column) == (0, 0):
if squadron.nickname:
nickname = f' "{squadron.nickname}"'
else:
nickname = ""
return f"{squadron.name}{nickname}"
elif (row, column) == (0, 1):
return squadron.aircraft.display_name
elif (row, column) == (1, 0):
return squadron.location.name
elif (row, column) == (1, 1):
squadron = self.squadron(index)
active = len(squadron.active_pilots)
available = len(squadron.available_pilots)
on_leave = len(squadron.pilots_on_leave)
return f"{active} active, {available} unassigned, {on_leave} on leave"
return ""
class SquadronList(QListView):
"""List view for displaying the air wing's squadrons."""
def __init__(
self,
ato_model: AtoModel,
air_wing_model: AirWingModel,
theater: ConflictTheater,
sim_controller: SimController,
) -> None:
super().__init__()
self.ato_model = ato_model
self.air_wing_model = air_wing_model
self.theater = theater
self.sim_controller = sim_controller
self.dialog: Optional[SquadronDialog] = None
self.setIconSize(QSize(91, 24))
self.setItemDelegate(SquadronDelegate(self.air_wing_model))
self.setModel(self.air_wing_model)
self.selectionModel().setCurrentIndex(
self.air_wing_model.index(0, 0, QModelIndex()),
QItemSelectionModel.SelectionFlag.Select,
)
# self.setIconSize(QSize(91, 24))
self.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectItems)
self.doubleClicked.connect(self.on_double_click)
def on_double_click(self, index: QModelIndex) -> None:
if not index.isValid():
return
self.dialog = SquadronDialog(
self.ato_model,
SquadronModel(self.air_wing_model.squadron_at_index(index)),
self.theater,
self.sim_controller,
self,
)
self.dialog.show()
@dataclass(frozen=True)
class AircraftInventoryData:
location: str
unit_type: str
task: str
target: str
pilot: str
player: str
@classmethod
def headers(cls) -> list[str]:
return ["Base", "Type", "Flight Type", "Target", "Pilot", "Player"]
@property
def columns(self) -> Iterator[str]:
yield self.location
yield self.unit_type
yield self.task
yield self.target
yield self.pilot
yield self.player
@classmethod
def from_flight(cls, flight: Flight) -> Iterator[AircraftInventoryData]:
num_units = flight.count
flight_type = flight.flight_type.value
target = flight.package.target.name
for idx in range(0, num_units):
pilot = flight.roster.pilot_at(idx)
if pilot is None:
pilot_name = "Unassigned"
player = ""
else:
pilot_name = pilot.name
player = "Player" if pilot.player else "AI"
yield AircraftInventoryData(
flight.departure.name,
flight.unit_type.display_name,
flight_type,
target,
pilot_name,
player,
)
@classmethod
def each_untasked_from_squadron(
cls, squadron: Squadron
) -> Iterator[AircraftInventoryData]:
for _ in range(0, squadron.untasked_aircraft):
yield AircraftInventoryData(
squadron.name,
squadron.aircraft.display_name,
"Idle",
"N/A",
"N/A",
"N/A",
)
class AirInventoryView(QWidget):
def __init__(self, game_model: GameModel) -> None:
super().__init__()
self.game_model = game_model
self.only_unallocated = False
self.enemy_info = False
layout = QVBoxLayout()
self.setLayout(layout)
checkbox_row = QHBoxLayout()
layout.addLayout(checkbox_row)
self.only_unallocated_cb = QCheckBox("Unallocated only")
self.only_unallocated_cb.toggled.connect(self.set_only_unallocated)
checkbox_row.addWidget(self.only_unallocated_cb)
self.enemy_info_cb = QCheckBox("Show enemy info")
self.enemy_info_cb.toggled.connect(self.set_enemy_info)
checkbox_row.addWidget(self.enemy_info_cb)
checkbox_row.addStretch()
self.table = QTableWidget()
layout.addWidget(self.table)
self.table.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)
self.table.verticalHeader().setVisible(False)
self.set_only_unallocated(False)
def set_only_unallocated(self, value: bool) -> None:
self.only_unallocated = value
self.update_table()
def set_enemy_info(self, value: bool) -> None:
self.enemy_info = value
self.update_table()
def update_table(self) -> None:
self.table.setSortingEnabled(False)
self.table.clear()
inventory_rows = list(self.get_data())
self.table.setRowCount(len(inventory_rows))
headers = AircraftInventoryData.headers()
self.table.setColumnCount(len(headers))
self.table.setHorizontalHeaderLabels(headers)
for row, data in enumerate(inventory_rows):
for column, value in enumerate(data.columns):
self.table.setItem(row, column, QTableWidgetItem(value))
self.table.resizeColumnsToContents()
self.table.setSortingEnabled(True)
def iter_allocated_aircraft(self) -> Iterator[AircraftInventoryData]:
coalition = self.game_model.game.coalition_for(not self.enemy_info)
for package in coalition.ato.packages:
for flight in package.flights:
yield from AircraftInventoryData.from_flight(flight)
def iter_unallocated_aircraft(self) -> Iterator[AircraftInventoryData]:
coalition = self.game_model.game.coalition_for(not self.enemy_info)
for squadron in coalition.air_wing.iter_squadrons():
yield from AircraftInventoryData.each_untasked_from_squadron(squadron)
def get_data(self) -> Iterator[AircraftInventoryData]:
yield from self.iter_unallocated_aircraft()
if not self.only_unallocated:
yield from self.iter_allocated_aircraft()
class AirWingTabs(QTabWidget):
def __init__(self, game_model: GameModel) -> None:
super().__init__()
self.game_model = game_model
self.addTab(
SquadronList(
game_model.ato_model,
game_model.blue_air_wing_model,
game_model.game.theater,
game_model.sim_controller,
),
"Squadrons OWNFOR",
)
self.addTab(
SquadronList(
game_model.red_ato_model,
game_model.red_air_wing_model,
game_model.game.theater,
game_model.sim_controller,
),
"Squadrons OPFOR",
)
self.addTab(AirInventoryView(game_model), "Inventory")
if game_model.game.settings.enable_air_wing_adjustments:
pb = QPushButton("Open Air Wing Config Dialog")
pb.clicked.connect(self.open_awcd)
pb.setMaximumWidth(300)
layout = QHBoxLayout()
layout.addWidget(pb)
w = QWidget(layout=layout)
self.addTab(w, "Cheats")
self.addTab(
QFactionUnits(
game_model.game.coalition_for(True).faction,
self,
),
"Faction OWNFOR",
)
self.addTab(
QFactionUnits(
game_model.game.coalition_for(False).faction,
self,
),
"Faction OPFOR",
)
def open_awcd(self):
AirWingConfigurationDialog(self.game_model.game, True, self).exec_()
events = GameUpdateEvents().begin_new_turn()
EventStream.put_nowait(events)
self.game_model.ato_model.on_sim_update(events)
class AirWingDialog(QDialog):
"""Dialog window showing the player's air wing."""
def __init__(self, game_model: GameModel, parent) -> None:
super().__init__(parent)
self.air_wing_model = game_model.blue_air_wing_model
self.setMinimumSize(1000, 440)
self.setWindowTitle(f"Air Wing")
# TODO: self.setWindowIcon()
layout = QVBoxLayout()
self.setLayout(layout)
layout.addWidget(AirWingTabs(game_model))