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 QmyMainWindow(QMainWindow): def __init__(self, parent=None): super().__init__(parent) #调用父类构造函数,创建窗体 self.ui = Ui_MainWindow() #创建UI对象 self.ui.setupUi(self) #构造UI界面 self.player = QMediaPlayer(self) #创建视频播放器 self.player.setNotifyInterval(1000) #信息更新周期, ms scene = QGraphicsScene(self) self.ui.graphicsView.setScene(scene) self.videoItem = QGraphicsVideoItem() #视频显示画面 self.videoItem.setSize(QSizeF(320, 220)) self.videoItem.setFlag(QGraphicsItem.ItemIsMovable) self.videoItem.setFlag(QGraphicsItem.ItemIsSelectable) self.videoItem.setFlag(QGraphicsItem.ItemIsFocusable) scene.addItem(self.videoItem) self.player.setVideoOutput(self.videoItem) #设置视频显示图形项 self.textItem = QGraphicsTextItem("面朝大海,春暖花开") #弹幕文字 font = self.textItem.font() font.setPointSize(20) self.textItem.setFont(font) self.textItem.setDefaultTextColor(Qt.red) self.textItem.setPos(100, 220) self.textItem.setFlag(QGraphicsItem.ItemIsMovable) self.textItem.setFlag(QGraphicsItem.ItemIsSelectable) self.textItem.setFlag(QGraphicsItem.ItemIsFocusable) scene.addItem(self.textItem) self.ui.btnText.setCheckable(True) #弹幕文字按钮 self.ui.btnText.setChecked(True) self.__duration = "" self.__curPos = "" self.player.stateChanged.connect(self.do_stateChanged) self.player.positionChanged.connect(self.do_positionChanged) self.player.durationChanged.connect(self.do_durationChanged) ## ==============自定义功能函数======================== ## ==============event处理函数========================== def closeEvent(self, event): #窗体关闭时 # 窗口关闭时不能自动停止播放,需手动停止 if (self.player.state() == QMediaPlayer.PlayingState): self.player.stop() ## ==========由connectSlotsByName()自动连接的槽函数============ @pyqtSlot() ##打开文件 def on_btnOpen_clicked(self): curPath = QDir.currentPath() #获取系统当前目录 ## curPath=os.getcwd() title = "选择视频文件" filt = "视频文件(*.wmv *.avi);;所有文件(*.*)" fileName, flt = QFileDialog.getOpenFileName(self, title, curPath, filt) if (fileName == ""): return fileInfo = QFileInfo(fileName) baseName = fileInfo.fileName() ## baseName=os.path.basename(fileName) self.ui.LabCurMedia.setText(baseName) curPath = fileInfo.absolutePath() QDir.setCurrent(curPath) #重设当前目录 media = QMediaContent(QUrl.fromLocalFile(fileName)) self.player.setMedia(media) #设置播放文件 self.player.play() @pyqtSlot() ##播放 def on_btnPlay_clicked(self): self.player.play() @pyqtSlot() ##暂停 def on_btnPause_clicked(self): self.player.pause() @pyqtSlot() ##停止 def on_btnStop_clicked(self): self.player.stop() @pyqtSlot() ##全屏 def on_btnFullScreen_clicked(self): self.videoWidget.setFullScreen(True) @pyqtSlot() ##静音按钮 def on_btnSound_clicked(self): mute = self.player.isMuted() self.player.setMuted(not mute) if mute: self.ui.btnSound.setIcon(QIcon(":/icons/images/volumn.bmp")) else: self.ui.btnSound.setIcon(QIcon(":/icons/images/mute.bmp")) @pyqtSlot(int) ##音量调节 def on_sliderVolumn_valueChanged(self, value): self.player.setVolume(value) @pyqtSlot(int) ##播放进度调节 def on_sliderPosition_valueChanged(self, value): self.player.setPosition(value) @pyqtSlot() ##放大 def on_btnZoomIn_clicked(self): sc = self.videoItem.scale() self.videoItem.setScale(sc + 0.1) @pyqtSlot() ##缩小 def on_btnZoomOut_clicked(self): sc = self.videoItem.scale() self.videoItem.setScale(sc - 0.1) @pyqtSlot(bool) ##弹幕 def on_btnText_clicked(self, checked): self.textItem.setVisible(checked) ## =============自定义槽函数=============================== def do_stateChanged(self, state): isPlaying = (state == QMediaPlayer.PlayingState) self.ui.btnPlay.setEnabled(not isPlaying) self.ui.btnPause.setEnabled(isPlaying) self.ui.btnStop.setEnabled(isPlaying) def do_durationChanged(self, duration): self.ui.sliderPosition.setMaximum(duration) secs = duration / 1000 #秒 mins = secs / 60 #分钟 secs = secs % 60 #余数秒 self.__duration = "%d:%d" % (mins, secs) self.ui.LabRatio.setText(self.__curPos + "/" + self.__duration) def do_positionChanged(self, position): if (self.ui.sliderPosition.isSliderDown()): return #如果正在拖动滑条,退出 self.ui.sliderPosition.setSliderPosition(position) secs = position / 1000 #秒 mins = secs / 60 #分钟 secs = secs % 60 #余数秒 self.__curPos = "%d:%d" % (mins, secs) self.ui.LabRatio.setText(self.__curPos + "/" + self.__duration)
class CallOut(): ''' This represents a callouton the diagram This class creates a text item and then surrounds it with a polygon that is a rectangle with a pointer to another object on the diagram. This can be used by hover over functions or be displayed as a part of a node or relationship ''' def __init__(self, scene, text, anchorPoint, diagramType, format): self.scene = scene self.model = self.scene.parent.model self.text = text self.anchorPoint = anchorPoint self.diagramType = diagramType self.format = format # initialize the two qgraphicsitems needed to draw a relationship to None self.itemText = None self.itemPolygon = None self.drawIt def name(self, ): return "no name" def NZID(self, ): return None def clearItem(self, ): if (not self.itemText is None and not self.itemText.scene() is None): self.itemText.scene().removeItem(self.itemText) if (not self.itemPolygon is None and not self.itemPolygon.scene() is None): self.itemPolygon.scene().removeItem(self.itemPolygon) def drawIt(self, ): ''' draw the callout ''' # if the polygon and text graphics items already exist on the scene then delete them self.clearItem() # draw the relationship arc pen = self.format.pen() brush = self.format.brush() # create text box # draw the text self.itemText = QGraphicsTextItem(self.relationInstance.relName, parent=None) self.itemText.setZValue(CALLOUTLAYER) self.itemText.setFlag(QGraphicsItem.ItemIsMovable, True) self.itemText.setFlag(QGraphicsItem.ItemIsSelectable, True) self.itemText.setSelected(False) self.itemText.setData(NODEID, self.relationInstance.NZID) self.itemText.setData(ITEMTYPE, CALLOUT) self.itemText.setHtml(self.genTextHTML()) # set the position of the text self.itemText.setPos(self.anchorPoint) # get the height and width of the text graphics item th = self.IRtext.boundingRect().height() tw = self.IRtext.boundingRect().width() #create an empty polygon arrowPolygon = QPolygonF() # add callout points arrowPolygon.append(self.anchorPoint) arrowPolygon.append( QPointF(self.anchorPoint.x() + tw, self.anchorPoint.y())) arrowPolygon.append( QPointF(self.anchorPoint.x() + tw, self.anchorPoint.y())) arrowPolygon.append( QPointF(self.anchorPoint.x() + tw, self.anchorPoint.y() + th)) arrowPolygon.append( QPointF(self.anchorPoint.x(), self.anchorPoint.y() + th)) self.itemPolygon = QGraphicsPolygonItem( arrowPolygon, parent=None, ) self.itemPolygon.setZValue(CALLOUTLAYER) self.itemPolygon.setBrush(brush) self.itemPolygon.setPen(pen) self.itemPolygon.setFlag(QGraphicsItem.ItemIsMovable, True) self.itemPolygon.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True) self.itemPolygon.setFlag(QGraphicsItem.ItemIsSelectable, False) self.itemPolygon.setSelected(False) # set data in the RelLine object self.IRel.setData(NODEID, self.relationInstance.NZID) self.IRel.setData(ITEMTYPE, RELINSTANCEARC) # add the polygon object to the scene self.scene.addItem(self.itemPolygon) # add text to the scene self.scene.addItem(self.itemText) def updateText(self, ): # # force the node instance to update its values in case it has been updated from another diagram or the tree view # self.relationInstance.reloadDictValues() # self.IRtext.setPlainText(self.relationInstance.relName) self.itemText.setHtml(self.genTextHTML()) def genTextHTML(self): '''generate html to display the text ''' prefix = '<html><body>' suffix = "</body></html>" myHTML = ('{}<p><font size="1"> [{}]</font></p>{}'.format( prefix, self.text, suffix)) return myHTML def moveIt(self, ): self.drawIt()
from PyQt5.QtGui import QStandardItemModel,QStandardItem if __name__ == "__main__": app = QApplication(sys.argv) mainWindow = QMainWindow() mainWindow.setWindowTitle("PyQt5Test") w = QWidget(mainWindow) elipse = QGraphicsEllipseItem(200, 200, 200, 200) elipse2 = QGraphicsEllipseItem(100, 100, 100, 100) text = QGraphicsTextItem("TEST", elipse) text.setTextInteractionFlags(Qt.TextEditorInteraction) text.setFlag(QGraphicsItem.ItemIsMovable) elipse.setFlag(QGraphicsItem.ItemIsMovable) elipse2.setFlag(QGraphicsItem.ItemIsMovable) scene = QGraphicsScene() scene.addItem(text) scene.addItem(elipse) scene.addItem(elipse2) view = QGraphicsView(scene, w) textEdit = QTextEdit("Moon text", w) #w.showMaximized() treeView = QTreeView()
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 SegmentItemBase(QGraphicsItemGroup): def __init__(self, startNode, endNode, parent: "ConnectionBase"): """ A connection is displayed as a chain of segmentItems (stored in Connection.segments) Parameters. ---------- startNode endNode parent: type(parent): Connection """ super().__init__(None) self.logger = parent.logger self.setFlag(self.ItemIsSelectable, True) self.dragged = False self.initialised = False self.connection = parent self.firstChild = None self.secondChild = None self.cornerChild = None self.linePoints = None self.startNode = startNode self.endNode = endNode # These nodes are the nodes before and after the crossing self.start = None self.end = None # Unused. Related to interrupting segments for a clearer diagram self.disrBeforeNode = None self.disrAfterNode = None self.disrBeforeSeg = None self.disrAfterSeg = None self.disrBefore = False self.disrAfter = False self.hasBridge = False self.bridgedSegment = None # Only for editorMode 1 self.firstLine = None self.secondLine = None self.secondCorner = None self.thirdCorner = None self.keyPr = 0 # Used to only create the child objects once self._isDraggingInProgress = False self.insertInParentSegments() self.label = QGraphicsTextItem(self.connection.displayName) self.connection.parent.diagramScene.addItem(self.label) self.label.setVisible(False) self.label.setFlag(self.ItemIsMovable, True) self.labelMass = QGraphicsTextItem() self.connection.parent.diagramScene.addItem(self.labelMass) self.labelMass.setVisible(False) self.labelMass.setFlag(self.ItemIsMovable, True) self.setToolTip(self.connection.displayName) def segLength(self): return calcDist(self.line().p1(), self.line().p2()) def interpolate( self, partLen2, totLenConn, ): # c1_r = 0 # c1_b = 255 c1_r = 160 c1_b = 160 c1_g = 160 # c2_r = 255 # c2_b = 0 c2_r = 0 c2_b = 0 c2_g = 0 try: f1 = int(partLen2 / totLenConn) f2 = int((totLenConn - partLen2) / totLenConn) except ZeroDivisionError: return QColor(100, 100, 100) else: return QColor(f1 * c2_r + f2 * c1_r, f1 * c2_g + f2 * c1_g, f1 * c2_b + f2 * c1_b) def line(self): return self.linePoints def setLine(self, *args): self.setZValue(-1) if len(args) == 2: p1, p2 = args x1 = p1.x() y1 = p1.y() x2 = p2.x() y2 = p2.y() else: x1, y1, x2, y2 = args self._setLineImpl(x1, y1, x2, y2) def _setLineImpl(self, x1, y1, x2, y2): raise NotImplementedError() def updateGrad(self): raise NotImplementedError() def insertInParentSegments(self): """ This function inserts the segment in correct order to the segment list of the connection. Returns ------- """ prevSeg = None for s in self.connection.segments: if s.endNode is self.startNode: prevSeg = s # Todo: Add support for disr segments # if the startNode parent is a connection: if not hasattr(self.startNode.parent, "fromPort"): self.connection.segments.insert( self.connection.segments.index(prevSeg) + 1, self) else: self.connection.segments.insert(0, self) def mousePressEvent(self, e): if e.button() == 1: self.keyPr = 1 self.logger.debug("Setting key to 1") self.connection.selectConnection() if self.isVertical(): try: self.oldX = self.startNode.parent.scenePos().x() except AttributeError: pass else: self.logger.debug("set oldx") def mouseMoveEvent(self, e): self.logger.debug(self.connection.parent.editorMode) if self.keyPr == 1: self.logger.debug("moved with button 1") newPos = e.pos() if self.connection.parent.editorMode == 0: if not self._isDraggingInProgress: self.initInMode0() else: self.dragInMode0(newPos) elif self.connection.parent.editorMode == 1: if type(self.startNode.parent) is CornerItem and type( self.endNode.parent) is CornerItem: if not self.startNode.parent.isVisible(): self.startNode.parent.setVisible(True) if not self.endNode.parent.isVisible(): self.endNode.parent.setVisible(True) if self.isVertical(): self.logger.debug("Segment is vertical: %s", self.connection.segments.index(self)) self.endNode.parent.setPos( newPos.x(), self.endNode.parent.scenePos().y()) self.startNode.parent.setPos( newPos.x(), self.startNode.parent.scenePos().y()) self.updateGrad() if self.isHorizontal(): self.logger.debug("Segment is horizontal") self.endNode.parent.setPos( self.endNode.parent.scenePos().x(), newPos.y()) self.startNode.parent.setPos( self.startNode.parent.scenePos().x(), newPos.y()) elif type(self.endNode.parent ) is CornerItem and self.isVertical(): self.logger.debug("Segment is vertical and can't be moved") if self.isHorizontal(): isFirstSegment = hasattr( self.startNode.parent, "fromPort") and not self.startNode.prevN() isLastSegment = hasattr( self.endNode.parent, "fromPort") and not self.endNode.nextN() if isLastSegment: self.logger.debug("A last segment is being dragged.") if not self._isDraggingInProgress: self._initInMode1(False) self._dragInMode1(False, newPos) elif isFirstSegment: self.logger.debug("A first segment is being dragged.") if not self._isDraggingInProgress: self._initInMode1(True) self._dragInMode1(True, newPos) else: self.logger.debug( "Unrecognized editorMode in segmentItem mouseMoveEvent") def deleteNextHorizSeg(self, b, nextS): if b: pass else: nodeTodelete1 = self.endNode nodeTodelete2 = self.endNode.nextN() self.endNode = nextS.endNode self.startNode.setNext(self.endNode) self.endNode.setPrev(self.startNode) # x-position of the ending point of the next segment line posx1 = self.connection.segments[ self.connection.segments.index(self) + 2].line().p2().x() self.connection.parent.diagramScene.removeItem(nextS) self.connection.segments.remove(nextS) self.connection.parent.diagramScene.removeItem( nodeTodelete1.parent) indexOfSelf = self.connection.segments.index(self) nextVS = self.connection.segments[indexOfSelf + 1] self.connection.parent.diagramScene.removeItem(nextVS) self.connection.segments.remove(nextVS) self.connection.parent.diagramScene.removeItem( nodeTodelete2.parent) self.setLine( self.startNode.parent.scenePos().x(), self.startNode.parent.scenePos().y(), posx1, self.startNode.parent.scenePos().y(), ) def deletePrevHorizSeg(self, b, prevS): if b: pass else: nodeTodelete1 = self.startNode nodeTodelete2 = self.startNode.prevN() self.startNode = prevS.startNode self.startNode.setNext(self.endNode) self.endNode.setPrev(self.startNode) posx1 = self.connection.segments[ self.connection.segments.index(self) - 2].line().p1().x() self.connection.parent.diagramScene.removeItem(prevS) self.connection.segments.remove(prevS) self.connection.parent.diagramScene.removeItem( nodeTodelete1.parent) indexOfSelf = self.connection.segments.index(self) nextVS = self.connection.segments[indexOfSelf - 1] self.connection.parent.diagramScene.removeItem(nextVS) self.connection.segments.remove(nextVS) self.connection.parent.diagramScene.removeItem( nodeTodelete2.parent) self.setLine( posx1, self.endNode.parent.scenePos().y(), self.endNode.parent.scenePos().x(), self.endNode.parent.scenePos().y(), ) def deleteSegment(self): nodeToConnect = self.startNode.prevN() nodeToConnect2 = self.endNode.nextN() nodeToConnect.setNext(nodeToConnect2) self.connection.parent.diagramScene.removeItem(self) self.connection.segments.remove(self) self.connection.parent.diagramScene.removeItem(self.startNode.parent) self.connection.parent.diagramScene.removeItem(self.endNode.parent) def splitSegment(self): pass def mouseReleaseEvent(self, e): # Should be same as below # self.scene().removeItem(self) if e.button() == 1: self.keyPr = 0 if self.connection.parent.editorMode == 0: if self._isDraggingInProgress: self.cornerChild.setFlag( self.ItemSendsScenePositionChanges, True) self.hide() self.connection.segments.remove(self) self.connection.parent.diagramScene.removeItem(self) elif self.connection.parent.editorMode == 1: if self.isVertical(): try: self.oldX except AttributeError: pass else: command = HorizSegmentMoveCommand( self, self.oldX, "Moving segment command") self.connection.parent.parent().undoStack.push(command) self.oldX = self.scenePos().x() if self.isHorizontal(): if type(self.startNode.parent) is CornerItem and type( self.endNode.parent) is CornerItem: try: nextHorizSeg = self.connection.segments[ self.connection.segments.index(self) + 2] prevHorizSeg = self.connection.segments[ self.connection.segments.index(self) - 2] except IndexError: self.logger.debug("no next or prev segments") else: if nextHorizSeg.isHorizontal( ) and int(self.endNode.parent.pos().y() - 10) <= int(nextHorizSeg.line().p2().y( )) <= int(self.endNode.parent.pos().y() + 10): self.deleteNextHorizSeg(False, nextHorizSeg) self.logger.debug("next horizontal") return if prevHorizSeg.isHorizontal() and int( self.startNode.parent.pos().y() - 10 ) <= int(prevHorizSeg.line().p2().y()) <= int( self.startNode.parent.pos().y() + 10): self.deletePrevHorizSeg(False, prevHorizSeg) self.logger.debug("previous horizontal") return if self.secondCorner is not None: self.logger.debug("Second corner is not none") # if PortItem if hasattr(self.endNode.parent, "fromPort"): segbef = self.connection.segments[ self.connection.getNodePos( self.secondCorner.node.prevN().parent)] segbef.setLine( segbef.line().p1().x(), segbef.line().p1().y(), segbef.line().p2().x(), self.secondCorner.scenePos().y(), ) self.setLine( self.thirdCorner.scenePos().x(), self.thirdCorner.scenePos().y(), self.line().p2().x(), self.line().p2().y(), ) self.secondCorner.setFlag( self.ItemSendsScenePositionChanges, True) self.thirdCorner.setFlag( self.ItemSendsScenePositionChanges, True) # Allow for iterative branching self.secondCorner = None self.thirdCorner = None self.firstLine = None self.secondLine = None self._isDraggingInProgress = False # if PortItem elif hasattr(self.startNode.parent, "fromPort"): segafter = self.connection.segments[ self.connection.getNodePos( self.thirdCorner.node.nextN().parent)] segafter.setLine( segafter.line().p1().x(), self.thirdCorner.scenePos().y(), segafter.line().p2().x(), segafter.line().p2().y(), ) self.setLine( self.line().p1().x(), self.line().p1().y(), self.secondCorner.scenePos().x(), self.secondCorner.scenePos().y(), ) self.secondCorner.setFlag( self.ItemSendsScenePositionChanges, True) self.thirdCorner.setFlag( self.ItemSendsScenePositionChanges, True) # Allow for iterative branching self.secondCorner = None self.thirdCorner = None self.firstLine = None self.secondLine = None self._isDraggingInProgress = False else: self.logger.debug("getting no start or end") else: self.logger.debug("Second corner is none") else: pass def initInMode0(self): if (hasattr(self.startNode.parent, "fromPort")) and (self.startNode.prevN() is not None): self.disrAfterNode = self.startNode self.start = self.startNode.prevN().prevN() segments = self.connection.segments for s in segments: if s.startNode is self.start: self.disrBeforeSeg = s self.disrAfterSeg = self self.disrBefore = True else: self.start = self.startNode if (hasattr(self.endNode.parent, "fromPort")) and (self.endNode.nextN() is not None): self.disrBeforeNode = self.endNode self.end = self.endNode.nextN().nextN() segments = self.connection.segments for s in segments: if s.endNode is self.end: self.disrAfterSeg = s self.disrBeforeSeg = self self.disrAfter = True else: self.end = self.endNode rad = self.connection.getRadius() self.cornerChild = CornerItem(-rad, -rad, 2 * rad, 2 * rad, self.start, self.end, self.connection) self.firstChild = self._createSegment(self.start, self.cornerChild.node) self.secondChild = self._createSegment(self.cornerChild.node, self.end) self.start.setNext(self.cornerChild.node) self.end.setPrev(self.cornerChild.node) self.firstChild.setVisible(False) self.secondChild.setVisible(False) self.cornerChild.setVisible(False) self.connection.parent.diagramScene.addItem(self.firstChild) self.connection.parent.diagramScene.addItem(self.secondChild) self.connection.parent.diagramScene.addItem(self.cornerChild) self._isDraggingInProgress = True def _initInMode1(self, b): rad = self.connection.getRadius() if b: if (hasattr(self.startNode.parent, "fromPort")) and (self.startNode.prevN() is None): # We are at the toPort. # self.end = self.endNode # self.start = self.startNode self.secondCorner = CornerItem(-rad, -rad, 2 * rad, 2 * rad, self.startNode, None, self.connection) self.thirdCorner = CornerItem(-rad, -rad, 2 * rad, 2 * rad, self.secondCorner.node, self.endNode, self.connection) self.secondCorner.node.setNext(self.thirdCorner.node) self.startNode.setNext(self.secondCorner.node) self.endNode.setPrev(self.thirdCorner.node) self.endNode = self.secondCorner.node self.firstLine = self._createSegment(self.secondCorner.node, self.thirdCorner.node) self.secondLine = self._createSegment( self.thirdCorner.node, self.thirdCorner.node.nextN()) self.secondCorner.setVisible(False) self.thirdCorner.setVisible(False) self.firstLine.setVisible(False) self.secondLine.setVisible(False) # self.thirdLine.setVisible(False) self.connection.parent.diagramScene.addItem(self.secondCorner) self.connection.parent.diagramScene.addItem(self.thirdCorner) self.connection.parent.diagramScene.addItem(self.firstLine) self.connection.parent.diagramScene.addItem(self.secondLine) self.logger.debug("inited") self._isDraggingInProgress = True else: if (hasattr(self.endNode.parent, "fromPort")) and (self.endNode.nextN() is None): # We are at the toPort. # self.end = self.endNode # self.start = self.startNode self.secondCorner = CornerItem(-rad, -rad, 2 * rad, 2 * rad, self.startNode, None, self.connection) self.thirdCorner = CornerItem(-rad, -rad, 2 * rad, 2 * rad, self.secondCorner.node, self.endNode, self.connection) self.secondCorner.node.setNext(self.thirdCorner.node) self.startNode.setNext(self.secondCorner.node) self.endNode.setPrev(self.thirdCorner.node) self.startNode = self.thirdCorner.node self.firstLine = self._createSegment( self.secondCorner.node.prevN(), self.secondCorner.node) self.secondLine = self._createSegment(self.secondCorner.node, self.thirdCorner.node) self.secondCorner.setVisible(False) self.thirdCorner.setVisible(False) self.firstLine.setVisible(False) self.secondLine.setVisible(False) # self.thirdLine.setVisible(False) self.connection.parent.diagramScene.addItem(self.secondCorner) self.connection.parent.diagramScene.addItem(self.thirdCorner) self.connection.parent.diagramScene.addItem(self.firstLine) self.connection.parent.diagramScene.addItem(self.secondLine) self.logger.debug("inited") self._isDraggingInProgress = True def _createSegment(self, startNode, endNode) -> "SegmentItemBase": raise NotImplementedError() def isVertical(self): return self.line().p1().x() == self.line().p2().x() def isHorizontal(self): return self.line().p1().y() == self.line().p2().y() def dragInMode0(self, newPos): p1 = self.line().p1() p2 = self.line().p2() if len(self.scene().items(newPos)) == 0: self.firstChild.setLine(p1.x(), p1.y(), newPos.x(), newPos.y()) self.secondChild.setLine(newPos.x(), newPos.y(), p2.x(), p2.y()) self.cornerChild.setPos(newPos) self.firstChild.updateGrad() self.secondChild.updateGrad() # Bring corner to front self.cornerChild.setZValue(100) self.firstChild.setZValue(1) self.secondChild.setZValue(1) self.firstChild.setVisible(True) self.secondChild.setVisible(True) self.cornerChild.setVisible(True) def _dragInMode1(self, b, newPos): self.logger.debug("after inited") if b: self.thirdCorner.setPos(newPos.x() - 10, newPos.y()) self.secondCorner.setPos(newPos.x() - 10, self.connection.fromPort.scenePos().y()) self.thirdCorner.node.nextN().parent.setY(newPos.y()) self.firstLine.setLine( self.secondCorner.scenePos().x(), self.secondCorner.scenePos().y(), self.thirdCorner.scenePos().x(), newPos.y(), ) self.secondLine.setLine( self.thirdCorner.scenePos().x(), self.thirdCorner.scenePos().y(), self.thirdCorner.node.nextN().parent.scenePos().x(), self.thirdCorner.node.nextN().parent.scenePos().y(), ) self.setLine( self.startNode.parent.fromPort.scenePos().x(), self.startNode.parent.fromPort.scenePos().y(), self.secondCorner.scenePos().x(), self.secondCorner.scenePos().y(), ) self.secondCorner.setZValue(100) self.thirdCorner.setZValue(100) self.firstLine.setZValue(1) self.secondLine.setZValue(1) self.secondCorner.setVisible(True) self.thirdCorner.setVisible(True) self.firstLine.setVisible(True) self.secondLine.setVisible(True) else: self.secondCorner.setPos(newPos.x() + 10, newPos.y()) self.thirdCorner.setPos(newPos.x() + 10, self.connection.toPort.scenePos().y()) self.secondCorner.node.prevN().parent.setY(newPos.y()) self.firstLine.setLine( self.secondCorner.node.prevN().parent.scenePos().x(), newPos.y(), self.secondCorner.scenePos().x(), newPos.y(), ) self.secondLine.setLine( self.secondCorner.scenePos().x(), self.secondCorner.scenePos().y(), self.thirdCorner.scenePos().x(), self.thirdCorner.scenePos().y(), ) self.setLine( self.thirdCorner.scenePos().x(), self.thirdCorner.scenePos().y(), self.endNode.parent.toPort.scenePos().x(), self.endNode.parent.toPort.scenePos().y(), ) self.secondCorner.setZValue(100) self.thirdCorner.setZValue(100) self.firstLine.setZValue(1) self.secondLine.setZValue(1) self.secondCorner.setVisible(True) self.thirdCorner.setVisible(True) self.firstLine.setVisible(True) self.secondLine.setVisible(True) def renameConn(self): self.scene().parent().showSegmentDlg(self) def printItemsAt(self): self.logger.debug("Items at startnode are %s", str(self.scene().items(self.line().p1()))) self.logger.debug("Items at endnode are %s", str(self.scene().items(self.line().p2()))) for s in self.connection.segments: self.logger.debug( "Segment in list is %s has startnode %s endnode %s", str(s), str(s.startNode.parent), str(s.endNode.parent), ) def contextMenuEvent(self, event): menu = self._getContextMenu() menu.exec(event.screenPos()) def _getContextMenu(self) -> QMenu: menu = QMenu() a1 = menu.addAction("Rename...") a1.triggered.connect(self.renameConn) a2 = menu.addAction("Delete this connection") a2.triggered.connect( self.connection.createDeleteUndoCommandAndAddToStack) a3 = menu.addAction("Invert this connection") a3.triggered.connect(self.connection.invertConnection) a4 = menu.addAction("Toggle name") a4.triggered.connect(self.connection.toggleLabelVisible) a5 = menu.addAction("Toggle mass flow") a5.triggered.connect(self.connection.toggleMassFlowLabelVisible) return menu def setLabelVisible(self, isVisible: bool) -> None: self.label.setVisible(isVisible) def toggleLabelVisible(self) -> None: wasVisible = self.label.isVisible() self.setLabelVisible(not wasVisible) def setMassFlowLabelVisible(self, isVisible: bool) -> None: self.labelMass.setVisible(isVisible) def toggleMassFlowLabelVisible(self) -> None: wasVisible = self.labelMass.isVisible() self.setMassFlowLabelVisible(not wasVisible) def setSelect(self, isSelected: bool) -> None: raise NotImplementedError() @staticmethod def _createSelectPen() -> QPen: color = QColor(125, 242, 189) width = 4 selectPen = QPen(color, width) return selectPen def setColorAndWidthAccordingToMassflow(self, color, width): raise NotImplementedError()