Skip to content
Snippets Groups Projects
main_window.py 63 KiB
Newer Older
Andreas Bolin's avatar
Andreas Bolin committed
#!/usr/bin/env python3
B-ASIC Scheduler-GUI Module.
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
"""
Oscar Gustafsson's avatar
Oscar Gustafsson committed
import inspect
Andreas Bolin's avatar
Andreas Bolin committed
import os
import pickle
Andreas Bolin's avatar
Andreas Bolin committed
import sys
from collections import defaultdict, deque
Oscar Gustafsson's avatar
Oscar Gustafsson committed
from copy import deepcopy
from importlib.machinery import SourceFileLoader
from typing import TYPE_CHECKING, Deque, Dict, List, Optional, cast, overload
Andreas Bolin's avatar
Andreas Bolin committed

# Qt/qtpy
import qtpy
import qtpy.QtCore
Andreas Bolin's avatar
Andreas Bolin committed

# QGraphics and QPainter imports
Oscar Gustafsson's avatar
Oscar Gustafsson committed
from qtpy.QtCore import (
    QByteArray,
    QCoreApplication,
Oscar Gustafsson's avatar
Oscar Gustafsson committed
    QRectF,
    QSettings,
    QStandardPaths,
    Qt,
    Slot,
)
from qtpy.QtGui import QCloseEvent, QColor, QFont, QIcon, QIntValidator, QPalette
Oscar Gustafsson's avatar
Oscar Gustafsson committed
from qtpy.QtWidgets import (
    QAbstractButton,
Oscar Gustafsson's avatar
Oscar Gustafsson committed
    QApplication,
    QCheckBox,
    QColorDialog,
    QDialog,
    QDialogButtonBox,
Oscar Gustafsson's avatar
Oscar Gustafsson committed
    QFileDialog,
    QFontDialog,
Oscar Gustafsson's avatar
Oscar Gustafsson committed
    QGraphicsItemGroup,
    QGraphicsScene,
    QGroupBox,
    QHBoxLayout,
Oscar Gustafsson's avatar
Oscar Gustafsson committed
    QInputDialog,
    QLabel,
    QLineEdit,
Oscar Gustafsson's avatar
Oscar Gustafsson committed
    QMainWindow,
    QMessageBox,
    QTableWidgetItem,
    QVBoxLayout,
Oscar Gustafsson's avatar
Oscar Gustafsson committed
)
Andreas Bolin's avatar
Andreas Bolin committed

# B-ASIC
import b_asic.logger as logger
from b_asic._version import __version__
Oscar Gustafsson's avatar
Oscar Gustafsson committed
from b_asic.graph_component import GraphComponent, GraphID
from b_asic.gui_utils.about_window import AboutWindow
from b_asic.gui_utils.color_button import ColorButton
Oscar Gustafsson's avatar
Oscar Gustafsson committed
from b_asic.gui_utils.icons import get_icon
from b_asic.gui_utils.mpl_window import MPLWindow
Oscar Gustafsson's avatar
Oscar Gustafsson committed
from b_asic.schedule import Schedule
from b_asic.scheduler_gui._preferences import (
    Active_Color,
    ColorDataType,
    Execution_Time_Color,
    Font,
    Latency_Color,
    Signal_Color,
    Signal_Warning_Color,
)
from b_asic.scheduler_gui.axes_item import AxesItem
from b_asic.scheduler_gui.operation_item import OperationItem
from b_asic.scheduler_gui.scheduler_item import SchedulerItem
from b_asic.scheduler_gui.ui_main_window import Ui_MainWindow
Andreas Bolin's avatar
Andreas Bolin committed

Oscar Gustafsson's avatar
Oscar Gustafsson committed
if TYPE_CHECKING:
    from logging import Logger

log: "Logger" = logger.getLogger(__name__, "scheduler-gui.log")
Andreas Bolin's avatar
Andreas Bolin committed
sys.excepthook = logger.handle_exceptions


Andreas Bolin's avatar
Andreas Bolin committed
if __debug__:
Oscar Gustafsson's avatar
Oscar Gustafsson committed
    log.setLevel("DEBUG")
Andreas Bolin's avatar
Andreas Bolin committed

if __debug__:
    # Print some system version information
Oscar Gustafsson's avatar
Oscar Gustafsson committed
    from qtpy import QtCore

Oscar Gustafsson's avatar
Oscar Gustafsson committed
    QT_API = os.environ.get("QT_API", "")
    log.debug(f"Qt version (runtime):      {QtCore.qVersion()}")
    log.debug(f"Qt version (compile time): {QtCore.__version__}")
    log.debug(f"QT_API:                    {QT_API}")
Oscar Gustafsson's avatar
Oscar Gustafsson committed
    if QT_API.lower().startswith("pyside"):
        import PySide6
        log.debug(f"PySide version:           {PySide6.__version__}")
Oscar Gustafsson's avatar
Oscar Gustafsson committed
    if QT_API.lower().startswith("pyqt"):
Andreas Bolin's avatar
Andreas Bolin committed
        from qtpy.QtCore import PYQT_VERSION_STR
        log.debug(f"PyQt version:             {PYQT_VERSION_STR}")
    log.debug(f"QtPy version:             {qtpy.__version__}")
Andreas Bolin's avatar
Andreas Bolin committed


# The following QCoreApplication values is used for QSettings among others
QCoreApplication.setOrganizationName("Linköping University")
Oscar Gustafsson's avatar
Oscar Gustafsson committed
QCoreApplication.setOrganizationDomain("liu.se")
QCoreApplication.setApplicationName("B-ASIC Scheduling GUI")
QCoreApplication.setApplicationVersion(__version__)
class ScheduleMainWindow(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: Optional[Schedule]
    _graph: Optional[SchedulerItem]
Andreas Bolin's avatar
Andreas Bolin committed
    _scale: float
    _debug_rectangles: QGraphicsItemGroup
Andreas Bolin's avatar
Andreas Bolin committed
    _splitter_pos: int
    _splitter_min: int
    _color_per_type: Dict[str, QColor] = dict()
    converted_colorPerType: Dict[str, str] = dict()
Andreas Bolin's avatar
Andreas Bolin committed

    def __init__(self):
        """Initialize Scheduler-GUI."""
Andreas Bolin's avatar
Andreas Bolin committed
        super().__init__()
        self._schedule = None
        self._graph = None
        self._scale = 75.0
        self._debug_rectangles = None
        self._zoom = 1.0
Andreas Bolin's avatar
Andreas Bolin committed

        self.setupUi(self)
        self._read_settings()
        self._init_ui()
        self._file_name = None
        self._show_incorrect_execution_time = True
Oscar Gustafsson's avatar
Oscar Gustafsson committed
        self._show_port_numbers = True
        self._execution_time_for_variables = None
        self._execution_time_plot_dialogs = defaultdict(lambda: None)
        self._ports_accesses_for_storage = None
        self._color_changed_perType = False
        self.changed_operation_colors: Dict[str, QColor] = dict()
Oscar Gustafsson's avatar
Oscar Gustafsson committed
        # Recent files
        self._max_recent_files = 4
        self._recent_files_actions: List[QAction] = []
        self._recent_file_paths: Deque[str] = deque(maxlen=self._max_recent_files)
        self._create_recent_file_actions_and_menus()

Andreas Bolin's avatar
Andreas Bolin committed
        self._init_graphics()

    def _init_ui(self) -> None:
        """Initialize the ui"""

        # Connect signals to slots
        self.menu_load_from_file.triggered.connect(self._load_schedule_from_pyfile)
        self.menu_load_from_file.setIcon(get_icon('import'))
        self.menu_open.setIcon(get_icon('open'))
        self.menu_open.triggered.connect(self.open_schedule)
Oscar Gustafsson's avatar
Oscar Gustafsson committed
        self.menu_close_schedule.triggered.connect(self.close_schedule)
        self.menu_close_schedule.setIcon(get_icon('close'))
Oscar Gustafsson's avatar
Oscar Gustafsson committed
        self.menu_save.triggered.connect(self.save)
Oscar Gustafsson's avatar
Oscar Gustafsson committed
        self.menu_save.setIcon(get_icon('save'))
Oscar Gustafsson's avatar
Oscar Gustafsson committed
        self.menu_save_as.triggered.connect(self.save_as)
Oscar Gustafsson's avatar
Oscar Gustafsson committed
        self.menu_save_as.setIcon(get_icon('save-as'))
        self.actionPreferences.triggered.connect(self.Preferences_Dialog_clicked)
Oscar Gustafsson's avatar
Oscar Gustafsson committed
        self.menu_quit.triggered.connect(self.close)
Oscar Gustafsson's avatar
Oscar Gustafsson committed
        self.menu_quit.setIcon(get_icon('quit'))
Oscar Gustafsson's avatar
Oscar Gustafsson committed
        self.menu_node_info.triggered.connect(self.show_info_table)
Oscar Gustafsson's avatar
Oscar Gustafsson committed
        self.menu_node_info.setIcon(get_icon('info'))
Oscar Gustafsson's avatar
Oscar Gustafsson committed
        self.menu_exit_dialog.triggered.connect(self.hide_exit_dialog)
        self.actionReorder.triggered.connect(self._action_reorder)
Oscar Gustafsson's avatar
Oscar Gustafsson committed
        self.actionReorder.setIcon(get_icon('reorder'))
        self.actionStatus_bar.triggered.connect(self._toggle_statusbar)
        self.action_incorrect_execution_time.setIcon(get_icon('warning'))
        self.action_incorrect_execution_time.triggered.connect(
            self._toggle_execution_time_warning
        )
Oscar Gustafsson's avatar
Oscar Gustafsson committed
        self.action_show_port_numbers.setIcon(get_icon('port-numbers'))
        self.action_show_port_numbers.triggered.connect(self._toggle_port_number)
Oscar Gustafsson's avatar
Oscar Gustafsson committed
        self.actionPlot_schedule.setIcon(get_icon('plot-schedule'))
        self.actionPlot_schedule.triggered.connect(self._plot_schedule)
        self.action_view_variables.triggered.connect(
            self._show_execution_times_for_variables
        )
        self.action_view_port_accesses.triggered.connect(
            self._show_ports_accesses_for_storage
        )
Oscar Gustafsson's avatar
Oscar Gustafsson committed
        self.actionZoom_to_fit.setIcon(get_icon('zoom-to-fit'))
        self.actionZoom_to_fit.triggered.connect(self._zoom_to_fit)
        self.actionToggle_full_screen.setIcon(get_icon('full-screen'))
Oscar Gustafsson's avatar
Oscar Gustafsson committed
        self.actionToggle_full_screen.triggered.connect(self._toggle_fullscreen)
Oscar Gustafsson's avatar
Oscar Gustafsson committed
        self.actionUndo.setIcon(get_icon('undo'))
        self.actionRedo.setIcon(get_icon('redo'))
Oscar Gustafsson's avatar
Oscar Gustafsson committed
        self.splitter.splitterMoved.connect(self._splitter_moved)
        self.actionDocumentation.triggered.connect(self._open_documentation)
Oscar Gustafsson's avatar
Oscar Gustafsson committed
        self.actionDocumentation.setIcon(get_icon('docs'))
        self.actionAbout.triggered.connect(self._open_about_window)
Oscar Gustafsson's avatar
Oscar Gustafsson committed
        self.actionAbout.setIcon(get_icon('about'))
        self.actionDecrease_time_resolution.triggered.connect(
            self._decrease_time_resolution
        )
Oscar Gustafsson's avatar
Oscar Gustafsson committed
        self.actionDecrease_time_resolution.setIcon(get_icon('decrease-timeresolution'))
        self.actionIncrease_time_resolution.triggered.connect(
            self._increase_time_resolution
        )
Oscar Gustafsson's avatar
Oscar Gustafsson committed
        self.actionIncrease_time_resolution.setIcon(get_icon('increase-timeresolution'))
Andreas Bolin's avatar
Andreas Bolin committed
        # Setup event member functions
        self.closeEvent = self._close_event

        # Setup info table
Oscar Gustafsson's avatar
Oscar Gustafsson 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
        self._splitter_min = self.splitter.minimumSizeHint().height()
        self.splitter.setStretchFactor(0, 1)
        self.splitter.setStretchFactor(1, 0)
        self.splitter.setCollapsible(0, False)
        self.splitter.setCollapsible(1, True)

    def _init_graphics(self) -> None:
        """Initialize the QGraphics framework"""
        self._scene = QGraphicsScene()
        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)
        OperationItem._scale = self._scale
        AxesItem._scale = self._scale
Andreas Bolin's avatar
Andreas Bolin committed
        self._scene.sceneRectChanged.connect(self.shrink_scene_to_min_size)

    @property
Oscar Gustafsson's avatar
Oscar Gustafsson committed
    def schedule(self) -> Optional[Schedule]:
        """The current schedule."""
Andreas Bolin's avatar
Andreas Bolin committed
        return self._schedule

Andreas Bolin's avatar
Andreas Bolin committed
    @Slot()
    def _plot_schedule(self) -> None:
        """Callback for plotting schedule using Matplotlib."""
Oscar Gustafsson's avatar
Oscar Gustafsson committed
        if self.schedule is None:
            return
        self.schedule.show()
Andreas Bolin's avatar
Andreas Bolin committed

    @Slot()
    def _open_documentation(self) -> None:
        """Callback to open documentation web page."""
        webbrowser.open_new_tab("https://da.gitlab-pages.liu.se/B-ASIC/")

    @Slot()
    def _action_reorder(self) -> None:
        """Callback to reorder all operations vertically based on start time."""
        if self.schedule is None:
            return
        if self._graph is not None:
            self._graph._redraw_from_start()
Oscar Gustafsson's avatar
Oscar Gustafsson committed
            self.update_statusbar("Operations reordered based on start time")
    @Slot()
    def _increase_time_resolution(self) -> None:
        """Callback for increasing time resolution."""
        # Create dialog asking for int
        factor, ok = QInputDialog.getInt(
            self, "Increase time resolution", "Factor", 1, 1
        )
        # Check return value
        if ok:
            if factor > 1:
                self.schedule.increase_time_resolution(factor)
                self.open(self.schedule)
                print(f"schedule.increase_time_resolution({factor})")
                self.update_statusbar(f"Time resolution increased by a factor {factor}")
        else:  # Cancelled
Oscar Gustafsson's avatar
Oscar Gustafsson committed
            self.update_statusbar("Cancelled")

    @Slot()
    def _decrease_time_resolution(self) -> None:
        """Callback for decreasing time resolution."""
        # Get possible factors
        vals = [str(v) for v in self.schedule.get_possible_time_resolution_decrements()]
        factor, ok = QInputDialog.getItem(
            self, "Decrease time resolution", "Factor", vals, editable=False
        )
        # Check return value
        if ok:
            if int(factor) > 1:
                self.schedule.decrease_time_resolution(int(factor))
                self.open(self.schedule)
                print(f"schedule.decrease_time_resolution({factor})")
                self.update_statusbar(f"Time resolution decreased by a factor {factor}")
        else:  # Cancelled
Oscar Gustafsson's avatar
Oscar Gustafsson committed
            self.update_statusbar("Cancelled")
    def wheelEvent(self, event) -> None:
        """Zoom in or out using mouse wheel if control is pressed."""
        if event.modifiers() == Qt.KeyboardModifier.ControlModifier:
            old_zoom = self._zoom
            self._zoom += event.angleDelta().y() / 2500
            self.view.scale(self._zoom, self._zoom)
            self._zoom = old_zoom

Andreas Bolin's avatar
Andreas Bolin committed
    @Slot()
    def _
Loading
Loading full blame...