Beispiel #1
0
    def __init__(self, element, model):
        super(ModelerGraphicItem, self).__init__(None, None)
        self.model = model
        self.element = element
        if isinstance(element, ModelerParameter):
            icon = QIcon(os.path.dirname(__file__) + '/../images/input.png')
            self.pixmap = icon.pixmap(20, 20, state=QIcon.On)
            self.text = element.param.description
        elif isinstance(element, ModelerOutput):
            # Output name
            icon = QIcon(os.path.dirname(__file__) + '/../images/output.png')
            self.pixmap = icon.pixmap(20, 20, state=QIcon.On)
            self.text = element.description
        else:
            self.text = element.description
            self.pixmap = element.algorithm.getIcon().pixmap(15, 15)
        self.arrows = []
        self.setFlag(QGraphicsItem.ItemIsMovable, True)
        self.setFlag(QGraphicsItem.ItemIsSelectable, True)
        self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True)
        self.setZValue(1000)

        if not isinstance(element, ModelerOutput):
            icon = QIcon(os.path.dirname(__file__) + '/../images/edit.png')
            pt = QPointF(
                ModelerGraphicItem.BOX_WIDTH / 2 -
                FlatButtonGraphicItem.WIDTH / 2,
                ModelerGraphicItem.BOX_HEIGHT / 2 -
                FlatButtonGraphicItem.HEIGHT / 2 + 1)
            self.editButton = FlatButtonGraphicItem(icon, pt, self.editElement)
            self.editButton.setParentItem(self)
            icon = QIcon(os.path.dirname(__file__) + '/../images/delete.png')
            pt = QPointF(
                ModelerGraphicItem.BOX_WIDTH / 2 -
                FlatButtonGraphicItem.WIDTH / 2,
                -ModelerGraphicItem.BOX_HEIGHT / 2 +
                FlatButtonGraphicItem.HEIGHT / 2 + 1)
            self.deleteButton = FlatButtonGraphicItem(icon, pt,
                                                      self.removeElement)
            self.deleteButton.setParentItem(self)

        if isinstance(element, Algorithm):
            alg = element.algorithm
            if alg.parameters:
                pt = self.getLinkPointForParameter(-1)
                pt = QPointF(0, pt.y() + 2)
                self.inButton = FoldButtonGraphicItem(
                    pt, self.foldInput, self.element.paramsFolded)
                self.inButton.setParentItem(self)
            if alg.outputs:
                pt = self.getLinkPointForOutput(-1)
                pt = QPointF(0, pt.y() + 2)
                self.outButton = FoldButtonGraphicItem(
                    pt, self.foldOutput, self.element.outputsFolded)
                self.outButton.setParentItem(self)
 def paint(self, painter, option, widget=None):
     pt = QPointF(-self.WIDTH / 2, -self.HEIGHT / 2) + self.position
     rect = QRectF(pt.x(), pt.y(), self.WIDTH, self.HEIGHT)
     if self.isIn:
         painter.setPen(QPen(Qt.transparent, 1))
         painter.setBrush(QBrush(Qt.lightGray, Qt.SolidPattern))
     else:
         painter.setPen(QPen(Qt.transparent, 1))
         painter.setBrush(QBrush(Qt.transparent, Qt.SolidPattern))
     painter.drawRect(rect)
     painter.drawPixmap(pt.x(), pt.y(), self.pixmap)
Beispiel #3
0
 def paint(self, painter, option, widget=None):
     pt = QPointF(-self.WIDTH / 2, -self.HEIGHT / 2) + self.position
     rect = QRectF(pt.x(), pt.y(), self.WIDTH, self.HEIGHT)
     if self.isIn:
         painter.setPen(QPen(Qt.transparent, 1))
         painter.setBrush(QBrush(Qt.lightGray, Qt.SolidPattern))
     else:
         painter.setPen(QPen(Qt.transparent, 1))
         painter.setBrush(QBrush(Qt.transparent, Qt.SolidPattern))
     painter.drawRect(rect)
     painter.drawPixmap(pt.x(), pt.y(), self.pixmap)
Beispiel #4
0
    def drawPipeline(self, qp):
        """Called by paintEvent, it draws figures and link between them.
        Parameters
        ----------
        qp : QtGui.QPainter
            Performs low-level painting
        """
        # If self.levels is empty, indeed, it does not make sense to draw
        # something.
        if self.levels is None:
            return
        # define Rep position because they change whan resising main windows

        width = self.size().width()
        height = self.size().height()

        if len(self.levels) != 0:
            total_height = (len(self.levels) - 1) * (
                STAGE_SIZE_Y + ROUTER_SIZE_Y)
            self.zoom = height / total_height
        else:
            self.zoom = 1

        #  center figures on screen
        last_point = QPointF(width / 2, -GAP_Y / 2 * self.zoom)
        for level in self.levels:
            total_x = len(level) * STAGE_SIZE_X * self.zoom + (
                len(level) - 1 * STAGE_GAP_X * self.zoom)
            last_point.setX(width / 2 - total_x / 2)
            last_point.setY(last_point.y() + GAP_Y * self.zoom)
            for figure in level:
                figure.setCenter(QPointF(last_point))
                last_point.setX(last_point.x() + STAGE_GAP_X * self.zoom)
        # Start to paint
        size = self.size()
        lines = list()
        last_level_pt = list()
        for level in self.levels:
            current_level_pt = list()
            for figure in level:
                figure.draw(qp, zoom=self.zoom)
                connexion_pt = QPointF(figure.center.x(), figure.center.y()
                                       - figure.size_y / 2 * self.zoom)
                current_level_pt.append(QPointF(connexion_pt.x(),
                                                connexion_pt.y() + figure.size_y * self.zoom))
                # Link to previous level connexion point(s)
                for point in last_level_pt:
                    lines.append(QLineF(point, connexion_pt))
            # Keep points for next level
            last_level_pt = list(current_level_pt)
        for line in lines:
            qp.setPen(QtGui.QPen(self.blue_cta, 1, QtCore.Qt.SolidLine))
            qp.drawLine(line)
Beispiel #5
0
    def __init__(self, element, model):
        super(ModelerGraphicItem, self).__init__(None, None)
        self.model = model
        self.element = element
        if isinstance(element, ModelerParameter):
            icon = QIcon(os.path.join(pluginPath, 'images', 'input.png'))
            self.pixmap = icon.pixmap(20, 20, state=QIcon.On)
            self.text = element.param.description
        elif isinstance(element, ModelerOutput):
            # Output name
            icon = QIcon(os.path.join(pluginPath, 'images', 'output.png'))
            self.pixmap = icon.pixmap(20, 20, state=QIcon.On)
            self.text = element.description
        else:
            self.text = element.description
            self.pixmap = element.algorithm.getIcon().pixmap(15, 15)
        self.arrows = []
        self.setFlag(QGraphicsItem.ItemIsMovable, True)
        self.setFlag(QGraphicsItem.ItemIsSelectable, True)
        self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True)
        self.setZValue(1000)

        if not isinstance(element, ModelerOutput):
            icon = QIcon(os.path.join(pluginPath, 'images', 'edit.png'))
            pt = QPointF(ModelerGraphicItem.BOX_WIDTH / 2
                         - FlatButtonGraphicItem.WIDTH / 2,
                         ModelerGraphicItem.BOX_HEIGHT / 2
                         - FlatButtonGraphicItem.HEIGHT / 2 + 1)
            self.editButton = FlatButtonGraphicItem(icon, pt, self.editElement)
            self.editButton.setParentItem(self)
            icon = QIcon(os.path.join(pluginPath, 'images', 'delete.png'))
            pt = QPointF(ModelerGraphicItem.BOX_WIDTH / 2
                         - FlatButtonGraphicItem.WIDTH / 2,
                         - ModelerGraphicItem.BOX_HEIGHT / 2
                         + FlatButtonGraphicItem.HEIGHT / 2 + 1)
            self.deleteButton = FlatButtonGraphicItem(icon, pt,
                                                      self.removeElement)
            self.deleteButton.setParentItem(self)

        if isinstance(element, Algorithm):
            alg = element.algorithm
            if alg.parameters:
                pt = self.getLinkPointForParameter(-1)
                pt = QPointF(0, pt.y() + 2)
                self.inButton = FoldButtonGraphicItem(pt, self.foldInput, self.element.paramsFolded)
                self.inButton.setParentItem(self)
            if alg.outputs:
                pt = self.getLinkPointForOutput(-1)
                pt = QPointF(0, pt.y() + 2)
                self.outButton = FoldButtonGraphicItem(pt, self.foldOutput, self.element.outputsFolded)
                self.outButton.setParentItem(self)
    def paint(self, painter, option, widget=None):
        myPen = self.pen()
        myPen.setColor(self.myColor)
        painter.setPen(myPen)
        painter.setBrush(self.myColor)

        controlPoints = []
        endPt = self.endItem.getLinkPointForParameter(self.endIndex)
        startPt = self.startItem.getLinkPointForOutput(self.startIndex)
        if isinstance(self.startItem.element, Algorithm):
            if self.startIndex != -1:
                controlPoints.append(self.startItem.pos() + startPt)
                controlPoints.append(self.startItem.pos() + startPt +
                                     QPointF(ModelerGraphicItem.BOX_WIDTH /
                                             2, 0))
                controlPoints.append(self.endItem.pos() + endPt -
                                     QPointF(ModelerGraphicItem.BOX_WIDTH /
                                             2, 0))
                controlPoints.append(self.endItem.pos() + endPt)
                pt = QPointF(self.startItem.pos() + startPt + QPointF(-3, -3))
                painter.drawEllipse(pt.x(), pt.y(), 6, 6)
                pt = QPointF(self.endItem.pos() + endPt + QPointF(-3, -3))
                painter.drawEllipse(pt.x(), pt.y(), 6, 6)
            else:
                # Case where there is a dependency on an algorithm not
                # on an output
                controlPoints.append(self.startItem.pos() + startPt)
                controlPoints.append(self.startItem.pos() + startPt +
                                     QPointF(ModelerGraphicItem.BOX_WIDTH /
                                             2, 0))
                controlPoints.append(self.endItem.pos() + endPt -
                                     QPointF(ModelerGraphicItem.BOX_WIDTH /
                                             2, 0))
                controlPoints.append(self.endItem.pos() + endPt)
        else:
            controlPoints.append(self.startItem.pos())
            controlPoints.append(self.startItem.pos() +
                                 QPointF(ModelerGraphicItem.BOX_WIDTH / 2, 0))
            controlPoints.append(self.endItem.pos() + endPt -
                                 QPointF(ModelerGraphicItem.BOX_WIDTH / 2, 0))
            controlPoints.append(self.endItem.pos() + endPt)
            pt = QPointF(self.endItem.pos() + endPt + QPointF(-3, -3))
            painter.drawEllipse(pt.x(), pt.y(), 6, 6)

        path = QPainterPath()
        path.moveTo(controlPoints[0])
        path.cubicTo(*controlPoints[1:])
        painter.strokePath(path, painter.pen())
        self.setPath(path)
Beispiel #7
0
 def draw(self, qpainter, zoom=1):
     """Draw this figure
     Parameters
     ----------
     qpainter: PySide.QtGui.QPainter
     """
     size_x = self.size_x * zoom
     size_y = self.size_y * zoom
     pensize = 3
     qpainter.setPen(
         QtGui.QPen(PipelineDrawer.blue_cta, pensize, QtCore.Qt.SolidLine))
     text_pos = QPointF(self.center)
     text_pos.setX(text_pos.x() - size_x / 2 + 2)
     text_pos.setY(text_pos.y() + pensize)
     qpainter.drawText(text_pos, str(self.nb_job_done))
     pt = QPointF(self.center)
     pt.setX(5)
     pos = self.name.find("$$thread_number$$")
     if pos != -1:
         name = self.name[0:pos]
     else:
         name = self.name
     qpainter.drawText(pt, name)
     if self.running == True:
         qpainter.setPen(
             QtGui.QPen(PipelineDrawer.mygreen, 3, QtCore.Qt.SolidLine))
     else:
         qpainter.setPen(
             QtGui.QPen(PipelineDrawer.blue_cta, 3, QtCore.Qt.SolidLine))
     x1 = self.center.x() - (size_x / 2)
     y1 = self.center.y() - (size_y / 2)
     qpainter.drawRoundedRect(x1, y1, size_x, size_y, 12.0, 12.0)
Beispiel #8
0
 def showMoveHelper(self, visible=True):
     """show help text In empty HandBoards"""
     if visible:
         if not self.__moveHelper:
             splitter = QGraphicsRectItem(self)
             hbCenter = self.rect().center()
             splitter.setRect(hbCenter.x() * 0.5, hbCenter.y(), hbCenter.x() * 1, 1)
             helpItems = [splitter]
             for name, yFactor in [(m18n('Move Exposed Tiles Here'), 0.5),
                                     (m18n('Move Concealed Tiles Here'), 1.5)]:
                 helper = QGraphicsSimpleTextItem(name, self)
                 helper.setScale(3)
                 nameRect = QRectF()
                 nameRect.setSize(helper.mapToParent(helper.boundingRect()).boundingRect().size())
                 center = QPointF(hbCenter)
                 center.setY(center.y() * yFactor)
                 helper.setPos(center - nameRect.center())
                 if self.sceneRotation() == 180:
                     rotateCenter(helper, 180)
                 helpItems.append(helper)
             self.__moveHelper = self.scene().createItemGroup(helpItems)
         self.__moveHelper.setVisible(True)
     else:
         if self.__moveHelper:
             self.__moveHelper.setVisible(False)
Beispiel #9
0
    def paint(self, painter, option, widget=None):
        myPen = self.pen()
        myPen.setColor(self.myColor)
        painter.setPen(myPen)
        painter.setBrush(self.myColor)

        controlPoints = []
        endPt = self.endItem.getLinkPointForParameter(self.endIndex)
        startPt = self.startItem.getLinkPointForOutput(self.startIndex)
        if isinstance(self.startItem.element, Algorithm):
            if self.startIndex != -1:
                controlPoints.append(self.startItem.pos() + startPt)
                controlPoints.append(self.startItem.pos() + startPt
                        + QPointF(ModelerGraphicItem.BOX_WIDTH / 2, 0))
                controlPoints.append(self.endItem.pos() + endPt
                        - QPointF(ModelerGraphicItem.BOX_WIDTH / 2, 0))
                controlPoints.append(self.endItem.pos() + endPt)
                pt = QPointF(self.startItem.pos() + startPt
                        + QPointF(-3, -3))
                painter.drawEllipse(pt.x(), pt.y(), 6, 6)
                pt = QPointF(self.endItem.pos() + endPt +
                        QPointF(-3, -3))
                painter.drawEllipse(pt.x(), pt.y(), 6, 6)
            else:
                # Case where there is a dependency on an algorithm not
                # on an output
                controlPoints.append(self.startItem.pos() + startPt)
                controlPoints.append(self.startItem.pos() + startPt
                        + QPointF(ModelerGraphicItem.BOX_WIDTH / 2, 0))
                controlPoints.append(self.endItem.pos() + endPt
                        - QPointF(ModelerGraphicItem.BOX_WIDTH / 2, 0))
                controlPoints.append(self.endItem.pos() + endPt)
        else:
            controlPoints.append(self.startItem.pos())
            controlPoints.append(self.startItem.pos()
                    + QPointF(ModelerGraphicItem.BOX_WIDTH / 2, 0))
            controlPoints.append(self.endItem.pos() + endPt
                    - QPointF(ModelerGraphicItem.BOX_WIDTH / 2, 0))
            controlPoints.append(self.endItem.pos() + endPt)
            pt = QPointF(self.endItem.pos() + endPt + QPointF(-3, -3))
            painter.drawEllipse(pt.x(), pt.y(), 6, 6)

        path = QPainterPath()
        path.moveTo(controlPoints[0])
        path.cubicTo(*controlPoints[1:])
        painter.strokePath(path, painter.pen())
        self.setPath(path)
Beispiel #10
0
 def foldInput(self, folded):
     self.element.paramsFolded = folded
     self.prepareGeometryChange()
     if self.element.algorithm.outputs:
         pt = self.getLinkPointForOutput(-1)
         pt = QPointF(0, pt.y())
         self.outButton.position = pt
     self.update()
Beispiel #11
0
 def foldInput(self, folded):
     self.element.paramsFolded = folded
     self.prepareGeometryChange()
     if self.element.algorithm.outputs:
         pt = self.getLinkPointForOutput(-1)
         pt = QPointF(0, pt.y())
         self.outButton.position = pt
     self.update()
Beispiel #12
0
def distToLine(pt,  p1,  p2):
    """
    Compute the distance from the point `pt` to the line segment [p1,p2]
    """
    u = p2-p1
    lu = u.x()*u.x() + u.y()*u.y()
    pmax = QPointF(max(abs(p1.x()), abs(p2.x())), max(abs(p1.y()), abs(p2.y())))
    if lu / (pmax.x()*pmax.x() + pmax.y()*pmax.y()) < 1e-10:
        diff = u - p1
        return sqrt(diff.x()*diff.x() + diff.y()*diff.y())
    dp = pt-p1
    proj = (u.x()*dp.x() + u.y()*dp.y())
    if proj >= 0 and proj <= lu:
        return abs(dp.x()*u.y() - u.x()*dp.y())/sqrt(lu)
    elif proj < 0:
        return dp.x()*dp.x() + dp.y()*dp.y()
    else:
        return dist(pt,  p2)
Beispiel #13
0
    def paint(self, painter, option, widget):

        arc_rect = 10
        connector_length = 5

        painter.setPen(self.pen)
        path = QtGui.QPainterPath()

        if self.source.x() == self.dest.x():
            path.moveTo(self.source.x(), self.source.y())
            path.lineTo(self.dest.x(), self.dest.y())
            painter.drawPath(path)

        else:

            #Define points starting from source
            point1 = QPointF(self.source.x(), self.source.y())
            point2 = QPointF(point1.x(), point1.y() - connector_length)
            point3 = QPointF(point2.x() + arc_rect, point2.y() - arc_rect)

            #Define points starting from dest
            point4 = QPointF(self.dest.x(), self.dest.y())
            point5 = QPointF(point4.x(),point3.y() - arc_rect)
            point6 = QPointF(point5.x() - arc_rect, point5.y() + arc_rect)

            start_angle_arc1 = 180
            span_angle_arc1 = 90
            start_angle_arc2 = 90
            span_angle_arc2 = -90

            # If the dest is at the left of the source, then we
            # need to reverse some values
            if self.source.x() > self.dest.x():
                point5 = QPointF(point4.x(), point4.y() + connector_length)
                point6 = QPointF(point5.x() + arc_rect, point5.y() + arc_rect)
                point3 = QPointF(self.source.x() - arc_rect, point6.y())
                point2 = QPointF(self.source.x(), point3.y() + arc_rect)

                span_angle_arc1 = 90

            path.moveTo(point1)
            path.lineTo(point2)
            path.arcTo(QRectF(point2, point3),
                       start_angle_arc1, span_angle_arc1)
            path.lineTo(point6)
            path.arcTo(QRectF(point6, point5),
                       start_angle_arc2, span_angle_arc2)
            path.lineTo(point4)
            painter.drawPath(path)
Beispiel #14
0
    def paint(self, painter, option, widget):

        arc_rect = 10
        connector_length = 5

        painter.setPen(self.pen)
        path = QtGui.QPainterPath()

        if self.source.x() == self.dest.x():
            path.moveTo(self.source.x(), self.source.y())
            path.lineTo(self.dest.x(), self.dest.y())
            painter.drawPath(path)

        else:

            #Define points starting from source
            point1 = QPointF(self.source.x(), self.source.y())
            point2 = QPointF(point1.x(), point1.y() - connector_length)
            point3 = QPointF(point2.x() + arc_rect, point2.y() - arc_rect)

            #Define points starting from dest
            point4 = QPointF(self.dest.x(), self.dest.y())
            point5 = QPointF(point4.x(), point3.y() - arc_rect)
            point6 = QPointF(point5.x() - arc_rect, point5.y() + arc_rect)

            start_angle_arc1 = 180
            span_angle_arc1 = 90
            start_angle_arc2 = 90
            span_angle_arc2 = -90

            # If the dest is at the left of the source, then we
            # need to reverse some values
            if self.source.x() > self.dest.x():
                point5 = QPointF(point4.x(), point4.y() + connector_length)
                point6 = QPointF(point5.x() + arc_rect, point5.y() + arc_rect)
                point3 = QPointF(self.source.x() - arc_rect, point6.y())
                point2 = QPointF(self.source.x(), point3.y() + arc_rect)

                span_angle_arc1 = 90

            path.moveTo(point1)
            path.lineTo(point2)
            path.arcTo(QRectF(point2, point3), start_angle_arc1,
                       span_angle_arc1)
            path.lineTo(point6)
            path.arcTo(QRectF(point6, point5), start_angle_arc2,
                       span_angle_arc2)
            path.lineTo(point4)
            painter.drawPath(path)
Beispiel #15
0
 def movePoints(self, image_name, pt_ids):
     if image_name == self.image_name:
         data = self.current_data
         dm = self.data_manager
         points = self.points
         cells = self.cells
         for pt_id in pt_ids:
             pos = data[pt_id]
             pos = QPointF(pos.x() / self.min_scale, pos.y() / self.min_scale)
             points[pt_id].setPos(pos)
             for cid in dm.cell_points[pt_id]:
                 cell = cells.get(cid, None)
                 if cell is not None and cell.isVisible():
                     cell.setGeometry()
Beispiel #16
0
    def updateSmooth(self):
        if self.m_smooth:
            if self.m_cursor.x() != self.m_smooth_x or self.m_cursor.y() != self.m_smooth_y:
                if abs(self.m_cursor.x() - self.m_smooth_x) <= 0.001:
                    self.m_smooth_x = self.m_cursor.x()
                    return
                elif abs(self.m_cursor.y() - self.m_smooth_y) <= 0.001:
                    self.m_smooth_y = self.m_cursor.y()
                    return

                new_x = (self.m_smooth_x + self.m_cursor.x() * 3) / 4
                new_y = (self.m_smooth_y + self.m_cursor.y() * 3) / 4
                pos = QPointF(new_x, new_y)

                self.m_cursor.setPos(pos)
                self.m_lineH.setY(pos.y())
                self.m_lineV.setX(pos.x())

                xp = pos.x() / (self.p_size.x() + self.p_size.width())
                yp = pos.y() / (self.p_size.y() + self.p_size.height())

                self.sendMIDI(xp, yp)
                self.emit(SIGNAL("cursorMoved(double, double)"), xp, yp)
Beispiel #17
0
 def draw(self, qpainter, zoom=1):
     """Draw this figure
     Parameters
     ----------
     qpainter: PySide.QtGui.QPainter
     """
     size_x = self.size_x * zoom
     size_y = self.size_y * zoom
     pensize = 3
     qpainter.setPen(
         QtGui.QPen(QtCore.Qt.black, pensize, QtCore.Qt.SolidLine))
     qpainter.drawEllipse(self.center, size_x / 2, size_y / 2)
     text_pos = QPointF(self.center)
     text_pos.setX(text_pos.x() - size_x / 2 + 10)
     text_pos.setY(text_pos.y() + pensize)
     qpainter.drawText(text_pos, str(self.queue_size))
     qpainter.setPen(
         QtGui.QPen(PipelineDrawer.blue_cta, 3, QtCore.Qt.SolidLine))
Beispiel #18
0
def path_link_disabled(basepath):
    """
    Return a QPainterPath 'styled' to indicate a 'disabled' link.

    A disabled link is displayed with a single disconnection symbol in the
    middle (--||--)

    Parameters
    ----------
    basepath : QPainterPath
        The base path (a simple curve spine).

    Returns
    -------
    path : QPainterPath
        A 'styled' link path
    """
    segmentlen = basepath.length()
    px = 5

    if segmentlen < 10:
        return QPainterPath(basepath)

    t = (px / 2) / segmentlen
    p1, _ = qpainterpath_simple_split(basepath, 0.50 - t)
    _, p2 = qpainterpath_simple_split(basepath, 0.50 + t)

    angle = -basepath.angleAtPercent(0.5) + 90
    angler = math.radians(angle)
    normal = QPointF(math.cos(angler), math.sin(angler))

    end1 = p1.currentPosition()
    start2 = QPointF(p2.elementAt(0).x, p2.elementAt(0).y)
    p1.moveTo(start2.x(), start2.y())
    p1.addPath(p2)

    def QPainterPath_addLine(path, line):
        path.moveTo(line.p1())
        path.lineTo(line.p2())

    QPainterPath_addLine(p1, QLineF(end1 - normal * 3, end1 + normal * 3))
    QPainterPath_addLine(p1, QLineF(start2 - normal * 3, start2 + normal * 3))
    return p1
Beispiel #19
0
def path_link_disabled(basepath):
    """
    Return a QPainterPath 'styled' to indicate a 'disabled' link.

    A disabled link is displayed with a single disconnection symbol in the
    middle (--||--)

    Parameters
    ----------
    basepath : QPainterPath
        The base path (a simple curve spine).

    Returns
    -------
    path : QPainterPath
        A 'styled' link path
    """
    segmentlen = basepath.length()
    px = 5

    if segmentlen < 10:
        return QPainterPath(basepath)

    t = (px / 2) / segmentlen
    p1, _ = qpainterpath_simple_split(basepath, 0.50 - t)
    _, p2 = qpainterpath_simple_split(basepath, 0.50 + t)

    angle = -basepath.angleAtPercent(0.5) + 90
    angler = math.radians(angle)
    normal = QPointF(math.cos(angler), math.sin(angler))

    end1 = p1.currentPosition()
    start2 = QPointF(p2.elementAt(0).x, p2.elementAt(0).y)
    p1.moveTo(start2.x(), start2.y())
    p1.addPath(p2)

    def QPainterPath_addLine(path, line):
        path.moveTo(line.p1())
        path.lineTo(line.p2())

    QPainterPath_addLine(p1, QLineF(end1 - normal * 3, end1 + normal * 3))
    QPainterPath_addLine(p1, QLineF(start2 - normal * 3, start2 + normal * 3))
    return p1
Beispiel #20
0
class ImageView2D(QGraphicsView):
    focusChanged = pyqtSignal()
    """
    Shows a ImageScene2D to the user and allows for interactive
    scrolling, panning, zooming etc.

    """
    @property
    def sliceShape(self):
        """
        (width, height) of the scene.
        Specifying the shape is necessary to allow for correct
        scrollbars
        """
        return self._sliceShape

    @sliceShape.setter
    def sliceShape(self, s):
        self._sliceShape = s
        self.scene().dataShape = s
        self._crossHairCursor.dataShape = s
        self._sliceIntersectionMarker.dataShape = s

    @property
    def hud(self):
        return self._hud
    @hud.setter
    def hud(self, hud):
        """
        Sets up a heads up display at the upper left corner of the view

        hud -- a QWidget
        """
        self._hud = hud
        self.setLayout(QVBoxLayout())
        self.layout().setContentsMargins(0,0,0,0)
        self.layout().addWidget(self._hud)
        self.layout().addStretch()

        scene = self.scene()
        hud.zoomToFitButtonClicked.connect(self.fitImage)
        hud.resetZoomButtonClicked.connect(self.doScaleTo)
        hud.rotLeftButtonClicked.connect(scene._onRotateLeft)
        hud.rotRightButtonClicked.connect(scene._onRotateRight)
        hud.swapAxesButtonClicked.connect(scene._onSwapAxes)

        scene.axesChanged.connect(hud.setAxes)


    def __init__(self, parent, imagescene2d):
        """
        Constructs a view upon a ImageScene2D

        imagescene2d -- a ImgeScene2D instance
        """

        QGraphicsView.__init__(self, parent)
        self.setScene(imagescene2d)
        self.mousePos = QPointF(0,0)
        # FIXME: These int members shadow QWidget.x() and QWidget.y(), which can lead to confusion when debugging...
        self.x, self.y = (0,0)

        self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)

        self._isRubberBandZoom = False
        self._cursorBackup = None

        #these attributes are exposed as public properties above
        self._sliceShape  = None #2D shape of this view's shown image
        self._slices = None #number of slices that are stacked
        self._hud    = None

        self._crossHairCursor         = None
        self._sliceIntersectionMarker = None

        self._ticker = QTimer(self)
        self._ticker.timeout.connect(self._tickerEvent)
        
        #
        # Setup the Viewport for fast painting
        #
        #With these flags turned on we could handle the drawing of the
        #white background ourselves thus removing the flicker
        #when scrolling fast through the slices
        #self.viewport().setAttribute(Qt.WA_OpaquePaintEvent)
        #self.viewport().setAttribute(Qt.WA_NoSystemBackground)
        #self.viewport().setAttribute(Qt.WA_PaintOnScreen)
        #self.viewport().setAutoFillBackground(False)

        self.setViewportUpdateMode(QGraphicsView.MinimalViewportUpdate)
        #as rescaling images is slow if done in software,
        #we use Qt's built-in background caching mode so that the cached
        #image need only be blitted on the screen when we only move
        #the cursor
        self.setCacheMode(QGraphicsView.CacheBackground)
        self.setRenderHint(QPainter.Antialiasing, False)

        self._crossHairCursor = CrossHairCursor(self.scene())
        self._crossHairCursor.setZValue(99)

        self._sliceIntersectionMarker = SliceIntersectionMarker(self.scene())
        self._sliceIntersectionMarker.setZValue(100)

        self._sliceIntersectionMarker.setVisibility(True)

        #FIXME: this should be private, but is currently used from
        #       within the image scene renderer
        self.tempImageItems = []

        self._zoomFactor = 1.0

        #for panning
        self._lastPanPoint = QPoint()
        self._dragMode = False
        self._deltaPan = QPointF(0,0)

        #FIXME: Is there are more elegant way to handle this?

        self.setMouseTracking(True)

        # invisible cursor to enable custom cursor
        self._hiddenCursor = QCursor(Qt.BlankCursor)
        # For screen recording BlankCursor doesn't work
        #self.hiddenCursor = QCursor(Qt.ArrowCursor)

    def _cleanUp(self):
        self._ticker.stop()
        del self._ticker

    def setZoomFactor(self,zoom):
        if self._hud is not None:
            self._hud.zoomLevelIndicator.updateLevel(zoom)
        self._zoomFactor = zoom

    def indicateSlicingPositionSettled(self, settled):
        self.scene().indicateSlicingPositionSettled(settled)

    def viewportRect(self):
        """
        Return a QRectF giving the part of the scene currently displayed in this
        widget's viewport in the scene's coordinates
        """
        r =  self.mapToScene(self.viewport().geometry()).boundingRect()
        return r

    def mapScene2Data(self, pos):
        return self.scene().scene2data.map(pos)

    def mapMouseCoordinates2Data(self, pos):
        return self.mapScene2Data(self.mapToScene(pos))

    def _panning(self):
        hBar = self.horizontalScrollBar()
        vBar = self.verticalScrollBar()
        vBar.setValue(vBar.value() - self._deltaPan.y())
        if self.isRightToLeft():
            hBar.setValue(hBar.value() + self._deltaPan.x())
        else:
            hBar.setValue(hBar.value() - self._deltaPan.x())

    def _deaccelerate(self, speed, a=1, maxVal=64):
        x = self._qBound(-maxVal, speed.x(), maxVal)
        y = self._qBound(-maxVal, speed.y(), maxVal)
        ax ,ay = self._setdeaccelerateAxAy(speed.x(), speed.y(), a)
        if x > 0:
            x = max(0.0, x - a*ax)
        elif x < 0:
            x = min(0.0, x + a*ax)
        if y > 0:
            y = max(0.0, y - a*ay)
        elif y < 0:
            y = min(0.0, y + a*ay)
        return QPointF(x, y)

    def _qBound(self, minVal, current, maxVal):
        """PyQt4 does not wrap the qBound function from Qt's global namespace
           This is equivalent."""
        return max(min(current, maxVal), minVal)

    def _setdeaccelerateAxAy(self, x, y, a):
        x = abs(x)
        y = abs(y)
        if x > y:
            if y > 0:
                ax = int(x / y)
                if ax != 0:
                    return ax, 1
            else:
                return x/a, 1
        if y > x:
            if x > 0:
                ay = int(y/x)
                if ay != 0:
                    return 1, ay
            else:
                return 1, y/a
        return 1, 1

    def _tickerEvent(self):
        if self._deltaPan.x() == 0.0 and self._deltaPan.y() == 0.0 or self._dragMode == True:
            self._ticker.stop()
        else:
            self._deltaPan = self._deaccelerate(self._deltaPan)
            self._panning()
        
    def zoomOut(self):
        self.doScale(0.9)

    def zoomIn(self):
        self.doScale(1.1)

    def fitImage(self):
        self.fitInView(self.sceneRect(), Qt.KeepAspectRatio)
        width, height = self.size().width() / self.sceneRect().width(), self.height() / self.sceneRect().height()
        self.setZoomFactor(min(width, height))
        

    def centerImage(self):
        self.centerOn(self.sceneRect().width()/2 + self.sceneRect().x(), self.sceneRect().height()/2 + self.sceneRect().y())

    def toggleHud(self):
        if self._hud is not None:
            self._hud.setVisible(not self._hud.isVisible())

    def setHudVisible(self, visible):
        if self._hud is not None:
            self._hud.setVisible(visible)
            
    def hudVisible(self):
        return self._hud.isVisible()

    def focusInEvent(self, event):
        self.setStyleSheet(".QFrame {border: 2px solid white; border-radius: 4px;}")
        self.focusChanged.emit()

    def focusOutEvent(self, event):
        self.setStyleSheet(".QFrame {}")

    def changeViewPort(self,qRectf):
        self.fitInView(qRectf,mode = Qt.KeepAspectRatio)
        width, height = self.size().width() / qRectf.width(), self.height() / qRectf.height()
        self.setZoomFactor(min(width, height))

    def doScale(self, factor):
        self.setZoomFactor(self._zoomFactor * factor)
        self.scale(factor, factor)

    def doScaleTo(self, zoom=1):
        factor = ( 1 / self._zoomFactor ) * zoom
        self.setZoomFactor(zoom)
        self.scale(factor, factor)
Beispiel #21
0
class BrushingModel(QObject):
    brushSizeChanged     = pyqtSignal(int)
    brushColorChanged    = pyqtSignal(QColor)
    brushStrokeAvailable = pyqtSignal(QPointF, object)
    drawnNumberChanged   = pyqtSignal(int)

    minBrushSize       = 1
    maxBrushSize       = 61
    defaultBrushSize   = 3
    defaultDrawnNumber = 1
    defaultColor       = Qt.white
    erasingColor       = Qt.black
    erasingNumber      = 100

    def __init__(self, parent=None):
        QObject.__init__(self, parent=parent)
        self.sliceRect = None
        self.bb    = QRect() #bounding box enclosing the drawing
        self.brushSize = self.defaultBrushSize
        self.drawColor = self.defaultColor
        self._temp_color = None
        self._temp_number = None
        self.drawnNumber = self.defaultDrawnNumber

        self.pos = None
        self.erasing = False
        self._hasMoved = False

        self.drawOnto = None

        #an empty scene, where we add all drawn line segments
        #a QGraphicsLineItem, and which we can use to then
        #render to an image
        self.scene = QGraphicsScene()

    def toggleErase(self):
        self.erasing = not(self.erasing)
        if self.erasing:
            self.setErasing()
        else:
            self.disableErasing()

    def setErasing(self):
        self.erasing = True
        self._temp_color = self.drawColor
        self._temp_number = self.drawnNumber
        self.setBrushColor(self.erasingColor)
        self.brushColorChanged.emit(self.erasingColor)
        self.setDrawnNumber(self.erasingNumber)

    def disableErasing(self):
        self.erasing = False
        self.setBrushColor(self._temp_color)
        self.brushColorChanged.emit(self.drawColor)
        self.setDrawnNumber(self._temp_number)

    def setBrushSize(self, size):
        self.brushSize = size
        self.brushSizeChanged.emit(self.brushSize)

    def setDrawnNumber(self, num):
        self.drawnNumber = num
        self.drawnNumberChanged.emit(num)

    def getBrushSize(self):
        return self.brushSize

    def brushSmaller(self):
        b = self.brushSize
        if b > self.minBrushSize:
            self.setBrushSize(b-1)

    def brushBigger(self):
        b = self.brushSize
        if self.brushSize < self.maxBrushSize:
            self.setBrushSize(b+1)

    def setBrushColor(self, color):
        self.drawColor = color
        self.brushColorChanged.emit(self.drawColor)

    def beginDrawing(self, pos, sliceRect):
        '''

        pos -- QPointF-like
        '''
        self.sliceRect = sliceRect
        self.scene.clear()
        self.bb = QRect()
        self.pos = QPointF(pos.x(), pos.y())
        self._hasMoved = False

    def endDrawing(self, pos):
        has_moved = self._hasMoved # _hasMoved will change after calling moveTo
        if has_moved:
            self.moveTo(pos)
        else:
            assert(self.pos == pos)
            self.moveTo(QPointF(pos.x()+0.0001, pos.y()+0.0001)) # move a little

        # Qt seems to use strange rules for determining which pixels to set when rendering a brush stroke to a QImage.
        # We seem to get better results if we do the following:
        # 1) Slightly offset the source window because apparently there is a small shift in the data
        # 2) Render the scene to an image that is MUCH larger than the scene resolution (4x by 4x)
        # 3) Downsample each 4x4 patch from the large image back to a single pixel in the final image,
        #     applying some threshold to determine if the final pixel is on or off. 

        tempi = QImage(QSize(4*self.bb.width(), 4*self.bb.height()), QImage.Format_ARGB32_Premultiplied) #TODO: format
        tempi.fill(0)
        painter = QPainter(tempi)
        # Offset the source window.  At first I thought the right offset was 0.5, because 
        #  that would seem to make sure points are rounded to pixel CENTERS, but 
        #  experimentation indicates that 0.25 is slightly better for some reason...
        source_rect = QRectF( QPointF(self.bb.x()+0.25, self.bb.y()+0.25), 
                              QSizeF(self.bb.width(), self.bb.height()) )
        target_rect = QRectF( QPointF(0,0),
                             QSizeF(4*self.bb.width(), 4*self.bb.height()) )
        self.scene.render(painter, target=target_rect, source=source_rect)
        painter.end()

        # Now downsample: convert each 4x4 patch into a single pixel by summing and dividing
        ndarr = qimage2ndarray.rgb_view(tempi)[:,:,0].astype(int)
        ndarr = ndarr.reshape( (ndarr.shape[0],) + (ndarr.shape[1]//4,) + (4,) )
        ndarr = ndarr.sum(axis=-1)
        ndarr = ndarr.transpose()
        ndarr = ndarr.reshape( (ndarr.shape[0],) + (ndarr.shape[1]//4,) + (4,) )
        ndarr = ndarr.sum(axis=-1)
        ndarr = ndarr.transpose()
        ndarr //= 4*4

        downsample_threshold = (7./16)*255
        labels = numpy.where(ndarr>=downsample_threshold, numpy.uint8(self.drawnNumber), numpy.uint8(0))
        labels = labels.swapaxes(0,1)
        assert labels.shape[0] == self.bb.width()
        assert labels.shape[1] == self.bb.height()

        ##
        ## ensure that at least one pixel is label when the brush size is 1
        ##
        ## this happens when the user just clicked without moving
        ## in that case the lineitem will be so tiny, that it won't be rendered
        ## into a single pixel by the code above
        if not has_moved and self.brushSize <= 1 and numpy.count_nonzero(labels) == 0:
            labels[labels.shape[0]//2, labels.shape[1]//2] = self.drawnNumber

        self.brushStrokeAvailable.emit(QPointF(self.bb.x(), self.bb.y()), labels)

    def dumpDraw(self, pos):
        res = self.endDrawing(pos)
        self.beginDrawing(pos, self.sliceRect)
        return res

    def moveTo(self, pos):
        #data coordinates
        oldX, oldY = self.pos.x(), self.pos.y()
        x,y = pos.x(), pos.y()

        line = QGraphicsLineItem(oldX, oldY, x, y)
        line.setPen(QPen( QBrush(Qt.white), self.brushSize, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
        self.scene.addItem(line)
        self._hasMoved = True

        #update bounding Box
        if not self.bb.isValid():
            self.bb = QRect(QPoint(oldX,oldY), QSize(1,1))
        #grow bounding box
        self.bb.setLeft(  min(self.bb.left(),   max(0,                   x-self.brushSize//2-1) ) )
        self.bb.setRight( max(self.bb.right(),  min(self.sliceRect[0]-1, x+self.brushSize//2+1) ) )
        self.bb.setTop(   min(self.bb.top(),    max(0,                   y-self.brushSize//2-1) ) )
        self.bb.setBottom(max(self.bb.bottom(), min(self.sliceRect[1]-1, y+self.brushSize//2+1) ) )

        #update/move position
        self.pos = pos
class Node(QGraphicsItem):
    Type = QGraphicsItem.UserType + 1

    def __init__(self, graphWidget, text=""):
        super(Node, self).__init__()

        self.graph = graphWidget
        self.edgeList = []
        self.newPos = QPointF()

        self.setFlag(QGraphicsItem.ItemIsMovable)
        self.setFlag(QGraphicsItem.ItemSendsGeometryChanges)
        self.setCacheMode(QGraphicsItem.DeviceCoordinateCache)
        self.setZValue(1)

        self.text = text
        self.active = False

        self.b = 15

    def type(self):
        return Node.Type

    def addEdge(self, edge):
        self.edgeList.append(edge)
        edge.adjust()

    def setActive(self, value=True):
        self.active = value

    def edges(self):
        return self.edgeList

    def calculateForces(self):
        if not self.scene() or self.scene().mouseGrabberItem() is self:
            self.newPos = self.pos()
            return

        # Sum up all forces pushing this item away.
        xvel = 0.0
        yvel = 0.0
        for item in self.scene().items():
            if not isinstance(item, Node):
                continue

            line = QLineF(self.mapFromItem(item, 0, 0), QPointF(0, 0))
            dx = line.dx()
            dy = line.dy()
            l = 2.0 * (dx * dx + dy * dy)
            if l > 0:
                xvel += (dx * 150.0) / l
                yvel += (dy * 150.0) / l

        # Now subtract all forces pulling items together.
        #weight = (len(self.edgeList) + 1) * 100.0
        for edge in self.edgeList:
            if edge.sourceNode() is self:
                pos = self.mapFromItem(edge.destNode(), 0, 0)
            else:
                weight = edge.weight * 6
                pos = self.mapFromItem(edge.sourceNode(), 0, 0)
                xvel += pos.x() / weight
                yvel += pos.y() / weight

        if qAbs(xvel) < 0.1 and qAbs(yvel) < 0.1:
            xvel = yvel = 0.0

        sceneRect = self.scene().sceneRect()
        self.newPos = self.pos() + QPointF(xvel, yvel)
        self.newPos.setX(
            min(max(self.newPos.x(),
                    sceneRect.left() + 10),
                sceneRect.right() - 10))
        self.newPos.setY(
            min(max(self.newPos.y(),
                    sceneRect.top() + 10),
                sceneRect.bottom() - 10))

    def advance(self):
        if self.newPos == self.pos():
            return False

        self.setPos(self.newPos)
        return True

    def boundingRect(self):
        adjust = 2.0
        return QRectF(-self.b - adjust, -self.b - adjust,
                      2 * self.b + 3 + adjust, 2 * self.b + 3 + adjust)

    def shape(self):
        path = QPainterPath()
        path.addEllipse(-self.b, -self.b, 2 * self.b, 2 * self.b)
        return path

    def paint(self, painter, option, widget):
        self.setZValue(5)
        palette = QPalette()

        painter.setPen(Qt.NoPen)
        painter.setBrush(Qt.darkGray)

        gradient = QRadialGradient(-3, -3, 15)
        if option.state & QStyle.State_Sunken or self.active:
            gradient.setCenter(3, 3)
            gradient.setFocalPoint(3, 3)
            gradient.setColorAt(
                1, palette.color(QPalette.Active, QPalette.Button))
            gradient.setColorAt(
                0, palette.color(QPalette.Active, QPalette.Button))
            pen = QPen(palette.color(QPalette.Active, QPalette.ButtonText), 2)
        else:
            gradient.setColorAt(
                1, palette.color(QPalette.Disabled, QPalette.Button))
            gradient.setColorAt(
                0, palette.color(QPalette.Disabled, QPalette.Button))
            pen = QPen(palette.color(QPalette.Disabled, QPalette.ButtonText),
                       0)

        painter.setBrush(QBrush(gradient))
        painter.setPen(pen)
        painter.drawEllipse(-self.b, -self.b, 2 * self.b, 2 * self.b)

        painter.drawText(self.boundingRect(), Qt.AlignCenter, self.text)

    def itemChange(self, change, value):
        if change == QGraphicsItem.ItemPositionHasChanged:
            for edge in self.edgeList:
                edge.adjust()
                self.graph.itemMoved()

        return super(Node, self).itemChange(change, value)

    def mousePressEvent(self, event):
        self.update()
        super(Node, self).mousePressEvent(event)

    def mouseReleaseEvent(self, event):
        self.update()
        super(Node, self).mouseReleaseEvent(event)
class Edge(QGraphicsItem):
    Pi = math.pi
    TwoPi = 2.0 * Pi

    Type = QGraphicsItem.UserType + 2

    def __init__(self, sourceNode, destNode, state=1, text=""):
        super(Edge, self).__init__()

        self.arrowSize = 15.0
        self.sourcePoint = QPointF()
        self.destPoint = QPointF()

        self.setAcceptedMouseButtons(Qt.NoButton)
        self.source = sourceNode
        self.dest = destNode
        self.source.addEdge(self)
        self.dest.addEdge(self)
        self.adjust()

        self.state = state
        self.text = text

        line = QLineF(sourceNode.pos(), destNode.pos())
        self.weight = line.length()

    def __len__(self):
        line = QLineF(self.sourceNode().pos(), self.destNode().pos())
        return line.length()

    def type(self):
        return Edge.Type

    def sourceNode(self):
        return self.source

    def setSourceNode(self, node):
        self.source = node
        self.adjust()

    def destNode(self):
        return self.dest

    def setDestNode(self, node):
        self.dest = node
        self.adjust()

    def adjust(self):
        if not self.source or not self.dest:
            return

        line = QLineF(self.mapFromItem(self.source, 0, 0),
                      self.mapFromItem(self.dest, 0, 0))
        length = line.length()

        self.prepareGeometryChange()

        if length > 20.0:
            edgeOffset = QPointF((line.dx() * 10) / length,
                                 (line.dy() * 10) / length)

            self.sourcePoint = line.p1() + edgeOffset
            self.destPoint = line.p2() - edgeOffset
        else:
            self.sourcePoint = line.p1()
            self.destPoint = line.p1()

    def setVisible(self, value=True):
        if value:
            self.state = 1
        else:
            self.state = 0

    def setActive(self, value=True):
        if value:
            self.state = 3
        else:
            self.state = 1

    def setTempActive(self, value=True):
        if value:
            self.state = 2
        else:
            self.state = 1

    def boundingRect(self):
        if not self.source or not self.dest:
            return QRectF()

        penWidth = 1.0
        extra = (penWidth + self.arrowSize) / 2.0 + 100

        return QRectF(
            self.sourcePoint,
            QSizeF(self.destPoint.x() - self.sourcePoint.x(),
                   self.destPoint.y() -
                   self.sourcePoint.y())).normalized().adjusted(
                       -extra, -extra, extra, extra)

    def paint(self, painter, option, widget):
        if not self.source or not self.dest:
            return

        # Draw the line itself.
        line = QLineF(self.sourcePoint, self.destPoint)

        if line.length() == 0.0:
            return

        palette = QPalette()

        self.setZValue(self.state)

        if self.state == 3:
            pen = QPen(Qt.red, 2, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)
        if self.state == 2:
            pen = QPen(Qt.red, 2, Qt.DashLine, Qt.RoundCap, Qt.RoundJoin)
        elif self.state == 1:
            pen = QPen(palette.color(QPalette.Disabled, QPalette.WindowText),
                       0, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)
        elif self.state == 0:
            pen = QPen()
            pen.setBrush(QBrush(Qt.NoBrush))

        painter.setPen(pen)

        painter.drawLine(line)

        angle = math.acos(line.dx() / line.length())
        if line.dy() >= 0:
            angle = Edge.TwoPi - angle

        # draw arrowheads
        if self.state == 2 or self.state == 3:

            # Draw the arrows if there's enough room.
            sourceArrowP1 = self.sourcePoint + QPointF(
                math.sin(angle + Edge.Pi / 3) * self.arrowSize,
                math.cos(angle + Edge.Pi / 3) * self.arrowSize)
            sourceArrowP2 = self.sourcePoint + QPointF(
                math.sin(angle + Edge.Pi - Edge.Pi / 3) * self.arrowSize,
                math.cos(angle + Edge.Pi - Edge.Pi / 3) * self.arrowSize)
            destArrowP1 = self.destPoint + QPointF(
                math.sin(angle - Edge.Pi / 3) * self.arrowSize,
                math.cos(angle - Edge.Pi / 3) * self.arrowSize)
            destArrowP2 = self.destPoint + QPointF(
                math.sin(angle - Edge.Pi + Edge.Pi / 3) * self.arrowSize,
                math.cos(angle - Edge.Pi + Edge.Pi / 3) * self.arrowSize)

            painter.setPen(
                QPen(Qt.red, 2, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
            painter.setBrush(QBrush(Qt.red, Qt.SolidPattern))
            painter.drawPolygon(
                QPolygonF([line.p1(), sourceArrowP1, sourceArrowP2]))
            #painter.drawPolygon(QPolygonF([line.p2(), destArrowP1, destArrowP2]))

        if self.state > 0 and self.source > self.dest:
            point = QPointF((self.sourcePoint.x() + self.destPoint.x()) / 2,
                            (self.sourcePoint.y() + self.destPoint.y()) / 2)
            point = QPointF(point.x() + math.sin(angle) * 16,
                            point.y() + math.cos(angle) * 16)
            painter.drawText(point, self.text)
Beispiel #24
0
class DrawManager(QObject):
    """
    DEPRECATED.
    Will be replaced with BrushingModel, BrushingControler, BrushStroke.
    """
     
    brushSizeChanged  = pyqtSignal(int)
    brushColorChanged = pyqtSignal(QColor)
    
    minBrushSize       = 1
    maxBrushSize       = 61
    defaultBrushSize   = 3
    defaultDrawnNumber = 1
    defaultColor       = Qt.white
    erasingColor       = Qt.black
    
    def __init__(self):
        QObject.__init__(self)
        self.shape = None
        self.bb    = QRect() #bounding box enclosing the drawing
        self.brushSize = self.defaultBrushSize
        self.drawColor = self.defaultColor
        self.drawnNumber = self.defaultDrawnNumber

        self.penVis  = QPen(self.drawColor, self.brushSize, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)
        self.penDraw = QPen(self.drawColor, self.brushSize, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)
        self.pos = None
        self.erasing = False
        
        #on which layer do we want to draw when self.drawingEnabled?
        self.drawOnto = None
        
        #an empty scene, where we add all drawn line segments
        #a QGraphicsLineItem, and which we can use to then
        #render to an image
        self.scene = QGraphicsScene()

    def growBoundingBox(self):
        self.bb.setLeft(  max(0, self.bb.left()-self.brushSize-1))
        self.bb.setTop(   max(0, self.bb.top()-self.brushSize-1 ))
        self.bb.setRight( min(self.shape[0], self.bb.right()+self.brushSize+1))
        self.bb.setBottom(min(self.shape[1], self.bb.bottom()+self.brushSize+1))

    def toggleErase(self):
        self.erasing = not(self.erasing)

    def setErasing(self):
        self.erasing = True
        self.brushColorChanged.emit(self.erasingColor)
    
    def disableErasing(self):
        self.erasing = False
        self.brushColorChanged.emit(self.drawColor)

    def setBrushSize(self, size):
        self.brushSize = size
        self.penVis.setWidth(size)
        self.penDraw.setWidth(size)
        self.brushSizeChanged.emit(self.brushSize)
    
    def setDrawnNumber(self, num):
        self.drawnNumber = num
        self.drawnNumberChanged.emit(num)
        
    def getBrushSize(self):
        return self.brushSize
    
    def brushSmaller(self):
        b = self.brushSize
        if b > self.minBrushSize:
            self.setBrushSize(b-1)
        
    def brushBigger(self):
        b = self.brushSize
        if self.brushSize < self.maxBrushSize:
            self.setBrushSize(b+1)
        
    def setBrushColor(self, color):
        self.drawColor = color
        self.penVis.setColor(color)
        self.emit.brushColorChanged(self.drawColor)
        
    def beginDrawing(self, pos, shape):
        self.shape = shape
        self.bb = QRectF(0, 0, self.shape[0], self.shape[1])
        self.scene.clear()
        if self.erasing:
            self.penVis.setColor(self.erasingColor)
        else:
            self.penVis.setColor(self.drawColor)
        self.pos = QPointF(pos.x()+0.0001, pos.y()+0.0001)
        line = self.moveTo(pos)
        return line

    def endDrawing(self, pos):
        self.moveTo(pos)
        self.growBoundingBox()

        tempi = QImage(QSize(self.bb.width(), self.bb.height()), QImage.Format_ARGB32_Premultiplied) #TODO: format
        tempi.fill(0)
        painter = QPainter(tempi)
        
        self.scene.render(painter, QRectF(QPointF(0,0), self.bb.size()), self.bb)
        
        return (self.bb.left(), self.bb.top(), tempi) #TODO: hackish, probably return a class ??

    def dumpDraw(self, pos):
        res = self.endDrawing(pos)
        self.beginDrawing(pos, self.shape)
        return res

    def moveTo(self, pos):    
        lineVis = QGraphicsLineItem(self.pos.x(), self.pos.y(), pos.x(), pos.y())
        lineVis.setPen(self.penVis)
        
        line = QGraphicsLineItem(self.pos.x(), self.pos.y(), pos.x(), pos.y())
        line.setPen(self.penDraw)
        self.scene.addItem(line)

        self.pos = pos
        x = pos.x()
        y = pos.y()
        #update bounding Box :
        if x > self.bb.right():
            self.bb.setRight(x)
        if x < self.bb.left():
            self.bb.setLeft(x)
        if y > self.bb.bottom():
            self.bb.setBottom(y)
        if y < self.bb.top():
            self.bb.setTop(y)
        return lineVis
Beispiel #25
0
    def paint(self, painter, rect, widget_size, draw_highlight):
        GraphXYView.paint(self, painter, rect, widget_size, draw_highlight)
        smooth = 2.5 # this is invert smoothing, increase it to sharpen graph

        if not self.model():
            return

        option = self.viewOptions()

        background = option.palette.base()
        foreground = QPen(option.palette.color(QPalette.Foreground))

        # Handle step by step size increment
        step_width = floor(widget_size.width() / RESIZE_INCREMENT) * RESIZE_INCREMENT
        step_height = floor(widget_size.height() / RESIZE_INCREMENT) * RESIZE_INCREMENT

        if self.model().rowCount(self.rootIndex()) != 0:
            value_max = self.getValueMaxAll()
        else:
            value_max = 0

        # If there is no data yet, or the max is at 0, use a max of 10 to dispaly an axe
        if self.fetcher.fragment.type == 'LoadStream':
            if value_max < 100:
                value_max = 100
        else:
            if value_max == 0:
                value_max = 10

        if len(self.data) != 0:
            xmin = self.getValueMin(0)
            xmax = self.getValueMax(0)
        else:
            return

        if xmin == xmax:
            return # avoid division by 0

        painter.save()

        # Offset to cerrectly center the graph after the resizing due to the step by step increment
        painter.translate((widget_size.width() - step_width) / 2.0, (widget_size.height() - step_height) / 2.0)

        painter.setPen(foreground)

        # Draw lines
        fm = QFontMetrics(painter.font())
        margin_size = fm.width("0")

        if self.fetcher.fragment.type == 'TrafficStream':
            x_axe_off = fm.width(unicode(value_max * 2000))
        else:
            x_axe_off = fm.width(unicode(value_max * 200))

        # Order them by max mean value
        means = {}
        for col in xrange(1, len(self.data[0])):
            means[col] = self.getMeanValue(col)

        def mean_sorter(x, y):
            return cmp(y[1], x[1])

        means_sorted = means.items()
        means_sorted.sort(mean_sorter)

        def slope_by_index(points, index, sens):
            if index >= len(points):
                return 0
            if index == 0:
                return points[index].y() + (points[index+1].y()-points[index].y())/smooth
            if index == (len(points) - 1):
                return points[index].y() - (points[index].y()-points[index-1].y())/smooth
            vpoints = [ points[index-1].y(), points[index].y(), points[index+1].y()]
            if points[index].y() == max(vpoints):
                return points[index].y()
            if points[index].y() == min(vpoints):
                return points[index].y()
            if sens == 0:
                return points[index].y() + (points[index+1].y()-points[index].y())/smooth
                #return points[index].y() + (points[index+1].y() - points[index-1].y()) / (points[index+1].x()-points[index-1].x()) * (points[index+1].x() - points[index].x()) / smooth
            else:
                return points[index].y() - (points[index].y()-points[index-1].y())/smooth
                #return points[index].y() + (points[index+1].y() - points[index-1].y()) / (points[index+1].x()-points[index-1].x()) * (points[index-1].x() - points[index].x()) / smooth

        colors = odict()

        for _col in means_sorted:
            col = _col[0]
            path = QPainterPath()
            path.setFillRule(Qt.WindingFill)
            last_point = None
            height_max = 0.0
            width_max =  step_width - 2 * margin_size - x_axe_off
            height_max = (step_height * (1.0 - TITLE_AREA)) - 2 * margin_size

            points = []

            for row in xrange(len(self.data)):
                index = self.model().index(row, col, self.rootIndex())
                value = index.data().toInt()[0]
                x_value = int(self.data[row][0])
                #print "x=", x_value, "y=", value

                if value >= 0.0:
                    height = height_max * value/value_max

                    x = (float(x_value - xmin) / float(xmax - xmin)) * width_max
                    point = QPointF(x + margin_size + x_axe_off, height_max - height + margin_size)

                    points.append(point)

            for index, point in enumerate(points):
                # draw simple point
                painter.setBrush(QBrush(QColor(self.colours[col]).dark(200)))
                painter.drawEllipse(QRectF(point.x() - 2, point.y() - 2, 4, 4))
            # init drawing
                if index == 0:
                    path.moveTo(point)
                    continue

                px = points[index-1].x() + (points[index].x() - points[index-1].x()) / smooth
                py = slope_by_index(points, index-1, 0)
                c1 = QPointF(px, py)
                px = points[index].x() - ( points[index].x() - points[index-1].x() ) / smooth
                py = slope_by_index(points, index, 1)
                c2 = QPointF(px, py)

                path.cubicTo(c1, c2, point)

            last_point = points[len(points)-1]
            if last_point:
                txt = self.model().headerData(col, Qt.Horizontal).toString()
                txt_width = fm.width(txt)
                txt_height = fm.height()

                colors[txt] = QColor(self.colours[col])
                path.lineTo(QPointF(x + margin_size + x_axe_off, height_max + margin_size))
                path.lineTo(QPointF(margin_size + x_axe_off, height_max + margin_size))

            color = QColor(self.colours[col])
            color.setAlpha(200)

            ## Create the gradient effect
            grad = QLinearGradient(QPointF(0.0, 0.0), QPointF(0.0, height_max))
            grad.setColorAt(1.0, color.dark(150))
            grad.setColorAt(0.95, color)
            grad.setColorAt(0.05, color)
            grad.setColorAt(0.0, Qt.white)

            painter.setBrush(QBrush(grad))

            painter.drawPath(path)

        # Graduations
        nbr_grad = xmax - xmin
        dgrad = 1
        while nbr_grad > 10:
            nbr_grad = floor(nbr_grad / 10)
            dgrad = dgrad * 10

        if nbr_grad <= 2:
            dgrad = dgrad / 10
            nbr_grad = 10

        # Prevent for infinite loops.
        if dgrad < 1:
            dgrad = 1

        dx = (float(dgrad) / float(xmax-xmin)) * width_max
        text_dy = fm.height()

        i = 0
        while (i * dgrad) <= xmax - xmin:

            if self.fetcher.fragment.type != 'TrafficStream' or (self.fetcher.fragment.type == 'TrafficStream' and i % 4 == 0):
                grad_width = fm.width("0")
                painter.drawLine(margin_size + x_axe_off + (i*dx), height_max + margin_size, margin_size + x_axe_off + (i*dx), height_max + margin_size + grad_width)
                text = unicode(int(dgrad * i))

                # Legend drawing:
                painter.translate(margin_size + x_axe_off + (i*dx), height_max + margin_size + 2*grad_width)
                painter.rotate(-45.0)
                int_time = (i * dgrad) + xmin
                text = QString('%ds' % (xmax - int_time))

                txt_width = fm.width(text)
                painter.drawText(QRect(-txt_width, -dx, txt_width, 2*dx), Qt.AlignRight|Qt.AlignVCenter, text)
                painter.rotate(45.0)
                painter.translate(-(margin_size + x_axe_off + (i*dx)), -(height_max + margin_size + 2*grad_width))

            i = i + 1

        interval = 0
        height = height_max + margin_size + txt_width * sin(45) + 30
        for k, v in colors.iteritems():
            painter.setPen(v)
            legendRect = QRect(interval, height, 10, 10)
            painter.drawRect(legendRect)
            painter.fillRect(legendRect, QBrush(v))
            painter.setPen(foreground)
            txt_width = fm.width(k)
            txt_height = fm.height()
            painter.drawText(QRect(interval + 10, height, txt_width, txt_height), Qt.AlignRight|Qt.AlignVCenter, k)
            interval += 20 + txt_width

        painter.translate((step_width - widget_size.width()) / 2.0, (step_height - widget_size.height()) / 2.0)
        painter.restore()
class TreeNode(object):
	"""store data for a class and manage hierarchy"""
	def __init__(self, classid,name,rect=None,color=None,pos=None,accu = 1.0,parent=None):
		super(TreeNode, self).__init__()
		self.classid = classid
		self.name = name
		self.parent = parent
		self.accu = accu
		self.rect = QRectF(*rect) if rect else rect
		self.color = QColor(color) if color else color
		self.pos = QPointF(*pos) if pos else pos 
		self.children = []
		if parent:
			parent.children.append(self)

	# def __init__(self,jsonobj):
	# 	self.__init__(jsonobj['id'],jsonobj['name'],jsonobj['rect'],jsonobj['color'],jsonobj['pos'])
	# 	for child in jsonobj['children']:
	# 		childItem = TreeNode(child)
	# 		self.children.append(childItem)
	# 		childItem.parent = self

	def isLeaf(self):
		if self.children:
			return False
		else:
			return True

	def isRoot(self):
		if self.parent:
			return False
		else:
			return True

	def toJSON(self):
		jsonObj = {}
		jsonObj['id']= self.classid
		jsonObj['name'] = self.name
		jsonObj['rect'] = [self.rect.left(),self.rect.top(),self.rect.width(),self.rect.height()] if self.rect else self.rect
		jsonObj['color'] = str(self.color.name()) if self.color else self.color
		jsonObj['pos'] = [self.pos.x(),self.pos.y()] if isinstance(self.pos,QPointF) else self.pos
		jsonObj['accu'] = self.accu
		jsonObj['children'] = []
		for child in self.children:
			jsonObj['children'].append(child.toJSON())
		return jsonObj

	def __str__(self):
		tempstr = "%s   %s\n" % (self.classid,self.name)
		for child in self.children:
			tempstr+= '   '+str(child)
		return tempstr 

	def findNode(self,id):
		if self.classid == id:
			return self
		for child in self.children:
			result = child.findNode(id)
			if result:
				return result
		return None

	def toplogicalDistance(self,id1,id2):
		if id1==id2:
			return 0
		else:
			node1 = self.findNode(id1)
			node2 = self.findNode(id2)
			if node1 and node2:
				if node1.parent == node2.parent:
					return 1
				else:
					return 2
		return 0

	def matrixDistance(self,id1,id2):
		if id1==id2:
			return 0
		else:
			node1 = self.findNode(id1)
			node2 = self.findNode(id2)
			if node1 and node2:
					pos1 = node1.pos + node1.parent.pos
					pos2 = node2.pos + node2.parent.pos
					return QLineF(pos1,pos2).length()
		return 2000.0

	def transactionPossibility(self,id1,id2):
		node1 = self.findNode(id1)
		node2 = self.findNode(id2)
		if node1 and node2:
			return node1.accu * node2.accu
		else:
			return 1.0
Beispiel #27
0
    def paint(self, painter, _1, _2):
        """ Main-Method of the Pointer-Class <br>
            calculates/renders/draws the Lines of the Arrow
        """
        if self.fromView.collidesWithItem(self.toView):
            return

        # antialiasing makes things look nicer :)
        painter.setRenderHint(QPainter.Antialiasing)

        self.toView.x()
        pM1 = QPointF(self.fromView.x() + self.fromView.size().width() / 2,
                      self.fromView.y() + self.fromView.size().height() / 2)
        pM2 = QPointF(self.toView.x() + self.toView.size().width() / 2,
                      self.toView.y() + self.toView.size().height() / 2)
        deltaX = pM2.x() - pM1.x()
        deltaY = pM2.y() - pM1.y()
        if deltaX == 0:
            deltaX = 0.01
        if deltaY == 0:
            deltaY = 0.01
        if deltaX >= 0:
            if deltaY >= 0:
                # rechts unten
                if deltaX / deltaY >= self.fromView.size().width() / self.fromView.size().height():
                    # Start von rechter Seite
                    pStart = QPointF(pM1.x() + self.fromView.size().width() / 2,
                                     pM1.y() + (self.fromView.size().width() / 2) * (deltaY / deltaX))
                else:
                    # Start von unterer Seite
                    pStart = QPointF(pM1.x() + (self.fromView.size().height() / 2) * (deltaX / deltaY),
                                     pM1.y() + self.fromView.size().height() / 2)

                if deltaX / deltaY >= self.toView.size().width() / self.toView.size().height():
                    # Ende bei linker Seite
                    pEnd = QPointF(pM2.x() - self.toView.size().width() / 2,
                                   pM2.y() - (self.toView.size().width() / 2) * (deltaY / deltaX))
                else:
                    # Ende bei oberer Seite
                    pEnd = QPointF(pM2.x() - (self.toView.size().height() / 2) * (deltaX / deltaY),
                                   pM2.y() - self.toView.size().height() / 2)
            else:
                # rechts oben
                if deltaX / deltaY * -1 >= self.fromView.size().width() / self.fromView.size().height():
                    # Start von rechter Seite
                    pStart = QPointF(pM1.x() + self.fromView.size().width() / 2,
                                     pM1.y() + (self.fromView.size().width() / 2) * (deltaY / deltaX))
                else:
                    # Start von oberer Seite
                    pStart = QPointF(pM1.x() - (self.fromView.size().height() / 2) * (deltaX / deltaY),
                                     pM1.y() - self.fromView.size().height() / 2)

                if deltaX / deltaY * -1 >= self.toView.size().width() / self.toView.size().height():
                    # Ende bei linker Seite
                    pEnd = QPointF(pM2.x() - self.toView.size().width() / 2,
                                   pM2.y() - (self.toView.size().width() / 2) * (deltaY / deltaX))
                else:
                    # Ende bei unterer Seite
                    pEnd = QPointF(pM2.x() + (self.toView.size().height() / 2) * (deltaX / deltaY),
                                   pM2.y() + self.toView.size().height() / 2)
        else:
            if deltaY >= 0:
                # links unten
                if deltaX / deltaY * -1 >= self.fromView.size().width() / self.fromView.size().height():
                    # Start von linker Seite
                    pStart = QPointF(pM1.x() - self.fromView.size().width() / 2,
                                     pM1.y() - (self.fromView.size().width() / 2) * (deltaY / deltaX))
                else:
                    # Start von unterer Seite
                    pStart = QPointF(pM1.x() + (self.fromView.size().height() / 2) * (deltaX / deltaY),
                                     pM1.y() + self.fromView.size().height() / 2)

                if deltaX / deltaY * -1 >= self.toView.size().width() / self.toView.size().height():
                    # Ende bei rechten Seite
                    pEnd = QPointF(pM2.x() + self.toView.size().width() / 2,
                                   pM2.y() + (self.toView.size().width() / 2) * (deltaY / deltaX))
                else:
                    # Ende bei oberer Seite
                    pEnd = QPointF(pM2.x() - (self.toView.size().height() / 2) * (deltaX / deltaY),
                                   pM2.y() - self.toView.size().height() / 2)
            else:
                # links oben
                if deltaX / deltaY >= self.fromView.size().width() / self.fromView.size().height():
                    # Start von linker Seite
                    pStart = QPointF(pM1.x() - self.fromView.size().width() / 2,
                                     pM1.y() - (self.fromView.size().width() / 2) * (deltaY / deltaX))
                else:
                    # Start von oberer Seite
                    pStart = QPointF(pM1.x() - (self.fromView.size().height() / 2) * (deltaX / deltaY),
                                     pM1.y() - self.fromView.size().height() / 2)

                if deltaX / deltaY >= self.toView.size().width() / self.toView.size().height():
                    # Ende bei rechter Seite
                    pEnd = QPointF(pM2.x() + self.toView.size().width() / 2,
                                   pM2.y() + (self.toView.size().width() / 2) * (deltaY / deltaX))
                else:
                    # Ende bei unterer Seite
                    pEnd = QPointF(pM2.x() + (self.toView.size().height() / 2) * (deltaX / deltaY),
                                   pM2.y() + self.toView.size().height() / 2)

        self.setLine(QLineF(pEnd, pStart))

        if self.line().length() != 0:
            angle = math.acos(self.line().dx() / self.line().length())
            if self.line().dy() >= 0:
                angle = math.pi * 2 - angle

            arrowP1 = self.line().p1() + QPointF(math.sin(angle + math.pi / 2.5) * self.arrowSize, math.cos(angle + math.pi / 2.5) * self.arrowSize)
            arrowP2 = self.line().p1() + QPointF(math.sin(angle + math.pi - math.pi / 2.5) * self.arrowSize, math.cos(angle + math.pi - math.pi / 2.5) * self.arrowSize)
            self.arrowhead.clear()
            self.arrowhead.append(self.line().p1())
            self.arrowhead.append(arrowP1)
            self.arrowhead.append(arrowP2)
            painter.setBrush(QBrush(self.bgcolor))
            painter.drawLine(self.line())
            painter.drawPolygon(self.arrowhead)
Beispiel #28
0
class BrushingModel(QObject):
    brushSizeChanged     = pyqtSignal(int)
    brushColorChanged    = pyqtSignal(QColor)
    brushStrokeAvailable = pyqtSignal(QPointF, object)
    drawnNumberChanged   = pyqtSignal(int)
    
    minBrushSize       = 1
    maxBrushSize       = 61
    defaultBrushSize   = 3
    defaultDrawnNumber = 1
    defaultColor       = Qt.white
    erasingColor       = Qt.black
    erasingNumber      = 100
    
    def __init__(self):
        QObject.__init__(self)
        self.sliceRect = None
        self.bb    = QRect() #bounding box enclosing the drawing
        self.brushSize = self.defaultBrushSize
        self.drawColor = self.defaultColor
        self._temp_color = None
        self._temp_number = None
        self.drawnNumber = self.defaultDrawnNumber

        self.pos = None
        self.erasing = False
        
        self.drawOnto = None
        
        #an empty scene, where we add all drawn line segments
        #a QGraphicsLineItem, and which we can use to then
        #render to an image
        self.scene = QGraphicsScene()

    def toggleErase(self):
        self.erasing = not(self.erasing)
        if self.erasing:
            self.setErasing()
        else:
            self.disableErasing()

    def setErasing(self):
        self.erasing = True
        self._temp_color = self.drawColor
        self._temp_number = self.drawnNumber
        self.setBrushColor(self.erasingColor)
        self.brushColorChanged.emit(self.erasingColor)
        self.setDrawnNumber(self.erasingNumber)
    
    def disableErasing(self):
        self.erasing = False
        self.setBrushColor(self._temp_color)
        self.brushColorChanged.emit(self.drawColor)
        self.setDrawnNumber(self._temp_number)

    def setBrushSize(self, size):
        self.brushSize = size
        self.brushSizeChanged.emit(self.brushSize)
    
    def setDrawnNumber(self, num):
        print "Setting Drawnnumer", num
        self.drawnNumber = num
        self.drawnNumberChanged.emit(num)
        
    def getBrushSize(self):
        return self.brushSize
    
    def brushSmaller(self):
        b = self.brushSize
        if b > self.minBrushSize:
            self.setBrushSize(b-1)
        
    def brushBigger(self):
        b = self.brushSize
        if self.brushSize < self.maxBrushSize:
            self.setBrushSize(b+1)
        
    def setBrushColor(self, color):
        self.drawColor = color
        self.brushColorChanged.emit(self.drawColor)
    
    def beginDrawing(self, pos, sliceRect):
        self.sliceRect = sliceRect
        self.scene.clear()
        self.bb = QRect()
        self.pos = QPointF(pos.x()+0.0001, pos.y()+0.0001)
        line = self.moveTo(pos)
        return line

    def endDrawing(self, pos):
        self.moveTo(pos)

        tempi = QImage(QSize(self.bb.width(), self.bb.height()), QImage.Format_ARGB32_Premultiplied) #TODO: format
        tempi.fill(0)
        painter = QPainter(tempi)
        self.scene.render(painter, target=QRectF(), source=QRectF(QPointF(self.bb.x(), self.bb.y()), QSizeF(self.bb.width(), self.bb.height())))
        painter.end()
        
        ndarr = qimage2ndarray.rgb_view(tempi)[:,:,0]
        labels = numpy.where(ndarr>0,numpy.uint8(self.drawnNumber),numpy.uint8(0))
        labels = labels.swapaxes(0,1)
        assert labels.shape[0] == self.bb.width()
        assert labels.shape[1] == self.bb.height()

        self.brushStrokeAvailable.emit(QPointF(self.bb.x(), self.bb.y()), labels)

    def dumpDraw(self, pos):
        res = self.endDrawing(pos)
        self.beginDrawing(pos, self.sliceRect)
        return res

    def moveTo(self, pos):
        oldX, oldY = self.pos.x(), self.pos.y()
        x,y = pos.x(), pos.y()
        
        #print "BrushingModel.moveTo(pos=%r)" % (pos) 
        line = QGraphicsLineItem(oldX, oldY, x, y)
        line.setPen(QPen( QBrush(Qt.white), self.brushSize, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
        self.scene.addItem(line)

        #update bounding Box 
        if not self.bb.isValid():
            self.bb = QRect(QPoint(x,y), QSize(1,1))
        #grow bounding box
        self.bb.setLeft(  min(self.bb.left(),   max(0,                   x-self.brushSize/2-1) ) )
        self.bb.setRight( max(self.bb.right(),  min(self.sliceRect[0]-1, x+self.brushSize/2+1) ) )
        self.bb.setTop(   min(self.bb.top(),    max(0,                   y-self.brushSize/2-1) ) )
        self.bb.setBottom(max(self.bb.bottom(), min(self.sliceRect[1]-1, y+self.brushSize/2+1) ) )
        
        #update/move position
        self.pos = pos
Beispiel #29
0
class EEdge(QGraphicsObject):
    def __init__(self, head, tail, uuid):
        QGraphicsObject.__init__(self)

        if not issubclass(head.__class__, dict) and not isinstance(
                tail.__class__, dict):
            raise AttributeError

        self.setZValue(0.0)

        self.__kId = uuid
        self.__head = head
        self.__tail = tail

        if head[ENode.kGuiAttributeType].match(EAttribute.kTypeInput):
            self.__head = tail
            self.__tail = head

        self.__head[ENode.kGuiAttributeParent].onMove.connect(self.update)
        self.__tail[ENode.kGuiAttributeParent].onMove.connect(self.update)

        self.__headPoint = QPointF(0.0, 0.0)
        self.__tailPoint = QPointF(0.0, 0.0)

        self.__pen = QPen(QColor(43, 43, 43), 2, Qt.SolidLine)

        self.update()

    @property
    def Id(self):
        return self.__kId

    @property
    def Head(self):
        return self.__head

    @property
    def Tail(self):
        return self.__tail

    def pen(self):
        return self.__pen

    def setPen(self, pen):
        if not isinstance(pen, QPen):
            raise AttributeError

        self.__pen = pen

    def update(self):

        QGraphicsObject.prepareGeometryChange(self)

        self.__headPoint = self.mapFromItem(
            self.__head[ENode.kGuiAttributeParent],
            self.__head[ENode.kGuiAttributePlug])

        self.__tailPoint = self.mapFromItem(
            self.__tail[ENode.kGuiAttributeParent],
            self.__tail[ENode.kGuiAttributePlug])

        self.__headOffsetLine = QLineF(
            self.__headPoint,
            QPointF(self.__headPoint.x() + 15, self.__headPoint.y()))
        self.__tailOffsetLine = QLineF(
            self.__tailPoint,
            QPointF(self.__tailPoint.x() - 15, self.__tailPoint.y()))

        line = QLineF(self.__headPoint, self.__tailPoint)
        self.__line = line

    def boundingRect(self):
        extra = (self.pen().width() * 64) / 2
        return QRectF(
            self.__line.p1(),
            QSizeF(self.__line.p2().x() - self.__line.p1().x(),
                   self.__line.p2().y() -
                   self.__line.p1().y())).normalized().adjusted(
                       -extra, -extra, extra, extra)

    def shape(self):
        return QGraphicsObject.shape(self)

    def drawPath(self, startPoint, endPoint):
        path = QPainterPath()

        one = (QPointF(endPoint.x(), startPoint.y()) + startPoint) / 2
        two = (QPointF(startPoint.x(), endPoint.y()) + endPoint) / 2

        path.moveTo(startPoint)

        angle = math.pi / 2
        bLine1 = QLineF()
        bLine1.setP1(startPoint)

        if startPoint.x() > endPoint.x():
            dist = startPoint.x() - endPoint.x()
            one = (bLine1.p1() +
                   QPointF(math.sin(angle) * dist,
                           math.cos(angle) * dist))
            bLine1.setP1(endPoint)
            two = (bLine1.p1() +
                   QPointF(math.sin(angle) * dist,
                           math.cos(angle) * dist))

        path.cubicTo(one, two, endPoint)
        return path, QLineF(one, two)

    def paint(self, painter, option, widget=None):

        painter.setPen(self.pen())

        headCenter = self.mapFromItem(
            self.__head[ENode.kGuiAttributeParent],
            self.__head[ENode.kGuiAttributeParent].boundingRect().center())

        tailCenter = self.mapFromItem(
            self.__tail[ENode.kGuiAttributeParent],
            self.__tail[ENode.kGuiAttributeParent].boundingRect().center())

        centerPoint = QLineF(headCenter, tailCenter).pointAt(0.5)

        centerPoint.setX(self.__headOffsetLine.p2().x())
        lineFromHead = QLineF(self.__headOffsetLine.p2(), centerPoint)
        centerPoint.setX(self.__tailOffsetLine.p2().x())
        lineFromTail = QLineF(self.__tailOffsetLine.p2(), centerPoint)

        painter.drawPath(
            self.drawPath(self.__headOffsetLine.p1(),
                          self.__tailOffsetLine.p1())[0])
Beispiel #30
0
class BrushingModel(QObject):
    brushSizeChanged = pyqtSignal(int)
    brushColorChanged = pyqtSignal(QColor)
    brushStrokeAvailable = pyqtSignal(QPointF, object)
    drawnNumberChanged = pyqtSignal(int)

    minBrushSize = 1
    maxBrushSize = 61
    defaultBrushSize = 3
    defaultDrawnNumber = 1
    defaultColor = Qt.white
    erasingColor = Qt.black
    erasingNumber = 100

    def __init__(self, parent=None):
        QObject.__init__(self, parent=parent)
        self.sliceRect = None
        self.bb = QRect()  #bounding box enclosing the drawing
        self.brushSize = self.defaultBrushSize
        self.drawColor = self.defaultColor
        self._temp_color = None
        self._temp_number = None
        self.drawnNumber = self.defaultDrawnNumber

        self.pos = None
        self.erasing = False
        self._hasMoved = False

        self.drawOnto = None

        #an empty scene, where we add all drawn line segments
        #a QGraphicsLineItem, and which we can use to then
        #render to an image
        self.scene = QGraphicsScene()

    def toggleErase(self):
        self.erasing = not (self.erasing)
        if self.erasing:
            self.setErasing()
        else:
            self.disableErasing()

    def setErasing(self):
        self.erasing = True
        self._temp_color = self.drawColor
        self._temp_number = self.drawnNumber
        self.setBrushColor(self.erasingColor)
        self.brushColorChanged.emit(self.erasingColor)
        self.setDrawnNumber(self.erasingNumber)

    def disableErasing(self):
        self.erasing = False
        self.setBrushColor(self._temp_color)
        self.brushColorChanged.emit(self.drawColor)
        self.setDrawnNumber(self._temp_number)

    def setBrushSize(self, size):
        self.brushSize = size
        self.brushSizeChanged.emit(self.brushSize)

    def setDrawnNumber(self, num):
        self.drawnNumber = num
        self.drawnNumberChanged.emit(num)

    def getBrushSize(self):
        return self.brushSize

    def brushSmaller(self):
        b = self.brushSize
        if b > self.minBrushSize:
            self.setBrushSize(b - 1)

    def brushBigger(self):
        b = self.brushSize
        if self.brushSize < self.maxBrushSize:
            self.setBrushSize(b + 1)

    def setBrushColor(self, color):
        self.drawColor = color
        self.brushColorChanged.emit(self.drawColor)

    def beginDrawing(self, pos, sliceRect):
        '''

        pos -- QPointF-like
        '''
        self.sliceRect = sliceRect
        self.scene.clear()
        self.bb = QRect()
        self.pos = QPointF(pos.x(), pos.y())
        self._hasMoved = False

    def endDrawing(self, pos):
        has_moved = self._hasMoved  # _hasMoved will change after calling moveTo
        if has_moved:
            self.moveTo(pos)
        else:
            assert (self.pos == pos)
            self.moveTo(QPointF(pos.x() + 0.0001,
                                pos.y() + 0.0001))  # move a little

        # Qt seems to use strange rules for determining which pixels to set when rendering a brush stroke to a QImage.
        # We seem to get better results if we do the following:
        # 1) Slightly offset the source window because apparently there is a small shift in the data
        # 2) Render the scene to an image that is MUCH larger than the scene resolution (4x by 4x)
        # 3) Downsample each 4x4 patch from the large image back to a single pixel in the final image,
        #     applying some threshold to determine if the final pixel is on or off.

        tempi = QImage(QSize(4 * self.bb.width(), 4 * self.bb.height()),
                       QImage.Format_ARGB32_Premultiplied)  #TODO: format
        tempi.fill(0)
        painter = QPainter(tempi)
        # Offset the source window.  At first I thought the right offset was 0.5, because
        #  that would seem to make sure points are rounded to pixel CENTERS, but
        #  experimentation indicates that 0.25 is slightly better for some reason...
        source_rect = QRectF(QPointF(self.bb.x() + 0.25,
                                     self.bb.y() + 0.25),
                             QSizeF(self.bb.width(), self.bb.height()))
        target_rect = QRectF(QPointF(0, 0),
                             QSizeF(4 * self.bb.width(), 4 * self.bb.height()))
        self.scene.render(painter, target=target_rect, source=source_rect)
        painter.end()

        # Now downsample: convert each 4x4 patch into a single pixel by summing and dividing
        ndarr = qimage2ndarray.rgb_view(tempi)[:, :, 0].astype(int)
        ndarr = ndarr.reshape((ndarr.shape[0], ) + (ndarr.shape[1] // 4, ) +
                              (4, ))
        ndarr = ndarr.sum(axis=-1)
        ndarr = ndarr.transpose()
        ndarr = ndarr.reshape((ndarr.shape[0], ) + (ndarr.shape[1] // 4, ) +
                              (4, ))
        ndarr = ndarr.sum(axis=-1)
        ndarr = ndarr.transpose()
        ndarr //= 4 * 4

        downsample_threshold = (7. / 16) * 255
        labels = numpy.where(ndarr >= downsample_threshold,
                             numpy.uint8(self.drawnNumber), numpy.uint8(0))
        labels = labels.swapaxes(0, 1)
        assert labels.shape[0] == self.bb.width()
        assert labels.shape[1] == self.bb.height()

        ##
        ## ensure that at least one pixel is label when the brush size is 1
        ##
        ## this happens when the user just clicked without moving
        ## in that case the lineitem will be so tiny, that it won't be rendered
        ## into a single pixel by the code above
        if not has_moved and self.brushSize <= 1 and numpy.count_nonzero(
                labels) == 0:
            labels[labels.shape[0] // 2,
                   labels.shape[1] // 2] = self.drawnNumber

        self.brushStrokeAvailable.emit(QPointF(self.bb.x(), self.bb.y()),
                                       labels)

    def dumpDraw(self, pos):
        res = self.endDrawing(pos)
        self.beginDrawing(pos, self.sliceRect)
        return res

    def moveTo(self, pos):
        #data coordinates
        oldX, oldY = self.pos.x(), self.pos.y()
        x, y = pos.x(), pos.y()

        line = QGraphicsLineItem(oldX, oldY, x, y)
        line.setPen(
            QPen(QBrush(Qt.white), self.brushSize, Qt.SolidLine, Qt.RoundCap,
                 Qt.RoundJoin))
        self.scene.addItem(line)
        self._hasMoved = True

        #update bounding Box
        if not self.bb.isValid():
            self.bb = QRect(QPoint(oldX, oldY), QSize(1, 1))
        #grow bounding box
        self.bb.setLeft(
            min(self.bb.left(), max(0, x - self.brushSize // 2 - 1)))
        self.bb.setRight(
            max(self.bb.right(),
                min(self.sliceRect[0] - 1, x + self.brushSize // 2 + 1)))
        self.bb.setTop(min(self.bb.top(), max(0, y - self.brushSize // 2 - 1)))
        self.bb.setBottom(
            max(self.bb.bottom(),
                min(self.sliceRect[1] - 1, y + self.brushSize // 2 + 1)))

        #update/move position
        self.pos = pos
Beispiel #31
0
class EEdge(QGraphicsObject):
    def __init__(self, head, tail, uuid, arrowed=False):
        QGraphicsObject.__init__(self)

        self.__arrowed = arrowed

        if not issubclass(head.__class__, dict) and not isinstance(tail.__class__, dict):
            raise AttributeError

        self.setZValue(0.0)

        self.__kId = uuid
        self.__head = head
        self.__tail = tail

        self.__path = QPainterPath()
        self.__headPoint = QPointF(0.0, 0.0)
        self.__tailPoint = QPointF(0.0, 0.0)

        self.__head[ENode.kGuiAttributeParent].onMove.connect(self.update)
        self.__tail[ENode.kGuiAttributeParent].onMove.connect(self.update)

        self.__pen = QPen(QColor(43, 43, 43), 2, Qt.SolidLine)

        self.update()

    @property
    def Id(self):
        return self.__kId

    @property
    def Line(self):
        return QLineF(self.__headPoint, self.__tailPoint)

    @property
    def Head(self):
        return self.__head

    @Head.setter
    def Head(self, newHead):
        self.__head = newHead

    @property
    def Tail(self):
        return self.__tail

    @Tail.setter
    def Tail(self, newTail):
        self.__tail = newTail

    def pen(self):
        return self.__pen

    def setPen(self, pen):
        if not isinstance(pen, QPen):
            raise AttributeError

        self.__pen = pen

    def update(self):

        QGraphicsObject.prepareGeometryChange(self)

        self.__headPoint = self.mapFromItem(self.__head[ENode.kGuiAttributeParent],
                                            self.__head[ENode.kGuiAttributePlug])

        self.__tailPoint = self.mapFromItem(self.__tail[ENode.kGuiAttributeParent],
                                            self.__tail[ENode.kGuiAttributePlug])

        self.__headOffsetLine = QLineF(self.__headPoint, QPointF(self.__headPoint.x() + 15, self.__headPoint.y()))
        self.__tailOffsetLine = QLineF(self.__tailPoint, QPointF(self.__tailPoint.x() - 15, self.__tailPoint.y()))

        line = QLineF(self.__headPoint, self.__tailPoint)
        self.__line = line

    def boundingRect(self):
        extra = (self.pen().width() * 64) / 2
        return QRectF(self.__line.p1(),
                      QSizeF(self.__line.p2().x() - self.__line.p1().x(),
                             self.__line.p2().y() - self.__line.p1().y())).normalized().adjusted(-extra,
                                                                                                 -extra,
                                                                                                 extra,
                                                                                                 extra)

    def shape(self):
        if self.__arrowed:
            return QGraphicsObject.shape(self)

        return QPainterPath(self.__path)

    def getIntersectPoint(self, polygon, point1, point2):

        p1 = polygon[0] + point1
        intersectPoint = QPointF()

        for i in polygon:
            p2 = i + point2
            polyLine = QLineF(p1, p2)

            intersectType = polyLine.intersect(QLineF(point1, point2), intersectPoint)

            if intersectType == QLineF.BoundedIntersection:
                break

            p1 = p2

        return intersectPoint

    def getArrow(self, line):

        Pi = math.pi
        TwoPi = 2.0 * Pi

        arrowSize = 14

        if line.length() > 0:

            angle = math.acos(line.dx() / line.length())
            if line.dy() >= 0:
                angle = TwoPi - angle

            sourceArrowP1 = line.p1() + QPointF(math.sin(angle + Pi / 3) * arrowSize,
                                                math.cos(angle + Pi / 3) * arrowSize)
            sourceArrowP2 = line.p1() + QPointF(math.sin(angle + Pi - Pi / 3) * arrowSize,
                                                math.cos(angle + Pi - Pi / 3) * arrowSize)
            destinationArrowP1 = line.p2() + QPointF(math.sin(angle - Pi / 3) * arrowSize,
                                                     math.cos(angle - Pi / 3) * arrowSize)
            destinationArrowP2 = line.p2() + QPointF(math.sin(angle - Pi + Pi / 3) * arrowSize,
                                                     math.cos(angle - Pi + Pi / 3) * arrowSize)

            arrows = [QPolygonF([line.p1(), sourceArrowP1, sourceArrowP2]),
                      QPolygonF([line.p2(), destinationArrowP1, destinationArrowP2])]

            return arrows[0]

        return QPolygonF()

    def drawPath(self, startPoint, endPoint):
        path = QPainterPath()

        one = (QPointF(endPoint.x(), startPoint.y()) + startPoint) / 2
        two = (QPointF(startPoint.x(), endPoint.y()) + endPoint) / 2

        path.moveTo(startPoint)

        if startPoint.x() > endPoint.x():
            dist = (startPoint.x() - endPoint.x()) * 2

            tLine = QLineF((dist / 2), 0.0, -(dist / 2), 0.0).translated(QLineF(startPoint, endPoint).pointAt(0.5))

            one = tLine.p1()
            two = tLine.p2()

        path.cubicTo(one, two, endPoint)

        self.__path = path
        return path, QLineF(one, two)

    def paint(self, painter, option, widget=None):

        painter.setPen(self.pen())

        if not self.__arrowed:
            headCenter = self.mapFromItem(self.__head[ENode.kGuiAttributeParent],
                                          self.__head[ENode.kGuiAttributeParent].boundingRect().center())

            tailCenter = self.mapFromItem(self.__tail[ENode.kGuiAttributeParent],
                                          self.__tail[ENode.kGuiAttributeParent].boundingRect().center())

            centerPoint = QLineF(headCenter, tailCenter).pointAt(0.5)

            centerPoint.setX(self.__headOffsetLine.p2().x())
            centerPoint.setX(self.__tailOffsetLine.p2().x())

            painter.drawPath(self.drawPath(self.__headOffsetLine.p1(), self.__tailOffsetLine.p1())[0])

        else:

            painter.drawLine(self.__line)
            painter.setPen(Qt.NoPen)
            painter.setBrush(QColor(43, 43, 43))

            headCutPoint = self.getIntersectPoint(self.__head[ENode.kGuiAttributeParent].Polygon,
                                                  self.__line.p1(), self.__line.p2())

            tailCutPoint = self.getIntersectPoint(self.__tail[ENode.kGuiAttributeParent].Polygon,
                                                  self.__line.p2(), self.__line.p1())

            painter.drawPolygon(self.getArrow(QLineF(headCutPoint, tailCutPoint)))
Beispiel #32
0
class TracepointWaveScene(QtGui.QGraphicsScene):
    '''QGraphicsView for wave'''
    def __init__(self, type_, values, duration):
        '''CTOR
           @param type_: string, must be in supported types
           @param values: list of values of variable
           @param duration: integer, holds stepwidth of wave
        '''
        QtGui.QGraphicsScene.__init__(self)
        self.supportedTypes = ["bool", "int", "float", "double"]
        self.vSpace = 10
        self.values = []
        self.curPos = QPointF(0, 2)
        self.type = type_
        self.width = duration
        self.valFont = QtGui.QFont("Arial", 7)
        self.valFontColor = QtGui.QColor()
        self.valFontColor.setGreen(100)
        self.setSceneRect(0, 0, self.width, 15)

        for v in values:
            self.appendValue(v, duration)

    def getSupportedTypes(self):
        ''' Returns supported waveform types
            @return list[string]
        '''
        return self.supportedTypes

    def getType(self):
        ''' @return string identifying type of TracepointWaveScene'''
        return self.type

    def appendValue(self, value, duration):
        ''' Append value to wave
            @param value: value to add
            @param duration: integer, defines duration(length) of value in wave
        '''
        drawEdge = len(
            self.values) > 0 and self.values[len(self.values) - 1] != value
        if drawEdge:
            self.__drawEdge()

        self.values.append(value)
        self.__drawLine(value, duration, drawEdge or len(self.values) == 1)
        self.setSceneRect(0, 0, self.width, 15)

    def __drawEdge(self):
        ''' Draws an edge depending on the type of the waveform. '''
        if self.type == "bool":
            self.addItem(
                QtGui.QGraphicsLineItem(
                    QLineF(
                        self.curPos,
                        QPointF(self.curPos.x(),
                                self.curPos.y() + self.vSpace))))
        elif self.type in self.supportedTypes:
            self.addItem(
                QtGui.QGraphicsLineItem(
                    QLineF(
                        self.curPos,
                        QPointF(self.curPos.x() + 2,
                                self.curPos.y() + self.vSpace))))
            self.addItem(
                QtGui.QGraphicsLineItem(
                    QLineF(
                        QPointF(self.curPos.x() + 2, self.curPos.y()),
                        QPointF(self.curPos.x(),
                                self.curPos.y() + self.vSpace))))

    def __drawLine(self, value, duration, printvalue=True):
        ''' Draws a line depending on the type of the waveform.
            @param value: value to add to wave
            @param duration: integer, defines duration(length) of value in wave
            @param printvalue: bool, add values to waveform (for value-type waveforms only)
        '''
        self.width = self.width + duration
        tmp = self.curPos
        self.curPos = QPointF(self.curPos.x() + duration, self.curPos.y())
        if self.type == "bool":
            if value:
                self.addItem(QtGui.QGraphicsLineItem(QLineF(tmp, self.curPos)))
            else:
                self.addItem(
                    QtGui.QGraphicsLineItem(tmp.x(),
                                            tmp.y() + self.vSpace,
                                            self.curPos.x(),
                                            self.curPos.y() + self.vSpace))
        elif self.type in self.supportedTypes:
            if printvalue:
                text = QtGui.QGraphicsTextItem(str(value))
                text.setFont(self.valFont)
                text.setDefaultTextColor(self.valFontColor)
                text.setPos(QPointF(tmp.x() + 4, tmp.y() - 5))
                self.addItem(text)

            self.addItem(QtGui.QGraphicsLineItem(QLineF(tmp, self.curPos)))
            self.addItem(
                QtGui.QGraphicsLineItem(tmp.x(),
                                        tmp.y() + self.vSpace, self.curPos.x(),
                                        self.curPos.y() + self.vSpace))
Beispiel #33
0
class BrushingModel(QObject):
    brushSizeChanged = pyqtSignal(int)
    brushColorChanged = pyqtSignal(QColor)
    brushStrokeAvailable = pyqtSignal(QPointF, object)
    drawnNumberChanged = pyqtSignal(int)

    minBrushSize = 1
    maxBrushSize = 61
    defaultBrushSize = 3
    defaultDrawnNumber = 1
    defaultColor = Qt.white
    erasingColor = Qt.black
    erasingNumber = 100

    def __init__(self, parent=None):
        QObject.__init__(self, parent=parent)
        self.sliceRect = None
        self.bb = QRect()  #bounding box enclosing the drawing
        self.brushSize = self.defaultBrushSize
        self.drawColor = self.defaultColor
        self._temp_color = None
        self._temp_number = None
        self.drawnNumber = self.defaultDrawnNumber

        self.pos = None
        self.erasing = False
        self._hasMoved = False

        self.drawOnto = None

        #an empty scene, where we add all drawn line segments
        #a QGraphicsLineItem, and which we can use to then
        #render to an image
        self.scene = QGraphicsScene()

    def toggleErase(self):
        self.erasing = not (self.erasing)
        if self.erasing:
            self.setErasing()
        else:
            self.disableErasing()

    def setErasing(self):
        self.erasing = True
        self._temp_color = self.drawColor
        self._temp_number = self.drawnNumber
        self.setBrushColor(self.erasingColor)
        self.brushColorChanged.emit(self.erasingColor)
        self.setDrawnNumber(self.erasingNumber)

    def disableErasing(self):
        self.erasing = False
        self.setBrushColor(self._temp_color)
        self.brushColorChanged.emit(self.drawColor)
        self.setDrawnNumber(self._temp_number)

    def setBrushSize(self, size):
        self.brushSize = size
        self.brushSizeChanged.emit(self.brushSize)

    def setDrawnNumber(self, num):
        self.drawnNumber = num
        self.drawnNumberChanged.emit(num)

    def getBrushSize(self):
        return self.brushSize

    def brushSmaller(self):
        b = self.brushSize
        if b > self.minBrushSize:
            self.setBrushSize(b - 1)

    def brushBigger(self):
        b = self.brushSize
        if self.brushSize < self.maxBrushSize:
            self.setBrushSize(b + 1)

    def setBrushColor(self, color):
        self.drawColor = color
        self.brushColorChanged.emit(self.drawColor)

    def beginDrawing(self, pos, sliceRect):
        '''

        pos -- QPointF-like
        '''
        self.sliceRect = sliceRect
        self.scene.clear()
        self.bb = QRect()
        self.pos = QPointF(pos.x(), pos.y())
        self._hasMoved = False

    def endDrawing(self, pos):
        has_moved = self._hasMoved  # _hasMoved will change after calling moveTo
        if has_moved:
            self.moveTo(pos)
        else:
            assert (self.pos == pos)
            self.moveTo(QPointF(pos.x() + 0.0001,
                                pos.y() + 0.0001))  # move a little

        tempi = QImage(QSize(self.bb.width(), self.bb.height()),
                       QImage.Format_ARGB32_Premultiplied)  #TODO: format
        tempi.fill(0)
        painter = QPainter(tempi)
        self.scene.render(painter,
                          target=QRectF(),
                          source=QRectF(
                              QPointF(self.bb.x(), self.bb.y()),
                              QSizeF(self.bb.width(), self.bb.height())))
        painter.end()

        ndarr = qimage2ndarray.rgb_view(tempi)[:, :, 0]
        labels = numpy.where(ndarr > 0, numpy.uint8(self.drawnNumber),
                             numpy.uint8(0))
        labels = labels.swapaxes(0, 1)
        assert labels.shape[0] == self.bb.width()
        assert labels.shape[1] == self.bb.height()

        ##
        ## ensure that at least one pixel is label when the brush size is 1
        ##
        ## this happens when the user just clicked without moving
        ## in that case the lineitem will be so tiny, that it won't be rendered
        ## into a single pixel by the code above
        if not has_moved and self.brushSize <= 1 and numpy.count_nonzero(
                labels) == 0:
            labels[labels.shape[0] // 2,
                   labels.shape[1] // 2] = self.drawnNumber

        self.brushStrokeAvailable.emit(QPointF(self.bb.x(), self.bb.y()),
                                       labels)

    def dumpDraw(self, pos):
        res = self.endDrawing(pos)
        self.beginDrawing(pos, self.sliceRect)
        return res

    def moveTo(self, pos):
        #data coordinates
        oldX, oldY = self.pos.x(), self.pos.y()
        x, y = pos.x(), pos.y()

        line = QGraphicsLineItem(oldX, oldY, x, y)
        line.setPen(
            QPen(QBrush(Qt.white), self.brushSize, Qt.SolidLine, Qt.RoundCap,
                 Qt.RoundJoin))
        self.scene.addItem(line)
        self._hasMoved = True

        #update bounding Box
        if not self.bb.isValid():
            self.bb = QRect(QPoint(oldX, oldY), QSize(1, 1))
        #grow bounding box
        self.bb.setLeft(min(self.bb.left(), max(0,
                                                x - self.brushSize / 2 - 1)))
        self.bb.setRight(
            max(self.bb.right(),
                min(self.sliceRect[0] - 1, x + self.brushSize / 2 + 1)))
        self.bb.setTop(min(self.bb.top(), max(0, y - self.brushSize / 2 - 1)))
        self.bb.setBottom(
            max(self.bb.bottom(),
                min(self.sliceRect[1] - 1, y + self.brushSize / 2 + 1)))

        #update/move position
        self.pos = pos
Beispiel #34
0
class BrushingModel(QObject):
    brushSizeChanged     = pyqtSignal(int)
    brushColorChanged    = pyqtSignal(QColor)
    brushStrokeAvailable = pyqtSignal(QPointF, object)
    drawnNumberChanged   = pyqtSignal(int)

    minBrushSize       = 1
    maxBrushSize       = 61
    defaultBrushSize   = 3
    defaultDrawnNumber = 1
    defaultColor       = Qt.white
    erasingColor       = Qt.black
    erasingNumber      = 100

    def __init__(self, parent=None):
        QObject.__init__(self, parent=parent)
        self.sliceRect = None
        self.bb    = QRect() #bounding box enclosing the drawing
        self.brushSize = self.defaultBrushSize
        self.drawColor = self.defaultColor
        self._temp_color = None
        self._temp_number = None
        self.drawnNumber = self.defaultDrawnNumber

        self.pos = None
        self.erasing = False
        self._hasMoved = False

        self.drawOnto = None

        #an empty scene, where we add all drawn line segments
        #a QGraphicsLineItem, and which we can use to then
        #render to an image
        self.scene = QGraphicsScene()

    def toggleErase(self):
        self.erasing = not(self.erasing)
        if self.erasing:
            self.setErasing()
        else:
            self.disableErasing()

    def setErasing(self):
        self.erasing = True
        self._temp_color = self.drawColor
        self._temp_number = self.drawnNumber
        self.setBrushColor(self.erasingColor)
        self.brushColorChanged.emit(self.erasingColor)
        self.setDrawnNumber(self.erasingNumber)

    def disableErasing(self):
        self.erasing = False
        self.setBrushColor(self._temp_color)
        self.brushColorChanged.emit(self.drawColor)
        self.setDrawnNumber(self._temp_number)

    def setBrushSize(self, size):
        self.brushSize = size
        self.brushSizeChanged.emit(self.brushSize)

    def setDrawnNumber(self, num):
        self.drawnNumber = num
        self.drawnNumberChanged.emit(num)

    def getBrushSize(self):
        return self.brushSize

    def brushSmaller(self):
        b = self.brushSize
        if b > self.minBrushSize:
            self.setBrushSize(b-1)

    def brushBigger(self):
        b = self.brushSize
        if self.brushSize < self.maxBrushSize:
            self.setBrushSize(b+1)

    def setBrushColor(self, color):
        self.drawColor = color
        self.brushColorChanged.emit(self.drawColor)

    def beginDrawing(self, pos, sliceRect):
        '''

        pos -- QPointF-like
        '''
        self.sliceRect = sliceRect
        self.scene.clear()
        self.bb = QRect()
        self.pos = QPointF(pos.x(), pos.y())
        self._hasMoved = False

    def endDrawing(self, pos):
        has_moved = self._hasMoved # _hasMoved will change after calling moveTo
        if has_moved:
            self.moveTo(pos)
        else:
            assert(self.pos == pos)
            self.moveTo(QPointF(pos.x()+0.0001, pos.y()+0.0001)) # move a little

        tempi = QImage(QSize(self.bb.width(), self.bb.height()), QImage.Format_ARGB32_Premultiplied) #TODO: format
        tempi.fill(0)
        painter = QPainter(tempi)
        self.scene.render(painter, target=QRectF(), source=QRectF(QPointF(self.bb.x(), self.bb.y()), QSizeF(self.bb.width(), self.bb.height())))
        painter.end()

        ndarr = qimage2ndarray.rgb_view(tempi)[:,:,0]
        labels = numpy.where(ndarr>0,numpy.uint8(self.drawnNumber),numpy.uint8(0))
        labels = labels.swapaxes(0,1)
        assert labels.shape[0] == self.bb.width()
        assert labels.shape[1] == self.bb.height()

        ##
        ## ensure that at least one pixel is label when the brush size is 1
        ##
        ## this happens when the user just clicked without moving
        ## in that case the lineitem will be so tiny, that it won't be rendered
        ## into a single pixel by the code above
        if not has_moved and self.brushSize <= 1 and numpy.count_nonzero(labels) == 0:
            labels[labels.shape[0]//2, labels.shape[1]//2] = self.drawnNumber

        self.brushStrokeAvailable.emit(QPointF(self.bb.x(), self.bb.y()), labels)

    def dumpDraw(self, pos):
        res = self.endDrawing(pos)
        self.beginDrawing(pos, self.sliceRect)
        return res

    def moveTo(self, pos):
        #data coordinates
        oldX, oldY = self.pos.x(), self.pos.y()
        x,y = pos.x(), pos.y()

        line = QGraphicsLineItem(oldX, oldY, x, y)
        line.setPen(QPen( QBrush(Qt.white), self.brushSize, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
        self.scene.addItem(line)
        self._hasMoved = True

        #update bounding Box
        if not self.bb.isValid():
            self.bb = QRect(QPoint(oldX,oldY), QSize(1,1))
        #grow bounding box
        self.bb.setLeft(  min(self.bb.left(),   max(0,                   x-self.brushSize/2-1) ) )
        self.bb.setRight( max(self.bb.right(),  min(self.sliceRect[0]-1, x+self.brushSize/2+1) ) )
        self.bb.setTop(   min(self.bb.top(),    max(0,                   y-self.brushSize/2-1) ) )
        self.bb.setBottom(max(self.bb.bottom(), min(self.sliceRect[1]-1, y+self.brushSize/2+1) ) )

        #update/move position
        self.pos = pos
Beispiel #35
0
class Particle(QGraphicsItem):
    def __init__(self, parent=None):
        super(Particle, self).__init__(parent)
        self.effect = QGraphicsBlurEffect()
        self.blur_radius = random.uniform(0.8, 1.6)
        self.effect.setBlurRadius(self.blur_radius)
        self.setGraphicsEffect(self.effect)
        self.height = random.uniform(1, 6)
        self.width = random.uniform(1, 6)
        self.depth = 1
        self.setZValue(self.depth)
        self.newPos = QPointF()
        self.animation_timer = QTimer()
        self.animation_timer.setInterval(1000 / 25)
        self.animation_timer.timeout.connect(self.advance)
        self.animation_timer.start()
        self.speed = 1
        self.next_pos = QPointF(0, 0)
        self.animated = True
        self.max_speed = 3.0
        self.color = QColor(0, 0, 0, 50)
        # self.change_position_timer = QTimer()
        # self.change_position_timer.setInterval(5000)
        # self.change_position_timer.timeout.connect(self.calculate_next_pos)

    def animate(self, bool):
        self.animated = bool

    def calculate_forces(self):

        # Sum up all forces pushing this item away.
        xvel = 0.0
        yvel = 0.0

        line = QtCore.QLineF(self.next_pos, self.pos())
        dx = line.dx()
        dy = line.dy()
        l = math.sqrt(math.pow(dx, 2) + math.pow(dy, 2))
        # l = 2.0 * (dx * dx + dy * dy)
        if l > 0:
            xvel -= (dx * self.speed) / l
            yvel -= (dy * self.speed) / l
        if l < 10:
            self.calculate_next_pos()

        scene_rect = self.scene().sceneRect()
        self.newPos = self.pos() + QtCore.QPointF(xvel, yvel)
        self.newPos.setX(
            min(max(self.newPos.x(),
                    scene_rect.left() + 10),
                scene_rect.right() - 10))
        self.newPos.setY(
            min(max(self.newPos.y(),
                    scene_rect.top() + 10),
                scene_rect.bottom() - 10))

    def calculate_next_pos(self):
        particle_x = random.uniform(-self.scene().sceneRect().width() / 2,
                                    self.scene().sceneRect().width() / 2)
        particle_y = random.uniform(-self.scene().sceneRect().height() / 2,
                                    self.scene().sceneRect().height() / 2)
        self.next_pos = QPointF(particle_x, particle_y)
        self.blur_radius = random.uniform(0.8, 1.6)
        self.effect.setBlurRadius(self.blur_radius)
        self.setGraphicsEffect(self.effect)
        self.height = random.uniform(1, 6)
        self.width = random.uniform(1, 6)
        self.depth = random.uniform(0, 6)
        self.speed *= random.uniform(0.1, 2.0)
        self.speed %= self.max_speed
        # print self.speed

    def advance(self):
        if self.animated:
            self.calculate_forces()
            if self.newPos == self.pos():
                return False

            self.setPos(self.newPos)
            return True
        else:
            return False

    def paint(self, painter, options, widget=None):
        # painter.drawRect(self.boundingRect())
        painter.setBrush(self.color)
        painter.setPen(self.color)
        painter.drawEllipse(-self.width / 2, -self.height / 2, self.width,
                            self.height)

    def boundingRect(self):
        return QRectF(-self.width / 2, -self.height / 2, self.width,
                      self.height)

    def reduce_speed(self, factor=0.9):
        if factor < 1 and factor > 0:
            self.speed *= factor

    def increase_speed(self, factor=1.1):
        if factor > 1:
            self.speed *= factor

    def set_color(self, color):
        self.color = color

    def instant_pos_change(self):
        particle_x = random.uniform(-self.scene().sceneRect().width() / 2,
                                    self.scene().sceneRect().width() / 2)
        particle_y = random.uniform(-self.scene().sceneRect().height() / 2,
                                    self.scene().sceneRect().height() / 2)
        self.setPos(QPointF(particle_x, particle_y))
        self.next_pos = QPointF(particle_x, particle_y)
Beispiel #36
0
class QgsMapToolAnnotation(QgsMapTool):
    def __init__(self, canvas):
        self.mCanvas = canvas
        QgsMapTool.__init__(self, self.mCanvas)
        self.mCurrentMoveAction = QgsAnnotationItem.NoAction
        self.mLastMousePosition = QPointF(0, 0)
        self.mCursor = QCursor(Qt.ArrowCursor)

    def createItem(self, e):
        return None

    def createItemEditor(self, item):
        if (item == None):
            return None
        item._class_ = QgsTextAnnotationItem
        if isinstance(item, QgsTextAnnotationItem):
            return QgsTextAnnotationDialog(item)
        return None

    def canvasReleaseEvent(self, e):
        self.mCurrentMoveAction = QgsAnnotationItem.NoAction
        self.mCanvas.setCursor(self.mCursor)

    def canvasPressEvent(self, e):
        sItem = self.selectedItem()
        if (sItem != None):
            self.mCurrentMoveAction = sItem.moveActionForPosition(e.posF())
            if (self.mCurrentMoveAction != QgsAnnotationItem.NoAction):
                return
        if (sItem == None
                or self.mCurrentMoveAction == QgsAnnotationItem.NoAction):
            self.mCanvas.scene().clearSelection()
            existingItem = self.itemAtPos(e.posF())
            if (existingItem != None):
                existingItem.setSelected(True)
            else:
                self.createItem(e)

    def keyPressEvent(self, e):
        if (e.key() == Qt.Key_T and e.modifiers() == Qt.ControlModifier):
            self.toggleTextItemVisibilities()
        sItem = self.selectedItem()
        if (sItem != None):
            if (e.key() == Qt.Key_Backspace or e.key() == Qt.Key_Delete):
                neutralCursor = QCursor(
                    sItem.cursorShapeForAction(QgsAnnotationItem.NoAction))
                self.mCanvas.scene().removeItem(sItem)
                self.mCanvas.setCursor(neutralCursor)

                #// Override default shortcut management in MapCanvas
                e.ignore()

    def canvasMoveEvent(self, e):
        sItem = self.selectedItem()
        if (sItem != None and (e.buttons() & Qt.LeftButton)):
            if (self.mCurrentMoveAction == QgsAnnotationItem.MoveMapPosition):
                sItem.setMapPosition(self.toMapCoordinates(e.pos()))
                sItem.update()
            elif (self.mCurrentMoveAction ==
                  QgsAnnotationItem.MoveFramePosition):
                if (sItem.mapPositionFixed()):
                    sItem.setOffsetFromReferencePoint(
                        sItem.offsetFromReferencePoint() +
                        (e.posF() - self.mLastMousePosition))
                else:
                    newCanvasPos = sItem.pos() + (e.posF() -
                                                  self.mLastMousePosition)
                    sItem.setMapPosition(
                        self.toMapCoordinates(newCanvasPos.toPoint()))
                sItem.update()
            elif (self.mCurrentMoveAction != QgsAnnotationItem.NoAction):
                size = sItem.frameSize()
                xmin = sItem.offsetFromReferencePoint().x()
                ymin = sItem.offsetFromReferencePoint().y()
                xmax = xmin + size.width()
                ymax = ymin + size.height()

                if (self.mCurrentMoveAction
                        == QgsAnnotationItem.ResizeFrameRight
                        or self.mCurrentMoveAction
                        == QgsAnnotationItem.ResizeFrameRightDown
                        or self.mCurrentMoveAction
                        == QgsAnnotationItem.ResizeFrameRightUp):
                    xmax += e.posF().x() - self.mLastMousePosition.x()
                if (self.mCurrentMoveAction
                        == QgsAnnotationItem.ResizeFrameLeft
                        or self.mCurrentMoveAction
                        == QgsAnnotationItem.ResizeFrameLeftDown
                        or self.mCurrentMoveAction
                        == QgsAnnotationItem.ResizeFrameLeftUp):
                    xmin += e.posF().x() - self.mLastMousePosition.x()
                if (self.mCurrentMoveAction == QgsAnnotationItem.ResizeFrameUp
                        or self.mCurrentMoveAction
                        == QgsAnnotationItem.ResizeFrameLeftUp
                        or self.mCurrentMoveAction
                        == QgsAnnotationItem.ResizeFrameRightUp):
                    ymin += e.posF().y() - self.mLastMousePosition.y()
                if (self.mCurrentMoveAction
                        == QgsAnnotationItem.ResizeFrameDown
                        or self.mCurrentMoveAction
                        == QgsAnnotationItem.ResizeFrameLeftDown
                        or self.mCurrentMoveAction
                        == QgsAnnotationItem.ResizeFrameRightDown):
                    ymax += e.posF().y() - self.mLastMousePosition.y()

                #//switch min / max if necessary
                tmp = 0.0
                if (xmax < xmin):
                    tmp = xmax
                    xmax = xmin
                    xmin = tmp
                if (ymax < ymin):
                    tmp = ymax
                    ymax = ymin
                    ymin = tmp
                sItem.setOffsetFromReferencePoint(QPointF(xmin, ymin))
                sItem.setFrameSize(QSizeF(xmax - xmin, ymax - ymin))
                sItem.update()
        elif (sItem != None):
            moveAction = sItem.moveActionForPosition(e.posF())
            self.mCanvas.setCursor(
                QCursor(sItem.cursorShapeForAction(moveAction)))
        self.mLastMousePosition = e.posF()

    def canvasDoubleClickEvent(self, e):
        item = self.itemAtPos(e.posF())
        if (item == None):
            return
        itemEditor = self.createItemEditor(item)
        if (itemEditor != None):
            itemEditor.exec_()

    def itemAtPos(self, pos):
        graphicItems = self.mCanvas.items(pos.toPoint())
        for annotationItem in graphicItems:
            annotationItem._class_ = QgsAnnotationItem
            if (isinstance(annotationItem, QgsAnnotationItem)):
                return annotationItem
        return None

    def selectedItem(self):
        gItemList = self.mCanvas.scene().selectedItems()
        for aItem in gItemList:
            aItem._class_ = QgsAnnotationItem
            if (isinstance(aItem, QgsAnnotationItem)):
                return aItem
        return None

    def annotationItems(self):
        annotationItemList = []
        itemList = self.mCanvas.scene().items()
        for aItem in itemList:
            aItem._class_ = QgsAnnotationItem
            if (isinstance(aItem, QgsAnnotationItem)):
                self.annotationItemList.append(aItem)
        return annotationItemList

    def toggleTextItemVisibilities(self):
        itemList = self.annotationItems()
        for textItem in itemList:
            textItem._class_ = QgsTextAnnotationItem
            if (isinstance(textItem, QgsTextAnnotationItem)):
                textItem.setVisible(not textItem.isVisible())
Beispiel #37
0
class ImageView2D(QGraphicsView):
    focusChanged = pyqtSignal()
    """
    Shows a ImageScene2D to the user and allows for interactive
    scrolling, panning, zooming etc.

    """
    @property
    def sliceShape(self):
        """
        (width, height) of the scene.
        Specifying the shape is necessary to allow for correct
        scrollbars
        """
        return self._sliceShape

    @sliceShape.setter
    def sliceShape(self, s):
        self._sliceShape = s
        self.scene().dataShape = s
        self._crossHairCursor.dataShape = s
        self._sliceIntersectionMarker.dataShape = s

    @property
    def hud(self):
        return self._hud

    @hud.setter
    def hud(self, hud):
        """
        Sets up a heads up display at the upper left corner of the view

        hud -- a QWidget
        """
        self._hud = hud
        self.setLayout(QVBoxLayout())
        self.layout().setContentsMargins(0, 0, 0, 0)
        self.layout().addWidget(self._hud)
        self.layout().addStretch()

        scene = self.scene()
        hud.zoomToFitButtonClicked.connect(self.fitImage)
        hud.resetZoomButtonClicked.connect(self.doScaleTo)
        hud.rotLeftButtonClicked.connect(scene._onRotateLeft)
        hud.rotRightButtonClicked.connect(scene._onRotateRight)
        hud.swapAxesButtonClicked.connect(scene._onSwapAxes)

        scene.axesChanged.connect(hud.setAxes)

    def __init__(self, parent, imagescene2d):
        """
        Constructs a view upon a ImageScene2D

        imagescene2d -- a ImgeScene2D instance
        """

        QGraphicsView.__init__(self, parent)
        self.setScene(imagescene2d)
        self.mousePos = QPointF(0, 0)
        # FIXME: These int members shadow QWidget.x() and QWidget.y(), which can lead to confusion when debugging...
        self.x, self.y = (0, 0)

        self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)

        self._isRubberBandZoom = False
        self._cursorBackup = None

        #these attributes are exposed as public properties above
        self._sliceShape = None  #2D shape of this view's shown image
        self._slices = None  #number of slices that are stacked
        self._hud = None

        self._crossHairCursor = None
        self._sliceIntersectionMarker = None

        self._ticker = QTimer(self)
        self._ticker.timeout.connect(self._tickerEvent)

        #
        # Setup the Viewport for fast painting
        #
        #With these flags turned on we could handle the drawing of the
        #white background ourselves thus removing the flicker
        #when scrolling fast through the slices
        #self.viewport().setAttribute(Qt.WA_OpaquePaintEvent)
        #self.viewport().setAttribute(Qt.WA_NoSystemBackground)
        #self.viewport().setAttribute(Qt.WA_PaintOnScreen)
        #self.viewport().setAutoFillBackground(False)

        self.setViewportUpdateMode(QGraphicsView.MinimalViewportUpdate)
        #as rescaling images is slow if done in software,
        #we use Qt's built-in background caching mode so that the cached
        #image need only be blitted on the screen when we only move
        #the cursor
        self.setCacheMode(QGraphicsView.CacheBackground)
        self.setRenderHint(QPainter.Antialiasing, False)

        self._crossHairCursor = CrossHairCursor(self.scene())
        self._crossHairCursor.setZValue(99)

        self._sliceIntersectionMarker = SliceIntersectionMarker(self.scene())
        self._sliceIntersectionMarker.setZValue(100)

        self._sliceIntersectionMarker.setVisibility(True)

        #FIXME: this should be private, but is currently used from
        #       within the image scene renderer
        self.tempImageItems = []

        self._zoomFactor = 1.0

        #for panning
        self._lastPanPoint = QPoint()
        self._dragMode = False
        self._deltaPan = QPointF(0, 0)

        #FIXME: Is there are more elegant way to handle this?

        self.setMouseTracking(True)

        # invisible cursor to enable custom cursor
        self._hiddenCursor = QCursor(Qt.BlankCursor)
        # For screen recording BlankCursor doesn't work
        #self.hiddenCursor = QCursor(Qt.ArrowCursor)

    def _cleanUp(self):
        self._ticker.stop()
        del self._ticker

    def setZoomFactor(self, zoom):
        if self._hud is not None:
            self._hud.zoomLevelIndicator.updateLevel(zoom)
        self._zoomFactor = zoom

    def indicateSlicingPositionSettled(self, settled):
        self.scene().indicateSlicingPositionSettled(settled)

    def viewportRect(self):
        """
        Return a QRectF giving the part of the scene currently displayed in this
        widget's viewport in the scene's coordinates
        """
        r = self.mapToScene(self.viewport().geometry()).boundingRect()
        return r

    def mapScene2Data(self, pos):
        return self.scene().scene2data.map(pos)

    def mapMouseCoordinates2Data(self, pos):
        return self.mapScene2Data(self.mapToScene(pos))

    def _panning(self):
        hBar = self.horizontalScrollBar()
        vBar = self.verticalScrollBar()
        vBar.setValue(vBar.value() - self._deltaPan.y())
        if self.isRightToLeft():
            hBar.setValue(hBar.value() + self._deltaPan.x())
        else:
            hBar.setValue(hBar.value() - self._deltaPan.x())

    def _deaccelerate(self, speed, a=1, maxVal=64):
        x = self._qBound(-maxVal, speed.x(), maxVal)
        y = self._qBound(-maxVal, speed.y(), maxVal)
        ax, ay = self._setdeaccelerateAxAy(speed.x(), speed.y(), a)
        if x > 0:
            x = max(0.0, x - a * ax)
        elif x < 0:
            x = min(0.0, x + a * ax)
        if y > 0:
            y = max(0.0, y - a * ay)
        elif y < 0:
            y = min(0.0, y + a * ay)
        return QPointF(x, y)

    def _qBound(self, minVal, current, maxVal):
        """PyQt4 does not wrap the qBound function from Qt's global namespace
           This is equivalent."""
        return max(min(current, maxVal), minVal)

    def _setdeaccelerateAxAy(self, x, y, a):
        x = abs(x)
        y = abs(y)
        if x > y:
            if y > 0:
                ax = int(x / y)
                if ax != 0:
                    return ax, 1
            else:
                return x / a, 1
        if y > x:
            if x > 0:
                ay = int(y / x)
                if ay != 0:
                    return 1, ay
            else:
                return 1, y / a
        return 1, 1

    def _tickerEvent(self):
        if self._deltaPan.x() == 0.0 and self._deltaPan.y(
        ) == 0.0 or self._dragMode == True:
            self._ticker.stop()
        else:
            self._deltaPan = self._deaccelerate(self._deltaPan)
            self._panning()

    def zoomOut(self):
        self.doScale(0.9)

    def zoomIn(self):
        self.doScale(1.1)

    def fitImage(self):
        self.fitInView(self.sceneRect(), Qt.KeepAspectRatio)
        width, height = self.size().width() / self.sceneRect().width(
        ), self.height() / self.sceneRect().height()
        self.setZoomFactor(min(width, height))

    def centerImage(self):
        self.centerOn(self.sceneRect().width() / 2 + self.sceneRect().x(),
                      self.sceneRect().height() / 2 + self.sceneRect().y())

    def toggleHud(self):
        if self._hud is not None:
            self._hud.setVisible(not self._hud.isVisible())

    def setHudVisible(self, visible):
        if self._hud is not None:
            self._hud.setVisible(visible)

    def hudVisible(self):
        return self._hud.isVisible()

    def focusInEvent(self, event):
        self.setStyleSheet(
            ".QFrame {border: 2px solid white; border-radius: 4px;}")
        self.focusChanged.emit()

    def focusOutEvent(self, event):
        self.setStyleSheet(".QFrame {}")

    def changeViewPort(self, qRectf):
        self.fitInView(qRectf, mode=Qt.KeepAspectRatio)
        width, height = self.size().width() / qRectf.width(), self.height(
        ) / qRectf.height()
        self.setZoomFactor(min(width, height))

    def doScale(self, factor):
        self.setZoomFactor(self._zoomFactor * factor)
        self.scale(factor, factor)

    def doScaleTo(self, zoom=1):
        factor = (1 / self._zoomFactor) * zoom
        self.setZoomFactor(zoom)
        self.scale(factor, factor)
Beispiel #38
0
 def render(self):
     """ Main-Method of the Pointer-Class <br>
         calculates/renders/draws the Lines of the Arrow
     """
     points = QPolygonF()
     self.toView.x()
     pM1 = QPointF(self.fromView.x() + self.fromView.size().width()/2,
                   self.fromView.y() + self.fromView.size().height()/2)
     pM2 = QPointF(self.toView.x() + self.toView.size().width()/2,
                   self.toView.y() + self.toView.size().height()/2)
     deltaX = pM2.x()-pM1.x()
     deltaY = pM2.y()-pM1.y()
     if deltaX == 0:
         deltaX = 0.01 
     if deltaY == 0:
         deltaY = 0.01
     if deltaX >= 0:
         if deltaY >= 0:
             # rechts unten
             if deltaX/deltaY >= self.fromView.size().width()/self.fromView.size().height():
                 # Start von rechter Seite
                 pStart = QPointF(pM1.x() + self.fromView.size().width()/2,
                                  pM1.y() + (self.fromView.size().width()/2)*(deltaY/deltaX))
             else:
                 # Start von unterer Seite
                 pStart = QPointF(pM1.x() + (self.fromView.size().height()/2)*(deltaX/deltaY),
                                  pM1.y() + self.fromView.size().height()/2)
             
             if deltaX/deltaY >= self.toView.size().width()/self.toView.size().height():
                 # Ende bei linker Seite
                 pEnd = QPointF(pM2.x() - self.toView.size().width()/2,
                                pM2.y() - (self.toView.size().width()/2)*(deltaY/deltaX))
             else:
                 # Ende bei oberer Seite
                 pEnd = QPointF(pM2.x() - (self.toView.size().height()/2)*(deltaX/deltaY),
                                pM2.y() - self.toView.size().height()/2)
         else:
             # rechts oben
             if deltaX/deltaY*-1 >= self.fromView.size().width()/self.fromView.size().height():
                 # Start von rechter Seite
                 pStart = QPointF(pM1.x() + self.fromView.size().width()/2,
                                  pM1.y() + (self.fromView.size().width()/2)*(deltaY/deltaX))
             else:
                 # Start von oberer Seite
                 pStart = QPointF(pM1.x() - (self.fromView.size().height()/2)*(deltaX/deltaY),
                                  pM1.y() - self.fromView.size().height()/2)
             
             if deltaX/deltaY*-1 >= self.toView.size().width()/self.toView.size().height():
                 # Ende bei linker Seite
                 pEnd = QPointF(pM2.x() - self.toView.size().width()/2,
                                pM2.y() - (self.toView.size().width()/2)*(deltaY/deltaX))
             else:
                 # Ende bei unterer Seite
                 pEnd = QPointF(pM2.x() + (self.toView.size().height()/2)*(deltaX/deltaY),
                                pM2.y() + self.toView.size().height()/2)
     else:
         if deltaY >= 0:
             # links unten
             if deltaX/deltaY*-1 >= self.fromView.size().width()/self.fromView.size().height():
                 # Start von linker Seite
                 pStart = QPointF(pM1.x() - self.fromView.size().width()/2,
                                  pM1.y() - (self.fromView.size().width()/2)*(deltaY/deltaX))
             else:
                 # Start von unterer Seite
                 pStart = QPointF(pM1.x() + (self.fromView.size().height()/2)*(deltaX/deltaY),
                                  pM1.y() + self.fromView.size().height()/2)
             
             if deltaX/deltaY*-1 >= self.toView.size().width()/self.toView.size().height():
                 # Ende bei rechten Seite
                 pEnd = QPointF(pM2.x() + self.toView.size().width()/2,
                                pM2.y() + (self.toView.size().width()/2)*(deltaY/deltaX))
             else:
                 # Ende bei oberer Seite
                 pEnd = QPointF(pM2.x() - (self.toView.size().height()/2)*(deltaX/deltaY),
                                pM2.y() - self.toView.size().height()/2)
         else:
             # links oben
             if deltaX/deltaY >= self.fromView.size().width()/self.fromView.size().height():
                 # Start von linker Seite
                 pStart = QPointF(pM1.x() - self.fromView.size().width()/2,
                                  pM1.y() - (self.fromView.size().width()/2)*(deltaY/deltaX))
             else:
                 # Start von oberer Seite
                 pStart = QPointF(pM1.x() - (self.fromView.size().height()/2)*(deltaX/deltaY),
                                  pM1.y() - self.fromView.size().height()/2)
             
             if deltaX/deltaY >= self.toView.size().width()/self.toView.size().height():
                 # Ende bei rechter Seite
                 pEnd = QPointF(pM2.x() + self.toView.size().width()/2,
                                pM2.y() + (self.toView.size().width()/2)*(deltaY/deltaX))
             else:
                 # Ende bei unterer Seite
                 pEnd = QPointF(pM2.x() + (self.toView.size().height()/2)*(deltaX/deltaY),
                                pM2.y() + self.toView.size().height()/2)
     
     #pStart = QPointF(self.fromView.x() + self.fromView.size().width(),
     #                 self.fromView.y() + self.fromView.size().height()/2)
     #pEnd = QPointF(self.toView.x(), self.toView.y() + self.toView.size().height()/2)
     p1 = pStart
     p2 = QPointF(pEnd.x()-(pEnd.x()-pStart.x())/7, pEnd.y()-(pEnd.y()-pStart.y())/7)
     p3 = QPointF(p2.x()-(p2.y()-p1.y())/20, p2.y()+(p2.x()-p1.x())/20)
     p4 = pEnd
     p5 = QPointF(p2.x()+(p2.y()-p1.y())/20, p2.y()-(p2.x()-p1.x())/20)
     p6 = p2
     points.append(p1)
     points.append(p2)
     points.append(p3)
     points.append(p4)
     points.append(p5)
     points.append(p6)
     self.setPolygon(points)
Beispiel #39
0
class TracepointWaveScene(QtGui.QGraphicsScene):
    '''QGraphicsView for wave'''
    def __init__(self, type_, values, duration):
        '''CTOR
           @param type_: string, must be in supported types
           @param values: list of values of variable
           @param duration: integer, holds stepwidth of wave
        '''
        QtGui.QGraphicsScene.__init__(self)
        self.supportedTypes = ["bool", "int", "float", "double"]
        self.vSpace = 10
        self.values = []
        self.curPos = QPointF(0, 2)
        self.type = type_
        self.width = duration
        self.valFont = QtGui.QFont("Arial", 7)
        self.valFontColor = QtGui.QColor()
        self.valFontColor.setGreen(100)
        self.setSceneRect(0, 0, self.width, 15)

        for v in values:
            self.appendValue(v, duration)

    def getSupportedTypes(self):
        ''' Returns supported waveform types
            @return list[string]
        '''
        return self.supportedTypes

    def getType(self):
        ''' @return string identifying type of TracepointWaveScene'''
        return self.type

    def appendValue(self, value, duration):
        ''' Append value to wave
            @param value: value to add
            @param duration: integer, defines duration(length) of value in wave
        '''
        drawEdge = len(self.values) > 0 and self.values[len(self.values) - 1] != value
        if drawEdge:
            self.__drawEdge()

        self.values.append(value)
        self.__drawLine(value, duration, drawEdge or len(self.values) == 1)
        self.setSceneRect(0, 0, self.width, 15)

    def __drawEdge(self):
        ''' Draws an edge depending on the type of the waveform. '''
        if self.type == "bool":
            self.addItem(QtGui.QGraphicsLineItem(QLineF(self.curPos, QPointF(self.curPos.x(), self.curPos.y() + self.vSpace))))
        elif self.type in self.supportedTypes:
            self.addItem(QtGui.QGraphicsLineItem(QLineF(self.curPos, QPointF(self.curPos.x() + 2, self.curPos.y() + self.vSpace))))
            self.addItem(QtGui.QGraphicsLineItem(QLineF(QPointF(self.curPos.x() + 2, self.curPos.y()), QPointF(self.curPos.x(), self.curPos.y() + self.vSpace))))

    def __drawLine(self, value, duration, printvalue=True):
        ''' Draws a line depending on the type of the waveform.
            @param value: value to add to wave
            @param duration: integer, defines duration(length) of value in wave
            @param printvalue: bool, add values to waveform (for value-type waveforms only)
        '''
        self.width = self.width + duration
        tmp = self.curPos
        self.curPos = QPointF(self.curPos.x() + duration, self.curPos.y())
        if self.type == "bool":
            if value:
                self.addItem(QtGui.QGraphicsLineItem(QLineF(tmp, self.curPos)))
            else:
                self.addItem(QtGui.QGraphicsLineItem(tmp.x(), tmp.y() + self.vSpace, self.curPos.x(), self.curPos.y() + self.vSpace))
        elif self.type in self.supportedTypes:
            if printvalue:
                text = QtGui.QGraphicsTextItem(str(value))
                text.setFont(self.valFont)
                text.setDefaultTextColor(self.valFontColor)
                text.setPos(QPointF(tmp.x() + 4, tmp.y() - 5))
                self.addItem(text)

            self.addItem(QtGui.QGraphicsLineItem(QLineF(tmp, self.curPos)))
            self.addItem(QtGui.QGraphicsLineItem(tmp.x(), tmp.y() + self.vSpace, self.curPos.x(), self.curPos.y() + self.vSpace))
Beispiel #40
0
    def getXover(self, phg, strandtype, fromHelix,\
                       fromIndex, toHelix, toIndex):
        """
        Draws a line from the center of the fromBase (pA) to the
        top or bottom of that same base, depending on its direction (qA),
        then a quad curve to the top or bottom of the toBase (qB), and
        finally to the center of the toBase (pB).
        """
        # if we need to speed this up, we could keep track if pA changed?
        pA = QPointF(*fromHelix.baseLocation(strandtype,\
                                             fromIndex,\
                                             center=True))
        pA = phg.mapFromItem(fromHelix, pA)
        pB = QPointF(*toHelix.baseLocation(strandtype,\
                                           toIndex,\
                                           center=True))
        pB = phg.mapFromItem(toHelix, pB)
        yA = yB = self._baseWidth / 2
        if fromHelix.vhelix().directionOfStrandIs5to3(strandtype):
            orientA = HandleOrient.LeftUp
            yA = -yA
        else:
            orientA = HandleOrient.RightDown
        if toHelix.vhelix().directionOfStrandIs5to3(strandtype):
            orientB = HandleOrient.RightUp
            yB = -yB
        else:
            orientB = HandleOrient.LeftDown

        # Determine start and end points of quad curve
        qA = QPointF(pA.x(), pA.y() + yA)
        qB = QPointF(pB.x(), pB.y() + yB)

        # Determine control point of quad curve
        c1 = QPointF()
        # case 1: same strand
        if fromHelix.number() == toHelix.number():
            if pA.x() < pB.x():  # draw only from left
                if orientA == HandleOrient.LeftUp or \
                   orientA == HandleOrient.RightUp:
                    dx = abs(pB.x() - pA.x())
                    c1.setX(0.5 * (pA.x() + pB.x()))
                    c1.setY(pA.y() - self.yScale * dx)
                # end if
            # end if
        # case 2: same parity
        elif fromHelix.evenParity() == toHelix.evenParity():
            dy = abs(pB.y() - pA.y())
            c1.setX(pA.x() + self.xScale * dy)
            c1.setY(0.5 * (pA.y() + pB.y()))
        # case 3: default
        else:
            if orientA == HandleOrient.LeftUp:
                c1.setX(pA.x() - self.xScale * abs(pB.y() - pA.y()))
            else:
                c1.setX(pA.x() + self.xScale * abs(pB.y() - pA.y()))
            c1.setY(0.5 * (pA.y() + pB.y()))

        # Construct painter path
        painterpath = QPainterPath()
        painterpath.moveTo(pA)
        painterpath.lineTo(qA)
        painterpath.quadTo(c1, qB)
        painterpath.lineTo(pB)
        return painterpath
Beispiel #41
0
 def mouseReleaseEvent(self, event):
     position = QPointF(event.scenePos())
     self.clicked.emit((position.x(), position.y()))
Beispiel #42
0
    def paint(self, painter, _1, _2):
        """ Main-Method of the Pointer-Class <br>
            calculates/renders/draws the Lines of the Arrow
        """
        if self.fromView.collidesWithItem(self.toView):
            return

        # antialiasing makes things look nicer :)
        painter.setRenderHint(QPainter.Antialiasing)

        self.toView.x()
        pM1 = QPointF(self.fromView.x() + self.fromView.size().width() / 2,
                      self.fromView.y() + self.fromView.size().height() / 2)
        pM2 = QPointF(self.toView.x() + self.toView.size().width() / 2,
                      self.toView.y() + self.toView.size().height() / 2)
        deltaX = pM2.x() - pM1.x()
        deltaY = pM2.y() - pM1.y()
        if deltaX == 0:
            deltaX = 0.01
        if deltaY == 0:
            deltaY = 0.01
        if deltaX >= 0:
            if deltaY >= 0:
                # rechts unten
                if deltaX / deltaY >= self.fromView.size().width(
                ) / self.fromView.size().height():
                    # Start von rechter Seite
                    pStart = QPointF(
                        pM1.x() + self.fromView.size().width() / 2,
                        pM1.y() + (self.fromView.size().width() / 2) *
                        (deltaY / deltaX))
                else:
                    # Start von unterer Seite
                    pStart = QPointF(
                        pM1.x() + (self.fromView.size().height() / 2) *
                        (deltaX / deltaY),
                        pM1.y() + self.fromView.size().height() / 2)

                if deltaX / deltaY >= self.toView.size().width(
                ) / self.toView.size().height():
                    # Ende bei linker Seite
                    pEnd = QPointF(
                        pM2.x() - self.toView.size().width() / 2,
                        pM2.y() - (self.toView.size().width() / 2) *
                        (deltaY / deltaX))
                else:
                    # Ende bei oberer Seite
                    pEnd = QPointF(
                        pM2.x() - (self.toView.size().height() / 2) *
                        (deltaX / deltaY),
                        pM2.y() - self.toView.size().height() / 2)
            else:
                # rechts oben
                if deltaX / deltaY * -1 >= self.fromView.size().width(
                ) / self.fromView.size().height():
                    # Start von rechter Seite
                    pStart = QPointF(
                        pM1.x() + self.fromView.size().width() / 2,
                        pM1.y() + (self.fromView.size().width() / 2) *
                        (deltaY / deltaX))
                else:
                    # Start von oberer Seite
                    pStart = QPointF(
                        pM1.x() - (self.fromView.size().height() / 2) *
                        (deltaX / deltaY),
                        pM1.y() - self.fromView.size().height() / 2)

                if deltaX / deltaY * -1 >= self.toView.size().width(
                ) / self.toView.size().height():
                    # Ende bei linker Seite
                    pEnd = QPointF(
                        pM2.x() - self.toView.size().width() / 2,
                        pM2.y() - (self.toView.size().width() / 2) *
                        (deltaY / deltaX))
                else:
                    # Ende bei unterer Seite
                    pEnd = QPointF(
                        pM2.x() + (self.toView.size().height() / 2) *
                        (deltaX / deltaY),
                        pM2.y() + self.toView.size().height() / 2)
        else:
            if deltaY >= 0:
                # links unten
                if deltaX / deltaY * -1 >= self.fromView.size().width(
                ) / self.fromView.size().height():
                    # Start von linker Seite
                    pStart = QPointF(
                        pM1.x() - self.fromView.size().width() / 2,
                        pM1.y() - (self.fromView.size().width() / 2) *
                        (deltaY / deltaX))
                else:
                    # Start von unterer Seite
                    pStart = QPointF(
                        pM1.x() + (self.fromView.size().height() / 2) *
                        (deltaX / deltaY),
                        pM1.y() + self.fromView.size().height() / 2)

                if deltaX / deltaY * -1 >= self.toView.size().width(
                ) / self.toView.size().height():
                    # Ende bei rechten Seite
                    pEnd = QPointF(
                        pM2.x() + self.toView.size().width() / 2,
                        pM2.y() + (self.toView.size().width() / 2) *
                        (deltaY / deltaX))
                else:
                    # Ende bei oberer Seite
                    pEnd = QPointF(
                        pM2.x() - (self.toView.size().height() / 2) *
                        (deltaX / deltaY),
                        pM2.y() - self.toView.size().height() / 2)
            else:
                # links oben
                if deltaX / deltaY >= self.fromView.size().width(
                ) / self.fromView.size().height():
                    # Start von linker Seite
                    pStart = QPointF(
                        pM1.x() - self.fromView.size().width() / 2,
                        pM1.y() - (self.fromView.size().width() / 2) *
                        (deltaY / deltaX))
                else:
                    # Start von oberer Seite
                    pStart = QPointF(
                        pM1.x() - (self.fromView.size().height() / 2) *
                        (deltaX / deltaY),
                        pM1.y() - self.fromView.size().height() / 2)

                if deltaX / deltaY >= self.toView.size().width(
                ) / self.toView.size().height():
                    # Ende bei rechter Seite
                    pEnd = QPointF(
                        pM2.x() + self.toView.size().width() / 2,
                        pM2.y() + (self.toView.size().width() / 2) *
                        (deltaY / deltaX))
                else:
                    # Ende bei unterer Seite
                    pEnd = QPointF(
                        pM2.x() + (self.toView.size().height() / 2) *
                        (deltaX / deltaY),
                        pM2.y() + self.toView.size().height() / 2)

        self.setLine(QLineF(pEnd, pStart))

        if self.line().length() != 0:
            angle = math.acos(self.line().dx() / self.line().length())
            if self.line().dy() >= 0:
                angle = math.pi * 2 - angle

            arrowP1 = self.line().p1() + QPointF(
                math.sin(angle + math.pi / 2.5) * self.arrowSize,
                math.cos(angle + math.pi / 2.5) * self.arrowSize)
            arrowP2 = self.line().p1() + QPointF(
                math.sin(angle + math.pi - math.pi / 2.5) * self.arrowSize,
                math.cos(angle + math.pi - math.pi / 2.5) * self.arrowSize)
            self.arrowhead.clear()
            self.arrowhead.append(self.line().p1())
            self.arrowhead.append(arrowP1)
            self.arrowhead.append(arrowP2)
            painter.setBrush(QBrush(self.bgcolor))
            painter.drawLine(self.line())
            painter.drawPolygon(self.arrowhead)
Beispiel #43
0
    def layout(self,
               scene,
               nodes,
               center=None,
               padX=None,
               padY=None,
               direction=None,
               animationGroup=None):
        """
        Lays out the nodes for this scene based on a block layering algorithm.
        
        :param      scene          | <XNodeScene>
                    nodes          | [<XNode>, ..]
                    center         | <QPointF> || None
                    padX           | <int> || None
                    padY           | <int> || None
                    direction      | <Qt.Direction>
                    animationGroup | <QAnimationGroup> || None
        
        :return     {<XNode>: <QRectF>, ..} | new rects per affected node
        """
        nodes = filter(lambda x: x is not None and x.isVisible(), nodes)

        # make sure we have at least 1 node, otherwise, it is already laid out
        if not nodes or len(nodes) == 1:
            return {}

        # calculate the default padding based on the scene
        if padX == None:
            if direction == Qt.Vertical:
                padX = 2 * scene.cellWidth()
            else:
                padX = 4 * scene.cellWidth()

        if padY == None:
            if direction == Qt.Vertical:
                padY = 4 * scene.cellHeight()
            else:
                padY = 2 * scene.cellWidth()

        # step 1: create a mapping of the connections
        connection_map = self.connectionMap(scene, nodes)

        # step 2: organize the nodes into layers based on their connection chain
        layers = self.generateLayers(scene, nodes, connection_map)
        layers = list(reversed(layers))

        # step 3: calculate the total dimensions for the layout
        bounds = QRectF()

        # step 3.1: compare the nodes together that have common connections
        layer_widths = []
        layer_heights = []
        node_heights = {}
        node_widths = {}

        for layer_index, layer in enumerate(layers):
            layer_w = 0
            layer_h = 0

            layer_node_w = []
            layer_node_h = []

            self.organizeLayer(layer, connection_map)

            for node in layer:
                rect = node.rect()

                layer_node_w.append(rect.width())
                layer_node_h.append(rect.height())

                if direction == Qt.Vertical:
                    layer_w += rect.width()
                    layer_h = max(rect.height(), layer_h)
                else:
                    layer_w = max(rect.width(), layer_w)
                    layer_h += rect.height()

            # update the bounding area
            if direction == Qt.Vertical:
                layer_w += padX * 1 - len(layer)
                bounds.setWidth(max(layer_w, bounds.width()))
                bounds.setHeight(bounds.height() + layer_h)
            else:
                layer_h += padY * 1 - len(layer)
                bounds.setWidth(bounds.width() + layer_w)
                bounds.setHeight(max(layer_h, bounds.height()))

            node_widths[layer_index] = layer_node_w
            node_heights[layer_index] = layer_node_h

            layer_widths.append(layer_w)
            layer_heights.append(layer_h)

        if not center:
            center = scene.sceneRect().center()

        w = bounds.width()
        h = bounds.height()
        bounds.setX(center.x() - bounds.width() / 2.0)
        bounds.setY(center.y() - bounds.height() / 2.0)
        bounds.setWidth(w)
        bounds.setHeight(h)

        # step 4: assign positions for each node by layer
        processed_nodes = {}
        layer_grps = [(i, layer) for i, layer in enumerate(layers)]
        layer_grps.sort(key=lambda x: len(x[1]))

        for layer_index, layer in reversed(layer_grps):
            layer_width = layer_widths[layer_index]
            layer_height = layer_heights[layer_index]

            # determine the starting point for this layer
            if direction == Qt.Vertical:
                offset = layer_index * padY + sum(layer_heights[:layer_index])
                point = QPointF(bounds.x(), offset + bounds.y())
            else:
                offset = layer_index * padX + sum(layer_widths[:layer_index])
                point = QPointF(offset + bounds.x(), bounds.y())

            # assign node positions based on existing connections
            for node_index, node in enumerate(layer):
                max_, min_ = (None, None)
                inputs, outputs = connection_map[node]
                for connected_node in inputs + outputs:
                    if not connected_node in processed_nodes:
                        continue

                    npos = processed_nodes[connected_node]
                    nrect = connected_node.rect()
                    rect = QRectF(npos.x(), npos.y(), nrect.width(),
                                  nrect.height())

                    if direction == Qt.Vertical:
                        if min_ is None:
                            min_ = rect.left()
                        min_ = min(rect.left(), min_)
                        max_ = max(rect.right(), max_)
                    else:
                        if min_ is None:
                            min_ = rect.top()
                        min_ = min(rect.top(), min_)
                        max_ = max(rect.bottom(), max_)

                if direction == Qt.Vertical:
                    off_x = 0
                    off_y = (layer_height - node.rect().height()) / 2.0
                    start_x = (bounds.width() - layer_width)
                    start_y = 0
                else:
                    off_x = (layer_width - node.rect().width()) / 2.0
                    off_y = 0
                    start_x = 0
                    start_y = (bounds.height() - layer_height)

                # align against existing nodes
                if not None in (min_, max):
                    if direction == Qt.Vertical:
                        off_x = (max_ - min_) / 2.0 - node.rect().width() / 2.0
                        point_x = min_ + off_x
                        point_y = point.y() + off_y
                    else:
                        off_y = (max_ -
                                 min_) / 2.0 - node.rect().height() / 2.0
                        point_x = point.x() + off_x
                        point_y = min_ + off_y

                # otherwise, align based on its position in the layer
                else:
                    if direction == Qt.Vertical:
                        off_x = sum(node_widths[layer_index][:node_index])
                        off_x += node_index * padX
                        off_x += start_x

                        point_x = point.x() + off_x
                        point_y = point.y() + off_y
                    else:
                        off_y = sum(node_heights[layer_index][:node_index])
                        off_y += node_index * padY
                        off_y += start_y

                        point_x = point.x() + off_x
                        point_y = point.y() + off_y

                if not animationGroup:
                    node.setPos(point_x, point_y)
                else:
                    anim = XNodeAnimation(node, 'setPos')
                    anim.setStartValue(node.pos())
                    anim.setEndValue(QPointF(point_x, point_y))
                    animationGroup.addAnimation(anim)

                processed_nodes[node] = QPointF(point_x, point_y)

                if self._testing:
                    QApplication.processEvents()
                    time.sleep(1)

        return processed_nodes
Beispiel #44
0
    class DataPlot(Qwt.QwtPlot):
        mouseCoordinatesChanged = Signal(QPointF)
        colors = [Qt.red, Qt.blue, Qt.magenta, Qt.cyan, Qt.green]
        dataNumValuesSaved = 1000
        dataNumValuesPloted = 1000

        def __init__(self, *args):
            super(DataPlot, self).__init__(*args)
            self.setCanvasBackground(Qt.white)
            self.insertLegend(Qwt.QwtLegend(), Qwt.QwtPlot.BottomLegend)

            self.curves = {}
            self.pauseFlag = False
            self.dataOffsetX = 0
            self.canvasOffsetX = 0
            self.canvasOffsetY = 0
            self.lastCanvasX = 0
            self.lastCanvasY = 0
            self.pressedCanvasY = 0
            self.redrawOnEachUpdate = False
            self.redrawOnFullUpdate = True
            self.redrawTimerInterval = None
            self.redrawManually = False
            self.oscilloscopeNextDataPosition = 0
            self.oscilloscopeMode = False
            self.lastClickCoordinates = None

            markerAxisY = Qwt.QwtPlotMarker()
            markerAxisY.setLabelAlignment(Qt.AlignRight | Qt.AlignTop)
            markerAxisY.setLineStyle(Qwt.QwtPlotMarker.HLine)
            markerAxisY.setYValue(0.0)
            markerAxisY.attach(self)

            #self.setAxisTitle(Qwt.QwtPlot.xBottom, "Time")
            #self.setAxisTitle(Qwt.QwtPlot.yLeft, "Value")


            self.picker = Qwt.QwtPlotPicker(
                Qwt.QwtPlot.xBottom, Qwt.QwtPlot.yLeft, Qwt.QwtPicker.PolygonSelection,
                Qwt.QwtPlotPicker.PolygonRubberBand, Qwt.QwtPicker.AlwaysOn, self.canvas()
            )
            self.picker.setRubberBandPen(QPen(self.colors[-1]))
            self.picker.setTrackerPen(QPen(self.colors[-1]))

            # Initialize data
            self.timeAxis = arange(self.dataNumValuesPloted)
            self.canvasDisplayHeight = 1000
            self.canvasDisplayWidth = self.canvas().width()
            self.dataOffsetX = self.dataNumValuesSaved - len(self.timeAxis)
            self.redraw()
            self.moveCanvas(0, 0)
            self.canvas().setMouseTracking(True)
            self.canvas().installEventFilter(self)

            # init and start redraw timer
            self.timerRedraw = QTimer(self)
            self.timerRedraw.timeout.connect(self.redraw)
            if self.redrawTimerInterval:
                self.timerRedraw.start(self.redrawTimerInterval)

        def eventFilter(self, _, event):
            if event.type() == QEvent.MouseButtonRelease:
                x = self.invTransform(Qwt.QwtPlot.xBottom, event.pos().x())
                y = self.invTransform(Qwt.QwtPlot.yLeft, event.pos().y())
                self.lastClickCoordinates = QPointF(x, y)
            elif event.type() == QEvent.MouseMove:
                x = self.invTransform(Qwt.QwtPlot.xBottom, event.pos().x())
                y = self.invTransform(Qwt.QwtPlot.yLeft, event.pos().y())
                coords = QPointF(x, y)
                if self.picker.isActive() and self.lastClickCoordinates is not None:
                    toolTip = 'origin x: %.5f, y: %.5f' % (self.lastClickCoordinates.x(), self.lastClickCoordinates.y())
                    delta = coords - self.lastClickCoordinates
                    toolTip += '\ndelta x: %.5f, y: %.5f\nlength: %.5f' % (delta.x(), delta.y(), math.sqrt(delta.x() ** 2 + delta.y() ** 2))
                else:
                    toolTip = 'buttons\nleft: measure\nmiddle: move\nright: zoom x/y\nwheel: zoom y'
                self.setToolTip(toolTip)
                self.mouseCoordinatesChanged.emit(coords)
            return False

        def setRedrawInterval(self, interval):
            self.redrawTimerInterval = interval
            if self.redrawTimerInterval:
                self.redrawOnEachUpdate = False
                self.redrawOnFullUpdate = False
                self.timerRedraw.start(self.redrawTimerInterval)

        def resizeEvent(self, event):
            super(DataPlot, self).resizeEvent(event)
            self.rescale()

        def getCurves(self):
            return self.curves

        def addCurve(self, curveId, curveName):
            curveId = str(curveId)
            if self.curves.get(curveId):
                return
            curveObject = Qwt.QwtPlotCurve(curveName)
            curveObject.attach(self)
            curveObject.setPen(QPen(self.colors[len(self.curves.keys()) % len(self.colors)]))
            self.curves[curveId] = {
                'name': curveName,
                'data': zeros(self.dataNumValuesSaved),
                'object': curveObject,
            }

        def removeCurve(self, curveId):
            curveId = str(curveId)
            if curveId in self.curves:
                self.curves[curveId]['object'].hide()
                self.curves[curveId]['object'].attach(None)
                del self.curves[curveId]['object']
                del self.curves[curveId]

        def removeAllCurves(self):
            for curveId in self.curves.keys():
                self.removeCurve(curveId)
            self.clear()

        @Slot(str, float)
        def updateValue(self, curveId, value):
            curveId = str(curveId)
            # update data plot
            if (not self.pauseFlag) and curveId in self.curves:
                if self.oscilloscopeMode:
                    self.curves[curveId]['data'][self.oscilloscopeNextDataPosition] = float(value)
                    # only advance the oscilloscopeNextDataPosition for the first curve in the dict
                    if self.curves.keys()[0] == curveId:
                        self.oscilloscopeNextDataPosition = (self.oscilloscopeNextDataPosition + 1) % len(self.timeAxis)
                else:
                    self.curves[curveId]['data'] = concatenate((self.curves[curveId]['data'][1:], self.curves[curveId]['data'][:1]), 1)
                    self.curves[curveId]['data'][-1] = float(value)

                if not self.redrawManually:
                    if self.redrawOnEachUpdate or (self.redrawOnFullUpdate and self.curves.keys()[0] == curveId):
                        self.redraw()

        @Slot(bool)
        def togglePause(self, enabled):
            self.pauseFlag = enabled

        @Slot(bool)
        def toggleOscilloscopeMode(self, enabled):
            self.oscilloscopeMode = enabled

        def hasCurve(self, curveId):
            curveId = str(curveId)
            return curveId in self.curves

        def redraw(self):
            for curveId in self.curves.keys():
                self.curves[curveId]['object'].setData(self.timeAxis, self.curves[curveId]['data'][self.dataOffsetX : self.dataOffsetX + len(self.timeAxis)])
                #self.curves[curveId]['object'].setStyle(Qwt.QwtPlotCurve.CurveStyle(3))
            self.replot()

        def rescale(self):
            yNumTicks = self.height() / 40
            yLowerLimit = self.canvasOffsetY - (self.canvasDisplayHeight / 2)
            yUpperLimit = self.canvasOffsetY + (self.canvasDisplayHeight / 2)

            # calculate a fitting step size for nice, round tick labels, depending on the displayed value area
            yDelta = yUpperLimit - yLowerLimit
            exponent = int(math.log10(yDelta))
            presicion = -(exponent - 2)
            yStepSize = round(yDelta / yNumTicks, presicion)

            self.setAxisScale(Qwt.QwtPlot.yLeft, yLowerLimit, yUpperLimit, yStepSize)

            self.setAxisScale(Qwt.QwtPlot.xBottom, 0, len(self.timeAxis))
            self.redraw()

        def rescaleAxisX(self, deltaX):
            newLen = len(self.timeAxis) + deltaX
            newLen = max(10, min(newLen, self.dataNumValuesSaved))
            self.timeAxis = arange(newLen)
            self.dataOffsetX = max(0, min(self.dataOffsetX, self.dataNumValuesSaved - len(self.timeAxis)))
            self.rescale()

        def scaleAxisY(self, maxValue):
            self.canvasDisplayHeight = maxValue
            self.rescale()

        def moveCanvas(self, deltaX, deltaY):
            self.dataOffsetX += deltaX * len(self.timeAxis) / float(self.canvas().width())
            self.dataOffsetX = max(0, min(self.dataOffsetX, self.dataNumValuesSaved - len(self.timeAxis)))
            self.canvasOffsetX += deltaX * self.canvasDisplayWidth / self.canvas().width()
            self.canvasOffsetY += deltaY * self.canvasDisplayHeight / self.canvas().height()
            self.rescale()

        def mousePressEvent(self, event):
            self.lastCanvasX = event.x() - self.canvas().x()
            self.lastCanvasY = event.y() - self.canvas().y()
            self.pressedCanvasY = event.y() - self.canvas().y()

        def mouseMoveEvent(self, event):
            canvasX = event.x() - self.canvas().x()
            canvasY = event.y() - self.canvas().y()
            if event.buttons() & Qt.MiddleButton: # middle button moves the canvas
                deltaX = self.lastCanvasX - canvasX
                deltaY = canvasY - self.lastCanvasY
                self.moveCanvas(deltaX, deltaY)
            elif event.buttons() & Qt.RightButton: # right button zooms
                zoomFactor = max(-0.6, min(0.6, (self.lastCanvasY - canvasY) / 20.0 / 2.0))
                deltaY = (self.canvas().height() / 2.0) - self.pressedCanvasY
                self.moveCanvas(0, zoomFactor * deltaY * 1.0225)
                self.scaleAxisY(max(0.005, self.canvasDisplayHeight - (zoomFactor * self.canvasDisplayHeight)))
                self.rescaleAxisX(self.lastCanvasX - canvasX)
            self.lastCanvasX = canvasX
            self.lastCanvasY = canvasY

        def wheelEvent(self, event): # mouse wheel zooms the y-axis
            canvasY = event.y() - self.canvas().y()
            zoomFactor = max(-0.6, min(0.6, (event.delta() / 120) / 6.0))
            deltaY = (self.canvas().height() / 2.0) - canvasY
            self.moveCanvas(0, zoomFactor * deltaY * 1.0225)
            self.scaleAxisY(max(0.0005, self.canvasDisplayHeight - zoomFactor * self.canvasDisplayHeight))
Beispiel #45
0
class ImageView2D(QGraphicsView):
    """
    Shows a ImageScene2D to the user and allows for interactive
    scrolling, panning, zooming etc. It intercepts all meaningful
    events on the widget for further interpretation (e.g. the view
    does not know which slicing axis it represents in the 3D slice
    viewer setting).
    """
    
    #notifies about the relative change in the slicing position
    #that is requested
    changeSliceDelta   = pyqtSignal(int)
    
    drawing            = pyqtSignal(QPointF)
    beginDraw          = pyqtSignal(QPointF, object)
    endDraw            = pyqtSignal(QPointF)
    
    erasingToggled     = pyqtSignal(bool)            
    
    #notifies that the mouse has moved to 2D coordinate x,y
    mouseMoved         = pyqtSignal(int, int)
    #notifies that the user has double clicked on the 2D coordinate x,y    
    mouseDoubleClicked = pyqtSignal(int, int)
    
    drawUpdateInterval = 300 #ms
    
    @property
    def shape(self):
        """
        (width, height) of the scene.
        Specifying the shape is necessary to allow for correct
        scrollbars
        """
        return self._shape
    @shape.setter
    def shape(self, s):
        self._shape = s
        self.scene().shape                  = s
        self._crossHairCursor.shape         = (s[1], s[0])
        self._sliceIntersectionMarker.shape = (s[1], s[0])
    
    #FIXME unused?
    @property
    def name(self):
        return self._name
    @name.setter
    def name(self, n):
        self._name = n
           
    @property
    def hud(self):
        return self._hud
    @hud.setter
    def hud(self, hud):
        """
        Sets up a heads up display at the upper left corner of the view
        
        hud -- a QWidget
        """
        
        self._hud = hud
        self.setLayout(QVBoxLayout())
        self.layout().setContentsMargins(0,0,0,0)
        self.layout().addWidget(self._hud)
        self.layout().addStretch()


    @property
    def drawingEnabled(self):
        return self._drawingEnabled
    @drawingEnabled.setter
    def drawingEnabled(self, enable):
        self._drawingEnabled = enable 

    def __init__(self, imagescene2d, useGL=False):
        """
        Constructs a view upon a ImageScene2D
        
        imagescene2d -- a ImgeScene2D instance
        useGL        -- wether to enable OpenGL rendering
        """
        
        QGraphicsView.__init__(self)
        self._useGL = useGL
        self.setScene(imagescene2d)
        
        #these attributes are exposed as public properties above
        self._shape  = None #2D shape of this view's shown image
        self._slices = None #number of slices that are stacked
        self._name   = ''
        self._hud    = None
        self._drawingEnabled = False
        
        self._crossHairCursor         = None
        self._sliceIntersectionMarker = None
        
        #
        # Setup the Viewport for fast painting
        #
        if self._useGL:
            self.openglWidget = QGLWidget(self)
            self.setViewport(self.openglWidget)
            #we clear the background ourselves
            self.viewport().setAutoFillBackground(False)
            #QGraphicsView cannot use partial updates when using 
            #an OpenGL widget as a viewport
            #http://doc.qt.nokia.com/qq/qq26-openglcanvas.html
            self.setViewportUpdateMode(QGraphicsView.FullViewportUpdate)
        else:
            #Unfortunately, setting these flags has no effect when
            #Cache background is turned on.
            #With these flags turned on we could handle the drawing of the
            #white background ourselves thus removing the flicker
            #when scrolling fast through the slices
            #self.viewport().setAttribute(Qt.WA_OpaquePaintEvent)
            #self.viewport().setAttribute(Qt.WA_NoSystemBackground)
            #self.viewport().setAttribute(Qt.WA_PaintOnScreen)
            #self.viewport().setAutoFillBackground(False)
            
            self.setViewportUpdateMode(QGraphicsView.MinimalViewportUpdate)
            #as rescaling images is slow if done in software,
            #we use Qt's built-in background caching mode so that the cached
            #image need only be blitted on the screen when we only move
            #the cursor
            self.setCacheMode(QGraphicsView.CacheBackground)
        self.setRenderHint(QPainter.Antialiasing, False)
        
        #Intitialize the scene
        if self._useGL:
            self.scene().activateOpenGL( self.openglWidget )

        self._crossHairCursor = CrossHairCursor(self.scene())
        self._crossHairCursor.setZValue(99)
        
        self._sliceIntersectionMarker = SliceIntersectionMarker()
        self._sliceIntersectionMarker.setZValue(100)
        self.scene().addItem(self._sliceIntersectionMarker)
        #FIXME: Use a QAction here so that we do not have to synchronize
        #between this initial state and the toggle button's initial state
        self._sliceIntersectionMarker.setVisibility(True)
 
        #FIXME: this should be private, but is currently used from
        #       within the image scene renderer
        self.tempImageItems = []
        
        self._isDrawing = False
        self._zoomFactor = 1.0
        
        #for panning
        self._lastPanPoint = QPoint()
        self._dragMode = False
        self._deltaPan = QPointF(0,0)
        
        #Unfortunately, setting the style like this make the scroll bars look
        #really crappy...
        #self.setStyleSheet("QWidget:!focus { border: 2px solid " + self._axisColor[self._axis].name() +"; border-radius: 4px; }\
        #                    QWidget:focus { border: 2px solid white; border-radius: 4px; }")

        #FIXME: Is there are more elegant way to handle this?

        self.setMouseTracking(True)

        self._ticker = QTimer(self)
        self._ticker.timeout.connect(self._tickerEvent)
        #label updates while drawing, needed for interactive segmentation
        self._drawTimer = QTimer(self)
        self._drawTimer.timeout.connect(self.notifyDrawing)
        
        # invisible cursor to enable custom cursor
        self._hiddenCursor = QCursor(Qt.BlankCursor)
        # For screen recording BlankCursor doesn't work
        #self.hiddenCursor = QCursor(Qt.ArrowCursor)

        self._tempErase = False
        
    def swapAxes(self):          
        """
        Displays this image as if the x and y axes were swapped.
        """
        #FIXME: This is needed for the current arrangements of the three
        #       3D slice views. Can this be made more elegant
        self.rotate(90.0)
        self.scale(1.0,-1.0)
        
    def _cleanUp(self):        
        self._ticker.stop()
        self._drawTimer.stop()
        del self._drawTimer
        del self._ticker

    def viewportRect(self):
        """
        Return a QRectF giving the part of the scene currently displayed in this
        widget's viewport in the scene's coordinates
        """
        return self.mapToScene(self.viewport().geometry()).boundingRect()

    def saveSlice(self, filename):
        """Legacy code."""
        #print "Saving in ", filename, "slice #", self.sliceNumber, "axis", self._axis
        result_image = QImage(self.scene().image.size(), self.scene().image.format())
        p = QPainter(result_image)
        for patchNr in range(self.patchAccessor.patchCount):
            bounds = self.patchAccessor.getPatchBounds(patchNr)
            if self.openglWidget is None:
                p.drawImage(0, 0, self.scene().image)
            else:
                p.drawImage(bounds[0], bounds[2], self.imagePatches[patchNr])
        p.end()
        #horrible way to transpose an image. but it works.
        transform = QTransform()
        transform.rotate(90)
        result_image = result_image.mirrored()
        result_image = result_image.transformed(transform)
        result_image.save(QString(filename))
   
    def notifyDrawing(self):
        print "ImageView2D.notifyDrawing"
        #FIXME: resurrect
        self.drawing.emit(self.mousePos)
    
    def beginDrawing(self, pos):
        self.mousePos = pos
        self._isDrawing  = True
        self.beginDraw.emit(pos, self.shape)
        
    def endDrawing(self, pos):
        InteractionLogger.log("%f: endDrawing()" % (time.clock()))     
        self._drawTimer.stop()
        self._isDrawing = False
        self.endDraw.emit(pos)

    def wheelEvent(self, event):
        keys = QApplication.keyboardModifiers()
        k_alt = (keys == Qt.AltModifier)
        k_ctrl = (keys == Qt.ControlModifier)

        self.mousePos = self.mapToScene(event.pos())
        grviewCenter  = self.mapToScene(self.viewport().rect().center())

        if event.delta() > 0:
            if k_alt:
                self.changeSlice(10)
            elif k_ctrl:
                scaleFactor = 1.1
                self.doScale(scaleFactor)
            else:
                self.changeSlice(1)
        else:
            if k_alt:
                self.changeSlice(-10)
            elif k_ctrl:
                scaleFactor = 0.9
                self.doScale(scaleFactor)
            else:
                self.changeSlice(-1)
        if k_ctrl:
            mousePosAfterScale = self.mapToScene(event.pos())
            offset = self.mousePos - mousePosAfterScale
            newGrviewCenter = grviewCenter + offset
            self.centerOn(newGrviewCenter)
            self.mouseMoveEvent(event)

    def mousePressEvent(self, event):
        if event.button() == Qt.MidButton:
            self.setCursor(QCursor(Qt.SizeAllCursor))
            self._lastPanPoint = event.pos()
            self._crossHairCursor.setVisible(False)
            self._dragMode = True
            if self._ticker.isActive():
                self._deltaPan = QPointF(0, 0)

        if event.buttons() == Qt.RightButton:
            #make sure that we have the cursor at the correct position
            #before we call the context menu
            self.mouseMoveEvent(event)
            self.customContextMenuRequested.emit(event.pos())
            return

        if not self.drawingEnabled:
            print "ImageView2D.mousePressEvent: drawing is not enabled"
            return
        
        if event.buttons() == Qt.LeftButton:
            #don't draw if flicker the view
            if self._ticker.isActive():
                return
            if QApplication.keyboardModifiers() == Qt.ShiftModifier:
                self.erasingToggled.emit(True)
                self._tempErase = True
            mousePos = self.mapToScene(event.pos())
            self.beginDrawing(mousePos)
            
    def mouseMoveEvent(self,event):
        if self._dragMode == True:
            #the mouse was moved because the user wants to change
            #the viewport
            self._deltaPan = QPointF(event.pos() - self._lastPanPoint)
            self._panning()
            self._lastPanPoint = event.pos()
            return
        if self._ticker.isActive():
            #the view is still scrolling
            #do nothing until it comes to a complete stop
            return
        
        self.mousePos = mousePos = self.mapToScene(event.pos())
        x = self.x = mousePos.x()
        y = self.y = mousePos.y()

        self.mouseMoved.emit(x,y)

        if self._isDrawing:
            self.drawing.emit(mousePos)

    def mouseReleaseEvent(self, event):
        if event.button() == Qt.MidButton:
            self.setCursor(QCursor())
            releasePoint = event.pos()
            self._lastPanPoint = releasePoint
            self._dragMode = False
            self._ticker.start(20)
        if self._isDrawing:
            mousePos = self.mapToScene(event.pos())
            self.endDrawing(mousePos)
        if self._tempErase:
            self.erasingToggled.emit(False)
            self._tempErase = False

    def _panning(self):
        hBar = self.horizontalScrollBar()
        vBar = self.verticalScrollBar()
        vBar.setValue(vBar.value() - self._deltaPan.y())
        if self.isRightToLeft():
            hBar.setValue(hBar.value() + self._deltaPan.x())
        else:
            hBar.setValue(hBar.value() - self._deltaPan.x())
        
    def _deaccelerate(self, speed, a=1, maxVal=64):
        x = self._qBound(-maxVal, speed.x(), maxVal)
        y = self._qBound(-maxVal, speed.y(), maxVal)
        ax ,ay = self._setdeaccelerateAxAy(speed.x(), speed.y(), a)
        if x > 0:
            x = max(0.0, x - a*ax)
        elif x < 0:
            x = min(0.0, x + a*ax)
        if y > 0:
            y = max(0.0, y - a*ay)
        elif y < 0:
            y = min(0.0, y + a*ay)
        return QPointF(x, y)

    def _qBound(self, minVal, current, maxVal):
        """PyQt4 does not wrap the qBound function from Qt's global namespace
           This is equivalent."""
        return max(min(current, maxVal), minVal)
    
    def _setdeaccelerateAxAy(self, x, y, a):
        x = abs(x)
        y = abs(y)
        if x > y:
            if y > 0:
                ax = int(x / y)
                if ax != 0:
                    return ax, 1
            else:
                return x/a, 1
        if y > x:
            if x > 0:
                ay = int(y/x)
                if ay != 0:
                    return 1, ay
            else:
                return 1, y/a
        return 1, 1

    def _tickerEvent(self):
        if self._deltaPan.x() == 0.0 and self._deltaPan.y() == 0.0 or self._dragMode == True:
            self._ticker.stop()


        else:
            self._deltaPan = self._deaccelerate(self._deltaPan)
            self._panning()
    
    def mouseDoubleClickEvent(self, event):
        mousePos = self.mapToScene(event.pos())
        self.mouseDoubleClicked.emit(mousePos.x(), mousePos.y())

    def changeSlice(self, delta):
        if self._isDrawing:
            self.endDrawing(self.mousePos)
            self._isDrawing = True
            
            #FIXME:
            #self._drawManager.beginDrawing(self.mousePos, self.self.shape2D)
        
        self.changeSliceDelta.emit(delta)
        
        #FIXME resurrect
        #InteractionLogger.log("%f: changeSliceDelta(axis, num) %d, %d" % (time.clock(), self._axis, delta))
        
    def zoomOut(self):
        self.doScale(0.9)

    def zoomIn(self):
        self.doScale(1.1)
        
    def changeViewPort(self,qRectf):
        self.fitInView(qRectf,mode = Qt.KeepAspectRatio)

    def doScale(self, factor):
        self._zoomFactor = self._zoomFactor * factor
        InteractionLogger.log("%f: zoomFactor(factor) %f" % (time.clock(), self._zoomFactor))     
        self.scale(factor, factor)
Beispiel #46
0
 def save(self, filename):
     growth_num = Result.growth_num
     fdata = StringIO()
     invert_pts, invert_cells = self.data.save(f=fdata)
     f = open(filename, 'w')
     w = csv.writer(f, delimiter=',')
     w.writerow(["TRKR_VERSION", Result.CURRENT_VERSION])
     w.writerow(["Growth computation parameters"])
     hf = self.header_fields
     for h in self.header_order:
         w.writerow([h] + hf[h][0](self))
     w.writerow([])
     w.writerow(["Growth per image"])
     w.writerow(Result.fields)
     wall_shift = self.fields_num["wall"] - 1
     for img_id in range(len(self.images)):
         img = self.images[img_id]
         w.writerow([img])
         cells = self.cells[img_id]
         cells_area = self.cells_area[img_id]
         walls = self.walls[img_id]
         data = self.data[img]
         cells_shape = data.cells
         rows = []
         for c in sorted(cells.keys()):
             # Get the center of mass of the cell
             cell = [data[p] for p in cells_shape[c] if p in data]
             center = QPointF(0, 0)
             area = 0.0
             u1 = cell[-1]
             for i in range(len(cell)):
                 u2 = cell[i]
                 loc_area = u1.x() * u2.y() - u1.y() * u2.x()
                 center += (u1 + u2) * loc_area
                 area += loc_area
                 u1 = u2
             center /= area
             row = [
                 "",
                 "Cell %d" % invert_cells[c], cells_area[c],
                 cells[c][growth_num["kmax"]], cells[c][growth_num["kmin"]],
                 cells[c][growth_num["theta"]] * 180 / pi,
                 cells[c][growth_num["phi"]],
                 center.x(),
                 center.y()
             ]
             rows.append(row)
         lr = len(rows)
         for i, ws in enumerate(sorted(walls.keys())):
             wll = [
                 "",
                 "Wall %d-%d" % (invert_pts[ws[0]], invert_pts[ws[1]]),
                 walls[ws]
             ]
             if i >= lr:
                 rows.append([""] * wall_shift)
             rows[i] += wll
         w.writerows(rows)
     w.writerow(["Actual cell shapes"])
     w.writerow(["Image", "Cell", "Begin/End", "Shape [x y]"])
     for img_id in range(len(self.images)):
         img = self.images[img_id]
         w.writerow([img])
         cells_shapes = self.cells_shapes[img_id]
         rows = []
         for c in sorted(cells_shapes.keys()):
             sh = cells_shapes[c]
             row1 = ["", "Cell %d" % invert_cells[c], "Begin"] + list(
                 sh[0].flatten())
             row2 = ["", "Cell %d" % invert_cells[c], "End"] + list(
                 sh[1].flatten())
             rows.append(row1)
             rows.append(row2)
         w.writerows(rows)
     w.writerow([])
     w.writerow(["Data"])
     f.write(fdata.getvalue())
     f.close()
     fdata.close()
    def paint(self, painter, option, widget):
        if not self.source or not self.dest:
            return

        # Draw the line itself.
        line = QLineF(self.sourcePoint, self.destPoint)

        if line.length() == 0.0:
            return

        palette = QPalette()

        self.setZValue(self.state)

        if self.state == 3:
            pen = QPen(Qt.red, 2, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)
        if self.state == 2:
            pen = QPen(Qt.red, 2, Qt.DashLine, Qt.RoundCap, Qt.RoundJoin)
        elif self.state == 1:
            pen = QPen(palette.color(QPalette.Disabled, QPalette.WindowText),
                       0, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)
        elif self.state == 0:
            pen = QPen()
            pen.setBrush(QBrush(Qt.NoBrush))

        painter.setPen(pen)

        painter.drawLine(line)

        angle = math.acos(line.dx() / line.length())
        if line.dy() >= 0:
            angle = Edge.TwoPi - angle

        # draw arrowheads
        if self.state == 2 or self.state == 3:

            # Draw the arrows if there's enough room.
            sourceArrowP1 = self.sourcePoint + QPointF(
                math.sin(angle + Edge.Pi / 3) * self.arrowSize,
                math.cos(angle + Edge.Pi / 3) * self.arrowSize)
            sourceArrowP2 = self.sourcePoint + QPointF(
                math.sin(angle + Edge.Pi - Edge.Pi / 3) * self.arrowSize,
                math.cos(angle + Edge.Pi - Edge.Pi / 3) * self.arrowSize)
            destArrowP1 = self.destPoint + QPointF(
                math.sin(angle - Edge.Pi / 3) * self.arrowSize,
                math.cos(angle - Edge.Pi / 3) * self.arrowSize)
            destArrowP2 = self.destPoint + QPointF(
                math.sin(angle - Edge.Pi + Edge.Pi / 3) * self.arrowSize,
                math.cos(angle - Edge.Pi + Edge.Pi / 3) * self.arrowSize)

            painter.setPen(
                QPen(Qt.red, 2, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
            painter.setBrush(QBrush(Qt.red, Qt.SolidPattern))
            painter.drawPolygon(
                QPolygonF([line.p1(), sourceArrowP1, sourceArrowP2]))
            #painter.drawPolygon(QPolygonF([line.p2(), destArrowP1, destArrowP2]))

        if self.state > 0 and self.source > self.dest:
            point = QPointF((self.sourcePoint.x() + self.destPoint.x()) / 2,
                            (self.sourcePoint.y() + self.destPoint.y()) / 2)
            point = QPointF(point.x() + math.sin(angle) * 16,
                            point.y() + math.cos(angle) * 16)
            painter.drawText(point, self.text)
 def save(self, filename):
     growth_num = Result.growth_num
     fdata = StringIO()
     invert_pts, invert_cells = self.data.save(f=fdata)
     f = open(filename, 'w')
     w = csv.writer(f, delimiter=',')
     w.writerow(["TRKR_VERSION", Result.CURRENT_VERSION])
     w.writerow(["Growth computation parameters"])
     hf = self.header_fields
     for h in self.header_order:
         w.writerow([h] + hf[h][0](self))
     w.writerow([])
     w.writerow(["Growth per image"])
     w.writerow(Result.fields)
     wall_shift = self.fields_num["wall"]-1
     for img_id in range(len(self.images)):
         img = self.images[img_id]
         w.writerow([img])
         cells = self.cells[img_id]
         cells_area = self.cells_area[img_id]
         walls = self.walls[img_id]
         data = self.data[img]
         cells_shape = data.cells
         rows = []
         for c in sorted(cells.keys()):
             # Get the center of mass of the cell
             cell = [data[p] for p in cells_shape[c] if p in data]
             center = QPointF(0, 0)
             area = 0.0
             u1 = cell[-1]
             for i in range(len(cell)):
                 u2 = cell[i]
                 loc_area = u1.x() * u2.y() - u1.y() * u2.x()
                 center += (u1 + u2)*loc_area
                 area += loc_area
                 u1 = u2
             center /= area
             row = ["", "Cell %d" % invert_cells[c], cells_area[c], cells[c][growth_num["kmax"]],
                    cells[c][growth_num["kmin"]], cells[c][growth_num["theta"]] * 180 / pi,
                    cells[c][growth_num["phi"]], center.x(), center.y()]
             rows.append(row)
         lr = len(rows)
         for i, ws in enumerate(sorted(walls.keys())):
             wll = ["", "Wall %d-%d" % (invert_pts[ws[0]], invert_pts[ws[1]]), walls[ws]]
             if i >= lr:
                 rows.append([""] * wall_shift)
             rows[i] += wll
         w.writerows(rows)
     w.writerow(["Actual cell shapes"])
     w.writerow(["Image", "Cell", "Begin/End", "Shape [x y]"])
     for img_id in range(len(self.images)):
         img = self.images[img_id]
         w.writerow([img])
         cells_shapes = self.cells_shapes[img_id]
         rows = []
         for c in sorted(cells_shapes.keys()):
             sh = cells_shapes[c]
             row1 = ["", "Cell %d" % invert_cells[c], "Begin"] + list(sh[0].flatten())
             row2 = ["", "Cell %d" % invert_cells[c], "End"] + list(sh[1].flatten())
             rows.append(row1)
             rows.append(row2)
         w.writerows(rows)
     w.writerow([])
     w.writerow(["Data"])
     f.write(fdata.getvalue())
     f.close()
     fdata.close()
Beispiel #49
0
    def __select(self, pos, bhighlight=True, select_type=None):
        """
        Select an object based on computed distance.
        The x and y values are normalized first to ensure the same units for distance in x and y coordinates.
        The x axis is in samples while the y axis is in BPM.

        :param pos:
        :param bhighlight:
        :param select_type: Select specific type of curve (annotation)
        :type pos: QPointF
        :type select_type: str
        :return:
        """
        # found, distance, point = None, 100, -1

        debug = False
        if debug:
            print 'SELECT'

        dmax = self._distance
        found_curve = None
        found_point = None

        point_dist = np.infty
        line_dist = np.infty
        note_dist = np.infty
        ellipse_dist = np.infty
        caliper_dist = np.infty
        point_i = -1
        bellipse = False
        bcaliper = False

        # the normalization have to be adjusted to aspect ratio of the plot
        size = self.__plot.size_plot_area()
        r_adjust = size.width() / float(size.height())

        xmin = self.__plot.viewXMinSample()
        xmax = self.__plot.viewXMaxSample()
        rx = (xmax - xmin) / r_adjust
        # print xmin, xmax, (xmax - xmin)
        ymin = self.__plot.viewYMinSample()
        ymax = self.__plot.viewYMaxSample()
        ry = abs(ymax - ymin)
        # print ymin, ymax, (ymax - ymin)

        # normalize, aby mely x a y stejne jednotky (puvodne x je ve vzorcich a y v bpm)
        pos_clicked = pos
        pos = QPointF((pos.x() - xmin) / rx, (pos.y() - ymin) / ry)
        # pos = QPointF((pos.x() - xmin), (pos.y() - ymin) )

        self.__unselect()

        # create local copy and add caliper (if visible)
        d_ann = self.__plot.ann_get().copy()

        if self.__plot.caliper_is_visible():
            d_ann[self.__plot.caliper.id] = self.__plot.caliper

        for key, curve in d_ann.iteritems():

            t = curve.get_curve_type()

            if select_type is not None:
                if t != select_type:
                    continue

            x_from = (curve.x_from - xmin) / rx
            x_to = (curve.x_to - xmin) / rx

            y1 = y2 = None
            if curve.yval1 is not None:
                y1 = (curve.yval1 - ymin) / ry

            if curve.yval2 is not None:
                y2 = (curve.yval2 - ymin) / ry

            # x_from = curve.x_from - xmin
            # x_to = curve.x_to - xmin
            # y1 = curve.yval1 - ymin
            # y2 = curve.yval2 - ymin

            # print rx, ry
            # print pos, x_from, x_to, y1, y2

            # if debug:
            #     print 'normalize x a y:', x_from, x_to, y1, y2

            if t == EnumAnnType.basal:
                line_dist = abs(pos.y() - y1)
                if debug:
                    print 'basal (line dist):', line_dist

            elif t == EnumAnnType.baseline or t == EnumAnnType.recovery or t == EnumAnnType.no_recovery or \
                    t == EnumAnnType.excessive_ua or t == EnumAnnType.acceleration or t == EnumAnnType.deceleration or \
                    t == EnumAnnType.uterine_contraction:
                d0 = abs(x_from - pos.x())
                d1 = abs(x_to - pos.x())
                # print d0, d1

                if d0 < d1:
                    point_i = 0
                    point_dist = d0
                else:
                    point_i = 1
                    point_dist = d1

                line_dist = abs(pos.y() - y1)

                if debug:
                    print 'baseline etc (d0,d1,line dist):', d0, d1, line_dist

            elif t == EnumAnnType.ellipsenote:

                d = compute_dist_ellipse(x_from, x_to, y1, y2, pos)
                ellipse_dist = min(d)
                point_i = d.index(ellipse_dist)

                point_i = -1 if point_i == 0 else point_i

                bx = x_from <= pos.x() <= x_to
                by = y1 <= pos.y() <= y2
                bellipse = bx and by

                if debug:
                    print 'ellipse etc (ellipse_dist, bellipse):', ellipse_dist, bellipse

            elif t == EnumAnnType.note or t == EnumAnnType.evaluation_note:
                note_dist = abs(pos.x() - x_from)
                if debug:
                    print 'note etc (note_dist):', note_dist

            elif isinstance(curve, PyQwtPlotFloatingBaseline):
                baseline_x, baseline_y = curve.get_points()

                # potrebuji normalizovat
                baseline_x = [(t - xmin) / rx for t in baseline_x]
                baseline_y = [(t - ymin) / ry for t in baseline_y]

                d = [distance_to_point(pos, x, y) for x, y in zip(baseline_x, baseline_y)]
                point_dist = min(d)
                point_i = d.index(point_dist)

                line_dist = point_dist

                if debug:
                    print 'floating baseline (distances, point , dist):', d, point_i, point_dist

            elif isinstance(curve, Caliper):

                d = compute_dist_caliper(x_from, x_to, y1, y2, pos)
                caliper_dist = min(d)
                point_i = d.index(caliper_dist)

                point_i = -1 if point_i == 0 else point_i

                bx = x_from <= pos.x() <= x_to
                by = y1 <= pos.y() <= y2
                bcaliper = bx and by

                # if clicked inside box and all distances are small

                if debug:
                    print 'caliper etc (caliper_dist, point_i, bcaliper, d):', caliper_dist, point_i, bcaliper, d

            else:
                continue
                # return 0

            if point_dist < dmax and line_dist < dmax:
                # if a point/line is close
                found_curve = curve
                found_point = point_i
                dmax = min(point_dist, line_dist)

            elif line_dist <= dmax and x_from < pos.x() < x_to:
                # if a line is close
                found_curve = curve
                found_point = -1
                dmax = line_dist

            elif note_dist < dmax:
                # if a note is close
                found_curve = curve
                found_point = -1
                dmax = note_dist

            elif ellipse_dist < dmax:
                # if an ellipse is close
                found_curve = curve
                found_point = point_i
                dmax = ellipse_dist

            elif caliper_dist < dmax:
                found_curve = curve
                found_point = point_i
                dmax = caliper_dist

            elif bellipse and found_curve is None:  # if not another curve has been selected
                found_curve = curve
                found_point = -1

            elif bcaliper and found_curve is None:  # if not another curve has been selected
                found_curve = curve
                found_point = -1

        if found_curve is not None:
            self.__selected_curve = found_curve
            self.__selected_point = found_point
            self.__highlight(bhighlight)
            self.__point_clicked = pos_clicked
            return True

        return False
Beispiel #50
0
class EEdge(QGraphicsObject):
    def __init__(self, head, tail, uuid):
        QGraphicsObject.__init__(self)

        if not issubclass(head.__class__, dict) and not isinstance(tail.__class__, dict):
            raise AttributeError

        self.setZValue(0.0)

        self.__kId = uuid
        self.__head = head
        self.__tail = tail

        if  head[ENode.kGuiAttributeType].match(EAttribute.kTypeInput):
            self.__head = tail
            self.__tail = head

        self.__head[ENode.kGuiAttributeParent].onMove.connect(self.update)
        self.__tail[ENode.kGuiAttributeParent].onMove.connect(self.update)

        self.__headPoint = QPointF(0.0, 0.0)
        self.__tailPoint = QPointF(0.0, 0.0)

        self.__pen = QPen(QColor(43, 43, 43), 2, Qt.SolidLine)

        self.update()

    @property
    def Id(self):
        return self.__kId

    @property
    def Head(self):
        return self.__head

    @property
    def Tail(self):
        return self.__tail

    def pen(self):
        return self.__pen

    def setPen(self, pen):
        if not isinstance(pen, QPen):
            raise AttributeError

        self.__pen = pen

    def update(self):

        QGraphicsObject.prepareGeometryChange(self)

        self.__headPoint = self.mapFromItem(self.__head[ENode.kGuiAttributeParent],
                                            self.__head[ENode.kGuiAttributePlug])

        self.__tailPoint = self.mapFromItem(self.__tail[ENode.kGuiAttributeParent],
                                            self.__tail[ENode.kGuiAttributePlug])

        self.__headOffsetLine = QLineF(self.__headPoint, QPointF(self.__headPoint.x() + 15, self.__headPoint.y()))
        self.__tailOffsetLine = QLineF(self.__tailPoint, QPointF(self.__tailPoint.x() - 15, self.__tailPoint.y()))

        line = QLineF(self.__headPoint, self.__tailPoint)
        self.__line = line

    def boundingRect(self):
        extra = (self.pen().width() * 64) / 2
        return QRectF(self.__line.p1(),
                      QSizeF(self.__line.p2().x() - self.__line.p1().x(),
                             self.__line.p2().y() - self.__line.p1().y())).normalized().adjusted(-extra,
                                                                                                 -extra,
                                                                                                 extra,
                                                                                                 extra)

    def shape(self):
        return QGraphicsObject.shape(self)

    def drawPath(self, startPoint, endPoint):
        path = QPainterPath()

        one = (QPointF(endPoint.x(), startPoint.y()) + startPoint) / 2
        two = (QPointF(startPoint.x(), endPoint.y()) + endPoint) / 2

        path.moveTo(startPoint)

        angle = math.pi / 2
        bLine1 = QLineF()
        bLine1.setP1(startPoint)

        if startPoint.x() > endPoint.x():
            dist = startPoint.x() - endPoint.x()
            one = (bLine1.p1() + QPointF(math.sin(angle) * dist,  math.cos(angle) * dist))
            bLine1.setP1(endPoint)
            two = (bLine1.p1() + QPointF(math.sin(angle) * dist,  math.cos(angle) * dist))

        path.cubicTo(one, two,  endPoint)
        return path, QLineF(one, two)

    def paint(self, painter, option, widget=None):

        painter.setPen(self.pen())

        headCenter = self.mapFromItem(self.__head[ENode.kGuiAttributeParent],
                                      self.__head[ENode.kGuiAttributeParent].boundingRect().center())

        tailCenter = self.mapFromItem(self.__tail[ENode.kGuiAttributeParent],
                                      self.__tail[ENode.kGuiAttributeParent].boundingRect().center())

        centerPoint = QLineF(headCenter, tailCenter).pointAt(0.5)

        centerPoint.setX(self.__headOffsetLine.p2().x())
        lineFromHead = QLineF(self.__headOffsetLine.p2(), centerPoint)
        centerPoint.setX(self.__tailOffsetLine.p2().x())
        lineFromTail = QLineF(self.__tailOffsetLine.p2(), centerPoint)

        painter.drawPath(self.drawPath(self.__headOffsetLine.p1(), self.__tailOffsetLine.p1())[0])
class XNodeConnection( QGraphicsPathItem ):
    """ 
    Defines the base graphics item class that is used to draw a connection
    between two nodes.
    """
    def __init__( self, scene ):
        self._visible               = True
        
        super(XNodeConnection, self).__init__()
        
        # define custom properties
        self._textItem              = None
        self._polygons              = []
        self._style                 = XConnectionStyle.Linear
        self._padding               = 20
        self._squashThreshold       = 2 * scene.cellWidth()
        self._showDirectionArrow    = False
        self._highlightPen          = QPen(QColor('yellow'))
        self._disabledPen           = QPen(QColor(100, 100, 100))
        self._disableWithLayer      = False
        self._enabled               = True
        self._dirty                 = True
        self._customData            = {}
        self._layer                 = None
        self._font                  = QApplication.instance().font()
        self._text                  = ''
        
        self._inputNode                     = None
        self._inputFixedY                   = None
        self._inputFixedX                   = None
        self._inputPoint                    = QPointF()
        self._inputLocation                 = XConnectionLocation.Left
        self._autoCalculateInputLocation    = False
        self._showInputArrow                = False
        
        self._outputNode                    = None
        self._outputFixedX                  = None
        self._outputFixedY                  = None
        self._outputPoint                   = QPointF()
        self._outputLocation                = XConnectionLocation.Right
        self._autoCalculateOutputLocation   = False
        self._showOutputArrow               = False
        
        # set standard properties
        self.setFlags( self.ItemIsSelectable )
        self.setZValue(-1)
        self.setPen( QColor('white') )
        self.setLayer( scene.currentLayer() )
        
    def autoCalculateInputLocation( self ):
        """
        :remarks    Returns whether or not to auto calculate the input
                    location based on the proximity to the output node
                    or point.
        
        :return     <bool>
        """
        return self._autoCalculateInputLocation
    
    def autoCalculateOutputLocation( self ):
        """
        :remarks    Returns whether or not to auto calculate the input
                    location based on the proximity to the output node
                    or point.
        
        :return     <bool>
        """
        return self._autoCalculateOutputLocation
    
    def connectSignals( self, node ):
        """
        :remarks    Connects to signals of the inputed node, if the node
                    is a valid XNode type.
        
        :param      node    <XNode> || None
        
        :return     <bool> success
        """
        from projexui.widgets.xnodewidget.xnode import XNode
        
        # make sure we're connecting to a valid node
        if ( not isinstance(node, XNode) ):
            return False
        
        node.dispatch.geometryChanged.connect(  self.setDirty )
        node.dispatch.visibilityChanged.connect(self.setDirty)
        node.dispatch.removed.connect(          self.forceRemove )
        return True
    
    def controlPoints( self ):
        """
        :remarks    Generates the control points for this path
        
        :return     <list> [ <tuple> ( <float> x, <float> y), .. ]
        """
        
        # calculate the positions
        outputPoint  = self.outputPoint()
        inputPoint   = self.inputPoint()
        
        points = []
        
        x0 = outputPoint.x()
        y0 = outputPoint.y()
        
        xN = inputPoint.x()
        yN = inputPoint.y()
        
        xC = (x0 + xN) / 2.0
        yC = (y0 + yN) / 2.0
        
        points.append((x0, y0))
        
        oloc    = self.outputLocation()
        iloc    = self.inputLocation()
        left    = XConnectionLocation.Left
        right   = XConnectionLocation.Right
        bot     = XConnectionLocation.Bottom
        top     = XConnectionLocation.Top
        
        # create a right-to-left
        if ( (oloc & right) and (iloc & left) ):
            if ( xN < (x0 + self.squashThreshold()) ):
                points.append((x0+self.padding(), y0))
                points.append((x0+self.padding(), yC))
                points.append((xN-self.padding(), yC))
                points.append((xN-self.padding(), yN))
            else:
                points.append((xC, y0))
                points.append((xC, yN))
        
        # create a left-to-right
        elif ( (oloc & left) and (iloc & right) ):
            if ( (x0 - self.squashThreshold()) < xN ):
                points.append((x0-self.padding(), y0))
                points.append((x0-self.padding(), yC))
                points.append((xN+self.padding(), yC))
                points.append((xN+self.padding(), yN))
            else:
                points.append((xC, y0))
                points.append((xC, yN))
        
        # create a bottom-to-top
        elif ( (oloc & bot) and (iloc & top) ):
            if ( yN < (y0 + self.squashThreshold()) ):
                points.append((x0, y0+self.padding()))
                points.append((xC, y0+self.padding()))
                points.append((xC, yN-self.padding()))
                points.append((xN, yN-self.padding()))
            else:
                points.append((x0, yC))
                points.append((xN, yC))
        
        # create a top-to-bottom
        elif ( (oloc & top) and (iloc & bot) ):
            if ( (y0 - self.squashThreshold()) < yN ):
                points.append((x0, y0-self.padding()))
                points.append((xC, y0-self.padding()))
                points.append((xC, yN+self.padding()))
                points.append((xN, yN+self.padding()))
            else:
                points.append((x0, yC))
                points.append((xN, yC))
        
        # create a left-to-left
        elif ( (oloc & left) and (iloc & left) ):
            xMin = min(x0-self.padding(), xN-self.padding())
            points.append((xMin, y0))
            points.append((xMin, yN))
        
        # create a right-to-right
        elif ( (oloc & right) and (iloc & right) ):
            xMax = max(x0+self.padding(), xN+self.padding())
            points.append((xMax, y0))
            points.append((xMax, yN))
        
        # create a bottom-to-bottom
        elif ( (oloc & top) and (iloc & top) ):
            yMin = min(y0-self.padding(), yN-self.padding())
            points.append((x0, yMin))
            points.append((xN, yMin))
        
        # create a bottom-to-bottom
        elif ( (oloc & bot) and (iloc & bot) ):
            yMax = max(y0+self.padding(), yN+self.padding())
            points.append((x0, yMax))
            points.append((xN, yMax))
        
        # create a bottom-to-left or left-to-bottom
        elif (  ((oloc & bot) and (iloc & left)) or 
                ((oloc & left) and (iloc & bot)) ):
            points.append((x0, yN))
        
        # create a bottom-to-right or right-to-bottom
        elif (  ((oloc & bot) and (iloc & right)) or 
                ((oloc & right) and (iloc & bot)) ):
            points.append((x0, y0))
        
        # create a top-to-left or left-to-top
        elif (  ((oloc & top) and (iloc & left)) or 
                ((oloc & left) and (iloc & top)) ):
            points.append((xN, yN))
            
        # create a top-to-right or right-to-top
        elif (  ((oloc & top) and (iloc & right)) or 
                ((oloc & right) and (iloc & top)) ):
            points.append((xN, y0))
        
        points.append((xN, yN))
        
        return points
    
    def customData( self, key, default = None ):
        """
        Returns custom defined data that can be tracked per connection.
        
        :param      key         <str>
        :param      default     <variant>
        
        :return     <variant>
        """
        return self._customData.get(str(key), default)
    
    def direction( self ):
        """
        Returns the output-to-input direction as a tuple of the output \
        and input locations.
        
        :return     (<XConnectionLocation> output, <XConnectionLocation> input)
        """
        return (self.outputLocation(), self.inputLocation())
    
    def disabledPen( self ):
        """
        Returns the pen that should be used when rendering a disabled \
        connection.
        
        :return     <QPen>
        """
        return self._disabledPen
    
    def disableWithLayer( self ):
        """
        Returns whether or not this connection's enabled state should be \
        affected by its layer.
        
        :return     <bool>
        """
        return self._disableWithLayer
    
    def disconnectSignals( self, node ):
        """
        Disconnects from signals of the inputed node, if the node is a \
        valid XNode type.
        
        :param      node    <XNode> || None
        
        :return     <bool> success
        """
        from projexui.widgets.xnodewidget.xnode import XNode
        
        # make sure we're disconnecting from a valid node
        if ( not isinstance(node, XNode) ):
            return False
        
        node.dispatch.geometryChanged.disconnect(   self.setDirty )
        node.dispatch.removed.disconnect(           self.forceRemove )
        return True
    
    def forceRemove( self ):
        """
        Removes the object from the scene by queuing it up for removal.
        """
        scene = self.scene()
        if ( scene ):
            scene.forceRemove(self)
    
    def font( self ):
        """
        Returns the font for this connection.
        
        :return     <QFont>
        """
        return self._font
    
    def hasCustomData( self, key ):
        """
        Returns whether or not there is the given key in the custom data.
        
        :param      key | <str>
        
        :return     <bool>
        """
        return str(key) in self._customData
    
    def highlightPen( self ):
        """
        Return the highlight pen for this connection.
        
        :return     <QPen>
        """
        return self._highlightPen
    
    def inputLocation( self ):
        """
        Returns the input location for this connection.
        
        :return     <XConnectionLocation>
        """
        if ( not self.autoCalculateInputLocation() ):
            return self._inputLocation
        
        # auto calculate directions based on the scene
        if ( self._outputNode ):
            outputRect = self._outputNode.sceneRect()
        else:
            y = self._outputPoint.y()
            outputRect = QRectF( self._outputPoint.x(), y, 0, 0 )
        
        if ( self._inputNode ):
            inputRect  = self._inputNode.sceneRect()
        else:
            y = self._inputPoint.y()
            inputRect  = QRectF( self._inputPoint.x(), y, 0, 0 )
        
        # use the input location as potential places where it can be
        iloc    = self._inputLocation
        left    = XConnectionLocation.Left
        right   = XConnectionLocation.Right
        top     = XConnectionLocation.Top
        bot     = XConnectionLocation.Bottom
        
        if ( self._inputNode == self._outputNode ):
            if ( iloc & right ):
                return right
            elif ( iloc & left ):
                return left
            elif ( iloc & top ):
                return top
            else:
                return bot
                
        elif ( (iloc & left)    and outputRect.right() < inputRect.left() ):
            return left
        elif ( (iloc & right)   and inputRect.right() < outputRect.left() ):
            return right
        elif ( (iloc & top)     and outputRect.bottom() < inputRect.top() ):
            return top
        elif ( (iloc & bot) ):
            return bot
        elif ( (iloc & left) ):
            return left
        elif ( (iloc & right) ):
            return right
        elif ( (iloc & top) ):
            return top
        else:
            return left
    
    def inputNode( self ):
        """
        Returns the input node that is connected to this connection.
        
        :return     <XNode>
        """
        return self._inputNode
    
    def inputFixedX( self ):
        """
        Returns the fixed X value for the input option
        
        :return     <float> || None
        """
        return self._inputFixedX
    
    def inputFixedY( self ):
        """
        Returns the fixed Y value for the input option.
        
        :return     <float> || None
        """
        return self._inputFixedY
    
    def inputPoint( self ):
        """
        Returns a scene space point that the connection \
        will draw to as its input target.  If the connection \
        has a node defined, then it will calculate the input \
        point based on the position of the node, factoring in \
        preference for input location and fixed information. \
        If there is no node connected, then the point defined \
        using the setInputPoint method will be used.
        
        :return     <QPointF>
        """
        node = self.inputNode()
        
        # return the set point
        if ( not node ):
            return self._inputPoint
        
        # otherwise, calculate the point based on location and fixed info
        ilocation   = self.inputLocation()
        ifixedx     = self.inputFixedX()
        ifixedy     = self.inputFixedY()
        return node.positionAt( ilocation, ifixedx, ifixedy )
    
    def isDirection( self, outputLocation, inputLocation ):
        """
        Checks to see if the output and input locations match the settings \
        for this item.
        
        :param      outputLocation      | <XConnectionLocation>
        :param      inputLocation       | <XConnectionLocation>
        
        :return     <bool>
        """
        return (self.isOutputLocation(outputLocation) and 
               self.isInputLocation(inputLocation))
    
    def isDirty( self ):
        """
        Returns whether or not this path object is dirty and needs to \
        be rebuilt.
        
        :return     <bool>
        """
        return self._dirty
    
    def isEnabled( self ):
        """
        Returns whether or not this connection is enabled.
        
        :sa     disableWithLayer
        
        :return     <bool>
        """
        if self._disableWithLayer and self._layer:
            lenabled = self._layer.isEnabled()
        elif self._inputNode and self._outputNode:
            lenabled = self._inputNode.isEnabled() and self._outputNode.isEnabled()
        else:
            lenabled = True
        
        return self._enabled and lenabled
    
    def isInputLocation( self, location ):
        """
        Returns whether or not the inputed location value matches the \
        given input location.
        
        :param      location    | <XConnectionLocation>
        
        :return     <bool>
        """
        return (self.inputLocation() & location) != 0
    
    def isOutputLocation( self, location ):
        """
        Returns whether or not the inputed location value matches the \
        given output location.
        
        :param      location    | <XConnectionLocation>
        
        :return     <bool>
        """
        return (self.outputLocation() & location) != 0
    
    def isStyle( self, style ):
        """
        Return whether or not the connection is set to a particular style.
        
        :param      style       | <XConnectionStyle>
        
        :return     <bool>
        """
        return (self._style & style) != 0
    
    def isVisible( self ):
        """
        Returns whether or not this connection is visible.  If either node it is
        connected to is hidden, then it should be as well.
        
        :return     <bool>
        """
        in_node  = self.inputNode()
        out_node = self.outputNode()
        
        if ( in_node and not in_node.isVisible() ):
            return False
        
        if ( out_node and not out_node.isVisible() ):
            return False
        
        return self._visible
    
    def layer( self ):
        """
        Returns the layer that this node is assigned to.
        
        :return     <XNodeLayer> || None
        """
        return self._layer
    
    def mappedPolygon( self, polygon, path = None, percent = 0.5 ):
        """
        Maps the inputed polygon to the inputed path \
        used when drawing items along the path.  If no \
        specific path is supplied, then this object's own \
        path will be used.  It will rotate and move the \
        polygon according to the inputed percentage.
        
        :param      polygon     <QPolygonF>
        :param      path        <QPainterPath>
        :param      percent     <float>
        
        :return     <QPolygonF> mapped_poly
        """
        translatePerc   = percent
        anglePerc       = percent
        
        # we don't want to allow the angle percentage greater than 0.85
        # or less than 0.05 or we won't get a good rotation angle
        if ( 0.95 <= anglePerc ):
            anglePerc = 0.98
        elif ( anglePerc <= 0.05 ):
            anglePerc = 0.05
        
        if ( not path ):
            path = self.path()
        if ( not (path and path.length()) ):
            return QPolygonF()
        
        # transform the polygon to the path
        point   = path.pointAtPercent(translatePerc)
        angle   = path.angleAtPercent(anglePerc)
        
        # rotate about the 0 axis
        transform   = QTransform().rotate(-angle)
        polygon     = transform.map(polygon)
        
        # move to the translation point
        transform   = QTransform().translate(point.x(), point.y())
        
        # create the rotated polygon
        mapped_poly = transform.map(polygon)
        self._polygons.append(mapped_poly)
        
        return mapped_poly
    
    def mousePressEvent( self, event ):
        """
        Overloads the mouse press event to handle special cases and \
        bypass when the scene is in view mode.
        
        :param      event   <QMousePressEvent>
        """
        # ignore events when the scene is in view mode
        scene = self.scene()
        if ( scene and scene.inViewMode() ):
            event.ignore()
            return
        
        # block the selection signals
        if ( scene ):
            scene.blockSelectionSignals(True)
            
            # clear the selection
            if ( not (self.isSelected() or 
                      event.modifiers() == Qt.ControlModifier) ):
                for item in scene.selectedItems():
                    if ( item != self ):
                        item.setSelected(False)
        
        # try to start the connection
        super(XNodeConnection, self).mousePressEvent(event)
        
    def mouseMoveEvent( self, event ):
        """
        Overloads the mouse move event to ignore the event when \
        the scene is in view mode.
        
        :param      event   <QMouseMoveEvent>
        """
        # ignore events when the scene is in view mode
        scene = self.scene()
        if ( scene and (scene.inViewMode() or scene.isConnecting()) ):
            event.ignore()
            return
        
        # call the base method
        super(XNodeConnection, self).mouseMoveEvent(event)
    
    def mouseReleaseEvent( self, event ):
        """
        Overloads the mouse release event to ignore the event when the \
        scene is in view mode, and release the selection block signal.
         
         :param     event   <QMouseReleaseEvent>
        """
        # ignore events when the scene is in view mode
        scene = self.scene()
        if ( scene and (scene.inViewMode() or scene.isConnecting()) ):
            event.ignore()
            return
        
        # emit the scene's connection menu requested signal if
        # the button was a right mouse button
        if ( event.button() == Qt.RightButton and scene ):
            scene.emitConnectionMenuRequested(self)
            event.accept()
        else:
            super(XNodeConnection, self).mouseReleaseEvent(event)
        
        # unblock the selection signals
        if ( scene ):
            scene.blockSelectionSignals(False)
    
    def opacity( self ):
        """
        Returns the opacity amount for this connection.
        
        :return     <float>
        """
        in_node     = self.inputNode()
        out_node    = self.outputNode()
        
        if ( in_node and out_node and \
             (in_node.isIsolateHidden() or out_node.isIsolateHidden()) ):
            return 0.1
        
        opacity = super(XNodeConnection, self).opacity()
        layer = self.layer()
        if ( layer ):
            return layer.opacity() * opacity
        
        return opacity
    
    def outputLocation( self ):
        """
        Returns the location for the output source position.
        
        :return     <XConnectionLocation>
        """
        if ( not self.autoCalculateOutputLocation() ):
            return self._outputLocation
        
        # auto calculate directions based on the scene
        if ( self._outputNode ):
            outputRect = self._outputNode.sceneRect()
        else:
            y = self._outputPoint.y()
            outputRect = QRectF( self._outputPoint.x(), y, 0, 0 )
        
        if ( self._inputNode ):
            inputRect  = self._inputNode.sceneRect()
        else:
            y = self._inputPoint.y()
            inputRect  = QRectF( self._inputPoint.x(), y, 0, 0 )
            
        oloc    = self._outputLocation
        left    = XConnectionLocation.Left
        right   = XConnectionLocation.Right
        top     = XConnectionLocation.Top
        bot     = XConnectionLocation.Bottom
        
        if ( self._inputNode == self._outputNode ):
            if ( oloc & right ):
                return right
            elif ( oloc & left ):
                return left
            elif ( oloc & top ):
                return top
            else:
                return bot
                
        elif ( (oloc & right)   and outputRect.right() < inputRect.left() ):
            return right
        elif ( (oloc & left)    and inputRect.right() < outputRect.left() ):
            return left
        elif ( (oloc & bot)     and outputRect.bottom() < inputRect.top() ):
            return bot
        elif ( (oloc & top) ):
            return top
        elif ( (oloc & right) ):
            return right
        elif ( (oloc & left) ):
            return left
        elif ( (oloc & bot) ):
            return bot
        else:
            return right
    
    def outputNode( self ):
        """
        Returns the output source node that this connection is currently \
        connected to.
        
        :return     <XNode> || None
        """
        return self._outputNode
    
    def outputFixedX( self ):
        """
        Returns the fixed X position for the output component of this \
        connection.
                    
        :return     <float> || None
        """
        return self._outputFixedX
    
    def outputFixedY( self ):
        """
        Returns the fixed Y position for the output component of this \
        connection.
                    
        :return     <float> || None
        """
        return self._outputFixedY
    
    def outputPoint( self ):
        """
        Returns a scene space point that the connection \
        will draw to as its output source.  If the connection \
        has a node defined, then it will calculate the output \
        point based on the position of the node, factoring in \
        preference for output location and fixed positions.  If \ 
        there is no node connected, then the point defined using \
        the setOutputPoint method will be used.
        
        :return     <QPointF>
        """
        node = self.outputNode()
        
        # return the set point
        if ( not node ):
            return self._outputPoint
        
        # otherwise, calculate the point based on location and fixed positions
        olocation   = self.outputLocation()
        ofixedx     = self.outputFixedX()
        ofixedy     = self.outputFixedY()
        return node.positionAt( olocation, ofixedx, ofixedy )
    
    def padding( self ):
        """
        Returns the amount of padding to be used when drawing a connection \
        that will be drawn backwards.
        
        :return     <float>
        """
        return self._padding
    
    def paint( self, painter, option, widget ):
        """
        Overloads the paint method from QGraphicsPathItem to \
        handle custom drawing of the path using this items \
        pens and polygons.
        
        :param      painter     <QPainter>
        :param      option      <QGraphicsItemStyleOption>
        :param      widget      <QWidget>
        """
        # following the arguments required by Qt
        # pylint: disable-msg=W0613
        
        painter.setOpacity(self.opacity())
        
        # show the connection selected
        if ( not self.isEnabled() ):
            pen = QPen(self.disabledPen())
        elif ( self.isSelected() ):
            pen = QPen(self.highlightPen())
        else:
            pen = QPen(self.pen())
        
        if ( self._textItem ):
            self._textItem.setOpacity(self.opacity())
            self._textItem.setDefaultTextColor(pen.color().darker(110))
        
        # rebuild first if necessary
        if ( self.isDirty() ):
            self.setPath(self.rebuild())
        
        # store the initial hint
        hint = painter.renderHints()
        painter.setRenderHint( painter.Antialiasing )
        
        pen.setWidthF(1.25)
        painter.setPen(pen)
        painter.drawPath(self.path())
        
        # redraw the polys to force-fill them
        for poly in self._polygons:
            if ( not poly.isClosed() ):
                continue
            
            painter.setBrush(pen.color())
            painter.drawPolygon(poly)
        
        # restore the render hints
        painter.setRenderHints(hint)
    
    def prepareToRemove( self ):
        """
        Handles any code that needs to run to cleanup the connection \
        before it gets removed from the scene.
        
        :return     <bool> success
        """
        # disconnect the signals from the input and output nodes
        for node in (self._outputNode, self._inputNode):
            self.disconnectSignals(node)
        
        # clear the pointers to the nodes
        self._inputNode     = None
        self._outputNode    = None
        
        return True
    
    def rebuild( self ):
        """
        Rebuilds the path for this connection based on the given connection \
        style parameters that have been set.
        
        :return     <QPainterPath>
        """
        # create the path
        path            = self.rebuildPath()
        self._polygons  = self.rebuildPolygons(path)
        
        if ( self._textItem ):
            point   = path.pointAtPercent(0.5)
            metrics = QFontMetrics(self._textItem.font())
            
            point.setY(point.y() - metrics.height() / 2.0)
            
            self._textItem.setPos(point)
        
        # create the path for the item
        for poly in self._polygons:
            path.addPolygon(poly)
        
        # unmark as dirty
        self.setDirty(False)
        
        return path
    
    def rebuildPath( self ):
        """
        Rebuilds the path for the given style options based on the currently \
        set parameters.
        
        :return     <QPainterPath>
        """
        # rebuild linear style
        if ( self.isStyle( XConnectionStyle.Linear ) ):
            return self.rebuildLinear()
        
        # rebuild block style
        elif ( self.isStyle( XConnectionStyle.Block ) ):
            return self.rebuildBlock()
        
        # rebuild smooth style
        elif ( self.isStyle( XConnectionStyle.Smooth ) ):
            return self.rebuildSmooth()
        
        # otherwise, we have an invalid style, or a style
        # defined by a subclass
        else:
            return QPainterPath()
    
    def rebuildPolygons( self, path ):
        """
        Rebuilds the polygons that will be used on this path.
        
        :param      path    | <QPainterPath>
        
        :return     <list> [ <QPolygonF>, .. ]
        """
        output = []
        
        # create the input arrow
        if ( self.showInputArrow() ):
            a = QPointF(-10, -3)
            b = QPointF(0, 0)
            c = QPointF(-10, 3)
            
            mpoly = self.mappedPolygon(QPolygonF([a, b, c, a]), path, 1.0 )
            output.append( mpoly )
        
        # create the direction arrow
        if ( self.showDirectionArrow() ):
            a = QPointF(-5, -3)
            b = QPointF(5, 0)
            c = QPointF(-5, 3)
            
            mpoly = self.mappedPolygon(QPolygonF([a, b, c, a]), path, 0.5 )
            output.append( mpoly )
        
        # create the output arrow
        if ( self.showOutputArrow() ):
            a = QPointF(10, -3)
            b = QPointF(0, 0)
            c = QPointF(10, 3)
            
            mpoly = self.mappedPolygon(QPolygonF([a, b, c, a]), path, 0 )
            output.append( mpoly )
        
        return output
        
    def rebuildLinear( self ):
        """ 
        Rebuilds a linear path from the output to input points.
        
        :return     <QPainterPath>
        """
        
        points = self.controlPoints()
        
        x0, y0 = points[0]
        xN, yN = points[-1]
        
        # create a simple line between the output and
        # input points
        path = QPainterPath()
        path.moveTo(x0, y0)
        path.lineTo(xN, yN)
        
        return path
    
    def rebuildBlock( self ):
        """
        Rebuilds a blocked path from the output to input points.
        
        :return     <QPainterPath>
        """
        # collect the control points
        points = self.controlPoints()
        
        # create the path
        path = QPainterPath()
        for i, point in enumerate(points):
            if ( not i ):
                path.moveTo( point[0], point[1] )
            else:
                path.lineTo( point[0], point[1] )
        
        return path
    
    def rebuildSmooth( self ):
        """
        Rebuilds a smooth path based on the inputed points and set \
        parameters for this item.
        
        :return     <QPainterPath>
        """
        # collect the control points
        points = self.controlPoints()
        
        # create the path
        path = QPainterPath()
        
        if ( len(points) == 3 ):
            x0, y0 = points[0]
            x1, y1 = points[1]
            xN, yN = points[2]
            
            path.moveTo(x0, y0)
            path.quadTo(x1, y1, xN, yN)
        
        elif ( len(points) == 4 ):
            x0, y0 = points[0]
            x1, y1 = points[1]
            x2, y2 = points[2]
            xN, yN = points[3]
            
            path.moveTo(x0, y0)
            path.cubicTo(x1, y1, x2, y2, xN, yN)
            
        elif ( len(points) == 6 ):
            x0, y0 = points[0]
            x1, y1 = points[1]
            x2, y2 = points[2]
            x3, y3 = points[3]
            x4, y4 = points[4]
            xN, yN = points[5]
            
            xC      = (x2+x3) / 2.0
            yC      = (y2+y3) / 2.0
            
            path.moveTo(x0, y0)
            path.cubicTo(x1, y1, x2, y2, xC, yC)
            path.cubicTo(x3, y3, x4, y4, xN, yN)
            
        else:
            x0, y0 = points[0]
            xN, yN = points[-1]
            
            path.moveTo(x0, y0)
            path.lineTo(xN, yN)
        
        return path
    
    def refreshVisible( self ):
        """
        Refreshes whether or not this node should be visible based on its
        current visible state.
        """
        super(XNodeConnection, self).setVisible(self.isVisible())
    
    def setAutoCalculateInputLocation( self, state = True ):
        """
        Sets whether or not to auto calculate the input location based on \
        the proximity to the output node or point.
        
        :param     state       | <bool>
        """
        self._autoCalculateInputLocation = state
        self.setDirty()
    
    def setAutoCalculateOutputLocation( self, state = True ):
        """
        Sets whether or not to auto calculate the input location based on \
        the proximity to the output node or point.
        
        :param     state       | <bool>
        """
        self._autoCalculateOutputLocation = state
        self.setDirty()
    
    def setCustomData( self, key, value ):
        """
        Stores the inputed value as custom data on this connection for \
        the given key.
        
        :param      key     | <str>
        :param      value   | <variant>
        """
        self._customData[str(key)] = value
    
    def setDirection( self, outputLocation, inputLocation ):
        """
        Sets the output-to-input direction by setting both the locations \
        at the same time.
        
        :param      outputLocation      | <XConnectionLocation>
        :param      inputLocation       | <XConnectionLocation>
        """
        self.setOutputLocation(outputLocation)
        self.setInputLocation(inputLocation)
    
    def setDirty( self, state = True ):
        """
        Flags the connection as being dirty and needing a rebuild.
        
        :param      state   | <bool>
        """
        self._dirty = state
        
        # set if this connection should be visible
        if ( self._inputNode and self._outputNode ):
            vis = self._inputNode.isVisible() and self._outputNode.isVisible()
            self.setVisible(vis)
    
    def setDisabledPen( self, pen ):
        """
        Sets the disabled pen that will be used when rendering a connection \
        in a disabled state.
        
        :param      pen | <QPen>
        """
        self._disabledPen = QPen(pen)
    
    def setDisableWithLayer( self, state ):
        """
        Sets whether or not this connection's layer's current state should \
        affect its enabled state.
        
        :param      state | <bool>
        """
        self._disableWithLayer = state
        self.setDirty()
    
    def setEnabled( self, state ):
        """
        Sets whether or not this connection is enabled or not.
        
        :param      state | <bool>
        """
        self._enabled = state
    
    def setFont( self, font ):
        """
        Sets the font for this connection to the inputed font.
        
        :param      font | <QFont>
        """
        self._font = font
    
    def setHighlightPen( self, pen ):
        """
        Sets the pen to be used when highlighting a selected connection.
        
        :param      pen     | <QPen> || <QColor>
        """
        self._highlightPen = QPen(pen)
    
    def setInputLocation( self, location ):
        """
        Sets the input location for where this connection should point to.
        
        :param      location       | <XConnectionLocation>
        """
        self._inputLocation = location
        self.setDirty()
    
    def setInputNode( self, node ):
        """
        Sets the node that will be recieving this connection as an input.
        
        :param      node    | <XNode>
        """
        # if the node already matches the current input node, ignore
        if ( self._inputNode == node ):
            return
        
        # disconnect from the existing node
        self.disconnectSignals( self._inputNode )
        
        # store the node
        self._inputNode = node
        
        # connect to the new node
        self.connectSignals( self._inputNode )
        
        # force the rebuilding of the path
        self.setPath(self.rebuild())
    
    def setInputFixedX( self, x ):
        """
        Sets the fixed x position for the input component of this connection.
        
        :param      x       | <float> || None
        """
        self._inputFixedX = x
        self.setDirty()
    
    def setInputFixedY( self, y ):
        """
        Sets the fixed y position for the input component of this connection.
        
        :param      y       | <float> || None
        """
        self._inputFixedY = y
        self.setDirty()
        
    def setInputPoint( self, point ):
        """
        Sets the scene level input point position to draw the connection to. \
        This is used mainly by the scene when drawing a user connection - \
        it will only be used when there is no connected input node.
        
        :param      point       | <QPointF>
        """
        self._inputPoint = point
        self.setPath(self.rebuild())
    
    def setLayer( self, layer ):
        """
        Sets the layer that this node is associated with to the given layer.
        
        :param      layer       | <XNodeLayer> || None
        
        :return     <bool> changed
        """
        if ( layer == self._layer ):
            return False
        
        self._layer = layer
        self.syncLayerData()
        
        return True
    
    def setOutputLocation( self, location ):
        """
        Sets the location for the output part of the connection to generate \
        from.
        
        :param      location      | <XConnectionLocation>
        """
        self._outputLocation = location
        self.setDirty()
    
    def setOutputNode( self, node ):
        """
        Sets the node that will be generating the output information for \
        this connection.
        
        :param      node         | <XNode>
        """
        # if the output node matches the current, ignore
        if ( node == self._outputNode ):
            return
        
        # disconnect from an existing node
        self.disconnectSignals( self._outputNode )
        
        # set the current node
        self._outputNode = node
        self.connectSignals( self._outputNode )
        
        # force the rebuilding of the path
        self.setPath( self.rebuild() )
    
    def setOutputFixedX( self, x ):
        """
        Sets the fixed x position for the output component of this connection.
        
        :param      x       | <float> || None
        """
        self._outputFixedX = x
        self.setDirty()
    
    def setOutputFixedY( self, y ):
        """
        Sets the fixed y position for the output component of this connection.
        
        :param      y       | <float> || None
        """
        self._outputFixedY = y
        self.setDirty()
        
    def setOutputPoint( self, point ):
        """
        Sets the scene space point for where this connection should draw \
        its output from.  This value will only be used if no output \
        node is defined.
        
        :param      point      | <QPointF>
        """
        self._outputPoint = point
        self.setPath( self.rebuild() )
    
    def setPadding( self, padding ):
        """
        Sets the padding amount that will be used when drawing a connection \
        whose points will overlap.
        
        :param      padding    | <float>
        """
        self._padding = padding
        self.setDirty()
    
    def setShowDirectionArrow( self, state = True ):
        """
        Marks whether or not an arrow in the center of the path should be \
        drawn, showing the direction that the connection is flowing in.
        
        :param      state      | <bool>
        """
        self._showDirectionArrow = state
        self.setDirty()
    
    def setShowInputArrow( self, state = True ):
        """
        :remarks    Marks whether or not an arrow should be shown pointing
                    at the input node.
        
        :param      state       <bool>
        """
        self._showInputArrow = state
        self.setDirty()
    
    def setShowOutputArrow( self, state = True ):
        """
        :remarks    Marks whether or not an arrow should be shown pointing at
                    the output node.
        
        :param      state       <bool>
        """
        self._showOutputArrow = state
        self.setDirty()
    
    def setSquashThreshold( self, amount ):
        """
        :remarks    Sets the threshold limit of when the connection should
                    start 'squashing', calculated based on the distance between
                    the input and output points when rebuilding.
        
        :param      amount      <float>
        """
        self._squashThreshold = amount
        self.setDirty()
    
    def setStyle( self, style ):
        """
        :remarks    Sets the style of the connection that will be used.
        
        :param      style       <XConnectionStyle>
        """
        self._style = style
        self.setDirty()
        self.update()
    
    def setVisible( self, state ):
        """
        Sets whether or not this connection's local visibility should be on.
        
        :param      state | ,bool>
        """
        self._visible = state
        
        super(XNodeConnection, self).setVisible(self.isVisible())
    
    def setZValue( self, value ):
        """
        Sets the z value for this connection, also updating the text item to 
        match the value if one is defined.
        
        :param      value | <int>
        """
        super(XNodeConnection, self).setZValue(value)
        
        if ( self._textItem ):
            self._textItem.setZValue(value)
    
    def showDirectionArrow( self ):
        """
        :remarks    Return whether or not the direction arrow is visible
                    for this connection.
        
        :return     <bool>
        """
        return self._showDirectionArrow
    
    def showInputArrow( self ):
        """
        :remarks    Return whether or not the input arrow is visible
                    for this connection.
        
        :return     <bool>
        """
        return self._showInputArrow
    
    def showOutputArrow( self ):
        """
        :remarks    Return whether or not the output arrow is visible
                    for this connection.
        
        :return     <bool>
        """
        return self._showOutputArrow
    
    def squashThreshold( self ):
        """
        :remarks    Returns the sqash threshold for when the line
                    should be squashed based on the input and output
                    points becoming too close together.
        
        :return     <float>
        """
        return self._squashThreshold
    
    def setText( self, text ):
        """
        Sets the text for this connection to the inputed text.
        
        :param      text | <str>
        """
        self._text = text
        
        if ( text ):
            if ( self._textItem is None ):
                self._textItem = QGraphicsTextItem()
                self._textItem.setParentItem(self)
            
            self._textItem.setPlainText(text)
            
        elif ( self._textItem ):
            self.scene().removeItem(self._textItem)
            self._textItem = None
    
    def style( self ):
        """
        :remarks    Returns the style of the connection that is being drawn.
        
        :return     style       <XConnectionStyle>
        """
        return self._style
    
    def syncLayerData( self, layerData = None ):
        """
        Syncs the layer information for this item from the given layer data.
        
        :param      layerData | <dict>
        """
        if ( not self._layer ):
            return
        
        if ( not layerData ):
            layerData = self._layer.layerData()
        
        self.setVisible( layerData.get('visible', True) )
        
        if ( layerData.get('current') ):
            # set the default parameters
            self.setFlags( self.ItemIsSelectable )
            self.setAcceptHoverEvents(True)
            self.setZValue(99)
            
        else:
            # set the default parameters
            self.setFlags(  self.GraphicsItemFlags(0) )
            self.setAcceptHoverEvents(True)
            self.setZValue(layerData.get('zValue', 0)-1)
    
    def text( self ):
        """
        Returns the text for this connection.
        
        :return     <str>
        """
        return self._text
Beispiel #52
0
    def drawCorner(self, painter, position, cornerType, maxRadius=None):
        #logging.debug(self.__class__.__name__ +": drawCorner() "+ self.cornerTypeString(cornerType))
        thickness = self.CONNECTION_THICKNESS * self.zoomFactor()
        halfthick = thickness / 2
        cornerRoundness = halfthick**0.5
        cornerOffset = halfthick * (cornerRoundness)
        innerCorner = halfthick * (cornerRoundness - 1)
        outerCorner = halfthick * (cornerRoundness + 1)
        innerWidth = halfthick * (cornerRoundness - 1)
        radius = halfthick * (cornerRoundness + 1)
        if maxRadius:
            maxRadius = max(maxRadius, thickness)
            radius = min(radius, maxRadius)

        if cornerType == self.CornerType.TOP_RIGHT:
            startAngle = 0

            outerCorner = QPointF(position.x() + halfthick - 2 * radius,
                                  position.y() - halfthick)
            innerCorner = QPointF(outerCorner.x(),
                                  outerCorner.y() + (thickness))
            center = QPointF(outerCorner.x() + radius,
                             outerCorner.y() + radius)

            outerRect = QRectF(outerCorner, QSizeF(2 * radius, 2 * radius))
            innerRect = QRectF(
                innerCorner,
                QSizeF((2 * radius - thickness), (2 * radius - thickness)))

            outerStart = QPointF(outerCorner.x() + 2 * radius,
                                 outerCorner.y() + (radius + halfthick))
            innerStart = QPointF(outerCorner.x() + (radius - halfthick),
                                 outerCorner.y())

        elif cornerType == self.CornerType.TOP_LEFT:
            startAngle = 90

            outerCorner = QPointF(position.x() - halfthick,
                                  position.y() - halfthick)
            innerCorner = QPointF(outerCorner.x() + (thickness),
                                  outerCorner.y() + (thickness))
            center = QPointF(outerCorner.x() + radius,
                             outerCorner.y() + radius)

            outerRect = QRectF(outerCorner, QSizeF(2 * radius, 2 * radius))
            innerRect = QRectF(
                innerCorner,
                QSizeF((2 * radius - thickness), (2 * radius - thickness)))

            outerStart = QPointF(outerCorner.x() + (radius + halfthick),
                                 outerCorner.y())
            innerStart = QPointF(outerCorner.x(),
                                 outerCorner.y() + (radius + halfthick))

        elif cornerType == self.CornerType.BOTTOM_LEFT:
            startAngle = 180

            outerCorner = QPointF(position.x() - halfthick,
                                  position.y() + halfthick - 2 * radius)
            innerCorner = QPointF(outerCorner.x() + (thickness),
                                  outerCorner.y())
            center = QPointF(outerCorner.x() + radius,
                             outerCorner.y() + radius)

            outerRect = QRectF(outerCorner, QSizeF(2 * radius, 2 * radius))
            innerRect = QRectF(
                innerCorner,
                QSizeF((2 * radius - thickness), (2 * radius - thickness)))

            outerStart = QPointF(outerCorner.x(),
                                 outerCorner.y() + (radius - halfthick))
            innerStart = QPointF(outerCorner.x() + (radius + halfthick),
                                 outerCorner.y() + (2 * radius))

        elif cornerType == self.CornerType.BOTTOM_RIGHT:
            startAngle = 270

            outerCorner = QPointF(position.x() + halfthick - 2 * radius,
                                  position.y() + halfthick - 2 * radius)
            innerCorner = QPointF(outerCorner.x(), outerCorner.y())
            center = QPointF(outerCorner.x() + radius,
                             outerCorner.y() + radius)

            outerRect = QRectF(outerCorner, QSizeF(2 * radius, 2 * radius))
            innerRect = QRectF(
                innerCorner,
                QSizeF((2 * radius - thickness), (2 * radius - thickness)))

            outerStart = QPointF(outerCorner.x() + (radius - halfthick),
                                 outerCorner.y() + 2 * radius)
            innerStart = QPointF(outerCorner.x() + 2 * radius,
                                 outerCorner.y() + (radius - halfthick))

        else:
            # No defined corner, so nothing to draw.
            #print "PointToPointConnection.drawCorner() - No valid corner, aborting..."
            return

        if painter.redirected(painter.device()):
            # e.q. QPixmap.grabWidget()
            painter.setBrush(self.FILL_COLOR1)
        else:
            brush = QRadialGradient(center, radius)
            if radius >= thickness:
                brush.setColorAt((radius - thickness) / radius,
                                 self.FILL_COLOR1)  # inner border
                brush.setColorAt((radius - halfthick + 1) / radius,
                                 self.FILL_COLOR2)  # center of line
            else:
                # If zoom is too small use single color
                brush.setColorAt(0, self.FILL_COLOR1)
            brush.setColorAt(1, self.FILL_COLOR1)  # outer border
            painter.setBrush(brush)

        path = QPainterPath()
        path.moveTo(outerStart)
        path.arcTo(outerRect, startAngle, 90)
        path.lineTo(innerStart)
        path.arcTo(innerRect, startAngle + 90, -90)
        path.closeSubpath()

        #painter.setPen(Qt.NoPen)
        painter.drawPath(path)