def _updateLabel(self, isLeft): """Called by updatePositionAndAppearance during init. Updates drawing and position of the label. Args: isLeft (TYPE): Description """ lbl = self._label if self._idx is not None: if lbl is None: bw = _BASE_WIDTH num = self._partner_virtual_helix_id_num tbr = _FM.tightBoundingRect(str(num)) half_label_h = tbr.height() / 2.0 half_label_w = tbr.width() / 2.0 # determine x and y positions labelX = bw / 2.0 - half_label_w if self.is_forward: labelY = -0.25 * half_label_h - 0.5 - 0.5 * bw else: labelY = 2 * half_label_h + 0.5 + 0.5 * bw # adjust x for left vs right labelXoffset = 0.25 * bw if isLeft else -0.25 * bw labelX += labelXoffset # adjust x for numeral 1 if num == 1: labelX -= half_label_w / 2.0 # create text item lbl = QGraphicsSimpleTextItem(str(num), self) lbl.setPos(labelX, labelY) lbl.setBrush(_ENAB_BRUSH) lbl.setFont(_toHelixNumFont) self._label = lbl # end if lbl.setText(str(self._partner_virtual_helix_id_num))
def _updateLabel(self, isLeft): """ Called by updatePositionAndAppearance during init, or later by updateConnectivity. Updates drawing and position of the label. """ lbl = self._label if self._idx != None: if lbl == None: bw = _BASE_WIDTH num = self._partner_virtual_helix.number() tbr = _FM.tightBoundingRect(str(num)) half_label_h = tbr.height() / 2.0 half_label_w = tbr.width() / 2.0 # determine x and y positions labelX = bw / 2.0 - half_label_w if self._is_on_top: labelY = -0.25 * half_label_h - 0.5 - 0.5 * bw else: labelY = 2 * half_label_h + 0.5 + 0.5 * bw # adjust x for left vs right labelXoffset = 0.25 * bw if isLeft else -0.25 * bw labelX += labelXoffset # adjust x for numeral 1 if num == 1: labelX -= half_label_w / 2.0 # create text item lbl = QGraphicsSimpleTextItem(str(num), self) lbl.setPos(labelX, labelY) lbl.setBrush(_ENAB_BRUSH) lbl.setFont(_toHelixNumFont) self._label = lbl # end if lbl.setText(str(self._partner_virtual_helix.number()))
def _updateLabel(self, is_left): """ Called by updatePositionAndAppearance during init, or later by updateConnectivity. Updates drawing and position of the label. """ lbl = self._label if self._idx != None: bw = _BASE_WIDTH num = self._partner_virtual_helix.number() tBR = _FM.tightBoundingRect(str(num)) half_label_h = tBR.height() / 2.0 half_label_w = tBR.width() / 2.0 # determine x and y positions label_x = bw / 2.0 - half_label_w if self._is_on_top: label_y = -0.25 * half_label_h - 0.5 - 0.5 * bw else: label_y = 2 * half_label_h + 0.5 + 0.5 * bw # adjust x for left vs right label_x_offset = 0.25 * bw if is_left else -0.25 * bw label_x += label_x_offset # adjust x for numeral 1 if num == 1: label_x -= half_label_w / 2.0 # create text item if lbl == None: lbl = QGraphicsSimpleTextItem(str(num), self) lbl.setPos(label_x, label_y) lbl.setBrush(_ENAB_BRUSH) lbl.setFont(_TO_HELIX_NUM_FONT) self._label = lbl lbl.setText(str(self._partner_virtual_helix.number())) lbl.show()
class FloatingTextWidget(QGraphicsWidget): def __init__(self, parent=None, anchor="center"): QGraphicsWidget.__init__(self, parent) assert anchor in {"center", "corner"} self.anchor = anchor self._label = QGraphicsSimpleTextItem(self) self._label.setBrush(QColor(255, 255, 255)) # Add drop shadow self._dropShadowEffect = QGraphicsDropShadowEffect() self.setGraphicsEffect(self._dropShadowEffect) self._dropShadowEffect.setOffset(0.0, 10.0) self._dropShadowEffect.setBlurRadius(8.0) self._dropShadowEffect.setColor(QColor(0, 0, 0, 50)) self._spacingConstant = 5.0 def updateLayout(self): width = self._label.boundingRect().width() height = self._label.boundingRect().height() width = self._spacingConstant + width + self._spacingConstant height = self._spacingConstant + height + self._spacingConstant self._label.setPos(self._spacingConstant, self._spacingConstant) self.resize(width, height) self.update() def paint(self, painter, option, widget): shape = QPainterPath() shape.addRoundedRect(self.rect(), 1, 1) painter.setBrush(QBrush(QColor(0, 0, 0))) painter.drawPath(shape) # painter.setPen(self._pen) # painter.drawPath(self._path) def onUpdated(self, center_position, text): self._label.setText(text) self.updateLayout() rect = self.rect() x_pos = center_position.x() y_pos = center_position.y() if self.anchor == "center": x_pos -= rect.width() / 2 y_pos -= rect.height() / 2 else: y_pos -= rect.height() self.setPos(x_pos, y_pos)
class WotNode(BaseNode): def __init__(self, nx_node, pos): """ Create node in the graph scene :param tuple nx_node: Node info :param x_y: Position of the node """ super().__init__(nx_node, pos) # color around ellipse outline_color = QColor('grey') outline_style = Qt.SolidLine outline_width = 1 if self.status_wallet: outline_color = QColor('black') outline_width = 2 if not self.status_member: outline_color = QColor('red') outline_style = Qt.SolidLine self.setPen(QPen(outline_color, outline_width, outline_style)) # text inside ellipse self.text_item = QGraphicsSimpleTextItem(self) self.text_item.setText(self.text) text_color = QColor('grey') if self.status_wallet == NodeStatus.HIGHLIGHTED: text_color = QColor('black') self.text_item.setBrush(QBrush(text_color)) # center ellipse around text self.setRect(0, 0, self.text_item.boundingRect().width() * 2, self.text_item.boundingRect().height() * 2) # set anchor to the center self.setTransform(QTransform().translate( -self.boundingRect().width() / 2.0, -self.boundingRect().height() / 2.0)) # center text in ellipse self.text_item.setPos(self.boundingRect().width() / 4.0, self.boundingRect().height() / 4.0) # create gradient inside the ellipse gradient = QRadialGradient( QPointF(0, self.boundingRect().height() / 4), self.boundingRect().width()) gradient.setColorAt(0, QColor('white')) gradient.setColorAt(1, QColor('darkgrey')) self.setBrush(QBrush(gradient)) # cursor change on hover self.setAcceptHoverEvents(True) self.setZValue(1)
class WotNode(BaseNode): def __init__(self, nx_node, pos): """ Create node in the graph scene :param tuple nx_node: Node info :param x_y: Position of the node """ super().__init__(nx_node, pos) # color around ellipse outline_color = QColor('grey') outline_style = Qt.SolidLine outline_width = 1 if self.status_wallet: outline_color = QColor('black') outline_width = 2 if not self.status_member: outline_color = QColor('red') outline_style = Qt.SolidLine self.setPen(QPen(outline_color, outline_width, outline_style)) # text inside ellipse self.text_item = QGraphicsSimpleTextItem(self) self.text_item.setText(self.text) text_color = QColor('grey') if self.status_wallet == NodeStatus.HIGHLIGHTED: text_color = QColor('black') self.text_item.setBrush(QBrush(text_color)) # center ellipse around text self.setRect( 0, 0, self.text_item.boundingRect().width() * 2, self.text_item.boundingRect().height() * 2 ) # set anchor to the center self.setTransform( QTransform().translate(-self.boundingRect().width() / 2.0, -self.boundingRect().height() / 2.0)) # center text in ellipse self.text_item.setPos(self.boundingRect().width() / 4.0, self.boundingRect().height() / 4.0) # create gradient inside the ellipse gradient = QRadialGradient(QPointF(0, self.boundingRect().height() / 4), self.boundingRect().width()) gradient.setColorAt(0, QColor('white')) gradient.setColorAt(1, QColor('darkgrey')) self.setBrush(QBrush(gradient)) # cursor change on hover self.setAcceptHoverEvents(True) self.setZValue(1)
def _draw_node(self, node: Node, x: float, y: float): """ Drawing the Node on the map. """ text_item = QGraphicsSimpleTextItem() font = text_item.font() font.setPointSize(font.pointSize() - 2) font.setPixelSize(self._font_size) text_item.setText(str(node.value())) text_item.setPos(x, y) text_item.setFont(font) self._scene.addItem(text_item)
def addRectText(x, w, parent, text="", level=0, tooltip=""): deltaH = LEVEL_HEIGHT if level else 0 r = OutlineRect(0, 0, w, parent.rect().height()-deltaH, parent, title=text) r.setPos(x, deltaH) txt = QGraphicsSimpleTextItem(text, r) f = txt.font() f.setPointSize(8) fm = QFontMetricsF(f) elidedText = fm.elidedText(text, Qt.ElideMiddle, w) txt.setFont(f) txt.setText(elidedText) txt.setPos(r.boundingRect().center() - txt.boundingRect().center()) txt.setY(0) return r
def updateGraphicsSimpleTextItem(textItem: QGraphicsSimpleTextItem, text: str = None, color: QColor = None, pos: QPointF = None, fontSize: int = 18): """ 更新普通文本QGraphicsSimpleTextItem属性 :param textItem: QGraphicsSimpleTextItem :param text: 显示文字 :param color: 文字颜色 :param pos: 文字位置 :param fontSize: 字体大小 """ if text: textItem.setText(text) if fontSize: font = QFont() font.setPointSize(fontSize) textItem.setFont(font) if color: textItem.setBrush(color) if pos: textItem.setPos(pos)
def _updateLabel(self, is_left): """Called by updatePositionAndAppearance during init. Updates drawing and position of the label. Args: is_left (TYPE): Description """ lbl = self._label if self._idx is not None: bw = _BASE_WIDTH num = self._partner_virtual_helix.idNum() tBR = _FM.tightBoundingRect(str(num)) half_label_h = tBR.height()/2.0 half_label_w = tBR.width()/2.0 # determine x and y positions label_x = bw/2.0 - half_label_w if self._is_on_top: label_y = -0.25*half_label_h - 0.5 - 0.5*bw else: label_y = 2*half_label_h + 0.5 + 0.5*bw # adjust x for left vs right label_x_offset = 0.25*bw if is_left else -0.25*bw label_x += label_x_offset # adjust x for numeral 1 if num == 1: label_x -= half_label_w/2.0 # create text item if lbl is None: lbl = QGraphicsSimpleTextItem(str(num), self) lbl.setPos(label_x, label_y) lbl.setBrush(_ENAB_BRUSH) lbl.setFont(_TO_HELIX_NUM_FONT) self._label = lbl lbl.setText(str(self._partner_virtual_helix.idNum())) lbl.show()
class DigitalClock(Desklet): def __init__(self): super().__init__() self.seconds = QGraphicsSimpleTextItem(self.root) self.time = QGraphicsSimpleTextItem(self.root) self.date = QGraphicsSimpleTextItem(self.root) def set_style(self, style): super().set_style(style) self.seconds.setBrush(style.foreground_color) self.time.setBrush(style.foreground_color) self.date.setBrush(style.foreground_color) font = QFont(style.font) font.setPixelSize(192 * 0.5) self.seconds.setFont(font) font = QFont(style.font) font.setPixelSize(192 * 0.75) self.time.setFont(font) font = QFont(style.font) font.setPixelSize(56) self.date.setFont(font) self.layout() def set_rect(self, rect): super().set_rect(rect) self.layout() def layout(self): time_fm = QFontMetrics(self.time.font()) seconds_fm = QFontMetrics(self.seconds.font()) x = self.rect.left() y = self.rect.top() self.time.setPos(x, y) self.seconds.setPos(x + time_fm.width("00:00") + 20, y + time_fm.ascent() - seconds_fm.ascent()) self.date.setPos(x, y + time_fm.ascent()) def update(self, now): date = now.strftime("%A, %d. %B %Y") time = now.strftime("%H:%M") seconds = now.strftime("%S") self.time.setText(time) self.seconds.setText(seconds) self.date.setText(date)
class BaseGraphic(object): def __init__(self, *args): super(BaseGraphic, self).__init__() self.label = args[3] self.parent = args[0] self.section_analyzer = self.parent.section_analyzer self.width = None self.height = None self.margin = None self.position = args[1], args[2] self.content_width = None self.content_height = None self.distance_pointer = None self.distance_label = None self.section_num = len(self.section_analyzer.section_list) self.section_distance = self.section_analyzer.section_distance self.length_multiplier = 100.0 self.height_multiplier = 100.0 self.line_extend = 20 self.margin = 50 self.material_legend = MaterialLegend(self) self._inited = False self.items = [] self.add_title() def add_title(self): if self.label: title = QGraphicsSimpleTextItem(self.label) title.setPos(self.position[0] + self.margin, self.position[1] + self.line_extend) self.addToGroup(title) def addToGroup(self, item): self.items.append(item) def create_distance_pointer(self): self.distance_pointer = QGraphicsLineItem() pen = QPen() pen.setWidthF(1.0) pen.setStyle(Qt.DashDotLine) color = Color.create_qcolor_from_rgb_tuple_f((1,0,0)) pen.setColor(color) self.distance_pointer.setPen(pen) self.distance_pointer.setZValue(1.0) self.addToGroup(self.distance_pointer) self.distance_label = QGraphicsSimpleTextItem() self.distance_label.setZValue(1.0) self.addToGroup(self.distance_label) def init_dimension(self): section_num = len(self.section_analyzer.section_list) section_distance = self.section_analyzer.section_distance self.content_width = section_num * section_distance * self.length_multiplier self.create_distance_pointer() self._inited = True def update_graph_size(self): if self.content_height and self.content_width: self.width = self.content_width + self.margin * 2 self.height = self.content_height + self.margin * 2 # bounding_rect.setWidth(self.width) # bounding_rect.setHeight(self.height) def set_distance_pointer(self, distance): if self._inited: x1 = self.position[0] + self.margin + distance * self.length_multiplier y1 = self.position[1] x2 = x1 y2 = y1 + self.height self.distance_pointer.setLine(x1, y1, x2, y2) self.distance_label.setText("%.2f" % distance) self.distance_label.setPos(x2,y2) pass @staticmethod def set_rect_fill(*args): if args[0] == 0: #surface color mode rect = args[1] color = args[2] qcolor = Color.create_qcolor_from_rgb_tuple_f(color) brush = QBrush(qcolor) rect.setBrush(brush) def create_legend(self): x = self.position[0] + self.width y = self.position[1] self.material_legend.create_material_legend(x, y) for item in self.material_legend.graphic_items: self.addToGroup(item)
class QtNode(QGraphicsWidget): def __init__(self, node, view): super(QtNode, self).__init__() self._spacingConstant = 5.0 self._roundness = 3 self._labelColor = QColor(255, 255, 255) self._label = QGraphicsSimpleTextItem(self) self._label.setBrush(self._labelColor) self._label.setText(node.name) self._selectedColor = QColor(255, 255, 255) self._shapePen = QPen(Qt.NoPen) self._shapePen.setColor(self._selectedColor) self._shapePen.setWidthF(1.5) self._brush = QBrush(QColor(*COLOUR_THEMES[node.node_type])) self._dropShadowEffect = QGraphicsDropShadowEffect() self.setGraphicsEffect(self._dropShadowEffect) self._dropShadowEffect.setOffset(0.0, 10.0) self._dropShadowEffect.setBlurRadius(8.0) self._dropShadowEffect.setColor(QColor(0, 0, 0, 50)) self.setFlag(QGraphicsItem.ItemIsMovable) self.setFlag(QGraphicsItem.ItemIsSelectable) self.setAcceptHoverEvents(True) self.setToolTip(node.tooltip) self._name = node.name self._node = node self._view = weakref.ref(view) self._busy = False self._socketRows = OrderedDict() # Build IO pin socket rows for pin_name in node.pin_order: if pin_name in node.inputs: pin = node.inputs[pin_name] else: pin = node.outputs[pin_name] socket_row = SocketRow(self, pin) self._socketRows[pin_name] = socket_row self.updateLayout() def node(self): return self._node def view(self): return self._view() def name(self): return self._name def setName(self, name): self._name = name self._label.setText(name) self.updateLayout() def labelColor(self): return self._labelColor def onDeleted(self): if self.isSelected(): self.setSelected(False) for socket_row in self._socketRows.values(): socket_row.onDeleted() self._socketRows.clear() def hoverEnterEvent(self, event): self.view().guiOnHoverEnter(self) def hoverLeaveEvent(self, event): self.view().guiOnHoverExit(self) def itemChange(self, change, value): if change == QGraphicsItem.ItemPositionHasChanged: for socket_row in self._socketRows.values(): socket_row.socket().updateConnectionPositions() # Move node if not self._busy: self._busy = True self.view().guiOnMoved(self) self._busy = False elif change == QGraphicsItem.ItemSelectedHasChanged: self.onSelected() return QGraphicsItem.itemChange(self, change, value) def contextMenuEvent(self, event): self.view().guiOnNodeRightClick(self, event) def onSelected(self): if self.isSelected(): self._shapePen.setStyle(Qt.SolidLine) self.view().guiOnNodeSelected(self) else: self._shapePen.setStyle(Qt.NoPen) self.view().guiOnNodeDeselected(self) def paint(self, painter, option, widget): shape = QPainterPath() shape.addRoundedRect(self.rect(), self._roundness, self._roundness) painter.setPen(self._shapePen) painter.setBrush(self._brush) painter.drawPath(shape) def setPos(self, *pos): if len(pos) == 1: point = QPointF(pos[0]) else: point = QPointF(*pos) self._lastPos = point QGraphicsWidget.setPos(self, point) def mouseDoubleClickEvent(self, event): pass def mousePressEvent(self, event): if event.button() == Qt.RightButton: pass else: QGraphicsWidget.mousePressEvent(self, event) def mouseReleaseEvent(self, event): self.view().guiOnFinishedMove() QGraphicsWidget.mouseReleaseEvent(self, event) def mouseMoveEvent(self, event): QGraphicsWidget.mouseMoveEvent(self, event) def dragMoveEvent(self, *args, **kwargs): pass def getSocketRow(self, name): return self._socketRows[name] def refreshSocketRows(self): for socket_row in self._socketRows.values(): socket_row.refresh() def updateLayout(self): label_width = self._label.boundingRect().width() width = label_width y_pos = self._label.boundingRect().bottom() + self._spacingConstant for socket_row in self._socketRows.values(): if socket_row.isVisible(): socket_row.updateLayout() socket_row.setPos(self._spacingConstant, y_pos) height = socket_row.boundingRect().height() y_pos += height attributeWidth = socket_row.boundingRect().width() if attributeWidth > width: width = attributeWidth for socket_row in self._socketRows.values(): if socket_row.isVisible(): hook = socket_row.socket() if hook.isOutput(): hook.setPos(width - hook.boundingRect().width(), hook.pos().y()) width = self._spacingConstant + width + self._spacingConstant self._label.setPos((width - label_width) / 2.0, self._spacingConstant) self.resize(width, y_pos + self._spacingConstant) self.update()
class PackageItem(UMLItem): """ Class implementing a package item. """ ItemType = "package" def __init__(self, model=None, x=0, y=0, rounded=False, noModules=False, parent=None, scene=None): """ Constructor @param model package model containing the package data (PackageModel) @param x x-coordinate (integer) @param y y-coordinate (integer) @param rounded flag indicating a rounded corner (boolean) @keyparam noModules flag indicating, that no module names should be shown (boolean) @keyparam parent reference to the parent object (QGraphicsItem) @keyparam scene reference to the scene object (QGraphicsScene) """ UMLItem.__init__(self, model, x, y, rounded, parent) self.noModules = noModules scene.addItem(self) if self.model: self.__createTexts() self.__calculateSize() def __createTexts(self): """ Private method to create the text items of the class item. """ if self.model is None: return boldFont = QFont(self.font) boldFont.setBold(True) modules = self.model.getModules() x = self.margin + self.rect().x() y = self.margin + self.rect().y() self.header = QGraphicsSimpleTextItem(self) self.header.setFont(boldFont) self.header.setText(self.model.getName()) self.header.setPos(x, y) y += self.header.boundingRect().height() + self.margin if not self.noModules: if modules: txt = "\n".join(modules) else: txt = " " self.modules = QGraphicsSimpleTextItem(self) self.modules.setFont(self.font) self.modules.setText(txt) self.modules.setPos(x, y) else: self.modules = None def __calculateSize(self): """ Private method to calculate the size of the package widget. """ if self.model is None: return width = self.header.boundingRect().width() height = self.header.boundingRect().height() if self.modules: width = max(width, self.modules.boundingRect().width()) height = height + self.modules.boundingRect().height() latchW = width / 3.0 latchH = min(15.0, latchW) self.setSize(width + 2 * self.margin, height + latchH + 2 * self.margin) x = self.margin + self.rect().x() y = self.margin + self.rect().y() + latchH self.header.setPos(x, y) y += self.header.boundingRect().height() + self.margin if self.modules: self.modules.setPos(x, y) def setModel(self, model): """ Public method to set the package model. @param model package model containing the package data (PackageModel) """ self.scene().removeItem(self.header) self.header = None if self.modules: self.scene().removeItem(self.modules) self.modules = None self.model = model self.__createTexts() self.__calculateSize() def paint(self, painter, option, widget=None): """ Public method to paint the item in local coordinates. @param painter reference to the painter object (QPainter) @param option style options (QStyleOptionGraphicsItem) @param widget optional reference to the widget painted on (QWidget) """ pen = self.pen() if (option.state & QStyle.State_Selected) == \ QStyle.State(QStyle.State_Selected): pen.setWidth(2) else: pen.setWidth(1) offsetX = self.rect().x() offsetY = self.rect().y() w = self.rect().width() latchW = w / 3.0 latchH = min(15.0, latchW) h = self.rect().height() - latchH + 1 painter.setPen(pen) painter.setBrush(self.brush()) painter.setFont(self.font) painter.drawRect(offsetX, offsetY, latchW, latchH) painter.drawRect(offsetX, offsetY + latchH, w, h) y = self.margin + self.header.boundingRect().height() + latchH painter.drawLine(offsetX, offsetY + y, offsetX + w - 1, offsetY + y) self.adjustAssociations() def buildItemDataString(self): """ Public method to build a string to persist the specific item data. This string must start with ", " and should be built like "attribute=value" with pairs separated by ", ". value must not contain ", " or newlines. @return persistence data (string) """ entries = [ "no_modules={0}".format(self.noModules), "name={0}".format(self.model.getName()), ] modules = self.model.getModules() if modules: entries.append("modules={0}".format("||".join(modules))) return ", " + ", ".join(entries) def parseItemDataString(self, version, data): """ Public method to parse the given persistence data. @param version version of the data (string) @param data persisted data to be parsed (string) @return flag indicating success (boolean) """ parts = data.split(", ") if len(parts) < 2: return False name = "" modules = [] for part in parts: key, value = part.split("=", 1) if key == "no_modules": self.external = Utilities.toBool(value.strip()) elif key == "name": name = value.strip() elif key == "modules": modules = value.strip().split("||") else: return False self.model = PackageModel(name, modules) self.__createTexts() self.__calculateSize() return True
class ModuleItem(UMLItem): """ Class implementing a module item. """ ItemType = "module" def __init__(self, model=None, x=0, y=0, rounded=False, parent=None, scene=None): """ Constructor @param model module model containing the module data (ModuleModel) @param x x-coordinate (integer) @param y y-coordinate (integer) @keyparam rounded flag indicating a rounded corner (boolean) @keyparam parent reference to the parent object (QGraphicsItem) @keyparam scene reference to the scene object (QGraphicsScene) """ UMLItem.__init__(self, model, x, y, rounded, parent) scene.addItem(self) if self.model: self.__createTexts() self.__calculateSize() def __createTexts(self): """ Private method to create the text items of the module item. """ if self.model is None: return boldFont = QFont(self.font) boldFont.setBold(True) classes = self.model.getClasses() x = self.margin + self.rect().x() y = self.margin + self.rect().y() self.header = QGraphicsSimpleTextItem(self) self.header.setFont(boldFont) self.header.setText(self.model.getName()) self.header.setPos(x, y) y += self.header.boundingRect().height() + self.margin if classes: txt = "\n".join(classes) else: txt = " " self.classes = QGraphicsSimpleTextItem(self) self.classes.setFont(self.font) self.classes.setText(txt) self.classes.setPos(x, y) def __calculateSize(self): """ Private method to calculate the size of the module item. """ if self.model is None: return width = self.header.boundingRect().width() height = self.header.boundingRect().height() if self.classes: width = max(width, self.classes.boundingRect().width()) height = height + self.classes.boundingRect().height() self.setSize(width + 2 * self.margin, height + 2 * self.margin) def setModel(self, model): """ Public method to set the module model. @param model module model containing the module data (ModuleModel) """ self.scene().removeItem(self.header) self.header = None if self.classes: self.scene().removeItem(self.classes) self.meths = None self.model = model self.__createTexts() self.__calculateSize() def paint(self, painter, option, widget=None): """ Public method to paint the item in local coordinates. @param painter reference to the painter object (QPainter) @param option style options (QStyleOptionGraphicsItem) @param widget optional reference to the widget painted on (QWidget) """ pen = self.pen() if (option.state & QStyle.State_Selected) == \ QStyle.State(QStyle.State_Selected): pen.setWidth(2) else: pen.setWidth(1) painter.setPen(pen) painter.setBrush(self.brush()) painter.setFont(self.font) offsetX = self.rect().x() offsetY = self.rect().y() w = self.rect().width() h = self.rect().height() painter.drawRect(offsetX, offsetY, w, h) y = self.margin + self.header.boundingRect().height() painter.drawLine(offsetX, offsetY + y, offsetX + w - 1, offsetY + y) self.adjustAssociations() def buildItemDataString(self): """ Public method to build a string to persist the specific item data. This string must start with ", " and should be built like "attribute=value" with pairs separated by ", ". value must not contain ", " or newlines. @return persistence data (string) """ entries = [ "name={0}".format(self.model.getName()), ] classes = self.model.getClasses() if classes: entries.append("classes={0}".format("||".join(classes))) return ", " + ", ".join(entries) def parseItemDataString(self, version, data): """ Public method to parse the given persistence data. @param version version of the data (string) @param data persisted data to be parsed (string) @return flag indicating success (boolean) """ parts = data.split(", ") if len(parts) < 1: return False name = "" classes = [] for part in parts: key, value = part.split("=", 1) if key == "name": name = value.strip() elif key == "classes": classes = value.strip().split("||") else: return False self.model = ModuleModel(name, classes) self.__createTexts() self.__calculateSize() return True
class Node(QGraphicsEllipseItem): def __init__(self, metadata, x_y): """ Create node in the graph scene :param dict metadata: Node metadata :param x_y: Position of the node """ # unpack tuple x, y = x_y super(Node, self).__init__() self.metadata = metadata self.id = metadata['id'] self.status_wallet = self.metadata['status'] & NODE_STATUS_HIGHLIGHTED self.status_member = not self.metadata['status'] & NODE_STATUS_OUT self.text = self.metadata['text'] self.setToolTip(self.metadata['tooltip']) self.arcs = [] self.menu = None self.action_sign = None self.action_transaction = None self.action_contact = None self.action_show_member = None # color around ellipse outline_color = QColor('grey') outline_style = Qt.SolidLine outline_width = 1 if self.status_wallet: outline_color = QColor('black') outline_width = 2 if not self.status_member: outline_color = QColor('red') outline_style = Qt.SolidLine self.setPen(QPen(outline_color, outline_width, outline_style)) # text inside ellipse self.text_item = QGraphicsSimpleTextItem(self) self.text_item.setText(self.text) text_color = QColor('grey') if self.status_wallet == NODE_STATUS_HIGHLIGHTED: text_color = QColor('black') self.text_item.setBrush(QBrush(text_color)) # center ellipse around text self.setRect( 0, 0, self.text_item.boundingRect().width() * 2, self.text_item.boundingRect().height() * 2 ) # set anchor to the center self.setTransform( QTransform().translate(-self.boundingRect().width() / 2.0, -self.boundingRect().height() / 2.0)) self.setPos(x, y) # center text in ellipse self.text_item.setPos(self.boundingRect().width() / 4.0, self.boundingRect().height() / 4.0) # create gradient inside the ellipse gradient = QRadialGradient(QPointF(0, self.boundingRect().height() / 4), self.boundingRect().width()) gradient.setColorAt(0, QColor('white')) gradient.setColorAt(1, QColor('darkgrey')) self.setBrush(QBrush(gradient)) # cursor change on hover self.setAcceptHoverEvents(True) self.setZValue(1) def mousePressEvent(self, event: QMouseEvent): """ Click on mouse button :param event: mouse event """ if event.button() == Qt.LeftButton: # trigger scene signal self.scene().node_clicked.emit(self.metadata) def hoverEnterEvent(self, event: QGraphicsSceneHoverEvent): """ Mouse enter on node zone :param event: scene hover event """ self.setCursor(Qt.ArrowCursor) def contextMenuEvent(self, event: QGraphicsSceneContextMenuEvent): """ Right click on node to show node menu Except on wallet node :param event: scene context menu event """ # no menu on the wallet node if self.status_wallet: return None # create node context menus self.menu = QMenu() # action show member QT_TRANSLATE_NOOP('WoT.Node', 'Informations') self.action_show_member = QAction(QCoreApplication.translate('WoT.Node', 'Informations'), self.scene()) self.menu.addAction(self.action_show_member) self.action_show_member.triggered.connect(self.member_action) # action add identity as contact QT_TRANSLATE_NOOP('WoT.Node', 'Add as contact') self.action_contact = QAction(QCoreApplication.translate('WoT.Node', 'Add as contact'), self.scene()) self.menu.addAction(self.action_contact) self.action_contact.triggered.connect(self.contact_action) # action transaction toward identity QT_TRANSLATE_NOOP('WoT.Node', 'Send money') self.action_transaction = QAction(QCoreApplication.translate('WoT.Node', 'Send money'), self.scene()) self.menu.addAction(self.action_transaction) self.action_transaction.triggered.connect(self.transaction_action) # action sign identity QT_TRANSLATE_NOOP('WoT.Node', 'Certify identity') self.action_sign = QAction(QCoreApplication.translate('WoT.Node', 'Certify identity'), self.scene()) self.menu.addAction(self.action_sign) self.action_sign.triggered.connect(self.sign_action) # run menu self.menu.exec(event.screenPos()) def add_arc(self, arc): """ Add arc to the arc list :param arc: Arc """ self.arcs.append(arc) def member_action(self): """ Transaction action to identity node """ # trigger scene signal self.scene().node_member.emit(self.metadata) def contact_action(self): """ Transaction action to identity node """ # trigger scene signal self.scene().node_contact.emit(self.metadata) def sign_action(self): """ Sign identity node """ # trigger scene signal self.scene().node_signed.emit(self.metadata) def transaction_action(self): """ Transaction action to identity node """ # trigger scene signal self.scene().node_transaction.emit(self.metadata)
class ExplorerNode(BaseNode): def __init__(self, nx_node, center_pos, nx_pos, steps, steps_max): """ Create node in the graph scene :param tuple nx_node: Node info :param center_pos: The position of the center node :param nx_pos: Position of the nodes in the graph :param int steps: The steps from the center identity :param int steps_max: The steps max of the graph """ super().__init__(nx_node, nx_pos) self.steps = steps self.steps_max = steps_max self.highlighted = False # text inside ellipse self.text_item = QGraphicsSimpleTextItem(self) self.text_item.setText(self.text) # center ellipse around text self.setRect( 0, 0, self.text_item.boundingRect().width() * 2, self.text_item.boundingRect().height() * 2 ) # set anchor to the center self.setTransform( QTransform().translate(-self.boundingRect().width() / 2.0, -self.boundingRect().height() / 2.0)) # center text in ellipse self.text_item.setPos(self.boundingRect().width() / 4.0, self.boundingRect().height() / 4.0) # cursor change on hover self.setAcceptHoverEvents(True) self.setZValue(1) # animation and moves self.timeline = None self.loading_timer = QTimer() self.loading_timer.timeout.connect(self.next_tick) self.loading_counter = 0 self._refresh_colors() self.setPos(center_pos) self.move_to(nx_pos) def _refresh_colors(self): """ Refresh elements in the node """ # color around ellipse outline_color = QColor('black') outline_style = Qt.SolidLine outline_width = 1 if self.status_wallet: outline_color = QColor('grey') outline_width = 2 if not self.status_member: outline_color = QColor('red') outline_style = Qt.SolidLine self.setPen(QPen(outline_color, outline_width, outline_style)) if self.highlighted: text_color = QColor('grey') else: text_color = QColor('black') if self.status_wallet == NodeStatus.HIGHLIGHTED: text_color = QColor('grey') self.text_item.setBrush(QBrush(text_color)) # create gradient inside the ellipse gradient = QRadialGradient(QPointF(0, self.boundingRect().height() / 4), self.boundingRect().width()) color = QColor() color.setHsv(120 - 60 / self.steps_max * self.steps, 180 + 50 / self.steps_max * self.steps, 60 + 170 / self.steps_max * self.steps) if self.highlighted: color = color.darker(200) color = color.lighter(math.fabs(math.sin(self.loading_counter / 100 * math.pi) * 100) + 100) gradient.setColorAt(0, color) gradient.setColorAt(1, color.darker(150)) self.setBrush(QBrush(gradient)) def move_to(self, nx_pos): """ Move to corresponding position :param nx_pos: :return: """ origin_x = self.x() origin_y = self.y() final_x = nx_pos[self.id][0] final_y = nx_pos[self.id][1] def frame_move(frame): value = self.timeline.valueForTime(self.timeline.currentTime()) x = origin_x + (final_x - origin_x) * value y = origin_y + (final_y - origin_y) * value self.setPos(x, y) self.scene().node_moved.emit(self.id, x, y) def timeline_ends(): self.setPos(final_x, final_y) self.timeline = None # Remember to hold the references to QTimeLine and QGraphicsItemAnimation instances. # They are not kept anywhere, even if you invoke QTimeLine.start(). self.timeline = QTimeLine(1000) self.timeline.setFrameRange(0, 100) self.timeline.frameChanged.connect(frame_move) self.timeline.finished.connect(timeline_ends) self.timeline.start() def highlight(self): """ Highlight the edge in the scene """ self.highlighted = True self._refresh_colors() self.update(self.boundingRect()) def neutralize(self): """ Neutralize the edge in the scene """ self.highlighted = False self._refresh_colors() self.update(self.boundingRect()) def start_loading_animation(self): """ Neutralize the edge in the scene """ if not self.loading_timer.isActive(): self.loading_timer.start(10) def stop_loading_animation(self): """ Neutralize the edge in the scene """ self.loading_timer.stop() self.loading_counter = 100 self._refresh_colors() self.update(self.boundingRect()) def next_tick(self): """ Next tick :return: """ self.loading_counter += 1 self.loading_counter %= 100 self._refresh_colors() self.update(self.boundingRect())
class StopWatch(Desklet): def __init__(self): super().__init__() self.timer = Timer() self.timeout_handle = None self.qtimer = QTimer() self.qtimer.timeout.connect(self.my_update) self.label = QGraphicsSimpleTextItem("Stopwatch:", self.root) self.time = QGraphicsSimpleTextItem("00:00", self.root) self.seconds = QGraphicsSimpleTextItem("00'00", self.root) def update(self): t = self.timer.get_time() time = "%02d:%02d" % (t.seconds / (60 * 60), (t.seconds % (60 * 60)) / 60) seconds = "%02d'%02d" % (t.seconds % 60, t.microseconds / 10000) self.time.setText(time) self.seconds.setText(seconds) def set_style(self, style): super().set_style(style) font = QFont(style.font) font.setPixelSize(24) self.time.setFont(font) self.label.setFont(font) font = QFont(style.font) font.setPixelSize(192 / 2) self.time.setFont(font) font = QFont(style.font) font.setPixelSize(192 / 2 * 0.6) self.seconds.setFont(font) self.label.setBrush(self.style.foreground_color) self.time.setBrush(self.style.foreground_color) self.seconds.setBrush(self.style.foreground_color) self.layout() def set_rect(self, rect): super().set_rect(rect) self.layout() def layout(self): x = self.rect.left() y = self.rect.top() fm = QFontMetrics(self.time.font()) rect = fm.boundingRect("00:00") sfm = QFontMetrics(self.seconds.font()) self.time.setPos(x, y + 20) self.seconds.setPos(x + 20 + rect.width(), y + 20 + fm.ascent() - sfm.ascent()) self.label.setPos(x, y) def my_update(self): self.update() def is_running(self): return self.timer.is_running() def start_stop_watch(self): self.timer.start_stop() if self.timer.is_running(): self.qtimer.setInterval(31) self.qtimer.start() else: self.qtimer.stop() def clear_stop_watch(self): self.timer.reset()
class Scene(QGraphicsScene): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # initialize 2D grid of cell data self.cells = { (i,j): Cell(val = CellType.empty) for i in range(Config.NUM_CELLS_X) for j in range(Config.NUM_CELLS_Y) } self.animations = dict() self.setItemIndexMethod(QGraphicsScene.NoIndex) self.draw_grid() self.init_start_goal() self.set_diagonal() self.repaint_cells() self.display_text = QGraphicsSimpleTextItem() self.display_text.setPos(5, self.height() - 60) self.display_text.setFont(QFont("Helvetica", 12, QFont.Bold)) def drawForeground(self, *args, **kwargs): if self.display_text in self.items(): self.removeItem(self.display_text) self.addItem(self.display_text) else: self.addItem(self.display_text) def set_text_log(self, s: str) -> None: self.display_text.setText(s) def init_start_goal(self) -> None: """Initialize start and goal positions""" height = (Config.NUM_CELLS_Y // 2) - 1 self.start = Vector2D(1, height) self.goal = Vector2D(Config.NUM_CELLS_X - 1, height) self.set_cell(self.start, Cell(val = CellType.start)) self.set_cell(self.goal, Cell(val = CellType.goal)) def set_diagonal(self) -> None: """Generates array of possible moves based on Config.DIAGONALS""" if Config.DIAGONALS: self.neighbor_steps = np.array([[1, 1], [1, -1], [-1, 1], [-1, -1], [-1, 0], [0, 1], [1, 0], [0, -1]]) else: self.neighbor_steps = np.array([[-1, 0], [0, 1], [1, 0], [0, -1]]) def get_unexplored_neighbors(self, cell: Vector2D) -> list: """Return neighbors to Vector2D inside the scene""" result = [] curr_cell = np.array([cell.x, cell.y]) for step in self.neighbor_steps: neighbor = step + curr_cell neighbor = Vector2D(x = neighbor[0], y = neighbor[1]) if in_bounds(neighbor) and not self.is_barrier(neighbor) and self.cell_type(neighbor) != CellType.searched: result.append(neighbor) return result def cost(self, coord: Vector2D) -> float: """Return weight of traversing cell at 'coord'""" return self.cells[coord].weight def set_cell(self, coord: Vector2D, new_cell: Cell) -> None: """Set value of cell at cells[x][y]""" self.cells[coord] = new_cell def is_barrier(self, coord: Vector2D): """Return whether cell at 'coord' is barrier""" return self.cells[coord].val == CellType.barrier def cell_type(self, coord: Vector2D): """Return type of cell at 'coord'""" return self.cells[coord].val def draw_grid(self) -> None: """Draws NUM_CELLS_X by NUM_CELLS_Y grid""" width = Config.CELL_LENGTH * Config.NUM_CELLS_X height = Config.CELL_LENGTH * Config.NUM_CELLS_Y self.setSceneRect(0, 0, width, height) pen = QPen(QColor(128, 128, 128), 1) # draw cells for x in range(0, Config.NUM_CELLS_X + 1): col = x * Config.CELL_LENGTH self.addLine(col, 0, col, height, pen) for y in range(0, Config.NUM_CELLS_Y + 1): row = y * Config.CELL_LENGTH self.addLine(0, row, width, row, pen) def resize_update(self) -> None: """Resizes grid and keeps 'start' and 'goal' cells inside of view""" self.clear() self.draw_grid() if self.start.x >= Config.NUM_CELLS_X - 1: self.start = Vector2D(Config.NUM_CELLS_X - 2, self.start.y) if self.start.y >= Config.NUM_CELLS_Y - 1: self.start = Vector2D(self.start.x, Config.NUM_CELLS_Y - 2) if self.goal.x >= Config.NUM_CELLS_X - 1: self.goal = Vector2D(Config.NUM_CELLS_X - 2, self.goal.y) if self.goal.y >= Config.NUM_CELLS_Y - 1: self.goal = Vector2D(self.goal.x, Config.NUM_CELLS_Y - 2) self.cells = { (i,j): Cell(val = CellType.empty) for i in range(Config.NUM_CELLS_X) for j in range(Config.NUM_CELLS_Y) } self.set_cell(self.start, Cell(val = CellType.start)) self.set_cell(self.goal, Cell(val = CellType.goal)) self.repaint_cells() def color_cell(self, coord: Vector2D, animate: bool = False) -> None: """Colors cell using the specified color. If 'animate' true, drawing is animated""" row = coord.y * Config.CELL_LENGTH + 1 # +1 so as to not paint over grid lines col = coord.x * Config.CELL_LENGTH + 1 color = self.cells[coord].val pen = QPen(color, 1) brush = QBrush(color) if animate: threading.Thread ( target=self.animate_rect, \ args=(col, row, Config.CELL_LENGTH - 2, Config.CELL_LENGTH - 2, pen, brush), \ daemon=True \ ).start() else: self.addRect(col, row, Config.CELL_LENGTH - 2, Config.CELL_LENGTH - 2, pen, brush) # -2 so as to not paint over grid lines def animate_rect(self, x: int, y: int, w: int, h: int, pen: QPen, brush: QBrush, duration: float = 0.125, n_steps: float = 16) -> list: """Creates RectObject that transposes from dimensions 0 x 0 to 'w' x 'h' in 'n_steps' steps over the course of 'duration' seconds""" rect = RectObject(x, y, w, h, pen, brush) rect.setScale(0.0) self.addItem(rect) time_step = duration / n_steps scale_step = 1.0 / n_steps scale = 0.0 while scale <= 1.0: rect.setScale(scale) sleep(time_step) scale += scale_step self.update() # this fixes random issue where drawing completely hangs def repaint_cells(self) -> None: """Repaints all cells""" self.set_cell(self.start, Cell(val = CellType.start)) self.set_cell(self.goal, Cell(val = CellType.goal)) for x in range(Config.NUM_CELLS_X): for y in range(Config.NUM_CELLS_Y): self.color_cell(Vector2D(x,y)) def clear_path(self) -> None: """Removes searched and explored-type cells from grid""" for x in range(Config.NUM_CELLS_X): for y in range(Config.NUM_CELLS_Y): if self.cells[Vector2D(x,y)].val == CellType.path \ or self.cells[Vector2D(x,y)].val == CellType.searched: self.set_cell(Vector2D(x,y), Cell(val = CellType.empty)) self.color_cell(Vector2D(x,y)) def draw_cell_sequence(self, cell_sequence: list, cell_type: CellType, animate: bool = False, prev_thread: threading.Thread = None): """ Draws a sequence of cells and sets them to a given type. If 'animate', animate cells. If 'prev_thread', wait for 'prev_thread' before beginning to draw cells """ if prev_thread: # supports waiting for a previous animation prev_thread.join() for current_cell in cell_sequence: if self.cell_type(current_cell) not in (CellType.goal, CellType.start): self.set_cell(current_cell, Cell(val = cell_type)) self.color_cell(current_cell, animate) sleep(0.03125)
class NodeItem(QGraphicsItem): # 面向图形界面, 负责控制显示效果 MIN_SIZE, MAX_SIZE = 10, 100 press_callback = EMPTY_FUNC release_callback = EMPTY_FUNC double_click_callback = EMPTY_FUNC move_callback = EMPTY_FUNC def __init__(self, node_id): super().__init__() self.setZValue(2) self.setAcceptHoverEvents(True) self.setFlag(QGraphicsItem.ItemIsMovable) # 可以移动 self.setFlag(QGraphicsItem.ItemSendsGeometryChanges) self.setCacheMode(QGraphicsItem.DeviceCoordinateCache) # 面向UINet 负责增添逻辑操作 self.node_id = node_id self.hover = False self.cached_size = None self.bounding_rect = QRectF() # XXX 在重绘时会被更新,重绘前可能会节点可能会被覆盖显示 # self.call_backs = CallTable() self.text_item = QGraphicsSimpleTextItem(self) self.text_item.setZValue(3) # 面向图形界面, 负责控制显示效果 self.style = { 'name': f' {node_id}', 'color': Qt.white, 'shape': 'Pie', # ('Pie', 'Rect', QPixmap) 'size': 0.5, # 0~1 的中间值 'text': '', 'text_color': Qt.black, 'show_text': False, } def type(self) -> int: return QGraphicsItem.UserType + abs(hash(NodeItem)) def boundingRect(self): return self.bounding_rect def shape(self): path = QPainterPath() path.addRect(self.bounding_rect) return path def paint(self, painter, option, widget=None) -> None: # 绘制尺寸 size = threshold(0.0, self.style['size'], 1.0) size = size * (self.MAX_SIZE - self.MIN_SIZE) + self.MIN_SIZE if size != self.cached_size: self.bounding_rect = QRectF(-size / 2, -size / 2, size, size) self.cached_size = size # 绘制图标或颜色和形状 if isinstance(self.style['shape'], QPixmap): pixmap = self.style['shape'] painter.drawPixmap(self.bounding_rect, pixmap, QRectF(pixmap.rect())) elif self.style['shape'] == 'Pie': painter.setBrush(self.style['color']) painter.drawEllipse(self.bounding_rect) # or drawRect elif self.style['shape'] == 'Rect': painter.setBrush(self.style['color']) painter.drawRect(self.bounding_rect) # or drawRect else: raise ValueError('未知shape类型', self.style['shape']) # 绘制说明 text = f"{self.style['name']}\n" if self.style['show_text'] or self.hover: text += str(self.style['text']) self.text_item.setPen(self.style['text_color']) self.text_item.setText(text) self.text_item.show() # ------------------------------------------------------------------------------------------------------------------ def itemChange(self, change, value): if change == QGraphicsItem.ItemPositionHasChanged: self.style['pos'] = self.pos() # 更新位置变化 self.move_callback(self.node_id) return super().itemChange(change, value) def mousePressEvent(self, event): self.press_callback(self.node_id) return super().mousePressEvent(event) def mouseReleaseEvent(self, event): self.release_callback(self.node_id) return super().mouseReleaseEvent(event) def mouseDoubleClickEvent(self, event): self.double_click_callback(self.node_id) return super().mouseDoubleClickEvent(event) def hoverEnterEvent(self, event): self.hover = True return super().hoverEnterEvent(event) def hoverLeaveEvent(self, event): self.hover = False return super().hoverLeaveEvent(event) # ------------------------------------------------------------------------------------------------------------------ def setStyle(self, style) -> None: for key in self.style: if key in style: self.style[key] = style[key] self.update() def checkPos(self, x, y): pos = QPointF(x, y) if self.pos() != pos: self.setPos(pos)
class View(QGraphicsView): def __init__(self, parent=None): super().__init__(QGraphicsScene(), parent) self.m_tooltip = None self.m_callouts = [] self.setDragMode(QGraphicsView.NoDrag) self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) # chart self.m_chart = QChart() self.m_chart.setMinimumSize(640, 480) self.m_chart.setTitle( "Hover the line to show callout. Click the line to make it stay" ) self.m_chart.legend().hide() series = QLineSeries() series.append(1, 3) series.append(4, 5) series.append(5, 4.5) series.append(7, 1) series.append(11, 2) self.m_chart.addSeries(series) series2 = QSplineSeries() series2.append(1.6, 1.4) series2.append(2.4, 3.5) series2.append(3.7, 2.5) series2.append(7, 4) series2.append(10, 2) self.m_chart.addSeries(series2) self.m_chart.createDefaultAxes() self.m_chart.setAcceptHoverEvents(True) self.setRenderHint(QPainter.Antialiasing) self.scene().addItem(self.m_chart) self.m_coordX = QGraphicsSimpleTextItem(self.m_chart) self.m_coordX.setPos( self.m_chart.size().width() / 2 - 50, self.m_chart.size().height() ) self.m_coordX.setText("X: ") self.m_coordY = QGraphicsSimpleTextItem(self.m_chart) self.m_coordY.setPos( self.m_chart.size().width() / 2 + 50, self.m_chart.size().height() ) self.m_coordY.setText("Y: ") series.clicked.connect(self.keepCallout) series.hovered.connect(self.tooltip) series2.clicked.connect(self.keepCallout) series2.hovered.connect(self.tooltip) self.setMouseTracking(True) def resizeEvent(self, event): if self.scene() is not None: self.scene().setSceneRect(QRectF(QRect(QPoint(0, 0), event.size()))) self.m_chart.resize(QSizeF(event.size())) self.m_coordX.setPos( self.m_chart.size().width() / 2 - 50, self.m_chart.size().height() - 20 ) self.m_coordY.setPos( self.m_chart.size().width() / 2 + 50, self.m_chart.size().height() - 20 ) for callout in self.m_callouts: callout.updateGeometry() super().resizeEvent(event) def mouseMoveEvent(self, event): self.m_coordX.setText("X: %f" % self.m_chart.mapToValue(event.pos()).x()) self.m_coordY.setText("Y: %f" % self.m_chart.mapToValue(event.pos()).y()) super().mouseMoveEvent(event) def keepCallout(self): self.m_callouts.append(self.m_tooltip) self.m_tooltip = Callout(self.m_chart) def tooltip(self, point, state): if self.m_tooltip is None: self.m_tooltip = Callout(self.m_chart) if state: self.m_tooltip.setText("X: %f \nY: %f " % (point.x(), point.y())) self.m_tooltip.setAnchor(point) self.m_tooltip.setZValue(11) self.m_tooltip.updateGeometry() self.m_tooltip.show() else: self.m_tooltip.hide()
def game_update(self): self.player1.game_update(self.keys_pressed) self.player2.game_update(self.keys_pressed) for e in self.enemies: # ako je neprijatelj zavrsio sa padanjem, brisemo ga if e.frames == -1: self.removeItem(e) self.enemies.remove(e) continue # ako je neprijatelj izabran za padanje, nastavlja da pada if e.chosen: e.collapse() # igrac 1 pogodio neprijatelja if e.x() <= self.bullet1.x() <= e.x() + 32: if e.y() <= self.bullet1.y() <= e.y() + 26: if e.powerUp: temp = randint(0, 3) if temp == 0: if self.player1.speed == 2: self.removeItem(self.player1PowerDown) self.player1.speed = 8 self.player1PowerUp.setPos(770, 130) self.addItem(self.player1PowerUp) elif temp == 1: if self.player1.speed == 8: self.removeItem(self.player1PowerUp) self.player1.speed = 2 self.player1PowerDown.setPos(770, 130) self.addItem(self.player1PowerDown) elif temp == 2: if self.player1.lives == 3: pass elif self.player1.lives == 2: self.livesPlayer1.append(LifePlayer1(760, 175)) self.player1.lives += 1 self.addItem(self.livesPlayer1[-1]) elif self.player1.lives == 1: self.livesPlayer1.append(LifePlayer1(720, 175)) self.player1.lives += 1 self.addItem((self.livesPlayer1[-1])) else: pass else: self.player1Score += 100 self.player1Scores.setText("Score: \n" + str(self.player1Score)) self.removeItem(e) self.enemies.remove(e) self.removeItem(self.bullet1) self.bullet1.setPos(SCREEN_WIDTH, SCREEN_HEIGHT) self.addItem(self.bullet1) self.player1Score += 10 self.player1Scores.setText("Score: \n" + str(self.player1Score)) continue # igrac 2 pogodio neprijatelja if e.x() <= self.bullet2.x() <= e.x() + 32: if e.y() <= self.bullet2.y() <= e.y() + 26: if e.powerUp: temp = randint(0, 3) if temp == 0: if self.player2.speed == 2: self.removeItem(self.player2PowerDown) self.player2.speed = 8 self.player2PowerUp.setPos(770, 330) self.addItem(self.player2PowerUp) elif temp == 1: if self.player2.speed == 8: self.removeItem(self.player2PowerUp) self.player2.speed = 2 self.player2PowerDown.setPos(770, 330) self.addItem(self.player2PowerDown) elif temp == 2: if self.player2.lives == 3: pass elif self.player2.lives == 2: self.livesPlayer2.append(LifePlayer2(760, 375)) self.player2.lives += 1 self.addItem(self.livesPlayer2[-1]) elif self.player2.lives == 1: self.livesPlayer2.append(LifePlayer2(720, 375)) self.player2.lives += 1 self.addItem((self.livesPlayer2[-1])) else: pass else: self.player2Score += 100 self.player2Scores.setText("Score: \n" + str(self.player2Score)) self.removeItem(e) self.enemies.remove(e) self.removeItem(self.bullet2) self.bullet2.setPos(SCREEN_WIDTH, SCREEN_HEIGHT) self.addItem(self.bullet2) self.player2Score += 10 self.player2Scores.setText("Score: \n" + str(self.player2Score)) continue # na igraca 1 se obrusio neprijatelj if self.player1.y() <= e.y() + 26 <= self.player1.y() + 53: if self.player1.x() <= e.x( ) <= self.player1.x() + 69 or self.player1.x( ) <= e.x() + 32 <= self.player1.x() + 69: if self.player1.lives > 0: if self.player1.speed == 8: self.removeItem(self.player1PowerUp) elif self.player1.speed == 2: self.removeItem(self.player1PowerDown) self.player1.speed = 4 e.frames = -1 self.player1.lives -= 1 self.removeItem(self.livesPlayer1[-1]) self.livesPlayer1.remove(self.livesPlayer1[-1]) self.player1.setPos(20, 530) if self.player1.lives <= 0: self.removeItem(self.player1) if self.player1.speed == 8: self.removeItem(self.player1PowerUp) elif self.player1.speed == 2: self.removeItem(self.player1PowerDown) if self.player2.lives > 0: self.winner = 2 # na igraca 2 se obrusio neprijatelj if self.player2.y() <= e.y() + 26 <= self.player2.y() + 53: if self.player2.x() <= e.x( ) <= self.player2.x() + 69 or self.player2.x( ) <= e.x() + 32 <= self.player2.x() + 69: if self.player2.lives > 0: if self.player2.speed == 8: self.removeItem(self.player1PowerUp) elif self.player2.speed == 2: self.removeItem(self.player2PowerDown) self.player2.speed = 4 e.frames = -1 self.player2.lives -= 1 self.removeItem(self.livesPlayer2[-1]) self.livesPlayer2.remove(self.livesPlayer2[-1]) self.player2.setPos(589, 530) if self.player2.lives <= 0: self.removeItem(self.player2) if self.player2.speed == 8: self.removeItem(self.player2PowerUp) elif self.player2.speed == 2: self.removeItem(self.player2PowerDown) if self.player1.lives > 0: self.winner = 1 # igraca 1 pogodio laser neprijatelja if self.player1.x() + 69 >= self.bulletEnemy.x() >= self.player1.x(): if self.player1.y() + 53 >= self.bulletEnemy.y() >= self.player1.y( ): if self.player1.lives > 0: if self.player1.speed == 8: self.removeItem(self.player1PowerUp) elif self.player1.speed == 2: self.removeItem(self.player1PowerDown) self.player1.speed = 4 self.bulletEnemy.active = False self.player1.lives -= 1 self.removeItem(self.livesPlayer1[-1]) self.livesPlayer1.remove(self.livesPlayer1[-1]) self.player1.setPos(20, 530) if self.player1.lives <= 0: self.removeItem(self.player1) if self.player2.lives > 0: self.winner = 2 # igraca 2 pogodio laser neprijatelja if self.player2.x() + 69 >= self.bulletEnemy.x() >= self.player2.x(): if self.player2.y() + 53 >= self.bulletEnemy.y() >= self.player2.y( ): if self.player2.lives > 0: if self.player2.speed == 8: self.removeItem(self.player2PowerUp) elif self.player2.speed == 2: self.removeItem(self.player2PowerDown) self.player2.speed = 4 self.bulletEnemy.active = False self.player2.lives -= 1 self.removeItem(self.livesPlayer2[-1]) self.livesPlayer2.remove(self.livesPlayer2[-1]) self.player2.setPos(589, 530) if self.player2.lives <= 0: self.removeItem(self.player2) if self.player1.lives > 0: self.winner = 1 # pomeranje metaka self.bullet1.game_update(self.keys_pressed, self.player1) self.bullet2.game_update(self.keys_pressed, self.player2) try: self.bulletEnemy.game_update(self.enemies[self.randomEnemyIndex]) if not self.bulletEnemy.active: self.randomEnemyIndex = randint(0, len(self.enemies)) except: self.randomEnemyIndex = randint(0, len(self.enemies)) # nasumicno biranje obrusavanja try: if randint(0, 500) == 0: self.enemies[randint(0, len(self.enemies))].chosen = True except: pass # kraj igre if self.player1.lives == 0 and self.player2.lives == 0: self.timer.stop() self.timerEnemy.stop() self.removeItem(self.bg) self.bg.setPos(-128, 0) self.addItem(self.bg) fontWinner = QFont() fontWinner.setPixelSize(40) textWinner = QGraphicsSimpleTextItem() textWinner.setFont(fontWinner) if self.winner == 1: textWinner.setText("WINNER PLAYER 1") textWinner.setBrush(QBrush(Qt.blue)) self.player1.setPos(310, 300) self.addItem(self.player1) elif self.winner == 2: textWinner.setText("WINNER PLAYER 2") textWinner.setBrush(QBrush(Qt.red)) self.player2.setPos(310, 300) self.addItem(self.player2) textWinner.setPos(180, 200) self.addItem(textWinner) # pobedjen nivo if len(self.enemies) == 0: self.timer.stop() self.timerEnemy.stop() if self.player1.lives > 0: self.removeItem(self.player1) self.removeItem(self.bullet1) if self.player2.lives > 0: self.removeItem(self.player2) self.removeItem(self.bullet2) self.removeItem(self.bulletEnemy) self.new_level()
class OverlayGraphics(QGraphicsView): def __init__(self): super(OverlayGraphics, self).__init__() self.setStyleSheet("background:transparent") # ビューの背景透明化 self.overlayScene = QGraphicsScene() self.setScene(self.overlayScene) self.overlayScene.setSceneRect(QRectF(self.rect())) self.createItem() self.luRect = QRect() self.rbRect = QRect() self.isSelected = False self.setTargetMode(False) self.hide() # 初期状態は非表示 def createItem(self): # ターゲットマーカの作成 self.target_circle = QGraphicsEllipseItem( QtCore.QRectF(-10, -10, 20, 20)) self.target_circle.setBrush(QBrush(Qt.red)) self.target_circle.setPen(QPen(Qt.black)) self.overlayScene.addItem(self.target_circle) self.setTargetMode(False) # モーダルの作成:モーダルはターゲット位置に追従する self.pop_rect = QGraphicsRectItem(QtCore.QRectF(0, 0, 100, 60), self.target_circle) self.pop_rect.setBrush(QBrush(Qt.gray)) self.pop_rect.setPen(QPen(Qt.gray)) self.pop_rect.setOpacity(0.8) # 透明度を設定 self.operate_text = QGraphicsSimpleTextItem("", self.pop_rect) self.operate_text.setScale(1.7) self.sub_operate_text = QGraphicsSimpleTextItem("", self.pop_rect) self.sub_operate_text.setScale(1.7) self.setTargetPos(400, 180, DirectionEnum.VERTICAL.value) # オーバレイヤのサイズが変わると呼び出される.シーンのサイズをビューの大きさに追従(-5 はマージン) def resizeEvent(self, event): self.overlayScene.setSceneRect( QRectF(0, 0, self.size().width() - 5, self.size().height() - 5)) # ウィンドウ左上からの位置(画面位置からウインドウ左上の位置を引く) def setTargetPos(self, x_pos, y_pos, direction): self.target_circle.setPos(QPointF(x_pos, y_pos)) self.setPopPos(direction) def setPopTextPos(self, text1, text2): lentext1 = len(text1) lentext2 = len(text2) if lentext2 == 0: self.operate_text.setPos( (self.pop_rect.rect().size().width() / 2) - (lentext1 / 2 * 14), 15) else: self.operate_text.setPos( (self.pop_rect.rect().size().width() / 2) - (lentext1 / 2 * 14), 5) self.sub_operate_text.setPos( (self.pop_rect.rect().size().width() / 2) - (lentext2 / 2 * 14), 30) def setPopPos(self, direction): if direction == DirectionEnum.VERTICAL.value: # モーダルを右に表示しきれない場合に左に表示 if self.target_circle.pos().x() > self.overlayScene.width( ) - self.pop_rect.rect().size().width() * 1.5: self.pop_rect.setPos( -self.pop_rect.rect().size().width() * 1.5, -self.pop_rect.rect().size().height() / 2) else: # 右に表示 self.pop_rect.setPos(self.pop_rect.rect().size().width() / 2, -self.pop_rect.rect().size().height() / 2) else: # モーダルを下に表示しきれない場合に上に表示 if self.target_circle.pos().y() > self.overlayScene.height( ) - self.pop_rect.rect().size().height() * 1.5: self.pop_rect.setPos( -self.pop_rect.rect().size().width() / 2, -self.pop_rect.rect().size().height() * 1.5) else: # 下に表示 self.pop_rect.setPos(-self.pop_rect.rect().size().width() / 2, self.pop_rect.rect().size().height() / 2) def setTargetMode(self, active): self.targetMode = active if active: self.target_circle.setRect(-10, -10, 20, 20) else: # ターゲットを非表示にする self.target_circle.setRect(0, 0, 0, 0) # def targetVisible(self, visible): # if visible: # self.target_circle.setRect(-10, -10, 20, 20) # else: # self.target_circle.setRect(0, 0, 0, 0) def feedbackShow(self, text1, text2, direction): if self.isSelected: self.operate_text.setText(text1) self.sub_operate_text.setText(text2) # ターゲットモードがアクティブでないとき,ターゲットマーカの位置は選択セルに依存 if not self.targetMode: if direction == DirectionEnum.HORIZON.value: x_pos = (self.luRect.left() + self.rbRect.right()) / 2 + 20 y_pos = self.rbRect.bottom( ) - self.rbRect.height() / 2 + 20 else: x_pos = self.rbRect.right() - self.rbRect.width() / 2 + 20 y_pos = (self.luRect.top() + self.rbRect.bottom()) / 2 + 20 self.setTargetPos(x_pos, y_pos, direction) self.setPopTextPos(text1, text2) self.show()
class Scene(QGraphicsScene): def __init__(self, parent=None): QGraphicsScene.__init__(self, parent) # set pritisnutih tastera self.keys_pressed = set() # pozadina self.bg = QGraphicsRectItem() self.bg.setRect(-1, -1, SCREEN_WIDTH + 2, SCREEN_HEIGHT + 2) self.bg.setBrush(QBrush(Qt.black)) self.addItem(self.bg) # brojaci za pomeranje protivnika self.left = 1 self.right = 5 # lista protivnika self.enemies = [] # nasumicni indeks protivnika koji puca self.randomEnemyIndex = randint(0, len(self.enemies)) # nasumicna lista indeksa protivnika koji ce imati specijalnu moc self.enemyPowerUps = [] # pravljenje i iscrtavanje protivnika self.threadFinished = False _thread.start_new_thread(self.draw_enemies, ()) while not self.threadFinished: continue self.threadFinished = False # igrac 1 (plavi) self.player1 = Player1() self.player1.setPos(20, 530) self.addItem(self.player1) # igrac 2 (crveni) self.player2 = Player2() self.player2.setPos(589, 530) self.addItem(self.player2) # metak igraca 1 (plavi) self.bullet1 = Bullet1(PLAYER_BULLET_X_OFFSETS, PLAYER_BULLET_Y) self.bullet1.setPos(SCREEN_WIDTH, SCREEN_HEIGHT) self.addItem(self.bullet1) # metak igraca 2 (crveni) self.bullet2 = Bullet2(PLAYER_BULLET_X_OFFSETS, PLAYER_BULLET_Y) self.bullet2.setPos(SCREEN_WIDTH, SCREEN_HEIGHT) self.addItem(self.bullet2) # pobednik self.winner = 0 # power up i power down igraca 1 (plavi) self.player1PowerUp = QGraphicsPixmapItem() self.player1PowerUp.setPixmap(QPixmap("Images/powerUp.png")) self.player1PowerDown = QGraphicsPixmapItem() self.player1PowerDown.setPixmap(QPixmap("Images/powerDown.png")) # power up i power down igraca 2 (crveni) self.player2PowerUp = QGraphicsPixmapItem() self.player2PowerUp.setPixmap(QPixmap("Images/powerUp.png")) self.player2PowerDown = QGraphicsPixmapItem() self.player2PowerDown.setPixmap(QPixmap("Images/powerDown.png")) # protivnicki metak (narandzasti) self.bulletEnemy = BulletEnemy(ENEMY_BULLET_X_OFFSET, ENEMY_BULLET_Y_OFFSET) self.bulletEnemy.setPos(SCREEN_WIDTH, SCREEN_HEIGHT) self.addItem(self.bulletEnemy) # polje za rezultate self.scoreField = QGraphicsRectItem() self.scoreField.setRect(672, -1, 128, SCREEN_HEIGHT + 2) self.scoreField.setBrush(QBrush(Qt.darkGray)) self.addItem(self.scoreField) # trenutni nivo self.level = 1 # iscrtavanje trenutnog nivoa self.levelFont = QFont() self.levelFont.setPixelSize(25) self.levelFont.setBold(1) self.levelField = QGraphicsSimpleTextItem("Level: " + str(self.level)) self.levelField.setBrush(QBrush(Qt.green)) self.levelField.setPos(677, 20) self.levelField.setFont(self.levelFont) self.addItem(self.levelField) self.playerFont = QFont() self.playerFont.setPixelSize(20) self.playerFont.setBold(1) # iscrtavanje broja zivota igraca 1 self.player1Lives = QGraphicsSimpleTextItem("Player 1: ") self.player1Lives.setBrush(QBrush(Qt.blue)) self.player1Lives.setPos(674, 130) self.player1Lives.setFont(self.playerFont) self.addItem(self.player1Lives) self.livesPlayer1 = [ LifePlayer1(680, 175), LifePlayer1(720, 175), LifePlayer1(760, 175) ] for l in self.livesPlayer1: self.addItem(l) # iscrtavanje broja zivota igraca 2 self.player2Lives = QGraphicsSimpleTextItem("Player 2: ") self.player2Lives.setBrush(QBrush(Qt.blue)) self.player2Lives.setPos(674, 330) self.player2Lives.setFont(self.playerFont) self.addItem(self.player2Lives) self.livesPlayer2 = [ LifePlayer2(680, 375), LifePlayer2(720, 375), LifePlayer2(760, 375) ] for l in self.livesPlayer2: self.addItem(l) self.scoreFont = QFont() self.scoreFont.setPixelSize(20) self.scoreFont.setBold(1) # iscrtavanje rezultata igraca 1 self.player1Score = 0 self.player1Scores = QGraphicsSimpleTextItem("Score: \n" + str(self.player1Score)) self.player1Scores.setBrush(QBrush(Qt.blue)) self.player1Scores.setPos(674, 220) self.player1Scores.setFont(self.scoreFont) self.addItem(self.player1Scores) # iscrtavanje rezultata igraca 1 self.player2Score = 0 self.player2Scores = QGraphicsSimpleTextItem("Score: \n" + str(self.player2Score)) self.player2Scores.setBrush(QBrush(Qt.blue)) self.player2Scores.setPos(674, 420) self.player2Scores.setFont(self.scoreFont) self.addItem(self.player2Scores) # tajmer za iscrtavanje slike 60 puta u sekndi (60 fps) self.timer = QBasicTimer() self.timer.start(16, self) # tajmer za pomeranje neprijatelja self.timerEnemy = QTimer() self.timerEnemy.timeout.connect(self.game_update_enemy) self.timerEnemy.start(1000) # icrtavanje scene self.view = QGraphicsView(self) self.view.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.view.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.view.show() self.view.setFixedSize(SCREEN_WIDTH, SCREEN_HEIGHT) self.setSceneRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT) self.view.setGeometry(560, 240, 0, 0) def keyPressEvent(self, event): self.keys_pressed.add(event.key()) def keyReleaseEvent(self, event): self.keys_pressed.remove(event.key()) def timerEvent(self, event): self.game_update() self.update() def game_update(self): self.player1.game_update(self.keys_pressed) self.player2.game_update(self.keys_pressed) for e in self.enemies: # ako je neprijatelj zavrsio sa padanjem, brisemo ga if e.frames == -1: self.removeItem(e) self.enemies.remove(e) continue # ako je neprijatelj izabran za padanje, nastavlja da pada if e.chosen: e.collapse() # igrac 1 pogodio neprijatelja if e.x() <= self.bullet1.x() <= e.x() + 32: if e.y() <= self.bullet1.y() <= e.y() + 26: if e.powerUp: temp = randint(0, 3) if temp == 0: if self.player1.speed == 2: self.removeItem(self.player1PowerDown) self.player1.speed = 8 self.player1PowerUp.setPos(770, 130) self.addItem(self.player1PowerUp) elif temp == 1: if self.player1.speed == 8: self.removeItem(self.player1PowerUp) self.player1.speed = 2 self.player1PowerDown.setPos(770, 130) self.addItem(self.player1PowerDown) elif temp == 2: if self.player1.lives == 3: pass elif self.player1.lives == 2: self.livesPlayer1.append(LifePlayer1(760, 175)) self.player1.lives += 1 self.addItem(self.livesPlayer1[-1]) elif self.player1.lives == 1: self.livesPlayer1.append(LifePlayer1(720, 175)) self.player1.lives += 1 self.addItem((self.livesPlayer1[-1])) else: pass else: self.player1Score += 100 self.player1Scores.setText("Score: \n" + str(self.player1Score)) self.removeItem(e) self.enemies.remove(e) self.removeItem(self.bullet1) self.bullet1.setPos(SCREEN_WIDTH, SCREEN_HEIGHT) self.addItem(self.bullet1) self.player1Score += 10 self.player1Scores.setText("Score: \n" + str(self.player1Score)) continue # igrac 2 pogodio neprijatelja if e.x() <= self.bullet2.x() <= e.x() + 32: if e.y() <= self.bullet2.y() <= e.y() + 26: if e.powerUp: temp = randint(0, 3) if temp == 0: if self.player2.speed == 2: self.removeItem(self.player2PowerDown) self.player2.speed = 8 self.player2PowerUp.setPos(770, 330) self.addItem(self.player2PowerUp) elif temp == 1: if self.player2.speed == 8: self.removeItem(self.player2PowerUp) self.player2.speed = 2 self.player2PowerDown.setPos(770, 330) self.addItem(self.player2PowerDown) elif temp == 2: if self.player2.lives == 3: pass elif self.player2.lives == 2: self.livesPlayer2.append(LifePlayer2(760, 375)) self.player2.lives += 1 self.addItem(self.livesPlayer2[-1]) elif self.player2.lives == 1: self.livesPlayer2.append(LifePlayer2(720, 375)) self.player2.lives += 1 self.addItem((self.livesPlayer2[-1])) else: pass else: self.player2Score += 100 self.player2Scores.setText("Score: \n" + str(self.player2Score)) self.removeItem(e) self.enemies.remove(e) self.removeItem(self.bullet2) self.bullet2.setPos(SCREEN_WIDTH, SCREEN_HEIGHT) self.addItem(self.bullet2) self.player2Score += 10 self.player2Scores.setText("Score: \n" + str(self.player2Score)) continue # na igraca 1 se obrusio neprijatelj if self.player1.y() <= e.y() + 26 <= self.player1.y() + 53: if self.player1.x() <= e.x( ) <= self.player1.x() + 69 or self.player1.x( ) <= e.x() + 32 <= self.player1.x() + 69: if self.player1.lives > 0: if self.player1.speed == 8: self.removeItem(self.player1PowerUp) elif self.player1.speed == 2: self.removeItem(self.player1PowerDown) self.player1.speed = 4 e.frames = -1 self.player1.lives -= 1 self.removeItem(self.livesPlayer1[-1]) self.livesPlayer1.remove(self.livesPlayer1[-1]) self.player1.setPos(20, 530) if self.player1.lives <= 0: self.removeItem(self.player1) if self.player1.speed == 8: self.removeItem(self.player1PowerUp) elif self.player1.speed == 2: self.removeItem(self.player1PowerDown) if self.player2.lives > 0: self.winner = 2 # na igraca 2 se obrusio neprijatelj if self.player2.y() <= e.y() + 26 <= self.player2.y() + 53: if self.player2.x() <= e.x( ) <= self.player2.x() + 69 or self.player2.x( ) <= e.x() + 32 <= self.player2.x() + 69: if self.player2.lives > 0: if self.player2.speed == 8: self.removeItem(self.player1PowerUp) elif self.player2.speed == 2: self.removeItem(self.player2PowerDown) self.player2.speed = 4 e.frames = -1 self.player2.lives -= 1 self.removeItem(self.livesPlayer2[-1]) self.livesPlayer2.remove(self.livesPlayer2[-1]) self.player2.setPos(589, 530) if self.player2.lives <= 0: self.removeItem(self.player2) if self.player2.speed == 8: self.removeItem(self.player2PowerUp) elif self.player2.speed == 2: self.removeItem(self.player2PowerDown) if self.player1.lives > 0: self.winner = 1 # igraca 1 pogodio laser neprijatelja if self.player1.x() + 69 >= self.bulletEnemy.x() >= self.player1.x(): if self.player1.y() + 53 >= self.bulletEnemy.y() >= self.player1.y( ): if self.player1.lives > 0: if self.player1.speed == 8: self.removeItem(self.player1PowerUp) elif self.player1.speed == 2: self.removeItem(self.player1PowerDown) self.player1.speed = 4 self.bulletEnemy.active = False self.player1.lives -= 1 self.removeItem(self.livesPlayer1[-1]) self.livesPlayer1.remove(self.livesPlayer1[-1]) self.player1.setPos(20, 530) if self.player1.lives <= 0: self.removeItem(self.player1) if self.player2.lives > 0: self.winner = 2 # igraca 2 pogodio laser neprijatelja if self.player2.x() + 69 >= self.bulletEnemy.x() >= self.player2.x(): if self.player2.y() + 53 >= self.bulletEnemy.y() >= self.player2.y( ): if self.player2.lives > 0: if self.player2.speed == 8: self.removeItem(self.player2PowerUp) elif self.player2.speed == 2: self.removeItem(self.player2PowerDown) self.player2.speed = 4 self.bulletEnemy.active = False self.player2.lives -= 1 self.removeItem(self.livesPlayer2[-1]) self.livesPlayer2.remove(self.livesPlayer2[-1]) self.player2.setPos(589, 530) if self.player2.lives <= 0: self.removeItem(self.player2) if self.player1.lives > 0: self.winner = 1 # pomeranje metaka self.bullet1.game_update(self.keys_pressed, self.player1) self.bullet2.game_update(self.keys_pressed, self.player2) try: self.bulletEnemy.game_update(self.enemies[self.randomEnemyIndex]) if not self.bulletEnemy.active: self.randomEnemyIndex = randint(0, len(self.enemies)) except: self.randomEnemyIndex = randint(0, len(self.enemies)) # nasumicno biranje obrusavanja try: if randint(0, 500) == 0: self.enemies[randint(0, len(self.enemies))].chosen = True except: pass # kraj igre if self.player1.lives == 0 and self.player2.lives == 0: self.timer.stop() self.timerEnemy.stop() self.removeItem(self.bg) self.bg.setPos(-128, 0) self.addItem(self.bg) fontWinner = QFont() fontWinner.setPixelSize(40) textWinner = QGraphicsSimpleTextItem() textWinner.setFont(fontWinner) if self.winner == 1: textWinner.setText("WINNER PLAYER 1") textWinner.setBrush(QBrush(Qt.blue)) self.player1.setPos(310, 300) self.addItem(self.player1) elif self.winner == 2: textWinner.setText("WINNER PLAYER 2") textWinner.setBrush(QBrush(Qt.red)) self.player2.setPos(310, 300) self.addItem(self.player2) textWinner.setPos(180, 200) self.addItem(textWinner) # pobedjen nivo if len(self.enemies) == 0: self.timer.stop() self.timerEnemy.stop() if self.player1.lives > 0: self.removeItem(self.player1) self.removeItem(self.bullet1) if self.player2.lives > 0: self.removeItem(self.player2) self.removeItem(self.bullet2) self.removeItem(self.bulletEnemy) self.new_level() # pomeranje neprijatelja def game_update_enemy(self): if 0 < self.right < 9: self.right += 1 if self.right == 9: self.right = 0 self.left = 1 for e in self.enemies: e.move_right() elif 0 < self.left < 9: self.left += 1 if self.left == 9: self.left = 0 self.right = 1 for e in self.enemies: e.move_left() # pravljenje novog nivoa def new_level(self): self.level += 1 self.levelField.setText("Level: " + str(self.level)) self.left = 1 self.right = 5 self.threadFinished = False _thread.start_new_thread(self.draw_enemies, ()) while not self.threadFinished: continue self.threadFinished = False for e in self.enemies: e.collapseSpeed += self.level e.collapseFrames = 700 / e.collapseSpeed if self.player1.lives > 0: self.player1.setPos(20, 530) self.bullet1 = Bullet1(PLAYER_BULLET_X_OFFSETS, PLAYER_BULLET_Y) self.bullet1.setPos(SCREEN_WIDTH, SCREEN_HEIGHT) self.addItem(self.bullet1) self.addItem(self.player1) if self.player2.lives > 0: self.player2.setPos(589, 530) self.bullet2 = Bullet2(PLAYER_BULLET_X_OFFSETS, PLAYER_BULLET_Y) self.bullet2.setPos(SCREEN_WIDTH, SCREEN_HEIGHT) self.addItem(self.bullet2) self.addItem(self.player2) self.bulletEnemy = BulletEnemy(ENEMY_BULLET_X_OFFSET, ENEMY_BULLET_Y_OFFSET) self.bulletEnemy.setPos(SCREEN_WIDTH, SCREEN_HEIGHT) self.bulletEnemy.speed += self.level self.bulletEnemy.frames = 700 / self.bulletEnemy.speed self.addItem(self.bulletEnemy) self.timer.start(16, self) self.timerEnemy.start(1000) def draw_enemies(self): self.threadFinished = False self.enemies = [ Enemy(131, 10), Enemy(173, 10), Enemy(215, 10), Enemy(257, 10), Enemy(299, 10), Enemy(341, 10), Enemy(383, 10), Enemy(425, 10), Enemy(467, 10), Enemy(509, 10), Enemy(131, 50), Enemy(173, 50), Enemy(215, 50), Enemy(257, 50), Enemy(299, 50), Enemy(341, 50), Enemy(383, 50), Enemy(425, 50), Enemy(467, 50), Enemy(509, 50), Enemy(131, 90), Enemy(173, 90), Enemy(215, 90), Enemy(257, 90), Enemy(299, 90), Enemy(341, 90), Enemy(383, 90), Enemy(425, 90), Enemy(467, 90), Enemy(509, 90) ] pool = multiprocessing.Pool(processes=1) result = pool.apply_async(get_enemy_power_ups, (len(self.enemies) - 1, 5)) self.enemyPowerUps = result.get(timeout=1) pool.close() for i in self.enemyPowerUps: self.enemies[i].setPixmap(QPixmap("Images/enemyPowerUp.png")) self.enemies[i].powerUp = True for e in self.enemies: self.addItem(e) self.randomEnemyIndex = randint(0, len(self.enemies)) self.threadFinished = True
class EdgeItem(QGraphicsItem): LINE_WIDTH = 1 OFFSET = 8 # 方向线偏离中心线的距离 MIN_ARROW_WIDTH, MAX_ARROW_WIDTH = 1, 8 double_click_callback = EMPTY_FUNC def __init__(self, edge_id): super().__init__() self.setZValue(1) self.setFlag(QGraphicsItem.ItemSendsGeometryChanges) self.setAcceptHoverEvents(True) self.edge_id = edge_id self.text_item = QGraphicsSimpleTextItem('', self) self.text_item.setZValue(4) self.style = { 'name': f'Edge{edge_id}', 'color': Qt.black, 'width': 0.5, # 0~1 的中间值 'line': Qt.SolidLine, 'show_arrow': False, 'text': '', 'text_color': Qt.black, 'show_text': False, } self.hover = False def type(self): return QGraphicsItem.UserType + abs(hash(EdgeItem)) def boundingRect(self): return self.bounding_rect def shape(self): path = QPainterPath() path.addPolygon(self.shape_polygon) path.closeSubpath() return path # ------------------------------------------------------------------------- def adjust(self, src_p: QPointF, dst_p: QPointF): self.angle = getAngle(src_p, dst_p) self.src_p = src_p self.arrow_p = (src_p + 2 * dst_p) / 3 # 箭头开始位置, 前端2/3处 self.dst_p = dst_p W1 = 1 * self.OFFSET W2 = 2 * self.OFFSET W3 = 3 * self.OFFSET vec = getRightOffsetVector(self.angle) self.arrow_polygon = QPolygonF([ src_p + vec * W1, dst_p + vec * W1, self.arrow_p + vec * W2, self.arrow_p + vec * W1 ]) self.shape_polygon = QPolygonF( [src_p, src_p + vec * W2, dst_p + vec * W2, dst_p]) self.bounding_rect = QRectF(src_p, dst_p).normalized() # normalized 正方向 self.bounding_rect.adjust(-W3, -W3, W3, W3) self.text_p = ((src_p + dst_p) / 2) + vec * W1 self.text_item.setPos(self.text_p) self.prepareGeometryChange() # ------------------------------------------------------------------------- def paint(self, painter, option, widget=None): if self.style['show_arrow'] or self.hover: width = threshold(0.0, self.style['width'], 1.0) width = width * (self.MAX_ARROW_WIDTH - self.MIN_ARROW_WIDTH) + self.MIN_ARROW_WIDTH painter.setPen(QPen(self.style['color'], width, self.style['line'])) painter.setBrush(self.style['color']) painter.drawPolygon(self.arrow_polygon) else: # TODO 定制线类型 虚线或实线 painter.setPen(QPen(Qt.black, self.LINE_WIDTH)) painter.drawLine(self.src_p, self.dst_p) if (self.style['show_arrow'] and self.style['show_text']) or self.hover: self.text_item.setPen(self.style['text_color']) self.text_item.setText( f"{self.style['name']}\n{self.style['text']}") self.text_item.show() else: self.text_item.hide() # ------------------------------------------------------------------------- def mouseDoubleClickEvent(self, event): self.double_click_callback(self.edge_id) super().mouseDoubleClickEvent(event) def hoverEnterEvent(self, event): self.hover = True self.update() super().hoverEnterEvent(event) def hoverLeaveEvent(self, event): self.hover = False self.update() super().hoverLeaveEvent(event) # ------------------------------------------------------------------------- def setStyle(self, style) -> None: for key in self.style: try: self.style[key] = style[key] except KeyError: pass self.update()
class Node(QGraphicsItem): logger = logging.getLogger('ViewNode') i = 0 NODE_MIN_WIDTH = 100 NODE_MAX_WIDTH = 150 NODE_HEIGHT = 50 NODE_COLOR = (152, 193, 217) # LIGHT BLUE TACTIC_COLOR = (255, 51, 51) # RED STRATEGY_COLOR = (77, 255, 77) # GREEN ROLE_COLOR = (166, 77, 255) # PURPLE KEEPER_COLOR = (255, 255, 26) # YELLOW OTHER_SUBTREE_COLOR = (147, 147, 147) # GREY DECORATOR_COLOR = (51, 51, 255) # DARK BLUE COMPOSITE_COLOR = (255, 153, 0) # ORANGE OTHER_NODE_TYPES_COLOR = (255, 102, 153) # PINK DEFAULT_SIMULATOR_COLOR = Qt.white def __init__(self, x: float, y: float, scene: QGraphicsScene, model_node: ModelNode, title: str = None, parent: QGraphicsItem = None, node_types: NodeTypes = None): """ The constructor for a UI node :param x: x position for the center of the node :param y: y position for the center of the node :param title: title of the node displayed in the ui :param parent: parent of this graphics item """ if title: self.title = title else: # give node a unique title self.title = "node {}".format(Node.i) self.id = model_node.id self.x = x self.y = y Node.i += 1 self.scene = scene self.model_node = model_node self.children = [] self.edges = [] # store node positional data when detaching from parent self.expand_data = None # add node name label centered in the eclipse, elide if title is too long self.node_text = QGraphicsSimpleTextItem() metrics = QFontMetrics(self.node_text.font()) elided_title = metrics.elidedText(self.title, Qt.ElideRight, self.NODE_MAX_WIDTH) self.node_text.setText(elided_title) self.node_text.setAcceptedMouseButtons(Qt.NoButton) self.node_text.setAcceptHoverEvents(False) self.text_width = self.node_text.boundingRect().width() self.text_height = self.node_text.boundingRect().height() self.node_text.setX(x - (self.text_width / 2)) # call super function now we know the node size super(Node, self).__init__(parent) self.node_text.setParentItem(self) # indicates if node is being dragged self.dragging = False self.setCursor(Qt.PointingHandCursor) self.setAcceptHoverEvents(True) # give the node a default color self.brush = QBrush(QColor(*self.NODE_COLOR)) self.simulator_brush = QBrush(self.DEFAULT_SIMULATOR_COLOR) # give node another color if node_types: # check for node types and color them types = node_types.get_node_type_by_name(model_node.title) if len(types) > 0: category, node_type = types[0] if category == 'decorators': self.brush.setColor(QColor(*self.DECORATOR_COLOR)) elif category == 'composites': self.brush.setColor(QColor(*self.COMPOSITE_COLOR)) else: self.brush.setColor(QColor(*self.OTHER_NODE_TYPES_COLOR)) # check for a strategy, role, tactic or keeper if 'name' in model_node.attributes.keys() or 'role' in model_node.attributes.keys(): if model_node.title == 'Tactic': self.brush.setColor(QColor(*self.TACTIC_COLOR)) elif model_node.title == 'Strategy': self.brush.setColor(QColor(*self.STRATEGY_COLOR)) elif model_node.title == 'Keeper': self.brush.setColor(QColor(*self.KEEPER_COLOR)) elif model_node.title == 'Role': self.brush.setColor(QColor(*self.ROLE_COLOR)) else: self.brush.setColor(QColor(*self.OTHER_SUBTREE_COLOR)) self.info_display = [] self.max_width = 0 self.total_height = 0 self.bottom_collapse_expand_button = None self.top_collapse_expand_button = None self._rect = None self.initiate_view() def initiate_view(self, propagate=False): """ Initiates all the children for the current view :param propagate: Propagate initiate view signal to children """ for rect in self.info_display: rect.setParentItem(None) if self.top_collapse_expand_button and self.bottom_collapse_expand_button: self.top_collapse_expand_button.setParentItem(None) self.bottom_collapse_expand_button.setParentItem(None) self.info_display = [] self.max_width = self.text_width + 10 self.total_height = self.NODE_HEIGHT if self.scene.info_mode: model_node = self.scene.gui.tree.nodes[self.id] self.create_info_display(self.x, self.y, model_node.attributes) if self.max_width > self.NODE_MIN_WIDTH - 10: self._rect = QRect(self.x - self.max_width / 2, self.y - self.total_height / 2, self.max_width, self.total_height) else: self._rect = QRect(self.x - self.NODE_MIN_WIDTH / 2, self.y - self.total_height / 2, self.NODE_MIN_WIDTH, self.total_height) # set node size based on children self.node_text.setY(self.y - self.total_height / 2 + self.NODE_HEIGHT / 2 - self.text_height / 2) self.create_expand_collapse_buttons() self.scene.update() if propagate: for c in self.children: c.initiate_view(True) for e in self.edges: e.change_position() def create_expand_collapse_buttons(self): """ Creates the expand/collapse buttons of the node """ # create the bottom collapse/expand button for this node if self.bottom_collapse_expand_button: bottom_collapsed = self.bottom_collapse_expand_button.isCollapsed else: bottom_collapsed = False self.bottom_collapse_expand_button = CollapseExpandButton(self) self.bottom_collapse_expand_button.setParentItem(self) self.bottom_collapse_expand_button.collapse.connect(self.collapse_children) self.bottom_collapse_expand_button.expand.connect(self.expand_children) self.bottom_collapse_expand_button.isCollapsed = bottom_collapsed # position the bottom button at the bottom-center of the node button_x = self.x - (self.bottom_collapse_expand_button.boundingRect().width() / 2) button_y = self.y + self.total_height / 2 - (self.bottom_collapse_expand_button.boundingRect().height() / 2) self.bottom_collapse_expand_button.setPos(button_x, button_y) # hidden by default, the button is only needed if the node has children if not self.children: self.bottom_collapse_expand_button.hide() # create the top collapse/expand button for this node if self.top_collapse_expand_button: top_collapsed = self.top_collapse_expand_button.isCollapsed else: top_collapsed = False self.top_collapse_expand_button = CollapseExpandButton(self) self.top_collapse_expand_button.setParentItem(self) self.top_collapse_expand_button.collapse.connect(self.collapse_upwards) self.top_collapse_expand_button.expand.connect(self.expand_upwards) self.top_collapse_expand_button.isCollapsed = top_collapsed if self.scene.root_ui_node == self or self in self.scene.disconnected_nodes \ or self.scene.reconnecting_node == self: self.top_collapse_expand_button.hide() # position the top button at the top-center of the node button_x = self.x - (self.top_collapse_expand_button.boundingRect().width() / 2) button_y = self.y - self.total_height / 2 - (self.top_collapse_expand_button.boundingRect().height() / 2) self.top_collapse_expand_button.setPos(button_x, button_y) def create_info_display(self, x, y, attributes): """ Creates view elements for the info display :param x: x position of the node :param y: y position of the node :param attributes: attributes that will be displayed in the view :return: """ start_height = y + (self.NODE_HEIGHT / 2) # unfold dictionary values at the bottom of the list sorted_attributes = [] for k, v in sorted(attributes.items(), key=lambda tup: isinstance(tup[1], dict)): if isinstance(v, dict): sorted_attributes.append((k, v)) sorted_attributes.extend(v.items()) else: sorted_attributes.append((k, v)) # create property rows for i, (k, v) in enumerate(sorted_attributes): value_text = None value_height = 0 if isinstance(v, dict): # display dictionary key as title text = "{}".format(k) if len(text) > 20: text = text[:20] + "..." key_text = QGraphicsSimpleTextItem(text) f = key_text.font() f.setBold(True) key_text.setFont(f) text_width = key_text.boundingRect().width() else: key_text = QGraphicsSimpleTextItem("{}:".format(k) if k else " ") text = str(v) if len(text) > 20: text = text[:20] + "..." value_text = QGraphicsSimpleTextItem(text) value_height = value_text.boundingRect().height() text_width = key_text.boundingRect().width() + value_text.boundingRect().width() # create box around property attribute_container = QGraphicsRectItem(x, start_height, text_width + 10, max(key_text.boundingRect().height(), value_height) + 10) attribute_container.setBrush(QBrush(Qt.white)) self.total_height += attribute_container.rect().height() key_text.setParentItem(attribute_container) if value_text: value_text.setParentItem(attribute_container) self.max_width = max(self.max_width, attribute_container.rect().width()) attribute_container.setParentItem(self) self.info_display.append(attribute_container) start_height += max(key_text.boundingRect().height(), value_height) + 10 # calculate correct coordinates for positioning of the attribute boxes if self.max_width > self.NODE_MIN_WIDTH - 10: x -= (self.max_width + 10) / 2 y -= self.total_height / 2 self.max_width += 10 else: x -= self.NODE_MIN_WIDTH / 2 y -= self.total_height / 2 self.max_width = self.NODE_MIN_WIDTH h = 0 # position all the elements previously created for attribute_container in self.info_display: rect: QRectF = attribute_container.rect() rect.setX(x) rect_height = rect.height() rect.setY(y + self.NODE_HEIGHT + h) rect.setHeight(rect_height) key_child = attribute_container.childItems()[0] if len(attribute_container.childItems()) == 2: key_child.setX(x + 5) value_child = attribute_container.childItems()[1] value_child.setX(x + self.max_width - value_child.boundingRect().width() - 5) value_child.setY(y + self.NODE_HEIGHT + h + 5) else: key_child.setX(x - key_child.boundingRect().width() / 2 + self.max_width / 2) key_child.setY(y + self.NODE_HEIGHT + h + 5) h += rect.height() rect.setWidth(self.max_width) attribute_container.setRect(rect) def paint(self, painter: QPainter, style_options: QStyleOptionGraphicsItem, widget=None): """ Paint the basic shape of the node (ellipse or rectangle) :param painter: painter used to paint objects :param style_options: Styling options for the graphics item :param widget: The widget being painted """ painter.setPen(Qt.SolidLine) if self == self.scene.root_ui_node: pen = QPen(Qt.black, 2.0) pen.setStyle(Qt.DotLine) painter.setPen(pen) if self.scene.simulator_mode: brush = self.simulator_brush else: brush = self.brush painter.setBrush(brush) if self.scene.info_mode: painter.drawRect(self.rect().x(), self.rect().y(), self.rect().width(), self.NODE_HEIGHT) else: painter.drawEllipse(self.rect()) def add_child(self, child): """ Add a child node Inheritance looks like: parent > edge > child :param child: Another ui node """ edge = Edge(self, child) edge.setParentItem(self) # edge should stay behind the expand/collapse button edge.stackBefore(self.bottom_collapse_expand_button) self.children.append(child) self.edges.append(edge) # show the expand/collapse button when the first child is added if not self.bottom_collapse_expand_button.isVisible(): self.bottom_collapse_expand_button.show() if not child.top_collapse_expand_button.isVisible(): child.top_collapse_expand_button.show() def remove_child(self, child): """ Removes child from this node (no data changes) :param child: Child of this node """ if child not in self.children: Node.logger.error("Incorrect child can not be removed from wrong parent.") edge = child.parentItem() child.setParentItem(None) self.children.remove(child) self.edges.remove(edge) edge.setParentItem(None) self.scene.removeItem(edge) if not self.children: self.bottom_collapse_expand_button.hide() def nodes_below(self): nodes = [] for c in self.children: nodes.append(c) nodes.extend(c.nodes_below()) return nodes def moveBy(self, x, y): super(Node, self).moveBy(x, y) # move edge correctly with node if self.parentItem() and isinstance(self.parentItem(), Edge): self.parentItem().change_position() def setPos(self, *args): super(Node, self).setPos(*args) # move edge correctly with node if self.parentItem() and isinstance(self.parentItem(), Edge): self.parentItem().change_position() def xoffset(self): """ recursively adds the relative x distances from this node up until the root node. :return: the sum of the relative x distances """ if self.parentItem(): return self.pos().x() + self.parentItem().xoffset() else: return self.pos().x() + self.rect().x() + self.rect().width() / 2 def yoffset(self): """ recursively adds the relative y distances from this node up until the root node. :return: the sum of the relative y distances """ if self.parentItem(): return self.pos().y() + self.parentItem().yoffset() else: return self.pos().y() + self.rect().y() + self.rect().height() / 2 def xpos(self): """ Calculates the x position of this node using the x offset :return: the x position of the node """ return self.xoffset() def ypos(self): """ Calculates the y position of this node using the y offset :return: the y position of the node """ return self.yoffset() def boundingRect(self): return QRectF(self._rect) def rect(self): return self._rect def detach_from_parent(self): """ Detaches node from parent (no data changes) :return: Positional data that can be used to reattach node """ if not self.parentItem() or not self.parentItem().parentItem(): Node.logger.error("The node can't detach from parent, no parent") return # store attach data used to restore the state when attaching xpos, ypos = self.xpos(), self.ypos() root_item = self.scene.root_ui_node parent_node = self.parentItem().parentItem() attach_data = { "abs_pos": QPointF(xpos, ypos), "old_parent": parent_node, "top_level_item": self.topLevelItem(), } parent_node.remove_child(self) # move node to retain correct position self.setPos(0, 0) root_x = root_item.xpos() if root_item else self.scene.node_init_pos[0] root_y = root_item.ypos() if root_item else self.scene.node_init_pos[1] move_x = xpos - root_x - (self.scene.node_init_pos[0] - root_x) move_y = ypos - root_y - (self.scene.node_init_pos[1] - root_y) self.moveBy(move_x, move_y) return attach_data def attach_to_parent(self, data, parent=None): """ Attaches node to parent (no data changes) :param: data: Positional data from detachment used for attaching """ if not parent: parent = data['old_parent'] new_abs_pos = QPointF(self.xpos(), self.ypos()) # reset parent item e = Edge(parent, self) e.setParentItem(parent) parent.children.append(self) parent.edges.append(e) parent.sort_children() parent_abs_pos = QPointF(parent.xpos(), parent.ypos()) # reset relative position to parent self.setPos(new_abs_pos - parent_abs_pos) def collapse_upwards(self): """ Collapses the tree upwards only displaying this node and its children :return: """ self.expand_data = self.detach_from_parent() # hide parent nodes self.expand_data['top_level_item'].hide() def expand_upwards(self): """ Expands the tree upwards displaying all expanded parent nodes :return: """ self.attach_to_parent(self.expand_data) # show expanded parent nodes self.topLevelItem().show() def collapse_children(self): """ Collapses this node's children by hiding all child edges (and therefore the whole subtree) """ for c in self.childItems(): if isinstance(c, Edge): c.hide() def expand_children(self): """ Expands this node's children by showing all child edges previously hidden by the collapse function """ for c in self.childItems(): if isinstance(c, Edge): c.show() def sort_children(self): """ Sort child edges/nodes based on x position :return: The model nodes in order """ # gather all the edges child_edges = [edge for edge in self.childItems() if isinstance(edge, Edge)] # sort edges by x position of the child nodes child_edges.sort(key=lambda c: c.end_node.xpos()) # reset internal structure self.edges.clear() self.children.clear() # add children back in correct order for e in child_edges: e.setParentItem(None) self.edges.append(e) self.children.append(e.end_node) # set the parent of the children in the correct order for e in child_edges: e.setParentItem(self) # return the model nodes in the correct order model_nodes_order = [e.end_node.model_node for e in child_edges] return model_nodes_order def detect_order_change(self): """ Detects if node order has changed and updates model accordingly """ if not self.parentItem(): # sort top level nodes, this prevents alignment issues self.scene.disconnected_nodes = sorted(self.scene.disconnected_nodes, key=lambda n: n.xpos()) else: # parent node of self parent_node = self.parentItem().parentItem() parent_model_node = self.scene.gui.tree.nodes.get(parent_node.id) # own child index node_index = parent_node.children.index(self) # check if node is swapped with left neighbour try: if node_index - 1 >= 0: # can throw IndexError if there is no left neighbour left_node = parent_node.children[node_index - 1] # check if node is swapped if left_node.xpos() > self.xpos(): # sort children of parent sorted_nodes = parent_node.sort_children() # change model tree structure accordingly parent_model_node.children = [n.id for n in sorted_nodes] self.scene.gui.update_tree(parent_model_node) except IndexError: pass # check if node is swapped with right neighbour try: # can throw IndexError if there is no right neighbour right_node = parent_node.children[node_index + 1] # check if node is swapped if right_node.xpos() < self.xpos(): # sort children of parent sorted_nodes = parent_node.sort_children() # change model tree structure accordingly parent_model_node.children = [n.id for n in sorted_nodes] self.scene.gui.update_tree(parent_model_node) except IndexError: pass def delete_self(self): """ Deletes this node and makes children disconnected subtrees/nodes """ for c in self.children[:]: c.detach_from_parent() # add child to disconnected nodes if self in self.scene.disconnected_nodes: index = self.scene.disconnected_nodes.index(self) self.scene.disconnected_nodes.insert(index, c) else: self.scene.disconnected_nodes.insert(0, c) c.top_collapse_expand_button.hide() parent_model_node = None if self.parentItem(): parent_node: Node = self.parentItem().parentItem() parent_node.remove_child(self) parent_model_node = self.scene.gui.tree.nodes.get(parent_node.id) parent_model_node.children.remove(self.id) if self in self.scene.disconnected_nodes: self.scene.disconnected_nodes.remove(self) self.scene.removeItem(self) self.scene.close_property_display() del self.scene.nodes[self.id] # reset root if this is the root if self.scene.gui.tree.root == self.id: self.scene.gui.tree.root = '' # remove node from internal tree structure del self.scene.gui.tree.nodes[self.id] if parent_model_node: self.scene.gui.update_tree(parent_model_node) def delete_subtree(self, delete_parent_relation=True, update_tree=True): """ Deletes node and its children :param delete_parent_relation: Boolean indicating if parent relation should be modified :param update_tree: Boolean indicating if the tree needs an update """ # remove children for c in self.children: c.delete_subtree(delete_parent_relation=False) # remove child reference from parent parent_node = None if delete_parent_relation and self.parentItem(): parent_node: Node = self.parentItem().parentItem() parent_node.remove_child(self) try: self.scene.gui.tree.nodes[parent_node.id].children.remove(self.id) except ValueError: pass self.scene.removeItem(self) self.scene.close_property_display() if self in self.scene.disconnected_nodes: self.scene.disconnected_nodes.remove(self) self.scene.nodes.pop(self.id, None) if self.scene.gui.tree.root == self.id: self.scene.gui.tree.root = '' # remove node from internal tree structure self.scene.gui.tree.nodes.pop(self.id, None) if delete_parent_relation and parent_node and update_tree: node = self.scene.gui.tree.nodes.get(parent_node.id) self.scene.gui.update_tree(node) def reconnect_edge(self): """ Starts edge reconnection process """ if not self.parentItem() and self not in self.scene.disconnected_nodes: Node.logger.error("The edge trying to reconnect does not exist.") else: self.scene.start_reconnect_edge(self) def mousePressEvent(self, m_event): """ Handles a mouse press on a node :param m_event: The mouse press event and its details """ super(Node, self).mousePressEvent(m_event) tree = self.scene.gui.tree.nodes[self.id] if self.scene.view.parent().property_display: self.scene.view.parent().property_display.setParent(None) self.scene.view.parent().property_display.deleteLater() self.scene.view.parent().property_display = view.widgets.TreeViewPropertyDisplay( self.scene.view.parent().graphics_scene, tree.attributes, parent=self.scene.view.parent(), node_id=tree.id, node_title=tree.title) def mouseMoveEvent(self, m_event): """ Handles a mouse move over a node :param m_event: The mouse move event and its details """ super(Node, self).mouseMoveEvent(m_event) if self.dragging: # move the node with the mouse and adjust the edges to the new position dx = m_event.scenePos().x() - m_event.lastScenePos().x() dy = m_event.scenePos().y() - m_event.lastScenePos().y() self.setPos(self.pos().x() + dx, self.pos().y() + dy) # Set correct order for children if node has a parent and the order of disconnected nodes self.detect_order_change() # reposition incoming edge if isinstance(self.parentItem(), Edge): self.parentItem().change_position() def contextMenuEvent(self, menu_event): """ Creates context menu for right clicks on this node :param menu_event: Context about the right click event """ menu = QMenu() reconnect_edge_action = QAction("Reconnect Edge" if self.parentItem() else "Connect Edge") reconnect_edge_action.triggered.connect(self.reconnect_edge) menu.addAction(reconnect_edge_action) delete_action = QAction("Delete Node") delete_action.setToolTip('Delete only this node.') delete_action.triggered.connect(self.delete_self) menu.addAction(delete_action) delete_subtree_action = QAction("Delete Subtree") delete_subtree_action.setToolTip('Delete node and all its children.') delete_subtree_action.triggered.connect(lambda: self.delete_subtree()) menu.addAction(delete_subtree_action) menu.exec(menu_event.screenPos()) menu_event.setAccepted(True)
class StickWidget(QGraphicsObject): font: QFont = QFont("monospace", 32) delete_clicked = pyqtSignal(Stick) link_initiated = pyqtSignal('PyQt_PyObject') # Actually StickWidget link_accepted = pyqtSignal('PyQt_PyObject') hovered = pyqtSignal(['PyQt_PyObject', 'PyQt_PyObject']) stick_changed = pyqtSignal('PyQt_PyObject') sibling_changed = pyqtSignal(bool) right_clicked = pyqtSignal('PyQt_PyObject') handle_idle_brush = QBrush(QColor(0, 125, 125, 50)) handle_hover_brush = QBrush(QColor(125, 125, 0, 50)) handle_press_brush = QBrush(QColor(200, 200, 0, 0)) handle_idle_pen = QPen(QColor(0, 0, 0, 255)) handle_press_pen = QPen(QColor(200, 200, 0, 255)) handle_size = 20 normal_color = QColor(0, 200, 120) negative_color = QColor(200, 0, 0) positive_color = QColor(0, 200, 0) mismatched = pyqtSignal('PyQt_PyObject') misplaced = pyqtSignal('PyQt_PyObject') measurement_corrected = pyqtSignal('PyQt_PyObject') clearly_visible = pyqtSignal('PyQt_PyObject') zero_clicked = pyqtSignal('PyQt_PyObject') def __init__(self, stick: Stick, camera: Camera, parent: Optional[QGraphicsItem] = None): QGraphicsObject.__init__(self, parent) self.camera = camera self.stick = stick self.line = QLineF() self.gline = QGraphicsLineItem(self.line) self.stick_label_text = QGraphicsSimpleTextItem("0", self) self.stick_label_text.setFont(StickWidget.font) self.stick_label_text.setPos(self.line.p1() - QPoint(0, 24)) self.stick_label_text.setBrush(QBrush(QColor(0, 255, 0))) self.stick_label_text.hide() self.setZValue(10) self.mode = StickMode.Display self.btn_delete = Button("delete", "x", parent=self) self.btn_delete.setFlag(QGraphicsItem.ItemIgnoresTransformations, True) self.btn_delete.set_base_color([ButtonColor.RED]) self.btn_delete.setVisible(False) btn_size = max(int(np.linalg.norm(self.stick.top - self.stick.bottom) / 5.0), 15) self.btn_delete.set_height(12) self.btn_delete.clicked.connect(self.handle_btn_delete_clicked) self.btn_delete.setPos(self.line.p1() - QPointF(0.5 * self.btn_delete.boundingRect().width(), 1.1 * self.btn_delete.boundingRect().height())) self.btn_delete.set_opacity(0.7) self.top_handle = QGraphicsEllipseItem(0, 0, self.handle_size, self.handle_size, self) self.mid_handle = QGraphicsEllipseItem(0, 0, self.handle_size, self.handle_size, self) self.bottom_handle = QGraphicsEllipseItem(0, 0, self.handle_size, self.handle_size, self) self.top_handle.setAcceptedMouseButtons(Qt.NoButton) self.mid_handle.setAcceptedMouseButtons(Qt.NoButton) self.bottom_handle.setAcceptedMouseButtons(Qt.NoButton) self.top_handle.setBrush(self.handle_idle_brush) self.top_handle.setPen(self.handle_idle_pen) self.mid_handle.setBrush(self.handle_idle_brush) self.mid_handle.setPen(self.handle_idle_pen) self.bottom_handle.setBrush(self.handle_idle_brush) self.bottom_handle.setPen(self.handle_idle_pen) self.hovered_handle: Optional[QGraphicsRectItem] = None self.handles = [self.top_handle, self.mid_handle, self.bottom_handle] self.link_button = Button("link", "Link to...", parent=self) self.link_button.set_base_color([ButtonColor.GREEN]) self.link_button.set_height(12) self.link_button.set_label("Link", direction="vertical") self.link_button.fit_to_contents() self.link_button.clicked.connect(lambda: self.link_initiated.emit(self)) self.link_button.setVisible(False) self.link_button.setFlag(QGraphicsObject.ItemIgnoresTransformations, False) self.adjust_line() self.setAcceptHoverEvents(True) self.top_handle.setZValue(4) self.bottom_handle.setZValue(4) self.mid_handle.setZValue(4) self.top_handle.hide() self.mid_handle.hide() self.bottom_handle.hide() self.handle_mouse_offset = QPointF(0, 0) self.available_for_linking = False self.link_source = False self.current_highlight_color: QColor = StickWidget.normal_color self.highlighted = False self.frame_color: Optional[None] = self.normal_color self.is_linked = False self.is_master = True self.selected = False self.measured_height: int = -1 self.current_color = self.normal_color self.show_label = False self.highlight_animation = QPropertyAnimation(self, b"highlight_color") self.highlight_animation.valueChanged.connect(self.handle_highlight_animation_value_changed) self.deleting = False self.update_tooltip() self.show_measurements: bool = False self.proposed_snow_height: int = -1 self.zero_btn = Button("zero_btn", "0", parent=self) self.zero_btn.setFlag(QGraphicsItem.ItemIgnoresTransformations, True) self.zero_btn.setVisible(False) self.zero_btn.setPos(self.boundingRect().center() + QPointF(self.zero_btn.boundingRect().width() * -0.5, self.boundingRect().height() * 0.5)) self.zero_btn.clicked.connect(self.handle_zero) @pyqtSlot() def handle_btn_delete_clicked(self): self.delete_clicked.emit(self.stick) def prepare_for_deleting(self): self.deleting = True self.highlight_animation.stop() self.btn_delete.setParentItem(None) self.scene().removeItem(self.btn_delete) self.btn_delete.deleteLater() def paint(self, painter: QPainter, option: QStyleOptionGraphicsItem, widget: Optional[PyQt5.QtWidgets.QWidget] = ...): painter.setPen(QPen(self.current_color, 1.0)) brush = QBrush(self.current_highlight_color) pen = QPen(brush, 4) painter.setPen(pen) if self.highlighted: painter.fillRect(self.boundingRect(), QBrush(self.current_highlight_color)) if self.frame_color is not None and self.mode != StickMode.Edit and self.mode != StickMode.EditDelete: painter.setPen(QPen(self.frame_color, 4)) painter.drawRect(self.boundingRect()) pen = QPen(QColor(0, 255, 0, 255)) pen.setWidth(1.0) pen.setColor(QColor(255, 0, 255, 255)) pen.setStyle(Qt.DotLine) painter.setPen(pen) off = 10 painter.drawLine(self.line.p1() - QPointF(0, off), self.line.p1() + QPointF(0, off)) painter.drawLine(self.line.p1() - QPointF(off, 0), self.line.p1() + QPointF(off, 0)) painter.drawLine(self.line.p2() - QPointF(0, off), self.line.p2() + QPointF(0, off)) painter.drawLine(self.line.p2() - QPointF(off, 0), self.line.p2() + QPointF(off, 0)) pen.setStyle(Qt.SolidLine) pen.setColor(QColor(0, 255, 0, 255)) painter.setPen(pen) if self.mode != StickMode.EditDelete: pen.setWidth(2.0) br = painter.brush() painter.setPen(pen) painter.drawEllipse(self.line.p1(), 10, 10) painter.drawEllipse(self.line.p2(), 10, 10) painter.setBrush(br) if self.mode == StickMode.Measurement and self.proposed_snow_height >= 0: point = QPointF(self.boundingRect().x(), -self.proposed_snow_height + self.line.p2().y()) pen = QPen(QColor(200, 100, 0, 255), 3.0) painter.setPen(pen) painter.drawLine(point, point + QPointF(self.boundingRect().width(), 0.0)) if self.measured_height >= 0: vec = (self.stick.top - self.stick.bottom) / np.linalg.norm(self.stick.top - self.stick.bottom) dist_along_stick = self.measured_height / np.dot(np.array([0.0, -1.0]), vec) point = self.line.p2() + dist_along_stick * QPointF(vec[0], vec[1]) point = QPointF(self.boundingRect().x(), point.y()) pen = QPen(QColor(0, 100, 200, 255), 3.0) painter.setPen(pen) painter.drawLine(point, point + QPointF(self.boundingRect().width(), 0.0)) else: painter.drawLine(self.line.p1(), self.line.p2()) if self.selected: pen.setColor(QColor(255, 125, 0, 255)) pen.setStyle(Qt.DashLine) painter.setPen(pen) painter.drawRect(self.boundingRect().marginsAdded(QMarginsF(5, 5, 5, 5))) if self.show_measurements: painter.fillRect(self.stick_label_text.boundingRect().translated(self.stick_label_text.pos()), QBrush(QColor(0, 0, 0, 120))) def boundingRect(self) -> PyQt5.QtCore.QRectF: return self.gline.boundingRect().united(self.top_handle.boundingRect()).\ united(self.mid_handle.boundingRect()).united(self.bottom_handle.boundingRect()) def set_edit_mode(self, value: bool): if value: self.set_mode(StickMode.EditDelete) else: self.set_mode(StickMode.Display) def set_mode(self, mode: StickMode): if mode == StickMode.Display: self.btn_delete.setVisible(False) self.top_handle.setVisible(False) self.mid_handle.setVisible(False) self.bottom_handle.setVisible(False) self.link_button.setVisible(False) self.available_for_linking = False self.link_source = False self.zero_btn.setVisible(False) self.setVisible(self.stick.is_visible) elif mode == StickMode.EditDelete: self.set_mode(StickMode.Display) self.top_handle.setVisible(True) self.mid_handle.setVisible(True) self.bottom_handle.setVisible(True) self.available_for_linking = False self.link_source = False self.btn_delete.setVisible(True) elif mode == StickMode.LinkSource: self.set_mode(StickMode.Display) self.link_source = True self.available_for_linking = False self.link_button.setPos(self.boundingRect().topLeft()) self.link_button.set_width(int(self.boundingRect().width())) self.link_button.set_button_height(int(self.boundingRect().height())) self.link_button.adjust_text_to_button() elif mode == StickMode.LinkTarget: self.set_mode(StickMode.Display) self.link_source = False self.available_for_linking = True elif mode == StickMode.Edit: self.set_mode(StickMode.EditDelete) self.btn_delete.setVisible(False) elif mode == StickMode.Measurement: self.zero_btn.setVisible(True) self.setVisible(True) self.mode = mode self.update_tooltip() self.update() def mousePressEvent(self, event: QGraphicsSceneMouseEvent): if self.mode != StickMode.EditDelete: return if self.hovered_handle is None: return self.hovered_handle.setBrush(self.handle_press_brush) if self.hovered_handle == self.mid_handle: self.bottom_handle.setBrush(self.handle_press_brush) self.bottom_handle.setPen(self.handle_press_pen) self.bottom_handle.setOpacity(0.5) self.top_handle.setBrush(self.handle_press_brush) self.top_handle.setPen(self.handle_press_pen) self.top_handle.setOpacity(0.5) self.hovered_handle.setPen(self.handle_press_pen) self.hovered_handle.setOpacity(0.5) self.handle_mouse_offset = self.hovered_handle.rect().center() - event.pos() self.btn_delete.setVisible(False) def mouseReleaseEvent(self, event: QGraphicsSceneMouseEvent): if self.available_for_linking: self.link_accepted.emit(self) return if self.mode == StickMode.Measurement: old_snow = self.stick.snow_height_px self.measured_height = self.proposed_snow_height self.stick.set_snow_height_px(self.proposed_snow_height) if abs(old_snow - self.proposed_snow_height) > 0: self.measurement_corrected.emit(self) self.proposed_snow_height = -1 if self.mode != StickMode.EditDelete and self.mode != StickMode.Edit: return if self.hovered_handle is not None: self.hovered_handle.setBrush(self.handle_hover_brush) self.hovered_handle.setPen(self.handle_idle_pen) self.hovered_handle.setOpacity(1.0) if self.hovered_handle == self.mid_handle: self.bottom_handle.setBrush(self.handle_idle_brush) self.bottom_handle.setPen(self.handle_idle_pen) self.bottom_handle.setOpacity(1.0) self.top_handle.setBrush(self.handle_idle_brush) self.top_handle.setPen(self.handle_idle_pen) self.top_handle.setOpacity(1.0) self.stick_changed.emit(self) self.hovered_handle = None if self.mode == StickMode.EditDelete: self.btn_delete.setVisible(True) def mouseMoveEvent(self, event: QGraphicsSceneMouseEvent): if self.hovered_handle is None: return if self.hovered_handle == self.top_handle: self.line.setP1((event.pos() + self.handle_mouse_offset).toPoint()) elif self.hovered_handle == self.bottom_handle: self.line.setP2((event.pos() + self.handle_mouse_offset).toPoint()) else: displacement = event.pos() - event.lastPos() self.setPos(self.pos() + displacement) self.adjust_handles() self.adjust_stick() self.scene().update() def set_top(self, pos: QPoint): self.line.setP1(pos) self.adjust_handles() self.adjust_stick() self.scene().update() def set_bottom(self, pos: QPoint): self.line.setP2(pos) self.adjust_handles() self.adjust_stick() self.scene().update() def hoverEnterEvent(self, event: QGraphicsSceneHoverEvent): if self.available_for_linking: self.hovered.emit(True, self) elif self.link_source: self.link_button.setVisible(True) self.scene().update() def hoverLeaveEvent(self, event: QGraphicsSceneHoverEvent): for h in self.handles: h.setBrush(self.handle_idle_brush) self.hovered_handle = None if self.available_for_linking: self.hovered.emit(False, self) self.link_button.setVisible(False) self.proposed_snow_height = -1 self.scene().update() def hoverMoveEvent(self, event: QGraphicsSceneHoverEvent): if self.mode != StickMode.EditDelete and self.mode != StickMode.Edit and self.mode != StickMode.Measurement: return if self.mode == StickMode.Measurement: self.proposed_snow_height = max(self.line.p2().y() - event.pos().y(), 0) self.update() return hovered_handle = list(filter(lambda h: h.rect().contains(event.pos()), self.handles)) if len(hovered_handle) == 0: if self.hovered_handle is not None: self.hovered_handle.setBrush(self.handle_idle_brush) self.hovered_handle = None return if self.hovered_handle is not None and self.hovered_handle != hovered_handle[0]: self.hovered_handle.setBrush(self.handle_idle_brush) self.hovered_handle = hovered_handle[0] if self.hovered_handle == self.top_handle: self.top_handle.setBrush(self.handle_hover_brush) elif self.hovered_handle == self.bottom_handle: self.bottom_handle.setBrush(self.handle_hover_brush) else: self.mid_handle.setBrush(self.handle_hover_brush) self.scene().update() def adjust_stick(self): self.stick.top[0] = self.pos().x() + self.line.p1().x() self.stick.top[1] = self.pos().y() + self.line.p1().y() self.stick.bottom[0] = self.pos().x() + self.line.p2().x() self.stick.bottom[1] = self.pos().y() + self.line.p2().y() def adjust_handles(self): if self.line.p1().y() > self.line.p2().y(): p1, p2 = self.line.p1(), self.line.p2() self.line.setP1(p2) self.line.setP2(p1) if self.hovered_handle is not None: self.hovered_handle.setBrush(self.handle_idle_brush) self.hovered_handle.setPen(self.handle_idle_pen) self.hovered_handle = self.top_handle if self.hovered_handle == self.bottom_handle else self.bottom_handle self.hovered_handle.setBrush(self.handle_press_brush) self.hovered_handle.setPen(self.handle_press_pen) rect = self.top_handle.rect() rect.moveCenter(self.line.p1()) self.top_handle.setRect(rect) rect = self.bottom_handle.rect() rect.moveCenter(self.line.p2()) self.bottom_handle.setRect(rect) rect = self.mid_handle.rect() rect.moveCenter(self.line.center()) self.mid_handle.setRect(rect) self.btn_delete.setPos(self.top_handle.rect().center() - QPointF(self.btn_delete.boundingRect().width() / 2, self.btn_delete.boundingRect().height() + self.top_handle.boundingRect().height() / 2)) def set_available_for_linking(self, available: bool): self.available_for_linking = available def set_is_link_source(self, is_source: bool): self.link_source = is_source self.link_button.setPos(self.boundingRect().topLeft()) self.link_button.set_width(int(self.boundingRect().width())) self.link_button.set_button_height(int(self.boundingRect().height())) self.link_button.adjust_text_to_button() def set_frame_color(self, color: Optional[QColor]): self.frame_color = color if color is not None else self.normal_color self.update() def set_is_linked(self, value: bool): self.is_linked = value if not self.is_linked: self.set_frame_color(None) if self.available_for_linking: self.highlight(QColor(0, 255, 0, 100)) else: self.highlight(None) self.update_tooltip() def adjust_line(self): self.setPos(QPointF(0.5 * (self.stick.top[0] + self.stick.bottom[0]), 0.5 * (self.stick.top[1] + self.stick.bottom[1]))) vec = 0.5 * (self.stick.top - self.stick.bottom) self.line.setP1(QPointF(vec[0], vec[1])) self.line.setP2(-self.line.p1()) self.gline.setLine(self.line) self.adjust_handles() self.stick_label_text.setPos(self.line.p1() - QPointF(0.5 * self.stick_label_text.boundingRect().width(), 1.3 * self.stick_label_text.boundingRect().height())) self.update() def set_selected(self, selected: bool): self.selected = selected self.update() def is_selected(self) -> bool: return self.selected def set_snow_height(self, height: int): self.measured_height = height self.update() def border_normal(self): self.current_color = self.normal_color self.update() def border_positive(self): self.current_color = self.positive_color self.update() def border_negative(self): self.current_color = self.negative_color self.update() @pyqtProperty(QColor) def highlight_color(self) -> QColor: return self.current_highlight_color @highlight_color.setter def highlight_color(self, color: QColor): self.current_highlight_color = color def highlight(self, color: Optional[QColor], animated: bool = False): self.highlighted = color is not None if not animated or color is None: self.highlight_animation.stop() self.current_highlight_color = self.normal_color if color is None else color self.update() return self.highlight_animation.setStartValue(color) self.highlight_animation.setEndValue(color) self.highlight_animation.setKeyValueAt(0.5, color.darker()) self.highlight_animation.setDuration(2000) self.highlight_animation.setLoopCount(-1) self.highlight_animation.start() def handle_link_button_hovered(self, btn: Dict[str, Any]): self.link_button.setVisible(btn['hovered']) def handle_highlight_animation_value_changed(self, new: QColor): if not self.deleting: self.update(self.boundingRect().marginsAdded(QMarginsF(10, 10, 10, 10))) def contextMenuEvent(self, event: QGraphicsSceneContextMenuEvent) -> None: self.right_clicked.emit({'stick_widget': self}) def set_stick_label(self, label: str): self.stick.label = label self.stick_label_text.setText(label) self.update_tooltip() self.update() def get_stick_label(self) -> str: return self.stick.label def get_stick_length_cm(self) -> int: return self.stick.length_cm def set_stick_length_cm(self, length: int): self.stick.length_cm = length self.update_tooltip() self.update() def update_tooltip(self): if self.mode != StickMode.Display or self.mode == StickMode.Measurement: self.setToolTip("") return snow_txt = "Snow height: " if self.stick.snow_height_px >= 0: snow_txt += str(self.stick.snow_height_cm) + " cm" self.stick_label_text.setText(str(self.stick.snow_height_cm)) else: snow_txt = "not measured" self.stick_label_text.setVisible(False) self.stick_label_text.setText(self.stick.label) self.stick_label_text.setVisible(True) stick_view_text = '' role = '' if self.stick.alternative_view is not None: alt_view = self.stick.alternative_view role = " - primary" alt = "Secondary" if not self.stick.primary: role = " - secondary" alt = "Primary" stick_view_text = f'\n{alt} view: {alt_view.label} in {alt_view.camera_folder.name}\n' mark = '*' if self.stick.determines_quality else '' self.setToolTip(f'{mark}{self.stick.label}{role}{stick_view_text}\nLength: {self.stick.length_cm} cm\n{snow_txt}') def set_stick(self, stick: Stick): self.reset_d_btns() self.stick = stick self.adjust_line() self.adjust_handles() self.set_snow_height(stick.snow_height_px) self.update_tooltip() self.set_show_measurements(self.show_measurements) if self.mode == StickMode.Measurement: self.set_frame_color(QColor(200, 100, 0, 100) if not self.stick.is_visible else None) self.setVisible(True) self.clearly_visible_btn.setVisible(not self.stick.is_visible) else: self.setVisible(self.stick.is_visible) def set_show_measurements(self, show: bool): self.show_measurements = show if self.show_measurements: self.stick_label_text.setText(str(self.stick.snow_height_cm) if self.stick.snow_height_cm >= 0 else "n/a") else: self.stick_label_text.setText(self.stick.label) self.update() def handle_zero(self): self.measured_height = 0 self.stick.set_snow_height_px(0) self.measurement_corrected.emit(self) def reset_d_btns(self): self.zero_btn.set_default_state()
class Node(QGraphicsEllipseItem): def __init__(self, metadata, x_y): """ Create node in the graph scene :param dict metadata: Node metadata :param x_y: Position of the node """ # unpack tuple x, y = x_y super(Node, self).__init__() self.metadata = metadata self.id = metadata['id'] self.status_wallet = self.metadata['status'] & NODE_STATUS_HIGHLIGHTED self.status_member = not self.metadata['status'] & NODE_STATUS_OUT self.text = self.metadata['text'] self.setToolTip(self.metadata['tooltip']) self.arcs = [] self.menu = None self.action_sign = None self.action_transaction = None self.action_contact = None self.action_show_member = None # color around ellipse outline_color = QColor('grey') outline_style = Qt.SolidLine outline_width = 1 if self.status_wallet: outline_color = QColor('black') outline_width = 2 if not self.status_member: outline_color = QColor('red') outline_style = Qt.SolidLine self.setPen(QPen(outline_color, outline_width, outline_style)) # text inside ellipse self.text_item = QGraphicsSimpleTextItem(self) self.text_item.setText(self.text) text_color = QColor('grey') if self.status_wallet == NODE_STATUS_HIGHLIGHTED: text_color = QColor('black') self.text_item.setBrush(QBrush(text_color)) # center ellipse around text self.setRect(0, 0, self.text_item.boundingRect().width() * 2, self.text_item.boundingRect().height() * 2) # set anchor to the center self.setTransform(QTransform().translate( -self.boundingRect().width() / 2.0, -self.boundingRect().height() / 2.0)) self.setPos(x, y) # center text in ellipse self.text_item.setPos(self.boundingRect().width() / 4.0, self.boundingRect().height() / 4.0) # create gradient inside the ellipse gradient = QRadialGradient( QPointF(0, self.boundingRect().height() / 4), self.boundingRect().width()) gradient.setColorAt(0, QColor('white')) gradient.setColorAt(1, QColor('darkgrey')) self.setBrush(QBrush(gradient)) # cursor change on hover self.setAcceptHoverEvents(True) self.setZValue(1) def mousePressEvent(self, event: QMouseEvent): """ Click on mouse button :param event: mouse event """ if event.button() == Qt.LeftButton: # trigger scene signal self.scene().node_clicked.emit(self.metadata) def hoverEnterEvent(self, event: QGraphicsSceneHoverEvent): """ Mouse enter on node zone :param event: scene hover event """ self.setCursor(Qt.ArrowCursor) def contextMenuEvent(self, event: QGraphicsSceneContextMenuEvent): """ Right click on node to show node menu Except on wallet node :param event: scene context menu event """ # no menu on the wallet node if self.status_wallet: return None # create node context menus self.menu = QMenu() # action show member QT_TRANSLATE_NOOP('WoT.Node', 'Informations') self.action_show_member = QAction( QCoreApplication.translate('WoT.Node', 'Informations'), self.scene()) self.menu.addAction(self.action_show_member) self.action_show_member.triggered.connect(self.member_action) # action add identity as contact QT_TRANSLATE_NOOP('WoT.Node', 'Add as contact') self.action_contact = QAction( QCoreApplication.translate('WoT.Node', 'Add as contact'), self.scene()) self.menu.addAction(self.action_contact) self.action_contact.triggered.connect(self.contact_action) # action transaction toward identity QT_TRANSLATE_NOOP('WoT.Node', 'Send money') self.action_transaction = QAction( QCoreApplication.translate('WoT.Node', 'Send money'), self.scene()) self.menu.addAction(self.action_transaction) self.action_transaction.triggered.connect(self.transaction_action) # action sign identity QT_TRANSLATE_NOOP('WoT.Node', 'Certify identity') self.action_sign = QAction( QCoreApplication.translate('WoT.Node', 'Certify identity'), self.scene()) self.menu.addAction(self.action_sign) self.action_sign.triggered.connect(self.sign_action) # run menu self.menu.exec(event.screenPos()) def add_arc(self, arc): """ Add arc to the arc list :param arc: Arc """ self.arcs.append(arc) def member_action(self): """ Transaction action to identity node """ # trigger scene signal self.scene().node_member.emit(self.metadata) def contact_action(self): """ Transaction action to identity node """ # trigger scene signal self.scene().node_contact.emit(self.metadata) def sign_action(self): """ Sign identity node """ # trigger scene signal self.scene().node_signed.emit(self.metadata) def transaction_action(self): """ Transaction action to identity node """ # trigger scene signal self.scene().node_transaction.emit(self.metadata)
class SocketRow(QGraphicsWidget): def __init__(self, qt_node, pin): super(SocketRow, self).__init__() assert qt_node is not None self.setParentItem(qt_node) self._parent_node = weakref.ref(qt_node) self._pin = pin self._spacerConstant = 5.0 self._label = QGraphicsSimpleTextItem(self) self._socket = None self._outputHook = None socket_colour = QColor(*pin.colour) socket_type = pin.shape if pin.io_type == "input": self._socket = QtSocket(self, "input", socket_type) self._socket.setColor(socket_colour) else: self._socket = QtSocket(self, "output", socket_type) self._socket.setColor(socket_colour) self.setLabelColor(self.defaultColor()) self.setLabelText(self._pin.name) self._socket.setVisible(True) def parentNode(self): return self._parent_node() def pin(self): return self._pin def socket(self): return self._socket def defaultColor(self): return self._parent_node().labelColor() def labelColor(self): return self._label.brush().color() def setLabelColor(self, color): self._label.setBrush(color) def labelText(self): return self._label.text() def setLabelText(self, text): self._label.setText(text) def refresh(self): # Update cosmetics colour = QColor(*self._pin.colour) self._socket.setColor(colour) self._socket.setShape(self._pin.shape) self._socket.update() def updateLayout(self): height = self._label.boundingRect().height() hook = self._socket if hook.mode() == "output": hook_y_pos = (height - hook.boundingRect().height()) / 2.0 else: hook_y_pos = (height - hook.boundingRect().height()) / 2.0 hook.setPos(0.0, hook_y_pos) input_width = self._spacerConstant * 2.0 self._label.setPos(input_width + self._spacerConstant, 0) if hook.mode() == "output": hook.setPos(self._label.pos().x() + self._label.boundingRect().width() + self._spacerConstant, hook_y_pos) self.resize(hook.pos().x() + hook.boundingRect().width(), height) else: self.resize(self._label.pos().x() + self._label.boundingRect().width(), height) def onDeleted(self): if self._socket: self._socket.onDeleted()
class VCTemporalSeries(VCCommons): def __init__(self): VCCommons.__init__(self) self.__chart = QChart() #After setChart you must call it with chart() self.customContextMenuRequested.connect( self.on_customContextMenuRequested) self._allowHideSeries = True #Axis cration self.axisX = QDateTimeAxis() self.axisX.setTickCount(8) self.axisX.setFormat("yyyy-MM") self.maxx = None self.maxy = None self.minx = None self.miny = None self.__ohclduration = eOHCLDuration.Day self.axisY = QValueAxis() self.axisY.setLabelFormat("%i") self.setRenderHint(QPainter.Antialiasing) self.series = [] self.popup = MyPopup(self) def appendCandlestickSeries(self, name): ls = QCandlestickSeries() ls.setName(name) ls.setIncreasingColor(QColor(Qt.green)) ls.setDecreasingColor(QColor(Qt.red)) self.series.append(ls) return ls def appendCandlestickSeriesData(self, ls, dtaware, ope, hig, clo, low): x = dtaware2epochms(dtaware) ls.append( QCandlestickSet(float(ope), float(hig), float(clo), float(low), x)) if self.maxy == None: self.maxy = float(hig) self.miny = float(low) self.maxx = x self.minx = x if hig > self.maxy: self.maxy = float(hig) if low < self.miny: self.miny = float(low) if x > self.maxx: self.maxx = x if x < self.minx: self.minx = x def setOHCLDuration(self, ohclduration): self.__ohclduration = ohclduration def appendScatterSeries(self, name): ls = QScatterSeries() ls.setName(name) self.series.append(ls) return ls def appendScatterSeriesData(self, ls, x, y): self.appendTemporalSeriesData(ls, x, y) def setAxisFormat(self, axis, min, max, type, zone=None): """ type=0 #Value type=1 # Datetime if zone=None remains in UTC, zone is a zone object. """ if type == 0: if max - min <= Decimal(0.01): axis.setLabelFormat("%.4f") elif max - min <= Decimal(100): axis.setLabelFormat("%.2f") else: axis.setLabelFormat("%i") elif type == 1: max = epochms2dtaware(max) #UTC aware min = epochms2dtaware(min) if max - min < timedelta(days=1): axis.setFormat("hh:mm") else: axis.setFormat("yyyy-MM-dd") def setAllowHideSeries(self, boolean): self._allowHideSeries = boolean def appendTemporalSeries(self, name): ls = QLineSeries() ls.setName(name) self.series.append(ls) return ls def appendTemporalSeriesData(self, ls, x, y): """ x is a datetime zone aware """ x = dtaware2epochms(x) x = float(x) y = float(y) ls.append(x, y) if self.maxy == None: #Gives first maxy and miny self.maxy = y * 1.01 self.miny = y * 0.99 self.maxx = x * 1.01 self.minx = x * 0.99 if y > self.maxy: self.maxy = y if y < self.miny: self.miny = y if x > self.maxx: self.maxx = x if x < self.minx: self.minx = x def mouseMoveEvent(self, event): ##Sets the place of the popup in the windows to avoid getout of the screen ##frmshow can be a frmShowCasilla or a frmShowFicha def placePopUp(): resultado = QPoint(event.x() + 15, event.y() + 15) if event.x() > self.width() - self.popup.width() - 15: resultado.setX(event.x() - self.popup.width() - 15) if event.y() > self.height() - self.popup.height() - 15: resultado.setY(event.y() - self.popup.height() - 15) return resultado def showCurrentPosition(): if hasattr(self, "qgstiCurrentX") == False: self.qgstiCurrentX = QGraphicsSimpleTextItem(self.chart()) self.qgstiCurrentY = QGraphicsSimpleTextItem(self.chart()) self.qgstiCurrentX.setPos(event.pos().x(), maxY - 10) self.qgstiCurrentY.setPos(self.chart().size().width() - 47, event.pos().y()) self.qgstiCurrentX.setText(str(epochms2dtaware(xVal).date())) self.qgstiCurrentY.setText(str(round(yVal, 2))) # --------------------------------------- QChartView.mouseMoveEvent(self, event) xVal = self.chart().mapToValue(event.pos()).x() yVal = self.chart().mapToValue(event.pos()).y() maxX = self.axisX.max().toMSecsSinceEpoch() minX = self.axisX.min().toMSecsSinceEpoch() maxY = self.axisY.max() minY = self.axisY.min() if xVal <= maxX and xVal >= minX and yVal <= maxY and yVal >= minY: self.popup.move(self.mapToGlobal(placePopUp())) self.popup.refresh(self, xVal, yVal) showCurrentPosition() self.popup.show() else: self.popup.hide() ## Return the value of the serie in x def series_value(self, serie, x): for point in serie.pointsVector(): if point.x() >= x: return point.y() @pyqtSlot() def on_marker_clicked(self): marker = QObject.sender( self ) #Busca el objeto que ha hecho la signal en el slot en el que está conectado, ya que estaban conectados varios objetos a una misma señal marker.series().setVisible(not marker.series().isVisible()) marker.setVisible(True) if marker.series().isVisible(): alpha = 1 else: alpha = 0.5 lbrush = marker.labelBrush() color = lbrush.color() color.setAlphaF(alpha) lbrush.setColor(color) marker.setLabelBrush(lbrush) brush = marker.brush() color = brush.color() color.setAlphaF(alpha) brush.setColor(color) marker.setBrush(brush) pen = marker.pen() color = pen.color() color.setAlphaF(alpha) pen.setColor(color) marker.setPen(pen) ## Used to display chart. You cannot use it twice. close the view widget and create another one def display(self): if self.__chart != None: del self.__chart self.__chart = QChart() self.setChart(self.__chart) if self._animations == True: self.chart().setAnimationOptions(QChart.AllAnimations) else: self.chart().setAnimationOptions(QChart.NoAnimation) self.chart().layout().setContentsMargins(0, 0, 0, 0) self._display_set_title() self.setAxisFormat(self.axisX, self.minx, self.maxx, 1) self.setAxisFormat(self.axisY, self.miny, self.maxy, 0) self.chart().addAxis(self.axisY, Qt.AlignLeft) self.chart().addAxis(self.axisX, Qt.AlignBottom) for s in self.series: self.chart().addSeries(s) s.attachAxis(self.axisX) s.attachAxis(self.axisY) self.axisY.setRange(self.miny, self.maxy) #Legend positions if len(self.chart().legend().markers()) > 6: self.chart().legend().setAlignment(Qt.AlignLeft) else: self.chart().legend().setAlignment(Qt.AlignTop) if self._allowHideSeries == True: for marker in self.chart().legend().markers(): try: marker.clicked.disconnect() except: pass marker.clicked.connect(self.on_marker_clicked) self.repaint() ## Returns a qmenu to be used in other qmenus def qmenu(self, title="Chart options"): menu = QMenu(self) menu.setTitle(self.tr(title)) menu.addAction(self.actionSave) return menu def on_customContextMenuRequested(self, pos): self.qmenu().exec_(self.mapToGlobal(pos))
class CameraView(QGraphicsObject): font: QFont = QFont("monospace", 16) stick_link_requested = pyqtSignal(StickWidget) stick_context_menu = pyqtSignal('PyQt_PyObject', 'PyQt_PyObject') stick_widgets_out_of_sync = pyqtSignal('PyQt_PyObject') visibility_toggled = pyqtSignal() synchronize_clicked = pyqtSignal('PyQt_PyObject') previous_photo_clicked = pyqtSignal('PyQt_PyObject') next_photo_clicked = pyqtSignal('PyQt_PyObject') sync_confirm_clicked = pyqtSignal('PyQt_PyObject') sync_cancel_clicked = pyqtSignal('PyQt_PyObject') first_photo_clicked = pyqtSignal('PyQt_PyObject') enter_pressed = pyqtSignal() def __init__(self, scale: float, parent: Optional[QGraphicsItem] = None): QGraphicsObject.__init__(self, parent) self.current_highlight_color = QColor(0, 0, 0, 0) self.current_timer = -1 self.scaling = scale self.pixmap = QGraphicsPixmapItem(self) self.stick_widgets: List[StickWidget] = [] self.link_cam_text = QGraphicsSimpleTextItem("Link camera...", self) self.link_cam_text.setZValue(40) self.link_cam_text.setVisible(False) self.link_cam_text.setFont(CameraView.font) self.link_cam_text.setPos(0, 0) self.link_cam_text.setPen(QPen(QColor(255, 255, 255, 255))) self.link_cam_text.setBrush(QBrush(QColor(255, 255, 255, 255))) self.show_add_buttons = False self.camera = None self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True) self.show_stick_widgets = False self.setAcceptHoverEvents(True) self.stick_edit_mode = False self.original_pixmap = self.pixmap.pixmap() self.hovered = False self.mode = 0 # TODO make enum Mode self.click_handler = None self.double_click_handler: Callable[[int, int], None] = None self.stick_widget_mode = StickMode.Display self.highlight_animation = QPropertyAnimation(self, b"highlight_color") self.highlight_animation.setEasingCurve(QEasingCurve.Linear) self.highlight_animation.valueChanged.connect( self.handle_highlight_color_changed) self.highlight_rect = QGraphicsRectItem(self) self.highlight_rect.setZValue(4) self.highlight_rect.setPen(QPen(QColor(0, 0, 0, 0))) self.title_btn = Button('btn_title', '', parent=self) self.title_btn.setFlag(QGraphicsItem.ItemIgnoresTransformations, False) self.title_btn.setZValue(5) self.title_btn.setVisible(False) self.sticks_without_width: List[Stick] = [] self.current_image_name: str = '' self.control_widget = ControlWidget(parent=self) self.control_widget.setFlag(QGraphicsItem.ItemIgnoresTransformations, False) self.control_widget.setVisible(True) self._connect_control_buttons() self.image_available = True self.blur_eff = QGraphicsBlurEffect() self.blur_eff.setBlurRadius(5.0) self.blur_eff.setEnabled(False) self.pixmap.setGraphicsEffect(self.blur_eff) self.overlay_message = QGraphicsSimpleTextItem('not available', parent=self) font = self.title_btn.font font.setPointSize(48) self.overlay_message.setFont(font) self.overlay_message.setBrush(QBrush(QColor(200, 200, 200, 200))) self.overlay_message.setPen(QPen(QColor(0, 0, 0, 200), 2.0)) self.overlay_message.setVisible(False) self.overlay_message.setZValue(6) self.stick_box = QGraphicsRectItem(parent=self) self.stick_box.setFlag(QGraphicsItem.ItemIsMovable, True) self.stick_box.setVisible(False) self.stick_box_start_pos = QPoint() def _connect_control_buttons(self): self.control_widget.synchronize_btn.clicked.connect( lambda: self.synchronize_clicked.emit(self)) self.control_widget.prev_photo_btn.clicked.connect( lambda: self.previous_photo_clicked.emit(self)) self.control_widget.next_photo_btn.clicked.connect( lambda: self.next_photo_clicked.emit(self)) self.control_widget.accept_btn.clicked.connect( lambda: self.sync_confirm_clicked.emit(self)) self.control_widget.cancel_btn.clicked.connect( lambda: self.sync_cancel_clicked.emit(self)) self.control_widget.first_photo_btn.clicked.connect( lambda: self.first_photo_clicked.emit(self)) def paint(self, painter: QPainter, option: PyQt5.QtWidgets.QStyleOptionGraphicsItem, widget: QWidget): if self.pixmap.pixmap().isNull(): return painter.setRenderHint(QPainter.Antialiasing, True) if self.show_stick_widgets: brush = QBrush(QColor(255, 255, 255, 100)) painter.fillRect(self.boundingRect(), brush) if self.mode and self.hovered: pen = QPen(QColor(0, 125, 200, 255)) pen.setWidth(4) painter.setPen(pen) def boundingRect(self) -> PyQt5.QtCore.QRectF: return self.pixmap.boundingRect().united( self.title_btn.boundingRect().translated(self.title_btn.pos())) def initialise_with(self, camera: Camera): if self.camera is not None: self.camera.stick_added.disconnect(self.handle_stick_created) self.camera.sticks_added.disconnect(self.handle_sticks_added) self.camera.stick_removed.disconnect(self.handle_stick_removed) self.camera.sticks_removed.disconnect(self.handle_sticks_removed) self.camera.stick_changed.disconnect(self.handle_stick_changed) self.camera = camera self.prepareGeometryChange() self.set_image(camera.rep_image, Path(camera.rep_image_path).name) self.title_btn.set_label(self.camera.folder.name) self.title_btn.set_height(46) self.title_btn.fit_to_contents() self.title_btn.set_width(int(self.boundingRect().width())) self.title_btn.setPos(0, self.boundingRect().height()) self.control_widget.title_btn.set_label(self.camera.folder.name) self.camera.stick_added.connect(self.handle_stick_created) self.camera.sticks_added.connect(self.handle_sticks_added) self.camera.stick_removed.connect(self.handle_stick_removed) self.camera.sticks_removed.connect(self.handle_sticks_removed) self.camera.stick_changed.connect(self.handle_stick_changed) self.control_widget.set_font_height(32) self.control_widget.set_widget_height( self.title_btn.boundingRect().height()) self.control_widget.set_widget_width(int(self.boundingRect().width())) self.control_widget.setPos(0, self.pixmap.boundingRect().height() ) #self.boundingRect().height()) self.control_widget.set_mode('view') self.update_stick_widgets() def set_image(self, img: Optional[np.ndarray] = None, image_name: Optional[str] = None): if img is None: self.show_overlay_message('not available') return self.show_overlay_message(None) self.prepareGeometryChange() barray = QByteArray(img.tobytes()) image = QImage(barray, img.shape[1], img.shape[0], QImage.Format_BGR888) self.original_pixmap = QPixmap.fromImage(image) self.pixmap.setPixmap(self.original_pixmap) self.highlight_rect.setRect(self.boundingRect()) self.current_image_name = image_name def update_stick_widgets(self): stick_length = 60 for stick in self.camera.sticks: sw = StickWidget(stick, self.camera, self) sw.set_mode(self.stick_widget_mode) self.connect_stick_widget_signals(sw) self.stick_widgets.append(sw) stick_length = stick.length_cm self.update_stick_box() self.scene().update() def scale_item(self, factor: float): self.prepareGeometryChange() pixmap = self.original_pixmap.scaledToHeight( int(self.original_pixmap.height() * factor)) self.pixmap.setPixmap(pixmap) self.__update_title() def set_show_stick_widgets(self, value: bool): for sw in self.stick_widgets: sw.setVisible(value) self.scene().update() def hoverEnterEvent(self, e: QGraphicsSceneHoverEvent): self.hovered = True self.scene().update(self.sceneBoundingRect()) def hoverLeaveEvent(self, e: QGraphicsSceneHoverEvent): self.hovered = False self.scene().update(self.sceneBoundingRect()) def mousePressEvent(self, e: QGraphicsSceneMouseEvent): super().mousePressEvent(e) def mouseReleaseEvent(self, e: QGraphicsSceneMouseEvent): if self.mode == 1: self.click_handler(self.camera) def mouseDoubleClickEvent(self, event: QGraphicsSceneMouseEvent): if self.stick_widget_mode == StickMode.EditDelete: x = event.pos().toPoint().x() y = event.pos().toPoint().y() stick = self.camera.create_new_sticks( [(np.array([[x, y - 50], [x, y + 50]]), 3)], self.current_image_name)[ 0] #self.dataset.create_new_stick(self.camera) self.sticks_without_width.append(stick) def set_button_mode(self, click_handler: Callable[[Camera], None], data: str): self.mode = 1 # TODO make a proper ENUM self.click_handler = lambda c: click_handler(c, data) def set_display_mode(self): self.mode = 0 # TODO make a proper ENUM self.click_handler = None def _remove_stick_widgets(self): for sw in self.stick_widgets: sw.setParentItem(None) self.scene().removeItem(sw) sw.deleteLater() self.stick_widgets.clear() def handle_stick_created(self, stick: Stick): if stick.camera_id != self.camera.id: return sw = StickWidget(stick, self.camera, self) sw.set_mode(self.stick_widget_mode) self.connect_stick_widget_signals(sw) self.stick_widgets.append(sw) self.stick_widgets_out_of_sync.emit(self) self.update() def handle_stick_removed(self, stick: Stick): if stick.camera_id != self.camera.id: return stick_widget = next( filter(lambda sw: sw.stick.id == stick.id, self.stick_widgets)) self.disconnect_stick_widget_signals(stick_widget) self.stick_widgets.remove(stick_widget) stick_widget.setParentItem(None) self.scene().removeItem(stick_widget) stick_widget.deleteLater() self.update() def handle_sticks_removed(self, sticks: List[Stick]): if sticks[0].camera_id != self.camera.id: return for stick in sticks: to_remove: StickWidget = None for sw in self.stick_widgets: if sw.stick.id == stick.id: to_remove = sw break self.stick_widgets.remove(to_remove) to_remove.setParentItem(None) if self.scene() is not None: self.scene().removeItem(to_remove) to_remove.deleteLater() self.update() def handle_sticks_added(self, sticks: List[Stick]): if len(sticks) == 0: return if sticks[0].camera_id != self.camera.id: return for stick in sticks: sw = StickWidget(stick, self.camera, self) sw.set_mode(self.stick_widget_mode) self.connect_stick_widget_signals(sw) self.stick_widgets.append(sw) self.update_stick_box() self.stick_widgets_out_of_sync.emit(self) self.update() def connect_stick_widget_signals(self, stick_widget: StickWidget): stick_widget.delete_clicked.connect( self.handle_stick_widget_delete_clicked) stick_widget.stick_changed.connect(self.handle_stick_widget_changed) stick_widget.link_initiated.connect(self.handle_stick_link_initiated) stick_widget.right_clicked.connect( self.handle_stick_widget_context_menu) def disconnect_stick_widget_signals(self, stick_widget: StickWidget): stick_widget.delete_clicked.disconnect( self.handle_stick_widget_delete_clicked) stick_widget.stick_changed.disconnect(self.handle_stick_widget_changed) stick_widget.link_initiated.disconnect( self.handle_stick_link_initiated) stick_widget.right_clicked.disconnect( self.handle_stick_widget_context_menu) def handle_stick_widget_delete_clicked(self, stick: Stick): self.camera.remove_stick(stick) def set_stick_widgets_mode(self, mode: StickMode): self.stick_widget_mode = mode for sw in self.stick_widgets: sw.set_mode(mode) self.set_stick_edit_mode(mode == StickMode.Edit) def handle_stick_widget_changed(self, stick_widget: StickWidget): self.camera.stick_changed.emit(stick_widget.stick) def handle_stick_changed(self, stick: Stick): if stick.camera_id != self.camera.id: return sw = next( filter(lambda _sw: _sw.stick.id == stick.id, self.stick_widgets)) sw.adjust_line() sw.update_tooltip() def handle_stick_link_initiated(self, stick_widget: StickWidget): self.stick_link_requested.emit(stick_widget) def get_top_left(self) -> QPointF: return self.sceneBoundingRect().topLeft() def get_top_right(self) -> QPointF: return self.sceneBoundingRect().topRight() def highlight(self, color: Optional[QColor]): if color is None: self.highlight_animation.stop() self.highlight_rect.setVisible(False) return alpha = color.alpha() color.setAlpha(0) self.highlight_animation.setStartValue(color) self.highlight_animation.setEndValue(color) color.setAlpha(alpha) self.highlight_animation.setKeyValueAt(0.5, color) self.highlight_animation.setDuration(2000) self.highlight_animation.setLoopCount(-1) self.highlight_rect.setPen(QPen(color)) self.highlight_rect.setVisible(True) self.highlight_animation.start() @pyqtProperty(QColor) def highlight_color(self) -> QColor: return self.current_highlight_color @highlight_color.setter def highlight_color(self, color: QColor): self.current_highlight_color = color def handle_highlight_color_changed(self, color: QColor): self.highlight_rect.setBrush(QBrush(color)) self.update() def handle_stick_widget_context_menu(self, sender: Dict[str, StickWidget]): self.stick_context_menu.emit(sender['stick_widget'], self) def show_overlay_message(self, msg: Optional[str]): if msg is None: self.overlay_message.setVisible(False) self.blur_eff.setEnabled(False) return self.overlay_message.setText(msg) self.overlay_message.setPos( self.pixmap.boundingRect().center() - QPointF(0.5 * self.overlay_message.boundingRect().width(), 0.5 * self.overlay_message.boundingRect().height())) self.overlay_message.setVisible(True) self.blur_eff.setEnabled(True) def show_status_message(self, msg: Optional[str]): if msg is None: self.control_widget.set_title_text(self.camera.folder.name) else: self.control_widget.set_title_text(msg) def update_stick_box(self): left = 9000 right = 0 top = 9000 bottom = -1 for stick in self.camera.sticks: left = min(left, min(stick.top[0], stick.bottom[0])) right = max(right, max(stick.top[0], stick.bottom[0])) top = min(top, min(stick.top[1], stick.bottom[1])) bottom = max(bottom, max(stick.top[1], stick.bottom[1])) left -= 100 right += 100 top -= 100 bottom += 100 self.stick_box.setRect(left, top, right - left, bottom - top) pen = QPen(QColor(0, 100, 200, 200)) pen.setWidth(2) pen.setStyle(Qt.DashLine) self.stick_box.setPen(pen) def set_stick_edit_mode(self, is_edit: bool): if is_edit: self.update_stick_box() self.stick_box_start_pos = self.stick_box.pos() for sw in self.stick_widgets: sw.setParentItem(self.stick_box) else: offset = self.stick_box.pos() - self.stick_box_start_pos for sw in self.stick_widgets: stick = sw.stick stick.translate(np.array([int(offset.x()), int(offset.y())])) sw.setParentItem(self) sw.set_stick(stick) self.stick_box.setParentItem(None) self.stick_box = QGraphicsRectItem(self) self.stick_box.setFlag(QGraphicsItem.ItemIsMovable, True) self.stick_box.setVisible(False) self.stick_box.setVisible(is_edit) def keyPressEvent(self, event: QKeyEvent) -> None: pass def keyReleaseEvent(self, event: QKeyEvent) -> None: if event.key() in [Qt.Key_Right, Qt.Key_Tab, Qt.Key_Space]: self.control_widget.next_photo_btn.click_button(True) elif event.key() in [Qt.Key_Left]: self.control_widget.prev_photo_btn.click_button(True) elif event.key() == Qt.Key_S: self.enter_pressed.emit()
class ClearanceWidthGraph(BaseGraphic): def __init__(self, *args): super(ClearanceWidthGraph, self).__init__(*args) self.dimension_analysis = self.section_analyzer.dimension_analysis self.clearance_analysis = self.section_analyzer.clearance_analysis self.min_horizontal_clearance = self.dimension_analysis.min_horizontal_clearance self.graph_zero = [None, None] self.graph_end = [None, None] self.clearance_label = QGraphicsSimpleTextItem() self.addToGroup(self.clearance_label) self.clearance_label.setZValue(1.0) self.init_dimension() def init_dimension(self): super(ClearanceWidthGraph, self).init_dimension() height_start = self.dimension_analysis.bounding_rect[2] height_end = self.dimension_analysis.bounding_rect[3] self.content_height = (height_end - height_start) * self.height_multiplier self.update_graph_size() self.create_axis() self.create_scale() self.add_clearance_graph() def create_axis(self): bounding_end = abs(self.dimension_analysis.bounding_rect[3]) bounding_start = abs(self.dimension_analysis.bounding_rect[2]) pen = QPen() pen.setWidthF(0.5) # horizontal line self.graph_zero[0] = self.position[0] + self.margin - self.line_extend self.graph_zero[1] = self.position[1] + bounding_start * self.height_multiplier + self.margin self.graph_end[0] = self.graph_zero[0] + self.content_width + self.line_extend self.graph_end[1] = self.graph_zero[1] line_item_horizontal = QGraphicsLineItem(self.graph_zero[0], self.graph_zero[1], self.graph_end[0], self.graph_end[1]) line_item_horizontal.setPen(pen) self.addToGroup(line_item_horizontal) center = (self.graph_zero[0] + self.line_extend), self.graph_zero[1] y_top = center[1] - (bounding_start*self.height_multiplier) y_bottom = center[1]+(bounding_end*self.height_multiplier) line_item_vertical = QGraphicsLineItem(center[0], y_top, center[0], y_bottom) line_item_vertical.setPen(pen) self.addToGroup(line_item_vertical) pen_thin = QPen() pen_thin.setWidthF(0.2) start_graph = center[1] - 10 while start_graph > center[1] - bounding_start * self.height_multiplier: line_item_horizontal = QGraphicsLineItem(self.graph_zero[0], start_graph, self.graph_end[0], start_graph) line_item_horizontal.setPen(pen_thin) line_item_horizontal.setZValue(-0.5) self.addToGroup(line_item_horizontal) start_graph -= 10 start_graph = center[1] + 10 while start_graph < center[1] + bounding_end * self.height_multiplier: line_item_horizontal = QGraphicsLineItem(self.graph_zero[0], start_graph, self.graph_end[0], start_graph) line_item_horizontal.setPen(pen_thin) line_item_horizontal.setZValue(-0.5) self.addToGroup(line_item_horizontal) start_graph += 10 def create_scale(self): section_num = len(self.section_analyzer.section_list) section_distance = self.section_analyzer.section_distance total_distance = section_num * section_distance div = int(Math.integer_division(total_distance, 1.0)) print(total_distance) for i in range(div+1): x = self.graph_zero[0] + i * self.length_multiplier + self.line_extend y = self.graph_zero[1] scale_text = QGraphicsSimpleTextItem("%.2f" % float(i)) scale_text.setPos(x, y) self.addToGroup(scale_text) start_to_zero = self.graph_zero[1] - self.position[1] step = abs(start_to_zero) // 25 x = self.graph_zero[0] - 15 y = self.graph_zero[1] for i in range(int(step)-1): if i > 0: value = i * 25 / 100 scene_y = y - i * 25 text = QGraphicsSimpleTextItem("-%.2f" % value) text.setPos(x, scene_y) self.addToGroup(text) start_to_zero = self.position[1] + self.height - self.graph_zero[1] step = abs(start_to_zero) // 25 x = self.graph_zero[0] - 15 y = self.graph_zero[1] for i in range(int(step)-1): if i > 0: value = i * 25 / 100 scene_y = y + i * 25 text = QGraphicsSimpleTextItem("%.2f" % value) text.setPos(x, scene_y) self.addToGroup(text) def add_clearance_graph(self): print("-----------------------------------------") horizontal_clearance = self.clearance_analysis.horizontal_clearance x_init = self.graph_zero[0] + self.line_extend y_init = self.graph_zero[1] for i in range(len(horizontal_clearance)): clearance_points = horizontal_clearance[i] x = x_init + i * self.dimension_analysis.section_distance * self.length_multiplier left = -self.dimension_analysis.domain_length right = self.dimension_analysis.domain_length if clearance_points[0]: left = clearance_points[0] if clearance_points[1]: right = clearance_points[1] clearance = right - left y_top = y_init + left * self.height_multiplier y_bottom = y_init + right * self.height_multiplier pen_red = QPen() red = Color.create_qcolor_from_rgb_tuple(Color.red) pen_red.setColor(red) pen_green = QPen() green = Color.create_qcolor_from_rgb_tuple(Color.green) pen_green.setColor(green) line = QGraphicsLineItem(x, y_top, x, y_bottom) if clearance < self.min_horizontal_clearance: line.setPen(pen_red) else: line.setPen(pen_green) self.addToGroup(line) pass def set_distance_pointer(self, distance): horizontal_clearance = self.clearance_analysis.horizontal_clearance super(ClearanceWidthGraph, self).set_distance_pointer(distance) index = int(distance/self.section_distance) clearance = None if index < len(horizontal_clearance): clearance = horizontal_clearance[index] x = self.distance_pointer.line().x1() y = self.distance_pointer.line().y1() + (self.distance_pointer.line().y2() - self.distance_pointer.line().y1())/8 self.clearance_label.setPos(x, y) if clearance: if clearance[1] and clearance[0]: distance = clearance[1] - clearance[0] self.clearance_label.setText("clearance = %.2f" % distance)
class QView(QGraphicsView): def __init__(self, parent=None): super(QView, self).__init__(QGraphicsScene(), parent) self.m_coordX = None self.m_coordY = None self.m_chart = None self.m_tooltip = None self.m_callouts = [] self.setMouseTracking(True) self.data = None self.__dragged = False self.__pos = QPoint(0, 0) def combinedata(self, data): self.data = data def addQchart(self, chart): self.m_chart = chart self.scene().addItem(self.m_chart) self.m_coordX = QGraphicsSimpleTextItem(self.m_chart) self.m_coordX.setPos(self.m_chart.size().width() / 2 - 50, self.m_chart.size().height()) self.m_coordX.setText("X: ") self.m_coordY = QGraphicsSimpleTextItem(self.m_chart) self.m_coordY.setPos(self.m_chart.size().width() / 2 + 50, self.m_chart.size().height()) self.m_coordY.setText("Y: ") for series in self.m_chart.series(): series.hovered.connect(self.tooltip) series.doubleClicked.connect(self.keepCallout) def resizeEvent(self, event): if (self.scene()): self.scene().setSceneRect( QRectF(QPoint(0, 0), QSizeF(event.size()))) self.m_chart.resize(QSizeF(event.size())) self.m_coordX.setPos(self.m_chart.size().width() / 2 - 50, self.m_chart.size().height() - 20) self.m_coordY.setPos(self.m_chart.size().width() / 2 + 50, self.m_chart.size().height() - 20) for callout in self.m_callouts: callout.updateGeometry() super(QView, self).resizeEvent(event) def keyPressEvent(self, event): if self.m_chart == None: return else: self.m_chart.setAnimationOptions(QChart.SeriesAnimations) if event.key() == Qt.Key_Plus: self.m_chart.zoomIn() elif event.key() == Qt.Key_Minus: self.m_chart.zoomOut() elif event.key() == Qt.Key_Up: self.m_chart.scroll(0, 10) elif event.key() == Qt.Key_Left: self.m_chart.scroll(-10, 0) elif event.key() == Qt.Key_Right: self.m_chart.scroll(10, 0) elif event.key() == Qt.Key_Down: self.m_chart.scroll(0, -10) def mouseMoveEvent(self, event): self.m_coordX.setText("X: {}".format( round(self.m_chart.mapToValue(event.pos()).x(), 3))) self.m_coordY.setText("Y: {}".format( round(self.m_chart.mapToValue(event.pos()).y(), 3))) if event.buttons() == Qt.MidButton: value = self.m_chart.mapToValue(event.pos()) pos = (self.m_chart.mapToValue(self.__pos) - value) self.m_chart.scroll(pos.x(), pos.y()) super(QView, self).mouseMoveEvent(event) def mousePressEvent(self, event): if event.buttons() == Qt.MidButton: self.__dragged = True self.__pos = event.pos() def mouseReleaseEvent(self, event): self.__dragged = False def keepCallout(self): self.m_callouts.append(self.m_tooltip) self.m_tooltip = Callout(self.m_chart) def cleanTag(self): del self.m_callouts[:] def tooltip(self, point, state): if self.m_tooltip == None: self.m_tooltip = Callout(self.m_chart) if state: self.m_tooltip.setText("X: {} \nY: {} ".format( round(point.x(), 3), round(point.y(), 3))) self.m_tooltip.setAnchor(point) self.m_tooltip.setZValue(11) self.m_tooltip.updateGeometry() self.m_tooltip.show() else: self.m_tooltip.hide()
class ExplorerNode(BaseNode): def __init__(self, nx_node, center_pos, nx_pos, steps, steps_max, small): """ Create node in the graph scene :param tuple nx_node: Node info :param center_pos: The position of the center node :param nx_pos: Position of the nodes in the graph :param int steps: The steps from the center identity :param int steps_max: The steps max of the graph :param bool small: Small dots for big networks """ super().__init__(nx_node, nx_pos) self.steps = steps self.steps_max = steps_max self.highlighted = False self.status_sentry = False if small: self.setRect(0, 0, 10, 10) self.text_item = None else: # text inside ellipse self.text_item = QGraphicsSimpleTextItem(self) self.text_item.setText(self.text) # center ellipse around text self.setRect(0, 0, self.text_item.boundingRect().width() * 2, self.text_item.boundingRect().height() * 2) # center text in ellipse self.text_item.setPos(self.boundingRect().width() / 4.0, self.boundingRect().height() / 4.0) # set anchor to the center self.setTransform(QTransform().translate( -self.boundingRect().width() / 2.0, -self.boundingRect().height() / 2.0)) # cursor change on hover self.setAcceptHoverEvents(True) self.setZValue(1) # animation and moves self.timeline = None self.loading_timer = QTimer() self.loading_timer.timeout.connect(self.next_tick) self.loading_counter = 0 self._refresh_colors() self.setPos(center_pos) self.move_to(nx_pos) def update_metadata(self, metadata): super().update_metadata(metadata) self.status_sentry = self.metadata[ 'is_sentry'] if 'is_sentry' in self.metadata else False self._refresh_colors() def _refresh_colors(self): """ Refresh elements in the node """ # color around ellipse outline_color = QColor('grey') outline_style = Qt.SolidLine outline_width = 1 if self.status_wallet: outline_width = 2 if not self.status_member: outline_color = QColor('red') if self.status_sentry: outline_color = QColor('black') outline_width = 3 self.setPen(QPen(outline_color, outline_width, outline_style)) if self.highlighted: text_color = QColor('grey') else: text_color = QColor('black') if self.status_wallet == NodeStatus.HIGHLIGHTED: text_color = QColor('grey') if self.text_item: self.text_item.setBrush(QBrush(text_color)) # create gradient inside the ellipse gradient = QRadialGradient( QPointF(0, self.boundingRect().height() / 4), self.boundingRect().width()) color = QColor() color.setHsv(120 - 60 / self.steps_max * self.steps, 180 + 50 / self.steps_max * self.steps, 60 + 170 / self.steps_max * self.steps) if self.highlighted: color = color.darker(200) color = color.lighter( math.fabs(math.sin(self.loading_counter / 100 * math.pi) * 100) + 100) gradient.setColorAt(0, color) gradient.setColorAt(1, color.darker(150)) self.setBrush(QBrush(gradient)) def move_to(self, nx_pos): """ Move to corresponding position :param nx_pos: :return: """ origin_x = self.x() origin_y = self.y() final_x = nx_pos[self.id][0] final_y = nx_pos[self.id][1] def frame_move(frame): value = self.timeline.valueForTime(self.timeline.currentTime()) x = origin_x + (final_x - origin_x) * value y = origin_y + (final_y - origin_y) * value self.setPos(x, y) if self.scene(): self.scene().node_moved.emit(self.id, x, y) def timeline_ends(): self.setPos(final_x, final_y) self.timeline = None # Remember to hold the references to QTimeLine and QGraphicsItemAnimation instances. # They are not kept anywhere, even if you invoke QTimeLine.start(). self.timeline = QTimeLine(1000) self.timeline.setFrameRange(0, 100) self.timeline.frameChanged.connect(frame_move) self.timeline.finished.connect(timeline_ends) self.timeline.start() def highlight(self): """ Highlight the edge in the scene """ self.highlighted = True self._refresh_colors() self.update(self.boundingRect()) def neutralize(self): """ Neutralize the edge in the scene """ self.highlighted = False self._refresh_colors() self.update(self.boundingRect()) def start_loading_animation(self): """ Neutralize the edge in the scene """ if not self.loading_timer.isActive(): self.loading_timer.start(10) def stop_loading_animation(self): """ Neutralize the edge in the scene """ self.loading_timer.stop() self.loading_counter = 100 self._refresh_colors() self.update(self.boundingRect()) def next_tick(self): """ Next tick :return: """ self.loading_counter += 1 self.loading_counter %= 100 self._refresh_colors() self.update(self.boundingRect())
class Tile(QGraphicsRectItem): def __init__(self, letter, points, coords, scale, on_position_change=None, move_to_rack=None, parent=None): QGraphicsRectItem.__init__(self, MARGIN, MARGIN, SQUARE_SIZE - 2 * MARGIN, SQUARE_SIZE - 2 * MARGIN, parent) if on_position_change: self.on_position_change = on_position_change if move_to_rack: self.move_to_rack = move_to_rack self.setFlag(QGraphicsItem.ItemIsMovable, True) self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True) self.points = points self.letter = letter self.scale = scale self.setScale(self.scale) self.setZValue(3) self.setPen(QPen(YELLOW2, 0)) self.setBrush(QBrush(YELLOW)) tile_letter = letter.upper() self.letter_item = QGraphicsSimpleTextItem(tile_letter, self) self.font = QFont("Verdana", 20) if not points: self.font.setBold(True) font_metrics = QFontMetrics(self.font) height = font_metrics.height() width = font_metrics.width(tile_letter) self.letter_item.setX((SQUARE_SIZE - width) / 2 - MARGIN) self.letter_item.setY((SQUARE_SIZE - height) / 2 - MARGIN) self.letter_item.setFont(self.font) self.letter_item.setBrush(QBrush(SEA_GREEN)) self.shadow = QGraphicsRectItem(MARGIN * 2, MARGIN * 2, SQUARE_SIZE, SQUARE_SIZE, self) self.shadow.setFlag(QGraphicsItem.ItemStacksBehindParent) self.shadow.setBrush(QBrush(TRANSPARENT_BLACK)) self.shadow.setPen(QPen(TRANSPARENT, 0)) self.shadow.hide() self.setPos(coords.x * SQUARE_SIZE * scale, coords.y * SQUARE_SIZE * scale) self.coords = None self.update_coords() self.old_position = None self.old_coords = None self.is_placed = False if points: self.add_points() def __str__(self): return self.letter def add_points(self): points = QGraphicsSimpleTextItem(str(self.points), self) font = QFont("Verdana", 10) font_metrics = QFontMetrics(font) height = font_metrics.height() width = font_metrics.width(str(self.points)) points.setFont(font) points.setBrush(QBrush(SEA_GREEN)) points.setX(SQUARE_SIZE - MARGIN - width) points.setY(SQUARE_SIZE - MARGIN - height) def resize(self, scale): self.scale = scale self.setScale(scale) self.setPos(self.coords.x * SQUARE_SIZE * scale, self.coords.y * SQUARE_SIZE * scale) def change_to_blank(self, new_letter): if self.letter == BLANK: self.letter = new_letter self.letter_item.setText(new_letter.upper()) self.font.setBold(True) font_metrics = QFontMetrics(self.font) height = font_metrics.height() width = font_metrics.width(self.letter) self.letter_item.setFont(self.font) self.letter_item.setX((SQUARE_SIZE - width) / 2 - MARGIN) self.letter_item.setY((SQUARE_SIZE - height) / 2 - MARGIN) def change_back(self): self.letter = BLANK self.letter_item.setText(BLANK) def get_letter_and_points(self): return self.letter, self.points def mousePressEvent(self, event): if self.is_placed: return if event.button() == Qt.RightButton: return self.setScale(self.scale * 1.1) self.setZValue(10) self.old_position = self.pos() self.old_coords = self.coords self.setPos(self.x() - 2 * MARGIN, self.y() - 2 * MARGIN) self.shadow.show() QGraphicsRectItem.mousePressEvent(self, event) def mouseReleaseEvent(self, event): if self.is_placed: return if event.button() == Qt.RightButton: self.move_to_rack(self) return self.setScale(self.scale) current_position = self.pos() self.setX( round((self.x() + MARGIN * 2) / (SQUARE_SIZE * self.scale)) * SQUARE_SIZE * self.scale) self.setY( round((self.y() + MARGIN * 2) / (SQUARE_SIZE * self.scale)) * SQUARE_SIZE * self.scale) if current_position != self.pos(): self.update_coords() self.on_position_change(self) self.setZValue(3) self.shadow.hide() QGraphicsRectItem.mouseReleaseEvent(self, event) def update_coords(self): x = round(self.x() / SQUARE_SIZE / self.scale) y = round(self.y() / SQUARE_SIZE / self.scale) self.coords = Coords(x, y) def move(self, position): self.setPos(position) self.update_coords() def move_to_coords(self, coords): position = QPoint(coords.x * SQUARE_SIZE * self.scale, coords.y * SQUARE_SIZE * self.scale) self.move(position) def undo_move(self): self.setPos(self.old_position) self.update_coords() def swap_with_other(self, other): other.move(self.old_position) def remove_highlight(self): self.letter_item.setBrush(QBrush(SEA_GREEN)) def place(self): self.letter_item.setBrush(QBrush(LIGHT_SEA_GREEN)) self.setBrush(QBrush(YELLOW2)) self.setPen(QPen(YELLOW2, 0)) self.setFlag(QGraphicsItem.ItemIsMovable, False) self.is_placed = True
class CalendarDesklet(Desklet): def __init__(self): super().__init__() self.model = CalendarModel() self.cursor_pos = None self.cursor = QGraphicsRectItem(self.root) self.header = QGraphicsSimpleTextItem(self.root) self.weekdays = [] days = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] for day in days: self.weekdays.append(QGraphicsSimpleTextItem(day, self.root)) self.header_line = QGraphicsLineItem(self.root) self.days = [] for _ in range(0, 6 * 7): self.days.append(QGraphicsSimpleTextItem(self.root)) def next_month(self): self.model.next_month() self.layout() def previous_month(self): self.model.previous_month() self.layout() def set_rect(self, rect): super().set_rect(rect) self.layout() def set_style(self, style): super().set_style(style) font = QFont(style.font) font.setPixelSize(48) self.header.setBrush(style.midcolor) self.header.setFont(font) font = QFont(style.font) font.setPixelSize(32) self.header_line.setPen(style.foreground_color) self.cursor.setBrush(style.midcolor) self.cursor.setPen(QPen(Qt.NoPen)) for widget in self.weekdays: widget.setFont(font) widget.setBrush(style.foreground_color) for widget in self.days: widget.setFont(font) widget.setBrush(self.style.foreground_color) self.layout() def layout(self): cell_width = (self.rect.width()) / 7.0 cell_height = (self.rect.height() - 64) / 7.0 x = self.rect.left() y = self.rect.top() fm = QFontMetrics(self.header.font()) rect = fm.boundingRect(self.header.text()) self.header.setPos(x + self.rect.width() / 2 - rect.width() / 2, y) y += fm.height() for row, day in enumerate(self.weekdays): fm = QFontMetrics(day.font()) rect = fm.boundingRect(day.text()) day.setPos(x + row * cell_width + cell_width / 2 - rect.width() / 2, y) y += fm.height() self.header_line.setLine(x, y, x + self.rect.width() - 3, y) y += 8 for n, widget in enumerate(self.days): col = n % 7 row = n // 7 rect = fm.boundingRect(widget.text()) widget.setPos(x + col * cell_width + cell_width / 2 - rect.width() / 2, y + row * cell_height + cell_height / 2 - fm.height() / 2) # if day.month != self.now.month: # widget.setBrush(self.style.midcolor) # else: if self.cursor_pos is not None: self.cursor.setRect(x + self.cursor_pos[0] * cell_width, y + self.cursor_pos[1] * cell_height, cell_width, cell_height) self.cursor.show() else: self.cursor.hide() def update(self, now): self.model.update(now) # update header self.header.setText( date(self.model.year, self.model.month, 1).strftime("%B %Y")) # calculate the date of the top/left calendar entry current_date = date(self.model.year, self.model.month, 1) current_date = current_date - timedelta(current_date.weekday()) self.cursor_pos = None for n, widget in enumerate(self.days): col = n % 7 row = n // 7 if current_date == self.model.today: self.cursor_pos = (col, row) widget.setText("%d" % current_date.day) self.days[n] = widget current_date += timedelta(days=1) self.layout()