diff --git a/b_asic/scheduler-gui/__init__.py b/b_asic/scheduler-gui/__init__.py index 87bb8be2b965bc8a508534d1bf7c40469f6096db..5c558d3445b4b0bae91f4a168dfb9e7a83d36980 100644 --- a/b_asic/scheduler-gui/__init__.py +++ b/b_asic/scheduler-gui/__init__.py @@ -7,9 +7,10 @@ from logger import * from main_window import * from graphics_graph_item import * from graphics_graph_event import * -from graphics_axis_item import * from graphics_component_item import * +from graphics_axes_item import * +from graphics_timeline_item import * -# __all__ = ['main_window', 'graphics_graph', 'graphics_axis', 'component_item'] +# __all__ = ['main_window', 'graphics_graph', 'component_item', 'graphics_axes', 'graphics_timeline_item'] __version__ = '0.1' __author__ = 'Andreas Bolin' diff --git a/b_asic/scheduler-gui/graphics_graph_event.py b/b_asic/scheduler-gui/graphics_graph_event.py index 00aaa934a0cd626af0436bbd3884b0cf529bdd8e..a2cbc20f792c0db58ea4c75221c415ac59968622 100644 --- a/b_asic/scheduler-gui/graphics_graph_event.py +++ b/b_asic/scheduler-gui/graphics_graph_event.py @@ -43,16 +43,22 @@ from graphics_timeline_item import GraphicsTimelineItem -sys.settrace -# class GraphicsGraphEvent(ABC): -class GraphicsGraphEvent(QGraphicsItemGroup, QObject): -# class GraphicsGraphEvent(QGraphicsObject): +# sys.settrace +# class GraphicsGraphEvent(QGraphicsItemGroup, QObject): # PySide2 +class GraphicsGraphEvent(): # PyQt5 """Event filter and handlers for GraphicsGraphItem""" + class Signals(QObject): # PyQt5 + """A class respresenting signals.""" + component_selected = Signal(str) + schedule_time_changed = Signal() + _axes: GraphicsAxesItem _current_pos: QPointF _delta_time: int - component_selected = Signal(str) - schedule_time_changed = Signal() + signals: Signals # PyQt5 + # component_selected = Signal(str) # PySide2 + # schedule_time_changed = Signal() # PySide2 + @overload def is_component_valid_pos(self, pos: float, end_time: int) -> bool: ... @@ -61,12 +67,15 @@ class GraphicsGraphEvent(QGraphicsItemGroup, QObject): @overload def set_schedule_time(self, delta_time: int) -> None: ... - def __init__(self, parent: Optional[QGraphicsItem] = None): - QObject.__init__(self) - QGraphicsItemGroup.__init__(self, parent) - # QGraphicsObject.__init__(self) - # super().__init__(parent) - # super().__init__() + # def __init__(self, parent: Optional[QGraphicsItem] = None): # PySide2 + # QObject.__init__(self) + # QGraphicsItemGroup.__init__(self, parent) + + def __init__(self, parent: Optional[QGraphicsItem] = None): # PyQt5 + # QGraphicsItemGroup.__init__(self, parent) + # QObject.__init__(self) + super().__init__() + self.signals = self.Signals() ################# #### Filters #### @@ -187,7 +196,8 @@ class GraphicsGraphEvent(QGraphicsItemGroup, QObject): by default be accepted, and this item is then the mouse grabber. This allows the item to receive future move, release and double-click events.""" item: GraphicsComponentItem = self.scene().mouseGrabberItem() - self.component_selected.emit(item.op_id) + self.signals.component_selected.emit(item.op_id) + # self.component_selected.emit(item.op_id) self._current_pos = item.mapToParent(event.pos()) item.setCursor(QCursor(Qt.ClosedHandCursor)) event.accept() @@ -248,5 +258,4 @@ class GraphicsGraphEvent(QGraphicsItemGroup, QObject): item.hide_label() if self._delta_time != 0: self.set_schedule_time(self._delta_time) - self.schedule_time_changed.emit() - + self.signals.schedule_time_changed.emit() diff --git a/b_asic/scheduler-gui/graphics_graph_item.py b/b_asic/scheduler-gui/graphics_graph_item.py index 692c179d2d842886240a451f27ecf31783948429..b04e25ade1705569b7c37a09b0066bc60ecfb4d2 100644 --- a/b_asic/scheduler-gui/graphics_graph_item.py +++ b/b_asic/scheduler-gui/graphics_graph_item.py @@ -47,7 +47,8 @@ from graphics_axes_item import GraphicsAxesItem from graphics_graph_event import GraphicsGraphEvent -class GraphicsGraphItem(GraphicsGraphEvent, QGraphicsItemGroup): +# class GraphicsGraphItem(GraphicsGraphEvent, QGraphicsItemGroup): # PySide2 / PyQt5 +class GraphicsGraphItem(QGraphicsItemGroup, GraphicsGraphEvent): # PyQt5 """A class to represent a graph in a QGraphicsScene. This class is a subclass of QGraphicsItemGroup and contains the objects, axes from GraphicsAxesItem, as well as components from GraphicsComponentItem. It @@ -160,4 +161,4 @@ class GraphicsGraphItem(GraphicsGraphEvent, QGraphicsItemGroup): # self._axes.update_axes(schedule_time - 2, self._components_height, self._x_axis_indent) for component in self._components: self.addToGroup(component) - # self.addToGroup(self._components) \ No newline at end of file + # self.addToGroup(self._components) diff --git a/b_asic/scheduler-gui/main_window.py b/b_asic/scheduler-gui/main_window.py index b6d67de4abcdd28c8848eaa5594d212c1b8d37f6..3bd01de571906dacc7ee47ee5415faf40c2c112a 100644 --- a/b_asic/scheduler-gui/main_window.py +++ b/b_asic/scheduler-gui/main_window.py @@ -6,16 +6,12 @@ Contains the scheduler-gui MainWindow class for scheduling operations in an SFG. Start main-window with start_gui(). """ -from copy import deepcopy import os import sys -from pathlib import Path -from types import ModuleType -from typing import Any, Iterable, List, Sequence, Type, Dict, Union -from pprint import pprint -#from matplotlib.pyplot import bar -#from diagram import * -from importlib.machinery import SourceFileLoader +from typing import Union +from pprint import pprint +from copy import deepcopy +from importlib.machinery import SourceFileLoader import inspect @@ -26,18 +22,12 @@ from qtpy.QtCore import QCoreApplication, Qt, Slot, Signal, QSettings, QStand from qtpy.QtGui import QCloseEvent from qtpy.QtWidgets import ( QApplication, QMainWindow, QMessageBox, QFileDialog, QInputDialog, QCheckBox, QAbstractButton, - QTableWidgetItem, QSizePolicy) + QTableWidgetItem) # QGraphics and QPainter imports -from qtpy.QtCore import ( - QRect, QRectF, QPoint, QSize, QByteArray, QMarginsF, QObject) -from qtpy.QtGui import ( - QPaintEvent, QPainter, QPainterPath, QColor, QBrush, QPen, QFont, QPolygon, QIcon, QPixmap, - QLinearGradient) -from qtpy.QtWidgets import ( - QGraphicsView, QGraphicsScene, QGraphicsWidget, QGraphicsScale, - QGraphicsLayout, QGraphicsLinearLayout, QGraphicsGridLayout, QGraphicsLayoutItem, QGraphicsAnchorLayout, - QGraphicsItem, QGraphicsItemGroup, QGraphicsRectItem, QHeaderView) +from qtpy.QtCore import QRectF, QByteArray +from qtpy.QtGui import QIcon +from qtpy.QtWidgets import QGraphicsScene, QGraphicsItemGroup # B-ASIC import logger @@ -59,7 +49,6 @@ sys.excepthook = logger.handle_exceptions if __debug__: log.setLevel('DEBUG') - if __debug__: # Print some system version information QT_API = os.environ.get('QT_API') @@ -102,7 +91,7 @@ sys.path.insert(0, 'icons/') # Needed for the compiled '*_rc.py' from ui_main_window import Ui_MainWindow # Only availible when the form (.ui) is compiled -# The folowing QCoreApplication values is used for QSettings among others +# The following QCoreApplication values is used for QSettings among others QCoreApplication.setOrganizationName('Linöping University') QCoreApplication.setOrganizationDomain('liu.se') QCoreApplication.setApplicationName('B-ASIC Scheduler') @@ -114,22 +103,21 @@ QCoreApplication.setApplicationName('B-ASIC Scheduler') class MainWindow(QMainWindow, Ui_MainWindow): """Schedule of an SFG with scheduled Operations.""" _scene: QGraphicsScene + _schedule: Union[Schedule, None] _graph: Union[GraphicsGraphItem, None] _scale: float _debug_rects: QGraphicsItemGroup _splitter_pos: int _splitter_min: int - _schedule: Union[Schedule, None] def __init__(self): - """Initialize Schedule-gui.""" + """Initialize Scheduler-gui.""" super().__init__() + self._schedule = None self._graph = None - self._open_file_dialog_opened = False - self._scale = 75 + self._scale = 75.0 self._debug_rects = None - self._schedule = None QIcon.setThemeName('breeze') log.debug('themeName: \'{}\''.format(QIcon.themeName())) @@ -149,9 +137,9 @@ class MainWindow(QMainWindow, Ui_MainWindow): 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.toggle_component_info) - self.menu_exit_dialog .triggered .connect(self.toggle_exit_dialog) - self.actionT .triggered .connect(self.actionTbtn) + 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) self.splitter .splitterMoved .connect(self._splitter_moved) # Setup event member functions @@ -181,6 +169,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): @property def schedule(self) -> Schedule: + """Get the current schedule.""" return self._schedule @@ -188,15 +177,18 @@ class MainWindow(QMainWindow, Ui_MainWindow): #### Slots #### ############### @Slot() - def actionTbtn(self) -> None: + def _actionTbtn(self) -> None: + # TODO: remove self.schedule.plot_schedule() print(f'filtersChildEvents(): {self._graph.filtersChildEvents()}') - # self.printButtonPressed('callback_pushButton()') + # self._printButtonPressed('callback_pushButton()') @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.""" settings = QSettings() - # open_dir = QStandardPaths.standardLocations(QStandardPaths.HomeLocation)[0] if not self._open_file_dialog_opened else '' 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) + '/' @@ -211,7 +203,6 @@ class MainWindow(QMainWindow, Ui_MainWindow): if not abs_path_filename: # return if empty filename (QFileDialog was canceled) return log.debug('abs_path_filename = {}.'.format(abs_path_filename)) - self._open_file_dialog_opened = True module_name = inspect.getmodulename(abs_path_filename) if not module_name: # return if empty module name @@ -253,9 +244,11 @@ class MainWindow(QMainWindow, Ui_MainWindow): @Slot() def close_schedule(self) -> None: + """SLOT() for SIGNAL(menu_close_schedule.triggered) + Closes current schedule.""" if self._graph: - self._graph.component_selected.disconnect(self.info_table_update_component) - self._graph.schedule_time_changed.disconnect(self.info_table_update_schedule) + self._graph.signals.component_selected.disconnect(self.info_table_update_component) + self._graph.signals.schedule_time_changed.disconnect(self.info_table_update_schedule) self._graph.removeSceneEventFilters(self._graph.event_items) self._scene.removeItem(self._graph) self.menu_close_schedule.setEnabled(False) @@ -267,21 +260,25 @@ class MainWindow(QMainWindow, Ui_MainWindow): @Slot() def save(self) -> None: - """This method save an schedule.""" + """SLOT() for SIGNAL(menu_save.triggered) + This method save an schedule.""" #TODO: all - self.printButtonPressed('save_schedule()') + self._printButtonPressed('save_schedule()') self.update_statusbar(self.tr('Schedule saved successfully')) @Slot() def save_as(self) -> None: - """This method save as an schedule.""" + """SLOT() for SIGNAL(menu_save_as.triggered) + This method save as an schedule.""" #TODO: all - self.printButtonPressed('save_schedule()') + self._printButtonPressed('save_schedule()') self.update_statusbar(self.tr('Schedule saved successfully')) @Slot(bool) - def toggle_component_info(self, checked: bool) -> None: - """This method toggles the right hand side info window.""" + 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'.""" # Note: splitter handler index 0 is a hidden splitter handle far most left, use index 1 # settings = QSettings() _, max = self.splitter.getRange(1) # tuple(min, max) @@ -295,13 +292,17 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.splitter.moveSplitter(max, 1) @Slot(bool) - def toggle_exit_dialog(self, checked: bool) -> None: + 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) @Slot(int, int) def _splitter_moved(self, pos: int, index: int) -> None: - """Callback method used to check if the right widget (info window) + """SLOT(int, int) for SIGNAL(splitter.splitterMoved) + Callback method used to check if the right widget (info window) has collapsed. Update the checkbutton accordingly.""" width = self.splitter.sizes()[1] @@ -315,7 +316,8 @@ class MainWindow(QMainWindow, Ui_MainWindow): @Slot(str) def info_table_update_component(self, op_id: str) -> None: - """Taked in an operator-id, first clears the 'Operator' part of the info + """SLOT(str) for SIGNAL(_graph.signals.component_selected) + Taked 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'.""" self.info_table_clear_component() @@ -323,11 +325,16 @@ class MainWindow(QMainWindow, Ui_MainWindow): @Slot() def info_table_update_schedule(self) -> None: - """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) + 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.""" self._scene.setSceneRect(self._scene.itemsBoundingRect()) @@ -336,7 +343,9 @@ class MainWindow(QMainWindow, Ui_MainWindow): #### Events #### ################ def _close_event(self, event: QCloseEvent) -> None: - """Replaces QMainWindow default closeEvent(QCloseEvent) event""" + """EVENT: Replaces QMainWindow default closeEvent(QCloseEvent) event. Takes + in a QCloseEvent and display an exit dialog, depending on + 'hide_exit_dialog' in settings.""" s = QSettings() hide_dialog = s.value('mainwindow/hide_exit_dialog', False, bool) ret = QMessageBox.StandardButton.Yes @@ -370,7 +379,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): ################################# #### Helper member functions #### ################################# - def printButtonPressed(self, func_name: str) -> None: + def _printButtonPressed(self, func_name: str) -> None: #TODO: remove alert = QMessageBox(self) @@ -386,14 +395,13 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.menu_close_schedule.setEnabled(True) self._scene.addItem(self._graph) self._graph.installSceneEventFilters(self._graph.event_items) - self._graph.component_selected.connect(self.info_table_update_component) - self._graph.schedule_time_changed.connect(self.info_table_update_schedule) - + self._graph.signals.component_selected.connect(self.info_table_update_component) + self._graph.signals.schedule_time_changed.connect(self.info_table_update_schedule) self.info_table_fill_schedule(self.schedule) self.update_statusbar(self.tr('Schedule loaded successfully')) def update_statusbar(self, msg: str) -> None: - """Write the given str to the statusbar with temporarily policy.""" + """Takes in an str and write 'msg' to the statusbar with temporarily policy.""" self.statusbar.showMessage(msg) def _write_settings(self) -> None: @@ -475,7 +483,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): row = self.info_table.findItems('Operator', Qt.MatchExactly) if row: row = row[0].row() - if row > 1: + if row > 2: for _ in range(3): self.info_table.removeRow(1) else: