diff --git a/MAVProxy/modules/mavproxy_qt_console/__init__.py b/MAVProxy/modules/mavproxy_qt_console/__init__.py new file mode 100644 index 0000000000..9e552803b9 --- /dev/null +++ b/MAVProxy/modules/mavproxy_qt_console/__init__.py @@ -0,0 +1,159 @@ +from MAVProxy.modules.lib import mp_module +from MAVProxy.modules.mavproxy_qt_console.ui_qt_console import Ui_QtConsole +from PySide6.QtWidgets import QApplication, QMainWindow +from PySide6.QtCore import QTimer +from PySide6.QtGui import QColor +import sys +import threading +from MAVProxy.modules.lib import multiproc +import multiprocessing +import time +from MAVProxy.modules.lib.wxconsole_util import Text, Value +import socket +import errno +from MAVProxy.modules.lib import textconsole + +class QtConsoleWindow(QMainWindow): + def __init__(self, parent): + super(QtConsoleWindow, self).__init__() + self._parent = parent + self._ui = Ui_QtConsole() + self._ui.setupUi(self) + + self._timer = QTimer(self) + self._timer.timeout.connect(self.update) + self._timer.start(200) + + self._ui.actionShow_Map.triggered.connect(self.show_map) + + def update(self): + '''Slot called by QTimer at a specified interval''' + if self._parent.close_event.wait(0.001): + self._timer.stop() + self.close() + return + + try: + poll_success = self._parent.child_pipe_recv.poll() + if not poll_success: + return + except socket.error as e: + if e.errno == errno.EPIPE: + self._timer.stop() + return + else: + raise e + + try: + msg = self._parent.child_pipe_recv.recv() + except EOFError: + self._timer.stop() + return + + if isinstance(msg, Text): + self._ui.textEdit.setTextColor(QColor(msg.fg)) + self._ui.textEdit.setTextBackgroundColor(QColor(msg.bg)) + self._ui.textEdit.append(msg.text) + + def show_map(self): + self._parent.child_pipe_send.send("# module load map") + + def closeEvent(self, event) -> None: + """Handles the cross button on the UI""" + if not self._parent.close_event.is_set(): + self._parent.child_pipe_send.send("# module unload qt_console") + return super().closeEvent(event) + +class QtConsole(textconsole.SimpleConsole): + def __init__(self, mpstate) -> None: + super(QtConsole, self).__init__() + self.mpstate = mpstate + self.parent_pipe_recv, self.child_pipe_send = multiproc.Pipe(duplex=False) + self.child_pipe_recv,self.parent_pipe_send = multiproc.Pipe(duplex=False) + + # For quitting cleanly + self.close_event = multiproc.Event() + self.close_event.clear() + + # main process in which GUI (child) lives + self.child = multiprocessing.Process(target=self.child_task) + self.child.start() + + # This class (parent) doesn't need the child pipes + self.child_pipe_send.close() + self.child_pipe_recv.close() + + # Thread that listens to clicks etc. from the GUI + t = threading.Thread(target=self.watch_thread) + t.daemon = True + t.start() + + def watch_thread(self): + '''watch for menu events from child''' + from MAVProxy.modules.lib.mp_settings import MPSetting + try: + while True: + msg = self.parent_pipe_recv.recv() + if msg.startswith("#"): # Header for command packet + self.mpstate.functions.process_stdin(msg[2:]) + # print(msg) + # if isinstance(msg, win_layout.WinLayout): + # win_layout.set_layout(msg, self.set_layout) + # elif self.menu_callback is not None: + # self.menu_callback(msg) + time.sleep(0.1) + except EOFError: + pass + + def child_task(self): + '''Main Process in which the Qt GUI lives''' + self.parent_pipe_send.close() # Good sense to close pipes that are not used by this process + self.parent_pipe_recv.close() + app = QApplication.instance() + if app == None: + app = QApplication() + + window = QtConsoleWindow(self) + window.show() + app.exec() + + def write(self, text, fg='black', bg='white'): + '''write to the console''' + try: + self.parent_pipe_send.send(Text(text, fg, bg)) + except Exception: + pass + + def writeln(self, text, fg='black', bg='white'): + '''write to the console with linefeed''' + if not isinstance(text, str): + text = str(text) + self.write(text, fg=fg, bg=bg) + + def close(self): + '''close the console''' + self.close_event.set() + if self.child.is_alive(): + self.child.join() + +class QtConsoleModule(mp_module.MPModule): + def __init__(self, mpstate): + super().__init__(mpstate, "qt_console", "GUI Console (Qt)", public=True, multi_vehicle=True) + self.add_command('qt_console', self.cmd_qt_console, "qt console module", ['add','list','remove']) + self.mpstate.console = QtConsole(mpstate) + + def cmd_qt_console(self, args): + pass + + def mavlink_packet(self, packet): + # print("Packet recieved") + return super().mavlink_packet(packet) + + def unload(self): + '''unload module''' + self.mpstate.console.close() + self.mpstate.console = textconsole.SimpleConsole() + +def init(mpstate): + '''initialise module''' + return QtConsoleModule(mpstate) \ No newline at end of file diff --git a/MAVProxy/modules/mavproxy_qt_console/qt_console.ui b/MAVProxy/modules/mavproxy_qt_console/qt_console.ui new file mode 100644 index 0000000000..4f40d3d150 --- /dev/null +++ b/MAVProxy/modules/mavproxy_qt_console/qt_console.ui @@ -0,0 +1,92 @@ + + + QtConsole + + + + 0 + 0 + 799 + 308 + + + + Qt Console + + + + + + 0 + 40 + 791 + 221 + + + + 0 + + + + Messages + + + + + 0 + 0 + 791 + 191 + + + + true + + + + + + Tab 2 + + + + + + + + 0 + 0 + 799 + 22 + + + + + MAVProxy + + + + + + + + + + + Settings + + + + + Show Map + + + + + Show HUD + + + + + + diff --git a/MAVProxy/modules/mavproxy_qt_console/ui_qt_console.py b/MAVProxy/modules/mavproxy_qt_console/ui_qt_console.py new file mode 100644 index 0000000000..45cb356a17 --- /dev/null +++ b/MAVProxy/modules/mavproxy_qt_console/ui_qt_console.py @@ -0,0 +1,82 @@ +# -*- coding: utf-8 -*- + +################################################################################ +## Form generated from reading UI file 'qt_console.ui' +## +## Created by: Qt User Interface Compiler version 6.4.2 +## +## WARNING! All changes made in this file will be lost when recompiling UI file! +################################################################################ + +from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale, + QMetaObject, QObject, QPoint, QRect, + QSize, QTime, QUrl, Qt) +from PySide6.QtGui import (QAction, QBrush, QColor, QConicalGradient, + QCursor, QFont, QFontDatabase, QGradient, + QIcon, QImage, QKeySequence, QLinearGradient, + QPainter, QPalette, QPixmap, QRadialGradient, + QTransform) +from PySide6.QtWidgets import (QApplication, QMainWindow, QMenu, QMenuBar, + QSizePolicy, QStatusBar, QTabWidget, QTextEdit, + QWidget) + +class Ui_QtConsole(object): + def setupUi(self, QtConsole): + if not QtConsole.objectName(): + QtConsole.setObjectName(u"QtConsole") + QtConsole.resize(799, 308) + self.actionSettings = QAction(QtConsole) + self.actionSettings.setObjectName(u"actionSettings") + self.actionShow_Map = QAction(QtConsole) + self.actionShow_Map.setObjectName(u"actionShow_Map") + self.actionShow_HUD = QAction(QtConsole) + self.actionShow_HUD.setObjectName(u"actionShow_HUD") + self.centralwidget = QWidget(QtConsole) + self.centralwidget.setObjectName(u"centralwidget") + self.tabWidget = QTabWidget(self.centralwidget) + self.tabWidget.setObjectName(u"tabWidget") + self.tabWidget.setGeometry(QRect(0, 40, 791, 221)) + self.tab = QWidget() + self.tab.setObjectName(u"tab") + self.textEdit = QTextEdit(self.tab) + self.textEdit.setObjectName(u"textEdit") + self.textEdit.setGeometry(QRect(0, 0, 791, 191)) + self.textEdit.setReadOnly(True) + self.tabWidget.addTab(self.tab, "") + self.tab_2 = QWidget() + self.tab_2.setObjectName(u"tab_2") + self.tabWidget.addTab(self.tab_2, "") + QtConsole.setCentralWidget(self.centralwidget) + self.menubar = QMenuBar(QtConsole) + self.menubar.setObjectName(u"menubar") + self.menubar.setGeometry(QRect(0, 0, 799, 22)) + self.menuFIle = QMenu(self.menubar) + self.menuFIle.setObjectName(u"menuFIle") + QtConsole.setMenuBar(self.menubar) + self.statusbar = QStatusBar(QtConsole) + self.statusbar.setObjectName(u"statusbar") + QtConsole.setStatusBar(self.statusbar) + + self.menubar.addAction(self.menuFIle.menuAction()) + self.menuFIle.addAction(self.actionSettings) + self.menuFIle.addAction(self.actionShow_Map) + self.menuFIle.addAction(self.actionShow_HUD) + + self.retranslateUi(QtConsole) + + self.tabWidget.setCurrentIndex(0) + + + QMetaObject.connectSlotsByName(QtConsole) + # setupUi + + def retranslateUi(self, QtConsole): + QtConsole.setWindowTitle(QCoreApplication.translate("QtConsole", u"Qt Console", None)) + self.actionSettings.setText(QCoreApplication.translate("QtConsole", u"Settings", None)) + self.actionShow_Map.setText(QCoreApplication.translate("QtConsole", u"Show Map", None)) + self.actionShow_HUD.setText(QCoreApplication.translate("QtConsole", u"Show HUD", None)) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab), QCoreApplication.translate("QtConsole", u"Messages", None)) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2), QCoreApplication.translate("QtConsole", u"Tab 2", None)) + self.menuFIle.setTitle(QCoreApplication.translate("QtConsole", u"MAVProxy", None)) + # retranslateUi +