diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f6ec43f..f1aac36 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,7 +9,7 @@ on: env: CACHE_VERSION: 1 - DEFAULT_PYTHON: "3.10" + DEFAULT_PYTHON: "3.11" PRE_COMMIT_HOME: ~/.cache/pre-commit jobs: @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.10"] + python-version: ["3.11"] steps: - name: Check out code from GitHub uses: actions/checkout@v2 @@ -48,7 +48,6 @@ jobs: . venv/bin/activate pip install -U pip setuptools pre-commit pip install -r requirements.txt - pip install -e . pre-commit: name: Prepare pre-commit environment diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1d964a1..4e96665 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,13 +1,13 @@ repos: - repo: https://github.com/psf/black - rev: 22.3.0 + rev: 23.3.0 hooks: - id: black args: - --safe - --quiet - - repo: https://gitlab.com/pycqa/flake8 + - repo: https://github.com/pycqa/flake8 rev: 3.9.2 hooks: - id: flake8 @@ -21,6 +21,6 @@ repos: hooks: - id: codespell args: - - --ignore-words-list=dout,hass + - --ignore-words-list=dout,hass,hsi - --skip="./.*" - --quiet-level=2 \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..99d1a50 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,23 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "ViewTester", + "type": "python", + "request": "launch", + "module": "pyG5.pyG5ViewTester", + "justMyCode": true + }, + { + "name": "App", + "type": "python", + "request": "launch", + "module": "pyG5.pyG5Main", + "args": ["-v"], + "justMyCode": true + } + ] +} diff --git a/pyG5/pyG5Main.py b/pyG5/pyG5Main.py index b1dbdf0..2b3ff67 100644 --- a/pyG5/pyG5Main.py +++ b/pyG5/pyG5Main.py @@ -9,24 +9,32 @@ import argparse import logging -import platform import sys +import platform -from PyQt5.QtCore import Qt -from PyQt5.QtGui import QFont, QFontDatabase -from PyQt5.QtWidgets import ( +from PySide6.QtCore import ( + Qt, + QTimer, + QCoreApplication, + QSettings, + Slot, + QByteArray, + Signal, + QEvent, +) +from PySide6.QtGui import QFont, QFontDatabase, QCloseEvent, QAction +from PySide6.QtWidgets import ( QApplication, QMainWindow, - QMenu, ) from pyG5.pyG5Network import pyG5NetWorkManager -from pyG5.pyG5View import pyG5DualStack +from pyG5.pyG5View import pyG5DualStackFMA, pyG5SecondaryWidget class pyG5App(QApplication): - """pyG5App PyQt5 application. + """pyG5App PySide6 application. Args: sys.argv @@ -46,6 +54,16 @@ def __init__(self): """ QApplication.__init__(self, sys.argv) + QCoreApplication.setOrganizationName("pyG5") + QCoreApplication.setOrganizationDomain("pyg5.org") + QCoreApplication.setApplicationName("pyG5") + self.settings = QSettings( + QSettings.Format.IniFormat, + QSettings.Scope.UserScope, + QCoreApplication.organizationDomain(), + "pyG5", + ) + # parse the command line arguments self.argument_parser() @@ -59,7 +77,13 @@ def __init__(self): self.networkManager = pyG5NetWorkManager() - # The QWidget widget is the base class of all user interface objects in PyQt4. + self.paintTimer = QTimer() + self.paintTimer.timeout.connect( + self.painTimerCB + ) # Let the interpreter run each 500 ms. + self.paintTimer.start(25) # You may change this if you wish. + + # The QWidget widget is the base class of all user interface objects in PySide6. self.mainWindow = pyG5MainWindow() self.networkManager.drefUpdate.connect( @@ -69,12 +93,65 @@ def __init__(self): self.mainWindow.pyG5DualStacked.pyG5HSI.drefHandler ) + self.networkManager.drefUpdate.connect( + self.mainWindow.pyG5DualStacked.pyG5FMA.drefHandler + ) + # Show window - # full screen for the RPI - if platform.machine() in "armv7l": - self.mainWindow.showFullScreen() - else: - self.mainWindow.show() + self.mainWindow.loadSettings() + + if platform.machine() == "aarch64": + self.mainWindow.setWindowFlags( + self.mainWindow.windowFlags() | Qt.FramelessWindowHint + ) + self.mainWindow.setWindowState(Qt.WindowFullScreen) + + self.mainWindow.show() + + if self.args.mode == "full": + self.secondaryWindow = pyG5SecondWindow() + + self.secondaryWindow.loadSettings() + + if platform.machine() == "aarch64": + self.secondaryWindow.setWindowFlags( + self.secondaryWindow.windowFlags() | Qt.FramelessWindowHint + ) + + self.secondaryWindow.setWindowState(Qt.WindowFullScreen) + + # connect the value coming from the simulator + self.networkManager.drefUpdate.connect( + self.secondaryWindow.cWidget.drefHandler + ) + + # connect the value to update to the simulator + self.secondaryWindow.cWidget.xpdrCodeSignal.connect( + self.send_transponder_code + ) + self.secondaryWindow.cWidget.xpdrModeSignal.connect( + self.send_transponder_mode + ) + + self.secondaryWindow.show() + + self.secondaryWindow.closed.connect(self.mainWindow.close) + self.mainWindow.closed.connect(self.secondaryWindow.close) + + def send_transponder_code(self, code): + """Trigger the xpdr transmission to xplane.""" + self.networkManager.write_data_ref("sim/cockpit/radios/transponder_code", code) + + def send_transponder_mode(self, mode): + """Trigger the xpdr transmission to xplane.""" + self.networkManager.write_data_ref("sim/cockpit/radios/transponder_mode", mode) + + def painTimerCB(self): + """Trigger update of all the widgets.""" + self.mainWindow.pyG5DualStacked.pyG5HSI.update() + self.mainWindow.pyG5DualStacked.update() + if self.args.mode == "full": + self.secondaryWindow.cWidget.update() def argument_parser(self): """Initialize the arguments passed from the command line.""" @@ -84,11 +161,22 @@ def argument_parser(self): self.parser.add_argument( "-v", "--verbose", help="increase verbosity", action="store_true" ) + self.parser.add_argument( + "-m", + "--mode", + help="Define the operating mode", + choices=[ + "hsi", + "full", + ], + default="hsi", + ) + self.args = self.parser.parse_args() -class pyG5MainWindow(QMainWindow): - """pyG5App PyQt5 application. +class pyG5BaseWindow(QMainWindow): + """pyG5App PySide6 application. Args: sys.argv @@ -97,6 +185,8 @@ class pyG5MainWindow(QMainWindow): self """ + closed = Signal() + def __init__(self, parent=None): """g5Widget Constructor. @@ -108,36 +198,143 @@ def __init__(self, parent=None): """ QMainWindow.__init__(self, parent) + self.settings = QCoreApplication.instance().settings + self.setStyleSheet("background-color: black;") target = "FreeSans" - if target in QFontDatabase().families(): - print("set font") + if target in QFontDatabase.families(): font = QFont(target) self.setFont(font) self.setWindowTitle(__appName__) + action = QAction("&Quit", self) + action.setShortcut("Ctrl+w") + action.triggered.connect(self.close) + self.addAction(action) - self.file_menu = QMenu("&File", self) - self.file_menu.addAction("&Quit", self.close, Qt.CTRL + Qt.Key_W) - self.file_menu.setStyleSheet("color : white;background: transparent;") + def changeEvent(self, event): + """Window change event overload. - menuBar = self.menuBar() - menuBar.addMenu(self.file_menu) - menuBar.setStyleSheet( - """QMenuBar::item { color : white; background: transparent; }""" + Args: + event + + Returns: + self + """ + if QEvent.Type.ActivationChange == event.type(): + self.settings.setValue( + "{}/windowState".format(self.__class__.__name__), self.saveState() + ) + elif QEvent.Type.WindowStateChange == event.type(): + if Qt.WindowMinimized != self.windowState(): + try: + self.restoreState( + self.settings.value( + "{}/windowState".format(self.__class__.__name__) + ) + ) + except Exception as inst: + logging.warning("State restore: {}".format(inst)) + pass + + def loadSettings(self): + """Load settings helper.""" + try: + self.restoreGeometry( + self.settings.value( + "{}/geometry".format(self.__class__.__name__), QByteArray() + ) + ) + self.restoreState( + self.settings.value("{}/windowState".format(self.__class__.__name__)) + ) + except Exception as inst: + logging.warning("State restore: {}".format(inst)) + pass + + @Slot(QCloseEvent) + def closeEvent(self, event): + """Close event overload. + + Args: + event + + Returns: + self + """ + self.settings.setValue( + "{}/geometry".format(self.__class__.__name__), self.saveGeometry() ) + self.settings.setValue( + "{}/windowState".format(self.__class__.__name__), self.saveState() + ) + event.accept() + self.closed.emit() + QMainWindow.closeEvent(self, event) + - self.pyG5DualStacked = pyG5DualStack() +class pyG5MainWindow(pyG5BaseWindow): + """pyG5App PySide6 application. + + Args: + sys.argv + + Returns: + self + """ + + closed = Signal() + + def __init__(self, parent=None): + """g5Widget Constructor. + + Args: + parent: Parent Widget + + Returns: + self + """ + pyG5BaseWindow.__init__(self, parent) + + self.pyG5DualStacked = pyG5DualStackFMA() self.setCentralWidget(self.pyG5DualStacked) +class pyG5SecondWindow(pyG5BaseWindow): + """pyG5App PyQt5 application. + + Args: + sys.argv + + Returns: + self + """ + + closed = Signal() + + def __init__(self, parent=None): + """g5Widget Constructor. + + Args: + parent: Parent Widget + + Returns: + self + """ + pyG5BaseWindow.__init__(self, parent) + + self.cWidget = pyG5SecondaryWidget() + + self.setCentralWidget(self.cWidget) + + if __name__ == "__main__": """Main application.""" a = pyG5App() - sys.exit(a.exec_()) + sys.exit(a.exec()) pass diff --git a/pyG5/pyG5Network.py b/pyG5/pyG5Network.py index 4ab60bf..33d8682 100644 --- a/pyG5/pyG5Network.py +++ b/pyG5/pyG5Network.py @@ -11,11 +11,11 @@ import os from datetime import datetime as datetime_, timedelta -from PyQt5.QtCore import QObject, pyqtSlot, pyqtSignal, QTimer +from PySide6.QtCore import QObject, Slot, Signal, QTimer -from PyQt5.QtNetwork import QUdpSocket, QHostAddress, QAbstractSocket +from PySide6.QtNetwork import QUdpSocket, QHostAddress, QAbstractSocket -from PyQt5 import QtGui +from PySide6 import QtGui class pyG5NetWorkManager(QObject): @@ -32,8 +32,7 @@ class pyG5NetWorkManager(QObject): self """ - xpInstance = pyqtSignal(QHostAddress, int) - drefUpdate = pyqtSignal(dict) + drefUpdate = Signal(object) def __init__(self, parent=None): """Object constructor. @@ -45,10 +44,179 @@ def __init__(self, parent=None): self """ QObject.__init__(self, parent) - # sim/cockpit/radios/gps_cdi_sensitivity int n enum GPS CDI sensitivity: 0=OCN, 1=ENR, 2=TERM, 3=DPRT, 4=MAPR, 5=APR, 6=RNPAR, 7=LNAV, 8=LNAV+V, 9=L/VNAV, 10=LP, 11=LPV, 12=LP+V, 13=GLS + + self.xpHost = None # list the datarefs to request self.datarefs = [ # ( dataref, frequency, unit, description, num decimals to display in formatted output ) + ( + "sim/cockpit2/autopilot/altitude_hold_ft", + 20, + "ft", + "Altitude Hold", + 0, + "_altitudeHold", + ), + ( + "sim/cockpit2/autopilot/altitude_vnav_ft", + 20, + "ft", + "Altitude VNAV", + 0, + "_altitudeVNAV", + ), + ( + "sim/cockpit2/radios/indicators/nav_src_ref", + 20, + "enum", + "NAV source", + 0, + "_navSrc", + ), + ( + "sim/cockpit/autopilot/altitude", + 20, + "feet", + "AP altitude selected", + 0, + "_apAltitude", + ), + ( + "sim/cockpit/autopilot/vertical_velocity", + 20, + "fpm", + "NAV source", + 0, + "_apVS", + ), + ( + "sim/cockpit/autopilot/airspeed", + 20, + "kt", + "AP air speed", + 0, + "_apAirSpeed", + ), + ( + "sim/cockpit/autopilot/autopilot_mode", + 20, + "enum", + "AP mode", + 0, + "_apMode", + ), + ( + "sim/cockpit/autopilot/autopilot_state", + 20, + "enum", + "AP state", + 0, + "_apState", + ), + ( + "sim/flightmodel/controls/parkbrake", + 1, + "onoff", + "Parking brake set", + 0, + "_parkBrake", + ), + ( + "sim/cockpit/warnings/annunciators/fuel_quantity", + 1, + "onoff", + "fuel selector", + 0, + "_lowFuel", + ), + ( + "sim/cockpit/warnings/annunciators/oil_pressure_low[0]", + 1, + "onoff", + "fuel selector", + 0, + "_oilPres", + ), + ( + "sim/cockpit/warnings/annunciators/fuel_pressure_low[0]", + 1, + "onoff", + "fuel selector", + 0, + "_fuelPress", + ), + ( + "sim/cockpit/warnings/annunciators/low_vacuum", + 1, + "onoff", + "fuel selector", + 0, + "_lowVacuum", + ), + ( + "sim/cockpit/warnings/annunciators/low_voltage", + 1, + "onoff", + "fuel selector", + 0, + "_lowVolts", + ), + ( + "sim/cockpit2/fuel/fuel_tank_selector", + 30, + "onoff", + "fuel selector", + 0, + "_fuelSel", + ), + ( + "sim/cockpit2/engine/actuators/carb_heat_ratio[0]", + 30, + "onoff", + "fuel pump on", + 0, + "_carbheat", + ), + ( + "sim/cockpit/engine/fuel_pump_on[0]", + 10, + "onoff", + "fuel pump on", + 0, + "_fuelpump", + ), + ( + "sim/flightmodel/controls/elv_trim", + 30, + "mode", + "Transponder mode", + 0, + "_trims", + ), + ( + "sim/flightmodel/controls/flaprat", + 30, + "mode", + "Transponder mode", + 0, + "_flaps", + ), + ( + "sim/cockpit/radios/transponder_mode", + 5, + "mode", + "Transponder mode", + 0, + "_xpdrMode", + ), + ( + "sim/cockpit/radios/transponder_code", + 5, + "code", + "Transponder code", + 0, + "_xpdrCode", + ), ( "sim/cockpit/radios/gps_dme_dist_m", 1, @@ -66,12 +234,13 @@ def __init__(self, parent=None): "_gpsvnavavailable", ), ( + # int n enum GPS CDI sensitivity: 0=OCN, 1=ENR, 2=TERM, 3=DPRT, 4=MAPR, 5=APR, 6=RNPAR, 7=LNAV, 8=LNAV+V, 9=L/VNAV, 10=LP, 11=LPV, 12=LP+V, 13=GLS "sim/cockpit/radios/gps_cdi_sensitivity", 1, - "boolean", - "Avionics powered on", + "index", + "GPS Horizontal Situation Indicator sensitivity mode", 0, - "_test", + "_gpshsisens", ), ( "sim/cockpit/radios/gps_has_glideslope", @@ -81,14 +250,6 @@ def __init__(self, parent=None): 0, "_gpsgsavailable", ), - ( - "sim/cockpit/radios/gps_hdef_nm_per_dot", - 1, - "boolean", - "Avionics powered on", - 0, - "_gpshsisens", - ), ( "sim/cockpit/radios/gps_gp_mtr_per_dot", 1, @@ -98,7 +259,7 @@ def __init__(self, parent=None): "_gpsvsens", ), ( - "sim/cockpit/radios/", + "sim/cockpit/radios/nav_type[0]", 1, "boolean", "Avionics powered on", @@ -361,6 +522,14 @@ def __init__(self, parent=None): 0, "_altitude", ), + ( + "sim/cockpit2/autopilot/altitude_dial_ft", + 30, + "feet", + "Altitude", + 0, + "_altitudeSel", + ), ( "sim/cockpit2/gauges/actuators/barometer_setting_in_hg_pilot", 30, @@ -369,6 +538,14 @@ def __init__(self, parent=None): 0, "_alt_setting", ), + ( + "sim/physics/metric_press", + 1, + "feet", + "Altimeter setting", + 0, + "_alt_setting_metric", + ), ( "sim/cockpit2/gauges/indicators/slip_deg", 30, @@ -452,9 +629,21 @@ def __init__(self, parent=None): self.udpSock.stateChanged.connect(self.socketStateHandler) # bind the socket - self.udpSock.bind(QHostAddress.AnyIPv4, 0, QUdpSocket.ShareAddress) + self.udpSock.bind( + QHostAddress.SpecialAddress.AnyIPv4, 0, QUdpSocket.BindFlag.ShareAddress + ) - @pyqtSlot() + @Slot() + def write_data_ref(self, path, data): + """Idle timer expired. Trigger reconnection process.""" + cmd = b"DREF\x00" # DREF command + message = struct.pack("<5sf", cmd, data) + message += bytes(path, "utf-8") + b"\x00" + message += " ".encode("utf-8") * (509 - len(message)) + if self.xpHost: + self.udpSock.writeDatagram(message, self.xpHost, self.xpPort) + + @Slot() def reconnect(self): """Idle timer expired. Trigger reconnection process.""" self.logger.info("Connection Timeout expired") @@ -463,16 +652,19 @@ def reconnect(self): self.idleTimer.stop() # let the screensaver activate - if platform.machine() in "armv7l": + if platform.machine() in "aarch64": os.system("xset s on") os.system("xset s 1") - @pyqtSlot(QHostAddress, int) + @Slot(QHostAddress, int) def xplaneConnect(self, addr, port): """Slot connecting triggering the connection to the XPlane.""" self.listener.xpInstance.disconnect(self.xplaneConnect) self.listener.deleteLater() + self.xpHost = addr + self.xpPort = port + self.logger.info("Request datatefs") # initiate connection for idx, dataref in enumerate(self.datarefs): @@ -491,16 +683,16 @@ def xplaneConnect(self, addr, port): self.idleTimer.start(self.idleTimerDuration) # now we can inhibit the screensaver - if platform.machine() in "armv7l": + if platform.machine() in "aarch64": os.system("xset s reset") os.system("xset s off") - @pyqtSlot() + @Slot() def socketStateHandler(self): """Socket State handler.""" self.logger.info("socketStateHandler: {}".format(self.udpSock.state())) - if self.udpSock.state() == QAbstractSocket.BoundState: + if self.udpSock.state() == QAbstractSocket.SocketState.BoundState: self.logger.info("Started Multicast listenner") # instantiate the multicast listener self.listener = pyG5MulticastListener(self) @@ -508,11 +700,13 @@ def socketStateHandler(self): # connect the multicast listenner to the connect function self.listener.xpInstance.connect(self.xplaneConnect) - elif self.udpSock.state() == QAbstractSocket.UnconnectedState: + elif self.udpSock.state() == QAbstractSocket.SocketState.UnconnectedState: # socket got disconnected issue reconnection - self.udpSock.bind(QHostAddress.AnyIPv4, 0, QUdpSocket.ShareAddress) + self.udpSock.bind( + QHostAddress.SpecialAddress.AnyIPv4, 0, QUdpSocket.BindFlag.ShareAddress + ) - @pyqtSlot() + @Slot() def dataHandler(self): """dataHandler.""" # data received restart the idle timer @@ -536,14 +730,14 @@ def dataHandler(self): value = 0 for i in range(0, numvalues): singledata = data[(5 + lenvalue * i) : (5 + lenvalue * (i + 1))] - (idx, value) = struct.unpack("= 4: + return "LOC" + navIndex + + logging.error("Failed to decode navtype") + + +secWidth = 800 +secHeight = 480 + + +class pyG5SecondaryWidget(pyG5Widget): + """Generate G5 wdiget view.""" + + xpdrCodeSignal = Signal(int) + xpdrModeSignal = Signal(int) + + def __init__(self, parent=None): + """g5Widget Constructor. + + Args: + parent: Parent Widget + + Returns: + self + """ + pyG5Widget.__init__(self, parent) + + self.xpdrKeyboard = False + + self.setFixedSize(secWidth, secHeight) + + self.xpdrXbase = 20 + self.xpdrYbase = 20 + + self.xpdrwidth = 160 + self.xpdrheight = 40 + + self.xpdrRect = QRectF( + self.xpdrXbase, self.xpdrYbase, self.xpdrwidth, self.xpdrheight + ) + + self.xpdrKeyXbase = 20 + self.xpdrKeyYbase = self.xpdrYbase + self.xpdrheight + + self.xpdrKeyWidth = 420 + self.xpdrKeyHeight = secHeight - (self.xpdrYbase + self.xpdrheight) - 20 + + self.xpdrkeyRect = QRectF( + self.xpdrKeyXbase, self.xpdrKeyYbase, self.xpdrKeyWidth, self.xpdrKeyHeight + ) + + self.xpdrPos = 3 + + self.keyArea = [] + + index = 0 + for i in [1, 2, 3, 4]: + rect = QRectF( + self.xpdrKeyXbase + 26.125 + index * 95, + self.xpdrKeyYbase + 20, + 82.5, + 82.5, + ) + self.keyArea.append([rect, i]) + index += 1 + + index = 0 + for i in [5, 6, 7, 0]: + rect = QRectF( + self.xpdrKeyXbase + 26.125 + index * 95, + self.xpdrKeyYbase + 20 + 95 + 20, + 82.5, + 82.5, + ) + self.keyArea.append([rect, i]) + index += 1 + + self.keyCtrlArea = [] + self.keyCtrlArea.append( + [ + QRectF( + self.xpdrKeyXbase, + self.xpdrKeyYbase + self.xpdrKeyHeight / 2 + 40, + self.xpdrKeyWidth / 2, + self.xpdrKeyHeight / 4 - 20, + ), + "OFF", + 0, + ] + ) + self.keyCtrlArea.append( + [ + QRectF( + self.xpdrKeyXbase, + self.xpdrKeyYbase + self.xpdrKeyHeight * 3 / 4 + 20, + self.xpdrKeyWidth / 2, + self.xpdrKeyHeight / 4 - 20, + ), + "ON", + 2, + ] + ) + + self.keyCtrlArea.append( + [ + QRectF( + self.xpdrKeyXbase + self.xpdrKeyWidth / 2, + self.xpdrKeyYbase + self.xpdrKeyHeight / 2 + 40, + self.xpdrKeyWidth / 2, + self.xpdrKeyHeight / 4 - 20, + ), + "STBY", + 1, + ] + ) + + self.keyCtrlArea.append( + [ + QRectF( + self.xpdrKeyXbase + self.xpdrKeyWidth / 2, + self.xpdrKeyYbase + self.xpdrKeyHeight * 3 / 4 + 20, + self.xpdrKeyWidth / 2, + self.xpdrKeyHeight / 4 - 20, + ), + "ALT", + 3, + ] + ) + + def mousePressEvent(self, event): + """Mouse Pressed event overload.""" + if self._avionicson: + if self.xpdrRect.contains(event.position()): + self.xpdrKeyboard = not self.xpdrKeyboard + + else: + if self.xpdrkeyRect.contains(event.position()): + for key in self.keyArea: + if key[0].contains(event.position()): + # the input is a BCD value received as integer. + # First step is to turn it into a real integer + codestr = "{:04d}".format(int(self._xpdrCode)) + code = 0 + for idx, c in enumerate(codestr): + code |= int(c) << (4 * (3 - idx)) + + # code is the integer value of the string + # now apply on code the new number + code = code & ((0xF << (4 * self.xpdrPos)) ^ 0xFFFF) + code = code | (key[1] << (4 * self.xpdrPos)) + + # shift the position we update + self.xpdrPos = (self.xpdrPos - 1) % 4 + + # emit the new code value + self._xpdrCode = int("{:04x}".format(code)) + self.xpdrCodeSignal.emit(self._xpdrCode) + + for key in self.keyCtrlArea: + if key[0].contains(event.position()): + self.xpdrMode(key[2]) + self.xpdrModeSignal.emit(key[2]) + self.xpdrKeyboard = False + else: + self.xpdrKeyboard = False + + if not self.xpdrKeyboard: + self.xpdrPos = 3 + self.update() + + def paintEvent(self, event): + """Paint the widget.""" + self.qp = QPainter(self) + + # Draw the background + self.setPen(1, Qt.GlobalColor.black) + self.qp.setBrush(QBrush(Qt.GlobalColor.black)) + self.qp.drawRect(0, 0, secWidth, secHeight) + + self.setPen(1, Qt.GlobalColor.white) + self.qp.setBrush(QBrush(Qt.GlobalColor.black)) + + # flaps settings + flapXBase = 620 + flapYBase = 20 + flapHeight = secHeight - 40 + flapWidth = 130 + + self.setPen(1, Qt.GlobalColor.white) + self.qp.setBrush(QBrush(Qt.GlobalColor.white)) + font = self.qp.font() + font.setPixelSize(30) + font.setBold(True) + self.qp.setFont(font) + + # draw the title + self.qp.drawText( + QRectF( + flapXBase, + flapYBase, + flapWidth, + 40, + ), + Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignTop, + "FLAPS", + ) + + font.setPixelSize(20) + font.setBold(False) + self.qp.setFont(font) + + # draw the flaps angle legend + for i in range(0, 4): + self.qp.drawText( + QRectF( + flapXBase, + flapYBase + 40 + int((flapHeight) * i / 4), + 40, + 40, + ), + Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignVCenter, + "{:02d}°".format(10 * i), + ) + + # draw the indicator rectangle + self.qp.setBrush(QBrush(Qt.GlobalColor.black)) + self.qp.drawRect(flapXBase + 90, flapYBase + 40, 40, flapHeight - 40) + + # draw the indicator legend white + self.setPen(1, Qt.GlobalColor.white) + self.qp.setBrush(QBrush(Qt.GlobalColor.white)) + rect = QRectF( + flapXBase + 50, + flapYBase + 40 + int((flapHeight - 40) / 3), + 40, + flapHeight - 40 - +int((flapHeight - 40) / 3), + ) + self.qp.drawRect(rect) + self.setPen(1, Qt.GlobalColor.black) + self.qp.setBrush(QBrush(Qt.GlobalColor.black)) + self.qp.drawText( + rect, Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignVCenter, "8\n5" + ) + + # draw the indicator legend cyan + self.setPen(1, Qt.GlobalColor.cyan) + self.qp.setBrush(QBrush(Qt.GlobalColor.cyan)) + rect = QRectF( + flapXBase + 50, flapYBase + 40, 40, int((flapHeight - 40) / 3 + 20) + ) + self.qp.drawRect(rect) + self.setPen(1, Qt.GlobalColor.black) + self.qp.setBrush(QBrush(Qt.GlobalColor.black)) + self.qp.drawText( + rect, + Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignVCenter, + "1\n1\n0", + ) + + self.setPen(1, Qt.GlobalColor.white) + self.qp.setBrush(QBrush(Qt.GlobalColor.white)) + + self.qp.drawPolygon( + QPolygonF( + [ + QPointF( + flapXBase + flapWidth, + flapYBase + 50 + self._flaps * int((flapHeight - 100)), + ), + QPointF( + flapXBase + flapWidth, + flapYBase + 70 + self._flaps * int((flapHeight - 100)), + ), + QPointF( + flapXBase + flapWidth - 30, + flapYBase + 70 + self._flaps * int((flapHeight - 100)), + ), + QPointF( + flapXBase + flapWidth - 40, + flapYBase + 60 + self._flaps * int((flapHeight - 100)), + ), + QPointF( + flapXBase + flapWidth - 30, + flapYBase + 50 + self._flaps * int((flapHeight - 100)), + ), + QPointF( + flapXBase + flapWidth, + flapYBase + 50 + self._flaps * int((flapHeight - 100)), + ), + ] + ) + ) + + # trim settings + trimXBase = 460 + trimYBase = 20 + trimHeight = secHeight - 40 + trimWidth = 130 + + self.setPen(1, Qt.GlobalColor.white) + self.qp.setBrush(QBrush(Qt.GlobalColor.white)) + font = self.qp.font() + font.setPixelSize(30) + font.setBold(True) + self.qp.setFont(font) + + # draw the title + self.qp.drawText( + QRectF( + trimXBase + 40, + trimYBase, + 90, + 40, + ), + Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignTop, + "TRIM", + ) + + font.setPixelSize(20) + font.setBold(False) + self.qp.setFont(font) + + # draw the flaps angle legend + self.qp.drawText( + QRectF( + trimXBase, + trimYBase + 40 + int((trimHeight) / 2 - 40), + 80, + 40, + ), + Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignVCenter, + "Take-off", + ) + + # draw the indicator rectangle + self.qp.setBrush(QBrush(Qt.GlobalColor.black)) + self.qp.drawRect(trimXBase + 90, trimYBase + 40, 40, trimHeight - 40) + + self.setPen(1, Qt.GlobalColor.white) + self.qp.setBrush(QBrush(Qt.GlobalColor.white)) + + trimShift = (trimHeight - 60) * (self._trims / 2 + 0.5) + + self.qp.drawPolygon( + QPolygonF( + [ + QPointF(trimXBase + trimWidth, trimYBase + 40 + trimShift), + QPointF(trimXBase + trimWidth, trimYBase + 60 + trimShift), + QPointF(trimXBase + trimWidth - 30, trimYBase + 60 + trimShift), + QPointF(trimXBase + trimWidth - 40, trimYBase + 50 + trimShift), + QPointF(trimXBase + trimWidth - 30, trimYBase + 40 + trimShift), + QPointF(trimXBase + trimWidth, trimYBase + 40 + trimShift), + ] + ) + ) + + # sqawk code and status + if self._avionicson: + font = self.qp.font() + font.setPixelSize(self.xpdrheight - 6) + font.setBold(True) + self.qp.setFont(font) + + # draw the indicator rectangle + self.setPen(2, Qt.GlobalColor.white) + self.qp.setBrush(QBrush(Qt.GlobalColor.white)) + self.qp.drawRect(self.xpdrRect) + + self.setPen(1, Qt.GlobalColor.black) + self.qp.setBrush(QBrush(Qt.GlobalColor.black)) + self.qp.drawText( + self.xpdrRect, + Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignVCenter, + "XPDR", + ) + + self.setPen(2, Qt.GlobalColor.white) + self.qp.setBrush(QBrush(Qt.GlobalColor.black)) + rect = QRectF( + self.xpdrXbase + self.xpdrwidth, + self.xpdrYbase, + 420 - self.xpdrwidth, + self.xpdrheight, + ) + self.qp.drawRect(rect) + + self.setPen(1, Qt.GlobalColor.white) + self.qp.setBrush(QBrush(Qt.GlobalColor.white)) + + if int(self._xpdrMode) == 0: + xpdrMode = "OFF" + elif int(self._xpdrMode) == 1: + xpdrMode = "STDBY" + elif int(self._xpdrMode) == 2: + xpdrMode = "ON" + elif int(self._xpdrMode) == 3: + xpdrMode = "ALT" + elif int(self._xpdrMode) == 4: + xpdrMode = "TEST" + + self.qp.drawText( + rect, + Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignVCenter, + "{:04d} {}".format(int(self._xpdrCode), xpdrMode), + ) + + if self.xpdrKeyboard: + self.setPen(2, Qt.GlobalColor.white) + self.qp.setBrush(QBrush(Qt.GlobalColor.black)) + self.qp.drawRect(self.xpdrkeyRect) + + for key in self.keyArea: + self.qp.drawEllipse(key[0]) + self.qp.drawText( + key[0], + Qt.AlignmentFlag.AlignCenter, + "{:01d}".format(key[1]), + ) + + for key in self.keyCtrlArea: + self.qp.drawRect(key[0]) + self.qp.drawText( + key[0], + Qt.AlignmentFlag.AlignCenter, + key[1], + ) + + else: + # carb heat status + carbXbase = 20 + carbYbase = self.xpdrYbase + self.xpdrheight + 20 + + carbwidth = 80 + carbheight = 80 + + font.setPixelSize(20) + font.setBold(False) + self.qp.setFont(font) + + rect = QRectF(carbXbase, carbYbase, carbwidth, 40) + + self.qp.drawText( + rect, + Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignVCenter, + "CARB", + ) + + self.setPen(2, Qt.GlobalColor.white) + if self._carbheat > 0.1: + self.qp.setBrush(QBrush(Qt.GlobalColor.green)) + else: + self.qp.setBrush(QBrush(Qt.GlobalColor.black)) + + rect = QRectF(carbXbase, carbYbase + 40, carbwidth, carbheight) + + self.qp.drawEllipse(rect) + + # fuel pump status + fuelXbase = 20 + fuelYbase = carbYbase + carbheight + 20 + + fuelwidth = 80 + fuelheight = 80 + + font.setPixelSize(20) + font.setBold(False) + self.qp.setFont(font) + + rect = QRectF(fuelXbase, fuelYbase, fuelwidth, 100) + + self.qp.drawText( + rect, + Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignVCenter, + "FUEL\nPUMP", + ) + + self.setPen(2, Qt.GlobalColor.white) + self.qp.setBrush(QBrush(Qt.GlobalColor.green)) + + rect = QRectF(fuelXbase, fuelYbase + 80, fuelwidth, fuelheight) + + if self._fuelpump > 0 and self._avionicson: + self.qp.setBrush(QBrush(Qt.GlobalColor.green)) + else: + self.qp.setBrush(QBrush(Qt.GlobalColor.black)) + + self.qp.drawEllipse(rect) + + # fuel feed settings + + ffXBase = 120 + ffYBase = carbYbase + 40 + + ffWdidth = 440 - ffXBase + ffHeight = 220 + + self.setPen(1, Qt.GlobalColor.white) + self.qp.setBrush(QBrush(Qt.GlobalColor.white)) + rect = QRectF(ffXBase, carbYbase, ffWdidth, 20) + self.qp.drawText( + rect, + Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignVCenter, + "FUEL FEED", + ) + + self.setPen(1, Qt.GlobalColor.white) + self.qp.setBrush(QBrush(Qt.GlobalColor.black)) + + rect = QRectF(ffXBase, ffYBase, ffWdidth, ffHeight) + self.qp.drawRect(rect) + + self.qp.drawText( + rect, + Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignTop, + "BOTH", + ) + self.qp.drawText( + rect, + Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignBottom, + "OFF", + ) + + self.qp.drawText( + rect, + Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter, + "LEFT", + ) + + self.qp.drawText( + rect, + Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter, + "RIGHT", + ) + + self.qp.translate(rect.center()) + + if self._fuelSel == 0: + self.qp.rotate(180) + elif self._fuelSel == 1: + self.qp.rotate(-90) + elif self._fuelSel == 2: + self.qp.rotate(45) + elif self._fuelSel == 3: + self.qp.rotate(90) + elif self._fuelSel == 4: + self.qp.rotate(0) + else: + self.qp.rotate(0) + + brect = QRectF(-50, -50, 100, 100) + self.qp.drawEllipse(brect) + + self.setPen(1, Qt.GlobalColor.white) + self.qp.setBrush(QBrush(Qt.GlobalColor.white)) + self.qp.drawPolygon( + QPolygonF( + [ + QPointF(-10, +50), + QPointF(+10, +50), + QPointF(+10, -50), + QPointF(0, -70), + QPointF(-10, -50), + ] + ) + ) + + self.setPen(1, Qt.GlobalColor.black) + self.qp.setBrush(QBrush(Qt.GlobalColor.black)) + brect = QRectF(-5, -5, 10, 10) + self.qp.drawEllipse(brect) + + self.qp.resetTransform() + + # advisory panel (low voltage) + advXBase = 20 + advYBase = fuelYbase + fuelheight + 100 + + advWdidth = 420 + advHeight = 100 + + advTable = [ + { + "text": "LOW\nVOLTS", + "color": Qt.GlobalColor.red, + "name": "_lowVolts", + }, + {"text": "LOW\nFUEL", "color": Qt.GlobalColor.red, "name": "_lowFuel"}, + {"text": "OIL\nPRESS", "color": Qt.GlobalColor.red, "name": "_oilPres"}, + {"text": "BRAKE", "color": Qt.GlobalColor.red, "name": "_parkBrake"}, + { + "text": "LOW\nVACUUM", + "color": Qt.GlobalColor.yellow, + "name": "_lowVacuum", + }, + { + "text": "FUEL\nPRESS", + "color": Qt.GlobalColor.yellow, + "name": "_fuelPress", + }, + ] + + grayColor = QColor("#5d5b59") + self.setPen(1, grayColor) + self.qp.setBrush(QBrush(Qt.GlobalColor.black)) - @wraps(val) - def setter(inputVal): - setattr(self, "_{}".format(val), inputVal) - self.repaint() + rect = QRectF(advXBase, advYBase, advWdidth, advHeight) + self.qp.drawRect(rect) - return setter + for i in range(0, 2): + for j in range(0, 4): + advrect = QRectF( + advXBase + j * advWdidth / 4, + advYBase + i * advHeight / 2, + advWdidth / 4, + advHeight / 2, + ) + self.qp.drawRect(advrect) - for prop in propertyList: - setattr(self, "_{}".format(prop[0]), prop[1]) - setattr(self, "{}".format(prop[0]), _make_setter(prop[0])) + if j + 4 * i < len(advTable): + if getattr(self, advTable[4 * i + j]["name"]) == 1: + self.setPen(1, advTable[4 * i + j]["color"]) - def setPen(self, width, color, style=Qt.SolidLine): - """Set the pen color and width.""" - pen = self.qp.pen() - pen.setColor(color) - pen.setWidth(width) - pen.setStyle(style) - self.qp.setPen(pen) + self.qp.drawText( + advrect, + Qt.AlignmentFlag.AlignHCenter + | Qt.AlignmentFlag.AlignVCenter, + advTable[4 * i + j]["text"], + ) - @pyqtSlot(dict) - def drefHandler(self, retValues): - """Handle the DREF update.""" - for idx, value in retValues.items(): - try: - setattr(self, value[3], value[0]) - except Exception as e: - self.logger.error("failed to set value {}: {}".format(value[5], e)) - self.repaint() + self.setPen(1, grayColor) + + self.qp.end() class pyG5HSIWidget(pyG5Widget): @@ -172,26 +888,6 @@ def __init__(self, parent=None): """ pyG5Widget.__init__(self, parent) - def getNavTypeString(self, navType, navIndex): - """getNavTypeString. - - Args: - type: type number - - Returns: - string - """ - value = int(navType) - - if value == 0: - return "" - elif value == 3: - return "VOR" + navIndex - elif value >= 4: - return "LOC" + navIndex - - logging.error("Failed to decode navtype") - def paintEvent(self, event): """Paint the widget.""" self.qp = QPainter(self) @@ -212,12 +908,12 @@ def paintEvent(self, event): self.qp.setFont(font) # Draw the background - self.setPen(1, Qt.black) - self.qp.setBrush(QBrush(Qt.black)) + self.setPen(1, Qt.GlobalColor.black) + self.qp.setBrush(QBrush(Qt.GlobalColor.black)) self.qp.drawRect(0, 0, g5Width, g5Height) if self._avionicson == 0: - self.setPen(1, Qt.white) + self.setPen(1, Qt.GlobalColor.white) self.qp.drawLine(0, 0, g5Width, g5Height) self.qp.drawLine(0, g5Height, g5Width, 0) self.qp.end() @@ -247,7 +943,7 @@ def paintEvent(self, event): 270, 315, ] - self.setPen(2, Qt.white) + self.setPen(2, Qt.GlobalColor.white) for marker in hsiPeripheralMarkers: self.qp.rotate(-marker) @@ -294,7 +990,7 @@ def paintEvent(self, event): self.qp.font().pixelSize() + 6, self.qp.font().pixelSize(), ), - Qt.AlignHCenter | Qt.AlignVCenter, + Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignVCenter, text, ) self.qp.rotate(-self._magHeading + currentHead) @@ -304,43 +1000,53 @@ def paintEvent(self, event): currentHead += 5 # draw the Heading bug - self.setPen(1, Qt.cyan) - self.qp.setBrush(QBrush(Qt.cyan)) + self.setPen(1, Qt.GlobalColor.cyan) + self.qp.setBrush(QBrush(Qt.GlobalColor.cyan)) self.qp.rotate(180 + self._headingBug) self.qp.drawPolygon( QPolygonF( [ - QPointF(-9, rotatinghsiCircleRadius - 1), - QPointF(+9, rotatinghsiCircleRadius - 1), - QPointF(+9, rotatinghsiCircleRadius + 6), + QPointF(-15, rotatinghsiCircleRadius - 3), + QPointF(+15, rotatinghsiCircleRadius - 3), + QPointF(+15, rotatinghsiCircleRadius + 6), QPointF(+6, rotatinghsiCircleRadius + 6), QPointF(0, rotatinghsiCircleRadius + 1), QPointF(-6, rotatinghsiCircleRadius + 6), - QPointF(-9, rotatinghsiCircleRadius + 6), + QPointF(-15, rotatinghsiCircleRadius + 6), ] ) ) - self.setPen(1, Qt.black) + self.setPen(1, Qt.GlobalColor.black) gpscdianonciator = "" if int(self._hsiSource) == 2: cdiSource = "GPS" + # 0=OCN, 1=ENR, 2=TERM, 3=DPRT, 4=MAPR, 5=APR, 6=RNPAR, 7=LNAV, 8=LNAV+V, 9=L/VNAV, 10=LP, 11=LPV, 12=LP+V, 13=GLS + tableMap = [ + "OCN", + "ENR", + "TERM", + "DPRT", + "MAPR", + "APR", + "RNPAR", + "LNAV", + "LNAV+V", + "L/VNAV", + "LP", + "LPV", + "LP+V", + "GLS", + "", + ] + try: + gpscdianonciator = tableMap[int(self._gpshsisens)] + except IndexError: + gpscdianonciator = tableMap[-1] - sensi = round(self._gpshsisens, 1) - if sensi <= 0.1: - gpscdianonciator = "LNAV" - elif sensi == 0.12: - gpscdianonciator = "DEPT" - elif sensi == 0.4: - gpscdianonciator = "TERM" - elif sensi == 0.8: - gpscdianonciator = "ENR" - else: - gpscdianonciator = "" - - navColor = Qt.magenta + navColor = Qt.GlobalColor.magenta navdft = self._gpsdft navfromto = self._gpsfromto navcrs = self._gpscrs @@ -351,7 +1057,7 @@ def paintEvent(self, event): gsDev = self._gpsgs elif int(self._hsiSource) == 1: cdiSource = "{}".format(self.getNavTypeString(self._nav2type, "2")) - navColor = Qt.green + navColor = Qt.GlobalColor.green navdft = self._nav2dft navfromto = self._nav2fromto navcrs = self._nav2crs @@ -359,7 +1065,7 @@ def paintEvent(self, event): gsDev = self._nav2gs else: cdiSource = "{}".format(self.getNavTypeString(self._nav1type, "1")) - navColor = Qt.green + navColor = Qt.GlobalColor.green navdft = self._nav1dft navfromto = self._nav1fromto navcrs = self._nav1crs @@ -432,8 +1138,8 @@ def paintEvent(self, event): self.qp.rotate(90) # CDI deflection circle - self.setPen(2, Qt.white) - self.qp.setBrush(QBrush(Qt.black)) + self.setPen(2, Qt.GlobalColor.white) + self.qp.setBrush(QBrush(Qt.GlobalColor.black)) for i in [-81, -41, 31, 69]: self.qp.drawArc( @@ -454,34 +1160,34 @@ def paintEvent(self, event): font.setBold(False) self.qp.setFont(font) if int(self._hsiSource) == 2: - self.setPen(2, Qt.magenta) + self.setPen(2, Qt.GlobalColor.magenta) else: - self.setPen(2, Qt.green) + self.setPen(2, Qt.GlobalColor.green) self.qp.drawText( QRectF(g5CenterX - 70, hsiCenter - 50, 65, 18), - Qt.AlignLeft | Qt.AlignVCenter, + Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter, cdiSource, ) if len(gpscdianonciator): self.qp.drawText( QRectF(g5CenterX + 25, hsiCenter - 50, 65, 18), - Qt.AlignLeft | Qt.AlignVCenter, + Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter, gpscdianonciator, ) # Draw the heading Bug indicator bottom corner - self.setPen(2, Qt.cyan) - self.qp.setBrush(QBrush(Qt.black)) + self.setPen(2, Qt.GlobalColor.cyan) + self.qp.setBrush(QBrush(Qt.GlobalColor.black)) headingWidth = 105 headingHeigth = 30 self.qp.drawRect(QRectF(g5Width, g5Height, -headingWidth, -headingHeigth)) # draw the bug symbol - self.setPen(1, Qt.cyan) - self.qp.setBrush(QBrush(Qt.cyan)) + self.setPen(1, Qt.GlobalColor.cyan) + self.qp.setBrush(QBrush(Qt.GlobalColor.cyan)) self.qp.drawPolygon( QPolygonF( @@ -500,7 +1206,7 @@ def paintEvent(self, event): self.qp.drawText( QRectF(412, 336, 65, 18), - Qt.AlignLeft | Qt.AlignVCenter, + Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter, "{:03d}˚".format(int(self._headingBug)), ) @@ -512,12 +1218,12 @@ def paintEvent(self, event): distRect = QRectF(g5Width - 105, 0, 105, 45) self.setPen(2, greyColor) - self.qp.setBrush(QBrush(Qt.black)) + self.qp.setBrush(QBrush(Qt.GlobalColor.black)) self.qp.drawRect(distRect) self.qp.drawText( distRect, - Qt.AlignHCenter | Qt.AlignTop, + Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignTop, "Dist NM", ) @@ -529,7 +1235,7 @@ def paintEvent(self, event): distRect = QRectF(g5Width - 105, 12, 105, 45 - 12) self.qp.drawText( distRect, - Qt.AlignCenter, + Qt.AlignmentFlag.AlignCenter, "{}".format(round(self._gpsdmedist, 1)), ) @@ -541,12 +1247,12 @@ def paintEvent(self, event): # draw the wind box self.setPen(2, greyColor) - self.qp.setBrush(QBrush(Qt.black)) + self.qp.setBrush(QBrush(Qt.GlobalColor.black)) self.qp.drawRect(0, 0, 105, 45) - self.setPen(1, Qt.white) - self.qp.setBrush(QBrush(Qt.white)) + self.setPen(1, Qt.GlobalColor.white) + self.qp.setBrush(QBrush(Qt.GlobalColor.white)) self.qp.translate(25, 25) @@ -570,19 +1276,19 @@ def paintEvent(self, event): self.qp.drawText( QRectF(50, 2, 50, 20), - Qt.AlignLeft | Qt.AlignVCenter, + Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter, "{:03d}˚".format(int(self._windDirection)), ) self.qp.drawText( QRectF(50, 22, 50, 20), - Qt.AlignLeft | Qt.AlignVCenter, + Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter, "{:02d}kt".format(int(self._windSpeed * mstokt)), ) # Draw the magnetic heading box self.setPen(2, greyColor) - self.qp.setBrush(QBrush(Qt.black)) + self.qp.setBrush(QBrush(Qt.GlobalColor.black)) self.qp.drawPolygon( QPolygonF( [ @@ -601,13 +1307,13 @@ def paintEvent(self, event): QRectF( g5CenterX - headingBoxWidth / 2, 1, headingBoxWidth, headingBoxHeight ), - Qt.AlignHCenter | Qt.AlignVCenter, + Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignVCenter, "{:03d}˚".format(int(self._magHeading)), ) # Draw the ground track - self.setPen(0, Qt.transparent) - self.qp.setBrush(QBrush(Qt.magenta)) + self.setPen(0, Qt.GlobalColor.transparent) + self.qp.setBrush(QBrush(Qt.GlobalColor.magenta)) self.qp.translate(g5CenterX, hsiCenter) self.qp.rotate(-self._magHeading + self._groundTrack) self.qp.drawPolygon( @@ -625,13 +1331,13 @@ def paintEvent(self, event): ] ) ) - self.setPen(3, greyColor, Qt.DashLine) + self.setPen(3, greyColor, Qt.PenStyle.DashLine) self.qp.drawLine(0, 0, 0, -rotatinghsiCircleRadius) self.qp.resetTransform() # draw the aircraft - self.setPen(1, Qt.white) - self.qp.setBrush(QBrush(Qt.white)) + self.setPen(1, Qt.GlobalColor.white) + self.qp.setBrush(QBrush(Qt.GlobalColor.white)) self.qp.drawPolygon( QPolygonF( @@ -684,12 +1390,12 @@ def paintEvent(self, event): self.qp.drawText( rect, - Qt.AlignCenter | Qt.AlignVCenter, + Qt.AlignmentFlag.AlignCenter | Qt.AlignmentFlag.AlignVCenter, vertSourceTxt, ) self.setPen(2, greyColor) - self.qp.setBrush(QBrush(Qt.transparent)) + self.qp.setBrush(QBrush(Qt.GlobalColor.transparent)) self.qp.drawRect(rect) @@ -712,7 +1418,7 @@ def paintEvent(self, event): for offset in [-70, -35, 35, 70]: self.qp.drawEllipse( - QPoint( + QPointF( int(g5Width - gsFromLeft - gsWidth / 2), int(hsiCenter + offset), ), @@ -720,7 +1426,7 @@ def paintEvent(self, event): gsCircleRad / 2, ) - self.setPen(1, Qt.black) + self.setPen(1, Qt.GlobalColor.black) self.qp.setBrush(QBrush(navColor)) self.qp.translate( @@ -745,30 +1451,34 @@ def paintEvent(self, event): crsBoxWidth = 105 self.setPen(2, greyColor) - self.qp.setBrush(QBrush(Qt.black)) + self.qp.setBrush(QBrush(Qt.GlobalColor.black)) rect = QRectF(0, g5Height - crsBoxHeight, crsBoxWidth, crsBoxHeight) self.qp.drawRect(rect) - self.setPen(1, Qt.white) + self.setPen(1, Qt.GlobalColor.white) font = self.qp.font() font.setPixelSize(15) self.qp.setFont(font) rect = QRectF(1, g5Height - crsBoxHeight + 1, crsBoxWidth - 2, crsBoxHeight - 2) - self.qp.drawText(rect, Qt.AlignLeft | Qt.AlignBottom, "CRS") + self.qp.drawText( + rect, Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignBottom, "CRS" + ) font = self.qp.font() font.setPixelSize(25) self.qp.setFont(font) if int(self._hsiSource) == 2: - self.setPen(1, Qt.magenta) + self.setPen(1, Qt.GlobalColor.magenta) else: - self.setPen(1, Qt.green) + self.setPen(1, Qt.GlobalColor.green) rect = QRectF(40, g5Height - crsBoxHeight + 1, 65, crsBoxHeight - 2) self.qp.drawText( - rect, Qt.AlignLeft | Qt.AlignVCenter, "{:03d}˚".format(int(navcrs)) + rect, + Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter, + "{:03d}˚".format(int(navcrs)), ) self.qp.end() @@ -800,10 +1510,10 @@ def paintEvent(self, event): self.qp = QPainter(self) if self._avionicson == 0: - self.setPen(1, Qt.black) - self.qp.setBrush(QBrush(Qt.black)) + self.setPen(1, Qt.GlobalColor.black) + self.qp.setBrush(QBrush(Qt.GlobalColor.black)) self.qp.drawRect(0, 0, g5Width, g5Height) - self.setPen(1, Qt.white) + self.setPen(1, Qt.GlobalColor.white) self.qp.drawLine(0, 0, g5Width, g5Height) self.qp.drawLine(0, g5Height, g5Width, 0) self.qp.end() @@ -815,7 +1525,7 @@ def paintEvent(self, event): font.setBold(True) self.qp.setFont(font) - self.setPen(1, Qt.white) + self.setPen(1, Qt.GlobalColor.white) grad = QLinearGradient(g5CenterX, g5Height, g5CenterX, 0) grad.setColorAt(1, QColor(0, 50, 200, 255)) grad.setColorAt(0, QColor(0, 255, 255, 255)) @@ -905,7 +1615,7 @@ def paintEvent(self, event): mode = (mode + 1) % 4 # draw the static roll arc - self.setPen(3, Qt.white) + self.setPen(3, Qt.GlobalColor.white) bondingRect = QRectF( -self.rollArcRadius, @@ -929,12 +1639,12 @@ def paintEvent(self, event): [-120, 10], ] - self.qp.setBrush(QBrush(Qt.white)) - self.setPen(2, Qt.white) + self.qp.setBrush(QBrush(Qt.GlobalColor.white)) + self.setPen(2, Qt.GlobalColor.white) for lineParam in rollangleindicator: self.qp.drawLine(self.alongRadiusCoord(lineParam[0], lineParam[1])) - self.setPen(1, Qt.white) + self.setPen(1, Qt.GlobalColor.white) # draw the diamond on top of the roll arc self.qp.drawPolygon( QPolygonF( @@ -970,10 +1680,10 @@ def paintEvent(self, event): self.qp.drawPolygon(fixedDiamond) # create the nose - self.qp.setBrush(QBrush(Qt.yellow)) - self.qp.setBackgroundMode(Qt.OpaqueMode) + self.qp.setBrush(QBrush(Qt.GlobalColor.yellow)) + self.qp.setBackgroundMode(Qt.BGMode.OpaqueMode) - self.setPen(1, Qt.black) + self.setPen(1, Qt.GlobalColor.black) # solid polygon left nose = QPolygonF( @@ -1042,7 +1752,7 @@ def paintEvent(self, event): ) self.qp.drawPolygon(nose) - self.setPen(0, Qt.transparent) + self.setPen(0, Qt.GlobalColor.transparent) # solid polygon right nose = QPolygonF( [ @@ -1077,14 +1787,13 @@ def paintEvent(self, event): tapeScale = 50 - self.setPen(0, Qt.transparent) + self.setPen(0, Qt.GlobalColor.transparent) self.qp.setBrush(QBrush(QColor(0, 0, 0, 90))) self.qp.drawRect(QRectF(0, 0, speedBoxLeftAlign + speedBoxWdith + 15, g5Height)) if (self._kias + tapeScale / 2) > self._vne: - - brush = QBrush(QColor(Qt.red)) + brush = QBrush(QColor(Qt.GlobalColor.red)) self.qp.setBrush(brush) self.qp.drawRect( @@ -1097,8 +1806,7 @@ def paintEvent(self, event): ) if (self._kias + tapeScale / 2) > self._vno: - - brush = QBrush(QColor(Qt.yellow)) + brush = QBrush(QColor(Qt.GlobalColor.yellow)) self.qp.setBrush(brush) self.qp.drawRect( @@ -1111,8 +1819,7 @@ def paintEvent(self, event): ) if (self._kias + tapeScale / 2) > self._vs: - - brush = QBrush(QColor(Qt.green)) + brush = QBrush(QColor(Qt.GlobalColor.green)) self.qp.setBrush(brush) self.qp.drawRect( QRectF( @@ -1124,8 +1831,7 @@ def paintEvent(self, event): ) if (self._kias + tapeScale / 2) > self._vs: - - brush = QBrush(QColor(Qt.white)) + brush = QBrush(QColor(Qt.GlobalColor.white)) self.qp.setBrush(brush) self.qp.drawRect( QRectF( @@ -1136,9 +1842,9 @@ def paintEvent(self, event): ) ) - self.setPen(2, Qt.white) + self.setPen(2, Qt.GlobalColor.white) - self.qp.setBackgroundMode(Qt.TransparentMode) + self.qp.setBackgroundMode(Qt.BGMode.TransparentMode) font = self.qp.font() font.setPixelSize(speedBoxHeight - 15) @@ -1148,7 +1854,6 @@ def paintEvent(self, event): currentTape = int(self._kias + tapeScale / 2) while currentTape > max(0, self._kias - tapeScale / 2): if (currentTape % 10) == 0: - tapeHeight = ( 1 - 2 * (currentTape - self._kias) / tapeScale ) * g5CenterY @@ -1164,7 +1869,7 @@ def paintEvent(self, event): speedBoxWdith, speedBoxHeight, ), - Qt.AlignRight | Qt.AlignVCenter, + Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter, "{:d}".format(int(currentTape)), ) @@ -1207,7 +1912,7 @@ def paintEvent(self, event): ] ) - self.setPen(2, Qt.white) + self.setPen(2, Qt.GlobalColor.white) brush = QBrush(QColor(0, 0, 0, 255)) self.qp.setBrush(brush) @@ -1226,7 +1931,7 @@ def paintEvent(self, event): speedBoxWdith, speedBoxHeight, ), - Qt.AlignHCenter | Qt.AlignVCenter, + Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignVCenter, "{:03d}".format(int(self._kias)), ) @@ -1246,7 +1951,7 @@ def paintEvent(self, event): self.qp.drawText( rect, - Qt.AlignHCenter | Qt.AlignVCenter, + Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignVCenter, "TAS {:03d} kt".format(int(self._ktas)), ) @@ -1258,19 +1963,21 @@ def paintEvent(self, event): tasHeight, ) self.qp.drawRect(rect) - self.qp.drawText(rect, Qt.AlignLeft | Qt.AlignVCenter, "GS") + self.qp.drawText( + rect, Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter, "GS" + ) - self.setPen(2, Qt.magenta) + self.setPen(2, Qt.GlobalColor.magenta) self.qp.drawText( rect, - Qt.AlignRight | Qt.AlignVCenter, + Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter, "{:03d} kt".format(int(self._gs * mstokt)), ) - self.setPen(1, Qt.magenta) + self.setPen(1, Qt.GlobalColor.magenta) - brush = QBrush(Qt.magenta) + brush = QBrush(Qt.GlobalColor.magenta) self.qp.setBrush(brush) self.qp.drawRect( @@ -1298,14 +2005,14 @@ def paintEvent(self, event): vsIndicatorWidth = 7 alttapteLeftBound = altTapeLeftAlign - 1.5 * altBoxSpikedimension - self.setPen(0, Qt.transparent) + self.setPen(0, Qt.GlobalColor.transparent) self.qp.setBrush(QBrush(QColor(0, 0, 0, 90))) self.qp.drawRect( QRectF(alttapteLeftBound, 0, g5Width - alttapteLeftBound, int(g5Height)) ) - self.setPen(2, Qt.white) + self.setPen(2, Qt.GlobalColor.white) - self.qp.setBackgroundMode(Qt.TransparentMode) + self.qp.setBackgroundMode(Qt.BGMode.TransparentMode) font = self.qp.font() font.setPixelSize(10) # set default font size @@ -1317,7 +2024,6 @@ def paintEvent(self, event): while currentTape >= 0: tapeHeight = (vsScale - currentTape) / vsScale * g5Height if (currentTape % 5) == 0: - self.qp.drawLine( QPointF(g5Width - 10, tapeHeight), QPointF(g5Width, tapeHeight), @@ -1329,7 +2035,7 @@ def paintEvent(self, event): 15, vsIndicatorWidth + 3, ), - Qt.AlignRight | Qt.AlignVCenter, + Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter, "{:d}".format(abs(int(currentTape - vsScale / 2))), ) else: @@ -1343,14 +2049,14 @@ def paintEvent(self, event): vsHeight = -self._vh_ind_fpm / 100 / vsScale * g5Height vsRect = QRectF(g5Width, g5CenterY, -vsIndicatorWidth, vsHeight) - self.setPen(0, Qt.transparent) + self.setPen(0, Qt.GlobalColor.transparent) - brush = QBrush(QColor(Qt.magenta)) + brush = QBrush(QColor(Qt.GlobalColor.magenta)) self.qp.setBrush(brush) self.qp.drawRect(vsRect) - self.setPen(2, Qt.white) + self.setPen(2, Qt.GlobalColor.white) font = self.qp.font() font.setPixelSize(20) @@ -1362,7 +2068,6 @@ def paintEvent(self, event): while currentTape > self._altitude - altTapeScale / 2: if (currentTape % 20) == 0: - tapeHeight = ( 1 - 2 * (currentTape - self._altitude) / altTapeScale ) * g5CenterY @@ -1371,7 +2076,6 @@ def paintEvent(self, event): QPointF(altTapeLeftAlign - altBoxSpikedimension / 2, tapeHeight), ) if (currentTape % 100) == 0: - self.qp.drawText( QRectF( altTapeLeftAlign, @@ -1379,15 +2083,59 @@ def paintEvent(self, event): speedBoxWdith, speedBoxHeight, ), - Qt.AlignLeft | Qt.AlignVCenter, + Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter, "{:d}".format(int(currentTape)), ) currentTape -= 1 + # altitude selector + pen = self.qp.pen() + pen.setColor(Qt.GlobalColor.cyan) + pen.setWidth(2) + self.qp.setPen(pen) + brush = QBrush(QColor(Qt.GlobalColor.cyan)) + self.qp.setBrush(brush) + + altSelCenter = g5CenterY + if self._altitudeSel >= int(self._altitude + altTapeScale / 2 - 24): + altSelCenter = altSettingHeight + elif self._altitudeSel <= int(self._altitude - altTapeScale / 2 + 24): + altSelCenter = g5Height - altSettingHeight + else: + altSelCenter = ( + (floor(self._altitude + altTapeScale / 2) - self._altitudeSel) + / altTapeScale + * g5Height + ) + + altSel = QPolygonF( + [ + QPointF(alttapteLeftBound, altSelCenter - altBoxHeight / 2), + QPointF(alttapteLeftBound, altSelCenter + altBoxHeight / 2), + QPointF(altTapeLeftAlign, altSelCenter + altBoxHeight / 2), + QPointF(altTapeLeftAlign, altSelCenter + altBoxSpikedimension), + QPointF(altTapeLeftAlign - altBoxSpikedimension, altSelCenter), + QPointF(altTapeLeftAlign, altSelCenter - altBoxSpikedimension), + QPointF(altTapeLeftAlign, altSelCenter - altBoxHeight / 2), + ] + ) + self.qp.drawPolygon(altSel) + + # Altitude Box + self.setPen(2, Qt.GlobalColor.white) + altBoxTextSplitRatio = 2 / 5 altBox = QPolygonF( [ - QPointF(g5Width - altBoxRightAlign, g5CenterY - altBoxHeight / 2), + QPointF(g5Width - altBoxRightAlign, g5CenterY - altBoxHeight), + QPointF( + g5Width - altBoxRightAlign - altBoxWdith * altBoxTextSplitRatio, + g5CenterY - altBoxHeight, + ), + QPointF( + g5Width - altBoxRightAlign - altBoxWdith * altBoxTextSplitRatio, + g5CenterY - altBoxHeight / 2, + ), QPointF( altTapeLeftAlign, g5CenterY - altBoxHeight / 2, @@ -1408,7 +2156,15 @@ def paintEvent(self, event): altTapeLeftAlign, g5CenterY + altBoxHeight / 2, ), - QPointF(g5Width - altBoxRightAlign, g5CenterY + altBoxHeight / 2), + QPointF( + g5Width - altBoxRightAlign - altBoxWdith * altBoxTextSplitRatio, + g5CenterY + altBoxHeight / 2, + ), + QPointF( + g5Width - altBoxRightAlign - altBoxWdith * altBoxTextSplitRatio, + g5CenterY + altBoxHeight, + ), + QPointF(g5Width - altBoxRightAlign, g5CenterY + altBoxHeight), ] ) @@ -1417,19 +2173,236 @@ def paintEvent(self, event): self.qp.drawPolygon(altBox) - self.qp.drawText( - QRectF( + # implement the last 2 digits in 20 ft steps + altStep = 20 + charWidth = 15 + + if self._altitude < 0 and self._altitude > -1000: + # Add the minus sign + dispRect = QRectF( altTapeLeftAlign, g5CenterY - altBoxHeight / 2, - altBoxWdith, + charWidth, altBoxHeight, - ), - Qt.AlignHCenter | Qt.AlignVCenter, - "{:05d}".format(int(self._altitude)), - ) + ) + + self.qp.drawText( + dispRect, + Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignVCenter, + "-", + ) + + # extract lower digits + altLowerDigit = int("{:05d}".format(int(self._altitude))[3:5]) + + # floor the last to digit to the closest multiple of 20 + altLowerDigitrounded = 20 * floor(altLowerDigit / 20) + + altLowerDigitMod20 = altLowerDigit % 20 + + if self._altitude >= -40: + pass + if altLowerDigitrounded == 20: + altArray = [20, 0, 20, 40, 60] + elif altLowerDigitrounded == 40: + altArray = [0, 20, 40, 60, 80] + else: + altArray = [40, 20, 0, 20, 40] + + else: + altArray = [] + for i in range(5): + tmp = altLowerDigitrounded + altStep * (i - 2) + if int(self._altitude / 100) * 100 + tmp >= 0: + altArray.append(tmp % 100) + else: + altArray.append((100 - tmp) % 100) + + # define a clip rect to avoid overflowing the alt box + self.qp.setClipRect( + QRectF( + g5Width - altBoxRightAlign - altBoxWdith * altBoxTextSplitRatio, + g5CenterY - altBoxHeight, + altBoxWdith * altBoxTextSplitRatio, + 2 * altBoxHeight, + ) + ) + + # draw the last 2 digits altitude + self.qp.drawText( + QRectF( + altTapeLeftAlign + altBoxWdith * (1 - altBoxTextSplitRatio), + g5CenterY + - 2 * altBoxHeight + - 0.8 * altBoxHeight * (altLowerDigitMod20 / 20), + altBoxWdith * altBoxTextSplitRatio, + 4 * altBoxHeight, + ), + Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignVCenter, + "\n".join("{:02d}".format(t) for t in altArray), + ) + + # clear clip rect + self.qp.setClipRect(0, 0, g5Width, g5Height) + + if self._altitude >= 0: + # extract the last 2 digit + altLowerDigit = int(self._altitude % 100) + + # floor the last to digit to the closest multiple of 20 + altLowerDigitrounded = 20 * floor(altLowerDigit / 20) + + altLowerDigitMod20 = altLowerDigit % 20 + + # fill the array centered on the floor value in multiple of 20ft + altArray = [] + for i in range(5): + tmp = altLowerDigitrounded + altStep * (2 - i) + if int(self._altitude / 100) * 100 + tmp >= 0: + altArray.append(tmp % 100) + else: + altArray.append((100 - tmp) % 100) + + altString = "{:05d}".format(int(self._altitude)) + + if self._altitude > 9900: + dispRect = QRectF( + altTapeLeftAlign, + g5CenterY - altBoxHeight / 2, + charWidth, + altBoxHeight, + ) + + if ( + altString[1] == "9" + and altString[2] == "9" + and altLowerDigitrounded == 80 + ): + self.qp.setClipRect(dispRect) + + self.qp.drawText( + QRectF( + altTapeLeftAlign, + g5CenterY + - altBoxHeight / 2 + - 20 + + +0.8 * altBoxHeight * (altLowerDigitMod20 / 20), + charWidth, + 60, + ), + Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignTop, + "{:01d}\n{}".format((int(altString[0]) + 1) % 10, altString[0]) + if self._altitude >= 10000 + else "{:01d}\n ".format((int(altString[0]) + 1) % 10), + ) + + self.qp.setClipRect(0, 0, g5Width, g5Height) + else: + self.qp.drawText( + dispRect, + Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignVCenter, + altString[0] if self._altitude >= 10000 else "", + ) + + if self._altitude >= 980: + dispRect = QRectF( + altTapeLeftAlign + charWidth, + g5CenterY - altBoxHeight / 2, + charWidth, + altBoxHeight, + ) + + if altString[2] == "9" and altLowerDigitrounded == 80: + self.qp.setClipRect(dispRect) + + self.qp.drawText( + QRectF( + altTapeLeftAlign + charWidth, + g5CenterY + - altBoxHeight / 2 + - 20 + + +0.8 * altBoxHeight * (altLowerDigitMod20 / 20), + charWidth, + 60, + ), + Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignTop, + "{:01d}\n{}".format((int(altString[1]) + 1) % 10, altString[1]) + if self._altitude >= 1000 + else "{:01d}\n ".format((int(altString[1]) + 1) % 10), + ) + + self.qp.setClipRect(0, 0, g5Width, g5Height) + + else: + self.qp.drawText( + dispRect, + Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignVCenter, + altString[1] if self._altitude >= 1000 else "", + ) + pass + + dispRect = QRectF( + altTapeLeftAlign + 2 * charWidth, + g5CenterY - altBoxHeight / 2, + charWidth, + altBoxHeight, + ) + + if altLowerDigitrounded == 80: + self.qp.setClipRect(dispRect) + + self.qp.drawText( + QRectF( + altTapeLeftAlign + 2 * charWidth, + g5CenterY + - altBoxHeight / 2 + - 20 + + +0.8 * altBoxHeight * (altLowerDigitMod20 / 20), + charWidth, + 60, + ), + Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignTop, + "{:01d}\n{}".format((int(altString[2]) + 1) % 10, altString[2]), + ) + self.qp.setClipRect(0, 0, g5Width, g5Height) + else: + self.qp.drawText( + dispRect, + Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignVCenter, + altString[2], + ) + + # define a clip rect to avoid overflowing the alt box + self.qp.setClipRect( + QRectF( + g5Width - altBoxRightAlign - altBoxWdith * altBoxTextSplitRatio, + g5CenterY - altBoxHeight, + altBoxWdith * altBoxTextSplitRatio, + 2 * altBoxHeight, + ) + ) + + # draw the last 2 digits altitude + self.qp.drawText( + QRectF( + altTapeLeftAlign + altBoxWdith * (1 - altBoxTextSplitRatio), + g5CenterY + - 2 * altBoxHeight + + 0.8 * altBoxHeight * (altLowerDigitMod20 / 20), + altBoxWdith * altBoxTextSplitRatio, + 4 * altBoxHeight, + ), + Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignVCenter, + "\n".join("{:02d}".format(t) for t in altArray), + ) + + # clear clip rect + self.qp.setClipRect(0, 0, g5Width, g5Height) + + # draw the altimeter setting pen = self.qp.pen() - pen.setColor(Qt.cyan) + pen.setColor(Qt.GlobalColor.cyan) pen.setWidth(2) self.qp.setPen(pen) leftAlign = altTapeLeftAlign - 1.5 * altBoxSpikedimension @@ -1440,10 +2413,38 @@ def paintEvent(self, event): altSettingHeight, ) self.qp.drawRect(rect) + + if 1: + self.qp.drawText( + rect, + Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignVCenter, + "{:04.00f}".format(33.863886 * self._alt_setting), + ) + else: + self.qp.drawText( + rect, + Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignVCenter, + "{:02.02f}".format(self._alt_setting), + ) + + # draw the altitude selector + pen = self.qp.pen() + pen.setColor(Qt.GlobalColor.cyan) + pen.setWidth(2) + self.qp.setPen(pen) + leftAlign = altTapeLeftAlign - 1.5 * altBoxSpikedimension + rect = QRectF( + leftAlign, + 0, + g5Width - leftAlign, + altSettingHeight, + ) + self.qp.drawRect(rect) + self.qp.drawText( rect, - Qt.AlignHCenter | Qt.AlignVCenter, - "{:02.02f}".format(self._alt_setting), + Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignVCenter, + "{:d}ft".format(int(self._altitudeSel)), ) ################################################# @@ -1469,9 +2470,9 @@ def paintEvent(self, event): QPointF(g5CenterX + turnrateHalfWidth, g5Height - turnrateHeight), ) - self.setPen(0, Qt.transparent) + self.setPen(0, Qt.GlobalColor.transparent) - brush = QBrush(QColor(Qt.magenta)) + brush = QBrush(QColor(Qt.GlobalColor.magenta)) self.qp.setBrush(brush) rect = QRectF( g5CenterX, @@ -1574,3 +2575,165 @@ def alongRadiusCoord(self, angle, length): ) return QLine(startPoint, endPoint) + + +class pyG5FMA(pyG5Widget): + """Generate G5 wdiget view.""" + + def __init__(self, parent=None): + """g5Widget Constructor. + + Args: + parent: Parent Widget + + Returns: + self + """ + pyG5Widget.__init__(self, parent) + + def paintEvent(self, event): + """Paint the widget.""" + self.qp = QPainter(self) + + self.setPen(1, Qt.GlobalColor.black) + self.qp.setBrush(QBrush(Qt.GlobalColor.black)) + self.qp.drawRect(0, 0, g5Width, fmaHeight) + + if self._avionicson == 0: + self.setPen(1, Qt.GlobalColor.white) + self.qp.drawLine(0, 0, g5Width, fmaHeight) + self.qp.drawLine(0, fmaHeight, g5Width, 0) + self.qp.end() + return + + # draw the FMA sections delimiters + delimMargin = 5 + self.setPen(2, Qt.GlobalColor.white) + self.qp.drawLine( + QLineF(g5Width / 2, delimMargin, g5Width / 2, fmaHeight - delimMargin) + ) + self.qp.drawLine( + QLineF(g5Width / 3, delimMargin, g5Width / 3, fmaHeight - delimMargin) + ) + + self.setPen(2, Qt.GlobalColor.green) + + font = self.qp.font() + font.setPixelSize(20) + font.setBold(True) + self.qp.setFont(font) + + # draw the text when the AP is engaged + if self._apMode != 0: + # Draw the AP mode + mode = "AP" if self._apMode == 2 else "FD" + self.qp.drawText( + QRectF( + g5Width / 3 + delimMargin, + delimMargin, + g5Width / 6 - 2 * delimMargin, + fmaHeight - 2 * delimMargin, + ), + Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignVCenter, + mode, + ) + + # find the engaged horizontal navigation mode + if int(self._apState) & 0x2: + hmode = "HDG" + elif int(self._apState) & 0x4: + hmode = "ROL" + elif int(self._apState) & 0x200: + if int(self._hsiSource) == 2: + hmode = "GPS" + elif int(self._hsiSource) == 1: + hmode = "{}".format(self.getNavTypeString(self._nav2type, "")) + elif int(self._hsiSource) == 0: + hmode = "{}".format(self.getNavTypeString(self._nav1type, "")) + else: + hmode = "ERR" + else: + hmode = "" + self.qp.drawText( + QRectF( + g5Width / 6 + delimMargin, + delimMargin, + g5Width / 6 - 2 * delimMargin, + fmaHeight - 2 * delimMargin, + ), + Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignVCenter, + hmode, + ) + + # find the engaged vertical navigation mode + if int(self._apState) & 0x8: + vmode = "FLC {} kts".format(int(self._apAirSpeed)) + elif int(self._apState) & 0x10: + vmode = "VS {} fpm".format(int(self._apVS)) + elif int(self._apState) & 0x800: + vmode = "GS" + elif int(self._apState) & 0x4000: + vmode = "ALT {} ft".format(int(self._altitudeHold)) + elif int(self._apState) & 0x40000: + vmode = "VPATH" + else: + vmode = "PIT" + + self.qp.drawText( + QRectF( + g5Width / 2 + delimMargin, + delimMargin, + g5Width * 2 / 6 - 2 * delimMargin, + fmaHeight - 2 * delimMargin, + ), + Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter, + vmode, + ) + + # draw the armed horizontal navigation mode + self.setPen(2, Qt.GlobalColor.white) + + if int(self._apState) & 0x100: + if int(self._hsiSource) == 2: + hmode = "GPS" + elif int(self._hsiSource) == 1: + hmode = "{}".format(self.getNavTypeString(self._nav2type, "")) + elif int(self._hsiSource) == 0: + hmode = "{}".format(self.getNavTypeString(self._nav1type, "")) + else: + hmode = "ERR" + + self.qp.drawText( + QRectF( + delimMargin, + delimMargin, + g5Width / 6 - 2 * delimMargin, + fmaHeight - 2 * delimMargin, + ), + Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignVCenter, + hmode, + ) + vmode = "" + if int(self._apState) & 0x20000: + vmode += " VPATH" if len(vmode) else "VPTH" + else: + if int(self._apState) & 0x20: + if self._altitudeVNAV > self._apAltitude: + vmode += " ALTV" if len(vmode) else "ALTV" + else: + vmode += " ALTS" if len(vmode) else "ALTS" + if int(self._apState) & 0x400: + vmode += " GS" if len(vmode) else "GS" + + self.qp.drawText( + QRectF( + g5Width * 4 / 6 + delimMargin, + delimMargin, + g5Width * 2 / 6 - 2 * delimMargin, + fmaHeight - 2 * delimMargin, + ), + Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignVCenter, + vmode, + ) + + self.qp.end() diff --git a/pyG5/pyG5ViewTester.py b/pyG5/pyG5ViewTester.py index 98e9d31..7c87119 100644 --- a/pyG5/pyG5ViewTester.py +++ b/pyG5/pyG5ViewTester.py @@ -6,8 +6,8 @@ import sys -from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import ( +from PySide6.QtCore import Qt +from PySide6.QtWidgets import ( QApplication, QHBoxLayout, QGridLayout, @@ -21,7 +21,9 @@ QScrollArea, ) -from pyG5.pyG5View import pyG5DualStack, g5Width, g5Height +from PySide6.QtGui import QKeySequence, QAction + +from pyG5.pyG5View import pyG5DualStackFMA, g5Width, g5Height, pyG5SecondaryWidget sliderWdith = 300 @@ -42,7 +44,7 @@ def controlWidgetGen(control): layout.addWidget(QLabel(control["name"], parent=w), 0, 0) - slider = QSlider(Qt.Horizontal, parent=w) + slider = QSlider(Qt.Orientation.Horizontal, parent=w) slider.setRange(control["min"], control["max"]) spinbox = QSpinBox(parent=w) @@ -80,11 +82,15 @@ def makeControlDict(name, min, max): # Set window size. w.resize(sliderWdith + g5Width, g5Height) - + w.move(0, 0) # Set window title w.setWindowTitle("Garmin G5") file_menu = QMenu("&File", w) - file_menu.addAction("&Quit", w.close, Qt.CTRL + Qt.Key_W) + + quitAction = QAction("&Quit", w) + quitAction.setShortcut(QKeySequence("Ctrl+w")) + quitAction.triggered.connect(w.close) + file_menu.addAction(quitAction) menuBar = w.menuBar() menuBar.addMenu(file_menu) @@ -102,12 +108,23 @@ def makeControlDict(name, min, max): scrollArea.setObjectName("scrollArea") controlVLayout = QVBoxLayout() controlWidget.setLayout(controlVLayout) - hlayout.addWidget(scrollArea) - g5View = pyG5DualStack() + secView = pyG5SecondaryWidget() + vlayout = QVBoxLayout() + vlayout.addWidget(scrollArea) + vlayout.addWidget(secView) + + hlayout.addLayout(vlayout) + g5View = pyG5DualStackFMA() hlayout.addWidget(g5View) controls = [ + makeControlDict("altitude", -1000, 45000), + makeControlDict("altitudeSel", -1000, 45000), + makeControlDict("flaps", 0, 4), + makeControlDict("trims", -1, 1), + makeControlDict("carbheat", 0, 1), + makeControlDict("fuelsel", -1, 1), makeControlDict("avionicson", 0, 1), makeControlDict("magHeading", 0, 360), makeControlDict("groundTrack", 0, 360), @@ -116,7 +133,6 @@ def makeControlDict(name, min, max): makeControlDict("kias", 0, 230), makeControlDict("kiasDelta", -30, 30), makeControlDict("gs", 0, 230), - makeControlDict("altitude", -1000, 45000), makeControlDict("vh_ind_fpm", -1500, 1500), makeControlDict("turnRate", -130, 130), makeControlDict("slip", -10, 10), @@ -136,6 +152,8 @@ def makeControlDict(name, min, max): makeControlDict("gpsdft", -3, 3), makeControlDict("gpsgsavailable", 0, 1), makeControlDict("gpsgs", -30, 30), + makeControlDict("gpshsisens", 0, 15), + makeControlDict("parkBrake", 0, 1), ] for control in controls: @@ -143,6 +161,8 @@ def makeControlDict(name, min, max): try: slider.valueChanged.connect(getattr(g5View.pyG5AI, control["name"])) slider.valueChanged.connect(getattr(g5View.pyG5HSI, control["name"])) + slider.valueChanged.connect(getattr(g5View.pyG5FMA, control["name"])) + slider.valueChanged.connect(getattr(secView, control["name"])) print("Slider connected: {}".format(control["name"])) except Exception as inst: print("{} control not connected to view: {}".format(control["name"], inst)) @@ -154,6 +174,6 @@ def makeControlDict(name, min, max): # Show window w.show() - sys.exit(a.exec_()) + sys.exit(a.exec()) pass diff --git a/requirements.txt b/requirements.txt index f0f8353..487f665 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,24 +1,4 @@ -backports.entry-points-selectable==1.1.0 -cfgv==3.3.0 -codespell==2.1.0 -distlib==0.3.2 -filelock==3.0.12 -flake8==3.9.2 -flake8-docstrings==1.5.0 -identify==2.2.12 -isort==5.9.3 -mccabe==0.6.1 -nodeenv==1.6.0 -platformdirs==2.2.0 -pre-commit==2.13.0 -pycodestyle==2.7.0 -pydocstyle==5.1.1 -pyflakes==2.3.1 -PyQt5==5.15.7 -PyQt5-Qt5==5.15.2 -PyQt5-sip==12.11.0 -PyYAML==5.4.1 -six==1.16.0 -snowballstemmer==2.1.0 -toml==0.10.2 -virtualenv==20.7.0 +PySide6==6.5.3 +PySide6-Addons==6.5.3 +PySide6-Essentials==6.5.3 +shiboken6==6.5.3 diff --git a/setup.py b/setup.py index 15fc614..95d014d 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ # commented out due to impossibility # to install PyQt5 automatically from pip on Raspbian # requirements = ["PyQt5"] -requirements = [] +requirements = ["PySide6"] test_requirements = [ # TODO: put package test requirements here