Skip to content
Snippets Groups Projects
main_window.py 21.5 KiB
Newer Older
  • Learn to ignore specific revisions
  • Andreas Bolin's avatar
    Andreas Bolin committed
    #!/usr/bin/env python3
    # -*- coding: utf-8 -*-
    
    Andreas Bolin's avatar
    Andreas Bolin committed
    """B-ASIC Scheduler-gui Module.
    
    Andreas Bolin's avatar
    Andreas Bolin committed
    
    
    Andreas Bolin's avatar
    Andreas Bolin committed
    Contains the scheduler-gui MainWindow class for scheduling operations in an SFG.
    
    Andreas Bolin's avatar
    Andreas Bolin committed
    
    Start main-window with start_gui().
    
    Andreas Bolin's avatar
    Andreas Bolin committed
    """
    
    import os
    import sys
    
    from typing                 import Union
    from pprint                 import pprint
    from copy                   import deepcopy
    from importlib.machinery    import SourceFileLoader
    
    import inspect
    
    Andreas Bolin's avatar
    Andreas Bolin committed
    
    
    Andreas Bolin's avatar
    Andreas Bolin committed
    
    
    Andreas Bolin's avatar
    Andreas Bolin committed
    from qtpy           import uic, QtCore, QtGui, QtWidgets
    
    Andreas Bolin's avatar
    Andreas Bolin committed
    from qtpy.QtCore    import QCoreApplication, Qt, Slot, Signal, QSettings, QStandardPaths
    
    Andreas Bolin's avatar
    Andreas Bolin committed
    from qtpy.QtGui     import QCloseEvent
    
    from qtpy.QtWidgets import (
    
    Andreas Bolin's avatar
    Andreas Bolin committed
        QApplication, QMainWindow, QMessageBox, QFileDialog, QInputDialog, QCheckBox, QAbstractButton,
    
    Andreas Bolin's avatar
    Andreas Bolin committed
    
    
    Andreas Bolin's avatar
    Andreas Bolin committed
    # QGraphics and QPainter imports
    
    from qtpy.QtCore    import QRectF, QByteArray
    from qtpy.QtGui     import QIcon
    from qtpy.QtWidgets import QGraphicsScene, QGraphicsItemGroup
    
    Andreas Bolin's avatar
    Andreas Bolin committed
    
    
    # B-ASIC
    import logger
    
    Andreas Bolin's avatar
    Andreas Bolin committed
    from b_asic.schedule            import Schedule
    
    Andreas Bolin's avatar
    Andreas Bolin committed
    from b_asic.graph_component     import GraphComponent
    
    Andreas Bolin's avatar
    Andreas Bolin committed
    from graphics_graph_item        import GraphicsGraphItem
    
    Andreas Bolin's avatar
    Andreas Bolin committed
    from graphics_axes_item         import GraphicsAxesItem
    
    Andreas Bolin's avatar
    Andreas Bolin committed
    from graphics_component_item    import GraphicsComponentItem
    
    Andreas Bolin's avatar
    Andreas Bolin committed
    # if sys.version_info >= (3, 9):
    #     List = list
    #     #Dict = dict
    
    Andreas Bolin's avatar
    Andreas Bolin committed
    log = logger.getLogger()
    
    Andreas Bolin's avatar
    Andreas Bolin committed
    sys.excepthook = logger.handle_exceptions
    
    Andreas Bolin's avatar
    Andreas Bolin committed
    
    
    Andreas Bolin's avatar
    Andreas Bolin committed
    
    
    if __debug__:
        log.setLevel('DEBUG')
    
    
    Andreas Bolin's avatar
    Andreas Bolin committed
    if __debug__:
    
        # Print some system version information
        QT_API = os.environ.get('QT_API')
    
    Andreas Bolin's avatar
    Andreas Bolin committed
        log.debug('Qt version (runtime):     {}'.format(QtCore.qVersion()))
        log.debug('Qt version (compiletime): {}'.format(QtCore.__version__))
        log.debug('QT_API:                   {}'.format(QT_API))
    
        if QT_API.lower().startswith('pyside'):
            import PySide2
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            log.debug('PySide version:           {}'.format(PySide2.__version__))
    
        if QT_API.lower().startswith('pyqt'):
    
            from qtpy.QtCore import PYQT_VERSION_STR
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            log.debug('PyQt version:             {}'.format(PYQT_VERSION_STR))
        log.debug('QtPy version:             {}'.format(qtpy.__version__))
    
    Andreas Bolin's avatar
    Andreas Bolin committed
        
    
        # Autocompile the .ui form to a python file.
        try:                                        # PyQt5, try autocompile
            from qtpy.uic import compileUiDir
    
            uic.compileUiDir('.', map=(lambda dir,file: (dir, 'ui_' + file)))
    
        except:
            try:                                    # PySide2, try manual compile
                import subprocess
    
    Andreas Bolin's avatar
    Andreas Bolin committed
                os_ = sys.platform
                if os_.startswith('linux'):
    
    Andreas Bolin's avatar
    Andreas Bolin committed
                    cmds = ['pyside2-uic -o ui_main_window.py main_window.ui']
    
                    for cmd in cmds:
                        subprocess.call(cmd.split())
                else:
                    #TODO: Implement (startswith) 'win32', 'darwin' (MacOs)
                    raise SystemExit
            except:                                 # Compile failed, look for pre-compiled file
    
    Andreas Bolin's avatar
    Andreas Bolin committed
                try:
    
    Andreas Bolin's avatar
    Andreas Bolin committed
                    from ui_main_window import Ui_MainWindow
    
                except:                             # Everything failed, exit
    
    Andreas Bolin's avatar
    Andreas Bolin committed
                    log.exception("Could not import 'Ui_MainWindow'.")
                    log.exception("Can't autocompile under", QT_API, "eviroment. Try to manual compile 'main_window.ui' to 'ui/main_window_ui.py'")
    
    Andreas Bolin's avatar
    Andreas Bolin committed
    
    
    Andreas Bolin's avatar
    Andreas Bolin committed
    
    
    Andreas Bolin's avatar
    Andreas Bolin committed
    sys.path.insert(0, 'icons/')                # Needed for the compiled '*_rc.py' files in 'ui_*.py' files
    from ui_main_window import Ui_MainWindow    # Only availible when the form (.ui) is compiled
    
    # The following QCoreApplication values is used for QSettings among others
    
    Andreas Bolin's avatar
    Andreas Bolin committed
    QCoreApplication.setOrganizationName('Linöping University')
    QCoreApplication.setOrganizationDomain('liu.se')
    QCoreApplication.setApplicationName('B-ASIC Scheduler')
    #QCoreApplication.setApplicationVersion(__version__)     # TODO: read from packet __version__
    
    
    Andreas Bolin's avatar
    Andreas Bolin committed
    
    
    
    Andreas Bolin's avatar
    Andreas Bolin committed
    
    
    class MainWindow(QMainWindow, Ui_MainWindow):
    
    Andreas Bolin's avatar
    Andreas Bolin committed
        """Schedule of an SFG with scheduled Operations."""
    
    Andreas Bolin's avatar
    Andreas Bolin committed
        _scene: QGraphicsScene
    
        _schedule: Union[Schedule, None]
    
    Andreas Bolin's avatar
    Andreas Bolin committed
        _graph: Union[GraphicsGraphItem, None]
    
    Andreas Bolin's avatar
    Andreas Bolin committed
        _scale: float
    
    Andreas Bolin's avatar
    Andreas Bolin committed
        _debug_rects: QGraphicsItemGroup
    
    Andreas Bolin's avatar
    Andreas Bolin committed
        _splitter_pos: int
        _splitter_min: int
    
    Andreas Bolin's avatar
    Andreas Bolin committed
    
    
    Andreas Bolin's avatar
    Andreas Bolin committed
        
    
    Andreas Bolin's avatar
    Andreas Bolin committed
        def __init__(self):
    
            """Initialize Scheduler-gui."""
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            super().__init__()
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            self._graph = None
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            self._debug_rects = None
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            QIcon.setThemeName('breeze')
    
            log.debug('themeName: \'{}\''.format(QIcon.themeName()))
            log.debug('themeSearchPaths: {}'.format(QIcon.themeSearchPaths()))
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            self.setupUi(self)
            self._read_settings()
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            self._init_ui()
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            self._init_graphics()
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            
    
    Andreas Bolin's avatar
    Andreas Bolin committed
    
    
    Andreas Bolin's avatar
    Andreas Bolin committed
        def _init_ui(self) -> None:
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            """Initialize the ui"""
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            # Connect signals to slots
    
            self.menu_load_from_file.triggered      .connect(self._load_schedule_from_pyfile)
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            self.menu_close_schedule.triggered      .connect(self.close_schedule)
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            self.menu_save          .triggered      .connect(self.save)
            self.menu_save_as       .triggered      .connect(self.save_as)
    
            self.menu_quit          .triggered      .connect(self.close)
    
            self.menu_node_info     .triggered      .connect(self.show_info_table)
            self.menu_exit_dialog   .triggered      .connect(self.hide_exit_dialog)
            self.actionT            .triggered      .connect(self._actionTbtn)
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            self.splitter           .splitterMoved  .connect(self._splitter_moved)
    
    Andreas Bolin's avatar
    Andreas Bolin committed
    
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            # Setup event member functions
            self.closeEvent = self._close_event
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            # Setup info table
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            self.info_table.setSpan(0, 0, 1, 2)                         # Span 'Schedule' over 2 columns
            self.info_table.setSpan(1, 0, 1, 2)                         # Span 'Operator' over 2 columns
    
    Andreas Bolin's avatar
    Andreas Bolin committed
    
            # Init central-widget splitter
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            self._splitter_min = self.splitter.minimumSizeHint().height()
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            self.splitter.setStretchFactor(0, 1)
            self.splitter.setStretchFactor(1, 0)
            self.splitter.setCollapsible(0, False)
            self.splitter.setCollapsible(1, True)
    
    Andreas Bolin's avatar
    Andreas Bolin committed
    
    
    Andreas Bolin's avatar
    Andreas Bolin committed
        def _init_graphics(self) -> None:
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            """Initialize the QGraphics framework"""
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            self._scene = QGraphicsScene()
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            self._scene.addRect(0, 0, 0, 0)             # dummy rect to be able to setPos() graph
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            self.view.setScene(self._scene)
            self.view.scale(self._scale, self._scale)
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            GraphicsComponentItem._scale = self._scale
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            GraphicsAxesItem._scale = self._scale
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            self._scene.sceneRectChanged.connect(self.shrink_scene_to_min_size)
    
    Andreas Bolin's avatar
    Andreas Bolin committed
    
    
    Andreas Bolin's avatar
    Andreas Bolin committed
       
    
    Andreas Bolin's avatar
    Andreas Bolin committed
        @property
        def schedule(self) -> Schedule:
    
            """Get the current schedule."""
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            return self._schedule
    
    Andreas Bolin's avatar
    Andreas Bolin committed
        
    
    Andreas Bolin's avatar
    Andreas Bolin committed
    
        ###############
        #### Slots ####
        ###############
    
    Andreas Bolin's avatar
    Andreas Bolin committed
        @Slot()
    
        def _actionTbtn(self) -> None:
            # TODO: remove
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            self.schedule.plot_schedule()
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            print(f'filtersChildEvents(): {self._graph.filtersChildEvents()}')
    
            # self._printButtonPressed('callback_pushButton()')
    
    Andreas Bolin's avatar
    Andreas Bolin committed
        
        @Slot()
    
        def _load_schedule_from_pyfile(self) -> None:
    
            """SLOT() for SIGNAL(menu_load_from_file.triggered)
            Load a python script as a module and search for a Schedule object. If
            found, opens it."""
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            settings = QSettings()
            last_file = settings.value('mainwindow/last_opened_file', QStandardPaths.standardLocations(QStandardPaths.HomeLocation)[0], str)
            if not os.path.exists(last_file):               # if filename does not exist
                last_file = os.path.dirname(last_file) + '/'
                if not os.path.exists(last_file):           # if path does not exist
                    last_file = QStandardPaths.standardLocations(QStandardPaths.HomeLocation)[0]
    
            abs_path_filename, _ = QFileDialog.getOpenFileName(self,
    
    Andreas Bolin's avatar
    Andreas Bolin committed
                                                            self.tr("Open python file"),
    
    Andreas Bolin's avatar
    Andreas Bolin committed
                                                            last_file,
    
    Andreas Bolin's avatar
    Andreas Bolin committed
                                                            self.tr("Python Files (*.py *.py3)"))
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            
    
            if not abs_path_filename:       # return if empty filename (QFileDialog was canceled)
                return
            log.debug('abs_path_filename = {}.'.format(abs_path_filename))
    
    Andreas Bolin's avatar
    Andreas Bolin committed
    
    
            module_name = inspect.getmodulename(abs_path_filename)
            if not module_name:             # return if empty module name
                log.error('Could not load module from file \'{}\'.'.format(abs_path_filename))
                return 
            
            try:
                module = SourceFileLoader(module_name, abs_path_filename).load_module()
            except:
                log.exception('Exception occurred. Could not load module from file \'{}\'.'.format(abs_path_filename))
                return
    
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            schedule_obj_list = dict(inspect.getmembers(module, (lambda x: isinstance(x, Schedule))))
    
            
            if not schedule_obj_list:       # return if no Schedule objects in script
                QMessageBox.warning(self,
                                    self.tr('File not found'),
                                    self.tr('Could not find any Schedule object in file \'{}\'.')
                                    .format(os.path.basename(abs_path_filename)))
                log.info('Could not find any Schedule object in file \'{}\'.'
                         .format(os.path.basename(abs_path_filename)))
    
    Andreas Bolin's avatar
    Andreas Bolin committed
                del module
    
                return
            
            ret_tuple = QInputDialog.getItem(self,
                                             self.tr('Load object'),
    
    Andreas Bolin's avatar
    Andreas Bolin committed
                                             self.tr('Found the following Schedule object(s) in file.\n\n'
    
                                                     'Select an object to proceed:'),
    
    Andreas Bolin's avatar
    Andreas Bolin committed
                                             schedule_obj_list.keys(), 0, False)
    
    
            if not ret_tuple[1]:                  # User canceled the operation
                log.debug('Load schedule operation: user canceled')
    
    Andreas Bolin's avatar
    Andreas Bolin committed
                del module
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            self.open(schedule_obj_list[ret_tuple[0]])
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            del module
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            settings.setValue("mainwindow/last_opened_file", abs_path_filename)
    
    
    Andreas Bolin's avatar
    Andreas Bolin committed
        @Slot()
        def close_schedule(self) -> None:
    
            """SLOT() for SIGNAL(menu_close_schedule.triggered)
            Closes current schedule."""
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            if self._graph:
    
                self._graph.signals.component_selected.disconnect(self.info_table_update_component)
                self._graph.signals.schedule_time_changed.disconnect(self.info_table_update_schedule)
    
    Andreas Bolin's avatar
    Andreas Bolin committed
                self._graph.removeSceneEventFilters(self._graph.event_items)
                self._scene.removeItem(self._graph)
                self.menu_close_schedule.setEnabled(False)
                del self._graph
                self._graph = None
                del self._schedule
                self._schedule = None
                self.info_table_clear()
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            
    
    Andreas Bolin's avatar
    Andreas Bolin committed
        @Slot()
    
    Andreas Bolin's avatar
    Andreas Bolin committed
        def save(self) -> None:
    
            """SLOT() for SIGNAL(menu_save.triggered)
            This method save an schedule."""
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            #TODO: all
    
            self._printButtonPressed('save_schedule()')
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            self.update_statusbar(self.tr('Schedule saved successfully'))
    
    Andreas Bolin's avatar
    Andreas Bolin committed
    
        @Slot()
        def save_as(self) -> None:
    
            """SLOT() for SIGNAL(menu_save_as.triggered)
            This method save as an schedule."""
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            #TODO: all
    
            self._printButtonPressed('save_schedule()')
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            self.update_statusbar(self.tr('Schedule saved successfully'))
    
    Andreas Bolin's avatar
    Andreas Bolin committed
        
    
    Andreas Bolin's avatar
    Andreas Bolin committed
        @Slot(bool)
    
        def show_info_table(self, checked: bool) -> None:
            """SLOT(bool) for SIGNAL(menu_node_info.triggered)
            Takes in a boolean and hide or show the info table accordingly with
            'checked'."""
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            # Note: splitter handler index 0 is a hidden splitter handle far most left, use index 1
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            # settings = QSettings()
            _, max = self.splitter.getRange(1)    # tuple(min, max)
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            if checked:
    
    Andreas Bolin's avatar
    Andreas Bolin committed
                if self._splitter_pos < self._splitter_min:
                    self.splitter.moveSplitter(max - self._splitter_min, 1)
                else:
                    self.splitter.moveSplitter(max - self._splitter_pos, 1)
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            else:
    
    Andreas Bolin's avatar
    Andreas Bolin committed
                self.splitter.moveSplitter(max, 1)
    
        def hide_exit_dialog(self, checked: bool) -> None:
            """SLOT(bool) for SIGNAL(menu_exit_dialog.triggered)
            Takes in a boolean and stores 'checked' in 'hide_exit_dialog' item in
            settings."""
    
            s = QSettings()
            s.setValue("mainwindow/hide_exit_dialog", checked)
    
    
    Andreas Bolin's avatar
    Andreas Bolin committed
        @Slot(int, int)
    
    Andreas Bolin's avatar
    Andreas Bolin committed
        def _splitter_moved(self, pos: int, index: int) -> None:
    
            """SLOT(int, int) for SIGNAL(splitter.splitterMoved)
            Callback method used to check if the right widget (info window) 
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            has collapsed. Update the checkbutton accordingly."""
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            width = self.splitter.sizes()[1]
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            if width == 0:
                if self.menu_node_info.isChecked() is True:
                    self.menu_node_info.setChecked(False)
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            else:
    
    Andreas Bolin's avatar
    Andreas Bolin committed
                if self.menu_node_info.isChecked() is False:
                    self.menu_node_info.setChecked(True)
                self._splitter_pos = width
    
    
    Andreas Bolin's avatar
    Andreas Bolin committed
        @Slot(str)
    
    Andreas Bolin's avatar
    Andreas Bolin committed
        def info_table_update_component(self, op_id: str) -> None:
    
            """SLOT(str) for SIGNAL(_graph.signals.component_selected)
            Taked in an operator-id, first clears the 'Operator' part of the info
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            table and then fill in the table with new values from the operator
            associated with 'op_id'."""
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            self.info_table_clear_component()
            self._info_table_fill_component(op_id)
    
    Andreas Bolin's avatar
    Andreas Bolin committed
        
    
    Andreas Bolin's avatar
    Andreas Bolin committed
        @Slot()
        def info_table_update_schedule(self) -> None:
    
            """SLOT() for SIGNAL(_graph.signals.schedule_time_changed)
            Updates the 'Schedule' part of the info table."""
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            self.info_table.item(1, 1).setText(str(self.schedule.schedule_time))
            
        @Slot(QRectF)
        def shrink_scene_to_min_size(self, rect: QRectF) -> None:
    
            """SLOT(QRectF) for SIGNAL(_scene.sceneRectChanged)
            Takes in a QRectF (unused) and shrink the scene bounding rectangle to
            it's minimum size, when the bounding rectangle signals a change in
            geometry."""
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            self._scene.setSceneRect(self._scene.itemsBoundingRect())
    
    Andreas Bolin's avatar
    Andreas Bolin committed
    
    
    Andreas Bolin's avatar
    Andreas Bolin committed
        ################
        #### Events ####
        ################
    
    Andreas Bolin's avatar
    Andreas Bolin committed
        def _close_event(self, event: QCloseEvent) -> None:
    
            """EVENT: Replaces QMainWindow default closeEvent(QCloseEvent) event. Takes
            in a QCloseEvent and display an exit dialog, depending on
            'hide_exit_dialog' in settings."""
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            s = QSettings()
            hide_dialog = s.value('mainwindow/hide_exit_dialog', False, bool)
            ret = QMessageBox.StandardButton.Yes
            
            if not hide_dialog:
                box = QMessageBox(self)
                box.setWindowTitle(self.tr('Confirm Exit'))
                box.setText('<h3>' + self.tr('Confirm Exit') + '</h3><p><br>' +
                            self.tr('Are you sure you want to exit?') +
                            '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br></p>')
    
    Andreas Bolin's avatar
    Andreas Bolin committed
                box.setIcon(QMessageBox.Question)
    
    Andreas Bolin's avatar
    Andreas Bolin committed
                box.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
    
                buttons: list[QAbstractButton] = box.buttons()
                buttons[0].setText(self.tr('&Exit'))
                buttons[1].setText(self.tr('&Cancel'))
    
    Andreas Bolin's avatar
    Andreas Bolin committed
                checkbox = QCheckBox(self.tr('Don\'t ask again'))
    
    Andreas Bolin's avatar
    Andreas Bolin committed
                box.setCheckBox(checkbox)
                ret = box.exec_()
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            if ret == QMessageBox.StandardButton.Yes:
    
    Andreas Bolin's avatar
    Andreas Bolin committed
                if not hide_dialog:
                    s.setValue('mainwindow/hide_exit_dialog', checkbox.isChecked())
                self._write_settings()
    
                log.info('Exit: {}'.format(os.path.basename(__file__)))
    
    Andreas Bolin's avatar
    Andreas Bolin committed
                event.accept()
            else:
                event.ignore()
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            
    
    Andreas Bolin's avatar
    Andreas Bolin committed
        #################################
        #### Helper member functions ####
        #################################
    
        def _printButtonPressed(self, func_name: str) -> None:
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            #TODO: remove        
    
    Andreas Bolin's avatar
    Andreas Bolin committed
    
            alert = QMessageBox(self)
            alert.setText("Called from " + func_name + '!')
            alert.exec_()
    
    Andreas Bolin's avatar
    Andreas Bolin committed
        
        def open(self, schedule: Schedule) -> None:
            """Takes in an Schedule and creates a GraphicsGraphItem object."""
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            self.close_schedule()
            self._schedule = deepcopy(schedule)
            self._graph = GraphicsGraphItem(self.schedule)
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            self._graph.setPos(1/self._scale, 1/self._scale)
            self.menu_close_schedule.setEnabled(True)
            self._scene.addItem(self._graph)
            self._graph.installSceneEventFilters(self._graph.event_items)
    
            self._graph.signals.component_selected.connect(self.info_table_update_component)
            self._graph.signals.schedule_time_changed.connect(self.info_table_update_schedule)
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            self.info_table_fill_schedule(self.schedule)
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            self.update_statusbar(self.tr('Schedule loaded successfully'))
    
    Andreas Bolin's avatar
    Andreas Bolin committed
    
    
    Andreas Bolin's avatar
    Andreas Bolin committed
        def update_statusbar(self, msg: str) -> None:
    
            """Takes in an str and write 'msg' to the statusbar with temporarily policy."""
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            self.statusbar.showMessage(msg)
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            
        def _write_settings(self) -> None:
            """Write settings from MainWindow to Settings."""
            s = QSettings()
    
            s.setValue('mainwindow/maximized',      self.isMaximized())     # window: maximized, in X11 - alwas False
            s.setValue('mainwindow/pos',            self.pos())             # window: pos
            s.setValue('mainwindow/size',           self.size())            # window: size
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            s.setValue('mainwindow/state',          self.saveState())       # toolbars, dockwidgets: pos, size
            s.setValue('mainwindow/menu/node_info', self.menu_node_info.isChecked())
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            s.setValue('mainwindow/splitter/state', self.splitter.saveState())
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            s.setValue('mainwindow/splitter/pos',   self.splitter.sizes()[1])
    
    Andreas Bolin's avatar
    Andreas Bolin committed
    
            if s.isWritable():
                log.debug('Settings written to \'{}\'.'.format(s.fileName()))
            else:
                log.warning('Settings cant be saved to file, read-only.')
        
        def _read_settings(self) -> None:
            """Read settings from Settings to MainWindow."""
            s = QSettings()
    
            if s.value('mainwindow/maximized', defaultValue=False, type=bool):
                self.showMaximized()
            else:
    
                self.move(                      s.value('mainwindow/pos', self.pos()))
                self.resize(                    s.value('mainwindow/size', self.size()))
            self.restoreState(                  s.value('mainwindow/state', QByteArray()))
            self.menu_node_info.setChecked(     s.value('mainwindow/menu/node_info', True, bool))
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            self.splitter.restoreState(         s.value('mainwindow/splitter/state', QByteArray()))
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            self._splitter_pos = (              s.value('mainwindow/splitter/pos', 200, int))
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            self.menu_exit_dialog.setChecked(   s.value('mainwindow/hide_exit_dialog', False, bool))
    
    Andreas Bolin's avatar
    Andreas Bolin committed
    
            log.debug('Settings read from \'{}\'.'.format(s.fileName()))
    
    Andreas Bolin's avatar
    Andreas Bolin committed
        
    
    Andreas Bolin's avatar
    Andreas Bolin committed
        def info_table_fill_schedule(self, schedule: Schedule) -> None:
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            """Takes in a Schedule and fill in the 'Schedule' part of the info table
            with values from 'schedule'"""
            self.info_table.insertRow(1)
            self.info_table.insertRow(1)
            self.info_table.insertRow(1)
            self.info_table.setItem(1, 0, QTableWidgetItem('Schedule Time'))
            self.info_table.setItem(2, 0, QTableWidgetItem('Cyclic'))
            self.info_table.setItem(3, 0, QTableWidgetItem('Resolution'))
            self.info_table.setItem(1, 1, QTableWidgetItem(str(schedule.schedule_time)))
            self.info_table.setItem(2, 1, QTableWidgetItem(str(schedule.cyclic)))
            self.info_table.setItem(3, 1, QTableWidgetItem(str(schedule.resolution)))
    
    
    Andreas Bolin's avatar
    Andreas Bolin committed
        def _info_table_fill_component(self, op_id: str) -> None:
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            """Taked in an operator-id and fill in the 'Operator' part of the info
            table with values from the operator associated with 'op_id'."""
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            op: GraphComponent = self.schedule.sfg.find_by_id(op_id)
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            si = self.info_table.rowCount()                                         # si = start index
    
            if op.graph_id:
                self.info_table.insertRow(si)
                self.info_table.setItem(si, 0, QTableWidgetItem('ID'))
                self.info_table.setItem(si, 1, QTableWidgetItem(str(op.graph_id)))
                si += 1
            if op.name:
                self.info_table.insertRow(si)
                self.info_table.setItem(si, 0, QTableWidgetItem('Name'))
                self.info_table.setItem(si, 1, QTableWidgetItem(str(op.name)))
                si += 1
            
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            for key, value in op.params.items():
    
    Andreas Bolin's avatar
    Andreas Bolin committed
                self.info_table.insertRow(si)
                self.info_table.setItem(si, 0, QTableWidgetItem(key))
                self.info_table.setItem(si, 1, QTableWidgetItem(str(value)))
                si += 1
                
        
    
    Andreas Bolin's avatar
    Andreas Bolin committed
        def info_table_clear(self) -> None:
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            """Clears the info table."""
    
    Andreas Bolin's avatar
    Andreas Bolin committed
            self.info_table_clear_component()
            self.info_table_clear_schedule()
    
    Andreas Bolin's avatar
    Andreas Bolin committed
        
    
    Andreas Bolin's avatar
    Andreas Bolin committed
        def info_table_clear_schedule(self) -> None:
            """Clears the schedule part of the info table."""
            row = self.info_table.findItems('Operator', Qt.MatchExactly)
            if row:
                row = row[0].row()
    
    Andreas Bolin's avatar
    Andreas Bolin committed
                    for _ in range(3):
                        self.info_table.removeRow(1)
            else:
                log.error("'Operator' not found in info table. It may have been renamed.")
      
        def info_table_clear_component(self) -> None:
            """Clears the component part of the info table."""
            row = self.info_table.findItems('Operator', Qt.MatchExactly)
            if row:
                row = row[0].row()
                for _ in range(self.info_table.rowCount() - row + 1):
                        self.info_table.removeRow(row + 1)
            else:
                log.error("'Operator' not found in info table. It may have been renamed.")
    
    Andreas Bolin's avatar
    Andreas Bolin committed
    
    
    Andreas Bolin's avatar
    Andreas Bolin committed
    
    
    Andreas Bolin's avatar
    Andreas Bolin committed
    def start_gui():
        app = QApplication(sys.argv)
    
    Andreas Bolin's avatar
    Andreas Bolin committed
        window = MainWindow()
    
    Andreas Bolin's avatar
    Andreas Bolin committed
        window.show()
    
    Andreas Bolin's avatar
    Andreas Bolin committed
        sys.exit(app.exec_())
    
    if __name__ == "__main__":
        start_gui()