Datalink support + pydcs update

Support for 2.9.10.4160, though without Iraq, but finally there's some basic support for datalink...
This commit is contained in:
Raffson
2024-12-16 03:07:26 +01:00
parent 0eb06f9af5
commit 64d60e5ccf
13 changed files with 415 additions and 36 deletions

View File

@@ -0,0 +1,145 @@
import re
from typing import Dict, Callable, Union
from PySide6.QtGui import QFocusEvent
from PySide6.QtWidgets import QLineEdit
from dcs.unitpropertydescription import UnitPropertyDescription
from game.ato import Flight
from game.ato.flightmember import FlightMember
from game.radio.datalink import DataLinkKey
class PropertyEditBox(QLineEdit):
def __init__(
self,
flight_member: FlightMember,
prop: UnitPropertyDescription,
flight: Flight,
) -> None:
super().__init__()
self.flight_member = flight_member
self.prop = prop
self.flight = flight
self.setFixedWidth(100)
self.setText(
self.flight_member.properties.get(self.prop.identifier, self.prop.default)
)
self.textChanged.connect(self.on_value_changed)
self._datalink_handlers: Dict[
DataLinkKey,
Union[Callable[[int, str], str], Callable[[int, str, bool], str]],
] = {
DataLinkKey.LINK16: self._link16_input,
DataLinkKey.SADL: self._sadl_input,
DataLinkKey.IDM: self._idm_input,
}
@property
def datalink_type(self) -> DataLinkKey:
return DataLinkKey.from_aircraft_type(self.flight.unit_type)
def focusOutEvent(self, e: QFocusEvent) -> None:
super(PropertyEditBox, self).focusOutEvent(e)
link_type = self.datalink_type
if (handler := self._datalink_handlers.get(link_type)) is not None:
value = handler(0, self.text(), True)
self.setText(value)
def on_value_changed(self, value: str) -> None:
cursor = self.cursorPosition()
link_type = self.datalink_type
if (handler := self._datalink_handlers.get(link_type)) is not None:
value = handler(cursor, value.upper())
if not value and self.prop.identifier in self.flight_member.properties:
del self.flight_member.properties[self.prop.identifier]
else:
self.flight_member.properties[self.prop.identifier] = value
self.setText(value)
self.setCursorPosition(cursor)
if cursor > len(value):
self.setCursorPosition(0)
def _link16_input(self, cursor: int, value: str, focus_lost: bool = False) -> str:
max_input_length = 5
if "VoiceCallsign" in self.prop.identifier:
max_input_length = 2
value = self.common_link16_sadl_logic(
cursor, focus_lost, max_input_length, "STN_L16", value
)
return value
def common_link16_sadl_logic(
self,
cursor: int,
focus_lost: bool,
max_input_length: int,
identifier: str,
value: str,
) -> str:
value = value[:max_input_length]
if not (
("Label" in self.prop.identifier and value.isalpha())
or ("Number" in self.prop.identifier and value.isdigit())
or (identifier == self.prop.identifier and self._is_octal(value))
):
value = self._restore_from_property(cursor)
if focus_lost and 2 < max_input_length > len(value):
# STN_L16 --> prepend with zeroes
value = "0" * (max_input_length - len(value)) + value
return value
def _restore_from_property(self, cursor):
props = self.flight_member.properties
value = props.get(self.prop.identifier, "")[: cursor - 1]
return value
@staticmethod
def _is_octal(value: str) -> bool:
if re.match(r"^[0-7]+$", value):
return True
return False
def _sadl_input(self, cursor: int, value: str, focus_lost: bool = False) -> str:
max_input_length = 4
if "VoiceCallsign" in self.prop.identifier:
max_input_length = 2
value = self.common_link16_sadl_logic(
cursor, focus_lost, max_input_length, "SADL_TN", value
)
return value
def _idm_input(self, cursor: int, value: str, _: bool = False) -> str:
if "TN_IDM_LB" == self.prop.identifier:
value = value[:2] # Originator ID
if len(value) == 1 and not value.isalnum():
return ""
elif len(value) == 2 and not self._valid_2char_idm(value):
value = str(value[0])
else:
value = value[:5] # Ownship CallSign
if not re.match(r"^[A-Z0-9/*\-+.]{,5}$", value):
value = self._restore_from_property(cursor)
return value
@staticmethod
def _valid_2char_idm(value: str) -> bool:
if len(value) != 2:
return False
first = value[0]
second = value[1]
return (
first in ["1", "2"]
and second.isalnum()
or first == "3"
and (re.match(r"^[A-I]$", second) or second.isdigit())
)
def set_flight_member(self, flight_member: FlightMember) -> None:
self.flight_member = flight_member
self.setText(
self.flight_member.properties.get(self.prop.identifier, self.prop.default)
)

View File

@@ -11,6 +11,7 @@ from game.ato.flightmember import FlightMember
from .missingpropertydataerror import MissingPropertyDataError
from .propertycheckbox import PropertyCheckBox
from .propertycombobox import PropertyComboBox
from .propertyeditbox import PropertyEditBox
from .propertyspinbox import PropertySpinBox
@@ -22,6 +23,7 @@ class UnhandledControlTypeError(RuntimeError):
class PropertyEditor(QGridLayout):
def __init__(self, flight: Flight, flight_member: FlightMember) -> None:
super().__init__()
self.flight = flight
self.flight_member = flight_member
self.flight_member_update_listeners: list[Callable[[FlightMember], None]] = []
@@ -78,7 +80,7 @@ class PropertyEditor(QGridLayout):
def control_for_property(self, prop: UnitPropertyDescription) -> Optional[QWidget]:
# Valid values are:
# "checkbox", "comboList", "groupbox", "label", "slider", "spinbox"
# "checkbox", "comboList", "groupbox", "label", "slider", "spinbox", "editbox"
match prop.control:
case "checkbox":
widget = PropertyCheckBox(self.flight_member, prop)
@@ -94,6 +96,10 @@ class PropertyEditor(QGridLayout):
widget = PropertySpinBox(self.flight_member, prop)
self.flight_member_update_listeners.append(widget.set_flight_member)
return widget
case "editbox":
widget = PropertyEditBox(self.flight_member, prop, self.flight)
self.flight_member_update_listeners.append(widget.set_flight_member)
return widget
case _:
raise UnhandledControlTypeError(prop.control)