diff --git a/b_asic/scheduler-gui/__init__.py b/b_asic/scheduler-gui/__init__.py index 469917bc7168f59aee898e7d49a2818e08a3976b..da8542a02e3b46d6ea02cfea99ada0ddb4bc12aa 100644 --- a/b_asic/scheduler-gui/__init__.py +++ b/b_asic/scheduler-gui/__init__.py @@ -5,7 +5,7 @@ Graphical user interface for B-ASIC scheduler. from logger import * from main_window import * -from graphics_scene import * +from graphics_graph import * #__all__ = ['main_window', 'scheduler'] __version__ = '0.1' diff --git a/b_asic/scheduler-gui/component_item.py b/b_asic/scheduler-gui/component_item.py index 3c9e4ae2a83067cd4ba70d0692410fe06856ac1f..aec497cd3d0f00aaacfc58bc1d04880922beed0f 100644 --- a/b_asic/scheduler-gui/component_item.py +++ b/b_asic/scheduler-gui/component_item.py @@ -31,30 +31,46 @@ from b_asic.schedule import Schedule # class ComponentItem(QGraphicsItemGroup, QGraphicsLayoutItem): -class ComponentItem(QGraphicsItemGroup, QGraphicsLayoutItem): +class ComponentItem(QGraphicsItemGroup): # class ComponentItem(QGraphicsLayoutItem, QGraphicsItemGroup): # class ComponentItem(QGraphicsLayoutItem, QGraphicsItem): _scale: float + _height: float _component_item: QGraphicsPathItem _execution_time_item: QGraphicsPathItem _item_group: QGraphicsItemGroup - def __init__(self, scale: float, parent: QGraphicsWidget = None): - QGraphicsItemGroup.__init__(self) - QGraphicsLayoutItem.__init__(self, parent = parent) - self.setGraphicsItem(self) + def __init__(self, scale: float, height: float = 1.5): + super().__init__() self._scale = scale + self._height = height + self._component_item = QGraphicsPathItem() print('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! A') self._item_group = QGraphicsItemGroup() print('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! B') self._populate() + self.setFlag(QGraphicsItem.ItemIsMovable) + self.setFlag(QGraphicsItem.ItemIsSelectable) # print(self.boundingRect().size()) - def _populate(self): + @property + def scale(self) -> float: + return self._scale + @scale.setter + def scale(self, scale: float) -> None: + self._scale = scale() + self.prepareGeometryChange() + self.update() + + @property + def height(self) -> float: + return self._height + + def _populate(self) -> None: # brush = QBrush(Qt.lightGray, bs=Qt.SolidPattern) brush = QBrush(Qt.lightGray) # brush.setStyle(Qt.SolidPattern) @@ -66,20 +82,22 @@ class ComponentItem(QGraphicsItemGroup, QGraphicsLayoutItem): # component path component_path = QPainterPath(QPoint(0,0)) - component_path.lineTo(0, 1) - component_path.lineTo(4, 1) + component_path.lineTo(0, self._height/2) + component_path.lineTo(0.2, self._height/2) + component_path.lineTo(0.2, self._height) + component_path.lineTo(4, self._height) component_path.lineTo(4, 0) component_path.closeSubpath() # component item - self._component_item = QGraphicsPathItem(component_path) + self._component_item.setPath(component_path) self._component_item.setPen(pen) self._component_item.setBrush(brush) self._component_item.setPos(0.5,0) # in parent (i.e. item_group) coordinates # execution time square execution_time_path = QPainterPath(QPoint(0,0)) - execution_time_path.addRect(0, 0, 1.0, 1.0) + execution_time_path.addRect(0, 0, self._height, self._height) # execution time item green_color = QColor(Qt.magenta) @@ -113,41 +131,7 @@ class ComponentItem(QGraphicsItemGroup, QGraphicsLayoutItem): - - # reimplement QGraphicsLayoutItem virtual functions - def updateGeometry(self): - print('updateGeometry()') - QGraphicsLayoutItem.updateGeometry(self) - - def setGeometry(self, geom: QRectF) -> None: - print(f'setGeometry({geom})') - self.prepareGeometryChange() - QGraphicsLayoutItem.setGeometry(self, geom) - self.setPos(geom.topLeft()) - - def sizeHint(self, which: Qt.SizeHint, constraint: QSizeF = QSizeF()) -> QSizeF: - print(f'sizeHint(which={which}, constraint={constraint}') - # return QSizeF(1000, 100) - # if self.isEmpty(): - # pass - - # item = self.graphicsItem() - switch = { - Qt.MinimumSize: self.boundingRect().size(), - Qt.PreferredSize: self.boundingRect().size(), - # Qt.MinimumSize: self.geometry().size(), - # Qt.PreferredSize: self.geometry().size(), - Qt.MaximumSize: QSizeF(float("inf"), float("inf")) - # Qt.MaximumSize: self.parentItem().boundingRect().size() - } - ret = switch.get(which, constraint) - print(f'ret: {ret}') - return switch.get(which, constraint) - - def minimumSize(self): - print('minimumSize()') - - + # # reimplement QGraphicsItem virtual functions # def boundingRect(self) -> QRectF: # print('boundingRect()') diff --git a/b_asic/scheduler-gui/graphics_axis.py b/b_asic/scheduler-gui/graphics_axis.py new file mode 100644 index 0000000000000000000000000000000000000000..941facc9b768633c8737dc06253b065f7ea99827 --- /dev/null +++ b/b_asic/scheduler-gui/graphics_axis.py @@ -0,0 +1,120 @@ +import os +import sys +from typing import Any, Optional +from pprint import pprint +from typing import Any, AnyStr, Generic, Protocol, TypeVar, Union, Optional, overload, Final, final +# from typing_extensions import Self, Final, Literal, LiteralString, TypeAlias, final +import numpy as np +from copy import deepcopy +from itertools import combinations + +import qtpy +from qtpy import QtCore +from qtpy import QtGui +from qtpy import QtWidgets + +# QGraphics and QPainter imports +from qtpy.QtCore import ( + Qt, QObject, QRect, QRectF, QPoint, QSize, QSizeF, QByteArray) +from qtpy.QtGui import ( + QPaintEvent, QPainter, QPainterPath, QColor, QBrush, QPen, QFont, QPolygon, QIcon, QPixmap, + QLinearGradient, QTransform) +from qtpy.QtWidgets import ( + QGraphicsView, QGraphicsScene, QGraphicsWidget, + QGraphicsLayout, QGraphicsLinearLayout, QGraphicsGridLayout, QGraphicsLayoutItem, QGraphicsAnchorLayout, + QGraphicsItem, QGraphicsItemGroup, QGraphicsPathItem, QGraphicsLineItem, QGraphicsTextItem, + QStyleOptionGraphicsItem, QWidget, QGraphicsObject) +from qtpy.QtCore import ( + QPoint, QPointF) + +# B-ASIC +import logger + + + +class GraphicsAxis(QGraphicsItemGroup): + + _scale: float + _width: float + _height: float + _axis: dict[str: Any] + + + def __init__(self, scale: float, width: float = 0.0, height: float = 0.0): + super().__init__() + + self._scale = scale + self._width = width + self._height = height + self._axis = {} + # self._axis['x'] = QGraphicsItemGroup() + # self._axis['y'] = QGraphicsLineItem() + + self._make_axis() + + + @property + def width(self) -> float: + return self._axis['w'] + @width.setter + def width(self, width: float) -> None: + self._axis['w'] = width + self.prepareGeometryChange() + self._axis.clear() + self._make_axis() + + @property + def height(self) -> float: + return self._axis['h'] + @height.setter + def height(self, height: float) -> None: + self._axis['h'] = height + self.prepareGeometryChange() + self._axis.clear() + self._make_axis() + + # def _clear(self) -> None: + # for child in self._axis.values(): + # del child + + def _make_axis(self) -> None: + + pen = QPen() + pen.setWidthF(2/self._scale) + + # x-axis + self._axis['x'] = QGraphicsItemGroup() + line1 = QGraphicsLineItem(0, 0, self._width, 0) + line1.setPen(pen) + self._axis['x'].addToGroup(line1) + # x-axis arrow + line2 = QGraphicsLineItem(0, 0, -5/self._scale, -5/self._scale) + line2.setPen(pen) + line2.setPos(self._width, 0) + self._axis['x'].addToGroup(line2) + line3 = QGraphicsLineItem(0, 0, -5/self._scale, 5/self._scale) + line3.setPen(pen) + line3.setPos(self._width, 0) + self._axis['x'].addToGroup(line3) + self._axis['x'].setPos(0, self._height) + # x-axis scale + ticks = [x for x in range(int(self._width) + 1)] + # print('xxxxxxxxxxxxxxxxxxxxxxx', x) + line4 = QGraphicsLineItem(0, -5/self._scale, 0, 5/self._scale) + # for i in range(len(ticks)): + # lines[i] = + + # x-axis label + label = QGraphicsTextItem('time') + label.setFlag(QGraphicsItem.ItemIgnoresTransformations) + label.setPos(self._width, self._height + 5/self._scale) + # add x-axis + self.addToGroup(self._axis['x']) + self.addToGroup(label) + + # y-axis + self._axis['y'] = QGraphicsLineItem(0, 0, 0, self._height) + self._axis['y'].setPen(pen) + self._axis['y'].setPos(0, 0) + # add y-axis + self.addToGroup(self._axis['y']) \ No newline at end of file diff --git a/b_asic/scheduler-gui/graphics_graph.py b/b_asic/scheduler-gui/graphics_graph.py new file mode 100644 index 0000000000000000000000000000000000000000..993d97535caf8bf72274cfdbb484e1096926c510 --- /dev/null +++ b/b_asic/scheduler-gui/graphics_graph.py @@ -0,0 +1,86 @@ +import os +import sys +from typing import Any, Optional +from pprint import pprint +from typing import Any, AnyStr, Generic, Protocol, TypeVar, Union, Optional, overload, Final, final +# from typing_extensions import Self, Final, Literal, LiteralString, TypeAlias, final +import numpy as np +from copy import deepcopy +from itertools import combinations + +import qtpy +from qtpy import QtCore +from qtpy import QtGui +from qtpy import QtWidgets + +# QGraphics and QPainter imports +from qtpy.QtCore import ( + Qt, QObject, QRect, QRectF, QPoint, QSize, QSizeF, QByteArray) +from qtpy.QtGui import ( + QPaintEvent, QPainter, QPainterPath, QColor, QBrush, QPen, QFont, QPolygon, QIcon, QPixmap, + QLinearGradient, QTransform) +from qtpy.QtWidgets import ( + QGraphicsView, QGraphicsScene, QGraphicsWidget, + QGraphicsLayout, QGraphicsLinearLayout, QGraphicsGridLayout, QGraphicsLayoutItem, QGraphicsAnchorLayout, + QGraphicsItem, QGraphicsItemGroup, QGraphicsPathItem, QGraphicsLineItem, + QStyleOptionGraphicsItem, QWidget, QGraphicsObject) +from qtpy.QtCore import ( + QPoint, QPointF) + +# B-ASIC +import logger +from b_asic.schedule import Schedule +from component_item import ComponentItem +from graphics_axis import GraphicsAxis + + +class GraphicsGraph(QGraphicsItemGroup): + + _schedule: Schedule + _scale: float + _axis: dict[str, QGraphicsLineItem] # {'x': <x-axis>, 'y': y-axis} + _component_group: QGraphicsItemGroup + _axis_group: QGraphicsItemGroup + + + def __init__(self, schedule: Schedule, scale: float = 100.0, *args, **kwargs): + super().__init__(*args, **kwargs) + + self._schedule = deepcopy(schedule) + self._scale = scale + self._axis = {} + self._component_group = QGraphicsItemGroup() + self._axis_group = QGraphicsItemGroup() + + # add components + y: float = 0.0 + for i in range(3): + component = ComponentItem(self._scale) + component.setPos(0, y) + self._component_group.addToGroup(component) + y += component.height + 0.2 + + self._component_group.setPos(0.2, 0.1) + self.addToGroup(self._component_group) + + y += 0.1 + # add x-axis + # self._component_group.boundingRect().width() + axis = GraphicsAxis(self._scale, 5, y) + self.addToGroup(axis) + + + + + @property + def scale(self) -> float: + return self._scale + @scale.setter + def scale(self, scale:float) -> None: + self._scale = scale + for component in self._component_group.childItems(): + component.scale = scale + + @property + def items(self) -> list[ComponentItem]: + return self._component_group.childItems() \ No newline at end of file diff --git a/b_asic/scheduler-gui/main_window.py b/b_asic/scheduler-gui/main_window.py index a6e5c212c267361c0898758c6c978cb3e93a2a27..b05b923019e8006ae97be235e20cdc96267c93a9 100644 --- a/b_asic/scheduler-gui/main_window.py +++ b/b_asic/scheduler-gui/main_window.py @@ -45,7 +45,7 @@ from qtpy.QtWidgets import ( # B-ASIC import logger from b_asic.schedule import Schedule -from graphics_scene import GraphicsScene +from graphics_graph import GraphicsGraph from component_item import ComponentItem @@ -109,10 +109,10 @@ QCoreApplication.setApplicationName('B-ASIC Scheduler') class MainWindow(QMainWindow, Ui_MainWindow): """Schedule of an SFG with scheduled Operations.""" # _schedules: dict - _scenes: dict[str, GraphicsScene] + # _scenes: dict[str, GraphicsScene] _scene: QGraphicsScene - _diagrams: dict[str, QGraphicsWidget] - _diagram_count: int + _graphs: dict[str, QGraphicsWidget] + _graph_count: int _scene_count: int _open_file_dialog_opened: bool _scale: float @@ -121,9 +121,9 @@ class MainWindow(QMainWindow, Ui_MainWindow): """Initialize Schedule-gui.""" super().__init__() self._scenes = {} - self._diagrams = {} + self._graphs = {} self._scene_count = 0 - self._diagram_count = 0 + self._graph_count = 0 self._open_file_dialog_opened = False self._scale = 100.0 @@ -270,45 +270,13 @@ class MainWindow(QMainWindow, Ui_MainWindow): # self._scene_count += 1 - # windowLayout = QGraphicsLinearLayout(Qt.Vertical) - vertical = QGraphicsLinearLayout(Qt.Vertical) - # linear1 = QGraphicsLinearLayout(windowLayout) - linear1 = QGraphicsLinearLayout(Qt.Horizontal) - # linear1.setAlignment(Qt.AlignLeft| Qt.AlignTop) - linear2 = QGraphicsLinearLayout(Qt.Horizontal) - linear1.setMaximumSize(linear1.minimumSize()) - linear2.setMaximumSize(linear2.minimumSize()) - vertical.setMaximumSize(vertical.minimumSize()) - linear1.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred, QSizePolicy.DefaultType) - linear2.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred, QSizePolicy.DefaultType) - vertical.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred, QSizePolicy.DefaultType) - widget = QGraphicsWidget() - item1 = ComponentItem(self._scale) - item2 = ComponentItem(self._scale) - item3 = ComponentItem(self._scale) - linear1.addItem(item1) - linear1.setStretchFactor(item1, 1) - linear1.addItem(item2) - linear2.addItem(item3) - print('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 1') - vertical.addItem(linear1) - vertical.addItem(linear2) - print('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 2') - print(f'boundingRect: {item1.boundingRect()}') - print(f'vertical.getContentsMargins(): {vertical.getContentsMargins()}') - print(f'linear1.getContentsMargins(): {linear1.getContentsMargins()}') - print(f'linear2.getContentsMargins(): {linear2.getContentsMargins()}') - - # widget.setLayout(windowLayout) - widget.setLayout(vertical) - widget.setWindowTitle(self.tr("Basic Graphics Layouts Example")) - print('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 3') - - self._scene.addItem(widget) + graph = GraphicsGraph(schedule, self._scale) + + self._scene.addItem(graph) print('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 4') - self._diagrams[self._diagram_count] = widget - self._diagram_count += 1 + self._graphs[self._graph_count] = graph + self._graph_count += 1 self.update_statusbar(self.tr('Schedule loaded successfully')) @Slot() diff --git a/b_asic/scheduler-gui/graphics_scene.py b/b_asic/scheduler-gui/tests/graphics_scene.py similarity index 100% rename from b_asic/scheduler-gui/graphics_scene.py rename to b_asic/scheduler-gui/tests/graphics_scene.py diff --git a/b_asic/scheduler-gui/tests/layout_version/component_item.py b/b_asic/scheduler-gui/tests/layout_version/component_item.py new file mode 100644 index 0000000000000000000000000000000000000000..3c9e4ae2a83067cd4ba70d0692410fe06856ac1f --- /dev/null +++ b/b_asic/scheduler-gui/tests/layout_version/component_item.py @@ -0,0 +1,163 @@ +import os +import sys +from typing import Any, Optional +from pprint import pprint +from typing import Any, AnyStr, Generic, Protocol, TypeVar, Union, Optional, overload, Final, final +# from typing_extensions import Self, Final, Literal, LiteralString, TypeAlias, final +import numpy as np + +import qtpy +from qtpy import QtCore +from qtpy import QtGui +from qtpy import QtWidgets + +# QGraphics and QPainter imports +from qtpy.QtCore import ( + Qt, QObject, QRect, QRectF, QPoint, QSize, QSizeF, QByteArray) +from qtpy.QtGui import ( + QPaintEvent, QPainter, QPainterPath, QColor, QBrush, QPen, QFont, QPolygon, QIcon, QPixmap, + QLinearGradient, QTransform) +from qtpy.QtWidgets import ( + QGraphicsView, QGraphicsScene, QGraphicsWidget, + QGraphicsLayout, QGraphicsLinearLayout, QGraphicsGridLayout, QGraphicsLayoutItem, QGraphicsAnchorLayout, + QGraphicsItem, QGraphicsItemGroup, QGraphicsPathItem, + QStyleOptionGraphicsItem, QWidget) +from qtpy.QtCore import ( + QPoint, QPointF) + +# B-ASIC +import logger +from b_asic.schedule import Schedule + + +# class ComponentItem(QGraphicsItemGroup, QGraphicsLayoutItem): +class ComponentItem(QGraphicsItemGroup, QGraphicsLayoutItem): +# class ComponentItem(QGraphicsLayoutItem, QGraphicsItemGroup): +# class ComponentItem(QGraphicsLayoutItem, QGraphicsItem): + + _scale: float + _component_item: QGraphicsPathItem + _execution_time_item: QGraphicsPathItem + _item_group: QGraphicsItemGroup + + + def __init__(self, scale: float, parent: QGraphicsWidget = None): + QGraphicsItemGroup.__init__(self) + QGraphicsLayoutItem.__init__(self, parent = parent) + self.setGraphicsItem(self) + + self._scale = scale + print('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! A') + self._item_group = QGraphicsItemGroup() + print('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! B') + self._populate() + + # print(self.boundingRect().size()) + + def _populate(self): + # brush = QBrush(Qt.lightGray, bs=Qt.SolidPattern) + brush = QBrush(Qt.lightGray) + # brush.setStyle(Qt.SolidPattern) + pen = QPen(Qt.SolidLine) + pen.setWidthF(1/self._scale) + pen.setBrush(Qt.darkGray) + # pen.setCapStyle(Qt.RoundCap) # Qt.FlatCap, Qt.SquareCap (default), Qt.RoundCap + pen.setJoinStyle(Qt.RoundJoin) # Qt.MiterJoin, Qt.BevelJoin (default), Qt.RoundJoin, Qt.SvgMiterJoin + + # component path + component_path = QPainterPath(QPoint(0,0)) + component_path.lineTo(0, 1) + component_path.lineTo(4, 1) + component_path.lineTo(4, 0) + component_path.closeSubpath() + + # component item + self._component_item = QGraphicsPathItem(component_path) + self._component_item.setPen(pen) + self._component_item.setBrush(brush) + self._component_item.setPos(0.5,0) # in parent (i.e. item_group) coordinates + + # execution time square + execution_time_path = QPainterPath(QPoint(0,0)) + execution_time_path.addRect(0, 0, 1.0, 1.0) + + # execution time item + green_color = QColor(Qt.magenta) + green_color.setAlpha(200) # 0-255 + pen.setColor(green_color) + self._execution_time_item = QGraphicsPathItem(execution_time_path) + self._execution_time_item.setPen(pen) + + # item group, consist of time_item and component_item + # item_group = QGraphicsItemGroup() + + # graphics_item = self.graphicsItem() + # print(graphics_item) + # self._item_group = graphics_item.childItems()[0] + # print(self._item_group) + # # item_group.setScale(self._scale) + # print('############################# 1') + # self._item_group.addToGroup(self._component_item) + # print('############################# 2') + # self._item_group.addToGroup(self._execution_time_item) + + print('############################# 1') + self.addToGroup(self._component_item) + print('############################# 2') + self.addToGroup(self._execution_time_item) + + # self.setGraphicsItem(self) + # QGraphicsItemGroup + # self.setGroup(item_group) + print('Populated!') + + + + + # reimplement QGraphicsLayoutItem virtual functions + def updateGeometry(self): + print('updateGeometry()') + QGraphicsLayoutItem.updateGeometry(self) + + def setGeometry(self, geom: QRectF) -> None: + print(f'setGeometry({geom})') + self.prepareGeometryChange() + QGraphicsLayoutItem.setGeometry(self, geom) + self.setPos(geom.topLeft()) + + def sizeHint(self, which: Qt.SizeHint, constraint: QSizeF = QSizeF()) -> QSizeF: + print(f'sizeHint(which={which}, constraint={constraint}') + # return QSizeF(1000, 100) + # if self.isEmpty(): + # pass + + # item = self.graphicsItem() + switch = { + Qt.MinimumSize: self.boundingRect().size(), + Qt.PreferredSize: self.boundingRect().size(), + # Qt.MinimumSize: self.geometry().size(), + # Qt.PreferredSize: self.geometry().size(), + Qt.MaximumSize: QSizeF(float("inf"), float("inf")) + # Qt.MaximumSize: self.parentItem().boundingRect().size() + } + ret = switch.get(which, constraint) + print(f'ret: {ret}') + return switch.get(which, constraint) + + def minimumSize(self): + print('minimumSize()') + + + # # reimplement QGraphicsItem virtual functions + # def boundingRect(self) -> QRectF: + # print('boundingRect()') + # # return self._item_group.boundingRect() + # return QRectF(QPointF(0,0), self.geometry().size()) + + # def paint(self, painter: QPainter, option: QStyleOptionGraphicsItem, widget: Optional[QWidget]= None) -> None: + # print(f'paint(painter={painter}, option={option}, widget={widget})') + # painter.drawRoundedRect(-10, -10, 20, 20, 5, 5) + # # self._item_group.paint(painter, option, widget) + +# print("MRO:") +# pprint(ComponentItem.__mro__) \ No newline at end of file diff --git a/b_asic/scheduler-gui/tests/layout_version/main_window.py b/b_asic/scheduler-gui/tests/layout_version/main_window.py new file mode 100644 index 0000000000000000000000000000000000000000..a6e5c212c267361c0898758c6c978cb3e93a2a27 --- /dev/null +++ b/b_asic/scheduler-gui/tests/layout_version/main_window.py @@ -0,0 +1,451 @@ +# This Python file uses the following encoding: utf-8 +"""B-ASIC Scheduler-gui Module. + +Contains the scheduler-gui class for scheduling operations in an SFG. + +Start main-window with start_gui(). +""" + + + + + +import os +import sys +from pathlib import Path +from types import ModuleType +from typing import Any +from pprint import pprint +#from matplotlib.pyplot import bar +#from diagram import * +from importlib.machinery import SourceFileLoader +import inspect + + +# Qt/qtpy +import qtpy +from qtpy import uic, QtCore, QtGui, QtWidgets +from qtpy.QtCore import QCoreApplication, Qt, Slot, QSettings, QStandardPaths +from qtpy.QtGui import QCloseEvent +from qtpy.QtWidgets import ( + QApplication, QMainWindow, QMessageBox, QFileDialog, QInputDialog, QCheckBox, QAbstractButton, + QTableWidgetItem, QSizePolicy) + +# QGraphics and QPainter imports +from qtpy.QtCore import ( + QRect, QRectF, QPoint, QSize, QByteArray) +from qtpy.QtGui import ( + QPaintEvent, QPainter, QPainterPath, QColor, QBrush, QPen, QFont, QPolygon, QIcon, QPixmap, + QLinearGradient) +from qtpy.QtWidgets import ( + QGraphicsView, QGraphicsScene, QGraphicsWidget, + QGraphicsLayout, QGraphicsLinearLayout, QGraphicsGridLayout, QGraphicsLayoutItem, QGraphicsAnchorLayout, + QGraphicsItem, QGraphicsItemGroup) + +# B-ASIC +import logger +from b_asic.schedule import Schedule +from graphics_scene import GraphicsScene +from component_item import ComponentItem + + +log = logger.getLogger() + +# Debug struff +if __debug__: + log.setLevel('DEBUG') + +if __debug__: + # Print some system version information + QT_API = os.environ.get('QT_API') + 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 + log.debug('PySide version: {}'.format(PySide2.__version__)) + if QT_API.lower().startswith('pyqt'): + from qtpy.QtCore import PYQT_VERSION_STR + log.debug('PyQt version: {}'.format(PYQT_VERSION_STR)) + log.debug('QtPy version: {}'.format(qtpy.__version__)) + + # 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 + os_ = sys.platform + if os_.startswith('linux'): + 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 + try: + from ui_main_window import Ui_MainWindow + except: # Everything failed, exit + 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'") + os._exit(1) + + +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 folowing QCoreApplication values is used for QSettings among others +QCoreApplication.setOrganizationName('Linöping University') +QCoreApplication.setOrganizationDomain('liu.se') +QCoreApplication.setApplicationName('B-ASIC Scheduler') +#QCoreApplication.setApplicationVersion(__version__) # TODO: read from packet __version__ + + + + +class MainWindow(QMainWindow, Ui_MainWindow): + """Schedule of an SFG with scheduled Operations.""" + # _schedules: dict + _scenes: dict[str, GraphicsScene] + _scene: QGraphicsScene + _diagrams: dict[str, QGraphicsWidget] + _diagram_count: int + _scene_count: int + _open_file_dialog_opened: bool + _scale: float + + def __init__(self): + """Initialize Schedule-gui.""" + super().__init__() + self._scenes = {} + self._diagrams = {} + self._scene_count = 0 + self._diagram_count = 0 + self._open_file_dialog_opened = False + self._scale = 100.0 + + QIcon.setThemeName('breeze') + log.debug('themeName: \'{}\''.format(QIcon.themeName())) + log.debug('themeSearchPaths: {}'.format(QIcon.themeSearchPaths())) + self._init_ui() + self._init_graphics() + self._read_settings() + + + + def _init_ui(self) -> None: + """Initialize the ui""" + self.setupUi(self) + + # Connect signals to slots + self.menu_load_from_file.triggered .connect(self._load_schedule_from_pyfile) + 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.splitter_center .splitterMoved .connect(self._splitter_center_moved) + + # Setup event member functions + self.closeEvent = self._close_event + + # Setup info table + self.info_table.setHorizontalHeaderLabels(['Property','Value']) + # test = '#b085b2' + # self.info_table.setStyleSheet('alternate-background-color: lightGray;background-color: white;') + self.info_table.setStyleSheet('alternate-background-color: #fadefb;background-color: #ebebeb;') + for i in range(10): + self.info_table.insertRow(i) + item = QTableWidgetItem('this is a very very very very long string that says abolutly nothing') + self.info_table.setItem(i,0, QTableWidgetItem('property {}: '.format(i))) + self.info_table.setItem(i,1,item) + + # Init central-widget splitter + self.splitter_center.setStretchFactor(0, 1) + self.splitter_center.setStretchFactor(1, 0) + self.splitter_center.setCollapsible(0, False) + self.splitter_center.setCollapsible(1, True) + + def _init_graphics(self) -> None: + """Initialize the QGraphics framework""" + # scene = GraphicsScene(0, parent=self) + # self.graphic_view.setScene(scene) + # self.graphic_view.setRenderHint(QPainter.Antialiasing) + # self.graphic_view.setGeometry(20, 20, self.width(), self.height()) + self.graphics_view.setDragMode(QGraphicsView.RubberBandDrag) + self.graphics_view.scale(self._scale, self._scale) + self._scene = QGraphicsScene() + self.graphics_view.setScene(self._scene) + + + + + ############### + #### Slots #### + ############### + @Slot() + def actionTbtn(self) -> None: + # print('_scene_count:', self._scene_count) + scene = self._scenes[self._scene_count - 1] + sched = scene.schedule + print('From MainWindow:\t\t\t', end='') + pprint(sched) + # print('') + self._scenes[self._scene_count - 1].plot_schedule() + # self.printButtonPressed('callback_pushButton()') + + @Slot() + def _load_schedule_from_pyfile(self) -> None: + open_dir = QStandardPaths.standardLocations(QStandardPaths.HomeLocation)[0] if not self._open_file_dialog_opened else '' + abs_path_filename = QFileDialog.getOpenFileName(self, self.tr("Open python file"), + open_dir, + self.tr("Python Files (*.py *.py3)")) + abs_path_filename = abs_path_filename[0] + + 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 + 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 + + schedule_obj_list = dict(inspect.getmembers(module, (lambda x: type(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))) + del module + return + + ret_tuple = QInputDialog.getItem(self, + self.tr('Load object'), + self.tr('Found the following Schedule object(s) in file.)\n\n' + 'Select an object to proceed:'), + schedule_obj_list.keys(),0,False) + + if not ret_tuple[1]: # User canceled the operation + log.debug('Load schedule operation: user canceled') + del module + return + + self.open(schedule_obj_list[ret_tuple[0]]) + del module + + + #@Slot() + def open(self, schedule: Schedule) -> None: + """Takes in an Schedule and place it in the schedule list.""" + #TODO: all + + #TODO: Unique hash keys + #TODO: self.open(schedule_obj_list[ret_tuple[0]) + + # scene = GraphicsScene(self._scene_count, schedule, self.graphics_view) + # #scene = QGraphicsScene() + # self._scenes[self._scene_count] = scene + # self.graphics_view.setScene(scene) + + # self.graphics_view.setRenderHint(QPainter.Antialiasing) + # # self.graphics_view.setGeometry(20, 20, self.width(), self.height()) + # self.graphics_view.setDragMode(QGraphicsView.RubberBandDrag) + # # self.graphics_view.scale(10.0, 10.0) + + # self._scene_count += 1 + + + # windowLayout = QGraphicsLinearLayout(Qt.Vertical) + vertical = QGraphicsLinearLayout(Qt.Vertical) + # linear1 = QGraphicsLinearLayout(windowLayout) + linear1 = QGraphicsLinearLayout(Qt.Horizontal) + # linear1.setAlignment(Qt.AlignLeft| Qt.AlignTop) + linear2 = QGraphicsLinearLayout(Qt.Horizontal) + linear1.setMaximumSize(linear1.minimumSize()) + linear2.setMaximumSize(linear2.minimumSize()) + vertical.setMaximumSize(vertical.minimumSize()) + linear1.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred, QSizePolicy.DefaultType) + linear2.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred, QSizePolicy.DefaultType) + vertical.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred, QSizePolicy.DefaultType) + widget = QGraphicsWidget() + item1 = ComponentItem(self._scale) + item2 = ComponentItem(self._scale) + item3 = ComponentItem(self._scale) + linear1.addItem(item1) + linear1.setStretchFactor(item1, 1) + linear1.addItem(item2) + linear2.addItem(item3) + print('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 1') + vertical.addItem(linear1) + vertical.addItem(linear2) + print('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 2') + print(f'boundingRect: {item1.boundingRect()}') + print(f'vertical.getContentsMargins(): {vertical.getContentsMargins()}') + print(f'linear1.getContentsMargins(): {linear1.getContentsMargins()}') + print(f'linear2.getContentsMargins(): {linear2.getContentsMargins()}') + + # widget.setLayout(windowLayout) + widget.setLayout(vertical) + widget.setWindowTitle(self.tr("Basic Graphics Layouts Example")) + print('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 3') + + self._scene.addItem(widget) + + print('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 4') + self._diagrams[self._diagram_count] = widget + self._diagram_count += 1 + self.update_statusbar(self.tr('Schedule loaded successfully')) + + @Slot() + def save(self) -> None: + """This method save an schedule.""" + #TODO: all + self.printButtonPressed('save_schedule()') + self.update_statusbar(self.tr('Schedule saved successfully')) + + @Slot() + def save_as(self) -> None: + """This method save as an schedule.""" + #TODO: all + 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.""" + # Note: splitter handler index 0 is a hidden splitter handle far most left, use index 1 + settings = QSettings() + range = self.splitter_center.getRange(1) # tuple(min, max) + + if checked: + self.splitter_center.restoreState(settings.value("mainwindow/splitter_center/last_state")) + # self.splitter_center.restoreState(settings.value("splitterSizes")) + else: + settings.setValue("mainwindow/splitter_center/last_state", self.splitter_center.saveState()) + self.splitter_center.moveSplitter(range[1], 1) + + @Slot(bool) + def toggle_exit_dialog(self, checked: bool) -> None: + s = QSettings() + s.setValue("mainwindow/hide_exit_dialog", checked) + + @Slot(int, int) + def _splitter_center_moved(self, pos: int, index: int) -> None: + """Callback method used to check if the right widget (info window) + has collapsed. Update the checkbutton accordingly.""" + # TODO: Custom move handler, save state on click-release? + widths: list[int, int] = list(self.splitter_center.sizes()) + + if widths[1] == 0: + self.menu_node_info.setChecked(False) + else: + self.menu_node_info.setChecked(True) + + + + ################ + #### Events #### + ################ + def _close_event(self, event: QCloseEvent) -> None: + """Replaces QMainWindow default closeEvent(QCloseEvent) event""" + 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?') + + ' <br></p>') + box.setIcon(QMessageBox.Question) + box.setStandardButtons(QMessageBox.Yes | QMessageBox.No) + buttons: list[QAbstractButton] = box.buttons() + buttons[0].setText(self.tr('&Exit')) + buttons[1].setText(self.tr('&Cancel')) + checkbox = QCheckBox(self.tr('Don\'t ask again')) + box.setCheckBox(checkbox) + ret = box.exec_() + + if ret == QMessageBox.StandardButton.Yes: + if not hide_dialog: + s.setValue('mainwindow/hide_exit_dialog', checkbox.isChecked()) + self._write_settings() + log.info('Exit: {}'.format(os.path.basename(__file__))) + event.accept() + else: + event.ignore() + + + + ################################# + #### Helper member functions #### + ################################# + def printButtonPressed(self, func_name: str) -> None: + #TODO: remove + self.label.setText("hello") + + + alert = QMessageBox(self) + alert.setText("Called from " + func_name + '!') + alert.exec_() + + def update_statusbar(self, msg: str) -> None: + """Write the given str to the statusbar with temporarily policy.""" + self.statusbar.showMessage(msg) + + 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 + s.setValue('mainwindow/state', self.saveState()) # toolbars, dockwidgets: pos, size + s.setValue('mainwindow/menu/node_info', self.menu_node_info.isChecked()) + s.setValue('mainwindow/splitter/state', self.splitter_center.saveState()) + + 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)) + self.splitter_center.restoreState( s.value('mainwindow/splitter/state', QByteArray())) + self.menu_exit_dialog.setChecked(s.value('mainwindow/hide_exit_dialog', False, bool)) + + log.debug('Settings read from \'{}\'.'.format(s.fileName())) + + + +def start_gui(): + app = QApplication(sys.argv) + window = MainWindow() + window.show() + sys.exit(app.exec_()) + +if __name__ == "__main__": + start_gui()