class SymbolItem(QGraphicsItemGroup): _cache = {} def __init__(self, symbol, parent=None, cache_dir=None): super().__init__(parent) self._symbol = symbol try: renderer, svg_symbol = self._cache[self._symbol] except KeyError: path = os.path.join(DATA_DIR, 'symbols', '%s.svg' % self._symbol) svg_symbol = SvgSymbol(path, cache_dir=cache_dir) renderer = QSvgRenderer(svg_symbol.get_xml()) self._cache[self._symbol] = (renderer, svg_symbol) self.grp = QGraphicsItemGroup(self) origin_x, origin_y = svg_symbol.get_origin() self.grp.setPos(-origin_x, -origin_y) self.body = QGraphicsSvgItem(self.grp) self.body.setSharedRenderer(renderer)
class CursorGraphicsView(QGraphicsView): coords = None def __init__(self, *__args): super().__init__(*__args) self.num = -1 self.scale = QPointF(1, 1) def mouseReleaseEvent(self, event): if not self.scene(): return # the event's position is relative to the CursorGraphicsView, but we need it relative to the image pos_x = event.x() - (self.width() - self.scene().width() - 1) / 2 pos_y = event.y() - (self.height() - self.scene().height() - 1) / 2 # if the position of the click event is on the image, we convert the pixel position on screen to a voxel # position in the image, and then update all CursorGraphicsViews to show the corresponding slice as well as a # cursor pointing at the clicked voxel if pos_x >= 0 and pos_x < self.scene().width( ) and pos_y >= 0 and pos_y < self.scene().height(): slider_values = [ floor(pos_x / self.scale.x()), floor((self.scene().height() - pos_y) / self.scale.y()) ] CursorGraphicsView.coords = self.get_coords(slider_values) for i, slider in enumerate(self.sliders): slider.setValue(self.coords[i]) for viewer in self.viewers: viewer.show_cursor(self.coords) def set_num(self, num: int): self.num = num def set_scale(self, scale: float): self.scale = scale def set_viewers(self, viewers): self.viewers = viewers def set_sliders(self, sliders): self.sliders = sliders def get_coords(self, pos): return [(s.value() if i == self.num else pos.pop(0)) for i, s in enumerate(self.sliders)] def make_cursor(self): pen = QPen(QColor(0, 255, 0)) h_line = QGraphicsLineItem(-10, 0, 10, 0) v_line = QGraphicsLineItem(0, -10, 0, 10) pen.setWidth(1) h_line.setPen(pen) v_line.setPen(pen) self.point_cursor = QGraphicsItemGroup() self.point_cursor.addToGroup(h_line) self.point_cursor.addToGroup(v_line) self.point_cursor.setZValue(1) self.point_cursor.setVisible(False) self.scene().addItem(self.point_cursor) def show_cursor(self, values): pos = values.copy() pos.pop(self.num) self.point_cursor.setVisible(True) self.point_cursor.setPos( pos[0] * self.scale.x(), self.scene().height() - pos[1] * self.scale.y())
def createGraphics(self): """ Create the graphical representation of the FMU's inputs and outputs """ def variableColor(variable): if variable.type == 'Real': return QColor.fromRgb(0, 0, 127) elif variable.type in ['Integer', 'Enumeration']: return QColor.fromRgb(255, 127, 0) elif variable.type == 'Boolean': return QColor.fromRgb(255, 0, 255) elif variable.type == 'String': return QColor.fromRgb(0, 128, 0) else: return QColor.fromRgb(0, 0, 0) inputVariables = [] outputVariables = [] maxInputLabelWidth = 0 maxOutputLabelWidth = 0 textItem = QGraphicsTextItem() fontMetrics = QFontMetricsF(textItem.font()) for variable in self.modelDescription.modelVariables: if variable.causality == 'input': inputVariables.append(variable) elif variable.causality == 'output': outputVariables.append(variable) for variable in inputVariables: maxInputLabelWidth = max(maxInputLabelWidth, fontMetrics.width(variable.name)) for variable in outputVariables: maxOutputLabelWidth = max(maxOutputLabelWidth, fontMetrics.width(variable.name)) from math import floor scene = QGraphicsScene() self.ui.graphicsView.setScene(scene) group = QGraphicsItemGroup() scene.addItem(group) group.setPos(200.5, -50.5) lh = 15 # line height w = max(150., maxInputLabelWidth + maxOutputLabelWidth + 20) h = max(50., 10 + lh * max(len(inputVariables), len(outputVariables))) block = QGraphicsRectItem(0, 0, w, h, group) block.setPen(QColor.fromRgb(0, 0, 255)) pen = QPen() pen.setWidthF(1) font = QFont() font.setPixelSize(10) # inputs y = floor((h - len(inputVariables) * lh) / 2 - 2) for variable in inputVariables: text = QGraphicsTextItem(variable.name, group) text.setDefaultTextColor(QColor.fromRgb(0, 0, 255)) text.setFont(font) text.setX(3) text.setY(y) polygon = QPolygonF([ QPointF(-13.5, y + 4), QPointF(1, y + 11), QPointF(-13.5, y + 18) ]) path = QPainterPath() path.addPolygon(polygon) path.closeSubpath() contour = QGraphicsPathItem(path, group) contour.setPen(QPen(Qt.NoPen)) contour.setBrush(variableColor(variable)) y += lh # outputs y = floor((h - len(outputVariables) * lh) / 2 - 2) for variable in outputVariables: text = QGraphicsTextItem(variable.name, group) text.setDefaultTextColor(QColor.fromRgb(0, 0, 255)) text.setFont(font) text.setX(w - 3 - text.boundingRect().width()) text.setY(y) polygon = QPolygonF([ QPointF(w, y + 0 + 7.5), QPointF(w + 7, y + 3.5 + 7.5), QPointF(w, y + 7 + 7.5) ]) path = QPainterPath() path.addPolygon(polygon) path.closeSubpath() contour = QGraphicsPathItem(path, group) pen = QPen() pen.setColor(variableColor(variable)) pen.setJoinStyle(Qt.MiterJoin) contour.setPen(pen) y += lh
class NodeItem(QGraphicsItem): def __init__ (self, nodeobj, parent=None, view=None, state=1): super().__init__() self.edge = None self.linkIDs = None self.children = None self.childpos = None self.nodeobj = nodeobj self.style = FlGlob.mainwindow.style self.view = weakref.proxy(view) self.refID = parent.realid() if parent is not None else None self.state = state self.setrank(parent) self.setCursor(Qt.ArrowCursor) self.yoffset = 0 self.graphicsetup() self.setstate(state) def id (self): return (self.refID, self.nodeobj.ID) def realid (self): return self.nodeobj.ID def childlist (self, generate=False): ID = self.nodeobj.ID itemtable = self.view.itemtable if self.state == 1 and ID in itemtable and not self.iscollapsed(): if self.children and self.nodeobj.linkIDs == self.linkIDs and None not in [c() for c in self.children]: ret = self.children else: children = [] for child in self.nodeobj.linkIDs: if child in itemtable[ID]: item = itemtable[ID][child] else: continue children.append(weakref.ref(item)) self.linkIDs = self.nodeobj.linkIDs.copy() self.children = children ret = children else: ret = [] if generate: x = self.x() y = self.y() self.childpos = [] for target in ret: t = target() self.childpos.append((t.x()+t.boundingRect().left()-self.style.activemargin-x, t.y()-y)) if self.edge: if self.childpos != self.edge.childpos: self.edge.prepareGeometryChange() self.edge.sourceright = self.boundingRect().right() self.edge.update(self.edge.boundingRect()) return ret def setedge (self, edge): self.edge = edge edge.setX(self.x()) def setactive (self, active): if active: self.activebox.show() self.mainbox.setBrush(QBrush(self.altcolor)) else: self.activebox.hide() self.mainbox.setBrush(QBrush(self.maincolor)) def setselected (self, selected): if selected: self.selectbox.show() else: self.selectbox.hide() def setstate (self, state): self.state = state if state == 1: # normal self.show() self.graphgroup.setOpacity(1) self.shadowbox.show() elif state == 0: # ghost self.show() self.graphgroup.setOpacity(0.7) self.shadowbox.hide() elif state == -1: # hidden self.hide() def setplaymode (self, playmode): if playmode: self.setOpacity(0.5) else: self.setOpacity(1) def setY (self, y): parent = self.view.itembyID(self.refID) y += self.getyoffset() if self.edge is not None: self.edge.setY(y) super().setY(y) def setrank (self, parent): if parent is None: return if self.issubnode(): x = parent.x() self.setX(x) else: x = parent.x()+self.style.rankwidth self.setX(x) self.nudgechildren() if self.edge is not None: self.edge.setX(x) def nudgechildren (self): for child in self.childlist(): child().setrank(self) def getyoffset (self): if self.nodeobj.nodebank == -1: return self.yoffset else: return self.view.itembyID(self.refID).getyoffset() + self.yoffset def hide (self): super().hide() if self.edge: self.edge.hide() def show (self): super().show() if self.edge: self.edge.show() def issubnode (self): return self.nodeobj.nodebank is not -1 def isghost (self): return not self.state def realnode (self): return self.view.itembyID(self.nodeobj.ID) def isactive (self): return self.view.activenode is self def isselected (self): return self.view.selectednode is self def iscollapsed (self): return self.id() in self.view.collapsednodes def y_top (self): return self.y() - self.boundingRect().height()//2 def y_bottom (self): return self.y() + self.boundingRect().height()//2 def bulkshift (self, children, diff): self.setY(self.y() + diff) if children is None: children = [c() for c in self.childlist()] for child in children: child.bulkshift(None, diff) def treeposition (self, ranks=None): if ranks is None: ranks = dict() localranks = dict() children = [c() for c in self.childlist()] for child in children: localranks = child.treeposition(localranks) rank = self.x() // self.style.rankwidth if children: top = children[0].y_top() bottom = children[-1].y_bottom() self.setY((top+bottom)//2) localranks[rank] = [self.y_top, self.y_bottom] streeshift = None for r in localranks: if r in ranks: rankshift = ranks[r][1]() + self.style.rowgap - localranks[r][0]() if streeshift is None or rankshift > streeshift: streeshift = rankshift ranks[r][1] = localranks[r][1] else: ranks[r] = localranks[r] if streeshift: self.bulkshift(children, streeshift) return ranks def siblings (self): if self.refID is None: return None parent = self.view.nodecontainer.nodes[self.refID] if self.issubnode(): return parent.subnodes else: return parent.linkIDs def siblingabove (self): sibs = self.siblings() if sibs is None or self.nodeobj.ID not in sibs: return None myindex = sibs.index(self.nodeobj.ID) if myindex: sibID = (self.refID, sibs[myindex-1]) return self.view.itembyfullID(sibID) else: return None def siblingbelow (self): sibs = self.siblings() if sibs is None or self.nodeobj.ID not in sibs: return None myindex = sibs.index(self.nodeobj.ID) if len(sibs) > myindex+1: sibID = (self.refID, sibs[myindex+1]) return self.view.itembyfullID(sibID) else: return None def subtreesize (self, depth=-1): """Find vertical extents of a subtree. Returns min/max y coordinates up to given depth (negative depth means whole subtree).""" # calculate child positions for EgdeItem only once when calculating scenerect if depth<0: generate = True else: generate = False children = [c() for c in self.childlist(generate=generate)] maxdepth = abs(depth) if children and depth: nextdepth = depth-1 ymin = self.y_top() ymax = self.y_bottom() for child in children: top, bottom, depth = child.subtreesize(nextdepth) ymin = min(ymin, top) ymax = max(ymax, bottom) maxdepth = max(maxdepth, depth) else: ymin = self.y_top() ymax = self.y_bottom() return ymin, ymax, maxdepth def boundingRect (self): return self.rect def paint (self, painter, style, widget): pass def pixmap (self, path): return QPixmap(path).scaledToWidth(self.style.boldheight, Qt.SmoothTransformation) def graphicsetup (self): lightbrush = QBrush(FlPalette.light) mainbrush = QBrush(self.maincolor) altbrush = QBrush(self.altcolor) nopen = QPen(0) viewport = self.view.viewport() self.graphgroup = QGraphicsItemGroup(self) self.fggroup = QGraphicsItemGroup(self) self.shadowbox = QGraphicsRectItem(self) self.shadowbox.setCacheMode(QGraphicsItem.DeviceCoordinateCache) self.shadowbox.setBrush(FlPalette.dark) self.shadowbox.setPen(nopen) self.shadowbox.setPos(*(self.style.shadowoffset,)*2) self.graphgroup.addToGroup(self.shadowbox) self.activebox = QGraphicsRectItem(self) self.activebox.setCacheMode(QGraphicsItem.DeviceCoordinateCache) activepen = QPen(self.maincolor, self.style.selectmargin, join=Qt.MiterJoin) self.activebox.setPen(activepen) self.activebox.hide() self.graphgroup.addToGroup(self.activebox) self.selectbox = QGraphicsRectItem(self) self.selectbox.setCacheMode(QGraphicsItem.DeviceCoordinateCache) selectpen = QPen(FlPalette.light, self.style.selectmargin, join=Qt.MiterJoin) self.selectbox.setPen(selectpen) self.selectbox.hide() self.graphgroup.addToGroup(self.selectbox) self.mainbox = QGraphicsRectItem(self) self.mainbox.setCacheMode(QGraphicsItem.DeviceCoordinateCache) self.mainbox.setBrush(mainbrush) self.mainbox.setPen(nopen) self.graphgroup.addToGroup(self.mainbox) self.nodelabel = QGraphicsSimpleTextItemCond(self, viewport) self.nodelabel.setBrush(lightbrush) self.nodelabel.setFont(self.style.boldfont) self.nodelabel.setText(self.label % self.realid()) self.nodelabel.setPos(self.style.itemmargin, self.style.itemmargin) self.fggroup.addToGroup(self.nodelabel) self.icon = self.pixmap("images/blank.png") self.iwidth = self.icon.width() self.iconx = self.style.nodetextwidth self.condicon = QGraphicsPixmapItemCond(self.icon, self, viewport) self.condicon.setPos(self.iconx-self.iwidth, self.style.itemmargin) self.iconx = self.condicon.x() self.fggroup.addToGroup(self.condicon) self.randicon = QGraphicsPixmapItemCond(self.icon, self, viewport) self.randicon.setPos(self.iconx-self.style.itemmargin-self.iwidth, self.style.itemmargin) self.iconx = self.randicon.x() self.fggroup.addToGroup(self.randicon) self.exiticon = QGraphicsPixmapItemCond(self.icon, self, viewport) self.exiticon.setPos(self.iconx-self.style.itemmargin-self.iwidth, self.style.itemmargin) self.iconx = self.exiticon.x() self.fggroup.addToGroup(self.exiticon) self.entericon = QGraphicsPixmapItemCond(self.icon, self, viewport) self.entericon.setPos(self.iconx-self.style.itemmargin-self.iwidth, self.style.itemmargin) self.iconx = self.entericon.x() self.fggroup.addToGroup(self.entericon) self.persisticon = QGraphicsPixmapItemCond(self.icon, self, viewport) self.persisticon.setPos(self.iconx-self.style.itemmargin-self.iwidth, self.style.itemmargin) self.iconx = self.persisticon.x() self.fggroup.addToGroup(self.persisticon) self.comment = QGraphicsTextItemCond(self, viewport) self.comment.setTextWidth(self.style.nodetextwidth) self.comment.setDefaultTextColor(FlPalette.light) self.comment.setPos(0, self.nodelabel.y()+self.nodelabel.boundingRect().height()+self.style.itemmargin) self.fggroup.addToGroup(self.comment) self.graphgroup.addToGroup(self.fggroup) self.view.nodedocs[self.realid()]["comment"].contentsChanged.connect(self.updatecomment) self.updatecondition() self.updateenterscripts() self.updateexitscripts() self.updaterandweight() self.updatepersistence() # Never call updatelayout() from here (or any inheritable reimplementation)! def collapse (self, collapse): for item in self.fggroup.childItems(): if item is not self.nodelabel: if collapse: item.hide() else: item.show() self.updatelayout() def updatecondition (self): icons = {True: "key", False: "blank"} pixmap = self.pixmap("images/%s.png" % icons[self.nodeobj.hascond()]) self.condicon.setPixmap(pixmap) if self.nodeobj.hascond(): self.condicon.setToolTip("Condition") else: self.condicon.setToolTip("") def updateenterscripts (self): icons = {True: "script-enter", False: "blank"} pixmap = self.pixmap("images/%s.png" % icons[self.nodeobj.hasenterscripts()]) self.entericon.setPixmap(pixmap) if self.nodeobj.hasenterscripts(): self.entericon.setToolTip("Enter Scripts") else: self.entericon.setToolTip("") def updateexitscripts (self): icons = {True: "script-exit", False: "blank"} pixmap = self.pixmap("images/%s.png" % icons[self.nodeobj.hasexitscripts()]) self.exiticon.setPixmap(pixmap) if self.nodeobj.hasexitscripts(): self.exiticon.setToolTip("Exit Scripts") else: self.exiticon.setToolTip("") def updaterandweight (self): icons = {True: "dice", False: "blank"} pixmap = self.pixmap("images/%s.png" % icons[bool(self.nodeobj.randweight)]) self.randicon.setPixmap(pixmap) if self.nodeobj.randweight: self.randicon.setToolTip("Random Weight: %s" % self.nodeobj.randweight) else: self.randicon.setToolTip("") def updatepersistence (self): icons = {"Mark": "mark", "OncePerConv": "once", "OnceEver": "onceever", "": "blank"} pixmap = self.pixmap("images/%s.png" % icons[self.nodeobj.persistence]) self.persisticon.setPixmap(pixmap) if self.nodeobj.persistence: self.persisticon.setToolTip("Persistence: %s" % self.nodeobj.persistence) else: self.persisticon.setToolTip("") def updatecomment (self): self.fggroup.removeFromGroup(self.comment) contents = self.view.nodedocs[self.realid()]["comment"].toPlainText() if not contents: self.comment.hide() else: self.comment.show() self.comment.setPlainText(contents) self.fggroup.addToGroup(self.comment) self.updatelayout() def updatelayout (self): if self.iscollapsed(): rect = self.nodelabel.mapRectToParent(self.nodelabel.boundingRect()) else: rect = self.fggroup.childrenBoundingRect() mainrect = rect.marginsAdded(self.style.nodemargins) self.mainbox.setRect(mainrect) self.shadowbox.setRect(mainrect) self.selectbox.setRect(mainrect.marginsAdded(self.style.selectmargins)) activerect = mainrect.marginsAdded(self.style.activemargins) self.activebox.setRect(activerect) self.graphgroup.setPos(-activerect.width()//2-activerect.x(), -activerect.height()//2-activerect.y()) self.prepareGeometryChange() self.rect = self.graphgroup.mapRectToParent(mainrect) self.view.updatelayout() def mouseDoubleClickEvent (self, event): super().mouseDoubleClickEvent(event) event.accept() if event.button() == Qt.LeftButton: self.view.setactivenode(self) def mousePressEvent (self, event): super().mousePressEvent(event) if event.button() & (Qt.LeftButton | Qt.RightButton) : self.view.setselectednode(self) event.accept() def __repr__ (self): return "<%s %s>" % (type(self).__name__, self.id())
class Threat(Node): """ This class handles the gui for a threat node """ def __init__(self, node, parent, x=0, y=0): """ Constructor for the threat node. 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 x: x-position of the node :param y: y-position of the node """ self.threatBox = None self.counterBox = None self.threatBoxText = None self.counterBoxText = None super().__init__(node, parent, Configuration.colors['threat']['node']['background'], Configuration.colors['threat']['node']['border'], Configuration.colors['threat']['node']['font'], x, y, 91) def printFooter(self, background, border, text): """ Prints the footer for the threat node The footer contains two columns where the conjunction will start from :param background: background color of the node :param border: border color for the node :param text: text color for the node """ self.threatBoxText = QGraphicsTextItem() self.threatBoxText.setFont(Configuration.font) self.threatBoxText.setDefaultTextColor(QColor(text)) self.threatBoxText.setPlainText('T') self.counterBoxText = QGraphicsTextItem() self.counterBoxText.setFont(Configuration.font) self.counterBoxText.setDefaultTextColor(QColor(text)) self.counterBoxText.setPlainText('C') self.threatBox = QGraphicsRectItem() self.counterBox = QGraphicsRectItem() self.threatBox.setBrush(QBrush(QColor(background))) self.counterBox.setBrush(QBrush(QColor(background))) self.threatBox.setPen(QPen(QColor(border), 2)) self.counterBox.setPen(QPen(QColor(border), 2)) self.footerGroup = QGraphicsItemGroup() self.footerGroup.addToGroup(self.threatBox) self.footerGroup.addToGroup(self.counterBox) self.footerGroup.addToGroup(self.threatBoxText) self.footerGroup.addToGroup(self.counterBoxText) self.threatBox.setRect(0, 0, 100, 20) self.counterBox.setRect(100, 0, 100, 20) self.threatBoxText.setPos(40, 0) self.counterBoxText.setPos(140, 0) self.footerGroup.setPos(self.x(), self.y() + self.headerHeight + self.attributesHeight) self.addToGroup(self.footerGroup) def redraw(self): """ Redraws the node with the colors set in the options menu """ super().redrawOptions(Configuration.colors['threat']['node']['background'], Configuration.colors['threat']['node']['border'], Configuration.colors['threat']['node']['font'])
class CursorGraphicsView(QGraphicsView): def __init__(self, *__args): super().__init__(*__args) self.num = -1 self.scale = (1, 1) ## @brief Mouse commands implementation # @param event Event containing the click action def mouseReleaseEvent(self, event): if not self.scene(): return # the event's position is relative to the CursorGraphicsView, but we need it relative to the image point = self.mapToScene(event.pos()) pos_x = point.x() pos_y = point.y() # if the position of the click event is on the image, we convert the pixel position on screen to a voxel # position in the image, and then update all CursorGraphicsViews to show the corresponding slice as well as a # cursor pointing at the clicked voxel if pos_x >= 0 and pos_x < self.scene().width( ) and pos_y >= 0 and pos_y < self.scene().height(): slider_values = [ floor(pos_x / self.scale[0]), floor((self.scene().height() - pos_y) / self.scale[1]) ] coords = self.get_coords(slider_values) for i, slider in enumerate(self.sliders): slider.setValue(coords[i]) for viewer in self.viewers: viewer.show_cursor(coords) ## @brief def set_num(self, num: int): self.num = num ## @brief Scale image of this viewer # @param scale Scaling factor def set_scale(self, scale: float): self.scale = scale ## @brief Set a reference to all viewers # @param viewers List of viewers def set_viewers(self, viewers): self.viewers = viewers ## @brief Set a reference to all slice sliders # @param sliders List of sliders def set_sliders(self, sliders): self.sliders = sliders ## @brief def set_fd_data(self, fd_data): self.fd_data = fd_data ## @brief Define the cycle slider associated to this viewer # @param cycle_slider Cycle slider def set_cycle_slider(self, cycle_slider): self.cycle_slider = cycle_slider ## @brief def get_coords(self, pos): return [(s.value() if i == self.num else pos.pop(0)) for i, s in enumerate(self.sliders)] ## @brief Create the cursor def make_cursor(self): pen = QPen(QColor(0, 255, 0)) h_line = QGraphicsLineItem(-10, 0, 10, 0) v_line = QGraphicsLineItem(0, -10, 0, 10) pen.setWidth(1) h_line.setPen(pen) v_line.setPen(pen) self.point_cursor = QGraphicsItemGroup() self.point_cursor.addToGroup(h_line) self.point_cursor.addToGroup(v_line) self.point_cursor.setZValue(1) self.point_cursor.setVisible(False) self.scene().addItem(self.point_cursor) ## @brief Draw the cursor # @param values def show_cursor(self, values): pos = values.copy() pos.pop(self.num) self.point_cursor.setVisible(True) self.point_cursor.setPos( pos[0] * self.scale[0], self.scene().height() - pos[1] * self.scale[1])