diff --git a/b_asic/gui_utils/icons.py b/b_asic/gui_utils/icons.py index 4433c8f3c0c958204a8107bb6d91b4cd9021f6dc..ea59c6e292193a4eed0d48bc742430dd0f409173 100644 --- a/b_asic/gui_utils/icons.py +++ b/b_asic/gui_utils/icons.py @@ -31,6 +31,7 @@ ICONS = { 'reorder': ('msc.graph-left', {'rotated': -90}), 'full-screen': 'mdi6.fullscreen', 'full-screen-exit': 'mdi6.fullscreen-exit', + 'warning': 'fa.warning', } diff --git a/b_asic/scheduler_gui/_preferences.py b/b_asic/scheduler_gui/_preferences.py index 6529b24efbe661439c94ab6c28ba80d8ce67ceed..b0cbcf7004b12f634f2fd8192eaf1c5a189199b1 100644 --- a/b_asic/scheduler_gui/_preferences.py +++ b/b_asic/scheduler_gui/_preferences.py @@ -4,8 +4,10 @@ from b_asic._preferences import EXECUTION_TIME_COLOR, LATENCY_COLOR, SIGNAL_COLO SIGNAL_INACTIVE = QColor(*SIGNAL_COLOR) SIGNAL_ACTIVE = QColor(0, 207, 181) +SIGNAL_WARNING = QColor(255, 0, 0) SIGNAL_WIDTH = 0.03 SIGNAL_WIDTH_ACTIVE = 0.05 +SIGNAL_WIDTH_WARNING = 0.05 OPERATION_LATENCY_INACTIVE = QColor(*LATENCY_COLOR) OPERATION_LATENCY_ACTIVE = QColor(0, 207, 181) diff --git a/b_asic/scheduler_gui/main_window.py b/b_asic/scheduler_gui/main_window.py index 9478b2c96db0113bd7026de3ff3d05d1723575e8..09120351124848f4a38047602b491ae45fd26665 100644 --- a/b_asic/scheduler_gui/main_window.py +++ b/b_asic/scheduler_gui/main_window.py @@ -120,6 +120,7 @@ class ScheduleMainWindow(QMainWindow, Ui_MainWindow): self._read_settings() self._init_ui() self._file_name = None + self._show_incorrect_execution_time = True # Recent files self._max_recent_files = 4 @@ -151,6 +152,10 @@ class ScheduleMainWindow(QMainWindow, Ui_MainWindow): self.actionReorder.triggered.connect(self._action_reorder) 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 + ) self.actionPlot_schedule.setIcon(get_icon('plot-schedule')) self.actionPlot_schedule.triggered.connect(self._plot_schedule) self.actionZoom_to_fit.setIcon(get_icon('zoom-to-fit')) @@ -821,6 +826,13 @@ class ScheduleMainWindow(QMainWindow, Ui_MainWindow): """Callback for toggling the status bar.""" self.statusbar.setVisible(self.actionStatus_bar.isChecked()) + def _toggle_execution_time_warning(self, event=None) -> None: + """Callback for toggling the status bar.""" + self._show_incorrect_execution_time = ( + self.action_incorrect_execution_time.isChecked() + ) + self._graph.set_warnings(self._show_incorrect_execution_time) + def _toggle_fullscreen(self, event=None): """Callback for toggling full screen mode.""" if self.isFullScreen(): diff --git a/b_asic/scheduler_gui/main_window.ui b/b_asic/scheduler_gui/main_window.ui index 7965f6459f6fe78b7efaed53957ab131fbfba19a..0de9d87255f34ca3cd98c4558759a9c4e1733ffc 100644 --- a/b_asic/scheduler_gui/main_window.ui +++ b/b_asic/scheduler_gui/main_window.ui @@ -231,6 +231,7 @@ <addaction name="menu_node_info"/> <addaction name="actionToolbar"/> <addaction name="actionStatus_bar"/> + <addaction name="action_incorrect_execution_time"/> <addaction name="separator"/> <addaction name="actionPlot_schedule"/> <addaction name="separator"/> @@ -287,6 +288,7 @@ <addaction name="actionRedo"/> <addaction name="separator"/> <addaction name="menu_node_info"/> + <addaction name="action_incorrect_execution_time"/> <addaction name="actionReorder"/> </widget> <action name="menu_load_from_file"> @@ -341,7 +343,7 @@ <string>&Node info</string> </property> <property name="toolTip"> - <string>Show(hide node information</string> + <string>Show/hide node information</string> </property> <property name="shortcut"> <string>Ctrl+I</string> @@ -501,6 +503,23 @@ <string>Show/hide toolbar</string> </property> </action> + <action name="action_incorrect_execution_time"> + <property name="checkable"> + <bool>true</bool> + </property> + <property name="checked"> + <bool>true</bool> + </property> + <property name="text"> + <string>&Incorrect execution time</string> + </property> + <property name="toolTip"> + <string>Highlight processes with execution time longer than schedule time</string> + </property> + <property name="iconVisibleInMenu"> + <bool>false</bool> + </property> + </action> <action name="menu_open"> <property name="text"> <string>&Open...</string> diff --git a/b_asic/scheduler_gui/operation_item.py b/b_asic/scheduler_gui/operation_item.py index eb6687c9c38fdbfca8e5130db577c408ed350510..d8f52d9d78e23560092bbb046896df868f24a651 100644 --- a/b_asic/scheduler_gui/operation_item.py +++ b/b_asic/scheduler_gui/operation_item.py @@ -29,6 +29,9 @@ from b_asic.scheduler_gui._preferences import ( OPERATION_HEIGHT, OPERATION_LATENCY_ACTIVE, OPERATION_LATENCY_INACTIVE, + SIGNAL_ACTIVE, + SIGNAL_INACTIVE, + SIGNAL_WARNING, ) if TYPE_CHECKING: @@ -89,14 +92,18 @@ class OperationItem(QGraphicsItemGroup): QCursor(Qt.CursorShape.OpenHandCursor) ) # default cursor when hovering over object - self._port_filling_brush = QBrush(Qt.GlobalColor.black) - self._port_outline_pen = QPen(Qt.GlobalColor.black) + self._port_filling_brush = QBrush(SIGNAL_INACTIVE) + self._port_outline_pen = QPen(SIGNAL_INACTIVE) self._port_outline_pen.setWidthF(0) - self._port_filling_brush_active = QBrush(OPERATION_LATENCY_ACTIVE) - self._port_outline_pen_active = QPen(OPERATION_LATENCY_ACTIVE) + self._port_filling_brush_active = QBrush(SIGNAL_ACTIVE) + self._port_outline_pen_active = QPen(SIGNAL_ACTIVE) self._port_outline_pen_active.setWidthF(0) + self._port_filling_brush_warning = QBrush(SIGNAL_WARNING) + self._port_outline_pen_warning = QPen(SIGNAL_WARNING) + self._port_outline_pen_warning.setWidthF(0) + self._make_component() # def sceneEvent(self, event: QEvent) -> bool: @@ -184,10 +191,14 @@ class OperationItem(QGraphicsItemGroup): item.setBrush(self._port_filling_brush_active) item.setPen(self._port_outline_pen_active) - def set_port_inactive(self, key: str): + def set_port_inactive(self, key: str, warning: bool = False): item = self._ports[key]["item"] - item.setBrush(self._port_filling_brush) - item.setPen(self._port_outline_pen) + item.setBrush( + self._port_filling_brush_warning if warning else self._port_filling_brush + ) + item.setPen( + self._port_outline_pen_warning if warning else self._port_outline_pen + ) def _set_background(self, color: QColor) -> None: brush = QBrush(color) diff --git a/b_asic/scheduler_gui/scheduler_event.py b/b_asic/scheduler_gui/scheduler_event.py index f8c74dbeffb106e3925c1de6921b2261d6346841..1270e5ff6c742ccb885ec3648d0df66fbfd0087e 100644 --- a/b_asic/scheduler_gui/scheduler_event.py +++ b/b_asic/scheduler_gui/scheduler_event.py @@ -198,8 +198,8 @@ class SchedulerEvent: # PyQt5 return item: OperationItem = self.scene().mouseGrabberItem() - self.set_item_inactive(item) self.set_new_start_time(item) + self.set_item_inactive(item) pos_x = item.x() redraw = False if pos_x < 0: diff --git a/b_asic/scheduler_gui/scheduler_item.py b/b_asic/scheduler_gui/scheduler_item.py index c8eebe7546d6293857ae69dd55fdfb4d52dc329f..a7917f9199e14b9b57b21dc1eeb3477a65d67dd0 100644 --- a/b_asic/scheduler_gui/scheduler_item.py +++ b/b_asic/scheduler_gui/scheduler_item.py @@ -55,7 +55,12 @@ class SchedulerItem(SchedulerEvent, QGraphicsItemGroup): # PySide2 / PyQt5 _event_items: List[QGraphicsItem] _signal_dict: Dict[OperationItem, Set[SignalItem]] - def __init__(self, schedule: Schedule, parent: Optional[QGraphicsItem] = None): + def __init__( + self, + schedule: Schedule, + warnings: bool = True, + parent: Optional[QGraphicsItem] = None, + ): """ Construct a SchedulerItem. *parent* is passed to QGraphicsItemGroup's constructor. @@ -68,6 +73,8 @@ class SchedulerItem(SchedulerEvent, QGraphicsItemGroup): # PySide2 / PyQt5 # else: # super().__init__(parent=self) self._schedule = schedule + self._parent = parent + self._warnings = warnings self._axes = None self._operation_items = {} self._x_axis_indent = SCHEDULE_INDENT @@ -129,6 +136,15 @@ class SchedulerItem(SchedulerEvent, QGraphicsItemGroup): # PySide2 / PyQt5 for signal in self._signal_dict[item]: signal.update_path() + def set_warnings(self, warnings: bool = True): + if warnings != self._warnings: + self._warnings = warnings + s = set() + for signals in self._signal_dict.values(): + s.update(signals) + for signal in s: + signal.set_inactive() + def set_item_active(self, item: OperationItem) -> None: """ Set *item* as active, i.e., draw it and connecting signals in special colors. diff --git a/b_asic/scheduler_gui/signal_item.py b/b_asic/scheduler_gui/signal_item.py index 0f28b0c50c67ff88cff247a136803f3bbec9c48f..ccb065dfdd66a5ce9ccd409777c0fe3c9b48e99f 100644 --- a/b_asic/scheduler_gui/signal_item.py +++ b/b_asic/scheduler_gui/signal_item.py @@ -17,8 +17,10 @@ from b_asic.scheduler_gui._preferences import ( SCHEDULE_INDENT, SIGNAL_ACTIVE, SIGNAL_INACTIVE, + SIGNAL_WARNING, SIGNAL_WIDTH, SIGNAL_WIDTH_ACTIVE, + SIGNAL_WIDTH_WARNING, ) from b_asic.scheduler_gui.operation_item import OperationItem from b_asic.signal import Signal @@ -63,6 +65,7 @@ class SignalItem(QGraphicsPathItem): self._src_key = f"out{self._signal.source.index}" self._dest_key = f"in{self._signal.destination.index}" self._refresh_pens() + self._parent = parent self.set_inactive() self.update_path() @@ -104,6 +107,9 @@ class SignalItem(QGraphicsPathItem): pen = QPen(SIGNAL_INACTIVE) pen.setWidthF(SIGNAL_WIDTH) self._inactive_pen = pen + pen = QPen(SIGNAL_WARNING) + pen.setWidthF(SIGNAL_WIDTH_WARNING) + self._warning_pen = pen def set_active(self) -> None: """ @@ -115,6 +121,10 @@ class SignalItem(QGraphicsPathItem): def set_inactive(self) -> None: """Set the signal color to the default color.""" - self.setPen(self._inactive_pen) - self._src_operation.set_port_inactive(self._src_key) - self._dest_operation.set_port_inactive(self._dest_key) + warning = self._parent._warnings and ( + self._parent._schedule._input_slacks(self._signal.destination)[self._signal] + > self._parent._schedule.schedule_time + ) + self.setPen(self._warning_pen if warning else self._inactive_pen) + self._src_operation.set_port_inactive(self._src_key, warning) + self._dest_operation.set_port_inactive(self._dest_key, warning) diff --git a/b_asic/scheduler_gui/ui_main_window.py b/b_asic/scheduler_gui/ui_main_window.py index cf910e471ef7599eb3b682bc0081d9f45210e0b0..7159bf2e7bd2ae08174143f98bf27e87b22d4512 100644 --- a/b_asic/scheduler_gui/ui_main_window.py +++ b/b_asic/scheduler_gui/ui_main_window.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Form implementation generated from reading ui file '.\main_window.ui' +# Form implementation generated from reading ui file './main_window.ui' # # Created by: PyQt5 UI code generator 5.15.7 # @@ -226,6 +226,13 @@ class Ui_MainWindow(object): self.actionToolbar.setCheckable(True) self.actionToolbar.setChecked(True) self.actionToolbar.setObjectName("actionToolbar") + self.action_incorrect_execution_time = QtWidgets.QAction(MainWindow) + self.action_incorrect_execution_time.setCheckable(True) + self.action_incorrect_execution_time.setChecked(True) + self.action_incorrect_execution_time.setIconVisibleInMenu(False) + self.action_incorrect_execution_time.setObjectName( + "action_incorrect_execution_time" + ) self.menu_open = QtWidgets.QAction(MainWindow) self.menu_open.setObjectName("menu_open") self.actionToggle_full_screen = QtWidgets.QAction(MainWindow) @@ -243,6 +250,7 @@ class Ui_MainWindow(object): self.menuView.addAction(self.menu_node_info) self.menuView.addAction(self.actionToolbar) self.menuView.addAction(self.actionStatus_bar) + self.menuView.addAction(self.action_incorrect_execution_time) self.menuView.addSeparator() self.menuView.addAction(self.actionPlot_schedule) self.menuView.addSeparator() @@ -271,6 +279,7 @@ class Ui_MainWindow(object): self.toolBar.addAction(self.actionRedo) self.toolBar.addSeparator() self.toolBar.addAction(self.menu_node_info) + self.toolBar.addAction(self.action_incorrect_execution_time) self.toolBar.addAction(self.actionReorder) self.retranslateUi(MainWindow) @@ -312,7 +321,7 @@ class Ui_MainWindow(object): self.menu_save.setShortcut(_translate("MainWindow", "Ctrl+S")) self.menu_node_info.setText(_translate("MainWindow", "&Node info")) self.menu_node_info.setToolTip( - _translate("MainWindow", "Show(hide node information") + _translate("MainWindow", "Show/hide node information") ) self.menu_node_info.setShortcut(_translate("MainWindow", "Ctrl+I")) self.menu_quit.setText(_translate("MainWindow", "&Quit")) @@ -353,6 +362,15 @@ class Ui_MainWindow(object): ) self.actionToolbar.setText(_translate("MainWindow", "&Toolbar")) self.actionToolbar.setToolTip(_translate("MainWindow", "Show/hide toolbar")) + self.action_incorrect_execution_time.setText( + _translate("MainWindow", "&Incorrect execution time") + ) + self.action_incorrect_execution_time.setToolTip( + _translate( + "MainWindow", + "Highlight processes with execution time longer than schedule time", + ) + ) self.menu_open.setText(_translate("MainWindow", "&Open...")) self.menu_open.setToolTip( _translate("MainWindow", "Open previously saved schedule")