diff options
Diffstat (limited to 'src/main/python/shapes/line.py')
-rw-r--r-- | src/main/python/shapes/line.py | 213 |
1 files changed, 122 insertions, 91 deletions
diff --git a/src/main/python/shapes/line.py b/src/main/python/shapes/line.py index 516f34d..85c9274 100644 --- a/src/main/python/shapes/line.py +++ b/src/main/python/shapes/line.py @@ -13,15 +13,18 @@ class Grabber(QGraphicsPathItem): def __init__(self, annotation_line, index, direction): super(Grabber, self).__init__() - self.m_index = index + # store line to which it connects self.m_annotation_item = annotation_line - self._direction = direction + # index of first point of segment of above line + self.m_index = index + self.direction = direction self.setPath(Grabber.circle) # set graphical settings for this item self.setFlag(QGraphicsItem.ItemIsSelectable, True) self.setFlag(QGraphicsItem.ItemIsMovable, True) self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True) self.setAcceptHoverEvents(True) + # initially make invisible self.pen = QPen(Qt.white, -1, Qt.SolidLine) self.brush = QBrush(Qt.transparent) @@ -29,7 +32,7 @@ class Grabber(QGraphicsPathItem): """ move position of grabber after resize""" if change == QGraphicsItem.ItemPositionChange and self.isEnabled(): p = QPointF(self.pos()) - if self._direction == Qt.Horizontal: + if self.direction == Qt.Horizontal: p.setX(value.x()) if self.parentItem().refLine and self.m_index == len(self.parentItem().points) - 2: points = self.parentItem().refLine.points @@ -41,7 +44,7 @@ class Grabber(QGraphicsPathItem): p.setX(min(point1.x(), point2.x())) elif p.x() > max(point1.x(), point2.x()): p.setX(max(point1.x(), point2.x())) - elif self._direction == Qt.Vertical: + elif self.direction == Qt.Vertical: p.setY(value.y()) if self.parentItem().refLine and self.m_index == len(self.parentItem().points) - 2: points = self.parentItem().refLine.points @@ -69,18 +72,16 @@ class Grabber(QGraphicsPathItem): painter.setPen(self.pen) painter.drawPath(self.path()) - # To paint path of shape - # color = Qt.red if self.isSelected() else Qt.black - # painter.setPen(QPen(Qt.blue, 1, Qt.SolidLine)) - # painter.drawPath(self.shape()) - def shape(self): """Overrides shape method and set shape to segment on which grabber is located""" index = self.m_index + # take start and end point of segment startPoint = QPointF(self.parentItem().points[index]) endPoint = QPointF(self.parentItem().points[index + 1]) + # map in grabber's co-ordinate startPoint = self.mapFromParent(startPoint) endPoint = self.mapFromParent(endPoint) + # create path as line path = QPainterPath(startPoint) path.lineTo(endPoint) # generate outlines for path @@ -91,15 +92,12 @@ class Grabber(QGraphicsPathItem): def boundingRect(self): return self.shape().boundingRect() - def mousePressEvent(self, event): - super(Grabber, self).mousePressEvent(event) - def hoverEnterEvent(self, event): """ Changes cursor to horizontal movement or vertical movement depending on the direction of the grabber on mouse enter """ - if self._direction == Qt.Horizontal: + if self.direction == Qt.Horizontal: self.setCursor(QCursor(Qt.SplitHCursor)) else: self.setCursor(QCursor(Qt.SplitVCursor)) @@ -113,10 +111,12 @@ class Grabber(QGraphicsPathItem): super(Grabber, self).hoverLeaveEvent(event) def show(self): + # set pen to show self.pen = QPen(Qt.black, 2, Qt.SolidLine) self.brush = QBrush(Qt.cyan) def hide(self): + # set pen to transparent self.pen = QPen(Qt.white, -1, Qt.SolidLine) self.brush = QBrush(Qt.transparent) @@ -124,56 +124,68 @@ class Grabber(QGraphicsPathItem): class LineLabel(QGraphicsTextItem): def __init__(self, pos, parent=None): super(LineLabel, self).__init__() + # initial text self.setPlainText("abc") + # stores index of first point of segment self.index = None + # distance from line segment self.gap = None + # set graphical setting self.setFlags(QGraphicsItem.ItemIsMovable | QGraphicsItem.ItemIsSelectable | QGraphicsItem.ItemIsFocusable) self.setTextInteractionFlags(Qt.NoTextInteraction) self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True) + # set center of text label at mouse pos self.setPos(pos - self.boundingRect().center()) self.setParentItem(parent) + # add line item to test label self.line = QGraphicsLineItem() self.line.setParentItem(self) self.line.setPen(QPen(Qt.black, 2, Qt.SolidLine)) self.line.setFlag(QGraphicsItem.ItemStacksBehindParent) + # reset position of line self.resetPos() def paint(self, painter, option, widget): - painter.save() - painter.setPen(QPen(Qt.black, 2, Qt.SolidLine)) - painter.setBrush(QBrush(Qt.white)) - - painter.drawEllipse(self.boundingRect()) - painter.restore() - + # draw ellipse shape + painter.save() # save painter + painter.setPen(QPen(Qt.black, 2, Qt.SolidLine)) # set pen to painter + painter.setBrush(QBrush(Qt.white)) # set brush + painter.drawEllipse(self.boundingRect()) # draw ellipse + painter.restore() # restore painter as before drawing ellipse super(LineLabel, self).paint(painter, option, widget) def updateLabel(self): - offset = self.gap - points = self.parentItem().points - firstPoint = points[self.index] - endPoint = points[self.index + 1] + # finds new position on segment + offset = self.gap # distance from segment + points = self.parentItem().points # points of line + firstPoint = points[self.index] # first point of segment on which label is attached + endPoint = points[self.index + 1] # second point center = self.mapToParent(self.boundingRect().center()) newPos = center + # if segment is vertical if firstPoint.x() == endPoint.x(): newPos.setX(firstPoint.x() + self.gap) if min(firstPoint.y(), endPoint.y()) > newPos.y(): newPos.setY(min(firstPoint.y(), endPoint.y())) elif newPos.y() > max(firstPoint.y(), endPoint.y()): newPos.setY(max(firstPoint.y(), endPoint.y())) - + # segment is horizontal elif firstPoint.y() == endPoint.y(): newPos.setY(firstPoint.y() + self.gap) if min(firstPoint.x(), endPoint.x()) > newPos.x(): newPos.setX(min(firstPoint.x(), endPoint.x())) elif newPos.x() > max(firstPoint.x(), endPoint.x()): newPos.setX(max(firstPoint.x(), endPoint.x())) + # transfer center positon to top left newPos -= QPointF(self.boundingRect().width() / 2, self.boundingRect().height() / 2) self.setPos(newPos) def resetPos(self): + """ finds the segment of line on which mouse is clicked for adding label + and resets it's position on that segment + """ points = self.parentItem().points min_A = QPointF() min_B = QPointF() @@ -209,9 +221,10 @@ class LineLabel(QGraphicsTextItem): self.gap = -30 + self.boundingRect().height() / 2 def itemChange(self, change, value): + # if position of label changes if change == QGraphicsItem.ItemPositionChange and self.scene(): - newPos = QPointF(value) - newPos += QPointF(self.boundingRect().width() / 2, self.boundingRect().height() / 2) + newPos = QPointF(value) # new position + newPos += QPointF(self.boundingRect().width() / 2, self.boundingRect().height() / 2) # pos of center points = self.parentItem().points firstPoint = points[self.index] endPoint = points[self.index + 1] @@ -225,7 +238,8 @@ class LineLabel(QGraphicsTextItem): newPos.setX(min(firstPoint.x(), endPoint.x())) elif newPos.x() > max(firstPoint.x(), endPoint.x()): newPos.setX(max(firstPoint.x(), endPoint.x())) - newPos -= QPointF(self.boundingRect().width() / 2, self.boundingRect().height() / 2) + newPos -= QPointF(self.boundingRect().width() / 2, + self.boundingRect().height() / 2) # change center pos to top-left return newPos if change == QGraphicsItem.ItemPositionHasChanged and self.scene(): self.updateGap() @@ -234,6 +248,7 @@ class LineLabel(QGraphicsTextItem): return super(LineLabel, self).itemChange(change, value) def updateGap(self): + # updates distance of line from it's connection segment points = self.parentItem().points firstPoint = points[self.index] endPoint = points[self.index + 1] @@ -246,6 +261,7 @@ class LineLabel(QGraphicsTextItem): self.gap = center.y() - firstPoint.y() def updateLine(self): + # updates line item of label points = self.parentItem().points firstPoint = points[self.index] endPoint = points[self.index + 1] @@ -257,14 +273,15 @@ class LineLabel(QGraphicsTextItem): self.line.setLine(center.x(), center.y(), center.x(), point.y()) def mouseDoubleClickEvent(self, event): + # set text editable self.setTextInteractionFlags(Qt.TextEditorInteraction) self.setFocus() super(LineLabel, self).mouseDoubleClickEvent(event) def focusOutEvent(self, event): super(LineLabel, self).focusOutEvent(event) - self.setTextInteractionFlags(Qt.NoTextInteraction) - + self.setTextInteractionFlags(Qt.NoTextInteraction) # set text non interactive + def __getstate__(self): return { "text": self.toPlainText(), @@ -272,7 +289,7 @@ class LineLabel(QGraphicsTextItem): "gap": self.gap, "pos": (self.pos().x(), self.pos().y()) } - + def __setstate__(self, dict): self.setPlainText(dict['text']) self.index = dict['index'] @@ -280,11 +297,13 @@ class LineLabel(QGraphicsTextItem): def findIndex(line, pos): + # finds index of first point of segment of line which is nearer to given position points = line.points min_A = QPointF() min_B = QPointF() min_dis = math.inf index = -1 + # iterating over points of line for i in range(len(points) - 1): A = points[i] B = points[i + 1] @@ -293,7 +312,7 @@ def findIndex(line, pos): BAy = B.y() - A.y() CAx = C.x() - A.x() CAy = C.y() - A.y() - length = math.sqrt(BAx * BAx + BAy * BAy) + length = math.sqrt(BAx * BAx + BAy * BAy) # length of segment if BAx == 0: if not min(A.y(), B.y()) <= C.y() <= max(A.y(), B.y()): continue @@ -317,10 +336,12 @@ class Line(QGraphicsPathItem): def __init__(self, startPoint, endPoint, **args): QGraphicsItem.__init__(self, **args) + # store startpoint and endpoint of line self.startPoint = startPoint self.endPoint = endPoint # stores all points of line self.points = [] + # stores grips of line self.startGripItem = None self.endGripItem = None self.m_grabbers = [] @@ -334,32 +355,38 @@ class Line(QGraphicsPathItem): self.commonPathsCenters = [] self.midLines = [] self.label = [] - self.arrowFlag = True + # if line point on rectangle grip + self.startGap = None # distance from center of start grip + self.endGap = None # distance from center of end grip def boundingRect(self): + # adjust bounding rect such that it contains intersection curve and arrow rect = self.shape().boundingRect() - rect.adjust(-10,-10,10,10) + rect.adjust(-10, -10, 10, 10) return rect - + def advance(self, phase): + """ called by scene when item moves or updates on scene + """ if not phase: return - # items colliding with line - # items = self.collidingItems(Qt.IntersectsItemShape) + # fine collision with line in ascending order items = self.scene().items(self.shape(), Qt.IntersectsItemShape, Qt.AscendingOrder) - self.commonPathsCenters = [] + self.commonPathsCenters = [] # remove all previous interaction # if item is line and stacked above for item in items: if type(item) in [type(self)]: if item == self: break - shape = item.shape() - shape = self.mapFromItem(item, item.shape()) - commonPath = self.shape().intersected(shape) - polygons = commonPath.toSubpathPolygons() + shape = item.shape() # shape of item + shape = self.mapFromItem(item, item.shape()) # map shape to self + commonPath = self.shape().intersected(shape) # find common path btw lines + polygons = commonPath.toSubpathPolygons() # chane common path to polygon for polygon in polygons: - center = polygon.boundingRect().center() + center = polygon.boundingRect().center() # center of polygon + # if collision is btw perpendicular line if polygon.size() == 5: + # ignore collision if other line has end point on this line if item.refLine: i = len(item.points) - 2 x1, y1 = item.points[i].x(), item.points[i].y() @@ -378,8 +405,7 @@ class Line(QGraphicsPathItem): def paint(self, painter, option, widget): color = Qt.red if self.isSelected() else Qt.black painter.setPen(QPen(color, 2, Qt.SolidLine)) - path = QPainterPath(self.startPoint) - arrowHead = QPolygonF() + path = QPainterPath(self.startPoint) # start path # iterating over all points of line for i in range(len(self.points) - 1): x1, y1 = self.points[i].x(), self.points[i].y() @@ -409,7 +435,8 @@ class Line(QGraphicsPathItem): path.arcTo(QRectF(x - 8, y - 8, 16, 16), 0, -180) path.lineTo(point - QPointF(8, 0)) path.lineTo(self.points[i + 1]) - if i == len(self.points) - 2 and self.arrowFlag: + # draw arrow in last segment + if i == len(self.points) - 2: arrow_size = 20.0 line = QLineF(self.points[i], self.points[i + 1]) if line.length() < 20: @@ -429,13 +456,12 @@ class Line(QGraphicsPathItem): arrowHead.append(line.p2()) arrowHead.append(arrow_p1) arrowHead.append(arrow_p2) - # path.addPolygon(arrowHead) painter.save() painter.setBrush(Qt.black) painter.drawPolygon(arrowHead) painter.restore() - painter.drawPath(path) + painter.drawPath(path) # draw final path def createPath(self): """ @@ -449,6 +475,7 @@ class Line(QGraphicsPathItem): self.points = [self.startPoint, QPointF((x0 + x1) / 2, y0), QPointF((x0 + x1) / 2, y1), self.endPoint] # final path of line if self.refLine: + # line has end point on other line from .shapes import LineGripItem direction = "left" points = self.refLine.points @@ -464,7 +491,7 @@ class Line(QGraphicsPathItem): direction = "top" else: direction = "bottom" - self.endGripItem = LineGripItem(self, -1, direction, self) + self.endGripItem = LineGripItem(-1, [0, 0, direction], self) self.endGripItem.setPos(self.endPoint) if self.startGripItem and self.endGripItem: @@ -490,8 +517,8 @@ class Line(QGraphicsPathItem): pe = QPointF(self.endPoint.x() + offset, self.endPoint.y()) start = self.startPoint end = self.endPoint - sitem = self.startGripItem.mapRectToScene(self.startGripItem.m_annotation_item.boundingRect()) - eitem = self.endGripItem.mapRectToScene(self.endGripItem.m_annotation_item.boundingRect()) + sitem = self.startGripItem.mapRectToScene(self.startGripItem.parentItem().boundingRect()) + eitem = self.endGripItem.mapRectToScene(self.endGripItem.parentItem().boundingRect()) if self.refLine: eitem = self.endGripItem.mapRectToScene(QRectF(0, 0, 0, 0)) @@ -751,6 +778,7 @@ class Line(QGraphicsPathItem): self.setPath(path) if self.refLine: + # remove added grip self.scene().removeItem(self.endGripItem) self.endGripItem = None self.addGrabber() @@ -767,9 +795,12 @@ class Line(QGraphicsPathItem): path.lineTo(self.points[i]) path.lineTo(self.endPoint) self.setPath(path) + # update grabber of line self.updateGrabber() + # update labels connected to line for label in self.label: label.updateLabel() + # update line have end point on this line self.updateMidLines() def updatePoints(self): @@ -848,6 +879,7 @@ class Line(QGraphicsPathItem): def itemChange(self, change, value): if change == QGraphicsItem.ItemSelectedHasChanged: + # on selection stack above to show curve on it if value == 1: self.showGripItem() items = self.collidingItems(Qt.IntersectsItemShape) @@ -859,46 +891,63 @@ class Line(QGraphicsPathItem): self.hideGripItem() return if change == QGraphicsItem.ItemSceneHasChanged and not self.scene(): + # if line is removed from scene for line in self.midLines: + # remove lines connected to it if line.scene(): line.scene().removeItem(line) - if self.startGripItem and self.startGripItem.line and not self.startGripItem.tempLine: - self.startGripItem.line = None - if self.endGripItem and self.endGripItem.line: - self.endGripItem.line = None + if self.startGripItem and self.startGripItem.lines and not self.startGripItem.tempLine: + # remove line reference from grips + if self in self.startGripItem.lines: self.startGripItem.lines.remove(self) + if self.endGripItem and self.endGripItem.lines: + if self in self.endGripItem.lines: self.endGripItem.lines.remove(self) if self.refLine: if self in self.refLine.midLines: self.refLine.midLines.remove(self) return super(Line, self).itemChange(change, value) - def updateLine(self, startPoint=None, endPoint=None): + def updateLine(self, endPoint=None): """This function is used to update connecting line when it add on canvas and when it's grip item moves :return: """ - self.prepareGeometryChange() - if startPoint: - self.startPoint = startPoint if endPoint: self.endPoint = endPoint self.createPath() self.updateGrabber() return - - if self.startGripItem and self.endGripItem: + # update start point + if self.startGripItem: item = self.startGripItem - self.startPoint = item.parentItem().mapToScene(item.pos()) + startPoint = item.parentItem().mapToScene(item.pos()) # center of grip + # end grip is rectangle + if item.size and item.m_location in ["top", "bottom"]: + startPoint.setX(startPoint.x() - self.startGap * item.boundingRect().width()) + elif item.size: + startPoint.setY( + startPoint.y() - self.startGap * item.boundingRect().height()) + self.startPoint = startPoint + # end point on line + if self.endGripItem: item = self.endGripItem - self.endPoint = item.parentItem().mapToScene(item.pos()) + endPoint = item.parentItem().mapToScene(item.pos()) # center of grip + # end grip is rectangle + if item.size and item.m_location in ["top", "bottom"]: + endPoint.setX( + endPoint.x() - self.endGap * item.boundingRect().width()) + elif item.size: + endPoint.setY( + endPoint.y() - self.endGap * item.boundingRect().height()) + self.endPoint = endPoint self.updatePath() - - if self.startGripItem and self.refLine: - item = self.startGripItem - self.startPoint = item.parentItem().mapToScene(item.pos()) + # end point on other line + elif self.refLine: self.updatePath() def updateMidLines(self): + """ Updates all lines connecting to it + """ for line in self.midLines: points = line.refLine.points point1 = points[line.refIndex] @@ -922,13 +971,6 @@ class Line(QGraphicsPathItem): line.points[i - 1].setX(max(point1.x(), point2.x())) line.updatePath() - def removeFromCanvas(self): - """This function is used to remove connecting line from canvas - :return: - """ - if self.scene(): - self.scene().removeItem(self) - def showGripItem(self): """shows grip items which contains line """ @@ -955,26 +997,14 @@ class Line(QGraphicsPathItem): """Pop up menu :return: """ + # create a menu and add action contextMenu = QMenu() - addLableAction = contextMenu.addAction("add Label") - if self.arrowFlag is True: - str = "Hide Arrow" - else: - str = "Add Arrow" - changeArrowFlag = contextMenu.addAction(str) + addLableAction = contextMenu.addAction("Add Text Label") # add action for text label action = contextMenu.exec_(event.screenPos()) + # check for label action and add text label as child if action == addLableAction: - self.label.append(LineLabel(event.scenePos(), self)) - if action == changeArrowFlag: - if str == "Hide Arrow": - self.arrowFlag =False - else: - self.arrowFlag =True - self.update() - - def setPenStyle(self, style): - """change current pen style for line""" - self.penStyle = style + print(event.scenePos(), event.pos()) + self.label.append(LineLabel(event.scenePos(), self)) # text label as child def __getstate__(self): return { @@ -989,5 +1019,6 @@ class Line(QGraphicsPathItem): "label": [i for i in self.label], "id": hex(id(self)) } + def __setstate__(self, dict): self.points = [QPointF(x, y) for x, y in dict["points"]] |