mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Show number of missing pilots in the UI.
https://github.com/dcs-liberation/dcs_liberation/issues/276
This commit is contained in:
parent
4147d2f684
commit
9c2bad85d5
@ -280,6 +280,10 @@ class Flight:
|
|||||||
self.squadron.return_pilot(current_pilot)
|
self.squadron.return_pilot(current_pilot)
|
||||||
self.pilots[index] = pilot
|
self.pilots[index] = pilot
|
||||||
|
|
||||||
|
@property
|
||||||
|
def missing_pilots(self) -> int:
|
||||||
|
return len([p for p in self.pilots if p is None])
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
name = db.unit_type_name(self.unit_type)
|
name = db.unit_type_name(self.unit_type)
|
||||||
if self.custom_name:
|
if self.custom_name:
|
||||||
|
|||||||
@ -1,13 +0,0 @@
|
|||||||
from contextlib import contextmanager
|
|
||||||
from typing import ContextManager
|
|
||||||
|
|
||||||
from PySide2.QtGui import QPainter
|
|
||||||
|
|
||||||
|
|
||||||
@contextmanager
|
|
||||||
def painter_context(painter: QPainter) -> ContextManager[None]:
|
|
||||||
try:
|
|
||||||
painter.save()
|
|
||||||
yield
|
|
||||||
finally:
|
|
||||||
painter.restore()
|
|
||||||
122
qt_ui/delegates.py
Normal file
122
qt_ui/delegates.py
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
from contextlib import contextmanager
|
||||||
|
from typing import ContextManager, Optional
|
||||||
|
|
||||||
|
from PySide2.QtCore import QModelIndex, Qt, QSize
|
||||||
|
from PySide2.QtGui import QPainter, QFont, QFontMetrics, QIcon
|
||||||
|
from PySide2.QtWidgets import QStyledItemDelegate, QStyleOptionViewItem, QStyle
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def painter_context(painter: QPainter) -> ContextManager[None]:
|
||||||
|
try:
|
||||||
|
painter.save()
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
painter.restore()
|
||||||
|
|
||||||
|
|
||||||
|
class TwoColumnRowDelegate(QStyledItemDelegate):
|
||||||
|
HMARGIN = 4
|
||||||
|
VMARGIN = 4
|
||||||
|
|
||||||
|
def __init__(self, rows: int, columns: int, font_size: int = 12) -> None:
|
||||||
|
if columns not in (1, 2):
|
||||||
|
raise ValueError(f"Only one or two columns may be used, not {columns}")
|
||||||
|
super().__init__()
|
||||||
|
self.font_size = font_size
|
||||||
|
self.rows = rows
|
||||||
|
self.columns = columns
|
||||||
|
|
||||||
|
def get_font(self, option: QStyleOptionViewItem) -> QFont:
|
||||||
|
font = QFont(option.font)
|
||||||
|
font.setPointSize(self.font_size)
|
||||||
|
return font
|
||||||
|
|
||||||
|
def text_for(self, index: QModelIndex, row: int, column: int) -> str:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def paint(
|
||||||
|
self, painter: QPainter, option: QStyleOptionViewItem, index: QModelIndex
|
||||||
|
) -> None:
|
||||||
|
# Draw the list item with all the default selection styling, but with an
|
||||||
|
# invalid index so text formatting is left to us.
|
||||||
|
super().paint(painter, option, QModelIndex())
|
||||||
|
|
||||||
|
rect = option.rect.adjusted(
|
||||||
|
self.HMARGIN, self.VMARGIN, -self.HMARGIN, -self.VMARGIN
|
||||||
|
)
|
||||||
|
|
||||||
|
with painter_context(painter):
|
||||||
|
painter.setFont(self.get_font(option))
|
||||||
|
|
||||||
|
icon: Optional[QIcon] = index.data(Qt.DecorationRole)
|
||||||
|
|
||||||
|
if icon is not None:
|
||||||
|
icon.paint(
|
||||||
|
painter,
|
||||||
|
rect,
|
||||||
|
Qt.AlignLeft | Qt.AlignVCenter,
|
||||||
|
self.icon_mode(option),
|
||||||
|
self.icon_state(option),
|
||||||
|
)
|
||||||
|
rect = rect.adjusted(self.icon_size(option).width() + self.HMARGIN, 0, 0, 0)
|
||||||
|
|
||||||
|
row_height = rect.height() / self.rows
|
||||||
|
for row in range(self.rows):
|
||||||
|
y = row_height * row
|
||||||
|
location = rect.adjusted(0, y, 0, y)
|
||||||
|
painter.drawText(location, Qt.AlignLeft, self.text_for(index, row, 0))
|
||||||
|
if self.columns == 2:
|
||||||
|
painter.drawText(
|
||||||
|
location, Qt.AlignRight, self.text_for(index, row, 1)
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def icon_mode(option: QStyleOptionViewItem) -> QIcon.Mode:
|
||||||
|
if not (option.state & QStyle.State_Enabled):
|
||||||
|
return QIcon.Disabled
|
||||||
|
elif option.state & QStyle.State_Selected:
|
||||||
|
return QIcon.Selected
|
||||||
|
elif option.state & QStyle.State_Active:
|
||||||
|
return QIcon.Active
|
||||||
|
return QIcon.Normal
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def icon_state(option: QStyleOptionViewItem) -> QIcon.State:
|
||||||
|
return QIcon.On if option.state & QStyle.State_Open else QIcon.Off
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def icon_size(option: QStyleOptionViewItem) -> QSize:
|
||||||
|
icon_size: Optional[QSize] = option.decorationSize
|
||||||
|
if icon_size is None:
|
||||||
|
return QSize(0, 0)
|
||||||
|
else:
|
||||||
|
return icon_size
|
||||||
|
|
||||||
|
def sizeHint(self, option: QStyleOptionViewItem, index: QModelIndex) -> QSize:
|
||||||
|
metrics = QFontMetrics(self.get_font(option))
|
||||||
|
widths = []
|
||||||
|
heights = []
|
||||||
|
|
||||||
|
icon_size = self.icon_size(option)
|
||||||
|
icon_width = 0
|
||||||
|
icon_height = 0
|
||||||
|
if icon_size.width():
|
||||||
|
icon_width = icon_size.width() + self.HMARGIN
|
||||||
|
if icon_size.height():
|
||||||
|
icon_height = icon_size.height() + self.VMARGIN
|
||||||
|
|
||||||
|
for row in range(self.rows):
|
||||||
|
width = 0
|
||||||
|
height = 0
|
||||||
|
for column in range(self.columns):
|
||||||
|
size = metrics.size(0, self.text_for(index, row, column))
|
||||||
|
width += size.width()
|
||||||
|
height = max(height, size.height())
|
||||||
|
widths.append(width)
|
||||||
|
heights.append(height)
|
||||||
|
|
||||||
|
return QSize(
|
||||||
|
icon_width + max(widths) + 2 * self.HMARGIN,
|
||||||
|
max(icon_height, sum(heights)) + 2 * self.VMARGIN,
|
||||||
|
)
|
||||||
@ -10,10 +10,6 @@ from PySide2.QtCore import (
|
|||||||
)
|
)
|
||||||
from PySide2.QtGui import (
|
from PySide2.QtGui import (
|
||||||
QContextMenuEvent,
|
QContextMenuEvent,
|
||||||
QFont,
|
|
||||||
QFontMetrics,
|
|
||||||
QIcon,
|
|
||||||
QPainter,
|
|
||||||
)
|
)
|
||||||
from PySide2.QtWidgets import (
|
from PySide2.QtWidgets import (
|
||||||
QAbstractItemView,
|
QAbstractItemView,
|
||||||
@ -25,9 +21,6 @@ from PySide2.QtWidgets import (
|
|||||||
QMenu,
|
QMenu,
|
||||||
QPushButton,
|
QPushButton,
|
||||||
QSplitter,
|
QSplitter,
|
||||||
QStyle,
|
|
||||||
QStyleOptionViewItem,
|
|
||||||
QStyledItemDelegate,
|
|
||||||
QVBoxLayout,
|
QVBoxLayout,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -35,111 +28,42 @@ from gen.ato import Package
|
|||||||
from gen.flights.flight import Flight
|
from gen.flights.flight import Flight
|
||||||
from gen.flights.traveltime import TotEstimator
|
from gen.flights.traveltime import TotEstimator
|
||||||
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
|
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
|
||||||
from ..delegate_helpers import painter_context
|
from ..delegates import TwoColumnRowDelegate
|
||||||
from ..models import AtoModel, GameModel, NullListModel, PackageModel
|
from ..models import AtoModel, GameModel, NullListModel, PackageModel
|
||||||
|
|
||||||
|
|
||||||
class FlightDelegate(QStyledItemDelegate):
|
class FlightDelegate(TwoColumnRowDelegate):
|
||||||
FONT_SIZE = 10
|
|
||||||
HMARGIN = 4
|
|
||||||
VMARGIN = 4
|
|
||||||
|
|
||||||
def __init__(self, package: Package) -> None:
|
def __init__(self, package: Package) -> None:
|
||||||
super().__init__()
|
super().__init__(rows=2, columns=2, font_size=10)
|
||||||
self.package = package
|
self.package = package
|
||||||
|
|
||||||
def get_font(self, option: QStyleOptionViewItem) -> QFont:
|
|
||||||
font = QFont(option.font)
|
|
||||||
font.setPointSize(self.FONT_SIZE)
|
|
||||||
return font
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def flight(index: QModelIndex) -> Flight:
|
def flight(index: QModelIndex) -> Flight:
|
||||||
return index.data(PackageModel.FlightRole)
|
return index.data(PackageModel.FlightRole)
|
||||||
|
|
||||||
def first_row_text(self, index: QModelIndex) -> str:
|
def text_for(self, index: QModelIndex, row: int, column: int) -> str:
|
||||||
flight = self.flight(index)
|
flight = self.flight(index)
|
||||||
estimator = TotEstimator(self.package)
|
if (row, column) == (0, 0):
|
||||||
delay = estimator.mission_start_time(flight)
|
estimator = TotEstimator(self.package)
|
||||||
return f"{flight} in {delay}"
|
delay = estimator.mission_start_time(flight)
|
||||||
|
return f"{flight} in {delay}"
|
||||||
def second_row_text(self, index: QModelIndex) -> str:
|
elif (row, column) == (0, 1):
|
||||||
flight = self.flight(index)
|
|
||||||
origin = flight.from_cp.name
|
|
||||||
if flight.arrival != flight.departure:
|
|
||||||
return f"From {origin} to {flight.arrival.name}"
|
|
||||||
return f"From {origin}"
|
|
||||||
|
|
||||||
def paint(
|
|
||||||
self, painter: QPainter, option: QStyleOptionViewItem, index: QModelIndex
|
|
||||||
) -> None:
|
|
||||||
# Draw the list item with all the default selection styling, but with an
|
|
||||||
# invalid index so text formatting is left to us.
|
|
||||||
super().paint(painter, option, QModelIndex())
|
|
||||||
|
|
||||||
rect = option.rect.adjusted(
|
|
||||||
self.HMARGIN, self.VMARGIN, -self.HMARGIN, -self.VMARGIN
|
|
||||||
)
|
|
||||||
|
|
||||||
with painter_context(painter):
|
|
||||||
painter.setFont(self.get_font(option))
|
|
||||||
|
|
||||||
icon: Optional[QIcon] = index.data(Qt.DecorationRole)
|
|
||||||
if icon is not None:
|
|
||||||
icon.paint(
|
|
||||||
painter,
|
|
||||||
rect,
|
|
||||||
Qt.AlignLeft | Qt.AlignVCenter,
|
|
||||||
self.icon_mode(option),
|
|
||||||
self.icon_state(option),
|
|
||||||
)
|
|
||||||
|
|
||||||
rect = rect.adjusted(self.icon_size(option).width() + self.HMARGIN, 0, 0, 0)
|
|
||||||
painter.drawText(rect, Qt.AlignLeft, self.first_row_text(index))
|
|
||||||
line2 = rect.adjusted(0, rect.height() / 2, 0, rect.height() / 2)
|
|
||||||
painter.drawText(line2, Qt.AlignLeft, self.second_row_text(index))
|
|
||||||
|
|
||||||
clients = self.num_clients(index)
|
clients = self.num_clients(index)
|
||||||
if clients:
|
return f"Player Slots: {clients}" if clients else ""
|
||||||
painter.drawText(rect, Qt.AlignRight, f"Player Slots: {clients}")
|
elif (row, column) == (1, 0):
|
||||||
|
origin = flight.from_cp.name
|
||||||
|
if flight.arrival != flight.departure:
|
||||||
|
return f"From {origin} to {flight.arrival.name}"
|
||||||
|
return f"From {origin}"
|
||||||
|
elif (row, column) == (1, 1):
|
||||||
|
missing_pilots = flight.missing_pilots
|
||||||
|
return f"Missing pilots: {flight.missing_pilots}" if missing_pilots else ""
|
||||||
|
return ""
|
||||||
|
|
||||||
def num_clients(self, index: QModelIndex) -> int:
|
def num_clients(self, index: QModelIndex) -> int:
|
||||||
flight = self.flight(index)
|
flight = self.flight(index)
|
||||||
return flight.client_count
|
return flight.client_count
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def icon_mode(option: QStyleOptionViewItem) -> QIcon.Mode:
|
|
||||||
if not (option.state & QStyle.State_Enabled):
|
|
||||||
return QIcon.Disabled
|
|
||||||
elif option.state & QStyle.State_Selected:
|
|
||||||
return QIcon.Selected
|
|
||||||
elif option.state & QStyle.State_Active:
|
|
||||||
return QIcon.Active
|
|
||||||
return QIcon.Normal
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def icon_state(option: QStyleOptionViewItem) -> QIcon.State:
|
|
||||||
return QIcon.On if option.state & QStyle.State_Open else QIcon.Off
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def icon_size(option: QStyleOptionViewItem) -> QSize:
|
|
||||||
icon_size: Optional[QSize] = option.decorationSize
|
|
||||||
if icon_size is None:
|
|
||||||
return QSize(0, 0)
|
|
||||||
else:
|
|
||||||
return icon_size
|
|
||||||
|
|
||||||
def sizeHint(self, option: QStyleOptionViewItem, index: QModelIndex) -> QSize:
|
|
||||||
left = self.icon_size(option).width() + self.HMARGIN
|
|
||||||
metrics = QFontMetrics(self.get_font(option))
|
|
||||||
first = metrics.size(0, self.first_row_text(index))
|
|
||||||
second = metrics.size(0, self.second_row_text(index))
|
|
||||||
text_width = max(first.width(), second.width())
|
|
||||||
return QSize(
|
|
||||||
left + text_width + 2 * self.HMARGIN,
|
|
||||||
first.height() + second.height() + 2 * self.VMARGIN,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class QFlightList(QListView):
|
class QFlightList(QListView):
|
||||||
"""List view for displaying the flights of a package."""
|
"""List view for displaying the flights of a package."""
|
||||||
@ -310,62 +234,35 @@ class QFlightPanel(QGroupBox):
|
|||||||
self.flight_list.delete_flight(index)
|
self.flight_list.delete_flight(index)
|
||||||
|
|
||||||
|
|
||||||
class PackageDelegate(QStyledItemDelegate):
|
class PackageDelegate(TwoColumnRowDelegate):
|
||||||
FONT_SIZE = 12
|
def __init__(self) -> None:
|
||||||
HMARGIN = 4
|
super().__init__(rows=2, columns=2)
|
||||||
VMARGIN = 4
|
|
||||||
|
|
||||||
def get_font(self, option: QStyleOptionViewItem) -> QFont:
|
|
||||||
font = QFont(option.font)
|
|
||||||
font.setPointSize(self.FONT_SIZE)
|
|
||||||
return font
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def package(index: QModelIndex) -> Package:
|
def package(index: QModelIndex) -> Package:
|
||||||
return index.data(AtoModel.PackageRole)
|
return index.data(AtoModel.PackageRole)
|
||||||
|
|
||||||
def left_text(self, index: QModelIndex) -> str:
|
def text_for(self, index: QModelIndex, row: int, column: int) -> str:
|
||||||
package = self.package(index)
|
package = self.package(index)
|
||||||
return f"{package.package_description} {package.target.name}"
|
if (row, column) == (0, 0):
|
||||||
|
return f"{package.package_description} {package.target.name}"
|
||||||
def right_text(self, index: QModelIndex) -> str:
|
elif (row, column) == (0, 1):
|
||||||
package = self.package(index)
|
|
||||||
return f"TOT T+{package.time_over_target}"
|
|
||||||
|
|
||||||
def paint(
|
|
||||||
self, painter: QPainter, option: QStyleOptionViewItem, index: QModelIndex
|
|
||||||
) -> None:
|
|
||||||
# Draw the list item with all the default selection styling, but with an
|
|
||||||
# invalid index so text formatting is left to us.
|
|
||||||
super().paint(painter, option, QModelIndex())
|
|
||||||
|
|
||||||
rect = option.rect.adjusted(
|
|
||||||
self.HMARGIN, self.VMARGIN, -self.HMARGIN, -self.VMARGIN
|
|
||||||
)
|
|
||||||
|
|
||||||
with painter_context(painter):
|
|
||||||
painter.setFont(self.get_font(option))
|
|
||||||
|
|
||||||
painter.drawText(rect, Qt.AlignLeft, self.left_text(index))
|
|
||||||
line2 = rect.adjusted(0, rect.height() / 2, 0, rect.height() / 2)
|
|
||||||
painter.drawText(line2, Qt.AlignLeft, self.right_text(index))
|
|
||||||
|
|
||||||
clients = self.num_clients(index)
|
clients = self.num_clients(index)
|
||||||
if clients:
|
return f"Player Slots: {clients}" if clients else ""
|
||||||
painter.drawText(rect, Qt.AlignRight, f"Player Slots: {clients}")
|
elif (row, column) == (1, 0):
|
||||||
|
return f"TOT T+{package.time_over_target}"
|
||||||
|
elif (row, column) == (1, 1):
|
||||||
|
unassigned_pilots = self.missing_pilots(index)
|
||||||
|
return f"Missing pilots: {unassigned_pilots}" if unassigned_pilots else ""
|
||||||
|
return ""
|
||||||
|
|
||||||
def num_clients(self, index: QModelIndex) -> int:
|
def num_clients(self, index: QModelIndex) -> int:
|
||||||
package = self.package(index)
|
package = self.package(index)
|
||||||
return sum(f.client_count for f in package.flights)
|
return sum(f.client_count for f in package.flights)
|
||||||
|
|
||||||
def sizeHint(self, option: QStyleOptionViewItem, index: QModelIndex) -> QSize:
|
def missing_pilots(self, index: QModelIndex) -> int:
|
||||||
metrics = QFontMetrics(self.get_font(option))
|
package = self.package(index)
|
||||||
left = metrics.size(0, self.left_text(index))
|
return sum(f.missing_pilots for f in package.flights)
|
||||||
right = metrics.size(0, self.right_text(index))
|
|
||||||
return QSize(
|
|
||||||
max(left.width(), right.width()) + 2 * self.HMARGIN,
|
|
||||||
left.height() + right.height() + 2 * self.VMARGIN,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class QPackageList(QListView):
|
class QPackageList(QListView):
|
||||||
@ -376,7 +273,7 @@ class QPackageList(QListView):
|
|||||||
self.ato_model = model
|
self.ato_model = model
|
||||||
self.setModel(model)
|
self.setModel(model)
|
||||||
self.setItemDelegate(PackageDelegate())
|
self.setItemDelegate(PackageDelegate())
|
||||||
self.setIconSize(QSize(91, 24))
|
self.setIconSize(QSize(0, 0))
|
||||||
self.setSelectionBehavior(QAbstractItemView.SelectItems)
|
self.setSelectionBehavior(QAbstractItemView.SelectItems)
|
||||||
self.model().rowsInserted.connect(self.on_new_packages)
|
self.model().rowsInserted.connect(self.on_new_packages)
|
||||||
self.doubleClicked.connect(self.on_double_click)
|
self.doubleClicked.connect(self.on_double_click)
|
||||||
|
|||||||
@ -18,7 +18,7 @@ from PySide2.QtWidgets import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from game.squadrons import Squadron
|
from game.squadrons import Squadron
|
||||||
from qt_ui.delegate_helpers import painter_context
|
from qt_ui.delegates import painter_context
|
||||||
from qt_ui.models import GameModel, AirWingModel, SquadronModel
|
from qt_ui.models import GameModel, AirWingModel, SquadronModel
|
||||||
from qt_ui.windows.SquadronDialog import SquadronDialog
|
from qt_ui.windows.SquadronDialog import SquadronDialog
|
||||||
|
|
||||||
|
|||||||
@ -23,7 +23,7 @@ from PySide2.QtWidgets import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from game.transfers import TransferOrder
|
from game.transfers import TransferOrder
|
||||||
from qt_ui.delegate_helpers import painter_context
|
from qt_ui.delegates import painter_context
|
||||||
from qt_ui.models import GameModel, TransferModel
|
from qt_ui.models import GameModel, TransferModel
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -18,7 +18,7 @@ from PySide2.QtWidgets import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from game.squadrons import Pilot
|
from game.squadrons import Pilot
|
||||||
from qt_ui.delegate_helpers import painter_context
|
from qt_ui.delegates import painter_context
|
||||||
from qt_ui.models import SquadronModel
|
from qt_ui.models import SquadronModel
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user