Пример #1
0
class ParticipantItem(QGraphicsItem):
    def __init__(self, model_item: Participant, parent=None):
        super().__init__(parent)

        self.model_item = model_item

        self.text = QGraphicsTextItem(self)

        self.line = QGraphicsLineItem(self)
        self.line.setPen(
            QPen(Qt.darkGray, 1, Qt.DashLine, Qt.RoundCap, Qt.RoundJoin))

        self.refresh()

    def update_position(self, x_pos=-1, y_pos=-1):
        if x_pos == -1:
            x_pos = self.x_pos()

        if y_pos == -1:
            y_pos = self.line.line().y2()

        self.text.setPos(x_pos - (self.text.boundingRect().width() / 2), 0)
        self.line.setLine(x_pos, 30, x_pos, y_pos)

    def x_pos(self):
        return self.line.line().x1()

    def width(self):
        return self.boundingRect().width()

    def refresh(self):
        self.text.setPlainText(
            "?" if not self.model_item else self.model_item.shortname)
        if hasattr(self.model_item, "simulate") and self.model_item.simulate:
            font = QFont()
            font.setBold(True)
            self.text.setFont(font)
            self.text.setDefaultTextColor(Qt.darkGreen)
            self.line.setPen(
                QPen(Qt.darkGreen, 2, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
        else:
            self.text.setFont(QFont())
            self.text.setDefaultTextColor(constants.LINECOLOR)
            self.line.setPen(
                QPen(Qt.darkGray, 1, Qt.DashLine, Qt.RoundCap, Qt.RoundJoin))

    def boundingRect(self):
        return self.childrenBoundingRect()

    def paint(self, painter, option, widget):
        pass
Пример #2
0
class WayPoint:
    def __init__(self, **kwargs):
        super().__init__()
        self.location = MapPoint()
        self.__dict__.update(kwargs)

        self.pixmap = QGraphicsPixmapItem(
            QPixmap('HOME_DIR + /nparse/data/maps/waypoint.png'))
        self.pixmap.setOffset(-10, -20)

        self.line = QGraphicsLineItem(0.0, 0.0, self.location.x,
                                      self.location.y)
        self.line.setPen(QPen(Qt.green, 1, Qt.DashLine))
        self.line.setVisible(False)

        self.pixmap.setZValue(5)
        self.line.setZValue(4)

        self.pixmap.setPos(self.location.x, self.location.y)

    def update_(self, scale, location=None):
        self.pixmap.setScale(scale)
        if location:
            line = self.line.line()
            line.setP1(QPointF(location.x, location.y))
            self.line.setLine(line)

            pen = self.line.pen()
            pen.setWidth(1 / scale)
            self.line.setPen(pen)

            self.line.setVisible(True)
Пример #3
0
class ParticipantItem(QGraphicsItem):
    def __init__(self, model_item: Participant, parent=None):
        super().__init__(parent)

        self.model_item = model_item

        self.text = QGraphicsTextItem(self)

        self.line = QGraphicsLineItem(self)
        self.line.setPen(QPen(Qt.darkGray, 1, Qt.DashLine, Qt.RoundCap, Qt.RoundJoin))

        self.refresh()

    def update_position(self, x_pos=-1, y_pos=-1):
        if x_pos == -1:
            x_pos = self.x_pos()

        if y_pos == -1:
            y_pos = self.line.line().y2()

        self.text.setPos(x_pos - (self.text.boundingRect().width() / 2), 0)
        self.line.setLine(x_pos, 30, x_pos, y_pos)

    def x_pos(self):
        return self.line.line().x1()

    def width(self):
        return self.boundingRect().width()

    def refresh(self):
        self.text.setPlainText("?" if not self.model_item else self.model_item.shortname)
        if hasattr(self.model_item, "simulate") and self.model_item.simulate:
            font = QFont()
            font.setBold(True)
            self.text.setFont(font)
            self.text.setDefaultTextColor(Qt.darkGreen)
            self.line.setPen(QPen(Qt.darkGreen, 2, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
        else:
            self.text.setFont(QFont())
            self.text.setDefaultTextColor(constants.LINECOLOR)
            self.line.setPen(QPen(Qt.darkGray, 1, Qt.DashLine, Qt.RoundCap, Qt.RoundJoin))

    def boundingRect(self):
        return self.childrenBoundingRect()

    def paint(self, painter, option, widget):
        pass
Пример #4
0
 def __init__(self, direction, point):
     super(displacementConstrain, self).__init__()
     self.direction = direction
     self.point = point
     if 1 in self.direction:
         line1 = QGraphicsLineItem()
         line1.setLine(self.point.position.x(), self.point.position.y(),
                       self.point.position.x() - 20,
                       self.point.position.y() + 10)
         line1.setPen(displacementConstrain.pen)
         line2 = QGraphicsLineItem()
         line2.setLine(self.point.position.x(), self.point.position.y(),
                       self.point.position.x() - 20,
                       self.point.position.y() - 10)
         line2.setPen(displacementConstrain.pen)
         line3 = QGraphicsLineItem()
         line3.setLine(line2.line().p2().x(),
                       line2.line().p2().y(),
                       line1.line().p2().x(),
                       line1.line().p2().y())
         line3.setPen(self.pen)
         self.addToGroup(line1)
         self.addToGroup(line2)
         self.addToGroup(line3)
     if 2 in direction:
         line1 = QGraphicsLineItem()
         line1.setLine(self.point.position.x(), self.point.position.y(),
                       self.point.position.x() - 10,
                       self.point.position.y() + 20)
         line1.setPen(displacementConstrain.pen)
         line2 = QGraphicsLineItem()
         line2.setLine(self.point.position.x(), self.point.position.y(),
                       self.point.position.x() + 10,
                       self.point.position.y() + 20)
         line2.setPen(displacementConstrain.pen)
         self.addToGroup(line1)
         self.addToGroup(line2)
         line3 = QGraphicsLineItem()
         line3.setLine(line2.line().p2().x(),
                       line2.line().p2().y(),
                       line1.line().p2().x(),
                       line1.line().p2().y())
         line3.setPen(self.pen)
         self.addToGroup(line3)
     if 3 in direction:
         # TODO: implement z rotation
         pass
Пример #5
0
class ParticipantItem(QGraphicsItem):
    def __init__(self, model_item: Participant, parent=None):
        super().__init__(parent)

        self.model_item = model_item

        self.text = QGraphicsTextItem(self)

        self.line = QGraphicsLineItem(self)
        self.line.setPen(QPen(Qt.darkGray, 1, Qt.DashLine, Qt.RoundCap, Qt.RoundJoin))

        self.refresh()

    def update_position(self, x_pos=-1, y_pos=-1):
        if x_pos == -1:
            x_pos = self.x_pos()

        if y_pos == -1:
            y_pos = self.line.line().y2()

        self.text.setPos(x_pos - (self.text.boundingRect().width() / 2), 0)
        self.line.setLine(x_pos, 30, x_pos, y_pos)

    def x_pos(self):
        return self.line.line().x1()

    def width(self):
        return self.boundingRect().width()

    def refresh(self):
        self.text.setPlainText("?" if not self.model_item else self.model_item.shortname)

    def boundingRect(self):
        return self.childrenBoundingRect()

    def paint(self, painter, option, widget):
        pass
    def connect_relation(self, x, y, type):
        ver_line = QGraphicsLineItem(self.en_shape)
        horz_line = QGraphicsLineItem(self.en_shape)
        ver_line.setLine(
            QLineF(self.rel_orgn.x(), self.rel_orgn.y(), self.rel_orgn.x(),
                   y - self.en_shape.y()))
        horz_line.setLine(
            QLineF(ver_line.line().x2(),
                   ver_line.line().y2(), x - self.en_shape.x(),
                   y - self.en_shape.y()))

        self.rel_orgn.setX(self.rel_orgn.x() + self.orgn_step)
        if (type == 'p'):
            ver_line.setPen(QPen(Qt.black, 3, Qt.DashLine))
            horz_line.setPen(QPen(Qt.black, 3, Qt.DashLine))
Пример #7
0
class DiagramScene(QGraphicsScene):
    insert_item, insert_line, move_item = range(3)

    item_inserted = pyqtSignal(DiagramItem)

    def __init__(self, context_menu: QMenu, parent: QGraphicsItem = None):
        super(DiagramScene, self).__init__(parent)

        self.context_menu = context_menu
        self.framework_name = frameworks_utils.get_sorted_frameworks_list()[0]
        self.mode = self.move_item
        self.item_type = None
        self.line = None

        # Could be used later to support items & lines coloring.
        self.item_color = Qt.white
        self.line_color = Qt.black

    def set_framework_name(self, framework_name: str):
        self.framework_name = framework_name

    def set_mode(self, mode: int):
        self.mode = mode

    def set_item_type(self, item_type: int):
        self.item_type = item_type

    def mousePressEvent(self, event: QGraphicsSceneMouseEvent):
        if event.button() != Qt.LeftButton:
            return

        if self.mode == self.insert_item:
            layers = frameworks_utils.get_framework_layers(self.framework_name)
            item = DiagramItem(layers[self.item_type](), self.context_menu)
            item.setBrush(self.item_color)
            item.setPos(event.scenePos())
            self.addItem(item)
            self.item_inserted.emit(item)
        elif self.mode == self.insert_line:
            self.line = QGraphicsLineItem(
                QLineF(event.scenePos(), event.scenePos()))
            self.line.setPen(QPen(self.line_color, 2))
            self.addItem(self.line)

        super(DiagramScene, self).mousePressEvent(event)

    def mouseMoveEvent(self, event: QGraphicsSceneMouseEvent):
        if self.mode == self.insert_line and self.line:
            new_line = QLineF(self.line.line().p1(), event.scenePos())
            self.line.setLine(new_line)
        elif self.mode == self.move_item:
            super(DiagramScene, self).mouseMoveEvent(event)

    def mouseReleaseEvent(self, event: QGraphicsSceneMouseEvent):
        if self.mode == self.insert_line and self.line:
            start_items = self.items(self.line.line().p1())
            if len(start_items) and start_items[0] == self.line:
                start_items.pop(0)

            end_items = self.items(self.line.line().p2())
            if len(end_items) and end_items[0] == self.line:
                end_items.pop(0)

            self.removeItem(self.line)
            self.line = None

            if len(start_items) and isinstance(start_items[0],
                                               QGraphicsSimpleTextItem):
                start_items[0] = start_items[0].parentItem()

            if len(end_items) and isinstance(end_items[0],
                                             QGraphicsSimpleTextItem):
                end_items[0] = end_items[0].parentItem()

            if (len(start_items) and len(end_items)
                    and isinstance(start_items[0], DiagramItem)
                    and isinstance(end_items[0], DiagramItem)
                    and start_items[0] != end_items[0]):
                start_item = start_items[0]
                end_item = end_items[0]
                arrow = Arrow(start_item, end_item)
                start_item.add_arrow(arrow)
                end_item.add_arrow(arrow)
                arrow.setZValue(-1000.0)
                self.addItem(arrow)
                arrow.updatePosition()

        self.line = None
        super(DiagramScene, self).mouseReleaseEvent(event)

    def isItemChange(self, type_: DiagramItem) -> bool:
        for item in self.selectedItems():
            if isinstance(item, type_):
                return True
        return False
Пример #8
0
class DiagramScene(QGraphicsScene):
    InsertItem, InsertLine, InsertText, MoveItem  = range(4)

    itemInserted = pyqtSignal(DiagramItem)

    textInserted = pyqtSignal(QGraphicsTextItem)

    itemSelected = pyqtSignal(QGraphicsItem)

    def __init__(self, itemMenu, parent=None):
        super(DiagramScene, self).__init__(parent)

        self.myItemMenu = itemMenu
        self.myMode = self.MoveItem
        self.myItemType = DiagramItem.Step
        self.line = None
        self.textItem = None
        self.myItemColor = Qt.white
        self.myTextColor = Qt.black
        self.myLineColor = Qt.black
        self.myFont = QFont()

    def setLineColor(self, color):
        self.myLineColor = color
        if self.isItemChange(Arrow):
            item = self.selectedItems()[0]
            item.setColor(self.myLineColor)
            self.update()

    def setTextColor(self, color):
        self.myTextColor = color
        if self.isItemChange(DiagramTextItem):
            item = self.selectedItems()[0]
            item.setDefaultTextColor(self.myTextColor)

    def setItemColor(self, color):
        self.myItemColor = color
        if self.isItemChange(DiagramItem):
            item = self.selectedItems()[0]
            item.setBrush(self.myItemColor)

    def setFont(self, font):
        self.myFont = font
        if self.isItemChange(DiagramTextItem):
            item = self.selectedItems()[0]
            item.setFont(self.myFont)

    def setMode(self, mode):
        self.myMode = mode

    def setItemType(self, type):
        self.myItemType = type

    def editorLostFocus(self, item):
        cursor = item.textCursor()
        cursor.clearSelection()
        item.setTextCursor(cursor)

        if item.toPlainText():
            self.removeItem(item)
            item.deleteLater()

    def mousePressEvent(self, mouseEvent):
        if (mouseEvent.button() != Qt.LeftButton):
            return

        if self.myMode == self.InsertItem:
            item = DiagramItem(self.myItemType, self.myItemMenu)
            item.setBrush(self.myItemColor)
            self.addItem(item)
            item.setPos(mouseEvent.scenePos())
            self.itemInserted.emit(item)
        elif self.myMode == self.InsertLine:
            self.line = QGraphicsLineItem(QLineF(mouseEvent.scenePos(),
                    mouseEvent.scenePos()))
            self.line.setPen(QPen(self.myLineColor, 2))
            self.addItem(self.line)
        elif self.myMode == self.InsertText:
            textItem = DiagramTextItem()
            textItem.setFont(self.myFont)
            textItem.setTextInteractionFlags(Qt.TextEditorInteraction)
            textItem.setZValue(1000.0)
            textItem.lostFocus.connect(self.editorLostFocus)
            textItem.selectedChange.connect(self.itemSelected)
            self.addItem(textItem)
            textItem.setDefaultTextColor(self.myTextColor)
            textItem.setPos(mouseEvent.scenePos())
            self.textInserted.emit(textItem)

        super(DiagramScene, self).mousePressEvent(mouseEvent)

    def mouseMoveEvent(self, mouseEvent):
        if self.myMode == self.InsertLine and self.line:
            newLine = QLineF(self.line.line().p1(), mouseEvent.scenePos())
            self.line.setLine(newLine)
        elif self.myMode == self.MoveItem:
            super(DiagramScene, self).mouseMoveEvent(mouseEvent)

    def mouseReleaseEvent(self, mouseEvent):
        if self.line and self.myMode == self.InsertLine:
            startItems = self.items(self.line.line().p1())
            if len(startItems) and startItems[0] == self.line:
                startItems.pop(0)
            endItems = self.items(self.line.line().p2())
            if len(endItems) and endItems[0] == self.line:
                endItems.pop(0)

            self.removeItem(self.line)
            self.line = None

            if len(startItems) and len(endItems) and \
                    isinstance(startItems[0], DiagramItem) and \
                    isinstance(endItems[0], DiagramItem) and \
                    startItems[0] != endItems[0]:
                startItem = startItems[0]
                endItem = endItems[0]
                arrow = Arrow(startItem, endItem)
                arrow.setColor(self.myLineColor)
                startItem.addArrow(arrow)
                endItem.addArrow(arrow)
                arrow.setZValue(-1000.0)
                self.addItem(arrow)
                arrow.updatePosition()

        self.line = None
        super(DiagramScene, self).mouseReleaseEvent(mouseEvent)

    def isItemChange(self, type):
        for item in self.selectedItems():
            if isinstance(item, type):
                return True
        return False
class DiagramScene(QGraphicsScene):
    InsertItem, InsertLine, InsertText, MoveItem, DefaultMode = range(5)

    itemInserted = pyqtSignal(int)

    textInserted = pyqtSignal(QGraphicsTextItem)

    itemSelected = pyqtSignal(QGraphicsItem)

    def __init__(self, item_menu, parent=None):
        super(DiagramScene, self).__init__(parent)

        self.my_item_menu = item_menu
        self.my_mode = self.DefaultMode
        self.my_item_type = "channel"
        self.line = None
        self.text_item = None
        self.my_item_color = Qt.white
        self.my_text_color = Qt.black
        self.my_line_color = Qt.black
        self.my_font = QFont()
        self.m_drag_offset = 0
        self.m_dragged = None

    def set_line_color(self, color):
        self.my_line_color = color
        if self.is_item_changed(Arrow):
            item = self.selectedItems()[0]
            item.set_color(self.my_line_color)
            self.update()

    def set_text_color(self, color):
        self.my_text_color = color
        if self.is_item_changed(DiagramTextItem):
            item = self.selectedItems()[0]
            item.setDefaultTextColor(self.my_text_color)

    def set_item_color(self, color):
        self.my_item_color = color
        if self.is_item_changed(DiagramTextItem):
            item = self.selectedItems()[0]
            item.setBrush(self.my_item_color)

    def setFont(self, font):
        self.my_font = font
        if self.is_item_changed(DiagramTextItem):
            item = self.selectedItems()[0]
            item.setFont(self.my_font)

    def set_mode(self, mode):
        self.my_mode = mode

    def set_item_type(self, new_type):
        self.my_item_type = new_type

    def editor_lost_focus(self, item):
        if item:
            cursor = item.text_cursor()
            cursor.clearSelection()
            item.set_text_cursor(cursor)

            if item.to_plain_text():
                self.removeItem(item)
                item.delete_later()

    # noinspection PyArgumentList
    def insert_item(self, item_type=None, x=None, y=None, text=None):
        if not text:
            text, ok = QInputDialog.getText(QInputDialog(), 'Insert name', 'Enter new object name:')
            if not ok:  # TODO
                return
        item = FlumeObject(item_type, text).pictogram
        item.setBrush(self.my_item_color)
        self.addItem(item)
        item.setPos(x, y)
        return item

    def mousePressEvent(self, mouse_event):

        if mouse_event.button() != Qt.LeftButton:
            return
        if self.my_mode == self.InsertItem:
            x = mouse_event.scenePos().x()  # // 50 * 50
            y = mouse_event.scenePos().y()  # // 50 * 50
            item = self.insert_item(self.my_item_type, x, y)
            self.itemInserted.emit(item.flume_component)
        elif self.my_mode == self.InsertLine:
            self.line = QGraphicsLineItem(QLineF(mouse_event.scenePos(),
                                                 mouse_event.scenePos()))
            self.line.setPen(QPen(self.my_line_color, 2))
            self.addItem(self.line)
        elif self.my_mode == self.InsertText:
            text_item = DiagramTextItem()
            text_item.setFont(self.my_font)
            text_item.setTextInteractionFlags(Qt.TextEditorInteraction)
            text_item.setZValue(1000.0)
            text_item.lostFocus.connect(self.editor_lost_focus)
            # text_item.selectedChange.connect(self.itemSelected)
            self.addItem(text_item)
            text_item.setDefaultTextColor(self.my_text_color)
            text_item.setPos(mouse_event.scenePos())
            self.textInserted.emit(text_item)
        else:
            self.m_dragged = QGraphicsScene.itemAt(self, mouse_event.scenePos(), QTransform())
            if self.m_dragged:
                self.my_mode = self.MoveItem
                self.m_drag_offset = mouse_event.scenePos() - self.m_dragged.pos()

        super(DiagramScene, self).mousePressEvent(mouse_event)

    def mouseMoveEvent(self, mouse_event):
        if self.my_mode == self.InsertLine and self.line:
            new_line = QLineF(self.line.line().p1(), mouse_event.scenePos())
            self.line.setLine(new_line)
        elif self.my_mode == self.MoveItem:
            if self.m_dragged:
                self.m_dragged.setPos(mouse_event.scenePos() - self.m_drag_offset)
            super(DiagramScene, self).mouseMoveEvent(mouse_event)

    def mouseReleaseEvent(self, mouse_event):
        if self.line and self.my_mode == self.InsertLine:
            start_items = self.items(self.line.line().p1())
            if len(start_items) and start_items[0] == self.line:
                start_items.pop(0)
            end_items = self.items(self.line.line().p2())
            if len(end_items) and end_items[0] == self.line:
                end_items.pop(0)

            self.removeItem(self.line)
            self.line = None

            if len(start_items) and len(end_items) and isinstance(start_items[0], FlumeDiagramItem) and \
                    isinstance(end_items[0], FlumeDiagramItem) and start_items[0] != end_items[0]:
                start_item = start_items[0]
                end_item = end_items[0]

                self.add_arrow(start_item, end_item)

        self.line = None

        if self.m_dragged:
            x = mouse_event.scenePos().x()  # // 50 * 50
            y = mouse_event.scenePos().y()  # // 50 * 50
            self.m_dragged.setPos(x, y)
            self.m_dragged = None
            self.my_mode = self.DefaultMode

        super(DiagramScene, self).mouseReleaseEvent(mouse_event)

    def add_arrow(self, start_item, end_item):
        arrow = Arrow(start_item, end_item)
        arrow.set_color(self.my_line_color)
        start_item.add_arrow(arrow)
        end_item.add_arrow(arrow)
        arrow.setZValue(-1000.0)
        self.addItem(arrow)
        arrow.update_position()

    def is_item_changed(self, new_type):
        for item in self.selectedItems():
            if isinstance(item, new_type):
                return True
        return False
Пример #10
0
class TransitionGraphicsItem(QGraphicsObject):
    # constant values
    SQUARE_SIDE = 10
    ARROW_SIZE = 12
    PEN_NORMAL_WIDTH = 1
    PEN_FOCUS_WIDTH = 3

    posChanged = pyqtSignal('QGraphicsItem')

    def __init__(self, data):
        super(QGraphicsObject, self).__init__()
        self.transitionData = data

        self.originLine = None
        self.destinationLine = None
        self.arrow = None
        self.textGraphics = None
        self.middleHandle = None

        self.graphicsOrigin = self.transitionData.origin.getGraphicsItem()
        self.graphicsDestination = self.transitionData.destination.getGraphicsItem()

        # connect position changed event
        self.graphicsOrigin.posChanged.connect(self.statePosChanged)
        self.graphicsDestination.posChanged.connect(self.statePosChanged)

        self.midPointX = (self.graphicsDestination.scenePos().x() + self.graphicsOrigin.scenePos().x()) / 2.0
        self.midPointY = (self.graphicsDestination.scenePos().y() + self.graphicsOrigin.scenePos().y()) / 2.0

        self.createOriginLine()
        self.createDestinationLine()

        self.createArrow()
        self.createMiddleHandle()
        self.createIdTextBox()

    def statePosChanged(self, state):
        if self.graphicsOrigin == state:
            self.createOriginLine()
        elif self.graphicsDestination == state:
            self.createDestinationLine()
            self.createArrow()

    def createOriginLine(self):
        if self.originLine == None:
            self.originLine = QGraphicsLineItem(self.midPointX, self.midPointY, self.graphicsOrigin.scenePos().x(),
                                                self.graphicsOrigin.scenePos().y(), self)
        else:
            self.originLine.setLine(QLineF(self.midPointX, self.midPointY, self.graphicsOrigin.scenePos().x(),
                                           self.graphicsOrigin.scenePos().y()))
        myLine = self.originLine.line()
        myLine.setLength(myLine.length() - StateGraphicsItem.NODE_WIDTH / 2)
        self.originLine.setLine(myLine)

    def createDestinationLine(self):
        if self.destinationLine == None:
            self.destinationLine = QGraphicsLineItem(self.midPointX, self.midPointY, self.graphicsDestination.scenePos().x(),
                                                     self.graphicsDestination.scenePos().y(), self)
        else:
            self.destinationLine.setLine(QLineF(self.midPointX, self.midPointY, self.graphicsDestination.scenePos().x(),
                                                self.graphicsDestination.scenePos().y()))

        myLine = self.destinationLine.line()
        myLine.setLength(myLine.length() - StateGraphicsItem.NODE_WIDTH / 2)
        self.destinationLine.setLine(myLine)

    def createArrow(self):
        # add an arrow to destination line
        myLine = self.destinationLine.line()
        myLine.setLength(myLine.length() - TransitionGraphicsItem.ARROW_SIZE)
        rotatePoint = myLine.p2() - self.destinationLine.line().p2()

        rightPointX = rotatePoint.x() * math.cos(math.pi / 6) - rotatePoint.y() * math.sin(math.pi / 6)
        rightPointY = rotatePoint.x() * math.sin(math.pi / 6) + rotatePoint.y() * math.cos(math.pi / 6)
        rightPoint = QPointF(rightPointX + self.destinationLine.line().x2(),
                             rightPointY + self.destinationLine.line().y2())

        leftPointX = rotatePoint.x() * math.cos(-math.pi / 6) - rotatePoint.y() * math.sin(-math.pi / 6)
        leftPointY = rotatePoint.x() * math.sin(-math.pi / 6) + rotatePoint.y() * math.cos(-math.pi / 6)
        leftPoint = QPointF(leftPointX + self.destinationLine.line().x2(),
                            leftPointY + self.destinationLine.line().y2())

        polygon = QPolygonF()
        polygon << rightPoint << leftPoint << self.destinationLine.line().p2() << rightPoint

        if self.arrow == None:
            self.arrow = QGraphicsPolygonItem(polygon, self)
        else:
            self.arrow.setPolygon(polygon)

        brush = QBrush(Qt.SolidPattern)
        brush.setColor(Qt.black)
        self.arrow.setBrush(brush)

    def createMiddleHandle(self):
        # create middle handle
        if self.middleHandle == None:
            self.middleHandle = RectHandleGraphicsItem(TransitionGraphicsItem.SQUARE_SIDE, self)
            self.middleHandle.setFlag(QGraphicsItem.ItemIsMovable)

        self.middleHandle.setPos(self.midPointX, self.midPointY)

    def createIdTextBox(self):
        if self.textGraphics == None:
            self.textGraphics = IdTextBoxGraphicsItem(self.transitionData.name, self)
            self.textGraphics.textChanged.connect(self.nameChanged)
        else:
            self.textGraphics.setPlainText(self.transitionData.name)
        textWidth = self.textGraphics.boundingRect().width()
        self.textGraphics.setPos(self.midPointX - textWidth / 2, self.midPointY + TransitionGraphicsItem.SQUARE_SIDE -
                                 (TransitionGraphicsItem.SQUARE_SIDE / 2) + 5)

    def updateMiddlePoints(self, newPosition):
        self.midPointX = newPosition.x()
        self.midPointY = newPosition.y()
        self.createOriginLine()
        self.createDestinationLine()
        self.createArrow()
        self.createIdTextBox()
        self.posChanged.emit(self)

    def nameChanged(self, name):
        self.transitionData.name = name
        self.createIdTextBox()

    def boundingRect(self):
        if self.middleHandle != None:
            return self.middleHandle.boundingRect()
        else:
            return None

    def disableInteraction(self):
        if self.middleHandle is not None:
            self.middleHandle.setFlag(QGraphicsItem.ItemIsMovable, False)
            self.middleHandle.disableInteraction()
Пример #11
0
class IMUChartView(QChartView):

    aboutToClose = pyqtSignal(QObject)
    cursorMoved = pyqtSignal(datetime.datetime)

    def __init__(self, parent=None):
        super(QChartView, self).__init__(parent=parent)

        #self.setFixedHeight(400)
        #self.setMinimumHeight(500)
        """self.setMaximumHeight(700)
        self.setFixedHeight(700)
        self.setMinimumWidth(1500)
        self.setSizePolicy(QSizePolicy.Fixed,QSizePolicy.Fixed)"""

        self.reftime = datetime.datetime.now()
        self.cursor = QGraphicsLineItem()
        self.scene().addItem(self.cursor)
        self.decim_factor = 1

        # self.setScene(QGraphicsScene())
        self.chart = QChart()
        # self.scene().addItem(self.chart)
        self.setChart(self.chart)
        self.chart.legend().setVisible(True)
        self.chart.legend().setAlignment(Qt.AlignTop)
        self.ncurves = 0
        self.setRenderHint(QPainter.Antialiasing)
        self.setRubberBand(QChartView.HorizontalRubberBand)

        # X, Y label on bottom
        # self.xTextItem = QGraphicsSimpleTextItem(self.chart)
        # self.xTextItem.setText('X: ')
        # self.yTextItem = QGraphicsSimpleTextItem(self.chart)
        # self.yTextItem.setText('Y: ')
        # self.update_x_y_coords()

        # Track mouse
        self.setMouseTracking(True)

        # Top Widgets
        newWidget = QWidget(self)
        newLayout = QHBoxLayout()
        newLayout.setContentsMargins(0, 0, 0, 0)
        newWidget.setLayout(newLayout)
        #labelx = QLabel(self)
        #labelx.setText('X:')
        #self.labelXValue = QLabel(self)
        #labely = QLabel(self)
        #labely.setText('Y:')
        #self.labelYValue = QLabel(self)

        # Test buttons
        #newLayout.addWidget(QToolButton(self))
        #newLayout.addWidget(QToolButton(self))
        #newLayout.addWidget(QToolButton(self))

        # Spacer
        #newLayout.addItem(QSpacerItem(10, 10, QSizePolicy.Expanding, QSizePolicy.Minimum))

        # Labels
        """newLayout.addWidget(labelx)
        newLayout.addWidget(self.labelXValue)
        self.labelXValue.setMinimumWidth(200)
        self.labelXValue.setMaximumWidth(200)
        newLayout.addWidget(labely)
        newLayout.addWidget(self.labelYValue)
        self.labelYValue.setMinimumWidth(200)
        self.labelYValue.setMaximumWidth(200)
        """
        """if parent is not None:
            parent.layout().setMenuBar(newWidget)
        """
        # self.layout()

        self.build_style()

    def build_style(self):
        self.setStyleSheet("QLabel{color:blue;}")

        self.setBackgroundBrush(QBrush(Qt.darkGray))
        self.chart.setPlotAreaBackgroundBrush(QBrush(Qt.black))
        self.chart.setPlotAreaBackgroundVisible(True)

    def save_as_png(self, file_path):
        pixmap = self.grab()

        child = self.findChild(QOpenGLWidget)

        painter = QPainter(pixmap)
        if child is not None:
            d = child.mapToGlobal(QPoint()) - self.mapToGlobal(QPoint())
            painter.setCompositionMode(QPainter.CompositionMode_SourceAtop)
            painter.drawImage(d, child.grabFramebuffer())

        painter.end()
        pixmap.save(file_path, 'PNG')

    def closeEvent(self, QCloseEvent):
        self.aboutToClose.emit(self)

    @pyqtSlot(QPointF)
    def lineseries_clicked(self, point):
        print('lineseries clicked', point)

    @pyqtSlot(QPointF)
    def lineseries_hovered(self, point):
        print('lineseries hovered', point)

    def update_x_y_coords(self):
        pass
        # self.xTextItem.setPos(self.chart.size().width() / 2 - 100, self.chart.size().height() - 40)
        # self.yTextItem.setPos(self.chart.size().width() / 2 + 100, self.chart.size().height() - 40)

    def decimate(self, xdata, ydata):
        assert (len(xdata) == len(ydata))

        # Decimate only if we have too much data
        decimate_factor = len(xdata) / 100000.0

        if decimate_factor > 1.0:
            decimate_factor = int(np.floor(decimate_factor))
            #print('decimate factor', decimate_factor)
            # x = decimate(xdata, decimate_factor)
            # y = decimate(ydata, decimate_factor)
            self.decim_factor = decimate_factor
            x = np.ndarray(int(len(xdata) / decimate_factor), dtype=np.float64)
            y = np.ndarray(int(len(ydata) / decimate_factor), dtype=np.float64)
            for i in range(len(x)):
                index = i * decimate_factor
                assert (index < len(xdata))
                x[i] = xdata[index]
                y[i] = ydata[index]
                if x[i] < x[0]:
                    print('timestamp error', x[i], x[0])

            #print('return size', len(x), len(y), 'timestamp', x[0])
            return x, y
        else:
            return xdata, ydata

    @pyqtSlot(float, float)
    def axis_range_changed(self, min, max):
        #print('axis_range_changed', min, max)
        for axis in self.chart.axes():
            axis.applyNiceNumbers()

    def update_axes(self):

        # Get and remove all axes
        for axis in self.chart.axes():
            self.chart.removeAxis(axis)

        # Create new axes
        # Create axis X
        # axisX = QDateTimeAxis()
        # axisX.setTickCount(5)
        # axisX.setFormat("dd MMM yyyy")
        # axisX.setTitleText("Date")
        # self.chart.addAxis(axisX, Qt.AlignBottom)
        # axisX.rangeChanged.connect(self.axis_range_changed)

        axisX = QValueAxis()
        axisX.setTickCount(10)
        axisX.setLabelFormat("%li")
        axisX.setTitleText("Seconds")
        self.chart.addAxis(axisX, Qt.AlignBottom)
        # axisX.rangeChanged.connect(self.axis_range_changed)

        # Create axis Y
        axisY = QValueAxis()
        axisY.setTickCount(5)
        axisY.setLabelFormat("%.3f")
        axisY.setTitleText("Values")
        self.chart.addAxis(axisY, Qt.AlignLeft)
        # axisY.rangeChanged.connect(self.axis_range_changed)

        ymin = None
        ymax = None

        # Attach axes to series, find min-max
        for series in self.chart.series():
            series.attachAxis(axisX)
            series.attachAxis(axisY)
            vect = series.pointsVector()
            for i in range(len(vect)):
                if ymin is None:
                    ymin = vect[i].y()
                    ymax = vect[i].y()
                else:
                    ymin = min(ymin, vect[i].y())
                    ymax = max(ymax, vect[i].y())

        # Update range
        # print('min max', ymin, ymax)
        if ymin is not None:
            axisY.setRange(ymin, ymax)

        # Make the X,Y axis more readable
        axisX.applyNiceNumbers()
        # axisY.applyNiceNumbers()

    def add_data(self, xdata, ydata, color=None, legend_text=None):
        curve = QLineSeries()
        pen = curve.pen()
        if color is not None:
            pen.setColor(color)
        pen.setWidthF(1.5)
        curve.setPen(pen)

        #curve.setUseOpenGL(True)

        # Decimate
        xdecimated, ydecimated = self.decimate(xdata, ydata)

        # Data must be in ms since epoch
        # curve.append(self.series_to_polyline(xdecimated * 1000.0, ydecimated))
        for i in range(len(xdecimated)):
            # TODO hack
            x = xdecimated[i] - xdecimated[0]
            curve.append(QPointF(x, ydecimated[i]))

        self.reftime = datetime.datetime.fromtimestamp(xdecimated[0])

        if legend_text is not None:
            curve.setName(legend_text)

        # Needed for mouse events on series
        self.chart.setAcceptHoverEvents(True)

        # connect signals / slots
        # curve.clicked.connect(self.lineseries_clicked)
        # curve.hovered.connect(self.lineseries_hovered)

        # Add series
        self.chart.addSeries(curve)
        self.ncurves += 1
        self.update_axes()

    def set_title(self, title):
        # print('Setting title: ', title)
        #self.chart.setTitle(title)
        pass

    def series_to_polyline(self, xdata, ydata):
        """Convert series data to QPolygon(F) polyline

        This code is derived from PythonQwt's function named
        `qwt.plot_curve.series_to_polyline`"""

        # print('series_to_polyline types:', type(xdata[0]), type(ydata[0]))
        size = len(xdata)
        polyline = QPolygonF(size)

        for i in range(0, len(xdata)):
            polyline[i] = QPointF(xdata[i] - xdata[0], ydata[i])

        # pointer = polyline.data()
        # dtype, tinfo = np.float, np.finfo  # integers: = np.int, np.iinfo
        # pointer.setsize(2*polyline.size()*tinfo(dtype).dtype.itemsize)
        # memory = np.frombuffer(pointer, dtype)
        # memory[:(size-1)*2+1:2] = xdata
        # memory[1:(size-1)*2+2:2] = ydata
        return polyline

    def add_test_data(self):

        # 100Hz, one day accelerometer values
        npoints = 1000 * 60 * 24

        xdata = np.linspace(0., 10., npoints)
        self.add_data(xdata, np.sin(xdata), color=Qt.red, legend_text='Acc. X')
        # self.add_data(xdata, np.cos(xdata), color=Qt.green, legend_text='Acc. Y')
        # self.add_data(xdata, np.cos(2 * xdata), color=Qt.blue, legend_text='Acc. Z')
        self.set_title("Simple example with %d curves of %d points " \
                          "(OpenGL Accelerated Series)" \
                          % (self.ncurves, npoints))

    def mouseMoveEvent(self, e: QMouseEvent):
        # Handling rubberbands
        super().mouseMoveEvent(e)

        # Go back to seconds (instead of ms)
        """xmap = self.chart.mapToValue(e.pos()).x()
        ymap = self.chart.mapToValue(e.pos()).y()

        self.labelXValue.setText(str(datetime.datetime.fromtimestamp(xmap + self.reftime.timestamp())))
        self.labelYValue.setText(str(ymap))"""

        # self.xTextItem.setText('X: ' + str(datetime.datetime.fromtimestamp(xmap + self.reftime.timestamp())))
        # self.yTextItem.setText('Y: ' + str(ymap))

    def mousePressEvent(self, e: QMouseEvent):
        # Handling rubberbands
        super().mousePressEvent(e)

        self.setCursorPosition(e.pos().x(), True)

        pass

    def setCursorPosition(self, pos, emit_signal=False):
        # print (pos)
        pen = self.cursor.pen()
        pen.setColor(Qt.cyan)
        pen.setWidthF(1.0)
        self.cursor.setPen(pen)
        # On Top
        self.cursor.setZValue(100.0)

        area = self.chart.plotArea()
        x = pos
        y1 = area.y()
        y2 = area.y() + area.height()

        # self.cursor.set
        self.cursor.setLine(x, y1, x, y2)
        self.cursor.show()

        xmap = self.chart.mapToValue(QPointF(pos, 0)).x()
        ymap = self.chart.mapToValue(QPointF(pos, 0)).y()

        #self.labelXValue.setText(str(datetime.datetime.fromtimestamp(xmap + self.reftime.timestamp())))
        #self.labelYValue.setText(str(ymap))

        if emit_signal:
            self.cursorMoved.emit(
                datetime.datetime.fromtimestamp(xmap +
                                                self.reftime.timestamp()))

        self.update()

    def setCursorPositionFromTime(self, timestamp, emit_signal=False):
        # Converts timestamp to x value
        pos = self.chart.mapToPosition(
            QPointF((timestamp - self.reftime).total_seconds(), 0)).x()
        self.setCursorPosition(pos, emit_signal)

    def mouseReleaseEvent(self, e: QMouseEvent):
        # Handling rubberbands
        super().mouseReleaseEvent(e)
        pass

    def resizeEvent(self, e: QResizeEvent):
        super().resizeEvent(e)

        # Update cursor height
        area = self.chart.plotArea()
        line = self.cursor.line()
        self.cursor.setLine(line.x1(), area.y(), line.x2(),
                            area.y() + area.height())

        # self.scene().setSceneRect(0, 0, e.size().width(), e.size().height())
        # Need to reposition X,Y labels
        self.update_x_y_coords()
Пример #12
0
class TransitionGraphicsItem(QGraphicsObject):
    # constant values
    SQUARE_SIDE = 10
    ARROW_SIZE = 12
    PEN_NORMAL_WIDTH = 1
    PEN_FOCUS_WIDTH = 3

    posChanged = pyqtSignal('QGraphicsItem')

    def __init__(self, data):
        super(QGraphicsObject, self).__init__()
        self.transitionData = data

        self.originLine = None
        self.destinationLine = None
        self.arrow = None
        self.textGraphics = None
        self.middleHandle = None

        self.graphicsOrigin = self.transitionData.origin.getGraphicsItem()
        self.graphicsDestination = self.transitionData.destination.getGraphicsItem(
        )

        # connect position changed event
        self.graphicsOrigin.posChanged.connect(self.statePosChanged)
        self.graphicsDestination.posChanged.connect(self.statePosChanged)

        self.midPointX = (self.graphicsDestination.scenePos().x() +
                          self.graphicsOrigin.scenePos().x()) / 2.0
        self.midPointY = (self.graphicsDestination.scenePos().y() +
                          self.graphicsOrigin.scenePos().y()) / 2.0

        self.createOriginLine()
        self.createDestinationLine()

        self.createArrow()
        self.createMiddleHandle()
        self.createIdTextBox()

    def statePosChanged(self, state):
        if self.graphicsOrigin == state:
            self.createOriginLine()
        elif self.graphicsDestination == state:
            self.createDestinationLine()
            self.createArrow()

    def createOriginLine(self):
        if self.originLine == None:
            self.originLine = QGraphicsLineItem(
                self.midPointX, self.midPointY,
                self.graphicsOrigin.scenePos().x(),
                self.graphicsOrigin.scenePos().y(), self)
        else:
            self.originLine.setLine(
                QLineF(self.midPointX, self.midPointY,
                       self.graphicsOrigin.scenePos().x(),
                       self.graphicsOrigin.scenePos().y()))
        myLine = self.originLine.line()
        myLine.setLength(myLine.length() - StateGraphicsItem.NODE_WIDTH / 2)
        self.originLine.setLine(myLine)

    def createDestinationLine(self):
        if self.destinationLine == None:
            self.destinationLine = QGraphicsLineItem(
                self.midPointX, self.midPointY,
                self.graphicsDestination.scenePos().x(),
                self.graphicsDestination.scenePos().y(), self)
        else:
            self.destinationLine.setLine(
                QLineF(self.midPointX, self.midPointY,
                       self.graphicsDestination.scenePos().x(),
                       self.graphicsDestination.scenePos().y()))

        myLine = self.destinationLine.line()
        myLine.setLength(myLine.length() - StateGraphicsItem.NODE_WIDTH / 2)
        self.destinationLine.setLine(myLine)

    def createArrow(self):
        # add an arrow to destination line
        myLine = self.destinationLine.line()
        myLine.setLength(myLine.length() - TransitionGraphicsItem.ARROW_SIZE)
        rotatePoint = myLine.p2() - self.destinationLine.line().p2()

        rightPointX = rotatePoint.x() * math.cos(
            math.pi / 6) - rotatePoint.y() * math.sin(math.pi / 6)
        rightPointY = rotatePoint.x() * math.sin(
            math.pi / 6) + rotatePoint.y() * math.cos(math.pi / 6)
        rightPoint = QPointF(rightPointX + self.destinationLine.line().x2(),
                             rightPointY + self.destinationLine.line().y2())

        leftPointX = rotatePoint.x() * math.cos(
            -math.pi / 6) - rotatePoint.y() * math.sin(-math.pi / 6)
        leftPointY = rotatePoint.x() * math.sin(
            -math.pi / 6) + rotatePoint.y() * math.cos(-math.pi / 6)
        leftPoint = QPointF(leftPointX + self.destinationLine.line().x2(),
                            leftPointY + self.destinationLine.line().y2())

        polygon = QPolygonF()
        polygon << rightPoint << leftPoint << self.destinationLine.line().p2(
        ) << rightPoint

        if self.arrow == None:
            self.arrow = QGraphicsPolygonItem(polygon, self)
        else:
            self.arrow.setPolygon(polygon)

        brush = QBrush(Qt.SolidPattern)
        brush.setColor(Qt.black)
        self.arrow.setBrush(brush)

    def createMiddleHandle(self):
        # create middle handle
        if self.middleHandle == None:
            self.middleHandle = RectHandleGraphicsItem(
                TransitionGraphicsItem.SQUARE_SIDE, self)
            self.middleHandle.setFlag(QGraphicsItem.ItemIsMovable)

        self.middleHandle.setPos(self.midPointX, self.midPointY)

    def createIdTextBox(self):
        if self.textGraphics == None:
            self.textGraphics = IdTextBoxGraphicsItem(self.transitionData.name,
                                                      self)
            self.textGraphics.textChanged.connect(self.nameChanged)
        else:
            self.textGraphics.setPlainText(self.transitionData.name)
        textWidth = self.textGraphics.boundingRect().width()
        self.textGraphics.setPos(
            self.midPointX - textWidth / 2,
            self.midPointY + TransitionGraphicsItem.SQUARE_SIDE -
            (TransitionGraphicsItem.SQUARE_SIDE / 2) + 5)

    def updateMiddlePoints(self, newPosition):
        self.midPointX = newPosition.x()
        self.midPointY = newPosition.y()
        self.createOriginLine()
        self.createDestinationLine()
        self.createArrow()
        self.createIdTextBox()
        self.posChanged.emit(self)

    def nameChanged(self, name):
        self.transitionData.name = name
        self.createIdTextBox()

    def boundingRect(self):
        if self.middleHandle != None:
            return self.middleHandle.boundingRect()
        else:
            return None

    def disableInteraction(self):
        if self.middleHandle is not None:
            self.middleHandle.setFlag(QGraphicsItem.ItemIsMovable, False)
            self.middleHandle.disableInteraction()
Пример #13
0
class IMUChartView(QChartView, BaseGraph):
    def __init__(self, parent=None):
        super().__init__(parent=parent)

        self.cursor = QGraphicsLineItem()
        self.scene().addItem(self.cursor)

        self.xvalues = {}

        self.chart = QChart()
        self.setChart(self.chart)
        self.chart.legend().setVisible(True)
        self.chart.legend().setAlignment(Qt.AlignTop)
        self.ncurves = 0
        self.setRenderHint(QPainter.Antialiasing)

        # Selection features
        # self.setRubberBand(QChartView.HorizontalRubberBand)
        self.selectionBand = QRubberBand(QRubberBand.Rectangle, self)
        self.selecting = False
        self.initialClick = QPoint()

        # Track mouse
        self.setMouseTracking(True)

        self.labelValue = QLabel(self)
        self.labelValue.setStyleSheet(
            "background-color: rgba(255,255,255,75%); color: black;")
        self.labelValue.setAlignment(Qt.AlignCenter)
        self.labelValue.setMargin(5)
        self.labelValue.setVisible(False)

        self.build_style()

    def build_style(self):
        self.setStyleSheet("QLabel{color:blue;}")
        self.chart.setTheme(QChart.ChartThemeBlueCerulean)
        self.setBackgroundBrush(QBrush(Qt.darkGray))
        self.chart.setPlotAreaBackgroundBrush(QBrush(Qt.black))
        self.chart.setPlotAreaBackgroundVisible(True)

    def save_as_png(self, file_path):
        pixmap = self.grab()

        child = self.findChild(QOpenGLWidget)

        painter = QPainter(pixmap)
        if child is not None:
            d = child.mapToGlobal(QPoint()) - self.mapToGlobal(QPoint())
            painter.setCompositionMode(QPainter.CompositionMode_SourceAtop)
            painter.drawImage(d, child.grabFramebuffer())

        painter.end()
        pixmap.save(file_path, 'PNG')

    #  def closeEvent(self, event):
    #     self.aboutToClose.emit(self)

    @staticmethod
    def decimate(xdata, ydata):
        # assert(len(xdata) == len(ydata))

        # Decimate only if we have too much data
        decimate_factor = len(xdata) / 100000.0
        # decimate_factor = 1.0

        if decimate_factor > 1.0:
            decimate_factor = int(np.floor(decimate_factor))
            # print('decimate factor', decimate_factor)

            x = np.ndarray(int(len(xdata) / decimate_factor), dtype=np.float64)
            y = np.ndarray(int(len(ydata) / decimate_factor), dtype=np.float64)
            # for i in range(len(x)):
            for i, _ in enumerate(x):
                index = i * decimate_factor
                # assert(index < len(xdata))
                x[i] = xdata[index]
                y[i] = ydata[index]
                if x[i] < x[0]:
                    print('timestamp error', x[i], x[0])
            return x, y
        else:
            return xdata, ydata

    @pyqtSlot(float, float)
    def axis_range_changed(self, min_value, max_value):
        # print('axis_range_changed', min, max)
        for axis in self.chart.axes():
            axis.applyNiceNumbers()

    def update_axes(self):

        # Get and remove all axes
        for axis in self.chart.axes():
            self.chart.removeAxis(axis)

        # Create new axes
        # Create axis X
        axisx = QDateTimeAxis()
        axisx.setTickCount(5)
        axisx.setFormat("dd MMM yyyy hh:mm:ss")
        axisx.setTitleText("Date")
        self.chart.addAxis(axisx, Qt.AlignBottom)

        # Create axis Y
        axisY = QValueAxis()
        axisY.setTickCount(5)
        axisY.setLabelFormat("%.3f")
        axisY.setTitleText("Values")
        self.chart.addAxis(axisY, Qt.AlignLeft)
        # axisY.rangeChanged.connect(self.axis_range_changed)

        ymin = None
        ymax = None

        # Attach axes to series, find min-max
        for series in self.chart.series():
            series.attachAxis(axisx)
            series.attachAxis(axisY)
            vect = series.pointsVector()
            # for i in range(len(vect)):
            for i, _ in enumerate(vect):
                if ymin is None:
                    ymin = vect[i].y()
                    ymax = vect[i].y()
                else:
                    ymin = min(ymin, vect[i].y())
                    ymax = max(ymax, vect[i].y())

        # Update range
        # print('min max', ymin, ymax)
        if ymin is not None:
            axisY.setRange(ymin, ymax)

        # Make the X,Y axis more readable
        # axisx.applyNiceNumbers()
        # axisY.applyNiceNumbers()

    def add_data(self, xdata, ydata, color=None, legend_text=None):
        curve = QLineSeries()
        pen = curve.pen()
        if color is not None:
            pen.setColor(color)
        pen.setWidthF(1.5)
        curve.setPen(pen)
        # curve.setPointsVisible(True)
        # curve.setUseOpenGL(True)
        self.total_samples = max(self.total_samples, len(xdata))

        # Decimate
        xdecimated, ydecimated = self.decimate(xdata, ydata)

        # Data must be in ms since epoch
        # curve.append(self.series_to_polyline(xdecimated * 1000.0, ydecimated))
        # self.reftime = datetime.datetime.fromtimestamp(xdecimated[0])

        # if len(xdecimated) > 0:
        #    xdecimated = xdecimated - xdecimated[0]

        xdecimated *= 1000  # No decimal expected
        points = []
        for i, _ in enumerate(xdecimated):
            # TODO hack
            # curve.append(QPointF(xdecimated[i], ydecimated[i]))
            points.append(QPointF(xdecimated[i], ydecimated[i]))

        curve.replace(points)
        points.clear()

        if legend_text is not None:
            curve.setName(legend_text)

        # Needed for mouse events on series
        self.chart.setAcceptHoverEvents(True)
        self.xvalues[self.ncurves] = np.array(xdecimated)

        # connect signals / slots
        # curve.clicked.connect(self.lineseries_clicked)
        # curve.hovered.connect(self.lineseries_hovered)

        # Add series
        self.chart.addSeries(curve)
        self.ncurves += 1
        self.update_axes()

    def update_data(self, xdata, ydata, series_id):
        if series_id < len(self.chart.series()):
            # Find start time to replace data
            current_series = self.chart.series()[series_id]
            current_points = current_series.pointsVector()

            # Find start and end indexes
            start_index = -1
            try:
                start_index = current_points.index(
                    QPointF(xdata[0] * 1000, ydata[0]))  # Right on the value!
                # print("update_data: start_index found exact match.")
            except ValueError:
                # print("update_data: start_index no exact match - scanning deeper...")
                for i, value in enumerate(current_points):
                    # print(str(current_points[i].x()) + " == " + str(xdata[0]*1000))
                    if current_points[i].x() == xdata[0] * 1000 or (
                            i > 0 and current_points[i - 1].x() <
                            xdata[0] * 1000 < current_points[i].x()):
                        start_index = i
                        # print("update_data: start_index found approximative match.")
                        break

            end_index = -1
            try:
                end_index = current_points.index(
                    QPointF(xdata[len(xdata) - 1] * 1000,
                            ydata[len(ydata) - 1]))  # Right on!
                # print("update_data: start_index found exact match.")
            except ValueError:
                # print("update_data: start_index no exact match - scanning deeper...")
                for i, value in enumerate(current_points):
                    # print(str(current_points[i].x()) + " == " + str(xdata[0]*1000))
                    if current_points[i].x(
                    ) == xdata[len(xdata) - 1] * 1000 or (
                            i > 0 and
                            current_points[i - 1].x() < xdata[len(xdata) - 1] *
                            1000 < current_points[i].x()):
                        end_index = i
                        # print("update_data: start_index found approximative match.")
                        break

            if start_index < 0 or end_index < 0:
                return

            # Decimate, if needed
            xdata, ydata = self.decimate(xdata, ydata)

            # Check if we have the same number of points for that range. If not, remove and replace!
            target_points = current_points[start_index:end_index]
            if len(target_points) != len(xdata):
                points = []
                for i, value in enumerate(xdata):
                    # TODO improve
                    points.append(QPointF(value * 1000, ydata[i]))

                new_points = current_points[0:start_index] + points[0:len(points)-1] + \
                             current_points[end_index:len(current_points)-1]

                current_series.replace(new_points)
                new_points.clear()

                # self.xvalues[series_id] = np.array(xdata)

        return

    @classmethod
    def set_title(cls, title):
        # print('Setting title: ', title)
        # self.chart.setTitle(title)
        return

    @staticmethod
    def series_to_polyline(xdata, ydata):
        """Convert series data to QPolygon(F) polyline

        This code is derived from PythonQwt's function named
        `qwt.plot_curve.series_to_polyline`"""

        # print('series_to_polyline types:', type(xdata[0]), type(ydata[0]))
        size = len(xdata)
        polyline = QPolygonF(size)

        # for i in range(0, len(xdata)):
        #   polyline[i] = QPointF(xdata[i] - xdata[0], ydata[i])
        for i, data in enumerate(xdata):
            polyline[i] = QPointF(data - xdata[0], ydata[i])

        # pointer = polyline.data()
        # dtype, tinfo = np.float, np.finfo  # integers: = np.int, np.iinfo
        # pointer.setsize(2*polyline.size()*tinfo(dtype).dtype.itemsize)
        # memory = np.frombuffer(pointer, dtype)
        # memory[:(size-1)*2+1:2] = xdata
        # memory[1:(size-1)*2+2:2] = ydata
        return polyline

    def add_test_data(self):

        # 100Hz, one day accelerometer values
        npoints = 1000 * 60 * 24

        xdata = np.linspace(0., 10., npoints)
        self.add_data(xdata, np.sin(xdata), color=Qt.red, legend_text='Acc. X')
        # self.add_data(xdata, np.cos(xdata), color=Qt.green, legend_text='Acc. Y')
        # self.add_data(xdata, np.cos(2 * xdata), color=Qt.blue, legend_text='Acc. Z')
        self.set_title(
            "Simple example with %d curves of %d points (OpenGL Accelerated Series)"
            % (self.ncurves, npoints))

    def mouseMoveEvent(self, e: QMouseEvent):
        if self.selecting:
            current_pos = e.pos()
            if self.interaction_mode == GraphInteractionMode.SELECT:
                if current_pos.x() < self.initialClick.x():
                    start_x = current_pos.x()
                    width = self.initialClick.x() - start_x
                else:
                    start_x = self.initialClick.x()
                    width = current_pos.x() - self.initialClick.x()
                self.selectionBand.setGeometry(
                    QRect(start_x,
                          self.chart.plotArea().y(), width,
                          self.chart.plotArea().height()))
            if self.interaction_mode == GraphInteractionMode.MOVE:
                new_pos = current_pos - self.initialClick
                self.chart.scroll(-new_pos.x(), new_pos.y())
                self.initialClick = current_pos

    def mousePressEvent(self, e: QMouseEvent):
        # Handling rubberbands
        # super().mousePressEvent(e)
        self.selecting = True
        self.initialClick = e.pos()
        if self.interaction_mode == GraphInteractionMode.SELECT:
            self.selectionBand.setGeometry(
                QRect(self.initialClick.x(),
                      self.chart.plotArea().y(), 1,
                      self.chart.plotArea().height()))
            self.selectionBand.show()

        if self.interaction_mode == GraphInteractionMode.MOVE:
            QGuiApplication.setOverrideCursor(Qt.ClosedHandCursor)
            self.labelValue.setVisible(False)

    def mouseReleaseEvent(self, e: QMouseEvent):
        # Handling rubberbands
        clicked_x = self.mapToScene(e.pos()).x()

        if self.interaction_mode == GraphInteractionMode.SELECT:
            self.selectionBand.hide()
            if clicked_x == self.mapToScene(self.initialClick).x():
                self.setCursorPosition(clicked_x, True)
            else:
                mapped_x = self.mapToScene(self.initialClick).x()
                if self.initialClick.x() < clicked_x:
                    self.setSelectionArea(mapped_x, clicked_x, True)
                else:
                    self.setSelectionArea(clicked_x, mapped_x, True)

        if self.interaction_mode == GraphInteractionMode.MOVE:
            QGuiApplication.restoreOverrideCursor()

        self.selecting = False

    def clearSelectionArea(self, emit_signal=False):
        self.scene().removeItem(self.selection_rec)
        self.selection_rec = None

        if emit_signal:
            self.clearedSelectionArea.emit()

    def setSelectionArea(self, start_pos, end_pos, emit_signal=False):
        selection_brush = QBrush(QColor(153, 204, 255, 128))
        selection_pen = QPen(Qt.transparent)
        self.scene().removeItem(self.selection_rec)
        self.selection_rec = self.scene().addRect(
            start_pos,
            self.chart.plotArea().y(), end_pos - start_pos,
            self.chart.plotArea().height(), selection_pen, selection_brush)
        if emit_signal:
            self.selectedAreaChanged.emit(
                self.chart.mapToValue(QPointF(start_pos, 0)).x(),
                self.chart.mapToValue(QPointF(end_pos, 0)).x())

    def setSelectionAreaFromTime(self,
                                 start_time,
                                 end_time,
                                 emit_signal=False):
        # Convert times to x values
        if isinstance(start_time, datetime.datetime):
            start_time = start_time.timestamp() * 1000
        if isinstance(end_time, datetime.datetime):
            end_time = end_time.timestamp() * 1000

        start_pos = self.chart.mapToPosition(QPointF(start_time, 0)).x()
        end_pos = self.chart.mapToPosition(QPointF(end_time, 0)).x()

        self.setSelectionArea(start_pos, end_pos)

    def setCursorPosition(self, pos, emit_signal=False):
        # print (pos)
        pen = self.cursor.pen()
        pen.setColor(Qt.cyan)
        pen.setWidthF(1.0)
        self.cursor.setPen(pen)
        # On Top
        self.cursor.setZValue(100.0)

        area = self.chart.plotArea()
        x = pos
        y1 = area.y()
        y2 = area.y() + area.height()

        # self.cursor.set
        self.cursor.setLine(x, y1, x, y2)
        self.cursor.show()

        xmap_initial = self.chart.mapToValue(QPointF(pos, 0)).x()
        display = ''
        # '<i>' + (datetime.datetime.fromtimestamp(xmap + self.reftime.timestamp())).strftime('%d-%m-%Y %H:%M:%S') +
        # '</i><br />'
        ypos = 10
        last_val = None
        for i in range(self.ncurves):
            # Find nearest point
            idx = (np.abs(self.xvalues[i] - xmap_initial)).argmin()
            ymap = self.chart.series()[i].at(idx).y()
            xmap = self.chart.series()[i].at(idx).x()
            if i == 0:
                display += "<i>" + (datetime.datetime.fromtimestamp(xmap_initial/1000)).strftime('%d-%m-%Y %H:%M:%S:%f') + \
                           "</i>"

            # Compute where to display label
            if last_val is None or ymap > last_val:
                last_val = ymap
                ypos = self.chart.mapToPosition(QPointF(xmap, ymap)).y()
            if display != '':
                display += '<br />'

            display += self.chart.series()[i].name(
            ) + ': <b>' + '%.3f' % ymap + '</b>'

        self.labelValue.setText(display)
        self.labelValue.setGeometry(pos, ypos, 100, 100)
        self.labelValue.adjustSize()
        self.labelValue.setVisible(True)

        if emit_signal:
            self.cursorMoved.emit(xmap_initial)

        self.update()

    def setCursorPositionFromTime(self, timestamp, emit_signal=False):
        # Find nearest point
        if isinstance(timestamp, datetime.datetime):
            timestamp = timestamp.timestamp()
        pos = self.get_pos_from_time(timestamp)
        self.setCursorPosition(pos, emit_signal)

    def get_pos_from_time(self, timestamp):
        px = timestamp
        idx1 = (np.abs(self.xvalues[0] - px)).argmin()
        x1 = self.chart.series()[0].at(idx1).x()
        pos1 = self.chart.mapToPosition(QPointF(x1, 0)).x()
        idx2 = idx1 + 1
        if idx2 < len(self.chart.series()[0]):
            x2 = self.chart.series()[0].at(idx2).x()
            if x2 != x1:
                pos2 = self.chart.mapToPosition(QPointF(x2, 0)).x()
                x2 /= 1000
                x1 /= 1000
                pos = (((px - x1) / (x2 - x1)) * (pos2 - pos1)) + pos1
            else:
                pos = pos1
        else:
            pos = pos1
        return pos

    def resizeEvent(self, e: QResizeEvent):
        super().resizeEvent(e)

        # Update cursor height
        area = self.chart.plotArea()
        line = self.cursor.line()
        self.cursor.setLine(line.x1(), area.y(), line.x2(),
                            area.y() + area.height())

    def zoom_in(self):
        self.chart.zoomIn()
        self.update_axes()

    def zoom_out(self):
        self.chart.zoomOut()
        self.update_axes()

    def zoom_area(self):
        if self.selection_rec:
            zoom_rec = self.selection_rec.rect()
            zoom_rec.setY(0)
            zoom_rec.setHeight(self.chart.plotArea().height())
            self.chart.zoomIn(zoom_rec)
            self.clearSelectionArea(True)
            self.update_axes()

    def zoom_reset(self):
        self.chart.zoomReset()
        self.update_axes()

    def get_displayed_start_time(self):
        min_x = self.chart.mapToScene(self.chart.plotArea()).boundingRect().x()
        xmap = self.chart.mapToValue(QPointF(min_x, 0)).x()
        return datetime.datetime.fromtimestamp(xmap / 1000)

    def get_displayed_end_time(self):
        max_x = self.chart.mapToScene(self.chart.plotArea()).boundingRect().x()
        max_x += self.chart.mapToScene(
            self.chart.plotArea()).boundingRect().width()
        xmap = self.chart.mapToValue(QPointF(max_x, 0)).x()
        return datetime.datetime.fromtimestamp(xmap / 1000)

    @property
    def is_zoomed(self):
        return self.chart.isZoomed()
class TreeScene(QGraphicsScene):
    NODE_X_OFFSET = 100
    NODE_Y_OFFSET = 100
    ZOOM_SENSITIVITY = 0.05

    def __init__(self, view, gui, parent=None):
        """
        The constructor for a tree scene
        :param view: The view for this scene
        :param gui: the main window
        :param parent: The parent widget of this scene
        """
        super(TreeScene, self).__init__(QRectF(0, 0, 1, 1), parent)
        self.gui = gui
        self.app = self.gui.app
        self.view = view
        # mapping for model node to view node
        self.nodes = {}
        # disconnected graphical nodes in the view
        self.disconnected_nodes = []
        # Indicates if the scene is in info mode
        self.info_mode = False
        # Indicates if the scene is in simulator mode
        self.simulator_mode = False
        # Indicates if the TreeScene is dragged around
        self.dragging = False
        # The tree ModelNode being added to the scene
        self.adding_node: ModelNode = None
        # The drag drop node being added
        self.drag_drop_node: ViewNode = None
        # The node being connected to the tree
        self.connecting_node = None
        # Line connected to cursor when connecting nodes
        self.connecting_line = None
        # Indicates the node being dragged
        self.dragging_node = None
        # Position data for node reconnection
        self.reconnecting_node = None
        self.reconnect_edge_data = None
        # start position of every node
        self.node_init_pos = None
        # root of the tree
        self.root_ui_node = None

    def add_tree(self, tree: Tree, x: int = None, y: int = 0):
        """
        Adds model tree recursively to the scene
        :param x: The x position for the root node
        :param y: The y position for the root node
        :param tree: Model tree
        """
        # use the top center as default tree position
        if not x:
            x = self.width() / 2
        if not y:
            y = -(self.view.viewport().height() / 2) + ViewNode.NODE_HEIGHT
        self.node_init_pos = (x, y)
        # remove old content
        self.clear()
        tree = deepcopy(tree)
        # check if there is a root, otherwise do not display the tree
        if not tree.root:
            return
        # add disconnected nodes to root
        all_children = [
            c for node in tree.nodes.values() for c in node.children
        ]
        disconnected_model_nodes = [
            tree.nodes[node] for node in tree.nodes
            if node != tree.root and node not in all_children
        ]
        # sort nodes based on number of children
        disconnected_model_nodes = sorted(disconnected_model_nodes,
                                          key=lambda n: len(n.children),
                                          reverse=True)
        for i, d_node in enumerate(disconnected_model_nodes):
            d_view_node = DisconnectedNode(d_node)
            tree.nodes[d_view_node.id] = d_view_node
            tree.nodes[tree.root].children.append(d_view_node.id)
        # start recursively drawing tree
        root_node = tree.nodes[tree.root]
        self.root_ui_node = self.add_subtree(tree, root_node)[0]
        self.root_ui_node.top_collapse_expand_button.hide()
        self.addItem(self.root_ui_node)

    def clear(self):
        self.root_ui_node = None
        self.nodes.clear()
        self.disconnected_nodes.clear()
        return super(TreeScene, self).clear()

    def keyReleaseEvent(self, key_event):
        if key_event.key() == Qt.Key_Escape:
            if self.connecting_node:
                self.disconnected_nodes.append(self.connecting_node)
                self.removeItem(self.connecting_line)
                self.app.restoreOverrideCursor()
                # remove reset cursor filter (cursor already reset)
                self.app.removeEventFilter(self.app.wait_for_click_filter)
                node = self.gui.tree.nodes.get(self.connecting_node.id)
                self.connecting_node = None
                self.gui.update_tree(node)
            elif self.reconnecting_node:
                if self.reconnecting_node not in self.disconnected_nodes:
                    self.disconnected_nodes.append(self.reconnecting_node)
                self.removeItem(self.connecting_line)
                self.app.restoreOverrideCursor()
                # remove reset cursor filter (cursor already reset)
                self.app.removeEventFilter(self.app.wait_for_click_filter)
                old_parent = self.gui.tree.nodes[
                    self.reconnect_edge_data['old_parent'].id]
                self.reconnecting_node = None
                self.reconnect_edge_data = None
                self.gui.update_tree(old_parent)

    def add_subtree(self, tree: Tree, subtree_root: ModelNode):
        """
        Recursive functions that adds node and its children to the tree.
        :param tree: The complete tree, used for node lookup
        :param subtree_root: The root of this subtree/branch
        :return: The created subtree root node,
                 the width of both sides of the subtree
        """
        subtree_root_node = ViewNode(*self.node_init_pos,
                                     scene=self,
                                     title=subtree_root.title,
                                     model_node=subtree_root,
                                     node_types=self.gui.load_node_types)
        if isinstance(subtree_root, DisconnectedNode):
            subtree_root_node.top_collapse_expand_button.hide()
        self.nodes[subtree_root.id] = subtree_root_node
        if subtree_root.id not in self.gui.tree.nodes:
            self.gui.tree.nodes[subtree_root.id] = subtree_root
        if subtree_root.id == tree.root:
            connected_children = [
                c for c in subtree_root.children
                if not isinstance(tree.nodes[c], DisconnectedNode)
            ]
            middle_index = (len(connected_children) - 1) / 2
        else:
            middle_index = (len(subtree_root.children) - 1) / 2
        # keep track of level width to prevent overlapping nodes
        subtree_left_width = subtree_right_width = 0
        # store the left nodes so that they can be moved left during creation
        left_nodes = []
        # iterate over the left nodes
        for i, child_id in enumerate(
                subtree_root.children[:math.ceil(middle_index)]):
            child = tree.nodes[child_id]
            # add the child and its own subtree,
            # returned values are used to adjust the nodes position to prevent overlap
            child_view_node, child_subtree_width_left, child_subtree_width_right = self.add_subtree(
                tree, child)
            subtree_root_node.add_child(child_view_node)
            move_x = -(child_subtree_width_left + child_subtree_width_right)
            # prevent double spacing when there is no middle node
            if i == math.floor(middle_index) and not middle_index.is_integer():
                # use half the offset because the other half is added later for the other part of the tree
                move_x -= self.NODE_X_OFFSET / 2
                child_view_node.moveBy(
                    -(child_subtree_width_right + (self.NODE_X_OFFSET / 2)),
                    (subtree_root_node.rect().height() / 2) +
                    self.NODE_Y_OFFSET)
            else:
                # use the default node offset
                move_x -= self.NODE_X_OFFSET
                child_view_node.moveBy(
                    -(child_subtree_width_right + self.NODE_X_OFFSET),
                    (subtree_root_node.rect().height() / 2) +
                    self.NODE_Y_OFFSET)
            # add width to total left subtree width
            subtree_left_width += abs(move_x)
            # move all previous nodes to the left to make room for the new node
            for n in left_nodes:
                n.moveBy(move_x, 0)
            left_nodes.append(child_view_node)
        # add middle node
        if middle_index.is_integer():
            child_id = subtree_root.children[int(middle_index)]
            child = tree.nodes[child_id]
            # add the child and its own subtree,
            # returned values are used to adjust the nodes position to prevent overlap
            child_view_node, child_subtree_width_left, child_subtree_width_right = self.add_subtree(
                tree, child)
            subtree_root_node.add_child(child_view_node)
            child_view_node.moveBy(0, (subtree_root_node.rect().height() / 2) +
                                   self.NODE_Y_OFFSET)
            # move all left nodes further to the left to make room for the middle node
            move_x = -self.NODE_X_OFFSET + child_subtree_width_left
            if child_subtree_width_left > self.NODE_X_OFFSET:
                move_x = -child_subtree_width_left
            for n in left_nodes:
                n.moveBy(move_x, 0)
            subtree_left_width += child_subtree_width_left
            subtree_right_width += child_subtree_width_right
        # iterate over the right nodes
        for i, child_id in enumerate(
                subtree_root.children[math.floor(middle_index) + 1:]):
            child = tree.nodes[child_id]
            # add the child and its own subtree,
            # returned values are used to adjust the nodes position to prevent overlap
            child_view_node, child_subtree_width_left, child_subtree_width_right = self.add_subtree(
                tree, child)
            if not isinstance(child, DisconnectedNode):
                subtree_root_node.add_child(child_view_node)
            else:
                self.disconnected_nodes.append(child_view_node)
                self.addItem(child_view_node)
            move_x = subtree_right_width + child_subtree_width_left
            # add width to total right subtree width
            subtree_right_width += child_subtree_width_left + child_subtree_width_right
            # prevent double spacing when there is no middle node
            if i == 0 and not middle_index.is_integer():
                # use half the offset because the other half is added already
                move_x += self.NODE_X_OFFSET / 2
                subtree_right_width += (self.NODE_X_OFFSET / 2)
            else:
                # use the default node offset
                move_x += self.NODE_X_OFFSET
                subtree_right_width += self.NODE_X_OFFSET
            # move node next to the previous node, all the way to the right
            child_view_node.moveBy(move_x, 0)
            if not isinstance(child, DisconnectedNode):
                child_view_node.moveBy(
                    0, (subtree_root_node.rect().height() / 2) +
                    self.NODE_Y_OFFSET)
        # set the widths of both subtree sides to a default value if no children or a smaller child node
        if not subtree_root.children or subtree_left_width < subtree_root_node.rect().width() / 2 or \
                subtree_right_width < subtree_root_node.rect().width() / 2:
            subtree_left_width = subtree_right_width = subtree_root_node.rect(
            ).width() / 2
        return subtree_root_node, subtree_left_width, subtree_right_width

    def change_root(self, node_id: str):
        self.gui.tree.root = node_id
        if node_id == '':
            if self.root_ui_node and not self.root_ui_node.parentItem():
                self.disconnected_nodes.append(self.root_ui_node)
            self.root_ui_node = None
        else:
            if self.root_ui_node:
                self.disconnected_nodes.append(self.root_ui_node)
            node = self.nodes[node_id]
            self.root_ui_node = node
            try:
                self.disconnected_nodes.remove(node)
            except ValueError:
                pass
        self.update()
        self.gui.update_tree()

    def update_children(self, node_ids: List[str]):
        for node_id in node_ids:
            if node_id in self.nodes:
                model_node = self.gui.tree.nodes[node_id]
                view_node = self.nodes[node_id]
                if self.view.parent().property_display and \
                        self.view.parent().property_display.node_id in [n.id for n in view_node.nodes_below()]:
                    self.close_property_display()
                for c in view_node.children:
                    c.delete_subtree(update_tree=False)
                for e in view_node.edges:
                    e.setParentItem(None)
                    self.removeItem(e)
                view_node.edges.clear()
                for c_id in model_node.children:
                    child_model_node = self.gui.tree.nodes[c_id]
                    child_view_node = self.add_subtree(self.gui.tree,
                                                       child_model_node)[0]
                    view_node.add_child(child_view_node)
                self.align_while_colliding()

    def align_tree(self):
        """
        Aligns the tree currently visible in the ui
        """
        if self.gui.tree:
            if self.root_ui_node:
                self.align_from_node(self.root_ui_node)

    def align_while_colliding(self, node: ViewNode = None):
        if not node:
            node = self.root_ui_node
        if self.gui.tree:
            if node:
                colliding_below = [
                    nb for nb in node.nodes_below() if [
                        ci for ci in nb.collidingItems()
                        if isinstance(ci, ViewNode)
                    ]
                ]
                colliding_below.sort(key=lambda n: len(n.nodes_below()),
                                     reverse=True)
                for node in colliding_below:
                    align_node = node
                    while [
                            ci for ci in node.collidingItems()
                            if isinstance(ci, ViewNode)
                    ]:
                        if align_node.parentItem():
                            align_node = align_node.parentItem().parentItem()
                            self.align_from_node(align_node)
                        else:
                            break

    def align_from_node(self, node: ViewNode):
        """
        Align all the children of a node
        Works like add_subtree but repositions existing nodes instead of creating nodes
        :param node: The root of the alignment
        """
        middle_index = (len(node.children) - 1) / 2
        # keep track of level width to prevent overlapping nodes
        subtree_left_width = subtree_right_width = 0
        # store the left nodes so that they can be moved left during repositioning
        left_nodes = []
        # iterate over the left nodes
        for i, child in enumerate(node.children[:math.ceil(middle_index)]):
            # calculate values for position adjustment to prevent overlap
            child_subtree_width_left, child_subtree_width_right = self.align_from_node(
                child)
            # only align visible nodes
            if node.isVisible() and child.isVisible():
                move_x = -(child_subtree_width_left +
                           child_subtree_width_right)
                # reset node to start position
                child.setPos(0,
                             (node.rect().height() / 2) + self.NODE_Y_OFFSET)
                # prevent double spacing when there is no middle node
                if i == math.floor(
                        middle_index) and not middle_index.is_integer():
                    # use half the offset because the other half is added later for the other part of the tree
                    move_x -= self.NODE_X_OFFSET / 2
                    child.moveBy(
                        -(child_subtree_width_right +
                          (self.NODE_X_OFFSET / 2)), 0)
                else:
                    # use the default node offset
                    move_x -= self.NODE_X_OFFSET
                    child.moveBy(
                        -(child_subtree_width_right + self.NODE_X_OFFSET), 0)
                # add width to total left subtree width
                subtree_left_width += abs(move_x)
                # move all previous nodes to the left to make room for the current node
                for n in left_nodes:
                    n.moveBy(move_x, 0)
                left_nodes.append(child)
        # reposition middle node
        if middle_index.is_integer():
            child = node.children[int(middle_index)]
            # calculate values for position adjustment to prevent overlap
            child_subtree_width_left, child_subtree_width_right = self.align_from_node(
                child)
            # only align visible nodes
            if node.isVisible() and child.isVisible():
                # reset node to start position
                child.setPos(0,
                             (node.rect().height() / 2) + self.NODE_Y_OFFSET)
                # move all left nodes further to the left to make room for the middle node
                move_x = -self.NODE_X_OFFSET + child_subtree_width_left
                if child_subtree_width_left > self.NODE_X_OFFSET:
                    move_x = -child_subtree_width_left
                for n in left_nodes:
                    n.moveBy(move_x, 0)
                subtree_left_width += child_subtree_width_left
                subtree_right_width += child_subtree_width_right
        # iterate over the right nodes
        right_nodes = node.children[math.floor(middle_index) + 1:]
        if node == self.root_ui_node:
            right_nodes.extend(self.disconnected_nodes)
        for i, child in enumerate(right_nodes):
            # calculate values for position adjustment to prevent overlap
            child_subtree_width_left, child_subtree_width_right = self.align_from_node(
                child)
            # only align visible nodes
            if node.isVisible() and child.isVisible():
                move_x = subtree_right_width + child_subtree_width_left
                # add width to total right subtree width
                subtree_right_width += child_subtree_width_left + child_subtree_width_right
                # prevent double spacing when there is no middle node
                if i == 0 and not middle_index.is_integer():
                    # use half the offset because the other half is added already
                    move_x += self.NODE_X_OFFSET / 2
                    subtree_right_width += (self.NODE_X_OFFSET / 2)
                else:
                    # use the default node offset
                    move_x += self.NODE_X_OFFSET
                    subtree_right_width += self.NODE_X_OFFSET
                # reset node to start position
                if child not in self.disconnected_nodes:
                    child.setPos(0, (node.rect().height() / 2) +
                                 self.NODE_Y_OFFSET)
                else:
                    child.setPos(self.root_ui_node.pos())
                # move node next to the previous node, all the way to the right
                child.moveBy(move_x, 0)
        # set the widths of both subtree sides to a default value if no children or a smaller child node
        if not node.children or subtree_left_width < node.rect().width() / 2 or \
                subtree_right_width < node.rect().width() / 2:
            subtree_left_width = subtree_right_width = node.rect().width() / 2
        return subtree_left_width, subtree_right_width

    def switch_info_mode(self, info_mode: bool):
        self.info_mode = info_mode
        if self.root_ui_node:
            self.root_ui_node.initiate_view(propagate=True)
        for n in self.disconnected_nodes:
            n.initiate_view(propagate=True)

    def mousePressEvent(self, m_event):
        """
        Handles a mouse press on the scene
        :param m_event: The mouse press event and its details
        """
        # hide connecting line to prevent it from being clicked
        if self.connecting_line:
            self.connecting_line.hide()
        item = self.itemAt(m_event.scenePos(), self.view.transform())
        if self.connecting_line:
            self.connecting_line.show()
        if self.adding_node:
            x = int(m_event.scenePos().x())
            y = int(m_event.scenePos().y())
            self.start_node_addition(x, y)
            self.adding_node = None
            self.close_property_display()
            return
        elif self.connecting_node:
            if item and (isinstance(item, ViewNode) or
                         (item.parentItem()
                          and isinstance(item.parentItem(), ViewNode))):
                clicked_node = item if isinstance(
                    item, ViewNode) else item.parentItem()
                if clicked_node == self.connecting_node:
                    return
                self.finish_connect_edge(clicked_node)
            elif not item and m_event.button() == Qt.LeftButton:
                self.dragging = True
                self.view.setCursor(Qt.ClosedHandCursor)
            return
        elif self.reconnecting_node:
            if item and (isinstance(item, ViewNode) or
                         (item.parentItem()
                          and isinstance(item.parentItem(), ViewNode))):
                clicked_node = item if isinstance(
                    item, ViewNode) else item.parentItem()
                if clicked_node == self.reconnecting_node:
                    return
                self.finish_reconnect_edge(clicked_node)
            elif not item and m_event.button() == Qt.LeftButton:
                self.dragging = True
                self.view.setCursor(Qt.ClosedHandCursor)
            return
        else:
            if m_event.button() == Qt.LeftButton and item:
                if isinstance(item, CollapseExpandButton):
                    pass
                elif isinstance(item, ViewNode):
                    self.dragging_node = item
                    item.dragging = True
                elif isinstance(item.parentItem(), ViewNode):
                    self.dragging_node = item.parentItem()
                    item.parentItem().dragging = True
                elif item.parentItem():
                    if isinstance(item.parentItem().parentItem(), ViewNode):
                        self.dragging_node = item.parentItem().parentItem()
                        item.parentItem().parentItem().dragging = True
            # Set dragging state of the scene
            elif m_event.button() == Qt.LeftButton:
                self.dragging = True
                self.view.setCursor(Qt.ClosedHandCursor)
            # Remove property display window and save changes
            if not item:
                self.close_property_display()
        super(TreeScene, self).mousePressEvent(m_event)

    def close_property_display(self):
        if self.view.parent().property_display:
            self.view.parent().property_display.setParent(None)
            self.view.parent().property_display.deleteLater()
            self.view.parent().property_display = None

    def mouseReleaseEvent(self, m_event):
        """
        Handles a mouse release on the scene
        :param m_event: The mouse release event and its details
        """
        super(TreeScene, self).mouseReleaseEvent(m_event)
        # reset dragging state of the scene and all nodes
        if m_event.button() == Qt.LeftButton:
            if self.dragging:
                self.dragging = False
                self.view.setCursor(Qt.OpenHandCursor)
            elif self.dragging_node:
                # reset node to default mode
                self.dragging_node.dragging = False
                self.dragging_node = None

    def mouseMoveEvent(self, m_event):
        """
        Handles a mouse move on the scene
        :param m_event: The mouse move event and its details
        """
        super(TreeScene, self).mouseMoveEvent(m_event)
        # pass move event to dragged node
        if self.drag_drop_node:
            # initiate connection state if tree has a root
            if self.gui.tree and self.gui.tree.root != '':
                self.connecting_node = self.drag_drop_node
                x, y = self.drag_drop_node.xpos(), self.drag_drop_node.ypos()
                self.connecting_line = QGraphicsLineItem(
                    x, y - self.drag_drop_node.rect().height() / 2, x, y)
                # keep connection line on top
                self.connecting_line.setZValue(1)
                self.addItem(self.connecting_line)
                self.app.add_cross_cursor(self)
            else:
                # add root to model of the tree
                self.gui.tree.root = self.drag_drop_node.id
                self.root_ui_node = self.drag_drop_node
            node = self.gui.tree.nodes.get(self.drag_drop_node.id)
            self.gui.update_tree(node)
            self.drag_drop_node = None
        if self.dragging_node:
            self.dragging_node.mouseMoveEvent(m_event)
            return
        # adjust connection line when connecting node
        if self.connecting_line:
            line = self.connecting_line.line()
            line.setP2(m_event.scenePos() - QPoint(-1, 1))
            self.connecting_line.setLine(line)
        # pass mouse move event to top item that accepts hover events
        item = self.itemAt(m_event.scenePos(), self.view.transform())
        if item:
            if item.acceptHoverEvents():
                item.mouseMoveEvent(m_event)
                return
            else:
                # look for parent that accepts hover events
                while item.parentItem():
                    item = item.parentItem()
                    if item.acceptHoverEvents():
                        item.mouseMoveEvent(m_event)
                        return
        # check if scene is being dragged and move all items accordingly
        if self.dragging:
            dx = m_event.scenePos().x() - m_event.lastScenePos().x()
            dy = m_event.scenePos().y() - m_event.lastScenePos().y()
            for g_item in [i for i in self.items() if not i.parentItem()]:
                if g_item == self.connecting_line:
                    line = self.connecting_line.line()
                    line.setP1(line.p1() + QPointF(dx, dy))
                    self.connecting_line.setLine(line)
                else:
                    g_item.moveBy(dx, dy)

    def zoom(self, zoom_x, zoom_y):
        """
        Zooms the view
        :param zoom_x: Zoom percentage for the x-axis
        :param zoom_y: Zoom percentage for the y-axis
        """
        self.view.scale(zoom_x, zoom_y)

    def wheelEvent(self, wheel_event):
        """
        Handles a mousewheel scroll in the scene
        :param wheel_event: The mousewheel event and its details
        """
        zoom_value = 1 + (self.ZOOM_SENSITIVITY * (wheel_event.delta() / 120))
        self.zoom(zoom_value, zoom_value)

    def dragEnterEvent(self, drag_drop_event):
        if self.drag_drop_node:
            return
        mime_data = drag_drop_event.mimeData()
        if mime_data.hasText() and self.gui.tree:
            drag_drop_event.accept()
            node_type = json.loads(mime_data.text())
            node = NodeTypes.create_node_from_node_type(node_type)
            # setting this attribute starts node addition sequence in the scene
            self.gui.tree.add_node(node)
            node = ViewNode(*self.node_init_pos,
                            scene=self,
                            model_node=node,
                            title=node.title,
                            node_types=self.gui.load_node_types)
            self.drag_drop_node = node
            node.top_collapse_expand_button.hide()
            self.nodes[node.id] = node
            x, y = drag_drop_event.scenePos().x(), drag_drop_event.scenePos(
            ).y()
            node.moveBy(x - self.node_init_pos[0], y - self.node_init_pos[1])
            self.addItem(node)
        else:
            drag_drop_event.ignore()

    def dragMoveEvent(self, drag_drop_event):
        x, y = drag_drop_event.scenePos().x(), drag_drop_event.scenePos().y()
        if self.drag_drop_node:
            self.drag_drop_node.setPos(x - self.node_init_pos[0],
                                       y - self.node_init_pos[1])

    def dragLeaveEvent(self, drag_drop_event):
        if self.drag_drop_node:
            self.removeItem(self.drag_drop_node)
            self.gui.tree.nodes.pop(self.drag_drop_node.id, None)
            self.drag_drop_node = None

    def start_node_addition(self, x, y):
        """
        Starts node addition sequence, spawn a node and let a connection line follow the cursor
        :param x: Clicked x position in the scene
        :param y: Clicked y position in the scene
        :return:
        """
        # create subtree based on model node
        node = self.add_subtree(self.gui.tree, self.adding_node)[0]
        node.top_collapse_expand_button.hide()
        self.nodes[self.adding_node.id] = node
        # adjust to correct position
        node.moveBy(x - self.node_init_pos[0], y - self.node_init_pos[1])
        self.addItem(node)
        self.gui.tree.add_node(self.adding_node)
        # initiate connection state if tree has a root
        if self.gui.tree and self.gui.tree.root != '':
            self.connecting_node = node
            self.connecting_line = QGraphicsLineItem(
                x, y - node.rect().height() / 2, x, y)
            # keep connection line on top
            self.connecting_line.setZValue(1)
            self.addItem(self.connecting_line)
        else:
            # add root to model of the tree
            self.gui.tree.root = node.id
            # reset back to normal cursor
            self.app.restoreOverrideCursor()

    def finish_connect_edge(self, parent_node):
        # check for cycles in subtree
        connecting_model_node = self.gui.tree.nodes.get(
            self.connecting_node.id)
        parent_model_node = self.gui.tree.nodes.get(parent_node.id)
        if TreeScene.check_for_cycles_when_connecting(connecting_model_node,
                                                      parent_model_node,
                                                      self.gui.tree):
            return
        # remember current node position
        node_pos = (self.connecting_node.xpos(), self.connecting_node.ypos())
        # add child to parent ViewNode
        parent_node.add_child(self.connecting_node)

        # move node back to original position
        self.connecting_node.moveBy(node_pos[0] - self.connecting_node.xpos(),
                                    node_pos[1] - self.connecting_node.ypos())
        # sort the children in the UI and get correct model node order
        sorted_children = parent_node.sort_children()
        # set correct child order
        parent_model_node.children = [c.id for c in sorted_children]
        self.gui.tree.nodes[parent_model_node.id].children = [
            c.id for c in sorted_children
        ]
        self.removeItem(self.connecting_line)
        # reset back to normal cursor
        self.app.restoreOverrideCursor()
        # remove reset cursor filter (cursor already reset)
        self.app.removeEventFilter(self.app.wait_for_click_filter)
        node = self.gui.tree.nodes.get(self.connecting_node.id)
        self.gui.update_tree(node)
        self.connecting_node = None
        self.connecting_line = None

    def start_reconnect_edge(self, node):
        self.reconnecting_node = node
        self.connecting_line = QGraphicsLineItem(
            node.xpos(),
            node.ypos() - node.rect().height() / 2, node.xpos(), node.ypos())
        self.connecting_line.setZValue(1)
        self.addItem(self.connecting_line)
        if node.parentItem():
            self.reconnect_edge_data = node.detach_from_parent()
            self.gui.tree.nodes[
                self.reconnect_edge_data['old_parent'].id].children.remove(
                    node.id)
            node.top_collapse_expand_button.hide()
        self.app.add_cross_cursor(self)

    def finish_reconnect_edge(self, parent_node):
        reconnecting_model_node = self.gui.tree.nodes.get(
            self.reconnecting_node.id)
        parent_model_node = self.gui.tree.nodes.get(parent_node.id)
        if TreeScene.check_for_cycles_when_connecting(reconnecting_model_node,
                                                      parent_model_node,
                                                      self.gui.tree):
            return
        self.reconnecting_node.attach_to_parent(self.reconnect_edge_data,
                                                parent_node)
        self.reconnecting_node.top_collapse_expand_button.show()
        if self.reconnecting_node in self.disconnected_nodes:
            self.disconnected_nodes.remove(self.reconnecting_node)
        sorted_children = parent_node.sort_children()
        self.gui.tree.nodes[parent_node.id].children = [
            c.id for c in sorted_children
        ]
        self.removeItem(self.connecting_line)
        # reset back to normal cursor
        self.app.restoreOverrideCursor()
        # remove reset cursor filter (cursor already reset)
        self.app.removeEventFilter(self.app.wait_for_click_filter)
        node = self.gui.tree.nodes.get(self.reconnecting_node.id)
        self.reconnecting_node = None
        self.reconnect_edge_data = None
        self.gui.update_tree(node)

    def change_colors(self, node_colors: dict):
        for node in self.nodes:
            if node in node_colors:
                self.nodes[node].simulator_brush.setColor(node_colors[node])
        self.update()

    @staticmethod
    def check_for_cycles_when_connecting(subtree_node, parent_node: ModelNode,
                                         tree: Tree) -> bool:
        cycles = False
        cycles |= True if parent_node.id is subtree_node.id else False
        cycles |= True if parent_node.id in tree.nodes[
            subtree_node.id].children else False
        for child_id in tree.nodes[subtree_node.id].children:
            cycles |= TreeScene.check_for_cycles_when_connecting(
                tree.nodes.get(child_id), parent_node, tree)
        return cycles
Пример #15
0
class AttackTreeScene(QGraphicsScene):
    """
    This Class Implements the click actions for the graphics scene
    """

    def __init__(self, parent=None):
        """
        Constructor for the AttackTreeScene.
        Sets the needed class variables and initializes the context menu

        :param parent: Parent widget for the AttackTreeScene
        """
        super().__init__(parent)
        self.startCollisions = None
        self.dstCollisions = None
        self.conjunction = None
        self.insertLine = None

        self.mousePos = (0, 0)

        self.menu = QMenu(parent)

        self.menu.addAction('Alternative', self.addAlternative)
        self.menu.addAction('Composition', self.addComposition)
        self.menu.addAction('Sequence', self.addSequence)
        self.menu.addAction('Threshold', self.addThreshold)

    def addAlternative(self):
        """
        Adds an alternative as edge
        """
        self.addEdge('alternative')

    def addComposition(self):
        """
        Adds an composition as edge
        """
        self.addEdge('composition')

    def addSequence(self):
        """
        Adds an sequence as edge
        """
        self.addEdge('sequence')

    def addThreshold(self):
        """
        Adds an threshold as edge
        """
        self.addEdge('threshold')

    def addEdge(self, type):
        """
        Adds an edge to the graph with the specific type
        :param type: Type of the edge (alternative|alternative|sequence|threshold)
        """

        node = types.Conjunction(conjunctionType=type)
        self.parent().tree.addNode(node)

        n = Conjunction(node, self.parent(), x=self.mousePos[0], y=self.mousePos[1])
        self.addItem(n)

        self.reset()
        self.parent().saved = False

    def reset(self):
        """
        Resets all actions if a mode was selected.
        Also deletes the Line for inserting a edge
        """
        self.startCollisions = None
        self.dstCollisions = None
        self.conjunction = None
        self.parent().mode = 0
        self.parent().modeAction.setChecked(False)
        self.parent().modeAction = self.parent().defaultModeAction
        self.parent().modeAction.setChecked(True)
        if self.insertLine is not None:
            self.removeItem(self.insertLine)
            self.insertLine = None
        self.parent().setCursor(Qt.ArrowCursor)
        self.parent().graphicsView.setDragMode(QGraphicsView.RubberBandDrag)

    def mousePressEvent(self, mouseEvent):
        """
        Handles the press event for the mouse
        On click it will insert a node or set the start position for a conjunction

        :param mouseEvent: Mouse Event
        """
        if mouseEvent.button() == Qt.LeftButton:
            if self.parent().mode == 1:
                """
                Mode 1: Insert threat node
                """
                self.parent().addLastAction()

                node = types.Threat()
                self.parent().tree.addNode(node)
                n = Threat(node, self.parent(), mouseEvent.scenePos().x(), mouseEvent.scenePos().y())
                self.addItem(n)

                edit = NodeEdit(n, n.parent)
                edit.exec()

                self.parent().saved = False
                self.reset()
                super().mousePressEvent(mouseEvent)
            elif self.parent().mode == 2:
                """
                Mode 2: Insert countermeasure node
                """
                self.parent().addLastAction()

                node = types.Countermeasure()
                self.parent().tree.addNode(node)
                n = Countermeasure(node, self.parent(), mouseEvent.scenePos().x(), mouseEvent.scenePos().y())
                self.addItem(n)
                self.parent().saved = False

                edit = NodeEdit(n, n.parent)
                edit.exec()

                super().mousePressEvent(mouseEvent)
                self.reset()

            elif self.parent().mode == 3:
                """
                Mode 3: Insert conjunction node
                Displays an popup menu
                """
                self.mousePos = mouseEvent.scenePos().x(), mouseEvent.scenePos().y()
                self.menu.popup(
                    self.parent().mapToGlobal(self.parent().graphicsView.mapFromScene(mouseEvent.scenePos())), None)
                super().mousePressEvent(mouseEvent)
                self.reset()
            elif self.parent().mode == 4:
                """
                Mode 4: Insert line
                Start node of the line is set here
                """
                self.startCollisions = self.itemAt(mouseEvent.scenePos(), QTransform())
                self.insertLine = QGraphicsLineItem(QLineF(mouseEvent.scenePos(), mouseEvent.scenePos()))
                self.insertLine.setPen(QPen(Qt.black, 2))
                self.addItem(self.insertLine)
            elif self.parent().mode == 6:
                """
                Mode 6: Insert copy buffer
                """
                self.parent().insertCopyBuffer(mouseEvent.scenePos().x(), mouseEvent.scenePos().y())
                self.reset()
            else:
                super().mousePressEvent(mouseEvent)
        elif mouseEvent != Qt.RightButton:
            self.reset()
            super().mousePressEvent(mouseEvent)

    def contextMenuEvent(self, event):
        """
        Handles the event to open a context menu on a node
        The event will open a context menu to edit the node

        :param event: context menu Event
        """
        try:
            if len(self.selectedItems()) > 0:
                menu = QMenu(self.parent())
                menu.addAction('Delete', self.deleteSelected)
                menu.addAction('Select Children', self.selectNodesChildren)
                menu.addAction('Copy', self.parent().copy)
                menu.addAction('Cut', self.parent().cut)

                menu.popup(event.screenPos(), None)
            elif self.itemAt(event.scenePos(), QTransform()) is not None:
                item = self.itemAt(event.scenePos(), QTransform())
                item.setSelected(True)
                menu = QMenu(self.parent())

                if isinstance(item.parentItem(), Node):
                    item = item.parentItem()
                    menu.addAction('Edit', item.edit)
                    menu.addAction('Delete', item.delete)
                    menu.addAction('Select Children', item.selectChildren)
                    menu.addAction('Copy', self.parent().copy)
                    menu.addAction('Cut', self.parent().cut)
                elif isinstance(item.parentItem(), QGraphicsItemGroup) and isinstance(item.parentItem().parentItem(),
                                                                                      Node):
                    item = item.parentItem().parentItem()
                    menu.addAction('Edit', item.edit)
                    menu.addAction('Delete', item.delete)
                    menu.addAction('Select Children', item.selectChildren)
                    menu.addAction('Copy', self.parent().copy)
                    menu.addAction('Cut', self.parent().cut)
                elif isinstance(item, Edge):
                    menu.addAction('Delete', functools.partial(self.deleteEdge, item))
                    menu.addAction('Select Children', item.selectChildren)
                else:
                    menu.popup(event.screenPos(), None)
            else:
                menu = QMenu(self.parent())
                menu.addAction('Paste', functools.partial(self.parent().insertCopyBuffer, event.scenePos().x(),
                                                          event.scenePos().y()))
                menu.popup(event.screenPos(), None)

        except Exception as e:
            print(traceback.format_exc())

    def deleteEdge(self, edge):
        """
        Deletes an edge
        :param edge: Edge to delete
        """
        self.removeItem(edge)
        edge.start.childEdges.remove(edge)
        edge.dst.parentEdges.remove(edge)
        self.parent().tree.removeEdge(edge.start.node.id + '-' + edge.dst.node.id)

    def deleteSelected(self):
        """
        Deletes an selection
        """
        self.parent().addLastAction()
        deleted = []
        for i in self.selectedItems():
            if i not in deleted:
                if isinstance(i, Node):
                    deleted.append(i)
                    i.delete()
                elif isinstance(i, Edge):
                    deleted.append(i)
                    if not (i.start in deleted or i.dst in deleted):
                        self.removeItem(i)
                        i.start.childEdges.remove(i)
                        i.dst.parentEdges.remove(i)
                        self.parent().tree.removeEdge(i.start.node.id + '-' + i.dst.node.id)

    def selectNodesChildren(self):
        """
        Selects all children of the selection
        """
        for i in self.selectedItems():
            i.selectChildren()

    def mouseMoveEvent(self, mouseEvent):
        """
        Handler for the move event of the mouse.
        If the mode is to draw a line (3) it will update the feedback line

        :param mouseEvent: Mouse Event
        """
        if self.insertLine is not None:
            newLine = QLineF(self.insertLine.line().p1(), mouseEvent.scenePos())
            self.insertLine.setLine(newLine)
        super().mouseMoveEvent(mouseEvent)

    def mouseReleaseEvent(self, mouseEvent):
        """
        Handles the mouse release event.
        In this event the edge will be completed or the item to delete are certain

        :param mouseEvent: Mouse Event
        """
        if mouseEvent.button() == Qt.LeftButton:
            if self.parent().mode == 4:
                """
                Mode 4: Insert edge
                Inserts the edge
                """
                self.parent().addLastAction()
                self.insertLine.setZValue(-1)
                self.dstCollisions = self.itemAt(mouseEvent.scenePos(), QTransform())
                if self.startCollisions is None or self.dstCollisions is None \
                        or self.startCollisions == self.dstCollisions:
                    self.reset()
                    super().mouseReleaseEvent(mouseEvent)
                    return
                if isinstance(self.startCollisions.parentItem(), Node):
                    """
                    Gets the start node view object
                    """
                    self.startCollisions = self.startCollisions.parentItem()
                elif isinstance(self.startCollisions.parentItem(), QGraphicsItemGroup) \
                        and isinstance(self.startCollisions.parentItem().parentItem(), Node):
                    """
                    Gets the start node view object if the user clicks on the text in the item
                    """
                    self.startCollisions = self.startCollisions.parentItem().parentItem()
                else:
                    self.reset()
                    super().mouseReleaseEvent(mouseEvent)
                    return
                if isinstance(self.dstCollisions.parentItem(), Node):
                    """
                    Gets the destination node view object
                    """
                    self.dstCollisions = self.dstCollisions.parentItem()
                elif isinstance(self.dstCollisions.parentItem(), QGraphicsItemGroup) \
                        and isinstance(self.dstCollisions.parentItem().parentItem(), Node):
                    """
                    Gets the destination node view object if the user clicks on the text in the item
                    """
                    self.dstCollisions = self.dstCollisions.parentItem().parentItem()
                else:
                    self.reset()
                    super().mouseReleaseEvent(mouseEvent)
                    return
                if self.parent().tree.addEdge(self.startCollisions.node.id, self.dstCollisions.node.id) is True:
                    self.startCollisions.addEdge(self.dstCollisions)
                    if isinstance(self.startCollisions, Conjunction):
                        self.startCollisions.fixParentEdgeRec()
                        self.startCollisions.redraw()
                    self.reset()
                    self.parent().saved = False
                else:
                    MessageBox('Adding Edge is not possible', 'The Edge is not supported',
                               icon=QMessageBox.Critical).run()
                    self.reset()
            elif self.parent().mode == 5:
                """
                Mode 4: Deletes selected items
                Deletes all selected items plus edges from on to the selection
                """
                self.deleteSelected()
                self.reset()
                self.parent().saved = False
            else:
                self.reset()
        super().mouseReleaseEvent(mouseEvent)
Пример #16
0
class SinglePipeSegmentItem(SegmentItemBase):
    def __init__(self, startNode, endNode, parent: SinglePipeConnection):
        super().__init__(startNode, endNode, parent)

        self._singlePipeConnection = parent

        self.singleLine = QGraphicsLineItem(self)
        self.linearGrad = None
        self.initGrad()

    def _createSegment(self, startNode, endNode) -> "SegmentItemBase":
        return SinglePipeSegmentItem(startNode, endNode,
                                     self._singlePipeConnection)

    def _getContextMenu(self) -> QMenu:
        menu = super()._getContextMenu()

        editHydraulicLoopAction = menu.addAction("Edit hydraulic loop")

        editHydraulicLoopAction.triggered.connect(
            self._singlePipeConnection.editHydraulicLoop)

        return menu

    def _setLineImpl(self, x1, y1, x2, y2):
        self.initGrad()
        self.singleLine.setLine(x1, y1, x2, y2)
        self.linePoints = self.singleLine.line()

    def initGrad(self):
        color = QtCore.Qt.white

        pen1 = QtGui.QPen(color, 4)

        if isinstance(self.startNode.parent, CornerItem):
            startBlock = self.startNode.firstNode().parent
        else:
            startBlock = self.startNode.parent

        if isinstance(self.endNode.parent, CornerItem):
            endBlock = self.endNode.lastNode().parent
        else:
            endBlock = self.endNode.parent

        self.linearGrad = QLinearGradient(
            QPointF(startBlock.fromPort.scenePos().x(),
                    startBlock.fromPort.scenePos().y()),
            QPointF(endBlock.toPort.scenePos().x(),
                    endBlock.toPort.scenePos().y()),
        )
        self.linearGrad.setColorAt(0, QtCore.Qt.blue)
        self.linearGrad.setColorAt(1, QtCore.Qt.red)

        self.linearGrad.setColorAt(0, QtCore.Qt.gray)
        self.linearGrad.setColorAt(1, QtCore.Qt.black)

        pen1.setBrush(QBrush(self.linearGrad))

        self.singleLine.setPen(pen1)

    def updateGrad(self):
        color = QtCore.Qt.white
        pen1 = QtGui.QPen(color, 4)

        totLenConn = self.connection.totalLength()
        partLen1 = self.connection.partialLength(self.startNode)
        partLen2 = self.connection.partialLength(self.endNode)

        if isinstance(self.startNode.parent, CornerItem):
            startGradP = QPointF(self.startNode.parent.scenePos().x(),
                                 self.startNode.parent.scenePos().y())
        elif self.startNode.prevN() is None:
            startGradP = QPointF(self.startNode.parent.fromPort.scenePos().x(),
                                 self.startNode.parent.fromPort.scenePos().y())
        else:
            startGradP = QPointF(self.line().p1().x(), self.line().p1().y())

        if isinstance(self.endNode.parent, CornerItem):
            endGradP = QPointF(self.endNode.parent.scenePos().x(),
                               self.endNode.parent.scenePos().y())
        elif self.endNode.nextN() is None:
            endGradP = QPointF(self.endNode.parent.toPort.scenePos().x(),
                               self.endNode.parent.toPort.scenePos().y())
        else:
            endGradP = QPointF(self.line().p2().x(), self.line().p2().y())

        self.linearGrad = QLinearGradient(startGradP, endGradP)

        self.linearGrad.setColorAt(0, self.interpolate(partLen1, totLenConn))
        self.linearGrad.setColorAt(1, self.interpolate(partLen2, totLenConn))

        pen1.setBrush(QBrush(self.linearGrad))

        self.singleLine.setPen(pen1)

    def setSelect(self, isSelected: bool) -> None:
        if isSelected:
            selectPen = self._createSelectPen()
            self.singleLine.setPen(selectPen)
        else:
            self.updateGrad()

    def setColorAndWidthAccordingToMassflow(self, color, width):
        pen1 = QPen(color, width)
        self.singleLine.setPen(pen1)