import itertools
import logging
from collections import defaultdict
from typing import Optional, Callable, Iterator
from PySide2.QtCore import (
QItemSelectionModel,
QModelIndex,
QSize,
Qt,
)
from PySide2.QtWidgets import (
QAbstractItemView,
QDialog,
QListView,
QVBoxLayout,
QGroupBox,
QGridLayout,
QLabel,
QWidget,
QScrollArea,
QLineEdit,
QTextEdit,
QCheckBox,
QHBoxLayout,
)
from game import Game
from game.squadrons import Squadron, AirWing, Pilot
from gen.flights.flight import FlightType
from qt_ui.models import AirWingModel, SquadronModel
from qt_ui.windows.AirWingDialog import SquadronDelegate
from qt_ui.windows.SquadronDialog import SquadronDialog
class SquadronList(QListView):
"""List view for displaying the air wing's squadrons."""
def __init__(self, air_wing_model: AirWingModel) -> None:
super().__init__()
self.air_wing_model = air_wing_model
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.Select
)
# self.setIconSize(QSize(91, 24))
self.setSelectionBehavior(QAbstractItemView.SelectItems)
self.doubleClicked.connect(self.on_double_click)
def on_double_click(self, index: QModelIndex) -> None:
if not index.isValid():
return
self.dialog = SquadronDialog(
SquadronModel(self.air_wing_model.squadron_at_index(index)), self
)
self.dialog.show()
class AllowedMissionTypeControls(QVBoxLayout):
def __init__(self, squadron: Squadron) -> None:
super().__init__()
self.squadron = squadron
self.allowed_mission_types = set()
self.addWidget(QLabel("Allowed mission types"))
def make_callback(toggled_task: FlightType) -> Callable[[bool], None]:
def callback(checked: bool) -> None:
self.on_toggled(toggled_task, checked)
return callback
for task in FlightType:
enabled = task in squadron.mission_types
if enabled:
self.allowed_mission_types.add(task)
checkbox = QCheckBox(text=task.value)
checkbox.setChecked(enabled)
checkbox.toggled.connect(make_callback(task))
self.addWidget(checkbox)
self.addStretch()
def on_toggled(self, task: FlightType, checked: bool) -> None:
if checked:
self.allowed_mission_types.add(task)
else:
self.allowed_mission_types.remove(task)
class SquadronConfigurationBox(QGroupBox):
def __init__(self, squadron: Squadron) -> None:
super().__init__()
self.setCheckable(True)
self.squadron = squadron
self.reset_title()
columns = QHBoxLayout()
self.setLayout(columns)
left_column = QVBoxLayout()
columns.addLayout(left_column)
left_column.addWidget(QLabel("Name:"))
self.name_edit = QLineEdit(squadron.name)
self.name_edit.textChanged.connect(self.on_name_changed)
left_column.addWidget(self.name_edit)
left_column.addWidget(QLabel("Nickname:"))
self.nickname_edit = QLineEdit(squadron.nickname)
self.nickname_edit.textChanged.connect(self.on_nickname_changed)
left_column.addWidget(self.nickname_edit)
left_column.addWidget(
QLabel("Players (one per line, leave empty for an AI-only squadron):")
)
players = [p for p in squadron.available_pilots if p.player]
for player in players:
squadron.available_pilots.remove(player)
self.player_list = QTextEdit("
".join(p.name for p in players))
self.player_list.setAcceptRichText(False)
left_column.addWidget(self.player_list)
left_column.addStretch()
self.allowed_missions = AllowedMissionTypeControls(squadron)
columns.addLayout(self.allowed_missions)
def on_name_changed(self, text: str) -> None:
self.squadron.name = text
self.reset_title()
def on_nickname_changed(self, text: str) -> None:
self.squadron.nickname = text
def reset_title(self) -> None:
self.setTitle(f"{self.squadron.name} - {self.squadron.aircraft}")
def apply(self) -> Squadron:
player_names = self.player_list.toPlainText().splitlines()
# Prepend player pilots so they get set active first.
self.squadron.pilot_pool = [
Pilot(n, player=True) for n in player_names
] + self.squadron.pilot_pool
self.squadron.mission_types = tuple(self.allowed_missions.allowed_mission_types)
return self.squadron
class AirWingConfigurationLayout(QVBoxLayout):
def __init__(self, air_wing: AirWing) -> None:
super().__init__()
self.air_wing = air_wing
self.squadron_configs = []
doc_url = (
"https://github.com/dcs-liberation/dcs_liberation/wiki/Squadrons-and-pilots"
)
doc_label = QLabel(
"Use this opportunity to customize the squadrons available to your "
"coalition. This is your
"
"only opportunity to make changes.
"
"
"
"To accept your changes and continue, close this window.
"
"
"
"To remove a squadron from the game, uncheck the box in the title. New "
"squadrons cannot
"
"be added via the UI at this time. To add a custom squadron, see "
f'the wiki.'
)
doc_label.setOpenExternalLinks(True)
self.addWidget(doc_label)
for squadron in self.air_wing.iter_squadrons():
squadron_config = SquadronConfigurationBox(squadron)
self.squadron_configs.append(squadron_config)
self.addWidget(squadron_config)
def apply(self) -> None:
keep_squadrons = defaultdict(list)
for squadron_config in self.squadron_configs:
if squadron_config.isChecked():
squadron = squadron_config.apply()
keep_squadrons[squadron.aircraft].append(squadron)
self.air_wing.squadrons = keep_squadrons
class AirWingConfigurationDialog(QDialog):
"""Dialog window for air wing configuration."""
def __init__(self, game: Game, parent) -> None:
super().__init__(parent)
self.air_wing = game.blue.air_wing
self.setMinimumSize(500, 800)
self.setWindowTitle(f"Air Wing Configuration")
# TODO: self.setWindowIcon()
self.air_wing_config = AirWingConfigurationLayout(self.air_wing)
scrolling_layout = QVBoxLayout()
scrolling_widget = QWidget()
scrolling_widget.setLayout(self.air_wing_config)
scrolling_area = QScrollArea()
scrolling_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
scrolling_area.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
scrolling_area.setWidgetResizable(True)
scrolling_area.setWidget(scrolling_widget)
scrolling_layout.addWidget(scrolling_area)
self.setLayout(scrolling_layout)
def reject(self) -> None:
self.air_wing_config.apply()
super().reject()