diff --git a/sw/supervision/python/Makefile b/sw/supervision/python/Makefile index e5b90826ca..0e4ef570af 100644 --- a/sw/supervision/python/Makefile +++ b/sw/supervision/python/Makefile @@ -1,19 +1,25 @@ # Copyright (C) 2008-2022 The Paparazzi Team # released under GNU GPLv2 or later. See COPYING file. -all: - mkdir -p generated - pyuic5 ui/conf_header.ui -o generated/ui_conf_header.py - pyuic5 ui/conf_item.ui -o generated/ui_conf_item.py - pyuic5 ui/build.ui -o generated/ui_build.py - pyuic5 ui/configuration_panel.ui -o generated/ui_configuration_panel.py - pyuic5 ui/new_ac_dialog.ui -o generated/ui_new_ac_dialog.py - pyuic5 ui/session.ui -o generated/ui_session.py - pyuic5 ui/program.ui -o generated/ui_program.py - pyuic5 ui/operation_panel.ui -o generated/ui_operation_panel.py - pyuic5 ui/console.ui -o generated/ui_console.py - pyuic5 ui/tools_list.ui -o generated/ui_tools_list.py - pyuic5 ui/app_settings.ui -o generated/ui_app_settings.py - pyuic5 ui/conf_settings.ui -o generated/ui_conf_settings.py -clean_ui: +SOURCEDIR = ui +BUILDDIR = generated + +SOURCES = $(wildcard $(SOURCEDIR)/*.ui) +OBJECTS = $(patsubst $(SOURCEDIR)/%.ui,$(BUILDDIR)/%.py,$(SOURCES)) + +CC = pyuic5 + +all: $(BUILDDIR) $(OBJECTS) + + +$(OBJECTS): $(BUILDDIR)/%.py : $(SOURCEDIR)/%.ui + $(CC) -o $@ $<; + +$(BUILDDIR): + mkdir -p $@ + +clean: rm -r generated + + +.PHONY: build_dir diff --git a/sw/supervision/python/conf.py b/sw/supervision/python/conf.py index e7540aab37..de98bbdb3e 100644 --- a/sw/supervision/python/conf.py +++ b/sw/supervision/python/conf.py @@ -1,3 +1,5 @@ +# Copyright (C) 2008-2022 The Paparazzi Team +# released under GNU GPLv2 or later. See COPYING file. from dataclasses import dataclass, field from typing import List, Dict import lxml.etree as ET @@ -65,11 +67,6 @@ class Aircraft: completed = subprocess.run([MOD_DEP, "-ac", self.name, "-af", self.airframe, "-fp", self.flight_plan], capture_output=True) if completed.returncode == 0: - def remove_prefix(s): - if s.startswith(utils.CONF_DIR): - return s[len(utils.CONF_DIR):] - else: - return s def make_setting(m): setting = Setting(m, True) @@ -80,7 +77,7 @@ class Aircraft: new_settings_modules = [] for module_path in completed.stdout.decode().strip().split(): - module = remove_prefix(module_path) + module = utils.remove_prefix(module_path, utils.CONF_DIR) xml = ET.parse(module_path) for xml_setting in xml.getroot().findall("settings"): name = xml_setting.get("name") @@ -215,6 +212,7 @@ class Conf: self.tree_orig = self.to_xml_tree() def restore_conf(self): + print("conf restored.") self.parse(self.tree_orig) def to_string(self): diff --git a/sw/supervision/python/conf_settings_widget.py b/sw/supervision/python/conf_settings_widget.py index 9f67e4cdb4..69788d6560 100644 --- a/sw/supervision/python/conf_settings_widget.py +++ b/sw/supervision/python/conf_settings_widget.py @@ -21,7 +21,7 @@ class ConfSettingsWidget(QWidget, Ui_SettingsConf): base_settings_path = os.path.join(utils.CONF_DIR, "settings") filenames, _ = QFileDialog.getOpenFileNames(self, "Add settings", base_settings_path, "Settings (*.xml)") for filename in filenames: - name = utils.removeprefix(filename, utils.CONF_DIR) + name = utils.remove_prefix(filename, utils.CONF_DIR) print(name) item = QListWidgetItem(name) item.setCheckState(QtCore.Qt.Checked) diff --git a/sw/supervision/python/configuration_panel.py b/sw/supervision/python/configuration_panel.py index 9cb86d71e5..d0dde6b5a0 100644 --- a/sw/supervision/python/configuration_panel.py +++ b/sw/supervision/python/configuration_panel.py @@ -1,114 +1,44 @@ -import os, sys, copy from PyQt5.QtWidgets import * from PyQt5 import QtCore, QtGui, QtWidgets import utils from generated.ui_configuration_panel import Ui_ConfigurationPanel -from generated.ui_new_ac_dialog import Ui_Dialog from program_widget import ProgramWidget from conf import * from programs_conf import parse_tools import subprocess -PAPARAZZI_SRC = os.getenv("PAPARAZZI_SRC") -PAPARAZZI_HOME = os.getenv("PAPARAZZI_HOME", PAPARAZZI_SRC) -lib_path = os.path.normpath(os.path.join(PAPARAZZI_SRC, 'sw', 'lib', 'python')) -sys.path.append(lib_path) -import paparazzi - # TODO make a setting ? REMOVE_PROGRAMS_FINISHED = True class ConfigurationPanel(QWidget, Ui_ConfigurationPanel): - msg_error = QtCore.pyqtSignal(str) clear_error = QtCore.pyqtSignal() - ac_changed = QtCore.pyqtSignal(Aircraft) + ac_edited = QtCore.pyqtSignal(Aircraft) def __init__(self, parent=None, *args, **kwargs): QWidget.__init__(self, parent=parent, *args, **kwargs) self.setupUi(self) self.console_widget.filter_widget.hide() - self.conf = None # type: conf.Conf - self.currentAC = None # type: str + self.currentAC = None # type: Aircraft self.flight_plan_editor = None - self.header.set_changed.connect(self.handle_set_changed) - self.header.ac_changed.connect(self.update_ac) - self.header.id_changed.connect(self.handle_id_changed) - self.header.refresh_button.clicked.connect(self.refresh_ac) - self.header.color_button.clicked.connect(self.change_color) - self.header.save_button.clicked.connect(lambda: self.conf.save()) - self.addAction(self.save_conf_action) - self.save_conf_action.triggered.connect(lambda: self.conf.save()) 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) - self.header.rename_action.triggered.connect(self.rename_ac) - self.header.new_ac_action.triggered.connect(self.new_ac) - self.header.duplicate_action.triggered.connect(self.duplicate_ac) - self.header.remove_ac_action.triggered.connect(self.remove_ac) self.build_widget.spawn_program.connect(self.launch_program) def init(self): - sets = paparazzi.get_list_of_conf_files() settings = utils.get_settings() - self.header.set_sets(sets, conf_init=Conf.get_current_conf()) - last_ac: QtCore.QVariant = settings.value("ui/last_AC", None, str) - last_target: QtCore.QVariant = settings.value("ui/last_target", None, str) - if last_ac is not None: - self.header.ac_combo.setCurrentText(last_ac) - if last_target is not None: - self.build_widget.target_combo.setCurrentText(last_target) window_size: QtCore.QSize = settings.value("ui/window_size", QtCore.QSize(1000, 600), QtCore.QSize) lpw = settings.value("ui/left_pane_width", 100, int) self.splitter.setSizes([lpw, window_size.width() - lpw]) - def handle_set_changed(self, conf_file): - self.conf = Conf(conf_file) - Conf.set_current_conf(conf_file) - self.build_widget.set_conf(self.conf) - acs = [ac.name for ac in self.conf.aircrafts] - self.header.set_acs(acs) - - def disable_sets(self): - self.header.set_combo.setDisabled(True) - - def enable_sets(self): - self.header.set_combo.setDisabled(False) - - def update_ac(self, ac_name): - ac = self.conf[ac_name] - if ac_name != "" and ac is not None: - self.conf_widget.setDisabled(False) - self.currentAC = ac_name - status, stderr = ac.update() - if status != 0: - self.msg_error.emit(stderr.decode().strip()) - else: - self.clear_error.emit() - self.header.set_ac(ac) - self.conf_widget.set_ac(ac) - self.build_widget.update_targets(self.conf[ac_name]) - self.ac_changed.emit(self.conf[ac_name]) - else: - # self.conf_widget.reset() + def set_ac(self, ac: Aircraft): + if ac is None: self.conf_widget.setDisabled(True) - - def get_current_ac(self) -> str: - """ - :return: name of the current AC - """ - return self.currentAC - - def refresh_ac(self): - self.update_ac(self.currentAC) - - def handle_id_changed(self, id): - self.conf[self.currentAC].ac_id = id - if len(self.conf.get_all(id)) > 1: - self.header.id_spinBox.setStyleSheet("background-color: red;") - else: - self.header.id_spinBox.setStyleSheet("background-color: white;") + self.currentAC = ac + self.conf_widget.set_ac(ac) + self.build_widget.update_targets(ac) def handle_setting_changed(self): def make_setting(item: QListWidgetItem): @@ -125,142 +55,18 @@ class ConfigurationPanel(QWidget, Ui_ConfigurationPanel): else: settings.append(s) - self.conf[self.currentAC].settings_modules = modules - self.conf[self.currentAC].settings = settings + self.currentAC.settings_modules = modules + self.currentAC.settings = settings + self.ac_edited.emit(self.currentAC) # should we save each time a tiny change is made ? very inefficient ! # self.conf.save() def handle_conf_changed(self): - self.conf[self.currentAC].airframe = self.conf_widget.airframe.path - self.conf[self.currentAC].flight_plan = self.conf_widget.flight_plan.path - self.conf[self.currentAC].radio = self.conf_widget.radio.path - self.conf[self.currentAC].telemetry = self.conf_widget.telemetry.path - # reload settings modules, and update UI - self.update_ac(self.currentAC) - - def add_ac(self, ac: Aircraft): - self.conf.append(ac) - self.header.add_ac(ac.name) - - def new_ac(self): - orig = Aircraft() - self.create_ac(orig) - - def remove_ac(self): - button = QMessageBox.question(self, "Remove AC", "Remove AC {}?".format(self.currentAC)) - if button == QMessageBox.Yes: - self.conf.remove(self.conf[self.currentAC]) - self.header.remove_current() - - def duplicate_ac(self): - orig = self.conf[self.currentAC] - self.create_ac(orig) - - def create_ac(self, orig): - ui_dialog = Ui_Dialog() - dialog = QDialog(parent=self) - ui_dialog.setupUi(dialog) - - def verify(): - ok = True - id = ui_dialog.id_spinbox.value() - name = ui_dialog.name_edit.text() - if self.conf[id] is not None or id == 0: - ui_dialog.id_spinbox.setStyleSheet("background-color: red;") - ok = False - else: - ui_dialog.id_spinbox.setStyleSheet("") - - if self.conf[name] is not None or name == "": - ui_dialog.name_edit.setStyleSheet("background-color: red;") - ok = False - else: - ui_dialog.name_edit.setStyleSheet("") - - return ok - - def accept(): - if verify(): - dialog.accept() - - def reject(): - dialog.reject() - - def duplicate(result): - if result: - new_ac = copy.deepcopy(orig) - name = ui_dialog.name_edit.text() - ac_id = ui_dialog.id_spinbox.value() - new_ac.name = name - new_ac.ac_id = ac_id - self.add_ac(new_ac) - self.header.set_current(name) - - ui_dialog.id_spinbox.setValue(self.conf.get_free_id()) - ui_dialog.buttonBox.accepted.connect(accept) - ui_dialog.buttonBox.rejected.connect(reject) - ui_dialog.id_spinbox.valueChanged.connect(verify) - ui_dialog.name_edit.textChanged.connect(verify) - dialog.finished.connect(duplicate) - dialog.open() - - def rename_ac(self): - orig = self.conf[self.currentAC] - ui_dialog = Ui_Dialog() - dialog = QDialog(parent=self) - ui_dialog.setupUi(dialog) - ui_dialog.name_edit.setText(orig.name) - ui_dialog.id_spinbox.setValue(orig.ac_id) - - def verify(): - ok = True - id = ui_dialog.id_spinbox.value() - name = ui_dialog.name_edit.text() - - acs_name = self.conf.get_all(name) - if len(acs_name) > 1 or (len(acs_name) == 1 and acs_name[0] != orig): - ui_dialog.name_edit.setStyleSheet("background-color: red;") - ok = False - else: - ui_dialog.name_edit.setStyleSheet("") - - acs_id = self.conf.get_all(id) - if len(acs_id) > 1 or (len(acs_id) == 1 and acs_id[0] != orig): - ui_dialog.id_spinbox.setStyleSheet("background-color: red;") - ok = False - else: - ui_dialog.id_spinbox.setStyleSheet("") - - return ok - - def accept(): - if verify(): - dialog.accept() - - def reject(): - dialog.reject() - - def rename(result): - if result: - orig.name = ui_dialog.name_edit.text() - orig.ac_id = ui_dialog.id_spinbox.value() - self.header.rename_ac(orig.name) - - ui_dialog.buttonBox.accepted.connect(accept) - ui_dialog.buttonBox.rejected.connect(reject) - ui_dialog.id_spinbox.valueChanged.connect(verify) - ui_dialog.name_edit.textChanged.connect(verify) - dialog.finished.connect(rename) - dialog.open() - - def change_color(self): - ac = self.conf[self.currentAC] - initial = QtGui.QColor(ac.get_color()) - color = QColorDialog.getColor(initial, self, "AC color") - if color.isValid(): - color_name = color.name() - ac.set_color(color_name) - self.header.set_color(color_name) + self.currentAC.airframe = self.conf_widget.airframe.path + self.currentAC.flight_plan = self.conf_widget.flight_plan.path + self.currentAC.radio = self.conf_widget.radio.path + self.currentAC.telemetry = self.conf_widget.telemetry.path + self.ac_edited.emit(self.currentAC) def edit_flightplan_gcs(self, path): if self.flight_plan_editor is None: diff --git a/sw/supervision/python/doc_panel.py b/sw/supervision/python/doc_panel.py new file mode 100644 index 0000000000..e91cbcecc6 --- /dev/null +++ b/sw/supervision/python/doc_panel.py @@ -0,0 +1,125 @@ +# Copyright (C) 2008-2022 The Paparazzi Team +# released under GNU GPLv2 or later. See COPYING file. + +from generated.ui_doc_viewer import Ui_DocPanel +from PyQt5.QtWidgets import * +from PyQt5.QtCore import QUrl +from PyQt5.QtGui import QDesktopServices +import utils +import os +import subprocess +import conf + +LOCAL_DOC_ROOT = os.path.join(utils.PAPARAZZI_HOME, "doc/sphinx/build/html/") +INTERNET_DOC_ROOT = "https://paparazzi-uav.readthedocs.io/en/latest/" + + +class DocPanel(QWidget, Ui_DocPanel): + + def __init__(self, parent=None): + QWidget.__init__(self, parent=parent) + self.setupUi(self) + self.current_ac = None + self.doc_source_combo.currentTextChanged.connect(self.change_doc_source) + self.open_browser_button.clicked.connect(lambda: QDesktopServices.openUrl(self.webView.url())) + self.urlLineEdit.returnPressed.connect(lambda: self.webView.setUrl(QUrl(self.urlLineEdit.text()))) + self.webView.loadFinished.connect(self.load_finished) + self.webView.loadProgress.connect(self.load_progress) + self.modules_list.currentTextChanged.connect(self.handle_select_module) + self.depends_modules_list.currentTextChanged.connect(self.handle_select_module) + self.unloaded_modules_list.currentTextChanged.connect(self.handle_select_module) + self.searchLineEdit.textChanged.connect(self.filter_modules) + self.target_combo.currentTextChanged.connect(self.target_changed) + self.backButton.clicked.connect(self.webView.back) + self.webView.urlChanged.connect(lambda u: self.urlLineEdit.setText(u.toString())) + url = self.make_url("index.html") + self.webView.load(url) + + def load_finished(self, finished): + # print(f"load finished: {finished}") + ... + # if not finished: + # self.webView.setHtml("

Doc not found!

" + # "

Documentation not found! Please build it first.

") + + def load_progress(self, progress): + # print(f"load progress: {progress}") + ... + + def set_aircraft(self, ac: conf.Aircraft): + self.current_ac = ac + targets = ac.boards.keys() + self.target_combo.clear() + self.target_combo.addItem("all") + self.target_combo.addItems(targets) + + def target_changed(self, target): + self.modules_list.clear() + self.depends_modules_list.clear() + self.unloaded_modules_list.clear() + if target != "": + modules = self.get_all_modules(self.current_ac, target) + for module_path, module_type in modules: + if module_type == "U" or module_type == "_": + self.modules_list.addItem(module_path) + elif module_type == "N": + self.unloaded_modules_list.addItem(module_path) + else: + self.depends_modules_list.addItem(module_path) + self.filter_modules(self.searchLineEdit.text()) + + def filter_modules(self, filter_txt): + def filter_list(list): + for i in range(list.count()): + if filter_txt != "": + txt = list.item(i).text() + list.item(i).setHidden(filter_txt not in txt) + else: + list.item(i).setHidden(False) + filter_list(self.modules_list) + filter_list(self.depends_modules_list) + filter_list(self.unloaded_modules_list) + + def make_url(self, doc): + if self.doc_source_combo.currentText() == "Local": + path = os.path.join(LOCAL_DOC_ROOT, doc) + return QUrl.fromLocalFile(path) + else: + path = INTERNET_DOC_ROOT + doc + return QUrl(path) + + def change_doc_source(self, source): + current_url = self.webView.url().toString() + lu = QUrl.fromLocalFile(LOCAL_DOC_ROOT).toString() + iu = QUrl(INTERNET_DOC_ROOT).toString() + url = QUrl("http://perdu.com") + if source == "Internet" and lu in current_url: + url = QUrl(current_url.replace(lu, iu)) + if source == "Local" and iu in current_url: + url = QUrl(current_url.replace(iu, lu)) + self.webView.load(url) + + def handle_select_module(self, txt): + if txt == "": + return + url = self.make_url("modules/{}.html".format(txt)) + self.webView.load(url) + + def get_all_modules(self, ac: conf.Aircraft, target: str): + args = [conf.MOD_DEP, "-ac", ac.name, "-af", ac.airframe, "-fp", ac.flight_plan] + if target != "all": + args.extend(["-t", target]) + completed = subprocess.run(args, capture_output=True) + if completed.returncode != 0: + return completed.returncode, completed.stderr + + modules_list = [] + module_lines = completed.stdout.decode().strip().split("\n") + for module_line in module_lines: + args = module_line.split(" ") + module_path = args[0] + module = utils.remove_prefix(args[0], os.path.join(utils.CONF_DIR, "modules/")) + module = utils.remove_suffix(module, ".xml") + module_type = args[1] if len(args) > 1 else "_" + modules_list.append((module, module_type)) + return modules_list diff --git a/sw/supervision/python/generated/ui_app_settings.py b/sw/supervision/python/generated/ui_app_settings.py index 3f8f2aa4c1..131fb8a27a 100644 --- a/sw/supervision/python/generated/ui_app_settings.py +++ b/sw/supervision/python/generated/ui_app_settings.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Form implementation generated from reading ui file 'ui/app_settings.ui' +# Form implementation generated from reading ui file 'ui/ui_app_settings.ui' # # Created by: PyQt5 UI code generator 5.14.1 # diff --git a/sw/supervision/python/generated/ui_build.py b/sw/supervision/python/generated/ui_build.py index b2d0095f24..e49da087c2 100644 --- a/sw/supervision/python/generated/ui_build.py +++ b/sw/supervision/python/generated/ui_build.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Form implementation generated from reading ui file 'ui/build.ui' +# Form implementation generated from reading ui file 'ui/ui_build.ui' # # Created by: PyQt5 UI code generator 5.14.1 # diff --git a/sw/supervision/python/generated/ui_conf_header.py b/sw/supervision/python/generated/ui_conf_header.py index b5331f61a4..1ad40771c2 100644 --- a/sw/supervision/python/generated/ui_conf_header.py +++ b/sw/supervision/python/generated/ui_conf_header.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Form implementation generated from reading ui file 'ui/conf_header.ui' +# Form implementation generated from reading ui file 'ui/ui_conf_header.ui' # # Created by: PyQt5 UI code generator 5.14.1 # diff --git a/sw/supervision/python/generated/ui_conf_item.py b/sw/supervision/python/generated/ui_conf_item.py index 0e8b83985e..17781175d6 100644 --- a/sw/supervision/python/generated/ui_conf_item.py +++ b/sw/supervision/python/generated/ui_conf_item.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Form implementation generated from reading ui file 'ui/conf_item.ui' +# Form implementation generated from reading ui file 'ui/ui_conf_item.ui' # # Created by: PyQt5 UI code generator 5.14.1 # diff --git a/sw/supervision/python/generated/ui_conf_settings.py b/sw/supervision/python/generated/ui_conf_settings.py index 8897841183..228450bab4 100644 --- a/sw/supervision/python/generated/ui_conf_settings.py +++ b/sw/supervision/python/generated/ui_conf_settings.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Form implementation generated from reading ui file 'ui/conf_settings.ui' +# Form implementation generated from reading ui file 'ui/ui_conf_settings.ui' # # Created by: PyQt5 UI code generator 5.14.1 # diff --git a/sw/supervision/python/generated/ui_configuration_panel.py b/sw/supervision/python/generated/ui_configuration_panel.py index 494017000e..93e4b1f47b 100644 --- a/sw/supervision/python/generated/ui_configuration_panel.py +++ b/sw/supervision/python/generated/ui_configuration_panel.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Form implementation generated from reading ui file 'ui/configuration_panel.ui' +# Form implementation generated from reading ui file 'ui/ui_configuration_panel.ui' # # Created by: PyQt5 UI code generator 5.14.1 # @@ -16,19 +16,6 @@ class Ui_ConfigurationPanel(object): ConfigurationPanel.resize(562, 480) self.verticalLayout_2 = QtWidgets.QVBoxLayout(ConfigurationPanel) self.verticalLayout_2.setObjectName("verticalLayout_2") - self.header = HeaderWidget(ConfigurationPanel) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.header.sizePolicy().hasHeightForWidth()) - self.header.setSizePolicy(sizePolicy) - self.header.setObjectName("header") - self.verticalLayout_2.addWidget(self.header) - self.line = QtWidgets.QFrame(ConfigurationPanel) - self.line.setFrameShape(QtWidgets.QFrame.HLine) - self.line.setFrameShadow(QtWidgets.QFrame.Sunken) - self.line.setObjectName("line") - self.verticalLayout_2.addWidget(self.line) self.splitter = QtWidgets.QSplitter(ConfigurationPanel) self.splitter.setOrientation(QtCore.Qt.Horizontal) self.splitter.setObjectName("splitter") @@ -74,4 +61,3 @@ class Ui_ConfigurationPanel(object): from build_widget import BuildWidget from conf_widget import ConfWidget from console_widget import ConsoleWidget -from header_widget import HeaderWidget diff --git a/sw/supervision/python/generated/ui_console.py b/sw/supervision/python/generated/ui_console.py index 2ba3261794..8dcd04f903 100644 --- a/sw/supervision/python/generated/ui_console.py +++ b/sw/supervision/python/generated/ui_console.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Form implementation generated from reading ui file 'ui/console.ui' +# Form implementation generated from reading ui file 'ui/ui_console.ui' # # Created by: PyQt5 UI code generator 5.14.1 # diff --git a/sw/supervision/python/generated/ui_doc_viewer.py b/sw/supervision/python/generated/ui_doc_viewer.py new file mode 100644 index 0000000000..28301de964 --- /dev/null +++ b/sw/supervision/python/generated/ui_doc_viewer.py @@ -0,0 +1,142 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'ui/ui_doc_viewer.ui' +# +# Created by: PyQt5 UI code generator 5.14.1 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_DocPanel(object): + def setupUi(self, DocPanel): + DocPanel.setObjectName("DocPanel") + DocPanel.resize(654, 658) + self.verticalLayout_5 = QtWidgets.QVBoxLayout(DocPanel) + self.verticalLayout_5.setObjectName("verticalLayout_5") + self.splitter_2 = QtWidgets.QSplitter(DocPanel) + self.splitter_2.setOrientation(QtCore.Qt.Horizontal) + self.splitter_2.setObjectName("splitter_2") + self.conf = QtWidgets.QWidget(self.splitter_2) + self.conf.setLayoutDirection(QtCore.Qt.LeftToRight) + self.conf.setObjectName("conf") + self.verticalLayout = QtWidgets.QVBoxLayout(self.conf) + self.verticalLayout.setContentsMargins(0, 0, 0, 0) + self.verticalLayout.setObjectName("verticalLayout") + self.verticalLayout_4 = QtWidgets.QVBoxLayout() + self.verticalLayout_4.setObjectName("verticalLayout_4") + self.horizontalLayout_2 = QtWidgets.QHBoxLayout() + self.horizontalLayout_2.setObjectName("horizontalLayout_2") + self.label_4 = QtWidgets.QLabel(self.conf) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.label_4.sizePolicy().hasHeightForWidth()) + self.label_4.setSizePolicy(sizePolicy) + self.label_4.setObjectName("label_4") + self.horizontalLayout_2.addWidget(self.label_4) + self.target_combo = QtWidgets.QComboBox(self.conf) + self.target_combo.setObjectName("target_combo") + self.horizontalLayout_2.addWidget(self.target_combo) + self.verticalLayout_4.addLayout(self.horizontalLayout_2) + self.searchLineEdit = QtWidgets.QLineEdit(self.conf) + self.searchLineEdit.setClearButtonEnabled(True) + self.searchLineEdit.setObjectName("searchLineEdit") + self.verticalLayout_4.addWidget(self.searchLineEdit) + self.verticalLayout.addLayout(self.verticalLayout_4) + self.momo = QtWidgets.QWidget(self.conf) + self.momo.setObjectName("momo") + self.verticalLayout_8 = QtWidgets.QVBoxLayout(self.momo) + self.verticalLayout_8.setObjectName("verticalLayout_8") + self.splitter = QtWidgets.QSplitter(self.momo) + self.splitter.setOrientation(QtCore.Qt.Vertical) + self.splitter.setObjectName("splitter") + self.layoutWidget = QtWidgets.QWidget(self.splitter) + self.layoutWidget.setObjectName("layoutWidget") + self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.layoutWidget) + self.verticalLayout_3.setContentsMargins(0, 0, 0, 0) + self.verticalLayout_3.setObjectName("verticalLayout_3") + self.label = QtWidgets.QLabel(self.layoutWidget) + self.label.setObjectName("label") + self.verticalLayout_3.addWidget(self.label) + self.modules_list = QtWidgets.QListWidget(self.layoutWidget) + self.modules_list.setObjectName("modules_list") + self.verticalLayout_3.addWidget(self.modules_list) + self.layoutWidget1 = QtWidgets.QWidget(self.splitter) + self.layoutWidget1.setObjectName("layoutWidget1") + self.verticalLayout_6 = QtWidgets.QVBoxLayout(self.layoutWidget1) + self.verticalLayout_6.setContentsMargins(0, 0, 0, 0) + self.verticalLayout_6.setObjectName("verticalLayout_6") + self.label_2 = QtWidgets.QLabel(self.layoutWidget1) + self.label_2.setObjectName("label_2") + self.verticalLayout_6.addWidget(self.label_2) + self.depends_modules_list = QtWidgets.QListWidget(self.layoutWidget1) + self.depends_modules_list.setObjectName("depends_modules_list") + self.verticalLayout_6.addWidget(self.depends_modules_list) + self.layoutWidget2 = QtWidgets.QWidget(self.splitter) + self.layoutWidget2.setObjectName("layoutWidget2") + self.verticalLayout_7 = QtWidgets.QVBoxLayout(self.layoutWidget2) + self.verticalLayout_7.setContentsMargins(0, 0, 0, 0) + self.verticalLayout_7.setObjectName("verticalLayout_7") + self.label_3 = QtWidgets.QLabel(self.layoutWidget2) + self.label_3.setObjectName("label_3") + self.verticalLayout_7.addWidget(self.label_3) + self.unloaded_modules_list = QtWidgets.QListWidget(self.layoutWidget2) + self.unloaded_modules_list.setObjectName("unloaded_modules_list") + self.verticalLayout_7.addWidget(self.unloaded_modules_list) + self.verticalLayout_8.addWidget(self.splitter) + self.verticalLayout.addWidget(self.momo) + self.verticalLayout.setStretch(1, 1) + self.layoutWidget3 = QtWidgets.QWidget(self.splitter_2) + self.layoutWidget3.setObjectName("layoutWidget3") + self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.layoutWidget3) + self.verticalLayout_2.setContentsMargins(0, 0, 0, 0) + self.verticalLayout_2.setObjectName("verticalLayout_2") + self.horizontalLayout = QtWidgets.QHBoxLayout() + self.horizontalLayout.setObjectName("horizontalLayout") + self.backButton = QtWidgets.QPushButton(self.layoutWidget3) + self.backButton.setText("") + icon = QtGui.QIcon.fromTheme("go-previous") + self.backButton.setIcon(icon) + self.backButton.setObjectName("backButton") + self.horizontalLayout.addWidget(self.backButton) + self.urlLineEdit = QtWidgets.QLineEdit(self.layoutWidget3) + self.urlLineEdit.setObjectName("urlLineEdit") + self.horizontalLayout.addWidget(self.urlLineEdit) + self.open_browser_button = QtWidgets.QToolButton(self.layoutWidget3) + icon = QtGui.QIcon.fromTheme("applications-internet") + self.open_browser_button.setIcon(icon) + self.open_browser_button.setObjectName("open_browser_button") + self.horizontalLayout.addWidget(self.open_browser_button) + self.doc_source_combo = QtWidgets.QComboBox(self.layoutWidget3) + self.doc_source_combo.setObjectName("doc_source_combo") + self.doc_source_combo.addItem("") + self.doc_source_combo.addItem("") + self.horizontalLayout.addWidget(self.doc_source_combo) + self.verticalLayout_2.addLayout(self.horizontalLayout) + self.webView = QtWebKitWidgets.QWebView(self.layoutWidget3) + self.webView.setUrl(QtCore.QUrl("about:blank")) + self.webView.setObjectName("webView") + self.verticalLayout_2.addWidget(self.webView) + self.verticalLayout_5.addWidget(self.splitter_2) + + self.retranslateUi(DocPanel) + QtCore.QMetaObject.connectSlotsByName(DocPanel) + + def retranslateUi(self, DocPanel): + _translate = QtCore.QCoreApplication.translate + DocPanel.setWindowTitle(_translate("DocPanel", "Form")) + self.label_4.setText(_translate("DocPanel", "Target:")) + self.searchLineEdit.setPlaceholderText(_translate("DocPanel", "search...")) + self.label.setText(_translate("DocPanel", "Modules")) + self.label_2.setText(_translate("DocPanel", "Modules Dependencies")) + self.label_3.setText(_translate("DocPanel", "Modules Unloaded")) + self.backButton.setToolTip(_translate("DocPanel", "Go back")) + self.open_browser_button.setToolTip(_translate("DocPanel", "Open in Browser")) + self.open_browser_button.setText(_translate("DocPanel", "...")) + self.doc_source_combo.setToolTip(_translate("DocPanel", "source")) + self.doc_source_combo.setItemText(0, _translate("DocPanel", "Internet")) + self.doc_source_combo.setItemText(1, _translate("DocPanel", "Local")) +from PyQt5 import QtWebKitWidgets diff --git a/sw/supervision/python/generated/ui_new_ac_dialog.py b/sw/supervision/python/generated/ui_new_ac_dialog.py index d120aef70f..190f0d1e1a 100644 --- a/sw/supervision/python/generated/ui_new_ac_dialog.py +++ b/sw/supervision/python/generated/ui_new_ac_dialog.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Form implementation generated from reading ui file 'ui/new_ac_dialog.ui' +# Form implementation generated from reading ui file 'ui/ui_new_ac_dialog.ui' # # Created by: PyQt5 UI code generator 5.14.1 # @@ -10,38 +10,38 @@ from PyQt5 import QtCore, QtGui, QtWidgets -class Ui_Dialog(object): - def setupUi(self, Dialog): - Dialog.setObjectName("Dialog") - Dialog.resize(187, 106) - self.gridLayout = QtWidgets.QGridLayout(Dialog) +class Ui_NewACDialog(object): + def setupUi(self, NewACDialog): + NewACDialog.setObjectName("NewACDialog") + NewACDialog.resize(187, 106) + self.gridLayout = QtWidgets.QGridLayout(NewACDialog) self.gridLayout.setObjectName("gridLayout") - self.label = QtWidgets.QLabel(Dialog) + self.label = QtWidgets.QLabel(NewACDialog) self.label.setObjectName("label") self.gridLayout.addWidget(self.label, 0, 0, 1, 1) - self.name_edit = QtWidgets.QLineEdit(Dialog) + self.name_edit = QtWidgets.QLineEdit(NewACDialog) self.name_edit.setPlaceholderText("") self.name_edit.setObjectName("name_edit") self.gridLayout.addWidget(self.name_edit, 0, 1, 1, 1) - self.label_2 = QtWidgets.QLabel(Dialog) + self.label_2 = QtWidgets.QLabel(NewACDialog) self.label_2.setObjectName("label_2") self.gridLayout.addWidget(self.label_2, 1, 0, 1, 1) - self.id_spinbox = QtWidgets.QSpinBox(Dialog) + self.id_spinbox = QtWidgets.QSpinBox(NewACDialog) self.id_spinbox.setMinimum(1) self.id_spinbox.setMaximum(255) self.id_spinbox.setObjectName("id_spinbox") self.gridLayout.addWidget(self.id_spinbox, 1, 1, 1, 1) - self.buttonBox = QtWidgets.QDialogButtonBox(Dialog) + self.buttonBox = QtWidgets.QDialogButtonBox(NewACDialog) self.buttonBox.setOrientation(QtCore.Qt.Horizontal) self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) self.buttonBox.setObjectName("buttonBox") self.gridLayout.addWidget(self.buttonBox, 2, 0, 1, 2) - self.retranslateUi(Dialog) - QtCore.QMetaObject.connectSlotsByName(Dialog) + self.retranslateUi(NewACDialog) + QtCore.QMetaObject.connectSlotsByName(NewACDialog) - def retranslateUi(self, Dialog): + def retranslateUi(self, NewACDialog): _translate = QtCore.QCoreApplication.translate - Dialog.setWindowTitle(_translate("Dialog", "Dialog")) - self.label.setText(_translate("Dialog", "Name")) - self.label_2.setText(_translate("Dialog", "ID")) + NewACDialog.setWindowTitle(_translate("NewACDialog", "Aircraft")) + self.label.setText(_translate("NewACDialog", "Name")) + self.label_2.setText(_translate("NewACDialog", "ID")) diff --git a/sw/supervision/python/generated/ui_operation_panel.py b/sw/supervision/python/generated/ui_operation_panel.py index f83f320a00..461963e47b 100644 --- a/sw/supervision/python/generated/ui_operation_panel.py +++ b/sw/supervision/python/generated/ui_operation_panel.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Form implementation generated from reading ui file 'ui/operation_panel.ui' +# Form implementation generated from reading ui file 'ui/ui_operation_panel.ui' # # Created by: PyQt5 UI code generator 5.14.1 # diff --git a/sw/supervision/python/generated/ui_program.py b/sw/supervision/python/generated/ui_program.py index c685c856ca..dfb3f4c1eb 100644 --- a/sw/supervision/python/generated/ui_program.py +++ b/sw/supervision/python/generated/ui_program.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Form implementation generated from reading ui file 'ui/program.ui' +# Form implementation generated from reading ui file 'ui/ui_program.ui' # # Created by: PyQt5 UI code generator 5.14.1 # diff --git a/sw/supervision/python/generated/ui_session.py b/sw/supervision/python/generated/ui_session.py index 09ab39345b..f238ef1d43 100644 --- a/sw/supervision/python/generated/ui_session.py +++ b/sw/supervision/python/generated/ui_session.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Form implementation generated from reading ui file 'ui/session.ui' +# Form implementation generated from reading ui file 'ui/ui_session.ui' # # Created by: PyQt5 UI code generator 5.14.1 # diff --git a/sw/supervision/python/generated/ui_supervision_window.py b/sw/supervision/python/generated/ui_supervision_window.py new file mode 100644 index 0000000000..1bc764e962 --- /dev/null +++ b/sw/supervision/python/generated/ui_supervision_window.py @@ -0,0 +1,74 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'ui/ui_supervision_window.ui' +# +# Created by: PyQt5 UI code generator 5.14.1 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_SupervisionWindow(object): + def setupUi(self, SupervisionWindow): + SupervisionWindow.setObjectName("SupervisionWindow") + SupervisionWindow.resize(800, 600) + self.centralwidget = QtWidgets.QWidget(SupervisionWindow) + self.centralwidget.setObjectName("centralwidget") + self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget) + self.verticalLayout.setObjectName("verticalLayout") + self.header = HeaderWidget(self.centralwidget) + self.header.setObjectName("header") + self.verticalLayout.addWidget(self.header) + self.tabwidget = QtWidgets.QTabWidget(self.centralwidget) + self.tabwidget.setObjectName("tabwidget") + self.configuration_panel = ConfigurationPanel() + self.configuration_panel.setObjectName("configuration_panel") + self.tabwidget.addTab(self.configuration_panel, "") + self.operation_panel = OperationPanel() + self.operation_panel.setObjectName("operation_panel") + self.tabwidget.addTab(self.operation_panel, "") + self.doc_panel = DocPanel() + self.doc_panel.setObjectName("doc_panel") + self.tabwidget.addTab(self.doc_panel, "") + self.verticalLayout.addWidget(self.tabwidget) + SupervisionWindow.setCentralWidget(self.centralwidget) + self.menubar = QtWidgets.QMenuBar(SupervisionWindow) + self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 22)) + self.menubar.setObjectName("menubar") + self.menuFile = QtWidgets.QMenu(self.menubar) + self.menuFile.setObjectName("menuFile") + self.menuHelp = QtWidgets.QMenu(self.menubar) + self.menuHelp.setObjectName("menuHelp") + SupervisionWindow.setMenuBar(self.menubar) + self.statusbar = QtWidgets.QStatusBar(SupervisionWindow) + self.statusbar.setObjectName("statusbar") + SupervisionWindow.setStatusBar(self.statusbar) + self.settings_action = QtWidgets.QAction(SupervisionWindow) + self.settings_action.setObjectName("settings_action") + self.about_action = QtWidgets.QAction(SupervisionWindow) + self.about_action.setObjectName("about_action") + self.menuFile.addAction(self.settings_action) + self.menuHelp.addAction(self.about_action) + self.menubar.addAction(self.menuFile.menuAction()) + self.menubar.addAction(self.menuHelp.menuAction()) + + self.retranslateUi(SupervisionWindow) + self.tabwidget.setCurrentIndex(0) + QtCore.QMetaObject.connectSlotsByName(SupervisionWindow) + + def retranslateUi(self, SupervisionWindow): + _translate = QtCore.QCoreApplication.translate + SupervisionWindow.setWindowTitle(_translate("SupervisionWindow", "Paparazzi Center")) + self.tabwidget.setTabText(self.tabwidget.indexOf(self.configuration_panel), _translate("SupervisionWindow", "Configuration")) + self.tabwidget.setTabText(self.tabwidget.indexOf(self.operation_panel), _translate("SupervisionWindow", "Operation")) + self.tabwidget.setTabText(self.tabwidget.indexOf(self.doc_panel), _translate("SupervisionWindow", "Documentation")) + self.menuFile.setTitle(_translate("SupervisionWindow", "File")) + self.menuHelp.setTitle(_translate("SupervisionWindow", "Help")) + self.settings_action.setText(_translate("SupervisionWindow", "Edit Settings")) + self.about_action.setText(_translate("SupervisionWindow", "About")) +from configuration_panel import ConfigurationPanel +from doc_panel import DocPanel +from header_widget import HeaderWidget +from operation_panel import OperationPanel diff --git a/sw/supervision/python/generated/ui_tools_list.py b/sw/supervision/python/generated/ui_tools_list.py index 1ac6019775..2ffe6c92f0 100644 --- a/sw/supervision/python/generated/ui_tools_list.py +++ b/sw/supervision/python/generated/ui_tools_list.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Form implementation generated from reading ui file 'ui/tools_list.ui' +# Form implementation generated from reading ui file 'ui/ui_tools_list.ui' # # Created by: PyQt5 UI code generator 5.14.1 # diff --git a/sw/supervision/python/header_widget.py b/sw/supervision/python/header_widget.py index 93b99c80a6..ffb43c9685 100644 --- a/sw/supervision/python/header_widget.py +++ b/sw/supervision/python/header_widget.py @@ -3,53 +3,95 @@ from PyQt5.QtWidgets import * from PyQt5 import QtCore, QtGui, QtWidgets from generated.ui_conf_header import Ui_ConfHeader -import conf +from conf import Aircraft, Conf +import utils +import os +import sys +lib_path = os.path.normpath(os.path.join(utils.PAPARAZZI_SRC, 'sw', 'lib', 'python')) +sys.path.append(lib_path) +import paparazzi +from typing import List class HeaderWidget(QWidget, Ui_ConfHeader): set_changed = QtCore.pyqtSignal(str) ac_changed = QtCore.pyqtSignal(str) - id_changed = QtCore.pyqtSignal(int) + ac_edited = QtCore.pyqtSignal(Aircraft) + ac_rename = QtCore.pyqtSignal(Aircraft) + ac_delete = QtCore.pyqtSignal(Aircraft) + ac_duplicate = QtCore.pyqtSignal(Aircraft) + ac_save = QtCore.pyqtSignal(Aircraft) + ac_new = QtCore.pyqtSignal() def __init__(self, parent=None): QWidget.__init__(self, parent=parent) self.setupUi(self) + self.currentAc: Aircraft = None self.set_combo.currentTextChanged.connect(self.set_changed) self.ac_combo.currentTextChanged.connect(self.ac_changed) - self.id_spinBox.valueChanged.connect(self.id_changed) - self.menu_button.addAction(self.rename_action) - self.menu_button.addAction(self.new_ac_action) - self.menu_button.addAction(self.duplicate_action) - self.menu_button.addAction(self.remove_ac_action) + self.id_spinBox.valueChanged.connect(self.handle_id_changed) - def set_sets(self, sets, conf_init: str = None): + self.refresh_button.clicked.connect(lambda: self.ac_edited.emit(self.currentAc)) + self.color_button.clicked.connect(self.change_color) + self.save_button.clicked.connect(lambda: self.ac_save.emit(self.currentAc)) + + self.menu_button.addAction(self.rename_action) + self.rename_action.triggered.connect(lambda: self.ac_rename.emit(self.currentAc)) + self.menu_button.addAction(self.remove_ac_action) + self.remove_ac_action.triggered.connect(lambda: self.ac_delete.emit(self.currentAc)) + self.menu_button.addAction(self.duplicate_action) + self.duplicate_action.triggered.connect(lambda: self.ac_duplicate.emit(self.currentAc)) + self.menu_button.addAction(self.new_ac_action) + self.new_ac_action.triggered.connect(self.ac_new) + + def handle_id_changed(self, new_id): + self.currentAc.ac_id = new_id + self.ac_edited.emit(self.currentAc) + + def update_sets(self): + sets = paparazzi.get_list_of_conf_files() + conf_init = Conf.get_current_conf() self.set_combo.addItems(sets) if conf_init in sets: self.set_combo.setCurrentText(conf_init) - def set_acs(self, acs): + def set_acs(self, acs: List[str]): self.ac_combo.clear() self.ac_combo.addItems(acs) - def set_ac(self, ac: conf.Aircraft): + def set_ac(self, ac: Aircraft): + self.currentAc = ac + self.id_spinBox.blockSignals(True) self.id_spinBox.setValue(ac.ac_id) - self.set_color(ac.get_color()) + self.id_spinBox.blockSignals(False) + self.color_button.setStyleSheet("background-color: {};".format(ac.get_color())) + self.ac_combo.setCurrentText(ac.name) - def remove_current(self): - i = self.ac_combo.currentIndex() - self.ac_combo.removeItem(i) + def add_ac(self, ac: Aircraft): + self.ac_combo.addItem(ac.name) - def set_current(self, ac_name): - self.ac_combo.setCurrentText(ac_name) - - def add_ac(self, ac_name): - self.ac_combo.addItem(ac_name) - self.set_current(ac_name) + def remove_ac(self, ac): + for i in range(self.ac_combo.count()): + if self.ac_combo.itemText(i) == ac.name: + self.ac_combo.removeItem(i) + break def rename_ac(self, new_name): i = self.ac_combo.currentIndex() self.ac_combo.setItemText(i, new_name) - def set_color(self, color: str): - self.color_button.setStyleSheet("background-color: {};".format(color)) + def change_color(self): + initial = QtGui.QColor(self.currentAc.get_color()) + color = QColorDialog.getColor(initial, self, "AC color") + if color.isValid(): + color_name = color.name() + self.currentAc.set_color(color_name) + self.color_button.setStyleSheet("background-color: {};".format(color_name)) + self.ac_edited.emit(self.currentAc) + + def disable_sets(self): + self.set_combo.setDisabled(True) + + def enable_sets(self): + self.set_combo.setDisabled(False) diff --git a/sw/supervision/python/paparazzicenter.py b/sw/supervision/python/paparazzicenter.py index 22bd241f5e..89166bab8e 100755 --- a/sw/supervision/python/paparazzicenter.py +++ b/sw/supervision/python/paparazzicenter.py @@ -2,92 +2,244 @@ # Copyright (C) 2008-2022 The Paparazzi Team # released under GNU GPLv2 or later. See COPYING file. import os -import conf +import sys +import signal +import copy from PyQt5.QtWidgets import * from PyQt5 import QtCore, QtGui -from configuration_panel import ConfigurationPanel -from operation_panel import OperationPanel import utils -from typing import Dict from lxml import etree as ET +from conf import Conf, Aircraft from app_settings import AppSettings +from generated.ui_supervision_window import Ui_SupervisionWindow +from generated.ui_new_ac_dialog import Ui_NewACDialog -class PprzCenter(QMainWindow): +class PprzCenter(QMainWindow, Ui_SupervisionWindow): def __init__(self, parent=None): QMainWindow.__init__(self, parent=parent) - self.setWindowTitle("Paparazzi Center") + self.setupUi(self) + self.conf: Conf = None + self.currentAc: Aircraft = None icon = QtGui.QIcon(os.path.join(utils.PAPARAZZI_HOME, "data", "pictures", "penguin_logo.svg")) self.setWindowIcon(icon) - self.addMenu() - self.tabwidget = QTabWidget(parent=self) - self.setCentralWidget(self.tabwidget) - self.configuration_panel = ConfigurationPanel(self.tabwidget) - self.operation_panel = OperationPanel(self.tabwidget) - self.tabwidget.addTab(self.configuration_panel, "Configuration") - self.tabwidget.addTab(self.operation_panel, "Operation") + + self.settings_action.triggered.connect(self.edit_settings) + self.about_action.triggered.connect(lambda: QMessageBox.about(self, "About Paparazzi", utils.ABOUT_TEXT)) + self.status_msg = QLabel() self.statusBar().addWidget(self.status_msg) self.fill_status_bar() - self.statusBar().show() - self.configuration_panel.msg_error.connect(self.handle_error) - self.configuration_panel.clear_error.connect(self.clear_error) - self.operation_panel.session.program_spawned.connect(self.configuration_panel.disable_sets) - self.operation_panel.session.programs_all_stopped.connect(self.configuration_panel.enable_sets) - self.configuration_panel.ac_changed.connect(self.operation_panel.session.set_aircraft) + + self.header.set_changed.connect(self.handle_set_changed) + self.header.ac_changed.connect(self.handle_ac_changed) + self.header.ac_edited.connect(self.handle_ac_edited) + self.header.ac_rename.connect(self.handle_rename_ac) + self.header.ac_delete.connect(self.handle_remove_ac) + self.header.ac_duplicate.connect(self.handle_new_ac) + self.header.ac_save.connect(lambda _: self.conf.save()) + self.header.ac_new.connect(self.handle_new_ac) + + self.operation_panel.session.program_spawned.connect(self.header.disable_sets) + self.operation_panel.session.programs_all_stopped.connect(self.header.enable_sets) + self.configuration_panel.splitter.splitterMoved.connect(self.update_left_pane_width) settings = utils.get_settings() window_size = settings.value("ui/window_size", QtCore.QSize(1000, 600), QtCore.QSize) self.resize(window_size) self.configuration_panel.init() self.operation_panel.session.init() + self.header.update_sets() - def addMenu(self): - menubar = QMenuBar() - file_menu = QMenu("&File", menubar) - help_menu = QMenu("&Help", menubar) - menubar.addMenu(file_menu) - menubar.addMenu(help_menu) - settings_action = QAction("&Edit Settings", file_menu) - file_menu.addAction(settings_action) - about_action = QAction("&About", help_menu) - help_menu.addAction(about_action) + def handle_set_changed(self, conf_file): + self.conf = Conf(conf_file) + Conf.set_current_conf(conf_file) + self.configuration_panel.build_widget.set_conf(self.conf) + acs = [ac.name for ac in self.conf.aircrafts] + self.header.set_acs(acs) - def edit_settings(): - settings_dialog = AppSettings(self) - settings_dialog.show() - settings_action.triggered.connect(edit_settings) - about_action.triggered.connect(lambda: QMessageBox.about(self, "About Paparazzi", utils.ABOUT_TEXT)) + # set last AC as current if it exits in the current conf + settings = utils.get_settings() + last_ac: QtCore.QVariant = settings.value("ui/last_AC", None, str) + if last_ac in acs: + self.handle_ac_changed(last_ac) + last_target: QtCore.QVariant = settings.value("ui/last_target", None, str) + if last_target: + self.configuration_panel.build_widget.target_combo.setCurrentText(last_target) - self.setMenuBar(menubar) + def handle_ac_edited(self, ac: Aircraft): + # check AC ID + if len(self.conf.get_all(ac.ac_id)) > 1: + self.header.id_spinBox.setStyleSheet("background-color: red;") + else: + self.header.id_spinBox.setStyleSheet("background-color: white;") + # update ac, then update all widgets + status, stderr = ac.update() + if status != 0: + self.handle_error(stderr.decode().strip()) + else: + self.clear_error() + pass + self.change_ac(ac) - def closeEvent(self, e: QtGui.QCloseEvent) -> None: + def handle_ac_changed(self, ac_name): + ac = self.conf[ac_name] + if ac is not None: + # self.handle_ac_edited(ac) # update AC ? + self.change_ac(ac) + + def handle_remove_ac(self, ac: Aircraft): + button = QMessageBox.question(self, "Remove AC", "Remove AC {}?".format(ac.name)) + if button == QMessageBox.Yes: + self.conf.remove(ac) + self.header.remove_ac(ac) + + def handle_new_ac(self, orig: Aircraft = None): + if orig is None: + orig = Aircraft() + + ui_dialog = Ui_NewACDialog() + dialog = QDialog(parent=self) + ui_dialog.setupUi(dialog) + + def verify(): + ok = True + id = ui_dialog.id_spinbox.value() + name = ui_dialog.name_edit.text() + if self.conf[id] is not None or id == 0: + ui_dialog.id_spinbox.setStyleSheet("background-color: red;") + ok = False + else: + ui_dialog.id_spinbox.setStyleSheet("") + + if self.conf[name] is not None or name == "": + ui_dialog.name_edit.setStyleSheet("background-color: red;") + ok = False + else: + ui_dialog.name_edit.setStyleSheet("") + + return ok + + def accept(): + if verify(): + dialog.accept() + + def reject(): + dialog.reject() + + def duplicate(result): + if result: + new_ac = copy.deepcopy(orig) + name = ui_dialog.name_edit.text() + ac_id = ui_dialog.id_spinbox.value() + new_ac.name = name + new_ac.ac_id = ac_id + self.conf.append(new_ac) + self.header.add_ac(new_ac) + self.change_ac(new_ac) + + ui_dialog.id_spinbox.setValue(self.conf.get_free_id()) + ui_dialog.buttonBox.accepted.connect(accept) + ui_dialog.buttonBox.rejected.connect(reject) + ui_dialog.id_spinbox.valueChanged.connect(verify) + ui_dialog.name_edit.textChanged.connect(verify) + dialog.finished.connect(duplicate) + dialog.open() + + def change_ac(self, ac): + self.currentAc = ac + self.header.set_ac(ac) + self.configuration_panel.set_ac(ac) + self.operation_panel.session.set_aircraft(ac) + self.doc_panel.set_aircraft(ac) + + def handle_rename_ac(self, orig: Aircraft): + ui_dialog = Ui_NewACDialog() + dialog = QDialog(parent=self) + ui_dialog.setupUi(dialog) + ui_dialog.name_edit.setText(orig.name) + ui_dialog.id_spinbox.setValue(orig.ac_id) + + def verify(): + ok = True + id = ui_dialog.id_spinbox.value() + name = ui_dialog.name_edit.text() + + acs_name = self.conf.get_all(name) + if len(acs_name) > 1 or (len(acs_name) == 1 and acs_name[0] != orig): + ui_dialog.name_edit.setStyleSheet("background-color: red;") + ok = False + else: + ui_dialog.name_edit.setStyleSheet("") + + acs_id = self.conf.get_all(id) + if len(acs_id) > 1 or (len(acs_id) == 1 and acs_id[0] != orig): + ui_dialog.id_spinbox.setStyleSheet("background-color: red;") + ok = False + else: + ui_dialog.id_spinbox.setStyleSheet("") + + return ok + + def accept(): + if verify(): + dialog.accept() + + def reject(): + dialog.reject() + + def rename(result): + if result: + orig.name = ui_dialog.name_edit.text() + orig.ac_id = ui_dialog.id_spinbox.value() + self.header.rename_ac(orig.name) + + ui_dialog.buttonBox.accepted.connect(accept) + ui_dialog.buttonBox.rejected.connect(reject) + ui_dialog.id_spinbox.valueChanged.connect(verify) + ui_dialog.name_edit.textChanged.connect(verify) + dialog.finished.connect(rename) + dialog.open() + + def edit_settings(self): + settings_dialog = AppSettings(self) + settings_dialog.show() + + def quit(self, interactive=True): + quit_accepted = True if self.operation_panel.session.any_program_running(): + quit_accepted = False self.operation_panel.session.programs_all_stopped.connect(self.close) self.operation_panel.session.stop_all() - e.ignore() self.operation_panel.session.programs_all_stopped.connect(self.close) else: + restore_conf = True if utils.get_settings().value("always_keep_changes", False, bool): - self.configuration_panel.conf.save() + restore_conf = False else: - conf_tree_orig = self.configuration_panel.conf.tree_orig - conf_tree = self.configuration_panel.conf.to_xml_tree() - if ET.tostring(conf_tree) != ET.tostring(conf_tree_orig): + conf_tree_orig = self.conf.tree_orig + conf_tree = self.conf.to_xml_tree() + if ET.tostring(conf_tree) != ET.tostring(conf_tree_orig) and interactive: buttons = QMessageBox.question(self, "Save configuration?", "The configuration has changed, do you want to save it?") if buttons == QMessageBox.Yes: - self.configuration_panel.conf.save() - else: - self.configuration_panel.conf.restore_conf() - self.configuration_panel.conf.save() + restore_conf = False + if restore_conf: + self.conf.restore_conf() + self.conf.save() self.save_gconf() + return quit_accepted + + def closeEvent(self, e: QtGui.QCloseEvent) -> None: + if self.quit(): e.accept() + else: + e.ignore() def save_gconf(self): settings = utils.get_settings() settings.setValue("ui/window_size", self.size()) - settings.setValue("ui/last_AC", self.configuration_panel.get_current_ac()) + settings.setValue("ui/last_AC", self.currentAc.name) settings.setValue("ui/last_session", self.operation_panel.session.get_current_session()) def update_left_pane_width(self, pos, index): @@ -119,10 +271,24 @@ class PprzCenter(QMainWindow): if __name__ == "__main__": - import sys + app = QApplication(sys.argv) + + timer = QtCore.QTimer() + timer.start(100) + timer.timeout.connect(lambda: None) # Let the interpreter run each 100 ms. + main_window = PprzCenter() main_window.show() # qApp.aboutToQuit.connect(main_window.quit) - sys.exit(app.exec_()) + def sigint_handler(*args): + """Handler for the SIGINT signal.""" + print("catched SIGINT") + sys.stderr.write('\r') + if main_window.quit(False): + QApplication.quit() + + signal.signal(signal.SIGINT, sigint_handler) + + sys.exit(app.exec_()) diff --git a/sw/supervision/python/session_widget.py b/sw/supervision/python/session_widget.py index 28e3033619..a955e39bfe 100644 --- a/sw/supervision/python/session_widget.py +++ b/sw/supervision/python/session_widget.py @@ -215,6 +215,8 @@ class SessionWidget(QWidget, Ui_Session): return programs = self.get_programs() session = Session(session_name, programs) + self.sessions_combo.addItem(session_name) + self.sessions_combo.setCurrentText(session_name) self.sessions.append(session) self.save_sessions() diff --git a/sw/supervision/python/ui/app_settings.ui b/sw/supervision/python/ui/ui_app_settings.ui similarity index 100% rename from sw/supervision/python/ui/app_settings.ui rename to sw/supervision/python/ui/ui_app_settings.ui diff --git a/sw/supervision/python/ui/build.ui b/sw/supervision/python/ui/ui_build.ui similarity index 100% rename from sw/supervision/python/ui/build.ui rename to sw/supervision/python/ui/ui_build.ui diff --git a/sw/supervision/python/ui/conf_header.ui b/sw/supervision/python/ui/ui_conf_header.ui similarity index 100% rename from sw/supervision/python/ui/conf_header.ui rename to sw/supervision/python/ui/ui_conf_header.ui diff --git a/sw/supervision/python/ui/conf_item.ui b/sw/supervision/python/ui/ui_conf_item.ui similarity index 100% rename from sw/supervision/python/ui/conf_item.ui rename to sw/supervision/python/ui/ui_conf_item.ui diff --git a/sw/supervision/python/ui/conf_settings.ui b/sw/supervision/python/ui/ui_conf_settings.ui similarity index 100% rename from sw/supervision/python/ui/conf_settings.ui rename to sw/supervision/python/ui/ui_conf_settings.ui diff --git a/sw/supervision/python/ui/configuration_panel.ui b/sw/supervision/python/ui/ui_configuration_panel.ui similarity index 80% rename from sw/supervision/python/ui/configuration_panel.ui rename to sw/supervision/python/ui/ui_configuration_panel.ui index cb057069b7..d77cbeec5a 100644 --- a/sw/supervision/python/ui/configuration_panel.ui +++ b/sw/supervision/python/ui/ui_configuration_panel.ui @@ -14,23 +14,6 @@ Form - - - - - 0 - 0 - - - - - - - - Qt::Horizontal - - - @@ -96,12 +79,6 @@
build_widget.h
1 - - HeaderWidget - QWidget -
header_widget.h
- 1 -
ConsoleWidget QWidget diff --git a/sw/supervision/python/ui/console.ui b/sw/supervision/python/ui/ui_console.ui similarity index 100% rename from sw/supervision/python/ui/console.ui rename to sw/supervision/python/ui/ui_console.ui diff --git a/sw/supervision/python/ui/ui_doc_viewer.ui b/sw/supervision/python/ui/ui_doc_viewer.ui new file mode 100644 index 0000000000..c69756a77c --- /dev/null +++ b/sw/supervision/python/ui/ui_doc_viewer.ui @@ -0,0 +1,196 @@ + + + DocPanel + + + + 0 + 0 + 654 + 658 + + + + Form + + + + + + Qt::Horizontal + + + + Qt::LeftToRight + + + + + + + + + + + 0 + 0 + + + + Target: + + + + + + + + + + + + search... + + + true + + + + + + + + + + + + Qt::Vertical + + + + + + + Modules + + + + + + + + + + + + + + Modules Dependencies + + + + + + + + + + + + + + Modules Unloaded + + + + + + + + + + + + + + + + + + + + + + + Go back + + + + + + + .. + + + + + + + + + + Open in Browser + + + ... + + + + .. + + + + + + + source + + + + Internet + + + + + Local + + + + + + + + + + + about:blank + + + + + + + + + + + + + QWebView + QWidget +
QtWebKitWidgets/QWebView
+
+
+ + +
diff --git a/sw/supervision/python/ui/new_ac_dialog.ui b/sw/supervision/python/ui/ui_new_ac_dialog.ui similarity index 93% rename from sw/supervision/python/ui/new_ac_dialog.ui rename to sw/supervision/python/ui/ui_new_ac_dialog.ui index 013d924f74..05cab93095 100644 --- a/sw/supervision/python/ui/new_ac_dialog.ui +++ b/sw/supervision/python/ui/ui_new_ac_dialog.ui @@ -1,7 +1,7 @@ - Dialog - + NewACDialog + 0 @@ -11,7 +11,7 @@ - Dialog + Aircraft diff --git a/sw/supervision/python/ui/operation_panel.ui b/sw/supervision/python/ui/ui_operation_panel.ui similarity index 100% rename from sw/supervision/python/ui/operation_panel.ui rename to sw/supervision/python/ui/ui_operation_panel.ui diff --git a/sw/supervision/python/ui/program.ui b/sw/supervision/python/ui/ui_program.ui similarity index 100% rename from sw/supervision/python/ui/program.ui rename to sw/supervision/python/ui/ui_program.ui diff --git a/sw/supervision/python/ui/session.ui b/sw/supervision/python/ui/ui_session.ui similarity index 100% rename from sw/supervision/python/ui/session.ui rename to sw/supervision/python/ui/ui_session.ui diff --git a/sw/supervision/python/ui/ui_supervision_window.ui b/sw/supervision/python/ui/ui_supervision_window.ui new file mode 100644 index 0000000000..394623e539 --- /dev/null +++ b/sw/supervision/python/ui/ui_supervision_window.ui @@ -0,0 +1,109 @@ + + + SupervisionWindow + + + + 0 + 0 + 800 + 600 + + + + Paparazzi Center + + + + + + + + + + 0 + + + + Configuration + + + + + Operation + + + + + Documentation + + + + + + + + + + 0 + 0 + 800 + 22 + + + + + File + + + + + + Help + + + + + + + + + + Edit Settings + + + + + About + + + + + + HeaderWidget + QWidget +
header_widget.h
+ 1 +
+ + ConfigurationPanel + QWidget +
configuration_panel.h
+ 1 +
+ + OperationPanel + QWidget +
operation_panel.h
+ 1 +
+ + DocPanel + QWidget +
doc_panel.h
+ 1 +
+
+ + +
diff --git a/sw/supervision/python/ui/tools_list.ui b/sw/supervision/python/ui/ui_tools_list.ui similarity index 100% rename from sw/supervision/python/ui/tools_list.ui rename to sw/supervision/python/ui/ui_tools_list.ui diff --git a/sw/supervision/python/utils.py b/sw/supervision/python/utils.py index aed6e26732..dc9088b8b0 100644 --- a/sw/supervision/python/utils.py +++ b/sw/supervision/python/utils.py @@ -18,12 +18,20 @@ PAPARAZZI_HOME = os.getenv("PAPARAZZI_HOME", PAPARAZZI_SRC) CONF_DIR = os.path.join(PAPARAZZI_HOME, "conf/") -def removeprefix(string: str, prefix: str, /) -> str: +def remove_prefix(string: str, prefix: str, /) -> str: if string.startswith(prefix): return string[len(prefix):] else: return string[:] + +def remove_suffix(s: str, suffix: str, /) -> str: + if s.endswith(suffix): + return s[:-len(suffix)] + else: + return s + + # TODO: make it work with shell program such as vim. def edit_file(file_path, prefix=CONF_DIR): path = prefix + file_path diff --git a/sw/tools/generators/dump_modules_list.ml b/sw/tools/generators/dump_modules_list.ml index aa2d33b19c..0cd33968e7 100644 --- a/sw/tools/generators/dump_modules_list.ml +++ b/sw/tools/generators/dump_modules_list.ml @@ -24,16 +24,24 @@ let (//) = Filename.concat let conf_dir = Env.paparazzi_home // "conf" let conf_xml_file = conf_dir // "conf.xml" +let letter_of_load_tyoe = function + | Aircraft.UserLoad -> "U" + | Aircraft.Depend -> "D" + | Aircraft.AutoLoad -> "A" + | Aircraft.Unloaded -> "N" + let () = let ac_name = ref None and af_xml = ref None and fp_xml = ref None + and target = ref None and output = ref None in let options = [ "-ac", Arg.String (fun x -> ac_name := Some x), "Aircraft name (mandatory)"; "-af", Arg.String (fun x -> af_xml := Some x), "Airframe XML file (optinal)"; "-fp", Arg.String (fun x -> fp_xml := Some x), "Flight_plan XML file (optional)"; + "-t", Arg.String (fun x -> target := Some x), "Target name (optional)"; "-o", Arg.String (fun x -> output := Some x), "Output file name (stdout if not specified)" ] in Arg.parse options (fun _ -> ()) "Usage:"; @@ -56,7 +64,15 @@ let () = in let ac = Aircraft.parse_aircraft ~parse_af:true ~parse_ap:true ~parse_fp:true "" aircraft_xml in - let modules_filenames = List.map (fun m -> m.Module.xml_filename) ac.Aircraft.all_modules in + let modules_filenames = match !target with + | None -> List.map (fun m -> m.Module.xml_filename) ac.Aircraft.all_modules + | Some t -> + try + let selected = Hashtbl.find ac.Aircraft.config_by_target t in + List.map (fun (load_type, m) -> Printf.sprintf "%s %s" m.Module.xml_filename (letter_of_load_tyoe load_type)) + selected.Aircraft.modules + with Not_found -> failwith "Dump modules: unknown target" + in let modules_filenames = String.concat "\n" modules_filenames in match !output with