示例#1
0
class PathWorkplaneOutline(QGraphicsRectItem):
    def __init__(self, parent=None):
        super(PathWorkplaneOutline, self).__init__(parent)
        self.setPen(getNoPen())
        self._path = QGraphicsPathItem(self)
        self._path.setBrush(getNoBrush())
        self._path.setPen(newPenObj(styles.BLUE_STROKE, 0))
    # end def

    def updateAppearance(self):
        tl = self.rect().topLeft()
        tl1 = tl + QPointF(0, -BASE_WIDTH/2)
        tl2 = tl + QPointF(BASE_WIDTH/2, -BASE_WIDTH/2)
        bl = self.rect().bottomLeft()
        bl1 = bl + QPointF(0, BASE_WIDTH/2)
        bl2 = bl + QPointF(BASE_WIDTH/2, BASE_WIDTH/2)
        tr = self.rect().topRight()
        tr1 = tr + QPointF(0, -BASE_WIDTH/2)
        tr2 = tr + QPointF(-BASE_WIDTH/2, -BASE_WIDTH/2)
        br = self.rect().bottomRight()
        br1 = br + QPointF(0, BASE_WIDTH/2)
        br2 = br + QPointF(-BASE_WIDTH/2, BASE_WIDTH/2)
        pp = QPainterPath()
        pp.moveTo(tl2)
        pp.lineTo(tl1)
        pp.lineTo(bl1)
        pp.lineTo(bl2)
        pp.moveTo(tr2)
        pp.lineTo(tr1)
        pp.lineTo(br1)
        pp.lineTo(br2)
        self._path.setPath(pp)
示例#2
0
    def createNetPath(self, brushColor: str, painterPath: QPainterPath,
                      isOccupyPathItem: bool):
        """ Create a QGraphicsPathItem for a network path
            
            Args:
                brushColor (str)            : The color for filling the rectangles
                painterPath (QPainterPath)  : The path to be inserted to the item
                isOccupyPathItem (bool)     : Whether the path is occupied or unoccupied path
        """
        # Generate the path item if not created
        if isOccupyPathItem:
            if self.occupiedPathItem is None:
                self.occupiedPathItem = QGraphicsPathItem(self)
            pathItem = self.occupiedPathItem
        else:
            if self.unoccupiedPathItem is None:
                self.unoccupiedPathItem = QGraphicsPathItem(self)
            pathItem = self.unoccupiedPathItem
        if pathItem is None:
            pathItem = QGraphicsPathItem(self)

        # Set the item parameters
        pathItem.setPath(painterPath)
        pathItem.setPen(
            QPen(QColor(self.netColor), self.netThickness, style=Qt.SolidLine))
        pathItem.setBrush(QBrush(QColor(brushColor)))
        pathItem.setZValue(0)
示例#3
0
    def createNetPath(self, brushColor: str, painterPath: QPainterPath, isOccupyPathItem: bool):
        """ Create a QGraphicsPathItem for a network path
            
            Args:
                brushColor (str)            : The color for filling the rectangles
                painterPath (QPainterPath)  : The path to be inserted to the item
                isOccupyPathItem (bool)     : Whether the path is occupied or unoccupied path
        """
        # Generate the path item if not created
        if isOccupyPathItem:
            if self.occupiedPathItem is None:
                self.occupiedPathItem = QGraphicsPathItem(self)
            pathItem = self.occupiedPathItem
        else:
            if self.unoccupiedPathItem is None:
                self.unoccupiedPathItem = QGraphicsPathItem(self)
            pathItem = self.unoccupiedPathItem
        if pathItem is None:
            pathItem = QGraphicsPathItem(self)

        # Set the item parameters
        pathItem.setPath(painterPath)
        pathItem.setPen(QPen(QColor(self.netColor), self.netThickness, style=Qt.SolidLine))
        pathItem.setBrush(QBrush(QColor(brushColor)))
        pathItem.setZValue(0)
示例#4
0
class createDiscretionWindow(view.descritiondefinition_ui.Ui_discretionWindow, QDialog):
    def __init__(self, isCrack, he, parent=None):
        super(createDiscretionWindow, self).__init__(parent)
        self.highlight = QGraphicsPathItem()
        self.highlight.setPath(he)
        pen = QPen(Qt.red, 2)
        self.highlight.setPen(pen)
        self.highlight.setZValue(1)
        self.parent().scene.addItem(self.highlight)
        self.setupUi(self)
        self.move(1450, -1)
        self.isCrack = isCrack
        self.lineEdit_number_of_elements.setFocus(True)
        self.lineEdit_number_of_elements.textChanged.connect(self.change_number_of_discontinous_elements)
        self.radioButton_2.toggled.connect(self.setDiscontinousBoxState)
        if self.isCrack == True:
            self.checkBox_isCrack.setChecked(True)
            self.radioButton_2.setChecked(True)
            self.checkBox_isCrack.setEnabled(False)
            self.groupBox_type_of_discretization.setEnabled(False)

    def setDiscontinousBoxState(self):
        newState = not self.groupBox_discontinous_discretion.isEnabled()
        self.groupBox_discontinous_discretion.setEnabled(newState)

    def change_number_of_discontinous_elements(self):
        if self.radioButton_2.isChecked():
            if self.lineEdit_number_of_elements.text() != "":
                if int(self.lineEdit_number_of_elements.text()) > self.verticalLayout_2.count():
                    for i in range(int(self.lineEdit_number_of_elements.text()) - self.verticalLayout_2.count()):
                        lineEdit = QLineEdit()
                        validator = QDoubleValidator()
                        validator.setNotation(0)
                        validator.setRange(0.1, 0.9, decimals=3)
                        lineEdit.setValidator(validator)
                        self.verticalLayout_2.addWidget(lineEdit)
                else:
                    for i in range(self.verticalLayout_2.count()-1, int(self.lineEdit_number_of_elements.text())-1, -1):
                        itemToDelete = self.verticalLayout_2.itemAt(i).widget()
                        self.verticalLayout_2.removeWidget(itemToDelete)
                        itemToDelete.deleteLater()
            else:
                pass

    @classmethod
    def getDiscretion(cls, he, isCrack=False, parent=None):
        dialog = cls(isCrack, he, parent)
        dialog.exec_()
        if dialog.radioButton.isChecked():
            dialog.parent().scene.removeItem(dialog.highlight)
            if dialog.lineEdit_number_of_elements.text() != "":
                return int(dialog.lineEdit_number_of_elements.text()), False
            else:
                return 1, False
        else:
            spaces = [0]
            for i in range(dialog.verticalLayout_2.count()):
                spaces.append(float(dialog.verticalLayout_2.itemAt(i).widget().text()) + spaces[-1])
            dialog.parent().scene.removeItem(dialog.highlight)
            return spaces, True
示例#5
0
    def drawGlyph(self,
                  scene,
                  glyph,
                  offsetX=0,
                  offsetY=0,
                  color=(255, 255, 255)):
        path = QPainterPath()
        path.setFillRule(Qt.WindingFill)
        for c in self.decomposedPaths(glyph):
            segs = c.segments
            path.moveTo(segs[-1].points[-1].x, segs[-1].points[-1].y)
            for seg in segs:
                tuples = [(a.x, a.y) for a in seg.points]
                flattuples = list(sum(tuples, ()))
                if len(tuples) == 2:
                    path.quadTo(*flattuples)
                elif len(tuples) == 3:
                    path.cubicTo(*flattuples)
                else:
                    path.lineTo(*flattuples)

        line = QGraphicsPathItem()
        line.setBrush(QColor(*color))
        p = QPen()
        p.setStyle(Qt.NoPen)
        line.setPen(p)
        line.setPath(path)
        reflect = QTransform(1, 0, 0, -1, 0, 0)
        reflect.translate(offsetX, offsetY)
        # print(f"Drawing {glyph} at offset {offsetX} {offsetY}")
        line.setTransform(reflect)
        scene.addItem(line)
示例#6
0
 def drawCross(self, scene, x, y, color):
     path = QPainterPath()
     path.moveTo(x - 50, y)
     path.lineTo(x + 50, y)
     path.moveTo(x, y - 50)
     path.lineTo(x, y + 50)
     line = QGraphicsPathItem()
     p = QPen(QColor(*color))
     p.setWidth(5)
     line.setPen(p)
     line.setPath(path)
     reflect = QTransform(1, 0, 0, -1, 0, 0)
     line.setTransform(reflect)
     scene.addItem(line)
示例#7
0
def show_shape(item):
    """Highlight the shape of item."""

    sh = QGraphicsPathItem()
    path = item.shape()
    # The shape path was returned in the item's coordinates
    # FIXME: translate is not enough when itemIgnoresTranformation
    path.translate(item.scenePos())
    sh.setPath(path)
    pen = QPen(Qt.magenta, 1.5, Qt.DashLine)
    pen.setCosmetic(True)  # thickness does not scale
    sh.setPen(pen)
    item.scene().addItem(sh)
    return sh
示例#8
0
 def updateConnector(self,
                     item: QGraphicsPathItem,
                     p1: QPointF,
                     p2: QPointF,
                     select: bool = True):
     path = QPainterPath(p1)
     path.quadTo(p1 + QPointF(-15, 0), (p1 + p2) * 0.5)
     path.quadTo(p2 + QPointF(15, 0), p2)
     if item is None:
         item = self.nodesScene.addPath(path)
     else:
         item.setPath(path)
     item.setZValue(-1)
     if select:
         item.setFlag(QGraphicsItem.ItemIsSelectable)
     return item
示例#9
0
class PathWorkplaneOutline(QGraphicsRectItem):
    """
    """
    def __init__(self, parent: QGraphicsItem = None):
        """
        Args:
            parent: default is ``None``
        """
        super(PathWorkplaneOutline, self).__init__(parent)
        self.setPen(getNoPen())
        self._path = QGraphicsPathItem(self)
        self._path.setBrush(getNoBrush())
        self._path.setPen(newPenObj(styles.BLUE_STROKE, 0))
    # end def

    def updateAppearance(self):
        tl = self.rect().topLeft()
        tl1 = tl + QPointF(0, -BASE_WIDTH/2)
        tl2 = tl + QPointF(BASE_WIDTH/2, -BASE_WIDTH/2)
        bl = self.rect().bottomLeft()
        bl1 = bl + QPointF(0, BASE_WIDTH/2)
        bl2 = bl + QPointF(BASE_WIDTH/2, BASE_WIDTH/2)
        tr = self.rect().topRight()
        tr1 = tr + QPointF(0, -BASE_WIDTH/2)
        tr2 = tr + QPointF(-BASE_WIDTH/2, -BASE_WIDTH/2)
        br = self.rect().bottomRight()
        br1 = br + QPointF(0, BASE_WIDTH/2)
        br2 = br + QPointF(-BASE_WIDTH/2, BASE_WIDTH/2)
        pp = QPainterPath()
        pp.moveTo(tl2)
        pp.lineTo(tl1)
        pp.lineTo(bl1)
        pp.lineTo(bl2)
        pp.moveTo(tr2)
        pp.lineTo(tr1)
        pp.lineTo(br1)
        pp.lineTo(br2)
        self._path.setPath(pp)
示例#10
0
class ImageViewer(QGraphicsView, QObject):
    def __init__(self, parent=None):
        super(ImageViewer, self).__init__(parent)
        self.setRenderHints(QPainter.Antialiasing
                            | QPainter.SmoothPixmapTransform)
        #self.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop)
        self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)
        self.setDragMode(QGraphicsView.ScrollHandDrag)
        #self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
        #self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
        self._scene = ImageViewerScene(self)
        self.setScene(self._scene)
        self._create_grid()
        self._create_grid_lines()
        self._pixmap = None
        self._selection_mode = SELECTION_MODE.NONE

        # polygon selection
        _polygon_guide_line_pen = QPen(QtGui.QColor(235, 72, 40))
        _polygon_guide_line_pen.setWidth(2)
        _polygon_guide_line_pen.setStyle(QtCore.Qt.DotLine)
        self._polygon_guide_line = QGraphicsLineItem()
        self._polygon_guide_line.setVisible(False)
        self._polygon_guide_line.setPen(_polygon_guide_line_pen)
        self._scene.addItem(self._polygon_guide_line)
        self._current_polygon = None
        # rectangle selection
        self._box_origin = QPoint()
        self._box_picker = QRubberBand(QRubberBand.Rectangle, self)

        # free selection
        self._current_free_path = None
        self._is_drawing = False
        self._last_point_drawn = QPoint()
        self._current_label = None

    @property
    def current_label(self):
        return self._current_label

    @current_label.setter
    def current_label(self, value):
        self._current_label = value

    @property
    def pixmap(self) -> ImagePixmap:
        return self._pixmap

    @pixmap.setter
    def pixmap(self, value: QPixmap):
        self.selection_mode = SELECTION_MODE.NONE
        self.resetTransform()
        if self.pixmap:
            self._scene.removeItem(self._pixmap)
        self.remove_annotations()
        self._pixmap = ImagePixmap()
        self._pixmap.setPixmap(value)
        self._pixmap.setOffset(-value.width() / 2, -value.height() / 2)
        self._pixmap.setTransformationMode(QtCore.Qt.SmoothTransformation)
        self._pixmap.signals.hoverEnterEventSgn.connect(
            self.pixmap_hoverEnterEvent_slot)
        self._pixmap.signals.hoverLeaveEventSgn.connect(
            self.pixmap_hoverLeaveEvent_slot)
        self._pixmap.signals.hoverMoveEventSgn.connect(
            self.pixmap_hoverMoveEvent_slot)
        self._scene.addItem(self._pixmap)
        # rect=self._scene.addRect(QtCore.QRectF(0,0,100,100), QtGui.QPen(QtGui.QColor("red")))
        # rect.setZValue(1.0)
        self.fit_to_window()

    @property
    def selection_mode(self):
        return self._selection_mode

    @selection_mode.setter
    def selection_mode(self, value):
        self._polygon_guide_line.hide()
        self._current_polygon = None
        self._current_free_path = None
        self._is_drawing = value == SELECTION_MODE.FREE
        if value == SELECTION_MODE.NONE:
            self.enable_items(True)
        else:
            self.enable_items(False)
        self._selection_mode = value

    def remove_annotations(self):
        for item in self._scene.items():
            if isinstance(item, EditableBox):
                self._scene.removeItem(item)
            elif isinstance(item, EditablePolygon):
                item.delete_polygon()

    def remove_annotations_by_label(self, label_name):
        for item in self._scene.items():
            if isinstance(item, EditableBox):
                if item.label and item.label.name == label_name:
                    self._scene.removeItem(item)
            elif isinstance(item, EditablePolygon):
                if item.label and item.label.name == label_name:
                    item.delete_polygon()

    def enable_items(self, value):
        for item in self._scene.items():
            if isinstance(item, EditablePolygon) or isinstance(
                    item, EditableBox):
                item.setEnabled(value)

    def _create_grid(self):
        gridSize = 15
        backgroundPixmap = QtGui.QPixmap(gridSize * 2, gridSize * 2)
        #backgroundPixmap.fill(QtGui.QColor("white"))
        backgroundPixmap.fill(QtGui.QColor(20, 20, 20))
        #backgroundPixmap.fill(QtGui.QColor("powderblue"))
        painter = QtGui.QPainter(backgroundPixmap)
        #backgroundColor=QtGui.QColor("palegoldenrod")
        #backgroundColor=QtGui.QColor(237,237,237)
        backgroundColor = QtGui.QColor(0, 0, 0)
        painter.fillRect(0, 0, gridSize, gridSize, backgroundColor)
        painter.fillRect(gridSize, gridSize, gridSize, gridSize,
                         backgroundColor)
        painter.end()
        self._scene.setBackgroundBrush(QtGui.QBrush(backgroundPixmap))

    def _create_grid_lines(self):
        pen_color = QColor(255, 255, 255, 255)
        pen = QPen(pen_color)
        pen.setWidth(2)
        pen.setStyle(QtCore.Qt.DotLine)
        self.vline = QGraphicsLineItem()
        self.vline.setVisible(False)
        self.vline.setPen(pen)
        self.hline = QGraphicsLineItem()
        self.hline.setVisible(False)
        self.hline.setPen(pen)
        self._scene.addItem(self.vline)
        self._scene.addItem(self.hline)

    def wheelEvent(self, event: QWheelEvent):
        adj = (event.angleDelta().y() / 120) * 0.1
        self.scale(1 + adj, 1 + adj)

    def fit_to_window(self):
        """Fit image within view."""
        if not self.pixmap or not self._pixmap.pixmap():
            return
        #self._pixmap.setTransformationMode(QtCore.Qt.SmoothTransformation)
        self.fitInView(self._pixmap, QtCore.Qt.KeepAspectRatio)

    def show_guide_lines(self):
        if self.hline and self.vline:
            self.hline.show()
            self.vline.show()

    def hide_guide_lines(self):
        if self.hline and self.vline:
            self.hline.hide()
            self.vline.hide()

    def pixmap_hoverEnterEvent_slot(self):
        self.show_guide_lines()

    def pixmap_hoverLeaveEvent_slot(self):
        self.hide_guide_lines()

    def pixmap_hoverMoveEvent_slot(self, evt: QGraphicsSceneHoverEvent, x, y):
        bbox: QRect = self._pixmap.boundingRect()
        offset = QPointF(bbox.width() / 2, bbox.height() / 2)
        self.vline.setLine(x, -offset.y(), x, bbox.height() - offset.y())
        self.vline.setZValue(1)
        self.hline.setLine(-offset.x(), y, bbox.width() - offset.x(), y)
        self.hline.setZValue(1)

    def mouseMoveEvent(self, evt: QtGui.QMouseEvent) -> None:
        if self.selection_mode == SELECTION_MODE.BOX:
            if not self._box_origin.isNull():
                self._box_picker.setGeometry(
                    QRect(self._box_origin, evt.pos()).normalized())
        elif self.selection_mode == SELECTION_MODE.POLYGON:
            if self._current_polygon:
                if self._current_polygon.count > 0:
                    last_point: QPointF = self._current_polygon.last_point
                    self._polygon_guide_line.setZValue(1)
                    self._polygon_guide_line.show()
                    mouse_pos = self.mapToScene(evt.pos())
                    self._polygon_guide_line.setLine(last_point.x(),
                                                     last_point.y(),
                                                     mouse_pos.x(),
                                                     mouse_pos.y())
            else:
                self._polygon_guide_line.hide()

        elif self.selection_mode == SELECTION_MODE.FREE and evt.buttons(
        ) and QtCore.Qt.LeftButton:
            if self._current_free_path:
                painter: QPainterPath = self._current_free_path.path()
                self._last_point_drawn = self.mapToScene(evt.pos())
                painter.lineTo(self._last_point_drawn)
                self._current_free_path.setPath(painter)

        super(ImageViewer, self).mouseMoveEvent(evt)

    def mousePressEvent(self, evt: QtGui.QMouseEvent) -> None:

        if evt.buttons() == QtCore.Qt.LeftButton:
            if self.selection_mode == SELECTION_MODE.BOX:
                self.setDragMode(QGraphicsView.NoDrag)
                self._box_origin = evt.pos()
                self._box_picker.setGeometry(QRect(self._box_origin, QSize()))
                self._box_picker.show()

            elif self._selection_mode == SELECTION_MODE.POLYGON:
                pixmap_rect: QRectF = self._pixmap.boundingRect()
                new_point = self.mapToScene(evt.pos())
                # consider only the points intothe image
                if pixmap_rect.contains(new_point):
                    if self._current_polygon is None:
                        self._current_polygon = EditablePolygon()
                        self._current_polygon.signals.deleted.connect(
                            self.delete_polygon_slot)
                        self._scene.addItem(self._current_polygon)
                        self._current_polygon.addPoint(new_point)
                    else:
                        self._current_polygon.addPoint(new_point)

            elif self._selection_mode == SELECTION_MODE.FREE:
                # start drawing
                new_point = self.mapToScene(evt.pos())
                pixmap_rect: QRectF = self._pixmap.boundingRect()
                # consider only the points intothe image
                if pixmap_rect.contains(new_point):
                    self.setDragMode(QGraphicsView.NoDrag)
                    pen = QPen(QtGui.QColor(235, 72, 40))
                    pen.setWidth(10)
                    self._last_point_drawn = new_point
                    self._current_free_path = QGraphicsPathItem()
                    self._current_free_path.setOpacity(0.6)
                    self._current_free_path.setPen(pen)
                    painter = QPainterPath()
                    painter.moveTo(self._last_point_drawn)
                    self._current_free_path.setPath(painter)
                    self._scene.addItem(self._current_free_path)
        else:
            self.setDragMode(QGraphicsView.ScrollHandDrag)

        super(ImageViewer, self).mousePressEvent(evt)

    def mouseReleaseEvent(self, evt: QtGui.QMouseEvent) -> None:
        if evt.button() == QtCore.Qt.LeftButton:
            if self.selection_mode == SELECTION_MODE.BOX:
                roi: QRect = self._box_picker.geometry()
                roi: QRectF = self.mapToScene(roi).boundingRect()
                pixmap_rect = self._pixmap.boundingRect()
                self._box_picker.hide()
                if pixmap_rect == roi.united(pixmap_rect):
                    rect = EditableBox(roi)
                    rect.label = self.current_label
                    self._scene.addItem(rect)
                    self.selection_mode = SELECTION_MODE.NONE
                    self.setDragMode(QGraphicsView.ScrollHandDrag)

            elif self.selection_mode == SELECTION_MODE.FREE and self._current_free_path:
                # create polygon
                self._current_free_path: QGraphicsPathItem
                path_rect = self._current_free_path.boundingRect()
                pixmap_rect = self._pixmap.boundingRect()
                if pixmap_rect == path_rect.united(pixmap_rect):
                    path = self._current_free_path.path()
                    path_polygon = EditablePolygon()
                    path_polygon.label = self.current_label
                    self._scene.addItem(path_polygon)
                    for i in range(0, path.elementCount(), 10):
                        x, y = path.elementAt(i).x, path.elementAt(i).y
                        path_polygon.addPoint(QPointF(x, y))
                self._scene.removeItem(self._current_free_path)
                self.selection_mode = SELECTION_MODE.NONE
                self.setDragMode(QGraphicsView.ScrollHandDrag)

        super(ImageViewer, self).mouseReleaseEvent(evt)

    def keyPressEvent(self, event: QtGui.QKeyEvent) -> None:
        if self._current_polygon and event.key() == QtCore.Qt.Key_Space:
            points = self._current_polygon.points
            self._current_polygon.label = self.current_label
            self._current_polygon = None
            self.selection_mode = SELECTION_MODE.NONE
            self._polygon_guide_line.hide()
            self.setDragMode(QGraphicsView.ScrollHandDrag)
        super(ImageViewer, self).keyPressEvent(event)

    def delete_polygon_slot(self, polygon: EditablePolygon):
        self._current_polygon = None
        self.selection_mode = SELECTION_MODE.NONE
        self._polygon_guide_line.hide()
示例#11
0
class ForcedXoverNode3(QGraphicsRectItem):
    """
    This is a QGraphicsRectItem to allow actions and also a 
    QGraphicsSimpleTextItem to allow a label to be drawn
    """
    def __init__(self, virtual_helix_item, xover_item, strand3p, idx):
        super(ForcedXoverNode3, self).__init__(virtual_helix_item)
        self._vhi = virtual_helix_item
        self._xover_item = xover_item
        self._idx = idx
        self._is_on_top = virtual_helix_item.isStrandOnTop(strand3p)
        self._is_drawn_5_to_3 = strand3p.strandSet().isDrawn5to3()
        self._strand_type = strand3p.strandSet().strandType()

        self._partner_virtual_helix = virtual_helix_item

        self._blank_thing = QGraphicsRectItem(_blankRect, self)
        self._blank_thing.setBrush(QBrush(Qt.white))
        self._path_thing = QGraphicsPathItem(self)
        self.configurePath()

        self.setPen(_NO_PEN)
        self._label = None
        self.setPen(_NO_PEN)
        self.setBrush(_NO_BRUSH)
        self.setRect(_rect)

        self.setZValue(styles.ZENDPOINTITEM + 1)
    # end def

    def updateForFloatFromVHI(self, virtual_helix_item, strand_type, idx_x, idx_y):
        """

        """
        self._vhi = virtual_helix_item
        self.setParentItem(virtual_helix_item)
        self._strand_type = strand_type
        self._idx = idx_x
        self._is_on_top = self._is_drawn_5_to_3 = True if idx_y == 0 else False
        self.updatePositionAndAppearance(is_from_strand=False)
    # end def

    def updateForFloatFromStrand(self, virtual_helix_item, strand3p, idx):
        """

        """
        self._vhi = virtual_helix_item
        self._strand = strand3p
        self.setParentItem(virtual_helix_item)
        self._idx = idx
        self._is_on_top = virtual_helix_item.isStrandOnTop(strand3p)
        self._is_drawn_5_to_3 = strand3p.strandSet().isDrawn5to3()
        self._strand_type = strand3p.strandSet().strandType()
        self.updatePositionAndAppearance()
    # end def

    def strandType(self):
        return self._strand_type
    # end def

    def configurePath(self):
        self._path_thing.setBrush(QBrush(styles.RED_STROKE))
        path = PPR3 if self._is_drawn_5_to_3 else PPL3
        offset = -_BASE_WIDTH if self._is_drawn_5_to_3 else _BASE_WIDTH
        self._path_thing.setPath(path)
        self._path_thing.setPos(offset, 0)

        offset = -_BASE_WIDTH if self._is_drawn_5_to_3 else 0
        self._blank_thing.setPos(offset, 0)

        self._blank_thing.show()
        self._path_thing.show()
    # end def

    def refreshXover(self):
        self._xover_item.refreshXover()
    # end def

    def setPartnerVirtualHelix(self, virtual_helix_item):
        self._partner_virtual_helix = virtual_helix_item
    # end def

    def idx(self):
        return self._idx
    # end def

    def virtualHelixItem(self):
        return self._vhi
    # end def

    def point(self):
        return self._vhi.upperLeftCornerOfBaseType(self._idx, self._strand_type)
    # end def

    def floatPoint(self):
        pt = self.pos()
        return pt.x(), pt.y()
    # end def

    def isOnTop(self):
        return self._is_on_top
    # end def

    def isDrawn5to3(self):
        return self._is_drawn_5_to_3
    # end def

    def updatePositionAndAppearance(self, is_from_strand=True):
        """
        Sets position by asking the VirtualHelixItem
        Sets appearance by choosing among pre-defined painterpaths (from
        normalstrandgraphicsitem) depending on drawing direction.
        """
        self.setPos(*self.point())
        n5 = self._xover_item._node5
        if is_from_strand:
            from_strand, from_idx = (n5._strand, n5._idx) if n5 != self else (None, None)
            if self._strand.canInstallXoverAt(self._idx, from_strand, from_idx):
                self.configurePath()
                # We can only expose a 5' end. But on which side?
                is_left = True if self._is_drawn_5_to_3 else False
                self._updateLabel(is_left)
            else:
                self.hideItems()
        else:
            self.hideItems()
    # end def

    def updateConnectivity(self):
        is_left = True if self._is_drawn_5_to_3 else False
        self._updateLabel(is_left)
    # end def

    def remove(self):
        """
        Clean up this joint
        """
        scene = self.scene()
        scene.removeItem(self._label)
        self._label = None
        scene.removeItem(self._path_thing)
        self._path_thing = None
        scene.removeItem(self._blank_thing)
        self._blank_thing = None
        scene.removeItem(self)
    # end def

    def _updateLabel(self, is_left):
        """
        Called by updatePositionAndAppearance during init, or later by
        updateConnectivity. Updates drawing and position of the label.
        """
        lbl = self._label
        if self._idx != None:
            bw = _BASE_WIDTH
            num = self._partner_virtual_helix.number()
            tBR = _FM.tightBoundingRect(str(num))
            half_label_h = tBR.height()/2.0
            half_label_w = tBR.width()/2.0
            # determine x and y positions
            label_x = bw/2.0 - half_label_w
            if self._is_on_top:
                label_y = -0.25*half_label_h - 0.5 - 0.5*bw
            else:
                label_y = 2*half_label_h + 0.5 + 0.5*bw
            # adjust x for left vs right
            label_x_offset = 0.25*bw if is_left else -0.25*bw
            label_x += label_x_offset
            # adjust x for numeral 1
            if num == 1: label_x -= half_label_w/2.0
            # create text item
            if lbl == None:
                lbl = QGraphicsSimpleTextItem(str(num), self)
            lbl.setPos(label_x, label_y)
            lbl.setBrush(_ENAB_BRUSH)
            lbl.setFont(_TO_HELIX_NUM_FONT)
            self._label = lbl

            lbl.setText( str(self._partner_virtual_helix.number()) )
            lbl.show()
        # end if
    # end def

    def hideItems(self):
        if self._label:
            self._label.hide()
        if self._blank_thing:
            self._path_thing.hide()
        if self._blank_thing:
            self._blank_thing.hide()
示例#12
0
class EdgeItem(GraphItem):

    _qt_pen_styles = {
        'dashed': Qt.DashLine,
        'dotted': Qt.DotLine,
        'solid': Qt.SolidLine,
    }

    def __init__(self,
                 highlight_level,
                 spline,
                 label_center,
                 label,
                 from_node,
                 to_node,
                 parent=None,
                 penwidth=1,
                 edge_color=None,
                 style='solid'):
        super(EdgeItem, self).__init__(highlight_level, parent)

        self.from_node = from_node
        self.from_node.add_outgoing_edge(self)
        self.to_node = to_node
        self.to_node.add_incoming_edge(self)

        self._default_edge_color = self._COLOR_BLACK
        if edge_color is not None:
            self._default_edge_color = edge_color

        self._default_text_color = self._COLOR_BLACK
        self._default_color = self._COLOR_BLACK
        self._text_brush = QBrush(self._default_color)
        self._shape_brush = QBrush(self._default_color)
        if style in ['dashed', 'dotted']:
            self._shape_brush = QBrush(Qt.transparent)
        self._label_pen = QPen()
        self._label_pen.setColor(self._default_text_color)
        self._label_pen.setJoinStyle(Qt.RoundJoin)
        self._edge_pen = QPen(self._label_pen)
        self._edge_pen.setWidth(penwidth)
        self._edge_pen.setColor(self._default_edge_color)
        self._edge_pen.setStyle(self._qt_pen_styles.get(style, Qt.SolidLine))

        self._sibling_edges = set()

        self._label = None
        if label is not None:
            self._label = QGraphicsSimpleTextItem(label)
            self._label.setFont(GraphItem._LABEL_FONT)
            label_rect = self._label.boundingRect()
            label_rect.moveCenter(label_center)
            self._label.setPos(label_rect.x(), label_rect.y())
            self._label.hoverEnterEvent = self._handle_hoverEnterEvent
            self._label.hoverLeaveEvent = self._handle_hoverLeaveEvent
            self._label.setAcceptHoverEvents(True)

        # spline specification according to
        # http://www.graphviz.org/doc/info/attrs.html#k:splineType
        coordinates = spline.split(' ')
        # extract optional end_point
        end_point = None
        if (coordinates[0].startswith('e,')):
            parts = coordinates.pop(0)[2:].split(',')
            end_point = QPointF(float(parts[0]), -float(parts[1]))
        # extract optional start_point
        if (coordinates[0].startswith('s,')):
            parts = coordinates.pop(0).split(',')

        # first point
        parts = coordinates.pop(0).split(',')
        point = QPointF(float(parts[0]), -float(parts[1]))
        path = QPainterPath(point)

        while len(coordinates) > 2:
            # extract triple of points for a cubic spline
            parts = coordinates.pop(0).split(',')
            point1 = QPointF(float(parts[0]), -float(parts[1]))
            parts = coordinates.pop(0).split(',')
            point2 = QPointF(float(parts[0]), -float(parts[1]))
            parts = coordinates.pop(0).split(',')
            point3 = QPointF(float(parts[0]), -float(parts[1]))
            path.cubicTo(point1, point2, point3)

        self._arrow = None
        if end_point is not None:
            # draw arrow
            self._arrow = QGraphicsPolygonItem()
            polygon = QPolygonF()
            polygon.append(point3)
            offset = QPointF(end_point - point3)
            corner1 = QPointF(-offset.y(), offset.x()) * 0.35
            corner2 = QPointF(offset.y(), -offset.x()) * 0.35
            polygon.append(point3 + corner1)
            polygon.append(end_point)
            polygon.append(point3 + corner2)
            self._arrow.setPolygon(polygon)
            self._arrow.hoverEnterEvent = self._handle_hoverEnterEvent
            self._arrow.hoverLeaveEvent = self._handle_hoverLeaveEvent
            self._arrow.setAcceptHoverEvents(True)

        self._path = QGraphicsPathItem(parent)
        self._path.setPath(path)
        self.addToGroup(self._path)

        self.set_node_color()
        self.set_label_color()

    def add_to_scene(self, scene):
        scene.addItem(self)
        if self._label is not None:
            scene.addItem(self._label)
        if self._arrow is not None:
            scene.addItem(self._arrow)

    def setToolTip(self, tool_tip):
        super(EdgeItem, self).setToolTip(tool_tip)
        if self._label is not None:
            self._label.setToolTip(tool_tip)
        if self._arrow is not None:
            self._arrow.setToolTip(tool_tip)

    def add_sibling_edge(self, edge):
        self._sibling_edges.add(edge)

    def set_node_color(self, color=None):
        if color is None:
            self._label_pen.setColor(self._default_text_color)
            self._text_brush.setColor(self._default_color)
            if self._shape_brush.isOpaque():
                self._shape_brush.setColor(self._default_edge_color)
            self._edge_pen.setColor(self._default_edge_color)
        else:
            self._label_pen.setColor(color)
            self._text_brush.setColor(color)
            if self._shape_brush.isOpaque():
                self._shape_brush.setColor(color)
            self._edge_pen.setColor(color)

        self._path.setPen(self._edge_pen)
        if self._arrow is not None:
            self._arrow.setBrush(self._shape_brush)
            self._arrow.setPen(self._edge_pen)

    def set_label_color(self, color=None):
        if color is None:
            self._label_pen.setColor(self._default_text_color)
        else:
            self._label_pen.setColor(color)

        if self._label is not None:
            self._label.setBrush(self._text_brush)
            self._label.setPen(self._label_pen)

    def _handle_hoverEnterEvent(self, event):
        # hovered edge item in red
        self.set_node_color(self._COLOR_RED)
        self.set_label_color(self._COLOR_RED)

        if self._highlight_level > 1:
            if self.from_node != self.to_node:
                # from-node in blue
                self.from_node.set_node_color(self._COLOR_BLUE)
                # to-node in green
                self.to_node.set_node_color(self._COLOR_GREEN)
            else:
                # from-node/in-node in teal
                self.from_node.set_node_color(self._COLOR_TEAL)
                self.to_node.set_node_color(self._COLOR_TEAL)
        if self._highlight_level > 2:
            # sibling edges in orange
            for sibling_edge in self._sibling_edges:
                sibling_edge.set_node_color(self._COLOR_ORANGE)

    def _handle_hoverLeaveEvent(self, event):
        self.set_node_color()
        self.set_label_color()
        if self._highlight_level > 1:
            self.from_node.set_node_color()
            self.to_node.set_node_color()
        if self._highlight_level > 2:
            for sibling_edge in self._sibling_edges:
                sibling_edge.set_node_color()
示例#13
0
class GridItem(QGraphicsRectItem):
    def __init__(self,  part_item: SliceNucleicAcidPartItemT,
                        grid_type: EnumType):
        """previous_grid_bounds (tuple):  a tuple corresponding to the bounds of
        the grid.

        Args:
            part_item: Description
            grid_type: Description
        """
        super(GridItem, self).__init__(parent=part_item)
        self.setFlag(QGraphicsItem.ItemClipsChildrenToShape)

        self._path = None
        self.part_item = part_item
        self._path = QGraphicsPathItem(self)

        self.dots = (styles.DOT_SIZE, styles.DOT_SIZE / 2)
        # self.allow_snap = part_item.window().action_vhelix_snap.isChecked()
        self._draw_gridpoint_coordinates = False
        self.draw_lines = False
        self.points = []
        self.points_dict = dict()
        self.previous_grid_bounds = None
        self.bounds = None
        self.grid_type = None

        self.setPen(getPenObj(styles.GRAY_STROKE, styles.EMPTY_HELIX_STROKE_WIDTH))

        self.setGridType(grid_type)
        self.previous_grid_type = grid_type
    # end def

    def destroyItem(self):
        print("destroying sliceView GridItem")
        scene = self.scene()
        for point in self.points:
            point.destroyItem()
        self.points = None
        scene.removeItem(self)
    # end def

    def updateGrid(self):
        """Recreates the grid according to the latest part_item outline rect.
        """
        part_item = self.part_item
        part = part_item.part()
        radius = part.radius()
        self.bounds = part_item.bounds()
        self.removePoints()

        self.setRect(self.part_item.outline.rect())
        if self.grid_type == GridEnum.HONEYCOMB:
            self.createHoneycombGrid(part_item, radius, self.bounds)
        elif self.grid_type == GridEnum.SQUARE:
            self.createSquareGrid(part_item, radius, self.bounds)
        else:
            self._path.setPath(QPainterPath())
    # end def

    def setGridType(self, grid_type: EnumType):
        """Sets the grid type. See cadnano.cnenum.GridEnum.

        Args:
            grid_type: NONE, HONEYCOMB, or SQUARE
        """
        self.grid_type = grid_type
        self.updateGrid()
    # end def

    def createHoneycombGrid(self, part_item: SliceNucleicAcidPartItemT,
                                radius: float,
                                bounds: RectT):
        """Instantiate an area of griditems arranged on a honeycomb lattice.

        Args:
            part_item: Description
            radius: Description
            bounds: Description
        """
        doLattice = HoneycombDnaPart.latticeCoordToModelXY
        doPosition = HoneycombDnaPart.positionModelToLatticeCoord
        isEven = HoneycombDnaPart.isEvenParity
        x_l, x_h, y_l, y_h = bounds
        x_l = x_l + HoneycombDnaPart.PAD_GRID_XL
        x_h = x_h + HoneycombDnaPart.PAD_GRID_XH
        y_h = y_h + HoneycombDnaPart.PAD_GRID_YL
        y_l = y_l + HoneycombDnaPart.PAD_GRID_YH
        dot_size, half_dot_size = self.dots
        sf = part_item.scale_factor
        points = self.points
        row_l, col_l = doPosition(radius, x_l, -y_l, scale_factor=sf)
        row_h, col_h = doPosition(radius, x_h, -y_h, scale_factor=sf)

        redo_neighbors = (row_l, col_l, row_h, col_h) != self.previous_grid_bounds or\
            self.previous_grid_type != self.grid_type
        self.previous_grid_type = self.grid_type

        path = QPainterPath()
        is_pen_down = False
        draw_lines = self.draw_lines

        if redo_neighbors:
            self.points_dict = dict()

        for row in range(row_l, row_h):
            for column in range(col_l, col_h+1):
                x, y = doLattice(radius, row, column, scale_factor=sf)
                if draw_lines:
                    if is_pen_down:
                        path.lineTo(x, -y)
                    else:
                        is_pen_down = True
                        path.moveTo(x, -y)
                """
                +x is Left and +y is down
                origin of ellipse is Top Left corner so we subtract half in X and subtract in y
                """
                pt = GridPoint(x - half_dot_size,
                               -y - half_dot_size,
                               dot_size,
                               self,
                               coord=(row, column))

                if self._draw_gridpoint_coordinates:
                    font = QFont(styles.THE_FONT, 6)
                    path.addText(x - 5, -y + 4, font, "%s,%s" % (-row, column))

                pt.setPen(getPenObj(styles.GRAY_STROKE, styles.EMPTY_HELIX_STROKE_WIDTH))

                # if x == 0 and y == 0:
                #     pt.setBrush(getBrushObj(Qt.gray))

                points.append(pt)
                self.points_dict[(-row, column)] = pt

                if redo_neighbors:
                    self.previous_grid_bounds = (row_l, col_l, row_h, col_h)

            is_pen_down = False

        if draw_lines:
            for column in range(col_l, col_h+1):
                for row in range(row_l, row_h):
                    x, y = doLattice(radius, row, column, scale_factor=sf)
                    if is_pen_down and isEven(row, column):
                        path.lineTo(x, -y)
                        is_pen_down = False
                    else:
                        is_pen_down = True
                        path.moveTo(x, -y)
                is_pen_down = False
            # end for j
        self._path.setPath(path)
    # end def

    def createSquareGrid(self, part_item: SliceNucleicAcidPartItemT,
                            radius: float,
                            bounds: RectT):
        """Instantiate an area of griditems arranged on a square lattice.

        Args:
            part_item: Description
            radius: Description
            bounds: Description
        """
        doLattice = SquareDnaPart.latticeCoordToModelXY
        doPosition = SquareDnaPart.positionToLatticeCoordRound
        x_l, x_h, y_l, y_h = bounds
        x_l = x_l + SquareDnaPart.PAD_GRID_XL
        x_h = x_h + SquareDnaPart.PAD_GRID_XH
        y_h = y_h + SquareDnaPart.PAD_GRID_YL
        y_l = y_l + SquareDnaPart.PAD_GRID_YH

        dot_size, half_dot_size = self.dots
        sf = part_item.scale_factor
        points = self.points
        row_l, col_l = doPosition(radius, x_l, -y_l, scale_factor=sf)
        row_h, col_h = doPosition(radius, x_h, -y_h, scale_factor=sf)

        redo_neighbors = (row_l, col_l, row_h, col_h) != \
            self.previous_grid_bounds or self.previous_grid_type != self.grid_type
        self.previous_grid_type = self.grid_type

        if redo_neighbors:
            neighbor_map = dict()

        path = QPainterPath()
        is_pen_down = False
        draw_lines = self.draw_lines

        for row in range(row_l, row_h + 1):
            for column in range(col_l, col_h + 1):
                x, y = doLattice(radius, row, column, scale_factor=sf)
                if draw_lines:
                    if is_pen_down:
                        path.lineTo(x, -y)
                    else:
                        is_pen_down = True
                        path.moveTo(x, -y)
                """ +x is Left and +y is down
                origin of ellipse is Top Left corner so we subtract half in X
                and subtract in y
                """
                pt = GridPoint(x - half_dot_size,
                               -y - half_dot_size,
                               dot_size,
                               self,
                               coord=(row, column))

                if self._draw_gridpoint_coordinates:
                    font = QFont(styles.THE_FONT)
                    path.addText(x - 10, -y + 5, font, "%s,%s" % (-row, column))

                pt.setPen(getPenObj(styles.GRAY_STROKE, styles.EMPTY_HELIX_STROKE_WIDTH))

                # if x == 0 and y == 0:
                #     pt.setBrush(getBrushObj(Qt.gray))

                points.append(pt)
                self.points_dict[(-row, column)] = pt

                if redo_neighbors:
                    self.previous_grid_bounds = (row_l, col_l, row_h, col_h)

            is_pen_down = False  # pen up

        # DO VERTICAL LINES
        if draw_lines:
            for column in range(col_l, col_h + 1):
                for row in range(row_l, row_h + 1):
                    x, y = doLattice(radius, row, column, scale_factor=sf)
                    if is_pen_down:
                        path.lineTo(x, -y)
                    else:
                        is_pen_down = True
                        path.moveTo(x, -y)
                is_pen_down = False  # pen up
        self._path.setPath(path)
    # end def

    def removePoints(self):
        """Remove all points from the grid.
        """
        points = self.points
        scene = self.scene()
        while points:
            scene.removeItem(points.pop())
        self.points_dict = dict()
    # end def

    def showCreateHint(self, coord: Tuple[int, int],
                            next_idnums: Tuple[int, int] = (0, 1),
                            show_hint: bool = True,
                            color: str = None) -> Optional[bool]:
        point_item = self.points_dict.get(coord)
        if point_item is None:
            return

        if not show_hint:
            point_item.showCreateHint(show_hint=False)

        if point_item:
            row, column = coord
            if self.grid_type is GridEnum.HONEYCOMB:
                parity = 0 if HoneycombDnaPart.isEvenParity(row=row, column=column) else 1
            elif self.grid_type is GridEnum.SQUARE:
                parity = 0 if SquareDnaPart.isEvenParity(row=row, column=column) else 1
            else:
                return
            id_num = next_idnums[1] if parity else next_idnums[0]
            point_item.showCreateHint(id_num=id_num, show_hint=show_hint, color=color)
            return parity == 1
    # end def

    def setPath(self, path: QPainterPath):
        assert isinstance(path, QPainterPath)
        self._path = path
    # end def

    def path(self) -> QPainterPath:
        return self._path
    # end def

    def highlightGridPoint(self, row: int, column: int, on: bool = True):
        grid_point = self.points_dict.get((row, column))

        if grid_point:
            grid_point.highlightGridPoint(on)
示例#14
0
 def setPath(self, path):
     self.__shape = None
     QGraphicsPathItem.setPath(self, path)
示例#15
0
class ImageViewer(QGraphicsView, QObject):
    points_selection_sgn = pyqtSignal(list)
    key_press_sgn = pyqtSignal(QtGui.QKeyEvent)

    def __init__(self, parent=None):
        super(ImageViewer, self).__init__(parent)
        self.setDragMode(QGraphicsView.ScrollHandDrag)
        self.setRenderHints(QPainter.Antialiasing
                            | QPainter.SmoothPixmapTransform)
        self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)
        self.setResizeAnchor(QGraphicsView.AnchorUnderMouse)
        self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
        self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)

        self._scene = ImageViewerScene(self)
        self.setScene(self._scene)
        self._image = None
        self._image_original = None
        self._pixmap = None
        self._img_contrast = 1.0
        self._img_brightness = 50.0
        self._img_gamma = 1.0
        self._create_grid()
        self._channels = []
        self._current_tool = SELECTION_TOOL.POINTER
        self._dataset = None

        # create grid lines
        pen_color = QColor(255, 255, 255, 255)
        pen = QPen(pen_color)
        pen.setWidth(2)
        pen.setStyle(QtCore.Qt.DotLine)
        self.vline = QGraphicsLineItem()
        self.vline.setVisible(False)
        self.vline.setPen(pen)
        self.hline = QGraphicsLineItem()
        self.hline.setVisible(False)
        self.hline.setPen(pen)
        self._scene.addItem(self.vline)
        self._scene.addItem(self.hline)
        self._current_label = None

        # rectangle selection tool
        self._rectangle_tool_origin = QPoint()
        self._rectangle_tool_picker = QRubberBand(QRubberBand.Rectangle, self)

        # polygon selection tool
        app = QApplication.instance()
        color = app.palette().color(QPalette.Highlight)
        self._polygon_guide_line_pen = QPen(color)
        self._polygon_guide_line_pen.setWidth(3)
        self._polygon_guide_line_pen.setStyle(QtCore.Qt.DotLine)
        self._polygon_guide_line = QGraphicsLineItem()
        self._polygon_guide_line.setVisible(False)
        self._polygon_guide_line.setPen(self._polygon_guide_line_pen)
        self._scene.addItem(self._polygon_guide_line)
        self._current_polygon = None

        # circle
        self._current_ellipse = None

        # free selection tool
        self._current_free_path = None
        self._is_drawing = False
        self._last_point_drawn = QPoint()
        self._last_click_point = None
        self._free_Path_pen = QPen(color)
        self._free_Path_pen.setWidth(10)

        self._extreme_points = Queue(maxsize=4)

    @property
    def current_label(self):
        return self._current_label

    @current_label.setter
    def current_label(self, value):
        self._current_label = value
        if self._current_label:
            color = QColor(self._current_label.color)
            self._free_Path_pen.setColor(color)
            self._polygon_guide_line_pen.setColor(color)
            self._polygon_guide_line.setPen(self._polygon_guide_line_pen)

    @property
    def dataset(self):
        return self._dataset

    @dataset.setter
    def dataset(self, value):
        self._dataset = value

    @property
    def img_contrast(self):
        return self._img_contrast

    @img_contrast.setter
    def img_contrast(self, value):
        self._img_contrast = value

    @property
    def img_gamma(self):
        return self._img_gamma

    @img_gamma.setter
    def img_gamma(self, value):
        self._img_gamma = value

    @property
    def img_brightness(self):
        return self._img_brightness

    @img_brightness.setter
    def img_brightness(self, value):
        self._img_brightness = value

    @property
    def image(self):
        return self._image

    @image.setter
    def image(self, value):
        self._image = value
        self._image_original = value.copy()
        self.update_viewer()

    @property
    def pixmap(self) -> ImagePixmap:
        return self._pixmap

    @gui_exception
    def update_viewer(self, fit_image=True):
        rgb = cv2.cvtColor(self._image, cv2.COLOR_BGR2RGB)
        rgb = ImageUtilities.adjust_image(rgb, self._img_contrast,
                                          self._img_brightness)
        rgb = ImageUtilities.adjust_gamma(rgb, self._img_gamma)
        pil_image = Image.fromarray(rgb)
        qppixmap_image = pil_image.toqpixmap()
        x, y = -qppixmap_image.width() / 2, -qppixmap_image.height() / 2

        if self._pixmap:
            self._pixmap.resetTransform()
            self._pixmap.setPixmap(qppixmap_image)
            self._pixmap.setOffset(x, y)
        else:
            self._pixmap = ImagePixmap()
            self._pixmap.setPixmap(qppixmap_image)
            self._pixmap.setOffset(x, y)
            self._scene.addItem(self._pixmap)
            self._pixmap.signals.hoverEnterEventSgn.connect(
                self.pixmap_hoverEnterEvent_slot)
            self._pixmap.signals.hoverLeaveEventSgn.connect(
                self.pixmap_hoverLeaveEvent_slot)
            self._pixmap.signals.hoverMoveEventSgn.connect(
                self.pixmap_hoverMoveEvent_slot)
        self._hide_guide_lines()
        if fit_image:
            self.fit_to_window()

    @gui_exception
    def reset_viewer(self):
        self._img_contrast = 1.0
        self._img_brightness = 50.0
        self._img_gamma = 1.0
        self._image = self._image_original.copy()

    @gui_exception
    def equalize_histogram(self):
        self._image = ImageUtilities.histogram_equalization(self._image)

    @gui_exception
    def correct_lightness(self):
        self._image = ImageUtilities.correct_lightness(self._image)

    def clusterize(self, k):
        self._image = ImageUtilities.kmeans(self._image.copy(), k)

    @property
    def current_tool(self):
        return self._current_tool

    @current_tool.setter
    def current_tool(self, value):
        self._polygon_guide_line.hide()
        self._current_polygon = None
        self._current_free_path = None
        self._current_ellipse = None
        self._is_drawing = value == SELECTION_TOOL.FREE
        self._current_tool = value
        self.clear_extreme_points()
        if value == SELECTION_TOOL.POINTER:
            self.enable_items(True)
        else:
            self.enable_items(False)

    def fit_to_window(self):
        if not self._pixmap or not self._pixmap.pixmap():
            return
        self.resetTransform()
        self.setTransform(QtGui.QTransform())
        self.fitInView(self._pixmap, QtCore.Qt.KeepAspectRatio)

    def _create_grid(self, gridSize=15):
        app: QApplication = QApplication.instance()
        curr_theme = "dark"
        if app:
            curr_theme = app.property("theme")
        if curr_theme == "light":
            color1 = QtGui.QColor("white")
            color2 = QtGui.QColor(237, 237, 237)
        else:
            color1 = QtGui.QColor(20, 20, 20)
            color2 = QtGui.QColor(0, 0, 0)
        backgroundPixmap = QtGui.QPixmap(gridSize * 2, gridSize * 2)
        backgroundPixmap.fill(color1)
        painter = QtGui.QPainter(backgroundPixmap)
        painter.fillRect(0, 0, gridSize, gridSize, color2)
        painter.fillRect(gridSize, gridSize, gridSize, gridSize, color2)
        painter.end()
        self._scene.setBackgroundBrush(QtGui.QBrush(backgroundPixmap))

    def wheelEvent(self, event: QWheelEvent):
        adj = (event.angleDelta().y() / 120) * 0.1
        self.scale(1 + adj, 1 + adj)

    @gui_exception
    def keyPressEvent(self, event: QKeyEvent):
        if event.key() == QtCore.Qt.Key_Space:
            image_rect: QRectF = self._pixmap.sceneBoundingRect()
            if self.current_tool == SELECTION_TOOL.POLYGON and self._current_polygon:
                points = self._current_polygon.points
                self._polygon_guide_line.hide()
                self.setDragMode(QGraphicsView.ScrollHandDrag)
                if len(points) <= 2:
                    self._current_polygon.delete_item()
                self.current_tool = SELECTION_TOOL.POINTER
            elif self.current_tool == SELECTION_TOOL.EXTREME_POINTS and \
                    self._extreme_points.full():
                points = []
                image_offset = QPointF(image_rect.width() / 2,
                                       image_rect.height() / 2)
                for pt in self._extreme_points.queue:
                    pt: EditablePolygonPoint
                    center = pt.sceneBoundingRect().center()
                    x = math.floor(center.x() + image_offset.x())
                    y = math.floor(center.y() + image_offset.y())
                    points.append([x, y])
                self.points_selection_sgn.emit(points)
                self.current_tool = SELECTION_TOOL.POINTER
        else:
            event.ignore()

    # guide lines events
    def _show_guide_lines(self):
        if self.hline and self.vline:
            self.hline.show()
            self.vline.show()

    def _hide_guide_lines(self):
        if self.hline and self.vline:
            self.hline.hide()
            self.vline.hide()

    def _update_guide_lines(self, x, y):
        bbox: QRect = self._pixmap.boundingRect()
        offset = QPointF(bbox.width() / 2, bbox.height() / 2)
        self.vline.setLine(x, -offset.y(), x, bbox.height() - offset.y())
        self.vline.setZValue(1)
        self.hline.setLine(-offset.x(), y, bbox.width() - offset.x(), y)
        self.hline.setZValue(1)

    def pixmap_hoverMoveEvent_slot(self, evt: QGraphicsSceneHoverEvent, x, y):
        self._update_guide_lines(x, y)

    def pixmap_hoverEnterEvent_slot(self):
        self._show_guide_lines()

    def pixmap_hoverLeaveEvent_slot(self):
        self._hide_guide_lines()

    def delete_polygon_slot(self, polygon: EditablePolygon):
        self._current_polygon = None
        self.current_tool = SELECTION_TOOL.POINTER
        self._polygon_guide_line.hide()

    @gui_exception
    def mousePressEvent(self, evt: QtGui.QMouseEvent) -> None:
        image_rect: QRectF = self._pixmap.boundingRect()
        mouse_pos = self.mapToScene(evt.pos())
        if evt.buttons() == QtCore.Qt.LeftButton:
            if self.current_tool == SELECTION_TOOL.BOX:
                # create rectangle
                self.setDragMode(QGraphicsView.NoDrag)
                self._rectangle_tool_origin = evt.pos()
                geometry = QRect(self._rectangle_tool_origin, QSize())
                self._rectangle_tool_picker.setGeometry(geometry)
                self._rectangle_tool_picker.show()
            elif self.current_tool == SELECTION_TOOL.POLYGON:
                if image_rect.contains(mouse_pos):
                    if self._current_polygon is None:
                        self._current_polygon = EditablePolygon()
                        self._current_polygon.label = self._current_label
                        self._current_polygon.tag = self._dataset
                        self._current_polygon.signals.deleted.connect(
                            self.delete_polygon_slot)
                        self._scene.addItem(self._current_polygon)
                        self._current_polygon.addPoint(mouse_pos)
                    else:
                        self._current_polygon.addPoint(mouse_pos)
            elif self.current_tool == SELECTION_TOOL.ELLIPSE:
                if image_rect.contains(mouse_pos):
                    self.setDragMode(QGraphicsView.NoDrag)
                    ellipse_rec = QtCore.QRectF(mouse_pos.x(), mouse_pos.y(),
                                                0, 0)
                    self._current_ellipse = EditableEllipse()
                    self._current_ellipse.tag = self.dataset
                    self._current_ellipse.label = self._current_label
                    self._current_ellipse.setRect(ellipse_rec)
                    self._scene.addItem(self._current_ellipse)

            elif self.current_tool == SELECTION_TOOL.FREE:
                # consider only the points into the image
                if image_rect.contains(mouse_pos):
                    self.setDragMode(QGraphicsView.NoDrag)
                    self._last_point_drawn = mouse_pos
                    self._current_free_path = QGraphicsPathItem()
                    self._current_free_path.setOpacity(0.6)
                    self._current_free_path.setPen(self._free_Path_pen)
                    painter = QPainterPath()
                    painter.moveTo(self._last_point_drawn)
                    self._current_free_path.setPath(painter)
                    self._scene.addItem(self._current_free_path)

            elif self.current_tool == SELECTION_TOOL.EXTREME_POINTS:
                if image_rect.contains(mouse_pos):
                    if not self._extreme_points.full():

                        def delete_point(idx):
                            del self._extreme_points.queue[idx]

                        idx = self._extreme_points.qsize()
                        editable_pt = EditablePolygonPoint(idx)
                        editable_pt.signals.deleted.connect(delete_point)
                        editable_pt.setPos(mouse_pos)
                        self._scene.addItem(editable_pt)
                        self._extreme_points.put(editable_pt)

        else:
            self.setDragMode(QGraphicsView.ScrollHandDrag)
        super(ImageViewer, self).mousePressEvent(evt)

    @gui_exception
    def mouseMoveEvent(self, evt: QtGui.QMouseEvent) -> None:
        mouse_pos = self.mapToScene(evt.pos())
        image_rect: QRectF = self._pixmap.boundingRect()
        if self.current_tool == SELECTION_TOOL.BOX:
            if not self._rectangle_tool_origin.isNull():
                geometry = QRect(self._rectangle_tool_origin,
                                 evt.pos()).normalized()
                self._rectangle_tool_picker.setGeometry(geometry)
        elif self.current_tool == SELECTION_TOOL.POLYGON:
            if self._current_polygon and image_rect.contains(mouse_pos):
                if self._current_polygon.count > 0:
                    last_point: QPointF = self._current_polygon.last_point
                    self._polygon_guide_line.setZValue(1)
                    self._polygon_guide_line.show()
                    mouse_pos = self.mapToScene(evt.pos())
                    self._polygon_guide_line.setLine(last_point.x(),
                                                     last_point.y(),
                                                     mouse_pos.x(),
                                                     mouse_pos.y())
            else:
                self._polygon_guide_line.hide()
        elif self.current_tool == SELECTION_TOOL.ELLIPSE:
            if self._current_ellipse and image_rect.contains(mouse_pos):
                ellipse_rect = self._current_ellipse.rect()
                ellipse_pos = QPointF(ellipse_rect.x(), ellipse_rect.y())
                distance = math.hypot(mouse_pos.x() - ellipse_pos.x(),
                                      mouse_pos.y() - ellipse_pos.y())
                ellipse_rect.setWidth(distance)
                ellipse_rect.setHeight(distance)
                self._current_ellipse.setRect(ellipse_rect)
        elif self.current_tool == SELECTION_TOOL.FREE and evt.buttons(
        ) and QtCore.Qt.LeftButton:
            if self._current_free_path and image_rect.contains(mouse_pos):
                painter: QPainterPath = self._current_free_path.path()
                self._last_point_drawn = self.mapToScene(evt.pos())
                painter.lineTo(self._last_point_drawn)
                self._current_free_path.setPath(painter)
        super(ImageViewer, self).mouseMoveEvent(evt)

    @gui_exception
    def mouseReleaseEvent(self, evt: QtGui.QMouseEvent) -> None:
        image_rect: QRectF = self._pixmap.boundingRect()
        if self.current_tool == SELECTION_TOOL.BOX:
            roi: QRect = self._rectangle_tool_picker.geometry()
            roi: QRectF = self.mapToScene(roi).boundingRect()
            self._rectangle_tool_picker.hide()
            if image_rect == roi.united(image_rect):
                rect = EditableBox(roi)
                rect.label = self.current_label
                rect.tag = self._dataset
                self._scene.addItem(rect)
                self.current_tool = SELECTION_TOOL.POINTER
                self.setDragMode(QGraphicsView.ScrollHandDrag)

        elif self.current_tool == SELECTION_TOOL.ELLIPSE and self._current_ellipse:
            roi: QRect = self._current_ellipse.boundingRect()
            if image_rect == roi.united(image_rect):
                self.current_tool = SELECTION_TOOL.POINTER
                self.setDragMode(QGraphicsView.ScrollHandDrag)
            else:
                self._current_ellipse.delete_item()
        elif self.current_tool == SELECTION_TOOL.FREE and self._current_free_path:
            # create polygon
            self._current_free_path: QGraphicsPathItem
            path_rect = self._current_free_path.boundingRect()
            if image_rect == path_rect.united(image_rect):
                path = self._current_free_path.path()
                path_polygon = EditablePolygon()
                path_polygon.tag = self.dataset
                path_polygon.label = self.current_label
                self._scene.addItem(path_polygon)
                for i in range(0, path.elementCount(), 10):
                    x, y = path.elementAt(i).x, path.elementAt(i).y
                    path_polygon.addPoint(QPointF(x, y))
            self._scene.removeItem(self._current_free_path)
            self.current_tool = SELECTION_TOOL.POINTER
            self.setDragMode(QGraphicsView.ScrollHandDrag)
        super(ImageViewer, self).mouseReleaseEvent(evt)

    def remove_annotations(self):
        for item in self._scene.items():
            if isinstance(item, EditableItem):
                item.delete_item()

    def remove_annotations_by_label(self, label_name):
        for item in self._scene.items():
            if isinstance(item, EditableItem):
                if item.label and item.label.name == label_name:
                    item.delete_item()

    def enable_items(self, value):
        for item in self._scene.items():
            if isinstance(item, EditableItem):
                item.setEnabled(value)

    def clear_extreme_points(self):
        if self._extreme_points.qsize() > 0:
            for pt in self._extreme_points.queue:
                self._scene.removeItem(pt)
            self._extreme_points.queue.clear()
示例#16
0
class GridItem(QGraphicsRectItem):
    def __init__(self, part_item, grid_type):
        """Summary

        previous_grid_bounds (tuple):  a tuple corresponding to the bounds of
        the grid.

        Args:
            part_item (TYPE): Description
            grid_type (TYPE): Description
        """
        super(GridItem, self).__init__(parent=part_item)
        self.setFlag(QGraphicsItem.ItemClipsChildrenToShape)

        self._path = None
        self.part_item = part_item
        self._path = QGraphicsPathItem(self)

        self.dots = (styles.DOT_SIZE, styles.DOT_SIZE / 2)
        # self.allow_snap = part_item.window().action_vhelix_snap.isChecked()
        self._draw_gridpoint_coordinates = False
        self.draw_lines = False
        self.points = []
        self.points_dict = dict()
        self.previous_grid_bounds = None
        self.bounds = None
        self.grid_type = None

        self.setPen(
            getPenObj(styles.GRAY_STROKE, styles.EMPTY_HELIX_STROKE_WIDTH))

        self.setGridType(grid_type)
        self.previous_grid_type = grid_type

    # end def

    def updateGrid(self):
        """Recreates the grid according to the latest part_item outline rect.
        """
        part_item = self.part_item
        part = part_item.part()
        radius = part.radius()
        self.bounds = part_item.bounds()
        self.removePoints()

        self.setRect(self.part_item.outline.rect())
        if self.grid_type == GridType.HONEYCOMB:
            self.createHoneycombGrid(part_item, radius, self.bounds)
        elif self.grid_type == GridType.SQUARE:
            self.createSquareGrid(part_item, radius, self.bounds)
        else:
            self._path.setPath(QPainterPath())

    # end def

    def setGridType(self, grid_type):
        """Sets the grid type. See cadnano.cnenum.GridType.

        Args:
            grid_type (GridType): NONE, HONEYCOMB, or SQUARE
        """
        self.grid_type = grid_type
        self.updateGrid()

    # end def

    def createHoneycombGrid(self, part_item, radius, bounds):
        """Instantiate an area of griditems arranged on a honeycomb lattice.

        Args:
            part_item (TYPE): Description
            radius (TYPE): Description
            bounds (TYPE): Description

        Returns:
            TYPE: Description
        """
        doLattice = HoneycombDnaPart.latticeCoordToPositionXY
        doPosition = HoneycombDnaPart.positionToLatticeCoordRound
        isEven = HoneycombDnaPart.isEvenParity
        x_l, x_h, y_l, y_h = bounds
        x_l = x_l + HoneycombDnaPart.PAD_GRID_XL
        x_h = x_h + HoneycombDnaPart.PAD_GRID_XH
        y_h = y_h + HoneycombDnaPart.PAD_GRID_YL
        y_l = y_l + HoneycombDnaPart.PAD_GRID_YH
        dot_size, half_dot_size = self.dots
        sf = part_item.scale_factor
        points = self.points
        row_l, col_l = doPosition(radius,
                                  x_l,
                                  -y_l,
                                  False,
                                  False,
                                  scale_factor=sf)
        row_h, col_h = doPosition(radius,
                                  x_h,
                                  -y_h,
                                  True,
                                  True,
                                  scale_factor=sf)

        redo_neighbors = (row_l, col_l, row_h, col_h) != self.previous_grid_bounds or\
            self.previous_grid_type != self.grid_type
        self.previous_grid_type = self.grid_type

        path = QPainterPath()
        is_pen_down = False
        draw_lines = self.draw_lines

        if redo_neighbors:
            point_coordinates = dict()
            neighbor_map = dict()
            self.points_dict = dict()

        for row in range(row_l, row_h):
            for column in range(col_l, col_h + 1):
                x, y = doLattice(radius, row, column, scale_factor=sf)
                if draw_lines:
                    if is_pen_down:
                        path.lineTo(x, -y)
                    else:
                        is_pen_down = True
                        path.moveTo(x, -y)
                """
                +x is Left and +y is down
                origin of ellipse is Top Left corner so we subtract half in X and subtract in y
                """
                pt = GridPoint(x - half_dot_size,
                               -y - half_dot_size,
                               dot_size,
                               self,
                               coord=(row, column))

                if self._draw_gridpoint_coordinates:
                    font = QFont(styles.THE_FONT)
                    path.addText(x - 10, -y + 5, font,
                                 "%s,%s" % (-row, column))

                pt.setPen(
                    getPenObj(styles.GRAY_STROKE,
                              styles.EMPTY_HELIX_STROKE_WIDTH))

                # if x == 0 and y == 0:
                #     pt.setBrush(getBrushObj(Qt.gray))

                points.append(pt)
                self.points_dict[(-row, column)] = pt

                if redo_neighbors:
                    point_coordinates[(-row, column)] = (x, -y)

                    # This is reversed since the Y is mirrored
                    if not HoneycombDnaPart.isEvenParity(row, column):
                        neighbor_map[(-row, column)] = [(-row - 1, column),
                                                        (-row, column + 1),
                                                        (-row, column - 1)]
                    else:
                        neighbor_map[(-row, column)] = [(-row + 1, column),
                                                        (-row, column - 1),
                                                        (-row, column + 1)]
                    self.previous_grid_bounds = (row_l, col_l, row_h, col_h)

            is_pen_down = False

        if draw_lines:
            for column in range(col_l, col_h + 1):
                for row in range(row_l, row_h):
                    x, y = doLattice(radius, row, column, scale_factor=sf)
                    if is_pen_down and isEven(row, column):
                        path.lineTo(x, -y)
                        is_pen_down = False
                    else:
                        is_pen_down = True
                        path.moveTo(x, -y)
                is_pen_down = False
            # end for j
        self._path.setPath(path)

        if redo_neighbors:
            self.part_item.setNeighborMap(neighbor_map=neighbor_map)
            self.part_item.setPointMap(point_map=point_coordinates)

    # end def

    def createSquareGrid(self, part_item, radius, bounds):
        """Instantiate an area of griditems arranged on a square lattice.

        Args:
            part_item (TYPE): Description
            radius (TYPE): Description
            bounds (TYPE): Description

        Returns:
            TYPE: Description
        """
        doLattice = SquareDnaPart.latticeCoordToPositionXY
        doPosition = SquareDnaPart.positionToLatticeCoordRound
        x_l, x_h, y_l, y_h = bounds
        x_l = x_l + SquareDnaPart.PAD_GRID_XL
        x_h = x_h + SquareDnaPart.PAD_GRID_XH
        y_h = y_h + SquareDnaPart.PAD_GRID_YL
        y_l = y_l + SquareDnaPart.PAD_GRID_YH

        dot_size, half_dot_size = self.dots
        sf = part_item.scale_factor
        points = self.points
        row_l, col_l = doPosition(radius, x_l, -y_l, scale_factor=sf)
        row_h, col_h = doPosition(radius, x_h, -y_h, scale_factor=sf)

        redo_neighbors = (row_l, col_l, row_h, col_h) != \
            self.previous_grid_bounds or self.previous_grid_type != self.grid_type
        self.previous_grid_type = self.grid_type

        if redo_neighbors:
            point_map = dict()
            neighbor_map = dict()

        path = QPainterPath()
        is_pen_down = False
        draw_lines = self.draw_lines

        for row in range(row_l, row_h + 1):
            for column in range(col_l, col_h + 1):
                x, y = doLattice(radius, row, column, scale_factor=sf)
                if draw_lines:
                    if is_pen_down:
                        path.lineTo(x, -y)
                    else:
                        is_pen_down = True
                        path.moveTo(x, -y)
                """ +x is Left and +y is down
                origin of ellipse is Top Left corner so we subtract half in X
                and subtract in y
                """
                pt = GridPoint(x - half_dot_size,
                               -y - half_dot_size,
                               dot_size,
                               self,
                               coord=(row, column))

                if self._draw_gridpoint_coordinates:
                    font = QFont(styles.THE_FONT)
                    path.addText(x - 10, -y + 5, font,
                                 "%s,%s" % (-row, column))

                pt.setPen(
                    getPenObj(styles.GRAY_STROKE,
                              styles.EMPTY_HELIX_STROKE_WIDTH))

                # if x == 0 and y == 0:
                #     pt.setBrush(getBrushObj(Qt.gray))

                points.append(pt)
                self.points_dict[(-row, column)] = pt

                if redo_neighbors:
                    point_map[(-row, column)] = (x, -y)

                    neighbor_map[(-row, column)] = [(-row, column + 1),
                                                    (-row, column - 1),
                                                    (-row - 1, column),
                                                    (-row + 1, column)]

                    self.previous_grid_bounds = (row_l, col_l, row_h, col_h)

            is_pen_down = False  # pen up

        # DO VERTICAL LINES
        if draw_lines:
            for column in range(col_l, col_h + 1):
                for row in range(row_l, row_h + 1):
                    x, y = doLattice(radius, row, column, scale_factor=sf)
                    if is_pen_down:
                        path.lineTo(x, -y)
                    else:
                        is_pen_down = True
                        path.moveTo(x, -y)
                is_pen_down = False  # pen up
        self._path.setPath(path)

        if redo_neighbors:
            self.part_item.setNeighborMap(neighbor_map=neighbor_map)
            self.part_item.setPointMap(point_map=point_map)

    # end def

    def removePoints(self):
        """Remove all points from the grid.
        """
        points = self.points
        scene = self.scene()
        while points:
            scene.removeItem(points.pop())
        self.points_dict = dict()

    # end def

    def showCreateHint(self,
                       coord,
                       next_idnums=(0, 1),
                       show_hint=True,
                       color=None):
        point_item = self.points_dict.get(coord, None)
        if point_item is None:
            return

        if show_hint is False:
            point_item.showCreateHint(show_hint=False)

        if point_item:
            row, column = coord
            if self.grid_type is GridType.HONEYCOMB:
                parity = 0 if HoneycombDnaPart.isOddParity(
                    row=row, column=column) else 1
            elif self.grid_type is GridType.SQUARE:
                parity = 0 if SquareDnaPart.isEvenParity(row=row,
                                                         column=column) else 1
            else:
                return
            id_num = next_idnums[1] if parity else next_idnums[0]
            point_item.showCreateHint(id_num=id_num,
                                      show_hint=show_hint,
                                      color=color)
            return parity == 1

    # end def

    def setPath(self, path):
        assert isinstance(path, QPainterPath)
        self._path = path

    # end def

    def path(self):
        return self._path
示例#17
0
class TableItem(KineticsDisplayItem):
    defaultWidth = 30
    defaultHeight = 30
    defaultPenWidth = 2
    name = constants.ITEM

    def __init__(self, *args, **kwargs):
        KineticsDisplayItem.__init__(self, *args, **kwargs)

        points = [
            QtCore.QPointF(0, TableItem.defaultWidth / 2),
            QtCore.QPointF(TableItem.defaultHeight / 2 - 2, 0),
            QtCore.QPointF(TableItem.defaultWidth / 2 + 2, 0),
            QtCore.QPointF(TableItem.defaultWidth,
                           TableItem.defaultHeight / 2),
        ]

        path = QtGui.QPainterPath()
        path.moveTo(points[0])
        for p in points[1:]:
            path.lineTo(p)
            path.moveTo(p)
        path.moveTo(0, 0)
        path.lineTo(TableItem.defaultWidth, 0)
        path.moveTo(-(TableItem.defaultWidth / 3), TableItem.defaultHeight / 4)
        path.lineTo((TableItem.defaultWidth + 10), TableItem.defaultHeight / 4)

        self.gobj = QGraphicsPathItem(path, self)
        #self.gobj.setToolTip("Need to see what to show unlike conc/nint for pool")
        tabledoc = (moose.element(self.mobj.path)).outputValue
        self.gobj.setToolTip(str(tabledoc))
        self.gobj.setPen(
            QtGui.QPen(QtCore.Qt.black, 2, Qt.Qt.SolidLine, Qt.Qt.RoundCap,
                       Qt.Qt.RoundJoin))
        self.gobj.mobj = self.mobj

    def refresh(self, scale):
        defaultWidth = TableItem.defaultWidth * scale
        defaultHeight = TableItem.defaultHeight * scale
        points = [
            QtCore.QPointF(0, defaultWidth / 2),
            QtCore.QPointF(defaultHeight / 2 - 2, 0),
            QtCore.QPointF(defaultWidth / 2 + 2, 0),
            QtCore.QPointF(defaultWidth, defaultHeight / 2)
        ]
        path = QtGui.QPainterPath()
        path.moveTo(points[0])
        for p in points[1:]:
            path.lineTo(p)
            path.moveTo(p)
        path.moveTo(0, 0)
        path.lineTo(defaultWidth, 0)
        path.moveTo(-(defaultWidth / 3), defaultHeight / 4)
        path.lineTo((defaultWidth + 10), defaultHeight / 4)
        self.gobj.setPath(path)
        TablePen = self.gobj.pen()
        tableWidth = TableItem.defaultPenWidth * scale
        TablePen.setWidth(tableWidth)
        self.gobj.setPen(TablePen)

    def setDisplayProperties(self, x, y, textcolor, bgcolor):
        """Set the display properties of this item."""
        # TODO: check the table bounding reactangle b'cos selection looks ugly
        self.setGeometry(x, y,
                         self.gobj.boundingRect().width(),
                         self.gobj.boundingRect().height())
示例#18
0
class TextAnnotation(Annotation):
    """Text annotation item for the canvas scheme.

    """
    editingFinished = Signal()
    """Emitted when the editing is finished (i.e. the item loses focus)."""

    textEdited = Signal()
    """Emitted when the edited text changes."""
    def __init__(self, parent=None, **kwargs):
        Annotation.__init__(self, parent, **kwargs)
        self.setFlag(QGraphicsItem.ItemIsMovable)
        self.setFlag(QGraphicsItem.ItemIsSelectable)

        self.setFocusPolicy(Qt.ClickFocus)

        self.__textMargins = (2, 2, 2, 2)

        rect = self.geometry().translated(-self.pos())

        ################################
        # PyQt 5.10 crashes because of this call (3)
        #self.__framePathItem = QGraphicsPathItem(self)
        self.__framePathItem = QGraphicsPathItem(None)

        self.__framePathItem.setPen(QPen(Qt.NoPen))

        self.__textItem = GraphicsTextEdit(self)
        self.__textItem.setPlaceholderText(self.tr("Enter text here"))
        self.__textItem.setPos(2, 2)
        self.__textItem.setTextWidth(rect.width() - 4)
        self.__textItem.setTabChangesFocus(True)
        self.__textItem.setTextInteractionFlags(Qt.NoTextInteraction)
        self.__textItem.setFont(self.font())
        self.__textInteractionFlags = Qt.NoTextInteraction

        layout = self.__textItem.document().documentLayout()
        layout.documentSizeChanged.connect(self.__onDocumentSizeChanged)

        self.__updateFrame()

    def adjustSize(self):
        """Resize to a reasonable size.
        """
        self.__textItem.setTextWidth(-1)
        self.__textItem.adjustSize()
        size = self.__textItem.boundingRect().size()
        left, top, right, bottom = self.textMargins()
        geom = QRectF(self.pos(), size + QSizeF(left + right, top + bottom))
        self.setGeometry(geom)

    def setFramePen(self, pen):
        """Set the frame pen. By default Qt.NoPen is used (i.e. the frame
        is not shown).

        """
        self.__framePathItem.setPen(pen)

    def framePen(self):
        """Return the frame pen.
        """
        return self.__framePathItem.pen()

    def setFrameBrush(self, brush):
        """Set the frame brush.
        """
        self.__framePathItem.setBrush(brush)

    def frameBrush(self):
        """Return the frame brush.
        """
        return self.__framePathItem.brush()

    def setPlainText(self, text):
        """Set the annotation plain text.
        """
        self.__textItem.setPlainText(text)

    def toPlainText(self):
        return self.__textItem.toPlainText()

    def setHtml(self, text):
        """Set the annotation rich text.
        """
        self.__textItem.setHtml(text)

    def toHtml(self):
        return self.__textItem.toHtml()

    def setDefaultTextColor(self, color):
        """Set the default text color.
        """
        self.__textItem.setDefaultTextColor(color)

    def defaultTextColor(self):
        return self.__textItem.defaultTextColor()

    def setTextMargins(self, left, top, right, bottom):
        """Set the text margins.
        """
        margins = (left, top, right, bottom)
        if self.__textMargins != margins:
            self.__textMargins = margins
            self.__textItem.setPos(left, top)
            self.__textItem.setTextWidth(
                max(self.geometry().width() - left - right, 0))

    def textMargins(self):
        """Return the text margins.
        """
        return self.__textMargins

    def document(self):
        """Return the QTextDocument instance used internally.
        """
        return self.__textItem.document()

    def setTextCursor(self, cursor):
        self.__textItem.setTextCursor(cursor)

    def textCursor(self):
        return self.__textItem.textCursor()

    def setTextInteractionFlags(self, flags):
        self.__textInteractionFlags = flags

    def textInteractionFlags(self):
        return self.__textInteractionFlags

    def setDefaultStyleSheet(self, stylesheet):
        self.document().setDefaultStyleSheet(stylesheet)

    def mouseDoubleClickEvent(self, event):
        Annotation.mouseDoubleClickEvent(self, event)

        if event.buttons() == Qt.LeftButton and \
                self.__textInteractionFlags & Qt.TextEditable:
            self.startEdit()

    def startEdit(self):
        """Start the annotation text edit process.
        """
        self.__textItem.setTextInteractionFlags(self.__textInteractionFlags)
        self.__textItem.setFocus(Qt.MouseFocusReason)

        # Install event filter to find out when the text item loses focus.
        self.__textItem.installSceneEventFilter(self)
        self.__textItem.document().contentsChanged.connect(self.textEdited)

    def endEdit(self):
        """End the annotation edit.
        """
        self.__textItem.setTextInteractionFlags(Qt.NoTextInteraction)
        self.__textItem.removeSceneEventFilter(self)
        self.__textItem.document().contentsChanged.disconnect(self.textEdited)
        self.editingFinished.emit()

    def __onDocumentSizeChanged(self, size):
        # The size of the text document has changed. Expand the text
        # control rect's height if the text no longer fits inside.
        try:
            rect = self.geometry()
            _, top, _, bottom = self.textMargins()
            if rect.height() < (size.height() + bottom + top):
                rect.setHeight(size.height() + bottom + top)
                self.setGeometry(rect)
        except Exception:
            log.error("error in __onDocumentSizeChanged", exc_info=True)

    def __updateFrame(self):
        rect = self.geometry()
        rect.moveTo(0, 0)
        path = QPainterPath()
        path.addRect(rect)
        self.__framePathItem.setPath(path)

    def resizeEvent(self, event):
        width = event.newSize().width()
        left, _, right, _ = self.textMargins()
        self.__textItem.setTextWidth(max(width - left - right, 0))
        self.__updateFrame()
        QGraphicsWidget.resizeEvent(self, event)

    def sceneEventFilter(self, obj, event):
        if obj is self.__textItem and event.type() == QEvent.FocusOut:
            self.__textItem.focusOutEvent(event)
            self.endEdit()
            return True

        return Annotation.sceneEventFilter(self, obj, event)

    def itemChange(self, change, value):
        if change == QGraphicsItem.ItemSelectedHasChanged:
            if self.isSelected():
                self.setFramePen(QPen(Qt.DashDotLine))
            else:
                self.setFramePen(QPen(Qt.NoPen))

        return Annotation.itemChange(self, change, value)

    def changeEvent(self, event):
        if event.type() == QEvent.FontChange:
            self.__textItem.setFont(self.font())

        Annotation.changeEvent(self, event)
示例#19
0
class PreXoverItem(QGraphicsRectItem):
    """A PreXoverItem exists between a single 'from' VirtualHelixItem index
    and zero or more 'to' VirtualHelixItem Indices

    Attributes:
        adapter (:obj:`PropertyWrapperObject`): Description
        idx (int): the base index within the virtual helix
        is_fwd (bool): is this a forward strand?
        prexoveritem_manager (:obj:`PreXoverManager`): Manager of the PreXoverItems
        to_vh_id_num (int): Virtual Helix number this Xover point might connect to
    """
    FILTER_NAME = "xover"

    def __init__(self,  from_virtual_helix_item: PathVirtualHelixItemT,
                        is_fwd: bool,
                        from_index: int,
                        nearby_idxs: List[int],
                        to_vh_id_num: int,
                        prexoveritem_manager: PreXoverManagerT):
        """Summary

        Args:
            from_virtual_helix_item: Description
            is_fwd: is this a forward strand?
            from_index: index of the Virtual Helix this xover is coming from
            nearby_idxs:
            to_vh_id_num: Virtual Helix number this Xover point might connect to
            prexoveritem_manager: Manager of the PreXoverItems
        """
        super(QGraphicsRectItem, self).__init__(BASE_RECT, from_virtual_helix_item)
        self.adapter = PropertyWrapperObject(self)
        self._tick_marks = QGraphicsPathItem(self)
        self._tick_marks.setAcceptHoverEvents(True)
        self._bond_item = QGraphicsPathItem(self)
        self._bond_item.hide()
        self._label = PreXoverLabel(is_fwd, self)
        self._path = QGraphicsPathItem()
        self.setZValue(styles.ZPREXOVERITEM)
        self.setPen(getNoPen())
        self.resetItem(from_virtual_helix_item, is_fwd, from_index, nearby_idxs, to_vh_id_num, prexoveritem_manager)

        self._getActiveTool = from_virtual_helix_item.viewroot().manager.activeToolGetter
    # end def

    def shutdown(self):
        """Summary
        """
        self.setBrush(getNoBrush())
        self.to_vh_id_num = None
        self.adapter.resetAnimations()
        self.setAcceptHoverEvents(False)
        self.hide()
    # end def

    def resetItem(self, from_virtual_helix_item: PathVirtualHelixItemT,
                        is_fwd: bool,
                        from_index: int,
                        nearby_idxs: List[int],
                        to_vh_id_num: int,
                        prexoveritem_manager: PreXoverManagerT):
        """Update this pooled PreXoverItem with current info.
        Called by PreXoverManager.

        Args:
            from_virtual_helix_item: the associated vh_item
            is_fwd: True if associated with fwd strand, False if rev strand
            from_index: idx of associated vh
            nearby_idxs:
            to_vh_id_num: id_num of the other vh
            prexoveritem_manager: the manager
        """
        # to_vh_item = from_virtual_helix_item.partItem().idToVirtualHelixItem(to_vh_id_num)
        self.setParentItem(from_virtual_helix_item)
        # self.setParentItem(to_vh_item)
        self.resetTransform()
        self._id_num = from_virtual_helix_item.idNum()
        self._model_part = from_virtual_helix_item.part()
        self.idx = from_index
        self.is_low = False
        self.is_high = False
        self.nearby_idxs = nearby_idxs
        self.is_fwd = is_fwd
        self.color = None
        self.is3p = None
        self.enter_pos = None
        self.exit_pos = None
        self.to_vh_id_num = to_vh_id_num
        self._label_txt = None
        self.prexoveritem_manager = prexoveritem_manager

        # todo: check here if xover present and disable
        result = self.setPathAppearance(from_virtual_helix_item)

        if result:
            self.setBrush(getNoBrush())
            if is_fwd:
                self.setPos(from_index*BASE_WIDTH, -BASE_WIDTH - 0.1*BASE_WIDTH)
            else:
                self.setPos(from_index*BASE_WIDTH, 2*BASE_WIDTH)
            self.show()
            # label
            self._label_txt = lbt = None if to_vh_id_num is None else str(to_vh_id_num)
            self._label.resetItem(is_fwd, self.color)
            self.setLabel(text=lbt)

            # bond line
            bonditem = self._bond_item
            bonditem.setPen(getPenObj(  self.color,
                                        styles.PREXOVER_STROKE_WIDTH,
                                        penstyle=Qt.DotLine))
            bonditem.hide()
    # end def

    def setPathAppearance(self, from_virtual_helix_item: PathVirtualHelixItemT) -> bool:
            """Sets the PainterPath according to the index (low = Left, high = Right)
            and strand position (top = Up, bottom = Down).

            Args:
                from_virtual_helix_item:
            """
            part = self._model_part
            idx = self.idx
            is_fwd = self.is_fwd
            id_num = self._id_num
            strand_type = StrandEnum.FWD if is_fwd else StrandEnum.REV

            # relative position info
            bpr = from_virtual_helix_item.getProperty('bases_per_repeat')
            self.is_low = is_low = idx+1 in self.nearby_idxs or (idx+1) % bpr in self.nearby_idxs
            self.is_high = is_high = idx-1 in self.nearby_idxs or (idx-1) % bpr in self.nearby_idxs

            # check strand for xover and color
            if part.hasStrandAtIdx(id_num, idx)[strand_type]:
                strand = part.getStrand(self.is_fwd, id_num, idx)
                if strand.hasXoverAt(idx):
                    return False
                self.color = strand.getColor() if strand is not None else EMPTY_COL
            else:
                self.color = EMPTY_COL

            if is_low and is_high:
                path = (_FWD_DUAL_PATH, _REV_DUAL_PATH)[strand_type]
                raise NotImplementedError("Dual xovers not yet supported")
            elif is_low:
                path = (_FWD_LO_PATH, _REV_LO_PATH)[strand_type]
                self.is3p = True if is_fwd else False
                self.enter_pos = _FWD_LO_PTS[0][-1] if is_fwd else _REV_LO_PTS[0][-1]
                self.exit_pos = _FWD_LO_PTS[0][-1] if is_fwd else _REV_LO_PTS[0][-1]
            elif is_high:
                path = (_FWD_HI_PATH, _REV_HI_PATH)[strand_type]
                self.is3p = False if is_fwd else True
                self.enter_pos = _FWD_HI_PTS[0][-1] if is_fwd else _REV_HI_PTS[0][-1]
                self.exit_pos = _FWD_HI_PTS[0][-1] if is_fwd else _REV_HI_PTS[0][-1]
            else:
                # print("unpaired PreXoverItem at {}[{}]".format(self._id_num, self.idx), self.nearby_idxs)
                return False
            self._tick_marks.setPen(getPenObj(  self.color,
                                                styles.PREXOVER_STROKE_WIDTH,
                                                capstyle=Qt.FlatCap,
                                                joinstyle=Qt.RoundJoin))
            self._tick_marks.setPath(path)
            self._tick_marks.show()
            return True
    # end def

    ### ACCESSORS ###
    def color(self) -> str:
        """The PreXoverItem's color, derived from the associated strand's oligo.

        Returns:
            str: color in hex code
        """
        return self.color

    def getInfo(self) -> ABInfoT:
        """
        Returns:
            Tuple: (from_id_num, is_fwd, from_index, to_vh_id_num)
        """
        return (self._id_num, self.is_fwd, self.idx, self.to_vh_id_num)

    def destroyItem(self):
        """Removes animation adapter, label, bond_item, and this item from scene.
        """
        scene = self.scene()
        self.adapter.destroyItem()
        if scene:
            scene.removeItem(self._label)
            self._label = None
            scene.removeItem(self._bond_item)
            self._bond_item = None
            self.adapter.resetAnimations()
            self.adapter = None
            scene.removeItem(self)
    # end defS

    ### EVENT HANDLERS ###
    def hoverEnterEvent(self, event: QGraphicsSceneHoverEvent):
        """Only ``if enableActive(True)`` is called hover and key events disabled
        by default

        Args:
            event: the hover event
        """
        if self._getActiveTool().methodPrefix() != "selectTool":
            return
        self.setFocus(Qt.MouseFocusReason)
        self.prexoveritem_manager.updateModelActiveBaseInfo(self.getInfo())
        self.setActiveHovered(True)
        status_string = "%d[%d]" % (self._id_num, self.idx)
        self.parentItem().window().statusBar().showMessage(status_string)
        return QGraphicsItem.hoverEnterEvent(self, event)
    # end def

    def hoverLeaveEvent(self, event: QGraphicsSceneHoverEvent):
        """Summary

        Args:
            event (QGraphicsSceneHoverEvent): the hover event
        """
        self.prexoveritem_manager.updateModelActiveBaseInfo(None)
        self.setActiveHovered(False)
        self.clearFocus()
        self.parentItem().window().statusBar().showMessage("")
        return QGraphicsItem.hoverLeaveEvent(self, event)
    # end def

    def mousePressEvent(self, event: QGraphicsSceneMouseEvent):
        """TODO: NEED TO ADD FILTER FOR A CLICK ON THE 3' MOST END OF THE XOVER
        TO DISALLOW OR HANDLE DIFFERENTLY
        """
        viewroot = self.parentItem().viewroot()
        current_filter_set = viewroot.selectionFilterSet()
        if  (self._getActiveTool().methodPrefix() != "selectTool" or
            (self.FILTER_NAME not in current_filter_set) ):
            return

        part = self._model_part
        is_fwd = self.is_fwd

        if self.is3p:
            strand5p = part.getStrand(is_fwd, self._id_num, self.idx)
            strand3p = part.getStrand(not is_fwd, self.to_vh_id_num, self.idx)
        else:
            strand5p = part.getStrand(not is_fwd, self.to_vh_id_num, self.idx)
            strand3p = part.getStrand(is_fwd, self._id_num, self.idx)

        if strand5p is None or strand3p is None:
            return

        # print(strand3p, strand5p)
        part.createXover(strand5p, self.idx, strand3p, self.idx)

        nkey = (self.to_vh_id_num, not is_fwd, self.idx)
        npxi = self.prexoveritem_manager.neighbor_prexover_items.get(nkey, None)
        if npxi:
            npxi.shutdown()
        self.shutdown()

        part.setActiveVirtualHelix(self._id_num, is_fwd, self.idx)
        # self.prexoveritem_manager.handlePreXoverClick(self)

    def keyPressEvent(self, event: QKeyEvent):
        """
        Args:
            event: Description
        """
        self.prexoveritem_manager.handlePreXoverKeyPress(event.key())
    # end def

    ### PUBLIC SUPPORT METHODS ###
    def setLabel(self, text: str = None, outline: bool = False):
        """Summary

        Args:
            text: Default is ``None``
            outline: Default is ``False``
        """
        if text:
            self._label.setTextAndStyle(text=text, outline=outline)
            self._label.show()
        else:
            self._label.hide()
    # end def

    def animate(self, item: QGraphicsItem,
                    property_name: str, duration: int,
                    start_value, end_value):
        """
        Args:
            item: Description
            property_name: Description
            duration: Description
            start_value (QVariant): Description
            end_value (QVariant): Description
        """
        b_name = property_name.encode('ascii')
        anim = item.adapter.getRef(property_name)
        if anim is None:
            anim = QPropertyAnimation(item.adapter, b_name)
            item.adapter.saveRef(property_name, anim)
        anim.setDuration(duration)
        anim.setStartValue(start_value)
        anim.setEndValue(end_value)
        anim.start()
    # end def

    def setActiveHovered(self, is_active: bool):
        """Rotate phosphate Triangle if `self.to_vh_id_num` is not `None`

        Args:
            is_active: whether or not the PreXoverItem is parented to the
                active VirtualHelixItem
        """
        pass
    # end def

    def enableActive(self, is_active: bool, to_vh_id_num: int = None):
        """Call on PreXoverItems created on the active VirtualHelixItem

        Args:
            is_active: Description
            to_vh_id_num: Default is ``None``
        """
        if is_active:
            self.to_vh_id_num = to_vh_id_num
            self.setAcceptHoverEvents(True)
            if to_vh_id_num is None:
                self.setLabel(text=None)
            else:
                self.setLabel(text=str(to_vh_id_num))
        else:
            self.setBrush(getNoBrush())
            self.setAcceptHoverEvents(False)

    def activateNeighbor(self,  active_prexoveritem: 'PreXoverItem',
                                shortcut: str = None):
        """Draws a quad line starting from the item5p to the item3p.
        To be called with whatever the active_prexoveritem is for the parts
        `active_base`.

        Args:
            active_prexoveritem: Description
            shortcut: Default is None
        """
        if self._getActiveTool().methodPrefix() != "selectTool":
            return

        if self.is3p and not active_prexoveritem.is3p:
            item5p = active_prexoveritem
            item3p = self
        elif not self.is3p and active_prexoveritem.is3p:
            item5p = self
            item3p = active_prexoveritem
        else:
            return

        same_parity = self.is_fwd == active_prexoveritem.is_fwd

        p1 = item5p._tick_marks.scenePos() + item5p.exit_pos
        p2 = item3p._tick_marks.scenePos() + item3p.exit_pos

        c1 = QPointF()

        # case 1: same parity
        if same_parity:
            dy = abs(p2.y() - p1.y())
            c1.setX(p1.x() + _X_SCALE * dy)
            c1.setY(0.5 * (p1.y() + p2.y()))
        # case 2: different parity
        else:
            if item3p.is_fwd:
                c1.setX(p1.x() - _X_SCALE * abs(p2.y() - p1.y()))
            else:
                c1.setX(p1.x() + _X_SCALE * abs(p2.y() - p1.y()))
            c1.setY(0.5 * (p1.y() + p2.y()))

        pp = QPainterPath()
        pp.moveTo(self._tick_marks.mapFromScene(p1))
        pp.quadTo(self._tick_marks.mapFromScene(c1),
                  self._tick_marks.mapFromScene(p2))
        # pp.cubicTo(c1, c2, self._tick_marks.mapFromScene(p2))
        self._bond_item.setPath(pp)
        self._bond_item.show()
    # end def

    def deactivateNeighbor(self):
        """Summary
        """
        if self.isVisible():
            self._bond_item.hide()
            self.setLabel(text=self._label_txt)
示例#20
0
class MapData(dict):
    def __init__(self, zone=None):
        super().__init__()
        self.zone = zone
        self.raw = {'lines': [], 'poi': [], 'grid': []}
        self.geometry = None  # MapGeometry
        self.players = {}
        self.spawns = []
        self.way_point = None
        self.grid = None

        if self.zone is not None:
            self._load()

    def _load(self):
        # Get list of all map files for current zone
        map_file_name = MapData.get_zone_dict()[self.zone.strip().lower()]
        extensions = ['.txt', '_1.txt', '_2.txt', '_3.txt', '_4.txt', '_5.txt']
        maps = [
            os.path.join(MAP_FILES_LOCATION, m)
            for m in [(map_file_name + e) for e in extensions]
            if os.path.exists(os.path.join(MAP_FILES_LOCATION, m))
        ]

        all_x, all_y, all_z = [], [], []

        # TODO: Remove the references to raw
        # Create Lines and Points
        for map_file in maps:
            with open(map_file, 'r') as f:
                for line in f.readlines():
                    line_type = line.lower()[0:1]
                    data = [value.strip() for value in line[1:].split(',')]
                    if line_type == 'l':  # line
                        x1, y1, z1, x2, y2, z2 = list(map(float, data[0:6]))
                        self.raw['lines'].append(
                            MapLine(x1=x1,
                                    y1=y1,
                                    z1=z1,
                                    x2=x2,
                                    y2=y2,
                                    z2=z2,
                                    color=self.color_transform(
                                        QColor(int(data[6]), int(data[7]),
                                               int(data[8])))))
                        all_x.extend((x1, x2))
                        all_y.extend((y1, y2))
                        all_z.append(min(z1, z2))
                        # if abs(z1 - z2) < 2:
                        # if z1 == z2:
                        # all_z.extend((z1, z2))

                    elif line_type == 'p':  # point
                        x, y, z = map(float, data[0:3])
                        self.raw['poi'].append(
                            MapPoint(x=x,
                                     y=y,
                                     z=z,
                                     size=int(data[6]),
                                     text=str(data[7]),
                                     color=self.color_transform(
                                         QColor(int(data[3]), int(data[4]),
                                                int(data[5])))))

        # Create Grid Lines
        lowest_x, highest_x, lowest_y, highest_y, lowest_z, highest_z = min(
            all_x), max(all_x), min(all_y), max(all_y), min(all_z), max(all_z)

        left, right = int(math.floor(lowest_x / 1000) * 1000), int(
            math.ceil(highest_x / 1000) * 1000)
        top, bottom = int(math.floor(lowest_y / 1000) * 1000), int(
            math.ceil(highest_y / 1000) * 1000)

        for number in range(left, right + 1000, 1000):
            self.raw['grid'].append(
                MapLine(x1=number,
                        x2=number,
                        y1=top,
                        y2=bottom,
                        z1=0,
                        z2=0,
                        color=QColor(255, 255, 255, 25)))

        for number in range(top, bottom + 1000, 1000):
            self.raw['grid'].append(
                MapLine(y1=number,
                        y2=number,
                        x1=left,
                        x2=right,
                        z1=0,
                        z2=0,
                        color=QColor(255, 255, 255, 25)))

        self.grid = QGraphicsPathItem()
        line_path = QPainterPath()
        for line in self.raw['grid']:
            line_path.moveTo(line.x1, line.y1)
            line_path.lineTo(line.x2, line.y2)
        self.grid.setPath(line_path)
        self.grid.setPen(
            QPen(line.color, config.data['maps']['grid_line_width']))
        self.grid.setZValue(0)

        # Get z levels
        counter = Counter(all_z)

        # bunch together zgroups based on peaks with floor being low point before rise
        z_groups = []
        last_value = None
        first_run = True
        for z in sorted(counter.items(), key=lambda x: x[0]):
            if last_value is None:
                last_value = z
                continue
            if (abs(last_value[0] - z[0]) < 20) or z[1] < 8:
                last_value = (last_value[0], last_value[1] + z[1])
            else:
                if first_run:
                    first_run = False
                    if last_value[1] < 40 or abs(last_value[0] - z[0]) < 18:
                        last_value = z
                        continue
                z_groups.append(last_value[0])
                last_value = z

        # get last iteration
        if last_value[1] > 50:
            z_groups.append(last_value[0])

        self._z_groups = z_groups

        # Create QGraphicsPathItem for lines seperately to retain colors
        temp_dict = {}
        for l in self.raw['lines']:
            lz = min(l.z1, l.z2)
            lz = self.get_closest_z_group(lz)
            if not temp_dict.get(lz, None):
                temp_dict[lz] = {'paths': {}}
            lc = l.color.getRgb()
            if not temp_dict[lz]['paths'].get(lc, None):
                path_item = QGraphicsPathItem()
                path_item.setPen(
                    QPen(l.color, config.data['maps']['line_width']))
                temp_dict[lz]['paths'][lc] = path_item
            path = temp_dict[lz]['paths'][lc].path()
            path.moveTo(l.x1, l.y1)
            path.lineTo(l.x2, l.y2)
            temp_dict[lz]['paths'][lc].setPath(path)

        # Group QGraphicsPathItems into QGraphicsItemGroups and update self
        for z in temp_dict.keys():
            item_group = QGraphicsItemGroup()
            for (_, path) in temp_dict[z]['paths'].items():
                item_group.addToGroup(path)
            self[z] = {'paths': None, 'poi': []}
            self[z]['paths'] = item_group

        # Create Points of Interest
        for p in self.raw['poi']:
            z = self.get_closest_z_group(p.z)
            self[z]['poi'].append(PointOfInterest(location=p))

        self.geometry = MapGeometry(
            lowest_x=lowest_x,
            highest_x=highest_x,
            lowest_y=lowest_y,
            highest_y=highest_y,
            lowest_z=lowest_z,
            highest_z=highest_z,
            center_x=int(highest_x - (highest_x - lowest_x) / 2),
            center_y=int(highest_y - (highest_y - lowest_y) / 2),
            width=int(highest_x - lowest_x),
            height=int(highest_y - lowest_y),
            z_groups=z_groups)

    def get_closest_z_group(self, z):
        closest = min(self._z_groups, key=lambda x: abs(x - z))
        if z < closest:
            lower_index = self._z_groups.index(closest) - 1
            if lower_index > -1:
                closest = self._z_groups[lower_index]
        return closest

    @staticmethod
    def get_zone_dict():
        # Load Map Pairs from map_keys.ini
        zone_dict = {}
        with open(MAP_KEY_FILE, 'r') as file:
            for line in file.readlines():
                values = line.split('=')
                zone_dict[values[0].strip()] = values[1].strip()
        return zone_dict

    @staticmethod
    def color_transform(color):
        lightness = color.lightness()
        if lightness == 0:
            return QColor(255, 255, 255)
        elif (color.red == color.green == color.blue):
            return QColor(255, 255, 255)
        elif lightness < 150:
            return color.lighter(150)
        return color
示例#21
0
class PreXoverItem(QGraphicsRectItem):
    """A PreXoverItem exists between a single 'from' VirtualHelixItem index
    and zero or more 'to' VirtualHelixItem Indices

    Attributes:
        adapter (:obj:`PropertyWrapperObject`): Description
        idx (int): the base index within the virtual helix
        is_fwd (bool): is this a forward strand?
        prexoveritem_manager (:obj:`PreXoverManager`): Manager of the PreXoverItems
        to_vh_id_num (int): Virtual Helix number this Xover point might connect to
    """
    FILTER_NAME = "xover"

    def __init__(self, from_virtual_helix_item: PathVirtualHelixItemT,
                 is_fwd: bool, from_index: int, nearby_idxs: List[int],
                 to_vh_id_num: int, prexoveritem_manager: PreXoverManagerT):
        """Summary

        Args:
            from_virtual_helix_item: Description
            is_fwd: is this a forward strand?
            from_index: index of the Virtual Helix this xover is coming from
            nearby_idxs:
            to_vh_id_num: Virtual Helix number this Xover point might connect to
            prexoveritem_manager: Manager of the PreXoverItems
        """
        super(QGraphicsRectItem, self).__init__(BASE_RECT,
                                                from_virtual_helix_item)
        self.adapter = PropertyWrapperObject(self)
        self._tick_marks = QGraphicsPathItem(self)
        self._tick_marks.setAcceptHoverEvents(True)
        self._bond_item = QGraphicsPathItem(self)
        self._bond_item.hide()
        self._label = PreXoverLabel(is_fwd, self)
        self._path = QGraphicsPathItem()
        self.setZValue(styles.ZPREXOVERITEM)
        self.setPen(getNoPen())
        self.resetItem(from_virtual_helix_item, is_fwd, from_index,
                       nearby_idxs, to_vh_id_num, prexoveritem_manager)

        self._getActiveTool = from_virtual_helix_item.viewroot(
        ).manager.activeToolGetter

    # end def

    def shutdown(self):
        """Summary
        """
        self.setBrush(getNoBrush())
        self.to_vh_id_num = None
        self.adapter.resetAnimations()
        self.setAcceptHoverEvents(False)
        self.hide()

    # end def

    def resetItem(self, from_virtual_helix_item: PathVirtualHelixItemT,
                  is_fwd: bool, from_index: int, nearby_idxs: List[int],
                  to_vh_id_num: int, prexoveritem_manager: PreXoverManagerT):
        """Update this pooled PreXoverItem with current info.
        Called by PreXoverManager.

        Args:
            from_virtual_helix_item: the associated vh_item
            is_fwd: True if associated with fwd strand, False if rev strand
            from_index: idx of associated vh
            nearby_idxs:
            to_vh_id_num: id_num of the other vh
            prexoveritem_manager: the manager
        """
        # to_vh_item = from_virtual_helix_item.partItem().idToVirtualHelixItem(to_vh_id_num)
        self.setParentItem(from_virtual_helix_item)
        # self.setParentItem(to_vh_item)
        self.resetTransform()
        self._id_num = from_virtual_helix_item.idNum()
        self._model_part = from_virtual_helix_item.part()
        self.idx = from_index
        self.is_low = False
        self.is_high = False
        self.nearby_idxs = nearby_idxs
        self.is_fwd = is_fwd
        self.color = None
        self.is3p = None
        self.enter_pos = None
        self.exit_pos = None
        self.to_vh_id_num = to_vh_id_num
        self._label_txt = None
        self.prexoveritem_manager = prexoveritem_manager

        # todo: check here if xover present and disable
        result = self.setPathAppearance(from_virtual_helix_item)

        if result:
            self.setBrush(getNoBrush())
            if is_fwd:
                self.setPos(from_index * BASE_WIDTH,
                            -BASE_WIDTH - 0.1 * BASE_WIDTH)
            else:
                self.setPos(from_index * BASE_WIDTH, 2 * BASE_WIDTH)
            self.show()
            # label
            self._label_txt = lbt = None if to_vh_id_num is None else str(
                to_vh_id_num)
            self._label.resetItem(is_fwd, self.color)
            self.setLabel(text=lbt)

            # bond line
            bonditem = self._bond_item
            bonditem.setPen(
                getPenObj(self.color,
                          styles.PREXOVER_STROKE_WIDTH,
                          penstyle=Qt.DotLine))
            bonditem.hide()

    # end def

    def setPathAppearance(
            self, from_virtual_helix_item: PathVirtualHelixItemT) -> bool:
        """Sets the PainterPath according to the index (low = Left, high = Right)
            and strand position (top = Up, bottom = Down).

            Args:
                from_virtual_helix_item:
            """
        part = self._model_part
        idx = self.idx
        is_fwd = self.is_fwd
        id_num = self._id_num
        strand_type = StrandEnum.FWD if is_fwd else StrandEnum.REV

        # relative position info
        bpr = from_virtual_helix_item.getProperty('bases_per_repeat')
        self.is_low = is_low = idx + 1 in self.nearby_idxs or (
            idx + 1) % bpr in self.nearby_idxs
        self.is_high = is_high = idx - 1 in self.nearby_idxs or (
            idx - 1) % bpr in self.nearby_idxs

        # check strand for xover and color
        if part.hasStrandAtIdx(id_num, idx)[strand_type]:
            strand = part.getStrand(self.is_fwd, id_num, idx)
            if strand.hasXoverAt(idx):
                return False
            self.color = strand.getColor() if strand is not None else EMPTY_COL
        else:
            self.color = EMPTY_COL

        if is_low and is_high:
            path = (_FWD_DUAL_PATH, _REV_DUAL_PATH)[strand_type]
            raise NotImplementedError("Dual xovers not yet supported")
        elif is_low:
            path = (_FWD_LO_PATH, _REV_LO_PATH)[strand_type]
            self.is3p = True if is_fwd else False
            self.enter_pos = _FWD_LO_PTS[0][-1] if is_fwd else _REV_LO_PTS[0][
                -1]
            self.exit_pos = _FWD_LO_PTS[0][-1] if is_fwd else _REV_LO_PTS[0][-1]
        elif is_high:
            path = (_FWD_HI_PATH, _REV_HI_PATH)[strand_type]
            self.is3p = False if is_fwd else True
            self.enter_pos = _FWD_HI_PTS[0][-1] if is_fwd else _REV_HI_PTS[0][
                -1]
            self.exit_pos = _FWD_HI_PTS[0][-1] if is_fwd else _REV_HI_PTS[0][-1]
        else:
            # print("unpaired PreXoverItem at {}[{}]".format(self._id_num, self.idx), self.nearby_idxs)
            return False
        self._tick_marks.setPen(
            getPenObj(self.color,
                      styles.PREXOVER_STROKE_WIDTH,
                      capstyle=Qt.FlatCap,
                      joinstyle=Qt.RoundJoin))
        self._tick_marks.setPath(path)
        self._tick_marks.show()
        return True

    # end def

    ### ACCESSORS ###
    def color(self) -> str:
        """The PreXoverItem's color, derived from the associated strand's oligo.

        Returns:
            str: color in hex code
        """
        return self.color

    def getInfo(self) -> ABInfoT:
        """
        Returns:
            Tuple: (from_id_num, is_fwd, from_index, to_vh_id_num)
        """
        return (self._id_num, self.is_fwd, self.idx, self.to_vh_id_num)

    def destroyItem(self):
        """Removes animation adapter, label, bond_item, and this item from scene.
        """
        scene = self.scene()
        self.adapter.destroyItem()
        if scene:
            scene.removeItem(self._label)
            self._label = None
            scene.removeItem(self._bond_item)
            self._bond_item = None
            self.adapter.resetAnimations()
            self.adapter = None
            scene.removeItem(self)

    # end defS

    ### EVENT HANDLERS ###
    def hoverEnterEvent(self, event: QGraphicsSceneHoverEvent):
        """Only ``if enableActive(True)`` is called hover and key events disabled
        by default

        Args:
            event: the hover event
        """
        if self._getActiveTool().methodPrefix() != "selectTool":
            return
        self.setFocus(Qt.MouseFocusReason)
        self.prexoveritem_manager.updateModelActiveBaseInfo(self.getInfo())
        self.setActiveHovered(True)
        status_string = "%d[%d]" % (self._id_num, self.idx)
        self.parentItem().window().statusBar().showMessage(status_string)
        return QGraphicsItem.hoverEnterEvent(self, event)

    # end def

    def hoverLeaveEvent(self, event: QGraphicsSceneHoverEvent):
        """Summary

        Args:
            event (QGraphicsSceneHoverEvent): the hover event
        """
        self.prexoveritem_manager.updateModelActiveBaseInfo(None)
        self.setActiveHovered(False)
        self.clearFocus()
        self.parentItem().window().statusBar().showMessage("")
        return QGraphicsItem.hoverLeaveEvent(self, event)

    # end def

    def mousePressEvent(self, event: QGraphicsSceneMouseEvent):
        """TODO: NEED TO ADD FILTER FOR A CLICK ON THE 3' MOST END OF THE XOVER
        TO DISALLOW OR HANDLE DIFFERENTLY
        """
        viewroot = self.parentItem().viewroot()
        current_filter_set = viewroot.selectionFilterSet()
        if (self._getActiveTool().methodPrefix() != "selectTool"
                or (self.FILTER_NAME not in current_filter_set)):
            return

        part = self._model_part
        is_fwd = self.is_fwd

        if self.is3p:
            strand5p = part.getStrand(is_fwd, self._id_num, self.idx)
            strand3p = part.getStrand(not is_fwd, self.to_vh_id_num, self.idx)
        else:
            strand5p = part.getStrand(not is_fwd, self.to_vh_id_num, self.idx)
            strand3p = part.getStrand(is_fwd, self._id_num, self.idx)

        if strand5p is None or strand3p is None:
            return

        # print(strand3p, strand5p)
        part.createXover(strand5p, self.idx, strand3p, self.idx)

        nkey = (self.to_vh_id_num, not is_fwd, self.idx)
        npxi = self.prexoveritem_manager.neighbor_prexover_items.get(
            nkey, None)
        if npxi:
            npxi.shutdown()
        self.shutdown()

        part.setActiveVirtualHelix(self._id_num, is_fwd, self.idx)
        # self.prexoveritem_manager.handlePreXoverClick(self)

    def keyPressEvent(self, event: QKeyEvent):
        """
        Args:
            event: Description
        """
        self.prexoveritem_manager.handlePreXoverKeyPress(event.key())

    # end def

    ### PUBLIC SUPPORT METHODS ###
    def setLabel(self, text: str = None, outline: bool = False):
        """Summary

        Args:
            text: Default is ``None``
            outline: Default is ``False``
        """
        if text:
            self._label.setTextAndStyle(text=text, outline=outline)
            self._label.show()
        else:
            self._label.hide()

    # end def

    def animate(self, item: QGraphicsItem, property_name: str, duration: int,
                start_value, end_value):
        """
        Args:
            item: Description
            property_name: Description
            duration: Description
            start_value (QVariant): Description
            end_value (QVariant): Description
        """
        b_name = property_name.encode('ascii')
        anim = item.adapter.getRef(property_name)
        if anim is None:
            anim = QPropertyAnimation(item.adapter, b_name)
            item.adapter.saveRef(property_name, anim)
        anim.setDuration(duration)
        anim.setStartValue(start_value)
        anim.setEndValue(end_value)
        anim.start()

    # end def

    def setActiveHovered(self, is_active: bool):
        """Rotate phosphate Triangle if `self.to_vh_id_num` is not `None`

        Args:
            is_active: whether or not the PreXoverItem is parented to the
                active VirtualHelixItem
        """
        pass

    # end def

    def enableActive(self, is_active: bool, to_vh_id_num: int = None):
        """Call on PreXoverItems created on the active VirtualHelixItem

        Args:
            is_active: Description
            to_vh_id_num: Default is ``None``
        """
        if is_active:
            self.to_vh_id_num = to_vh_id_num
            self.setAcceptHoverEvents(True)
            if to_vh_id_num is None:
                self.setLabel(text=None)
            else:
                self.setLabel(text=str(to_vh_id_num))
        else:
            self.setBrush(getNoBrush())
            self.setAcceptHoverEvents(False)

    def activateNeighbor(self,
                         active_prexoveritem: 'PreXoverItem',
                         shortcut: str = None):
        """Draws a quad line starting from the item5p to the item3p.
        To be called with whatever the active_prexoveritem is for the parts
        `active_base`.

        Args:
            active_prexoveritem: Description
            shortcut: Default is None
        """
        if self._getActiveTool().methodPrefix() != "selectTool":
            return

        if self.is3p and not active_prexoveritem.is3p:
            item5p = active_prexoveritem
            item3p = self
        elif not self.is3p and active_prexoveritem.is3p:
            item5p = self
            item3p = active_prexoveritem
        else:
            return

        same_parity = self.is_fwd == active_prexoveritem.is_fwd

        p1 = item5p._tick_marks.scenePos() + item5p.exit_pos
        p2 = item3p._tick_marks.scenePos() + item3p.exit_pos

        c1 = QPointF()

        # case 1: same parity
        if same_parity:
            dy = abs(p2.y() - p1.y())
            c1.setX(p1.x() + _X_SCALE * dy)
            c1.setY(0.5 * (p1.y() + p2.y()))
        # case 2: different parity
        else:
            if item3p.is_fwd:
                c1.setX(p1.x() - _X_SCALE * abs(p2.y() - p1.y()))
            else:
                c1.setX(p1.x() + _X_SCALE * abs(p2.y() - p1.y()))
            c1.setY(0.5 * (p1.y() + p2.y()))

        pp = QPainterPath()
        pp.moveTo(self._tick_marks.mapFromScene(p1))
        pp.quadTo(self._tick_marks.mapFromScene(c1),
                  self._tick_marks.mapFromScene(p2))
        # pp.cubicTo(c1, c2, self._tick_marks.mapFromScene(p2))
        self._bond_item.setPath(pp)
        self._bond_item.show()

    # end def

    def deactivateNeighbor(self):
        """Summary
        """
        if self.isVisible():
            self._bond_item.hide()
            self.setLabel(text=self._label_txt)
示例#22
0
class ForcedXoverNode3(QGraphicsRectItem):
    """
    This is a QGraphicsRectItem to allow actions and also a 
    QGraphicsSimpleTextItem to allow a label to be drawn
    """
    def __init__(self, virtual_helix_item, xover_item, strand3p, idx):
        super(ForcedXoverNode3, self).__init__(virtual_helix_item)
        self._vhi = virtual_helix_item
        self._xover_item = xover_item
        self._idx = idx
        self._is_on_top = virtual_helix_item.isStrandOnTop(strand3p)
        self._is_drawn_5_to_3 = strand3p.strandSet().isDrawn5to3()
        self._strand_type = strand3p.strandSet().strandType()

        self._partner_virtual_helix = virtual_helix_item

        self._blank_thing = QGraphicsRectItem(_blankRect, self)
        self._blank_thing.setBrush(QBrush(Qt.white))
        self._path_thing = QGraphicsPathItem(self)
        self.configurePath()

        self.setPen(_NO_PEN)
        self._label = None
        self.setPen(_NO_PEN)
        self.setBrush(_NO_BRUSH)
        self.setRect(_rect)

        self.setZValue(styles.ZENDPOINTITEM + 1)

    # end def

    def updateForFloatFromVHI(self, virtual_helix_item, strand_type, idx_x,
                              idx_y):
        """

        """
        self._vhi = virtual_helix_item
        self.setParentItem(virtual_helix_item)
        self._strand_type = strand_type
        self._idx = idx_x
        self._is_on_top = self._is_drawn_5_to_3 = True if idx_y == 0 else False
        self.updatePositionAndAppearance(is_from_strand=False)

    # end def

    def updateForFloatFromStrand(self, virtual_helix_item, strand3p, idx):
        """

        """
        self._vhi = virtual_helix_item
        self._strand = strand3p
        self.setParentItem(virtual_helix_item)
        self._idx = idx
        self._is_on_top = virtual_helix_item.isStrandOnTop(strand3p)
        self._is_drawn_5_to_3 = strand3p.strandSet().isDrawn5to3()
        self._strand_type = strand3p.strandSet().strandType()
        self.updatePositionAndAppearance()

    # end def

    def strandType(self):
        return self._strand_type

    # end def

    def configurePath(self):
        self._path_thing.setBrush(QBrush(styles.RED_STROKE))
        path = PPR3 if self._is_drawn_5_to_3 else PPL3
        offset = -_BASE_WIDTH if self._is_drawn_5_to_3 else _BASE_WIDTH
        self._path_thing.setPath(path)
        self._path_thing.setPos(offset, 0)

        offset = -_BASE_WIDTH if self._is_drawn_5_to_3 else 0
        self._blank_thing.setPos(offset, 0)

        self._blank_thing.show()
        self._path_thing.show()

    # end def

    def refreshXover(self):
        self._xover_item.refreshXover()

    # end def

    def setPartnerVirtualHelix(self, virtual_helix_item):
        self._partner_virtual_helix = virtual_helix_item

    # end def

    def idx(self):
        return self._idx

    # end def

    def virtualHelixItem(self):
        return self._vhi

    # end def

    def point(self):
        return self._vhi.upperLeftCornerOfBaseType(self._idx,
                                                   self._strand_type)

    # end def

    def floatPoint(self):
        pt = self.pos()
        return pt.x(), pt.y()

    # end def

    def isOnTop(self):
        return self._is_on_top

    # end def

    def isDrawn5to3(self):
        return self._is_drawn_5_to_3

    # end def

    def updatePositionAndAppearance(self, is_from_strand=True):
        """
        Sets position by asking the VirtualHelixItem
        Sets appearance by choosing among pre-defined painterpaths (from
        normalstrandgraphicsitem) depending on drawing direction.
        """
        self.setPos(*self.point())
        n5 = self._xover_item._node5
        if is_from_strand:
            from_strand, from_idx = (n5._strand,
                                     n5._idx) if n5 != self else (None, None)
            if self._strand.canInstallXoverAt(self._idx, from_strand,
                                              from_idx):
                self.configurePath()
                # We can only expose a 5' end. But on which side?
                is_left = True if self._is_drawn_5_to_3 else False
                self._updateLabel(is_left)
            else:
                self.hideItems()
        else:
            self.hideItems()

    # end def

    def updateConnectivity(self):
        is_left = True if self._is_drawn_5_to_3 else False
        self._updateLabel(is_left)

    # end def

    def remove(self):
        """
        Clean up this joint
        """
        scene = self.scene()
        scene.removeItem(self._label)
        self._label = None
        scene.removeItem(self._path_thing)
        self._path_thing = None
        scene.removeItem(self._blank_thing)
        self._blank_thing = None
        scene.removeItem(self)

    # end def

    def _updateLabel(self, is_left):
        """
        Called by updatePositionAndAppearance during init, or later by
        updateConnectivity. Updates drawing and position of the label.
        """
        lbl = self._label
        if self._idx != None:
            bw = _BASE_WIDTH
            num = self._partner_virtual_helix.number()
            tBR = _FM.tightBoundingRect(str(num))
            half_label_h = tBR.height() / 2.0
            half_label_w = tBR.width() / 2.0
            # determine x and y positions
            label_x = bw / 2.0 - half_label_w
            if self._is_on_top:
                label_y = -0.25 * half_label_h - 0.5 - 0.5 * bw
            else:
                label_y = 2 * half_label_h + 0.5 + 0.5 * bw
            # adjust x for left vs right
            label_x_offset = 0.25 * bw if is_left else -0.25 * bw
            label_x += label_x_offset
            # adjust x for numeral 1
            if num == 1: label_x -= half_label_w / 2.0
            # create text item
            if lbl == None:
                lbl = QGraphicsSimpleTextItem(str(num), self)
            lbl.setPos(label_x, label_y)
            lbl.setBrush(_ENAB_BRUSH)
            lbl.setFont(_TO_HELIX_NUM_FONT)
            self._label = lbl

            lbl.setText(str(self._partner_virtual_helix.number()))
            lbl.show()
        # end if

    # end def

    def hideItems(self):
        if self._label:
            self._label.hide()
        if self._blank_thing:
            self._path_thing.hide()
        if self._blank_thing:
            self._blank_thing.hide()
示例#23
0
class CharItem(QGraphicsRectItem):
    """ This item represents character item

        The purpose of the class is to draw a character, create a matrix
        of rectangles and resolve in which rectangles the character passes

        The class allow the following operations 
        -#  Drawing a character using the mouse events:
            -#  Start by the mouse press event
            -#  Continues by the mouse move event
            -#  The character is stored in QGraphicsPathItem
        -#  Transform the character to occupy the whole item's space
        -#  Set operation : resolving the Occupied matrix which tell on which rectangle the character
            passes
        -#  Reset operation : reverse the character transform so it is possible to continue
            drawing the character
        -#  Save operation : To a QDataStream
        -#  Load operation : From a QDataStream

        The graphical view of the class is composed from:
        -#  This class which inherits from QGraphicsRectItem and holds :
        -#  A QGraphicsPathItem : representing the character
        -#  A QGraphicsPathItem : representing the occupied rectangles
        -#  A QGraphicsPathItem : representing the unoccupied rectangles
    """

    def __init__(self, rect: QRectF, pos: QPointF, viewIndex: int=-1):
        """ CharItem constructor

            Args:
                rect    (QRectF)    : The rectangle that the character should fill
                pos     (QPointF)   : The position of the item within the parent
                viewIndex (int)     : The index of the item in case it is presented in multi character presentation
        """
        super(CharItem, self).__init__(rect)
        self.setAcceptedMouseButtons(Qt.LeftButton)
        self.setPresentationPrms()
        self.occupied = [[False for idx in range(self.netCols)] for idx in range(self.netRows)]
        self.charPath = None
        self.wasSetted = False
        self.occupiedPathItem = None
        self.unoccupiedPathItem = None
        self.dirty = False
        self.viewIndex = viewIndex
        self.filename = ""
        self.boundaries = rect
        self.dx = 1
        self.dy = 1
        self.posInParent = pos
        self.setPos(self.posInParent)

    def setPresentationPrms(self):
        """ Setting the presentation prms

            The reason the there is a duplicate set of presentation parameters is
            that it allows changing the presentation parameters for one character
            (like in the select option
        """

        self.netColor = netColor
        self.netThickness = netThickness
        self.occupyColor = occupyColor
        self.unOccupyColor = unOccupyColor
        self.shapeColor = shapeColor
        self.shapeLineThickness = shapeLineThickness
        self.selectedOccupiedColor = selectedOccupiedColor
        self.selectedShapeColor = selectedShapeColor
        self.netRows = netRows
        self.netCols = netCols

    def setNetBoxDimensions(self, rect: QRectF):
        """ Set net box dimensions
        
            The net box is the rectangle that compose the network
            drawn to show the occupy matrix
        """
        self.netRectHeight = rect.height() / self.netRows
        self.netRectWidth = rect.width() / self.netCols
        self.left = rect.left()
        self.top = rect.top()

    def netRect(self, row_idx: int, col_idx: int) -> QRectF:
        """ Set net rect
        
            The net box is the rectangle that compose the network
            drawn to show the occupy matrix

            Args:
                row_idx  (int)   : The row of the network rectangle
                col_idx  (int)   : The col of the network rectangle

            Returns:
                QRectF  : The rectangle
        """
        return QRectF(self.left + col_idx * self.netRectWidth, self.top + row_idx * self.netRectHeight, self.netRectWidth,
                      self.netRectHeight)

    def mouseMoveEvent(self, event: QGraphicsSceneMouseEvent):
        """ Mouse move event : continue draw a line

            This event is activated when the mouse is pressed and moves

            The methods draws the line in 2 conditions:
            -# The item is not part of multi character presentation
            -# A character path was initiated (using the mouse press event)

            Args:
               event (QGraphicsSceneMouseEvent) : the event description
        """
        if self.viewIndex == -1:
            if self.charPath is not None:
                point = event.scenePos()
                path = self.charPath.path()
                path.lineTo(point)
                self.charPath.setPath(path)
                self.update()

    def mousePressEvent(self, event: QGraphicsSceneMouseEvent):
        """ Mouse Press Event : Start a new line / Select the character

            If the character is part of multi character presentation 
                activate the character selection
            If the character is in single character presentation
                Start a new line in the character

            Args:
               event (QGraphicsSceneMouseEvent) : the event description
        """
        if self.viewIndex == -1:
            self.startLine(event)
        else:
            self.setSelected()

    def startLine(self, event: QGraphicsSceneMouseEvent):
        """ Start drawing a line

            When the mouse button is pressed and we are in single character
            dialog this method is activated to start drowning a line in the
            character

            Args:
               event (QGraphicsSceneMouseEvent) : the event description
        """
        # There are 2 modes for the presentation:
        # Original mode where the character is it's original size
        # After setting mode when the set was done and the character fullfill all
        # the item's space
        # Drawing can be done only in original mode
        if self.wasSetted:
            QMessageBox.critical(None, "Char identifier window", "The shape was already setted use revert setting")
            return

        # If this is the first start of a line - generate the QPainterPath and QGraphicsPathItem
        if self.charPath is None:
            self.initCharPath()

        # Move to the mouse position
        point = event.scenePos()
        path = self.charPath.path()
        path.moveTo(point)
        self.charPath.setPath(path)
        self.dirty = True

    def initCharPath(self):
        """ Init the item that holds the character

            There is one path item that holds the character
            This method is activated by start line if the char item 
            was not created to create the new and only one
        """
        self.dirty = True
        self.charPath = QGraphicsPathItem(self)
        self.charPath.setPen(QPen(QColor(self.shapeColor), self.shapeLineThickness))
        self.charPath.setZValue(1)
        self.charPath.originalPos = self.charPath.pos()
        self.charPath.setPath(QPainterPath())

    def setSelected(self):
        """ Set the item a selected item

            This method is activated when the mouse button is presses
            and the item is part of multi character presentation
        """

        # Set the colors of the item
        self.occupiedPathItem.setBrush(QBrush(QColor(self.selectedOccupiedColor)))
        self.charPath.setPen(QPen(QColor(self.selectedShapeColor), self.shapeLineThickness))
        self.update()

        # Report to the parent item about the selection
        self.parentItem().setSelected(self.viewIndex)

    def resetSelected(self):
        """ Set the colors of the item to not selected
        """

        self.occupiedPathItem.setBrush(QBrush(QColor(self.occupyColor)))
        self.charPath.setPen(QPen(QColor(self.shapeColor), self.shapeLineThickness))
        self.update()

    def set(self):
        """ Calculate the occupied matrix and present the results

            This method does the following:
            -# Fill the occupied matrix
            -# Generate the occupied and unoccupied pathes items
            -# Transform the char path to fit to the item's boundaries
        """
        # If there is no shape drawn - return
        if self.charPath is None:
            QMessageBox.critical(None, "Char identifier window", "There is no shape drawn")
            return

        # If the item is in setted mode - return
        if self.wasSetted:
            QMessageBox.critical(None, "Char identifier window", "The shape was already setted use revert setting")
            return

        # fill the occupied matrix with the data before the scaling
        self.fillOccupied()
        self.setNetBoxDimensions(self.boundingRect())
        self.createNetPaths()

        # update the transform - change the dimensions and location
        # only on the first time
        self.transformCharPath()

        self.wasSetted = True
        # update the presentation
        self.update()

    def revertTransform(self):
        """ Change from Setted mode to drawing mode

            The drawing mode is the mode where the character can be drawn
            -#  Restore the original size of the character (Reset the transform of the char item)
            -#  Restor the char path item's position to the original one (saved when created)
            -#  Empty the occupiedPath and the unoccupiedPath
        """
        # If there is no character drawn - return
        if self.charPath is None:
            QMessageBox.critical(None, "Char identifier window", "There is no shape drawn")
            return

        # If the item is already in drawing mode - return
        if not self.wasSetted:
            QMessageBox.critical(None, "Char identifier window", "The shape was not setted use set button")
            return

        # The char path item
        transform = self.charPath.transform()
        transform.reset()

        # The self.dx and self.dy are the scale parameters created when the item
        # begins and they are the scale parameters that transform it to the boundaries
        # given by the parent item
        transform.scale(self.dx, self.dy)
        self.charPath.setTransform(transform)
        self.charPath.setPos(self.charPath.originalPos)

        # Empty the network pathes
        self.occupiedPathItem.setPath(QPainterPath())
        self.unoccupiedPathItem.setPath(QPainterPath())
        self.wasSetted = False

    def transformCharPath(self):
        """ Transform char path when the item is setted

            This method does the following
            -#  scale the char path to the size of the item
            -#  calculate the new position of the char path so that it will
                be placed at the top left corner of the item
        """
        dx = self.boundingRect().width() / self.charPath.boundingRect().width()
        dy = self.boundingRect().height() / self.charPath.boundingRect().height()
        transform = self.charPath.transform()
        transform.reset()
        transform.scale(dx, dy)
        self.charPath.setTransform(transform)

        # Move the shape to the origin
        moveX = -(self.charPath.boundingRect().left() - self.boundingRect().left()) * dx
        moveY = -(self.charPath.boundingRect().top() - self.boundingRect().top()) * dy
        self.charPath.setX(self.charPath.x() + moveX)
        self.charPath.setY(self.charPath.y() + moveY)

    def fillOccupied(self):
        """ Fill the occupied matrix

            The algorithm of filling the occupied matrix is 
            -#  Scanning the char path
            -#  For each point decide on where row and column of the net
            -#  Set the occupies matrix for this column and row to True
        """

        for idx in range(100):
            point = self.charPath.path().pointAtPercent(idx / 100.)
            row_idx, col_idx = self.calcRowCol(point)
            self.occupied[row_idx][col_idx] = True

    def calcRowCol(self, point: QPointF):
        """ Calculate the network row and column that a point is int
            calc the row and column indexes of a point

            The following is the algorithm:
            1.  Find the distance between the point and the left (or top)
            2.  Divide the distance with the width of path to find the relative position
            3.  Multipile this relative position with the number of rows/cols
            4.  Convert the result to int to find the indexes
            5.  If the index is the number of row/col reduce the index
                (This is for the case the the point is on the boundary and in this
                case the relative position is 1 which will cause the indexes to
                be the number of rows/cols - out of the matrix indexes)

            Args:
                point (QPointF) : The point to resolve

            Returns:
                int : The network row that the point is in
                int : The network column that the point is in  

        """
        partialX = (point.x() - self.charPath.boundingRect().left()) / self.charPath.boundingRect().width()
        partialY = (point.y() - self.charPath.boundingRect().top()) / self.charPath.boundingRect().height()
        col_idx = int(partialX * self.netCols)
        row_idx = int(partialY * self.netRows)
        if row_idx == self.netRows:
            row_idx -= 1
        if col_idx == self.netCols:
            col_idx -= 1
        return row_idx, col_idx

    def createNetPaths(self):
        """ Create the network pathes

            This method creates 2 network pathes items one for holding
            the occupied rectangles and one to hold the unoccupied rectangles
        """
        # Generate 2 QPainterPath
        occupiedPath = QPainterPath()
        unoccupiedPath = QPainterPath()

        # For each entry in occupied matrix :
        # Add a rectangle to the appropriate path according the entry value
        for row_idx in range(self.netRows):
            for col_idx in range(self.netCols):
                if self.occupied[row_idx][col_idx]:
                    occupiedPath.addRect(self.netRect(row_idx, col_idx))
                else:
                    unoccupiedPath.addRect(self.netRect(row_idx, col_idx))

        # Create the QGraphicsPathItems that will hold the path
        self.createNetPath(self.occupyColor, occupiedPath, True)
        self.createNetPath(self.unOccupyColor, unoccupiedPath, False)

    def createNetPath(self, brushColor: str, painterPath: QPainterPath, isOccupyPathItem: bool):
        """ Create a QGraphicsPathItem for a network path
            
            Args:
                brushColor (str)            : The color for filling the rectangles
                painterPath (QPainterPath)  : The path to be inserted to the item
                isOccupyPathItem (bool)     : Whether the path is occupied or unoccupied path
        """
        # Generate the path item if not created
        if isOccupyPathItem:
            if self.occupiedPathItem is None:
                self.occupiedPathItem = QGraphicsPathItem(self)
            pathItem = self.occupiedPathItem
        else:
            if self.unoccupiedPathItem is None:
                self.unoccupiedPathItem = QGraphicsPathItem(self)
            pathItem = self.unoccupiedPathItem
        if pathItem is None:
            pathItem = QGraphicsPathItem(self)

        # Set the item parameters
        pathItem.setPath(painterPath)
        pathItem.setPen(QPen(QColor(self.netColor), self.netThickness, style=Qt.SolidLine))
        pathItem.setBrush(QBrush(QColor(brushColor)))
        pathItem.setZValue(0)

    def save(self, stream: QDataStream, filename: str):
        """ Save the item to QDataStream
            
            Args:
                stream  (QDataStream)   : The data stream to write the item to
                filename (str)          : The filename (for documenting purposes)
        """

        # The item position
        stream << self.pos()

        # The dimensions
        stream << self.rect()

        # The presentation parameters
        stream.writeQString(self.netColor)
        stream.writeQString(self.occupyColor)
        stream.writeQString(self.unOccupyColor)
        stream.writeQString(self.shapeColor)
        stream.writeInt16(self.shapeLineThickness)
        stream.writeInt16(self.netRows)
        stream.writeInt16(self.netRows)

        # The items paths
        stream << self.charPath.path()
        self.dirty = False
        self.filename = filename

    def load(self, stream, filename):
        """ Loads the item from QDataStream
            
            Args:
                stream  (QDataStream)   : The data stream to read the item from
                filename (str)          : The filename (for documenting purposes)
        """

        # read the pos
        pos = QPointF()
        stream >> pos
        self.setPos(pos)

        # read the dimensions
        rect = QRectF()
        stream >> rect
        self.setRect(rect)

        # The presentation parameters
        self.netColor = stream.readQString()
        self.occupyColor = stream.readQString()
        self.unOccupyColor = stream.readQString()
        self.shapeColor = stream.readQString()
        self.shapeLineThickness = stream.readInt16()
        self.netRows = stream.readInt16()
        self.netRows = stream.readInt16()

        # read the paths
        self.initCharPath()
        path = self.charPath.path()
        stream >> path
        self.charPath.setPath(path)

        # Fit the item to the boundaries and position given by the item's parent
        self.fitToBoundaries()

        # The presentation of the item is in setted mode so we activate the set method
        self.wasSetted = False
        self.set()

        self.dirty = False
        self.filename = filename

    def fitToBoundaries(self):
        """ Fit the item to the boundaries and position given by it's parent

            This method was made to support the change of the character
            boundaries and that the char can be presented in different
            boundaries and position
        """
        self.setPos(self.posInParent)
        self.dx = self.boundaries.width() / self.rect().width()
        self.dy = self.boundaries.height() / self.rect().height()
        transform = self.transform()
        transform.scale(self.dx, self.dy)
        self.setTransform(transform)
示例#24
0
class ReacItem(KineticsDisplayItem):
    defaultWidth = 30
    defaultHeight = 30
    defaultPenWidth = 2
    name = constants.ITEM

    def __init__(self, *args, **kwargs):
        KineticsDisplayItem.__init__(self, *args, **kwargs)
        points = [
            QtCore.QPointF(ReacItem.defaultWidth / 4, 0),
            QtCore.QPointF(0, ReacItem.defaultHeight / 4),
            QtCore.QPointF(ReacItem.defaultWidth, ReacItem.defaultHeight / 4),
            QtCore.QPointF(3 * ReacItem.defaultWidth / 4,
                           ReacItem.defaultHeight / 2)
        ]
        path = QtGui.QPainterPath()
        path.moveTo(points[0])
        for p in points[1:]:
            path.lineTo(p)
            path.moveTo(p)
        self.gobj = QGraphicsPathItem(path, self)
        self.gobj.setPen(
            QtGui.QPen(QtCore.Qt.black, 2, Qt.Qt.SolidLine, Qt.Qt.RoundCap,
                       Qt.Qt.RoundJoin))
        self.gobj.mobj = self.mobj
        self._Kf = self.gobj.mobj.Kf
        self._Kb = self.gobj.mobj.Kb
        doc = "Kf\t: " + str(self._Kf) + "\nKb\t: " + str(self._Kb)
        self.gobj.setToolTip(doc)

    def updateValue(self, gobj):
        self._gobj = gobj
        if (isinstance(self._gobj, moose.ReacBase)):
            self._Kf = self._gobj.Kf
            self._Kb = self._gobj.Kb
            doc = "Kf\t: " + str(self._Kf) + "\nKb\t: " + str(self._Kb)
            self.gobj.setToolTip(doc)

    def refresh(self, scale):
        defaultWidth = ReacItem.defaultWidth * scale
        defaultHeight = ReacItem.defaultHeight * scale
        points = [
            QtCore.QPointF(defaultWidth / 4, 0),
            QtCore.QPointF(0, defaultHeight / 4),
            QtCore.QPointF(defaultWidth, defaultHeight / 4),
            QtCore.QPointF(3 * defaultWidth / 4, defaultHeight / 2)
        ]
        path = QtGui.QPainterPath()
        path.moveTo(points[0])
        for p in points[1:]:
            path.lineTo(p)
            path.moveTo(p)

        self.gobj.setPath(path)
        ReacPen = self.gobj.pen()
        defaultpenwidth = ReacItem.defaultPenWidth
        reacWidth = defaultpenwidth * scale
        ReacPen.setWidth(reacWidth)
        self.gobj.setPen(ReacPen)

    def setDisplayProperties(self, x, y, textcolor, bgcolor):
        """Set the display properties of this item."""
        self.setGeometry(x, y,
                         self.gobj.boundingRect().width(),
                         self.gobj.boundingRect().height())
示例#25
0
class PreXoverItem(QGraphicsRectItem):
    """A PreXoverItem exists between a single 'from' VirtualHelixItem index
    and zero or more 'to' VirtualHelixItem Indices

    Attributes:
        adapter (TYPE): Description
        idx (int): the base index within the virtual helix
        is_fwd (TYPE): Description
        prexoveritem_manager (TYPE): Description
        to_vh_id_num (TYPE): Description
    """

    def __init__(self, from_virtual_helix_item, is_fwd, from_index, nearby_idxs,
                 to_vh_id_num, prexoveritem_manager):
        """Summary

        Args:
            from_virtual_helix_item (cadnano.views.pathview.virtualhelixitem.VirtualHelixItem): Description
            is_fwd (TYPE): Description
            from_index (TYPE): Description
            to_vh_id_num (TYPE): Description
            prexoveritem_manager (TYPE): Description
        """
        super(QGraphicsRectItem, self).__init__(BASE_RECT, from_virtual_helix_item)
        self.adapter = PropertyWrapperObject(self)
        self._tick_marks = QGraphicsPathItem(self)
        self._tick_marks.setAcceptHoverEvents(True)
        self._bond_item = QGraphicsPathItem(self)
        self._bond_item.hide()
        self._label = PreXoverLabel(is_fwd, self)
        self._path = QGraphicsPathItem()
        self.setZValue(styles.ZPREXOVERITEM)
        self.setPen(getNoPen())
        self.resetItem(from_virtual_helix_item, is_fwd, from_index, nearby_idxs, to_vh_id_num, prexoveritem_manager)
    # end def

    def shutdown(self):
        """Summary

        Returns:
            TYPE: Description
        """
        self.setBrush(getNoBrush())
        self.to_vh_id_num = None
        self.adapter.resetAnimations()
        self.setAcceptHoverEvents(False)
        self.hide()
    # end def

    def resetItem(self, from_virtual_helix_item, is_fwd, from_index, nearby_idxs,
                  to_vh_id_num, prexoveritem_manager):
        """Update this pooled PreXoverItem with current info.
        Called by PreXoverManager.

        Args:
            from_virtual_helix_item (cadnano.views.pathview.virtualhelixitem.VirtualHelixItem): the associated vh_item
            is_fwd (bool): True if associated with fwd strand, False if rev strand
            from_index (int): idx of associated vh
            to_vh_id_num (int): id_num of the other vh
            prexoveritem_manager (cadnano.views.pathview.prexoermanager.PreXoverManager): the manager
        """
        self.setParentItem(from_virtual_helix_item)
        self.resetTransform()
        self._id_num = from_virtual_helix_item.idNum()
        self._model_vh = from_virtual_helix_item.cnModel()
        self.idx = from_index
        self.is_low = False
        self.is_high = False
        self.nearby_idxs = nearby_idxs
        self.is_fwd = is_fwd
        self.color = None
        self.is3p = None
        self.enter_pos = None
        self.exit_pos = None
        self.to_vh_id_num = to_vh_id_num
        self.prexoveritem_manager = prexoveritem_manager

        # todo: check here if xover present and disable
        result = self.setPathAppearance(from_virtual_helix_item)

        if result:
            self.setBrush(getNoBrush())
            if is_fwd:
                self.setPos(from_index*BASE_WIDTH, -BASE_WIDTH)
            else:
                self.setPos(from_index*BASE_WIDTH, 2*BASE_WIDTH)
            self.show()
            # label
            self._label_txt = lbt = None if to_vh_id_num is None else str(to_vh_id_num)
            self.setLabel(text=lbt)
            self._label.resetItem(is_fwd, self.color)

            # bond line
            bonditem = self._bond_item
            bonditem.setPen(getPenObj(self.color, styles.PREXOVER_STROKE_WIDTH))
            bonditem.hide()
    # end def

    def setPathAppearance(self, from_virtual_helix_item):
            """
            Sets the PainterPath according to the index (low = Left, high = Right)
            and strand position (top = Up, bottom = Down).
            """
            part = self._model_vh.part()
            idx = self.idx
            is_fwd = self.is_fwd
            id_num = self._id_num
            strand_type = StrandType.FWD if is_fwd else StrandType.REV

            # relative position info
            bpr = from_virtual_helix_item.getProperty('bases_per_repeat')
            self.is_low = is_low = idx+1 in self.nearby_idxs or (idx+1) % bpr in self.nearby_idxs
            self.is_high = is_high = idx-1 in self.nearby_idxs or (idx-1) % bpr in self.nearby_idxs

            # check strand for xover and color
            if part.hasStrandAtIdx(id_num, idx)[strand_type]:
                strand = part.getStrand(self.is_fwd, id_num, idx)
                if strand.hasXoverAt(idx):
                    return False
                self.color = strand.getColor() if strand is not None else EMPTY_COL
            else:
                self.color = EMPTY_COL

            if is_low and is_high:
                print("dual xover")
                path = (_FWD_DUAL_PATH, _REV_DUAL_PATH)[strand_type]
            elif is_low:
                path = (_FWD_LO_PATH, _REV_LO_PATH)[strand_type]
                self.is3p = True if is_fwd else False
                self.enter_pos = _FWD_LO_PTS[0][-1] if is_fwd else _REV_LO_PTS[0][-1]
                self.exit_pos = _FWD_LO_PTS[0][-1] if is_fwd else _REV_LO_PTS[0][-1]
            elif is_high:
                path = (_FWD_HI_PATH, _REV_HI_PATH)[strand_type]
                self.is3p = False if is_fwd else True
                self.enter_pos = _FWD_HI_PTS[0][-1] if is_fwd else _REV_HI_PTS[0][-1]
                self.exit_pos = _FWD_HI_PTS[0][-1] if is_fwd else _REV_HI_PTS[0][-1]
            else:
                # print("unpaired PreXoverItem at {}[{}]".format(self._id_num, self.idx), self.nearby_idxs)
                return False
            self._tick_marks.setPen(getPenObj(self.color, styles.PREXOVER_STROKE_WIDTH,
                                              capstyle=Qt.FlatCap, joinstyle=Qt.RoundJoin))
            self._tick_marks.setPath(path)
            self._tick_marks.show()
            return True
    # end def

    ### ACCESSORS ###
    def color(self):
        """The PreXoverItem's color, derived from the associated strand's oligo.

        Returns:
            str: color in hex code
        """
        return self.color

    def getInfo(self):
        """
        Returns:
            Tuple: (from_id_num, is_fwd, from_index, to_vh_id_num)
        """
        return (self._id_num, self.is_fwd, self.idx, self.to_vh_id_num)

    def remove(self):
        """Removes animation adapter, label, bond_item, and this item from scene.
        """
        scene = self.scene()
        self.adapter.destroy()
        if scene:
            scene.removeItem(self._label)
            self._label = None
            scene.removeItem(self._bond_item)
            self._bond_item = None
            self.adapter.resetAnimations()
            self.adapter = None
            scene.removeItem(self)
    # end defS

    ### EVENT HANDLERS ###
    def hoverEnterEvent(self, event):
        """Only if enableActive(True) is called hover and key events disabled by default

        Args:
            event (QGraphicsSceneHoverEvent): the hover event
        """
        self.setFocus(Qt.MouseFocusReason)
        self.prexoveritem_manager.updateModelActiveBaseInfo(self.getInfo())
        self.setActiveHovered(True)
        status_string = "%d[%d]" % (self._id_num, self.idx)
        self.parentItem().window().statusBar().showMessage(status_string)
    # end def

    def hoverLeaveEvent(self, event):
        """Summary

        Args:
            event (QGraphicsSceneHoverEvent): the hover event

        Returns:
            TYPE: Description
        """
        self.prexoveritem_manager.updateModelActiveBaseInfo(None)
        self.setActiveHovered(False)
        self.clearFocus()
        self.parentItem().window().statusBar().showMessage("")
    # end def

    def mousePressEvent(self, event):
        part = self._model_vh.part()
        is_fwd = self.is_fwd

        if self.is3p:
            strand5p = part.getStrand(is_fwd, self._id_num, self.idx)
            strand3p = part.getStrand(not is_fwd, self.to_vh_id_num, self.idx)
        else:
            strand5p = part.getStrand(not is_fwd, self.to_vh_id_num, self.idx)
            strand3p = part.getStrand(is_fwd, self._id_num, self.idx)

        if strand5p is None or strand3p is None:
            return

        part.createXover(strand5p, self.idx, strand3p, self.idx)

        nkey = (self.to_vh_id_num, not is_fwd, self.idx)
        npxi = self.prexoveritem_manager.neighbor_prexover_items.get(nkey, None)
        if npxi:
            npxi.shutdown()
        self.shutdown()

        part.setActiveVirtualHelix(self._id_num, is_fwd, self.idx)
        # self.prexoveritem_manager.handlePreXoverClick(self)

    def keyPressEvent(self, event):
        """Summary

        Args:
            event (TYPE): Description

        Returns:
            TYPE: Description
        """
        self.prexoveritem_manager.handlePreXoverKeyPress(event.key())
    # end def

    ### PUBLIC SUPPORT METHODS ###
    def setLabel(self, text=None, outline=False):
        """Summary

        Args:
            text (None, optional): Description
            outline (bool, optional): Description

        Returns:
            TYPE: Description
        """
        if text:
            self._label.setTextAndStyle(text=text, outline=outline)
            self._label.show()
        else:
            self._label.hide()
    # end def

    def animate(self, item, property_name, duration, start_value, end_value):
        """Summary

        Args:
            item (TYPE): Description
            property_name (TYPE): Description
            duration (TYPE): Description
            start_value (TYPE): Description
            end_value (TYPE): Description

        Returns:
            TYPE: Description
        """
        b_name = property_name.encode('ascii')
        anim = item.adapter.getRef(property_name)
        if anim is None:
            anim = QPropertyAnimation(item.adapter, b_name)
            item.adapter.saveRef(property_name, anim)
        anim.setDuration(duration)
        anim.setStartValue(start_value)
        anim.setEndValue(end_value)
        anim.start()
    # end def

    def setActiveHovered(self, is_active):
        """Rotate phosphate Triangle if `self.to_vh_id_num` is not `None`

        Args:
            is_active (bool): whether or not the PreXoverItem is parented to the
                active VirtualHelixItem
        """
        pass
    # end def

    def enableActive(self, is_active, to_vh_id_num=None):
        """Call on PreXoverItems created on the active VirtualHelixItem

        Args:
            is_active (TYPE): Description
            to_vh_id_num (None, optional): Description
        """
        if is_active:
            self.to_vh_id_num = to_vh_id_num
            self.setAcceptHoverEvents(True)
            if to_vh_id_num is None:
                self.setLabel(text=None)
            else:
                self.setLabel(text=str(to_vh_id_num))
        else:
            self.setBrush(getNoBrush())
            self.setAcceptHoverEvents(False)

    def activateNeighbor(self, active_prexoveritem, shortcut=None):
        """
        Draws a quad line starting from the item5p to the item3p.
        To be called with whatever the active_prexoveritem is for the parts `active_base`.

        Args:
            active_prexoveritem (TYPE): Description
            shortcut (None, optional): Description
        """
        if self.is3p and not active_prexoveritem.is3p:
            item5p = active_prexoveritem
            item3p = self
        elif not self.is3p and active_prexoveritem.is3p:
            item5p = self
            item3p = active_prexoveritem
        else:
            return

        same_parity = self.is_fwd == active_prexoveritem.is_fwd

        p1 = item5p._tick_marks.scenePos() + item5p.exit_pos
        p2 = item3p._tick_marks.scenePos() + item3p.exit_pos

        c1 = QPointF()

        # case 1: same parity
        if same_parity:
            dy = abs(p2.y() - p1.y())
            c1.setX(p1.x() + _X_SCALE * dy)
            c1.setY(0.5 * (p1.y() + p2.y()))
        # case 2: different parity
        else:
            if item3p.is_fwd:
                c1.setX(p1.x() - _X_SCALE * abs(p2.y() - p1.y()))
            else:
                c1.setX(p1.x() + _X_SCALE * abs(p2.y() - p1.y()))
            c1.setY(0.5 * (p1.y() + p2.y()))

        pp = QPainterPath()
        pp.moveTo(self._tick_marks.mapFromScene(p1))
        pp.quadTo(self._tick_marks.mapFromScene(c1),
                  self._tick_marks.mapFromScene(p2))
        # pp.cubicTo(c1, c2, self._tick_marks.mapFromScene(p2))
        self._bond_item.setPath(pp)
        self._bond_item.show()
    # end def

    def deactivateNeighbor(self):
        """Summary

        Returns:
            TYPE: Description
        """
        if self.isVisible():
            self._bond_item.hide()
            self.setLabel(text=self._label_txt)
示例#26
0
class ForcedXoverNode3(QGraphicsRectItem):
    """
    This is a QGraphicsRectItem to allow actions and also a
    QGraphicsSimpleTextItem to allow a label to be drawn

    Attributes:
        is_forward (TYPE): Description
    """

    def __init__(self, virtual_helix_item, xover_item, strand3p, idx):
        """Summary

        Args:
            virtual_helix_item (cadnano.views.pathview.virtualhelixitem.VirtualHelixItem): from vhi
            xover_item (TYPE): Description
            strand3p (Strand): reference to the 3' strand
            idx (int): the base index within the virtual helix
        """
        super(ForcedXoverNode3, self).__init__(virtual_helix_item)
        self._vhi = virtual_helix_item
        self._xover_item = xover_item
        self._idx = idx

        self.is_forward = strand3p.strandSet().isForward()
        self._is_on_top = self.is_forward

        self._partner_virtual_helix = virtual_helix_item

        self._blank_thing = QGraphicsRectItem(_blankRect, self)
        self._blank_thing.setBrush(QBrush(Qt.white))
        self._path_thing = QGraphicsPathItem(self)
        self.configurePath()

        self._label = None
        self.setPen(_NO_PEN)
        self.setBrush(_NO_BRUSH)
        self.setRect(_rect)

        self.setZValue(styles.ZENDPOINTITEM + 1)
    # end def

    def updateForFloatFromVHI(self, virtual_helix_item, is_forward, idx_x, idx_y):
        """
        Args:
            virtual_helix_item (cadnano.views.pathview.virtualhelixitem.VirtualHelixItem): Description
            is_forward (TYPE): Description
            idx_x (TYPE): Description
            idx_y (TYPE): Description
        """
        self._vhi = virtual_helix_item
        self.setParentItem(virtual_helix_item)
        self._idx = idx_x
        self._is_on_top = self.is_forward = True if is_forward else False
        self.updatePositionAndAppearance(is_from_strand=False)
    # end def

    def updateForFloatFromStrand(self, virtual_helix_item, strand3p, idx):
        """
        Args:
            virtual_helix_item (cadnano.views.pathview.virtualhelixitem.VirtualHelixItem): Description
            strand3p (Strand): reference to the 3' strand
            idx (int): the base index within the virtual helix
        """
        self._vhi = virtual_helix_item
        self._strand = strand3p
        self.setParentItem(virtual_helix_item)
        self._idx = idx
        self._is_on_top = self.is_forward = strand3p.strandSet().isForward()
        self.updatePositionAndAppearance()
    # end def

    def configurePath(self):
        """Summary

        Returns:
            TYPE: Description
        """
        self._path_thing.setBrush(getBrushObj(_PENCIL_COLOR))
        path = PPR3 if self.is_forward else PPL3
        offset = -_BASE_WIDTH if self.is_forward else _BASE_WIDTH
        self._path_thing.setPath(path)
        self._path_thing.setPos(offset, 0)

        offset = -_BASE_WIDTH if self.is_forward else 0
        self._blank_thing.setPos(offset, 0)

        self._blank_thing.show()
        self._path_thing.show()
    # end def

    def refreshXover(self):
        """Summary

        Returns:
            TYPE: Description
        """
        self._xover_item.refreshXover()
    # end def

    def setPartnerVirtualHelix(self, virtual_helix_item):
        """Summary

        Args:
            virtual_helix_item (cadnano.views.pathview.virtualhelixitem.VirtualHelixItem): Description

        Returns:
            TYPE: Description
        """
        self._partner_virtual_helix = virtual_helix_item
    # end def

    def idx(self):
        """Summary

        Returns:
            TYPE: Description
        """
        return self._idx
    # end def

    def virtualHelixItem(self):
        """Summary

        Returns:
            TYPE: Description
        """
        return self._vhi
    # end def

    def point(self):
        """Summary

        Returns:
            TYPE: Description
        """
        return self._vhi.upperLeftCornerOfBaseType(self._idx, self.is_forward)
    # end def

    def floatPoint(self):
        """Summary

        Returns:
            TYPE: Description
        """
        pt = self.pos()
        return pt.x(), pt.y()
    # end def

    def isForward(self):
        """Summary

        Returns:
            TYPE: Description
        """
        return self.is_forward
    # end def

    def updatePositionAndAppearance(self, is_from_strand=True):
        """
        Sets position by asking the VirtualHelixItem
        Sets appearance by choosing among pre-defined painterpaths (from
        normalstrandgraphicsitem) depending on drawing direction.

        Args:
            is_from_strand (bool, optional): Description
        """
        self.setPos(*self.point())
        n5 = self._xover_item._node5
        if is_from_strand:
            from_strand, from_idx = (n5._strand, n5._idx) if n5 != self else (None, None)
            if self._strand.canInstallXoverAt(self._idx, from_strand, from_idx):
                self.configurePath()
                # We can only expose a 5' end. But on which side?
                is_left = True if self.is_forward else False
                self._updateLabel(is_left)
            else:
                self.hideItems()
        else:
            self.hideItems()
    # end def

    def remove(self):
        """Clean up this joint
        """
        scene = self.scene()
        scene.removeItem(self._label)
        self._label = None
        scene.removeItem(self._path_thing)
        self._path_thing = None
        scene.removeItem(self._blank_thing)
        self._blank_thing = None
        scene.removeItem(self)
    # end def

    def _updateLabel(self, is_left):
        """Called by updatePositionAndAppearance during init.
        Updates drawing and position of the label.

        Args:
            is_left (TYPE): Description
        """
        lbl = self._label
        if self._idx is not None:
            bw = _BASE_WIDTH
            num = self._partner_virtual_helix.idNum()
            tBR = _FM.tightBoundingRect(str(num))
            half_label_h = tBR.height()/2.0
            half_label_w = tBR.width()/2.0
            # determine x and y positions
            label_x = bw/2.0 - half_label_w
            if self._is_on_top:
                label_y = -0.25*half_label_h - 0.5 - 0.5*bw
            else:
                label_y = 2*half_label_h + 0.5 + 0.5*bw
            # adjust x for left vs right
            label_x_offset = 0.25*bw if is_left else -0.25*bw
            label_x += label_x_offset
            # adjust x for numeral 1
            if num == 1:
                label_x -= half_label_w/2.0
            # create text item
            if lbl is None:
                lbl = QGraphicsSimpleTextItem(str(num), self)
            lbl.setPos(label_x, label_y)
            lbl.setBrush(_ENAB_BRUSH)
            lbl.setFont(_TO_HELIX_NUM_FONT)
            self._label = lbl

            lbl.setText(str(self._partner_virtual_helix.idNum()))
            lbl.show()
        # end if
    # end def

    def hideItems(self):
        """Summary

        Returns:
            TYPE: Description
        """
        if self._label:
            self._label.hide()
        if self._blank_thing:
            self._path_thing.hide()
        if self._blank_thing:
            self._blank_thing.hide()
示例#27
0
class PenArrowItem(QGraphicsItemGroup):
    def __init__(self, path, parent=None):
        super(PenArrowItem, self).__init__()
        self.saveable = True
        self.pi = QGraphicsPathItem()
        self.path = path
        self.animator = [parent]
        self.animateFlag = False

        # set arrowhead initial
        e0 = self.path.elementAt(0)
        e1 = self.path.elementAt(1)
        pti = QPointF(e1.x, e1.y)
        ptf = QPointF(e0.x, e0.y)
        delta = ptf - pti
        el = sqrt(delta.x()**2 + delta.y()**2)
        ndelta = delta / el
        northog = QPointF(-ndelta.y(), ndelta.x())
        arBase = ptf - 16 * ndelta
        arTip = ptf + 8 * ndelta
        arLeft = arBase - 10 * northog - 4 * ndelta
        arRight = arBase + 10 * northog - 4 * ndelta
        self.ari = QPainterPath()
        self.ari.moveTo(ptf)
        self.ari.lineTo(arLeft)
        self.ari.lineTo(arBase)
        self.ari.lineTo(arRight)
        self.ari.lineTo(ptf)
        self.endi = QGraphicsPathItem()
        self.endi.setPath(self.ari)
        # set arrowhead final
        e2 = self.path.elementAt(self.path.elementCount() - 2)
        e3 = self.path.elementAt(self.path.elementCount() - 1)
        pti = QPointF(e2.x, e2.y)
        ptf = QPointF(e3.x, e3.y)
        delta = ptf - pti
        el = sqrt(delta.x()**2 + delta.y()**2)
        ndelta = delta / el
        northog = QPointF(-ndelta.y(), ndelta.x())
        arBase = ptf - 16 * ndelta
        arTip = ptf + 8 * ndelta
        arLeft = arBase - 10 * northog - 4 * ndelta
        arRight = arBase + 10 * northog - 4 * ndelta
        self.arf = QPainterPath()
        self.arf.moveTo(ptf)
        self.arf.lineTo(arLeft)
        self.arf.lineTo(arBase)
        self.arf.lineTo(arRight)
        self.arf.lineTo(ptf)
        self.endf = QGraphicsPathItem()
        self.endf.setPath(self.arf)
        # put everything together
        self.pi.setPath(self.path)
        self.pi.setPen(QPen(Qt.red, 2))
        self.endi.setPen(QPen(Qt.red, 2))
        self.endi.setBrush(QBrush(Qt.red))
        self.endf.setPen(QPen(Qt.red, 2))
        self.endf.setBrush(QBrush(Qt.red))
        self.setFlag(QGraphicsItem.ItemIsMovable)
        self.setFlag(QGraphicsItem.ItemSendsGeometryChanges)
        self.addToGroup(self.pi)
        self.addToGroup(self.endi)
        self.addToGroup(self.endf)

    def itemChange(self, change, value):
        if change == QGraphicsItem.ItemPositionChange and self.scene():
            command = CommandMoveItem(self, value)
            self.scene().undoStack.push(command)
        return QGraphicsItemGroup.itemChange(self, change, value)

    def pickle(self):
        pth = []
        for k in range(self.path.elementCount()):
            # e should be either a moveTo or a lineTo
            e = self.path.elementAt(k)
            if e.isMoveTo():
                pth.append(["m", e.x + self.x(), e.y + self.y()])
            else:
                if e.isLineTo():
                    pth.append(["l", e.x + self.x(), e.y + self.y()])
                else:
                    log.error("Problem pickling penarrowitem path {}".format(
                        self.path))
        return ["PenArrow", pth]

    def paint(self, painter, option, widget):
        if not self.collidesWithItem(self.scene().underImage,
                                     mode=Qt.ContainsItemShape):
            # paint a bounding rectangle out-of-bounds warning
            painter.setPen(QPen(QColor(255, 165, 0), 8))
            painter.setBrush(QBrush(QColor(255, 165, 0, 128)))
            painter.drawRoundedRect(option.rect, 10, 10)
        # paint the normal item with the default 'paint' method
        super(PenArrowItem, self).paint(painter, option, widget)
示例#28
0
class ForcedXoverNode3(QGraphicsRectItem):
    """
    This is a QGraphicsRectItem to allow actions and also a
    QGraphicsSimpleTextItem to allow a label to be drawn

    Attributes:
        is_forward (TYPE): Description
    """
    def __init__(self, virtual_helix_item, xover_item, strand3p, idx):
        """Summary

        Args:
            virtual_helix_item (cadnano.gui.views.pathview.virtualhelixitem.VirtualHelixItem): from vhi
            xover_item (TYPE): Description
            strand3p (Strand): reference to the 3' strand
            idx (int): the base index within the virtual helix
        """
        super(ForcedXoverNode3, self).__init__(virtual_helix_item)
        self._vhi = virtual_helix_item
        self._xover_item = xover_item
        self._idx = idx

        self.is_forward = strand3p.strandSet().isForward()
        self._is_on_top = self.is_forward

        self._partner_virtual_helix = virtual_helix_item

        self._blank_thing = QGraphicsRectItem(_blankRect, self)
        self._blank_thing.setBrush(QBrush(Qt.white))
        self._path_thing = QGraphicsPathItem(self)
        self.configurePath()

        self._label = None
        self.setPen(_NO_PEN)
        self.setBrush(_NO_BRUSH)
        self.setRect(_rect)

        self.setZValue(styles.ZENDPOINTITEM + 1)
    # end def

    def updateForFloatFromVHI(self, virtual_helix_item, is_forward, idx_x, idx_y):
        """
        Args:
            virtual_helix_item (cadnano.gui.views.pathview.virtualhelixitem.VirtualHelixItem): Description
            is_forward (TYPE): Description
            idx_x (TYPE): Description
            idx_y (TYPE): Description
        """
        self._vhi = virtual_helix_item
        self.setParentItem(virtual_helix_item)
        self._idx = idx_x
        self._is_on_top = self.is_forward = True if is_forward else False
        self.updatePositionAndAppearance(is_from_strand=False)
    # end def

    def updateForFloatFromStrand(self, virtual_helix_item, strand3p, idx):
        """
        Args:
            virtual_helix_item (cadnano.gui.views.pathview.virtualhelixitem.VirtualHelixItem): Description
            strand3p (Strand): reference to the 3' strand
            idx (int): the base index within the virtual helix
        """
        self._vhi = virtual_helix_item
        self._strand = strand3p
        self.setParentItem(virtual_helix_item)
        self._idx = idx
        self._is_on_top = self.is_forward = strand3p.strandSet().isForward()
        self.updatePositionAndAppearance()
    # end def

    def configurePath(self):
        """Summary

        Returns:
            TYPE: Description
        """
        self._path_thing.setBrush(getBrushObj(_PENCIL_COLOR))
        path = PPR3 if self.is_forward else PPL3
        offset = -_BASE_WIDTH if self.is_forward else _BASE_WIDTH
        self._path_thing.setPath(path)
        self._path_thing.setPos(offset, 0)

        offset = -_BASE_WIDTH if self.is_forward else 0
        self._blank_thing.setPos(offset, 0)

        self._blank_thing.show()
        self._path_thing.show()
    # end def

    def refreshXover(self):
        """Summary

        Returns:
            TYPE: Description
        """
        self._xover_item.refreshXover()
    # end def

    def setPartnerVirtualHelix(self, virtual_helix_item):
        """Summary

        Args:
            virtual_helix_item (cadnano.gui.views.pathview.virtualhelixitem.VirtualHelixItem): Description

        Returns:
            TYPE: Description
        """
        self._partner_virtual_helix = virtual_helix_item
    # end def

    def idx(self):
        """Summary

        Returns:
            TYPE: Description
        """
        return self._idx
    # end def

    def virtualHelixItem(self):
        """Summary

        Returns:
            TYPE: Description
        """
        return self._vhi
    # end def

    def point(self):
        """Summary

        Returns:
            TYPE: Description
        """
        return self._vhi.upperLeftCornerOfBaseType(self._idx, self.is_forward)
    # end def

    def floatPoint(self):
        """Summary

        Returns:
            TYPE: Description
        """
        pt = self.pos()
        return pt.x(), pt.y()
    # end def

    def isForward(self):
        """Summary

        Returns:
            TYPE: Description
        """
        return self.is_forward
    # end def

    def updatePositionAndAppearance(self, is_from_strand=True):
        """
        Sets position by asking the VirtualHelixItem
        Sets appearance by choosing among pre-defined painterpaths (from
        normalstrandgraphicsitem) depending on drawing direction.

        Args:
            is_from_strand (bool, optional): Description
        """
        self.setPos(*self.point())
        n5 = self._xover_item._node5
        if is_from_strand:
            from_strand, from_idx = (n5._strand, n5._idx) if n5 != self else (None, None)
            if self._strand.canInstallXoverAt(self._idx, from_strand, from_idx):
                self.configurePath()
                # We can only expose a 5' end. But on which side?
                is_left = True if self.is_forward else False
                self._updateLabel(is_left)
            else:
                self.hideItems()
        else:
            self.hideItems()
    # end def

    def remove(self):
        """Clean up this joint
        """
        scene = self.scene()
        scene.removeItem(self._label)
        self._label = None
        scene.removeItem(self._path_thing)
        self._path_thing = None
        scene.removeItem(self._blank_thing)
        self._blank_thing = None
        scene.removeItem(self)
    # end def

    def _updateLabel(self, is_left):
        """Called by updatePositionAndAppearance during init.
        Updates drawing and position of the label.

        Args:
            is_left (TYPE): Description
        """
        lbl = self._label
        if self._idx is not None:
            bw = _BASE_WIDTH
            num = self._partner_virtual_helix.idNum()
            tBR = _FM.tightBoundingRect(str(num))
            half_label_h = tBR.height()/2.0
            half_label_w = tBR.width()/2.0
            # determine x and y positions
            label_x = bw/2.0 - half_label_w
            if self._is_on_top:
                label_y = -0.25*half_label_h - 0.5 - 0.5*bw
            else:
                label_y = 2*half_label_h + 0.5 + 0.5*bw
            # adjust x for left vs right
            label_x_offset = 0.25*bw if is_left else -0.25*bw
            label_x += label_x_offset
            # adjust x for numeral 1
            if num == 1:
                label_x -= half_label_w/2.0
            # create text item
            if lbl is None:
                lbl = QGraphicsSimpleTextItem(str(num), self)
            lbl.setPos(label_x, label_y)
            lbl.setBrush(_ENAB_BRUSH)
            lbl.setFont(_TO_HELIX_NUM_FONT)
            self._label = lbl

            lbl.setText(str(self._partner_virtual_helix.idNum()))
            lbl.show()
        # end if
    # end def

    def hideItems(self):
        """Summary

        Returns:
            TYPE: Description
        """
        if self._label:
            self._label.hide()
        if self._blank_thing:
            self._path_thing.hide()
        if self._blank_thing:
            self._blank_thing.hide()
示例#29
0
class PreXoverItem(QGraphicsRectItem):
    """A PreXoverItem exists between a single 'from' VirtualHelixItem index
    and zero or more 'to' VirtualHelixItem Indices

    Attributes:
        adapter (TYPE): Description
        idx (int): the base index within the virtual helix
        is_fwd (TYPE): Description
        prexoveritemgroup (TYPE): Description
        to_vh_id_num (TYPE): Description
    """
    def __init__(self, from_virtual_helix_item, is_fwd, from_index,
                 to_vh_id_num, prexoveritemgroup, color):
        """Summary

        Args:
            from_virtual_helix_item (cadnano.gui.views.pathview.virtualhelixitem.VirtualHelixItem): Description
            is_fwd (TYPE): Description
            from_index (TYPE): Description
            to_vh_id_num (TYPE): Description
            prexoveritemgroup (TYPE): Description
            color (TYPE): Description
        """
        super(QGraphicsRectItem, self).__init__(BASE_RECT, from_virtual_helix_item)
        self.adapter = PropertyWrapperObject(self)
        self._bond_item = QGraphicsPathItem(self)
        self._bond_item.hide()
        self._label = PreXoverLabel(is_fwd, color, self)
        self._phos_item = Triangle(FWDPHOS_PP, self)
        self.setPen(getNoPen())
        self.resetItem(from_virtual_helix_item, is_fwd, from_index,
                       to_vh_id_num, prexoveritemgroup, color)
    # end def

    def shutdown(self):
        """Summary

        Returns:
            TYPE: Description
        """
        self.setBrush(getBrushObj(self._color, alpha=0))
        self.to_vh_id_num = None
        self.adapter.resetAnimations()
        phos = self._phos_item
        phos.adapter.resetAnimations()
        phos.resetTransform()
        phos.setPos(0, 0)
        self.setAcceptHoverEvents(False)
        self.setFlag(KEYINPUT_ACTIVE_FLAG, False)
        self.hide()
    # end def

    def resetItem(self, from_virtual_helix_item, is_fwd, from_index,
                  to_vh_id_num, prexoveritemgroup, color):
        """Summary

        Args:
            from_virtual_helix_item (cadnano.gui.views.pathview.virtualhelixitem.VirtualHelixItem): Description
            is_fwd (TYPE): Description
            from_index (TYPE): Description
            to_vh_id_num (TYPE): Description
            prexoveritemgroup (TYPE): Description
            color (TYPE): Description

        Returns:
            TYPE: Description
        """
        self.setParentItem(from_virtual_helix_item)
        self.resetTransform()
        self._id_num = from_virtual_helix_item.idNum()
        self.idx = from_index
        self.is_fwd = is_fwd
        self.to_vh_id_num = to_vh_id_num
        self._color = color
        self.prexoveritemgroup = prexoveritemgroup
        self._bond_item.hide()
        self._label_txt = lbt = None if to_vh_id_num is None else str(to_vh_id_num)
        self.setLabel(text=lbt)
        self._label.resetItem(is_fwd, color)

        phos = self._phos_item
        bonditem = self._bond_item

        if is_fwd:
            phos.setPath(FWDPHOS_PP)
            phos.setTransformOriginPoint(0, phos.boundingRect().center().y())
            phos.setPos(0.5*BASE_WIDTH, BASE_WIDTH)
            phos.setPen(getNoPen())
            phos.setBrush(getBrushObj(color))
            bonditem.setPen(getPenObj(color, styles.PREXOVER_STROKE_WIDTH))
            self.setPos(from_index*BASE_WIDTH, -BASE_WIDTH)
        else:
            phos.setPath(REVPHOS_PP)
            phos.setTransformOriginPoint(0, phos.boundingRect().center().y())
            phos.setPos(0.5*BASE_WIDTH, 0)
            phos.setPen(getPenObj(color, 0.25))
            phos.setBrush(getNoBrush())
            bonditem.setPen(getPenObj(color, styles.PREXOVER_STROKE_WIDTH,
                                      penstyle=Qt.DotLine, capstyle=Qt.RoundCap))
            self.setPos(from_index*BASE_WIDTH, 2*BASE_WIDTH)

        if to_vh_id_num is not None:
            inactive_alpha = PROX_ALPHA
            self.setBrush(getBrushObj(color, alpha=inactive_alpha))
        else:
            self.setBrush(getBrushObj(color, alpha=0))
        self.show()
    # end def

    def getInfo(self):
        """
        Returns:
            Tuple: (from_id_num, is_fwd, from_index, to_vh_id_num)
        """
        return (self._id_num, self.is_fwd, self.idx, self.to_vh_id_num)

    ### ACCESSORS ###
    def color(self):
        """Summary

        Returns:
            TYPE: Description
        """
        return self._color

    def remove(self):
        """Summary

        Returns:
            TYPE: Description
        """
        scene = self.scene()
        self.adapter.destroy()
        if scene:
            scene.removeItem(self._label)
            self._label = None
            self._phos_item.adapter.resetAnimations()
            self._phos_item.adapter = None
            scene.removeItem(self._phos_item)
            self._phos_item = None
            scene.removeItem(self._bond_item)
            self._bond_item = None
            self.adapter.resetAnimations()
            self.adapter = None
            scene.removeItem(self)
    # end defS

    ### EVENT HANDLERS ###
    def hoverEnterEvent(self, event):
        """Only if enableActive(True) is called
        hover and key events disabled by default

        Args:
            event (TYPE): Description
        """
        self.setFocus(Qt.MouseFocusReason)
        self.prexoveritemgroup.updateModelActiveBaseInfo(self.getInfo())
        self.setActiveHovered(True)
        status_string = "%d[%d]" % (self._id_num, self.idx)
        self.parentItem().window().statusBar().showMessage(status_string)
    # end def

    def hoverLeaveEvent(self, event):
        """Summary

        Args:
            event (TYPE): Description

        Returns:
            TYPE: Description
        """
        self.prexoveritemgroup.updateModelActiveBaseInfo(None)
        self.setActiveHovered(False)
        self.clearFocus()
        self.parentItem().window().statusBar().showMessage("")
    # end def

    def keyPressEvent(self, event):
        """Summary

        Args:
            event (TYPE): Description

        Returns:
            TYPE: Description
        """
        self.prexoveritemgroup.handlePreXoverKeyPress(event.key())
    # end def

    ### PUBLIC SUPPORT METHODS ###
    def setLabel(self, text=None, outline=False):
        """Summary

        Args:
            text (None, optional): Description
            outline (bool, optional): Description

        Returns:
            TYPE: Description
        """
        if text:
            self._label.setTextAndStyle(text=text, outline=outline)
            self._label.show()
        else:
            self._label.hide()
    # end def

    def animate(self, item, property_name, duration, start_value, end_value):
        """Summary

        Args:
            item (TYPE): Description
            property_name (TYPE): Description
            duration (TYPE): Description
            start_value (TYPE): Description
            end_value (TYPE): Description

        Returns:
            TYPE: Description
        """
        b_name = property_name.encode('ascii')
        anim = item.adapter.getRef(property_name)
        if anim is None:
            anim = QPropertyAnimation(item.adapter, b_name)
            item.adapter.saveRef(property_name, anim)
        anim.setDuration(duration)
        anim.setStartValue(start_value)
        anim.setEndValue(end_value)
        anim.start()
    # end def

    def setActiveHovered(self, is_active):
        """Rotate phosphate Triangle if `self.to_vh_id_num` is not `None`

        Args:
            is_active (bool): whether or not the PreXoverItem is parented to the
                active VirtualHelixItem
        """
        if is_active:
            self.setBrush(getBrushObj(self._color, alpha=128))
            self.animate(self, 'brush_alpha', 1, 0, 128)  # overwrite running anim
            # if self.to_vh_id_num is not None:
            self.animate(self._phos_item, 'rotation', 500, 0, -90)
        else:
            inactive_alpha = 0 if self.to_vh_id_num is None else PROX_ALPHA
            self.setBrush(getBrushObj(self._color, alpha=inactive_alpha))
            self.animate(self, 'brush_alpha', 1000, 128, inactive_alpha)
            self.animate(self._phos_item, 'rotation', 500, -90, 0)
    # end def

    def enableActive(self, is_active, to_vh_id_num=None):
        """Call on PreXoverItems created on the active VirtualHelixItem

        Args:
            is_active (TYPE): Description
            to_vh_id_num (None, optional): Description
        """
        if is_active:
            self.to_vh_id_num = to_vh_id_num
            self.setAcceptHoverEvents(True)
            if to_vh_id_num is None:
                self.setLabel(text=None)
                self.setBrush(getBrushObj(self._color, alpha=0))
            else:
                self.setLabel(text=str(to_vh_id_num))
                inactive_alpha = PROX_ALPHA
                self.setBrush(getBrushObj(self._color, alpha=inactive_alpha))
                self.animate(self, 'brush_alpha', 1000, 128, inactive_alpha)
                self.setFlag(KEYINPUT_ACTIVE_FLAG, True)
        else:
            self.setBrush(getNoBrush())
            # self.setLabel(text=None)
            self.setAcceptHoverEvents(False)
            self.setFlag(KEYINPUT_ACTIVE_FLAG, False)

    def activateNeighbor(self, active_prexoveritem, shortcut=None):
        """To be called with whatever the active_prexoveritem
        is for the parts `active_base`

        Args:
            active_prexoveritem (TYPE): Description
            shortcut (None, optional): Description
        """
        p1 = self._phos_item.scenePos()
        p2 = active_prexoveritem._phos_item.scenePos()
        scale = 3
        delta1 = -BASE_WIDTH*scale if self.is_fwd else BASE_WIDTH*scale
        delta2 = BASE_WIDTH*scale if active_prexoveritem.is_fwd else -BASE_WIDTH*scale
        c1 = self.mapFromScene(QPointF(p1.x(), p1.y() + delta1))
        c2 = self.mapFromScene(QPointF(p2.x(), p2.y() - delta2))
        pp = QPainterPath()
        pp.moveTo(self._phos_item.pos())
        pp.cubicTo(c1, c2, self._bond_item.mapFromScene(p2))
        self._bond_item.setPath(pp)
        self._bond_item.show()

        alpha = 32
        idx, active_idx = self.idx, active_prexoveritem.idx
        if self.is_fwd != active_prexoveritem.is_fwd:
            if idx == active_idx:
                alpha = 255
        elif idx == active_idx + 1:
            alpha = 255
        elif idx == active_idx - 1:
            alpha = 255

        inactive_alpha = PROX_ALPHA if self.to_vh_id_num is not None else 0
        self.setBrush(getBrushObj(self._color, alpha=inactive_alpha))
        self.animate(self, 'brush_alpha', 500, inactive_alpha, alpha)
        self.animate(self._phos_item, 'rotation', 500, 0, -90)
        self.setLabel(text=shortcut, outline=True)
    # end def

    def deactivateNeighbor(self):
        """Summary

        Returns:
            TYPE: Description
        """
        if self.isVisible():
            inactive_alpha = PROX_ALPHA if self.to_vh_id_num is not None else 0
            self.setBrush(getBrushObj(self._color, alpha=inactive_alpha))
            self.animate(self, 'brush_alpha', 1000, 128, inactive_alpha)
            self.animate(self._phos_item, 'rotation', 500, -90, 0)
            self._bond_item.hide()
            self.setLabel(text=self._label_txt)
示例#30
0
class PreXoverItem(QGraphicsRectItem):
    """A PreXoverItem exists between a single 'from' VirtualHelixItem index
    and zero or more 'to' VirtualHelixItem Indices

    Attributes:
        adapter (TYPE): Description
        idx (int): the base index within the virtual helix
        is_fwd (TYPE): Description
        prexoveritemgroup (TYPE): Description
        to_vh_id_num (TYPE): Description
    """
    def __init__(self, from_virtual_helix_item, is_fwd, from_index,
                 to_vh_id_num, prexoveritemgroup, color):
        """Summary

        Args:
            from_virtual_helix_item (cadnano.gui.views.pathview.virtualhelixitem.VirtualHelixItem): Description
            is_fwd (TYPE): Description
            from_index (TYPE): Description
            to_vh_id_num (TYPE): Description
            prexoveritemgroup (TYPE): Description
            color (TYPE): Description
        """
        super(QGraphicsRectItem, self).__init__(BASE_RECT,
                                                from_virtual_helix_item)
        self.adapter = PropertyWrapperObject(self)
        self._bond_item = QGraphicsPathItem(self)
        self._bond_item.hide()
        self._label = PreXoverLabel(is_fwd, color, self)
        self._phos_item = Triangle(FWDPHOS_PP, self)
        self.setPen(getNoPen())
        self.resetItem(from_virtual_helix_item, is_fwd, from_index,
                       to_vh_id_num, prexoveritemgroup, color)

    # end def

    def shutdown(self):
        """Summary

        Returns:
            TYPE: Description
        """
        self.setBrush(getBrushObj(self._color, alpha=0))
        self.to_vh_id_num = None
        self.adapter.resetAnimations()
        phos = self._phos_item
        phos.adapter.resetAnimations()
        phos.resetTransform()
        phos.setPos(0, 0)
        self.setAcceptHoverEvents(False)
        self.setFlag(KEYINPUT_ACTIVE_FLAG, False)
        self.hide()

    # end def

    def resetItem(self, from_virtual_helix_item, is_fwd, from_index,
                  to_vh_id_num, prexoveritemgroup, color):
        """Summary

        Args:
            from_virtual_helix_item (cadnano.gui.views.pathview.virtualhelixitem.VirtualHelixItem): Description
            is_fwd (TYPE): Description
            from_index (TYPE): Description
            to_vh_id_num (TYPE): Description
            prexoveritemgroup (TYPE): Description
            color (TYPE): Description

        Returns:
            TYPE: Description
        """
        self.setParentItem(from_virtual_helix_item)
        self.resetTransform()
        self._id_num = from_virtual_helix_item.idNum()
        self.idx = from_index
        self.is_fwd = is_fwd
        self.to_vh_id_num = to_vh_id_num
        self._color = color
        self.prexoveritemgroup = prexoveritemgroup
        self._bond_item.hide()
        self._label_txt = lbt = None if to_vh_id_num is None else str(
            to_vh_id_num)
        self.setLabel(text=lbt)
        self._label.resetItem(is_fwd, color)

        phos = self._phos_item
        bonditem = self._bond_item

        if is_fwd:
            phos.setPath(FWDPHOS_PP)
            phos.setTransformOriginPoint(0, phos.boundingRect().center().y())
            phos.setPos(0.5 * BASE_WIDTH, BASE_WIDTH)
            phos.setPen(getNoPen())
            phos.setBrush(getBrushObj(color))
            bonditem.setPen(getPenObj(color, styles.PREXOVER_STROKE_WIDTH))
            self.setPos(from_index * BASE_WIDTH, -BASE_WIDTH)
        else:
            phos.setPath(REVPHOS_PP)
            phos.setTransformOriginPoint(0, phos.boundingRect().center().y())
            phos.setPos(0.5 * BASE_WIDTH, 0)
            phos.setPen(getPenObj(color, 0.25))
            phos.setBrush(getNoBrush())
            bonditem.setPen(
                getPenObj(color,
                          styles.PREXOVER_STROKE_WIDTH,
                          penstyle=Qt.DotLine,
                          capstyle=Qt.RoundCap))
            self.setPos(from_index * BASE_WIDTH, 2 * BASE_WIDTH)

        if to_vh_id_num is not None:
            inactive_alpha = PROX_ALPHA
            self.setBrush(getBrushObj(color, alpha=inactive_alpha))
        else:
            self.setBrush(getBrushObj(color, alpha=0))
        self.show()

    # end def

    def getInfo(self):
        """
        Returns:
            Tuple: (from_id_num, is_fwd, from_index, to_vh_id_num)
        """
        return (self._id_num, self.is_fwd, self.idx, self.to_vh_id_num)

    ### ACCESSORS ###
    def color(self):
        """Summary

        Returns:
            TYPE: Description
        """
        return self._color

    def remove(self):
        """Summary

        Returns:
            TYPE: Description
        """
        scene = self.scene()
        self.adapter.destroy()
        if scene:
            scene.removeItem(self._label)
            self._label = None
            self._phos_item.adapter.resetAnimations()
            self._phos_item.adapter = None
            scene.removeItem(self._phos_item)
            self._phos_item = None
            scene.removeItem(self._bond_item)
            self._bond_item = None
            self.adapter.resetAnimations()
            self.adapter = None
            scene.removeItem(self)

    # end defS

    ### EVENT HANDLERS ###
    def hoverEnterEvent(self, event):
        """Only if enableActive(True) is called
        hover and key events disabled by default

        Args:
            event (TYPE): Description
        """
        self.setFocus(Qt.MouseFocusReason)
        self.prexoveritemgroup.updateModelActiveBaseInfo(self.getInfo())
        self.setActiveHovered(True)
        status_string = "%d[%d]" % (self._id_num, self.idx)
        self.parentItem().window().statusBar().showMessage(status_string)

    # end def

    def hoverLeaveEvent(self, event):
        """Summary

        Args:
            event (TYPE): Description

        Returns:
            TYPE: Description
        """
        self.prexoveritemgroup.updateModelActiveBaseInfo(None)
        self.setActiveHovered(False)
        self.clearFocus()
        self.parentItem().window().statusBar().showMessage("")

    # end def

    def keyPressEvent(self, event):
        """Summary

        Args:
            event (TYPE): Description

        Returns:
            TYPE: Description
        """
        self.prexoveritemgroup.handlePreXoverKeyPress(event.key())

    # end def

    ### PUBLIC SUPPORT METHODS ###
    def setLabel(self, text=None, outline=False):
        """Summary

        Args:
            text (None, optional): Description
            outline (bool, optional): Description

        Returns:
            TYPE: Description
        """
        if text:
            self._label.setTextAndStyle(text=text, outline=outline)
            self._label.show()
        else:
            self._label.hide()

    # end def

    def animate(self, item, property_name, duration, start_value, end_value):
        """Summary

        Args:
            item (TYPE): Description
            property_name (TYPE): Description
            duration (TYPE): Description
            start_value (TYPE): Description
            end_value (TYPE): Description

        Returns:
            TYPE: Description
        """
        b_name = property_name.encode('ascii')
        anim = item.adapter.getRef(property_name)
        if anim is None:
            anim = QPropertyAnimation(item.adapter, b_name)
            item.adapter.saveRef(property_name, anim)
        anim.setDuration(duration)
        anim.setStartValue(start_value)
        anim.setEndValue(end_value)
        anim.start()

    # end def

    def setActiveHovered(self, is_active):
        """Rotate phosphate Triangle if `self.to_vh_id_num` is not `None`

        Args:
            is_active (bool): whether or not the PreXoverItem is parented to the
                active VirtualHelixItem
        """
        if is_active:
            self.setBrush(getBrushObj(self._color, alpha=128))
            self.animate(self, 'brush_alpha', 1, 0,
                         128)  # overwrite running anim
            # if self.to_vh_id_num is not None:
            self.animate(self._phos_item, 'rotation', 500, 0, -90)
        else:
            inactive_alpha = 0 if self.to_vh_id_num is None else PROX_ALPHA
            self.setBrush(getBrushObj(self._color, alpha=inactive_alpha))
            self.animate(self, 'brush_alpha', 1000, 128, inactive_alpha)
            self.animate(self._phos_item, 'rotation', 500, -90, 0)

    # end def

    def enableActive(self, is_active, to_vh_id_num=None):
        """Call on PreXoverItems created on the active VirtualHelixItem

        Args:
            is_active (TYPE): Description
            to_vh_id_num (None, optional): Description
        """
        if is_active:
            self.to_vh_id_num = to_vh_id_num
            self.setAcceptHoverEvents(True)
            if to_vh_id_num is None:
                self.setLabel(text=None)
                self.setBrush(getBrushObj(self._color, alpha=0))
            else:
                self.setLabel(text=str(to_vh_id_num))
                inactive_alpha = PROX_ALPHA
                self.setBrush(getBrushObj(self._color, alpha=inactive_alpha))
                self.animate(self, 'brush_alpha', 1000, 128, inactive_alpha)
                self.setFlag(KEYINPUT_ACTIVE_FLAG, True)
        else:
            self.setBrush(getNoBrush())
            # self.setLabel(text=None)
            self.setAcceptHoverEvents(False)
            self.setFlag(KEYINPUT_ACTIVE_FLAG, False)

    def activateNeighbor(self, active_prexoveritem, shortcut=None):
        """To be called with whatever the active_prexoveritem
        is for the parts `active_base`

        Args:
            active_prexoveritem (TYPE): Description
            shortcut (None, optional): Description
        """
        p1 = self._phos_item.scenePos()
        p2 = active_prexoveritem._phos_item.scenePos()
        scale = 3
        delta1 = -BASE_WIDTH * scale if self.is_fwd else BASE_WIDTH * scale
        delta2 = BASE_WIDTH * scale if active_prexoveritem.is_fwd else -BASE_WIDTH * scale
        c1 = self.mapFromScene(QPointF(p1.x(), p1.y() + delta1))
        c2 = self.mapFromScene(QPointF(p2.x(), p2.y() - delta2))
        pp = QPainterPath()
        pp.moveTo(self._phos_item.pos())
        pp.cubicTo(c1, c2, self._bond_item.mapFromScene(p2))
        self._bond_item.setPath(pp)
        self._bond_item.show()

        alpha = 32
        idx, active_idx = self.idx, active_prexoveritem.idx
        if self.is_fwd != active_prexoveritem.is_fwd:
            if idx == active_idx:
                alpha = 255
        elif idx == active_idx + 1:
            alpha = 255
        elif idx == active_idx - 1:
            alpha = 255

        inactive_alpha = PROX_ALPHA if self.to_vh_id_num is not None else 0
        self.setBrush(getBrushObj(self._color, alpha=inactive_alpha))
        self.animate(self, 'brush_alpha', 500, inactive_alpha, alpha)
        self.animate(self._phos_item, 'rotation', 500, 0, -90)
        self.setLabel(text=shortcut, outline=True)

    # end def

    def deactivateNeighbor(self):
        """Summary

        Returns:
            TYPE: Description
        """
        if self.isVisible():
            inactive_alpha = PROX_ALPHA if self.to_vh_id_num is not None else 0
            self.setBrush(getBrushObj(self._color, alpha=inactive_alpha))
            self.animate(self, 'brush_alpha', 1000, 128, inactive_alpha)
            self.animate(self._phos_item, 'rotation', 500, -90, 0)
            self._bond_item.hide()
            self.setLabel(text=self._label_txt)
示例#31
0
class Edge(QGraphicsPathItem):
    """Muchia dintre 2 noduri

    Muchia dintre 2 noduri este un path de la primul nod al
    muchiei la cel de-al doilea nod. In principiu path-ul
    este o line dreapta, dar daca un alt nod se intersecteaza
    cu aceast path, ea se va cruba pentru claritatea grafului.

    Atribute
    --------
    node1 : Node
        primul nod al muchiei
    node2 : Node
        cel de-al doilea nod al muchiei
    engine : GraphEngine
        enginu-ul aplicatiei
    cost : int, optional
        costul muchiei
    arrow_length : int, 15
        reprezinta lungimea sagetii muchiei in cazul unui graf orientat
    direct_path : QPainterPath
        path-ul direct de la primul la al doilea nod

    Metode
    ------
    create_path(start_point, end_point, directed)
        creaza path-ul muchiei dintre cele 2 noduri
    handle_value_changed(value)
        schimba culoarea muchiei
    """
    def __init__(self, node1, node2, engine, cost=None):
        super().__init__()
        self.node1 = node1
        self.node2 = node2
        self.engine = engine
        self.cost = cost

        self.arrow_length = 15
        self.setPen(self.node1.pen)

        # Crearea unui path invizibil si adaugarea lui in scene
        self.direct_path = QGraphicsPathItem()
        self.direct_path.setPen(QPen(QColor(0, 0, 0, 0)))
        self.engine.view.scene.addItem(self.direct_path)

    def create_path(self, start_point, end_point, directed):
        """Creeaza path-ul muchiei

        Path-ul muchiei este o curba Bezier. In cazul in care nici-un nod nu se intersecteaza
        cu path-ul direct dintre noduri, punctele de control ale curbei vor fi la centrul de
        greutate al dreptei date de cele 2 noduri, astfel creeandu-se o linie dreapta. In caz contrar,
        daca un nod se intersecteaza cu path-ul direct, pucntele de control ale curbei se vor situa pe
        dreapta perpendiculara pe path-ul direct, ce trece centrul de greutate al acestuia
        (dat de punctul de control initial) la o distanta egala dublul razei nodului. Aceste pucnte
        se pot situa in 2 pozitii, una la 'stanga' path-ului, iar cealalta la 'dreaptea' acestuia.
        Pozitia finala a punctului de control se determina 'trasand' 2 linii de la nodul care se
        intersecteaza la cele 2 posibile puncte de control. Verificand lungimea celor 2 linii
        se alege locatia punctului de control.

        panta dreptei : m = (y2 - y1) / (x2 - x1)
        ecuatia dreptei : y - y1 = m(x - x1)
        panta drepntei perpendiculare pe o dreapta : m' = -1 / m
        lungimea unei drepte : AB ^ 2 = (x2 - x1) ^ 2 + (y2 - y1) ^ 2

        => primul pas pentru a afla pucntele de control in cazul unei intersectii este:
        de a calula panta dreptei perpendiculara pe path-ul direct
            => m' = -1 / (node2.y - node1.y) / (node2.x - node1.x)
                => m' = -1 * (node2.x - node1.x) / (node2.y - node1.y)
        => cel de-al doilea pas este calcularea ecuatiei dreptei de panta m' ce trece prin pucntul de control (not G)
            => y - G.y = m'(x - G.x) => y = m'(x - G.x) + G.y
        => cel de-al treilea pas este inlocuirea lui y in lungimea dreptei ( lungimea dreptei dorita este dublul razei
            nodului) pentru a afla cele 2 coordonate x posibile (la 'stanga' si la 'dreapta' path-ului direct)
            => (x2 - G.x) ^ 2 + (m'(x2 - G.x) + G.y - G.y) ^ 2 = (2raza) ^ 2
            => x2 ^ 2 - 2 x2 G.x + G.x ^ 2 + (m' x2) ^ 2 - 2 (m' ^ 2) x2 G.x + (m' G.x) ^ 2 - (2raza) ^ 2 = 0
            => (x2 ^ 2)(1 + m' ^ 2) + x2(2 G.x (1 + m' ^ 2)) + (G.x ^ 2)(1 + m' ^ 2) - (2raza) ^ 2 = 0
                => cele 2 coordonate pe Ox ale punctului de control, prentu a afla cele 2 coordonate pe Oy
                se inlocuiesc valorie obtinute in ecuatia dreptei.


        Parametrii
        ----------
        start_point : QPointF
            punctul de start al path-ului
        end_point : QPointF
            punctul de final al path-ului
        directed : bool
            orientarea grafului

        Returneaza
        ----------
        path : QPainterPath
            path-ul final al muchiei
        """

        # Centrul de greutate al dreptei formata de cele 2 noduri
        control_point = QPointF((start_point.x() + end_point.x()) / 2,
                                (start_point.y() + end_point.y()) / 2)

        path = QPainterPath(start_point)
        node_radius = self.engine.node_radius
        point1 = point2 = None

        # Creearea path-ului direct
        _path = QPainterPath(start_point)
        _path.lineTo(end_point)
        self.direct_path.setPath(_path)

        # Verificarea pentru intersectii cu path-ul direct
        intersecting_items = self.engine.view.scene.collidingItems(
            self.direct_path)
        intersecting_items.remove(self.node1)
        intersecting_items.remove(self.node2)

        # Calcularea coordonatelor pe Ox a punctelor de control in cazul unei intersectii
        try:
            m = -1 * (self.node2.x() - self.node1.x()) / (self.node2.y() -
                                                          self.node1.y())
            agent = 1 + (m**2)
            factors = [
                agent, -2 * control_point.x() * agent,
                (control_point.x()**2) * agent - (node_radius * 2)**2
            ]
            roots = np.roots(factors)
        # In cazul in care nodurile au acceleasi coordonate pe Ox sau Oy panta
        # dreptei nu exista. Atunci se va trata cazul de ZeroDivisionError
        except ZeroDivisionError:
            point1 = control_point + QPointF(0, node_radius * 2)
            point2 = control_point - QPointF(0, node_radius * 2)

        for item in intersecting_items:
            if isinstance(item, Node):
                # Daca exista o intersectie si exista si panta dreptei atunci se calculeaza
                # si coordonatele pe Oy ale posibilelor puncte de control
                if (point1 and point2) is None:
                    point1 = QPointF(
                        roots[0],
                        m * (roots[0] - control_point.x()) + control_point.y())
                    point2 = QPointF(
                        roots[1],
                        m * (roots[1] - control_point.x()) + control_point.y())
                # Cele 2 linii de la nod la posibilele puncte de control
                line1 = QLineF(item.pos(), point1)
                line2 = QLineF(item.pos(), point2)
                # Daca lungimea primei linii este mai mica decat lungimea celei de-a doua linie
                # inseamna ca nodul este mai aproape de prima linie deci path-ul va trebui sa se
                # curbeze in partea opusa => se alege cel de-al doilea punct
                control_point = point2 if line1.length() <= line2.length(
                ) else point1
                break

        # Creearea curbei Bezier
        path.cubicTo(control_point, control_point, end_point)

        # Daca graful este orientat se adauga la capatul muchiei o sageata pentru
        # a reprezenta orientarea acestuia
        if directed:
            pos = path.currentPosition()
            dx, dy, angle = self.engine.get_angle(control_point, end_point)

            path.lineTo(
                QPointF(pos.x() + self.arrow_length * math.cos(angle + 60),
                        pos.y() + self.arrow_length * math.sin(angle + 60)))
            path.moveTo(end_point)
            path.lineTo(
                QPointF(pos.x() + self.arrow_length * math.cos(angle - 60),
                        pos.y() + self.arrow_length * math.sin(angle - 60)))

        # In cazul in care muchia are un cost acesta va fi afisat la mijlocul muchiei
        font_metrics = QFontMetrics(TEXT_FONT)
        font_offset = QPointF(font_metrics.height(),
                              font_metrics.horizontalAdvance(self.cost))
        path.addText(control_point - font_offset / 2, TEXT_FONT, self.cost)

        return path

    def handle_value_changed(self, value):
        """Schimba culoarea muchiei

        Aceasta metoda este folosita pentru shimbarea
        culorii in timpul unei animatii

        Parametrii
        ----------
        value : QColor
            noua culoare a muchiei
        """

        self.setPen(QPen(value, 1.5, Qt.SolidLine))
示例#32
0
class CharItem(QGraphicsRectItem):
    """ This item represents character item

        The purpose of the class is to draw a character, create a matrix
        of rectangles and resolve in which rectangles the character passes

        The class allow the following operations 
        -#  Drawing a character using the mouse events:
            -#  Start by the mouse press event
            -#  Continues by the mouse move event
            -#  The character is stored in QGraphicsPathItem
        -#  Transform the character to occupy the whole item's space
        -#  Set operation : resolving the Occupied matrix which tell on which rectangle the character
            passes
        -#  Reset operation : reverse the character transform so it is possible to continue
            drawing the character
        -#  Save operation : To a QDataStream
        -#  Load operation : From a QDataStream

        The graphical view of the class is composed from:
        -#  This class which inherits from QGraphicsRectItem and holds :
        -#  A QGraphicsPathItem : representing the character
        -#  A QGraphicsPathItem : representing the occupied rectangles
        -#  A QGraphicsPathItem : representing the unoccupied rectangles
    """
    def __init__(self, rect: QRectF, pos: QPointF, viewIndex: int = -1):
        """ CharItem constructor

            Args:
                rect    (QRectF)    : The rectangle that the character should fill
                pos     (QPointF)   : The position of the item within the parent
                viewIndex (int)     : The index of the item in case it is presented in multi character presentation
        """
        super(CharItem, self).__init__(rect)
        self.setAcceptedMouseButtons(Qt.LeftButton)
        self.setPresentationPrms()
        self.occupied = [[False for idx in range(self.netCols)]
                         for idx in range(self.netRows)]
        self.charPath = None
        self.wasSetted = False
        self.occupiedPathItem = None
        self.unoccupiedPathItem = None
        self.dirty = False
        self.viewIndex = viewIndex
        self.filename = ""
        self.boundaries = rect
        self.dx = 1
        self.dy = 1
        self.posInParent = pos
        self.setPos(self.posInParent)

    def setPresentationPrms(self):
        """ Setting the presentation prms

            The reason the there is a duplicate set of presentation parameters is
            that it allows changing the presentation parameters for one character
            (like in the select option
        """

        self.netColor = netColor
        self.netThickness = netThickness
        self.occupyColor = occupyColor
        self.unOccupyColor = unOccupyColor
        self.shapeColor = shapeColor
        self.shapeLineThickness = shapeLineThickness
        self.selectedOccupiedColor = selectedOccupiedColor
        self.selectedShapeColor = selectedShapeColor
        self.netRows = netRows
        self.netCols = netCols

    def setNetBoxDimensions(self, rect: QRectF):
        """ Set net box dimensions
        
            The net box is the rectangle that compose the network
            drawn to show the occupy matrix
        """
        self.netRectHeight = rect.height() / self.netRows
        self.netRectWidth = rect.width() / self.netCols
        self.left = rect.left()
        self.top = rect.top()

    def netRect(self, row_idx: int, col_idx: int) -> QRectF:
        """ Set net rect
        
            The net box is the rectangle that compose the network
            drawn to show the occupy matrix

            Args:
                row_idx  (int)   : The row of the network rectangle
                col_idx  (int)   : The col of the network rectangle

            Returns:
                QRectF  : The rectangle
        """
        return QRectF(self.left + col_idx * self.netRectWidth,
                      self.top + row_idx * self.netRectHeight,
                      self.netRectWidth, self.netRectHeight)

    def mouseMoveEvent(self, event: QGraphicsSceneMouseEvent):
        """ Mouse move event : continue draw a line

            This event is activated when the mouse is pressed and moves

            The methods draws the line in 2 conditions:
            -# The item is not part of multi character presentation
            -# A character path was initiated (using the mouse press event)

            Args:
               event (QGraphicsSceneMouseEvent) : the event description
        """
        if self.viewIndex == -1:
            if self.charPath is not None:
                point = event.scenePos()
                path = self.charPath.path()
                path.lineTo(point)
                self.charPath.setPath(path)
                self.update()

    def mousePressEvent(self, event: QGraphicsSceneMouseEvent):
        """ Mouse Press Event : Start a new line / Select the character

            If the character is part of multi character presentation 
                activate the character selection
            If the character is in single character presentation
                Start a new line in the character

            Args:
               event (QGraphicsSceneMouseEvent) : the event description
        """
        if self.viewIndex == -1:
            self.startLine(event)
        else:
            self.setSelected()

    def startLine(self, event: QGraphicsSceneMouseEvent):
        """ Start drawing a line

            When the mouse button is pressed and we are in single character
            dialog this method is activated to start drowning a line in the
            character

            Args:
               event (QGraphicsSceneMouseEvent) : the event description
        """
        # There are 2 modes for the presentation:
        # Original mode where the character is it's original size
        # After setting mode when the set was done and the character fullfill all
        # the item's space
        # Drawing can be done only in original mode
        if self.wasSetted:
            QMessageBox.critical(
                None, "Char identifier window",
                "The shape was already setted use revert setting")
            return

        # If this is the first start of a line - generate the QPainterPath and QGraphicsPathItem
        if self.charPath is None:
            self.initCharPath()

        # Move to the mouse position
        point = event.scenePos()
        path = self.charPath.path()
        path.moveTo(point)
        self.charPath.setPath(path)
        self.dirty = True

    def initCharPath(self):
        """ Init the item that holds the character

            There is one path item that holds the character
            This method is activated by start line if the char item 
            was not created to create the new and only one
        """
        self.dirty = True
        self.charPath = QGraphicsPathItem(self)
        self.charPath.setPen(
            QPen(QColor(self.shapeColor), self.shapeLineThickness))
        self.charPath.setZValue(1)
        self.charPath.originalPos = self.charPath.pos()
        self.charPath.setPath(QPainterPath())

    def setSelected(self):
        """ Set the item a selected item

            This method is activated when the mouse button is presses
            and the item is part of multi character presentation
        """

        # Set the colors of the item
        self.occupiedPathItem.setBrush(
            QBrush(QColor(self.selectedOccupiedColor)))
        self.charPath.setPen(
            QPen(QColor(self.selectedShapeColor), self.shapeLineThickness))
        self.update()

        # Report to the parent item about the selection
        self.parentItem().setSelected(self.viewIndex)

    def resetSelected(self):
        """ Set the colors of the item to not selected
        """

        self.occupiedPathItem.setBrush(QBrush(QColor(self.occupyColor)))
        self.charPath.setPen(
            QPen(QColor(self.shapeColor), self.shapeLineThickness))
        self.update()

    def set(self):
        """ Calculate the occupied matrix and present the results

            This method does the following:
            -# Fill the occupied matrix
            -# Generate the occupied and unoccupied pathes items
            -# Transform the char path to fit to the item's boundaries
        """
        # If there is no shape drawn - return
        if self.charPath is None:
            QMessageBox.critical(None, "Char identifier window",
                                 "There is no shape drawn")
            return

        # If the item is in setted mode - return
        if self.wasSetted:
            QMessageBox.critical(
                None, "Char identifier window",
                "The shape was already setted use revert setting")
            return

        # fill the occupied matrix with the data before the scaling
        self.fillOccupied()
        self.setNetBoxDimensions(self.boundingRect())
        self.createNetPaths()

        # update the transform - change the dimensions and location
        # only on the first time
        self.transformCharPath()

        self.wasSetted = True
        # update the presentation
        self.update()

    def revertTransform(self):
        """ Change from Setted mode to drawing mode

            The drawing mode is the mode where the character can be drawn
            -#  Restore the original size of the character (Reset the transform of the char item)
            -#  Restor the char path item's position to the original one (saved when created)
            -#  Empty the occupiedPath and the unoccupiedPath
        """
        # If there is no character drawn - return
        if self.charPath is None:
            QMessageBox.critical(None, "Char identifier window",
                                 "There is no shape drawn")
            return

        # If the item is already in drawing mode - return
        if not self.wasSetted:
            QMessageBox.critical(None, "Char identifier window",
                                 "The shape was not setted use set button")
            return

        # The char path item
        transform = self.charPath.transform()
        transform.reset()

        # The self.dx and self.dy are the scale parameters created when the item
        # begins and they are the scale parameters that transform it to the boundaries
        # given by the parent item
        transform.scale(self.dx, self.dy)
        self.charPath.setTransform(transform)
        self.charPath.setPos(self.charPath.originalPos)

        # Empty the network pathes
        self.occupiedPathItem.setPath(QPainterPath())
        self.unoccupiedPathItem.setPath(QPainterPath())
        self.wasSetted = False

    def transformCharPath(self):
        """ Transform char path when the item is setted

            This method does the following
            -#  scale the char path to the size of the item
            -#  calculate the new position of the char path so that it will
                be placed at the top left corner of the item
        """
        dx = self.boundingRect().width() / self.charPath.boundingRect().width()
        dy = self.boundingRect().height() / self.charPath.boundingRect(
        ).height()
        transform = self.charPath.transform()
        transform.reset()
        transform.scale(dx, dy)
        self.charPath.setTransform(transform)

        # Move the shape to the origin
        moveX = -(self.charPath.boundingRect().left() -
                  self.boundingRect().left()) * dx
        moveY = -(self.charPath.boundingRect().top() -
                  self.boundingRect().top()) * dy
        self.charPath.setX(self.charPath.x() + moveX)
        self.charPath.setY(self.charPath.y() + moveY)

    def fillOccupied(self):
        """ Fill the occupied matrix

            The algorithm of filling the occupied matrix is 
            -#  Scanning the char path
            -#  For each point decide on where row and column of the net
            -#  Set the occupies matrix for this column and row to True
        """

        for idx in range(100):
            point = self.charPath.path().pointAtPercent(idx / 100.)
            row_idx, col_idx = self.calcRowCol(point)
            self.occupied[row_idx][col_idx] = True

    def calcRowCol(self, point: QPointF):
        """ Calculate the network row and column that a point is int
            calc the row and column indexes of a point

            The following is the algorithm:
            1.  Find the distance between the point and the left (or top)
            2.  Divide the distance with the width of path to find the relative position
            3.  Multipile this relative position with the number of rows/cols
            4.  Convert the result to int to find the indexes
            5.  If the index is the number of row/col reduce the index
                (This is for the case the the point is on the boundary and in this
                case the relative position is 1 which will cause the indexes to
                be the number of rows/cols - out of the matrix indexes)

            Args:
                point (QPointF) : The point to resolve

            Returns:
                int : The network row that the point is in
                int : The network column that the point is in  

        """
        partialX = (point.x() - self.charPath.boundingRect().left()
                    ) / self.charPath.boundingRect().width()
        partialY = (point.y() - self.charPath.boundingRect().top()
                    ) / self.charPath.boundingRect().height()
        col_idx = int(partialX * self.netCols)
        row_idx = int(partialY * self.netRows)
        if row_idx == self.netRows:
            row_idx -= 1
        if col_idx == self.netCols:
            col_idx -= 1
        return row_idx, col_idx

    def createNetPaths(self):
        """ Create the network pathes

            This method creates 2 network pathes items one for holding
            the occupied rectangles and one to hold the unoccupied rectangles
        """
        # Generate 2 QPainterPath
        occupiedPath = QPainterPath()
        unoccupiedPath = QPainterPath()

        # For each entry in occupied matrix :
        # Add a rectangle to the appropriate path according the entry value
        for row_idx in range(self.netRows):
            for col_idx in range(self.netCols):
                if self.occupied[row_idx][col_idx]:
                    occupiedPath.addRect(self.netRect(row_idx, col_idx))
                else:
                    unoccupiedPath.addRect(self.netRect(row_idx, col_idx))

        # Create the QGraphicsPathItems that will hold the path
        self.createNetPath(self.occupyColor, occupiedPath, True)
        self.createNetPath(self.unOccupyColor, unoccupiedPath, False)

    def createNetPath(self, brushColor: str, painterPath: QPainterPath,
                      isOccupyPathItem: bool):
        """ Create a QGraphicsPathItem for a network path
            
            Args:
                brushColor (str)            : The color for filling the rectangles
                painterPath (QPainterPath)  : The path to be inserted to the item
                isOccupyPathItem (bool)     : Whether the path is occupied or unoccupied path
        """
        # Generate the path item if not created
        if isOccupyPathItem:
            if self.occupiedPathItem is None:
                self.occupiedPathItem = QGraphicsPathItem(self)
            pathItem = self.occupiedPathItem
        else:
            if self.unoccupiedPathItem is None:
                self.unoccupiedPathItem = QGraphicsPathItem(self)
            pathItem = self.unoccupiedPathItem
        if pathItem is None:
            pathItem = QGraphicsPathItem(self)

        # Set the item parameters
        pathItem.setPath(painterPath)
        pathItem.setPen(
            QPen(QColor(self.netColor), self.netThickness, style=Qt.SolidLine))
        pathItem.setBrush(QBrush(QColor(brushColor)))
        pathItem.setZValue(0)

    def save(self, stream: QDataStream, filename: str):
        """ Save the item to QDataStream
            
            Args:
                stream  (QDataStream)   : The data stream to write the item to
                filename (str)          : The filename (for documenting purposes)
        """

        # The item position
        stream << self.pos()

        # The dimensions
        stream << self.rect()

        # The presentation parameters
        stream.writeQString(self.netColor)
        stream.writeQString(self.occupyColor)
        stream.writeQString(self.unOccupyColor)
        stream.writeQString(self.shapeColor)
        stream.writeInt16(self.shapeLineThickness)
        stream.writeInt16(self.netRows)
        stream.writeInt16(self.netRows)

        # The items paths
        stream << self.charPath.path()
        self.dirty = False
        self.filename = filename

    def load(self, stream, filename):
        """ Loads the item from QDataStream
            
            Args:
                stream  (QDataStream)   : The data stream to read the item from
                filename (str)          : The filename (for documenting purposes)
        """

        # read the pos
        pos = QPointF()
        stream >> pos
        self.setPos(pos)

        # read the dimensions
        rect = QRectF()
        stream >> rect
        self.setRect(rect)

        # The presentation parameters
        self.netColor = stream.readQString()
        self.occupyColor = stream.readQString()
        self.unOccupyColor = stream.readQString()
        self.shapeColor = stream.readQString()
        self.shapeLineThickness = stream.readInt16()
        self.netRows = stream.readInt16()
        self.netRows = stream.readInt16()

        # read the paths
        self.initCharPath()
        path = self.charPath.path()
        stream >> path
        self.charPath.setPath(path)

        # Fit the item to the boundaries and position given by the item's parent
        self.fitToBoundaries()

        # The presentation of the item is in setted mode so we activate the set method
        self.wasSetted = False
        self.set()

        self.dirty = False
        self.filename = filename

    def fitToBoundaries(self):
        """ Fit the item to the boundaries and position given by it's parent

            This method was made to support the change of the character
            boundaries and that the char can be presented in different
            boundaries and position
        """
        self.setPos(self.posInParent)
        self.dx = self.boundaries.width() / self.rect().width()
        self.dy = self.boundaries.height() / self.rect().height()
        transform = self.transform()
        transform.scale(self.dx, self.dy)
        self.setTransform(transform)
示例#33
0
class NodeItem(QGraphicsObject):
    """
    An widget node item in the canvas.
    """

    #: Signal emitted when the scene position of the node has changed.
    positionChanged = Signal()

    #: Signal emitted when the geometry of the channel anchors changes.
    anchorGeometryChanged = Signal()

    #: Signal emitted when the item has been activated (by a mouse double
    #: click or a keyboard)
    activated = Signal()

    #: The item is under the mouse.
    hovered = Signal()

    #: Span of the anchor in degrees
    ANCHOR_SPAN_ANGLE = 90

    #: Z value of the item
    Z_VALUE = 100

    def __init__(self, widget_description=None, parent=None, **kwargs):
        self.__boundingRect = None

        QGraphicsObject.__init__(self, parent, **kwargs)

        self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True)
        self.setFlag(QGraphicsItem.ItemHasNoContents, True)
        self.setFlag(QGraphicsItem.ItemIsSelectable, True)
        self.setFlag(QGraphicsItem.ItemIsMovable, True)
        self.setFlag(QGraphicsItem.ItemIsFocusable, True)

        # central body shape item
        self.shapeItem = None

        # in/output anchor items
        self.inputAnchorItem = None
        self.outputAnchorItem = None

        # title text item
        self.captionTextItem = None

        # error, warning, info items
        self.errorItem = None
        self.warningItem = None
        self.infoItem = None

        # background when selected
        self.backgroundItem = None

        self.__title = ""
        self.__processingState = 0
        self.__progress = -1
        self.__statusMessage = ""

        self.__error = None
        self.__warning = None
        self.__info = None

        self.__anchorLayout = None
        self.__animationEnabled = False

        self.setZValue(self.Z_VALUE)
        self.setupGraphics()
        self.setWidgetDescription(widget_description)

    @classmethod
    def from_node(cls, node):
        """
        Create an :class:`NodeItem` instance and initialize it from a
        :class:`SchemeNode` instance.

        """
        self = cls()
        self.setWidgetDescription(node.description)
#        self.setCategoryDescription(node.category)
        return self

    @classmethod
    def from_node_meta(cls, meta_description):
        """
        Create an `NodeItem` instance from a node meta description.
        """
        self = cls()
        self.setWidgetDescription(meta_description)
        return self

    def setupGraphics(self):
        """
        Set up the graphics.
        """
        shape_rect = QRectF(-24, -24, 48, 48)

        self.shapeItem = NodeBodyItem(self)
        self.shapeItem.setShapeRect(shape_rect)
        self.shapeItem.setAnimationEnabled(self.__animationEnabled)

        # Rect for widget's 'ears'.
        anchor_rect = QRectF(-31, -31, 62, 62)
        self.inputAnchorItem = SinkAnchorItem(self)
        input_path = QPainterPath()
        start_angle = 180 - self.ANCHOR_SPAN_ANGLE / 2
        input_path.arcMoveTo(anchor_rect, start_angle)
        input_path.arcTo(anchor_rect, start_angle, self.ANCHOR_SPAN_ANGLE)
        self.inputAnchorItem.setAnchorPath(input_path)

        self.outputAnchorItem = SourceAnchorItem(self)
        output_path = QPainterPath()
        start_angle = self.ANCHOR_SPAN_ANGLE / 2
        output_path.arcMoveTo(anchor_rect, start_angle)
        output_path.arcTo(anchor_rect, start_angle, - self.ANCHOR_SPAN_ANGLE)
        self.outputAnchorItem.setAnchorPath(output_path)

        self.inputAnchorItem.hide()
        self.outputAnchorItem.hide()

        # Title caption item
        self.captionTextItem = NameTextItem(self)

        self.captionTextItem.setPlainText("")
        self.captionTextItem.setPos(0, 33)


        def iconItem(standard_pixmap):
            item = GraphicsIconItem(self, icon=standard_icon(standard_pixmap),
                                    iconSize=QSize(16, 16))
            item.hide()

            return item


        self.errorItem = iconItem(QStyle.SP_MessageBoxCritical)
        self.warningItem = iconItem(QStyle.SP_MessageBoxWarning)
        self.infoItem = iconItem(QStyle.SP_MessageBoxInformation)


        ################################
        # PyQt 5.10 crashes because of this call (2)
        #self.backgroundItem = QGraphicsPathItem(self)
        self.backgroundItem = QGraphicsPathItem(None)
        ################################

        backgroundrect = QPainterPath()
        backgroundrect.addRoundedRect(anchor_rect.adjusted(-4, -2, 4, 2),
                                      5, 5, mode=Qt.AbsoluteSize)
        self.backgroundItem.setPen(QPen(Qt.NoPen))
        self.backgroundItem.setBrush(QPalette().brush(QPalette.Highlight))
        self.backgroundItem.setOpacity(0.5)
        self.backgroundItem.setPath(backgroundrect)
        self.backgroundItem.setZValue(-10)
        self.backgroundItem.setVisible(self.isSelected())

        self.prepareGeometryChange()
        self.__boundingRect = None


    # TODO: Remove the set[Widget|Category]Description. The user should
    # handle setting of icons, title, ...
    def setWidgetDescription(self, desc):
        """
        Set widget description.
        """
        self.widget_description = desc
        if desc is None:
            return

        icon = icon_loader.from_description(desc).get(desc.icon)
        if icon:
            self.setIcon(icon)

        if not self.title():
            self.setTitle(desc.name)

        if desc.inputs:
            self.inputAnchorItem.show()
        if desc.outputs:
            self.outputAnchorItem.show()

        tooltip = NodeItem_toolTipHelper(self)
        self.setToolTip(tooltip)

    def setWidgetCategory(self, desc):
        """
        Set the widget category.
        """
        self.category_description = desc
        if desc and desc.background:
            background = NAMED_COLORS.get(desc.background, desc.background)
            color = QColor(background)
            if color.isValid():
                self.setColor(color)

    def setIcon(self, icon):
        """
        Set the node item's icon (:class:`QIcon`).
        """
        if isinstance(icon, QIcon):
            self.icon_item = GraphicsIconItem(self.shapeItem, icon=icon,
                                              iconSize=QSize(36, 36))
            self.icon_item.setPos(-18, -18)
        else:
            raise TypeError

    def setColor(self, color, selectedColor=None):
        """
        Set the widget color.
        """
        if selectedColor is None:
            selectedColor = saturated(color, 150)
        palette = create_palette(color, selectedColor)
        self.shapeItem.setPalette(palette)

    def setPalette(self, palette):
        # TODO: The palette should override the `setColor`
        raise NotImplementedError

    def setTitle(self, title):
        """
        Set the node title. The title text is displayed at the bottom of the
        node.

        """
        self.__title = title
        self.__updateTitleText()

    def title(self):
        """
        Return the node title.
        """
        return self.__title

    title_ = Property(six.text_type, fget=title, fset=setTitle,
                      doc="Node title text.")

    def setFont(self, font):
        """
        Set the title text font (:class:`QFont`).
        """
        if font != self.font():
            self.prepareGeometryChange()
            self.captionTextItem.setFont(font)
            self.__updateTitleText()

    def font(self):
        """
        Return the title text font.
        """
        return self.captionTextItem.font()

    def setAnimationEnabled(self, enabled):
        """
        Set the node animation enabled state.
        """
        if self.__animationEnabled != enabled:
            self.__animationEnabled = enabled
            self.shapeItem.setAnimationEnabled(enabled)

    def animationEnabled(self):
        """
        Are node animations enabled.
        """
        return self.__animationEnabled

    def setProcessingState(self, state):
        """
        Set the node processing state i.e. the node is processing
        (is busy) or is idle.

        """
        if self.__processingState != state:
            self.__processingState = state
            self.shapeItem.setProcessingState(state)
            if not state:
                # Clear the progress meter.
                self.setProgress(-1)
                if self.__animationEnabled:
                    self.shapeItem.ping()

    def processingState(self):
        """
        The node processing state.
        """
        return self.__processingState

    processingState_ = Property(int, fget=processingState,
                                fset=setProcessingState)

    def setProgress(self, progress):
        """
        Set the node work progress state (number between 0 and 100).
        """
        if progress is None or progress < 0 or not self.__processingState:
            progress = -1

        progress = max(min(progress, 100), -1)
        if self.__progress != progress:
            self.__progress = progress
            self.shapeItem.setProgress(progress)
            self.__updateTitleText()

    def progress(self):
        """
        Return the node work progress state.
        """
        return self.__progress

    progress_ = Property(float, fget=progress, fset=setProgress,
                         doc="Node progress state.")

    def setStatusMessage(self, message):
        """
        Set the node status message text.

        This text is displayed below the node's title.

        """
        if self.__statusMessage != message:
            self.__statusMessage = six.text_type(message)
            self.__updateTitleText()

    def statusMessage(self):
        return self.__statusMessage

    def setStateMessage(self, message):
        """
        Set a state message to display over the item.

        Parameters
        ----------
        message : UserMessage
            Message to display. `message.severity` is used to determine
            the icon and `message.contents` is used as a tool tip.

        """
        # TODO: Group messages by message_id not by severity
        # and deprecate set[Error|Warning|Error]Message
        if message.severity == UserMessage.Info:
            self.setInfoMessage(message.contents)
        elif message.severity == UserMessage.Warning:
            self.setWarningMessage(message.contents)
        elif message.severity == UserMessage.Error:
            self.setErrorMessage(message.contents)

    def setErrorMessage(self, message):
        if self.__error != message:
            self.__error = message
            self.__updateMessages()

    def setWarningMessage(self, message):
        if self.__warning != message:
            self.__warning = message
            self.__updateMessages()

    def setInfoMessage(self, message):
        if self.__info != message:
            self.__info = message
            self.__updateMessages()

    def newInputAnchor(self):
        """
        Create and return a new input :class:`AnchorPoint`.
        """
        if not (self.widget_description and self.widget_description.inputs):
            raise ValueError("Widget has no inputs.")

        anchor = AnchorPoint()
        self.inputAnchorItem.addAnchor(anchor, position=1.0)

        positions = self.inputAnchorItem.anchorPositions()
        positions = uniform_linear_layout(positions)
        self.inputAnchorItem.setAnchorPositions(positions)

        return anchor

    def removeInputAnchor(self, anchor):
        """
        Remove input anchor.
        """
        self.inputAnchorItem.removeAnchor(anchor)

        positions = self.inputAnchorItem.anchorPositions()
        positions = uniform_linear_layout(positions)
        self.inputAnchorItem.setAnchorPositions(positions)

    def newOutputAnchor(self):
        """
        Create and return a new output :class:`AnchorPoint`.
        """
        if not (self.widget_description and self.widget_description.outputs):
            raise ValueError("Widget has no outputs.")

        anchor = AnchorPoint(self)
        self.outputAnchorItem.addAnchor(anchor, position=1.0)

        positions = self.outputAnchorItem.anchorPositions()
        positions = uniform_linear_layout(positions)
        self.outputAnchorItem.setAnchorPositions(positions)

        return anchor

    def removeOutputAnchor(self, anchor):
        """
        Remove output anchor.
        """
        self.outputAnchorItem.removeAnchor(anchor)

        positions = self.outputAnchorItem.anchorPositions()
        positions = uniform_linear_layout(positions)
        self.outputAnchorItem.setAnchorPositions(positions)

    def inputAnchors(self):
        """
        Return a list of all input anchor points.
        """
        return self.inputAnchorItem.anchorPoints()

    def outputAnchors(self):
        """
        Return a list of all output anchor points.
        """
        return self.outputAnchorItem.anchorPoints()

    def setAnchorRotation(self, angle):
        """
        Set the anchor rotation.
        """
        self.inputAnchorItem.setRotation(angle)
        self.outputAnchorItem.setRotation(angle)
        self.anchorGeometryChanged.emit()

    def anchorRotation(self):
        """
        Return the anchor rotation.
        """
        return self.inputAnchorItem.rotation()

    def boundingRect(self):
        # TODO: Important because of this any time the child
        # items change geometry the self.prepareGeometryChange()
        # needs to be called.
        if self.__boundingRect is None:
            self.__boundingRect = self.childrenBoundingRect()
        return self.__boundingRect

    def shape(self):
        # Shape for mouse hit detection.
        # TODO: Should this return the union of all child items?
        return self.shapeItem.shape()

    def __updateTitleText(self):
        """
        Update the title text item.
        """
        text = ['<div align="center">%s' % escape(self.title())]

        status_text = []

        progress_included = False
        if self.__statusMessage:
            msg = escape(self.__statusMessage)
            format_fields = dict(parse_format_fields(msg))
            if "progress" in format_fields and len(format_fields) == 1:
                # Insert progress into the status text format string.
                spec, _ = format_fields["progress"]
                if spec != None:
                    progress_included = True
                    progress_str = "{0:.0f}%".format(self.progress())
                    status_text.append(msg.format(progress=progress_str))
            else:
                status_text.append(msg)

        if self.progress() >= 0 and not progress_included:
            status_text.append("%i%%" % int(self.progress()))

        if status_text:
            text += ["<br/>",
                     '<span style="font-style: italic">',
                     "<br/>".join(status_text),
                     "</span>"]
        text += ["</div>"]
        text = "".join(text)

        # The NodeItems boundingRect could change.
        self.prepareGeometryChange()
        self.__boundingRect = None
        self.captionTextItem.setHtml(text)
        self.captionTextItem.document().adjustSize()
        width = self.captionTextItem.textWidth()
        self.captionTextItem.setPos(-width / 2.0, 33)

    def __updateMessages(self):
        """
        Update message items (position, visibility and tool tips).
        """
        items = [self.errorItem, self.warningItem, self.infoItem]

        messages = [self.__error, self.__warning, self.__info]
        for message, item in zip(messages, items):
            item.setVisible(bool(message))
            item.setToolTip(message or "")

        shown = [item for item in items if item.isVisible()]
        count = len(shown)
        if count:
            spacing = 3
            rects = [item.boundingRect() for item in shown]
            width = sum(rect.width() for rect in rects)
            width += spacing * max(0, count - 1)
            height = max(rect.height() for rect in rects)
            origin = self.shapeItem.boundingRect().top() - spacing - height
            origin = QPointF(-width / 2, origin)
            for item, rect in zip(shown, rects):
                item.setPos(origin)
                origin = origin + QPointF(rect.width() + spacing, 0)

    def mousePressEvent(self, event):
        if self.shapeItem.path().contains(event.pos()):
            return QGraphicsObject.mousePressEvent(self, event)
        else:
            event.ignore()

    def mouseDoubleClickEvent(self, event):
        if self.shapeItem.path().contains(event.pos()):
            QGraphicsObject.mouseDoubleClickEvent(self, event)
            QTimer.singleShot(0, self.activated.emit)
        else:
            event.ignore()

    def contextMenuEvent(self, event):
        if self.shapeItem.path().contains(event.pos()):
            return QGraphicsObject.contextMenuEvent(self, event)
        else:
            event.ignore()

    def focusInEvent(self, event):
        self.shapeItem.setHasFocus(True)
        return QGraphicsObject.focusInEvent(self, event)

    def focusOutEvent(self, event):
        self.shapeItem.setHasFocus(False)
        return QGraphicsObject.focusOutEvent(self, event)

    def itemChange(self, change, value):
        if change == QGraphicsItem.ItemSelectedChange:
            selected = bool(qtcompat.qunwrap(value))
            self.shapeItem.setSelected(selected)
            self.captionTextItem.setSelectionState(selected)
            self.backgroundItem.setVisible(selected)
        elif change == QGraphicsItem.ItemPositionHasChanged:
            self.positionChanged.emit()

        return QGraphicsObject.itemChange(self, change, value)