From 31cd1ec6fe72e6f9410e655c632a784429dd680a Mon Sep 17 00:00:00 2001 From: Johannes Kung <johku144@student.liu.se> Date: Fri, 2 Aug 2024 10:35:49 +0200 Subject: [PATCH] Fixed SignalGraphicsItem drawing --- src/simudator/gui/signal_graphics_item.py | 195 +++++++++------------- 1 file changed, 79 insertions(+), 116 deletions(-) diff --git a/src/simudator/gui/signal_graphics_item.py b/src/simudator/gui/signal_graphics_item.py index a0a4320..d2f7569 100644 --- a/src/simudator/gui/signal_graphics_item.py +++ b/src/simudator/gui/signal_graphics_item.py @@ -1,14 +1,16 @@ import itertools import math +import typing from qtpy import QtCore from qtpy.QtCore import QPoint, QPointF, QRectF, Qt -from qtpy.QtGui import QCursor, QPainter, QPen +from qtpy.QtGui import QCursor, QPainter, QPainterPath, QPen from qtpy.QtWidgets import ( QGraphicsItem, QGraphicsLineItem, QGraphicsSceneMouseEvent, QGraphicsWidget, + QWidget, ) from simudator.gui.color_scheme import ColorScheme @@ -63,30 +65,28 @@ class SignalGraphicsItem(QGraphicsWidget): # | # port -----| - start_point = self.start_port.getPoint() - end_point = self.end_port.getPoint() - self._init_line_points(start_point, end_point) - - # local_start = self.mapFromItem(start_port, start_point) - # local_end = self.mapFromItem(end_port, end_point) - # local_start = self.mapFromScene(start_point) - # local_end = self.mapFromScene(end_point) - local_start = start_point - self.start_port.scenePos() - local_end = end_point - self.end_port.scenePos() - + start_point = self.mapFromScene(self.start_port.getPoint()) + end_point = self.mapFromScene(self.end_port.getPoint()) self._init_line_points(start_point, end_point) - self.setPos(0, 0) - # Used to lock layout self.is_locked = False def _init_line_points(self, start_point: QPointF, end_point: QPointF) -> None: - """ """ - - # |---- port - # | - # port -----| + """Initialise the visual representation of the signal as a zig-zag + between the ports of the signal. + + |---- port + | + port -----| + + Parameters + ---------- + start_point : QPointF + Point of the end of one port. + end_point : QPointF + Point of the end of the other port. + """ half_x = (start_point.x() + end_point.x()) / 2 @@ -109,27 +109,17 @@ class SignalGraphicsItem(QGraphicsWidget): self.last_line_start_i = None self.last_line_end_i = None - # Initialise the visual representation of the signal as a zig-zag - # between the ports of the signal - # |---- port - # | - # port -----| - start_point = self.start_port.getPoint() end_point = self.end_port.getPoint() - self.points.append(start_point) - half_x = (start_point.x() + end_point.x()) / 2 - self.points.append(QPointF(half_x, start_point.y())) - self.points.append(QPointF(half_x, end_point.y())) - self.points.append(end_point) + self._init_line_points(start_point, end_point) self.setVisible(True) - self.drawSignal() + self.update() def splitLine(self, start_point_index: int, end_point_index: int) -> None: """ Splits an existing line of two segments into a new line of four line - segments by adding three new points and removing the old corer. + segments by adding three new points and removing the old corner. | -> __| ____| -> __| @@ -153,7 +143,7 @@ class SignalGraphicsItem(QGraphicsWidget): else: bot_corner = QPointF(half_point.x(), next_half_point.y()) - # Remove the old corner and insteart new points in the correct288.5 order + # Remove the old corner and insert new points in the correct order self.points.pop(end_point_index) self.points.insert(end_point_index, next_half_point) self.points.insert(end_point_index, bot_corner) @@ -167,10 +157,9 @@ class SignalGraphicsItem(QGraphicsWidget): no line segment will be chosen as pressed and the mouse press event is ignored. """ - print(892734) # Calculate and save which line segment was pressed (choose the one # closest to the point pressed) - press_point = event.scenePos() + press_point = self.mapFromScene(event.scenePos()) line_start_i, line_end_i = self.getClosestPoints(press_point) line_start = self.points[line_start_i] line_end = self.points[line_end_i] @@ -186,34 +175,36 @@ class SignalGraphicsItem(QGraphicsWidget): dist_to_line = self.getLineDistance(line_start, line_end, press_point) if dist_to_line > LONGEST_CLICK_DIST: event.ignore() + # event.accept() - # Save whether the pressed line segment is vertical or horisontal + # Save whether the pressed line segment is vertical or horizontal # for convenience if line_start.x() == line_end.x(): self.last_line_vertical = True elif line_start.y() == line_end.y(): self.last_line_vertical = False + event.accept() + def mouseReleaseEvent(self, event: QGraphicsSceneMouseEvent) -> None: """ Calls the functions 'handleLeftClick' or 'handleRightClick' if the user left or right clicked. """ - print(1111111111111) if event.button() == Qt.MouseButton.LeftButton: self.handleLeftClick() elif event.button() == Qt.MouseButton.RightButton: self.handleRightClick() + event.accept() def mouseMoveEvent(self, event: QGraphicsSceneMouseEvent) -> None: """ Handles moving a pressed line segment with left click. The segment is - moved horisontally of the segment itself is vertical and vice versa. + moved horizontally of the segment itself is vertical and vice versa. The two line sgments that make up the ends of the signal are not moved if pressed. """ - print(22222222222222222222) if not self.left_click or self.is_locked: return @@ -224,7 +215,7 @@ class SignalGraphicsItem(QGraphicsWidget): self.setCursor(QCursor(QtCore.Qt.SizeVerCursor)) # Retrieve which line segment to move and where to move it - current_mouse_point = event.scenePos() + current_mouse_point = self.mapFromScene(event.scenePos()) line_start = self.points[self.last_line_start_i] line_end = self.points[self.last_line_end_i] @@ -235,30 +226,30 @@ class SignalGraphicsItem(QGraphicsWidget): if line_end == self.points[0] or line_end == self.points[-1]: return - # If the line segment is vertical, move it horisontally288.5 + # If the line segment is vertical, move it horizontally if self.last_line_vertical: line_start.setX(current_mouse_point.x()) line_end.setX(current_mouse_point.x()) - # If the line segment is horisontal, move it vertically + # If the line segment is horizontal, move it vertically if not self.last_line_vertical: line_start.setY(current_mouse_point.y()) line_end.setY(current_mouse_point.y()) - self.drawSignal() + self.update() def handleLeftClick(self) -> None: self.align( self.last_line_start_i, self.last_line_end_i, self.last_line_vertical ) - self.drawSignal() # Reset cursor self.setCursor(QCursor(QtCore.Qt.ArrowCursor)) + self.update() def align_search(self, start_index, ascending=False) -> int: """ - Recursivly searches deeper into the line structure to get the smallest + Recursively searches deeper into the line structure to get the smallest and largest index of which all points in between should be removed to merge small line segments. """ @@ -316,22 +307,27 @@ class SignalGraphicsItem(QGraphicsWidget): return self.splitLine(self.last_line_start_i, self.last_line_end_i) - self.drawSignal() + self.update() + + def shape(self) -> QPainterPath: + # Construct the shape as the union of the shapes of the line segments + path = QPainterPath() + for line_segment in self._line_segments: + local_shape = self.mapFromItem(line_segment, line_segment.shape()) + path.addPath(local_shape) + return path def boundingRect(self): return self.childrenBoundingRect() - def paint(self, painter, *args): - # This avoids errors, it is a pure virtual function that - # needs an implementation + def paint( + self, + painter: typing.Optional[QPainter], + option: typing.Optional['QStyleOptionGraphicsItem'], + widget: typing.Optional[QWidget] = None, + ) -> None: # Remove old lines from the GUI - - painter.setPen(QPen(QtCore.Qt.red)) - painter.drawLine(0, 0, 5, 0) - - while self.lines: - item = self.lines.pop() - item.setParentItem(None) + painter.save() # Remove old lines form the GUI while self._line_segments: @@ -342,19 +338,12 @@ class SignalGraphicsItem(QGraphicsWidget): # to the third point and so on prev_point = self.points[0] for point in self.points[1:]: - # Coordinates - # Bounded box for clicking - x1 = prev_point.x() - y1 = prev_point.y() - x2 = point.x() - y2 = point.y() - # line = QGraphicsLineItem(x1, y1, x2, y2, self) - # line.setPen(ColorScheme.Signal) segment = LineSegment(prev_point, point, self) self._line_segments.append(segment) - # self.lines.append(line) prev_point = point + painter.restore() + def distance_sq(self, point1, point2): dx = point1.x() - point2.x() dy = point1.y() - point2.y() @@ -420,8 +409,8 @@ class SignalGraphicsItem(QGraphicsWidget): # Find out whether the start or the end of the signal moved old_start = self.points[0] old_end = self.points[-1] - new_start = self.start_port.getPoint() - new_end = self.end_port.getPoint() + new_start = self.mapFromScene(self.start_port.getPoint()) + new_end = self.mapFromScene(self.end_port.getPoint()) # Index the points that should be moved if old_start != new_start: @@ -475,42 +464,19 @@ class LineSegment(QGraphicsWidget): super().__init__(parent) self.setFlag(QGraphicsItem.ItemIsMovable) - self._parent = parent - self._margin = 5 - self._calc_area(start_pos, end_pos) - - self.setPos(self._start_pos) - - def _calc_area(self, start_pos: QPointF, end_pos: QPointF): - - start_pos = self.mapFromItem(self._parent, start_pos) - end_pos = self.mapFromItem(self._parent, end_pos) - - self._start_pos = start_pos - self._end_pos = end_pos - - top_left = QPointF( - min(self._start_pos.x(), self._end_pos.x()), - min(self._start_pos.y(), self._end_pos.y()), - ) - width = max(self._start_pos.x(), self._end_pos.x()) - top_left.x() - height = max(self._start_pos.y(), self._end_pos.y()) - top_left.y() - self.setGeometry( - top_left.x() - self._margin, - top_left.y() - self._margin, - width + 2 * self._margin, - height + 2 * self._margin, - ) - - def update_position(self, start_pos: QPointF, end_pos: QPointF): - self._calc_area(start_pos, end_pos) + self.setPos(start_pos) + self._start_pos = QPointF(0, 0) + self._end_pos = self.mapFromParent(end_pos) def paint(self, painter: QPainter, *args): painter.drawLine(self._start_pos, self._end_pos) - painter.setPen(QPen(QtCore.Qt.red)) - painter.drawRect(self.boundingRect()) + + def shape(self) -> QPainterPath: + path = QPainterPath() + path.addRect(self.boundingRect()) + return path def boundingRect(self): top_left = QPointF( @@ -526,21 +492,18 @@ class LineSegment(QGraphicsWidget): height + 2 * self._margin, ) - def mouseReleaseEvent(self, event: QGraphicsSceneMouseEvent) -> None: - """ - Calls the functions 'handleLeftClick' or 'handleRightClick' if - the user left or right clicked. - """ - print(123) - return - if event.button() == Qt.MouseButton.LeftButton: - self.handleLeftClick() - - elif event.button() == Qt.MouseButton.RightButton: - self.handleRightClick() - - def mousePressEvent(self, event: QGraphicsSceneMouseEvent) -> None: - print(321) - - def mouseMoveEvent(self, event: QGraphicsSceneMouseEvent) -> None: - print(654) + def mousePressEvent( + self, event: typing.Optional['QGraphicsSceneMouseEvent'] + ) -> None: + # Let the parent SignalGraphicsItem handle mouse events + event.ignore() + + def mouseReleaseEvent( + self, event: typing.Optional['QGraphicsSceneMouseEvent'] + ) -> None: + # Let the parent SignalGraphicsItem handle mouse events + event.ignore() + + def mouseMoveEvent(self, event: 'QGraphicsSceneMouseEvent | None') -> None: + # Let the parent SignalGraphicsItem handle mouse events + event.ignore() -- GitLab