def __init__(self, text=None, datatype: DataType = None, parent: QGraphicsItem = None): super().__init__(parent) self.setFlag( self.ItemSendsScenePositionChanges) # To enable edge adjusting self.setFlag(self.ItemIsSelectable) self.setFlag( self.ItemHasNoContents) # Drawing occurs using child items self.edges = set() """Edges attached to this connection. To change this set use `Connection`'s methods: attach, detach, detachAll. """ self._text_item = TextLineItem(text or "Connection", self) self._stem_item = QGraphicsLineItem(self) self._trigger_item = Trigger(self) self._datatype = datatype or DataType.Unknown self._is_multiple = False """Determines if the connection can have multiple edges coming out of it. By default, it is False for Input and True for Output. """ self._approved_selection = True """Connection is not selectable from outside sources. This flag is set by some internal methods to indicate that a selection is legitimate.""" self.hideText() self.styleChange() self.paletteChange()
def __init__(self, parent, color, text_color, text_start="", margins=(0, 0, 0, 0), dp=1, is_upload=False): self._parent = parent self._color = color self._text_color = text_color self._text_start = text_start self._left, self._top, self._right, self._bottom = margins self._dp = dp self._is_upload = is_upload self._line = QGraphicsLineItem(self._parent) self._line.setZValue(12) pen = QPen(self._color) pen.setWidth(1) pen.setStyle(Qt.DashLine) self._line.setPen(pen) if not self._is_upload: self._text_item = QGraphicsTextItem(self._parent) self._text_item.setZValue(11) self._text_item.setDefaultTextColor(self._text_color) font = self._text_item.font() font_size = 10 * self._dp if font_size > 0: self._text_item.setFont(QFont(font.family(), font_size))
def __init__(self, entity_child, entity_base, parent=None): QGraphicsLineItem.__init__(self, parent) self.child = entity_child self.parent = entity_base self.arrowHead = QtGui.QPolygonF() self.arrowSize = 10.0 self.line_width = 5 self.line_color = QtGui.QColor('black') self.setPen( QtGui.QPen(self.line_color, self.line_width, QtCore.Qt.SolidLine, QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin)) self.setZValue(-1000) # hide arrow behind all widgets ??
def create_diagonal_lines(self): left_top_right_bottom = QGraphicsLineItem( 0, 0, self.model.monitor.screen_width, self.model.monitor.screen_height) right_top_left_bottom = QGraphicsLineItem( self.model.monitor.screen_width, 0, 0, self.model.monitor.screen_height) self.diagonal_lines_layer.add_to_layer(left_top_right_bottom) self.diagonal_lines_layer.add_to_layer(right_top_left_bottom) self.graphics_scene.addItem(left_top_right_bottom) self.graphics_scene.addItem(right_top_left_bottom)
def overlay_line( self, color: int, line_width: int, x1: int, y1: int, x2: int, y2: int, timeout: int, ): pen = QPen(decode_color(color)) pen.setWidthF(max(1.0, line_width / 10)) gfx = QGraphicsLineItem(x1, y1, x2, y2) gfx.setPen(pen) self._finalize_gfx(gfx, timeout)
def _setup_inputs(self): for inp in self._inputs: self.scene().removeItem(inp[0]) self.scene().removeItem(inp[1]) self._inputs = list() n = self.desc.num_inputs for i in range(n): pos = _pin_pos(self.rect(), 0, (2 * i + 1) / (n * 2)) pin = Pin(self, self.desc.get_pin(f'in{i}')) pin.setPos(pos) pen = QPen(Qt.black, 2, c=Qt.RoundCap) r = self.rect() yy = r.height() * (2 * i + 1) / (n * 2) ext = QGraphicsLineItem(pos.x() + Pin._SIZE.width() / 2, pos.y() + Pin._SIZE.height() / 2, r.width() / 3, pos.y() + Pin._SIZE.height() / 2, self) ext.setPen(pen) ext.setFlag(QGraphicsItem.ItemStacksBehindParent) self._inputs.append((pin, ext))
def init_visual_scene_axis(): """ Create two lines that represent the axis of the scene :return: the two Items that are the lines """ # axis.append(QGraphicsLineItem(QPointF(-500, 0), QPointF(500, 0))) # axis.append(QGraphicsLineItem(QPointF(0, 500), QPointF(0, -500))) hor_line = QGraphicsLineItem(-500, 0, 500, 0) hor_line.setPen(initialize_qpen(Qt.green, 1)) vert_line = QGraphicsLineItem(0, 500, 0, -500) vert_line.setPen(initialize_qpen(Qt.green, 1)) return [hor_line, vert_line]
def __init__(self, parent=None): super().__init__(parent) self.setFlag(self.ItemIsMovable) self.setFlag(self.ItemIsSelectable) # Default size and text self._size = QSizeF(0, 115) self._body_item = QGraphicsPathItem(self) self._title_item = TextLineItem("Node", self) self._divider = QGraphicsLineItem(self) self._description_item = TextRectItem("No description", self) # Inputs and outputs self.inputs = [] self.outputs = [] self.messages = SortedList(key=lambda item: item.severity()) self._config_widget = None # Set proper style and color for the item self.styleChange() self.paletteChange()
def draw_2d(self): self.graphicsScene.clear() point, line = [], [] point.append((0, 0)) for i in range(1, 4): point.append( (point[i - 1][0] + (lengths[i - 1] * math.cos(np.deg2rad(-sum(angles[:i])))), point[i - 1][1] + (lengths[i - 1] * math.sin(np.deg2rad(-sum(angles[:i])))))) line.append( QGraphicsLineItem(point[i - 1][0], point[i - 1][1], point[i][0], point[i][1])) self.graphicsScene.addItem(line[i - 1]) self.label_5.setText("[" + str(round(point[3][0], PRECISION)) + ", " + str(round(-point[3][1], PRECISION)) + "]")
def __init__(self, simulator, desc): super().__init__(simulator, desc) self.setRect(QRectF(QPoint(), self._SIZE)) self._inputs = list() r = self.rect() pin = Pin(self, desc.get_pin('out')) pin.setPos(_pin_pos(self.rect(), 1, 0.5)) pin.setZValue(1) pen = QPen(Qt.black, 2, c=Qt.RoundCap) ext1 = QGraphicsLineItem(r.right() + _EXT_LENGTH, r.height() / 2, r.right(), r.height() / 2, self) ext1.setPen(pen) ext1.setFlag(QGraphicsItem.ItemStacksBehindParent) self._setup_inputs()
class MainView(QGraphicsView): def __init__(self, scene): QGraphicsView.__init__(self, scene) self.setMouseTracking(True) self.setFrameShape(QFrame.NoFrame) self.padding_h = 60 self.title_h = 25 self.scene = scene self.gray_pen = QPen(QColor(224, 224, 224), 1) self.timer_id = 0 self.price_model = PriceModel() self.secondary_num = 1 def mouseMoveEvent(self, event): x = event.pos().x() y = event.pos().y() if (x < self.width() - self.padding_h and x > self.padding_h) \ and ((y < self.price_area_height + self.title_h and y > self.title_h) or (y < (self.height() - self.title_h) and y > self.price_area_height + self.title_h * 2)): self.pos_h_line.setVisible(True) self.pos_v_line.setVisible(True) self.pos_h_line.setY(y) self.pos_v_line.setX(x) if y > self.title_h and y < self.price_area_height + self.title_h: self.price.setVisible(True) self.rov.setVisible(True) self.price.setY(y - 7) self.rov.setY(y - 7) price = round( (self.price_model.max_y - (y - self.title_h) / self.price_area_height * self.price_model.diff_y), 2) rov = round((price - self.price_model.yestoday_close) / self.price_model.yestoday_close * 100, 2) price = "{:.2f}".format(price) rov = "{:.2f}%".format(rov) self.price.set_text(price) self.rov.set_text(rov) if y > self.vol.y() and y < self.vol.y() + self.vol.size.height(): vol = int(self.price_model.max_vol - (y - self.vol.y()) / self.vol.size.height() * self.price_model.max_vol) self.vol_l.setVisible(True) self.vol_r.setVisible(True) self.vol_l.setY(y - 7) self.vol_r.setY(y - 7) self.vol_l.set_text(str(vol)) self.vol_r.set_text(str(vol)) else: self.pos_h_line.setVisible(False) self.pos_v_line.setVisible(False) self.price.setVisible(False) self.rov.setVisible(False) self.vol_l.setVisible(False) self.vol_r.setVisible(False) def resizeEvent(self, event): self.scene.setSceneRect(0, 0, self.width(), self.height()) self.price_area_height = (self.height() - self.title_h) * 3 / 5 self.secondary_area_h = self.height() - self.price_area_height - \ self.title_h * (self.secondary_num + 2) self.draw() if self.timer_id: self.killTimer(self.timer_id) self.timer_id = 0 self.timer_id = self.startTimer(100) # def timerEvent(self, event): # self.draw() def draw(self): self.scene.clear() # 画标题 self.title = self.scene.addText('掌趣科技') self.title.setPos(4, 0) # 画补横线 self.scene.addLine(0, self.title_h, self.padding_h, self.title_h, self.gray_pen) self.scene.addLine(self.width() - self.padding_h, self.title_h, self.width(), self.title_h, self.gray_pen) self.scene.addLine(0, self.price_area_height + self.title_h, self.padding_h, self.price_area_height + self.title_h, self.gray_pen) self.scene.addLine(self.width() - self.padding_h, self.price_area_height + self.title_h, self.width(), self.price_area_height + self.title_h, self.gray_pen) # 画背景价格区域背景 self.price_area_background = \ CrossLineBackground(QSizeF(self.width() - self.padding_h * 2, self.price_area_height), self.gray_pen) self.price_area_background.setPos(self.padding_h, self.title_h) # 画价格曲线 pen = QPen(QColor(33, 150, 243)) self.price_area = PriceLine( QSizeF(self.width() - self.padding_h * 2 - 2, self.price_area_height), pen, self.price_model) self.price_area.setPos(self.padding_h + 1, self.title_h) # 画价格区域左右Y轴 self.price_yaxis = PriceYaxis( QSizeF(self.width(), self.price_area_height), self.price_model) self.price_yaxis.setPos(0, self.title_h) # 画游标十字线,以及左右Y轴X轴显示 pos_pen = QPen(QColor(117, 117, 117), 1, Qt.DashDotLine) self.pos_h_line = QGraphicsLineItem(0, 0, self.width() - self.padding_h * 2, 0) self.pos_h_line.setPen(pos_pen) self.pos_h_line.setPos(self.padding_h, self.title_h) self.pos_v_line = QGraphicsLineItem(0, 0, 0, self.height() - self.title_h * 2) self.pos_v_line.setPen(pos_pen) self.pos_v_line.setPos(self.padding_h, self.title_h) self.pos_h_line.setVisible(False) self.pos_v_line.setVisible(False) self.price = PosText(QSizeF(self.padding_h - 2, 15), str(self.price_model.yestoday_close), Qt.AlignRight) self.price.setPos(1, (self.height() - self.title_h) * 3 / 10 - 7.5) self.price.setVisible(False) self.rov = PosText(QSizeF(self.padding_h - 2, 15), '0.00%', Qt.AlignLeft) self.rov.setPos(self.width() - self.padding_h + 1, (self.height() - self.title_h) * 3 / 10 - 7.5) self.rov.setVisible(False) self.vol_l = PosText(QSizeF(self.padding_h - 2, 15), '0', Qt.AlignRight) self.vol_l.setPos(1, (self.height() - self.title_h) * 3 / 10 - 7.5) self.vol_l.setVisible(False) self.vol_r = PosText(QSizeF(self.padding_h - 2, 15), '0', Qt.AlignLeft) self.vol_r.setPos(self.width() - self.padding_h + 1, (self.height() - self.title_h) * 3 / 10 - 7.5) self.vol_r.setVisible(False) # 画副图区域背景 self.second_area_background = \ CrossLineBackground(QSizeF(self.width() - self.padding_h * 2, self.height() - self.price_area_height - self.title_h * 3), self.gray_pen, 2) self.second_area_background.setPos( self.padding_h, self.title_h * 2 + self.price_area_height) # 画副标题一 self.title1 = self.scene.addText('成交量') self.title1.setPos(4, self.title_h + self.price_area_height) # 画副图分割线 self.scene.addLine(0, self.title_h * 2 + self.price_area_height, self.width(), self.title_h * 2 + self.price_area_height, self.gray_pen) self.scene.addLine(0, self.height() - self.title_h, self.width(), self.height() - self.title_h, self.gray_pen) # 画副图一 self.vol = VolumeBar( QSizeF(self.width() - self.padding_h * 2 - 2, self.height() - self.price_area_height - self.title_h * 3), self.price_model) self.vol.setPos(self.padding_h + 1, self.title_h * 2 + self.price_area_height) # 画副图区域左右Y轴 self.second_yaxis = SecondYaxis( QSizeF(self.width(), self.height() - self.title_h * 3 - self.price_area_height), self.price_model.max_vol, 0) self.second_yaxis.setPos(0, self.title_h * 2 + self.price_area_height) self.scene.addItem(self.price_area_background) self.scene.addItem(self.price_area) self.scene.addItem(self.price_yaxis) self.scene.addItem(self.second_area_background) self.scene.addItem(self.vol) self.scene.addItem(self.second_yaxis) self.scene.addItem(self.pos_h_line) self.scene.addItem(self.pos_v_line) self.scene.addItem(self.price) self.scene.addItem(self.rov) self.scene.addItem(self.vol_l) self.scene.addItem(self.vol_r)
def _create_line_indicator(self, addr, item_map, color=Qt.yellow, show_frontier=False, z=None, z_frontier=None): """ Generate a cursor at a given address. """ pos_x = self._get_pos_from_addr(addr) if pos_x is None: return pen = QPen(color) brush = QBrush(color) height = self.height tri_width = 7 tri_height = 4 pos_x = int(pos_x - tri_width / 2) # Center drawing center = pos_x + int(tri_width / 2) pos_y = 0 frontier_width = int(0.15 * max(self.width, self.height)) if show_frontier: # Draw frontier gradients r = QRectF(center - frontier_width, pos_y, frontier_width, height) bg = QLinearGradient(r.topLeft(), r.topRight()) color = Qt.red top_color = QColor(color) top_color.setAlpha(0) bg.setColorAt(0, top_color) bottom_color = QColor(color) bottom_color.setAlpha(180) bg.setColorAt(1, bottom_color) i = QGraphicsRectItem(r, parent=self) i.setPen(Qt.NoPen) i.setBrush(bg) if z_frontier is not None: i.setZValue(z_frontier) item_map.append(i) r = QRectF(center, pos_y, frontier_width, height) bg = QLinearGradient(r.topLeft(), r.topRight()) color = Qt.blue top_color = QColor(color) bg.setColorAt(0, top_color) bottom_color = QColor(color) bottom_color.setAlpha(0) bg.setColorAt(1, bottom_color) i = QGraphicsRectItem(r, parent=self) i.setPen(Qt.NoPen) i.setBrush(bg) if z_frontier is not None: i.setZValue(z_frontier) item_map.append(i) # Draw line i = QGraphicsLineItem(center, 0, center, height, parent=self) i.setPen(pen) if z is not None: i.setZValue(z) item_map.append(i) # Draw top and bottom triangles t = QPolygonF() t.append(QPointF(pos_x, pos_y)) t.append(QPointF(pos_x + tri_width - 1, pos_y)) t.append(QPointF(center, pos_y + tri_height - 1)) t.append(QPointF(pos_x, pos_y)) pos_y += height - 1 b = QPolygonF() b.append(QPointF(pos_x, pos_y)) b.append(QPointF(center, pos_y - tri_height + 1)) b.append(QPointF(pos_x + tri_width - 1, pos_y)) b.append(QPointF(pos_x, pos_y)) for i in [QGraphicsPolygonItem(t, parent=self), QGraphicsPolygonItem(b, parent=self)]: i.setPen(pen) i.setBrush(brush) if z is not None: i.setZValue(z) item_map.append(i)
class ScaleLine: def __init__(self, parent, color, text_color, text_start="", margins=(0, 0, 0, 0), dp=1, is_upload=False): self._parent = parent self._color = color self._text_color = text_color self._text_start = text_start self._left, self._top, self._right, self._bottom = margins self._dp = dp self._is_upload = is_upload self._line = QGraphicsLineItem(self._parent) self._line.setZValue(12) pen = QPen(self._color) pen.setWidth(1) pen.setStyle(Qt.DashLine) self._line.setPen(pen) if not self._is_upload: self._text_item = QGraphicsTextItem(self._parent) self._text_item.setZValue(11) self._text_item.setDefaultTextColor(self._text_color) font = self._text_item.font() font_size = 10 * self._dp if font_size > 0: self._text_item.setFont(QFont(font.family(), font_size)) def set_line(self, max_value=0, resize=False): height = self._parent.size().height() + self._top + self._bottom shift = int(height - height / 1.1) y = -self._top + shift if not self._is_upload: value = 0 max_value = int(max_value / 1.1) megabyte = 1024 * 1024 if max_value > megabyte: value = "{} MB".format(round(max_value / megabyte, 1)) elif max_value > 1024: max_value //= 1024 if max_value >= 10: max_value = max_value // 10 * 10 value = "{} KB".format(max_value) elif max_value > 0: if max_value >= 10: max_value = max_value // 10 * 10 value = "{} B".format(max_value) scale_text = self._text_start if not value \ else "{}{}/s".format(self._text_start, value) font_height = QFontMetrics(self._text_item.font())\ .boundingRect(scale_text).height() x = 10 self._text_item.setPos(x, y - font_height - 10) self._text_item.setPlainText(scale_text) if not resize: return self._line.setLine(QLineF(0, y, self._parent.size().width() + 30, y))
def draw(self): self.scene.clear() # 画标题 self.title = self.scene.addText('掌趣科技') self.title.setPos(4, 0) # 画补横线 self.scene.addLine(0, self.title_h, self.padding_h, self.title_h, self.gray_pen) self.scene.addLine(self.width() - self.padding_h, self.title_h, self.width(), self.title_h, self.gray_pen) self.scene.addLine(0, self.price_area_height + self.title_h, self.padding_h, self.price_area_height + self.title_h, self.gray_pen) self.scene.addLine(self.width() - self.padding_h, self.price_area_height + self.title_h, self.width(), self.price_area_height + self.title_h, self.gray_pen) # 画背景价格区域背景 self.price_area_background = \ CrossLineBackground(QSizeF(self.width() - self.padding_h * 2, self.price_area_height), self.gray_pen) self.price_area_background.setPos(self.padding_h, self.title_h) # 画价格曲线 pen = QPen(QColor(33, 150, 243)) self.price_area = PriceLine( QSizeF(self.width() - self.padding_h * 2 - 2, self.price_area_height), pen, self.price_model) self.price_area.setPos(self.padding_h + 1, self.title_h) # 画价格区域左右Y轴 self.price_yaxis = PriceYaxis( QSizeF(self.width(), self.price_area_height), self.price_model) self.price_yaxis.setPos(0, self.title_h) # 画游标十字线,以及左右Y轴X轴显示 pos_pen = QPen(QColor(117, 117, 117), 1, Qt.DashDotLine) self.pos_h_line = QGraphicsLineItem(0, 0, self.width() - self.padding_h * 2, 0) self.pos_h_line.setPen(pos_pen) self.pos_h_line.setPos(self.padding_h, self.title_h) self.pos_v_line = QGraphicsLineItem(0, 0, 0, self.height() - self.title_h * 2) self.pos_v_line.setPen(pos_pen) self.pos_v_line.setPos(self.padding_h, self.title_h) self.pos_h_line.setVisible(False) self.pos_v_line.setVisible(False) self.price = PosText(QSizeF(self.padding_h - 2, 15), str(self.price_model.yestoday_close), Qt.AlignRight) self.price.setPos(1, (self.height() - self.title_h) * 3 / 10 - 7.5) self.price.setVisible(False) self.rov = PosText(QSizeF(self.padding_h - 2, 15), '0.00%', Qt.AlignLeft) self.rov.setPos(self.width() - self.padding_h + 1, (self.height() - self.title_h) * 3 / 10 - 7.5) self.rov.setVisible(False) self.vol_l = PosText(QSizeF(self.padding_h - 2, 15), '0', Qt.AlignRight) self.vol_l.setPos(1, (self.height() - self.title_h) * 3 / 10 - 7.5) self.vol_l.setVisible(False) self.vol_r = PosText(QSizeF(self.padding_h - 2, 15), '0', Qt.AlignLeft) self.vol_r.setPos(self.width() - self.padding_h + 1, (self.height() - self.title_h) * 3 / 10 - 7.5) self.vol_r.setVisible(False) # 画副图区域背景 self.second_area_background = \ CrossLineBackground(QSizeF(self.width() - self.padding_h * 2, self.height() - self.price_area_height - self.title_h * 3), self.gray_pen, 2) self.second_area_background.setPos( self.padding_h, self.title_h * 2 + self.price_area_height) # 画副标题一 self.title1 = self.scene.addText('成交量') self.title1.setPos(4, self.title_h + self.price_area_height) # 画副图分割线 self.scene.addLine(0, self.title_h * 2 + self.price_area_height, self.width(), self.title_h * 2 + self.price_area_height, self.gray_pen) self.scene.addLine(0, self.height() - self.title_h, self.width(), self.height() - self.title_h, self.gray_pen) # 画副图一 self.vol = VolumeBar( QSizeF(self.width() - self.padding_h * 2 - 2, self.height() - self.price_area_height - self.title_h * 3), self.price_model) self.vol.setPos(self.padding_h + 1, self.title_h * 2 + self.price_area_height) # 画副图区域左右Y轴 self.second_yaxis = SecondYaxis( QSizeF(self.width(), self.height() - self.title_h * 3 - self.price_area_height), self.price_model.max_vol, 0) self.second_yaxis.setPos(0, self.title_h * 2 + self.price_area_height) self.scene.addItem(self.price_area_background) self.scene.addItem(self.price_area) self.scene.addItem(self.price_yaxis) self.scene.addItem(self.second_area_background) self.scene.addItem(self.vol) self.scene.addItem(self.second_yaxis) self.scene.addItem(self.pos_h_line) self.scene.addItem(self.pos_v_line) self.scene.addItem(self.price) self.scene.addItem(self.rov) self.scene.addItem(self.vol_l) self.scene.addItem(self.vol_r)
def shape(self): path = QGraphicsLineItem.shape(self) path.addPolygon(self.arrowHead) return path
class Node(SchemeItem): """The main component of the graph scene. A node represents a single vertex of the graph, with its associated information and inputs/outputs. """ def __init__(self, parent=None): super().__init__(parent) self.setFlag(self.ItemIsMovable) self.setFlag(self.ItemIsSelectable) # Default size and text self._size = QSizeF(0, 115) self._body_item = QGraphicsPathItem(self) self._title_item = TextLineItem("Node", self) self._divider = QGraphicsLineItem(self) self._description_item = TextRectItem("No description", self) # Inputs and outputs self.inputs = [] self.outputs = [] self.messages = SortedList(key=lambda item: item.severity()) self._config_widget = None # Set proper style and color for the item self.styleChange() self.paletteChange() # Operations ======================================================================================================= def showConnectionText(self): for input in self.inputs: input.showText() for output in self.outputs: output.showText() def hideConnectionText(self): for input in self.inputs: input.hideText() for output in self.outputs: output.hideText() # Input/output management ========================================================================================== def addInput(self, obj: Input): self.insertInput(len(self.inputs), obj) def insertInput(self, index, obj: Input): index = clamp(index, 0, len(self.inputs)) self.inputs.insert(index, obj) obj.setParentItem(self) self._updateInputPositions() def removeInput(self, index): removed = self.inputs.pop(index) removed.setParentItem(None) self._updateInputPositions() return removed def addOutput(self, obj: Output): self.insertOutput(len(self.outputs), obj) def insertOutput(self, index, obj: Output): index = clamp(index, 0, len(self.outputs)) self.outputs.insert(index, obj) obj.setParentItem(self) self._updateOutputPositions() def removeOutput(self, index): removed = self.outputs.pop(index) removed.setParentItem(None) self._updateOutputPositions() return removed # Message management =============================================================================================== def addMessage(self, obj: Message): self.messages.add(obj) obj.setParentItem(self) self._updateMessagePositions() def removeMessage(self, index): removed = self.messages.pop(index) removed.setParentItem(None) self._updateMessagePositions() return removed # Member variables ================================================================================================= def setTitle(self, title): self._title_item.setText(title) def setDescription(self, description): self._description_item.setText(description) def setConfigWidget(self, w): self._config_widget = w def size(self): return self._size def title(self): return self._title_item.text() def description(self): return self._description_item.text() def configWidget(self): """Return a widget for configuring this node, or None if it does not exist.""" return self._config_widget # Updating functions =============================================================================================== def _updateInputPositions(self): padding = self.style().pixelMetric(Style.NodeConnectionPadding) i = 0 for item in self.inputs: item.setPos(0, padding * (2 * i + 1)) i += 1 def _updateOutputPositions(self): padding = self.style().pixelMetric(Style.NodeConnectionPadding) i = 0 for item in self.outputs: item.setPos(self.size().width(), padding * (2 * i + 1)) i += 1 def _updateMessagePositions(self): padding = self.style().pixelMetric(Style.NodeMessagePadding) icon_size = self.style().pixelMetric(Style.MessageIconSize) i = 0 for msg in self.messages: msg.setPos( 0, self.size().height() + padding * (i + 1) + icon_size * i) i += 1 # Geometry and drawing ============================================================================================= def boundingRect(self) -> QRectF: frame_width = self.style().pixelMetric(Style.NodeFrameWidth) return QRectF(QPointF(-frame_width / 2, -frame_width / 2), self.size() + QSizeF(frame_width, frame_width)) def shape(self): frame_corner_radius = self.style().pixelMetric( Style.NodeFrameCornerRadius) path = QPainterPath() path.addRoundedRect(QRectF(QPointF(0, 0), self.size()), frame_corner_radius, frame_corner_radius) return path def paint(self, painter: QPainter, option, widget=...) -> None: pass # Event handlers =================================================================================================== def styleChange(self): self.prepareGeometryChange() style = self.style() # Size and Frame frame_width = style.pixelMetric(Style.NodeWidth) self._size.setWidth(frame_width) self._body_item.setPen(style.framePen(self.palette())) path = QPainterPath() path.addRoundedRect(QRectF(QPointF(), self.size()), style.pixelMetric(Style.NodeFrameCornerRadius), style.pixelMetric(Style.NodeFrameCornerRadius)) self._body_item.setPath(path) # Title item title_font = style.font(Style.NodeTitleFont) title_metrics = QFontMetricsF(title_font) padding = style.pixelMetric(Style.NodeFrameTextPadding) self._title_item.setFont(title_font) self._title_item.setPos(padding, padding) self._title_item.moveBy(0, title_metrics.capHeight()) self._title_item.setMaximumWidth(frame_width - padding * 2) # Divider item div_margin = style.pixelMetric(Style.NodeDividerTextMargin) div_voffset = padding + title_metrics.capHeight( ) + title_metrics.descent() + div_margin self._divider.setLine(0, div_voffset, frame_width, div_voffset) self._divider.setPen(style.framePen(self.palette())) # Description item self._description_item.setFont(style.font(Style.NodeDescriptionFont)) self._description_item.setPos(padding, div_voffset + div_margin) self._description_item.setFrame( QRectF( QPointF(0, 0), QSizeF( px(frame_width) - padding * 2, (px(self.size().height()) - padding) - (div_voffset + padding)))) def paletteChange(self): self._body_item.setPen(self.style().framePen(self.palette())) self._body_item.setBrush(self.palette().base()) self._title_item.setBrush(self.palette().text()) self._description_item.setBrush(self.palette().text()) self._divider.setPen(self.style().framePen(self.palette())) self.update(self.boundingRect()) def itemChange(self, change, value): if change == self.ItemSelectedHasChanged: selection = self.scene().selection() if len(selection.nodes) == 1: for edge in selection.edges: edge.autoSelect() # Update selection status for all connections for input in self.inputs: input.autoSelect() for output in self.outputs: output.autoSelect() return super().itemChange(change, value) def mouseDoubleClickEvent(self, event): if self.configWidget() is not None: view = event.widget().parent() view.configRequested.emit(self) # Serialization ==================================================================================================== def serialize(self) -> dict: return { "title": self.title(), "description": self.description(), "inputs": self.inputs, "outputs": self.outputs, "messages": list(self.messages), "position": inch(self.pos()) } @classmethod def deserialize(cls, data: dict): obj = cls() obj.setTitle(data["title"]) obj.setDescription(data["description"]) obj.setPos(px(data["position"])) for i in range(len(obj.inputs)): obj.removeInput(0) for i in range(len(obj.outputs)): obj.removeOutput(0) for i in range(len(obj.messages)): obj.removeMessage(0) for input in data["inputs"]: obj.addInput(input) for output in data["outputs"]: obj.addOutput(output) for message in data["messages"]: obj.addMessage(message) return obj
def _generate_map_items(self, **kwargs): # pylint: disable=unused-argument """ Generate the feature map items (memory region blocks, separating lines, etc). """ cfb = self.instance.cfb.am_obj if cfb is None: return for item in self._map_items: self.scene().removeItem(item) self._map_items.clear() self._calculate_memory_region_offsets() func_color = Conf.feature_map_color_regular_function data_color = Conf.feature_map_color_data unknown_color = Conf.feature_map_color_unknown delimiter_color = Conf.feature_map_color_delimiter offset = 0 current_region = None for addr, obj in cfb.ceiling_items(): if obj.size is None: continue # Are we in a new region? new_region = False if current_region is None or not current_region.addr <= addr < current_region.addr + current_region.size: try: current_region_addr = next( self._addr_to_region.irange(maximum=addr, reverse=True)) except StopIteration: # FIXME: it's not within any of the known regions # we should fix this in the future. for now, let's make sure it does not crash continue current_region = self._addr_to_region[current_region_addr] new_region = True if new_region: r = self._get_region_rect(current_region) pos = r.topLeft().x() pen = QPen(delimiter_color) hpw = pen.width() / 2 item = QGraphicsLineItem(pos, hpw, pos, self._height - hpw, parent=self) item.setPen(pen) item.setZValue(self.ZVALUE_SEPARATOR) self._map_items.append(item) # Clip item to possibly truncated region size adjusted_region_size = self._get_adjusted_region_size( current_region) adjusted_size = min( obj.size, current_region.addr + adjusted_region_size - addr) if adjusted_size <= 0: # Item falls outside truncated region. Drop the item. continue r = self._get_offset_size_rect(offset, adjusted_size) offset += adjusted_size if isinstance(obj, MemoryData): brush = QBrush(data_color) elif isinstance(obj, Block): # TODO: Check if it belongs to a function or not brush = QBrush(func_color) else: brush = QBrush(unknown_color) item = QGraphicsRectItem(r, parent=self) item.setPen(Qt.NoPen) item.setBrush(brush) self._map_items.append(item)
class Connection(SchemeItem): """Connection is an input or output from a Node.""" EdgeDragMimeType = Trigger.DragMimeType def __init__(self, text=None, datatype: DataType = None, parent: QGraphicsItem = None): super().__init__(parent) self.setFlag( self.ItemSendsScenePositionChanges) # To enable edge adjusting self.setFlag(self.ItemIsSelectable) self.setFlag( self.ItemHasNoContents) # Drawing occurs using child items self.edges = set() """Edges attached to this connection. To change this set use `Connection`'s methods: attach, detach, detachAll. """ self._text_item = TextLineItem(text or "Connection", self) self._stem_item = QGraphicsLineItem(self) self._trigger_item = Trigger(self) self._datatype = datatype or DataType.Unknown self._is_multiple = False """Determines if the connection can have multiple edges coming out of it. By default, it is False for Input and True for Output. """ self._approved_selection = True """Connection is not selectable from outside sources. This flag is set by some internal methods to indicate that a selection is legitimate.""" self.hideText() self.styleChange() self.paletteChange() # Working with edges =============================================================================================== def attach(self, edge): """Attach an edge to this connection.""" raise NotImplementedError def detach(self, edge): """Detach an edge from this connection. Other connection of this edge is unaffected. """ raise NotImplementedError def detachAll(self): """Detach all edges.""" raise NotImplementedError # Operations ======================================================================================================= def showText(self): self._text_item.setVisible(True) def hideText(self): self._text_item.setVisible(False) # Member access ==================================================================================================== def text(self): return self._text_item.text() def dataType(self): return self._datatype def isMultiple(self): return self._is_multiple def setText(self, text): self._text_item.setText(text) def setDataType(self, datatype): self._datatype = datatype for edge in self.edges: # Verify that all edges are fine with changing the data type. edge.checkDataType() def setMultiple(self, multiple: bool): self._is_multiple = multiple # Geometry and drawing ============================================================================================= def stemRoot(self): """Return position of stem's root (where the stem connects to the node) in local inches.""" raise NotImplementedError def stemTip(self): """Return position of stem's root (where the stem connects to the edge) in local inches.""" raise NotImplementedError def boundingRect(self): return QRectF() def paint(self, painter: QPainter, option, widget=...) -> None: pass # Style and palette ================================================================================================ def styleChange(self): super().styleChange() style = self.style() self._text_item.setFont(style.font(Style.ConnectionTextFont)) self._text_item.setMaximumWidth( style.pixelMetric(Style.ConnectionTextLength)) self._stem_item.setPen(style.edgePen(self.palette())) def paletteChange(self): super().paletteChange() text_bg = self.palette().background().color() text_bg.setAlpha(196) self._text_item.setBackgroundBrush(text_bg) self._text_item.setBrush(self.palette().text()) self._stem_item.setPen(self.style().edgePen(self.palette())) # Events =========================================================================================================== def itemChange(self, change, value): """A function that runs every time some change happens to the connection. This processes position changes by adjusting all the edges and forwards the arguments to the superclass. """ # ItemScenePositionHasChanged ---------------------------------------------------------------------------------- if change == self.ItemScenePositionHasChanged or change == self.ItemVisibleHasChanged: for edge in self.edges: edge.adjust() # ItemSelectedChange ------------------------------------------------------------------------------------------- if change == self.ItemSelectedChange: # Connection is not selectable from outside sources. Selection is approved only if the corresponding flag is # set (it is set by internal methods). if not self._approved_selection: value = self.isSelected() self._approved_selection = False return super().itemChange(change, value) def autoSelect(self): """This function updates connection selection. The connection asks the edges to autoSelect themselves, and they in turn call self.autoSelectFromEdge. """ for edge in self.edges: edge.autoSelect() def autoSelectFromEdge(self): """This function updates connection selection status when an edge changes selection.""" value = False if self.parentItem().isSelected(): for edge in self.edges: if edge.isSelected(): value = True break if value != self.isSelected(): self._approved_selection = True self.setSelected(value) # Serialization ==================================================================================================== def serialize(self) -> dict: return { "text": self.text(), "datatype": self.dataType(), "is_multiple": self.isMultiple(), } @classmethod def deserialize(cls, data: dict): obj = cls() obj.setText(data["text"]) obj.setDataType(data["datatype"]) obj.setMultiple(data["is_multiple"]) return obj # Drawing edges ==================================================================================================== # The heavy lifting such as detecting when the edge started being drawn, mouse moving and data transfers are handled # by a member item called self._trigger_item. Connection handles the logic. def edgeDragStart(self): """Called when user tries to drag an edge from this connection.""" if not self.isMultiple(): self.detachAll() self.scene().edgeDragStart(self) def edgeDragAccept(self) -> bool: """Called when a new edge is being dragged into the drop zone. Returns True or False depending on whether the dragged edge should be accepted or not. """ raise NotImplementedError def edgeDragDrop(self): """Called when a new edge has been dragged and dropped into this connection's trigger zone. This function is responsible for actually connecting the two nodes with a real edge. """ if not self.isMultiple(): self.detachAll() def edgeDragStop(self): """Conclude the edge drawing process. This function is called when a drag process from this connection has stopped, regardless of the outcome. (For example, when the edge drawing has failed). Default implementation calls the scene-wide version of this function. """ self.scene().edgeDragStop()