Move FlightJs out of MapModel.

This commit is contained in:
Dan Albert
2022-02-22 20:40:58 -08:00
parent ad0d3412fb
commit 45e76e12b6
18 changed files with 333 additions and 326 deletions

View File

@@ -169,9 +169,6 @@ class PackageModel(QAbstractListModel):
"""Removes the given flight from the package."""
index = self.package.flights.index(flight)
self.beginRemoveRows(QModelIndex(), index, index)
if flight.cargo is not None:
flight.cargo.transport = None
flight.return_pilots_and_aircraft()
self.package.remove_flight(flight)
self.endRemoveRows()
self.update_tot()
@@ -253,6 +250,8 @@ class AtoModel(QAbstractListModel):
"""Adds a package to the ATO."""
self.beginInsertRows(QModelIndex(), self.rowCount(), self.rowCount())
self.ato.add_package(package)
# We do not need to send events for new flights in the package here. Events were
# already sent when the flights were added to the in-progress package.
self.endInsertRows()
# noinspection PyUnresolvedReferences
self.client_slots_changed.emit()
@@ -264,14 +263,11 @@ class AtoModel(QAbstractListModel):
def delete_package(self, package: Package) -> None:
"""Removes the given package from the ATO."""
EventStream.put_nowait(GameUpdateEvents().delete_flights_in_package(package))
self.package_models.release(package)
index = self.ato.packages.index(package)
self.beginRemoveRows(QModelIndex(), index, index)
self.ato.remove_package(package)
for flight in package.flights:
flight.return_pilots_and_aircraft()
if flight.cargo is not None:
flight.cargo.transport = None
self.endRemoveRows()
# noinspection PyUnresolvedReferences
self.client_slots_changed.emit()
@@ -280,9 +276,7 @@ class AtoModel(QAbstractListModel):
def on_packages_changed(self) -> None:
if self.game is not None:
self.game.compute_unculled_zones()
events = GameUpdateEvents()
events.update_unculled_zones()
EventStream.put_nowait(events)
EventStream.put_nowait(GameUpdateEvents().update_unculled_zones())
def package_at_index(self, index: QModelIndex) -> Package:
"""Returns the package at the given index."""

View File

@@ -27,7 +27,8 @@ from PySide2.QtWidgets import (
from game.ato.flight import Flight
from game.ato.package import Package
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
from game.server import EventStream
from game.sim import GameUpdateEvents
from ..delegates import TwoColumnRowDelegate
from ..models import AtoModel, GameModel, NullListModel, PackageModel
@@ -128,8 +129,10 @@ class QFlightList(QListView):
)
def delete_flight(self, index: QModelIndex) -> None:
EventStream.put_nowait(
GameUpdateEvents().delete_flight(self.package_model.flight_at_index(index))
)
self.package_model.delete_flight_at_index(index)
GameUpdateSignal.get_instance().redraw_flight_paths()
def contextMenuEvent(self, event: QContextMenuEvent) -> None:
index = self.indexAt(event.pos())
@@ -209,13 +212,13 @@ class QFlightPanel(QGroupBox):
self.delete_button.setEnabled(enabled)
self.change_map_flight_selection(index)
@staticmethod
def change_map_flight_selection(index: QModelIndex) -> None:
def change_map_flight_selection(self, index: QModelIndex) -> None:
events = GameUpdateEvents()
if not index.isValid():
GameUpdateSignal.get_instance().select_flight(None)
return
GameUpdateSignal.get_instance().select_flight(index.row())
events.deselect_flight()
else:
events.select_flight(self.package_model.flight_at_index(index))
EventStream.put_nowait(events)
def on_edit(self) -> None:
"""Opens the flight edit dialog."""
@@ -303,7 +306,6 @@ class QPackageList(QListView):
def delete_package(self, index: QModelIndex) -> None:
self.ato_model.delete_package_at_index(index)
GameUpdateSignal.get_instance().redraw_flight_paths()
def on_new_packages(self, _parent: QModelIndex, first: int, _last: int) -> None:
# Select the newly created pacakges. This should only ever happen due to
@@ -390,14 +392,18 @@ class QPackagePanel(QGroupBox):
def change_map_package_selection(self, index: QModelIndex) -> None:
if not index.isValid():
GameUpdateSignal.get_instance().select_package(None)
EventStream.put_nowait(GameUpdateEvents().deselect_flight())
return
package = self.ato_model.get_package_model(index)
if package.rowCount() == 0:
GameUpdateSignal.get_instance().select_package(None)
EventStream.put_nowait(GameUpdateEvents().deselect_flight())
else:
GameUpdateSignal.get_instance().select_package(index.row())
EventStream.put_nowait(
GameUpdateEvents().select_flight(
package.flight_at_index(package.index(0))
)
)
def on_edit(self) -> None:
"""Opens the package edit dialog."""

View File

@@ -1,51 +0,0 @@
from __future__ import annotations
from PySide2.QtCore import Property, QObject, Signal, Slot
from game.ato import Flight
from game.ato.flightstate import InFlight
from game.server.leaflet import LeafletLatLon
from qt_ui.models import AtoModel
class FlightJs(QObject):
idChanged = Signal()
positionChanged = Signal()
blueChanged = Signal()
selectedChanged = Signal()
def __init__(self, flight: Flight, selected: bool, ato_model: AtoModel) -> None:
super().__init__()
self.flight = flight
self._selected = selected
self.ato_model = ato_model
@Property(str, notify=idChanged)
def id(self) -> str:
return str(self.flight.id)
@Property(list, notify=positionChanged)
def position(self) -> LeafletLatLon:
if isinstance(self.flight.state, InFlight):
return self.flight.state.estimate_position().latlng().as_list()
return []
@Property(bool, notify=blueChanged)
def blue(self) -> bool:
return self.flight.departure.captured
@Property(bool, notify=selectedChanged)
def selected(self) -> bool:
return self._selected
@Slot(result=bool)
def flightIsInAto(self) -> bool:
if self.flight.package not in self.flight.squadron.coalition.ato.packages:
return False
if self.flight not in self.flight.package.flights:
return False
return True
def set_selected(self, value: bool) -> None:
self._selected = value
self.selectedChanged.emit()

View File

@@ -1,13 +1,11 @@
from __future__ import annotations
import logging
from typing import List, Optional, Tuple
from typing import List, Optional
from PySide2.QtCore import Property, QObject, Signal
from dcs.mapping import LatLng
from game import Game
from game.ato.airtaaskingorder import AirTaskingOrder
from game.profiling import logged_duration
from game.server.leaflet import LeafletLatLon
from game.server.security import ApiKeyManager
@@ -17,7 +15,6 @@ from game.theater import (
from qt_ui.models import GameModel
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
from .controlpointjs import ControlPointJs
from .flightjs import FlightJs
from .frontlinejs import FrontLineJs
from .groundobjectjs import GroundObjectJs
from .supplyroutejs import SupplyRouteJs
@@ -47,9 +44,8 @@ class MapModel(QObject):
controlPointsChanged = Signal()
groundObjectsChanged = Signal()
supplyRoutesChanged = Signal()
flightsChanged = Signal()
frontLinesChanged = Signal()
selectedFlightChanged = Signal(str)
mapReset = Signal()
def __init__(self, game_model: GameModel) -> None:
super().__init__()
@@ -58,79 +54,18 @@ class MapModel(QObject):
self._control_points = []
self._ground_objects = []
self._supply_routes = []
self._flights: dict[tuple[bool, int, int], FlightJs] = {}
self._front_lines = []
self._selected_flight_index: Optional[Tuple[int, int]] = None
GameUpdateSignal.get_instance().game_loaded.connect(self.on_game_load)
GameUpdateSignal.get_instance().flight_paths_changed.connect(self.reset_atos)
GameUpdateSignal.get_instance().package_selection_changed.connect(
self.set_package_selection
)
GameUpdateSignal.get_instance().flight_selection_changed.connect(
self.set_flight_selection
)
self.reset()
def clear(self) -> None:
self._control_points = []
self._supply_routes = []
self._ground_objects = []
self._flights = {}
self._front_lines = []
self.cleared.emit()
def set_package_selection(self, index: int) -> None:
self.deselect_current_flight()
# Optional[int] isn't a valid type for a Qt signal. None will be converted to
# zero automatically. We use -1 to indicate no selection.
if index == -1:
self._selected_flight_index = None
else:
self._selected_flight_index = index, 0
self.select_current_flight()
def set_flight_selection(self, index: int) -> None:
self.deselect_current_flight()
if self._selected_flight_index is None:
if index != -1:
# We don't know what order update_package_selection and
# update_flight_selection will be called in when the last
# package is removed. If no flight is selected, it's not a
# problem to also have no package selected.
logging.error("Flight was selected with no package selected")
return
# Optional[int] isn't a valid type for a Qt signal. None will be converted to
# zero automatically. We use -1 to indicate no selection.
if index == -1:
self._selected_flight_index = self._selected_flight_index[0], None
self._selected_flight_index = self._selected_flight_index[0], index
self.select_current_flight()
@property
def _selected_flight(self) -> Optional[FlightJs]:
if self._selected_flight_index is None:
return None
package_index, flight_index = self._selected_flight_index
blue = True
return self._flights.get((blue, package_index, flight_index))
def deselect_current_flight(self) -> None:
flight = self._selected_flight
if flight is None:
return None
flight.set_selected(False)
def select_current_flight(self):
flight = self._selected_flight
if flight is None:
self.selectedFlightChanged.emit(None)
return None
flight.set_selected(True)
self.selectedFlightChanged.emit(str(flight.flight.id))
def reset(self) -> None:
if self.game_model.game is None:
self.clear()
@@ -139,8 +74,8 @@ class MapModel(QObject):
self.reset_control_points()
self.reset_ground_objects()
self.reset_routes()
self.reset_atos()
self.reset_front_lines()
self.mapReset.emit()
def on_game_load(self, game: Optional[Game]) -> None:
if game is not None:
@@ -158,29 +93,6 @@ class MapModel(QObject):
def mapCenter(self) -> LeafletLatLon:
return self._map_center.as_list()
def _flights_in_ato(
self, ato: AirTaskingOrder, blue: bool
) -> dict[tuple[bool, int, int], FlightJs]:
flights = {}
for p_idx, package in enumerate(ato.packages):
for f_idx, flight in enumerate(package.flights):
flights[blue, p_idx, f_idx] = FlightJs(
flight,
selected=blue and (p_idx, f_idx) == self._selected_flight_index,
ato_model=self.game_model.ato_model_for(blue),
)
return flights
def reset_atos(self) -> None:
self._flights = self._flights_in_ato(
self.game.blue.ato, blue=True
) | self._flights_in_ato(self.game.red.ato, blue=False)
self.flightsChanged.emit()
@Property(list, notify=flightsChanged)
def flights(self) -> list[FlightJs]:
return list(self._flights.values())
def reset_control_points(self) -> None:
self._control_points = [
ControlPointJs(c, self.game_model, self.game.theater)

View File

@@ -17,28 +17,12 @@ class GameUpdateSignal(QObject):
game_loaded = Signal(Game)
flight_paths_changed = Signal()
package_selection_changed = Signal(int) # -1 indicates no selection.
flight_selection_changed = Signal(int) # -1 indicates no selection.
def __init__(self):
super(GameUpdateSignal, self).__init__()
GameUpdateSignal.instance = self
self.game_loaded.connect(self.updateGame)
def select_package(self, index: Optional[int]) -> None:
# noinspection PyUnresolvedReferences
self.package_selection_changed.emit(-1 if index is None else index)
def select_flight(self, index: Optional[int]) -> None:
# noinspection PyUnresolvedReferences
self.flight_selection_changed.emit(-1 if index is None else index)
def redraw_flight_paths(self) -> None:
# noinspection PyUnresolvedReferences
self.flight_paths_changed.emit()
def updateGame(self, game: Optional[Game]):
# noinspection PyUnresolvedReferences
self.gameupdated.emit(game)

View File

@@ -5,9 +5,10 @@ from PySide2.QtWidgets import (
)
from game.ato.flight import Flight
from game.server import EventStream
from game.sim import GameUpdateEvents
from qt_ui.models import GameModel, PackageModel
from qt_ui.uiconstants import EVENT_ICONS
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
from qt_ui.windows.mission.flight.QFlightPlanner import QFlightPlanner
@@ -24,6 +25,7 @@ class QEditFlightDialog(QDialog):
super().__init__(parent=parent)
self.game_model = game_model
self.flight = flight
self.setWindowTitle("Edit flight")
self.setWindowIcon(EVENT_ICONS["strike"])
@@ -37,5 +39,5 @@ class QEditFlightDialog(QDialog):
self.finished.connect(self.on_close)
def on_close(self, _result) -> None:
GameUpdateSignal.get_instance().redraw_flight_paths()
EventStream.put_nowait(GameUpdateEvents().update_flight(self.flight))
self.game_model.ato_model.client_slots_changed.emit()

View File

@@ -16,14 +16,15 @@ from PySide2.QtWidgets import (
)
from game.ato.flight import Flight
from game.ato.flightplan import FlightPlanBuilder, PlanningError
from game.ato.package import Package
from game.game import Game
from game.server import EventStream
from game.sim import GameUpdateEvents
from game.theater.missiontarget import MissionTarget
from game.ato.flightplan import FlightPlanBuilder, PlanningError
from qt_ui.models import AtoModel, GameModel, PackageModel
from qt_ui.uiconstants import EVENT_ICONS
from qt_ui.widgets.ato import QFlightList
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
from qt_ui.windows.mission.flight.QFlightCreator import QFlightCreator
@@ -141,9 +142,10 @@ class QPackageDialog(QDialog):
def on_cancel(self) -> None:
pass
@staticmethod
def on_close(_result) -> None:
GameUpdateSignal.get_instance().redraw_flight_paths()
def on_close(self, _result) -> None:
EventStream.put_nowait(
GameUpdateEvents().update_flights_in_package(self.package_model.package)
)
def on_save(self) -> None:
self.save_tot()
@@ -183,13 +185,14 @@ class QPackageDialog(QDialog):
)
try:
planner.populate_flight_plan(flight)
self.package_model.update_tot()
EventStream.put_nowait(GameUpdateEvents().new_flight(flight))
except PlanningError as ex:
self.package_model.delete_flight(flight)
logging.exception("Could not create flight")
QMessageBox.critical(
self, "Could not create flight", str(ex), QMessageBox.Ok
)
self.package_model.update_tot()
# noinspection PyUnresolvedReferences
self.package_changed.emit()
@@ -252,7 +255,7 @@ class QNewPackageDialog(QPackageDialog):
def on_cancel(self) -> None:
super().on_cancel()
for flight in self.package_model.package.flights:
flight.return_pilots_and_aircraft()
self.package_model.delete_flight(flight)
class QEditPackageDialog(QPackageDialog):