def create_fromDB(self,pos_list=[],obj_type=""): if obj_type =="rois": num_rois = len(pos_list) for i in range(num_rois): points = pos_list[i] rect=QGraphicsRectItem() rect.setPen(QPen(Qt.green)) rect.setRect(points[0],points[1],points[2],points[3]) self.rectgroup.addToGroup(rect) self.scene.addItem(self.rectgroup) elif obj_type =="vector1": num_vec = len(pos_list) for i in range(num_vec): points = pos_list[i] vec=QGraphicsLineItem() vec.setPen(QPen(Qt.green)) vec.setLine(points[0],points[1],points[2],points[3]) self.linegroup.addToGroup(vec) self.scene.addItem(self.linegroup) elif obj_type =="vector2": num_vec = len(pos_list) for i in range(num_vec): points = pos_list[i] vec=QGraphicsLineItem() vec.setPen(QPen(Qt.green)) vec.setLine(points[0],points[1],points[2],points[3]) self.linegroup2.addToGroup(vec) self.scene.addItem(self.linegroup2)
def mouseMoveEvent(self, event): if event.buttons() & Qt.LeftButton: downPos = event.buttonDownPos(Qt.LeftButton) if not self.__tmpLine and self.__dragStartItem and \ (downPos - event.pos()).manhattanLength() > \ QApplication.instance().startDragDistance(): # Start a line drag line = QGraphicsLineItem(self) start = self.__dragStartItem.boundingRect().center() start = self.mapFromItem(self.__dragStartItem, start) line.setLine(start.x(), start.y(), event.pos().x(), event.pos().y()) pen = QPen(Qt.black, 4) pen.setCapStyle(Qt.RoundCap) line.setPen(pen) line.show() self.__tmpLine = line if self.__tmpLine: # Update the temp line line = self.__tmpLine.line() line.setP2(event.pos()) self.__tmpLine.setLine(line) QGraphicsWidget.mouseMoveEvent(self, event)
class VertexFixSign(QGraphicsItem): def __init__(self, vertex): super().__init__(vertex.circle) self.topLeft = QPointF(vertex.x() - 8, vertex.y() - 8) self.topRight = QPointF(vertex.x() + 8, vertex.y() - 8) self.bottomLeft = QPointF(vertex.x() - 8, vertex.y() + 8) self.bottomRight = QPointF(vertex.x() + 8, vertex.y() + 8) self.line1 = QGraphicsLineItem(QLineF(self.topLeft, self.bottomRight), self) self.line2 = QGraphicsLineItem(QLineF(self.topRight, self.bottomLeft), self) self.vertex = vertex self.setVisible(False) def paint(self, painter, option, widget): painter.setPen(Qt.black) painter.drawLine(self.topLeft, self.bottomRight) painter.drawLine(self.topRight, self.bottomLeft) def boundingRect(self): return QRectF(self.topLeft, self.bottomRight) def move(self): self.topLeft = QPointF(self.vertex.x() - 8, self.vertex.y() - 8) self.topRight = QPointF(self.vertex.x() + 8, self.vertex.y() - 8) self.bottomLeft = QPointF(self.vertex.x() - 8, self.vertex.y() + 8) self.bottomRight = QPointF(self.vertex.x() + 8, self.vertex.y() + 8) self.line1.setLine(QLineF(self.topLeft, self.bottomRight)) self.line2.setLine(QLineF(self.topRight, self.bottomLeft))
class RSegment(QObject): def __init__(self, x1, y1, x2, y2, color, line_width): self._x1 = x1 self._y1 = y1 self._x2 = x2 self._y2 = y2 self._pos = QPointF(x1, y1) super().__init__() self.line = QGraphicsLineItem() self.line.setLine(x1, y1, x2, y2) pen = QPen() pen.setWidthF(line_width) pen.setColor(color) self.line.setPen(pen) def x(self): return self._pos.x() def y(self): return self._pos.y() @pyqtProperty(QPointF) def pos(self): return self._pos @pos.setter def pos(self, value): delta_x = value.x() - self._pos.x() delta_y = value.y() - self._pos.y() self._x1 = self._x1 + delta_x self._y1 = self._y1 + delta_y self._x2 = self._x2 + delta_x self._y2 = self._y2 + delta_y self.line.setLine(self._x1, self._y1, self._x2, self._y2) self._pos = value
class WayPoint: def __init__(self, **kwargs): super().__init__() self.location = MapPoint() self.__dict__.update(kwargs) self.pixmap = QGraphicsPixmapItem( QPixmap('HOME_DIR + /nparse/data/maps/waypoint.png')) self.pixmap.setOffset(-10, -20) self.line = QGraphicsLineItem(0.0, 0.0, self.location.x, self.location.y) self.line.setPen(QPen(Qt.green, 1, Qt.DashLine)) self.line.setVisible(False) self.pixmap.setZValue(5) self.line.setZValue(4) self.pixmap.setPos(self.location.x, self.location.y) def update_(self, scale, location=None): self.pixmap.setScale(scale) if location: line = self.line.line() line.setP1(QPointF(location.x, location.y)) self.line.setLine(line) pen = self.line.pen() pen.setWidth(1 / scale) self.line.setPen(pen) self.line.setVisible(True)
def __drawPoint(self, x, y, index): #横线 line1 = QGraphicsLineItem() line1.setPen(self.pen) line1.setLine(x - self.lineRadius, y, x + self.lineRadius, y) #竖线 line2 = QGraphicsLineItem() line2.setPen(self.pen) line2.setLine(x, y - self.lineRadius, x, y + self.lineRadius) #文字说明 text = QGraphicsTextItem() text.setDefaultTextColor(Qt.blue) text.setFont(self.font) text.setPlainText(self.pointsName[index]) text.setPos(x, y) #放到组中 pointGroup = QGraphicsItemGroup() pointGroup.addToGroup(line1) pointGroup.addToGroup(line2) pointGroup.addToGroup(text) #显示 if self.pointsItem[index] is not None: self.scene.removeItem(self.pointsItem[index]) #保存到字典 self.pointsItem[index] = pointGroup #显示该点 self.scene.addItem(self.pointsItem[index])
def updateGraphicsLineItem(line: QGraphicsLineItem, lineF: QLineF = None, pen: QPen = None): """ 更新线 QGraphicsLineItem属性 :param line: QGraphicsLineItem :param lineF: 线的坐标大小 :param pen: 画笔 """ if lineF: line.setLine(lineF) if pen: line.setPen(pen)
class CrosshairGraphicsItem(QObject): def __init__(self, parent=None): super().__init__(parent) self.horizontal_line = QGraphicsLineItem() self.vertical_line = QGraphicsLineItem() self.horizontal_line.setLine(0, 1080 / 2, 1920, 1080 / 2) self.vertical_line.setLine(1920 / 2, 0, 1920 / 2, 1080) self.pen = QPen(Qt.white) self.horizontal_line.setPen(self.pen) self.vertical_line.setPen(self.pen)
class ParticipantItem(QGraphicsItem): def __init__(self, model_item: Participant, parent=None): super().__init__(parent) self.model_item = model_item self.text = QGraphicsTextItem(self) self.line = QGraphicsLineItem(self) self.line.setPen( QPen(Qt.darkGray, 1, Qt.DashLine, Qt.RoundCap, Qt.RoundJoin)) self.refresh() def update_position(self, x_pos=-1, y_pos=-1): if x_pos == -1: x_pos = self.x_pos() if y_pos == -1: y_pos = self.line.line().y2() self.text.setPos(x_pos - (self.text.boundingRect().width() / 2), 0) self.line.setLine(x_pos, 30, x_pos, y_pos) def x_pos(self): return self.line.line().x1() def width(self): return self.boundingRect().width() def refresh(self): self.text.setPlainText( "?" if not self.model_item else self.model_item.shortname) if hasattr(self.model_item, "simulate") and self.model_item.simulate: font = QFont() font.setBold(True) self.text.setFont(font) self.text.setDefaultTextColor(Qt.darkGreen) self.line.setPen( QPen(Qt.darkGreen, 2, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)) else: self.text.setFont(QFont()) self.text.setDefaultTextColor(constants.LINECOLOR) self.line.setPen( QPen(Qt.darkGray, 1, Qt.DashLine, Qt.RoundCap, Qt.RoundJoin)) def boundingRect(self): return self.childrenBoundingRect() def paint(self, painter, option, widget): pass
def generateData(self, canvas, lines, params): scene = QGraphicsScene() scene.setSceneRect(canvas) group = scene.createItemGroup([]) for line in lines: clone = QGraphicsLineItem(line) clone.setLine(line.line()) clone.setPen(line.pen()) scene.addItem(clone) group.addToGroup(clone) pixmaps = [] for i in xrange(params.count): pixmaps.append(self.generateRandom(scene, group, canvas, params)) return pixmaps
def __init__(self, parent, color): super().__init__(None) self.color = QColor(color) self.pen = QPen() self.pen.setColor(self.color) self.lines = [] self.offset_x = 10 for x in range(self.offset_x, 500 + self.offset_x): line = QGraphicsLineItem(self) line.setLine(x, 20 + 200, x + 1, 20 + 200) line.setPen(self.pen) self.lines.append(line)
class RSegment(QObject): def __init__(self, x1, y1, l, a, color, line_width): self._point_radius = 3 self._angle = a self._length = l self._x1 = x1 self._y1 = y1 self._x2 = x1 + self._length * math.cos(self._angle) self._y2 = y1 + self._length * math.sin(self._angle) self._pos = QVector3D(x1, y1, self._angle) super().__init__() self.line = QGraphicsLineItem() self.rect = QRectF(x1 - self._point_radius, y1 - self._point_radius, 2 * self._point_radius, 2 * self._point_radius) self.point = QGraphicsEllipseItem() self.point.setRect(self.rect) self.line.setLine(self._x1, self._y1, self._x2, self._y2) pen = QPen() pen.setWidthF(line_width) pen.setColor(color) self.line.setPen(pen) self.point.setPen(pen) def x(self): return self._x1 def y(self): return self._y1 def angle(self): return self._angle @pyqtProperty(QVector3D) def pos(self): return self._pos @pos.setter def pos(self, value): self._pos = value self._x1 = self._pos.x() self._y1 = self._pos.y() self._angle = value.z() self._x2 = self._x1 + self._length * math.cos(self._angle) self._y2 = self._y1 + self._length * math.sin(self._angle) self.line.setLine(self._x1, self._y1, self._x2, self._y2) self.rect = QRectF(self._x1 - self._point_radius, self._y1 - self._point_radius, 2 * self._point_radius, 2 * self._point_radius) self.point.setRect(self.rect)
def connect_relation(self, x, y, type): ver_line = QGraphicsLineItem(self.en_shape) horz_line = QGraphicsLineItem(self.en_shape) ver_line.setLine( QLineF(self.rel_orgn.x(), self.rel_orgn.y(), self.rel_orgn.x(), y - self.en_shape.y())) horz_line.setLine( QLineF(ver_line.line().x2(), ver_line.line().y2(), x - self.en_shape.x(), y - self.en_shape.y())) self.rel_orgn.setX(self.rel_orgn.x() + self.orgn_step) if (type == 'p'): ver_line.setPen(QPen(Qt.black, 3, Qt.DashLine)) horz_line.setPen(QPen(Qt.black, 3, Qt.DashLine))
class ParticipantItem(QGraphicsItem): def __init__(self, model_item: Participant, parent=None): super().__init__(parent) self.model_item = model_item self.text = QGraphicsTextItem(self) self.line = QGraphicsLineItem(self) self.line.setPen(QPen(Qt.darkGray, 1, Qt.DashLine, Qt.RoundCap, Qt.RoundJoin)) self.refresh() def update_position(self, x_pos=-1, y_pos=-1): if x_pos == -1: x_pos = self.x_pos() if y_pos == -1: y_pos = self.line.line().y2() self.text.setPos(x_pos - (self.text.boundingRect().width() / 2), 0) self.line.setLine(x_pos, 30, x_pos, y_pos) def x_pos(self): return self.line.line().x1() def width(self): return self.boundingRect().width() def refresh(self): self.text.setPlainText("?" if not self.model_item else self.model_item.shortname) if hasattr(self.model_item, "simulate") and self.model_item.simulate: font = QFont() font.setBold(True) self.text.setFont(font) self.text.setDefaultTextColor(Qt.darkGreen) self.line.setPen(QPen(Qt.darkGreen, 2, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)) else: self.text.setFont(QFont()) self.text.setDefaultTextColor(constants.LINECOLOR) self.line.setPen(QPen(Qt.darkGray, 1, Qt.DashLine, Qt.RoundCap, Qt.RoundJoin)) def boundingRect(self): return self.childrenBoundingRect() def paint(self, painter, option, widget): pass
class CTChartView(QChartView): def __init__(self, parent): super().__init__(parent) self.setRenderHint(QPainter.Antialiasing) self.chart = self.chart() self.chart.legend().setVisible(False) self._chart_loaded = False self._chart_horizontal_line = QGraphicsLineItem(0, 0, 0, 0) pen = self._chart_horizontal_line.pen() pen.setStyle(Qt.DashLine) self._chart_horizontal_line.setPen(pen) self.scene().addItem(self._chart_horizontal_line) self._chart_vertical_line = QGraphicsLineItem(0, 0, 0, 0) self._chart_vertical_line.setPen(pen) self.scene().addItem(self._chart_vertical_line) self._chart_tooltip = QGraphicsTextItem("") self._chart_tooltip.setPos(100, 20) self.scene().addItem(self._chart_tooltip) self._chart_crosshair = QGraphicsTextItem("") self._chart_crosshair.setPos(600, 20) self.scene().addItem(self._chart_crosshair) margins = self.chart.margins() margins.setTop(margins.top() + 80) self.chart.setMargins(margins) def mouseMoveEvent(self, event): self._chart_horizontal_line.setLine(0, event.pos().y(), self.width(), event.pos().y()) self._chart_vertical_line.setLine(event.pos().x(), 0, event.pos().x(), self.height()) crosshair_coordinates = self.chart.mapToValue(event.pos(), self.chart.series()[0]) self._chart_crosshair.setPlainText( " time:\t{0}\n level:\t{1:.8f}".format( datetime.fromtimestamp( int(crosshair_coordinates.x() / 1000)).strftime('%Y-%m-%d %H:%M:%S'), crosshair_coordinates.y())) return QChartView.mouseMoveEvent(self, event)
def mouseMoveEvent(self, e): self.oldPoint = self.newPoint self.newPoint = e.pos() if self.firstMove: #zamaz kropke self.del_point(self.oldPoint.x(), self.oldPoint.y()) self.firstMove = False #print(self.oldPoint.x(), self.oldPoint.y(), self.newPoint.x(), self.newPoint.y()) line = QGraphicsLineItem() line.setLine( QLineF(self.oldPoint.x(), self.oldPoint.y(), self.newPoint.x(), self.newPoint.y())) line.setPen( QPen(self.colors[self.color], self.size, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)) self.scene.addItem(line)
def approve_obj(self): self.scene.removeItem(self.rect) self.scene.removeItem(self.line) viewBBox = self.zoomStack[-1] if len( self.zoomStack) else self.sceneRect() selectionBBox = self.scene.selectionArea().boundingRect().intersected( viewBBox) rect = QGraphicsRectItem() rect.setRect(selectionBBox) rect.setPen(QPen(Qt.green)) self.rectgroup.addToGroup(rect) self.scene.addItem(self.rectgroup) line = QGraphicsLineItem() line.setLine(QLineF(self.start, self.current)) line.setPen(QPen(Qt.green)) self.linegroup.addToGroup(line) self.scene.addItem(self.linegroup) return (selectionBBox, self.start, self.current)
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 addLink(self, output, input): """ Add a link between `output` (:class:`OutputSignal`) and `input` (:class:`InputSignal`). """ if not compatible_channels(output, input): return if output not in self.source.output_channels(): raise ValueError("%r is not an output channel of %r" % \ (output, self.source)) if input not in self.sink.input_channels(): raise ValueError("%r is not an input channel of %r" % \ (input, self.sink)) if input.single: # Remove existing link if it exists. for s1, s2, _ in self.__links: if s2 == input: self.removeLink(s1, s2) line = QGraphicsLineItem(self) source_anchor = self.sourceNodeWidget.anchor(output) sink_anchor = self.sinkNodeWidget.anchor(input) source_pos = source_anchor.boundingRect().center() source_pos = self.mapFromItem(source_anchor, source_pos) sink_pos = sink_anchor.boundingRect().center() sink_pos = self.mapFromItem(sink_anchor, sink_pos) line.setLine(source_pos.x(), source_pos.y(), sink_pos.x(), sink_pos.y()) pen = QPen(Qt.black, 4) pen.setCapStyle(Qt.RoundCap) line.setPen(pen) self.__links.append(_Link(output, input, line))
class ParticipantItem(QGraphicsItem): def __init__(self, model_item: Participant, parent=None): super().__init__(parent) self.model_item = model_item self.text = QGraphicsTextItem(self) self.line = QGraphicsLineItem(self) self.line.setPen(QPen(Qt.darkGray, 1, Qt.DashLine, Qt.RoundCap, Qt.RoundJoin)) self.refresh() def update_position(self, x_pos=-1, y_pos=-1): if x_pos == -1: x_pos = self.x_pos() if y_pos == -1: y_pos = self.line.line().y2() self.text.setPos(x_pos - (self.text.boundingRect().width() / 2), 0) self.line.setLine(x_pos, 30, x_pos, y_pos) def x_pos(self): return self.line.line().x1() def width(self): return self.boundingRect().width() def refresh(self): self.text.setPlainText("?" if not self.model_item else self.model_item.shortname) def boundingRect(self): return self.childrenBoundingRect() def paint(self, painter, option, widget): pass
def addCircle(self, index, points, r): (x, y) = points circleItem = QGraphicsEllipseItem(x - r, y - r, 2 * r, 2 * r) circleItem.setPen(self.pen) # self.scene.addItem(circleItem) #横线 line1 = QGraphicsLineItem() line1.setPen(self.pen) line1.setLine(x - self.lineRadius, y, x + self.lineRadius, y) #竖线 line2 = QGraphicsLineItem() line2.setPen(self.pen) line2.setLine(x, y - self.lineRadius, x, y + self.lineRadius) #文字说明 text = QGraphicsTextItem() text.setDefaultTextColor(Qt.blue) text.setFont(self.font) text.setPlainText(str(self.names[index])) text.setPos(x, y) #放到组中 pointGroup = QGraphicsItemGroup() pointGroup.addToGroup(line1) pointGroup.addToGroup(line2) pointGroup.addToGroup(text) pointGroup.addToGroup(circleItem) #显示该点 self.scene.addItem(pointGroup) self.pointsItem.append(pointGroup)
class DoublePipeSegmentItem(SegmentItemBase): def __init__(self, startNode, endNode, parent: "ConnectionBase"): super().__init__(startNode, endNode, parent) self.blueLine = QGraphicsLineItem(self) self.redLine = QGraphicsLineItem(self) def _createSegment(self, startNode, endNode) -> "SegmentItemBase": return DoublePipeSegmentItem(startNode, endNode, self.connection) def _setLineImpl(self, x1, y1, x2, y2): self.blueLine.setPen(QtGui.QPen(QtCore.Qt.blue, 3)) self.redLine.setPen(QtGui.QPen(QtCore.Qt.red, 3)) offset = 3 if abs(y1 - y2) < 1: self.blueLine.setLine(x1, y1 + offset, x2, y2 + offset) self.redLine.setLine(x1, y1 - offset, x2, y2 - offset) elif abs(x1 - x2) < 1: self.blueLine.setLine(x1 + offset, y1, x2 + offset, y2) self.redLine.setLine(x1 - offset, y1, x2 - offset, y2) else: # Initially, connections go directly between its two ports: only # afterwards are they broken up into horizontal and vertical segments. # This is probably an artifact of back when connections didn't have to # be straight. Once, these artifacts have been removed, we should throw here. pass self.linePoints = QLineF(x1, y1, x2, y2) def updateGrad(self): self.blueLine.setPen(QtGui.QPen(QtCore.Qt.blue, 3)) self.redLine.setPen(QtGui.QPen(QtCore.Qt.red, 3)) def setSelect(self, isSelected: bool) -> None: if isSelected: selectPen = self._createSelectPen() self.blueLine.setPen(selectPen) self.redLine.setPen(selectPen) else: self.updateGrad() def setColorAndWidthAccordingToMassflow(self, color, width): pass
class ChartView(QChartView): def __init__(self, *args, **kwargs): super(ChartView, self).__init__(*args, **kwargs) self.resize(800, 600) self.setRenderHint(QPainter.Antialiasing) # 抗锯齿 self.initChart() self.toolTipWidget = GraphicsProxyWidget(self._chart) # line self.lineItem = QGraphicsLineItem(self._chart) self.lineItem.setZValue(998) self.lineItem.hide() # 一些固定计算,减少mouseMoveEvent中的计算量 # 获取x和y轴的最小最大值 axisX, axisY = self._chart.axisX(), self._chart.axisY() self.min_x, self.max_x = axisX.min(), axisX.max() self.min_y, self.max_y = axisY.min(), axisY.max() # 坐标系中左上角顶点 self.point_top = self._chart.mapToPosition(QPointF(self.min_x, self.max_y)) # 坐标原点坐标 self.point_bottom = self._chart.mapToPosition(QPointF(self.min_x, self.min_y)) self.step_x = (self.max_x - self.min_x) / (axisX.tickCount() - 1) # self.step_y = (self.max_y - self.min_y) / (axisY.tickCount() - 1) def mouseMoveEvent(self, event): super(ChartView, self).mouseMoveEvent(event) # 把鼠标位置所在点转换为对应的xy值 x = self._chart.mapToValue(event.pos()).x() y = self._chart.mapToValue(event.pos()).y() index = round((x - self.min_x) / self.step_x) pos_x = self._chart.mapToPosition( QPointF(index * self.step_x + self.min_x, self.min_y)) # print(x, pos_x, index, index * self.step_x + self.min_x) # 得到在坐标系中的所有series的类型和点 points = [(serie, serie.at(index)) for serie in self._chart.series() if self.min_x <= x <= self.max_x and self.min_y <= y <= self.max_y] if points: # 永远在轴上的黑线条 self.lineItem.setLine(pos_x.x(), self.point_top.y(), pos_x.x(), self.point_bottom.y()) self.lineItem.show() self.toolTipWidget.show("", points, event.pos() + QPoint(20, 20)) else: self.toolTipWidget.hide() self.lineItem.hide() def onSeriesHoverd(self, point, state): if state: try: name = self.sender().name() except: name = "" QToolTip.showText(QCursor.pos(), "%s\nx: %s\ny: %s" % (name, point.x(), point.y())) def initChart(self): self._chart = QChart(title="Line Chart") self._chart.setAcceptHoverEvents(True) dataTable = [ [120, 132, 101, 134, 90, 230, 210], [220, 182, 191, 234, 290, 330, 310], [150, 232, 201, 154, 190, 330, 410], [320, 332, 301, 334, 390, 330, 320], [820, 932, 901, 934, 1290, 1330, 1320] ] for i, data_list in enumerate(dataTable): series = QLineSeries(self._chart) for j, v in enumerate(data_list): series.append(j, v) series.setName("Series " + str(i)) series.setPointsVisible(True) # 显示原点 series.hovered.connect(self.onSeriesHoverd) self._chart.addSeries(series) self._chart.createDefaultAxes() # 创建默认的轴 self._chart.axisX().setTickCount(7) # x轴设置7个刻度 self._chart.axisY().setTickCount(7) # y轴设置7个刻度 self._chart.axisY().setRange(0, 1500) # 设置y轴范围 self.setChart(self._chart)
class ChartView(QChartView): def __init__(self, *args, **kwargs): super(ChartView, self).__init__(*args, **kwargs) self.resize(800, 600) self.setRenderHint(QPainter.Antialiasing) # 抗锯齿 # 自定义x轴label self.category = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"] self.initChart() # 提示widget self.toolTipWidget = GraphicsProxyWidget(self._chart) # line self.lineItem = QGraphicsLineItem(self._chart) pen = QPen(Qt.gray) pen.setWidth(1) self.lineItem.setPen(pen) self.lineItem.setZValue(996) self.lineItem.hide() self.lineItemX = QGraphicsLineItem(self._chart) penx = QPen(Qt.gray) penx.setWidth(1) self.lineItemX.setPen(penx) self.lineItemX.setZValue(997) self.lineItemX.hide() # 一些固定计算,减少mouseMoveEvent中的计算量 # 获取x和y轴的最小最大值 axisX, axisY = self._chart.axisX(), self._chart.axisY() self.min_x, self.max_x = axisX.min(), axisX.max() self.min_y, self.max_y = axisY.min(), axisY.max() def resizeEvent(self, event): super(ChartView, self).resizeEvent(event) # 当窗口大小改变时需要重新计算 # 坐标系中左上角顶点 self.point_top = self._chart.mapToPosition( QPointF(self.min_x, self.max_y)) # 坐标右上点 self.point_right_top = self._chart.mapToPosition( QPointF(self.max_x, self.max_y)) # 坐标右下点 self.point_right_bottom = self._chart.mapToPosition( QPointF(self.max_x, self.min_y)) # 坐标原点坐标 self.point_bottom = self._chart.mapToPosition( QPointF(self.min_x, self.min_y)) self.step_x = (self.max_x - self.min_x) / \ (self._chart.axisX().tickCount() - 1) def mouseMoveEvent(self, event): super(ChartView, self).mouseMoveEvent(event) pos = event.pos() # 把鼠标位置所在点转换为对应的xy值 x = self._chart.mapToValue(pos).x() y = self._chart.mapToValue(pos).y() # 根据间隔来确定鼠标当前所在的索引 index = round((x - self.min_x) / self.step_x) # 得到在坐标系中的所有正常显示的series的类型和点 points = [ (serie, serie.at(index)) for serie in self._chart.series() if self.min_x <= x <= self.max_x and self.min_y <= y <= self.max_y ] if points: # 根据鼠标的点获取对应曲线的坐标点, # pos_x = self._chart.mapToPosition( # QPointF(index * self.step_x + self.min_x, self.min_y)) # 设置鼠标的垂直线 self.lineItem.setLine(pos.x(), self.point_top.y(), pos.x(), self.point_bottom.y()) self.lineItem.show() # 设置鼠标的水平线 self.lineItemX.setLine(self.point_top.x(), pos.y(), self.point_right_top.x(), pos.y()) self.lineItemX.show() try: title = self.category[index] except: title = "" t_width = self.toolTipWidget.width() t_height = self.toolTipWidget.height() # 如果鼠标位置离右侧的距离小于tip宽度 x = pos.x() - t_width if self.width() - \ pos.x() - 20 < t_width else pos.x() # 如果鼠标位置离底部的高度小于tip高度 y = pos.y() - t_height if self.height() - \ pos.y() - 20 < t_height else pos.y() self.toolTipWidget.show(title, points, QPoint(x, y)) else: self.toolTipWidget.hide() self.lineItem.hide() self.lineItemX.hide() def handleMarkerClicked(self): marker = self.sender() # 信号发送者 if not marker: return visible = not marker.series().isVisible() # 隐藏或显示series marker.series().setVisible(visible) marker.setVisible(True) # 要保证marker一直显示 # 透明度 alpha = 1.0 if visible else 0.4 # 设置label的透明度 brush = marker.labelBrush() color = brush.color() color.setAlphaF(alpha) brush.setColor(color) marker.setLabelBrush(brush) # 设置marker的透明度 brush = marker.brush() color = brush.color() color.setAlphaF(alpha) brush.setColor(color) marker.setBrush(brush) # 设置画笔透明度 pen = marker.pen() color = pen.color() color.setAlphaF(alpha) pen.setColor(color) marker.setPen(pen) def handleMarkerHovered(self, status): # 设置series的画笔宽度 marker = self.sender() # 信号发送者 if not marker: return series = marker.series() if not series: return pen = series.pen() if not pen: return pen.setWidth(pen.width() + (1 if status else -1)) series.setPen(pen) def handleSeriesHoverd(self, point, state): # 设置series的画笔宽度 series = self.sender() # 信号发送者 pen = series.pen() if not pen: return pen.setWidth(pen.width() + (1 if state else -1)) series.setPen(pen) def initChart(self): self._chart = QChart(title="折线图堆叠") self._chart.setAcceptHoverEvents(True) # Series动画 self._chart.setAnimationOptions(QChart.SeriesAnimations) dataTable = [["邮件营销", [120, 132, 101, 134, 90, 230, 210]], ["联盟广告", [220, 182, 191, 234, 290, 330, 310]], ["视频广告", [150, 232, 201, 154, 190, 330, 410]], ["直接访问", [320, 332, 301, 334, 390, 330, 320]], ["搜索引擎", [820, 932, 901, 934, 1290, 1330, 1320]]] for series_name, data_list in dataTable: series = QLineSeries(self._chart) for j, v in enumerate(data_list): series.append(j, v) series.setName(series_name) series.setPointsVisible(True) # 显示圆点 series.hovered.connect(self.handleSeriesHoverd) # 鼠标悬停 self._chart.addSeries(series) self._chart.createDefaultAxes() # 创建默认的轴 axisX = self._chart.axisX() # x轴 axisX.setTickCount(7) # x轴设置7个刻度 axisX.setGridLineVisible(False) # 隐藏从x轴往上的线条 axisY = self._chart.axisY() axisY.setTickCount(7) # y轴设置7个刻度 axisY.setRange(0, 1500) # 设置y轴范围 # 自定义x轴 axis_x = QCategoryAxis( self._chart, labelsPosition=QCategoryAxis.AxisLabelsPositionOnValue) axis_x.setTickCount(7) axis_x.setGridLineVisible(False) min_x = axisX.min() max_x = axisX.max() step = (max_x - min_x) / (7 - 1) # 7个tick for i in range(0, 7): axis_x.append(self.category[i], min_x + i * step) self._chart.setAxisX(axis_x, self._chart.series()[-1]) # self._chart.addAxis(axis_x, Qt.AlignBottom) # chart的图例 legend = self._chart.legend() # 设置图例由Series来决定样式 legend.setMarkerShape(QLegend.MarkerShapeFromSeries) # 遍历图例上的标记并绑定信号 for marker in legend.markers(): # 点击事件 marker.clicked.connect(self.handleMarkerClicked) # 鼠标悬停事件 marker.hovered.connect(self.handleMarkerHovered) self.setChart(self._chart)
class DiagramScene(QGraphicsScene): insert_item, insert_line, move_item = range(3) item_inserted = pyqtSignal(DiagramItem) def __init__(self, context_menu: QMenu, parent: QGraphicsItem = None): super(DiagramScene, self).__init__(parent) self.context_menu = context_menu self.framework_name = frameworks_utils.get_sorted_frameworks_list()[0] self.mode = self.move_item self.item_type = None self.line = None # Could be used later to support items & lines coloring. self.item_color = Qt.white self.line_color = Qt.black def set_framework_name(self, framework_name: str): self.framework_name = framework_name def set_mode(self, mode: int): self.mode = mode def set_item_type(self, item_type: int): self.item_type = item_type def mousePressEvent(self, event: QGraphicsSceneMouseEvent): if event.button() != Qt.LeftButton: return if self.mode == self.insert_item: layers = frameworks_utils.get_framework_layers(self.framework_name) item = DiagramItem(layers[self.item_type](), self.context_menu) item.setBrush(self.item_color) item.setPos(event.scenePos()) self.addItem(item) self.item_inserted.emit(item) elif self.mode == self.insert_line: self.line = QGraphicsLineItem( QLineF(event.scenePos(), event.scenePos())) self.line.setPen(QPen(self.line_color, 2)) self.addItem(self.line) super(DiagramScene, self).mousePressEvent(event) def mouseMoveEvent(self, event: QGraphicsSceneMouseEvent): if self.mode == self.insert_line and self.line: new_line = QLineF(self.line.line().p1(), event.scenePos()) self.line.setLine(new_line) elif self.mode == self.move_item: super(DiagramScene, self).mouseMoveEvent(event) def mouseReleaseEvent(self, event: QGraphicsSceneMouseEvent): if self.mode == self.insert_line and self.line: start_items = self.items(self.line.line().p1()) if len(start_items) and start_items[0] == self.line: start_items.pop(0) end_items = self.items(self.line.line().p2()) if len(end_items) and end_items[0] == self.line: end_items.pop(0) self.removeItem(self.line) self.line = None if len(start_items) and isinstance(start_items[0], QGraphicsSimpleTextItem): start_items[0] = start_items[0].parentItem() if len(end_items) and isinstance(end_items[0], QGraphicsSimpleTextItem): end_items[0] = end_items[0].parentItem() if (len(start_items) and len(end_items) and isinstance(start_items[0], DiagramItem) and isinstance(end_items[0], DiagramItem) and start_items[0] != end_items[0]): start_item = start_items[0] end_item = end_items[0] arrow = Arrow(start_item, end_item) start_item.add_arrow(arrow) end_item.add_arrow(arrow) arrow.setZValue(-1000.0) self.addItem(arrow) arrow.updatePosition() self.line = None super(DiagramScene, self).mouseReleaseEvent(event) def isItemChange(self, type_: DiagramItem) -> bool: for item in self.selectedItems(): if isinstance(item, type_): return True return False
class ImageViewer(QGraphicsView, QObject): points_selection_sgn = pyqtSignal(list) key_press_sgn = pyqtSignal(QtGui.QKeyEvent) def __init__(self, parent=None): super(ImageViewer, self).__init__(parent) self.setDragMode(QGraphicsView.ScrollHandDrag) self.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform) self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse) self.setResizeAnchor(QGraphicsView.AnchorUnderMouse) self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded) self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded) self._scene = ImageViewerScene(self) self.setScene(self._scene) self._image = None self._image_original = None self._pixmap = None self._img_contrast = 1.0 self._img_brightness = 50.0 self._img_gamma = 1.0 self._create_grid() self._channels = [] self._current_tool = SELECTION_TOOL.POINTER self._dataset = None # create grid lines pen_color = QColor(255, 255, 255, 255) pen = QPen(pen_color) pen.setWidth(2) pen.setStyle(QtCore.Qt.DotLine) self.vline = QGraphicsLineItem() self.vline.setVisible(False) self.vline.setPen(pen) self.hline = QGraphicsLineItem() self.hline.setVisible(False) self.hline.setPen(pen) self._scene.addItem(self.vline) self._scene.addItem(self.hline) self._current_label = None # rectangle selection tool self._rectangle_tool_origin = QPoint() self._rectangle_tool_picker = QRubberBand(QRubberBand.Rectangle, self) # polygon selection tool app = QApplication.instance() color = app.palette().color(QPalette.Highlight) self._polygon_guide_line_pen = QPen(color) self._polygon_guide_line_pen.setWidth(3) self._polygon_guide_line_pen.setStyle(QtCore.Qt.DotLine) self._polygon_guide_line = QGraphicsLineItem() self._polygon_guide_line.setVisible(False) self._polygon_guide_line.setPen(self._polygon_guide_line_pen) self._scene.addItem(self._polygon_guide_line) self._current_polygon = None # circle self._current_ellipse = None # free selection tool self._current_free_path = None self._is_drawing = False self._last_point_drawn = QPoint() self._last_click_point = None self._free_Path_pen = QPen(color) self._free_Path_pen.setWidth(10) self._extreme_points = Queue(maxsize=4) @property def current_label(self): return self._current_label @current_label.setter def current_label(self, value): self._current_label = value if self._current_label: color = QColor(self._current_label.color) self._free_Path_pen.setColor(color) self._polygon_guide_line_pen.setColor(color) self._polygon_guide_line.setPen(self._polygon_guide_line_pen) @property def dataset(self): return self._dataset @dataset.setter def dataset(self, value): self._dataset = value @property def img_contrast(self): return self._img_contrast @img_contrast.setter def img_contrast(self, value): self._img_contrast = value @property def img_gamma(self): return self._img_gamma @img_gamma.setter def img_gamma(self, value): self._img_gamma = value @property def img_brightness(self): return self._img_brightness @img_brightness.setter def img_brightness(self, value): self._img_brightness = value @property def image(self): return self._image @image.setter def image(self, value): self._image = value self._image_original = value.copy() self.update_viewer() @property def pixmap(self) -> ImagePixmap: return self._pixmap @gui_exception def update_viewer(self, fit_image=True): rgb = cv2.cvtColor(self._image, cv2.COLOR_BGR2RGB) rgb = ImageUtilities.adjust_image(rgb, self._img_contrast, self._img_brightness) rgb = ImageUtilities.adjust_gamma(rgb, self._img_gamma) pil_image = Image.fromarray(rgb) qppixmap_image = pil_image.toqpixmap() x, y = -qppixmap_image.width() / 2, -qppixmap_image.height() / 2 if self._pixmap: self._pixmap.resetTransform() self._pixmap.setPixmap(qppixmap_image) self._pixmap.setOffset(x, y) else: self._pixmap = ImagePixmap() self._pixmap.setPixmap(qppixmap_image) self._pixmap.setOffset(x, y) self._scene.addItem(self._pixmap) self._pixmap.signals.hoverEnterEventSgn.connect( self.pixmap_hoverEnterEvent_slot) self._pixmap.signals.hoverLeaveEventSgn.connect( self.pixmap_hoverLeaveEvent_slot) self._pixmap.signals.hoverMoveEventSgn.connect( self.pixmap_hoverMoveEvent_slot) self._hide_guide_lines() if fit_image: self.fit_to_window() @gui_exception def reset_viewer(self): self._img_contrast = 1.0 self._img_brightness = 50.0 self._img_gamma = 1.0 self._image = self._image_original.copy() @gui_exception def equalize_histogram(self): self._image = ImageUtilities.histogram_equalization(self._image) @gui_exception def correct_lightness(self): self._image = ImageUtilities.correct_lightness(self._image) def clusterize(self, k): self._image = ImageUtilities.kmeans(self._image.copy(), k) @property def current_tool(self): return self._current_tool @current_tool.setter def current_tool(self, value): self._polygon_guide_line.hide() self._current_polygon = None self._current_free_path = None self._current_ellipse = None self._is_drawing = value == SELECTION_TOOL.FREE self._current_tool = value self.clear_extreme_points() if value == SELECTION_TOOL.POINTER: self.enable_items(True) else: self.enable_items(False) def fit_to_window(self): if not self._pixmap or not self._pixmap.pixmap(): return self.resetTransform() self.setTransform(QtGui.QTransform()) self.fitInView(self._pixmap, QtCore.Qt.KeepAspectRatio) def _create_grid(self, gridSize=15): app: QApplication = QApplication.instance() curr_theme = "dark" if app: curr_theme = app.property("theme") if curr_theme == "light": color1 = QtGui.QColor("white") color2 = QtGui.QColor(237, 237, 237) else: color1 = QtGui.QColor(20, 20, 20) color2 = QtGui.QColor(0, 0, 0) backgroundPixmap = QtGui.QPixmap(gridSize * 2, gridSize * 2) backgroundPixmap.fill(color1) painter = QtGui.QPainter(backgroundPixmap) painter.fillRect(0, 0, gridSize, gridSize, color2) painter.fillRect(gridSize, gridSize, gridSize, gridSize, color2) painter.end() self._scene.setBackgroundBrush(QtGui.QBrush(backgroundPixmap)) def wheelEvent(self, event: QWheelEvent): adj = (event.angleDelta().y() / 120) * 0.1 self.scale(1 + adj, 1 + adj) @gui_exception def keyPressEvent(self, event: QKeyEvent): if event.key() == QtCore.Qt.Key_Space: image_rect: QRectF = self._pixmap.sceneBoundingRect() if self.current_tool == SELECTION_TOOL.POLYGON and self._current_polygon: points = self._current_polygon.points self._polygon_guide_line.hide() self.setDragMode(QGraphicsView.ScrollHandDrag) if len(points) <= 2: self._current_polygon.delete_item() self.current_tool = SELECTION_TOOL.POINTER elif self.current_tool == SELECTION_TOOL.EXTREME_POINTS and \ self._extreme_points.full(): points = [] image_offset = QPointF(image_rect.width() / 2, image_rect.height() / 2) for pt in self._extreme_points.queue: pt: EditablePolygonPoint center = pt.sceneBoundingRect().center() x = math.floor(center.x() + image_offset.x()) y = math.floor(center.y() + image_offset.y()) points.append([x, y]) self.points_selection_sgn.emit(points) self.current_tool = SELECTION_TOOL.POINTER else: event.ignore() # guide lines events def _show_guide_lines(self): if self.hline and self.vline: self.hline.show() self.vline.show() def _hide_guide_lines(self): if self.hline and self.vline: self.hline.hide() self.vline.hide() def _update_guide_lines(self, x, y): bbox: QRect = self._pixmap.boundingRect() offset = QPointF(bbox.width() / 2, bbox.height() / 2) self.vline.setLine(x, -offset.y(), x, bbox.height() - offset.y()) self.vline.setZValue(1) self.hline.setLine(-offset.x(), y, bbox.width() - offset.x(), y) self.hline.setZValue(1) def pixmap_hoverMoveEvent_slot(self, evt: QGraphicsSceneHoverEvent, x, y): self._update_guide_lines(x, y) def pixmap_hoverEnterEvent_slot(self): self._show_guide_lines() def pixmap_hoverLeaveEvent_slot(self): self._hide_guide_lines() def delete_polygon_slot(self, polygon: EditablePolygon): self._current_polygon = None self.current_tool = SELECTION_TOOL.POINTER self._polygon_guide_line.hide() @gui_exception def mousePressEvent(self, evt: QtGui.QMouseEvent) -> None: image_rect: QRectF = self._pixmap.boundingRect() mouse_pos = self.mapToScene(evt.pos()) if evt.buttons() == QtCore.Qt.LeftButton: if self.current_tool == SELECTION_TOOL.BOX: # create rectangle self.setDragMode(QGraphicsView.NoDrag) self._rectangle_tool_origin = evt.pos() geometry = QRect(self._rectangle_tool_origin, QSize()) self._rectangle_tool_picker.setGeometry(geometry) self._rectangle_tool_picker.show() elif self.current_tool == SELECTION_TOOL.POLYGON: if image_rect.contains(mouse_pos): if self._current_polygon is None: self._current_polygon = EditablePolygon() self._current_polygon.label = self._current_label self._current_polygon.tag = self._dataset self._current_polygon.signals.deleted.connect( self.delete_polygon_slot) self._scene.addItem(self._current_polygon) self._current_polygon.addPoint(mouse_pos) else: self._current_polygon.addPoint(mouse_pos) elif self.current_tool == SELECTION_TOOL.ELLIPSE: if image_rect.contains(mouse_pos): self.setDragMode(QGraphicsView.NoDrag) ellipse_rec = QtCore.QRectF(mouse_pos.x(), mouse_pos.y(), 0, 0) self._current_ellipse = EditableEllipse() self._current_ellipse.tag = self.dataset self._current_ellipse.label = self._current_label self._current_ellipse.setRect(ellipse_rec) self._scene.addItem(self._current_ellipse) elif self.current_tool == SELECTION_TOOL.FREE: # consider only the points into the image if image_rect.contains(mouse_pos): self.setDragMode(QGraphicsView.NoDrag) self._last_point_drawn = mouse_pos self._current_free_path = QGraphicsPathItem() self._current_free_path.setOpacity(0.6) self._current_free_path.setPen(self._free_Path_pen) painter = QPainterPath() painter.moveTo(self._last_point_drawn) self._current_free_path.setPath(painter) self._scene.addItem(self._current_free_path) elif self.current_tool == SELECTION_TOOL.EXTREME_POINTS: if image_rect.contains(mouse_pos): if not self._extreme_points.full(): def delete_point(idx): del self._extreme_points.queue[idx] idx = self._extreme_points.qsize() editable_pt = EditablePolygonPoint(idx) editable_pt.signals.deleted.connect(delete_point) editable_pt.setPos(mouse_pos) self._scene.addItem(editable_pt) self._extreme_points.put(editable_pt) else: self.setDragMode(QGraphicsView.ScrollHandDrag) super(ImageViewer, self).mousePressEvent(evt) @gui_exception def mouseMoveEvent(self, evt: QtGui.QMouseEvent) -> None: mouse_pos = self.mapToScene(evt.pos()) image_rect: QRectF = self._pixmap.boundingRect() if self.current_tool == SELECTION_TOOL.BOX: if not self._rectangle_tool_origin.isNull(): geometry = QRect(self._rectangle_tool_origin, evt.pos()).normalized() self._rectangle_tool_picker.setGeometry(geometry) elif self.current_tool == SELECTION_TOOL.POLYGON: if self._current_polygon and image_rect.contains(mouse_pos): if self._current_polygon.count > 0: last_point: QPointF = self._current_polygon.last_point self._polygon_guide_line.setZValue(1) self._polygon_guide_line.show() mouse_pos = self.mapToScene(evt.pos()) self._polygon_guide_line.setLine(last_point.x(), last_point.y(), mouse_pos.x(), mouse_pos.y()) else: self._polygon_guide_line.hide() elif self.current_tool == SELECTION_TOOL.ELLIPSE: if self._current_ellipse and image_rect.contains(mouse_pos): ellipse_rect = self._current_ellipse.rect() ellipse_pos = QPointF(ellipse_rect.x(), ellipse_rect.y()) distance = math.hypot(mouse_pos.x() - ellipse_pos.x(), mouse_pos.y() - ellipse_pos.y()) ellipse_rect.setWidth(distance) ellipse_rect.setHeight(distance) self._current_ellipse.setRect(ellipse_rect) elif self.current_tool == SELECTION_TOOL.FREE and evt.buttons( ) and QtCore.Qt.LeftButton: if self._current_free_path and image_rect.contains(mouse_pos): painter: QPainterPath = self._current_free_path.path() self._last_point_drawn = self.mapToScene(evt.pos()) painter.lineTo(self._last_point_drawn) self._current_free_path.setPath(painter) super(ImageViewer, self).mouseMoveEvent(evt) @gui_exception def mouseReleaseEvent(self, evt: QtGui.QMouseEvent) -> None: image_rect: QRectF = self._pixmap.boundingRect() if self.current_tool == SELECTION_TOOL.BOX: roi: QRect = self._rectangle_tool_picker.geometry() roi: QRectF = self.mapToScene(roi).boundingRect() self._rectangle_tool_picker.hide() if image_rect == roi.united(image_rect): rect = EditableBox(roi) rect.label = self.current_label rect.tag = self._dataset self._scene.addItem(rect) self.current_tool = SELECTION_TOOL.POINTER self.setDragMode(QGraphicsView.ScrollHandDrag) elif self.current_tool == SELECTION_TOOL.ELLIPSE and self._current_ellipse: roi: QRect = self._current_ellipse.boundingRect() if image_rect == roi.united(image_rect): self.current_tool = SELECTION_TOOL.POINTER self.setDragMode(QGraphicsView.ScrollHandDrag) else: self._current_ellipse.delete_item() elif self.current_tool == SELECTION_TOOL.FREE and self._current_free_path: # create polygon self._current_free_path: QGraphicsPathItem path_rect = self._current_free_path.boundingRect() if image_rect == path_rect.united(image_rect): path = self._current_free_path.path() path_polygon = EditablePolygon() path_polygon.tag = self.dataset path_polygon.label = self.current_label self._scene.addItem(path_polygon) for i in range(0, path.elementCount(), 10): x, y = path.elementAt(i).x, path.elementAt(i).y path_polygon.addPoint(QPointF(x, y)) self._scene.removeItem(self._current_free_path) self.current_tool = SELECTION_TOOL.POINTER self.setDragMode(QGraphicsView.ScrollHandDrag) super(ImageViewer, self).mouseReleaseEvent(evt) def remove_annotations(self): for item in self._scene.items(): if isinstance(item, EditableItem): item.delete_item() def remove_annotations_by_label(self, label_name): for item in self._scene.items(): if isinstance(item, EditableItem): if item.label and item.label.name == label_name: item.delete_item() def enable_items(self, value): for item in self._scene.items(): if isinstance(item, EditableItem): item.setEnabled(value) def clear_extreme_points(self): if self._extreme_points.qsize() > 0: for pt in self._extreme_points.queue: self._scene.removeItem(pt) self._extreme_points.queue.clear()
class PreXoverItemGroup(QGraphicsEllipseItem): """Summary Attributes: active_wedge_gizmo (TYPE): Description fwd_prexover_items (dict): Description HUE_FACTOR (float): Description id_num (TYPE): Description is_active (TYPE): Description model_part (Part): The model part rev_prexover_items (dict): Description SPIRAL_FACTOR (float): Description virtual_helix_item (VirtualHelixItem): Description """ HUE_FACTOR = 1.6 SPIRAL_FACTOR = 0.4 def __init__(self, radius, rect, virtual_helix_item, is_active): """Summary Args: radius (TYPE): Description rect (TYPE): Description virtual_helix_item (VirtualHelixItem): Description is_active (TYPE): Description """ super(PreXoverItemGroup, self).__init__(rect, virtual_helix_item) self._radius = radius self._rect = rect self.virtual_helix_item = virtual_helix_item self.model_part = virtual_helix_item.part() self.id_num = virtual_helix_item.idNum() self.is_active = is_active self.active_wedge_gizmo = WedgeGizmo(radius, rect, self) self.fwd_prexover_items = fwd_pxis = {} self.rev_prexover_items = rev_pxis = {} self._colors = self._getColors() self.addItems() self.setPen(getNoPen()) z = styles.ZPXIGROUP + 10 if is_active else styles.ZPXIGROUP self.setZValue(z) self.setTransformOriginPoint(rect.center()) bpr, tpr, eulerZ = virtual_helix_item.getProperty(['bases_per_repeat', 'turns_per_repeat', 'eulerZ']) self.setRotation(-eulerZ) # add 180 # for baseNearestPoint fwd_pos, rev_pos = [], [] step_size = self.virtual_helix_item.getProperty('bases_per_repeat') for i in range(int(step_size)): fwd_pos.append((fwd_pxis[i].scenePos().x(), fwd_pxis[i].scenePos().y())) rev_pos.append((rev_pxis[i].scenePos().x(), rev_pxis[i].scenePos().y())) self.fwd_pos_array = np.asarray(fwd_pos) self.rev_pos_array = np.asarray(rev_pos) self.baseNearLine = QGraphicsLineItem(self) self.baseNearLine.setPen(getPenObj("#000000", 0.25, capstyle=Qt.RoundCap)) # end def def mousePressEvent(self, event): print("PreXoverGroup press") def mouseMoveEvent(self, event): print("PreXoverGroup move") def mouseReleaseEvent(self, event): print("PreXoverGroup release") ### ACCESSORS ### def partItem(self): """Summary Returns: TYPE: Description """ return self.virtual_helix_item.partItem() # end def def getItem(self, is_fwd, step_idx): """Summary Args: is_fwd (TYPE): Description step_idx (int): the base index within the virtual helix Returns: TYPE: Description """ items = self.fwd_prexover_items if is_fwd else self.rev_prexover_items if step_idx in items: return items[step_idx] else: return None # end def def getItemIdx(self, is_fwd, idx): """Summary Args: is_fwd (TYPE): Description idx (int): the base index within the virtual helix Returns: TYPE: Description """ step_size = self.virtual_helix_item.getProperty('bases_per_repeat') return self.getItem(is_fwd, idx % step_size) # end def ### EVENT HANDLERS ### ### PRIVATE SUPPORT METHODS ### def _getColors(self): """Summary Returns: TYPE: Description """ step_size = int(self.virtual_helix_item.getProperty('bases_per_repeat')) hue_scale = step_size*self.HUE_FACTOR return [QColor.fromHsvF(i / hue_scale, 0.75, 0.8).name() for i in range(step_size)] # end def ### PUBLIC SUPPORT METHODS ### def addItems(self): """Summary """ radius = self._radius step_size, bases_per_turn, tpb, mgroove = self.virtual_helix_item.getAngularProperties() # print("TPB", tpb, step_size) iw = PXI_PP_ITEM_WIDTH spiral_factor = self.SPIRAL_FACTOR colors = self._colors ctr = self.mapToParent(self._rect).boundingRect().center() x = ctr.x() + radius - PXI_PP_ITEM_WIDTH y = ctr.y() # tpb = -tpb # Qt +angle is Clockwise (CW), model +angle is CCW mgroove = -mgroove fwd_pxis = self.fwd_prexover_items rev_pxis = self.rev_prexover_items for i in range(int(step_size)): inset = i*spiral_factor # spiral layout fwd = PreXoverItem(i, tpb, step_size, colors[i], self, is_fwd=True) rev = PreXoverItem(i, tpb, step_size, colors[-1 - i], self, is_fwd=False) fwd.setPos(x - inset, y) rev.setPos(x - inset, y) fwd.setTransformOriginPoint((-radius + iw + inset), 0) rev.setTransformOriginPoint((-radius + iw + inset), 0) fwd.setRotation(round(i*tpb % 360, 3)) rev.setRotation(round((i*tpb + mgroove) % 360, 3)) fwd.setBondLineLength(inset + iw) rev.setBondLineLength(inset + iw) fwd_pxis[i] = fwd rev_pxis[i] = rev for i in range(int(step_size) - 1): fwd, next_fwd = fwd_pxis[i], fwd_pxis[i + 1] j = (step_size - 1) - i rev, next_rev = rev_pxis[j], rev_pxis[j - 1] fwd.set3pItem(next_fwd) rev.set3pItem(next_rev) next_fwd.set5pItem(fwd) next_rev.set5pItem(rev) # end def def baseNearestPoint(self, is_fwd, scene_pos): """Summary Args: is_fwd (bool): used to check fwd or rev lists. scene_pos (QPointF): scene coordinate position Returns: PreXoverItem: base nearest to position """ pos_array = self.fwd_pos_array if is_fwd else self.rev_pos_array dist_2 = np.sum((pos_array - (scene_pos.x(), scene_pos.y()))**2, axis=1) near_idx = np.argmin(dist_2) near_pxi = self.fwd_prexover_items[near_idx] if is_fwd else self.rev_prexover_items[near_idx] # Draw a line p1 = self.mapFromScene(scene_pos.x(), scene_pos.y()) p2 = self.mapFromScene(near_pxi.scenePos()) line = QLineF(p1, p2) self.baseNearLine.setLine(line) def destroyItem(self): """Summary """ fpxis = self.fwd_prexover_items rpxis = self.rev_prexover_items scene = self.scene() for i in range(len(fpxis)): x = fpxis.pop(i) x.destroyItem() x = rpxis.pop(i) x.destroyItem() self.virtual_helix_item = None self.model_part = None scene.removeItem(self.active_wedge_gizmo) self.active_wedge_gizmo = None scene.removeItem(self) # end def def updateTurnsPerRepeat(self): """Summary """ step_size, bases_per_turn, tpb, mgroove = self.virtual_helix_item.getAngularProperties() mgroove = -mgroove fpxis = self.fwd_prexover_items rpxis = self.rev_prexover_items for i in range(int(step_size)): fwd = self.fwd_prexover_items[i] rev = self.rev_prexover_items[i] fwd.setRotation(round((i*tpb) % 360, 3)) rev.setRotation(round((i*tpb + mgroove) % 360, 3)) for i in range(int(step_size) - 1): fwd, next_fwd = fpxis[i], fpxis[i + 1] j = (step_size - 1) - i rev, next_rev = rpxis[j], rpxis[j - 1] fwd.set3pItem(next_fwd) rev.set3pItem(next_rev) next_fwd.set5pItem(fwd) next_rev.set5pItem(rev) # end def def partCrossoverSpanAngle(self): """ Returns: int: Crossover span angle from Part. """ return self.virtual_helix_item.partCrossoverSpanAngle() def updateModelActiveBaseInfo(self, pre_xover_info): """Notify model of pre_xover_item hover state. """ self.model_part.setActiveBaseInfo(pre_xover_info)
class MonoScene(QGraphicsScene): def __init__(self): super().__init__() self.language = settings.LANG self.csv_separator = settings.CSV_SEPARATOR self.fmt_float = settings.FMT_FLOAT self.nodes = {} self.nb_nodes = 0 self.adj_list = {} self.current_line = None self.current_port = None self.project_path = '' self.setSceneRect( QRectF(0, 0, settings.SCENE_SIZE[0], settings.SCENE_SIZE[1])) self.transform = QTransform() self.selectionChanged.connect(self.selection_changed) self._init_with_default_node() def reinit(self): self.clear() self._init_with_default_node() self.update() def _init_without_node(self): self.nodes = {} self.nb_nodes = 0 self.adj_list = {} def _init_with_default_node(self): self._init_without_node() self.add_node(LoadSerafin2DNode(0), 50, 50) self._add_current_line() def _add_current_line(self): self.current_line = QGraphicsLineItem() self.addItem(self.current_line) self.current_line.setVisible(False) pen = QPen(QColor(0, 0, 0)) pen.setWidth(2) self.current_line.setPen(pen) self.current_port = None def mousePressEvent(self, event): super().mousePressEvent(event) for node_index, node in enumerate(self.nodes.values()): for port_index, port in enumerate(node.ports): if port.isSelected(): self.current_port = (node_index, port_index) def mouseMoveEvent(self, event): super().mouseMoveEvent(event) if self.current_port is not None: port = self.nodes[self.current_port[0]].ports[self.current_port[1]] self.current_line.setLine( QLineF(port.mapToScene(port.rect().center()), event.scenePos())) self.current_line.setVisible(True) def mouseReleaseEvent(self, event): if self.current_port is not None: target_item = self.itemAt(event.scenePos(), self.transform) if isinstance(target_item, Port): self._handle_add_link(target_item) self.current_line.setVisible(False) self.current_port = None super().mouseReleaseEvent(event) def mouseDoubleClickEvent(self, event): super().mouseDoubleClickEvent(event) target_item = self.itemAt(event.scenePos(), self.transform) if isinstance(target_item, Link): self._handle_remove_link(target_item) elif isinstance(target_item, Box): node = target_item.parentItem() node.configure() self.selection_changed() def keyPressEvent(self, event): if event.key() == Qt.Key_Delete: selected = self.selectedItems() if selected: link = selected[0] self._handle_remove_link(link) def selection_changed(self): view = self.views()[0] selected = self.selectedItems() if not selected: view.deselect_node() return selected = selected[0] if isinstance(selected, Node): view.select_node(selected) else: view.deselect_node() def add_node(self, node, x, y): logger.debug('Add node #%i %s' % (node.index(), node.label.replace('\n', ' '))) self.addItem(node) self.nodes[node.index()] = node self.adj_list[node.index()] = set() self.nb_nodes += 1 node.moveBy(x, y) def save(self): links = [] for item in self.items(): if isinstance(item, Link): links.append(item.save()) yield '.'.join([self.language, self.csv_separator]) yield '%d %d' % (self.nb_nodes, len(links)) for node in self.nodes.values(): yield node.save() for link in links: yield link def load(self, filename): logger.debug('Loading project in MONO: %s' % filename) self.project_path = filename self.clear() self._add_current_line() self._init_without_node() try: with open(filename, 'r') as f: self.language, self.csv_separator = f.readline().rstrip( ).split('.') nb_nodes, nb_links = map(int, f.readline().split()) for _ in range(nb_nodes): line = f.readline().rstrip().split('|') category, name, index, x, y = line[:5] node = NODES[category][name](int(index)) node.load(line[5:]) self.add_node(node, float(x), float(y)) for _ in range(nb_links): from_node_index, from_port_index, \ to_node_index, to_port_index = map(int, f.readline().rstrip().split('|')) from_node = self.nodes[from_node_index] to_node = self.nodes[to_node_index] from_port = from_node.ports[from_port_index] to_port = to_node.ports[to_port_index] _, _ = add_link(from_port, to_port) link = Link(from_port, to_port) from_node.add_link(link) to_node.add_link(link) self.addItem(link) link.setZValue(-1) self.adj_list[from_node_index].add(to_node_index) self.adj_list[to_node_index].add(from_node_index) self.update() return True except (IndexError, ValueError, KeyError) as e: logger.exception(e) logger.error("An exception occured while loading project in MONO.") self.reinit() return False def run_all(self): roots = self._to_sources() for root in roots: self.nodes[root].run_downward() def _to_sources(self): roots = [] visited = {node: False for node in self.nodes} def forward(node): for port in node.ports: if port.type == Port.OUTPUT: if port.has_children(): for child in port.children: child_node = child.parentItem() if not visited[child_node.index()]: visited[child_node.index()] = False forward(child_node) def backward(node_index): if not visited[node_index]: visited[node_index] = True node = self.nodes[node_index] if node.ports[0].type == Port.INPUT: backward(node.ports[0].parentItem().index()) if len(node.ports ) > 1 and node.ports[1].type == Port.INPUT: backward(node.ports[1].parentItem().index()) else: roots.append(node_index) forward(node) for node in self.nodes: backward(node) return roots def _handle_add_link(self, target_item): port_index = target_item.index() node_index = target_item.parentItem().index() if node_index == self.current_port[0]: return from_node, to_node, from_port, to_port = self._permute( self.current_port[0], self.current_port[1], node_index, port_index) if from_port is None: QMessageBox.critical( None, 'Error', 'Connections can only be established between Output and Input ports!', QMessageBox.Ok) return success, cause = add_link(from_port, to_port) if success: link = Link(from_port, to_port) from_node.add_link(link) to_node.add_link(link) self.addItem(link) link.setZValue(-1) self.update() self.adj_list[from_node.index()].add(to_node.index()) self.adj_list[to_node.index()].add(from_node.index()) else: if cause == 'already': QMessageBox.critical(None, 'Error', 'These two ports are already connected.', QMessageBox.Ok) elif cause == 'type': QMessageBox.critical( None, 'Error', 'These two ports cannot be connected: incompatible data types.', QMessageBox.Ok) else: QMessageBox.critical( None, 'Error', 'The input port is already connected to another port.', QMessageBox.Ok) def _handle_remove_link(self, target_item): msg = QMessageBox.warning(None, 'Confirm delete', 'Do you want to delete this link?', QMessageBox.Ok | QMessageBox.Cancel, QMessageBox.Ok) if msg == QMessageBox.Cancel: return from_index, to_index = target_item.from_port.parentItem().index( ), target_item.to_port.parentItem().index() self.adj_list[from_index].remove(to_index) self.adj_list[to_index].remove(from_index) target_item.remove() self.removeItem(target_item) def handle_remove_node(self, node): msg = QMessageBox.warning(None, 'Confirm delete', 'Do you want to delete this node?', QMessageBox.Ok | QMessageBox.Cancel, QMessageBox.Ok) if msg == QMessageBox.Cancel: return self.removeItem(node) for link in node.links.copy(): link.remove() self.removeItem(link) del self.nodes[node.index()] new_nodes = {} self.nb_nodes -= 1 for index, node in zip(range(self.nb_nodes), self.nodes.values()): new_nodes[index] = node node.set_index(index) self.nodes = new_nodes self.adj_list = {node: set() for node in self.nodes} for item in self.items(): if isinstance(item, Link): from_index, to_index = item.from_port.parentItem().index( ), item.to_port.parentItem().index() self.adj_list[from_index].add(to_index) self.adj_list[to_index].add(from_index) def _permute(self, first_node_index, first_port_index, second_node_index, second_port_index): first_node = self.nodes[first_node_index] second_node = self.nodes[second_node_index] p1 = first_node.ports[first_port_index] p2 = second_node.ports[second_port_index] if p1.type == Port.OUTPUT and p2.type == Port.INPUT: return first_node, second_node, p1, p2 elif p1.type == Port.INPUT and p2.type == Port.OUTPUT: return second_node, first_node, p2, p1 return None, None, None, None def suffix_pool(self): suffix = [] for node in self.nodes.values(): if hasattr(node, 'suffix'): suffix.append(node.suffix) return suffix def not_connected(self): nb_cc, _ = count_cc(list(self.adj_list.keys()), self.adj_list) return nb_cc > 1
class DiagramScene(QGraphicsScene): InsertItem, InsertLine, InsertText, MoveItem = range(4) itemInserted = pyqtSignal(DiagramItem) textInserted = pyqtSignal(QGraphicsTextItem) itemSelected = pyqtSignal(QGraphicsItem) def __init__(self, itemMenu, parent=None): super(DiagramScene, self).__init__(parent) self.myItemMenu = itemMenu self.myMode = self.MoveItem self.myItemType = DiagramItem.Step self.line = None self.textItem = None self.myItemColor = Qt.white self.myTextColor = Qt.black self.myLineColor = Qt.black self.myFont = QFont() def setLineColor(self, color): self.myLineColor = color if self.isItemChange(Arrow): item = self.selectedItems()[0] item.setColor(self.myLineColor) self.update() def setTextColor(self, color): self.myTextColor = color if self.isItemChange(DiagramTextItem): item = self.selectedItems()[0] item.setDefaultTextColor(self.myTextColor) def setItemColor(self, color): self.myItemColor = color if self.isItemChange(DiagramItem): item = self.selectedItems()[0] item.setBrush(self.myItemColor) def setFont(self, font): self.myFont = font if self.isItemChange(DiagramTextItem): item = self.selectedItems()[0] item.setFont(self.myFont) def setMode(self, mode): self.myMode = mode def setItemType(self, type): self.myItemType = type def editorLostFocus(self, item): cursor = item.textCursor() cursor.clearSelection() item.setTextCursor(cursor) if item.toPlainText(): self.removeItem(item) item.deleteLater() def mousePressEvent(self, mouseEvent): if (mouseEvent.button() != Qt.LeftButton): return if self.myMode == self.InsertItem: item = DiagramItem(self.myItemType, self.myItemMenu) item.setBrush(self.myItemColor) self.addItem(item) item.setPos(mouseEvent.scenePos()) self.itemInserted.emit(item) elif self.myMode == self.InsertLine: self.line = QGraphicsLineItem(QLineF(mouseEvent.scenePos(), mouseEvent.scenePos())) self.line.setPen(QPen(self.myLineColor, 2)) self.addItem(self.line) elif self.myMode == self.InsertText: textItem = DiagramTextItem() textItem.setFont(self.myFont) textItem.setTextInteractionFlags(Qt.TextEditorInteraction) textItem.setZValue(1000.0) textItem.lostFocus.connect(self.editorLostFocus) textItem.selectedChange.connect(self.itemSelected) self.addItem(textItem) textItem.setDefaultTextColor(self.myTextColor) textItem.setPos(mouseEvent.scenePos()) self.textInserted.emit(textItem) super(DiagramScene, self).mousePressEvent(mouseEvent) def mouseMoveEvent(self, mouseEvent): if self.myMode == self.InsertLine and self.line: newLine = QLineF(self.line.line().p1(), mouseEvent.scenePos()) self.line.setLine(newLine) elif self.myMode == self.MoveItem: super(DiagramScene, self).mouseMoveEvent(mouseEvent) def mouseReleaseEvent(self, mouseEvent): if self.line and self.myMode == self.InsertLine: startItems = self.items(self.line.line().p1()) if len(startItems) and startItems[0] == self.line: startItems.pop(0) endItems = self.items(self.line.line().p2()) if len(endItems) and endItems[0] == self.line: endItems.pop(0) self.removeItem(self.line) self.line = None if len(startItems) and len(endItems) and \ isinstance(startItems[0], DiagramItem) and \ isinstance(endItems[0], DiagramItem) and \ startItems[0] != endItems[0]: startItem = startItems[0] endItem = endItems[0] arrow = Arrow(startItem, endItem) arrow.setColor(self.myLineColor) startItem.addArrow(arrow) endItem.addArrow(arrow) arrow.setZValue(-1000.0) self.addItem(arrow) arrow.updatePosition() self.line = None super(DiagramScene, self).mouseReleaseEvent(mouseEvent) def isItemChange(self, type): for item in self.selectedItems(): if isinstance(item, type): return True return False
class DiagramScene(QGraphicsScene): InsertItem, InsertLine, InsertText, MoveItem, DefaultMode = range(5) itemInserted = pyqtSignal(int) textInserted = pyqtSignal(QGraphicsTextItem) itemSelected = pyqtSignal(QGraphicsItem) def __init__(self, item_menu, parent=None): super(DiagramScene, self).__init__(parent) self.my_item_menu = item_menu self.my_mode = self.DefaultMode self.my_item_type = "channel" self.line = None self.text_item = None self.my_item_color = Qt.white self.my_text_color = Qt.black self.my_line_color = Qt.black self.my_font = QFont() self.m_drag_offset = 0 self.m_dragged = None def set_line_color(self, color): self.my_line_color = color if self.is_item_changed(Arrow): item = self.selectedItems()[0] item.set_color(self.my_line_color) self.update() def set_text_color(self, color): self.my_text_color = color if self.is_item_changed(DiagramTextItem): item = self.selectedItems()[0] item.setDefaultTextColor(self.my_text_color) def set_item_color(self, color): self.my_item_color = color if self.is_item_changed(DiagramTextItem): item = self.selectedItems()[0] item.setBrush(self.my_item_color) def setFont(self, font): self.my_font = font if self.is_item_changed(DiagramTextItem): item = self.selectedItems()[0] item.setFont(self.my_font) def set_mode(self, mode): self.my_mode = mode def set_item_type(self, new_type): self.my_item_type = new_type def editor_lost_focus(self, item): if item: cursor = item.text_cursor() cursor.clearSelection() item.set_text_cursor(cursor) if item.to_plain_text(): self.removeItem(item) item.delete_later() # noinspection PyArgumentList def insert_item(self, item_type=None, x=None, y=None, text=None): if not text: text, ok = QInputDialog.getText(QInputDialog(), 'Insert name', 'Enter new object name:') if not ok: # TODO return item = FlumeObject(item_type, text).pictogram item.setBrush(self.my_item_color) self.addItem(item) item.setPos(x, y) return item def mousePressEvent(self, mouse_event): if mouse_event.button() != Qt.LeftButton: return if self.my_mode == self.InsertItem: x = mouse_event.scenePos().x() # // 50 * 50 y = mouse_event.scenePos().y() # // 50 * 50 item = self.insert_item(self.my_item_type, x, y) self.itemInserted.emit(item.flume_component) elif self.my_mode == self.InsertLine: self.line = QGraphicsLineItem(QLineF(mouse_event.scenePos(), mouse_event.scenePos())) self.line.setPen(QPen(self.my_line_color, 2)) self.addItem(self.line) elif self.my_mode == self.InsertText: text_item = DiagramTextItem() text_item.setFont(self.my_font) text_item.setTextInteractionFlags(Qt.TextEditorInteraction) text_item.setZValue(1000.0) text_item.lostFocus.connect(self.editor_lost_focus) # text_item.selectedChange.connect(self.itemSelected) self.addItem(text_item) text_item.setDefaultTextColor(self.my_text_color) text_item.setPos(mouse_event.scenePos()) self.textInserted.emit(text_item) else: self.m_dragged = QGraphicsScene.itemAt(self, mouse_event.scenePos(), QTransform()) if self.m_dragged: self.my_mode = self.MoveItem self.m_drag_offset = mouse_event.scenePos() - self.m_dragged.pos() super(DiagramScene, self).mousePressEvent(mouse_event) def mouseMoveEvent(self, mouse_event): if self.my_mode == self.InsertLine and self.line: new_line = QLineF(self.line.line().p1(), mouse_event.scenePos()) self.line.setLine(new_line) elif self.my_mode == self.MoveItem: if self.m_dragged: self.m_dragged.setPos(mouse_event.scenePos() - self.m_drag_offset) super(DiagramScene, self).mouseMoveEvent(mouse_event) def mouseReleaseEvent(self, mouse_event): if self.line and self.my_mode == self.InsertLine: start_items = self.items(self.line.line().p1()) if len(start_items) and start_items[0] == self.line: start_items.pop(0) end_items = self.items(self.line.line().p2()) if len(end_items) and end_items[0] == self.line: end_items.pop(0) self.removeItem(self.line) self.line = None if len(start_items) and len(end_items) and isinstance(start_items[0], FlumeDiagramItem) and \ isinstance(end_items[0], FlumeDiagramItem) and start_items[0] != end_items[0]: start_item = start_items[0] end_item = end_items[0] self.add_arrow(start_item, end_item) self.line = None if self.m_dragged: x = mouse_event.scenePos().x() # // 50 * 50 y = mouse_event.scenePos().y() # // 50 * 50 self.m_dragged.setPos(x, y) self.m_dragged = None self.my_mode = self.DefaultMode super(DiagramScene, self).mouseReleaseEvent(mouse_event) def add_arrow(self, start_item, end_item): arrow = Arrow(start_item, end_item) arrow.set_color(self.my_line_color) start_item.add_arrow(arrow) end_item.add_arrow(arrow) arrow.setZValue(-1000.0) self.addItem(arrow) arrow.update_position() def is_item_changed(self, new_type): for item in self.selectedItems(): if isinstance(item, new_type): return True return False
class BaseGraphic(object): def __init__(self, *args): super(BaseGraphic, self).__init__() self.label = args[3] self.parent = args[0] self.section_analyzer = self.parent.section_analyzer self.width = None self.height = None self.margin = None self.position = args[1], args[2] self.content_width = None self.content_height = None self.distance_pointer = None self.distance_label = None self.section_num = len(self.section_analyzer.section_list) self.section_distance = self.section_analyzer.section_distance self.length_multiplier = 100.0 self.height_multiplier = 100.0 self.line_extend = 20 self.margin = 50 self.material_legend = MaterialLegend(self) self._inited = False self.items = [] self.add_title() def add_title(self): if self.label: title = QGraphicsSimpleTextItem(self.label) title.setPos(self.position[0] + self.margin, self.position[1] + self.line_extend) self.addToGroup(title) def addToGroup(self, item): self.items.append(item) def create_distance_pointer(self): self.distance_pointer = QGraphicsLineItem() pen = QPen() pen.setWidthF(1.0) pen.setStyle(Qt.DashDotLine) color = Color.create_qcolor_from_rgb_tuple_f((1,0,0)) pen.setColor(color) self.distance_pointer.setPen(pen) self.distance_pointer.setZValue(1.0) self.addToGroup(self.distance_pointer) self.distance_label = QGraphicsSimpleTextItem() self.distance_label.setZValue(1.0) self.addToGroup(self.distance_label) def init_dimension(self): section_num = len(self.section_analyzer.section_list) section_distance = self.section_analyzer.section_distance self.content_width = section_num * section_distance * self.length_multiplier self.create_distance_pointer() self._inited = True def update_graph_size(self): if self.content_height and self.content_width: self.width = self.content_width + self.margin * 2 self.height = self.content_height + self.margin * 2 # bounding_rect.setWidth(self.width) # bounding_rect.setHeight(self.height) def set_distance_pointer(self, distance): if self._inited: x1 = self.position[0] + self.margin + distance * self.length_multiplier y1 = self.position[1] x2 = x1 y2 = y1 + self.height self.distance_pointer.setLine(x1, y1, x2, y2) self.distance_label.setText("%.2f" % distance) self.distance_label.setPos(x2,y2) pass @staticmethod def set_rect_fill(*args): if args[0] == 0: #surface color mode rect = args[1] color = args[2] qcolor = Color.create_qcolor_from_rgb_tuple_f(color) brush = QBrush(qcolor) rect.setBrush(brush) def create_legend(self): x = self.position[0] + self.width y = self.position[1] self.material_legend.create_material_legend(x, y) for item in self.material_legend.graphic_items: self.addToGroup(item)
class ChartView(QChartView): def __init__(self, *args, **kwargs): super(ChartView, self).__init__(*args, **kwargs) self.resize(800, 600) self.setRenderHint(QPainter.Antialiasing) # 抗锯齿 self.initChart() # 提示widget self.toolTipWidget = GraphicsProxyWidget(self._chart) # line 宽度需要调整 self.lineItem = QGraphicsLineItem(self._chart) pen = QPen(Qt.gray) self.lineItem.setPen(pen) self.lineItem.setZValue(998) self.lineItem.hide() # 一些固定计算,减少mouseMoveEvent中的计算量 # 获取x和y轴的最小最大值 axisX, axisY = self._chart.axisX(), self._chart.axisY() self.category_len = len(axisX.categories()) self.min_x, self.max_x = -0.5, self.category_len - 0.5 self.min_y, self.max_y = axisY.min(), axisY.max() # 坐标系中左上角顶点 self.point_top = self._chart.mapToPosition( QPointF(self.min_x, self.max_y)) def mouseMoveEvent(self, event): super(ChartView, self).mouseMoveEvent(event) pos = event.pos() # 把鼠标位置所在点转换为对应的xy值 x = self._chart.mapToValue(pos).x() y = self._chart.mapToValue(pos).y() index = round(x) # 得到在坐标系中的所有bar的类型和点 serie = self._chart.series()[0] bars = [(bar, bar.at(index)) for bar in serie.barSets() if self.min_x <= x <= self.max_x and self.min_y <= y <= self.max_y] if bars: right_top = self._chart.mapToPosition( QPointF(self.max_x, self.max_y)) # 等分距离比例 step_x = round( (right_top.x() - self.point_top.x()) / self.category_len) posx = self._chart.mapToPosition(QPointF(x, self.min_y)) self.lineItem.setLine(posx.x(), self.point_top.y(), posx.x(), posx.y()) self.lineItem.show() try: title = self.categories[index] except: title = "" t_width = self.toolTipWidget.width() t_height = self.toolTipWidget.height() # 如果鼠标位置离右侧的距离小于tip宽度 x = pos.x() - t_width if self.width() - \ pos.x() - 20 < t_width else pos.x() # 如果鼠标位置离底部的高度小于tip高度 y = pos.y() - t_height if self.height() - \ pos.y() - 20 < t_height else pos.y() self.toolTipWidget.show( title, bars, QPoint(x, y)) else: self.toolTipWidget.hide() self.lineItem.hide() def handleMarkerClicked(self): marker = self.sender() # 信号发送者 if not marker: return bar = marker.barset() if not bar: return # bar透明度 brush = bar.brush() color = brush.color() alpha = 0.0 if color.alphaF() == 1.0 else 1.0 color.setAlphaF(alpha) brush.setColor(color) bar.setBrush(brush) # marker brush = marker.labelBrush() color = brush.color() alpha = 0.4 if color.alphaF() == 1.0 else 1.0 # 设置label的透明度 color.setAlphaF(alpha) brush.setColor(color) marker.setLabelBrush(brush) # 设置marker的透明度 brush = marker.brush() color = brush.color() color.setAlphaF(alpha) brush.setColor(color) marker.setBrush(brush) def handleMarkerHovered(self, status): # 设置bar的画笔宽度 marker = self.sender() # 信号发送者 if not marker: return bar = marker.barset() if not bar: return pen = bar.pen() if not pen: return pen.setWidth(pen.width() + (1 if status else -1)) bar.setPen(pen) def handleBarHoverd(self, status, index): # 设置bar的画笔宽度 bar = self.sender() # 信号发送者 pen = bar.pen() if not pen: return pen.setWidth(pen.width() + (1 if status else -1)) bar.setPen(pen) def initChart(self): self._chart = QChart(title="柱状图堆叠") self._chart.setAcceptHoverEvents(True) # Series动画 self._chart.setAnimationOptions(QChart.SeriesAnimations) self.categories = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"] # names = ["邮件营销", "联盟广告", "视频广告", "直接访问", "搜索引擎"] names = ["邮件营销", ] series = QBarSeries(self._chart) for name in names: bar = QBarSet(name) # 随机数据 for _ in range(7): bar.append(randint(0, 10)) series.append(bar) bar.hovered.connect(self.handleBarHoverd) # 鼠标悬停 self._chart.addSeries(series) self._chart.createDefaultAxes() # 创建默认的轴 # x轴 axis_x = QBarCategoryAxis(self._chart) axis_x.append(self.categories) self._chart.setAxisX(axis_x, series) # chart的图例 legend = self._chart.legend() legend.setVisible(True) # 遍历图例上的标记并绑定信号 for marker in legend.markers(): # 点击事件 marker.clicked.connect(self.handleMarkerClicked) # 鼠标悬停事件 marker.hovered.connect(self.handleMarkerHovered) self.setChart(self._chart)
class CalendarDesklet(Desklet): def __init__(self): super().__init__() self.model = CalendarModel() self.cursor_pos = None self.cursor = QGraphicsRectItem(self.root) self.header = QGraphicsSimpleTextItem(self.root) self.weekdays = [] days = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] for day in days: self.weekdays.append(QGraphicsSimpleTextItem(day, self.root)) self.header_line = QGraphicsLineItem(self.root) self.days = [] for _ in range(0, 6 * 7): self.days.append(QGraphicsSimpleTextItem(self.root)) def next_month(self): self.model.next_month() self.layout() def previous_month(self): self.model.previous_month() self.layout() def set_rect(self, rect): super().set_rect(rect) self.layout() def set_style(self, style): super().set_style(style) font = QFont(style.font) font.setPixelSize(48) self.header.setBrush(style.midcolor) self.header.setFont(font) font = QFont(style.font) font.setPixelSize(32) self.header_line.setPen(style.foreground_color) self.cursor.setBrush(style.midcolor) self.cursor.setPen(QPen(Qt.NoPen)) for widget in self.weekdays: widget.setFont(font) widget.setBrush(style.foreground_color) for widget in self.days: widget.setFont(font) widget.setBrush(self.style.foreground_color) self.layout() def layout(self): cell_width = (self.rect.width()) / 7.0 cell_height = (self.rect.height() - 64) / 7.0 x = self.rect.left() y = self.rect.top() fm = QFontMetrics(self.header.font()) rect = fm.boundingRect(self.header.text()) self.header.setPos(x + self.rect.width() / 2 - rect.width() / 2, y) y += fm.height() for row, day in enumerate(self.weekdays): fm = QFontMetrics(day.font()) rect = fm.boundingRect(day.text()) day.setPos(x + row * cell_width + cell_width / 2 - rect.width() / 2, y) y += fm.height() self.header_line.setLine(x, y, x + self.rect.width() - 3, y) y += 8 for n, widget in enumerate(self.days): col = n % 7 row = n // 7 rect = fm.boundingRect(widget.text()) widget.setPos(x + col * cell_width + cell_width / 2 - rect.width() / 2, y + row * cell_height + cell_height / 2 - fm.height() / 2) # if day.month != self.now.month: # widget.setBrush(self.style.midcolor) # else: if self.cursor_pos is not None: self.cursor.setRect(x + self.cursor_pos[0] * cell_width, y + self.cursor_pos[1] * cell_height, cell_width, cell_height) self.cursor.show() else: self.cursor.hide() def update(self, now): self.model.update(now) # update header self.header.setText( date(self.model.year, self.model.month, 1).strftime("%B %Y")) # calculate the date of the top/left calendar entry current_date = date(self.model.year, self.model.month, 1) current_date = current_date - timedelta(current_date.weekday()) self.cursor_pos = None for n, widget in enumerate(self.days): col = n % 7 row = n // 7 if current_date == self.model.today: self.cursor_pos = (col, row) widget.setText("%d" % current_date.day) self.days[n] = widget current_date += timedelta(days=1) self.layout()
class PreXoverItemGroup(QGraphicsEllipseItem): """Summary Attributes: active_wedge_gizmo (TYPE): Description fwd_prexover_items (dict): Description HUE_FACTOR (float): Description id_num (TYPE): Description is_active (TYPE): Description model_part (Part): The model part rev_prexover_items (dict): Description SPIRAL_FACTOR (float): Description virtual_helix_item (VirtualHelixItem): Description """ HUE_FACTOR = 1.6 SPIRAL_FACTOR = 0.4 def __init__(self, radius, rect, virtual_helix_item, is_active): """Summary Args: radius (TYPE): Description rect (TYPE): Description virtual_helix_item (VirtualHelixItem): Description is_active (TYPE): Description """ super(PreXoverItemGroup, self).__init__(rect, virtual_helix_item) self._radius = radius self._rect = rect self.virtual_helix_item = virtual_helix_item self.model_part = virtual_helix_item.part() self.id_num = virtual_helix_item.idNum() self.is_active = is_active self.active_wedge_gizmo = WedgeGizmo(radius, rect, self) self.fwd_prexover_items = fwd_pxis = {} self.rev_prexover_items = rev_pxis = {} self._colors = self._getColors() self.addItems() self.setPen(getNoPen()) z = styles.ZPXIGROUP + 10 if is_active else styles.ZPXIGROUP self.setZValue(z) self.setTransformOriginPoint(rect.center()) bpr, tpr, eulerZ = virtual_helix_item.getProperty( ['bases_per_repeat', 'turns_per_repeat', 'eulerZ']) self.setRotation(-eulerZ) # add 180 # for baseNearestPoint fwd_pos, rev_pos = [], [] step_size = self.virtual_helix_item.getProperty('bases_per_repeat') for i in range(int(step_size)): fwd_pos.append( (fwd_pxis[i].scenePos().x(), fwd_pxis[i].scenePos().y())) rev_pos.append( (rev_pxis[i].scenePos().x(), rev_pxis[i].scenePos().y())) self.fwd_pos_array = np.asarray(fwd_pos) self.rev_pos_array = np.asarray(rev_pos) self.baseNearLine = QGraphicsLineItem(self) self.baseNearLine.setPen( getPenObj("#000000", 0.25, capstyle=Qt.RoundCap)) # end def # def mousePressEvent(self, event): # print("PreXoverGroup press") # def mouseMoveEvent(self, event): # print("PreXoverGroup move") # def mouseReleaseEvent(self, event): # print("PreXoverGroup release") ### ACCESSORS ### def partItem(self): """Summary Returns: TYPE: Description """ return self.virtual_helix_item.partItem() # end def def getItem(self, is_fwd, step_idx): """Summary Args: is_fwd (TYPE): Description step_idx (int): the base index within the virtual helix Returns: TYPE: Description """ items = self.fwd_prexover_items if is_fwd else self.rev_prexover_items if step_idx in items: return items[step_idx] else: return None # end def def getItemIdx(self, is_fwd, idx): """Summary Args: is_fwd (TYPE): Description idx (int): the base index within the virtual helix Returns: TYPE: Description """ step_size = self.virtual_helix_item.getProperty('bases_per_repeat') return self.getItem(is_fwd, idx % step_size) # end def ### EVENT HANDLERS ### ### PRIVATE SUPPORT METHODS ### def _getColors(self): """Summary Returns: TYPE: Description """ step_size = int( self.virtual_helix_item.getProperty('bases_per_repeat')) hue_scale = step_size * self.HUE_FACTOR return [ QColor.fromHsvF(i / hue_scale, 0.75, 0.8).name() for i in range(step_size) ] # end def ### PUBLIC SUPPORT METHODS ### def addItems(self): """Summary """ radius = self._radius step_size, bases_per_turn, tpb, mgroove = self.virtual_helix_item.getAngularProperties( ) # print("TPB", tpb, step_size) iw = PXI_PP_ITEM_WIDTH spiral_factor = self.SPIRAL_FACTOR colors = self._colors ctr = self.mapToParent(self._rect).boundingRect().center() x = ctr.x() + radius - PXI_PP_ITEM_WIDTH y = ctr.y() # tpb = -tpb # Qt +angle is Clockwise (CW), model +angle is CCW mgroove = -mgroove fwd_pxis = self.fwd_prexover_items rev_pxis = self.rev_prexover_items for i in range(int(step_size)): inset = i * spiral_factor # spiral layout fwd = PreXoverItem(i, tpb, step_size, colors[i], self, is_fwd=True) rev = PreXoverItem(i, tpb, step_size, colors[-1 - i], self, is_fwd=False) fwd.setPos(x - inset, y) rev.setPos(x - inset, y) fwd.setTransformOriginPoint((-radius + iw + inset), 0) rev.setTransformOriginPoint((-radius + iw + inset), 0) fwd.setRotation(round(i * tpb % 360, 3)) rev.setRotation(round((i * tpb + mgroove) % 360, 3)) fwd.setBondLineLength(inset + iw) rev.setBondLineLength(inset + iw) fwd_pxis[i] = fwd rev_pxis[i] = rev for i in range(int(step_size) - 1): fwd, next_fwd = fwd_pxis[i], fwd_pxis[i + 1] j = (step_size - 1) - i rev, next_rev = rev_pxis[j], rev_pxis[j - 1] fwd.set3pItem(next_fwd) rev.set3pItem(next_rev) next_fwd.set5pItem(fwd) next_rev.set5pItem(rev) # end def def baseNearestPoint(self, is_fwd, scene_pos): """Summary Args: is_fwd (bool): used to check fwd or rev lists. scene_pos (QPointF): scene coordinate position Returns: PreXoverItem: base nearest to position """ pos_array = self.fwd_pos_array if is_fwd else self.rev_pos_array dist_2 = np.sum((pos_array - (scene_pos.x(), scene_pos.y()))**2, axis=1) near_idx = np.argmin(dist_2) near_pxi = self.fwd_prexover_items[ near_idx] if is_fwd else self.rev_prexover_items[near_idx] # Draw a line p1 = self.mapFromScene(scene_pos.x(), scene_pos.y()) p2 = self.mapFromScene(near_pxi.scenePos()) line = QLineF(p1, p2) self.baseNearLine.setLine(line) def destroyItem(self): """Summary """ fpxis = self.fwd_prexover_items rpxis = self.rev_prexover_items scene = self.scene() for i in range(len(fpxis)): x = fpxis.pop(i) x.destroyItem() x = rpxis.pop(i) x.destroyItem() self.virtual_helix_item = None self.model_part = None scene.removeItem(self.active_wedge_gizmo) self.active_wedge_gizmo = None scene.removeItem(self) # end def def updateTurnsPerRepeat(self): """Summary """ step_size, bases_per_turn, tpb, mgroove = self.virtual_helix_item.getAngularProperties( ) mgroove = -mgroove fpxis = self.fwd_prexover_items rpxis = self.rev_prexover_items for i in range(int(step_size)): fwd = self.fwd_prexover_items[i] rev = self.rev_prexover_items[i] fwd.setRotation(round((i * tpb) % 360, 3)) rev.setRotation(round((i * tpb + mgroove) % 360, 3)) for i in range(int(step_size) - 1): fwd, next_fwd = fpxis[i], fpxis[i + 1] j = (step_size - 1) - i rev, next_rev = rpxis[j], rpxis[j - 1] fwd.set3pItem(next_fwd) rev.set3pItem(next_rev) next_fwd.set5pItem(fwd) next_rev.set5pItem(rev) # end def def partCrossoverSpanAngle(self): """ Returns: int: Crossover span angle from Part. """ return self.virtual_helix_item.partCrossoverSpanAngle() def updateModelActiveBaseInfo(self, pre_xover_info): """Notify model of pre_xover_item hover state. """ self.model_part.setActiveBaseInfo(pre_xover_info)
class ImageViewer(QGraphicsView, QObject): def __init__(self, parent=None): super(ImageViewer, self).__init__(parent) self.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform) #self.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop) self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse) self.setDragMode(QGraphicsView.ScrollHandDrag) #self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded) #self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded) self._scene = ImageViewerScene(self) self.setScene(self._scene) self._create_grid() self._create_grid_lines() self._pixmap = None self._selection_mode = SELECTION_MODE.NONE # polygon selection _polygon_guide_line_pen = QPen(QtGui.QColor(235, 72, 40)) _polygon_guide_line_pen.setWidth(2) _polygon_guide_line_pen.setStyle(QtCore.Qt.DotLine) self._polygon_guide_line = QGraphicsLineItem() self._polygon_guide_line.setVisible(False) self._polygon_guide_line.setPen(_polygon_guide_line_pen) self._scene.addItem(self._polygon_guide_line) self._current_polygon = None # rectangle selection self._box_origin = QPoint() self._box_picker = QRubberBand(QRubberBand.Rectangle, self) # free selection self._current_free_path = None self._is_drawing = False self._last_point_drawn = QPoint() self._current_label = None @property def current_label(self): return self._current_label @current_label.setter def current_label(self, value): self._current_label = value @property def pixmap(self) -> ImagePixmap: return self._pixmap @pixmap.setter def pixmap(self, value: QPixmap): self.selection_mode = SELECTION_MODE.NONE self.resetTransform() if self.pixmap: self._scene.removeItem(self._pixmap) self.remove_annotations() self._pixmap = ImagePixmap() self._pixmap.setPixmap(value) self._pixmap.setOffset(-value.width() / 2, -value.height() / 2) self._pixmap.setTransformationMode(QtCore.Qt.SmoothTransformation) self._pixmap.signals.hoverEnterEventSgn.connect( self.pixmap_hoverEnterEvent_slot) self._pixmap.signals.hoverLeaveEventSgn.connect( self.pixmap_hoverLeaveEvent_slot) self._pixmap.signals.hoverMoveEventSgn.connect( self.pixmap_hoverMoveEvent_slot) self._scene.addItem(self._pixmap) # rect=self._scene.addRect(QtCore.QRectF(0,0,100,100), QtGui.QPen(QtGui.QColor("red"))) # rect.setZValue(1.0) self.fit_to_window() @property def selection_mode(self): return self._selection_mode @selection_mode.setter def selection_mode(self, value): self._polygon_guide_line.hide() self._current_polygon = None self._current_free_path = None self._is_drawing = value == SELECTION_MODE.FREE if value == SELECTION_MODE.NONE: self.enable_items(True) else: self.enable_items(False) self._selection_mode = value def remove_annotations(self): for item in self._scene.items(): if isinstance(item, EditableBox): self._scene.removeItem(item) elif isinstance(item, EditablePolygon): item.delete_polygon() def remove_annotations_by_label(self, label_name): for item in self._scene.items(): if isinstance(item, EditableBox): if item.label and item.label.name == label_name: self._scene.removeItem(item) elif isinstance(item, EditablePolygon): if item.label and item.label.name == label_name: item.delete_polygon() def enable_items(self, value): for item in self._scene.items(): if isinstance(item, EditablePolygon) or isinstance( item, EditableBox): item.setEnabled(value) def _create_grid(self): gridSize = 15 backgroundPixmap = QtGui.QPixmap(gridSize * 2, gridSize * 2) #backgroundPixmap.fill(QtGui.QColor("white")) backgroundPixmap.fill(QtGui.QColor(20, 20, 20)) #backgroundPixmap.fill(QtGui.QColor("powderblue")) painter = QtGui.QPainter(backgroundPixmap) #backgroundColor=QtGui.QColor("palegoldenrod") #backgroundColor=QtGui.QColor(237,237,237) backgroundColor = QtGui.QColor(0, 0, 0) painter.fillRect(0, 0, gridSize, gridSize, backgroundColor) painter.fillRect(gridSize, gridSize, gridSize, gridSize, backgroundColor) painter.end() self._scene.setBackgroundBrush(QtGui.QBrush(backgroundPixmap)) def _create_grid_lines(self): pen_color = QColor(255, 255, 255, 255) pen = QPen(pen_color) pen.setWidth(2) pen.setStyle(QtCore.Qt.DotLine) self.vline = QGraphicsLineItem() self.vline.setVisible(False) self.vline.setPen(pen) self.hline = QGraphicsLineItem() self.hline.setVisible(False) self.hline.setPen(pen) self._scene.addItem(self.vline) self._scene.addItem(self.hline) def wheelEvent(self, event: QWheelEvent): adj = (event.angleDelta().y() / 120) * 0.1 self.scale(1 + adj, 1 + adj) def fit_to_window(self): """Fit image within view.""" if not self.pixmap or not self._pixmap.pixmap(): return #self._pixmap.setTransformationMode(QtCore.Qt.SmoothTransformation) self.fitInView(self._pixmap, QtCore.Qt.KeepAspectRatio) def show_guide_lines(self): if self.hline and self.vline: self.hline.show() self.vline.show() def hide_guide_lines(self): if self.hline and self.vline: self.hline.hide() self.vline.hide() def pixmap_hoverEnterEvent_slot(self): self.show_guide_lines() def pixmap_hoverLeaveEvent_slot(self): self.hide_guide_lines() def pixmap_hoverMoveEvent_slot(self, evt: QGraphicsSceneHoverEvent, x, y): bbox: QRect = self._pixmap.boundingRect() offset = QPointF(bbox.width() / 2, bbox.height() / 2) self.vline.setLine(x, -offset.y(), x, bbox.height() - offset.y()) self.vline.setZValue(1) self.hline.setLine(-offset.x(), y, bbox.width() - offset.x(), y) self.hline.setZValue(1) def mouseMoveEvent(self, evt: QtGui.QMouseEvent) -> None: if self.selection_mode == SELECTION_MODE.BOX: if not self._box_origin.isNull(): self._box_picker.setGeometry( QRect(self._box_origin, evt.pos()).normalized()) elif self.selection_mode == SELECTION_MODE.POLYGON: if self._current_polygon: if self._current_polygon.count > 0: last_point: QPointF = self._current_polygon.last_point self._polygon_guide_line.setZValue(1) self._polygon_guide_line.show() mouse_pos = self.mapToScene(evt.pos()) self._polygon_guide_line.setLine(last_point.x(), last_point.y(), mouse_pos.x(), mouse_pos.y()) else: self._polygon_guide_line.hide() elif self.selection_mode == SELECTION_MODE.FREE and evt.buttons( ) and QtCore.Qt.LeftButton: if self._current_free_path: painter: QPainterPath = self._current_free_path.path() self._last_point_drawn = self.mapToScene(evt.pos()) painter.lineTo(self._last_point_drawn) self._current_free_path.setPath(painter) super(ImageViewer, self).mouseMoveEvent(evt) def mousePressEvent(self, evt: QtGui.QMouseEvent) -> None: if evt.buttons() == QtCore.Qt.LeftButton: if self.selection_mode == SELECTION_MODE.BOX: self.setDragMode(QGraphicsView.NoDrag) self._box_origin = evt.pos() self._box_picker.setGeometry(QRect(self._box_origin, QSize())) self._box_picker.show() elif self._selection_mode == SELECTION_MODE.POLYGON: pixmap_rect: QRectF = self._pixmap.boundingRect() new_point = self.mapToScene(evt.pos()) # consider only the points intothe image if pixmap_rect.contains(new_point): if self._current_polygon is None: self._current_polygon = EditablePolygon() self._current_polygon.signals.deleted.connect( self.delete_polygon_slot) self._scene.addItem(self._current_polygon) self._current_polygon.addPoint(new_point) else: self._current_polygon.addPoint(new_point) elif self._selection_mode == SELECTION_MODE.FREE: # start drawing new_point = self.mapToScene(evt.pos()) pixmap_rect: QRectF = self._pixmap.boundingRect() # consider only the points intothe image if pixmap_rect.contains(new_point): self.setDragMode(QGraphicsView.NoDrag) pen = QPen(QtGui.QColor(235, 72, 40)) pen.setWidth(10) self._last_point_drawn = new_point self._current_free_path = QGraphicsPathItem() self._current_free_path.setOpacity(0.6) self._current_free_path.setPen(pen) painter = QPainterPath() painter.moveTo(self._last_point_drawn) self._current_free_path.setPath(painter) self._scene.addItem(self._current_free_path) else: self.setDragMode(QGraphicsView.ScrollHandDrag) super(ImageViewer, self).mousePressEvent(evt) def mouseReleaseEvent(self, evt: QtGui.QMouseEvent) -> None: if evt.button() == QtCore.Qt.LeftButton: if self.selection_mode == SELECTION_MODE.BOX: roi: QRect = self._box_picker.geometry() roi: QRectF = self.mapToScene(roi).boundingRect() pixmap_rect = self._pixmap.boundingRect() self._box_picker.hide() if pixmap_rect == roi.united(pixmap_rect): rect = EditableBox(roi) rect.label = self.current_label self._scene.addItem(rect) self.selection_mode = SELECTION_MODE.NONE self.setDragMode(QGraphicsView.ScrollHandDrag) elif self.selection_mode == SELECTION_MODE.FREE and self._current_free_path: # create polygon self._current_free_path: QGraphicsPathItem path_rect = self._current_free_path.boundingRect() pixmap_rect = self._pixmap.boundingRect() if pixmap_rect == path_rect.united(pixmap_rect): path = self._current_free_path.path() path_polygon = EditablePolygon() path_polygon.label = self.current_label self._scene.addItem(path_polygon) for i in range(0, path.elementCount(), 10): x, y = path.elementAt(i).x, path.elementAt(i).y path_polygon.addPoint(QPointF(x, y)) self._scene.removeItem(self._current_free_path) self.selection_mode = SELECTION_MODE.NONE self.setDragMode(QGraphicsView.ScrollHandDrag) super(ImageViewer, self).mouseReleaseEvent(evt) def keyPressEvent(self, event: QtGui.QKeyEvent) -> None: if self._current_polygon and event.key() == QtCore.Qt.Key_Space: points = self._current_polygon.points self._current_polygon.label = self.current_label self._current_polygon = None self.selection_mode = SELECTION_MODE.NONE self._polygon_guide_line.hide() self.setDragMode(QGraphicsView.ScrollHandDrag) super(ImageViewer, self).keyPressEvent(event) def delete_polygon_slot(self, polygon: EditablePolygon): self._current_polygon = None self.selection_mode = SELECTION_MODE.NONE self._polygon_guide_line.hide()
class DiagramScene(QGraphicsScene): InsertItem, InsertLine, InsertText, MoveItem = range(4) itemInserted = pyqtSignal(DiagramItem) textInserted = pyqtSignal(QGraphicsTextItem) itemSelected = pyqtSignal(QGraphicsItem) def __init__(self, itemMenu, parent=None): super(DiagramScene, self).__init__(parent) self.myItemMenu = itemMenu self.myMode = self.MoveItem self.myItemType = DiagramItem.Step self.line = None self.textItem = None self.myItemColor = Qt.white self.myTextColor = Qt.black self.myLineColor = Qt.black self.myFont = QFont() def setLineColor(self, color): self.myLineColor = color if self.isItemChange(Arrow): item = self.selectedItems()[0] item.setColor(self.myLineColor) self.update() def setTextColor(self, color): self.myTextColor = color if self.isItemChange(DiagramTextItem): item = self.selectedItems()[0] item.setDefaultTextColor(self.myTextColor) def setItemColor(self, color): self.myItemColor = color if self.isItemChange(DiagramItem): item = self.selectedItems()[0] item.setBrush(self.myItemColor) def setFont(self, font): self.myFont = font if self.isItemChange(DiagramTextItem): item = self.selectedItems()[0] item.setFont(self.myFont) def setMode(self, mode): self.myMode = mode def setItemType(self, type): self.myItemType = type def editorLostFocus(self, item): cursor = item.textCursor() cursor.clearSelection() item.setTextCursor(cursor) if item.toPlainText()=='': self.removeItem(item) item.deleteLater() def mousePressEvent(self, mouseEvent): if (mouseEvent.button() != Qt.LeftButton): return if self.myMode == self.InsertItem: item = DiagramItem(self.myItemType, self.myItemMenu) item.setBrush(self.myItemColor) self.addItem(item) item.setPos(mouseEvent.scenePos()) self.itemInserted.emit(item) elif self.myMode == self.InsertLine: self.line = QGraphicsLineItem(QLineF(mouseEvent.scenePos(), mouseEvent.scenePos())) self.line.setPen(QPen(self.myLineColor, 2)) self.addItem(self.line) elif self.myMode == self.InsertText: textItem = DiagramTextItem() textItem.setFont(self.myFont) textItem.setTextInteractionFlags(Qt.TextEditorInteraction) textItem.setZValue(1000.0) textItem.lostFocus.connect(self.editorLostFocus) textItem.selectedChange.connect(self.itemSelected) self.addItem(textItem) textItem.setDefaultTextColor(self.myTextColor) textItem.setPos(mouseEvent.scenePos()) self.textInserted.emit(textItem) super(DiagramScene, self).mousePressEvent(mouseEvent) def mouseMoveEvent(self, mouseEvent): if self.myMode == self.InsertLine and self.line: newLine = QLineF(self.line.line().p1(), mouseEvent.scenePos()) self.line.setLine(newLine) elif self.myMode == self.MoveItem: super(DiagramScene, self).mouseMoveEvent(mouseEvent) def mouseReleaseEvent(self, mouseEvent): if self.line and self.myMode == self.InsertLine: startItems = self.items(self.line.line().p1()) if len(startItems) and startItems[0] == self.line: startItems.pop(0) endItems = self.items(self.line.line().p2()) if len(endItems) and endItems[0] == self.line: endItems.pop(0) self.removeItem(self.line) self.line = None if len(startItems) and len(endItems) and \ isinstance(startItems[0], DiagramItem) and \ isinstance(endItems[0], DiagramItem) and \ startItems[0] != endItems[0]: startItem = startItems[0] endItem = endItems[0] arrow = Arrow(startItem, endItem) arrow.setColor(self.myLineColor) startItem.addArrow(arrow) endItem.addArrow(arrow) arrow.setZValue(-1000.0) self.addItem(arrow) arrow.updatePosition() self.line = None super(DiagramScene, self).mouseReleaseEvent(mouseEvent) def isItemChange(self, type): for item in self.selectedItems(): if isinstance(item, type): return True return False
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()
class TransitionGraphicsItem(QGraphicsObject): # constant values SQUARE_SIDE = 10 ARROW_SIZE = 12 PEN_NORMAL_WIDTH = 1 PEN_FOCUS_WIDTH = 3 posChanged = pyqtSignal('QGraphicsItem') def __init__(self, data): super(QGraphicsObject, self).__init__() self.transitionData = data self.originLine = None self.destinationLine = None self.arrow = None self.textGraphics = None self.middleHandle = None self.graphicsOrigin = self.transitionData.origin.getGraphicsItem() self.graphicsDestination = self.transitionData.destination.getGraphicsItem() # connect position changed event self.graphicsOrigin.posChanged.connect(self.statePosChanged) self.graphicsDestination.posChanged.connect(self.statePosChanged) self.midPointX = (self.graphicsDestination.scenePos().x() + self.graphicsOrigin.scenePos().x()) / 2.0 self.midPointY = (self.graphicsDestination.scenePos().y() + self.graphicsOrigin.scenePos().y()) / 2.0 self.createOriginLine() self.createDestinationLine() self.createArrow() self.createMiddleHandle() self.createIdTextBox() def statePosChanged(self, state): if self.graphicsOrigin == state: self.createOriginLine() elif self.graphicsDestination == state: self.createDestinationLine() self.createArrow() def createOriginLine(self): if self.originLine == None: self.originLine = QGraphicsLineItem(self.midPointX, self.midPointY, self.graphicsOrigin.scenePos().x(), self.graphicsOrigin.scenePos().y(), self) else: self.originLine.setLine(QLineF(self.midPointX, self.midPointY, self.graphicsOrigin.scenePos().x(), self.graphicsOrigin.scenePos().y())) myLine = self.originLine.line() myLine.setLength(myLine.length() - StateGraphicsItem.NODE_WIDTH / 2) self.originLine.setLine(myLine) def createDestinationLine(self): if self.destinationLine == None: self.destinationLine = QGraphicsLineItem(self.midPointX, self.midPointY, self.graphicsDestination.scenePos().x(), self.graphicsDestination.scenePos().y(), self) else: self.destinationLine.setLine(QLineF(self.midPointX, self.midPointY, self.graphicsDestination.scenePos().x(), self.graphicsDestination.scenePos().y())) myLine = self.destinationLine.line() myLine.setLength(myLine.length() - StateGraphicsItem.NODE_WIDTH / 2) self.destinationLine.setLine(myLine) def createArrow(self): # add an arrow to destination line myLine = self.destinationLine.line() myLine.setLength(myLine.length() - TransitionGraphicsItem.ARROW_SIZE) rotatePoint = myLine.p2() - self.destinationLine.line().p2() rightPointX = rotatePoint.x() * math.cos(math.pi / 6) - rotatePoint.y() * math.sin(math.pi / 6) rightPointY = rotatePoint.x() * math.sin(math.pi / 6) + rotatePoint.y() * math.cos(math.pi / 6) rightPoint = QPointF(rightPointX + self.destinationLine.line().x2(), rightPointY + self.destinationLine.line().y2()) leftPointX = rotatePoint.x() * math.cos(-math.pi / 6) - rotatePoint.y() * math.sin(-math.pi / 6) leftPointY = rotatePoint.x() * math.sin(-math.pi / 6) + rotatePoint.y() * math.cos(-math.pi / 6) leftPoint = QPointF(leftPointX + self.destinationLine.line().x2(), leftPointY + self.destinationLine.line().y2()) polygon = QPolygonF() polygon << rightPoint << leftPoint << self.destinationLine.line().p2() << rightPoint if self.arrow == None: self.arrow = QGraphicsPolygonItem(polygon, self) else: self.arrow.setPolygon(polygon) brush = QBrush(Qt.SolidPattern) brush.setColor(Qt.black) self.arrow.setBrush(brush) def createMiddleHandle(self): # create middle handle if self.middleHandle == None: self.middleHandle = RectHandleGraphicsItem(TransitionGraphicsItem.SQUARE_SIDE, self) self.middleHandle.setFlag(QGraphicsItem.ItemIsMovable) self.middleHandle.setPos(self.midPointX, self.midPointY) def createIdTextBox(self): if self.textGraphics == None: self.textGraphics = IdTextBoxGraphicsItem(self.transitionData.name, self) self.textGraphics.textChanged.connect(self.nameChanged) else: self.textGraphics.setPlainText(self.transitionData.name) textWidth = self.textGraphics.boundingRect().width() self.textGraphics.setPos(self.midPointX - textWidth / 2, self.midPointY + TransitionGraphicsItem.SQUARE_SIDE - (TransitionGraphicsItem.SQUARE_SIDE / 2) + 5) def updateMiddlePoints(self, newPosition): self.midPointX = newPosition.x() self.midPointY = newPosition.y() self.createOriginLine() self.createDestinationLine() self.createArrow() self.createIdTextBox() self.posChanged.emit(self) def nameChanged(self, name): self.transitionData.name = name self.createIdTextBox() def boundingRect(self): if self.middleHandle != None: return self.middleHandle.boundingRect() else: return None def disableInteraction(self): if self.middleHandle is not None: self.middleHandle.setFlag(QGraphicsItem.ItemIsMovable, False) self.middleHandle.disableInteraction()
class AnalogClock(Desklet): def __init__(self): super().__init__() self.center_x = 0 self.center_y = 0 self.radius = 1 self.now = datetime.utcfromtimestamp(0) # build the clock face self.circle = QGraphicsEllipseItem(self.root) self.lines = [] for _ in range(0, 60): self.lines.append(QGraphicsLineItem(self.root)) self.hours_hand = QGraphicsLineItem(self.root) self.minutes_hand = QGraphicsLineItem(self.root) self.seconds_hand = QGraphicsLineItem(self.root) self.hand_circle = QGraphicsEllipseItem(self.root) def set_style(self, style): super().set_style(style) # minute pen = QPen(self.style.foreground_color) pen.setWidth(12) pen.setCapStyle(Qt.RoundCap) self.minutes_hand.setPen(pen) # hour pen = QPen(self.style.foreground_color) pen.setWidth(16) pen.setCapStyle(Qt.RoundCap) self.hours_hand.setPen(pen) # second pen = QPen(self.style.midcolor) pen.setWidth(4) pen.setCapStyle(Qt.RoundCap) self.seconds_hand.setPen(pen) # outer circle pen = QPen(self.style.foreground_color) pen.setWidth(6) self.circle.setPen(pen) # inner circle self.hand_circle.setBrush(QBrush(self.style.background_color)) # minute lines pen = QPen() pen.setCapStyle(Qt.RoundCap) pen.setColor(self.style.midcolor) pen.setWidth(4) bold_pen = QPen() bold_pen.setCapStyle(Qt.RoundCap) bold_pen.setColor(self.style.foreground_color) bold_pen.setWidth(6) for i, line in enumerate(self.lines): if i % 5 == 0: line.setPen(bold_pen) else: line.setPen(pen) self.layout() def set_rect(self, rect): super().set_rect(rect) self.layout() def layout(self): self.center_x = self.rect.left() + self.rect.width() / 2.0 self.center_y = self.rect.top() + self.rect.height() / 2.0 self.radius = min(self.rect.width(), self.rect.height()) / 2.0 - 3.0 self.circle.setRect(self.center_x - self.radius, self.center_y - self.radius, 2 * self.radius, 2 * self.radius) self.hand_circle.setRect(self.center_x - 5, self.center_y - 5, 10, 10) for i, line in enumerate(self.lines): angle = i * 2.0 * math.pi / 60.0 if i % 5 == 0: line.setLine(self.center_x + math.cos(angle) * self.radius * 0.85, self.center_y + math.sin(angle) * self.radius * 0.85, self.center_x + math.cos(angle) * self.radius * 0.95, self.center_y + math.sin(angle) * self.radius * 0.95) else: line.setLine(self.center_x + math.cos(angle) * self.radius * 0.90, self.center_y + math.sin(angle) * self.radius * 0.90, self.center_x + math.cos(angle) * self.radius * 0.95, self.center_y + math.sin(angle) * self.radius * 0.95) self.update(self.now) def update(self, now): self.now = now hour = (self.now.hour / 12.0 + self.now.minute / 60.0 / 12.0) * 2.0 * math.pi - math.pi / 2.0 minute = (self.now.minute / 60.0 + self.now.second / 60.0 / 60.0) * 2.0 * math.pi - math.pi / 2.0 second = (self.now.second / 60.0) * 2.0 * math.pi - math.pi / 2.0 self.set_seconds(second) self.set_minutes(minute) self.set_hours(hour) def set_seconds(self, n): self.seconds_hand.setLine( self.center_x + math.cos(n) * self.radius * 0.0, self.center_y + math.sin(n) * self.radius * 0.0, self.center_x + math.cos(n) * self.radius * 0.8, self.center_y + math.sin(n) * self.radius * 0.8) def set_minutes(self, n): self.minutes_hand.setLine( self.center_x + math.cos(n) * self.radius * 0.0, self.center_y + math.sin(n) * self.radius * 0.0, self.center_x + math.cos(n) * self.radius * 0.8, self.center_y + math.sin(n) * self.radius * 0.8) def set_hours(self, n): self.hours_hand.setLine( self.center_x + math.cos(n) * self.radius * 0.0, self.center_y + math.sin(n) * self.radius * 0.0, self.center_x + math.cos(n) * self.radius * 0.45, self.center_y + math.sin(n) * self.radius * 0.45)