From 594ab29331f47ecde384237f0e6308ffd6010a51 Mon Sep 17 00:00:00 2001 From: Oscar Gustafsson <oscar.gustafsson@gmail.com> Date: Sat, 21 Jan 2023 16:38:02 +0100 Subject: [PATCH] Initial support to wrap operations during scheduling --- b_asic/scheduler_gui/graphics_axes_item.py | 19 ++-- .../scheduler_gui/graphics_component_item.py | 37 +++---- b_asic/scheduler_gui/graphics_graph_event.py | 22 +++-- b_asic/scheduler_gui/graphics_graph_item.py | 6 +- .../scheduler_gui/graphics_timeline_item.py | 4 +- b_asic/scheduler_gui/main_window.py | 96 +++++++++++-------- test/test_scheduler_gui.py | 15 +++ 7 files changed, 120 insertions(+), 79 deletions(-) diff --git a/b_asic/scheduler_gui/graphics_axes_item.py b/b_asic/scheduler_gui/graphics_axes_item.py index de660c26..1dc36e33 100644 --- a/b_asic/scheduler_gui/graphics_axes_item.py +++ b/b_asic/scheduler_gui/graphics_axes_item.py @@ -108,8 +108,10 @@ class GraphicsAxesItem(QGraphicsItemGroup): @property def width(self) -> int: - """Get or set the current x-axis width. Setting the width to a new - value will update the axes automatically.""" + """ + Get or set the current x-axis width. Setting the width to a new + value will update the axes automatically. + """ return self._width # @width.setter @@ -119,8 +121,10 @@ class GraphicsAxesItem(QGraphicsItemGroup): @property def height(self) -> int: - """Get or set the current y-axis height. Setting the height to a new - value will update the axes automatically.""" + """ + Get or set the current y-axis height. Setting the height to a new + value will update the axes automatically. + """ return self._height # @height.setter @@ -140,7 +144,7 @@ class GraphicsAxesItem(QGraphicsItemGroup): @property def event_items(self) -> List[QGraphicsItem]: - """Returnes a list of objects, that receives events.""" + """Return a list of objects, that receives events.""" return [self._x_ledger[-1]] def _register_event_item(self, item: QGraphicsItem) -> None: @@ -150,7 +154,6 @@ class GraphicsAxesItem(QGraphicsItemGroup): def set_height(self, height: int) -> "GraphicsAxesItem": # TODO: implement, docstring raise NotImplementedError - return self def set_width(self, width: int) -> "GraphicsAxesItem": # TODO: docstring @@ -205,7 +208,7 @@ class GraphicsAxesItem(QGraphicsItemGroup): index -= 1 is_timeline = False - ## make a new x-tick + # make a new x-tick # x-axis scale line self._x_scale.insert(index, QGraphicsLineItem(0, 0, 0, 0.05)) self._x_scale[index].setPen(self._base_pen) @@ -249,7 +252,7 @@ class GraphicsAxesItem(QGraphicsItemGroup): self.addToGroup(self._x_ledger[index]) self._x_ledger[index].stackBefore(self._x_axis) - ## expand x-axis and move arrow,x-axis label, last x-scale, last x-scale-label + # expand x-axis and move arrow,x-axis label, last x-scale, last x-scale-label if not is_timeline: # expand x-axis, move arrow and x-axis label self._x_axis.setLine( diff --git a/b_asic/scheduler_gui/graphics_component_item.py b/b_asic/scheduler_gui/graphics_component_item.py index ffc9b1a6..4feb0929 100644 --- a/b_asic/scheduler_gui/graphics_component_item.py +++ b/b_asic/scheduler_gui/graphics_component_item.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -"""B-ASIC Scheduler-gui Graphics Component Item Module. +""" +B-ASIC Scheduler-gui Graphics Component Item Module. Contains the scheduler-gui GraphicsComponentItem class for drawing and maintain a component in a graph. """ @@ -28,7 +29,7 @@ from b_asic.scheduler_gui._preferences import ( class GraphicsComponentItem(QGraphicsItemGroup): - """A class to represent an component in a graph.""" + """Class to represent a component in a graph.""" _scale: float = 1.0 """Static, changed from MainWindow.""" @@ -49,7 +50,8 @@ class GraphicsComponentItem(QGraphicsItemGroup): height: float = 0.75, parent: Optional[QGraphicsItem] = None, ): - """Constructs a GraphicsComponentItem. 'parent' is passed to QGraphicsItemGroup's constructor. + """ + Construct a GraphicsComponentItem. *parent* is passed to QGraphicsItemGroup's constructor. """ super().__init__(parent=parent) self._operation = operation @@ -80,7 +82,7 @@ class GraphicsComponentItem(QGraphicsItemGroup): # return True def clear(self) -> None: - """Sets all children's parent to 'None' and delete the axis.""" + """Sets all children's parent to None and delete the axis.""" for item in self.childItems(): item.setParentItem(None) del item @@ -97,8 +99,10 @@ class GraphicsComponentItem(QGraphicsItemGroup): @property def height(self) -> float: - """Get or set the current component height. Setting the height to a new - value will update the component automatically.""" + """ + Get or set the current component height. Setting the height to a new + value will update the component automatically. + """ return self._height @height.setter @@ -115,7 +119,7 @@ class GraphicsComponentItem(QGraphicsItemGroup): @property def event_items(self) -> List[QGraphicsItem]: - """Returnes a list of objects, that receives events.""" + """Returns a list of objects, that receives events.""" return [self] def get_port_location(self, key) -> QPointF: @@ -146,7 +150,7 @@ class GraphicsComponentItem(QGraphicsItemGroup): pen2 = QPen(Qt.black) # used by port outline pen2.setWidthF(0) # pen2.setCosmetic(True) - port_size = 7 / self._scale # the diameter of an port + port_size = 7 / self._scale # the diameter of a port execution_time = QColor(OPERATION_EXECUTION_TIME_INACTIVE) execution_time.setAlpha(200) # 0-255 @@ -154,9 +158,10 @@ class GraphicsComponentItem(QGraphicsItemGroup): pen3.setColor(execution_time) pen3.setWidthF(3 / self._scale) - ## component path + # component path def draw_component_path(keys: List[str], reversed: bool) -> None: - """Draws component path and also register port positions in self._ports dictionary. + """ + Draws component path and also register port positions in self._ports dictionary. """ nonlocal x nonlocal y @@ -171,7 +176,7 @@ class GraphicsComponentItem(QGraphicsItemGroup): y = old_y + neg * (self._height / len(keys)) component_path.lineTo(x, y) # vertical line # register the port pos in dictionary - port_x = x # Port coords is at the center + port_x = x # Port coordinates is at the center port_y = ( y - neg * abs(y - old_y) / 2 ) # of previous vertical line. @@ -205,12 +210,12 @@ class GraphicsComponentItem(QGraphicsItemGroup): draw_component_path(output_keys, True) # draw output side component_path.closeSubpath() - ## component item + # component item self._component_item = QGraphicsPathItem(component_path) self._component_item.setPen(pen1) self._set_background(Qt.lightGray) # used by component filling - ## ports item + # ports item for port_dict in self._ports.values(): port_pos = self.mapToParent(port_dict["pos"]) port = QGraphicsEllipseItem( @@ -221,14 +226,14 @@ class GraphicsComponentItem(QGraphicsItemGroup): port.setPos(port_pos.x(), port_pos.y()) self._port_items.append(port) - ## op-id/label + # op-id/label self._label_item = QGraphicsSimpleTextItem(self._operation.graph_id) self._label_item.setScale(self._label_item.scale() / self._scale) center = self._component_item.boundingRect().center() center -= self._label_item.boundingRect().center() / self._scale self._label_item.setPos(self._component_item.pos() + center) - ## execution time + # execution time if self._operation.execution_time is not None: self._execution_time_item = QGraphicsRectItem( 0, 0, self._operation.execution_time, self._height @@ -236,7 +241,7 @@ class GraphicsComponentItem(QGraphicsItemGroup): self._execution_time_item.setPen(pen3) # self._execution_time_item.setBrush(brush3) - ## item group, consist of component_item, port_items and execution_time_item + # item group, consist of component_item, port_items and execution_time_item self.addToGroup(self._component_item) for port in self._port_items: self.addToGroup(port) diff --git a/b_asic/scheduler_gui/graphics_graph_event.py b/b_asic/scheduler_gui/graphics_graph_event.py index dab4c1e9..7af6938a 100644 --- a/b_asic/scheduler_gui/graphics_graph_event.py +++ b/b_asic/scheduler_gui/graphics_graph_event.py @@ -196,12 +196,11 @@ class GraphicsGraphEvent: # PyQt5 # Qt.DragMoveCursor # button = event.button() - def update_pos(item, delta_x): - pos = item.x() + delta_x + def update_pos(item, dx): + pos = item.x() + dx if self.is_component_valid_pos(item, pos): - # self.prepareGeometryChange() item.setX(pos) - self._current_pos.setX(self._current_pos.x() + delta_x) + self._current_pos.setX(self._current_pos.x() + dx) self._redraw_lines(item) item: GraphicsComponentItem = self.scene().mouseGrabberItem() @@ -229,6 +228,11 @@ class GraphicsGraphEvent: # PyQt5 item: GraphicsComponentItem = self.scene().mouseGrabberItem() self.set_item_inactive(item) self.set_new_starttime(item) + pos = item.x() + if pos > self.schedule.schedule_time: + pos = pos % self.schedule.schedule_time + item.setX(pos) + self._redraw_lines(item) def comp_mouseDoubleClickEvent( self, event: QGraphicsSceneMouseEvent @@ -249,13 +253,13 @@ class GraphicsGraphEvent: # PyQt5 # Qt.DragMoveCursor # button = event.button() - def update_pos(item, delta_x): - pos = item.x() + delta_x - if self.is_valid_delta_time(self._delta_time + delta_x): + def update_pos(item, dx): + pos = item.x() + dx + if self.is_valid_delta_time(self._delta_time + dx): # self.prepareGeometryChange() item.setX(pos) - self._current_pos.setX(self._current_pos.x() + delta_x) - self._delta_time += delta_x + self._current_pos.setX(self._current_pos.x() + dx) + self._delta_time += dx item.set_text(self._delta_time) item: GraphicsTimelineItem = self.scene().mouseGrabberItem() diff --git a/b_asic/scheduler_gui/graphics_graph_item.py b/b_asic/scheduler_gui/graphics_graph_item.py index 8a3c43dd..053b67e5 100644 --- a/b_asic/scheduler_gui/graphics_graph_item.py +++ b/b_asic/scheduler_gui/graphics_graph_item.py @@ -84,12 +84,12 @@ class GraphicsGraphItem( return False if ( self.schedule.cyclic - and new_start_time > self.schedule.schedule_time + and new_start_time > self.schedule.schedule_time + 1 ): return False if ( not self.schedule.cyclic - and new_start_time + end_time > self.schedule.schedule_time + and new_start_time + end_time > self.schedule.schedule_time + 1 ): return False @@ -153,7 +153,7 @@ class GraphicsGraphItem( @property def event_items(self) -> List[QGraphicsItem]: - """Returnes a list of objects, that receives events.""" + """Return a list of objects that receives events.""" return self._event_items def _make_graph(self) -> None: diff --git a/b_asic/scheduler_gui/graphics_timeline_item.py b/b_asic/scheduler_gui/graphics_timeline_item.py index 9566fd71..fe02ce2a 100644 --- a/b_asic/scheduler_gui/graphics_timeline_item.py +++ b/b_asic/scheduler_gui/graphics_timeline_item.py @@ -3,7 +3,7 @@ """ B-ASIC Scheduler-gui Graphics Timeline Item Module. -Contains the a scheduler-gui GraphicsTimelineItem class for drawing and +Contains the scheduler-gui GraphicsTimelineItem class for drawing and maintain the timeline in a graph. """ from typing import List, Optional, overload @@ -114,5 +114,5 @@ class GraphicsTimelineItem(QGraphicsLineItem): @property def event_items(self) -> List[QGraphicsItem]: - """Returnes a list of objects, that receives events.""" + """Return a list of objects that receives events.""" return [self] diff --git a/b_asic/scheduler_gui/main_window.py b/b_asic/scheduler_gui/main_window.py index cb3e9a20..5d24b40a 100644 --- a/b_asic/scheduler_gui/main_window.py +++ b/b_asic/scheduler_gui/main_window.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -"""B-ASIC Scheduler-gui Module. +""" +B-ASIC Scheduler-gui Module. Contains the scheduler-gui MainWindow class for scheduling operations in an SFG. @@ -55,7 +56,7 @@ log = logger.getLogger() sys.excepthook = logger.handle_exceptions -# Debug struff +# Debug stuff if __debug__: log.setLevel("DEBUG") @@ -79,7 +80,7 @@ if __debug__: # The following QCoreApplication values is used for QSettings among others -QCoreApplication.setOrganizationName("Linöping University") +QCoreApplication.setOrganizationName("Linköping University") QCoreApplication.setOrganizationDomain("liu.se") QCoreApplication.setApplicationName("B-ASIC Scheduler") # QCoreApplication.setApplicationVersion(__version__) # TODO: read from packet __version__ @@ -156,9 +157,9 @@ class MainWindow(QMainWindow, Ui_MainWindow): """Get the current schedule.""" return self._schedule - ############### - #### Slots #### - ############### + ######### + # Slots # + ######### @Slot() def _actionTbtn(self) -> None: # TODO: remove @@ -280,8 +281,10 @@ class MainWindow(QMainWindow, Ui_MainWindow): @Slot() def save(self) -> None: - """SLOT() for SIGNAL(menu_save.triggered) - This method save an schedule.""" + """ + SLOT() for SIGNAL(menu_save.triggered) + This method save a schedule. + """ # TODO: all self._printButtonPressed("save_schedule()") self.update_statusbar(self.tr("Schedule saved successfully")) @@ -289,7 +292,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): @Slot() def save_as(self) -> None: """SLOT() for SIGNAL(menu_save_as.triggered) - This method save as an schedule.""" + This method save as a schedule.""" # TODO: all self._printButtonPressed("save_schedule()") self.update_statusbar(self.tr("Schedule saved successfully")) @@ -313,17 +316,21 @@ class MainWindow(QMainWindow, Ui_MainWindow): @Slot(bool) def hide_exit_dialog(self, checked: bool) -> None: - """SLOT(bool) for SIGNAL(menu_exit_dialog.triggered) + """ + SLOT(bool) for SIGNAL(menu_exit_dialog.triggered) Takes in a boolean and stores 'checked' in 'hide_exit_dialog' item in - settings.""" + settings. + """ s = QSettings() s.setValue("mainwindow/hide_exit_dialog", checked) @Slot(int, int) def _splitter_moved(self, pos: int, index: int) -> None: - """SLOT(int, int) for SIGNAL(splitter.splitterMoved) + """ + SLOT(int, int) for SIGNAL(splitter.splitterMoved) Callback method used to check if the right widget (info window) - has collapsed. Update the checkbutton accordingly.""" + has collapsed. Update the checkbutton accordingly. + """ width = self.splitter.sizes()[1] if width == 0: @@ -336,34 +343,42 @@ class MainWindow(QMainWindow, Ui_MainWindow): @Slot(str) 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 + """ + SLOT(str) for SIGNAL(_graph._signals.component_selected) + Takes in an operator-id, first clears the 'Operator' part of the info table and then fill in the table with new values from the operator - associated with 'op_id'.""" + associated with 'op_id'. + """ self.info_table_clear_component() self._info_table_fill_component(op_id) @Slot() def info_table_update_schedule(self) -> None: - """SLOT() for SIGNAL(_graph._signals.schedule_time_changed) - Updates the 'Schedule' part of the info table.""" + """ + SLOT() for SIGNAL(_graph._signals.schedule_time_changed) + Updates the 'Schedule' part of the info table. + """ 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) + """ + 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.""" + its minimum size, when the bounding rectangle signals a change in + geometry. + """ self._scene.setSceneRect(self._scene.itemsBoundingRect()) - ################ - #### Events #### - ################ + ########## + # Events # + ########## def _close_event(self, event: QCloseEvent) -> None: - """EVENT: Replaces QMainWindow default closeEvent(QCloseEvent) event. Takes + """ + EVENT: Replaces QMainWindow default closeEvent(QCloseEvent) event. Takes in a QCloseEvent and display an exit dialog, depending on - 'hide_exit_dialog' in settings.""" + 'hide_exit_dialog' in settings. + """ s = QSettings() hide_dialog = s.value("mainwindow/hide_exit_dialog", False, bool) ret = QMessageBox.StandardButton.Yes @@ -396,9 +411,9 @@ class MainWindow(QMainWindow, Ui_MainWindow): else: event.ignore() - ################################# - #### Helper member functions #### - ################################# + ########################### + # Helper member functions # + ########################### def _printButtonPressed(self, func_name: str) -> None: # TODO: remove @@ -407,7 +422,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): alert.exec_() def open(self, schedule: Schedule) -> None: - """Takes in an Schedule and creates a GraphicsGraphItem object.""" + """Take a Schedule and create a GraphicsGraphItem object.""" self.close_schedule() self._schedule = deepcopy(schedule) self._graph = GraphicsGraphItem(self.schedule) @@ -425,7 +440,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.update_statusbar(self.tr("Schedule loaded successfully")) def update_statusbar(self, msg: str) -> None: - """Takes in an str and write 'msg' to the statusbar with temporarily policy. + """Take a str and write *msg* to the statusbar with temporarily policy. """ self.statusbar.showMessage(msg) @@ -434,7 +449,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): s = QSettings() s.setValue( "mainwindow/maximized", self.isMaximized() - ) # window: maximized, in X11 - alwas False + ) # window: maximized, in X11 - always False s.setValue("mainwindow/pos", self.pos()) # window: pos s.setValue("mainwindow/size", self.size()) # window: size s.setValue( @@ -474,25 +489,24 @@ class MainWindow(QMainWindow, Ui_MainWindow): log.debug("Settings read from '{}'.".format(s.fileName())) def info_table_fill_schedule(self, schedule: Schedule) -> None: - """Takes in a Schedule and fill in the 'Schedule' part of the info table - with values from 'schedule'""" - self.info_table.insertRow(1) + """ + Take 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.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)) - ) def _info_table_fill_component(self, op_id: str) -> None: - """Taked in an operator-id and fill in the 'Operator' part of the info - table with values from the operator associated with 'op_id'.""" + """ + Take an operator-id and fill in the 'Operator' part of the info + table with values from the operator associated with *op_id*. + """ op: GraphComponent = self.schedule.sfg.find_by_id(op_id) si = self.info_table.rowCount() # si = start index diff --git a/test/test_scheduler_gui.py b/test/test_scheduler_gui.py index 3a045f85..ebfdad70 100644 --- a/test/test_scheduler_gui.py +++ b/test/test_scheduler_gui.py @@ -1,5 +1,7 @@ import pytest +from b_asic.core_operations import Addition, ConstantMultiplication +from b_asic.schedule import Schedule try: import b_asic.scheduler_gui as GUI except ImportError: @@ -11,3 +13,16 @@ def test_start(qtbot): qtbot.addWidget(widget) widget.exit_app() + + +def test_load_schedule(qtbot, sfg_simple_filter): + sfg_simple_filter.set_latency_of_type(Addition.type_name(), 5) + sfg_simple_filter.set_latency_of_type( + ConstantMultiplication.type_name(), 4 + ) + + widget = GUI.MainWindow() + qtbot.addWidget(widget) + schedule = Schedule(sfg_simple_filter) + widget.open(schedule) + assert widget.statusbar.currentMessage() == "Schedule loaded successfully" -- GitLab