Skip to content

Commit

Permalink
Added squadron configuration gui
Browse files Browse the repository at this point in the history
- added add/remove buttons to Air Wing Menu, implemented remove
  • Loading branch information
Chomper64 authored and RndName committed Dec 23, 2021
1 parent 4803ae5 commit e4ba9a8
Showing 4 changed files with 340 additions and 47 deletions.
21 changes: 21 additions & 0 deletions game/observer.py
Original file line number Diff line number Diff line change
@@ -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)
22 changes: 21 additions & 1 deletion game/squadrons/airwing.py
Original file line number Diff line number Diff line change
@@ -9,19 +9,39 @@
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]

199 changes: 153 additions & 46 deletions qt_ui/windows/AirWingConfigurationDialog.py
Original file line number Diff line number Diff line change
@@ -24,15 +24,20 @@
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 @@ def on_toggled(self, task: FlightType, checked: bool) -> None:
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 @@ def __init__(


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 @@ def __init__(self, squadron: Squadron, theater: ConflictTheater) -> None:
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 @@ def on_base_changed(self, index: int) -> None:
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 @@ def __init__(self, squadrons: list[Squadron], theater: ConflictTheater) -> None:

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 @@ def __init__(self, air_wing: AirWing) -> None:
self.setIconSize(QSize(91, 24))
self.setMinimumWidth(300)

model = QStandardItemModel(self)
self.setModel(model)
self.air_wing = air_wing

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)
self.item_model.appendRow(aircraft_item)

self.selectionModel().setCurrentIndex(
model.index(0, 0), QItemSelectionModel.Select
self.item_model.index(0, 0), QItemSelectionModel.Select
)
self.selectionModel().selectionChanged.connect(self.on_selection_changed)
for aircraft in air_wing.squadrons:
aircraft_item = QStandardItem(aircraft.name)
icon = self.icon_for(aircraft)

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)
model.appendRow(aircraft_item)
self.item_model.appendRow(aircraft_item)
self.update()

def on_selection_changed(
self, selected: QItemSelection, _deselected: QItemSelection
@@ -264,6 +348,18 @@ def on_selection_changed(
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 @@ def icon_for(aircraft: AircraftType) -> Optional[QIcon]:


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)

layout.addWidget(self.type_list, 1, 1, 1, 2)

add_button = QPushButton("Add Aircraft/Squadron")
add_button.clicked.connect(lambda state: self.addAircraftType())
layout.addWidget(add_button, 2, 1, 1, 1)

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(air_wing, theater)
layout.addLayout(self.squadrons_panel)
self.squadrons_panel = AircraftSquadronsPanel(self.air_wing, self.theater)
layout.addLayout(self.squadrons_panel, 1, 3, 2, 1)

def apply(self) -> None:
self.squadrons_panel.apply()
self.type_list.page_index_changed.connect(self.squadrons_panel.setCurrentIndex)

def on_aircraft_changed(self, index: QModelIndex) -> None:
self.squadrons_panel.setCurrentIndex(index)
def addAircraftType(self) -> None:
SquadronConfigPopup(self.coalition, self.theater, self.game).exec_()


class AirWingConfigurationDialog(QDialog):
@@ -328,12 +437,10 @@ def __init__(self, game: Game, parent) -> None:

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()
Loading

0 comments on commit e4ba9a8

Please sign in to comment.