mirror of
https://github.com/dcs-liberation/dcs_liberation.git
synced 2025-11-10 14:22:26 +00:00
Added squadron configuration gui
- added add/remove buttons to Air Wing Menu, implemented remove
This commit is contained in:
parent
4803ae5f78
commit
e4ba9a8b72
21
game/observer.py
Normal file
21
game/observer.py
Normal 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)
|
||||
@ -9,19 +9,39 @@ from gen.flights.ai_flight_planner_db import aircraft_for_task
|
||||
from gen.flights.closestairfields import ObjectiveDistanceCache
|
||||
from ..theater import ControlPoint, MissionTarget
|
||||
|
||||
from ..observer import Observable
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..ato.flighttype import FlightType
|
||||
from .squadron import Squadron
|
||||
|
||||
|
||||
class AirWing:
|
||||
class AirWing(Observable):
|
||||
def __init__(self, player: bool) -> None:
|
||||
super().__init__()
|
||||
self.player = player
|
||||
self.squadrons: dict[AircraftType, list[Squadron]] = defaultdict(list)
|
||||
|
||||
def add_squadron(self, squadron: Squadron) -> None:
|
||||
new_aircraft_type = squadron.aircraft not in self.squadrons
|
||||
|
||||
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]:
|
||||
return self.squadrons[aircraft]
|
||||
|
||||
|
||||
@ -24,15 +24,20 @@ from PySide2.QtWidgets import (
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
QCheckBox,
|
||||
QPushButton,
|
||||
QGridLayout,
|
||||
)
|
||||
|
||||
from game import Game
|
||||
from game.ato.flighttype import FlightType
|
||||
from game.coalition import Coalition
|
||||
from game.dcs.aircrafttype import AircraftType
|
||||
from game.squadrons import AirWing, Pilot, Squadron
|
||||
from game.theater import ConflictTheater, ControlPoint
|
||||
from qt_ui.uiconstants import AIRCRAFT_ICONS
|
||||
|
||||
from qt_ui.windows.SquadronConfigPopup import SquadronConfigPopup
|
||||
|
||||
|
||||
class AllowedMissionTypeControls(QVBoxLayout):
|
||||
def __init__(self, squadron: Squadron) -> None:
|
||||
@ -67,6 +72,7 @@ class AllowedMissionTypeControls(QVBoxLayout):
|
||||
self.allowed_mission_types.add(task)
|
||||
else:
|
||||
self.allowed_mission_types.remove(task)
|
||||
self.squadron.set_allowed_mission_types(self.allowed_mission_types)
|
||||
|
||||
|
||||
class SquadronBaseSelector(QComboBox):
|
||||
@ -95,10 +101,13 @@ class SquadronBaseSelector(QComboBox):
|
||||
|
||||
|
||||
class SquadronConfigurationBox(QGroupBox):
|
||||
def __init__(self, squadron: Squadron, theater: ConflictTheater) -> None:
|
||||
def __init__(
|
||||
self, squadron: Squadron, theater: ConflictTheater, air_wing: AirWing
|
||||
) -> None:
|
||||
super().__init__()
|
||||
self.setCheckable(True)
|
||||
self.setCheckable(False)
|
||||
self.squadron = squadron
|
||||
self.air_wing = air_wing
|
||||
self.reset_title()
|
||||
|
||||
columns = QHBoxLayout()
|
||||
@ -141,6 +150,14 @@ class SquadronConfigurationBox(QGroupBox):
|
||||
self.player_list.setAcceptRichText(False)
|
||||
self.player_list.setEnabled(squadron.player)
|
||||
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()
|
||||
|
||||
@ -163,42 +180,56 @@ class SquadronConfigurationBox(QGroupBox):
|
||||
def reset_title(self) -> None:
|
||||
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()
|
||||
# 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.set_allowed_mission_types(
|
||||
self.allowed_missions.allowed_mission_types
|
||||
)
|
||||
return self.squadron
|
||||
|
||||
|
||||
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__()
|
||||
self.squadron_configs = []
|
||||
self.theater = theater
|
||||
self.air_wing = air_wing
|
||||
self.squadron_configs: dict[Squadron, SquadronConfigurationBox] = {}
|
||||
for squadron in squadrons:
|
||||
squadron_config = SquadronConfigurationBox(squadron, theater)
|
||||
self.squadron_configs.append(squadron_config)
|
||||
squadron_config = SquadronConfigurationBox(
|
||||
squadron, self.theater, self.air_wing
|
||||
)
|
||||
self.squadron_configs[squadron] = squadron_config
|
||||
self.addWidget(squadron_config)
|
||||
|
||||
def apply(self) -> list[Squadron]:
|
||||
keep_squadrons = []
|
||||
for squadron_config in self.squadron_configs:
|
||||
if squadron_config.isChecked():
|
||||
keep_squadrons.append(squadron_config.apply())
|
||||
return keep_squadrons
|
||||
def addSquadron(self, squadron: Squadron) -> None:
|
||||
if squadron not in self.squadron_configs:
|
||||
squadron_config = SquadronConfigurationBox(
|
||||
squadron, self.theater, self.air_wing
|
||||
)
|
||||
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):
|
||||
def __init__(self, squadrons: list[Squadron], theater: ConflictTheater) -> None:
|
||||
def __init__(
|
||||
self, squadrons: list[Squadron], theater: ConflictTheater, air_wing: AirWing
|
||||
) -> None:
|
||||
super().__init__()
|
||||
layout = QVBoxLayout()
|
||||
self.setLayout(layout)
|
||||
|
||||
self.squadrons_config = SquadronConfigurationLayout(squadrons, theater)
|
||||
self.squadrons_config = SquadronConfigurationLayout(
|
||||
squadrons, theater, air_wing
|
||||
)
|
||||
|
||||
scrolling_widget = QWidget()
|
||||
scrolling_widget.setLayout(self.squadrons_config)
|
||||
@ -211,23 +242,50 @@ class AircraftSquadronsPage(QWidget):
|
||||
|
||||
layout.addWidget(scrolling_area)
|
||||
|
||||
def apply(self) -> list[Squadron]:
|
||||
return self.squadrons_config.apply()
|
||||
def addSquadron(self, squadron: Squadron) -> None:
|
||||
self.squadrons_config.addSquadron(squadron)
|
||||
|
||||
def removeSquadron(self, squadron: Squadron) -> None:
|
||||
self.squadrons_config.removeSquadron(squadron)
|
||||
|
||||
|
||||
class AircraftSquadronsPanel(QStackedLayout):
|
||||
def __init__(self, air_wing: AirWing, theater: ConflictTheater) -> None:
|
||||
super().__init__()
|
||||
self.air_wing = air_wing
|
||||
self.theater = theater
|
||||
self.air_wing.subscribe(self.handleChanges)
|
||||
|
||||
self.squadrons_pages: dict[AircraftType, AircraftSquadronsPage] = {}
|
||||
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.squadrons_pages[aircraft] = page
|
||||
|
||||
def apply(self) -> None:
|
||||
for aircraft, page in self.squadrons_pages.items():
|
||||
self.air_wing.squadrons[aircraft] = page.apply()
|
||||
def __del__(self) -> None:
|
||||
self.air_wing.unsubscribe(self.handleChanges)
|
||||
|
||||
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):
|
||||
@ -238,21 +296,47 @@ class AircraftTypeList(QListView):
|
||||
self.setIconSize(QSize(91, 24))
|
||||
self.setMinimumWidth(300)
|
||||
|
||||
model = QStandardItemModel(self)
|
||||
self.setModel(model)
|
||||
self.air_wing = air_wing
|
||||
|
||||
self.selectionModel().setCurrentIndex(
|
||||
model.index(0, 0), QItemSelectionModel.Select
|
||||
)
|
||||
self.selectionModel().selectionChanged.connect(self.on_selection_changed)
|
||||
for aircraft in air_wing.squadrons:
|
||||
self.item_model = QStandardItemModel(self)
|
||||
self.setModel(self.item_model)
|
||||
|
||||
for aircraft in self.air_wing.squadrons:
|
||||
aircraft_item = QStandardItem(aircraft.name)
|
||||
icon = self.icon_for(aircraft)
|
||||
if icon is not None:
|
||||
aircraft_item.setIcon(icon)
|
||||
aircraft_item.setEditable(False)
|
||||
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(
|
||||
self, selected: QItemSelection, _deselected: QItemSelection
|
||||
@ -264,6 +348,18 @@ class AircraftTypeList(QListView):
|
||||
return
|
||||
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
|
||||
def icon_for(aircraft: AircraftType) -> Optional[QIcon]:
|
||||
name = aircraft.dcs_id
|
||||
@ -273,24 +369,37 @@ class AircraftTypeList(QListView):
|
||||
|
||||
|
||||
class AirWingConfigurationTab(QWidget):
|
||||
def __init__(self, air_wing: AirWing, theater: ConflictTheater) -> None:
|
||||
def __init__(
|
||||
self, coalition: Coalition, theater: ConflictTheater, game: Game
|
||||
) -> None:
|
||||
super().__init__()
|
||||
self.game = game
|
||||
self.theater = theater
|
||||
self.coalition = coalition
|
||||
self.air_wing = coalition.air_wing
|
||||
|
||||
layout = QHBoxLayout()
|
||||
layout = QGridLayout()
|
||||
self.setLayout(layout)
|
||||
|
||||
type_list = AircraftTypeList(air_wing)
|
||||
type_list.page_index_changed.connect(self.on_aircraft_changed)
|
||||
layout.addWidget(type_list)
|
||||
self.type_list = AircraftTypeList(self.air_wing)
|
||||
|
||||
self.squadrons_panel = AircraftSquadronsPanel(air_wing, theater)
|
||||
layout.addLayout(self.squadrons_panel)
|
||||
layout.addWidget(self.type_list, 1, 1, 1, 2)
|
||||
|
||||
def apply(self) -> None:
|
||||
self.squadrons_panel.apply()
|
||||
add_button = QPushButton("Add Aircraft/Squadron")
|
||||
add_button.clicked.connect(lambda state: self.addAircraftType())
|
||||
layout.addWidget(add_button, 2, 1, 1, 1)
|
||||
|
||||
def on_aircraft_changed(self, index: QModelIndex) -> None:
|
||||
self.squadrons_panel.setCurrentIndex(index)
|
||||
remove_button = QPushButton("Remove Aircraft")
|
||||
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):
|
||||
@ -328,12 +437,10 @@ class AirWingConfigurationDialog(QDialog):
|
||||
|
||||
self.tabs = []
|
||||
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"
|
||||
tab_widget.addTab(coalition_tab, name)
|
||||
self.tabs.append(coalition_tab)
|
||||
|
||||
def reject(self) -> None:
|
||||
for tab in self.tabs:
|
||||
tab.apply()
|
||||
super().reject()
|
||||
|
||||
145
qt_ui/windows/SquadronConfigPopup.py
Normal file
145
qt_ui/windows/SquadronConfigPopup.py
Normal 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()
|
||||
Loading…
x
Reference in New Issue
Block a user