def plot_vert_line_graph(self, qp, x_line, color, c, arrow_up=False, arrow_down=False): if x_line < self._start_date or x_line > self._end_date: return x_line -= self._start_date qp.save() qp.setPen(color) qp.setBrush(color) qp.setRenderHint(QPainter.Antialiasing) arrowSize = 2.0 x, y = self.origGraph(c) line = QLineF(x + self.convX(x_line), y + 10, x + self.convX(x_line), y + 50) qp.drawLine(line) if arrow_up: arrowP1 = line.p1() + QPointF(arrowSize, arrowSize * 3) arrowP2 = line.p1() + QPointF(-arrowSize, arrowSize * 3) qp.drawLine(line.p1(), arrowP1) qp.drawLine(line.p1(), arrowP2) if arrow_down: arrowP1 = line.p2() + QPointF(arrowSize, -arrowSize * 3) arrowP2 = line.p2() + QPointF(-arrowSize, -arrowSize * 3) qp.drawLine(line.p2(), arrowP1) qp.drawLine(line.p2(), arrowP2) qp.restore()
class GuideLine(Guide): def __init__(self, line_or_point, follows=None): super(GuideLine, self).__init__(follows) if isinstance(line_or_point, QLineF): self.line = line_or_point elif follows is not None: self.line = QLineF(self.prevGuide.endPos(), line_or_point) else: self.line = QLineF(QPointF(0, 0), line_or_point) def length(self): return self.line.length() def startPos(self): return QPointF(self.line.p1().x() * self.scaleX, self.line.p1().y() * self.scaleY) def endPos(self): return QPointF(self.line.p2().x() * self.scaleX, self.line.p2().y() * self.scaleY) def guide(self, item, moveSpeed): frame = item.guideFrame - self.startLength endX = (self.line.p1().x() + (frame * self.line.dx() / self.length())) * self.scaleX endY = (self.line.p1().y() + (frame * self.line.dy() / self.length())) * self.scaleY pos = QPointF(endX, endY) self.move(item, pos, moveSpeed)
def __init__(self, position, angle, arrow_size, value, unit): super(StressRepresentation, self).__init__() arrow_line = QLineF() arrow_line.setP1(position) arrow_line.setLength(arrow_size) arrow_line.setAngle(angle) arrow = QGraphicsLineItem() arrow.setLine(arrow_line) self.addToGroup(arrow) arrow_head1_line = QLineF() arrow_head1_line.setP1(arrow_line.p1()) arrow_head1_line.setLength(arrow_size / 4) arrow_head1_line.setAngle(arrow_line.angle() + 45) arrow_head1 = QGraphicsLineItem() arrow_head1.setLine(arrow_head1_line) self.addToGroup(arrow_head1) arrow_head2_line = QLineF() arrow_head2_line.setP1(arrow_line.p1()) arrow_head2_line.setLength(arrow_size / 4) arrow_head2_line.setAngle(arrow_line.angle() - 45) arrow_head2 = QGraphicsLineItem() arrow_head2.setLine(arrow_head2_line) self.addToGroup(arrow_head2) text = QGraphicsTextItem() text.setPlainText(f'{str(value)}{unit}') text.setPos(arrow_line.p2()) self.addToGroup(text) self._set_color()
def adjust(self): if not self.source or not self.dest: return line = QLineF( self.mapFromItem(self.source, self.source.getDrawSize() * -0.5, self.source.getDrawSize() * -0.5), self.mapFromItem(self.dest, self.dest.getDrawSize() * -0.5, self.dest.getDrawSize() * -0.5)) length = line.length() self.prepareGeometryChange() if length > self.source.getDrawSize(): edgeOffset = QPointF( (line.dx() * self.source.getDrawSize() / 2) / length, (line.dy() * self.source.getDrawSize() / 2) / length) self.sourcePoint = line.p1() + edgeOffset self.destPoint = line.p2() - edgeOffset else: self.sourcePoint = line.p1() self.destPoint = line.p1()
def plot_vert_line_graph(self, qp, x_line, color, c, arrow_up=False, arrow_down=False): if x_line < self._start_date or x_line > self._end_date: return x_line -= self._start_date qp.save() qp.setPen(color) qp.setBrush(color) qp.setRenderHint(QPainter.Antialiasing) arrowSize = 2.0 x, y = self.origGraph(c) line = QLineF(x + self.convX(x_line), y + 10, x + self.convX(x_line), y + 50) qp.drawLine(line) if arrow_up: arrowP1 = line.p1() + QPointF(arrowSize, arrowSize * 3) arrowP2 = line.p1() + QPointF(-arrowSize, arrowSize * 3) qp.drawLine(line.p1(), arrowP1) qp.drawLine(line.p1(), arrowP2) if arrow_down: arrowP1 = line.p2() + QPointF(arrowSize, - arrowSize * 3) arrowP2 = line.p2() + QPointF(-arrowSize, - arrowSize * 3) qp.drawLine(line.p2(), arrowP1) qp.drawLine(line.p2(), arrowP2) qp.restore()
def calc_angle_from_l1_to_l2(a0: QLineF, a1: QLineF, degree: bool): """ Return the angle vector line a0 needs rotated from current pos to a1, Rotate direction: clockwise Return value unit: in radians or degrees(degree = True) """ angle_a0 = calc_angle_from_p1_to_p2(a0.p1(), a0.p2(), False) angle_a1 = calc_angle_from_p1_to_p2(a1.p1(), a1.p2(), False) angle = angle_a1 - angle_a0 if degree is True: return degrees(angle) return angle
def adjust(self): if not self._source or not self._dest: return line = QLineF(self.mapFromItem(self._source, 0, 0), self.mapFromItem(self._dest, 0, 0)) length = line.length() self.prepareGeometryChange() min_len = self._source.radius() + self._dest.radius( ) + self._source.pen().widthF() + self._dest.pen().widthF() if length > min_len: offset = QPointF( (line.dx() * (self._source.radius() + self._source.pen().widthF() + 1)) / length, (line.dy() * (self._source.radius() + self._source.pen().widthF() + 1)) / length) self.source_point = line.p1() + offset offset = QPointF( (line.dx() * (self._dest.radius() + self._dest.pen().widthF() + 1)) / length, (line.dy() * (self._dest.radius() + self._dest.pen().widthF() + 1)) / length) self.dest_point = line.p2() - offset else: self.source_point = self.dest_point = line.p1() path = QPainterPath() if self.sourceNode() == self.destNode(): # Draw self-loops self.__is_self_loop = True radius = self._source.radius() path.moveTo( self.source_point.x() - radius - 2 * self._source.pen().widthF(), self.source_point.y()) path.cubicTo( QPointF(self.source_point.x() - 4 * radius, self.source_point.y()), QPointF(self.source_point.x(), self.source_point.y() - 4 * radius), QPointF( self.dest_point.x(), self.dest_point.y() - radius - 2 * self._source.pen().widthF())) else: self.__is_self_loop = False path.moveTo(self.source_point) path.lineTo(self.dest_point) self.setPath(path)
def adjust(self): if not self.source or not self.dest: return line = QLineF(self.mapFromItem(self.source, 0, 0), self.mapFromItem(self.dest, 0, 0)) length = line.length() self.prepareGeometryChange() if length > 20: edge_offset = QPointF( (line.dx() * self.source.size.width() / 2) / length, (line.dy() * self.source.size.height() / 2) / length) self.source_point = line.p1() + edge_offset self.dest_point = line.p2() - edge_offset else: self.source_point = self.dest_point = line.p1()
def update_nodes(self): p1 = self.from_port.rect().center() p2 = self.to_port.rect().center() p1 = self.from_port.parentItem().mapToScene(p1) p2 = self.to_port.parentItem().mapToScene(p2) line = QLineF(p1, p2) self.setLine(line) self.update_polygon() box1 = self.from_port.parentItem().mapRectToScene( self.from_port.parentItem().box.rect()) if box1.intersects(self.boundingRect()): p3 = box1.bottomRight() + QPointF(0, Port.WIDTH / 2) self.head = QLineF(p1, p3) line = QLineF(p3, p2) else: self.head = None box2 = self.to_port.parentItem().mapRectToScene( self.to_port.parentItem().box.rect()) if box2.intersects(self.boundingRect()): p4 = box2.topLeft() + QPointF(-Port.WIDTH / 2, 0) self.tail = QLineF(p4, p2) line = QLineF(line.p1(), p4) else: self.tail = None self.setLine(line) self.update_polygon() self.update()
def paint(self, painter, option, widget): if not self.source or not self.dest: return # Draw the line itself. line = QLineF(self.sourcePoint, self.destPoint) if line.length() == 0.0: return painter.setPen(QPen(Qt.black, 1, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)) painter.drawLine(line) # Draw the arrows if there's enough room. angle = math.acos(line.dx() / line.length()) if line.dy() >= 0: angle = Edge.TwoPi - angle sourceArrowP1 = self.sourcePoint + QPointF(math.sin(angle + Edge.Pi / 3) * self.arrowSize, math.cos(angle + Edge.Pi / 3) * self.arrowSize) sourceArrowP2 = self.sourcePoint + QPointF(math.sin(angle + Edge.Pi - Edge.Pi / 3) * self.arrowSize, math.cos(angle + Edge.Pi - Edge.Pi / 3) * self.arrowSize); destArrowP1 = self.destPoint + QPointF(math.sin(angle - Edge.Pi / 3) * self.arrowSize, math.cos(angle - Edge.Pi / 3) * self.arrowSize) destArrowP2 = self.destPoint + QPointF(math.sin(angle - Edge.Pi + Edge.Pi / 3) * self.arrowSize, math.cos(angle - Edge.Pi + Edge.Pi / 3) * self.arrowSize) painter.setBrush(Qt.black) painter.drawPolygon(QPolygonF([line.p1(), sourceArrowP1, sourceArrowP2])) painter.drawPolygon(QPolygonF([line.p2(), destArrowP1, destArrowP2]))
def setLine(self, line): """ Set the arrow base line (a `QLineF` in object coordinates). """ if self.__line != line: self.__line = line # local item coordinate system geom = self.geometry().translated(-self.pos()) if geom.isNull() and not line.isNull(): geom = QRectF(0, 0, 1, 1) arrow_shape = arrow_path_concave(line, self.lineWidth()) arrow_rect = arrow_shape.boundingRect() if not (geom.contains(arrow_rect)): geom = geom.united(arrow_rect) if self.__autoAdjustGeometry: # Shrink the geometry if required. geom = geom.intersected(arrow_rect) # topLeft can move changing the local coordinates. diff = geom.topLeft() line = QLineF(line.p1() - diff, line.p2() - diff) self.__arrowItem.setLine(line) self.__line = line # parent item coordinate system geom.translate(self.pos()) self.setGeometry(geom)
def paint(self, painter, index): widget = self.parent() if index != widget.activeIndex(): return scale = widget.inverseScale() # metrics if self._rulerObject is not None: line, a = self._rulerObject origin = line.p1() cursor = line.p2() size = 8 * scale halfSize = 4 * scale color = QColor(255, 85, 127, 170) # line painter.save() painter.setPen(color) drawing.drawLine(painter, origin.x(), origin.y(), cursor.x(), cursor.y(), scale) # ellipses ellipses = [ (origin.x(), origin.y()), (cursor.x(), cursor.y()), ] path = QPainterPath() path.setFillRule(Qt.WindingFill) for x, y in itertools.chain(self._rulerPts.values(), ellipses): x -= halfSize y -= halfSize path.addEllipse(x, y, size, size) painter.fillPath(path, color) painter.restore() # text line = QLineF(line) xAlign = yAlign = "center" ellipses.pop(0) rp = self._rulerPts # XXX: sort shouldn't be performed in paintEvent for pt in itertools.chain((rp[k] for k in sorted(rp)), ellipses): p = QPointF(*pt) line.setP2(p) if line.length(): d = str(round(line.length(), 1)) pos = (line.p1() + line.p2()) / 2 drawing.drawTextAtPoint(painter, d, pos.x(), pos.y(), scale, xAlign, yAlign) line.setP1(p) xAlign, yAlign = "left", "top" dx = cursor.x() - origin.x() px = size if dx < 0: xAlign = "right" px = -px drawing.drawTextAtPoint(painter, a, cursor.x() + px, cursor.y() + size, scale, xAlign, yAlign)
def adjust(self): if not self.source or not self.dest: return line = QLineF(self.mapFromItem(self.source, 0, 0), self.mapFromItem(self.dest, 0, 0)) length = line.length() self.prepareGeometryChange() if length > 20.0: edgeOffset = QPointF((line.dx() * 10) / length, (line.dy() * 10) / length) self.sourcePoint = line.p1() + edgeOffset self.destPoint = line.p2() - edgeOffset else: self.sourcePoint = line.p1() self.destPoint = line.p1()
def updatePosition(self, force=False): #if self.toNode() is None and self.fromNode() is None: #if not force: #self.updateArrowHead() #self.updateTextPosition() #return if self.dest() and self.source(): self._updatingPos = True #self.saveTextPosition() self.prepareGeometryChange() if len(self._points) == 2: a = self.source().closestBoundaryPosToItem(self.dest()) b = self.dest().closestBoundaryPosToItem(self.source()) a = self.mapFromItem(self.source(), a) b = self.mapFromItem(self.dest(), b) else: a = self.source().closestBoundaryPosToItem(self._points[1]) b = self.dest().closestBoundaryPosToItem(self._points[-2]) a = self.mapFromItem(self.source(), a) b = self.mapFromItem(self.dest(), b) line = QLineF(a, b) length = line.length() # BUGFIX if abs(length) == 0: line = QLineF(a, QPointF(a.x() + 1, a.y())) length = 1 dx = line.dx() dx /= length if dx > 1: dx = 1 elif dx < -1: dx = -1 angle = acos(dx) size = self.arrowHeadSize() head = self._arrowHead if line.dy() >= 0: angle = pi * 2 - angle p2 = line.p2() u = p2 + QPointF( sin(angle + pi + pi / 3) * size, cos(angle + pi + pi / 3) * size) v = p2 + QPointF( sin(angle - pi / 3) * size, cos(angle - pi / 3) * size) if self.dest() != self.toPoint(): self.toPoint().setPos(line.p2()) if self.source() != self.fromPoint(): self.fromPoint().setPos(line.p1()) self.updateArrowHead() #self.updateTextPosition() self._updatingPos = False else: self.updateArrowHead() self.updateTextPosition()
def moveUIPoint(contour, point, delta): if point.segmentType is None: # point is an offCurve. Get its sibling onCurve and the other # offCurve. onCurve, otherPoint = _getOffCurveSiblingPoints(contour, point) # if the onCurve is selected, the offCurve will move along with it if onCurve.selected: return point.move(delta) if not onCurve.smooth: contour.dirty = True return # if the onCurve is smooth, we need to either... if otherPoint.segmentType is None and not otherPoint.selected: # keep the other offCurve inline line = QLineF(point.x, point.y, onCurve.x, onCurve.y) otherLine = QLineF( onCurve.x, onCurve.y, otherPoint.x, otherPoint.y) line.setLength(line.length() + otherLine.length()) otherPoint.x = line.x2() otherPoint.y = line.y2() else: # keep point in tangency with onCurve -> otherPoint segment, # ie. do an orthogonal projection line = QLineF(otherPoint.x, otherPoint.y, onCurve.x, onCurve.y) n = line.normalVector() n.translate(QPointF(point.x, point.y) - n.p1()) targetPoint = QPointF() n.intersect(line, targetPoint) # check that targetPoint is beyond its neighbor onCurve # we do this by calculating position of the offCurve and second # onCurve relative to the first onCurve. If there is no symmetry # in at least one of the axis, then we need to clamp onCurvePoint = line.p2() onDistance = line.p1() - onCurvePoint newDistance = targetPoint - onCurvePoint if (onDistance.x() >= 0) != (newDistance.x() <= 0) or \ (onDistance.y() >= 0) != (newDistance.y() <= 0): targetPoint = onCurvePoint # ok, now set pos point.x, point.y = targetPoint.x(), targetPoint.y() else: # point is an onCurve. Move its offCurves along with it. index = contour.index(point) point.move(delta) for d in (-1, 1): # edge-case: contour open, trailing offCurve and moving first # onCurve in contour if contour.open and index == 0 and d == -1: continue pt = contour.getPoint(index + d) if pt.segmentType is None: pt.move(delta) contour.dirty = True
def moveUIPoint(contour, point, delta): if point.segmentType is None: # point is an offCurve. Get its sibling onCurve and the other # offCurve. onCurve, otherPoint = _getOffCurveSiblingPoints(contour, point) # if the onCurve is selected, the offCurve will move along with it if onCurve.selected: return point.move(delta) if not onCurve.smooth: contour.dirty = True return # if the onCurve is smooth, we need to either... if otherPoint.segmentType is None and not otherPoint.selected: # keep the other offCurve inline line = QLineF(point.x, point.y, onCurve.x, onCurve.y) otherLine = QLineF(onCurve.x, onCurve.y, otherPoint.x, otherPoint.y) line.setLength(line.length() + otherLine.length()) otherPoint.x = line.x2() otherPoint.y = line.y2() else: # keep point in tangency with onCurve -> otherPoint segment, # ie. do an orthogonal projection line = QLineF(otherPoint.x, otherPoint.y, onCurve.x, onCurve.y) n = line.normalVector() n.translate(QPointF(point.x, point.y) - n.p1()) targetPoint = QPointF() n.intersect(line, targetPoint) # check that targetPoint is beyond its neighbor onCurve # we do this by calculating position of the offCurve and second # onCurve relative to the first onCurve. If there is no symmetry # in at least one of the axis, then we need to clamp onCurvePoint = line.p2() onDistance = line.p1() - onCurvePoint newDistance = targetPoint - onCurvePoint if (onDistance.x() >= 0) != (newDistance.x() <= 0) or \ (onDistance.y() >= 0) != (newDistance.y() <= 0): targetPoint = onCurvePoint # ok, now set pos point.x, point.y = targetPoint.x(), targetPoint.y() else: # point is an onCurve. Move its offCurves along with it. index = contour.index(point) point.move(delta) for d in (-1, 1): # edge-case: contour open, trailing offCurve and moving first # onCurve in contour if contour.open and index == 0 and d == -1: continue pt = contour.getPoint(index + d) if pt.segmentType is None: pt.move(delta) contour.dirty = True
def adjust(self): if not self.source or not self.dest: return line = QLineF(self.mapFromItem(self.source, 0, 0), self.mapFromItem(self.dest, 0, 0)) length = line.length() self.prepareGeometryChange() self.sourcePoint = line.p1() self.destPoint = line.p2()
def adjust(self): if not self._handle1 or not self._handle2: return line = QLineF(self.mapFromItem(self._handle1, 0, 0), self.mapFromItem(self._handle2, 0, 0)) length = line.length() self.prepareGeometryChange() if length > self.width() * 0.9: offset = QPointF((line.dx() * self.width() / 2) / length, (line.dy() * self.width() / 2) / length) p1 = line.p1() + offset p2 = line.p2() - offset else: p1 = p2 = line.p1() path = QPainterPath() path.moveTo(p1) path.lineTo(p2) self.setPath(path)
def paint(self, painter, option, widget): """ Customize line adding an arrow head :param QPainter painter: Painter instance of the item :param option: Painter option of the item :param widget: Widget instance """ if not self.source or not self.destination: return line = QLineF(self.source_point, self.destination_point) if qFuzzyCompare(line.length(), 0): return # Draw the line itself color = QColor() color.setHsv(120 - 60 / self.steps_max * self.steps, 180 + 50 / self.steps_max * self.steps, 150 + 80 / self.steps_max * self.steps) if self.highlighted: color.setHsv(0, 0, 0) style = self.line_style painter.setPen(QPen(color, 1, style, Qt.RoundCap, Qt.RoundJoin)) painter.drawLine(line) painter.setPen(QPen(color, 1, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)) # Draw the arrows angle = math.acos(line.dx() / line.length()) if line.dy() >= 0: angle = (2.0 * math.pi) - angle # arrow in the middle of the arc hpx = line.p1().x() + (line.dx() / 2.0) hpy = line.p1().y() + (line.dy() / 2.0) head_point = QPointF(hpx, hpy) painter.setPen(QPen(color, 1, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)) destination_arrow_p1 = head_point + QPointF( math.sin(angle - math.pi / 3) * self.arrow_size, math.cos(angle - math.pi / 3) * self.arrow_size) destination_arrow_p2 = head_point + QPointF( math.sin(angle - math.pi + math.pi / 3) * self.arrow_size, math.cos(angle - math.pi + math.pi / 3) * self.arrow_size) painter.setBrush(color) painter.drawPolygon(QPolygonF([head_point, destination_arrow_p1, destination_arrow_p2])) if self.metadata["confirmation_text"]: painter.drawText(head_point, self.metadata["confirmation_text"])
def paint(self, painter, option, widget): """ Customize line adding an arrow head :param QPainter painter: Painter instance of the item :param option: Painter option of the item :param widget: Widget instance """ if not self.source or not self.destination: return line = QLineF(self.source_point, self.destination_point) if qFuzzyCompare(line.length(), 0): return # Draw the line itself color = QColor() style = Qt.SolidLine if self.status == ARC_STATUS_STRONG: color.setNamedColor('blue') if self.status == ARC_STATUS_WEAK: color.setNamedColor('salmon') style = Qt.DashLine painter.setPen(QPen(color, 1, style, Qt.RoundCap, Qt.RoundJoin)) painter.drawLine(line) painter.setPen(QPen(color, 1, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)) # Draw the arrows angle = math.acos(line.dx() / line.length()) if line.dy() >= 0: angle = (2.0 * math.pi) - angle # Â arrow in the middle of the arc hpx = line.p1().x() + (line.dx() / 2.0) hpy = line.p1().y() + (line.dy() / 2.0) head_point = QPointF(hpx, hpy) painter.setPen(QPen(color, 1, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)) destination_arrow_p1 = head_point + QPointF( math.sin(angle - math.pi / 3) * self.arrow_size, math.cos(angle - math.pi / 3) * self.arrow_size) destination_arrow_p2 = head_point + QPointF( math.sin(angle - math.pi + math.pi / 3) * self.arrow_size, math.cos(angle - math.pi + math.pi / 3) * self.arrow_size) painter.setBrush(color) painter.drawPolygon(QPolygonF([head_point, destination_arrow_p1, destination_arrow_p2])) if self.metadata["confirmation_text"]: painter.drawText(head_point, self.metadata["confirmation_text"])
def paint(self, painter, option, widget): """ Customize line adding an arrow head :param QPainter painter: Painter instance of the item :param option: Painter option of the item :param widget: Widget instance """ if not self.source or not self.destination: return line = QLineF(self.source_point, self.destination_point) if qFuzzyCompare(line.length(), 0): return # Draw the line itself color = QColor() style = Qt.SolidLine if self.status == ARC_STATUS_STRONG: color.setNamedColor('blue') if self.status == ARC_STATUS_WEAK: color.setNamedColor('salmon') style = Qt.DashLine painter.setPen(QPen(color, 1, style, Qt.RoundCap, Qt.RoundJoin)) painter.drawLine(line) painter.setPen(QPen(color, 1, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)) # Draw the arrows angle = math.acos(line.dx() / line.length()) if line.dy() >= 0: angle = (2.0 * math.pi) - angle # Â arrow in the middle of the arc hpx = line.p1().x() + (line.dx() / 2.0) hpy = line.p1().y() + (line.dy() / 2.0) head_point = QPointF(hpx, hpy) painter.setPen(QPen(color, 1, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)) destination_arrow_p1 = head_point + QPointF( math.sin(angle - math.pi / 3) * self.arrow_size, math.cos(angle - math.pi / 3) * self.arrow_size) destination_arrow_p2 = head_point + QPointF( math.sin(angle - math.pi + math.pi / 3) * self.arrow_size, math.cos(angle - math.pi + math.pi / 3) * self.arrow_size) painter.setBrush(color) painter.drawPolygon( QPolygonF([head_point, destination_arrow_p1, destination_arrow_p2]))
def getSelectionPolygon(self): line = QLineF(self.source_point, self.dest_point) angle = line.angle() * np.pi / 180 dx = self.selection_offset * np.sin(angle) dy = self.selection_offset * np.cos(angle) offset1 = QPointF(dx, dy) offset2 = QPointF(-dx, -dy) points = [ line.p1() + offset1, line.p1() + offset2, line.p2() + offset2, line.p2() + offset1 ] polygon = QPolygonF(points) return polygon
def arrow_path_concave(line, width): """ Return a :class:`QPainterPath` of a pretty looking arrow. """ path = QPainterPath() p1, p2 = line.p1(), line.p2() if p1 == p2: return path baseline = QLineF(line) # Require some minimum length. baseline.setLength(max(line.length() - width * 3, width * 3)) start, end = baseline.p1(), baseline.p2() mid = (start + end) / 2.0 normal = QLineF.fromPolar(1.0, baseline.angle() + 90).p2() path.moveTo(start) path.lineTo(start + (normal * width / 4.0)) path.quadTo(mid + (normal * width / 4.0), end + (normal * width / 1.5)) path.lineTo(end - (normal * width / 1.5)) path.quadTo(mid - (normal * width / 4.0), start - (normal * width / 4.0)) path.closeSubpath() arrow_head_len = width * 4 arrow_head_angle = 50 line_angle = line.angle() - 180 angle_1 = line_angle - arrow_head_angle / 2.0 angle_2 = line_angle + arrow_head_angle / 2.0 points = [ p2, p2 + QLineF.fromPolar(arrow_head_len, angle_1).p2(), baseline.p2(), p2 + QLineF.fromPolar(arrow_head_len, angle_2).p2(), p2 ] poly = QPolygonF(points) path_head = QPainterPath() path_head.addPolygon(poly) path = path.united(path_head) return path
def adjust(self): """ Draw the arc line """ if not self.source or not self.destination: return line = QLineF( self.mapFromItem( self.source, self.source.boundingRect().width() - (self.source.boundingRect().width() / 2.0), self.source.boundingRect().height() / 2.0), self.mapFromItem(self.destination, self.destination.boundingRect().width() / 2.0, self.destination.boundingRect().height() / 2.0)) self.prepareGeometryChange() self.source_point = line.p1() self.destination_point = line.p2() # mouse over on line only self.setLine(line)
def paint(self, painter, options, widget=None): painter.setPen(self.pen) if self.head is not None: painter.drawLine(self.head) if self.tail is not None: painter.drawLine(self.tail) painter.drawLine(self.line()) if self.isSelected(): painter.setPen(self.dashed_pen) painter.drawPolygon(self.selection_polygon) # draw the arrow tip if self.tail is not None: tail_line = self.tail else: tail_line = self.line() intersection_point = QPointF(0, 0) for line in self.to_port.lines(): line = QLineF(self.mapFromScene(line.p1()), self.mapFromScene(line.p2())) intersection_type = tail_line.intersect(line, intersection_point) if intersection_type == QLineF.BoundedIntersection: break angle = math.acos(tail_line.dx() / tail_line.length()) if tail_line.dy() >= 0: angle = (math.pi * 2) - angle arrow_p1 = intersection_point - QPointF( math.sin(angle + math.pi / 3) * self.arrow_size, math.cos(angle + math.pi / 3) * self.arrow_size) arrow_p2 = intersection_point - QPointF( math.sin(angle + math.pi - math.pi / 3) * self.arrow_size, math.cos(angle + math.pi - math.pi / 3) * self.arrow_size) self.arrow_head.clear() for p in [intersection_point, arrow_p1, arrow_p2]: self.arrow_head.append(p) path = QPainterPath() path.addPolygon(self.arrow_head) painter.fillPath(path, self.brush)
def arrow_path_plain(line, width): """ Return an :class:`QPainterPath` of a plain looking arrow. """ path = QPainterPath() p1, p2 = line.p1(), line.p2() if p1 == p2: return path baseline = QLineF(line) # Require some minimum length. baseline.setLength(max(line.length() - width * 3, width * 3)) path.moveTo(baseline.p1()) path.lineTo(baseline.p2()) stroker = QPainterPathStroker() stroker.setWidth(width) path = stroker.createStroke(path) arrow_head_len = width * 4 arrow_head_angle = 50 line_angle = line.angle() - 180 angle_1 = line_angle - arrow_head_angle / 2.0 angle_2 = line_angle + arrow_head_angle / 2.0 points = [ p2, p2 + QLineF.fromPolar(arrow_head_len, angle_1).p2(), p2 + QLineF.fromPolar(arrow_head_len, angle_2).p2(), p2 ] poly = QPolygonF(points) path_head = QPainterPath() path_head.addPolygon(poly) path = path.united(path_head) return path
def adjust(self): """ Draw the arc line """ if not self.source or not self.destination: return line = QLineF( self.mapFromItem( self.source, self.source.boundingRect().width() - (self.source.boundingRect().width() / 2.0), self.source.boundingRect().height() / 2.0 ), self.mapFromItem( self.destination, self.destination.boundingRect().width() / 2.0, self.destination.boundingRect().height() / 2.0 ) ) self.prepareGeometryChange() self.source_point = line.p1() self.destination_point = line.p2() # mouse over on line only self.setLine(line)
def adjustGeometry(self): """ Adjust the widget geometry to exactly fit the arrow inside while preserving the arrow path scene geometry. """ # local system coordinate geom = self.geometry().translated(-self.pos()) line = self.__line arrow_rect = self.__arrowItem.shape().boundingRect() if geom.isNull() and not line.isNull(): geom = QRectF(0, 0, 1, 1) if not (geom.contains(arrow_rect)): geom = geom.united(arrow_rect) geom = geom.intersected(arrow_rect) diff = geom.topLeft() line = QLineF(line.p1() - diff, line.p2() - diff) geom.translate(self.pos()) self.setGeometry(geom) self.setLine(line)
def showWedge(self, angle, color, extended=False, rev_gradient=False, outline_only=False): """Summary Args: angle (TYPE): Description color (TYPE): Description extended (bool, optional): Description rev_gradient (bool, optional): Description outline_only (bool, optional): Description """ # Hack to keep wedge in front # self.setRotation(self.pre_xover_item_group.rotation()) self._last_params = (angle, color, extended, rev_gradient, outline_only) radius = self._radius span = self.pre_xover_item_group.partCrossoverSpanAngle() / 2 radius_adjusted = radius + (_WEDGE_RECT_GAIN / 2) tip = QPointF(radius_adjusted, radius_adjusted) EXT = 1.35 if extended else 1.0 # print("wtf", tip, pos) base_p2 = QPointF(1, 1) line0 = QLineF(tip, QPointF(base_p2)) line1 = QLineF(tip, QPointF(base_p2)) line2 = QLineF(tip, QPointF(base_p2)) quad_scale = 1 + (.22 * (span - 5) / 55) # lo+(hi-lo)*(val-min)/(max-min) line0.setLength(radius_adjusted * EXT * quad_scale) # for quadTo control point line1.setLength(radius_adjusted * EXT) line2.setLength(radius_adjusted * EXT) line0.setAngle(angle) line1.setAngle(angle - span) line2.setAngle(angle + span) path = QPainterPath() if outline_only: self.setPen(getPenObj(color, 0.5, alpha=128, capstyle=Qt.RoundCap)) path.moveTo(line1.p2()) path.quadTo(line0.p2(), line2.p2()) else: gradient = QRadialGradient(tip, radius_adjusted * EXT) color1 = getColorObj(color, alpha=80) color2 = getColorObj(color, alpha=0) if rev_gradient: color1, color2 = color2, color1 if extended: gradient.setColorAt(0, color1) gradient.setColorAt(radius_adjusted / (radius_adjusted * EXT), color1) gradient.setColorAt( radius_adjusted / (radius_adjusted * EXT) + 0.01, color2) gradient.setColorAt(1, color2) else: gradient.setColorAt(0, getColorObj(color, alpha=50)) brush = QBrush(gradient) self.setBrush(brush) path.moveTo(line1.p1()) path.lineTo(line1.p2()) path.quadTo(line0.p2(), line2.p2()) path.lineTo(line2.p1()) self.setPath(path) self.show()
def _drawGuidelines(painter, glyph, scale, rect, guidelines, drawLines=True, drawText=True, drawSelection=True, color=None): if not (drawLines or drawText): return xMin, yMin, width, height = rect xMax = xMin + width yMax = yMin + height for line in guidelines: color_ = color if color_ is None: if line.color: color_ = colorToQColor(line.color) else: color_ = defaultColor("glyphGuideline") painter.save() painter.setPen(color) line1 = None if None not in (line.x, line.y): if line.angle is not None: # make an infinite line that intersects *(line.x, line.y)* # 1. make horizontal line from *(line.x, line.y)* of length # *diagonal* diagonal = math.sqrt(width**2 + height**2) line1 = QLineF(line.x, line.y, line.x + diagonal, line.y) # 2. set the angle # defcon guidelines are clockwise line1.setAngle(line.angle) # 3. reverse the line and set length to 2 * *diagonal* line1.setPoints(line1.p2(), line1.p1()) line1.setLength(2 * diagonal) else: line1 = QLineF(xMin, line.y, xMax, line.y) textX = 0 textY = 0 if drawLines: if line1 is not None: # line drawLine(painter, line1.x1(), line1.y1(), line1.x2(), line1.y2()) # point x, y = line.x, line.y smoothWidth = 8 * scale smoothHalf = smoothWidth / 2.0 painter.save() pointPath = QPainterPath() x -= smoothHalf y -= smoothHalf pointPath.addEllipse(x, y, smoothWidth, smoothWidth) pen = QPen(color_) pen.setWidthF(1 * scale) painter.setPen(pen) if drawSelection and line.selected: painter.fillPath(pointPath, color_) painter.drawPath(pointPath) painter.restore() else: if line.y is not None: drawLine(painter, xMin, line.y, xMax, line.y) elif line.x is not None: drawLine(painter, line.x, yMin, line.x, yMax) if drawText and line.name: if line1 is not None: textX = line.x textY = line.y - 6 * scale xAlign = "center" else: if line.y is not None: fontSize = painter.font().pointSize() textX = glyph.width + 6 * scale textY = line.y - (fontSize / 3.5) * scale elif line.x is not None: textX = line.x + 6 * scale textY = 0 xAlign = "left" drawTextAtPoint(painter, line.name, textX, textY, scale, xAlign=xAlign) painter.restore()
def _drawGuidelines(painter, glyph, scale, rect, guidelines, drawLines=True, drawText=True, drawSelection=True, color=None): if not (drawLines or drawText): return xMin, yMin, width, height = rect xMax = xMin + width yMax = yMin + height fontSize = painter.font().pointSize() for line in guidelines: color_ = color if color_ is None: if line.color: color_ = colorToQColor(line.color) else: color_ = defaultColor("glyphGuideline") painter.save() painter.setPen(color) line1 = None if None not in (line.x, line.y): if line.angle is not None: # make an infinite line that intersects *(line.x, line.y)* # 1. make horizontal line from *(line.x, line.y)* of length # *diagonal* diagonal = math.sqrt(width**2 + height**2) line1 = QLineF(line.x, line.y, line.x + diagonal, line.y) # 2. set the angle # defcon guidelines are clockwise line1.setAngle(line.angle) # 3. reverse the line and set length to 2 * *diagonal* line1.setPoints(line1.p2(), line1.p1()) line1.setLength(2 * diagonal) else: line1 = QLineF(xMin, line.y, xMax, line.y) textX = 0 textY = 0 if drawLines: if line1 is not None: # line drawLine( painter, line1.x1(), line1.y1(), line1.x2(), line1.y2()) # point x, y = line.x, line.y smoothWidth = 8 * scale smoothHalf = smoothWidth / 2.0 painter.save() pointPath = QPainterPath() x -= smoothHalf y -= smoothHalf pointPath.addEllipse(x, y, smoothWidth, smoothWidth) pen = QPen(color_) pen.setWidthF(1 * scale) painter.setPen(pen) if drawSelection and line.selected: painter.fillPath(pointPath, color_) painter.drawPath(pointPath) painter.restore() else: if line.y is not None: drawLine(painter, xMin, line.y, xMax, line.y) elif line.x is not None: drawLine(painter, line.x, yMin, line.x, yMax) if drawText and line.name: if line1 is not None: textX = line.x textY = line.y - 6 * scale xAlign = "center" else: if line.y is not None: textX = glyph.width + 6 * scale textY = line.y - (fontSize / 3.5) * scale elif line.x is not None: textX = line.x + 6 * scale textY = 0 xAlign = "left" drawTextAtPoint( painter, line.name, textX, textY, scale, xAlign=xAlign) painter.restore()
class KnifeTool(BaseTool): icon = _path name = QApplication.translate("KnifeTool", "Knife") shortcut = "E" def __init__(self, parent=None): super().__init__(parent) self._cachedIntersections = None self._knifeLine = None self._knifePts = None def _appendIntersection(self, contour, index, pt): x, y, t = pt self._knifePts.append((x, y)) if (contour, index) in self._cachedIntersections: self._cachedIntersections[(contour, index)].append(t) else: self._cachedIntersections[(contour, index)] = [t] def _findIntersections(self): self._cachedIntersections = OrderedDict() self._knifePts = [] line = self._knifeLine for contour in self._glyph: segments = contour.segments for index, seg in enumerate(segments): if seg[-1].segmentType == "move": continue prev = segments[index - 1][-1] if len(seg) == 3: if seg[-1].segmentType == "qcurve": i = bezierMath.qcurveIntersections( line.x1(), line.y1(), line.x2(), line.y2(), prev, *seg) else: i = bezierMath.curveIntersections( line.x1(), line.y1(), line.x2(), line.y2(), prev, seg[0], seg[1], seg[2], ) for pt in i: self._appendIntersection(contour, index, pt) elif len(seg) == 1: pt = bezierMath.lineIntersection( prev.x, prev.y, seg[0].x, seg[0].y, line.x1(), line.y1(), line.x2(), line.y2(), ) if pt is not None: self._appendIntersection(contour, index, pt) # events def mousePressEvent(self, event): if event.button() == Qt.LeftButton: pos = event.localPos() self._knifeLine = QLineF(pos, pos) else: super().mousePressEvent(event) def mouseMoveEvent(self, event): if event.buttons() & Qt.LeftButton: pos = event.localPos() if self._knifeLine is None: self._knifeLine = QLineF(pos, pos) return line = self._knifeLine if event.modifiers() & Qt.ShiftModifier: pos = self.clampToOrigin(pos, line.p1()) line.setP2(pos) self._findIntersections() self.parent().update() else: super().mouseMoveEvent(event) def mouseReleaseEvent(self, event): if event.button() != Qt.LeftButton: super().mouseReleaseEvent(event) return if self._knifeLine is not None: p1, p2 = self._knifeLine.p1(), self._knifeLine.p2() self._knifeLine = None self._knifePts = None self.parent().update() # no-move clicks if not self._cachedIntersections: return self._glyph.beginUndoGroup() cutContours = (not event.modifiers() & Qt.AltModifier and len(self._cachedIntersections) > 1) if cutContours: oldPts = {pt for contour in self._glyph for pt in contour} path = self._glyph.getRepresentation("defconQt.QPainterPath") # reverse so as to not invalidate our cached segment indexes for loc, ts in reversed(list(self._cachedIntersections.items())): contour, index = loc prev = 1 # reverse so as to cut from higher to lower value and compensate for t in sorted(ts, reverse=True): contour.splitAndInsertPointAtSegmentAndT(index, t / prev) prev = t # TODO: optimize if cutContours: newPts = { pt for contour in self._glyph for pt in contour if pt.segmentType } - oldPts del oldPts distances = dict() for point in newPts: d = bezierMath.distance(p1.x(), p1.y(), point.x, point.y) distances[d] = point del newPts sortedPts = [distances[dist] for dist in sorted(distances.keys())] del distances # group points by belonging to contour "black area" siblings = [] stack = None if not path.contains(p1): stack = [] for pt, nextPt in zip(sortedPts, sortedPts[1:]): qPt = QPointF(pt.x, pt.y) qHalf = qPt + 0.5 * (QPointF(nextPt.x, nextPt.y) - qPt) if path.contains(qHalf): if stack is not None: stack.append(pt) else: if stack: stack.append(pt) siblings.extend(stack) stack = [] if not path.contains(p2): if stack: stack.append(nextPt) siblings.extend(stack) del stack # ok, now i = siblings.index(loc); siblings[i+1-2(i%2)] will yield # sibling newGlyph = self._glyph.__class__() pen = newGlyph.getPointPen() def _visitPath(contour, index): didJump = False pen.beginPath() while True: pt = contour.getPoint(index) if pt in visited: pen.endPath() break if pt not in siblings: visited.add(pt) segmentType = pt.segmentType smooth = pt.smooth if didJump or pt in siblings: smooth = False if didJump: segmentType = "line" pen.addPoint((pt.x, pt.y), segmentType, smooth) if pt in siblings and not didJump: i = siblings.index(pt) otherPt = siblings[i + 1 - 2 * (i % 2)] # TODO: optimize-out this lookup for c in self._glyph: try: index = c.index(otherPt) except Exception: pass else: contour = c break didJump = True continue didJump = False index += 1 visited = set() for contour in self._glyph: for index, pt in enumerate(contour): if pt in visited or pt in siblings: continue _visitPath(contour, index) self._glyph.clearContours() pen = self._glyph.getPointPen() newGlyph.drawPoints(pen) self._glyph.endUndoGroup() ## self._cachedIntersections = None # custom painting def paint(self, painter, index): widget = self.parent() if index != widget.activeIndex(): return line = self._knifeLine if line is not None and line.length(): painter.save() pen = painter.pen() pen.setWidth(0) painter.setPen(pen) drawing.drawLine(painter, line.x1(), line.y1(), line.x2(), line.y2()) if self._knifePts is not None: scale = widget.inverseScale() dotSize = 5 * scale dotHalf = dotSize / 2 path = QPainterPath() for x, y in self._knifePts: x -= dotHalf y -= dotHalf path.addEllipse(x, y, dotSize, dotSize) painter.drawPath(path) painter.restore()
def showWedge(self, angle, color, extended=False, rev_gradient=False, outline_only=False): """Summary Args: angle (TYPE): Description color (TYPE): Description extended (bool, optional): Description rev_gradient (bool, optional): Description outline_only (bool, optional): Description """ # Hack to keep wedge in front # self.setRotation(self.pre_xover_item_group.rotation()) self._last_params = (angle, color, extended, rev_gradient, outline_only) radius = self._radius span = self.pre_xover_item_group.partCrossoverSpanAngle() / 2 radius_adjusted = radius + (_WEDGE_RECT_GAIN / 2) tip = QPointF(radius_adjusted, radius_adjusted) EXT = 1.35 if extended else 1.0 # print("wtf", tip, pos) base_p2 = QPointF(1, 1) line0 = QLineF(tip, QPointF(base_p2)) line1 = QLineF(tip, QPointF(base_p2)) line2 = QLineF(tip, QPointF(base_p2)) quad_scale = 1 + (.22*(span - 5) / 55) # lo+(hi-lo)*(val-min)/(max-min) line0.setLength(radius_adjusted * EXT*quad_scale) # for quadTo control point line1.setLength(radius_adjusted * EXT) line2.setLength(radius_adjusted * EXT) line0.setAngle(angle) line1.setAngle(angle - span) line2.setAngle(angle + span) path = QPainterPath() if outline_only: self.setPen(getPenObj(color, 0.5, alpha=128, capstyle=Qt.RoundCap)) path.moveTo(line1.p2()) path.quadTo(line0.p2(), line2.p2()) else: gradient = QRadialGradient(tip, radius_adjusted * EXT) color1 = getColorObj(color, alpha=80) color2 = getColorObj(color, alpha=0) if rev_gradient: color1, color2 = color2, color1 if extended: gradient.setColorAt(0, color1) gradient.setColorAt(radius_adjusted / (radius_adjusted * EXT), color1) gradient.setColorAt(radius_adjusted / (radius_adjusted * EXT) + 0.01, color2) gradient.setColorAt(1, color2) else: gradient.setColorAt(0, getColorObj(color, alpha=50)) brush = QBrush(gradient) self.setBrush(brush) path.moveTo(line1.p1()) path.lineTo(line1.p2()) path.quadTo(line0.p2(), line2.p2()) path.lineTo(line2.p1()) self.setPath(path) self.show()
class ControlPointLine(QGraphicsObject): lineChanged = Signal(QLineF) lineEdited = Signal(QLineF) def __init__(self, parent=None, **kwargs): QGraphicsObject.__init__(self, parent, **kwargs) self.setFlag(QGraphicsItem.ItemHasNoContents) self.setFlag(QGraphicsItem.ItemIsFocusable) self.__line = QLineF() self.__points = \ [ControlPoint(self, ControlPoint.TopLeft), # TopLeft is line start ControlPoint(self, ControlPoint.BottomRight) # line end ] self.__activeControl = None if self.scene(): self.__installFilter() for p in self.__points: p.setFlag(QGraphicsItem.ItemIsFocusable) p.setFocusProxy(self) def setLine(self, line): if not isinstance(line, QLineF): raise TypeError() if line != self.__line: self.__line = line self.__pointsLayout() self.lineChanged.emit(line) def line(self): return self.__line def isControlActive(self): """Return the state of the control. True if the control is active (user is dragging one of the points) False otherwise. """ return self.__activeControl is not None def __installFilter(self): for p in self.__points: p.installSceneEventFilter(self) def itemChange(self, change, value): if change == QGraphicsItem.ItemSceneHasChanged: if self.scene(): self.__installFilter() return QGraphicsObject.itemChange(self, change, value) def sceneEventFilter(self, obj, event): try: obj = toGraphicsObjectIfPossible(obj) if isinstance(obj, ControlPoint): etype = event.type() if etype == QEvent.GraphicsSceneMousePress: self.__setActiveControl(obj) elif etype == QEvent.GraphicsSceneMouseRelease: self.__setActiveControl(None) return QGraphicsObject.sceneEventFilter(self, obj, event) except Exception: log.error("", exc_info=True) def __pointsLayout(self): self.__points[0].setPos(self.__line.p1()) self.__points[1].setPos(self.__line.p2()) def __setActiveControl(self, control): if self.__activeControl != control: if self.__activeControl is not None: self.__activeControl.positionChanged[QPointF].disconnect( self.__activeControlMoved) self.__activeControl = control if control is not None: control.positionChanged[QPointF].connect( self.__activeControlMoved) def __activeControlMoved(self, pos): line = QLineF(self.__line) control = self.__activeControl if control.anchor() == ControlPoint.TopLeft: line.setP1(pos) elif control.anchor() == ControlPoint.BottomRight: line.setP2(pos) if self.__line != line: self.blockSignals(True) self.setLine(line) self.blockSignals(False) self.lineEdited.emit(line) def boundingRect(self): return QRectF()
def drawToolButtonMenuIndicator(self, option, painter, widget=None): arrow_rect = self.proxy().subControlRect(QStyle.CC_ToolButton, option, QStyle.SC_ToolButtonMenu, widget) text_color = option.palette.color( QPalette.WindowText if option.state & QStyle.State_AutoRaise else QPalette.ButtonText ) button_color = option.palette.color(QPalette.Button) background_color = self.background_color(button_color, 0.5) painter.save() # draw separating vertical line if option.state & (QStyle.State_On | QStyle.State_Sunken): top_offset, bottom_offset = 4, 3 else: top_offset, bottom_offset = 2, 2 if option.direction == Qt.LeftToRight: separator_line = QLineF( arrow_rect.x() - 3, arrow_rect.top() + top_offset, arrow_rect.x() - 3, arrow_rect.bottom() - bottom_offset, ) else: separator_line = QLineF( arrow_rect.right() + 3, arrow_rect.top() + top_offset, arrow_rect.right() + 3, arrow_rect.bottom() - bottom_offset, ) light_gradient = QLinearGradient(separator_line.p1(), separator_line.p2()) light_gradient.setColorAt( 0.0, ColorScheme.shade(self.background_top_color(button_color), ColorScheme.LightShade, 0.0) ) light_gradient.setColorAt( 1.0, ColorScheme.shade(self.background_bottom_color(button_color), ColorScheme.MidlightShade, 0.5) ) separator_color = ColorScheme.shade(self.background_bottom_color(button_color), ColorScheme.MidShade, 0.0) painter.setRenderHint(QPainter.Antialiasing, False) painter.setPen(QPen(light_gradient, 1)) painter.drawLine(separator_line.translated(-1, 0)) painter.drawLine(separator_line.translated(+1, 0)) painter.setPen(QPen(separator_color, 1)) painter.drawLine(separator_line) # draw arrow arrow = QPolygonF([QPointF(-3, -1.5), QPointF(0.5, 2.5), QPointF(4, -1.5)]) if option.direction == Qt.LeftToRight: arrow.translate(-2, 1) else: arrow.translate(+2, 1) pen_thickness = 1.6 painter.setRenderHint(QPainter.Antialiasing, True) painter.translate(arrow_rect.center()) painter.translate(0, +1) painter.setPen( QPen(self.calc_light_color(background_color), pen_thickness, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin) ) painter.drawPolyline(arrow) painter.translate(0, -1) painter.setPen( QPen(self.deco_color(background_color, text_color), pen_thickness, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin) ) painter.drawPolyline(arrow) painter.restore()
def drawToolButtonMenuIndicator(self, option, painter, widget=None): arrow_rect = self.proxy().subControlRect(QStyle.CC_ToolButton, option, QStyle.SC_ToolButtonMenu, widget) text_color = option.palette.color( QPalette.WindowText if option.state & QStyle.State_AutoRaise else QPalette.ButtonText) button_color = option.palette.color(QPalette.Button) background_color = self.background_color(button_color, 0.5) painter.save() # draw separating vertical line if option.state & (QStyle.State_On | QStyle.State_Sunken): top_offset, bottom_offset = 4, 3 else: top_offset, bottom_offset = 2, 2 if option.direction == Qt.LeftToRight: separator_line = QLineF(arrow_rect.x() - 3, arrow_rect.top() + top_offset, arrow_rect.x() - 3, arrow_rect.bottom() - bottom_offset) else: separator_line = QLineF(arrow_rect.right() + 3, arrow_rect.top() + top_offset, arrow_rect.right() + 3, arrow_rect.bottom() - bottom_offset) light_gradient = QLinearGradient(separator_line.p1(), separator_line.p2()) light_gradient.setColorAt( 0.0, ColorScheme.shade(self.background_top_color(button_color), ColorScheme.LightShade, 0.0)) light_gradient.setColorAt( 1.0, ColorScheme.shade(self.background_bottom_color(button_color), ColorScheme.MidlightShade, 0.5)) separator_color = ColorScheme.shade( self.background_bottom_color(button_color), ColorScheme.MidShade, 0.0) painter.setRenderHint(QPainter.Antialiasing, False) painter.setPen(QPen(light_gradient, 1)) painter.drawLine(separator_line.translated(-1, 0)) painter.drawLine(separator_line.translated(+1, 0)) painter.setPen(QPen(separator_color, 1)) painter.drawLine(separator_line) # draw arrow arrow = QPolygonF( [QPointF(-3, -1.5), QPointF(0.5, 2.5), QPointF(4, -1.5)]) if option.direction == Qt.LeftToRight: arrow.translate(-2, 1) else: arrow.translate(+2, 1) pen_thickness = 1.6 painter.setRenderHint(QPainter.Antialiasing, True) painter.translate(arrow_rect.center()) painter.translate(0, +1) painter.setPen( QPen(self.calc_light_color(background_color), pen_thickness, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)) painter.drawPolyline(arrow) painter.translate(0, -1) painter.setPen( QPen(self.deco_color(background_color, text_color), pen_thickness, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)) painter.drawPolyline(arrow) painter.restore()
class StickWidget(QGraphicsObject): font: QFont = QFont("monospace", 32) delete_clicked = pyqtSignal(Stick) link_initiated = pyqtSignal('PyQt_PyObject') # Actually StickWidget link_accepted = pyqtSignal('PyQt_PyObject') hovered = pyqtSignal(['PyQt_PyObject', 'PyQt_PyObject']) stick_changed = pyqtSignal('PyQt_PyObject') sibling_changed = pyqtSignal(bool) right_clicked = pyqtSignal('PyQt_PyObject') handle_idle_brush = QBrush(QColor(0, 125, 125, 50)) handle_hover_brush = QBrush(QColor(125, 125, 0, 50)) handle_press_brush = QBrush(QColor(200, 200, 0, 0)) handle_idle_pen = QPen(QColor(0, 0, 0, 255)) handle_press_pen = QPen(QColor(200, 200, 0, 255)) handle_size = 20 normal_color = QColor(0, 200, 120) negative_color = QColor(200, 0, 0) positive_color = QColor(0, 200, 0) mismatched = pyqtSignal('PyQt_PyObject') misplaced = pyqtSignal('PyQt_PyObject') measurement_corrected = pyqtSignal('PyQt_PyObject') clearly_visible = pyqtSignal('PyQt_PyObject') zero_clicked = pyqtSignal('PyQt_PyObject') def __init__(self, stick: Stick, camera: Camera, parent: Optional[QGraphicsItem] = None): QGraphicsObject.__init__(self, parent) self.camera = camera self.stick = stick self.line = QLineF() self.gline = QGraphicsLineItem(self.line) self.stick_label_text = QGraphicsSimpleTextItem("0", self) self.stick_label_text.setFont(StickWidget.font) self.stick_label_text.setPos(self.line.p1() - QPoint(0, 24)) self.stick_label_text.setBrush(QBrush(QColor(0, 255, 0))) self.stick_label_text.hide() self.setZValue(10) self.mode = StickMode.Display self.btn_delete = Button("delete", "x", parent=self) self.btn_delete.setFlag(QGraphicsItem.ItemIgnoresTransformations, True) self.btn_delete.set_base_color([ButtonColor.RED]) self.btn_delete.setVisible(False) btn_size = max(int(np.linalg.norm(self.stick.top - self.stick.bottom) / 5.0), 15) self.btn_delete.set_height(12) self.btn_delete.clicked.connect(self.handle_btn_delete_clicked) self.btn_delete.setPos(self.line.p1() - QPointF(0.5 * self.btn_delete.boundingRect().width(), 1.1 * self.btn_delete.boundingRect().height())) self.btn_delete.set_opacity(0.7) self.top_handle = QGraphicsEllipseItem(0, 0, self.handle_size, self.handle_size, self) self.mid_handle = QGraphicsEllipseItem(0, 0, self.handle_size, self.handle_size, self) self.bottom_handle = QGraphicsEllipseItem(0, 0, self.handle_size, self.handle_size, self) self.top_handle.setAcceptedMouseButtons(Qt.NoButton) self.mid_handle.setAcceptedMouseButtons(Qt.NoButton) self.bottom_handle.setAcceptedMouseButtons(Qt.NoButton) self.top_handle.setBrush(self.handle_idle_brush) self.top_handle.setPen(self.handle_idle_pen) self.mid_handle.setBrush(self.handle_idle_brush) self.mid_handle.setPen(self.handle_idle_pen) self.bottom_handle.setBrush(self.handle_idle_brush) self.bottom_handle.setPen(self.handle_idle_pen) self.hovered_handle: Optional[QGraphicsRectItem] = None self.handles = [self.top_handle, self.mid_handle, self.bottom_handle] self.link_button = Button("link", "Link to...", parent=self) self.link_button.set_base_color([ButtonColor.GREEN]) self.link_button.set_height(12) self.link_button.set_label("Link", direction="vertical") self.link_button.fit_to_contents() self.link_button.clicked.connect(lambda: self.link_initiated.emit(self)) self.link_button.setVisible(False) self.link_button.setFlag(QGraphicsObject.ItemIgnoresTransformations, False) self.adjust_line() self.setAcceptHoverEvents(True) self.top_handle.setZValue(4) self.bottom_handle.setZValue(4) self.mid_handle.setZValue(4) self.top_handle.hide() self.mid_handle.hide() self.bottom_handle.hide() self.handle_mouse_offset = QPointF(0, 0) self.available_for_linking = False self.link_source = False self.current_highlight_color: QColor = StickWidget.normal_color self.highlighted = False self.frame_color: Optional[None] = self.normal_color self.is_linked = False self.is_master = True self.selected = False self.measured_height: int = -1 self.current_color = self.normal_color self.show_label = False self.highlight_animation = QPropertyAnimation(self, b"highlight_color") self.highlight_animation.valueChanged.connect(self.handle_highlight_animation_value_changed) self.deleting = False self.update_tooltip() self.show_measurements: bool = False self.proposed_snow_height: int = -1 self.zero_btn = Button("zero_btn", "0", parent=self) self.zero_btn.setFlag(QGraphicsItem.ItemIgnoresTransformations, True) self.zero_btn.setVisible(False) self.zero_btn.setPos(self.boundingRect().center() + QPointF(self.zero_btn.boundingRect().width() * -0.5, self.boundingRect().height() * 0.5)) self.zero_btn.clicked.connect(self.handle_zero) @pyqtSlot() def handle_btn_delete_clicked(self): self.delete_clicked.emit(self.stick) def prepare_for_deleting(self): self.deleting = True self.highlight_animation.stop() self.btn_delete.setParentItem(None) self.scene().removeItem(self.btn_delete) self.btn_delete.deleteLater() def paint(self, painter: QPainter, option: QStyleOptionGraphicsItem, widget: Optional[PyQt5.QtWidgets.QWidget] = ...): painter.setPen(QPen(self.current_color, 1.0)) brush = QBrush(self.current_highlight_color) pen = QPen(brush, 4) painter.setPen(pen) if self.highlighted: painter.fillRect(self.boundingRect(), QBrush(self.current_highlight_color)) if self.frame_color is not None and self.mode != StickMode.Edit and self.mode != StickMode.EditDelete: painter.setPen(QPen(self.frame_color, 4)) painter.drawRect(self.boundingRect()) pen = QPen(QColor(0, 255, 0, 255)) pen.setWidth(1.0) pen.setColor(QColor(255, 0, 255, 255)) pen.setStyle(Qt.DotLine) painter.setPen(pen) off = 10 painter.drawLine(self.line.p1() - QPointF(0, off), self.line.p1() + QPointF(0, off)) painter.drawLine(self.line.p1() - QPointF(off, 0), self.line.p1() + QPointF(off, 0)) painter.drawLine(self.line.p2() - QPointF(0, off), self.line.p2() + QPointF(0, off)) painter.drawLine(self.line.p2() - QPointF(off, 0), self.line.p2() + QPointF(off, 0)) pen.setStyle(Qt.SolidLine) pen.setColor(QColor(0, 255, 0, 255)) painter.setPen(pen) if self.mode != StickMode.EditDelete: pen.setWidth(2.0) br = painter.brush() painter.setPen(pen) painter.drawEllipse(self.line.p1(), 10, 10) painter.drawEllipse(self.line.p2(), 10, 10) painter.setBrush(br) if self.mode == StickMode.Measurement and self.proposed_snow_height >= 0: point = QPointF(self.boundingRect().x(), -self.proposed_snow_height + self.line.p2().y()) pen = QPen(QColor(200, 100, 0, 255), 3.0) painter.setPen(pen) painter.drawLine(point, point + QPointF(self.boundingRect().width(), 0.0)) if self.measured_height >= 0: vec = (self.stick.top - self.stick.bottom) / np.linalg.norm(self.stick.top - self.stick.bottom) dist_along_stick = self.measured_height / np.dot(np.array([0.0, -1.0]), vec) point = self.line.p2() + dist_along_stick * QPointF(vec[0], vec[1]) point = QPointF(self.boundingRect().x(), point.y()) pen = QPen(QColor(0, 100, 200, 255), 3.0) painter.setPen(pen) painter.drawLine(point, point + QPointF(self.boundingRect().width(), 0.0)) else: painter.drawLine(self.line.p1(), self.line.p2()) if self.selected: pen.setColor(QColor(255, 125, 0, 255)) pen.setStyle(Qt.DashLine) painter.setPen(pen) painter.drawRect(self.boundingRect().marginsAdded(QMarginsF(5, 5, 5, 5))) if self.show_measurements: painter.fillRect(self.stick_label_text.boundingRect().translated(self.stick_label_text.pos()), QBrush(QColor(0, 0, 0, 120))) def boundingRect(self) -> PyQt5.QtCore.QRectF: return self.gline.boundingRect().united(self.top_handle.boundingRect()).\ united(self.mid_handle.boundingRect()).united(self.bottom_handle.boundingRect()) def set_edit_mode(self, value: bool): if value: self.set_mode(StickMode.EditDelete) else: self.set_mode(StickMode.Display) def set_mode(self, mode: StickMode): if mode == StickMode.Display: self.btn_delete.setVisible(False) self.top_handle.setVisible(False) self.mid_handle.setVisible(False) self.bottom_handle.setVisible(False) self.link_button.setVisible(False) self.available_for_linking = False self.link_source = False self.zero_btn.setVisible(False) self.setVisible(self.stick.is_visible) elif mode == StickMode.EditDelete: self.set_mode(StickMode.Display) self.top_handle.setVisible(True) self.mid_handle.setVisible(True) self.bottom_handle.setVisible(True) self.available_for_linking = False self.link_source = False self.btn_delete.setVisible(True) elif mode == StickMode.LinkSource: self.set_mode(StickMode.Display) self.link_source = True self.available_for_linking = False self.link_button.setPos(self.boundingRect().topLeft()) self.link_button.set_width(int(self.boundingRect().width())) self.link_button.set_button_height(int(self.boundingRect().height())) self.link_button.adjust_text_to_button() elif mode == StickMode.LinkTarget: self.set_mode(StickMode.Display) self.link_source = False self.available_for_linking = True elif mode == StickMode.Edit: self.set_mode(StickMode.EditDelete) self.btn_delete.setVisible(False) elif mode == StickMode.Measurement: self.zero_btn.setVisible(True) self.setVisible(True) self.mode = mode self.update_tooltip() self.update() def mousePressEvent(self, event: QGraphicsSceneMouseEvent): if self.mode != StickMode.EditDelete: return if self.hovered_handle is None: return self.hovered_handle.setBrush(self.handle_press_brush) if self.hovered_handle == self.mid_handle: self.bottom_handle.setBrush(self.handle_press_brush) self.bottom_handle.setPen(self.handle_press_pen) self.bottom_handle.setOpacity(0.5) self.top_handle.setBrush(self.handle_press_brush) self.top_handle.setPen(self.handle_press_pen) self.top_handle.setOpacity(0.5) self.hovered_handle.setPen(self.handle_press_pen) self.hovered_handle.setOpacity(0.5) self.handle_mouse_offset = self.hovered_handle.rect().center() - event.pos() self.btn_delete.setVisible(False) def mouseReleaseEvent(self, event: QGraphicsSceneMouseEvent): if self.available_for_linking: self.link_accepted.emit(self) return if self.mode == StickMode.Measurement: old_snow = self.stick.snow_height_px self.measured_height = self.proposed_snow_height self.stick.set_snow_height_px(self.proposed_snow_height) if abs(old_snow - self.proposed_snow_height) > 0: self.measurement_corrected.emit(self) self.proposed_snow_height = -1 if self.mode != StickMode.EditDelete and self.mode != StickMode.Edit: return if self.hovered_handle is not None: self.hovered_handle.setBrush(self.handle_hover_brush) self.hovered_handle.setPen(self.handle_idle_pen) self.hovered_handle.setOpacity(1.0) if self.hovered_handle == self.mid_handle: self.bottom_handle.setBrush(self.handle_idle_brush) self.bottom_handle.setPen(self.handle_idle_pen) self.bottom_handle.setOpacity(1.0) self.top_handle.setBrush(self.handle_idle_brush) self.top_handle.setPen(self.handle_idle_pen) self.top_handle.setOpacity(1.0) self.stick_changed.emit(self) self.hovered_handle = None if self.mode == StickMode.EditDelete: self.btn_delete.setVisible(True) def mouseMoveEvent(self, event: QGraphicsSceneMouseEvent): if self.hovered_handle is None: return if self.hovered_handle == self.top_handle: self.line.setP1((event.pos() + self.handle_mouse_offset).toPoint()) elif self.hovered_handle == self.bottom_handle: self.line.setP2((event.pos() + self.handle_mouse_offset).toPoint()) else: displacement = event.pos() - event.lastPos() self.setPos(self.pos() + displacement) self.adjust_handles() self.adjust_stick() self.scene().update() def set_top(self, pos: QPoint): self.line.setP1(pos) self.adjust_handles() self.adjust_stick() self.scene().update() def set_bottom(self, pos: QPoint): self.line.setP2(pos) self.adjust_handles() self.adjust_stick() self.scene().update() def hoverEnterEvent(self, event: QGraphicsSceneHoverEvent): if self.available_for_linking: self.hovered.emit(True, self) elif self.link_source: self.link_button.setVisible(True) self.scene().update() def hoverLeaveEvent(self, event: QGraphicsSceneHoverEvent): for h in self.handles: h.setBrush(self.handle_idle_brush) self.hovered_handle = None if self.available_for_linking: self.hovered.emit(False, self) self.link_button.setVisible(False) self.proposed_snow_height = -1 self.scene().update() def hoverMoveEvent(self, event: QGraphicsSceneHoverEvent): if self.mode != StickMode.EditDelete and self.mode != StickMode.Edit and self.mode != StickMode.Measurement: return if self.mode == StickMode.Measurement: self.proposed_snow_height = max(self.line.p2().y() - event.pos().y(), 0) self.update() return hovered_handle = list(filter(lambda h: h.rect().contains(event.pos()), self.handles)) if len(hovered_handle) == 0: if self.hovered_handle is not None: self.hovered_handle.setBrush(self.handle_idle_brush) self.hovered_handle = None return if self.hovered_handle is not None and self.hovered_handle != hovered_handle[0]: self.hovered_handle.setBrush(self.handle_idle_brush) self.hovered_handle = hovered_handle[0] if self.hovered_handle == self.top_handle: self.top_handle.setBrush(self.handle_hover_brush) elif self.hovered_handle == self.bottom_handle: self.bottom_handle.setBrush(self.handle_hover_brush) else: self.mid_handle.setBrush(self.handle_hover_brush) self.scene().update() def adjust_stick(self): self.stick.top[0] = self.pos().x() + self.line.p1().x() self.stick.top[1] = self.pos().y() + self.line.p1().y() self.stick.bottom[0] = self.pos().x() + self.line.p2().x() self.stick.bottom[1] = self.pos().y() + self.line.p2().y() def adjust_handles(self): if self.line.p1().y() > self.line.p2().y(): p1, p2 = self.line.p1(), self.line.p2() self.line.setP1(p2) self.line.setP2(p1) if self.hovered_handle is not None: self.hovered_handle.setBrush(self.handle_idle_brush) self.hovered_handle.setPen(self.handle_idle_pen) self.hovered_handle = self.top_handle if self.hovered_handle == self.bottom_handle else self.bottom_handle self.hovered_handle.setBrush(self.handle_press_brush) self.hovered_handle.setPen(self.handle_press_pen) rect = self.top_handle.rect() rect.moveCenter(self.line.p1()) self.top_handle.setRect(rect) rect = self.bottom_handle.rect() rect.moveCenter(self.line.p2()) self.bottom_handle.setRect(rect) rect = self.mid_handle.rect() rect.moveCenter(self.line.center()) self.mid_handle.setRect(rect) self.btn_delete.setPos(self.top_handle.rect().center() - QPointF(self.btn_delete.boundingRect().width() / 2, self.btn_delete.boundingRect().height() + self.top_handle.boundingRect().height() / 2)) def set_available_for_linking(self, available: bool): self.available_for_linking = available def set_is_link_source(self, is_source: bool): self.link_source = is_source self.link_button.setPos(self.boundingRect().topLeft()) self.link_button.set_width(int(self.boundingRect().width())) self.link_button.set_button_height(int(self.boundingRect().height())) self.link_button.adjust_text_to_button() def set_frame_color(self, color: Optional[QColor]): self.frame_color = color if color is not None else self.normal_color self.update() def set_is_linked(self, value: bool): self.is_linked = value if not self.is_linked: self.set_frame_color(None) if self.available_for_linking: self.highlight(QColor(0, 255, 0, 100)) else: self.highlight(None) self.update_tooltip() def adjust_line(self): self.setPos(QPointF(0.5 * (self.stick.top[0] + self.stick.bottom[0]), 0.5 * (self.stick.top[1] + self.stick.bottom[1]))) vec = 0.5 * (self.stick.top - self.stick.bottom) self.line.setP1(QPointF(vec[0], vec[1])) self.line.setP2(-self.line.p1()) self.gline.setLine(self.line) self.adjust_handles() self.stick_label_text.setPos(self.line.p1() - QPointF(0.5 * self.stick_label_text.boundingRect().width(), 1.3 * self.stick_label_text.boundingRect().height())) self.update() def set_selected(self, selected: bool): self.selected = selected self.update() def is_selected(self) -> bool: return self.selected def set_snow_height(self, height: int): self.measured_height = height self.update() def border_normal(self): self.current_color = self.normal_color self.update() def border_positive(self): self.current_color = self.positive_color self.update() def border_negative(self): self.current_color = self.negative_color self.update() @pyqtProperty(QColor) def highlight_color(self) -> QColor: return self.current_highlight_color @highlight_color.setter def highlight_color(self, color: QColor): self.current_highlight_color = color def highlight(self, color: Optional[QColor], animated: bool = False): self.highlighted = color is not None if not animated or color is None: self.highlight_animation.stop() self.current_highlight_color = self.normal_color if color is None else color self.update() return self.highlight_animation.setStartValue(color) self.highlight_animation.setEndValue(color) self.highlight_animation.setKeyValueAt(0.5, color.darker()) self.highlight_animation.setDuration(2000) self.highlight_animation.setLoopCount(-1) self.highlight_animation.start() def handle_link_button_hovered(self, btn: Dict[str, Any]): self.link_button.setVisible(btn['hovered']) def handle_highlight_animation_value_changed(self, new: QColor): if not self.deleting: self.update(self.boundingRect().marginsAdded(QMarginsF(10, 10, 10, 10))) def contextMenuEvent(self, event: QGraphicsSceneContextMenuEvent) -> None: self.right_clicked.emit({'stick_widget': self}) def set_stick_label(self, label: str): self.stick.label = label self.stick_label_text.setText(label) self.update_tooltip() self.update() def get_stick_label(self) -> str: return self.stick.label def get_stick_length_cm(self) -> int: return self.stick.length_cm def set_stick_length_cm(self, length: int): self.stick.length_cm = length self.update_tooltip() self.update() def update_tooltip(self): if self.mode != StickMode.Display or self.mode == StickMode.Measurement: self.setToolTip("") return snow_txt = "Snow height: " if self.stick.snow_height_px >= 0: snow_txt += str(self.stick.snow_height_cm) + " cm" self.stick_label_text.setText(str(self.stick.snow_height_cm)) else: snow_txt = "not measured" self.stick_label_text.setVisible(False) self.stick_label_text.setText(self.stick.label) self.stick_label_text.setVisible(True) stick_view_text = '' role = '' if self.stick.alternative_view is not None: alt_view = self.stick.alternative_view role = " - primary" alt = "Secondary" if not self.stick.primary: role = " - secondary" alt = "Primary" stick_view_text = f'\n{alt} view: {alt_view.label} in {alt_view.camera_folder.name}\n' mark = '*' if self.stick.determines_quality else '' self.setToolTip(f'{mark}{self.stick.label}{role}{stick_view_text}\nLength: {self.stick.length_cm} cm\n{snow_txt}') def set_stick(self, stick: Stick): self.reset_d_btns() self.stick = stick self.adjust_line() self.adjust_handles() self.set_snow_height(stick.snow_height_px) self.update_tooltip() self.set_show_measurements(self.show_measurements) if self.mode == StickMode.Measurement: self.set_frame_color(QColor(200, 100, 0, 100) if not self.stick.is_visible else None) self.setVisible(True) self.clearly_visible_btn.setVisible(not self.stick.is_visible) else: self.setVisible(self.stick.is_visible) def set_show_measurements(self, show: bool): self.show_measurements = show if self.show_measurements: self.stick_label_text.setText(str(self.stick.snow_height_cm) if self.stick.snow_height_cm >= 0 else "n/a") else: self.stick_label_text.setText(self.stick.label) self.update() def handle_zero(self): self.measured_height = 0 self.stick.set_snow_height_px(0) self.measurement_corrected.emit(self) def reset_d_btns(self): self.zero_btn.set_default_state()