# Paparazzi center utilities # # Copyright (C) 2016 ENAC, Florian BITARD (intern student) # # This file is part of paparazzi. # # paparazzi is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2, or (at your option) # any later version. # # paparazzi is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with paparazzi; see the file COPYING. If not, write to # the Free Software Foundation, 59 Temple Place - Suite 330, # Boston, MA 02111-1307, USA. ############################################################################### # [Imports] import ui.main_window as ui import dialogs as dial import lib.console as cs import lib.database as db import lib.environment as env import lib.gui as gui import parser import processes as proc import PyQt5.QtCore as Core import PyQt5.QtWidgets as Widgets import functools import logging import os import sys import re import shutil ############################################################################### # [Constants] LOGGER = logging.getLogger("[HMI]") CONF_PATH = env.PAPARAZZI_CONF SET_SYMBOLIC_LINK = CONF_PATH + "/conf.xml" CONF_XML_COPY = CONF_PATH + "/conf_copy.xml" OK_QPIXMAP = "icons/dialog-apply.svg" ERROR_QPIXMAP = "icons/dialog-error.svg" START_ICON = "icons/media-playback-start.svg" STOP_ICON = "icons/process-stop.svg" CHANGED_ICON = "icons/dialog-warning-symbolic.svg" UNKNOWN_VALUE = "---" PROGRESS_BAR_LINE = " " PROGRESS_BAR_CURSOR = "***" PROGRESS_BAR_SPEED = 10 ############################################################################### # [Functions] System functions def control_cwd(): """ -> Get the current directory path and log it. """ cwd = os.getcwd() LOGGER.info("Current directory : '%s'.", cwd) return cwd def point_symbol_link_to(set_file): """ :param set_file: -> Make the 'conf.xml' symbolic file point to the given 'set_file'. """ if os.path.exists(SET_SYMBOLIC_LINK): os.remove(SET_SYMBOLIC_LINK) os.symlink(set_file, SET_SYMBOLIC_LINK) LOGGER.debug("'%s' -> '%s'.", SET_SYMBOLIC_LINK, set_file) def init_conf_xml_path(): """ -> Create a copy of the conf.xml file if it's an existing and real file. -> Make the conf.xml file point to the copy created. """ if os.path.exists(SET_SYMBOLIC_LINK) \ and not os.path.islink(SET_SYMBOLIC_LINK): shutil.copy(SET_SYMBOLIC_LINK, CONF_XML_COPY) point_symbol_link_to(CONF_XML_COPY) ############################################################################### # [Functions] Widgets management functions def set_widgets_visibility(widgets, set_visible_bool): """ :param widgets: :param set_visible_bool: -> Set a list of widgets visible or not together. """ for widget in widgets: widget.setVisible(set_visible_bool) def set_widgets_availability(widgets, set_available_bool): """ :param widgets: :param set_available_bool: -> Set a list of widgets available or not together. """ for widget in widgets: widget.setEnabled(set_available_bool) def set_items_icons(list_widget_items, qicon): """ :param list_widget_items: :param qicon: -> Put the same QIcon to items of a QListWidget. """ for item in list_widget_items: item.setIcon(qicon) def clear_widgets(widgets): """ :param widgets: -> Clear each widget in a list of widgets. """ for widget in widgets: widget.clear() def clear_list_widgets_selection(widgets): """ :param widgets: -> Clear the selection of each widget in a list of QListWidget widgets. """ for widget in widgets: widget.clearSelection() widget.clearFocus() ############################################################################### # [Hmi class] class Hmi(Widgets.QMainWindow): """Class to manage the main HMI behavior.""" def __init__(self): """ -> Creation of a MainWindow object generated by the command : 'pyuic5 *.ui -o *.py' from the PyQt5 HMI designed under QtDesigner. -> Creation of Dialog objects used from the main HMI. -> Declaration of widgets groups for code factorization. -> Declaration of HMI private attributes for its own methods. """ super(Hmi, self).__init__() self.ui = ui.Ui_MainWindow() self.ui.setupUi(self) # Existing popups : self.settings = None self.data_changed_popup = None self.credits_popup = None self.tutorial_popup = None # Shortcuts for lists of widgets : self.change_config_widgets = [self.ui.current_airframes, self.ui.current_settings, self.ui.current_flight_plan, self.ui.current_radio, self.ui.current_telemetry] self.display_config_widgets = [self.ui.airframes_overview, self.ui.settings_overview, self.ui.flight_plan_overview, self.ui.radio_overview, self.ui.telemetry_overview, self.ui.airframes_overview_2, self.ui.settings_overview_2, self.ui.flight_plan_overview_2, self.ui.radio_overview_2, self.ui.telemetry_overview_2] self.flight_plan_buttons = [self.ui.open_gui, self.ui.select_kml] self.session_widgets = [self.ui.session, self.ui.session_overview_1, self.ui.session_overview_2] self.display_programs_widgets = [self.ui.programs_overview_1, self.ui.programs_overview_2] self.target_widgets = [self.ui.quick_target, self.ui.target] self.messages_level_widgets = [self.ui.important, self.ui.custom, self.ui.all] self.log_filters_widgets = [self.ui.display_default, self.ui.display_info, self.ui.display_warnings, self.ui.display_errors] self.messages_nb_widgets = [self.ui.info_nb, self.ui.warnings_nb, self.ui.errors_nb] self.start_all_buttons = [self.ui.start_all_button, self.ui.quick_restart, self.ui.quick_restart_3] self.kill_all_buttons = [self.ui.kill_all_button, self.ui.quick_kill, self.ui.quick_kill_2] # Icons used in widgets (can't be constants because qpixmap objects # must be declared into a QApplication) : self.ok_qpixmap = gui.generate_qpixmap(OK_QPIXMAP) self.error_qpixmap = gui.generate_qpixmap(ERROR_QPIXMAP) self.start_qicon = gui.generate_qicon(START_ICON) self.stop_qicon = gui.generate_qicon(STOP_ICON) self.changed_qicon = gui.generate_qicon(CHANGED_ICON) # Progress bar initialization : self.max_result_field_size = self.ui.build_result.width() // 10 self.progress_bar_to_left = False # HMI parameters initialization (could be set in settings menu) : self.dev_mode = False self.current_log_filter = None self.data = None self.current_set = None self.current_config = None self.current_item = None self.current_target = None self.simulation_targets_names = ['nps', 'sim'] self.change_target_bool = True self.current_device = None self.current_session = None self.current_program = None self.current_option = None self.run_version = None self.build_version = None self.configurations_changed = {} self.sessions_changed = {} self.build = None self.build_running = False self.clean = None self.clean_running = False self.upload = None self.upload_running = False self.running_programs = {} self.console = cs.Console(self.ui.console) # Logger output redirected to a special stream to allow an integrated # console display (and logger level could be set in settings menu) : self.logger_stream = proc.LoggerStream() self.logger_stream.logger_log_sent.connect(self.write_log_in_console) logging.basicConfig(level=logging.INFO, stream=self.logger_stream) ############################################################################### # [Hmi methods] Init HMI methods def init_hmi_data(self): try: self.init_hmi_data_core() except: " if something goes wrong, delete cache and load again (to be improved)" LOGGER.error("ERROR while load HMI cache" "Original message : '%s'.", sys.exc_info()[0]) print("HMI error in cache, load default instead") parser.delete_cache() self.init_hmi_data_core() # Run and build Paparazzi versions found by existing program # './paparazzi_version' and file './var/build_version.txt' : run_version_cmd = env.RUN_VERSION_EXE self.run_version = os.popen(run_version_cmd).readline().strip() build_version_cmd = " ".join(["cat", env.BUILD_VERSION_FILE]) self.build_version = os.popen(build_version_cmd).readline().strip() def init_hmi_data_core(self): """ -> Initialization of a 'Data' object with all necessary data from XML files found in the 'CONF_PATH' Paparazzi UAV directory. -> Loading of cache data (last settings used) to restore them. -> Initialization of main HMI widgets with necessary data found. """ init_conf_xml_path() self.data = parser.Data(CONF_PATH) # Cache parameters extracted from the cache dictionary : last_geometry = self.data.cache[parser.LAST_GEOMETRY].split(" ") last_x, last_y, last_width, last_height = map(int, last_geometry) self.setGeometry(last_x, last_y, last_width, last_height) last_set_name = self.data.cache[parser.LAST_SET] self.current_set = self.data.sets[last_set_name] point_symbol_link_to(self.current_set.name) last_config_name = self.data.cache[parser.LAST_CONFIG] self.current_config = self.data.configurations[last_config_name] last_target_name = self.data.cache[parser.LAST_TARGET] self.current_target = self.current_config.targets[last_target_name] self.ui.upload.setEnabled(self.current_target.name not in self.simulation_targets_names) last_device_name = self.data.cache[parser.LAST_DEVICE] self.current_device = self.data.devices[last_device_name] last_session_name = self.data.cache[parser.LAST_SESSION] self.current_session = self.data.sessions[last_session_name] last_log_filters = self.data.cache[parser.LAST_LOG_FILTERS].split(" ") last_level = last_log_filters[0] last_default, last_info, last_warning, last_error =\ map(int, last_log_filters[1:]) self.current_log_filter = cs.LogFilter(last_level, last_default, last_info, last_warning, last_error) def init_hmi_widgets(self): """ -> Initialization of all the HMI widgets content. -> Initialization of existing popups. """ # Update functions used for initialization : self.update_home_button() self.update_mode_button() self.update_versions() self.update_set_combo() self.update_config_combo() self.update_config_items_lists() self.update_target_combos(self.target_widgets) self.update_log_filters() self.change_messages_level() self.update_device_combo() self.update_session_combos() self.update_programs_list() self.update_program_options_list() self.init_tools_menu() # Popup initialization : self.settings = dial.SettingsManager() self.data_changed_popup = dial.Popup(dial.DATA_CHANGED_POPUP_TYPE) self.credits_popup = dial.Popup(dial.CREDITS_POPUP_TYPE) self.tutorial_popup = dial.Popup(dial.TUTORIAL_POPUP_TYPE) def init_all_hmi(self): """ -> Centralize all initializations before the mainwindow is shown. """ control_cwd() LOGGER.info("HMI init in progress...\n") print("\nHMI init in progress...") self.init_hmi_data() self.init_hmi_widgets() self.connect_signals() LOGGER.info("HMI init finished.\n") print("HMI init finished.\n") ############################################################################### # [Hmi methods] Connect signals methods def connect_signals(self): """ -> Connection of 'triggered' signals from 'QMenu' or 'QAction'. -> Connection of 'currentIndexChanged' signals from 'QComboBox'. -> Connection of 'clicked' signals from 'QPushButton'. """ ####################################################################### # Actions in menus self.ui.actionQuit_Paparazzi_UAV.triggered.connect(self.close) self.ui.actionSetManager.triggered.connect(self.settings.show) self.ui.actionSave_2.triggered.connect(self.save_current_config) self.ui.actionSave_3.triggered.connect(self.save_current_session) self.ui.actionFull_screen.triggered.connect(self.fullscreen_view) self.ui.actionHide_overviews.triggered.connect(self.toggle_overview) self.ui.actionAbout_Paparazzi_UAV.triggered.connect( self.credits_popup.show) self.ui.actionTutorial.triggered.connect(self.tutorial_popup.show) ####################################################################### # Equipment tab for list_widget in self.change_config_widgets: list_widget.itemSelectionChanged.connect(functools.partial( self.change_current_item, list_widget)) self.ui.current_settings.itemClicked.connect( self.change_setting_check_state) self.ui.current_set.currentIndexChanged.connect(self.change_current_set) self.ui.current_configuration.currentIndexChanged.connect( self.change_current_config) self.ui.remove_item.clicked.connect(self.remove_item_from_config) self.ui.add_item.clicked.connect(self.open_item_file_chooser) self.ui.edit.clicked.connect(self.open_gedit) self.ui.quick_build.clicked.connect(self.quick_build) ####################################################################### # Build / flash tab for combo_widget in self.target_widgets: combo_widget.currentIndexChanged.connect(functools.partial( self.change_current_target, combo_widget)) for widget in self.log_filters_widgets: widget.clicked.connect(self.change_sensitivity_filters) for widget in self.messages_level_widgets: widget.clicked.connect(self.change_messages_level) self.ui.clean.clicked.connect(self.clean_config) self.ui.build.clicked.connect(self.build_config) self.ui.upload.clicked.connect(self.flash_device) self.ui.show_console.clicked.connect(functools.partial( self.switch_to_tab, 3)) self.ui.device.currentIndexChanged.connect(self.change_current_device) ####################################################################### # Session tab for combo_widget in self.session_widgets: combo_widget.currentIndexChanged.connect(functools.partial( self.change_current_session, combo_widget)) self.ui.programs.itemSelectionChanged.connect( self.change_current_program) self.ui.options.itemSelectionChanged.connect( self.change_current_option) self.ui.play_stop_program.clicked.connect(functools.partial( self.run_program, None)) for button in self.start_all_buttons: button.clicked.connect(self.start_all_programs) for button in self.kill_all_buttons: button.clicked.connect(self.kill_all_programs) self.ui.remove_program.clicked.connect( self.remove_program_from_session) self.ui.remove_option.clicked.connect(self.remove_option_from_program) self.ui.add_option.clicked.connect(self.add_option_to_program) self.ui.options.itemChanged.connect(self.edit_current_option) ####################################################################### # Console tab self.ui.clean_console.clicked.connect(self.clean_console) ####################################################################### # Other widgets connected self.ui.open_home_terminal.clicked.connect(self.open_terminal) self.ui.switch_mode.clicked.connect(self.switch_current_mode) ############################################################################### # [Hmi methods] Reimplemented methods def closeEvent(self, event): """ :param event: QEvent object automatically filled when 'closed' signal caught. -> Called automatically when when 'closed' signal caught. -> Kill all processes still running. -> Update the cache file. -> Call the real mainWindow closeEvent to finish the 'app.exec_' events loop. """ for process in [self.clean, self.build, self.upload]: # + list(self.running_programs.values()): # /!\ REQUIREMENT : DON'T KILL SESSION PROCESSES WHEN THE # APPLICATION EXITS... if process is not None: try: process.subprocess.kill() except ProcessLookupError: print("Process : '%s' already stopped. " "Can't be killed again !\n" % process.name) else: print("Process : '%s' killed !\n" % process.name) # Ask for a confirmation if some data are unsaved and quit saving # these data : if self.configurations_changed != {} or self.sessions_changed != {}: if self.sessions_changed != {parser.SIMULATION_NAME: parser.SIMULATION_SESSION} \ and self.sessions_changed != {parser.REPLAY_NAME: parser.REPLAY_SESSION}: self.data_changed_popup.show() self.data_changed_popup.accepted.connect( self.save_all_changed_data) self.data_changed_popup.rejected.connect( self.save_cache_data) else: print("A special in-code defined session (Session or Replay) " "has been changed and could not have been saved...\n") self.save_cache_data() else: self.save_cache_data() ############################################################################### # [Hmi methods] Update widgets methods def update_home_button(self): self.ui.open_home_terminal.setText(env.PAPARAZZI_HOME) def update_mode_button(self): if self.dev_mode: self.ui.switch_mode.setText("Developer") else: self.ui.switch_mode.setText("Standard") def update_versions(self): for field, value in zip([self.ui.run_version, self.ui.build_version], [self.run_version, self.build_version]): if value is not None: field.setText(value) else: field.setText(UNKNOWN_VALUE) def switch_to_tab(self, tab_index): self.ui.main_tab.setCurrentIndex(tab_index) def init_tools_menu(self): """ -> Clear the 'Tools' menu actions (menubar). -> Fill the 'Tools' menu with the names found in 'control_panel.xml' -> Connect each action to the corresponding tool process. """ self.ui.menuTools.clear() sorted_names = parser.sorted_tools_names(self.data.tools) for name in sorted_names: action = Widgets.QAction(name, self) self.ui.menuTools.addAction(action) for action in self.ui.menuTools.actions(): action.triggered.connect(functools.partial( self.add_program_to_session, self.data.tools[action.text()])) def fullscreen_view(self): if self.isMaximized(): self.resize(800, 600) else: self.showMaximized() def toggle_overview(self): if self.ui.dockWidget.isVisible(): self.ui.dockWidget.hide() self.ui.actionHide_overviews.setText("Show overviews") else: self.ui.dockWidget.show() self.ui.actionHide_overviews.setText("Hide overviews") def update_set_combo(self): """ -> Clear the 'Set' combobox. -> Fill the combobox with the names found in 'conf.xml'. """ self.ui.current_set.clear() sorted_names = parser.sorted_sets_names(self.data.sets) self.ui.current_set.addItems(sorted_names) self.ui.current_set.setCurrentText(self.current_set.name) def update_config_combo(self): """ -> Clear the 'Configuration' combobox. -> Fill the combobox with the configurations names corresponding to the current selected set. -> Save the current settings check state when configuration changed. (allows to the configuration to be recovered when selected again) """ self.ui.current_configuration.clear() sorted_names = parser.sorted_current_configs_names( self.data.configurations, self.current_set) self.ui.current_configuration.addItems(sorted_names) self.ui.current_configuration.setCurrentText(self.current_config.name) def update_config_items_lists(self): """ -> Disable or hide widgets and buttons to init config. -> Set config ID and color. -> Set config items by category. -> Make 'settings' items checkable and set check-boxes. """ set_widgets_visibility(self.display_config_widgets + self.change_config_widgets, False) set_widgets_availability([self.ui.remove_item, self.ui.edit], False) set_widgets_visibility(self.flight_plan_buttons, False) self.ui.current_id.setText(self.current_config.id) color = gui.generate_qcolor(self.current_config.color[0]) palette = gui.generate_widget_palette(self.ui.current_color, color) self.ui.current_color.setPalette(palette) clear_widgets(self.change_config_widgets) for widget, values in zip(self.change_config_widgets, [self.current_config.airframes, self.current_config.settings + self.current_config.modules, self.current_config.flight_plan, self.current_config.radio, self.current_config.telemetry]): for value in values: value = value.strip() if value: widget.addItem(value) if values: set_widgets_visibility([widget], True) for i in range(self.ui.current_settings.count()): item = self.ui.current_settings.item(i) item.setFlags(Core.Qt.ItemIsUserCheckable | Core.Qt.ItemIsEnabled | Core.Qt.ItemIsSelectable) if item.text().startswith("["): item.setText(item.text().strip("[]")) item.setCheckState(Core.Qt.Unchecked) else: item.setCheckState(Core.Qt.Checked) self.update_config_overview_lists() def update_config_overview_lists(self): """ -> Hide all the configuration overview widgets. -> Clear these widgets. -> Copy the content of the real configuration QListWidget widgets. -> Keep only the basename of long items (put full name in tooltip). -> Display the widgets only if not empty (save some vertical space). """ set_widgets_visibility(self.display_config_widgets, False) clear_widgets(self.display_config_widgets) for input_widget, display_widget in zip(self.change_config_widgets*2, self.display_config_widgets): items_full_names = [] for i in range(input_widget.count()): item_full_name = input_widget.item(i).text() item_name = os.path.splitext( os.path.basename(item_full_name))[0] display_widget.addItem(item_name) items_full_names.append(item_full_name) display_widget.setToolTip("\n".join(items_full_names)) if display_widget.count() != 0: display_widget.setVisible(True) def update_target_combos(self, combo_widgets): """ :param combo_widgets: -> Clear the 2 'Target' combo widgets. -> Fill them with the targets available for the current configuration. """ clear_widgets(combo_widgets) sorted_names = parser.sorted_current_targets_names( self.current_config.targets) for widget in combo_widgets: widget.addItems(sorted_names) widget.setCurrentText(self.current_target.name) def update_log_filters(self): """ -> Load the last filters from the cache. -> Disable the filters if the level is minimum or maximum. -> Set the filters check state by the current LogFilter object properties. """ if self.current_log_filter.level == cs.MINIMAL_MESSAGES_LEVEL: self.ui.important.setChecked(True) set_widgets_availability(self.log_filters_widgets, False) elif self.current_log_filter.level == cs.ALL_MESSAGES_LEVEL: self.ui.all.setChecked(True) set_widgets_availability(self.log_filters_widgets, False) else: self.ui.custom.setChecked(True) set_widgets_availability(self.log_filters_widgets, True) self.ui.display_default.setChecked(self.current_log_filter.default) self.ui.display_info.setChecked(self.current_log_filter.info) self.ui.display_warnings.setChecked(self.current_log_filter.warning) self.ui.display_errors.setChecked(self.current_log_filter.error) def update_device_combo(self): """ -> Clear the 'Device' combobox. -> Search compatible boards for current target. -> Update the combo-box with devices corresponding to each board. -> Deal with empty devices list because of default Wi-Fi flash. """ self.ui.device.clear() current_devices = [] for device in self.data.devices.values(): if device.name != parser.DEFAULT_DEVICE_NAME: for board_regex in device.boards_regex: found_match = re.search(board_regex, self.current_target.board) if found_match: current_devices.append(device) break else: current_devices.append(device) sorted_names = parser.sorted_current_devices_names(current_devices) self.ui.device.addItems(sorted_names) self.ui.device.setCurrentText(self.current_device.name) self.ui.device.setEnabled(True) def update_session_combos(self): """ -> Clear the 'Session' combobox. -> Fill it with the sessions found in 'control_panel.xml' """ clear_widgets(self.session_widgets) sorted_names = parser.sorted_sessions_names(self.data.sessions) for widget in self.session_widgets: widget.addItems(sorted_names) sep_index = sorted_names.index(parser.SESSIONS_COMBO_SEP) variant = Core.QVariant(not Core.Qt.ItemIsEnabled) widget.setItemData(sep_index, variant, Core.Qt.UserRole - 1) widget.setCurrentText(self.current_session.name) def update_programs_list(self): """ -> Clear the programs QListWidget. -> Fill it with the programs available for the current session. -> Update the programs overview widgets with the same programs. -> Init the play/stop program button HMI. """ self.ui.programs.clear() self.current_program = None sorted_names = parser.sorted_current_programs_names( self.current_session) self.ui.programs.addItems(sorted_names) for i in range(self.ui.programs.count()): self.ui.programs.item(i).setIcon(self.stop_qicon) self.update_programs_overviews() self.update_program_button() self.update_program_options_list() set_widgets_availability([self.ui.play_stop_program, self.ui.remove_program, self.ui.add_option] + self.kill_all_buttons, False) def update_programs_overviews(self): """ -> Clear the programs overview QListWidget widgets. -> Fill them with the current programs widget items. -> Set their icon to stopped state. """ clear_widgets(self.display_programs_widgets) for input_widget, display_widget in zip([self.ui.programs]*2, self.display_programs_widgets): for i in range(input_widget.count()): display_widget.addItem(input_widget.item(i).text()) item = display_widget.item(i) item.setIcon(self.stop_qicon) item.setFlags(Core.Qt.ItemIsEnabled) def update_program_options_list(self): """ -> Clear the options QListWidget. -> Fill them with the current options of the selected program if possible (and detect the option shape : tuple or str). """ self.ui.options.clear() self.current_option = None if self.current_program is not None: options = self.current_program[1].options for option in options: option_str = option if type(option) is tuple: option_str = " ".join(option) self.ui.options.addItem(option_str) for i in range(self.ui.options.count()): item = self.ui.options.item(i) item.setFlags(Core.Qt.ItemIsEditable | Core.Qt.ItemIsEnabled | Core.Qt.ItemIsSelectable) self.ui.remove_option.setEnabled(False) def init_build_flash_hmi(self): """ -> Kill the build processes if already running. -> Clear the build widgets. """ clear_widgets([self.ui.build_result_icon, self.ui.build_result, self.ui.flash_resut_icon, self.ui.flash_result] + self.messages_nb_widgets) def update_flags_nb_hmi(self, compilation_object): """ :param compilation_object: -> Update the flags number QLabel widgets at each log sent to the console. """ self.ui.errors_nb.setText(str( compilation_object.flags[cs.ERROR_FLAG])) self.ui.warnings_nb.setText(str( compilation_object.flags[cs.WARNING_FLAG])) self.ui.info_nb.setText(str( compilation_object.flags[cs.INFO_FLAG])) ############################################################################### # [Hmi methods] Clean methods def set_clean_started_hmi(self): """ -> Turn HMI to clean running state. """ self.init_build_flash_hmi() self.clean_running = True set_widgets_availability([self.ui.build, self.ui.upload, self.ui.quick_build] + self.target_widgets, False) self.ui.clean.setText("Stop !") self.setCursor(gui.generate_qcursor(gui.WAIT_CURSOR_SHAPE)) def set_clean_stopped_hmi(self): """ -> Turn HMI to clean stopped state. -> Display the result of the clean process. -> Switch to the build / flash tab. """ self.clean_running = False set_widgets_availability([self.ui.build, self.ui.quick_build, self.ui.clean] + self.target_widgets, True) if self.current_target.name not in self.simulation_targets_names: self.ui.upload.setEnabled(True) self.ui.clean.setText("Clean") self.setCursor(gui.generate_qcursor(gui.NORMAL_CURSOR_SHAPE)) self.update_flags_nb_hmi(self.clean) errors = self.clean.flags[cs.ERROR_FLAG] if self.clean.exit_code == proc.INTERRUPTED_EXIT_CODE: self.ui.build_result.setText("Clean aborted by user !") self.ui.build_result_icon.setPixmap(self.error_qpixmap) elif errors != 0 or self.clean.exit_code == proc.DEFAULT_EXIT_CODE: self.ui.build_result.setText("Error(s) found " + "during clean. " + "Please try again !") self.ui.build_result_icon.setPixmap(self.error_qpixmap) elif self.clean.exit_code == proc.SUCCESS_EXIT_CODE: self.ui.build_result.setText("Clean done with success !") self.ui.build_result_icon.setPixmap(self.ok_qpixmap) else: self.ui.build_result.setText("Unknown error !") self.ui.build_result_icon.setPixmap(self.error_qpixmap) self.clean = None self.switch_to_tab(1) ############################################################################### # [Hmi methods] Build methods def set_build_started_hmi(self): """ -> Turn HMI to build running state. """ self.init_build_flash_hmi() self.build_running = True set_widgets_availability([self.ui.clean, self.ui.upload, self.ui.quick_build] + self.target_widgets, False) self.ui.build.setText("Stop !") self.setCursor(gui.generate_qcursor(gui.WAIT_CURSOR_SHAPE)) def set_build_stopped_hmi(self): """ -> Turn HMI to build stopped state. -> Display the result of the build process. -> Switch to the build / flash tab. """ self.build_running = False set_widgets_availability([self.ui.clean, self.ui.quick_build, self.ui.build] + self.target_widgets, True) if self.current_target.name not in self.simulation_targets_names: self.ui.upload.setEnabled(True) self.ui.build.setText("Build") self.setCursor(gui.generate_qcursor(gui.NORMAL_CURSOR_SHAPE)) self.update_flags_nb_hmi(self.build) errors = self.build.flags[cs.ERROR_FLAG] if self.build.exit_code == proc.INTERRUPTED_EXIT_CODE: self.ui.build_result.setText("Build aborted by user !") self.ui.build_result_icon.setPixmap(self.error_qpixmap) elif errors != 0 or self.build.exit_code == proc.DEFAULT_EXIT_CODE: self.ui.build_result.setText("Error(s) found " + "during build. " + "Please try again !") self.ui.build_result_icon.setPixmap(self.error_qpixmap) elif self.build.exit_code == proc.SUCCESS_EXIT_CODE: self.ui.build_result.setText("Build done with success !") self.ui.build_result_icon.setPixmap(self.ok_qpixmap) else: self.ui.build_result.setText("Unknown error !") self.ui.build_result_icon.setPixmap(self.error_qpixmap) self.build = None self.switch_to_tab(1) ############################################################################### # [Hmi methods] Flash methods def set_flash_started_hmi(self): """ -> Turn HMI to flash running state. """ self.init_build_flash_hmi() self.upload_running = True set_widgets_availability([self.ui.clean, self.ui.build, self.ui.quick_build, self.ui.device] + self.target_widgets, False) self.ui.upload.setText("Stop !") self.setCursor(gui.generate_qcursor(gui.WAIT_CURSOR_SHAPE)) clear_widgets([self.ui.flash_resut_icon, self.ui.flash_result]) def set_flash_stopped_hmi(self): """ -> Turn HMI to flash stopped state. -> Display the result of the upload process. -> Switch to the build / flash tab. """ self.upload_running = False set_widgets_availability([self.ui.clean, self.ui.build, self.ui.upload, self.ui.quick_build, self.ui.device] + self.target_widgets, True) self.ui.upload.setText("Upload") self.setCursor(gui.generate_qcursor(gui.NORMAL_CURSOR_SHAPE)) self.update_flags_nb_hmi(self.upload) errors = self.upload.flags[cs.ERROR_FLAG] if self.upload.exit_code == proc.INTERRUPTED_EXIT_CODE: self.ui.flash_result.setText("Upload aborted by user !") self.ui.flash_resut_icon.setPixmap(self.error_qpixmap) elif errors != 0 or self.upload.exit_code == proc.DEFAULT_EXIT_CODE: self.ui.flash_result.setText("Error(s) found " + "during upload. " + "Please try again !") self.ui.flash_resut_icon.setPixmap(self.error_qpixmap) elif self.upload.exit_code == proc.SUCCESS_EXIT_CODE: self.ui.flash_result.setText("Upload done with success !") self.ui.flash_resut_icon.setPixmap(self.ok_qpixmap) else: self.ui.flash_result.setText("Unknown error !") self.ui.flash_resut_icon.setPixmap(self.error_qpixmap) self.upload = None self.switch_to_tab(1) ############################################################################### # [Hmi methods] Program methods def update_program_button(self): """ -> Update the play/stop program button HMI to correct state. """ if self.current_program is not None: self.ui.play_stop_program.setEnabled(True) if self.current_program[1].name in self.running_programs.keys(): self.ui.play_stop_program.setIcon(self.stop_qicon) self.ui.play_stop_program.setText("Stop") else: self.ui.play_stop_program.setIcon(self.start_qicon) self.ui.play_stop_program.setText("Start") else: self.ui.play_stop_program.setEnabled(False) self.ui.play_stop_program.setIcon(self.start_qicon) def set_program_started_hmi(self, program_process): """ :param program_process: -> Turn HMI to program running state. """ self.running_programs[program_process.program.name] = program_process items = [widget.findItems(program_process.program.name, Core.Qt.MatchExactly)[0] for widget in [self.ui.programs, self.ui.programs_overview_1, self.ui.programs_overview_2]] set_items_icons(items, self.start_qicon) self.update_program_button() if self.running_programs != {}: set_widgets_availability([self.ui.session, self.ui.session_overview_1, self.ui.session_overview_2, self.ui.current_set], False) set_widgets_availability(self.kill_all_buttons, True) set_widgets_availability(self.start_all_buttons, False) def set_program_stopped_hmi(self, program_process): """ :param program_process: -> Turn HMI to program stopped state. -> Display the result of the program process. -> Switch to the sessions management tab. """ self.running_programs.pop(program_process.program.name) items = [widget.findItems(program_process.program.name, Core.Qt.MatchExactly)[0] for widget in [self.ui.programs, self.ui.programs_overview_1, self.ui.programs_overview_2]] set_items_icons(items, self.stop_qicon) self.update_program_button() if self.running_programs == {}: set_widgets_availability([self.ui.session, self.ui.session_overview_1, self.ui.session_overview_2, self.ui.current_set], True) set_widgets_availability(self.kill_all_buttons, False) set_widgets_availability(self.start_all_buttons, True) self.switch_to_tab(2) ############################################################################### # [Hmi methods] Write log methods def write_log_in_console(self, log, flag, log_type): """ :param log: :param flag: :param log_type: -> Get the logs sent by the processes when a 'log_sent' signal is caught. -> Treat the log depending if it's an application or a process one. -> Put color to the log line and write it into the console. """ if log_type == cs.PROCESS_MESSAGE_TYPE: if self.current_log_filter.write_line_decision(flag): str_color = cs.BACKGROUNDS_COLORS[flag] background_qcolor = gui.generate_qcolor(str_color) self.console.write(log, backgroud_color=background_qcolor) elif log_type == cs.APPLICATION_MESSAGE_TYPE: if log != "\n": str_color = cs.FONTS_COLORS[flag] font_qcolor = gui.generate_qcolor(str_color) self.console.write(log, font_color=font_qcolor) def animate_progress_bar(self): """ -> For all running build / flash processes, animate the progress bar. -> Change the text displayed in the result field according to its size and the current direction. """ for program, label in zip([self.clean, self.build, self.upload], [self.ui.build_result, self.ui.build_result, self.ui.flash_result]): if program is not None: last_result_text = label.text() last_result_text_size = label.fontMetrics().\ boundingRect(last_result_text).width() field_width = label.width() if len(last_result_text) < PROGRESS_BAR_SPEED+1: new_result_text = PROGRESS_BAR_LINE * PROGRESS_BAR_SPEED \ + PROGRESS_BAR_CURSOR self.progress_bar_to_left = False elif last_result_text_size > field_width - 100\ or self.progress_bar_to_left: new_result_text = last_result_text[PROGRESS_BAR_SPEED:] self.progress_bar_to_left = True else: new_result_text = PROGRESS_BAR_LINE * PROGRESS_BAR_SPEED \ + last_result_text self.progress_bar_to_left = False label.setText(new_result_text) ############################################################################### # [Hmi methods] Widgets connected with signals methods def switch_current_mode(self): """ -> Real switch but no real effect (no dev mode yet...) -> RELOAD MAIN WINDOW WITH NEW WIDGETS ? """ self.dev_mode = not self.dev_mode self.update_mode_button() def change_current_set(self): """ -> Get current set when combobox value changed. -> Symbolic link to 'config.xml' changed to the new set. -> Update configuration and its items. """ self.save_all_changed_configurations() current_set_name = self.ui.current_set.currentText() if current_set_name: self.current_set = self.data.sets[current_set_name] point_symbol_link_to(self.current_set.name) self.update_config_combo() self.update_config_items_lists() def change_current_config(self): """ -> Get current configuration when combobox value changed. -> Update target and device values. -> Stop the build processes if some running. -> Init the build / flash HMI to be ready to a new process. """ current_config_name = self.ui.current_configuration.currentText() if current_config_name: self.current_config = self.data.configurations[current_config_name] self.update_config_items_lists() self.update_target_combos([self.ui.target]) self.update_device_combo() self.init_build_flash_hmi() def change_current_item(self, list_widget): """ :param list_widget: -> Update the current selected item : (widget, item, object). -> Clear the others selected items in other categories. -> Update the configuration management HMI (buttons). -> Make the flight plan buttons appear or disappear. -> Update all the check states of the settings list if the current selected item is a setting. """ current_selected_item = list_widget.currentItem() if current_selected_item is not None: current_item_text = current_selected_item.text() self.current_item = (list_widget, current_selected_item, current_item_text) widgets_to_clear = [_ for _ in self.change_config_widgets] widgets_to_clear.remove(list_widget) clear_list_widgets_selection(widgets_to_clear) set_widgets_availability([self.ui.remove_item, self.ui.edit], True) set_widgets_visibility(self.flight_plan_buttons, list_widget.objectName() == "current_flight_plan") def change_current_target(self, combo_widget): """ :param combo_widget: -> Update the current target and the devices combobox (related). -> Possible to change the target from 2 widgets. That's why the solution found is strange... """ current_target_name = combo_widget.currentText() if current_target_name and self.change_target_bool: self.current_target = self.current_config.targets[ current_target_name] self.ui.upload.setEnabled(self.current_target.name not in self.simulation_targets_names) other_widgets = [_ for _ in self.target_widgets] other_widgets.remove(combo_widget) # TODO BETTER : AVOID THE SIGNALS PING-PONG BETWEEN TARGETS WIDGETS self.change_target_bool = False self.update_target_combos(other_widgets) self.change_target_bool = True self.update_device_combo() def change_sensitivity_filters(self): """ -> Get checkbox values to set the current filters. -> Update the filters values for the console. """ self.current_log_filter.set_sensitivity_filter( self.ui.display_default.checkState(), self.ui.display_info.checkState(), self.ui.display_warnings.checkState(), self.ui.display_errors.checkState()) self.update_log_filters() def change_messages_level(self): """ -> Get radiobutton value to set the current message level. -> Update the level value for the console. """ if self.ui.all.isChecked(): self.current_log_filter.set_level(cs.ALL_MESSAGES_LEVEL) self.current_log_filter.set_sensitivity_filter(2, 2, 2, 2) elif self.ui.custom.isChecked(): self.current_log_filter.set_level(cs.CUSTOM_MESSAGES_LEVEL) elif self.ui.important.isChecked(): self.current_log_filter.set_level(cs.MINIMAL_MESSAGES_LEVEL) self.current_log_filter.set_sensitivity_filter(0, 0, 2, 2) self.update_log_filters() def change_current_device(self): """ -> Get current device when combobox value changed. -> If no device available, the default flash mode is WiFi. """ current_device_name = self.ui.device.currentText() if current_device_name: self.current_device = self.data.devices[current_device_name] def change_current_session(self, combo_widget): """ :param combo_widget: -> Get current session when combobox value changed. -> Update programs HMI for the new selected session. -> Deselect the last selected program and clear the options. """ current_session_name = combo_widget.currentText() if current_session_name: clear_list_widgets_selection([self.ui.programs] + self.display_programs_widgets) self.current_session = self.data.sessions[current_session_name] widgets = [_ for _ in self.session_widgets] widgets.remove(combo_widget) for widget in widgets: widget.setCurrentText(self.current_session.name) self.update_programs_list() self.update_program_options_list() self.ui.options.clear() def change_current_program(self): """ -> Get current selected program when QListWidget selection changed. -> Update options HMI for the new selected program. """ current_selected_item = self.ui.programs.currentItem() if current_selected_item is not None: item_name = current_selected_item.text() if item_name in self.current_session.programs.keys(): self.current_program = (current_selected_item, self.current_session.programs[ item_name]) self.ui.remove_program.setEnabled(item_name not in self.running_programs.keys()) self.ui.add_option.setEnabled(True) self.update_program_options_list() self.update_program_button() def change_current_option(self): """ -> Get the current selected option when QListWidget selection changed. -> Enable options management HMI. """ current_selected_item = self.ui.options.currentItem() if current_selected_item is not None: item_name = current_selected_item.text() self.current_option = (current_selected_item, item_name) self.ui.remove_option.setEnabled(True) def open_item_file_chooser(self): """ -> While left part of equipment tab is not finished, a simple file chooser is used to select new equipment items. -> Add the selected new item to the correct category if possible. """ file_chooser = Widgets.QFileDialog() fc_title = "Select a file to add to '" +\ self.current_config.name + "'..." fc_dir = env.PAPARAZZI_CONF fc_filter = "XML file(*.xml)" path, other = file_chooser.getOpenFileName(file_chooser, fc_title, fc_dir, fc_filter) if path: filename = parser.full_to_conf_path(path) self.add_item_to_config(filename) def open_gedit(self): """ -> Open a text editor depending on the OS found. """ if self.current_item is not None: if env.OS in ["linux", "linux2"]: file_path = os.path.join(CONF_PATH, self.current_item[2]) command = " ".join(["gedit", file_path]) try: os.popen(command) except ChildProcessError: LOGGER.error("Gedit text editor is not available !\n") self.switch_to_tab(3) elif env.OS in []: pass else: LOGGER.error("No text file editor found for your OS !\n") self.switch_to_tab(3) def clean_console(self): """ -> Just clear the console QTextEdit widget. """ self.ui.console.clear() def open_terminal(self): """ -> Open a terminal in the Paparazzi home directory depending on the OS found. """ if env.OS in ["linux", "linux2"]: command_terms = ["gnome-terminal", "--working-directory ", env.PAPARAZZI_HOME] command = " ".join(command_terms) os.popen(command) else: LOGGER.error("No terminal shortcut available for your OS !\n") self.switch_to_tab(3) ############################################################################### # [Hmi methods] Processes management (connected with signals) methods def quick_build(self): """ -> Clean the current configuration when the quick build button is clicked. -> Give the information to the clean function that a build process is planned after the clean process. """ self.switch_to_tab(1) self.clean_config(is_quick_build=True) def clean_config(self, is_quick_build=False): """ :param is_quick_build: -> Clean the current configuration if not already started. -> Else, abort the current clean process and set the HMI to the initial state. """ if not self.clean_running: self.clean = proc.Process(proc.CLEAN, configuration=self.current_config) if self.clean.check_before_start(): self.clean.start() self.clean.subprocess.finished.connect(self.set_clean_stopped_hmi) self.clean.process_log_sent.connect(functools.partial( self.update_flags_nb_hmi, self.clean)) self.clean.process_log_sent.connect(self.write_log_in_console) self.clean.process_log_sent.connect(self.animate_progress_bar) self.set_clean_started_hmi() if is_quick_build: self.clean.subprocess.finished.connect(functools.partial( self.build_config)) else: LOGGER.error("Incorrect config, can't clean !\n") self.switch_to_tab(3) else: self.clean.process_killed.emit() self.ui.clean.setEnabled(False) def build_config(self): """ -> Build the current configuration if not already started. -> Else, abort the current build process and set the HMI to the initial state. """ if not self.build_running: self.build = proc.Process(proc.BUILD, configuration=self.current_config, target=self.current_target) self.save_current_config() if self.build.check_before_start(): self.build.start() self.build.subprocess.finished.connect(self.set_build_stopped_hmi) self.build.process_log_sent.connect(functools.partial( self.update_flags_nb_hmi, self.build)) self.build.process_log_sent.connect(self.write_log_in_console) self.build.process_log_sent.connect(self.animate_progress_bar) self.set_build_started_hmi() else: LOGGER.error("Incorrect config, can't build !\n") self.switch_to_tab(3) else: self.build.process_killed.emit() self.ui.build.setEnabled(False) def flash_device(self): """ -> Flash the current configuration if not already started. -> Else, abort the current upload process and set the HMI to the initial state. """ if not self.upload_running: self.upload = proc.Process(proc.UPLOAD, configuration=self.current_config, target=self.current_target, device=self.current_device) if self.upload.check_before_start(): self.upload.start() self.upload.subprocess.finished.connect(self.set_flash_stopped_hmi) self.upload.process_log_sent.connect(functools.partial( self.update_flags_nb_hmi, self.upload)) self.upload.process_log_sent.connect(self.write_log_in_console) self.upload.process_log_sent.connect(self.animate_progress_bar) self.set_flash_started_hmi() else: LOGGER.error("Incorrect device, can't upload !\n") self.switch_to_tab(3) else: self.upload.process_killed.emit() self.ui.upload.setEnabled(False) def run_program(self, program): """ :param program: -> Run the current selected program if not already started. Else, abort it and set the HMI to the initial state. """ if program is None and self.current_program is not None: program_item, program = self.current_program if program.name not in self.running_programs.keys(): new_program_proc = proc.Process(proc.PROGRAM, program=program, configuration=self.current_config, target=self.current_target) if new_program_proc.check_before_start(): new_program_proc.start() new_program_proc.subprocess.finished.connect(functools.partial( self.set_program_stopped_hmi, new_program_proc)) new_program_proc.process_log_sent.connect( self.write_log_in_console) self.set_program_started_hmi(new_program_proc) else: self.running_programs[program.name].process_killed.emit() self.ui.play_stop_program.setEnabled(False) def start_all_programs(self): """ -> Kill all running programs of the current session. -> Start all programs not running (so it's a restart). -> Keep the current selected program if there's one. """ for program in self.current_session.programs.values(): if program.name not in self.running_programs.keys(): self.run_program(program) def kill_all_programs(self): """ -> Kill all running programs of the current session. -> Keep the current selected program if there's one. """ for program in self.current_session.programs.values(): if program.name in self.running_programs.keys(): self.run_program(program) ############################################################################### # [Hmi methods] Data management (connected with signals) methods def change_setting_check_state(self, clicked_item): """ :param clicked_item: -> Save the new check state of settings items. -> Add the current config to changed configs => ask to save before quitting the application. """ if self.current_config is not None\ and self.current_item is not None: list_widget, list_item, item_name = self.current_item setting_name = item_name setting_state = list_item.checkState() # TODO BETTER : NO SOLUTION (OR SIGNAL) FOUND TO DETECT A CLICK ON # : TODO : 'QListWidgetItem' SO ONLY CURRENT ITEM CHECKSTATE # : TODO : SHOULD BE CHANGED... (NOT WORKING WELL) if clicked_item == list_item: if parser.SETTINGS_REF in setting_name: settings_modules = self.current_config.settings else: settings_modules = self.current_config.modules settings_modules_names = [item.strip("[]") for item in settings_modules] setting_index = settings_modules_names.index(setting_name) settings_modules.pop(setting_index) if not setting_state: setting_name = "[" + setting_name + "]" settings_modules.insert(setting_index, setting_name) self.configurations_changed[self.current_config.name] = \ self.current_config index = self.ui.current_configuration.currentIndex() self.ui.current_configuration.setItemIcon(index, self.changed_qicon) else: clicked_item.setCheckState(setting_state) def remove_item_from_config(self): """ -> Remove an item from the current selected configuration. -> Impossible to remove all airframes because it doesn't make sense. """ if self.current_config is not None \ and self.current_item is not None: list_widget, list_item, item_name = self.current_item if not (list_widget is self.ui.current_airframes and len(list_widget) == 1): if list_widget == self.ui.current_settings and \ not list_item.checkState(): item_name = "[" + item_name + "]" config_attributes_lists = [self.current_config.airframes, self.current_config.settings, self.current_config.modules, self.current_config.flight_plan, self.current_config.radio, self.current_config.telemetry] for attributes_list in config_attributes_lists: if item_name in attributes_list: attributes_list.remove(item_name) break list_widget.takeItem(list_widget.row(list_item)) list_widget.clearSelection() self.current_item = None self.ui.remove_item.setEnabled(False) if not list_widget: list_widget.setVisible(False) if list_widget is self.ui.current_flight_plan: set_widgets_visibility(self.flight_plan_buttons, False) self.update_config_overview_lists() self.configurations_changed[self.current_config.name] = \ self.current_config index = self.ui.current_configuration.currentIndex() self.ui.current_configuration.setItemIcon(index, self.changed_qicon) else: LOGGER.error("You can't remove all airframes from a " "configuration !\n") self.switch_to_tab(3) def add_item_to_config(self, filename): """ :param filename: -> Add the selected filename to the correct category if it's possible. -> Else, an error is raised in the console. -> If a setting is added, it's checked by default. """ if self.current_config is not None: keywords = [parser.AIRFRAME_REF, parser.SETTINGS_REF, parser.MODULES_REF, parser.FP_REF, parser.RADIO_REF, parser.TELEMETRY_REF] fields = [self.current_config.airframes, self.current_config.settings, self.current_config.modules, self.current_config.flight_plan, self.current_config.radio, self.current_config.telemetry] widgets = self.change_config_widgets.copy() widgets.insert(2, self.ui.current_settings) match_found = False for keyword, field, widget in zip(keywords, fields, widgets): if keyword in filename: field.append(filename) widget.addItem(filename) last_item = widget.item(widget.count()-1) if widget == self.ui.current_settings: last_item.setFlags(Core.Qt.ItemIsUserCheckable | Core.Qt.ItemIsEnabled | Core.Qt.ItemIsSelectable) last_item.setCheckState(Core.Qt.Checked) widget.setCurrentItem(last_item) match_found = True break if match_found: self.configurations_changed[self.current_config.name] = \ self.current_config index = self.ui.current_configuration.currentIndex() self.ui.current_configuration.setItemIcon(index, self.changed_qicon) else: LOGGER.error("The selected file '%s' is not an acceptable file in" " a configuration !\n", filename) self.switch_to_tab(3) def remove_program_from_session(self): """ -> Remove the selected program from the current selected session. -> Add the current session to changed sessions => ask to save before quitting the application. """ if self.current_session is not None \ and self.current_program is not None: program_item, program = self.current_program self.current_session.programs.pop(program.name) self.update_programs_list() self.sessions_changed[self.current_session.name] = \ self.current_session index = self.ui.session.currentIndex() self.ui.session.setItemIcon(index, self.changed_qicon) self.switch_to_tab(2) def add_program_to_session(self, program): """ :param program: -> Add a program object (only a copy of a tool from the Tools menu for now) to the current selected session. """ if self.current_session is not None: new_program_name = parser.find_unused_item_name( self.current_session.programs, program.name) new_program = db.Program(new_program_name, program.command, program.options) self.current_session.programs[new_program.name] = new_program self.update_programs_list() new_item = self.ui.programs.findItems(new_program.name, Core.Qt.MatchExactly)[0] self.ui.programs.setCurrentItem(new_item) self.sessions_changed[self.current_session.name] = \ self.current_session index = self.ui.session.currentIndex() self.ui.session.setItemIcon(index, self.changed_qicon) self.switch_to_tab(2) def remove_option_from_program(self): """ -> Remove the selected option from the current selected program. """ if self.current_program is not None \ and self.current_option is not None: program_item, program = self.current_program option_item, option = self.current_option if type(option) is str: program.options.remove(option) else: program.options.remove(tuple(option.split())) self.update_program_options_list() self.sessions_changed[self.current_session.name] = \ self.current_session index = self.ui.session.currentIndex() self.ui.session.setItemIcon(index, self.changed_qicon) def add_option_to_program(self): """ -> Add an option to the current selected program. """ if self.current_session is not None \ and self.current_program is not None: program_item, program = self.current_program new_option = "" program.options.append(new_option) self.update_program_options_list() last_item = self.ui.options.item(self.ui.options.count()-1) self.ui.options.setCurrentItem(last_item) self.sessions_changed[self.current_session.name] = \ self.current_session index = self.ui.session.currentIndex() self.ui.session.setItemIcon(index, self.changed_qicon) def edit_current_option(self): """ -> Edit the selected option when enter pressed after changes. """ if self.current_program is not None \ and self.current_option is not None: program_item, program = self.current_program old_option_item, old_option = self.current_option old_option_index = program.options.index(old_option) program.options.remove(old_option) self.change_current_option() new_option_item, new_option = self.current_option program.options.insert(old_option_index, new_option) self.sessions_changed[self.current_session.name] = \ self.current_session index = self.ui.session.currentIndex() self.ui.session.setItemIcon(index, self.changed_qicon) ############################################################################### # [Hmi methods] Data management : XML writing methods def save_current_config(self): """ -> Update the new values of the current configuration in the corresponding XML file. """ if self.current_config.name in self.configurations_changed.keys(): self.configurations_changed.pop(self.current_config.name) index = self.ui.current_configuration.currentIndex() self.ui.current_configuration.setItemIcon(index, gui.generate_qicon()) LOGGER.info("Saving '%s' configuration...", self.current_config.name) parser.save_configuration(self.current_set.name, self.current_config) LOGGER.info("'%s' configuration saved.\n", self.current_config.name) else: LOGGER.info("Nothing to save, configuration already up-to-date.\n") def save_current_session(self): """ -> Update the new values of the current session in the corresponding 'control_panel.xml' file. -> Deal with the in-code defined sessions (Simulation, Replay). """ if self.current_session.name in self.sessions_changed.keys(): self.sessions_changed.pop(self.current_session.name) index = self.ui.session.currentIndex() self.ui.session.setItemIcon(index, gui.generate_qicon()) if self.current_session.name not in [parser.SIMULATION_NAME, parser.REPLAY_NAME]: LOGGER.info("Saving '%s' session...", self.current_session.name) parser.save_session(parser.CONTROL_PANEL_FILE, self.current_session) LOGGER.info("'%s' session saved.\n", self.current_session.name) else: LOGGER.error("'%s' is an in-code defined session. " "Impossible to save it !\n", self.current_session.name) print("'%s' is an in-code defined session. " "Impossible to save it !\n" % self.current_session.name) self.switch_to_tab(3) else: LOGGER.info("Nothing to save, session already up-to-date.\n") def save_all_changed_configurations(self): changed_configs = [_ for _ in self.configurations_changed.values()] for config in changed_configs: self.current_config = config self.save_current_config() def save_all_changed_sessions(self): changed_sessions = [_ for _ in self.sessions_changed.values()] for session in changed_sessions: self.current_session = session self.save_current_session() def save_all_changed_data(self): self.save_all_changed_configurations() self.save_all_changed_sessions() print("All changes in data saved.\n") self.save_cache_data() def save_cache_data(self): """ -> Change values in cache with the 'write' mode of the common parsing function. """ LOGGER.info("Cache updating...") print("Cache updating...") current_geometry_str = " ".join(map(str, [self.geometry().x(), self.geometry().y(), self.geometry().width(), self.geometry().height()])) current_log_filters_str = " ".join(map(str, [self.current_log_filter.level, self.current_log_filter.default, self.current_log_filter.info, self.current_log_filter.warning, self.current_log_filter.error])) parser.parse_cache_file("w", self.data.cache_file, geometry_str=current_geometry_str, set_str=self.current_set.name, config_str=self.current_config.name, target_str=self.current_target.name, log_filters_str=current_log_filters_str, device_str=self.current_device.name, session_str=self.current_session.name) LOGGER.info("Cache now up-to-date.\n") print("Cache now up-to-date.\n")