diff --git a/b_asic/scheduler-gui/graphics_graph_event.py b/b_asic/scheduler-gui/graphics_graph_event.py index c04827593c1d0fbef8a158375a43a49c700b400e..f239a53e437eb54ab9a128dddd6d026d6b497856 100644 --- a/b_asic/scheduler-gui/graphics_graph_event.py +++ b/b_asic/scheduler-gui/graphics_graph_event.py @@ -74,13 +74,13 @@ class GraphicsGraphEvent(QGraphicsItem): sceneEventFilter() function.""" for item in self._components: item.installSceneEventFilter(self) - self.setFiltersChildEvents(True) # default false + # self.setFiltersChildEvents(True) # default false def removeSceneEventFilters(self) -> None: """Removes an event filter on 'item' from 'self'.""" for item in self._components: item.removeSceneEventFilter(self) - self.setFiltersChildEvents(False) + # self.setFiltersChildEvents(False) # def sceneEventFilter(self, item: QGraphicsItem, event: QEvent) -> bool: @@ -88,17 +88,9 @@ class GraphicsGraphEvent(QGraphicsItem): """Returns true if the event was filtered (i.e. stopped), otherwise false. If false is returned, the event is forwarded to the appropriate child in the event chain.""" - # type_ = type(event) - # if type_ != QEvent.GraphicsSceneHoverMove: print(f'Graph -->\t{type(item).__name__}\t{type_}') - # if event.button(): - # # print(f'Graph -->\t{type_}\t{item}') - # print(f'-------->\t{event.button()}') - # scene = self.scene() - # mouse_grabber = scene.mouseGrabberItem() - # print(f'mouseGrabberItem() before: {mouse_grabber}') + handler = None if isinstance(item, GraphicsComponentItem): - switch = { QEvent.FocusIn: self.comp_focusInEvent, QEvent.GraphicsSceneContextMenu: self.comp_contextMenuEvent, @@ -116,14 +108,13 @@ class GraphicsGraphEvent(QGraphicsItem): QEvent.GraphicsSceneWheel: self.comp_wheelEvent } - handler = switch.get(event.type(), lambda x,y : False) - # ret = handler(item, event) - # print(f'mouseGrabberItem() after: {mouse_grabber}') - # return ret - return handler(item, event) - # else: - # print(f'Graph -->\t{type(item).__name__}\t{type_}') + handler = switch.get(event.type()) + else: + raise TypeError + if handler: + handler(event) + return True return False # returns False if event is ignored and pass through event to its child # def sceneEvent(self, event: QEvent) -> bool: @@ -136,32 +127,24 @@ class GraphicsGraphEvent(QGraphicsItem): ############################################### #### Event Handlers: GraphicsComponentItem #### ############################################### - def comp_focusInEvent(self, item: QGraphicsItem, event: QFocusEvent) -> bool: - return False - def comp_contextMenuEvent(self, item: QGraphicsItem, event: QGraphicsSceneContextMenuEvent) -> bool: - return False - def comp_dragEnterEvent(self, item: QGraphicsItem, event: QGraphicsSceneDragDropEvent) -> bool: - return False - def comp_dragMoveEvent(self, item: QGraphicsItem, event: QGraphicsSceneDragDropEvent) -> bool: - return False - def comp_dragLeaveEvent(self, item: QGraphicsItem, event: QGraphicsSceneDragDropEvent) -> bool: - return False - def comp_dropEvent(self, item: QGraphicsItem, event: QGraphicsSceneDragDropEvent) -> bool: - return False + def comp_focusInEvent(self, event: QFocusEvent) -> None: ... + def comp_contextMenuEvent(self, event: QGraphicsSceneContextMenuEvent) -> None: ... + def comp_dragEnterEvent(self, event: QGraphicsSceneDragDropEvent) -> None: ... + def comp_dragMoveEvent(self, event: QGraphicsSceneDragDropEvent) -> None: ... + def comp_dragLeaveEvent(self, event: QGraphicsSceneDragDropEvent) -> None: ... + def comp_dropEvent(self, event: QGraphicsSceneDragDropEvent) -> None: ... - def comp_hoverEnterEvent(self, item: QGraphicsItem, event: QGraphicsSceneHoverEvent) -> bool: + def comp_hoverEnterEvent(self, event: QGraphicsSceneHoverEvent) -> None: """Changes the cursor to OpenHandCursor when hovering an object.""" self.setCursor(QCursor(Qt.OpenHandCursor)) - return True - def comp_hoverMoveEvent(self, item: QGraphicsItem, event: QGraphicsSceneHoverEvent) -> bool: - return False - def comp_hoverLeaveEvent(self, item: QGraphicsItem, event: QGraphicsSceneHoverEvent) -> bool: + def comp_hoverMoveEvent(self, event: QGraphicsSceneHoverEvent) -> None: ... + + def comp_hoverLeaveEvent(self, event: QGraphicsSceneHoverEvent) -> None: """Changes the cursor to ArrowCursor when leaving an object.""" self.setCursor(QCursor(Qt.ArrowCursor)) - return True - def comp_mouseMoveEvent(self, item_: QGraphicsItem, event: QGraphicsSceneMouseEvent) -> bool: + def comp_mouseMoveEvent(self, event: QGraphicsSceneMouseEvent) -> None: """Set the position of the graphical element in the graphic scene, translate coordinates of the cursor within the graphic element in the coordinate system of the parent object.""" @@ -172,34 +155,30 @@ class GraphicsGraphEvent(QGraphicsItem): if dx > 5.05: pos = item.x() + 10.0 if self.is_valid_pos(pos): + self.prepareGeometryChange() item.setX(pos) self._current_pos.setX(self._current_pos.x() + 10.0) elif dx < -5.05: pos = item.x() - 10.0 if self.is_valid_pos(pos): + self.prepareGeometryChange() item.setX(pos) self._current_pos.setX(self._current_pos.x() - 10.0) - return True - def comp_mousePressEvent(self, item_: QGraphicsItem, event: QGraphicsSceneMouseEvent) -> bool: + def comp_mousePressEvent(self, event: QGraphicsSceneMouseEvent) -> None: """Changes the cursor to ClosedHandCursor when grabbing an object.""" item = self.scene().mouseGrabberItem() self._current_pos = item.mapToParent(event.pos()) - item.setCursor(QCursor(Qt.ClosedHandCursor)) + self.setCursor(QCursor(Qt.ClosedHandCursor)) event.accept() - return True - def comp_mouseReleaseEvent(self, item: QGraphicsItem, event: QGraphicsSceneMouseEvent) -> bool: + def comp_mouseReleaseEvent(self, event: QGraphicsSceneMouseEvent) -> None: """Changes the cursor to OpenHandCursor when releasing an object.""" - item.setCursor(QCursor(Qt.OpenHandCursor)) + self.setCursor(QCursor(Qt.OpenHandCursor)) event.accept() - return True - - def comp_mouseDoubleClickEvent(self, item: QGraphicsItem, event: QGraphicsSceneMouseEvent) -> bool: - return False - def comp_wheelEvent(self, item: QGraphicsItem, event: QGraphicsSceneWheelEvent) -> bool: - return False + def comp_mouseDoubleClickEvent(self, event: QGraphicsSceneMouseEvent) -> None: ... + def comp_wheelEvent(self, event: QGraphicsSceneWheelEvent) -> None: ... ############################################### #### Event Handlers: GraphicsComponentItem #### diff --git a/b_asic/scheduler-gui/graphics_graph_item.py b/b_asic/scheduler-gui/graphics_graph_item.py index 5f1951d049bf937ac7553760a566d1da1b224fd8..c9a95ed0cccc45a7764d48cb8de36f22366e0ab3 100644 --- a/b_asic/scheduler-gui/graphics_graph_item.py +++ b/b_asic/scheduler-gui/graphics_graph_item.py @@ -97,6 +97,9 @@ class GraphicsGraphItem(QGraphicsItemGroup, GraphicsGraphEvent): def is_valid_pos(self, pos: float) -> bool: """Returns true if component new pos is valid, false otherwise.""" # TODO: implement + # item = self.scene().mouseGrabberItem() + if pos < 0: + return False return True def update_(self) -> None: @@ -104,7 +107,7 @@ class GraphicsGraphItem(QGraphicsItemGroup, GraphicsGraphEvent): # self.removeFromGroup(self._axis) self._axis.update(40 + 6, self._components_height, self._x_axis_indent) # self.addToGroup(self._axis) - + @property def schedule(self) -> Schedule: return self._schedule diff --git a/b_asic/scheduler-gui/logger.py b/b_asic/scheduler-gui/logger.py index 8f552c979e9774126b5b452d815490738f2a2046..113b631c534fb3e8c6cb5bac8bf147006252052e 100644 --- a/b_asic/scheduler-gui/logger.py +++ b/b_asic/scheduler-gui/logger.py @@ -24,26 +24,35 @@ Usage: The last `exception(str)` is used to capture exceptions output, that normally won't be captured. See https://docs.python.org/3/howto/logging.html for more information. -""" + +Log Uncaught Exceptions: +------------------------ +To log uncaught exceptions, implement the following in your program. +Â `sys.excepthook = logger.log_exceptions`""" import os import sys +from typing import Type, Optional +from types import TracebackType from logging import Logger import logging import logging.handlers +import traceback -def getLogger(name: str='scheduler-gui.log', loglevel: str='INFO') -> Logger: +def getLogger(filename: str='scheduler-gui.log', loglevel: str='INFO') -> Logger: """This function creates console- and filehandler and from those, creates a logger object. Args: - name (str, optional): Logger-id and output filename. Defaults to 'scheduler-gui.log'. - loglevel (str, optional): The minimum level that the logger will log. Defaults to 'DEBUG'. + filename (str, optional): Output filename. Defaults to 'scheduler-gui.log'. + loglevel (str, optional): The minimum level that the logger will log. Defaults to 'INFO'. Returns: Logger: 'logging.Logger' object. """ - logger = logging.getLogger(name) + # logger = logging.getLogger(name) + # logger = logging.getLogger('root') + logger = logging.getLogger() # if logger 'name' already exists, return it to avoid logging duplicate # messages by attaching multiple handlers of the same type @@ -68,7 +77,7 @@ def getLogger(name: str='scheduler-gui.log', loglevel: str='INFO') -> Logger: f_fmt_date = '%Y-%m-%dT%T%Z' f_fmt = '%(asctime)s %(filename)18s:%(lineno)-4s %(funcName)20s() %(levelname)-8s: %(message)s' f_formatter = logging.Formatter(f_fmt, f_fmt_date) - f_handler = logging.FileHandler(name, mode = 'w') + f_handler = logging.FileHandler(filename, mode = 'w') f_handler.setFormatter(f_formatter) f_handler.setLevel(logging.DEBUG) logger.addHandler(f_handler) @@ -79,3 +88,15 @@ def getLogger(name: str='scheduler-gui.log', loglevel: str='INFO') -> Logger: ' '.join(sys.argv[1:])) return logger + + +# log uncaught exceptions +def handle_exceptions(exc_type: Type[BaseException], exc_value: BaseException, exc_traceback: TracebackType | None) -> None: +# def log_exceptions(type, value, tb): + """This function is a helper function to log uncaught exceptions. Install with: + `sys.excepthook = <module>.handle_exceptions`""" + if issubclass(exc_type, KeyboardInterrupt): + sys.__excepthook__(exc_type, exc_value, exc_traceback) + return + + logging.exception("Uncaught exception", exc_info=(exc_type, exc_value, exc_traceback)) diff --git a/b_asic/scheduler-gui/main_window.py b/b_asic/scheduler-gui/main_window.py index 4134efbaee6157d55cbe0b389ad8cbbade340f79..07b5a1ff58ad769c81d5cea794a580730c9ea1ed 100644 --- a/b_asic/scheduler-gui/main_window.py +++ b/b_asic/scheduler-gui/main_window.py @@ -15,7 +15,7 @@ import os import sys from pathlib import Path from types import ModuleType -from typing import Any +from typing import Any, Iterable, List from pprint import pprint #from matplotlib.pyplot import bar #from diagram import * @@ -34,7 +34,7 @@ from qtpy.QtWidgets import ( # QGraphics and QPainter imports from qtpy.QtCore import ( - QRect, QRectF, QPoint, QSize, QByteArray, QMarginsF) + QRect, QRectF, QPoint, QSize, QByteArray, QMarginsF, QObject) from qtpy.QtGui import ( QPaintEvent, QPainter, QPainterPath, QColor, QBrush, QPen, QFont, QPolygon, QIcon, QPixmap, QLinearGradient) @@ -50,8 +50,12 @@ from graphics_graph_item import GraphicsGraphItem from graphics_axis_item import GraphicsAxisItem from graphics_component_item import GraphicsComponentItem +# if sys.version_info >= (3, 9): +# List = list +# #Dict = dict log = logger.getLogger() +sys.excepthook = logger.handle_exceptions # Debug struff if __debug__: @@ -172,14 +176,14 @@ class MainWindow(QMainWindow, Ui_MainWindow): def _init_graphics(self) -> None: """Initialize the QGraphics framework""" self._scene = QGraphicsScene() - self.graphics_view.setScene(self._scene) - self.graphics_view.scale(self._scale, self._scale) + self.view.setScene(self._scene) + self.view.scale(self._scale, self._scale) # self.setMouseTracking(True) GraphicsComponentItem._scale = self._scale GraphicsAxisItem._scale = self._scale + self._scene.changed.connect(self.shrink_scene_to_min_size) - - + ############### #### Slots #### @@ -215,7 +219,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): 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))) + schedule_obj_list = dict(inspect.getmembers(module, (lambda x: isinstance(x, Schedule)))) if not schedule_obj_list: # return if no Schedule objects in script QMessageBox.warning(self, @@ -244,7 +248,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): #@Slot() def open(self, schedule: Schedule) -> None: - """Takes in an Schedule and place it in the schedule list.""" + """Takes in an Schedule and creates a GraphicsGraphItem object.""" self._graph = GraphicsGraphItem(schedule) self._scene.addItem(self._graph) @@ -319,6 +323,10 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.menu_node_info.setChecked(False) else: self.menu_node_info.setChecked(True) + + @Slot(list) + def shrink_scene_to_min_size(self, region: list[QRectF]) -> None: + self._scene.setSceneRect(self._scene.itemsBoundingRect()) diff --git a/b_asic/scheduler-gui/main_window.ui b/b_asic/scheduler-gui/main_window.ui index 55dc51a10ead3f69167ce108c2ffb0a6f7f34fc8..7844ba5864ce85193cbd6678f049628b37685f2a 100644 --- a/b_asic/scheduler-gui/main_window.ui +++ b/b_asic/scheduler-gui/main_window.ui @@ -49,7 +49,7 @@ <property name="orientation"> <enum>Qt::Horizontal</enum> </property> - <widget class="QGraphicsView" name="graphics_view"> + <widget class="QGraphicsView" name="view"> <property name="alignment"> <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> </property>