[supervision] Adds documentation tab. (#2972)

* [supervision] Adds documentation tab.

* [generator] add a target option to dump modules by target with load type

* [supervision] doc: handle modules type.

* [supervision] modules doc: add filter.

* [supervision] Use internet doc by default.

* [supervision] Move the header out of the configuration tab.

* [supervision] Add target label.

* [supervision] Gracefully handle Ctrl-C.

* [supervision] Add new session to combobox.

* Update sw/supervision/python/doc_panel.py

---------

Co-authored-by: Gautier Hattenberger <gautier.hattenberger@enac.fr>
This commit is contained in:
Fabien-B
2023-02-07 15:51:55 +01:00
committed by GitHub
parent 902f9adf65
commit 8833bb4fc5
38 changed files with 1024 additions and 371 deletions
+21 -15
View File
@@ -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
+4 -6
View File
@@ -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):
@@ -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)
+15 -209
View File
@@ -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 <strong>{}</strong>?".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:
+125
View File
@@ -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("<h1>Doc not found!</h1>"
# "<p>Documentation not found! Please build it first.</p>")
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
@@ -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
#
+1 -1
View File
@@ -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
#
@@ -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
#
@@ -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
#
@@ -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
#
@@ -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
@@ -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
#
@@ -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
@@ -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"))
@@ -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
#
@@ -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
#
@@ -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
#
@@ -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
@@ -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
#
+64 -22
View File
@@ -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)
+214 -48
View File
@@ -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 <strong>{}</strong>?".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_())
+2
View File
@@ -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()
@@ -14,23 +14,6 @@
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="HeaderWidget" name="header" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QSplitter" name="splitter">
<property name="orientation">
@@ -96,12 +79,6 @@
<header>build_widget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>HeaderWidget</class>
<extends>QWidget</extends>
<header>header_widget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ConsoleWidget</class>
<extends>QWidget</extends>
+196
View File
@@ -0,0 +1,196 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>DocPanel</class>
<widget class="QWidget" name="DocPanel">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>654</width>
<height>658</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="QSplitter" name="splitter_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QWidget" name="conf" native="true">
<property name="layoutDirection">
<enum>Qt::LeftToRight</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,1">
<item>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="label_4">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Target:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="target_combo"/>
</item>
</layout>
</item>
<item>
<widget class="QLineEdit" name="searchLineEdit">
<property name="placeholderText">
<string>search...</string>
</property>
<property name="clearButtonEnabled">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QWidget" name="momo" native="true">
<layout class="QVBoxLayout" name="verticalLayout_8">
<item>
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<widget class="QWidget" name="layoutWidget">
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Modules</string>
</property>
</widget>
</item>
<item>
<widget class="QListWidget" name="modules_list"/>
</item>
</layout>
</widget>
<widget class="QWidget" name="layoutWidget">
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Modules Dependencies</string>
</property>
</widget>
</item>
<item>
<widget class="QListWidget" name="depends_modules_list"/>
</item>
</layout>
</widget>
<widget class="QWidget" name="layoutWidget">
<layout class="QVBoxLayout" name="verticalLayout_7">
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>Modules Unloaded</string>
</property>
</widget>
</item>
<item>
<widget class="QListWidget" name="unloaded_modules_list"/>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="layoutWidget">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="backButton">
<property name="toolTip">
<string>Go back</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset theme="go-previous">
<normaloff>.</normaloff>.</iconset>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="urlLineEdit"/>
</item>
<item>
<widget class="QToolButton" name="open_browser_button">
<property name="toolTip">
<string>Open in Browser</string>
</property>
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset theme="applications-internet">
<normaloff>.</normaloff>.</iconset>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="doc_source_combo">
<property name="toolTip">
<string>source</string>
</property>
<item>
<property name="text">
<string>Internet</string>
</property>
</item>
<item>
<property name="text">
<string>Local</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QWebView" name="webView">
<property name="url">
<url>
<string>about:blank</string>
</url>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>QWebView</class>
<extends>QWidget</extends>
<header location="global">QtWebKitWidgets/QWebView</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Dialog</class>
<widget class="QDialog" name="Dialog">
<class>NewACDialog</class>
<widget class="QDialog" name="NewACDialog">
<property name="geometry">
<rect>
<x>0</x>
@@ -11,7 +11,7 @@
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
<string>Aircraft</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
@@ -0,0 +1,109 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SupervisionWindow</class>
<widget class="QMainWindow" name="SupervisionWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>Paparazzi Center</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="HeaderWidget" name="header" native="true"/>
</item>
<item>
<widget class="QTabWidget" name="tabwidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="ConfigurationPanel" name="configuration_panel">
<attribute name="title">
<string>Configuration</string>
</attribute>
</widget>
<widget class="OperationPanel" name="operation_panel">
<attribute name="title">
<string>Operation</string>
</attribute>
</widget>
<widget class="DocPanel" name="doc_panel">
<attribute name="title">
<string>Documentation</string>
</attribute>
</widget>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>22</height>
</rect>
</property>
<widget class="QMenu" name="menuFile">
<property name="title">
<string>File</string>
</property>
<addaction name="settings_action"/>
</widget>
<widget class="QMenu" name="menuHelp">
<property name="title">
<string>Help</string>
</property>
<addaction name="about_action"/>
</widget>
<addaction name="menuFile"/>
<addaction name="menuHelp"/>
</widget>
<widget class="QStatusBar" name="statusbar"/>
<action name="settings_action">
<property name="text">
<string>Edit Settings</string>
</property>
</action>
<action name="about_action">
<property name="text">
<string>About</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>
<class>HeaderWidget</class>
<extends>QWidget</extends>
<header>header_widget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ConfigurationPanel</class>
<extends>QWidget</extends>
<header>configuration_panel.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>OperationPanel</class>
<extends>QWidget</extends>
<header>operation_panel.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>DocPanel</class>
<extends>QWidget</extends>
<header>doc_panel.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
+9 -1
View File
@@ -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