diff --git a/sw/supervision/python/configuration_panel.py b/sw/supervision/python/configuration_panel.py index 71b08a9285..d7d999bb37 100644 --- a/sw/supervision/python/configuration_panel.py +++ b/sw/supervision/python/configuration_panel.py @@ -1,8 +1,9 @@ from PyQt5.QtWidgets import * from PyQt5 import QtCore, QtGui, QtWidgets +from PyQt5.QtCore import QProcess import utils from generated.ui_configuration_panel import Ui_ConfigurationPanel -from program_widget import ProgramWidget +from program_widget import ProgramWidget, TabProgramsState from conf import * from programs_conf import parse_tools import subprocess @@ -12,6 +13,7 @@ class ConfigurationPanel(QWidget, Ui_ConfigurationPanel): clear_error = QtCore.pyqtSignal() ac_edited = QtCore.pyqtSignal(Aircraft) + program_state_changed = QtCore.pyqtSignal(TabProgramsState) def __init__(self, parent=None, *args, **kwargs): QWidget.__init__(self, parent=parent, *args, **kwargs) @@ -19,6 +21,7 @@ class ConfigurationPanel(QWidget, Ui_ConfigurationPanel): self.console_widget.filter_widget.hide() self.currentAC = None # type: Aircraft self.flight_plan_editor = None + self.programs_state: TabProgramsState = TabProgramsState.IDLE self.conf_widget.conf_changed.connect(self.handle_conf_changed) self.conf_widget.setting_changed.connect(self.handle_setting_changed) self.conf_widget.flight_plan.edit_alt.connect(self.edit_flightplan_gcs) @@ -84,7 +87,7 @@ class ConfigurationPanel(QWidget, Ui_ConfigurationPanel): self.programs_widget.layout().addWidget(pw) pw.ready_read_stderr.connect(lambda: self.console_widget.handle_stderr(pw)) pw.ready_read_stdout.connect(lambda: self.console_widget.handle_stdout(pw)) - pw.finished.connect(lambda c, s: self.console_widget.handle_program_finished(pw, c, s)) + pw.finished.connect(lambda c, s: self.handle_program_finished(pw, c, s)) if cb is not None: pw.finished.connect(cb) pw.remove.connect(lambda: self.remove_program(pw)) @@ -92,6 +95,17 @@ class ConfigurationPanel(QWidget, Ui_ConfigurationPanel): if not settings.value("keep_build_programs", False, bool): pw.finished.connect(lambda: self.remove_program(pw)) pw.start_program() + self.programs_state = TabProgramsState.RUNNING + self.program_state_changed.emit(self.programs_state) + + def handle_program_finished(self, pw: ProgramWidget, exit_code: int, exit_status: QProcess.ExitStatus): + self.console_widget.handle_program_finished(pw, exit_code, exit_status) + if exit_code != 0 and exit_code != 15: + self.programs_state = TabProgramsState.ERROR + else: + if len(self.programs_widget.layout().children()) == 0 and self.programs_state != TabProgramsState.ERROR: + self.programs_state = TabProgramsState.IDLE + self.program_state_changed.emit(self.programs_state) def remove_program(self, pw: ProgramWidget): self.programs_widget.layout().removeWidget(pw) diff --git a/sw/supervision/python/error.svg b/sw/supervision/python/error.svg new file mode 100644 index 0000000000..d7aae300e3 --- /dev/null +++ b/sw/supervision/python/error.svg @@ -0,0 +1,64 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/sw/supervision/python/paparazzicenter.py b/sw/supervision/python/paparazzicenter.py index 8cf294d618..025c5cd737 100755 --- a/sw/supervision/python/paparazzicenter.py +++ b/sw/supervision/python/paparazzicenter.py @@ -13,6 +13,14 @@ from conf import Conf, Aircraft, ConfError from app_settings import AppSettings from generated.ui_supervision_window import Ui_SupervisionWindow from generated.ui_new_ac_dialog import Ui_NewACDialog +from program_widget import TabProgramsState + +dirname = os.path.dirname(os.path.abspath(__file__)) + + +TAB_ICONS = {TabProgramsState.IDLE: QtGui.QIcon(), + TabProgramsState.RUNNING: QtGui.QIcon(os.path.join(dirname, "running.svg")), + TabProgramsState.ERROR: QtGui.QIcon(os.path.join(dirname, "error.svg"))} class PprzCenter(QMainWindow, Ui_SupervisionWindow): @@ -40,6 +48,9 @@ class PprzCenter(QMainWindow, Ui_SupervisionWindow): self.header.ac_save.connect(lambda _: self.conf.save()) self.header.ac_new.connect(self.handle_new_ac) + self.configuration_panel.program_state_changed.connect(lambda state: self.programs_state_changed(state, 0)) + self.operation_panel.session.program_state_changed.connect(lambda state: self.programs_state_changed(state, 1)) + self.operation_panel.session.program_spawned.connect(self.header.disable_sets) self.operation_panel.session.programs_all_stopped.connect(self.header.enable_sets) @@ -51,6 +62,9 @@ class PprzCenter(QMainWindow, Ui_SupervisionWindow): self.operation_panel.session.init() self.header.update_sets() + def programs_state_changed(self, state: TabProgramsState, tab_index): + self.tabwidget.setTabIcon(tab_index, TAB_ICONS[state]) + def handle_set_changed(self, conf_file): self.conf = Conf(conf_file) Conf.set_current_conf(conf_file) diff --git a/sw/supervision/python/program_widget.py b/sw/supervision/python/program_widget.py index 54ce45ec17..536028f461 100644 --- a/sw/supervision/python/program_widget.py +++ b/sw/supervision/python/program_widget.py @@ -9,12 +9,20 @@ from PyQt5.QtCore import QProcess from PyQt5.QtGui import QIcon import utils from typing import List +from enum import Enum + + +class TabProgramsState(Enum): + IDLE = 0 + RUNNING = 1 + ERROR = 2 class ProgramWidget(QWidget, Ui_Program): ready_read_stdout = QtCore.pyqtSignal() ready_read_stderr = QtCore.pyqtSignal() + started = QtCore.pyqtSignal() finished = QtCore.pyqtSignal(int, QProcess.ExitStatus) remove = QtCore.pyqtSignal() @@ -38,8 +46,10 @@ class ProgramWidget(QWidget, Ui_Program): self.icon_label.setToolTip(shortname) def start_program(self): + self.program_lineedit.setStyleSheet("") if self.process.state() == QProcess.NotRunning: self.process.start(self.cmd[0], self.cmd[1:]) + self.started.emit() def handle_cmd_return(self): if self.process.state() == QProcess.NotRunning: @@ -68,6 +78,8 @@ class ProgramWidget(QWidget, Ui_Program): self.program_lineedit.setReadOnly(True) def handle_finished(self, exit_code: int, exit_status: QProcess.ExitStatus): + if exit_code not in (0, 15): + self.program_lineedit.setStyleSheet("background: #f56464") icon = QIcon.fromTheme("media-playback-start") self.run_button.setIcon(icon) self.program_lineedit.setReadOnly(False) diff --git a/sw/supervision/python/running.svg b/sw/supervision/python/running.svg new file mode 100644 index 0000000000..db4659e242 --- /dev/null +++ b/sw/supervision/python/running.svg @@ -0,0 +1,64 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/sw/supervision/python/session_widget.py b/sw/supervision/python/session_widget.py index 2bd2c55e23..1e311763ff 100644 --- a/sw/supervision/python/session_widget.py +++ b/sw/supervision/python/session_widget.py @@ -10,7 +10,7 @@ import utils import lxml.etree as ET from typing import List, Optional, Tuple, Dict -from program_widget import ProgramWidget +from program_widget import ProgramWidget, TabProgramsState from tools_menu import ToolMenu from programs_conf import * from conf import * @@ -21,6 +21,7 @@ class SessionWidget(QWidget, Ui_Session): programs_all_stopped = QtCore.pyqtSignal() program_spawned = QtCore.pyqtSignal() + program_state_changed = QtCore.pyqtSignal(TabProgramsState) def __init__(self, parent=None): QWidget.__init__(self, parent=parent) @@ -33,6 +34,7 @@ class SessionWidget(QWidget, Ui_Session): self.tools_menu = ToolMenu() self.sessions_combo.addItems(["Simulation", "Replay"]) self.sessions_combo.insertSeparator(2) + self.programs_state: TabProgramsState = TabProgramsState.IDLE self.menu_button.addAction(self.save_session_action) self.menu_button.addAction(self.save_as_action) self.menu_button.addAction(self.rename_session_action) @@ -71,6 +73,7 @@ class SessionWidget(QWidget, Ui_Session): return self.sessions_combo.currentText() def start_session(self): + self.reset_programs_status() combo_text = self.sessions_combo.currentText() if combo_text == "Simulation": self.start_simulation() @@ -145,6 +148,7 @@ class SessionWidget(QWidget, Ui_Session): pw.ready_read_stderr.connect(lambda: self.console.handle_stderr(pw)) pw.ready_read_stdout.connect(lambda: self.console.handle_stdout(pw)) pw.finished.connect(lambda c, s: self.handle_program_finished(pw, c, s)) + pw.started.connect(self.handle_program_started) pw.remove.connect(lambda: self.remove_program(pw)) # if REMOVE_PROGRAMS_FINISHED: # pw.finished.connect(lambda: self.remove_program(pw)) @@ -169,17 +173,34 @@ class SessionWidget(QWidget, Ui_Session): if not self.any_program_running(): self.programs_all_stopped.emit() + if c != 0 and c != 15: + self.programs_state = TabProgramsState.ERROR + else: + if not self.any_program_running() and self.programs_state != TabProgramsState.ERROR: + self.programs_state = TabProgramsState.IDLE + self.program_state_changed.emit(self.programs_state) + + def handle_program_started(self): + self.programs_state = TabProgramsState.RUNNING + self.program_state_changed.emit(self.programs_state) + def reset_programs_status(self): + self.programs_state = TabProgramsState.IDLE + self.program_state_changed.emit(self.programs_state) + def stop_all(self): for pw in self.program_widgets: pw.terminate() + self.reset_programs_status() def start_all(self): for pw in self.program_widgets: pw.start_program() + self.reset_programs_status() def remove_all(self): for pw in list(self.program_widgets): pw.handle_remove() + self.reset_programs_status() def init_tools_menu(self): for t in self.tools.values():