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
def render_items(self, event=None): self.links = self.get_links_dict() self.render_preview() for item in self.settings['items']: text = QGraphicsTextItem() font_color = QColor('black') text.setPos(QPointF(item['x'], item['y'])) font = QFont() font_name = item['params'].get(const.ITEM_DATA_KEY_FONT) if font_name: font.fromString(font_name) font_size = item['params'].get(const.ITEM_DATA_KEY_FONT_SIZE) if font_size: font.setPointSize(font_size) else: font.setPointSize(12) font_color_list = item['params'].get( const.ITEM_DATA_KEY_FONT_COLOR) if font_color_list: font_color.setRgb(*font_color_list) text.setFont(font) text_align = item['params'].get(const.ITEM_DATA_KEY_FONT_ALIGN) text.setFont(font) text.setTextWidth(item['w']) text.setDefaultTextColor(font_color) text.setHtml( f"<div align='{text_align}'>{self.test_row[self.links[item['name']]]}</div>" ) self.scene.addItem(text)
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
def display_text(message, view: QGraphicsView): text = QGraphicsTextItem() text.setPos(0, 0) text.setPlainText(message.text) text.setTextWidth(view.width()) scene = QGraphicsScene() scene.addItem(text) view.setAlignment(Qt.AlignTop | Qt.AlignLeft) view.setScene(scene)
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)
def showImage(self): (newImg, newImgInfo) = self.loadImage() # return PicItem(Pixmap(QPixmap(newImg)), -1, -1, xFactor, yFactor, newImgInfo) self.scene.clear() imgSz = newImgInfo.imgSize self.setSceneRect(QRectF(0,0,imgSz.width(), imgSz.height())) pixMap = QPixmap.fromImage(newImg) # # pixMap.setWidth(self.width()) pixMapItem = self.scene.addPixmap(pixMap) # pixMapItem.setPos(50,50) # self.fitInView(QRectF(0, 0, self.width(), self.height()), Qt.KeepAspectRatio) # Add caption caption = QGraphicsTextItem() caption.setDefaultTextColor(QColor(255,255,255)) caption.setPos(0, self.height()*0.94) caption.setFont(QFont("Segoe UI", 30)) caption.setTextWidth(self.width()) # caption.setPos(100, 100) # caption.setTextWidth(1500) # if newImgInfo.createDate is not None: # caption.setPlainText(newImgInfo.createDate.format()); # else: # caption.setPlainText("Image is called bananas"); # print("Tags", newImgInfo.tags) # tagStr = "" # for tag in newImgInfo.tags: # if tag != "Duplicate": # tagStr += (", " if len(tagStr) != 0 else "") + tag # if tagStr == "": # tagStr = "NO TAGS" # captionStr = '<h1 style="text-align:center;width:100%">' + tagStr + '</h1>' # if newImgInfo.createDate is not None: # print(newImgInfo.createDate.format()) # captionStr += '<BR><h2>' + newImgInfo.createDate.format() + '</h2>' captionStr = "" try: if newImgInfo.rating is not None: for i in range(newImgInfo.rating): captionStr += "★" for i in range(5-newImgInfo.rating): captionStr += "☆" if newImgInfo.mainDate is not None: if len(captionStr) != 0: captionStr += " " captionStr += newImgInfo.mainDate.strftime("%d %b %Y") except Exception as excp: print("StaticPhotos: Cannot set caption") captionStr = '<div style="background-color:#000000;text-align: right;padding-right:10dp;">' + captionStr + "</div>" print(captionStr) caption.setHtml(captionStr) self.scene.addItem(caption) self.scene.update()
class AnimatedClock(): updateTimer = None calDataLock = threading.Lock() calDataUpdated = False curCalendars = None def __init__(self, scene, widthClkTextArea, heightClkTextArea, borders, updateSecs): self.masterScene = scene self.widthClkTextArea = widthClkTextArea self.heightClkTextArea = heightClkTextArea self.borders = borders self.updateSecs = updateSecs # Background self.textBkgd = QGraphicsRectItem(0, 0, self.widthClkTextArea, self.heightClkTextArea) self.textBkgd.setPos(self.borders[3], self.borders[0]) self.textBkgd.setBrush(QColor("light green")) self.textBkgd.setZValue(10) scene.addItem(self.textBkgd) # Text Item self.textItem = QGraphicsTextItem() self.textItem.setFont(QFont("Segoe UI", 80)) self.textItem.setDefaultTextColor(QColor("black")) self.textItem.setPos(QPointF(self.borders[3]+10,self.borders[0]+5)) self.textItem.setHtml("<B>Clock</B>") self.textItem.setZValue(20) self.textItem.setTextWidth(self.widthClkTextArea-20) scene.addItem(self.textItem) def start(self): self.updateTimer = QTimer() self.updateTimer.setInterval(self.updateSecs * 1000) self.updateTimer.timeout.connect(self.updateClock) self.updateTimer.start() def stop (self): if self.updateTimer != None: self.updateTimer.stop() def updateClock(self): localtime = time.localtime() # dateString = time.strftime("%a %d %b %Y", localtime) timeString = time.strftime("%H:%M:%S", localtime) self.textItem.setHtml(timeString) # self.textItem.setTextWidth(self.widthCalTextArea-20) self.textItem.update()
def actually_show_text(self, data): text_item = QGraphicsTextItem() text_item.setZValue(5) if len(data) >= 8 and data[7] != None: text_item.setDefaultTextColor(QColor(data[7])) else: text_item.setDefaultTextColor(QColor("#FFFFFF")) text_item.setX(data[2]) text_item.setY(data[3]) text_item.setTextWidth(data[4]) text_item.setPlainText(data[5]) temp_font = text_item.font() temp_font.setPointSize(data[6]) text_item.setFont(temp_font) self.addItem(text_item) self.texts[data[1]] = text_item self.call_next_action()
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 AnimatedCalendar(): updatesRunning = False updateTimer = None listUpdateThread = None calDataLock = threading.Lock() calDataUpdated = False curCalendars = None def __init__(self, scene, widthCalTextArea, heightCalTextArea, borders, calFeeds, calUpdateSecs): self.masterScene = scene self.widthCalTextArea = widthCalTextArea self.heightCalTextArea = heightCalTextArea self.borders = borders self.calFeeds = calFeeds self.calendarUpdateSecs = calUpdateSecs # Background self.textBkgd = QGraphicsRectItem(0, 0, self.widthCalTextArea, self.heightCalTextArea) self.textBkgd.setPos(self.borders[3], self.borders[0]) self.textBkgd.setBrush(QColor("light green")) self.textBkgd.setZValue(10) scene.addItem(self.textBkgd) # Text Item self.textItem = QGraphicsTextItem() self.textItem.setFont(QFont("Segoe UI", 24)) self.textItem.setDefaultTextColor(QColor("black")) self.textItem.setPos(QPointF(self.borders[3]+10,self.borders[0]+10)) self.textItem.setHtml("<B>Hello</B>Hello") self.textItem.setZValue(20) self.textItem.setTextWidth(self.widthCalTextArea-20) scene.addItem(self.textItem) def start(self): self.updatesRunning = True QTimer.singleShot(100, self.updateCalendar) self.listUpdateThread = CalendarUpdateThread(self, self.calFeeds, self.calendarUpdateSecs) self.listUpdateThread.start() # print("CalStarted") def stop (self): self.updatesRunning = False if self.updateTimer != None: self.updateTimer.stop() if self.listUpdateThread != None: self.listUpdateThread.stop() def setNewCalendarEntries(self, calendars): with self.calDataLock: self.curCalendars = calendars self.calDataUpdated = True def updateCalendar(self): # print("Update cal") with self.calDataLock: if self.calDataUpdated and self.curCalendars != None: for calEvents in self.curCalendars: calStr = "" lastDay = -1 for anEvent in calEvents: # date, duration, summary, location, UID eventDate = anEvent[0] duration = anEvent[1] summary = anEvent[2] location = anEvent[3] if lastDay != eventDate.day: if lastDay != -1: calStr += "<br/>" calStr += "<b>" + anEvent[0].strftime("%a") + " (" + anEvent[0].strftime("%d %B)") + ")</b><br/>" lastDay = eventDate.day strDurTime = str(duration).rpartition(":")[0] durStr = (str(duration.days) + "day" + ("s" if duration.days != 1 else "")) if duration.days > 0 else strDurTime locStr = "<small>("+location+")</small>" if location != "" else "" calStr += anEvent[0].strftime("%H:%M") + " <small>(" + durStr + ")</small> " + summary + " " + locStr + "<br/>" # print (anEvent) # print(date) self.textItem.setHtml(calStr) self.textItem.setTextWidth(self.widthCalTextArea-20) self.textItem.update() self.calDataUpdated = False if not self.updatesRunning: return self.updateTimer = QTimer() self.updateTimer.setInterval(5000) self.updateTimer.setSingleShot(True) self.updateTimer.timeout.connect(self.updateCalendar) self.updateTimer.start()
class Scene_Base(QGraphicsScene): def __init__(self, window): super().__init__() self.setSceneRect(0, 0, window.game_width, window.game_height) self.window = window self.init_fields() self.create_fade() self.dialog_box = None self.setup() def init_fields(self): self.actions = [] self.current_action = -1 self.dialog_box = None self.target_text = None self.current_dialog_text = None self.current_dialog_text_offset = 0 self.background = None self.fading_box = None self.fade_dir = 0 self.next_scene = None self.buttons = [] self.current_id = 1 self.texts = {} self.images = {} self.moving_images = [] self.updatable_images = [] self.vaxx = None self.entry = None self.pathogen = None self.tempPic = None def create_fade(self, opc=1): self.fading_box = QGraphicsRectItem(0, 0, self.width(), self.height()) self.fading_box.setBrush(QColor(0, 0, 0)) self.fading_box.setOpacity(opc) self.fading_box.setZValue(100) self.addItem(self.fading_box) # ============================================== # * Setup # # Overwrite with child classes to add actions. # ============================================== def setup(self): pass # ============================================== # * Update # # Updates the scene. Handles input, waiting, etc. # ============================================== def update(self): self.update_fade() self.update_dialog() self.update_dialog_text() self.update_moving_images() self.update_animated_images() def update_fade(self): if self.fading_box is not None: if self.fade_dir == 0 and self.fading_box.opacity() > 0: self.fading_box.setOpacity(self.fading_box.opacity() - 0.02) if self.fading_box.opacity() <= 0: self.removeItem(self.fading_box) self.fading_box = None elif self.fade_dir == 1 and self.fading_box.opacity() < 1: self.fading_box.setOpacity(self.fading_box.opacity() + 0.02) if self.fading_box.opacity() >= 1: if self.next_scene is not None: self.window.goto_scene(self.next_scene) else: self.actually_close_game() def update_dialog(self): if self.dialog_box is not None: if self.dialog_box.opacity() < 0.5: self.dialog_box.setOpacity(self.dialog_box.opacity() + 0.04) if self.dialog_box.opacity() >= 0.5: self.dialog_box.setOpacity(0.5) if self.actions[0][0] == 0: self.actually_show_dialog(self.actions[0]) def update_dialog_text(self): if self.current_dialog_text is not None: curr_text = self.current_dialog_text.toPlainText() if curr_text != self.target_text: if self.current_dialog_text_offset < 3: self.current_dialog_text_offset += 1 else: self.current_dialog_text_offset = 0 self.current_dialog_text.setPlainText(self.target_text[:len(curr_text) + 1]) def update_moving_images(self): if len(self.moving_images) > 0: index = 0 new_data = [] for image in self.moving_images: pic_id = image[0] item = self.images[pic_id] item.setX(item.x() + image[1]) item.setY(item.y() + image[2]) image[3] -= 1 if image[3] > 0: new_data.append(image) elif image[4] is not None: image[4]() index += 1 self.moving_images = new_data def update_animated_images(self): if len(self.updatable_images) > 0: index = 0 for image in self.updatable_images: image[2] += 1 if image[2] > image[4]: image[2] = 0 image[3] += 1 if image[3] >= len(image[1]): image[3] = 0 image[0].setPixmap(QPixmap(image[1][image[3]])) # ============================================== # * Mouse Events # # Handle mouse input. # ============================================== def mouseMoveEvent(self, mouseEvent): pass def mousePressEvent(self, mouseEvent): self.when_mouse_pressed(mouseEvent) def mouseDoubleClickEvent(self, mouseEvent): self.when_mouse_pressed(mouseEvent) def when_mouse_pressed(self, mouseEvent): if self.current_action == 0 and mouseEvent.button() == 1: if self.current_dialog_text is not None: if self.current_dialog_text.toPlainText() != self.target_text: self.current_dialog_text.setPlainText(self.target_text) else: self.removeItem(self.current_dialog_text) self.call_next_action() else: super(Scene_Base, self).mousePressEvent(mouseEvent) # ============================================== # * Action Management # ============================================== def finish_action(self): if len(self.actions) > 0: self.actions.pop(0) else: self.wait_for_button_press() def check_if_first(self): if len(self.actions) == 1: self.perform_next_action() def perform_next_action(self): if len(self.actions) > 0: action = self.actions[0] self.current_action = action_type = action[0] if action_type is -1: self.actually_hide_dialog_box() elif action_type is 0: self.actually_show_dialog(action) elif action_type is 1: self.actually_show_button(action) elif action_type is 2: self.actually_set_background(action) elif action_type is 3: self.actually_goto_scene(action) elif action_type is 4: self.actually_close_game() elif action_type is 5: self.actually_play_song(action) elif action_type is 6: self.actually_wait_for_button_press() elif action_type is 7: self.actually_remove_all_buttons() elif action_type is 8: self.actually_show_text(action) elif action_type is 9: self.actually_hide_text(action) elif action_type is 10: self.actually_show_image(action) elif action_type is 11: self.actually_hide_image(action) elif action_type is 12: self.actually_move_image(action) elif action_type is 50: self.actually_play_sound(action) def call_next_action(self): self.finish_action() self.perform_next_action() # ============================================== # * Actual Implementations # ============================================== def actually_show_dialog(self, data): if self.dialog_box is None: self.create_dialog_box() else: self.current_dialog_text = QGraphicsTextItem() self.current_dialog_text.setZValue(20) self.current_dialog_text.setDefaultTextColor(QColor(255, 255, 255)) temp_font = self.current_dialog_text.font() temp_font.setPointSize(data[2]) self.current_dialog_text.setFont(temp_font) self.addItem(self.current_dialog_text) self.current_dialog_text.setX(self.dialog_box.x() + 10) self.current_dialog_text.setY(self.dialog_box.y() + 10) self.current_dialog_text.setTextWidth(self.dialog_box.boundingRect().width() - 20) self.target_text = data[1] self.current_dialog_text_offset = 0 def create_dialog_box(self): self.dialog_box = QGraphicsRectItem(0, 0, self.width() - 20, self.height() / 4) self.dialog_box.setBrush(QColor(0, 0, 0)) self.dialog_box.setX(10) self.dialog_box.setY(self.height() - self.dialog_box.boundingRect().height() - 10) self.dialog_box.setZValue(15); self.dialog_box.setOpacity(0) self.addItem(self.dialog_box) def actually_hide_dialog_box(self): if self.dialog_box is not None: self.removeItem(self.dialog_box) self.dialog_box = None def actually_show_button(self, data): button = Button(self, data[3], data[4], data[5], data[6], data[7], data[8], data[9]) button.setX(data[1]) button.setY(data[2]) self.buttons.append(button) self.addItem(button) self.call_next_action() def actually_set_background(self, data): if self.background is not None: self.removeItem(self.background) self.background = None self.background = QGraphicsPixmapItem(QPixmap(data[1]).scaled(self.window.game_width, self.window.game_height)) self.background.setZValue(-10) self.addItem(self.background) self.call_next_action() def actually_goto_scene(self, data): self.next_scene = data[1] self.fade_dir = 1 self.create_fade(0) def actually_close_game(self): self.window.close_game() def actually_play_song(self, data): AudioPlayer.play_song(data[1]) self.call_next_action() def actually_wait_for_button_press(self): self.current_action = -1 def actually_remove_all_buttons(self): for b in self.buttons: self.removeItem(b) self.buttons = [] self.call_next_action() def actually_show_text(self, data): text_item = QGraphicsTextItem() text_item.setZValue(5) if len(data) >= 8 and data[7] != None: text_item.setDefaultTextColor(QColor(data[7])) else: text_item.setDefaultTextColor(QColor("#FFFFFF")) text_item.setX(data[2]) text_item.setY(data[3]) text_item.setTextWidth(data[4]) text_item.setPlainText(data[5]) temp_font = text_item.font() temp_font.setPointSize(data[6]) text_item.setFont(temp_font) self.addItem(text_item) self.texts[data[1]] = text_item self.call_next_action() def actually_hide_text(self, data): self.removeItem(self.texts[data[1]]) self.texts[data[1]] = None self.call_next_action() def actually_show_image(self, data): image = None if isinstance(data[2], list): image = QGraphicsPixmapItem(QPixmap(data[2][0])) self.updatable_images.append([image, data[2], 0, 0, data[5]]) else: image = QGraphicsPixmapItem(QPixmap(data[2])) image.setX(data[3]) image.setY(data[4]) image.setZValue(0) self.addItem(image) self.images[data[1]] = image self.call_next_action() def actually_hide_image(self, data): self.removeItem(self.images[data[1]]) self.images[data[1]] = None self.call_next_action() def actually_move_image(self, data): image_id = data[1] image = self.images[image_id] duration = data[4] x_speed = (data[2] - image.x()) / duration y_speed = (data[3] - image.y()) / duration callback = self.call_next_action if data[5] else None image_data = [image_id, x_speed, y_speed, duration, callback] self.moving_images.append(image_data) if not data[5]: self.call_next_action() def actually_play_sound(self, data): AudioPlayer.play_sound_effect(data[1]) self.call_next_action() # ============================================== # * Setup Calls # ============================================== def add_call(self, data): self.actions.append(data) self.check_if_first() # ============================================== # * Extended Calls # ============================================== # ============================================== # Adds a dialog to the game. # # Ex: # self.add_dialog("Hello World!") # ============================================== def add_dialog(self, msg, fontSize=20): self.add_call([0, msg, fontSize]) # ============================================== # Hides the dialog box # # Ex: # self.hide_dialog_box() # ============================================== def hide_dialog_box(self): self.add_call([-1]) # ============================================== # Adds a button to the game. # After adding all buttons, be sure to call "self.wait_for_button_press". # Check scenes/scene_titlescreen.py for example of "font" and "buttonColors" # # Ex: # self.add_button(30, 30, 200, 200, "My Button!", self.another_function) # ============================================== def add_button(self, x, y, w, h, name, action, font=None, buttonColors=None, textColors=None): self.add_call([1, x, y, w, h, name, font, buttonColors, textColors, action]) # ============================================== # Sets the current background. # # Ex: # self.set_background("images/Background1.png") # ============================================== def set_background(self, path): self.add_call([2, path]) # ============================================== # Changes the game to the provided scene. # # Ex: # self.goto_scene(scenes.my_other_scene.My_Other_Scene) # ============================================== def goto_scene(self, scene): self.add_call([3, scene]) # ============================================== # Closes the game. # # Ex: # self.close_game() # ============================================== def close_game(self): self.add_call([4]) # ============================================== # Plays a song. # # Ex: # self.play_song("audio/testmusic2.mp3") # ============================================== def play_song(self, path): self.add_call([5, path]) # ============================================== # Plays a sound effect once. # # Ex: # self.play_sound("audio/testmusic2.mp3") # ============================================== def play_sound(self, path): self.add_call([50, path]) # ============================================== # Once all buttons are created, this will wait for the player to press one. # # Ex: # self.wait_for_button_press() # ============================================== def wait_for_button_press(self): self.add_call([6]) # ============================================== # Removes all buttons from the screen. # # Ex: # self.remove_all_buttons() # ============================================== def remove_all_buttons(self): self.add_call([7]) # ============================================== # Based on the value provided, there is a chance it will return True. # # Ex: # if self.generate_random_chance(30): # # there is a 30% chance of this happening # else: # # there is a 70% chance of this happening # ============================================== def generate_random_chance(self, val): return random.randint(0, 100) <= val # ============================================== # Shows text on the screen (not in dialog). # This function returns an ID that can be used in self.hide_text. # # Ex: # text_id = self.show_text(10, 10, 200, "Hello Screen!", 40, "#FF44CC") # ============================================== def show_text(self, x, y, w, text, size=30, color=None): new_id = self.current_id self.current_id += 1 self.add_call([8, new_id, x, y, w, text, size, color]) return new_id # ============================================== # Hides the text connected to the ID. # # Ex: # self.hide_text(text_id) # ============================================== def hide_text(self, text_id): self.add_call([9, text_id]) # ============================================== # Shows a picture on the screen. # This function returns an ID that can be used in other functions. # # Ex: # pic_id = self.show_picture("images/TestImage1.png", 30, 30) # # ---------------------------------------------- # # Using an array of strings will create an animation. # In that case, the last argument is the frame-change frequency of the animation. # # Ex: # pic_id = self.show_picture(["images/p1_frame_0.png", "images/p1_frame_1.png"], 100, 100, 30) # ============================================== def show_picture(self, path, x, y, animation_speed=20): new_id = self.current_id self.current_id += 1 self.add_call([10, new_id, path, x, y, animation_speed]) return new_id # ============================================== # Removes the picture connected to the provided ID. # # Ex: # self.hide_picture(pic_id) # ============================================== def hide_picture(self, image_id): self.add_call([11, image_id]) # ============================================== # Moves the picture to new coordinates over a duration of time. # # Ex: # self.move_picture(pic_id, 90, 30, 120) # ============================================== def move_picture(self, image_id, x, y, duration, wait_until_finished=True): self.add_call([12, image_id, x, y, duration, wait_until_finished]) #=============================================== # Sets the vaccination status # True/False, 50% chance #=============================================== def set_vaxx(self): if self.generate_random_chance(50): Scene_Base.vaxx = True else: Scene_Base.vaxx = False #=============================================== # Sets the entry point # 0 is for blood (cut) # 1 is for stomach (mouth) #=============================================== def set_entry(self): if self.generate_random_chance(50): Scene_Base.entry = 0 else: Scene_Base.entry = 1 # ============================================== # Gets and sets global values # # Ex: # self.set_value("Health", 100) # # self.add_value("Health", -1) # # if self.get_value("Health") <= 30: # self.add_dialog("Player is less than 30 health!") # ============================================== def set_value(self, name, value): Scene_Base.GLOBAL_VARS[name] = value def add_value(self, name, value): Scene_Base.GLOBAL_VARS[name] += value def get_value(self, name): return Scene_Base.GLOBAL_VARS[name]
class GShaderNode(QGraphicsWidget): """This abstract class defines the look and feel of a Node. Specialized classes can subclass this instead of the Node class. """ edge_started = pyqtSignal(uuid.UUID, GEdge) edge_ended = pyqtSignal(uuid.UUID, GEdge) deleted = pyqtSignal(object) connection_changed = pyqtSignal(GNodeSocket, object) # Node input_changed = pyqtSignal(object) # Node @abc.abstractmethod def __init__(self, node_scene: NodeScene, shader: Shader = None, label: str = "", parent=None, backend_node: ShaderNode = None): super(QGraphicsWidget, self).__init__(parent) self._node = backend_node if self._node is None: if shader is None: raise ValueError( "shader or backend_node needs to be provided!") else: self._node = ShaderNode(shader=shader, label=label, set_default_inputs=True, container=self) else: self._node.set_container(self) if label: self._node.set_label(label) self.node_scene = node_scene self._in_socket_modules = [] self._out_socket_modules = [] # define Node properties self._selected = False self._deletable = True self._input_index = 1 self._output_index = 1 self._width = 250 self._height = 50 self._rounding = 5 self._padding = 8 self._bg_color = QColor(80, 80, 100, 200) self._title_color = Qt.white self._title_font = QFont("Corbel", 11) self._title_font.setBold(True) self._title_item = QGraphicsTextItem(self) # Define layout self._master_layout = GraphicsGridLayout() # Define widgets properties self._input_label_font = QFont("Corbel", 8) self._input_label_palette = QPalette() self._input_label_palette.setColor(QPalette.Background, QColor(0, 0, 0, 0)) self._input_label_palette.setColor(QPalette.Foreground, self._title_color) # Set flags to enable the widget to be moved and selected self.setFlag(QGraphicsItem.ItemIsMovable, True) self.setFlag(QGraphicsItem.ItemIsSelectable, True) self.setCacheMode(QGraphicsItem.DeviceCoordinateCache) self._init_title() self._init_layout() self._init_sockets() def _init_layout(self): self._master_layout.setContentsMargins(-5, 4, 4, -15) self._master_layout.setRowSpacing( 0, self._title_font.pointSize() + 12) # Add empty space for first row so that title is visible self._master_layout.setHorizontalSpacing(2.0) self._master_layout.setVerticalSpacing(0.0) self._master_layout.setColumnAlignment(2, Qt.AlignRight) self._master_layout.setColumnFixedWidth(0, 15) # Input socket column self._master_layout.setColumnFixedWidth(2, 15) # Output socket column self._master_layout.setColumnFixedWidth(1, self._width - 15 - 15) self.setLayout(self._master_layout) def _init_sockets(self): shader = self._node.get_shader() for i in range(self._node.num_output_sockets()): shader_output = shader.get_outputs()[i] label = shader_output.get_display_label() socket = self._node.get_output_socket(i) # socket.set_index(i) self._add_output_module(output_label=label, node_socket=socket) for i in range(self._node.num_input_sockets()): shader_input = shader.get_inputs()[i] socket = self._node.get_input_socket(i) # socket.set_index(i) self._add_input_module(socket, shader_input) def _notify_change(self): """Event is called when any of this node's widget's inputs are changed""" self.input_changed.emit(self) def delete(self): output_nodes = self.get_output_target_nodes() self._remove_edges_from_sockets(self.get_output_sockets()) self._remove_edges_from_sockets(self.get_input_sockets()) self._node.delete() for n in output_nodes: n.update_module_enabled_state() self.deleted.emit(self) def _remove_edges_from_sockets(self, sockets): for s in sockets: edges = s.get_connected_edges() for e in edges: s.remove_connected_edge(e) self.node_scene.removeItem(e) del s def id(self) -> uuid.UUID: return self._node.id() def label(self) -> str: return self._node.label() def set_label(self, label: str = None): """ Sets the label of this node and updates the node title accordingly. :param label: the new label, or None to use the old label and just update the title. """ if label is None: label = self.label() self._node.set_label(label) self._title_item.setPlainText(self.label() + " (" + str(self.get_num()) + ")") def get_num(self) -> int: """Returns the number that is assigned to this node. This number is unique among nodes with the same shader type.""" return self._node.get_num() def set_num(self, num: int): self._node.set_num(num) self.set_label(None) def _init_title(self): self._title_item.setDefaultTextColor(self._title_color) self._title_item.setFont(self._title_font) self._title_item.setPos(self._padding, 0) self._title_item.setTextWidth(self._width - self._padding) self.set_label() def is_deletable(self) -> bool: return self._deletable def set_deletable(self, value: bool): self._deletable = value def get_input( self, exclude_connected: True ) -> typing.List[typing.Tuple[str, str, typing.Any]]: """Returns a list of argument names, its modified name as well as the the value in the node input for that argument. Note that is the shader held by this node has not been recompiled, the modified name of the argument is undefined.""" out = [] for socket, mod in self._in_socket_modules: if not exclude_connected or not socket.is_connected(): value = mod.get_gl_value() argument: str = socket.label() modified_name = self.get_shader().get_parsed_code( ).primary_function.get_argument(argument).get_modified_name() out.append((argument, modified_name, value)) return out def get_output_target_nodes(self) -> list: return [ n.get_container() for n in self._node.get_output_target_nodes() ] def get_ancestor_nodes(self, add_self: bool = False) -> IndexedSet: """ Returns a list of all connected ancestors of this node. :param add_self: if True, adds this node to the set of returned nodes. :return: a Set of nodes that are ancestors of this node. """ nodes = self._node.get_ancestor_nodes(add_self=add_self) out = IndexedSet() for n in nodes: out.add(n.get_container()) return out def save_graph_state(self): """Saves the state (socket values) of this node and all ancestor nodes.""" self._node.save_graph_state() def save_state(self): """Saves the values of all sockets of this node.""" self._node.save_state() def restore_graph_state(self): """Restores the state (socket values) of this node and all ancestor nodes.""" self._node.restore_graph_state() def restore_state(self): """Restores the values of all sockets of this node.""" self._node.restore_state() def get_backend_node(self) -> ShaderNode: return self._node def get_shader(self) -> Shader: return self._node.get_shader() def has_socket(self, socket: GNodeSocket) -> bool: return self._node.has_socket(socket.get_backend_socket()) def get_input_sockets(self) -> typing.List[GNodeSocket]: return [s.get_container() for s in self._node.get_input_sockets()] def get_input_socket(self, index: int) -> typing.Union[GNodeSocket, None]: return self._node.get_input_socket(index).get_container() def update_module_enabled_state(self): for socket in self.get_input_sockets(): mod = self.get_input_module(socket) if mod is not None: mod.setEnabled(not socket.is_connected()) def get_input_module(self, socket=None, index=None) -> SocketModule: assert not (socket is None and index is None ), "Specify at least one identifier to find input module!" for i, (s, m) in enumerate(self._in_socket_modules): if socket and s == socket: return m elif index and i == index: return m return None def get_output_sockets(self) -> typing.List[GNodeSocket]: return [s.get_container() for s in self._node.get_output_sockets()] def get_output_socket(self, identifier) -> 'GNodeSocket': return self._node.get_output_socket(identifier).get_container() def _create_g_socket(self, socket: NodeSocket) -> GNodeSocket: socket = GNodeSocket(self, socket) socket.edge_started.connect(self._spawn_edge) socket.edge_released.connect(self._release_edge) socket.connection_changed.connect(self._handle_socket_connection) return socket def _add_input_module(self, node_socket: NodeSocket, shader_input: Shader): dtype = node_socket.dtype() socket = self._create_g_socket(node_socket) lim_min = shader_input.get_limits()[0] lim_max = shader_input.get_limits()[1] display_label = shader_input.get_display_label() z_value = 0 if dtype == DataType.Float: # Create an widgets widget input_widget = FloatInput(min_=lim_min, max_=lim_max, dtype=dtype) elif dtype == DataType.Int: input_widget = IntInput(min_=lim_min, max_=lim_max, dtype=dtype) elif dtype == DataType.Vec3_RGB: input_widget = ColorInput(dtype) elif dtype == DataType.Shader: input_widget = ShaderInput(dtype) elif dtype == DataType.Vec3_Float: size = 3 input_widget = ArrayInput(size, min_=lim_min, max_=lim_max, dtype=dtype) elif dtype == DataType.Int_Choice: input_widget = IntChoiceInput(values=shader_input.get_names()) input_widget.raise_() z_value = 1 else: raise TypeError("Data Type {} is not yet supported!".format(dtype)) # Create a module and add to this node module = SocketModule(socket, display_label, input_widget) module.input_changed.connect(self._notify_change) module.set_label_palette(self._input_label_palette) module.set_value(socket.value()) self._in_socket_modules.append((socket, module)) module_item = self.node_scene.addWidget(module) module_item.setZValue(z_value) self._master_layout.addItem(socket, self._input_index, 0, Qt.AlignVCenter) self._master_layout.addItem(module_item, self._input_index, 1, Qt.AlignVCenter) self._input_index += 1 self._height = self._master_layout.preferredHeight() + 10 if not shader_input.is_connectable(): socket.setVisible(False) def _add_output_module(self, output_label: str, node_socket: NodeSocket): socket = self._create_g_socket(node_socket) module = OutputModule(output_label) module.set_label_palette(self._input_label_palette) module_item = self.node_scene.addWidget(module) self._out_socket_modules.append((socket, module)) self._master_layout.addItem(module_item, self._output_index, 1, Qt.AlignVCenter) self._master_layout.addItem(socket, self._input_index, 2, Qt.AlignVCenter) self._output_index += 1 self._input_index += 1 def _handle_socket_connection(self, socket: GNodeSocket, _): mod = self.get_input_module(socket) if socket.is_connected( ): # Disable socket module to show that is not user controllable anymore mod.setEnabled(False) else: mod.setEnabled(True) self.connection_changed.emit(socket, self) def _spawn_edge(self, edge): self.edge_started.emit(self.id(), edge) def _release_edge(self, edge): self.edge_ended.emit(self.id(), edge) def boundingRect(self) -> QtCore.QRectF: return QRectF(0, 0, self._width, self._height).normalized() def paint(self, painter: QPainter, option, widget=None): if self.isSelected(): painter.setPen(QPen(Qt.black)) # Disables the border else: painter.setPen(Qt.NoPen) painter.setBrush(QBrush(self._bg_color)) painter.drawRoundedRect(0, 0, self._width, self._height, self._rounding, 1) def render(self, width: int, height: int, retain_graph=False) -> typing.Tuple[torch.Tensor, dict]: """ Renders an image from this node graph. :param width: pixel width of rendered image :param height: pixel height of rendered image :param retain_graph: If True, updated socket values will not be fetched, instead, saved tensor values will be used. If using backpropagation that updates the returned tensor parameters in-place, set this to True, otherwise set to False so that parameter values are fetched from input Sockets. :return: a Tensor containing the rendered image and a list of parameter Tensors (one for each unconnected graph input) """ return self._node.render(width, height, retain_graph=retain_graph) def randomize_input(self): """Randomizes the values of all input sockets.""" for socket, inp in zip(self.get_input_sockets(), self.get_shader().get_inputs()): if not socket.is_connected(): rand_val = inp.get_centered_random_value() socket.set_value(rand_val) mod = self.get_input_module(socket) mod.update_from_socket() def __str__(self): cls = self.__class__ return "{} '{}({})'".format(cls, self.label(), self.get_num()) def __hash__(self): return self._node.__hash__() def __eq__(self, other): if isinstance(other, GShaderNode): return self._node.__eq__(other.get_backend_node()) return False
class NodeItem(): ''' This represents one node on the diagram. This class creates the ellipse graphics item and the text graphics item and adds them to the scene. ''' def __init__(self, scene, x, y, nodeInstance=None): self.scene = scene self.logMsg = None self.x = x self.y = y self.itemInstance = nodeInstance self.diagramType = self.itemInstance.diagramType self.displayText = None self.model = self.scene.parent.model self.neoCon = self.scene.parent.model.modelNeoCon self.getFormat() # remember current width and height self.oldNodeWidth = 0 self.oldNodeHeight = 0 # init graphics objects to none self.INode = None self.INtext = None # # init list of qgrapphicellipseitems to none # self.ellipsePoints = [] # self.ellipseGraphicItems = [] # draw the ellipse self.drawIt() def name(self, ): return self.itemInstance.NZID def NZID(self, ): return self.itemInstance.NZID def getFormat(self, ): ''' determine the format to use to draw the instance node - start with the project default - if the instance node has a template then use the instance format defined on the template ''' # get the default self.nodeFormat = INodeFormat( formatDict=self.model.modelData["INformat"]) # get a custom template format if there is one if not self.itemInstance.nodeTemplate is None: index, nodeTemplateDict = self.model.getDictByName( topLevel="Node Template", objectName=self.itemInstance.nodeTemplate) if not nodeTemplateDict is None: self.instanceNodeFormatDict = nodeTemplateDict.get( "INformat", None) if not self.instanceNodeFormatDict is None: self.nodeFormat = INodeFormat( formatDict=self.instanceNodeFormatDict) def clearItem(self, ): if (not self.INode is None and not self.INode.scene() is None): self.INode.scene().removeItem(self.INode) if (not self.INtext is None and not self.INtext.scene() is None): self.INtext.scene().removeItem(self.INtext) # # remove the points on the ellipse - this code is only for debugging # for point in self.ellipseGraphicItems: # if (not point is None and not point.scene() is None): # point.scene().removeItem(point) def drawIt(self, ): # force the node instance to update its values in case it has been updated from another diagram or the tree view self.itemInstance.reloadDictValues() # get current format as it may have changed self.getFormat() if self.oldNodeWidth != self.nodeFormat.formatDict[ "nodeWidth"] or self.oldNodeHeight != self.nodeFormat.formatDict[ "nodeHeight"]: # remove graphic items that already exist self.clearItem() # create the node ellipse self.INode = QGraphicsEllipseItem(QRectF( self.x, self.y, self.nodeFormat.formatDict["nodeWidth"], self.nodeFormat.formatDict["nodeHeight"]), parent=None) # create the node text self.INtext = QGraphicsTextItem("", parent=None) self.INtext.setPos(self.x, self.y) self.x = self.INode.sceneBoundingRect().x() self.y = self.INode.sceneBoundingRect().y() # print("after create items before drawIt: sceneboundingrect {} ".format( self.INode.sceneBoundingRect())) # print("x:{} y:{}".format(self.x, self.y)) self.formatItem() self.scene.addItem(self.INode) self.scene.addItem(self.INtext) # # add points # for point in self.ellipseGraphicItems: # self.scene.addItem(point) # redraw all the rels associated to this node. self.moveRels() else: # print("before drawIt: sceneboundingrect {} ".format( self.INode.sceneBoundingRect())) # print("x:{} y:{}".format(self.x, self.y)) self.formatItem() # remember current width and height self.oldNodeWidth = self.nodeFormat.formatDict["nodeWidth"] self.oldNodeHeight = self.nodeFormat.formatDict["nodeHeight"] # print("after drawIt: sceneboundingrect {} ".format( self.INode.sceneBoundingRect())) # print("x:{} y:{}".format(self.x, self.y)) # def genPoints(self, ): # '''Ellipse Constructor - not sure of these, need to verify # def __init__(self, mx, my, rh, rv): # mx - center point x # my - center point y # rh - height of ellipse # rv - width of ellipse''' # x = self.INode.sceneBoundingRect().center().x() # y = self.INode.sceneBoundingRect().center().y() # w = self.INode.sceneBoundingRect().width()/2.0 # h = self.INode.sceneBoundingRect().height()/2.0 # myEllipse = Ellipse(x, y, w, h) # for d in range(0, 360, 10): # x, y = myEllipse.pointFromAngle(radians(d)) # self.ellipsePoints.append([d, x, y]) # aPoint = QGraphicsEllipseItem(QRectF(x-2.5,y-2.5,5, 5), parent=None) # self.ellipseGraphicItems.append(aPoint) ## print(self.ellipsePoints) def formatItem(self, ): # configure the formatting aspects of the qgraphics item pen = self.nodeFormat.pen() brush = self.nodeFormat.brush() self.INode.setZValue(NODELAYER) self.INode.setBrush(brush) self.INode.setPen(pen) self.INode.setFlag(QGraphicsItem.ItemIsMovable, True) self.INode.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True) self.INode.setFlag(QGraphicsItem.ItemIsSelectable, True) self.INode.setSelected(True) self.INode.setData( 1, self.itemInstance.NZID) # get with self.INode.data(1) self.INode.setData(ITEMTYPE, NODEINSTANCE) # draw the text self.updateText() self.INtext.setZValue(NODELAYER) self.INtext.setTextWidth(self.INode.boundingRect().width()) self.INtext.setFlag(QGraphicsItem.ItemIsMovable, True) self.INtext.setFlag(QGraphicsItem.ItemIsSelectable, False) self.INtext.setData(NODEID, self.itemInstance.NZID) self.INtext.setData(ITEMTYPE, NODEINSTANCETEXT) def updateText(self, ): ''' Generate the HTML that formats the node data inside the ellipse ''' # generate the html prefix = "<!DOCTYPE html><html><body>" suffix = "</body></html>" try: Lbl = str(self.itemInstance.labelList[0][0]) except: Lbl = "No Labels" firstLbl = "<center><b>{}</b></center>".format(Lbl) try: propName = str(self.itemInstance.propList[0][PROPERTY]) propVal = str(self.itemInstance.propList[0][VALUE]) prop = "{}: {}".format(propName, propVal) except: prop = "No Properties" firstProp = "<center>{}</center>".format(prop) genHTML = '{}{}<hr width="75%">{}{}'.format(prefix, firstLbl, firstProp, suffix) self.INtext.setHtml(genHTML) def moveIt(self, dx, dy): '''Move the node ellipse and the node textbox to the delta x,y coordinate.''' # print("before moveIt: sceneboundingrect {} ".format( self.INode.sceneBoundingRect())) self.INode.moveBy(dx, dy) self.x = self.INode.sceneBoundingRect().x() self.y = self.INode.sceneBoundingRect().y() self.INtext.moveBy(dx, dy) # print("after moveIt: sceneboundingrect {} ".format( self.INode.sceneBoundingRect())) # # recalc points # self.genPoints() # for point in self.ellipseGraphicItems: # point.moveBy(dx, dy) self.moveRels() def moveRels(self, ): '''Redraw all the relationship arcs connected to the Node ellipse.''' # print("moveRels") for key, diagramItem in self.scene.parent.itemDict.items(): if diagramItem.diagramType == "Instance Relationship": if self.itemInstance.NZID in [ diagramItem.relationInstance.startNZID, diagramItem.relationInstance.endNZID ]: diagramItem.drawRelationship() # if diagramItem.relationInstance.startNZID == self.itemInstance.NZID: # diagramItem.moveRelationshipLine() ## print("move startnode {}-{}".format(self.x, self.y)) # if diagramItem.relationInstance.endNZID == self.itemInstance.NZID: # diagramItem.moveRelationshipLine() ## print("move endnode {}-{}".format(self.x, self.y)) def getObjectDict(self, ): ''' This function returns a dictionary with all the data that represents this node item. The dictionary is added to the Instance Diagram dictionary.''' objectDict = {} objectDict["NZID"] = self.itemInstance.NZID objectDict["x"] = self.INode.sceneBoundingRect().x() objectDict["y"] = self.INode.sceneBoundingRect().y() objectDict["diagramType"] = self.diagramType 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 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 GraphicsNode(QGraphicsRectItem): """Implement the graphics version of a node""" def __init__(self, node: 'Node', parent=None, resizeable=True, min_width=180, width=180, min_height=240, height=240): super().__init__(parent=parent) self.node = node # Reference to parent class Node implementing the logic # init flags self._was_moved = False self._resized = False self._last_selected_state = False self._hovered = False try: self.handles = {} self._currentRect = None self._currentPos = None self.initSizes(resizeable=resizeable, min_width=min_width, width=width, min_height=min_height, height=height) self.initAssets() self.initUI() except Exception as e: dumpException(e) @property def content(self): """Reference to the `Node` Content""" return self.node.content if self.node else None @property def height(self): return self.rect().height() @property def width(self): return self.rect().width() @height.setter def height(self, value): currentRect = self.rect() currentRect.setHeight(value) self.setRect(currentRect) @width.setter def width(self, value): currentRect = self.rect() currentRect.setWidth(value) self.setRect(currentRect) @property def title(self, ): return self._title @title.setter def title(self, value): self._title = value # fm = QFontMetrics(self._title_font) # width = fm.width(self.node.title) self.title_item.setPlainText(self._title) # self.title_item.setTextWidth(width) def initUI(self): # Define the node as selectable and movable self.setFlag(QGraphicsItem.ItemIsSelectable) self.setFlag(QGraphicsItem.ItemIsMovable) self.setAcceptHoverEvents(True) if self.resizeable: self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True) self.setFlag(QGraphicsItem.ItemIsFocusable, True) self.initHandles() # init _title self.title_item = None self.initTitle() self.title = self.node.title # init content self.initContent() self.updateLayout() def initSizes(self, resizeable=True, min_width=180, width=180, min_height=240, height=240): # Diverse parameters for drawing self.edge_roundness = 15. self.edge_padding = 10. self.title_height = 24 self.title_horizontal_padding = 5. self.title_vertical_padding = 4. self.content_offset = 2 # define margin for the content self.resizeable = resizeable self.min_width = min_width self.min_height = min_height self.width = width self.height = height # Socket parameters self.socket_spacing = 22 self.socket_offsets = { SocketPosition.BottomLeft: -1, SocketPosition.MiddleLeft: -1, SocketPosition.TopLeft: -1, SocketPosition.BottomRight: 1, SocketPosition.MiddleRight: 1, SocketPosition.TopRight: 1, } def initAssets(self): self._title_color = colors[THEME]['title_color'] self._title_font = QFont('Ubuntu', 8) self._color = colors[THEME]['color'] self._color_selected = colors[THEME]['color_selected'] self._color_hovered = colors[THEME]['color_hovered'] self._pen_default = QPen(self._color) self._pen_default.setWidthF(OUTLINE_WIDTH) self._pen_selected = QPen(self._color_selected) self._pen_selected.setWidthF(OUTLINE_WIDTH) self._pen_hovered = QPen(self._color_hovered) self._pen_hovered.setWidthF(OUTLINE_WIDTH + 1) self._brush_title = colors[THEME]['brush_title'] self._brush_background = colors[THEME]['brush_background'] self.icons = QImage('../../node_editor/icons/status_icons.png') shadow = QGraphicsDropShadowEffect(blurRadius=5, xOffset=3, yOffset=3) self.setGraphicsEffect(shadow) def initHandles(self): for position in (HandlePosition.MiddleRight, HandlePosition.BottomMiddle, HandlePosition.BottomRight): self.handles[position] = Handle(self, position) def initContent(self): """Add the `Content` of the `Node` to the `Graphical Scene`""" # Draw the contents # defines the content as a proxy widget with parent self if self.content: self.grContent = self.node.scene.grScene.addWidget(self.content) self.grContent.setParentItem(self) def initTitle(self): """Title is instanciated as `QGraphicsTextItem`""" # Draw the _title self.title_item = QGraphicsTextItem(self) self.title_item.node = self.node # add reference to the node self.title_item.setDefaultTextColor(self._title_color) self.title_item.setFont(self._title_font) self.title_item.setPos(self.title_horizontal_padding, 0) def setContentGeometry(self, ): """Set Content geometry to fit the space available in the `Graphical Node`""" if self.title_item: self.title_item.setTextWidth(self.width - 2 * self.title_horizontal_padding) offset = self.content_offset if self.content is not None: self.content.setGeometry( self.edge_padding + offset, self.title_height + self.edge_padding + offset, self.width - 2 * self.edge_padding - 2 * offset, self.height - 2 * self.edge_padding - self.title_height - 2 * offset) def enableHoverEvents(self, enabled: bool = True): self.setAcceptHoverEvents(enabled) if not enabled: self._hovered = False def doSelect(self, new_state=True): self.setSelected(new_state) self._last_selected_state = new_state if new_state: self.onSelected() def onSelected(self): """onSelected event""" self.print('On selected event', self.node) self.node.scene.grScene.itemSelected.emit() def mousePressEvent(self, event: 'QGraphicsSceneMouseEvent') -> None: """Overrides Qt's mousePressEvent. Parameters ---------- event : QGraphicsSceneMouseEvent current event """ try: # self.handleSelected = self.getHandleAt(event.pos()) # if self.handleSelected: # # record the position where the mouse was pressed # self._currentPos = event.pos() # # # current rectangle at mouse pressed # self._currentRect = self.boundingRect() super().mousePressEvent(event) except Exception as e: dumpException(e) def mouseMoveEvent(self, event: 'QGraphicsSceneMouseEvent') -> None: """Mouse move event Handle two cases : - either the `Graphical Node` is resizeable and a handle is selected, the trigger the resize event - or the node is selected and update the `Graphical Socket` and `Graphical Edge`. Parameters ---------- event : QGraphicsSceneMouseEvent event trigerring the mouseMoveEvent """ self.updateSocketAndEdges() self._was_moved = True super().mouseMoveEvent(event) def mouseReleaseEvent(self, event: 'QGraphicsSceneMouseEvent'): """Mouse Release Event On release, if moved, trigger storeHistory from :class:`~node_editor.node_scene_history.SceneHistory`. Also handles selection of the node and triggers onSelected event. Parameters ---------- event : QGraphicsSceneMouseEvent event triggering the mouseReleaseEvent """ # TODO onDeselected super().mouseReleaseEvent(event) if self.resizeable: self.handleSelected = None self._currentRect = None self._currentPos = None # handle when grNode moved if self._was_moved: self._was_moved = False self.node.scene.history.storeHistory('Node moved', setModified=True) self.node.scene.resetLastSelectedStates() self.doSelect() # store the last selected state, because moving also select the node self.node.scene._last_selected_items = self.node.scene.getSelectedItems( ) # skip storing selection return # handle when grNode was clicked on # condition met when changing from one selection to another or # when multiple items are selected and the current node is then selected if self._last_selected_state != self.isSelected() or \ self.node.scene._last_selected_items != self.node.scene.getSelectedItems(): # reset all other selected flags to False self.node.scene.resetLastSelectedStates() # set the new state of this object only self._last_selected_state = self.isSelected() self.onSelected() def mouseDoubleClickEvent(self, event: 'QGraphicsSceneMouseEvent') -> None: """Overrides double click event. Resent to `Node::onDoubleClicked`""" self.node.onDoubleClicked(event) def wheelEvent(self, event: 'QGraphicsSceneWheelEvent') -> None: """wheelEvent on the GraphicsNode. Avoid the wheelEvent from the GraphicsScene.""" self.print('wheelEvent') def hoverEnterEvent(self, event: 'QGraphicsSceneHoverEvent') -> None: super().hoverEnterEvent(event) self._hovered = True self.update() def hoverLeaveEvent(self, event: 'QGraphicsSceneHoverEvent') -> None: super().hoverLeaveEvent(event) self._hovered = False self.update() def resize(self, rect: QRectF): """Update rectangle and bounding rectangle""" if rect.width() < self.min_width: rect.setWidth(self.min_width) if rect.height() < self.min_height: rect.setHeight(self.min_height) self.setRect(rect) self.updateSocketAndEdges() self.setContentGeometry() def getSocketPosition(self, index: int, position: SocketPosition, num_out_of: int = 1) -> List[float]: """Helper function - returns the position of a socket in pixels relative to the `Graphical Node` Parameters ---------- index : ```int``` Index of the socket in the list position : SocketPosition One of enumeration num_out_of : ```int``` total number of `Socket` on this position Returns ------- ```list``` x, y position relative to the node """ if position in (SocketPosition.TopLeft, SocketPosition.MiddleLeft, SocketPosition.BottomLeft): x = self.socket_offsets[position] else: x = self.width + self.socket_offsets[position] if position in (SocketPosition.BottomLeft, SocketPosition.BottomRight): # start from bottom y = self.height - (index * self.socket_spacing + self.grNode.title_vertical_padding + self.edge_roundness) elif position in (SocketPosition.MiddleLeft, SocketPosition.MiddleRight): num_sockets = num_out_of node_height = self.height top_offset = self.title_height + 2 * self.title_vertical_padding + self.edge_padding available_height = node_height - top_offset total_height_of_all_socket = num_sockets * self.socket_spacing new_top = available_height - total_height_of_all_socket # y = top_offset + index * self.socket_spacing + new_top / 2 y = top_offset + available_height / 2. + (index - 0.5) * self.socket_spacing - \ (num_sockets - 1) * self.socket_spacing / 2 elif position in (SocketPosition.TopLeft, SocketPosition.TopRight): # start from top y = index * self.socket_spacing + self.title_height + self.title_vertical_padding + self.edge_roundness else: y = 0 return [x, y] def updateSocketAndEdges(self): """Update the `Socket` position and the `Graphical Edges` when available""" # As this method can if self.resizeable: # update the socket position if hasattr(self.node, 'inputs') and hasattr(self.node, 'outputs'): for socket in self.node.getSockets(): socket.setSocketPosition() # in any case if self.scene() is not None: for node in self.scene().scene.nodes: if node.isSelected(): node.updateConnectedEdges() def updateHandles(self): for handle in self.handles.values(): handle.setHandlePosition() def updateLayout(self): """Updates the layout. Updates : - Socket position - Edges connected to the node - Position of the handle to resize - Size of the content """ self.updateSocketAndEdges() self.updateHandles() self.setContentGeometry() def print(self, *args): if DEBUG: print('>GraphicsNode : ', *args) def boundingRect(self): # Return rectangle for selection detection rect = self.rect() x, y = rect.left(), rect.top() return QRectF(x, y, self.width, self.height) def paint(self, painter: QPainter, option: 'QStyleOptionGraphicsItem', widget: Optional[QWidget] = ...) -> None: x = y = 0 path_title = QPainterPath() path_title.setFillRule(Qt.WindingFill) # Rectangle for title path_title.addRoundedRect(x, y, self.width, self.title_height, self.edge_roundness, self.edge_roundness) path_title.addRect(x, y + self.title_height - self.edge_roundness, self.edge_roundness, self.edge_roundness) path_title.addRect(x + self.width - self.edge_roundness, y + self.title_height - self.edge_roundness, self.edge_roundness, self.edge_roundness) painter.setPen(Qt.NoPen) painter.setBrush(self._brush_title) painter.drawPath(path_title.simplified()) # content path_content = QPainterPath() path_content.setFillRule(Qt.WindingFill) path_content.addRoundedRect(x, y + self.title_height, self.width, self.height - self.title_height, self.edge_roundness, self.edge_roundness) path_content.addRect(x, y + self.title_height, self.edge_roundness, self.edge_roundness) path_content.addRect(x + self.width - self.edge_roundness, y + self.title_height, self.edge_roundness, self.edge_roundness) painter.setPen(Qt.NoPen) painter.setBrush(self._brush_background) painter.drawPath(path_content.simplified()) # outline path_outline = QPainterPath() path_outline.addRoundedRect(x, y, self.width, self.height, self.edge_roundness, self.edge_roundness) painter.setBrush(Qt.NoBrush) if self._hovered: painter.setPen(self._pen_hovered) painter.drawPath(path_outline.simplified()) painter.setPen(self._pen_default) painter.drawPath(path_outline.simplified()) else: painter.setPen(self._pen_default if not self.isSelected() else self._pen_selected) painter.drawPath(path_outline.simplified())
class QDMGraphicsNode(QGraphicsItem): def __init__(self, node, parent=None): super().__init__(parent) self.node = node self.content = self.node.content self._title_color = Qt.white self._title_font = QFont("Arial", 12) self.width = 180 self.height = 240 self.edge_size = 10 self.title_height = 24 self._padding = 4 self._pen_default = QPen(QColor(self.get_outline_color())) self._pen_selected = QPen(QColor("#e18b32")) self._title_brush = QBrush(QColor(self.get_title_color())) self._background_brush = QBrush(QColor(self.get_background_color())) self.initTitle() self.title = self.node.title # init content self.initContent() # init sockets #self.initSockets() self.setFlag(QGraphicsItem.ItemIsFocusable, True) self.initUI() def boundingRect(self): return QRectF( 0, 0, self.width, self.height ).normalized() def initUI(self): self.setFlag(QGraphicsItem.ItemIsSelectable) self.setFlag(QGraphicsItem.ItemIsMovable) def initTitle(self): self.title_item = QGraphicsTextItem(self) self.title_item.setDefaultTextColor(self._title_color) self.title_item.setFont(self._title_font) self.title_item.setTextWidth(self.width - 2 * self._padding) self.title_item.setX(self._padding) def get_title_color(self): return "#954a4a" def get_background_color(self): return "#2b2b2b" def get_outline_color(self): return "#181818" def initSockets(self): self.content.drawSocketItems() def initContent(self): self.grContent = QGraphicsProxyWidget(self) self.content.setGeometry(self.edge_size, self.title_height + self.edge_size, self.width - 2 * self.edge_size, self.height - 2 * self.edge_size - self.title_height) self.grContent.setWidget(self.content) @property def title(self): return self._title @title.setter def title(self, value): self._title = value self.title_item.setPlainText(self._title) def paint(self, painter, option, widget=None): # title path_title = QPainterPath() path_title.setFillRule(Qt.WindingFill) path_title.addRoundedRect(0, 0, self.width, self.title_height, self.edge_size, self.edge_size) path_title.addRect(0, self.title_height - self.edge_size, self.edge_size, self.edge_size) path_title.addRect(self.width - self.edge_size, self.title_height - self.edge_size, self.edge_size, self.edge_size) painter.setPen(Qt.NoPen) painter.setBrush(self._title_brush) painter.drawPath(path_title.simplified()) # content path_content = QPainterPath() path_content.setFillRule(Qt.WindingFill) path_content.addRoundedRect(0, self.title_height, self.width, self.height - self.title_height, self.edge_size, self.edge_size) path_content.addRect(0, self.title_height, self.edge_size, self.edge_size) path_content.addRect(self.width - self.edge_size, self.title_height, self.edge_size, self.edge_size) painter.setPen(Qt.NoPen) painter.setBrush(self._background_brush) painter.drawPath(path_content.simplified()) # outline path_outline = QPainterPath() path_outline.addRoundedRect(0, 0, self.width, self.height, self.edge_size, self.edge_size) painter.setPen(self._pen_default if not self.isSelected() else self._pen_selected) painter.setBrush(Qt.NoBrush) painter.drawPath(path_outline.simplified()) def mouseMoveEvent(self, event): super().mouseMoveEvent(event) self.node.update_linked_sockets() def keyReleaseEvent(self, event): super().keyReleaseEvent(event) if not self.hasFocus(): return if event.key() == Qt.Key_Backspace or event.key() == Qt.Key_Delete: self.node.scene.removeNode(self.node)
def initdetailgroup(self): view = QGraphicsView(parent=self) brush = QBrush(QColor(242, 242, 242)) view.setBackgroundBrush(brush) view.setFrameStyle(16) # QFrame.Plain def clickEventHandler(event): self.detailFigure_2Clicked.emit() detailFigure_1 = QGraphicsPixmapItem( QPixmap(cwd + '/guiunits/imags/pon56gdemo/detailfigure_1.png')) detailFigure_2_Qobj = fadingPic( QPixmap(cwd + '/guiunits/imags/pon56gdemo/detailfigure_2.png')) detailFigure_2 = detailFigure_2_Qobj.pixmap_item detailFigure_2_title = detailFigure_2_Qobj.text_item detailFigure_1.mousePressEvent = clickEventHandler title = QGraphicsTextItem("Our Innovation/Contribution") font = QFont("Nokia Pure Text Light", 25, QFont.Bold) title.setFont(font) title.setDefaultTextColor(self.nokia_blue) textItem1 = QGraphicsTextItem() textItem1.setHtml( '''<body style="font-family:Nokia Pure Text Light;color:#124191;font-size:23px;"> <div >10GHz</div> <div > Optics </div> </body>''') textItem1.setTextWidth(80) textItem2 = QGraphicsTextItem() textItem2.setHtml( '''<body style="font-family:Nokia Pure Text Light;color:#124191;font-size:23px;"> <div > 10GHz</div> <div > Optics </div> </body>''') textItem2.setTextWidth(100) fan = Fan() # a QObject which wraps a QGraphicsItem inside scene = QGraphicsScene() scene.setSceneRect(0, 0, 1285, 420) scene.addItem(detailFigure_2) scene.addItem(detailFigure_1) scene.addItem(detailFigure_2_title) scene.addItem(textItem1) scene.addItem(textItem2) scene.addItem(title) scene.addItem(fan.pixmap_item) detailFigure_1.setPos(QPointF(35, 88)) detailFigure_2.setPos(QPointF(570, 96)) detailFigure_2.setOpacity(0) # hided at first detailFigure_2_title.setPos(QPointF(750, 46)) detailFigure_2_title.setOpacity(0) title.setPos(QPointF(50, 20)) textItem1.setPos(QPointF(40, 168)) textItem2.setPos(QPointF(361, 168)) fan.pixmap_item.setPos(QPointF(456.5, 138)) self.fanAnim = fan.fanAnimation() view.setScene(scene) view.setSceneRect(0, 0, 1285, 420) view.setAlignment(Qt.AlignLeft | Qt.AlignTop) view.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) view.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) view.setRenderHint(QPainter.Antialiasing) self.detailGrpTextItem1 = textItem1 self.detailGrpTextItem2 = textItem2 self.detailFigTitle = title self.detailFigure_2_title = detailFigure_2_title self.turbofan = fan self.NNfigure_fadeIn = detailFigure_2_Qobj.fadeIn() self.NNfigure_fadeOut = detailFigure_2_Qobj.fadeOut() self._detailFigure_2_state = 0 # 0-hided, 1-showed return view
class Node(QGraphicsItemGroup): """ Parent class for all types of nodes. It contains all necessary functions to print a node in the GUI """ def __init__(self, node, parent, background, border, text, x=0, y=0, offset=20): """ Constructor for the node class. It generates all necessary variables and calls the draw function :param node: data node which it gets the data from :param parent: parent widget :param background: background color of the node :param border: border color for the node :param text: text color for the node :param x: x-position of the node :param y: y-position of the node :param offset: offset for the type to center it """ super().__init__() self.typeOffset = offset self.node = node self.parent = parent self.threatEdge = None self.counterEdge = None self.defaultEdge = None self.childEdges = [] self.parentEdges = [] self.headerGroup = QGraphicsItemGroup() self.attributes = QGraphicsItemGroup() self.footerGroup = QGraphicsItemGroup() self.setFlag(QGraphicsItem.ItemIsMovable, True) self.setFlag(QGraphicsItem.ItemIsSelectable, True) self.attributesHeight = 0 self.headerHeight = 0 self.printHeader(background, border, text) self.printAttributes(background, border, text) self.printFooter(background, border, text) self.setAcceptDrops(True) self.setPos(x, y) def getTypeRecursiveDown(self): """ Searches the children of a node to get a node with type != Conjunction If there is no other node with type != Conjunction, Conjunction will be returned :return: type of first child node with type != Conjunction or Conjunction """ if isinstance(self, Conjunction): for c in self.childEdges: if isinstance(c.dst, Conjunction): return c.dst.getTypeRecursiveDown() else: return type(c.dst) return type(self) else: return type(self) def getTypeRecursiveUp(self): """ Searches the parents of a node to get a node with type != Conjunction If there is no other node with type != Conjunction, Conjunction will be returned :return: type of first parent node with type != Conjunction or Conjunction """ if isinstance(self, Conjunction): for c in self.childEdges: if isinstance(c.dst, Conjunction): return c.dst.getTypeRecursiveUp() else: return type(c.dst) return type(self) else: return type(self) def addEdge(self, dst): """ Adds an child edge to this node an places the start of the arrow in the right place :param dst: destination node for the edge """ if isinstance(self, Threat) and dst.getTypeRecursiveDown() is Threat: edge = Edge(self, dst, -50) elif isinstance(self, Threat) and dst.getTypeRecursiveDown() is Countermeasure: edge = Edge(self, dst, 50) else: edge = Edge(self, dst, 0) self.parent.scene.addItem(edge) self.childEdges.append(edge) dst.parentEdges.append(self.childEdges[-1]) def actualizeEdges(self): """ Actualizes all child edges of this node so they start at the right position """ for e in self.childEdges: if isinstance(self, Threat) and e.dst.getTypeRecursiveDown() is Threat: e.offset = -50 elif isinstance(self, Threat) and e.dst.getTypeRecursiveDown() is Countermeasure: e.offset = 50 else: e.offset = 0 def fixParentEdgeRec(self): """ Fixes all starts of the parent edges so they start at the right position """ for p in self.parentEdges: if isinstance(p.start, Conjunction): p.start.fixParentEdgeRec() else: p.start.actualizeEdges() def getLeftRightChildren(self): """ Splits the children in to arrays with the same size :return: Tuple (left, right) with child elements split in to arrays """ left = [] right = [] neutralLeft = False for e in self.childEdges: if e.offset < 0: left.append(e.dst) elif e.offset > 0: right.append(e.dst) else: if neutralLeft is False: left.append(e.dst) neutralLeft = True else: right.append(e.dst) neutralLeft = False return left, right def printHeader(self, background, border, text): """ Prints the the header of the node. It contains the Node id, title and type :param background: background color of the node :param border: border color for the node :param text: text color for the node """ x = self.x() y = self.y() self.idText = QGraphicsTextItem() self.typeText = QGraphicsTextItem() self.titleText = QGraphicsTextItem() self.idRect = QGraphicsRectItem() self.typeRect = QGraphicsRectItem() self.titleRect = QGraphicsRectItem() self.typeText.setFont(Configuration.font) self.titleText.setFont(Configuration.font) self.idText.setFont(Configuration.font) self.typeText.setDefaultTextColor(QColor(text)) self.titleText.setDefaultTextColor(QColor(text)) self.idText.setDefaultTextColor(QColor(text)) self.titleText.setTextWidth(200) self.idText.setPlainText(self.node.id) self.typeText.setPlainText(type(self.node).__name__) self.titleText.setPlainText(self.node.title) titleHeight = int(self.titleText.boundingRect().height() / 20 + 0.5) * 20 self.idRect.setRect(x, y, 50, 20) self.typeRect.setRect(x + 50, y, 150, 20) self.titleRect.setRect(x, y + 20, 200, titleHeight) self.idRect.setBrush(QBrush(QColor(background))) self.typeRect.setBrush(QBrush(QColor(background))) self.titleRect.setBrush(QBrush(QColor(background))) self.idRect.setPen(QPen(QColor(border), 2)) self.typeRect.setPen(QPen(QColor(border), 2)) self.titleRect.setPen(QPen(QColor(border), 2)) self.idText.setPos(x, y - 2) self.typeText.setPos(x + self.typeOffset, y - 2) self.titleText.setPos(x, y + 18) self.headerHeight = titleHeight + 20 self.setToolTip(self.node.description) self.headerGroup.addToGroup(self.idRect) self.headerGroup.addToGroup(self.typeRect) self.headerGroup.addToGroup(self.titleRect) self.headerGroup.addToGroup(self.idText) self.headerGroup.addToGroup(self.typeText) self.headerGroup.addToGroup(self.titleText) self.addToGroup(self.headerGroup) 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) def redrawOptions(self, background, border, text): """ Redraws the node with option for the background, border and text color :param background: background color of the node :param border: border color for the node :param text: text color for the node """ y = self.y() x = self.x() self.prepareGeometryChange() for i in self.attributes.childItems(): self.attributes.removeFromGroup(i) self.parent.scene.removeItem(i) for i in self.headerGroup.childItems(): self.headerGroup.removeFromGroup(i) self.parent.scene.removeItem(i) for i in self.footerGroup.childItems(): self.footerGroup.removeFromGroup(i) self.parent.scene.removeItem(i) self.removeFromGroup(self.attributes) self.removeFromGroup(self.footerGroup) self.removeFromGroup(self.headerGroup) self.printHeader(background, border, text) self.printAttributes(background, border, text) self.printFooter(background, border, text) self.parent.scene.removeItem(self) self.parent.scene.addItem(self) self.setPos(x, y) def redraw(self): """ Redraws the node with standard colors """ self.redrawOptions(Qt.white, Qt.black, Qt.black) def printFooter(self, background, border, text): """ Prototype function for the footer. Implemented in the child classes :param background: background color of the node :param border: border color for the node :param text: text color for the node """ pass def setPos(self, x, y): """ Overloads setPos to set the position of the visible node in the data node :param x: X part of the position :param y: Y part of the position """ self.node.position = (x, y) super().setPos(x, y) def paint(self, painter, options, widget=None): """ Reimplementation for the paint function of the QGraphicsItemGroup. The Reimplementation is needed to print a proper border when the item is selected :param painter: The painter, which draws the node :param options: options for the paint job :param widget: widget of the Item """ myOption = QStyleOptionGraphicsItem(options) myOption.state &= ~QStyle.State_Selected super().paint(painter, myOption, widget=None) if options.state & QStyle.State_Selected: painter.setPen(QPen(Qt.black, 2, Qt.DotLine)) rect = QRect(self.boundingRect().x() - 1.5, self.boundingRect().y() - 1.5, self.boundingRect().x() + self.boundingRect().width() + 3, self.boundingRect().y() + self.boundingRect().height() + 0.5) painter.drawRect(rect) def selectChildren(self): """ Select all children """ self.setSelected(True) for i in self.childEdges: i.setSelected(True) i.dst.selectChildren() def delete(self): """ Deletes this node """ for e in self.parentEdges: self.parent.scene.removeItem(e) for e in self.childEdges: self.parent.scene.removeItem(e) self.parent.tree.removeNode(self.node.id) self.parent.scene.removeItem(self) def edit(self): """ Opens the edit dialog """ NodeEdit(self, self.parent).exec() def mouseDoubleClickEvent(self, event): """ Handles a double click on the node. The double click opens the edit window for this node :param event: click event """ self.edit() def dropEvent(self, event): """ Sets the correct position to the data node if the item is drag & dropped :param event: Drop event :return: Changed Value """ print("ok") self.node.position = (event.pos().x(), event.pos().y()) return super().dropEvent(self, event)
class QGraphicsResizableRectItem(QGraphicsRectItem): def __init__(self, min_height, min_width, *args): super().__init__(*args) # Diverse parameters for drawing self.handleSelected = None self.handles = {} self.min_width = min_width self.min_height = min_height self.initSizes() self.initContent() self.initAssets() self.initUI() self.initTitle() def initUI(self): # set flags self.setAcceptHoverEvents(True) self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True) self.setFlag(QGraphicsItem.ItemIsFocusable, True) self.updateHandles() self.setFlag(QGraphicsItem.ItemIsMovable, True) self.setFlag(QGraphicsItem.ItemIsSelectable, True) def initContent(self): self.content = NodeContentWidget(None) self.grContent = QGraphicsProxyWidget(self) self.setContentGeometry() self.grContent.setWidget(self.content) def initAssets(self): self._title_color = Qt.white self._title_font = QFont('Ubuntu', 8) self._color = QColor("#7F00000") self._color_selected = QColor("#FFFFA637") self._color_hovered = QColor("#FF37A6FF") self._pen_default = QPen(self._color) self._pen_default.setWidthF(OUTLINE_WIDTH) self._pen_selected = QPen(self._color_selected) self._pen_selected.setWidthF(OUTLINE_WIDTH) self._pen_hovered = QPen(self._color_hovered) self._pen_hovered.setWidthF(OUTLINE_WIDTH + 1) self._brush_title = QBrush(QColor("#FF313131")) self._brush_background = QBrush(QColor("#E3212121")) def initSizes(self): # self.width = 180 # self.height = 240 # Diverse parameters for drawing self.handleSize = 5 self.edge_roundness = 15. self.edge_padding = 10. self.title_height = 24 self.title_horizontal_padding = 5. self.title_vertical_padding = 4. def setContentGeometry(self): self.content.setGeometry( self.edge_roundness, self.title_height + self.edge_roundness, self.width - 2 * self.edge_roundness, self.height - 2 * self.edge_roundness - self.title_height) def initTitle(self): # Draw the _title self._title_color = Qt.white self._title_font = QFont('Ubuntu', 10) self._padding = 5. self.title_height = 24 self.title_item = QGraphicsTextItem(self) # self.title_item.node = self.node self.title_item.setDefaultTextColor(self._title_color) self.title_item.setFont(self._title_font) self.title_item.setPos(self._padding, 0) self.title_item.setTextWidth(self.width - 2 * self._padding) self.title_item.setPlainText('Resizeable node') @property def height(self): return self.rect().height() @property def width(self): return self.rect().width() def updateHandles(self): rect = self.boundingRect() left, width, top, height = rect.left(), rect.width(), rect.top( ), rect.height() offset = self.handleSize self.handles[Handle.TopLeft] = QRectF(left, top, offset, offset) self.handles[Handle.TopMiddle] = QRectF(left + offset, top, width - 2 * offset, offset) self.handles[Handle.TopRight] = QRectF(left + width - offset, top, offset, offset) self.handles[Handle.BottomLeft] = QRectF(left, top + height - offset, offset, offset) self.handles[Handle.MiddleLeft] = QRectF(left, top + offset, offset, height - 2 * offset) self.handles[Handle.BottomRight] = QRectF(left + width - offset, top + height - offset, offset, offset) self.handles[Handle.MiddleRight] = QRectF(left + width - offset, top + offset, offset, height - 2 * offset) self.handles[Handle.BottomMiddle] = QRectF(left + offset, top + height - offset, width - 2 * offset, offset) def boundingRect(self): # Return rectangle for selection detection return self.rect().normalized() def handleAt(self, point): for handle, rect in self.handles.items(): if rect.contains(point): if DEBUG: print(handle, rect) return handle else: return None def hoverMoveEvent(self, event: 'QGraphicsSceneHoverEvent') -> None: # if self.isSelected(): handle = self.handleAt(event.pos()) if handle is not None: self.setCursor(handleCursors[handle]) else: self.setCursor(Qt.ArrowCursor) super().hoverMoveEvent(event) def hoverLeaveEvent(self, event: 'QGraphicsSceneHoverEvent') -> None: # if self.isSelected(): self.setCursor(Qt.ArrowCursor) super().hoverLeaveEvent(event) def mousePressEvent(self, event: 'QGraphicsSceneMouseEvent') -> None: """ Executed when the mouse is pressed on the item. """ try: self.handleSelected = self.handleAt(event.pos()) if self.handleSelected: # record the position where the mouse was pressed self.currentPos = event.pos() # current rectangle at mouse pressed self.currentRect = self.boundingRect() super().mousePressEvent(event) except Exception as e: print(e) def mouseReleaseEvent(self, event: 'QGraphicsSceneMouseEvent') -> None: """ Executed when the mouse is released from the item. """ super().mouseReleaseEvent(event) self.handleSelected = None self.currentPos = None self.currentRect = None self.update() def mouseMoveEvent(self, event: 'QGraphicsSceneMouseEvent') -> None: """ Executed when the mouse is being moved over the item while being pressed. """ if self.handleSelected is not None: self.resize(event.pos()) else: super().mouseMoveEvent(event) def resize(self, pos): """Update rectangle and bounding rectangle""" rect = self.rect() boundingRect = self.boundingRect() from_left = self.currentRect.left() from_right = self.currentRect.right() from_top = self.currentRect.top() from_bottom = self.currentRect.bottom() to_left = from_left + pos.x() - self.currentPos.x() to_right = from_right + pos.x() - self.currentPos.x() to_top = from_top + pos.y() - self.currentPos.y() to_bottom = from_bottom + pos.y() - self.currentPos.y() self.prepareGeometryChange() update_left, update_top, update_right, update_bottom = handleUpdate[ self.handleSelected] if update_left: if from_right - to_left <= self.min_width: boundingRect.setLeft(from_right - self.min_width) else: boundingRect.setLeft(to_left) rect.setLeft(boundingRect.left()) if update_top: if from_bottom - to_top <= self.min_height: boundingRect.setTop(from_bottom - self.min_height) else: boundingRect.setTop(to_top) rect.setTop(boundingRect.top()) if update_bottom: if to_bottom - from_top <= self.min_height: boundingRect.setBottom(from_top + self.min_height) else: boundingRect.setBottom(to_bottom) rect.setBottom(boundingRect.bottom()) if update_right: if to_right - from_left <= self.min_width: boundingRect.setRight(from_left + self.min_width) else: boundingRect.setRight(to_right) rect.setRight(boundingRect.right()) self.setRect(rect) self.updateHandles() self.setContentGeometry() def shape(self): """ Returns the shape of this item as a QPainterPath in local coordinates. """ path = QPainterPath() # path.addRoundedRect(self.rect(), self.edge_size, self.edge_size) path.addRect(self.rect()) return path def paint(self, painter: QPainter, option: 'QStyleOptionGraphicsItem', widget: Optional[QWidget] = ...) -> None: # content rect = self.rect() path_content = QPainterPath() path_content.setFillRule(Qt.WindingFill) path_content.addRoundedRect(rect, self.edge_roundness, self.edge_roundness) painter.setPen(Qt.NoPen) painter.setBrush(self._brush_background) painter.drawPath(path_content.simplified()) # outline path_outline = QPainterPath() path_outline.addRoundedRect(rect, self.edge_roundness, self.edge_roundness) painter.setPen( self._pen_default if not self.isSelected() else self._pen_selected) painter.setBrush(Qt.NoBrush) painter.drawPath(path_outline.simplified()) for handle in self.handles.values(): path_handle = QPainterPath() path_handle.addRect(handle) painter.drawPath(path_handle)
class ItemConfigDialog(QMainWindow, Ui_MainWindow): """ Окно настроек элемента """ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.setupUi(self) self.text_aligns = [ ('center', 'По центру'), ('left', 'По левому краю'), ('right', 'По правому краю'), ] self.text_align = 'center' for align, align_text in self.text_aligns: self.textPosH.addItem(align_text) self.item: QGraphicsRectItem = QGraphicsRectItem() self.main_view: QGraphicsView = QGraphicsView() self.fontSelect.currentFontChanged.connect(self.render_text) self.fontSize.valueChanged.connect(self.render_text) self.textPosH.currentTextChanged.connect(self.render_text) self.exText.textChanged.connect(self.render_text) self.font_color = QColor('black') self.scene = QGraphicsScene() self.graphicsView.setScene(self.scene) self.text = QGraphicsTextItem() self.buttonBox.accepted.connect(self.accepted) self.colorPicker.clicked.connect(self.select_color) def show(self): super(ItemConfigDialog, self).show() self.text = QGraphicsTextItem() self.name.setText(self.item.toolTip()) font = QFont() font_name = self.item.data(int(const.ITEM_DATA_KEY_FONT)) if font_name: font.fromString(font_name) font_size = self.item.data(int(const.ITEM_DATA_KEY_FONT_SIZE)) if font_size: font.setPointSize(font_size) else: font.setPointSize(12) font_color = self.item.data(int(const.ITEM_DATA_KEY_FONT_COLOR)) if font_color: self.font_color.setRgb(*font_color) self.text.setFont(font) self.text_align = self.item.data(int(const.ITEM_DATA_KEY_FONT_ALIGN)) self.render_text() self.render_item() def select_color(self): color = QColorDialog.getColor() if color.isValid(): self.font_color = color self.render_text() def render_text(self, event=None): font = self.fontSelect.currentFont() font.setPointSize(self.fontSize.value()) self.text.setFont(font) self.text.setTextWidth(self.item.rect().width()) self.text.setDefaultTextColor(self.font_color) for align, align_text in self.text_aligns: if align_text == self.textPosH.currentText(): self.text_align = align self.text.setHtml(f"<div align='{self.text_align}'>{self.exText.text()}</div>") def render_item(self): self.scene.clear() pos_from = self.main_view.mapFromScene(self.item.sceneBoundingRect()) pos = self.main_view.mapToScene(pos_from) point = pos.boundingRect() width, height = point.width(), point.height() img = QImage(width, height, QImage.Format_ARGB32_Premultiplied) painter = QPainter(img) painter.setRenderHint(QPainter.Antialiasing) self.item.scene().render(painter, source=QRectF(point.x(), point.y(), width, height)) painter.end() self.scene.addItem(QGraphicsPixmapItem(QPixmap(img))) self.scene.addItem(self.text) self.graphicsView.fitInView(QRectF(0, 0, width, height), Qt.KeepAspectRatio) self.scene.update() def accepted(self): self.item.setData(int(const.ITEM_DATA_KEY_FONT), self.text.font().toString().split(',')[0]) self.item.setData(int(const.ITEM_DATA_KEY_FONT_SIZE), self.text.font().pointSize()) self.item.setData(int(const.ITEM_DATA_KEY_FONT_ALIGN), self.text_align) self.item.setData(int(const.ITEM_DATA_KEY_FONT_COLOR), self.font_color.getRgb()) self.item.setToolTip(self.name.text()) self.item.scene().update() self.close()