Exemple #1
0
    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()))
Exemple #3
0
    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)
Exemple #5
0
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)
Exemple #6
0
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
Exemple #9
0
        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
Exemple #10
0
 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)
Exemple #11
0
    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()
Exemple #12
0
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)
Exemple #14
0
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()
Exemple #15
0
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
Exemple #16
0
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
Exemple #17
0
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)
Exemple #18
0
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())
Exemple #19
0
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)
Exemple #21
0
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)
Exemple #22
0
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()
Exemple #23
0
    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()
Exemple #25
0
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
Exemple #26
0
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()
Exemple #27
0
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)
Exemple #28
0
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()
Exemple #29
0
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)
Exemple #30
0
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()
Exemple #31
0
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))
Exemple #32
0
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)
Exemple #34
0
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()
Exemple #35
0
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())
Exemple #36
0
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
Exemple #37
0
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()