def draw_word_boxes(self, word_boxes: list) -> None: """Sets up and draws translated words and their corresponding frames :param word_boxes: list of WordBox namedtuples, each representing a word and frame to be drawn. ex: [WordBox('word'=str, 'geometry'=[(int, int)], WordBox('word'=str, 'geometry'=[(int, int)]) """ for wPoly in self.__word_polygons: self.__scene.removeItem(wPoly[0]) self.__scene.removeItem(wPoly[1]) self.__word_polygons = [] for word_box in word_boxes: points = list(map(lambda x: QPoint(x[0], x[1]), word_box.geometry)) text = QGraphicsTextItem(word_box.word) text.setOpacity(0) text.setAcceptHoverEvents(False) font = QFont() font.setPixelSize(abs(points[0].y() - points[3].y())) text.setFont(font) w = text.boundingRect().width() h = text.boundingRect().height() text.setPos( points[0].x() + abs(points[0].x() - points[1].x()) / 2 - w / 2, points[0].y() + abs(points[0].y() - points[3].y()) / 2 - h / 2) frame = WordPolygon(QPolygonF(QPolygon(points)), text) self.__word_polygons.append([text, frame]) self.__scene.addItem(frame) self.__scene.addItem(text)
def texting(self): # image = self.image.copy() # cv2.putText(image, # self.ui.textEdit_edit.toPlainText(), # (10, 10), # cv2.FONT_HERSHEY_SIMPLEX, # 0.6, (0, 0, 0), lineType=cv2.LINE_AA) # qpixmap = mat2qpixmap(image) # self.scene_edit.clear() # self.scene_edit.addPixmap(qpixmap) # self.scene_edit.update() text = self.ui.textEdit_edit.toPlainText() self.scene_edit.clear() self.scene_edit.addPixmap(self.qPixmap) qText = QGraphicsTextItem() qText.setDefaultTextColor(QColor(0, 0, 0)) qText.setPlainText(text) qText.setFont(self.font) qPixmapWidth = self.qPixmap.width() qTextWidth = qText.boundingRect().width() qPixmapHeight = self.qPixmap.height() qTextHeigt = qText.boundingRect().height() posX = (qPixmapWidth - qTextWidth) / 2 + self.posX_default posY = (qPixmapHeight - qTextHeigt) / 2 + self.posY_default qText.setPos(posX, posY) self.scene_edit.addItem(qText) self.scene_edit.update()
def update_items(self): rect_cls = QGraphicsRectItem self.item = rect_cls(0, 0, self.width, self.row_h) seq_width = 0 nopen = QPen(Qt.NoPen) font = QFont(self.ftype, self.fsize) for i, letter in enumerate(self.seq): width = self.col_w rectitem = rect_cls(0, 0, width, self.row_h, parent=self.item) rectitem.setX(seq_width) # to give correct X to children item rectitem.setBrush(self.bg_col[letter]) rectitem.setPen(nopen) # write letter if enough space if width >= self.fsize: text = None if self.style == "codon": text = QGraphicsTextItem(parent=rectitem) text.setHtml(self.get_html(i, self.fg_col[letter])) else: text = QGraphicsSimpleTextItem(letter, parent=rectitem) text.setBrush(QBrush(QColor(self.fg_col[letter]))) text.setFont(font) # Center text according to rectitem size txtw = text.boundingRect().width() txth = text.boundingRect().height() text.setPos((width - txtw) / 2, (self.row_h - txth) / 2) seq_width += width self.width = seq_width
def createImage(self, transform): if self.type == DemoTextItem.DYNAMIC_TEXT: return None sx = min(transform.m11(), transform.m22()) sy = max(transform.m22(), sx) textItem = QGraphicsTextItem() textItem.setHtml(self.text) textItem.setTextWidth(self.textWidth) textItem.setFont(self.font) textItem.setDefaultTextColor(self.textColor) textItem.document().setDocumentMargin(2) w = textItem.boundingRect().width() h = textItem.boundingRect().height() image = QImage(int(w * sx), int(h * sy), QImage.Format_ARGB32_Premultiplied) image.fill(QColor(0, 0, 0, 0).rgba()) painter = QPainter(image) painter.scale(sx, sy) style = QStyleOptionGraphicsItem() textItem.paint(painter, style, None) return image
class MenuButton(QGraphicsRectItem): def __init__(self, name, height=200, width=50, parent=None): '''name: str ''' super().__init__(parent) # Set locked atribute self.locked = False # create my signal self.s = ButtonSignal() # draw the rect self.setRect(0, 0, height, width) # change color of rect brush = QBrush() brush.setStyle(Qt.SolidPattern) brush.setColor(Qt.darkCyan) self.setBrush(brush) # draw the text self.text = QGraphicsTextItem(name, self) x_pos = self.rect().width() / 2 - self.text.boundingRect().width() / 2 y_pos = self.rect().height() / 2 - self.text.boundingRect().height() / 2 self.text.setPos(x_pos, y_pos) # allow responding to hover events self.setAcceptHoverEvents(True) def mousePressEvent(self, event): if not self.locked: self.s.clicked.emit() def hoverEnterEvent(self, event): '''event: QGraphicsSceneHoverEvent ''' # change color to cyan brush = QBrush() brush.setStyle(Qt.SolidPattern) brush.setColor(Qt.cyan) self.setBrush(brush) def hoverLeaveEvent(self, event): '''event: QGraphicsSceneHoverEvent ''' # change color to dark cyan brush = QBrush() brush.setStyle(Qt.SolidPattern) brush.setColor(Qt.darkCyan) self.setBrush(brush) def unlock(self): self.setOpacity(1) self.locked = False def lock(self): self.setOpacity(0.3) self.locked = True
def printAttributes(self, background, border, text): """ Prints the attributes of the node The attributes are a key, value pair :param background: background color of the node :param border: border color for the node :param text: text color for the node """ y = self.y() + self.headerHeight x = self.x() self.attributesHeight = 0 for k, v in self.node.attributes.items(): key = QGraphicsTextItem() key.setFont(Configuration.font) key.setDefaultTextColor(QColor(text)) key.setTextWidth(100) key.setPlainText(k) keyHeight = int(key.boundingRect().height() / 20 + 0.5) * 20 value = QGraphicsTextItem() value.setFont(Configuration.font) value.setDefaultTextColor(QColor(text)) value.setTextWidth(100) value.setPlainText(v) valueHeight = int(value.boundingRect().height() / 20 + 0.5) * 20 height = valueHeight if valueHeight > keyHeight else keyHeight keyRect = QGraphicsRectItem() keyRect.setRect(x, y, 100, height) valueRect = QGraphicsRectItem() valueRect.setRect(x + 100, y, 100, height) keyRect.setBrush(QBrush(QColor(background))) valueRect.setBrush(QBrush(QColor(background))) keyRect.setPen(QPen(QColor(border), 2)) valueRect.setPen(QPen(QColor(border), 2)) key.setPos(x, y - 2) value.setPos(x + 100, y - 2) self.attributes.addToGroup(keyRect) self.attributes.addToGroup(valueRect) self.attributes.addToGroup(key) self.attributes.addToGroup(value) y = y + height self.attributesHeight += height self.addToGroup(self.attributes)
class ActionItem(GraphicsItem): def __init__(self, model_item: SimulatorItem, parent=None): super().__init__(model_item=model_item, parent=parent) self.setFlag(QGraphicsTextItem.ItemIsPanel, True) self.text = QGraphicsTextItem(self) self.text.setFont(GraphicsItem.font) def update_flags(self): if self.scene().mode == 0: self.set_flags(is_selectable=True, is_movable=True, accept_hover_events=True, accept_drops=True) def update_position(self, x_pos, y_pos): self.setPos(x_pos, y_pos) start_x = (self.scene().items_width() - self.labels_width()) / 2 self.number.setPos(start_x, 0) start_x += self.number.boundingRect().width() self.text.setPos(start_x, 0) width = self.scene().items_width() self.prepareGeometryChange() self.bounding_rect = QRectF(0, 0, width, self.childrenBoundingRect().height() + 5) #super().update_position(x_pos, y_pos) def labels_width(self): width = self.number.boundingRect().width() #width += 5 width += self.text.boundingRect().width() return width
class MyRect(QGraphicsRectItem): def __init__(self, parent=None): super(MyRect, self).__init__(parent) self.text_item = QGraphicsTextItem('My Text Here', self) self.value_item = QGraphicsTextItem('My Value Here', self) self.text_item.setDefaultTextColor(QColor(Qt.blue)) self.value_item.setDefaultTextColor(QColor(Qt.red)) self.value_item.setPos(self.text_item.boundingRect().bottomLeft()) width = max(self.text_item.boundingRect().width(), self.value_item.boundingRect().width()) height = self.text_item.boundingRect().height( ) + self.value_item.boundingRect().height() self.setRect(50, 50, width, height)
class ActionItem(GraphicsItem): def __init__(self, model_item: SimulatorItem, parent=None): super().__init__(model_item=model_item, parent=parent) self.setFlag(QGraphicsTextItem.ItemIsPanel, True) self.text = QGraphicsTextItem(self) self.text.setFont(self.font) def update_flags(self): if self.scene().mode == 0: self.set_flags(is_selectable=True, is_movable=True, accept_hover_events=True, accept_drops=True) def update_position(self, x_pos, y_pos): self.setPos(x_pos, y_pos) start_x = (self.scene().items_width() - self.labels_width()) / 2 self.number.setPos(start_x, 0) start_x += self.number.boundingRect().width() self.text.setPos(start_x, 0) width = self.scene().items_width() self.prepareGeometryChange() self.bounding_rect = QRectF(0, 0, width, self.childrenBoundingRect().height() + 5) def labels_width(self): width = self.number.boundingRect().width() width += self.text.boundingRect().width() return width
class PointOfInterest: def __init__(self, **kwargs): super().__init__() self.location = MapPoint() self.__dict__.update(kwargs) self.text = QGraphicsTextItem() self.text.setHtml("<font color='{}' size='{}'>{}</font>".format( self.location.color.name(), 1 + self.location.size, '\u272a' + self.location.text)) self.text.setZValue(2) self.text.setPos(self.location.x, self.location.y) def update_(self, scale): self.text.setScale(scale) self.text.setPos( self.location.x - self.text.boundingRect().width() * 0.05 * scale, self.location.y - self.text.boundingRect().height() / 2 * scale)
class MainWindow(QMainWindow): def __init__(self, parent=None): super().__init__(parent) self.view = VideoGraphicsView() self.setCentralWidget(self.view) self.view.videoItem.nativeSizeChanged.connect( self.handle_native_size_changed) url = QUrl( "https://www.learningcontainer.com/wp-content/uploads/2020/05/sample-mp4-file.mp4" ) self.view.player.setMedia(QMediaContent(url)) self.view.player.play() self.resize(640, 480) self.text_item = QGraphicsTextItem(self.view.videoItem) self.text_item.setHtml( """<div style="color: #41CD52; font-weight: bold; font-size: 20px;">Qt is awesome</div>""" ) self.text_item.hide() self.animation = QVariantAnimation() self.animation.setDuration(1000) self.animation.setStartValue(QPointF(0.0, 0.0)) self.animation.setEndValue(QPointF(0.0, 0.0)) self.animation.valueChanged.connect(self.text_item.setPos) self.animation.finished.connect(self.start_of_start_animation) def handle_native_size_changed(self): self.start_of_start_animation() def start_of_start_animation(self): w = self.view.videoItem.size().width() - self.text_item.boundingRect( ).width() h = self.view.videoItem.size().height() - self.text_item.boundingRect( ).height() end_pos_x = random.uniform(0, w) end_pos_y = random.uniform(0, h) self.animation.setStartValue(self.animation.endValue()) self.animation.setEndValue(QPointF(end_pos_x, end_pos_y)) self.text_item.show() self.animation.start()
def loadText(self): self.scene.clear() noImageTxt = QGraphicsTextItem() noImageTxt.setPlainText( u"Wählen Sie ein repräsentatives Luftbild aus ...") self.rect = noImageTxt.boundingRect() self.scene.addItem(noImageTxt) self.scene.setSceneRect(self.rect) self.uiRepresentativeImageView.fitInView(self.rect, Qt.KeepAspectRatio)
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 initUi(self): pos = QPointF(0, 0) for i in range(0, self.length): #每一秒有1個刻度,總共 length 秒 t1 = QGraphicsTextItem('|', self) t1.setDefaultTextColor(QColor(Qt.blue)) t1.setPos(pos) t2 = QGraphicsTextItem('{}'.format(i + int(self.start)), self) t2.setDefaultTextColor(QColor(Qt.blue)) t2.setPos(t1.boundingRect().bottomLeft() + pos) pos.setX(pos.x() + int(self.w / self.length)) self.setRect(0, 0, self.w, self.h)
def add_text(self, text, item_list): items = [] text_item = QGraphicsTextItem(text) text_item.setFont(ComparisionChart.arial_font) text_item.setPos(0,0) items.append(text_item) next_y = text_item.boundingRect().height()+5 html = "<br />".join(sorted(item_list)) text_item = QGraphicsTextItem() text_item.setHtml(html) text_item.setFont(ComparisionChart.font) text_item.setPos(5,next_y) items.append(text_item) group = self.scene.createItemGroup(items) return group
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 label_maker(node, label): """Form correctly formatted leaf label.""" face = QGraphicsTextItem() face.setHtml(label) # Create parent RectItem with TextItem in center fbox = face.boundingRect() rect = QGraphicsRectItem(0, 0, fbox.width(), fbox.height()) rbox = rect.boundingRect() face.setPos(rbox.x(), rbox.center().y() - fbox.height() / 2) # Remove border rect.setPen(QPen(QtCore.Qt.NoPen)) # Set as parent item so DynamicItemFace can use .rect() method face.setParentItem(rect) return rect
def update_view(self, steno_layout: StenoLayout, stroke: List[str]): ''' Updates the layout display for the provided layout and stroke ''' scene = self.graphics_scene pen = self._scene_pen font = QFont(steno_layout.font) if steno_layout.font else QFont() # Clear all items from the scene. Could be more efficient... scene.clear() scene.setBackgroundBrush(QBrush(QColor(steno_layout.background_color))) for key in steno_layout.keys: path = LayoutDisplayView._create_key_path(steno_layout, key) brush = LayoutDisplayView._get_key_path_brush(key, (key.name in stroke)) pen.setColor(QColor(key.stroke_color)) # Add the key path before its label, then center the label scene.addPath(path, pen, brush) if key.label: label = QGraphicsTextItem(key.label) label.setFont(font) label.setDefaultTextColor(QColor(key.font_color)) label_rect = label.boundingRect() label_rect.moveCenter(path.boundingRect().center()) label.setPos(label_rect.x(), label_rect.y()) scene.addItem(label) # Scene rects don't shrink when items are removed, so need to manually # set it to the current size needed by the contained items + margin margin = steno_layout.margin scene_rect = scene.itemsBoundingRect() scene_rect = scene_rect.marginsAdded(QMarginsF(margin, margin, margin, margin)) scene.setSceneRect(scene_rect) self.fitInView(scene.sceneRect(), Qt.KeepAspectRatio) self.setScene(scene) self.show()
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
class KeyWidget(QGraphicsRectItem): def __init__(self, rect: QRect, idx: int, note: str): super().__init__(rect.x(), rect.y(), rect.width(), rect.height()) self.rect = rect self.idx = idx self.note = note self.label = None if note in WHITE_KEYS: self.setBrush(WHITE_KEY_COLOR) # set the label self.label = QGraphicsTextItem() self.label.setDefaultTextColor(Qt.black) # font = self.label.font() # font.setBold(True) # self.label.setFont(font) self.label.setZValue(100) self.label.setPlainText(note) self.label.setPos( self.rect.x() + self.rect.width() / 2 - self.label.boundingRect().width() / 2, self.rect.y() + self.rect.height() * 0.8) else: self.setBrush(Qt.black) def press(self, out_of_range=False): if out_of_range: self.setBrush(Qt.yellow) else: self.setBrush(Qt.gray) def release(self): if self.note in WHITE_KEYS: self.setBrush(WHITE_KEY_COLOR) else: self.setBrush(BLACK_KEY_COLOR)
def add_chart(self, text, data, eq=False): items = [] text_item = QGraphicsTextItem(text) text_item.setFont(ComparisionChart.arial_font) text_item.setPos(0,0) items.append(text_item) next_y = text_item.boundingRect().height()+5 if not data: text_item = QGraphicsTextItem("prazno") text_item.setFont(ComparisionChart.font) text_item.setPos(0,next_y) items.append(text_item) group = self.scene.createItemGroup(items) return group item_max = 0 before_max = len("before") after_max = len("after") def fmt_num(number): return "{:.2f}".format(number) def fmt_num_align(number, width): fmt = "{:^%d.2f}" % width return fmt.format(number) for item, before, after in data: item_max = max(item_max, len(item)) before_max = max(before_max, len(fmt_num(before))) after_max = max(after_max, len(fmt_num(after))) before_larger = False if before > after: before_larger = True item_max_s= item_max*9 before_max_s= before_max*9 after_max_s= after_max*9 full_size = before_max_s+5+after_max_s start_chart = item_max_s+5 for item, before, after in data: if before_larger: large = before small = after else: large = after small = before if not eq: rect_item = QGraphicsRectItem() old_height = 0.8*text_item.boundingRect().height() rect_item.setRect(start_chart,next_y, full_size, old_height) if before_larger: rect_item.setBrush(ComparisionChart.color1_brush) else: rect_item.setBrush(ComparisionChart.color2_brush) rect_item.setPen(ComparisionChart.no_pen) items.append(rect_item) rect_item = QGraphicsRectItem() new_height = 0.4*text_item.boundingRect().height() rect_item.setRect(start_chart,next_y+(old_height-new_height)/2, full_size*(small/large), new_height) if before_larger: rect_item.setBrush(ComparisionChart.color2_brush) else: rect_item.setBrush(ComparisionChart.color1_brush) rect_item.setPen(ComparisionChart.no_pen) items.append(rect_item) text_item = QGraphicsTextItem(item) text_item.setFont(ComparisionChart.font) text_item.setPos(0, next_y) items.append(text_item) text_item = QGraphicsTextItem(fmt_num_align(before, before_max)) text_item.setFont(ComparisionChart.font) text_item.setPos(item_max_s, next_y) items.append(text_item) text_item = QGraphicsTextItem(fmt_num_align(after, after_max)) text_item.setFont(ComparisionChart.font) text_item.setPos(item_max_s+5+before_max_s, next_y) items.append(text_item) next_y+=text_item.boundingRect().height() #barsets = [QBarSet("previous"), QBarSet("current")] #cats = [] #for i, (item, before, now) in enumerate(data): #print (item, before, now) #barsets[1].append(before) #barsets[0].append(now) #cats.append(item) #axis_y = QBarCategoryAxis() #axis_y.append(cats) #barseries = QHorizontalBarSeries() #barseries.append(barsets) #chart = QChart() #chart.addSeries(barseries) #chart.setTitle(text) #chart.setAnimationOptions(QChart.SeriesAnimations) #chart.setAxisY(axis_y, barseries) #chart.setPos(0, next_y) #chart.setPreferredWidth(width) #chart.setPreferredHeight(500) ##chart.setSize(200,200) ##print ("SIZE:", chart.size().toSize().width(), ##chart.size().toSize().height()) ##print ("CHART:", chart.boundingRect().width(), ##chart.boundingRect().height()) #items.append(chart) group = self.scene.createItemGroup(items) return group
class RuleConditionItem(GraphicsItem): def __init__(self, model_item: SimulatorRuleCondition, parent=None): assert isinstance(model_item, SimulatorRuleCondition) super().__init__(model_item=model_item, parent=parent) self.number.setFont(self.font_bold) self.text = QGraphicsTextItem(self) self.text.setPlainText(self.model_item.type.value) self.text.setFont(self.font_bold) self.desc = QGraphicsTextItem(self) self.desc.setFont(self.font) def update_flags(self): if self.scene().mode == 0: self.set_flags(is_selectable=True, accept_hover_events=True, accept_drops=True) else: self.set_flags(is_selectable=True, accept_hover_events=True) def labels_width(self): return max(self.number.boundingRect().width() + self.text.boundingRect().width(), self.desc.boundingRect().width()) def refresh(self): if len(self.model_item.condition): if len(self.model_item.condition) > 20: self.desc.setPlainText(self.model_item.condition[:20] + "...") else: self.desc.setPlainText(self.model_item.condition) elif self.model_item.type != ConditionType.ELSE: self.desc.setPlainText("<Condition>") def update_position(self, x_pos, y_pos): self.setPos(x_pos, y_pos) start_y = 0 start_x = ((self.scene().items_width() + 40) - ( self.number.boundingRect().width() + self.text.boundingRect().width())) / 2 self.number.setPos(start_x, start_y) start_x += self.number.boundingRect().width() self.text.setPos(start_x, start_y) start_y += round(self.number.boundingRect().height()) start_x = ((self.scene().items_width() + 40) - self.desc.boundingRect().width()) / 2 self.desc.setPos(start_x, start_y) if self.model_item.type != ConditionType.ELSE: start_y += round(self.desc.boundingRect().height()) start_y += 5 for child in self.get_scene_children(): child.update_position(20, start_y) start_y += round(child.boundingRect().height()) width = self.scene().items_width() self.prepareGeometryChange() self.bounding_rect = QRectF(0, 0, width + 40, self.childrenBoundingRect().height() + 5) def update_drop_indicator(self, pos): rect = self.boundingRect() if pos.y() - rect.top() < rect.height() / 3: self.drop_indicator_position = QAbstractItemView.AboveItem elif rect.bottom() - pos.y() < rect.height() / 3: self.drop_indicator_position = QAbstractItemView.BelowItem else: self.drop_indicator_position = QAbstractItemView.OnItem self.update() def paint(self, painter, option, widget): if self.scene().mode == 1: self.setOpacity(1 if self.model_item.logging_active else 0.3) painter.setOpacity(constants.SELECTION_OPACITY) if self.hover_active or self.isSelected(): painter.setBrush(constants.SELECTION_COLOR) elif not self.is_valid(): painter.setBrush(QColor(255, 0, 0, 150)) else: painter.setBrush(QColor.fromRgb(204, 204, 204, 255)) height = self.number.boundingRect().height() if self.model_item.type != ConditionType.ELSE: height += self.desc.boundingRect().height() painter.drawRect(QRectF(0, 0, self.boundingRect().width(), height)) painter.setBrush(Qt.NoBrush) painter.drawRect(self.boundingRect()) if self.drag_over: self.paint_drop_indicator(painter) def paint_drop_indicator(self, painter): painter.setPen(QPen(Qt.darkRed, 2, Qt.SolidLine)) painter.setBrush(Qt.NoBrush) rect = self.boundingRect() if self.drop_indicator_position == QAbstractItemView.AboveItem: painter.drawLine(QLineF(rect.topLeft(), rect.topRight())) elif self.drop_indicator_position == QAbstractItemView.OnItem: painter.drawRect(rect) else: painter.drawLine(QLineF(rect.bottomLeft(), rect.bottomRight()))
class BlockItem(QGraphicsPixmapItem): def __init__(self, trnsysType, parent, **kwargs): super().__init__(None) self.logger = parent.logger self.w = 120 self.h = 120 self.parent = parent self.id = self.parent.parent().idGen.getID() self.propertyFile = [] if "displayName" in kwargs: self.displayName = kwargs["displayName"] else: self.displayName = trnsysType + "_" + str(self.id) if "loadedBlock" not in kwargs: self.parent.parent().trnsysObj.append(self) self.inputs = [] self.outputs = [] # Export related: self.name = trnsysType self.trnsysId = self.parent.parent().idGen.getTrnsysID() # Transform related self.flippedV = False self.flippedH = False self.rotationN = 0 self.flippedHInt = -1 self.flippedVInt = -1 pixmap = self._getPixmap() self.setPixmap(pixmap) # To set flags of this item self.setFlags(self.ItemIsSelectable | self.ItemIsMovable) self.setFlag(self.ItemSendsScenePositionChanges, True) self.setCursor(QCursor(QtCore.Qt.PointingHandCursor)) self.label = QGraphicsTextItem(self.displayName, self) self.label.setVisible(False) if self.name == "Bvi": self.inputs.append(_cspi.createSinglePipePortItem("i", 0, self)) self.outputs.append(_cspi.createSinglePipePortItem("o", 2, self)) if self.name == "StorageTank": # Inputs get appended in ConfigStorage pass self.logger.debug("Block name is " + str(self.name)) # Update size for generic block: if self.name == "Bvi": self.changeSize() # Experimental, used for detecting genereated blocks attached to storage ports self.inFirstRow = False # Undo framework related self.oldPos = None self.origOutputsPos = None self.origInputsPos = None def _getImageAccessor(self) -> _tp.Optional[_img.ImageAccessor]: if type(self) == BlockItem: raise AssertionError( "`BlockItem' cannot be instantiated directly.") currentClassName = BlockItem.__name__ currentMethodName = f"{currentClassName}.{BlockItem._getImageAccessor.__name__}" message = ( f"{currentMethodName} has been called. However, this method should not be called directly but must\n" f"implemented in a child class. This means that a) someone instantiated `{currentClassName}` directly\n" f"or b) a child class of it doesn't implement `{currentMethodName}`. Either way that's an\n" f"unrecoverable error and therefore the program will be terminated now. Please do get in touch with\n" f"the developers if you've encountered this error. Thanks.") exception = AssertionError(message) # I've seen exception messages mysteriously swallowed that's why we're logging the message here, too. self.logger.error(message, exc_info=exception, stack_info=True) raise exception def addTree(self): pass # Setter functions def setParent(self, p): self.parent = p if self not in self.parent.parent().trnsysObj: self.parent.parent().trnsysObj.append(self) # self.logger.debug("trnsysObj are " + str(self.parent.parent().trnsysObj)) def setId(self, newId): self.id = newId def setName(self, newName): self.displayName = newName self.label.setPlainText(newName) # Interaction related def contextMenuEvent(self, event): menu = QMenu() a1 = menu.addAction("Launch NotePad++") a1.triggered.connect(self.launchNotepadFile) rr = _img.ROTATE_TO_RIGHT_PNG.icon() a2 = menu.addAction(rr, "Rotate Block clockwise") a2.triggered.connect(self.rotateBlockCW) ll = _img.ROTATE_LEFT_PNG.icon() a3 = menu.addAction(ll, "Rotate Block counter-clockwise") a3.triggered.connect(self.rotateBlockCCW) a4 = menu.addAction("Reset Rotation") a4.triggered.connect(self.resetRotation) b1 = menu.addAction("Print Rotation") b1.triggered.connect(self.printRotation) c1 = menu.addAction("Delete this Block") c1.triggered.connect(self.deleteBlockCom) menu.exec_(event.screenPos()) def launchNotepadFile(self): self.logger.debug("Launching notpad") global FilePath os.system("start notepad++ " + FilePath) def mouseDoubleClickEvent(self, event): if hasattr(self, "isTempering"): self.parent.parent().showTVentilDlg(self) elif self.name == "Pump": self.parent.parent().showPumpDlg(self) elif self.name == "TeePiece" or self.name == "WTap_main": self.parent.parent().showBlockDlg(self) elif self.name in ["SPCnr", "DPCnr", "DPTee"]: self.parent.parent().showDoublePipeBlockDlg(self) else: self.parent.parent().showBlockDlg(self) if len(self.propertyFile) > 0: for files in self.propertyFile: os.startfile(files, "open") def mouseReleaseEvent(self, event): # self.logger.debug("Released mouse over block") if self.oldPos is None: self.logger.debug("For Undo Framework: oldPos is None") else: if self.scenePos() != self.oldPos: self.logger.debug("Block was dragged") self.logger.debug("Old pos is" + str(self.oldPos)) command = MoveCommand(self, self.oldPos, "Move BlockItem") self.parent.parent().parent().undoStack.push(command) self.oldPos = self.scenePos() super(BlockItem, self).mouseReleaseEvent(event) # Transform related def changeSize(self): self._positionLabel() w, h = self._getCappedWithAndHeight() if self.name == "Bvi": delta = 4 self.inputs[0].setPos( -2 * delta + 4 * self.flippedH * delta + self.flippedH * w, h / 3) self.outputs[0].setPos( -2 * delta + 4 * self.flippedH * delta + self.flippedH * w, 2 * h / 3) self.inputs[0].side = 0 + 2 * self.flippedH self.outputs[0].side = 0 + 2 * self.flippedH def _positionLabel(self): width, height = self._getCappedWithAndHeight() rect = self.label.boundingRect() labelWidth, lableHeight = rect.width(), rect.height() labelPosX = (height - labelWidth) / 2 self.label.setPos(labelPosX, width) def _getCappedWithAndHeight(self): width = self.w height = self.h if height < 20: height = 20 if width < 40: width = 40 return width, height def updateFlipStateH(self, state): self.flippedH = bool(state) pixmap = self._getPixmap() self.setPixmap(pixmap) self.flippedHInt = 1 if self.flippedH else -1 if self.flippedH: for i in range(0, len(self.inputs)): distanceToMirrorAxis = self.w / 2.0 - self.origInputsPos[i][0] self.inputs[i].setPos( self.origInputsPos[i][0] + 2.0 * distanceToMirrorAxis, self.inputs[i].pos().y(), ) for i in range(0, len(self.outputs)): distanceToMirrorAxis = self.w / 2.0 - self.origOutputsPos[i][0] self.outputs[i].setPos( self.origOutputsPos[i][0] + 2.0 * distanceToMirrorAxis, self.outputs[i].pos().y(), ) else: for i in range(0, len(self.inputs)): self.inputs[i].setPos(self.origInputsPos[i][0], self.inputs[i].pos().y()) for i in range(0, len(self.outputs)): self.outputs[i].setPos(self.origOutputsPos[i][0], self.outputs[i].pos().y()) def updateFlipStateV(self, state): self.flippedV = bool(state) pixmap = self._getPixmap() self.setPixmap(pixmap) self.flippedVInt = 1 if self.flippedV else -1 if self.flippedV: for i in range(0, len(self.inputs)): distanceToMirrorAxis = self.h / 2.0 - self.origInputsPos[i][1] self.inputs[i].setPos( self.inputs[i].pos().x(), self.origInputsPos[i][1] + 2.0 * distanceToMirrorAxis, ) for i in range(0, len(self.outputs)): distanceToMirrorAxis = self.h / 2.0 - self.origOutputsPos[i][1] self.outputs[i].setPos( self.outputs[i].pos().x(), self.origOutputsPos[i][1] + 2.0 * distanceToMirrorAxis, ) else: for i in range(0, len(self.inputs)): self.inputs[i].setPos(self.inputs[i].pos().x(), self.origInputsPos[i][1]) for i in range(0, len(self.outputs)): self.outputs[i].setPos(self.outputs[i].pos().x(), self.origOutputsPos[i][1]) def updateSidesFlippedH(self): if self.rotationN % 2 == 0: for p in self.inputs: if p.side == 0 or p.side == 2: self.updateSide(p, 2) for p in self.outputs: if p.side == 0 or p.side == 2: self.updateSide(p, 2) if self.rotationN % 2 == 1: for p in self.inputs: if p.side == 1 or p.side == 3: self.updateSide(p, 2) for p in self.outputs: if p.side == 1 or p.side == 3: self.updateSide(p, 2) def updateSidesFlippedV(self): if self.rotationN % 2 == 1: for p in self.inputs: if p.side == 0 or p.side == 2: self.updateSide(p, 2) for p in self.outputs: if p.side == 0 or p.side == 2: self.updateSide(p, 2) if self.rotationN % 2 == 0: for p in self.inputs: if p.side == 1 or p.side == 3: self.updateSide(p, 2) for p in self.outputs: if p.side == 1 or p.side == 3: self.updateSide(p, 2) def updateSide(self, port, n): port.side = (port.side + n) % 4 # self.logger.debug("Port side is " + str(port.side)) def rotateBlockCW(self): # Rotate block clockwise # self.setTransformOriginPoint(50, 50) # self.setTransformOriginPoint(self.w/2, self.h/2) self.setTransformOriginPoint(0, 0) self.setRotation((self.rotationN + 1) * 90) self.label.setRotation(-(self.rotationN + 1) * 90) self.rotationN += 1 self.logger.debug("rotated by " + str(self.rotationN)) for p in self.inputs: p.itemChange(27, p.scenePos()) self.updateSide(p, 1) for p in self.outputs: p.itemChange(27, p.scenePos()) self.updateSide(p, 1) pixmap = self._getPixmap() self.setPixmap(pixmap) def rotateBlockToN(self, n): if n > 0: while self.rotationN != n: self.rotateBlockCW() if n < 0: while self.rotationN != n: self.rotateBlockCCW() def rotateBlockCCW(self): # Rotate block clockwise # self.setTransformOriginPoint(50, 50) self.setTransformOriginPoint(0, 0) self.setRotation((self.rotationN - 1) * 90) self.label.setRotation(-(self.rotationN - 1) * 90) self.rotationN -= 1 self.logger.debug("rotated by " + str(self.rotationN)) for p in self.inputs: p.itemChange(27, p.scenePos()) self.updateSide(p, -1) for p in self.outputs: p.itemChange(27, p.scenePos()) self.updateSide(p, -1) pixmap = self._getPixmap() self.setPixmap(pixmap) def resetRotation(self): self.logger.debug("Resetting rotation...") self.setRotation(0) self.label.setRotation(0) for p in self.inputs: p.itemChange(27, p.scenePos()) self.updateSide(p, -self.rotationN) # self.logger.debug("Portside of port " + str(p) + " is " + str(p.portSide)) for p in self.outputs: p.itemChange(27, p.scenePos()) self.updateSide(p, -self.rotationN) # self.logger.debug("Portside of port " + str(p) + " is " + str(p.portSide)) self.rotationN = 0 pixmap = self._getPixmap() self.setPixmap(pixmap) def printRotation(self): self.logger.debug("Rotation is " + str(self.rotationN)) # Deletion related def deleteConns(self): for p in self.inputs: while len(p.connectionList) > 0: p.connectionList[0].deleteConn() for p in self.outputs: while len(p.connectionList) > 0: p.connectionList[0].deleteConn() def deleteBlock(self): self.parent.parent().trnsysObj.remove(self) self.parent.scene().removeItem(self) widgetToRemove = self.parent.parent().findChild( QTreeView, self.displayName + "Tree") if widgetToRemove: widgetToRemove.hide() def deleteBlockCom(self): self.parent.deleteBlockCom(self) def getConnections(self): """ Get the connections from inputs and outputs of this block. Returns ------- c : :obj:`List` of :obj:`BlockItem` """ c = [] for i in self.inputs: for cl in i.connectionList: c.append(cl) for o in self.outputs: for cl in o.connectionList: c.append(cl) return c # Scaling related def mousePressEvent(self, event): # create resizer """ Using try catch to avoid creating extra resizers. When an item is clicked on, it will check if a resizer already existed. If there exist a resizer, returns. else, creates one. Resizer will not be created for GenericBlock due to complications in the code. Resizer will not be created for storageTank as there's already a built in function for it in the storageTank dialog. Resizers are deleted inside mousePressEvent function inside GUI.py """ self.logger.debug("Inside Block Item mouse click") self.isSelected = True if self.name == "GenericBlock" or self.name == "StorageTank": return try: self.resizer except AttributeError: self.resizer = ResizerItem(self) self.resizer.setPos(self.w, self.h) self.resizer.itemChange(self.resizer.ItemPositionChange, self.resizer.pos()) else: return def setItemSize(self, w, h): self.logger.debug("Inside block item set item size") self.w, self.h = w, h # if h < 20: # self.h = 20 # if w < 40: # self.w = 40 def updateImage(self): self.logger.debug("Inside block item update image") pixmap = self._getPixmap() self.setPixmap(pixmap) if self.flippedH: self.updateFlipStateH(self.flippedH) if self.flippedV: self.updateFlipStateV(self.flippedV) def _getPixmap(self) -> QPixmap: imageAccessor = self._getImageAccessor() image = imageAccessor.image(width=self.w, height=self.h).mirrored( horizontal=self.flippedH, vertical=self.flippedV) pixmap = QPixmap(image) return pixmap def deleteResizer(self): try: self.resizer except AttributeError: self.logger.debug("No resizer") else: del self.resizer # AlignMode related def itemChange(self, change, value): # self.logger.debug(change, value) # Snap grid excludes alignment if change == self.ItemPositionChange: if self.parent.parent().snapGrid: snapSize = self.parent.parent().snapSize self.logger.debug("itemchange") self.logger.debug(type(value)) value = QPointF(value.x() - value.x() % snapSize, value.y() - value.y() % snapSize) return value else: # if self.hasElementsInYBand() and not self.elementInY() and not self.aligned: if self.parent.parent().alignMode: if self.hasElementsInYBand(): return self.alignBlock(value) else: # self.aligned = False return value else: return value else: return super(BlockItem, self).itemChange(change, value) def alignBlock(self, value): for t in self.parent.parent().trnsysObj: if isinstance(t, BlockItem) and t is not self: if self.elementInYBand(t): value = QPointF(self.pos().x(), t.pos().y()) self.parent.parent().alignYLineItem.setLine( self.pos().x() + self.w / 2, t.pos().y(), t.pos().x() + t.w / 2, t.pos().y()) self.parent.parent().alignYLineItem.setVisible(True) qtm = QTimer(self.parent.parent()) qtm.timeout.connect(self.timerfunc) qtm.setSingleShot(True) qtm.start(1000) e = QMouseEvent( QEvent.MouseButtonRelease, self.pos(), QtCore.Qt.NoButton, QtCore.Qt.NoButton, QtCore.Qt.NoModifier, ) self.parent.mouseReleaseEvent(e) self.parent.parent().alignMode = False # self.setPos(self.pos().x(), t.pos().y()) # self.aligned = True if self.elementInXBand(t): value = QPointF(t.pos().x(), self.pos().y()) self.parent.parent().alignXLineItem.setLine( t.pos().x(), t.pos().y() + self.w / 2, t.pos().x(), self.pos().y() + t.w / 2) self.parent.parent().alignXLineItem.setVisible(True) qtm = QTimer(self.parent.parent()) qtm.timeout.connect(self.timerfunc2) qtm.setSingleShot(True) qtm.start(1000) e = QMouseEvent( QEvent.MouseButtonRelease, self.pos(), QtCore.Qt.NoButton, QtCore.Qt.NoButton, QtCore.Qt.NoModifier, ) self.parent.mouseReleaseEvent(e) self.parent.parent().alignMode = False return value def timerfunc(self): self.parent.parent().alignYLineItem.setVisible(False) def timerfunc2(self): self.parent.parent().alignXLineItem.setVisible(False) def hasElementsInYBand(self): for t in self.parent.parent().trnsysObj: if isinstance(t, BlockItem): if self.elementInYBand(t): return True return False def hasElementsInXBand(self): for t in self.parent.parent().trnsysObj: if isinstance(t, BlockItem): if self.elementInXBand(t): return True return False def elementInYBand(self, t): eps = 50 return self.scenePos().y() - eps <= t.scenePos().y( ) <= self.scenePos().y() + eps def elementInXBand(self, t): eps = 50 return self.scenePos().x() - eps <= t.scenePos().x( ) <= self.scenePos().x() + eps def elementInY(self): for t in self.parent.parent().trnsysObj: if isinstance(t, BlockItem): if self.scenePos().y == t.scenePos().y(): return True return False def encode(self): portListInputs = [] portListOutputs = [] for inp in self.inputs: portListInputs.append(inp.id) for output in self.outputs: portListOutputs.append(output.id) blockPosition = (float(self.pos().x()), float(self.pos().y())) blockItemModel = BlockItemModel( self.name, self.displayName, blockPosition, self.id, self.trnsysId, portListInputs, portListOutputs, self.flippedH, self.flippedV, self.rotationN, ) dictName = "Block-" return dictName, blockItemModel.to_dict() def decode(self, i, resBlockList): model = BlockItemModel.from_dict(i) self.setName(model.BlockDisplayName) self.setPos(float(model.blockPosition[0]), float(model.blockPosition[1])) self.id = model.Id self.trnsysId = model.trnsysId if len(self.inputs) != len(model.portsIdsIn) or len( self.outputs) != len(model.portsIdsOut): temp = model.portsIdsIn model.portsIdsIn = model.portsIdsOut model.portsIdsOut = temp for index, inp in enumerate(self.inputs): inp.id = model.portsIdsIn[index] for index, out in enumerate(self.outputs): out.id = model.portsIdsOut[index] self.updateFlipStateH(model.flippedH) self.updateFlipStateV(model.flippedV) self.rotateBlockToN(model.rotationN) resBlockList.append(self) def decodePaste(self, i, offset_x, offset_y, resConnList, resBlockList, **kwargs): self.setPos( float(i["BlockPosition"][0] + offset_x), float(i["BlockPosition"][1] + offset_y), ) self.updateFlipStateH(i["FlippedH"]) self.updateFlipStateV(i["FlippedV"]) self.rotateBlockToN(i["RotationN"]) for x in range(len(self.inputs)): self.inputs[x].id = i["PortsIDIn"][x] for x in range(len(self.outputs)): self.outputs[x].id = i["PortsIDOut"][x] resBlockList.append(self) # Export related def exportBlackBox(self): equation = [] if (len(self.inputs + self.outputs) == 2 and self.isVisible() and not isinstance(self.outputs[0], DoublePipePortItem)): files = glob.glob(os.path.join(self.path, "**/*.ddck"), recursive=True) if not (files): status = "noDdckFile" else: status = "noDdckEntry" lines = [] for file in files: infile = open(file, "r") lines += infile.readlines() for i in range(len(lines)): if "output" in lines[i].lower() and "to" in lines[i].lower( ) and "hydraulic" in lines[i].lower(): for j in range(i, len(lines) - i): if lines[j][0] == "T": outputT = lines[j].split("=")[0].replace(" ", "") status = "success" break equation = ["T" + self.displayName + "=" + outputT] break else: status = "noBlackBoxOutput" if status == "noDdckFile" or status == "noDdckEntry": equation.append("T" + self.displayName + "=1") return status, equation def exportPumpOutlets(self): return "", 0 def exportMassFlows(self): return "", 0 def exportDivSetting1(self): return "", 0 def exportDivSetting2(self, nUnit): return "", nUnit def exportPipeAndTeeTypesForTemp(self, startingUnit): return "", startingUnit def getTemperatureVariableName(self, portItem: SinglePipePortItem) -> str: return f"T{self.displayName}" def getFlowSolverParametersId(self, portItem: SinglePipePortItem) -> int: return self.trnsysId def assignIDsToUninitializedValuesAfterJsonFormatMigration( self, generator: _id.IdGenerator) -> None: pass def deleteLoadedFile(self): for items in self.loadedFiles: try: self.parent.parent().fileList.remove(str(items)) except ValueError: self.logger.debug("File already deleted from file list.") self.logger.debug("filelist:", self.parent.parent().fileList)
class RuleConditionItem(GraphicsItem): def __init__(self, model_item: SimulatorRuleCondition, parent=None): assert isinstance(model_item, SimulatorRuleCondition) super().__init__(model_item=model_item, parent=parent) self.number.setFont(self.font_bold) self.text = QGraphicsTextItem(self) self.text.setPlainText(self.model_item.type.value) self.text.setFont(self.font_bold) self.desc = QGraphicsTextItem(self) self.desc.setFont(self.font) def update_flags(self): if self.scene().mode == 0: self.set_flags(is_selectable=True, accept_hover_events=True, accept_drops=True) else: self.set_flags(is_selectable=True, accept_hover_events=True) def labels_width(self): return max(self.number.boundingRect().width() + self.text.boundingRect().width(), self.desc.boundingRect().width()) def refresh(self): if len(self.model_item.condition): if len(self.model_item.condition) > 20: self.desc.setPlainText(self.model_item.condition[:20] + "...") else: self.desc.setPlainText(self.model_item.condition) elif self.model_item.type != ConditionType.ELSE: self.desc.setPlainText("<Condition>") def update_position(self, x_pos, y_pos): self.setPos(x_pos, y_pos) start_y = 0 start_x = ((self.scene().items_width() + 40) - ( self.number.boundingRect().width() + self.text.boundingRect().width())) / 2 self.number.setPos(start_x, start_y) start_x += self.number.boundingRect().width() self.text.setPos(start_x, start_y) start_y += round(self.number.boundingRect().height()) start_x = ((self.scene().items_width() + 40) - self.desc.boundingRect().width()) / 2 self.desc.setPos(start_x, start_y) if self.model_item.type != ConditionType.ELSE: start_y += round(self.desc.boundingRect().height()) start_y += 5 for child in self.get_scene_children(): child.update_position(20, start_y) start_y += round(child.boundingRect().height()) width = self.scene().items_width() self.prepareGeometryChange() self.bounding_rect = QRectF(0, 0, width + 40, self.childrenBoundingRect().height() + 5) def update_drop_indicator(self, pos): rect = self.boundingRect() if pos.y() - rect.top() < rect.height() / 3: self.drop_indicator_position = QAbstractItemView.AboveItem elif rect.bottom() - pos.y() < rect.height() / 3: self.drop_indicator_position = QAbstractItemView.BelowItem else: self.drop_indicator_position = QAbstractItemView.OnItem self.update() def paint(self, painter, option, widget): if self.scene().mode == 1: self.setOpacity(1 if self.model_item.logging_active else 0.3) painter.setOpacity(settings.SELECTION_OPACITY) if self.hover_active or self.isSelected(): painter.setBrush(settings.SELECTION_COLOR) elif not self.is_valid(): painter.setBrush(QColor(255, 0, 0, 150)) else: painter.setBrush(QColor.fromRgb(204, 204, 204, 255)) height = self.number.boundingRect().height() if self.model_item.type != ConditionType.ELSE: height += self.desc.boundingRect().height() painter.drawRect(QRectF(0, 0, self.boundingRect().width(), height)) painter.setBrush(Qt.NoBrush) painter.drawRect(self.boundingRect()) if self.drag_over: self.paint_drop_indicator(painter) def paint_drop_indicator(self, painter): painter.setPen(QPen(Qt.darkRed, 2, Qt.SolidLine)) painter.setBrush(Qt.NoBrush) rect = self.boundingRect() if self.drop_indicator_position == QAbstractItemView.AboveItem: painter.drawLine(QLineF(rect.topLeft(), rect.topRight())) elif self.drop_indicator_position == QAbstractItemView.OnItem: painter.drawRect(rect) else: painter.drawLine(QLineF(rect.bottomLeft(), rect.bottomRight()))
class QNEPort(QGraphicsPathItem): (NamePort, TypePort) = (1, 2) (Type) = (QGraphicsItem.UserType + 1) def __init__(self, parent): super(QNEPort, self).__init__(parent) self.textColor = Qt.black self.label = QGraphicsTextItem(self) self.radius_ = 4 self.margin = 3 self.setPen(QPen(Qt.darkRed)) self.setBrush(Qt.red) self.setFlag(QGraphicsItem.ItemSendsScenePositionChanges) self.m_portFlags = 0 self.isOutput_ = False self.m_block = None self.m_connections = [] def __del__(self): #print("Del QNEPort %s" % self.name) for connection in self.m_connections: if connection.port1(): connection.port1().removeConnection(connection) if connection.port2(): connection.port2().removeConnection(connection) if self.scene(): self.scene().removeItem(connection) def setName(self, name): self.name = name self.label.setPlainText(name) self.label.setDefaultTextColor(self.textColor) self.setBrush(Qt.red) def setIsOutput(self, isOutput): self.isOutput_ = isOutput path = QPainterPath() if self.isOutput_: if self.name == '': path.addRect(-2 * self.radius_, -self.radius_, 2 * self.radius_, 2 * self.radius_) self.label.setPos( -self.radius_ - self.margin - self.label.boundingRect().width(), -self.label.boundingRect().height() / 2) else: path.addEllipse(-2 * self.radius_, -self.radius_, 2 * self.radius_, 2 * self.radius_) self.label.setPos( -self.radius_ - self.margin - self.label.boundingRect().width(), -self.label.boundingRect().height() / 2) else: if self.name == '': path.addRect(0, -self.radius_, 2 * self.radius_, 2 * self.radius_) self.label.setPos(self.radius_ + self.margin, -self.label.boundingRect().height() / 2) else: path.addEllipse(0, -self.radius_, 2 * self.radius_, 2 * self.radius_) self.label.setPos(self.radius_ + self.margin, -self.label.boundingRect().height() / 2) self.setPath(path) def setNEBlock(self, block): self.m_block = block def setPortFlags(self, flags): self.m_portFlags = flags if self.m_portFlags & self.TypePort: font = self.scene().font() font.setItalic(True) self.label.setFont(font) self.setPath(QPainterPath()) elif self.m_portFlags & self.NamePort: font = self.scene().font() font.setBold(True) self.label.setFont(font) self.setPath(QPainterPath()) def setPtr(self, ptr): self.m_ptr = ptr def type(self): return self.Type def radius(self): return self.radius_ def portName(self): return self.name def isOutput(self): return self.isOutput_ def block(self): return self.m_block def portFlags(self): return self.m_portFlags def ptr(self): return self.m_ptr def addConnection(self, connection): self.m_connections.append(connection) def removeConnection(self, connection): try: self.m_connections.remove(connection) except: pass def connections(self): return self.m_connections def isConnected(self, other): for connection in self.m_connections: if connection.port1() == other or connection.port2() == other: return True return False def itemChange(self, change, value): if change == QGraphicsItem.ItemScenePositionHasChanged: for connection in self.m_connections: connection.updatePosFromPorts() connection.updatePath() return value
class Edge(Connection): ''' B-spline/Bezier connection shape ''' def __init__(self, edge, graph): ''' Set generic parameters from Connection class ''' self.text_label = None super(Edge, self).__init__(edge['source'], edge['target']) self.edge = edge self.graph = graph # Set connection points as not visible, by default self.bezier_visible = False # Initialize control point coordinates self.bezier = [self.mapFromScene(*self.edge['spline'][0])] # Bezier control points (groups of three points): assert(len(self.edge['spline']) % 3 == 1) for i in xrange(1, len(self.edge['spline']), 3): self.bezier.append([Controlpoint( self.mapFromScene(*self.edge['spline'][i + j]), self) for j in range(3)]) # Create connection points at start and end of the edge self.source_connection = Connectionpoint( self.start_point or self.bezier[0], self, self.parent) self.parent.movable_points.append(self.source_connection) self.end_connection = Connectionpoint( self.end_point or self.bezier[-1], self, self.child) self.child.movable_points.append(self.end_connection) self.reshape() @property def start_point(self): ''' Compute connection origin - redefine in subclasses ''' # Start point is optional - graphviz decision return self.mapFromScene(*self.edge['start']) \ if self.edge.get('start') else None @property def end_point(self): ''' Compute connection end point - redefine in subclasses ''' return self.mapFromScene(*self.edge['end']) \ if self.edge.get('end') else None def bezier_set_visible(self, visible=True): ''' Display or hide the edge control points ''' self.bezier_visible = visible for group in self.bezier[1:]: for ctrl_point in group: if visible: ctrl_point.show() else: ctrl_point.hide() if visible: self.end_connection.show() self.source_connection.show() else: self.end_connection.hide() self.source_connection.hide() self.update() def mousePressEvent(self, event): ''' On a mouse click, display the control points ''' self.bezier_set_visible(True) # pylint: disable=R0914 def reshape(self): ''' Update the shape of the edge (redefined function) ''' path = QPainterPath() # If there is a starting point, draw a line to the first curve point if self.start_point: path.moveTo(self.source_connection.center) path.lineTo(self.bezier[0]) else: path.moveTo(self.source_connection.center) # Loop over the curve points: for group in self.bezier[1:]: path.cubicTo(*[point.center for point in group]) # If there is an ending point, draw a line to it if self.end_point: path.lineTo(self.end_connection.center) end_point = path.currentPosition() arrowhead = self.angle_arrow(path) path.lineTo(arrowhead[0]) path.moveTo(end_point) path.lineTo(arrowhead[1]) path.moveTo(end_point) try: # Add the transition label, if any (none for the START edge) font = QFont('arial', pointSize=8) metrics = QFontMetrics(font) label = self.edge.get('label', '') lines = label.split('\n') width = metrics.width(max(lines)) # longest line height = metrics.height() * len(lines) # lp is the position of the center of the text pos = self.mapFromScene(*self.edge['lp']) if not self.text_label: self.text_label = QGraphicsTextItem( self.edge.get('label', ''), parent=self) self.text_label.setX(pos.x() - width / 2) self.text_label.setY(pos.y() - height / 2) self.text_label.setFont(font) # Make horizontal center alignment, as dot does self.text_label.setTextWidth(self.text_label.boundingRect().width()) fmt = QTextBlockFormat() fmt.setAlignment(Qt.AlignHCenter) cursor = self.text_label.textCursor() cursor.select(QTextCursor.Document) cursor.mergeBlockFormat(fmt) cursor.clearSelection() self.text_label.setTextCursor(cursor) self.text_label.show() except KeyError: # no label pass self.setPath(path) def __str__(self): ''' user-friendly information about the edge coordinates ''' return('Edge between ' + self.edge['source'].name + ' and ' + self.edge['target'].name + str(self.edge['spline'][0])) def paint(self, painter, option, widget): ''' Apply anti-aliasing to Edge Connections ''' painter.setRenderHint(QPainter.Antialiasing, True) super(Edge, self).paint(painter, option, widget) # Draw lines between connection points, if visible if self.bezier_visible: painter.setPen( QPen(Qt.lightGray, 0, Qt.SolidLine)) painter.setBrush(Qt.NoBrush) points_flat = [point.center for sub1 in self.bezier[1:] for point in sub1] painter.drawPolyline([self.source_connection.center] + points_flat + [self.end_connection.center])
class Node(QGraphicsRectItem): class io(QGraphicsRectItem): class BezierCurve(QGraphicsPathItem): def __init__(self, iostart=None, ioend=None): super().__init__() self.setEnabled(False) # Make it ignore events. Links can't be interacted with. self.iostart = iostart self.ioend = ioend if iostart is not None and ioend is not None: self.update() else: self.update(QPointF(0, 0)) def update(self, pos=None): path = QPainterPath() if pos is not None: if self.ioend is None: startpos = self.iostart.pos() + self.iostart.parent.pos() endpos = pos elif self.iostart is None: startpos = pos endpos = self.ioend.pos() + self.ioend.parent.pos() else: startpos = self.iostart.pos() + self.iostart.parent.pos() endpos = self.ioend.pos() + self.ioend.parent.pos() controlpoint = QPointF(abs((endpos - startpos).x()) * 0.8, 0) path.moveTo(startpos) path.cubicTo(startpos + controlpoint, endpos - controlpoint, endpos) self.setPath(path) def __init__(self, parent, index, iotype, iodir): self.parent = parent self.index = index self.iotype = iotype self.iodir = iodir super().__init__(-8, -8, 16, 16, self.parent) # Size of io-boxes is 16x16 self.setAcceptHoverEvents(True) self.iobrush = QBrush(QColor(70, 70, 70, 255)) self.setBrush(self.iobrush) self.newbezier = None # Variable for temporary storage of bezier curve while it's still being dragged self.bezier = [] def mousePressEvent(self, event): if event.button() == Qt.LeftButton: if self.iodir == "output": self.newbezier = Node.io.BezierCurve(self, None) elif self.iodir == "input": self.newbezier = Node.io.BezierCurve(None, self) if self.newbezier is not None: self.newbezier.update(QPointF(event.pos() + self.pos() + self.parent.pos())) self.parent.parent.scene.addItem(self.newbezier) elif event.button() == Qt.RightButton: self.delAllBezier() def mouseMoveEvent(self, event): if self.newbezier is not None: self.newbezier.update(QPointF(event.pos() + self.pos() + self.parent.pos())) def mouseReleaseEvent(self, event): if self.newbezier is not None: self.parent.parent.scene.removeItem(self.newbezier) self.newbezier = None # Find out if an io box lies under the cursor pos = event.pos() + self.pos() + self.parent.pos() pos = self.parent.parent.mapFromScene(pos) target = self.parent.parent.itemAt(pos.x(), pos.y()) # If io box is found, spawn a bezier curve if target is None: if self.iodir == "output": ns = NodeSelector(self.parent.parent.modman, modfilter={"type": {"type": self.iotype, "dir": "input"}}) elif self.iodir == "input": ns = NodeSelector(self.parent.parent.modman, modfilter={"type": {"type": self.iotype, "dir": "output"}}) if ns.exec(): if issubclass(ns.data["node"], baseModule.BaseNode): newNode = Node(self.parent.parent, ns.data["node"]) newNode.setPos(event.pos() + self.pos() + self.parent.pos()) self.parent.parent.scene.addItem(newNode) if self.iodir == "output": target = newNode.inputIO[ns.data["pin"]] elif self.iodir == "input": target = newNode.outputIO[ns.data["pin"]] if target is not None and isinstance(target, Node.io): bezier = None if self.iodir == "output" and target.iodir == "input" and (self.iotype == target.iotype or not self.iotype or not target.iotype): bezier = Node.io.BezierCurve(self, target) if not self.iotype == "exec": target.delAllBezier() elif self.iotype == "exec": if len(self.bezier) >= 1: self.delAllBezier() elif self.iodir == "input" and target.iodir == "output" and (self.iotype == target.iotype or not self.iotype or not target.iotype): bezier = Node.io.BezierCurve(target, self) if not self.iotype == "exec": self.delAllBezier() elif self.iotype == "exec": if len(target.bezier) >= 1: target.delAllBezier() if bezier is not None: self.bezier.append(bezier) target.bezier.append(bezier) self.parent.parent.scene.addItem(bezier) def hoverEnterEvent(self, event): self.parent.setPen(QPen(QColor("black"))) # For some f*****g reason QGraphicsSceneHoverEvents can't be accepted, so this dirty workaround has to be used... self.setPen(QPen(QColor("yellow"))) def hoverLeaveEvent(self, event): self.parent.setPen(QPen(QColor("yellow"))) # See above self.setPen(QPen(QColor("black"))) def updateBezier(self): for bezier in self.bezier: bezier.update() def delAllBezier(self): beziercpy = self.bezier[:] for bezier in beziercpy: bezier.iostart.bezier.remove(bezier) bezier.ioend.bezier.remove(bezier) self.parent.parent.scene.removeItem(bezier) def __init__(self, parent, nodedata=None, id=None): if nodedata is None: raise ValueError("Nodetype must not be 'None'") self.nodedata = nodedata self.parent = parent self.width = 32 self.height = 0 super().__init__(-self.width / 2, -self.height / 2, self.width, self.height) self.setAcceptHoverEvents(True) self.setFlag(QGraphicsItem.ItemIsSelectable, True) self.extraNodeData = {} if id is not None: self.id = id else: self.id = uuid.uuid4().hex # Random ID to identify node self.nodebrush = QBrush(QColor(100, 100, 100, 255)) self.setBrush(self.nodebrush) # Node Title self.nodetitle = self.nodedata.name self.nodetitleTextItem = QGraphicsTextItem(self.nodetitle, self) self.width = max(self.width, self.nodetitleTextItem.boundingRect().width()) self.height += self.nodetitleTextItem.boundingRect().height() # Create all the text items for the IO self.inputTextItems = [] self.outputTextItems = [] for i in range(max(len(self.nodedata.inputDefs), len(self.nodedata.outputDefs))): linewidth = 0 lineheight = 0 try: self.inputTextItems.append(QGraphicsTextItem(self.nodedata.inputDefs[i][0], self)) linewidth += self.inputTextItems[i].boundingRect().width() lineheight = max(lineheight, self.inputTextItems[i].boundingRect().height()) except IndexError: pass try: self.outputTextItems.append(QGraphicsTextItem(self.nodedata.outputDefs[i][0], self)) linewidth += self.outputTextItems[i].boundingRect().width() lineheight = max(lineheight, self.outputTextItems[i].boundingRect().height()) except IndexError: pass linewidth += 12 # Keep atleast 12px distance between the input and output text self.width = max(self.width, linewidth) self.height += lineheight # Set correct positions for all text items self.nodetitleTextItem.setPos(-self.width / 2, -self.height / 2) self.inputIO = [] heightPointer = 0 for i in range(max(len(self.nodedata.inputDefs), len(self.nodedata.outputDefs))): try: self.inputTextItems[i].setPos(-self.width / 2, -self.height / 2 + self.nodetitleTextItem.boundingRect().height() + heightPointer) heightPointer += self.inputTextItems[i].boundingRect().height() newinput = Node.io(self, i, self.nodedata.inputDefs[i][1], "input") self.inputIO.append(newinput) newinput.setPos(-self.width / 2 - newinput.rect().width() / 2, -self.height / 2 + heightPointer + self.inputTextItems[i].boundingRect().height() / 2) except IndexError: pass self.outputIO = [] heightPointer = 0 for i in range(max(len(self.nodedata.inputDefs), len(self.nodedata.outputDefs))): try: self.outputTextItems[i].setPos(self.width / 2 - self.outputTextItems[i].boundingRect().width(), -self.height / 2 + self.nodetitleTextItem.boundingRect().height() + heightPointer) heightPointer += self.outputTextItems[i].boundingRect().height() newoutput = Node.io(self, i, self.nodedata.outputDefs[i][1], "output") self.outputIO.append(newoutput) newoutput.setPos(self.width / 2 + newoutput.rect().width() / 2, -self.height / 2 + heightPointer + self.outputTextItems[i].boundingRect().height() / 2) except IndexError: pass # Set rect to correct size self.setRect(-self.width / 2, -self.height / 2, self.width, self.height) # Additional stuff self.setFlag(QGraphicsItem.ItemIsMovable, True) def delete(self): for io in self.inputIO + self.outputIO: io.delAllBezier() io.parent = None io.bezier = None del self.inputIO del self.outputIO def mouseMoveEvent(self, event): super().mouseMoveEvent(event) for io in self.inputIO + self.outputIO: io.updateBezier() def hoverEnterEvent(self, event): self.setPen(QPen(QColor("yellow"))) def hoverLeaveEvent(self, event): self.setPen(QPen(QColor("black")))
class NodeTemplateItem(): ''' This represents one node template on the diagram. A node template can be on many diagrams This class creates the rectangle graphics item and the text graphics item and adds them to the scene. ''' def __init__(self, scene, x, y, nodeTemplateDict=None, NZID=None): self.scene = scene self.logMsg = None self.x = x self.y = y self.nodeTemplateDict = nodeTemplateDict # self.name = self.nodeTemplateDict.get("name", "") THIS HAS BEEN REPLACED BY THE name FUNCTION - SEE BELOW self.diagramType = "Node Template" self.displayText = None self.model = self.scene.parent.model self.gap = 100 self.relList = [] # assign a unique key if it doesn't already have one if NZID == None: self.NZID = str(uuid.uuid4()) else: self.NZID = NZID # init graphics objects to none self.TNode = None self.TNtext = None # draw the node template on the diagram self.drawIt() def name(self, ): return self.nodeTemplateDict.get("name", "") def getX(self, ): return self.TNode.boundingRect().x() def getY(self, ): return self.TNode.boundingRect().y() def getHeight(self, ): return self.TNode.boundingRect().height() def getWidth(self, ): return self.TNode.boundingRect().width() def getRelList(self, ): '''return a list of all relationitems that are inbound or outbound from this node template. do not include self referencing relationships ''' return [ diagramItem for key, diagramItem in self.scene.parent.itemDict.items() if diagramItem.diagramType == "Relationship Template" and ( diagramItem.startNZID == self.NZID or diagramItem.endNZID == self.NZID) ] def getPoint(self, offset=None): ''' This function is used by the template diagram to calculate the location to drop a node template on the diagram ''' if offset is None: return QPointF(self.x, self.y) else: return QPointF(self.x + offset, self.y + offset) def getFormat(self, ): ''' determine if the Node Template has a template format or should use the project default format ''' # get the node Template custom format customFormat = self.nodeTemplateDict.get("TNformat", None) if not customFormat is None: # get the template custom format self.nodeFormat = TNodeFormat(formatDict=customFormat) else: # get the project default format self.nodeFormat = TNodeFormat( formatDict=self.model.modelData["TNformat"]) def clearItem(self, ): if (not self.TNode is None and not self.TNode.scene() is None): self.TNode.scene().removeItem(self.TNode) if (not self.TNtext is None and not self.TNtext.scene() is None): self.TNtext.scene().removeItem(self.TNtext) def drawIt(self, ): # get current format as it may have changed self.getFormat() # create the qgraphicsItems if they don't exist if self.TNode is None: # create the rectangle self.TNode = QGraphicsRectItem(QRectF( self.x, self.y, self.nodeFormat.formatDict["nodeWidth"], self.nodeFormat.formatDict["nodeHeight"]), parent=None) self.TNode.setZValue(NODELAYER) self.TNode.setFlag(QGraphicsItem.ItemIsMovable, True) self.TNode.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True) self.TNode.setFlag(QGraphicsItem.ItemIsSelectable, True) self.TNode.setSelected(True) self.TNode.setData(1, self.NZID) # get with self.INode.data(1) self.TNode.setData(ITEMTYPE, NODETEMPLATE) # create the text box self.TNtext = QGraphicsTextItem("", parent=None) self.TNtext.setPos(self.x, self.y) self.TNtext.setFlag(QGraphicsItem.ItemIsMovable, True) self.TNtext.setFlag(QGraphicsItem.ItemIsSelectable, False) self.TNtext.setData(NODEID, self.NZID) self.TNtext.setData(ITEMTYPE, NODETEMPLATETEXT) self.TNtext.setZValue(NODELAYER) # save the location self.x = self.TNode.sceneBoundingRect().x() self.y = self.TNode.sceneBoundingRect().y() # generate the html and resize the rectangle self.formatItem() # add the graphics items to the scene self.scene.addItem(self.TNode) self.scene.addItem(self.TNtext) else: # generate the html and resize the rectangle self.formatItem() def formatItem(self, ): # configure the formatting aspects of the qgraphics item pen = self.nodeFormat.pen() brush = self.nodeFormat.brush() self.TNode.setBrush(brush) self.TNode.setPen(pen) # generate the HTML genHTML = self.generateHTML() self.TNtext.prepareGeometryChange() # print("before html bounding rectangle width:{}".format(self.TNtext.boundingRect().width())) # print("before html text width:{}".format(self.TNtext.textWidth())) self.TNtext.setTextWidth( -1 ) # reset the width to unkonwn so it will calculate a new width based on the new html self.TNtext.setHtml(genHTML) # print("after html bounding rectangle width:{}".format(self.TNtext.boundingRect().width())) # print("after html text width:{}".format(self.TNtext.textWidth())) # make sure minimum width of 120 if self.TNtext.boundingRect().width() < 120: self.TNtext.setTextWidth(120) else: self.TNtext.setTextWidth( self.TNtext.boundingRect().width() ) # you have to do a setTextWidth to get the html to render correctly. # set the rectangle item to the same size as the formatted html self.TNode.prepareGeometryChange() currentRect = self.TNode.rect() # insure minimum height of 120 if self.TNtext.boundingRect().height() < 120: currentRect.setHeight(120) else: currentRect.setHeight(self.TNtext.boundingRect().height()) currentRect.setWidth(self.TNtext.boundingRect().width()) self.TNode.setRect(currentRect) def generateHTML(self, ): ''' Generate the HTML that formats the node template data inside the rectangle ''' # generate the html prefix = "<!DOCTYPE html><html><body>" # head = "<head><style>table, th, td {border: 1px solid black; border-collapse: collapse;}</style></head>" suffix = "</body></html>" # blankRow = "<tr><td><left>{}</left></td><td><left>{}</left></td><td><left>{}</left></td><td><left>{}</left></td></tr>".format("", "", "", "") name = "<center><b>{}</b></center>".format( self.nodeTemplateDict.get("name", "")) lbls = self.genLblHTML() props = self.genPropHTML() genHTML = "{}{}<hr>{}<br><hr>{}{}".format(prefix, name, lbls, props, suffix) # print("{} html: {}".format(self.name(), genHTML)) return genHTML def genLblHTML(self): # html = '<table width="90%">' html = '<table style="width:90%;border:1px solid black;">' if len(self.nodeTemplateDict.get("labels", [])) > 0: for lbl in self.nodeTemplateDict.get("labels", []): if lbl[NODEKEY] == Qt.Checked: nk = "NK" else: nk = " " if lbl[REQUIRED] == Qt.Checked: rq = "R" else: rq = "" html = html + '<tr align="left"><td width="15%"><left>{}</left></td><td width="65%"><left>{}</left></td><td width="10%"><left>{}</left></td><td width="10%"><left>{}</left></td></tr>'.format( nk, lbl[LABEL], "", rq) html = html + "</table>" else: html = '<tr align="left"><td width="15%"><left>{}</left></td><td width="65%"><left>{}</left></td><td width="10%"><left>{}</left></td><td width="10%"><left>{}</left></td></tr>'.format( " ", "NO{}LABELS".format(" "), "", "") html = html + "</table>" return html def genPropHTML(self): # PROPERTY, DATATYPE, PROPREQ, DEFAULT, EXISTS, UNIQUE, PROPNODEKEY html = '<table style="width:90%;border:1px solid black;">' if len(self.nodeTemplateDict.get("properties", [])) > 0: for prop in self.nodeTemplateDict.get("properties", []): if prop[PROPNODEKEY] == Qt.Checked: nk = "NK" else: nk = " " if prop[PROPREQ] == Qt.Checked: rq = "R" else: rq = "" if prop[EXISTS] == Qt.Checked: ex = "E" else: ex = "" if prop[UNIQUE] == Qt.Checked: uq = "U" else: uq = "" html = html + '<tr align="left"><td width="15%"><left>{}</left></td><td width="65%"><left>{}</left></td><td width="10%"><left>{}</left></td><td width="10%"><left>{}</left></td><td width="10%"><left>{}</left></td></tr>'.format( nk, prop[PROPERTY], rq, ex, uq) html = html + "</table>" else: html = html + '<tr align="left"><td width="15%"><left>{}</left></td><td width="65%"><left>{}</left></td><td width="10%"><left>{}</left></td><td width="10%"><left>{}</left></td></tr>'.format( " ", "NO{}PROPERTIES".format(" "), "", "", "") html = html + "</table>" return html def moveIt(self, dx, dy): ''' Move the node rectangle and the node textbox to the delta x,y coordinate. ''' # print("before moveIt: sceneboundingrect {} ".format( self.INode.sceneBoundingRect())) self.TNode.moveBy(dx, dy) self.x = self.TNode.sceneBoundingRect().x() self.y = self.TNode.sceneBoundingRect().y() self.TNtext.moveBy(dx, dy) # print("after moveIt: sceneboundingrect {} ".format( self.INode.sceneBoundingRect())) # now redraw all the relationships self.drawRels() def drawRels(self, ): '''Redraw all the relationship lines connected to the Node Template Rectangle''' # get a list of the relationship items connected to this node template self.relList = self.getRelList() # assign the correct inbound/outbound side for the rel for rel in self.relList: if rel.endNodeItem.NZID != rel.startNodeItem.NZID: # ignore bunny ears rel.assignSide() # get a set of all the nodes and sides involved nodeSet = set() for rel in self.relList: if rel.endNodeItem.NZID != rel.startNodeItem.NZID: # ignore bunny ears nodeSet.add((rel.endNodeItem, rel.inboundSide)) nodeSet.add((rel.startNodeItem, rel.outboundSide)) # tell each node side to assign rel locations for nodeSide in nodeSet: nodeSide[0].assignPoint(nodeSide[1]) ############################################ # now tell them all to redraw for rel in self.relList: rel.drawIt2() def calcOffset(self, index, totRels): offset = [-60, -40, -20, 0, 20, 40, 60] offsetStart = [3, 2, 2, 1, 1, 0, 0] if totRels > 7: totRels = 7 return offset[offsetStart[totRels - 1] + index] def assignPoint(self, side): # go through all the rels on a side and assign their x,y coord for that side self.relList = self.getRelList() sideList = [ rel for rel in self.relList if ((rel.startNZID == self.NZID and rel.outboundSide == side) or ( rel.endNZID == self.NZID and rel.inboundSide == side)) ] totRels = len(sideList) if totRels > 0: if side == R: # calc center of the side x = self.x + self.getWidth() y = self.y + self.getHeight() / 2 # sort the rels connected to this side by the y value sideList.sort(key=self.getSortY) # assign each of them a position on the side starting in the center and working out in both directions for index, rel in enumerate(sideList): if rel.startNZID == self.NZID: rel.outboundPoint = QPointF( x, y + (self.calcOffset(index, totRels))) if rel.endNZID == self.NZID: rel.inboundPoint = QPointF( x, y + (self.calcOffset(index, totRels))) elif side == L: x = self.x y = self.y + self.getHeight() / 2 sideList.sort(key=self.getSortY) for index, rel in enumerate(sideList): if rel.startNZID == self.NZID: rel.outboundPoint = QPointF( x, y + (self.calcOffset(index, totRels))) if rel.endNZID == self.NZID: rel.inboundPoint = QPointF( x, y + (self.calcOffset(index, totRels))) elif side == TOP: x = self.x + self.getWidth() / 2 y = self.y sideList.sort(key=self.getSortX) for index, rel in enumerate(sideList): if rel.startNZID == self.NZID: rel.outboundPoint = QPointF( x + (self.calcOffset(index, totRels)), y) if rel.endNZID == self.NZID: rel.inboundPoint = QPointF( x + (self.calcOffset(index, totRels)), y) elif side == BOTTOM: x = self.x + self.getWidth() / 2 y = self.y + self.getHeight() sideList.sort(key=self.getSortX) for index, rel in enumerate(sideList): if rel.startNZID == self.NZID: rel.outboundPoint = QPointF( x + (self.calcOffset(index, totRels)), y) if rel.endNZID == self.NZID: rel.inboundPoint = QPointF( x + (self.calcOffset(index, totRels)), y) else: print("error, no side") def getSortY(self, rel): # if this node is the start node then return the end node's Y if rel.startNZID == self.NZID: return rel.endNodeItem.TNode.sceneBoundingRect().center().y() # if this node is the end node then return the start node's Y if rel.endNZID == self.NZID: return rel.startNodeItem.TNode.sceneBoundingRect().center().y() # this should never happen return 0 def getSortX(self, rel): # if this node is the start node then return the end node's X if rel.startNZID == self.NZID: return rel.endNodeItem.TNode.sceneBoundingRect().center().x() # if this node is the end node then return the start node's X if rel.endNZID == self.NZID: return rel.startNodeItem.TNode.sceneBoundingRect().center().x() # this should never happen return 0 def getObjectDict(self, ): ''' This function returns a dictionary with all the data that represents this node template item. The dictionary is added to the Instance Diagram dictionary.''' objectDict = {} objectDict["NZID"] = self.NZID objectDict["name"] = self.nodeTemplateDict.get("name", "") objectDict["displayText"] = self.displayText objectDict["x"] = self.TNode.sceneBoundingRect().x() objectDict["y"] = self.TNode.sceneBoundingRect().y() objectDict["diagramType"] = self.diagramType objectDict["labels"] = self.nodeTemplateDict.get("labels", []) objectDict["properties"] = self.nodeTemplateDict.get("properties", []) return objectDict def setLogMethod(self, logMethod=None): if logMethod is None: if self.logMsg is None: self.logMsg = self.noLog else: self.logMsg = logMethod def noLog(self, msg): return
class MessageItem(GraphicsItem): def __init__(self, model_item: SimulatorMessage, parent=None): assert isinstance(model_item, SimulatorMessage) super().__init__(model_item=model_item, parent=parent) self.setFlag(QGraphicsItem.ItemIsPanel, True) self.arrow = MessageArrowItem(self) self.repeat_text = QGraphicsTextItem(self) self.repeat_text.setFont(self.font) def update_flags(self): if self.scene().mode == 0: self.set_flags(is_selectable=True, is_movable=True, accept_hover_events=True, accept_drops=True) def width(self): labels = self.labels() width = self.number.boundingRect().width() # width += 5 width += sum([lbl.boundingRect().width() for lbl in labels]) width += 5 * (len(labels) - 1) width += self.repeat_text.boundingRect().width() return width def refresh(self): self.repeat_text.setPlainText("(" + str(self.model_item.repeat) + "x)" if self.model_item.repeat > 1 else "") def labels(self): self.refresh_unlabeled_range_marker() unlabeled_range_items = [uri for uri in self.childItems() if isinstance(uri, UnlabeledRangeItem)] result = [] start = 0 i = 0 message = self.model_item if len(message) and not message.message_type: result.append(unlabeled_range_items[0]) else: for lbl in message.message_type: if lbl.start > start: result.append(unlabeled_range_items[i]) i += 1 result.append(self.scene().model_to_scene(lbl)) start = lbl.end if start < len(message): result.append(unlabeled_range_items[i]) return result def refresh_unlabeled_range_marker(self): msg = self.model_item urm = [item for item in self.childItems() if isinstance(item, UnlabeledRangeItem)] if len(msg): num_unlabeled_ranges = len(msg.message_type.unlabeled_ranges) if msg.message_type and msg.message_type[-1].end >= len(msg): num_unlabeled_ranges -= 1 else: num_unlabeled_ranges = 0 if len(urm) < num_unlabeled_ranges: for i in range(num_unlabeled_ranges - len(urm)): UnlabeledRangeItem(self) else: for i in range(len(urm) - num_unlabeled_ranges): self.scene().removeItem(urm[i]) def update_position(self, x_pos, y_pos): labels = self.labels() self.setPos(QPointF(x_pos, y_pos)) p_source = self.mapFromItem(self.source.line, self.source.line.line().p1()) p_destination = self.mapFromItem(self.destination.line, self.destination.line.line().p1()) arrow_width = abs(p_source.x() - p_destination.x()) start_x = min(p_source.x(), p_destination.x()) start_x += (arrow_width - self.width()) / 2 start_y = 0 self.number.setPos(start_x, start_y) start_x += self.number.boundingRect().width() for label in labels: label.setPos(start_x, start_y) start_x += label.boundingRect().width() + 5 self.repeat_text.setPos(start_x, start_y) if labels: start_y += labels[0].boundingRect().height() + 5 else: start_y += 26 self.arrow.setLine(p_source.x(), start_y, p_destination.x(), start_y) super().update_position(x_pos, y_pos) @property def source(self): return self.scene().participants_dict[self.model_item.participant] @property def destination(self): return self.scene().participants_dict[self.model_item.destination]
class MatchEnd(InfoScene): def __init__(self, parent): super().__init__() self.__parent__ = parent self.font = QFont() self.font.setPointSize(20) self.my_points = QGraphicsTextItem(str(self.__parent__.my_score)) self.my_points.setDefaultTextColor(Qt.white) self.my_points.setFont(self.font) self.opponent_points = QGraphicsTextItem( str(self.__parent__.opponent_score)) self.opponent_points.setDefaultTextColor(Qt.white) self.opponent_points.setFont(self.font) self.logo = QGraphicsPixmapItem() self.logo.setPos((SCENE_WIDTH - 600) / 2, 50) if self.__parent__.player == Player.PLAYER_1: self.my_points.setPos(50, 250) self.opponent_points.setPos( SCENE_WIDTH - (50 + self.opponent_points.boundingRect().width()), 250) else: self.opponent_points.setPos(50, 250) self.my_points.setPos( SCENE_WIDTH - (50 + self.opponent_points.boundingRect().width()), 250) self.buttons = [ Button(self.__parent__.load_info_scene, InfoScenes.MAIN_MENU.value, (SCENE_WIDTH - 300) / 2, SCENE_HEIGHT - 100, IMAGES_DIR + "menu/continue_highlighted.png", IMAGES_DIR + "menu/continue_highlighted.png", ButtonState.HIGHLIGHTED) ] if self.__parent__.my_score == self.__parent__.opponent_score: self.logo.setPixmap(QPixmap(IMAGES_DIR + "menu/draw.png")) elif self.__parent__.my_score > self.__parent__.opponent_score: self.logo.setPixmap(QPixmap(IMAGES_DIR + "menu/you_won.png")) else: self.logo.setPixmap(QPixmap(IMAGES_DIR + "menu/you_lost.png")) self.__draw_menu_buttons() self.addItem(self.my_points) self.addItem(self.opponent_points) self.addItem(self.logo) """ Handles keyboard presses """ def keyPressEvent(self, event): if event.key() == Qt.Key_W: self.__change_button_focus(Direction.UP) elif event.key() == Qt.Key_S: self.__change_button_focus(Direction.DOWN) elif event.key() == Qt.Key_Return: self.__execute_button() """ Draws buttons in the scene """ def __draw_menu_buttons(self): for button in self.buttons: self.addItem(button.graphics_item) """ Executes button logic """ def __execute_button(self): for index in range(len(self.buttons)): self.buttons[index].execute() """ Changes button focus """ def __change_button_focus(self, direction: Direction): if direction is None: return count = len(self.buttons) old_index = -1 new_index = -1 for index in range(count): if self.buttons[index].state is ButtonState.HIGHLIGHTED: old_index = index if direction is Direction.UP: if index - 1 > 0: new_index = index - 1 else: new_index = 0 elif direction is Direction.DOWN: if index + 1 > count - 1: new_index = count - 1 else: new_index = index + 1 self.buttons[old_index].set_state(ButtonState.NORMAL) self.buttons[new_index].set_state(ButtonState.HIGHLIGHTED)
textItem = QGraphicsTextItem() textItem.setHtml( "<font color=\"white\"><b>Stickman</b>" "<p>" "Tell the stickman what to do!" "</p>" "<p><i>" "<li>Press <font color=\"purple\">J</font> to make the stickman jump.</li>" "<li>Press <font color=\"purple\">D</font> to make the stickman dance.</li>" "<li>Press <font color=\"purple\">C</font> to make him chill out.</li>" "<li>When you are done, press <font color=\"purple\">Escape</font>.</li>" "</i></p>" "<p>If he is unlucky, the stickman will get struck by lightning, and never jump, dance or chill out again." "</p></font>") w = textItem.boundingRect().width() stickManBoundingRect = stickMan.mapToScene( stickMan.boundingRect()).boundingRect() textItem.setPos(-w / 2.0, stickManBoundingRect.bottom() + 25.0) scene = QGraphicsScene() scene.addItem(stickMan) scene.addItem(textItem) scene.setBackgroundBrush(Qt.black) view = GraphicsView() view.setRenderHints(QPainter.Antialiasing) view.setTransformationAnchor(QGraphicsView.NoAnchor) view.setScene(scene) view.show() view.setFocus()
def createGraphics(self): """ Create the graphical representation of the FMU's inputs and outputs """ def variableColor(variable): if variable.type == 'Real': return QColor.fromRgb(0, 0, 127) elif variable.type in ['Integer', 'Enumeration']: return QColor.fromRgb(255, 127, 0) elif variable.type == 'Boolean': return QColor.fromRgb(255, 0, 255) elif variable.type == 'String': return QColor.fromRgb(0, 128, 0) else: return QColor.fromRgb(0, 0, 0) inputVariables = [] outputVariables = [] maxInputLabelWidth = 0 maxOutputLabelWidth = 0 textItem = QGraphicsTextItem() fontMetrics = QFontMetricsF(textItem.font()) for variable in self.modelDescription.modelVariables: if variable.causality == 'input': inputVariables.append(variable) elif variable.causality == 'output': outputVariables.append(variable) for variable in inputVariables: maxInputLabelWidth = max(maxInputLabelWidth, fontMetrics.width(variable.name)) for variable in outputVariables: maxOutputLabelWidth = max(maxOutputLabelWidth, fontMetrics.width(variable.name)) from math import floor scene = QGraphicsScene() self.ui.graphicsView.setScene(scene) group = QGraphicsItemGroup() scene.addItem(group) group.setPos(200.5, -50.5) lh = 15 # line height w = max(150., maxInputLabelWidth + maxOutputLabelWidth + 20) h = max(50., 10 + lh * max(len(inputVariables), len(outputVariables))) block = QGraphicsRectItem(0, 0, w, h, group) block.setPen(QColor.fromRgb(0, 0, 255)) pen = QPen() pen.setWidthF(1) font = QFont() font.setPixelSize(10) # inputs y = floor((h - len(inputVariables) * lh) / 2 - 2) for variable in inputVariables: text = QGraphicsTextItem(variable.name, group) text.setDefaultTextColor(QColor.fromRgb(0, 0, 255)) text.setFont(font) text.setX(3) text.setY(y) polygon = QPolygonF([ QPointF(-13.5, y + 4), QPointF(1, y + 11), QPointF(-13.5, y + 18) ]) path = QPainterPath() path.addPolygon(polygon) path.closeSubpath() contour = QGraphicsPathItem(path, group) contour.setPen(QPen(Qt.NoPen)) contour.setBrush(variableColor(variable)) y += lh # outputs y = floor((h - len(outputVariables) * lh) / 2 - 2) for variable in outputVariables: text = QGraphicsTextItem(variable.name, group) text.setDefaultTextColor(QColor.fromRgb(0, 0, 255)) text.setFont(font) text.setX(w - 3 - text.boundingRect().width()) text.setY(y) polygon = QPolygonF([ QPointF(w, y + 0 + 7.5), QPointF(w + 7, y + 3.5 + 7.5), QPointF(w, y + 7 + 7.5) ]) path = QPainterPath() path.addPolygon(polygon) path.closeSubpath() contour = QGraphicsPathItem(path, group) pen = QPen() pen.setColor(variableColor(variable)) pen.setJoinStyle(Qt.MiterJoin) contour.setPen(pen) y += lh
class DetectorBox(QGraphicsRectItem): """ Represents a single detector box in the MultiDetectorSelector widget. """ length = 32 disabled_brush = QBrush(QColor(210, 210, 210, 200)) enabled_brush = QBrush(QColor(120, 123, 135, 255)) hovered_brush = QBrush(QColor(156, 186, 252, 255)) selected_brush = QBrush(QColor(80, 110, 206, 255)) invisible_pen = QPen(QColor(255, 255, 255, 0)) red_pen = QPen(QColor('red')) def __init__(self, row, column, enabled, *args): self._detector_id = box_id(row, column) self._enabled = enabled self._selected = False self._rect = QRectF(*args) self._rect.setHeight(self.length) self._rect.setWidth(self.length) super().__init__(self._rect) self.setPen(self.invisible_pen) self.setAcceptHoverEvents(True) if enabled: self.setBrush(self.enabled_brush) else: self.setBrush(self.disabled_brush) # states: enabled, disabled, hovered, selected self._label = QGraphicsTextItem(str(self.detector_id), self) self._label.setTransform(flip_vertical, True) self._label.setFont(QFont('Arial', 14)) self._label.setDefaultTextColor(QColor('white')) self._detector_selector = None @property def detector_id(self): return self._detector_id @property def size(self): return self._rect.size() @property def selected(self): return self._selected def set_parent_selector(self, parent): self._detector_selector = parent def hoverEnterEvent(self, event): if self._enabled: self.setBrush(self.hovered_brush) def hoverLeaveEvent(self, event): if self._enabled: if self._selected: self.setBrush(self.selected_brush) else: self.setBrush(self.enabled_brush) else: self.setBrush(self.disabled_brush) def mousePressEvent(self, event): if self._enabled: if event.button() == Qt.LeftButton: if not self._selected: self._selected = True self.setBrush(self.selected_brush) self.setPen(self.red_pen) else: self._selected = False self.setBrush(self.enabled_brush) self.setPen(self.invisible_pen) self._detector_selector.selection_changed() def place_label(self, rect): # FIXMe: placement should be done differently, so that flipping the text leaves it in the same place. x_center = 0.5 * (rect.left() + rect.right() ) - 0.5 * self._label.boundingRect().width() y_center = 0.5 * (rect.bottom() + rect.top( )) - 0.5 * self._label.boundingRect().height() + self.length self._label.setPos(x_center, y_center)
textItem.setHtml( '<font color="white"><b>Stickman</b>' "<p>" "Tell the stickman what to do!" "</p>" "<p><i>" '<li>Press <font color="purple">J</font> to make the stickman jump.</li>' '<li>Press <font color="purple">D</font> to make the stickman dance.</li>' '<li>Press <font color="purple">C</font> to make him chill out.</li>' '<li>When you are done, press <font color="purple">Escape</font>.</li>' "</i></p>" "<p>If he is unlucky, the stickman will get struck by lightning, and never jump, dance or chill out again." "</p></font>" ) w = textItem.boundingRect().width() stickManBoundingRect = stickMan.mapToScene(stickMan.boundingRect()).boundingRect() textItem.setPos(-w / 2.0, stickManBoundingRect.bottom() + 25.0) scene = QGraphicsScene() scene.addItem(stickMan) scene.addItem(textItem) scene.setBackgroundBrush(Qt.black) view = GraphicsView() view.setRenderHints(QPainter.Antialiasing) view.setTransformationAnchor(QGraphicsView.NoAnchor) view.setScene(scene) view.show() view.setFocus()
class Node(QGraphicsRectItem): class io(QGraphicsRectItem): class BezierCurve(QGraphicsPathItem): def __init__(self, iostart=None, ioend=None): super().__init__() self.setEnabled( False ) # Make it ignore events. Links can't be interacted with. self.iostart = iostart self.ioend = ioend if iostart is not None and ioend is not None: self.update() else: self.update(QPointF(0, 0)) def update(self, pos=None): path = QPainterPath() if pos is not None: if self.ioend is None: startpos = self.iostart.pos( ) + self.iostart.parent.pos() endpos = pos elif self.iostart is None: startpos = pos endpos = self.ioend.pos() + self.ioend.parent.pos() else: startpos = self.iostart.pos() + self.iostart.parent.pos() endpos = self.ioend.pos() + self.ioend.parent.pos() controlpoint = QPointF(abs((endpos - startpos).x()) * 0.8, 0) path.moveTo(startpos) path.cubicTo(startpos + controlpoint, endpos - controlpoint, endpos) self.setPath(path) def __init__(self, parent, index, iotype, iodir): self.parent = parent self.index = index self.iotype = iotype self.iodir = iodir super().__init__(-8, -8, 16, 16, self.parent) # Size of io-boxes is 16x16 self.setAcceptHoverEvents(True) self.iobrush = QBrush(QColor(70, 70, 70, 255)) self.setBrush(self.iobrush) self.newbezier = None # Variable for temporary storage of bezier curve while it's still being dragged self.bezier = [] def mousePressEvent(self, event): if event.button() == Qt.LeftButton: if self.iodir == "output": self.newbezier = Node.io.BezierCurve(self, None) elif self.iodir == "input": self.newbezier = Node.io.BezierCurve(None, self) if self.newbezier is not None: self.newbezier.update( QPointF(event.pos() + self.pos() + self.parent.pos())) self.parent.parent.scene.addItem(self.newbezier) elif event.button() == Qt.RightButton: self.delAllBezier() def mouseMoveEvent(self, event): if self.newbezier is not None: self.newbezier.update( QPointF(event.pos() + self.pos() + self.parent.pos())) def mouseReleaseEvent(self, event): if self.newbezier is not None: self.parent.parent.scene.removeItem(self.newbezier) self.newbezier = None # Find out if an io box lies under the cursor pos = event.pos() + self.pos() + self.parent.pos() pos = self.parent.parent.mapFromScene(pos) target = self.parent.parent.itemAt(pos.x(), pos.y()) # If io box is found, spawn a bezier curve if target is None: if self.iodir == "output": ns = NodeSelector(self.parent.parent.modman, modfilter={ "type": { "type": self.iotype, "dir": "input" } }) elif self.iodir == "input": ns = NodeSelector(self.parent.parent.modman, modfilter={ "type": { "type": self.iotype, "dir": "output" } }) if ns.exec(): if issubclass(ns.data["node"], baseModule.BaseNode): newNode = Node(self.parent.parent, ns.data["node"]) newNode.setPos(event.pos() + self.pos() + self.parent.pos()) self.parent.parent.scene.addItem(newNode) if self.iodir == "output": target = newNode.inputIO[ns.data["pin"]] elif self.iodir == "input": target = newNode.outputIO[ns.data["pin"]] if target is not None and isinstance(target, Node.io): bezier = None if self.iodir == "output" and target.iodir == "input" and ( self.iotype == target.iotype or not self.iotype or not target.iotype): bezier = Node.io.BezierCurve(self, target) if not self.iotype == "exec": target.delAllBezier() elif self.iotype == "exec": if len(self.bezier) >= 1: self.delAllBezier() elif self.iodir == "input" and target.iodir == "output" and ( self.iotype == target.iotype or not self.iotype or not target.iotype): bezier = Node.io.BezierCurve(target, self) if not self.iotype == "exec": self.delAllBezier() elif self.iotype == "exec": if len(target.bezier) >= 1: target.delAllBezier() if bezier is not None: self.bezier.append(bezier) target.bezier.append(bezier) self.parent.parent.scene.addItem(bezier) def hoverEnterEvent(self, event): self.parent.setPen( QPen(QColor("black")) ) # For some f*****g reason QGraphicsSceneHoverEvents can't be accepted, so this dirty workaround has to be used... self.setPen(QPen(QColor("yellow"))) def hoverLeaveEvent(self, event): self.parent.setPen(QPen(QColor("yellow"))) # See above self.setPen(QPen(QColor("black"))) def updateBezier(self): for bezier in self.bezier: bezier.update() def delAllBezier(self): beziercpy = self.bezier[:] for bezier in beziercpy: bezier.iostart.bezier.remove(bezier) bezier.ioend.bezier.remove(bezier) self.parent.parent.scene.removeItem(bezier) def __init__(self, parent, nodedata=None, id=None): if nodedata is None: raise ValueError("Nodetype must not be 'None'") self.nodedata = nodedata self.parent = parent self.width = 32 self.height = 0 super().__init__(-self.width / 2, -self.height / 2, self.width, self.height) self.setAcceptHoverEvents(True) self.setFlag(QGraphicsItem.ItemIsSelectable, True) self.extraNodeData = {} if id is not None: self.id = id else: self.id = uuid.uuid4().hex # Random ID to identify node self.nodebrush = QBrush(QColor(100, 100, 100, 255)) self.setBrush(self.nodebrush) # Node Title self.nodetitle = self.nodedata.name self.nodetitleTextItem = QGraphicsTextItem(self.nodetitle, self) self.width = max(self.width, self.nodetitleTextItem.boundingRect().width()) self.height += self.nodetitleTextItem.boundingRect().height() # Create all the text items for the IO self.inputTextItems = [] self.outputTextItems = [] for i in range( max(len(self.nodedata.inputDefs), len(self.nodedata.outputDefs))): linewidth = 0 lineheight = 0 try: self.inputTextItems.append( QGraphicsTextItem(self.nodedata.inputDefs[i][0], self)) linewidth += self.inputTextItems[i].boundingRect().width() lineheight = max( lineheight, self.inputTextItems[i].boundingRect().height()) except IndexError: pass try: self.outputTextItems.append( QGraphicsTextItem(self.nodedata.outputDefs[i][0], self)) linewidth += self.outputTextItems[i].boundingRect().width() lineheight = max( lineheight, self.outputTextItems[i].boundingRect().height()) except IndexError: pass linewidth += 12 # Keep atleast 12px distance between the input and output text self.width = max(self.width, linewidth) self.height += lineheight # Set correct positions for all text items self.nodetitleTextItem.setPos(-self.width / 2, -self.height / 2) self.inputIO = [] heightPointer = 0 for i in range( max(len(self.nodedata.inputDefs), len(self.nodedata.outputDefs))): try: self.inputTextItems[i].setPos( -self.width / 2, -self.height / 2 + self.nodetitleTextItem.boundingRect().height() + heightPointer) heightPointer += self.inputTextItems[i].boundingRect().height() newinput = Node.io(self, i, self.nodedata.inputDefs[i][1], "input") self.inputIO.append(newinput) newinput.setPos( -self.width / 2 - newinput.rect().width() / 2, -self.height / 2 + heightPointer + self.inputTextItems[i].boundingRect().height() / 2) except IndexError: pass self.outputIO = [] heightPointer = 0 for i in range( max(len(self.nodedata.inputDefs), len(self.nodedata.outputDefs))): try: self.outputTextItems[i].setPos( self.width / 2 - self.outputTextItems[i].boundingRect().width(), -self.height / 2 + self.nodetitleTextItem.boundingRect().height() + heightPointer) heightPointer += self.outputTextItems[i].boundingRect().height( ) newoutput = Node.io(self, i, self.nodedata.outputDefs[i][1], "output") self.outputIO.append(newoutput) newoutput.setPos( self.width / 2 + newoutput.rect().width() / 2, -self.height / 2 + heightPointer + self.outputTextItems[i].boundingRect().height() / 2) except IndexError: pass # Set rect to correct size self.setRect(-self.width / 2, -self.height / 2, self.width, self.height) # Additional stuff self.setFlag(QGraphicsItem.ItemIsMovable, True) def delete(self): for io in self.inputIO + self.outputIO: io.delAllBezier() io.parent = None io.bezier = None del self.inputIO del self.outputIO def mouseMoveEvent(self, event): super().mouseMoveEvent(event) for io in self.inputIO + self.outputIO: io.updateBezier() def hoverEnterEvent(self, event): self.setPen(QPen(QColor("yellow"))) def hoverLeaveEvent(self, event): self.setPen(QPen(QColor("black")))
class TextInputWidget(QGraphicsObject): input_entered = pyqtSignal(str) input_cancelled = pyqtSignal(str) font = QFont('monospace', 16) def __init__(self, mode: str = 'number', label: str = '', getter=None, setter=None, parser=str, validator=None, parent: Optional[QGraphicsItem] = None): QGraphicsObject.__init__(self, parent) self.getter: Callable[[], str] = getter self.setter: Callable[[str], None] = setter self.parser: Callable[[str], Any] = parser self.validator: Callable[[Any], bool] = validator self.accept_button = Button("btn_accept", "Accept", parent=self) self.accept_button.set_base_color([ButtonColor.GREEN]) self.accept_button.clicked.connect(self.handle_input_accepted) self.cancel_button = Button("btn_cancel", "Cancel", parent=self) self.cancel_button.set_base_color([ButtonColor.RED]) self.cancel_button.clicked.connect( lambda: self.input_cancelled.emit(self.text_field.toPlainText())) self.accept_button.set_height(12) self.cancel_button.set_height(12) self.hor_padding = 2 self.background_rect = QGraphicsRectItem(parent=self) self.background_rect.setBrush(QBrush(QColor(50, 50, 50, 200))) self.text_field = NumberInputItem(self) self.text_field.setFont(self.font) self.text_field.setTextInteractionFlags(Qt.TextEditable) self.text_field.setDefaultTextColor(Qt.white) self.text_field.text_changed.connect(self.adjust_layout) self.text_field.set_is_number_mode(mode == 'number') if self.getter is not None: self.text_field.setPlainText(str(self.getter())) self.adjust_layout() self.text_field.accepted.connect( lambda: self.accept_button.click_button(artificial_emit=True)) self.text_field.cancelled.connect( lambda: self.cancel_button.click_button(artificial_emit=True)) self.text_label = QGraphicsTextItem(self) self.text_label.setFont(self.font) self.text_label.setPlainText(label) self.text_label.setDefaultTextColor(Qt.white) self.setVisible(False) def paint(self, painter: QPainter, option: QStyleOptionGraphicsItem, widget: Optional[QWidget] = ...) -> None: pass def boundingRect(self) -> QRectF: return self.background_rect.boundingRect().united(self.accept_button.boundingRect())\ .united(self.cancel_button.boundingRect()) def adjust_layout(self): self.accept_button.fit_to_contents() self.cancel_button.fit_to_contents() total_width = 3 * self.hor_padding + self.text_field.boundingRect().width() \ + self.text_label.boundingRect().width() total_width = max( total_width, 3 * self.hor_padding + self.accept_button.boundingRect().width() + self.cancel_button.boundingRect().width()) total_height = self.text_field.boundingRect().height() + self.accept_button.boundingRect().height() \ + 3 * self.hor_padding self.background_rect.setRect(0, 0, total_width, total_height) self.setTransformOriginPoint( QPointF(0.5 * total_width, 0.5 * total_height)) self.text_label.setPos(-0.5 * total_width + self.hor_padding, -0.5 * total_height + self.hor_padding) self.text_field.setPos( self.hor_padding + self.text_label.pos().x() + self.text_label.boundingRect().width(), self.text_label.pos().y()) self.background_rect.setPos(-0.5 * total_width, -0.5 * total_height) self.accept_button.set_width( (total_width - 3 * self.hor_padding) * 0.5) self.cancel_button.set_width( (total_width - 3 * self.hor_padding) * 0.5) self.accept_button.setPos(-0.5 * total_width + self.hor_padding, 2 * self.hor_padding) self.cancel_button.setPos(self.accept_button.pos() + QPointF( self.hor_padding + self.accept_button.boundingRect().width(), 0.0)) self.accept_button.set_disabled( len(self.text_field.toPlainText()) == 0) if self.validator is not None: try: value = self.parser(self.text_field.toPlainText()) is_valid = self.validator(value) except ValueError: is_valid = False self.text_field.set_is_valid(is_valid) self.accept_button.set_disabled(not is_valid) self.update() def get_value(self) -> str: return self.text_field.toPlainText() def set_value(self, text: str): self.text_field.setPlainText(text) self.adjust_layout() self.set_focus() def set_focus(self): self.text_field.setFocus(Qt.PopupFocusReason) cursor = self.text_field.textCursor() cursor.movePosition(QTextCursor.EndOfLine) self.text_field.setTextCursor(cursor) def set_getter_setter_parser_validator(self, getter: Callable[[], Any], setter: Callable[[Any], None], parser: Callable[[str], Any], validator: Callable[[Any], bool]): self.getter = getter self.setter = setter self.parser = parser self.validator = validator if self.getter is not None: self.text_field.setPlainText(str(self.getter())) self.adjust_layout() def set_label(self, label: str): self.text_label.setPlainText(label) self.adjust_layout() def handle_input_accepted(self): self.setter(self.parser(self.text_field.toPlainText())) self.input_entered.emit(self.text_field.toPlainText()) def handle_input_cancelled(self): self.input_cancelled.emit(str(self.getter()))
class Block(QGraphicsRectItem): """ Entity-block with text & four connection ports """ def __init__(self, name='Untitled', parent=None): super(Block, self).__init__(None) self.parent = parent w = 60.0 h = 40.0 self.__base_color = QColor(158, 94, 155) # Properties of the rectangle: self.setPen(QtGui.QPen(self.__base_color, 2)) # self.setPen(QtGui.QPen(QtCore.Qt.blue, 2)) self.setBrush(QtGui.QBrush(self.__base_color)) self.setFlags(self.ItemIsSelectable | self.ItemIsMovable) self.setCursor(QCursor(QtCore.Qt.PointingHandCursor)) # Label: self.label = QGraphicsTextItem(name, self) self.label.setFont(QFont()) self.name = name # Create corner for resize: self.sizer = HandleItem(self) self.sizer.setPos(w, h) self.sizer.posChangeCallbacks.append( self.changeSize) # Connect the callback self.sizer.setFlag(self.sizer.ItemIsSelectable, True) # Inputs and outputs of the block: self.ports = [] self.ports.append(PortItem('a', self)) self.ports.append(PortItem('b', self)) self.ports.append(PortItem('c', self)) self.ports.append(PortItem('d', self)) # Update size: self.changeSize(w, h) def get_text(self): return self.label.toPlainText() def get_random_port(self): i = random.randint(0, len(self.ports) - 1) if i in range(0, 5): return self.ports[i] def delete(self): msg = QMessageBox() msg.setIcon(QMessageBox.Question) msg.setText("Вы действительно хотите удалить блок?") msg.setInformativeText("Это действие нельзя отменить") msg.setWindowTitle("Подтвердите действие") msg.setDetailedText("При удалении блока удалятся все его соединения!") msg.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel) confirm = msg.exec_() if confirm == QMessageBox.Ok: self.parent.remove_node(self) def edit_parameters(self): text, ok = QInputDialog.getText(None, 'Параметры', 'Текст блока:', text=self.label.toPlainText()) if ok: self.label.setPlainText(text) def contextMenuEvent(self, event): menu = QMenu() action_delete = menu.addAction('Удалить') action_params = menu.addAction('Параметры') action_delete.triggered.connect(self.delete) action_params.triggered.connect(self.edit_parameters) menu.exec_(event.screenPos()) def changeSize(self, w, h): """ Resize block function """ # Limit the block size: metric = QFontMetrics(self.label.font()) width = metric.width(self.name) height = metric.height() if h < height + 5: h = height + 5 if w < width + 5: w = width + 5 self.setRect(0.0, 0.0, w, h) # center label: rect = self.label.boundingRect() lw, lh = rect.width(), rect.height() lx = (w - lw) / 2 ly = (h - lh) / 2 self.label.setPos(lx, ly) # Update port positions: self.ports[0].setPos(0, h / 2) self.ports[1].setPos(w / 2, 0) self.ports[2].setPos(w, h / 2) self.ports[3].setPos(w / 2, h) return w, h