Added squadron configuration gui

- added add/remove buttons to Air Wing Menu, implemented remove
This commit is contained in:
Richard Pump 2021-11-16 23:16:19 +01:00 committed by RndName
parent 4803ae5f78
commit e4ba9a8b72
No known key found for this signature in database
GPG Key ID: 5EF516FD9537F7C0
4 changed files with 341 additions and 48 deletions

21
game/observer.py Normal file
View File

@ -0,0 +1,21 @@
class Event(object):
pass
class Observable(object):
def __init__(self) -> None:
self.callbacks = []
def subscribe(self, callback) -> None:
self.callbacks.append(callback)
def unsubscribe(self, callback) -> None:
self.callbacks.remove(callback)
def fire(self, **attrs) -> None:
e = Event()
e.source = self
for k, v in attrs.items():
setattr(e, k, v)
for fn in self.callbacks:
fn(e)

View File

@ -9,19 +9,39 @@ from gen.flights.ai_flight_planner_db import aircraft_for_task
from gen.flights.closestairfields import ObjectiveDistanceCache from gen.flights.closestairfields import ObjectiveDistanceCache
from ..theater import ControlPoint, MissionTarget from ..theater import ControlPoint, MissionTarget
from ..observer import Observable
if TYPE_CHECKING: if TYPE_CHECKING:
from ..ato.flighttype import FlightType from ..ato.flighttype import FlightType
from .squadron import Squadron from .squadron import Squadron
class AirWing: class AirWing(Observable):
def __init__(self, player: bool) -> None: def __init__(self, player: bool) -> None:
super().__init__()
self.player = player self.player = player
self.squadrons: dict[AircraftType, list[Squadron]] = defaultdict(list) self.squadrons: dict[AircraftType, list[Squadron]] = defaultdict(list)
def add_squadron(self, squadron: Squadron) -> None: def add_squadron(self, squadron: Squadron) -> None:
new_aircraft_type = squadron.aircraft not in self.squadrons
self.squadrons[squadron.aircraft].append(squadron) self.squadrons[squadron.aircraft].append(squadron)
if new_aircraft_type:
self.fire(type="add_aircraft_type", obj=squadron.aircraft)
self.fire(type="add_squadron", obj=squadron)
def remove_squadron(self, toRemove: Squadron) -> None:
if toRemove.aircraft in self.squadrons:
self.squadrons[toRemove.aircraft].remove(toRemove)
self.fire(type="remove_squadron", obj=toRemove)
if len(self.squadrons[toRemove.aircraft]) == 0:
self.remove_aircraft_type(toRemove.aircraft)
def remove_aircraft_type(self, toRemove: AircraftType) -> None:
self.squadrons.pop(toRemove)
self.fire(type="remove_aircraft_type", obj=toRemove)
def squadrons_for(self, aircraft: AircraftType) -> Sequence[Squadron]: def squadrons_for(self, aircraft: AircraftType) -> Sequence[Squadron]:
return self.squadrons[aircraft] return self.squadrons[aircraft]

View File

@ -24,15 +24,20 @@ from PySide2.QtWidgets import (
QVBoxLayout, QVBoxLayout,
QWidget, QWidget,
QCheckBox, QCheckBox,
QPushButton,
QGridLayout,
) )
from game import Game from game import Game
from game.ato.flighttype import FlightType from game.ato.flighttype import FlightType
from game.coalition import Coalition
from game.dcs.aircrafttype import AircraftType from game.dcs.aircrafttype import AircraftType
from game.squadrons import AirWing, Pilot, Squadron from game.squadrons import AirWing, Pilot, Squadron
from game.theater import ConflictTheater, ControlPoint from game.theater import ConflictTheater, ControlPoint
from qt_ui.uiconstants import AIRCRAFT_ICONS from qt_ui.uiconstants import AIRCRAFT_ICONS
from qt_ui.windows.SquadronConfigPopup import SquadronConfigPopup
class AllowedMissionTypeControls(QVBoxLayout): class AllowedMissionTypeControls(QVBoxLayout):
def __init__(self, squadron: Squadron) -> None: def __init__(self, squadron: Squadron) -> None:
@ -67,6 +72,7 @@ class AllowedMissionTypeControls(QVBoxLayout):
self.allowed_mission_types.add(task) self.allowed_mission_types.add(task)
else: else:
self.allowed_mission_types.remove(task) self.allowed_mission_types.remove(task)
self.squadron.set_allowed_mission_types(self.allowed_mission_types)
class SquadronBaseSelector(QComboBox): class SquadronBaseSelector(QComboBox):
@ -95,10 +101,13 @@ class SquadronBaseSelector(QComboBox):
class SquadronConfigurationBox(QGroupBox): class SquadronConfigurationBox(QGroupBox):
def __init__(self, squadron: Squadron, theater: ConflictTheater) -> None: def __init__(
self, squadron: Squadron, theater: ConflictTheater, air_wing: AirWing
) -> None:
super().__init__() super().__init__()
self.setCheckable(True) self.setCheckable(False)
self.squadron = squadron self.squadron = squadron
self.air_wing = air_wing
self.reset_title() self.reset_title()
columns = QHBoxLayout() columns = QHBoxLayout()
@ -141,6 +150,14 @@ class SquadronConfigurationBox(QGroupBox):
self.player_list.setAcceptRichText(False) self.player_list.setAcceptRichText(False)
self.player_list.setEnabled(squadron.player) self.player_list.setEnabled(squadron.player)
left_column.addWidget(self.player_list) left_column.addWidget(self.player_list)
self.player_list.textChanged.connect(self.on_pilots_changed)
delete_button = QPushButton("Remove")
delete_button.setMaximumWidth(80)
delete_button.clicked.connect(
lambda state: self.air_wing.remove_squadron(self.squadron)
)
left_column.addWidget(delete_button)
left_column.addStretch() left_column.addStretch()
@ -163,42 +180,56 @@ class SquadronConfigurationBox(QGroupBox):
def reset_title(self) -> None: def reset_title(self) -> None:
self.setTitle(f"{self.squadron.name} - {self.squadron.aircraft}") self.setTitle(f"{self.squadron.name} - {self.squadron.aircraft}")
def apply(self) -> Squadron: def on_pilots_changed(self) -> None:
player_names = self.player_list.toPlainText().splitlines() player_names = self.player_list.toPlainText().splitlines()
# Prepend player pilots so they get set active first. # Prepend player pilots so they get set active first.
self.squadron.pilot_pool = [ self.squadron.pilot_pool = [
Pilot(n, player=True) for n in player_names Pilot(n, player=True) for n in player_names
] + self.squadron.pilot_pool ] + self.squadron.pilot_pool
self.squadron.set_allowed_mission_types(
self.allowed_missions.allowed_mission_types
)
return self.squadron
class SquadronConfigurationLayout(QVBoxLayout): class SquadronConfigurationLayout(QVBoxLayout):
def __init__(self, squadrons: list[Squadron], theater: ConflictTheater) -> None: def __init__(
self, squadrons: list[Squadron], theater: ConflictTheater, air_wing: AirWing
) -> None:
super().__init__() super().__init__()
self.squadron_configs = [] self.theater = theater
self.air_wing = air_wing
self.squadron_configs: dict[Squadron, SquadronConfigurationBox] = {}
for squadron in squadrons: for squadron in squadrons:
squadron_config = SquadronConfigurationBox(squadron, theater) squadron_config = SquadronConfigurationBox(
self.squadron_configs.append(squadron_config) squadron, self.theater, self.air_wing
)
self.squadron_configs[squadron] = squadron_config
self.addWidget(squadron_config) self.addWidget(squadron_config)
def apply(self) -> list[Squadron]: def addSquadron(self, squadron: Squadron) -> None:
keep_squadrons = [] if squadron not in self.squadron_configs:
for squadron_config in self.squadron_configs: squadron_config = SquadronConfigurationBox(
if squadron_config.isChecked(): squadron, self.theater, self.air_wing
keep_squadrons.append(squadron_config.apply()) )
return keep_squadrons self.squadron_configs[squadron] = squadron_config
self.addWidget(squadron_config)
self.update()
def removeSquadron(self, squadron: Squadron) -> None:
if squadron in self.squadron_configs:
self.removeWidget(self.squadron_configs[squadron])
self.squadron_configs.pop(squadron)
self.update()
class AircraftSquadronsPage(QWidget): class AircraftSquadronsPage(QWidget):
def __init__(self, squadrons: list[Squadron], theater: ConflictTheater) -> None: def __init__(
self, squadrons: list[Squadron], theater: ConflictTheater, air_wing: AirWing
) -> None:
super().__init__() super().__init__()
layout = QVBoxLayout() layout = QVBoxLayout()
self.setLayout(layout) self.setLayout(layout)
self.squadrons_config = SquadronConfigurationLayout(squadrons, theater) self.squadrons_config = SquadronConfigurationLayout(
squadrons, theater, air_wing
)
scrolling_widget = QWidget() scrolling_widget = QWidget()
scrolling_widget.setLayout(self.squadrons_config) scrolling_widget.setLayout(self.squadrons_config)
@ -211,23 +242,50 @@ class AircraftSquadronsPage(QWidget):
layout.addWidget(scrolling_area) layout.addWidget(scrolling_area)
def apply(self) -> list[Squadron]: def addSquadron(self, squadron: Squadron) -> None:
return self.squadrons_config.apply() self.squadrons_config.addSquadron(squadron)
def removeSquadron(self, squadron: Squadron) -> None:
self.squadrons_config.removeSquadron(squadron)
class AircraftSquadronsPanel(QStackedLayout): class AircraftSquadronsPanel(QStackedLayout):
def __init__(self, air_wing: AirWing, theater: ConflictTheater) -> None: def __init__(self, air_wing: AirWing, theater: ConflictTheater) -> None:
super().__init__() super().__init__()
self.air_wing = air_wing self.air_wing = air_wing
self.theater = theater
self.air_wing.subscribe(self.handleChanges)
self.squadrons_pages: dict[AircraftType, AircraftSquadronsPage] = {} self.squadrons_pages: dict[AircraftType, AircraftSquadronsPage] = {}
for aircraft, squadrons in self.air_wing.squadrons.items(): for aircraft, squadrons in self.air_wing.squadrons.items():
page = AircraftSquadronsPage(squadrons, theater) page = AircraftSquadronsPage(squadrons, self.theater, self.air_wing)
self.addWidget(page) self.addWidget(page)
self.squadrons_pages[aircraft] = page self.squadrons_pages[aircraft] = page
def apply(self) -> None: def __del__(self) -> None:
for aircraft, page in self.squadrons_pages.items(): self.air_wing.unsubscribe(self.handleChanges)
self.air_wing.squadrons[aircraft] = page.apply()
def handleChanges(self, event) -> None:
if event.type == "add_aircraft_type":
aircraft_type = event.obj
if aircraft_type not in self.squadrons_pages:
page = AircraftSquadronsPage(
self.air_wing.squadrons[aircraft_type], self.theater, self.air_wing
)
self.addWidget(page)
self.squadrons_pages[aircraft_type] = page
elif event.type == "remove_aircraft_type":
aircraft_type = event.obj
if aircraft_type in self.squadrons_pages:
self.removeWidget(self.squadrons_pages[aircraft_type])
self.squadrons_pages.pop(aircraft_type)
elif event.type == "add_squadron":
squadron = event.obj
self.squadrons_pages[squadron.aircraft].addSquadron(squadron)
elif event.type == "remove_squadron":
squadron = event.obj
self.squadrons_pages[squadron.aircraft].removeSquadron(squadron)
self.update()
class AircraftTypeList(QListView): class AircraftTypeList(QListView):
@ -238,21 +296,47 @@ class AircraftTypeList(QListView):
self.setIconSize(QSize(91, 24)) self.setIconSize(QSize(91, 24))
self.setMinimumWidth(300) self.setMinimumWidth(300)
model = QStandardItemModel(self) self.air_wing = air_wing
self.setModel(model)
self.selectionModel().setCurrentIndex( self.item_model = QStandardItemModel(self)
model.index(0, 0), QItemSelectionModel.Select self.setModel(self.item_model)
)
self.selectionModel().selectionChanged.connect(self.on_selection_changed) for aircraft in self.air_wing.squadrons:
for aircraft in air_wing.squadrons:
aircraft_item = QStandardItem(aircraft.name) aircraft_item = QStandardItem(aircraft.name)
icon = self.icon_for(aircraft) icon = self.icon_for(aircraft)
if icon is not None: if icon is not None:
aircraft_item.setIcon(icon) aircraft_item.setIcon(icon)
aircraft_item.setEditable(False) aircraft_item.setEditable(False)
aircraft_item.setSelectable(True) aircraft_item.setSelectable(True)
model.appendRow(aircraft_item) self.item_model.appendRow(aircraft_item)
self.selectionModel().setCurrentIndex(
self.item_model.index(0, 0), QItemSelectionModel.Select
)
self.selectionModel().selectionChanged.connect(self.on_selection_changed)
self.air_wing.subscribe(self.handleChanges)
def __del__(self) -> None:
self.air_wing.unsubscribe(self.handleChanges)
def handleChanges(self, event) -> None:
if event.type == "remove_aircraft_type":
aircraft_type = event.obj
items = self.item_model.findItems(aircraft_type.name)
if len(items) == 1:
for item in items:
self.item_model.takeRow(item.row())
elif event.type == "add_aircraft_type":
aircraft_type = event.obj
aircraft_item = QStandardItem(aircraft_type.name)
icon = self.icon_for(aircraft_type)
if icon is not None:
aircraft_item.setIcon(icon)
aircraft_item.setEditable(False)
aircraft_item.setSelectable(True)
self.item_model.appendRow(aircraft_item)
self.update()
def on_selection_changed( def on_selection_changed(
self, selected: QItemSelection, _deselected: QItemSelection self, selected: QItemSelection, _deselected: QItemSelection
@ -264,6 +348,18 @@ class AircraftTypeList(QListView):
return return
self.page_index_changed.emit(indexes[0].row()) self.page_index_changed.emit(indexes[0].row())
def deleteSelectedType(self) -> None:
if self.selectionModel().currentIndex().isValid():
aircraftName = str(self.selectionModel().currentIndex().data())
to_remove = None
for type in self.air_wing.squadrons:
if str(type) == aircraftName:
to_remove = type
if to_remove != None:
self.air_wing.remove_aircraft_type(to_remove)
else:
raise RuntimeError("No aircraft was selected for removal")
@staticmethod @staticmethod
def icon_for(aircraft: AircraftType) -> Optional[QIcon]: def icon_for(aircraft: AircraftType) -> Optional[QIcon]:
name = aircraft.dcs_id name = aircraft.dcs_id
@ -273,24 +369,37 @@ class AircraftTypeList(QListView):
class AirWingConfigurationTab(QWidget): class AirWingConfigurationTab(QWidget):
def __init__(self, air_wing: AirWing, theater: ConflictTheater) -> None: def __init__(
self, coalition: Coalition, theater: ConflictTheater, game: Game
) -> None:
super().__init__() super().__init__()
self.game = game
self.theater = theater
self.coalition = coalition
self.air_wing = coalition.air_wing
layout = QHBoxLayout() layout = QGridLayout()
self.setLayout(layout) self.setLayout(layout)
type_list = AircraftTypeList(air_wing) self.type_list = AircraftTypeList(self.air_wing)
type_list.page_index_changed.connect(self.on_aircraft_changed)
layout.addWidget(type_list)
self.squadrons_panel = AircraftSquadronsPanel(air_wing, theater) layout.addWidget(self.type_list, 1, 1, 1, 2)
layout.addLayout(self.squadrons_panel)
def apply(self) -> None: add_button = QPushButton("Add Aircraft/Squadron")
self.squadrons_panel.apply() add_button.clicked.connect(lambda state: self.addAircraftType())
layout.addWidget(add_button, 2, 1, 1, 1)
def on_aircraft_changed(self, index: QModelIndex) -> None: remove_button = QPushButton("Remove Aircraft")
self.squadrons_panel.setCurrentIndex(index) remove_button.clicked.connect(lambda state: self.type_list.deleteSelectedType())
layout.addWidget(remove_button, 2, 2, 1, 1)
self.squadrons_panel = AircraftSquadronsPanel(self.air_wing, self.theater)
layout.addLayout(self.squadrons_panel, 1, 3, 2, 1)
self.type_list.page_index_changed.connect(self.squadrons_panel.setCurrentIndex)
def addAircraftType(self) -> None:
SquadronConfigPopup(self.coalition, self.theater, self.game).exec_()
class AirWingConfigurationDialog(QDialog): class AirWingConfigurationDialog(QDialog):
@ -328,12 +437,10 @@ class AirWingConfigurationDialog(QDialog):
self.tabs = [] self.tabs = []
for coalition in game.coalitions: for coalition in game.coalitions:
coalition_tab = AirWingConfigurationTab(coalition.air_wing, game.theater) coalition_tab = AirWingConfigurationTab(coalition, game.theater, game)
name = "Blue" if coalition.player else "Red" name = "Blue" if coalition.player else "Red"
tab_widget.addTab(coalition_tab, name) tab_widget.addTab(coalition_tab, name)
self.tabs.append(coalition_tab) self.tabs.append(coalition_tab)
def reject(self) -> None: def reject(self) -> None:
for tab in self.tabs:
tab.apply()
super().reject() super().reject()

View File

@ -0,0 +1,145 @@
from typing import Optional, Callable, Iterable
from PySide2.QtWidgets import (
QDialog,
QPushButton,
QVBoxLayout,
QLabel,
QLineEdit,
QTextEdit,
QCheckBox,
QHBoxLayout,
QComboBox,
)
from game.dcs.aircrafttype import AircraftType
from game.game import Game
from game.squadrons import Squadron
from game.theater import ConflictTheater, ControlPoint
from game.coalition import Coalition
from game.factions.faction import Faction
from game.campaignloader.squadrondefgenerator import SquadronDefGenerator
from gen.flights.flight import FlightType
class AircraftTypeSelector(QComboBox):
def __init__(self, faction: Faction) -> None:
super().__init__()
self.types = faction.aircrafts
self.setSizeAdjustPolicy(self.AdjustToContents)
self.addItem("Select aircraft type...", None)
self.setCurrentText("Select aircraft type...")
self.types.sort(key=str)
for type in self.types:
self.addItem(type.name, type)
class SquadronConfigPopup(QDialog):
def __init__(
self, coalition: Coalition, theater: ConflictTheater, game: Game
) -> None:
super().__init__()
self.game = game
self.coalition = coalition
self.theater = theater
self.squadron = None
# self.setMinimumSize(500, 800)
self.setWindowTitle(f"Add new Squadron")
self.column = QVBoxLayout()
self.setLayout(self.column)
self.aircraft_type_selector = AircraftTypeSelector(coalition.faction)
self.column.addWidget(self.aircraft_type_selector)
self.column.addWidget(QLabel("Name:"))
self.name_edit = QLineEdit("---")
self.name_edit.setEnabled(False)
self.name_edit.textChanged.connect(self.on_name_changed)
self.column.addWidget(self.name_edit)
self.column.addWidget(QLabel("Nickname:"))
self.nickname_edit = QLineEdit("---")
self.nickname_edit.setEnabled(False)
self.nickname_edit.textChanged.connect(self.on_nickname_changed)
self.column.addWidget(self.nickname_edit)
self.column.addStretch()
self.aircraft_type_selector.currentIndexChanged.connect(
self.on_aircraft_selection
)
self.button_layout = QHBoxLayout()
self.column.addLayout(self.button_layout)
self.accept_button = QPushButton("Accept")
self.accept_button.clicked.connect(lambda state: self.accept())
self.accept_button.setEnabled(False)
self.button_layout.addWidget(self.accept_button)
self.cancel_button = QPushButton("Cancel")
self.cancel_button.clicked.connect(lambda state: self.cancel())
self.button_layout.addWidget(self.cancel_button)
def create_Squadron(
self, aircraft_type: AircraftType, base: ControlPoint
) -> Squadron:
squadron_def = SquadronDefGenerator(self.coalition).generate_for_aircraft(
aircraft_type
)
squadron = Squadron(
squadron_def.name,
squadron_def.nickname,
squadron_def.country,
squadron_def.role,
squadron_def.aircraft,
squadron_def.livery,
squadron_def.mission_types,
squadron_def.operating_bases,
squadron_def.pilot_pool,
self.coalition,
self.game.settings,
base,
)
return squadron
def on_aircraft_selection(self, index: int) -> None:
aircraft_type_name = self.aircraft_type_selector.currentText()
aircraft_type = None
for aircraft in self.coalition.faction.aircrafts:
if str(aircraft) == aircraft_type_name:
aircraft_type = aircraft
if aircraft != None:
self.squadron = self.create_Squadron(
aircraft_type,
next(self.theater.control_points_for(self.coalition.player)),
)
self.name_edit.setText(self.squadron.name)
self.name_edit.setEnabled(True)
self.nickname_edit.setText(self.squadron.nickname)
self.nickname_edit.setEnabled(True)
self.accept_button.setStyleSheet("background-color: green")
self.accept_button.setEnabled(True)
self.update()
def on_name_changed(self, text: str) -> None:
self.squadron.name = text
def on_nickname_changed(self, text: str) -> None:
self.squadron.nickname = text
def accept(self) -> None:
self.coalition.air_wing.add_squadron(self.squadron)
return super().accept()
def cancel(self) -> None:
return super().reject()