Ejemplo n.º 1
0
class CartPoleShape:
    def __init__(self):
        self._boundingBox = QRectF(-239 / 2, -216 / 2, 239, 216)

        self._cartBody = QRectF(self._boundingBox.left(),
                                self._boundingBox.top() + 82,
                                self._boundingBox.width(), 106)

        self._cartJointBody = QRectF(
            self._cartBody.center().x() - 53 / 2,
            self._boundingBox.top() + 53 / 2, 53,
            self._cartBody.top() - self._boundingBox.top() - 53 / 2)

        self._cartJointBall = QRectF(
            self._cartJointBody.center().x() - self._cartJointBody.width() / 2,
            self._cartJointBody.top() - self._cartJointBody.width() / 2,
            self._cartJointBody.width(), self._cartJointBody.width())

        self._poleCenter = self._cartJointBall.center()

        self._leftGear = QRectF(self._boundingBox.left() + 43 - 26,
                                self._boundingBox.top() + 188 - 26, 26 * 2,
                                26 * 2)

        self._leftGearJoint = QRectF(self._boundingBox.left() + 43 - 2,
                                     self._boundingBox.top() + 188 - 2, 4, 4)

        self._rightGear = QRectF(self._boundingBox.left() + 194 - 26,
                                 self._boundingBox.top() + 188 - 26, 26 * 2,
                                 26 * 2)

        self._rightGearJoint = QRectF(self._boundingBox.left() + 194 - 2,
                                      self._boundingBox.top() + 188 - 2, 4, 4)

    def draw(self, qp, pen):
        qp.setPen(pen)
        qp.drawRect(self._cartBody)
        qp.drawLines([
            QLineF(self._cartJointBody.topLeft(),
                   self._cartJointBody.bottomLeft()),
            QLineF(self._cartJointBody.topRight(),
                   self._cartJointBody.bottomRight()),
            QLineF(self._cartJointBody.bottomLeft(),
                   self._cartJointBody.bottomRight())
        ])
        qp.drawArc(self._cartJointBall, 0, 180 * 16)
        qp.drawEllipse(self._leftGear)
        qp.drawEllipse(self._leftGearJoint)
        qp.drawEllipse(self._rightGear)
        qp.drawEllipse(self._rightGearJoint)

    # Computes the model matrix that moves the cart in center and has
    # size width
    def modelMatrix(self, center, width):
        return QTransform.fromScale(width / 239,
                                    width / 239) * QTransform.fromTranslate(
                                        -center.x(), -center.y())
Ejemplo n.º 2
0
    def _hitTest(self, rc: QRectF, mousePos: QPointF) -> Hit:
        maxdist = 4
        if not rc.adjusted(-maxdist, -maxdist, maxdist,
                           maxdist).contains(mousePos):
            return Hit.NoHit

        def dist(p1, p2):
            return (p1 - p2).manhattanLength()

        if dist(rc.topLeft(), mousePos) < maxdist:
            return Hit.TopLeft
        elif dist(rc.topRight(), mousePos) < maxdist:
            return Hit.TopRight
        elif dist(rc.bottomRight(), mousePos) < maxdist:
            return Hit.BottomRight
        elif dist(rc.bottomLeft(), mousePos) < maxdist:
            return Hit.BottomLeft
        elif abs(rc.left() - mousePos.x()) < maxdist:
            return Hit.Left
        elif abs(rc.right() - mousePos.x()) < maxdist:
            return Hit.Right
        elif abs(rc.top() - mousePos.y()) < maxdist:
            return Hit.Top
        elif abs(rc.bottom() - mousePos.y()) < maxdist:
            return Hit.Bottom
        elif rc.contains(mousePos):
            return Hit.Center
        else:
            return Hit.NoHit
Ejemplo n.º 3
0
    def draw_bg(self, qp, rect, text):
        path = QPainterPath()

        # add container
        path.addRoundedRect(rect, 4, 4)
        if self.isEnabled():
            highlight_color, bg_color, text_color = (
                self.settings['highlight'], self.settings['bg'],
                self.settings['text'])
        else:
            highlight_color, bg_color, text_color = (
                self.settings['highlight_disabled'],
                self.settings['bg_disabled'], self.settings['text_disabled'])
        qp.setPen(QPen(highlight_color, 2))
        qp.fillPath(path, bg_color)

        # add close button
        circle_size = rect.height() / 1.8
        pen_size = 2
        qp.setPen(QPen(text_color, pen_size, Qt.SolidLine))
        rect = QRectF(
            rect.right() - circle_size - self.settings['padding-x'] / 2,
            rect.top() + (rect.height() - circle_size) / 2, circle_size,
            circle_size)
        path.addEllipse(rect)
        qp.drawPath(path)
        # draw cross
        inside_rect = QRectF(rect)
        inside_rect.adjust(pen_size, pen_size, -pen_size, -pen_size)
        qp.drawLine(inside_rect.topLeft(), inside_rect.bottomRight())
        qp.drawLine(inside_rect.bottomLeft(), inside_rect.topRight())

        self.close_rectangles[text] = rect
Ejemplo n.º 4
0
    def drawNode(painter, isSelected, node):
        rect = NodePainter.getNodeDim(node)

        if isSelected:
            painter.setPen(QPen(Qt.cyan, 3.0))
        else:
            painter.setPen(Qt.black)

        painter.setBrush(Qt.gray)
        painter.drawRect(rect)

        for index, port in enumerate(node.data.inputs):
            NodePainter.drawPort(painter, False, index,
                                 node.data.getInput(index).value)

        # for index, port in enumerate(node.node.value):
        #    NodePainter.drawPort(painter, False, index, node.data.getOutput(index))
        NodePainter.drawPort(painter, True, 0, node.data.getOutput(0))

        metrics = QFontMetrics(painter.font())
        textBounds = metrics.boundingRect(node.title)

        painter.setPen(QColor(0, 0, 0, 128))
        painter.setBrush(QColor(0, 0, 0, 128))

        textRect = QRectF((rect.width() - textBounds.width()) / 2.0,
                          (rect.height() - textBounds.height()) / 2.0 - 3.5,
                          textBounds.width(), textBounds.height())
        painter.drawText(textRect.bottomLeft(), node.title)
Ejemplo n.º 5
0
class MapObjectOutline(QGraphicsItem):
    def __init__(self, object, parent=None):
        super().__init__(parent)

        self.mObject = object
        self.mBoundingRect = QRectF()
        self.setZValue(1)  # makes sure outlines are above labels

    def syncWithMapObject(self, renderer):
        pixelPos = renderer.pixelToScreenCoords_(self.mObject.position())
        bounds = objectBounds(self.mObject, renderer)
        bounds.translate(-pixelPos)
        self.setPos(pixelPos + self.mObject.objectGroup().offset())
        self.setRotation(self.mObject.rotation())
        if (self.mBoundingRect != bounds):
            self.prepareGeometryChange()
            self.mBoundingRect = bounds

    def boundingRect(self):
        return self.mBoundingRect

    def paint(self, painter, arg2, arg3):
        horizontal = [
            QLineF(self.mBoundingRect.topRight(),
                   self.mBoundingRect.topLeft()),
            QLineF(self.mBoundingRect.bottomRight(),
                   self.mBoundingRect.bottomLeft())
        ]

        vertical = [
            QLineF(self.mBoundingRect.bottomLeft(),
                   self.mBoundingRect.topLeft()),
            QLineF(self.mBoundingRect.bottomRight(),
                   self.mBoundingRect.topRight())
        ]

        dashPen = QPen(Qt.DashLine)
        dashPen.setCosmetic(True)
        dashPen.setDashOffset(max(0.0, self.x()))
        painter.setPen(dashPen)
        painter.drawLines(horizontal)
        dashPen.setDashOffset(max(0.0, self.y()))
        painter.setPen(dashPen)
        painter.drawLines(vertical)
Ejemplo n.º 6
0
    def draw(self, p: QtGui.QPainter, rect: QtCore.QRectF) -> None:
        p.setPen(self._pen)

        if self._orient_v == 'bottom':
            lp, rp = rect.topLeft(), rect.topRight()
            # p.drawLine(rect.topLeft(), rect.topRight())
        elif self._orient_v == 'top':
            lp, rp = rect.bottomLeft(), rect.bottomRight()

        p.drawLine(lp.x(), lp.y(), rp.x(), rp.y())
Ejemplo n.º 7
0
def qgraphicsview_map_rect_from_scene(view: QGraphicsView,
                                      rect: QRectF) -> QPolygonF:
    """Like QGraphicsView.mapFromScene(QRectF) but returning a QPolygonF
    (without rounding).
    """
    tr = view.viewportTransform()
    p1 = tr.map(rect.topLeft())
    p2 = tr.map(rect.topRight())
    p3 = tr.map(rect.bottomRight())
    p4 = tr.map(rect.bottomLeft())
    return QPolygonF([p1, p2, p3, p4])
Ejemplo n.º 8
0
def get_sides_of(rect: QRectF):
    """
    This method returns the sides of a rect as a dictionary.
    Parameters
    ----------
    rect : QRectF
        The rect to inspect.
    Returns
    ----------
    dict
        A container of the sides represented as QLineF
        objects and their position as a key.

    """

    return {
        "top": QLineF(rect.topLeft(), rect.topRight()),
        "right": QLineF(rect.topRight(), rect.bottomRight()),
        "bottom": QLineF(rect.bottomLeft(), rect.bottomRight()),
        "left": QLineF(rect.bottomLeft(), rect.topLeft())
    }
Ejemplo n.º 9
0
class MapObjectOutline(QGraphicsItem):

    def __init__(self, object, parent = None):
        super().__init__(parent)
        
        self.mObject = object
        self.mBoundingRect = QRectF()
        self.setZValue(1) # makes sure outlines are above labels
    
    def syncWithMapObject(self, renderer):
        pixelPos = renderer.pixelToScreenCoords_(self.mObject.position())
        bounds = objectBounds(self.mObject, renderer)
        bounds.translate(-pixelPos)
        self.setPos(pixelPos + self.mObject.objectGroup().offset())
        self.setRotation(self.mObject.rotation())
        if (self.mBoundingRect != bounds):
            self.prepareGeometryChange()
            self.mBoundingRect = bounds

    def boundingRect(self):
        return self.mBoundingRect
    
    def paint(self, painter, arg2, arg3):
        horizontal = [
            QLineF(self.mBoundingRect.topRight(), self.mBoundingRect.topLeft()), 
            QLineF(self.mBoundingRect.bottomRight(), self.mBoundingRect.bottomLeft())]
        
        vertical = [
            QLineF(self.mBoundingRect.bottomLeft(), self.mBoundingRect.topLeft()), 
            QLineF(self.mBoundingRect.bottomRight(), self.mBoundingRect.topRight())]
        
        dashPen = QPen(Qt.DashLine)
        dashPen.setCosmetic(True)
        dashPen.setDashOffset(max(0.0, self.x()))
        painter.setPen(dashPen)
        painter.drawLines(horizontal)
        dashPen.setDashOffset(max(0.0, self.y()))
        painter.setPen(dashPen)
        painter.drawLines(vertical)
Ejemplo n.º 10
0
    def _paint_stimulus_points_cell(self, painter, option, model_index):

        points = model_index.data(
            role=ProtocolSequence.Roles.STIMULUS_POINTS.value)

        if points:

            font_metrics = QFontMetrics(option.font)
            font_height = font_metrics.height()

            pixmap_plus1 = QPixmap()
            pixmap_plus1.load(Resources.get('one.png'))

            pixmap_random = QPixmap()
            pixmap_random.load(Resources.get('dice.png'))

            padding = 1

            max_img_height = max(pixmap_plus1.height(), pixmap_random.height())
            max_img_width = max(pixmap_plus1.width(), pixmap_random.width())

            cell_centre = (option.rect.x() + 0.5 * option.rect.width(),
                           option.rect.y() + 0.5 * option.rect.height())

            container_height = font_height + max_img_height + 2 * padding
            container_width = max_img_width + 2 * padding

            start_x = cell_centre[0] - container_width * (len(points) / 2)

            container = QRectF(start_x,
                               cell_centre[1] - 0.5 * container_height,
                               container_width, container_height)

            self.container_height = max(self.container_height,
                                        container.height())

            for point in points:
                pixmap = point.pattern.pixmap(max_img_height)
                painter.drawPixmap(container.topLeft().x() + padding,
                                   container.topLeft().y() + padding, pixmap)

                text = str(point.index())
                text_bounding_rect = font_metrics.boundingRect(text)
                painter.drawText(
                    container.center().x() - 0.5 * text_bounding_rect.width(),
                    container.bottomLeft().y() - padding, text)

                container.translate(container_width, 0)

            self.sizeHintChanged.emit(model_index)
Ejemplo n.º 11
0
    def _debug_draw(self, painter, option, model_index):

        num_points = 9

        font_metrics = QFontMetrics(option.font)

        pixmap = QPixmap()
        pixmap.load(Resources.get('dice.png'))

        padding = 1

        max_img_height = pixmap.height()

        cell_centre = (option.rect.x() + 0.5 * option.rect.width(),
                       option.rect.y() + 0.5 * option.rect.height())

        font_height = font_metrics.height()

        container_height = font_height + max_img_height + 2 * padding
        container_width = pixmap.width() + 2 * padding

        start_x = cell_centre[0] - container_width * (num_points / 2)

        painter.drawLine(QPointF(cell_centre[0], 0),
                         QPointF(cell_centre[0], option.rect.height()))
        painter.drawLine(QPointF(0, cell_centre[1]),
                         QPointF(option.rect.width(), cell_centre[1]))

        container = QRectF(start_x, cell_centre[1] - 0.5 * container_height,
                           container_width, container_height)

        for i in range(0, num_points):
            painter.drawRect(container)

            painter.drawPixmap(container.topLeft().x() + padding,
                               container.topLeft().y() + padding, pixmap)

            text = str(i)
            text_bounding_rect = font_metrics.boundingRect(text)
            painter.drawText(
                container.center().x() - 0.5 * text_bounding_rect.width(),
                container.bottomLeft().y() - padding, text)

            container.translate(container_width, 0)
Ejemplo n.º 12
0
    def redraw(self):
        self.graphicsScene.clear()

        # draw screenshot
        self.graphicsScene.addPixmap(self.screenPixel)

        # prepare for drawing selected area
        rect = QRectF(self.selectedArea)
        rect = rect.normalized()

        topLeftPoint = rect.topLeft()
        topRightPoint = rect.topRight()
        bottomLeftPoint = rect.bottomLeft()
        bottomRightPoint = rect.bottomRight()
        topMiddlePoint = (topLeftPoint + topRightPoint) / 2
        leftMiddlePoint = (topLeftPoint + bottomLeftPoint) / 2
        bottomMiddlePoint = (bottomLeftPoint + bottomRightPoint) / 2
        rightMiddlePoint = (topRightPoint + bottomRightPoint) / 2

        # draw the picture mask
        mask = QColor(0, 0, 0, 155)

        if self.selectedArea == QRect():
            self.graphicsScene.addRect(0, 0, self.screenPixel.width(),
                                       self.screenPixel.height(),
                                       QPen(Qt.NoPen), mask)
        else:
            self.graphicsScene.addRect(0, 0, self.screenPixel.width(),
                                       topRightPoint.y(), QPen(Qt.NoPen), mask)
            self.graphicsScene.addRect(0, topLeftPoint.y(), topLeftPoint.x(),
                                       rect.height(), QPen(Qt.NoPen), mask)
            self.graphicsScene.addRect(
                topRightPoint.x(), topRightPoint.y(),
                self.screenPixel.width() - topRightPoint.x(), rect.height(),
                QPen(Qt.NoPen), mask)
            self.graphicsScene.addRect(
                0, bottomLeftPoint.y(), self.screenPixel.width(),
                self.screenPixel.height() - bottomLeftPoint.y(),
                QPen(Qt.NoPen), mask)

        # draw the toolBar
        if self.action != ACTION_SELECT:
            spacing = 5
            # show the toolbar first, then move it to the correct position
            # because the width of it may be wrong if this is the first time it shows
            self.tooBar.show()

            dest = QPointF(rect.bottomRight() -
                           QPointF(self.tooBar.width(), 0) -
                           QPointF(spacing, -spacing))
            if dest.x() < spacing:
                dest.setX(spacing)
            pen_set_bar_height = self.penSetBar.height(
            ) if self.penSetBar is not None else 0
            if dest.y() + self.tooBar.height(
            ) + pen_set_bar_height >= self.height():
                if rect.top() - self.tooBar.height(
                ) - pen_set_bar_height < spacing:
                    dest.setY(rect.top() + spacing)
                else:
                    dest.setY(rect.top() - self.tooBar.height() -
                              pen_set_bar_height - spacing)

            self.tooBar.move(dest.toPoint())

            if self.penSetBar is not None:
                self.penSetBar.show()
                self.penSetBar.move(dest.toPoint() +
                                    QPoint(0,
                                           self.tooBar.height() + spacing))

                if self.action == ACTION_TEXT:
                    self.penSetBar.showFontWidget()
                else:
                    self.penSetBar.showPenWidget()
        else:
            self.tooBar.hide()

            if self.penSetBar is not None:
                self.penSetBar.hide()

        # draw the list
        for step in self.drawListResult:
            self.drawOneStep(step)

        if self.drawListProcess is not None:
            self.drawOneStep(self.drawListProcess)
            if self.action != ACTION_TEXT:
                self.drawListProcess = None

        if self.selectedArea != QRect():
            self.itemsToRemove = []

            # draw the selected rectangle
            pen = QPen(QColor(0, 255, 255), 2)
            self.itemsToRemove.append(self.graphicsScene.addRect(rect, pen))

            # draw the drag point
            radius = QPoint(3, 3)
            brush = QBrush(QColor(0, 255, 255))
            self.itemsToRemove.append(
                self.graphicsScene.addEllipse(
                    QRectF(topLeftPoint - radius, topLeftPoint + radius), pen,
                    brush))
            self.itemsToRemove.append(
                self.graphicsScene.addEllipse(
                    QRectF(topMiddlePoint - radius, topMiddlePoint + radius),
                    pen, brush))
            self.itemsToRemove.append(
                self.graphicsScene.addEllipse(
                    QRectF(topRightPoint - radius, topRightPoint + radius),
                    pen, brush))
            self.itemsToRemove.append(
                self.graphicsScene.addEllipse(
                    QRectF(leftMiddlePoint - radius, leftMiddlePoint + radius),
                    pen, brush))
            self.itemsToRemove.append(
                self.graphicsScene.addEllipse(
                    QRectF(rightMiddlePoint - radius,
                           rightMiddlePoint + radius), pen, brush))
            self.itemsToRemove.append(
                self.graphicsScene.addEllipse(
                    QRectF(bottomLeftPoint - radius, bottomLeftPoint + radius),
                    pen, brush))
            self.itemsToRemove.append(
                self.graphicsScene.addEllipse(
                    QRectF(bottomMiddlePoint - radius,
                           bottomMiddlePoint + radius), pen, brush))
            self.itemsToRemove.append(
                self.graphicsScene.addEllipse(
                    QRectF(bottomRightPoint - radius,
                           bottomRightPoint + radius), pen, brush))

        # draw the textedit
        if self.textPosition is not None:
            textSpacing = 50
            position = QPoint()
            if self.textPosition.x() + self.textInput.width(
            ) >= self.screenPixel.width():
                position.setX(self.textPosition.x() - self.textInput.width())
            else:
                position.setX(self.textPosition.x())

            if self.textRect is not None:
                if self.textPosition.y() + self.textInput.height(
                ) + self.textRect.height() >= self.screenPixel.height():
                    position.setY(self.textPosition.y() -
                                  self.textInput.height() -
                                  self.textRect.height())
                else:
                    position.setY(self.textPosition.y() +
                                  self.textRect.height())
            else:
                if self.textPosition.y() + self.textInput.height(
                ) >= self.screenPixel.height():
                    position.setY(self.textPosition.y() -
                                  self.textInput.height())
                else:
                    position.setY(self.textPosition.y())

            self.textInput.move(position)
            self.textInput.show()
            # self.textInput.getFocus()

        # draw the magnifier
        if self.action == ACTION_SELECT:
            self.drawMagnifier()
            if self.mousePressed:
                self.drawSizeInfo()

        if self.action == ACTION_MOVE_SELECTED:
            self.drawSizeInfo()
Ejemplo n.º 13
0
    def drawMagnifier(self):
        # First, calculate the magnifier position due to the mouse position
        watchAreaWidth = 16
        watchAreaHeight = 16
        watchAreaPixmap = QPixmap()

        cursor_pos = self.mousePoint

        watchArea = QRect(
            QPoint(cursor_pos.x() - watchAreaWidth / 2,
                   cursor_pos.y() - watchAreaHeight / 2),
            QPoint(cursor_pos.x() + watchAreaWidth / 2,
                   cursor_pos.y() + watchAreaHeight / 2))
        if watchArea.left() < 0:
            watchArea.moveLeft(0)
            watchArea.moveRight(watchAreaWidth)
        if self.mousePoint.x() + watchAreaWidth / 2 >= self.screenPixel.width(
        ):
            watchArea.moveRight(self.screenPixel.width() - 1)
            watchArea.moveLeft(watchArea.right() - watchAreaWidth)
        if self.mousePoint.y() - watchAreaHeight / 2 < 0:
            watchArea.moveTop(0)
            watchArea.moveBottom(watchAreaHeight)
        if self.mousePoint.y(
        ) + watchAreaHeight / 2 >= self.screenPixel.height():
            watchArea.moveBottom(self.screenPixel.height() - 1)
            watchArea.moveTop(watchArea.bottom() - watchAreaHeight)

        # tricks to solve the hidpi impact on QCursor.pos()
        watchArea.setTopLeft(
            QPoint(watchArea.topLeft().x() * self.scale,
                   watchArea.topLeft().y() * self.scale))
        watchArea.setBottomRight(
            QPoint(watchArea.bottomRight().x() * self.scale,
                   watchArea.bottomRight().y() * self.scale))
        watchAreaPixmap = self.screenPixel.copy(watchArea)

        # second, calculate the magnifier area
        magnifierAreaWidth = watchAreaWidth * 10
        magnifierAreaHeight = watchAreaHeight * 10
        fontAreaHeight = 40

        cursorSize = 24
        magnifierArea = QRectF(
            QPoint(QCursor.pos().x() + cursorSize,
                   QCursor.pos().y() + cursorSize),
            QPoint(QCursor.pos().x() + cursorSize + magnifierAreaWidth,
                   QCursor.pos().y() + cursorSize + magnifierAreaHeight))
        if magnifierArea.right() >= self.screenPixel.width():
            magnifierArea.moveLeft(QCursor.pos().x() - magnifierAreaWidth -
                                   cursorSize / 2)
        if magnifierArea.bottom() + fontAreaHeight >= self.screenPixel.height(
        ):
            magnifierArea.moveTop(QCursor.pos().y() - magnifierAreaHeight -
                                  cursorSize / 2 - fontAreaHeight)

        # third, draw the watch area to magnifier area
        watchAreaScaled = watchAreaPixmap.scaled(
            QSize(magnifierAreaWidth * self.scale,
                  magnifierAreaHeight * self.scale))
        magnifierPixmap = self.graphicsScene.addPixmap(watchAreaScaled)
        magnifierPixmap.setOffset(magnifierArea.topLeft())

        # then draw lines and text
        self.graphicsScene.addRect(QRectF(magnifierArea),
                                   QPen(QColor(255, 255, 255), 2))
        self.graphicsScene.addLine(
            QLineF(QPointF(magnifierArea.center().x(), magnifierArea.top()),
                   QPointF(magnifierArea.center().x(),
                           magnifierArea.bottom())),
            QPen(QColor(0, 255, 255), 2))
        self.graphicsScene.addLine(
            QLineF(QPointF(magnifierArea.left(),
                           magnifierArea.center().y()),
                   QPointF(magnifierArea.right(),
                           magnifierArea.center().y())),
            QPen(QColor(0, 255, 255), 2))

        # get the rgb of mouse point
        pointRgb = QColor(self.screenPixel.toImage().pixel(self.mousePoint))

        # draw information
        self.graphicsScene.addRect(
            QRectF(
                magnifierArea.bottomLeft(),
                magnifierArea.bottomRight() + QPoint(0, fontAreaHeight + 30)),
            Qt.black, QBrush(Qt.black))
        rgbInfo = self.graphicsScene.addSimpleText(
            ' Rgb: ({0}, {1}, {2})'.format(pointRgb.red(), pointRgb.green(),
                                           pointRgb.blue()))
        rgbInfo.setPos(magnifierArea.bottomLeft() + QPoint(0, 5))
        rgbInfo.setPen(QPen(QColor(255, 255, 255), 2))

        rect = self.selectedArea.normalized()
        sizeInfo = self.graphicsScene.addSimpleText(' Size: {0} x {1}'.format(
            rect.width() * self.scale,
            rect.height() * self.scale))
        sizeInfo.setPos(magnifierArea.bottomLeft() + QPoint(0, 15) +
                        QPoint(0, fontAreaHeight / 2))
        sizeInfo.setPen(QPen(QColor(255, 255, 255), 2))
Ejemplo n.º 14
0
# construct paths for breakpoint handles
def _hashMarkGen(path, p1, p2, p3):
    path.moveTo(p1)
    path.lineTo(p2)
    path.lineTo(p3)
# end

# create hash marks QPainterPaths only once
_PP_RECT = QRectF(0, 0, styles.PATH_BASE_WIDTH, styles.PATH_BASE_WIDTH)
_PATH_CENTER = QPointF(styles.PATH_BASE_WIDTH / 2,\
                          styles.PATH_BASE_WIDTH / 2)
_PATH_U_CENTER = QPointF(styles.PATH_BASE_WIDTH / 2, 0)
_PATH_D_CENTER = QPointF(styles.PATH_BASE_WIDTH / 2, styles.PATH_BASE_WIDTH)
_PPATH_LU = QPainterPath()
_hashMarkGen(_PPATH_LU, _PP_RECT.bottomLeft(), _PATH_D_CENTER, _PATH_CENTER)
_PPATH_RU = QPainterPath()
_hashMarkGen(_PPATH_RU, _PP_RECT.bottomRight(), _PATH_D_CENTER, _PATH_CENTER)
_PPATH_RD = QPainterPath()
_hashMarkGen(_PPATH_RD, _PP_RECT.topRight(), _PATH_U_CENTER, _PATH_CENTER)
_PPATH_LD = QPainterPath()
_hashMarkGen(_PPATH_LD, _PP_RECT.topLeft(), _PATH_U_CENTER, _PATH_CENTER)

_SCAF_PEN = QPen(styles.PXI_SCAF_STROKE, styles.PATH_STRAND_STROKE_WIDTH)
_SCAF_PEN.setCapStyle(Qt.FlatCap)  # or Qt.RoundCap
_SCAF_PEN.setJoinStyle(Qt.RoundJoin)
_STAP_PEN = QPen(styles.PXI_STAP_STROKE, styles.PATH_STRAND_STROKE_WIDTH)
_STAP_PEN.setCapStyle(Qt.FlatCap)  # or Qt.RoundCap
_STAP_PEN.setJoinStyle(Qt.RoundJoin)
_DISAB_PEN = QPen(styles.PXI_DISAB_STROKE, styles.PATH_STRAND_STROKE_WIDTH)
_DISAB_PEN.setCapStyle(Qt.FlatCap)
Ejemplo n.º 15
0
class PangoBboxGraphic(PangoGraphic):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setFlag(QGraphicsItem.ItemIsSelectable)
        self.fpath = None
        self.rect = QRectF()

    def paint(self, painter, option, widget):
        super().paint(painter, option, widget)
        w = self.dw()
        pen = self.pen()
        pen.setWidth(int(w))
        painter.setPen(pen)

        if not self.force_opaque:
            painter.setOpacity(0.8)
        painter.drawRect(self.rect)

        if self.parentItem() is not None:
            self.paint_text_rect(painter)

        painter.setOpacity(1)
        if option.state & QStyle.State_MouseOver or self.isSelected():
            painter.drawEllipse(self.rect.topLeft(), w, w)
            painter.drawEllipse(self.rect.topRight(), w, w)
            painter.drawEllipse(self.rect.bottomLeft(), w, w)
            painter.drawEllipse(self.rect.bottomRight(), w, w)

    def paint_text_rect(self, painter):
        p = painter.pen()

        font = QFont()
        font.setPointSizeF(self.dw()*3)
        painter.setFont(font)
        painter.setBrush(self.brush())

        fm = QFontMetrics(font)
        w = fm.width(self.parentItem().name)
        h = fm.height()
        br = self.rect.bottomRight()

        text_rect = QRectF(QPointF(br.x()-w, br.y()-h), br)

        if text_rect.width() < self.boundingRect().width()/2 and\
                text_rect.height() < self.boundingRect().height()/2:
            painter.drawRect(text_rect)
            pen = self.pen()
            pen.setColor(QColor("black"))
            painter.setPen(pen)
            painter.drawText(text_rect, Qt.AlignCenter, self.parentItem().name)

        painter.setPen(p)

    def boundingRect(self):
        w = self.dw()
        return self.shape().controlPointRect().adjusted(-w*2, -w*2, w*2, w*2)

    def shape(self):
        path = QPainterPath()
        path.addRect(self.rect)
        return self.shape_from_path(path, self.pen())
Ejemplo n.º 16
0
class TextBoxItem(QGraphicsItem):
    max_rect = QRect(-56, -20, 112, 40)
    init_offset = QPointF(66, -34)
    dummy_contents = 'XX-ABCDE  ####\n##### xxxxx\n10000 = 10000  X####'
    dummy_contents_compact = 'XX-ABCDE\n10000  #### '
    txt_rect_2lines = QRectF()  # STATIC
    txt_rect_3lines = QRectF()  # STATIC

    def setRectanglesFromFont(font):
        TextBoxItem.txt_rect_2lines = QRectF(
            QFontMetrics(font).boundingRect(
                TextBoxItem.max_rect, Qt.AlignLeft,
                TextBoxItem.dummy_contents_compact))
        TextBoxItem.txt_rect_3lines = QRectF(
            QFontMetrics(font).boundingRect(TextBoxItem.max_rect, Qt.AlignLeft,
                                            TextBoxItem.dummy_contents))

    def __init__(self, parent_item):
        QGraphicsItem.__init__(self, parent_item)
        self.radar_contact = parent_item.radar_contact
        self.info_text = ''
        self.rectangle = QRectF()
        self.setCursor(Qt.PointingHandCursor)
        self.setPos(TextBoxItem.init_offset)
        self.mouse_hovering = False
        self.paint_border = True
        self.setFlag(QGraphicsItem.ItemIsMovable, True)
        self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True)
        self.setAcceptHoverEvents(True)
        self.updateContents()

    def updateContents(self):
        self.prepareGeometryChange()
        expand = self.mouse_hovering or self.radar_contact is selection.acft or env.linkedStrip(
            self.radar_contact) != None
        self.paint_border = expand
        self.info_text = infoTextLines(self.radar_contact, not expand)
        self.rectangle = TextBoxItem.txt_rect_3lines if expand else TextBoxItem.txt_rect_2lines

    def positionQuadrant(self):
        return (1 if self.pos().x() > 0 else -1), (1 if self.pos().y() > 0 else
                                                   -1)

    def calloutConnectingPoint(self):
        q = self.positionQuadrant()
        if q == (-1, -1): return self.rectangle.bottomRight()
        elif q == (-1, 1): return self.rectangle.topRight()
        elif q == (1, -1): return self.rectangle.bottomLeft()
        elif q == (1, 1): return self.rectangle.topLeft()

    def paint(self, painter, option, widget):
        coloured_pen = new_pen(ACFT_pen_colour(self.radar_contact))
        # 1. Write info text
        painter.setPen(coloured_pen)
        painter.drawText(self.rectangle, Qt.AlignLeft | Qt.AlignVCenter,
                         self.info_text)
        # 2. Draw container box?
        if self.paint_border:
            pen = coloured_pen if self.radar_contact is selection.acft else new_pen(
                settings.colour('radar_tag_line'))
            if self.radar_contact.individual_cheat:
                pen.setStyle(Qt.DashLine)
            painter.setPen(pen)
            painter.drawRect(self.rectangle)

    def boundingRect(self):
        return self.rectangle

    # EVENTS

    def itemChange(self, change, value):
        if change == QGraphicsItem.ItemPositionChange:
            self.parentItem().textBoxChanged()
        return QGraphicsItem.itemChange(self, change, value)

    def hoverEnterEvent(self, event):
        self.mouse_hovering = True
        self.updateContents()
        self.parentItem().textBoxChanged()
        QGraphicsItem.hoverEnterEvent(self, event)

    def hoverLeaveEvent(self, event):
        self.mouse_hovering = False
        self.updateContents()
        self.parentItem().textBoxChanged()
        QGraphicsItem.hoverLeaveEvent(self, event)

    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton:
            selection.selectAircraft(self.radar_contact)
        elif event.button() == Qt.MiddleButton:
            if event.modifiers() & Qt.ShiftModifier:
                selection.unlinkAircraft(self.radar_contact)
            else:
                selection.linkAircraft(self.radar_contact)
            event.accept()
        QGraphicsItem.mousePressEvent(self, event)

    def mouseDoubleClickEvent(self, event):
        if event.button() == Qt.LeftButton:
            if event.modifiers() & Qt.ShiftModifier:  # reset box position
                self.setPos(TextBoxItem.init_offset)
                self.parentItem().textBoxChanged()
            else:
                strip = selection.strip
                if strip != None:
                    signals.stripEditRequest.emit(strip)
            event.accept()
        else:
            QGraphicsItem.mouseDoubleClickEvent(self, event)
Ejemplo n.º 17
0
class SelectionTool(BaseTool):
    name = "Selection"
    iconPath = ":/resources/cursor.svg"

    def __init__(self, parent=None):
        super().__init__(parent)
        self._itemTuple = None
        self._oldSelection = set()
        self._rubberBandRect = None
        self._shouldPrepareUndo = False

    # helpers

    def _createAnchor(self, *args):
        widget = self.parent()
        pos = widget.mapToCanvas(widget.mapFromGlobal(self._cachedPos))
        newAnchorName, ok = AddAnchorDialog.getNewAnchorName(widget, pos)
        if ok:
            anchor = TAnchor()
            anchor.x = pos.x()
            anchor.y = pos.y()
            anchor.name = newAnchorName
            self._glyph.appendAnchor(anchor)

    def _createComponent(self, *args):
        widget = self.parent()
        newGlyph, ok = AddComponentDialog.getNewGlyph(widget, self._glyph)
        if ok and newGlyph is not None:
            component = TComponent()
            component.baseGlyph = newGlyph.name
            self._glyph.appendComponent(component)

    def _getSelectedCandidatePoint(self):
        """
        If there is exactly one point selected in the glyph, return it.

        Else return None.
        """

        candidates = set()
        for contour in self._glyph:
            sel = contour.selection
            if len(sel) > 1:
                return None
            elif not len(sel):
                continue
            pt = next(iter(sel))
            candidates.add((pt, contour))
        if len(candidates) == 1:
            return next(iter(candidates))
        return None

    def _moveForEvent(self, event):
        key = event.key()
        modifiers = event.modifiers()
        dx, dy = 0, 0
        if key == Qt.Key_Left:
            dx = -1
        elif key == Qt.Key_Up:
            dy = 1
        elif key == Qt.Key_Right:
            dx = 1
        elif key == Qt.Key_Down:
            dy = -1
        if modifiers & Qt.ShiftModifier:
            dx *= 10
            dy *= 10
            if modifiers & Qt.ControlModifier:
                dx *= 10
                dy *= 10
        return (dx, dy)

    # actions

    def showContextMenu(self, pos):
        self._cachedPos = pos
        menu = QMenu(self.parent())
        menu.addAction("Add Anchor…", self._createAnchor)
        menu.addAction("Add Component…", self._createComponent)
        menu.exec_(self._cachedPos)
        self._cachedPos = None

    # events

    def keyPressEvent(self, event):
        key = event.key()
        if key == platformSpecific.deleteKey:
            glyph = self._glyph
            # TODO: prune
            glyph.prepareUndo()
            preserveShape = not event.modifiers() & Qt.ShiftModifier
            for anchor in glyph.anchors:
                if anchor.selected:
                    glyph.removeAnchor(anchor)
            for contour in reversed(glyph):
                removeUISelection(contour, preserveShape)
            for component in glyph.components:
                if component.selected:
                    glyph.removeComponent(component)
        elif key in arrowKeys:
            # TODO: prune
            self._glyph.prepareUndo()
            delta = self._moveForEvent(event)
            # TODO: seems weird that glyph.selection and selected don't incl.
            # anchors and components while glyph.move does... see what glyphs
            # does
            hadSelection = False
            for anchor in self._glyph.anchors:
                if anchor.selected:
                    anchor.move(delta)
                    hadSelection = True
            for contour in self._glyph:
                moveUISelection(contour, delta)
                # XXX: shouldn't have to recalc this
                if contour.selection:
                    hadSelection = True
            for component in self._glyph.components:
                if component.selected:
                    component.move(delta)
                    hadSelection = True
            if not hadSelection:
                event.ignore()
        elif key in navKeys:
            pack = self._getSelectedCandidatePoint()
            if pack is not None:
                point, contour = pack
                point.selected = False
                index = contour.index(point)
                offset = int(key == Qt.Key_Greater) or -1
                newPoint = contour.getPoint(index + offset)
                newPoint.selected = True
                contour.postNotification(
                    notification="Contour.SelectionChanged")

    def mousePressEvent(self, event):
        if event.button() & Qt.RightButton:
            self.showContextMenu(event.globalPos())
            return
        widget = self.parent()
        addToSelection = event.modifiers() & Qt.ShiftModifier
        self._origin = event.localPos()
        self._itemTuple = widget.itemAt(self._origin)
        if self._itemTuple is not None:
            itemUnderMouse, parentContour = self._itemTuple
            if not (itemUnderMouse.selected or addToSelection):
                for anchor in self._glyph.anchors:
                    anchor.selected = False
                for component in self._glyph.components:
                    component.selected = False
                self._glyph.selected = False
            itemUnderMouse.selected = True
            if parentContour is not None:
                parentContour.postNotification(
                    notification="Contour.SelectionChanged")
            self._shouldPrepareUndo = True
        else:
            if addToSelection:
                self._oldSelection = self._glyph.selection
            else:
                for anchor in self._glyph.anchors:
                    anchor.selected = False
                for component in self._glyph.components:
                    component.selected = False
                self._glyph.selected = False
        widget.update()

    def mouseMoveEvent(self, event):
        canvasPos = event.localPos()
        widget = self.parent()
        if self._itemTuple is not None:
            if self._shouldPrepareUndo:
                self._glyph.prepareUndo()
                self._shouldPrepareUndo = False
            dx = canvasPos.x() - self._origin.x()
            dy = canvasPos.y() - self._origin.y()
            for anchor in self._glyph.anchors:
                if anchor.selected:
                    anchor.move((dx, dy))
            for contour in self._glyph:
                moveUISelection(contour, (dx, dy))
            for component in self._glyph.components:
                if component.selected:
                    component.move((dx, dy))
            self._origin = canvasPos
        else:
            self._rubberBandRect = QRectF(self._origin, canvasPos).normalized()
            items = widget.items(self._rubberBandRect)
            points = set(items["points"])
            if event.modifiers() & Qt.ShiftModifier:
                points ^= self._oldSelection
            # TODO: fine-tune this more, maybe add optional args to items...
            if event.modifiers() & Qt.AltModifier:
                points = set(pt for pt in points if pt.segmentType)
            if points != self._glyph.selection:
                # TODO: doing this takes more time than by-contour
                # discrimination for large point count
                self._glyph.selection = points
        widget.update()

    def mouseReleaseEvent(self, event):
        self._itemTuple = None
        self._oldSelection = set()
        self._rubberBandRect = None
        self.parent().update()

    def mouseDoubleClickEvent(self, event):
        widget = self.parent()
        self._itemTuple = widget.itemAt(self._origin)
        if self._itemTuple is not None:
            item, parent = self._itemTuple
            if parent is None:
                return
            point, contour = item, parent
            if point.segmentType is not None:
                self._glyph.prepareUndo()
                point.smooth = not point.smooth
            contour.dirty = True

    # custom painting

    def paint(self, painter):
        if self._rubberBandRect is None:
            return
        widget = self.parent()
        # okay, OS-native rubber band does not support painting with
        # floating-point coordinates
        # paint directly on the widget with unscaled context
        widgetOrigin = widget.mapToWidget(self._rubberBandRect.bottomLeft())
        widgetMove = widget.mapToWidget(self._rubberBandRect.topRight())
        option = QStyleOptionRubberBand()
        option.initFrom(widget)
        option.opaque = False
        option.rect = QRectF(widgetOrigin, widgetMove).toRect()
        option.shape = QRubberBand.Rectangle
        painter.save()
        painter.setRenderHint(QPainter.Antialiasing, False)
        painter.setTransform(QTransform())
        widget.style().drawControl(
            QStyle.CE_RubberBand, option, painter, widget)
        painter.restore()
Ejemplo n.º 18
0
class JoyPad(QtWidgets.QWidget, IndicatorPosition):
    IndicatorPosition = IndicatorPosition
    Q_ENUM(IndicatorPosition)

    joy_btn_pressed = QtCore.pyqtSignal(str)
    joy_btn_released = QtCore.pyqtSignal(str)
    joy_l_pressed = QtCore.pyqtSignal(bool)
    joy_l_released = QtCore.pyqtSignal(bool)
    joy_r_pressed = QtCore.pyqtSignal(bool)
    joy_r_released = QtCore.pyqtSignal(bool)
    joy_c_pressed = QtCore.pyqtSignal(bool)
    joy_c_released = QtCore.pyqtSignal(bool)
    joy_t_pressed = QtCore.pyqtSignal(bool)
    joy_t_released = QtCore.pyqtSignal(bool)
    joy_b_pressed = QtCore.pyqtSignal(bool)
    joy_b_released = QtCore.pyqtSignal(bool)

    def __init__(self, parent=None):
        super(JoyPad, self).__init__(parent)
        self.rect1 = QRectF()
        self.rect2 = QRectF()
        self.left_image = None
        self.right_image = None
        self.top_image = None
        self.bottom_image = None
        self.center_image = None
        self._dummyPixmap = QtGui.QPixmap()
        self._font = QFont('Lato Heavy', 20)
        self._text_color = QColor('white')
        self._textL = ''
        self._textR = ''
        self._textC = ''
        self._textT = ''
        self._textB = ''
        self.colorState = False
        self._true_color = QColor('lawngreen')
        self._false_color = QColor('gray')
        self.highlight_color = self._false_color
        self._highlightPosition = IndicatorPosition.NONE
        self.highlight_left = False
        self.highlight_right = False
        self.highlight_top = False
        self.highlight_bottom = False
        self.highlight_center = False
        self.last_active_btn = None
        self.setMouseTracking(True)
        self.setToolTipDuration(2000)
        self.installEventFilter(self)
        self.btn_names = {
            'L': 'left',
            'R': 'right',
            'T': 'top',
            'B': 'bottom',
            'C': 'center'
        }
        self.tooltips = {'L': '', 'R': '', 'T': '', 'B': '', 'C': ''}
        self.axis_list = ('X', 'Y', 'Z', 'A')

    def eventFilter(self, obj, event):
        if obj is self and self.isEnabled():
            if event.type() == QEvent.MouseButtonPress:
                if event.button() == Qt.RightButton:
                    event.ignore()
                else:
                    pos = event.localPos()
                    active_btn = self.get_active_btn(pos)
                    self.last_active_btn = active_btn
                    if active_btn is not None:
                        self._pressedOutput(active_btn)
            elif event.type() == QEvent.MouseButtonRelease:
                if event.button() == Qt.RightButton:
                    event.ignore()
                elif self.last_active_btn is not None:
                    self._releasedOutput(self.last_active_btn)
            elif event.type() == QEvent.MouseMove:
                pos = event.pos()
                active_btn = self.get_active_btn(pos)
                if active_btn is not None:
                    self.setToolTip(self.tooltips[active_btn])
        return super(JoyPad, self).eventFilter(obj, event)

    def _pressedOutput(self, btncode):
        self.joy_btn_pressed.emit(btncode)
        self['joy_{}_pressed'.format(btncode.lower())].emit(True)

    def _releasedOutput(self, btncode):
        self.joy_btn_released.emit(btncode)
        self['joy_{}_released'.format(btncode.lower())].emit(False)

    def get_active_btn(self, pos):
        if self.center_path.contains(pos): return 'C'
        elif self.left_path.contains(pos): return 'L'
        elif self.right_path.contains(pos): return 'R'
        elif self.bottom_path.contains(pos): return 'B'
        elif self.top_path.contains(pos): return 'T'
        return None

    def paintEvent(self, event):
        painter = QPainter(self)
        painter.setRenderHint(painter.Antialiasing)
        w = min(event.rect().width(), event.rect().height())
        self.rect1.setSize(QSizeF(w * 0.4, w * 0.4))
        self.rect2.setSize(QSizeF(w * 0.9, w * 0.9))
        self.create_paths(painter, event)
        self.draw_painter_paths(painter, event)
        self.draw_icons(painter, event)
        self.draw_highlight(painter, event)
        painter.end()

    def create_paths(self, qp, event):
        self.left_path = QPainterPath()
        self.right_path = QPainterPath()
        self.bottom_path = QPainterPath()
        self.top_path = QPainterPath()
        self.center_path = QPainterPath()
        center = event.rect().center()
        self.rect1.moveCenter(center)
        self.rect2.moveCenter(center)
        left_start = QPointF(self.rect1.topLeft())
        right_start = QPointF(self.rect1.bottomRight())
        bottom_start = QPointF(self.rect1.bottomLeft())
        top_start = QPointF(self.rect1.topRight())
        path = (self.right_path, self.top_path, self.left_path,
                self.bottom_path)
        start = (right_start, top_start, left_start, bottom_start)
        angle = -45
        for i in range(4):
            path[i].moveTo(start[i])
            path[i].arcTo(self.rect1, angle, 90)
            path[i].arcTo(self.rect2, angle + 90, -90)
            path[i].closeSubpath()
            angle += 90
        cap = QRectF()
        cap.setSize(QSizeF(self.rect1.width() * 0.8,
                           self.rect1.height() * 0.8))
        cap.moveCenter(center)
        self.center_path.addEllipse(cap)

    def draw_painter_paths(self, qp, event):
        w = min(event.rect().width(), event.rect().height())
        center = event.rect().center()
        fp = QPoint(int(center.x() - w / 4), int(center.y() - w / 4))
        bg = QRadialGradient(center, w / 2, fp)
        bg.setColorAt(0, QColor(180, 180, 180))
        bg.setColorAt(1, QColor(40, 40, 40))
        qp.setBrush(QBrush(bg))
        qp.setPen(QPen(QColor(Qt.black), 4))
        qp.drawPath(self.left_path)
        qp.drawPath(self.right_path)
        qp.drawPath(self.top_path)
        qp.drawPath(self.bottom_path)
        qp.drawPath(self.center_path)

    def draw_icons(self, qp, event):
        rect = QRect()
        rect.setSize(
            QSize(int(self.rect1.width() * 0.4),
                  int(self.rect1.height() * 0.4)))
        center = event.rect().center()
        qp.setPen(QPen(self._text_color, 2))
        qp.setFont(self._font)
        # left button
        rect.moveCenter(
            QPoint(int(center.x() - self.rect2.width() / 3), center.y()))
        if isinstance(self.left_image, QPixmap):
            pix = self.left_image
            qp.drawPixmap(rect, pix, pix.rect())
        elif isinstance(self.left_image, str):
            qp.drawText(rect, Qt.AlignCenter, self.left_image)
        # right button
        rect.moveCenter(
            QPoint(int(center.x() + self.rect2.width() / 3), center.y()))
        if isinstance(self.right_image, QPixmap):
            pix = self.right_image
            qp.drawPixmap(rect, pix, pix.rect())
        elif isinstance(self.right_image, str):
            qp.drawText(rect, Qt.AlignCenter, self.right_image)
        # bottom button
        rect.moveCenter(
            QPoint(center.x(), int(center.y() + self.rect2.width() / 3)))
        if isinstance(self.bottom_image, QPixmap):
            pix = self.bottom_image
            qp.drawPixmap(rect, pix, pix.rect())
        elif isinstance(self.bottom_image, str):
            qp.drawText(rect, Qt.AlignCenter, self.bottom_image)
        # top button
        rect.moveCenter(
            QPoint(center.x(), int(center.y() - self.rect2.width() / 3)))
        if isinstance(self.top_image, QPixmap):
            pix = self.top_image
            qp.drawPixmap(rect, pix, pix.rect())
        elif isinstance(self.top_image, str):
            qp.drawText(rect, Qt.AlignCenter, self.top_image)
        # center button
        rect.moveCenter(QPoint(center.x(), center.y()))
        if isinstance(self.center_image, QPixmap):
            pix = self.center_image
            qp.drawPixmap(rect, pix, pix.rect())
        elif isinstance(self.center_image, str):
            qp.drawText(rect, Qt.AlignCenter, self.center_image)

    def draw_highlight(self, qp, event):
        rect = QRectF()
        rect.setSize(self.rect1.size() * 0.9)
        center = event.rect().center()
        rect.moveCenter(center)
        pen_width = self.rect1.width() * 0.08
        qp.setPen(QPen(self.highlight_color, pen_width, cap=Qt.FlatCap))
        if self.highlight_center is True:
            qp.drawArc(rect, 0, 5760)
        else:
            if self.highlight_right is True:
                qp.drawArc(rect, -45 * 16, 90 * 16)
            if self.highlight_left is True:
                qp.drawArc(rect, 135 * 16, 90 * 16)
            if self.highlight_top is True:
                qp.drawArc(rect, 45 * 16, 90 * 16)
            if self.highlight_bottom is True:
                qp.drawArc(rect, 225 * 16, 90 * 16)

    def reset_highlight(self):
        self.highlight_left = False
        self.highlight_right = False
        self.highlight_top = False
        self.highlight_bottom = False
        self.highlight_center = False

    def set_highlight(self, btn, state=True):
        if type(btn) == int:
            if btn == IndicatorPosition.LEFT:
                btn = 'L'
            elif btn == IndicatorPosition.RIGHT:
                btn = 'R'
            elif btn == IndicatorPosition.CENTER:
                btn = 'C'
            elif btn == IndicatorPosition.TOP:
                btn = 'T'
            elif btn == IndicatorPosition.BOTTOM:
                btn = 'B'
            elif btn == IndicatorPosition.LEFTRIGHT:
                btn = 'X'
            elif btn == IndicatorPosition.TOPBOTTOM:
                btn = 'Z'
            elif btn == IndicatorPosition.NONE:
                self.reset_highlight()
                return
            else:
                print('undefined position:{}'.format(btn))
                return
        if btn not in self.axis_list and btn not in self.btn_names.keys():
            return
        if btn == 'X' or btn == 'A':
            self.highlight_left = state
            self.highlight_right = state
        elif btn == 'Y' or btn == 'Z':
            self.highlight_top = state
            self.highlight_bottom = state
        else:
            name = self.btn_names[btn]
            self['highlight_' + name] = state
        self.update()

    def set_button_icon(self, btn, path):
        self.set_icon(btn, 'image', path)

    def set_button_text(self, btn, text):
        self.set_icon(btn, 'text', text)

    def set_icon(self, btn, kind, data):
        if btn not in self.btn_names.keys(): return
        name = self.btn_names[btn]
        if kind == 'image':
            if data is None:
                self[name + "_image"] = None
            else:
                self[name + "_image"] = QPixmap(data)
        elif kind == 'text':
            self[name + "_image"] = data
        else:
            return
        self.update()

    def set_tooltip(self, btn, tip):
        if btn in self.btn_names.keys():
            self.tooltips[btn] = tip

    def setLight(self, data):
        if data:
            self.highlight_color = self._true_color
        else:
            self.highlight_color = self._false_color
        self.update()

    def set_HighlightPosition(self, position):
        self._highlightPosition = position
        self.reset_highlight()
        self.set_highlight(position, True)
        self.update()

    def get_HighlightPosition(self):
        return self._highlightPosition

    def reset_HighlightPosition(self):
        self._highlightPosition = IndicatorPosition.NONE
        self.reset_highlight()
        self.update()

    highlightPosition = QtCore.pyqtProperty(IndicatorPosition,
                                            get_HighlightPosition,
                                            set_HighlightPosition,
                                            reset_HighlightPosition)

    @QtCore.pyqtSlot()
    def set_colorStateTrue(self):
        self.setLight(True)

    @QtCore.pyqtSlot()
    def set_colorStateFalse(self):
        self.setLight(False)

    @QtCore.pyqtSlot(bool)
    def set_colorState(self, state):
        self.colorState = bool(state)
        self.setLight(state)

    def get_colorState(self):
        return self.colorState

    def reset_colorState(self):
        self.colorState = False
        self.setLight(False)

    setColorState = QtCore.pyqtProperty(bool, get_colorState, set_colorState,
                                        reset_colorState)

    def setLeftImagePath(self, data):
        if data.isNull():
            data = None
        self.set_icon('L', 'image', data)

    def getLeftImagePath(self):
        if isinstance(self.left_image, QPixmap):
            self.left_image
        else:
            return self._dummyPixmap

    def resetLeftImagePath(self):
        pass

    def setRightImagePath(self, data):
        if data.isNull():
            data = None
        self.set_icon('R', 'image', data)

    def getRightImagePath(self):
        return self._dummyPixmap

    def resetRightImagePath(self):
        pass

    def setCenterImagePath(self, data):
        if data.isNull():
            data = None
        self.set_icon('C', 'image', data)

    def getCenterImagePath(self):
        return self._dummyPixmap

    def resetCenterImagePath(self):
        pass

    def setTopImagePath(self, data):
        if data.isNull():
            data = None
        self.set_icon('T', 'image', data)

    def getTopImagePath(self):
        return self._dummyPixmap

    def resetTopImagePath(self):
        pass

    def setBottomImagePath(self, data):
        if data.isNull():
            data = None
        self.set_icon('B', 'image', data)

    def getBottomImagePath(self):
        return self._dummyPixmap

    def resetBottomImagePath(self):
        pass

    left_image_path = QtCore.pyqtProperty(QPixmap, getLeftImagePath,
                                          setLeftImagePath, resetLeftImagePath)
    right_image_path = QtCore.pyqtProperty(QPixmap, getRightImagePath,
                                           setRightImagePath,
                                           resetRightImagePath)
    center_image_path = QtCore.pyqtProperty(QPixmap, getCenterImagePath,
                                            setCenterImagePath,
                                            resetCenterImagePath)
    top_image_path = QtCore.pyqtProperty(QPixmap, getTopImagePath,
                                         setTopImagePath, resetTopImagePath)
    bottom_image_path = QtCore.pyqtProperty(QPixmap, getBottomImagePath,
                                            setBottomImagePath,
                                            resetBottomImagePath)

    def getFont(self):
        return self._font

    def setFont(self, value):
        self._font = value

    def resetFont(self):
        self._font = QFont('Lato Heavy', 20)

    button_font = QtCore.pyqtProperty(QFont, getFont, setFont, resetFont)

    def setLeftText(self, data):
        self._textL = data
        if not data.strip():
            data = None
        self.set_icon('L', 'text', data)

    def getLeftText(self):
        return self._textL

    def resetLeftText(self):
        self._textL = ''
        self.set_icon('L', 'text', '')

    def setRightText(self, data):
        self._textR = data
        if not data.strip():
            data = None
        self.set_icon('R', 'text', data)

    def getRightText(self):
        return self._textR

    def resetRightText(self):
        self._textR = ''
        self.set_icon('R', 'text', '')

    def setCenterText(self, data):
        self._textC = data
        if not data.strip():
            data = None
        self.set_icon('C', 'text', data)

    def getCenterText(self):
        return self._textC

    def resetCenterText(self):
        self._textC = ''
        self.set_icon('C', 'text', '')

    def setTopText(self, data):
        self._textT = data
        if not data.strip():
            data = None
        self.set_icon('T', 'text', data)

    def getTopText(self):
        return self._textT

    def resetTopText(self):
        self._textT = ''
        self.set_icon('T', 'text', '')

    def setBottomText(self, data):
        self._textB = data
        if not data.strip():
            data = None
        self.set_icon('B', 'text', data)

    def getBottomText(self):
        return self._textB

    def resetBottomText(self):
        self._textB = ''
        self.set_icon('B', 'text', '')

    left_text = QtCore.pyqtProperty(str, getLeftText, setLeftText,
                                    resetLeftText)
    right_text = QtCore.pyqtProperty(str, getRightText, setRightText,
                                     resetRightText)
    center_text = QtCore.pyqtProperty(str, getCenterText, setCenterText,
                                      resetCenterText)
    top_text = QtCore.pyqtProperty(str, getTopText, setTopText, resetTopText)
    bottom_text = QtCore.pyqtProperty(str, getBottomText, setBottomText,
                                      resetBottomText)

    @QtCore.pyqtSlot(QColor)
    def set_true_color(self, color):
        self._true_color = color
        self.setLight(self.colorState)

    @QtCore.pyqtSlot(str)
    def set_true_color(self, color):
        self._true_color = QColor(color)
        self.setLight(self.colorState)

    def get_true_color(self):
        return self._true_color

    def reset_true_color(self):
        self._true_color = QColor('lawngreen')
        self.setLight(self.colorState)

    @QtCore.pyqtSlot(QColor)
    def set_false_color(self, color):
        self._false_color = color
        self.setLight(self.colorState)

    @QtCore.pyqtSlot(str)
    def set_false_color(self, color):
        self._false_color = QColor(color)
        self.setLight(self.colorState)

    def get_false_color(self):
        return self._false_color

    def reset_false_color(self):
        self._false_color = QColor('gray')
        self.setLight(self.colorState)

    def set_text_color(self, color):
        self._text_color = QColor(color)
        self.update()

    def get_text_color(self):
        return self._text_color

    def reset_text_color(self):
        self._text_color = QColor('white')
        self.update()

    true_color = QtCore.pyqtProperty(QColor, get_true_color, set_true_color,
                                     reset_true_color)
    false_color = QtCore.pyqtProperty(QColor, get_false_color, set_false_color,
                                      reset_false_color)
    text_color = QtCore.pyqtProperty(QColor, get_text_color, set_text_color,
                                     reset_text_color)

    @QtCore.pyqtSlot(str)
    def btn_pressed(self, btn):
        print("Button pressed", btn)

    @QtCore.pyqtSlot(str)
    def btn_released(self, btn):
        print("Button released", btn)

    # required code for object indexing
    def __getitem__(self, item):
        return getattr(self, item)

    def __setitem__(self, item, value):
        return setattr(self, item, value)
Ejemplo n.º 19
0
class SelectionTool(BaseTool):
    name = QApplication.translate("SelectionTool", "Selection")
    iconPath = ":cursor.svg"

    def __init__(self, parent=None):
        super().__init__(parent)
        self._itemTuple = None
        self._oldSelection = set()
        self._rubberBandRect = None
        self._shouldMove = False
        self._shouldPrepareUndo = False

    # helpers

    def _createAnchor(self, *args):
        widget = self.parent()
        glyph = self._glyph
        glyph.prepareUndo()
        pos = widget.mapToCanvas(widget.mapFromGlobal(self._cachedPos))
        # remove template anchors
        for anchor in glyph.anchors:
            if anchor.name == "new anchor":
                glyph.removeAnchor(anchor)
        # add one at position
        anchor = glyph.instantiateAnchor()
        anchor.x = pos.x()
        anchor.y = pos.y()
        anchor.name = "new anchor"
        glyph.appendAnchor(anchor)

    def _createComponent(self, *args):
        widget = self.parent()
        glyph = self._glyph
        glyph.prepareUndo()
        newGlyph, ok = AddComponentDialog.getNewGlyph(widget, glyph)
        if ok and newGlyph is not None:
            component = glyph.instantiateComponent()
            component.baseGlyph = newGlyph.name
            glyph.appendComponent(component)

    def _createGuideline(self, *args):
        widget = self.parent()
        glyph = self._glyph
        glyph.prepareUndo()
        pos = widget.mapToCanvas(widget.mapFromGlobal(self._cachedPos))
        content = dict(x=pos.x(), y=pos.y())
        guideline = glyph.instantiateGuideline(content)
        glyph.appendGuideline(guideline)

    def _goToGlyph(self, glyphName):
        widget = self.parent()
        font = self._glyph.font
        if glyphName in font:
            glyph = font[glyphName]
            glyphWindow = GlyphWindow(glyph, widget.window().parent())
            glyphWindow.show()

    def _toggleGuideline(self, guideline):
        glyph = self._glyph
        font = glyph.font
        if font is None:
            return
        if isinstance(guideline.getParent(), Glyph):
            glyph.removeGuideline(guideline)
            font.appendGuideline(guideline)
        else:
            font.removeGuideline(guideline)
            glyph.appendGuideline(guideline)

    def _getSelectedCandidatePoint(self):
        """
        If there is exactly one point selected in the glyph, return it.

        Else return None.
        """

        candidates = set()
        for contour in self._glyph:
            sel = contour.selection
            if len(sel) > 1:
                return None
            elif not len(sel):
                continue
            pt = next(iter(sel))
            candidates.add((pt, contour))
        if len(candidates) == 1:
            return next(iter(candidates))
        return None

    def _getOffCurveSiblingPoint(self, contour, point):
        index = contour.index(point)
        for d in (-1, 1):
            sibling = contour.getPoint(index + d)
            if sibling.segmentType is not None:
                return sibling
        raise IndexError

    def _moveOnCurveAlongHandles(self, contour, pt, x, y):
        # TODO: offCurves
        if pt.segmentType is not None and pt.smooth and len(contour) >= 3:
            index = contour.index(pt)
            prevCP = contour.getPoint(index - 1)
            nextCP = contour.getPoint(index + 1)
            # we need at least one offCurve so that it makes sense
            # slide the onCurve around
            if prevCP.segmentType is None or nextCP.segmentType is None:
                projX, projY, _ = bezierMath.lineProjection(
                    prevCP.x, prevCP.y, nextCP.x, nextCP.y, x, y, False)
                # short-circuit UIMove because we're only moving this point
                pt.x = projX
                pt.y = projY
                contour.dirty = True
                return True
        return False

    def _findSegmentUnderMouse(self, pos, action=None):
        scale = self.parent().inverseScale()
        for contour in self._glyph:
            for index, point in enumerate(contour):
                if point.segmentType == "line":
                    prev = contour.getPoint(index-1)
                    dist = bezierMath.lineDistance(
                        prev.x, prev.y, point.x, point.y, pos.x(), pos.y())
                    # TODO: somewhat arbitrary
                    if dist < 5 * scale:
                        return [prev, point], contour
                elif point.segmentType in ("curve", "qcurve"):
                    if action == "insert":
                        continue
                    if point.segmentType == "curve":
                        bez = [contour.getPoint(index-3+i) for i in range(4)]
                    else:
                        bez = [point]
                        i = 1
                        while i < 2 or point.segmentType is None:
                            point = contour.getPoint(index-i)
                            bez.append(point)
                            i += 1
                        bez.reverse()
                    if _pointWithinThreshold(pos.x(), pos.y(), bez, 5 * scale):
                        return bez, contour
        return None

    def _performSegmentClick(self, pos, action=None, segmentTuple=None):
        if segmentTuple is None:
            segmentTuple = self._findSegmentUnderMouse(pos, action)
        if segmentTuple is None:
            return
        segment, contour = segmentTuple
        prev, point = segment[0], segment[-1]
        if point.segmentType == "line":
            if action == "insert":
                index = contour.index(point)
                self._glyph.prepareUndo()
                contour.holdNotifications()
                for i, t in enumerate((.35, .65)):
                    xt = prev.x + t * (point.x - prev.x)
                    yt = prev.y + t * (point.y - prev.y)
                    contour.insertPoint(
                        index+i, point.__class__((xt, yt)))
                point.segmentType = "curve"
                contour.releaseHeldNotifications()
                return
        elif point.segmentType not in ("curve", "qcurve"):
            return
        if action == "selectContour":
            contour.selected = not contour.selected
            self._shouldMove = self._shouldPrepareUndo = True

    def _maybeJoinContour(self, pos):
        def getAtEdge(contour, pt):
            for index in range(2):
                if contour[index-1] == pt:
                    return index - 1
            return None

        if self._itemTuple is None:
            return
        item, parent = self._itemTuple
        if parent is None or not (item.segmentType and parent.open):
            return
        ptIndex = getAtEdge(parent, item)
        if ptIndex is None:
            return
        widget = self.parent()
        items = widget.itemsAt(pos)
        for point, contour in zip(items["points"], items["contours"]):
            if point == item or not (point.segmentType and contour.open):
                continue
            otherIndex = getAtEdge(contour, point)
            if otherIndex is None:
                continue
            if parent != contour:
                # TODO: blacklist single onCurve contours
                # Note reverse uses different point objects
                # TODO: does it have to work this way?
                if not ptIndex:
                    parent.reverse()
                if otherIndex == -1:
                    contour.reverse()
                dragPoint = parent[-1]
                parent.removePoint(dragPoint)
                contour[0].segmentType = dragPoint.segmentType
                contour.drawPoints(parent)
                glyph = contour.glyph
                glyph.removeContour(contour)
                parent.dirty = True
            else:
                if item.segmentType == "move":
                    item.x = point.x
                    item.y = point.y
                    contour.removePoint(point)
                else:
                    contour.removePoint(item)
                contour[0].segmentType = "line"
                contour.dirty = True
            return

    def _moveForEvent(self, event):
        key = event.key()
        modifiers = event.modifiers()
        dx, dy = 0, 0
        if key == Qt.Key_Left:
            dx = -1
        elif key == Qt.Key_Up:
            dy = 1
        elif key == Qt.Key_Right:
            dx = 1
        elif key == Qt.Key_Down:
            dy = -1
        if modifiers & Qt.ShiftModifier:
            dx *= 10
            dy *= 10
            if modifiers & Qt.ControlModifier:
                dx *= 10
                dy *= 10
        return (dx, dy)

    def _renameItem(self, item):
        widget = self.parent()
        newName, ok = RenameDialog.getNewName(widget, item.name)
        if ok:
            item.name = newName

    def _reverse(self, target=None):
        if target is None:
            selectedContours = set()
            for contour in self._glyph:
                if contour.selection:
                    selectedContours.add(contour)
            target = selectedContours or self._glyph
        for contour in target:
            contour.reverse()

    def _setStartPoint(self, point, contour):
        index = contour.index(point)
        contour.setStartPoint(index)

    # actions

    def showContextMenu(self, event):
        widget = self.parent()
        self._cachedPos = event.globalPos()
        menu = QMenu(widget)
        itemTuple = widget.itemAt(event.localPos())
        targetContour = None
        if itemTuple is not None:
            item, parent = itemTuple
            if parent is not None and item.segmentType:
                targetContour = [parent]
                menu.addAction(self.tr("Set Start Point"),
                               lambda: self._setStartPoint(item, parent))
            elif isinstance(item, Component):
                menu.addAction(self.tr("Go To Glyph"),
                               lambda: self._goToGlyph(item.baseGlyph))
                menu.addAction(self.tr("Decompose"),
                               lambda: self._glyph.decomposeComponent(item))
                menu.addAction(self.tr("Decompose All"),
                               self._glyph.decomposeAllComponents)
            elif isinstance(item, Guideline):
                if isinstance(item.getParent(), Glyph):
                    text = self.tr("Make Global")
                else:
                    text = self.tr("Make Local")
                menu.addAction(text, lambda: self._toggleGuideline(item))
        if targetContour is not None:
            reverseText = self.tr("Reverse Contour")
        else:
            # XXX: text and action shouldnt be decoupled
            if self._glyph.selection:
                reverseText = self.tr("Reverse Selected Contours")
            else:
                reverseText = self.tr("Reverse All Contours")
        menu.addAction(reverseText, lambda: self._reverse(targetContour))
        menu.addSeparator()
        menu.addAction(self.tr("Add Component…"), self._createComponent)
        menu.addAction(self.tr("Add Anchor"), self._createAnchor)
        menu.addAction(self.tr("Add Guideline"), self._createGuideline)
        menu.exec_(self._cachedPos)
        self._cachedPos = None

    # events

    def keyPressEvent(self, event):
        key = event.key()
        if platformSpecific.isDeleteEvent(event):
            glyph = self._glyph
            # TODO: fuse more the two methods, they're similar and delete is
            # Cut except not putting in the clipboard
            if event.modifiers() & Qt.AltModifier:
                deleteUISelection(glyph)
            else:
                preserveShape = not event.modifiers() & Qt.ShiftModifier
                # TODO: prune
                glyph.prepareUndo()
                removeUIGlyphElements(glyph, preserveShape)
        elif key in arrowKeys:
            # TODO: prune
            self._glyph.prepareUndo()
            dx, dy = self._moveForEvent(event)
            # TODO: seems weird that glyph.selection and selected don't incl.
            # anchors and components while glyph.move does... see what glyphs
            # does
            moveUIGlyphElements(self._glyph, dx, dy)
        elif key in navKeys:
            pack = self._getSelectedCandidatePoint()
            if pack is not None:
                point, contour = pack
                point.selected = False
                index = contour.index(point)
                offset = int(key == Qt.Key_Greater) or -1
                newPoint = contour.getPoint(index + offset)
                newPoint.selected = True
                contour.postNotification(
                    notification="Contour.SelectionChanged")

    def mousePressEvent(self, event):
        if event.button() & Qt.RightButton:
            self.showContextMenu(event)
            return
        widget = self.parent()
        addToSelection = event.modifiers() & Qt.ControlModifier
        self._origin = self._prevPos = pos = self.magnetPos(event.localPos())
        self._itemTuple = widget.itemAt(self._origin)
        if self._itemTuple is not None:
            itemUnderMouse, parentContour = self._itemTuple
            if not (itemUnderMouse.selected or addToSelection):
                unselectUIGlyphElements(self._glyph)
            itemUnderMouse.selected = True
            if parentContour is not None:
                parentContour.postNotification(
                    notification="Contour.SelectionChanged")
            self._shouldPrepareUndo = True
        else:
            action = "insert" if event.modifiers() & Qt.AltModifier else None
            segmentTuple = self._findSegmentUnderMouse(pos, action)
            if segmentTuple is not None:
                segment, contour = segmentTuple
                selected = segment[0].selected and segment[-1].selected
            else:
                selected = False
            if not selected:
                if addToSelection:
                    self._oldSelection = self._glyph.selection
                else:
                    unselectUIGlyphElements(self._glyph)
                self._performSegmentClick(pos, action, segmentTuple)
            else:
                self._shouldMove = True
        widget.update()

    def mouseMoveEvent(self, event):
        canvasPos = event.localPos()
        glyph = self._glyph
        widget = self.parent()
        if self._shouldMove or self._itemTuple is not None:
            if self._shouldPrepareUndo:
                self._glyph.prepareUndo()
                self._shouldPrepareUndo = False
            modifiers = event.modifiers()
            if self._itemTuple is not None:
                # Alt: move point along handles
                if modifiers & Qt.AltModifier and len(glyph.selection) == 1:
                    item, parent = self._itemTuple
                    if parent is not None:
                        x, y = canvasPos.x(), canvasPos.y()
                        didMove = self._moveOnCurveAlongHandles(
                            parent, item, x, y)
                        if didMove:
                            return
                # Shift: clamp pos on axis
                elif modifiers & Qt.ShiftModifier:
                        item, parent = self._itemTuple
                        if parent is not None:
                            if item.segmentType is None:
                                onCurve = self._getOffCurveSiblingPoint(
                                    parent, item)
                                canvasPos = self.clampToOrigin(
                                    canvasPos, QPointF(onCurve.x, onCurve.y))
                            else:
                                canvasPos = self.clampToOrigin(
                                    canvasPos, self._origin)
            dx = canvasPos.x() - self._prevPos.x()
            dy = canvasPos.y() - self._prevPos.y()
            moveUIGlyphElements(glyph, dx, dy)
            self._prevPos = canvasPos
        else:
            self._rubberBandRect = QRectF(self._origin, canvasPos).normalized()
            items = widget.items(self._rubberBandRect)
            points = set(items["points"])
            if event.modifiers() & Qt.ControlModifier:
                points ^= self._oldSelection
            # TODO: fine-tune this more, maybe add optional args to items...
            if event.modifiers() & Qt.AltModifier:
                points = set(pt for pt in points if pt.segmentType)
            if points != self._glyph.selection:
                # TODO: doing this takes more time than by-contour
                # discrimination for large point count
                glyph.selection = points
        widget.update()

    def mouseReleaseEvent(self, event):
        self._maybeJoinContour(event.localPos())
        self._itemTuple = None
        self._oldSelection = set()
        self._rubberBandRect = None
        self._shouldMove = False
        self.parent().update()

    def mouseDoubleClickEvent(self, event):
        widget = self.parent()
        self._itemTuple = widget.itemAt(self._origin)
        if self._itemTuple is not None:
            item, parent = self._itemTuple
            if parent is None:
                if isinstance(item, (Anchor, Guideline)):
                    self._renameItem(item)
            else:
                point, contour = item, parent
                if point.segmentType is not None:
                    self._glyph.prepareUndo()
                    point.smooth = not point.smooth
                    contour.dirty = True
                    # if we have one offCurve, make it tangent
                    maybeProjectUISmoothPointOffcurve(contour, point)
        else:
            self._performSegmentClick(event.localPos(), "selectContour")

    # custom painting

    def paint(self, painter):
        if self._rubberBandRect is None:
            return
        widget = self.parent()
        # okay, OS-native rubber band does not support painting with
        # floating-point coordinates
        # paint directly on the widget with unscaled context
        widgetOrigin = widget.mapFromCanvas(self._rubberBandRect.bottomLeft())
        widgetMove = widget.mapFromCanvas(self._rubberBandRect.topRight())
        option = QStyleOptionRubberBand()
        option.initFrom(widget)
        option.opaque = False
        option.rect = QRectF(widgetOrigin, widgetMove).toRect()
        option.shape = QRubberBand.Rectangle
        painter.save()
        painter.setRenderHint(QPainter.Antialiasing, False)
        painter.resetTransform()
        widget.style().drawControl(
            QStyle.CE_RubberBand, option, painter, widget)
        painter.restore()
Ejemplo n.º 20
0
class SelectionTool(BaseTool):
    name = QApplication.translate("SelectionTool", "Selection")
    iconPath = ":cursor.svg"

    def __init__(self, parent=None):
        super().__init__(parent)
        self._itemTuple = None
        self._oldSelection = set()
        self._rubberBandRect = None
        self._shouldMove = False
        self._shouldPrepareUndo = False

    # helpers

    def _createAnchor(self, *args):
        widget = self.parent()
        pos = widget.mapToCanvas(widget.mapFromGlobal(self._cachedPos))
        newAnchorName, ok = AddAnchorDialog.getNewAnchorName(widget, pos)
        if ok:
            anchor = TAnchor()
            anchor.x = pos.x()
            anchor.y = pos.y()
            anchor.name = newAnchorName
            self._glyph.appendAnchor(anchor)

    def _createComponent(self, *args):
        widget = self.parent()
        newGlyph, ok = AddComponentDialog.getNewGlyph(widget, self._glyph)
        if ok and newGlyph is not None:
            component = TComponent()
            component.baseGlyph = newGlyph.name
            self._glyph.appendComponent(component)

    def _getSelectedCandidatePoint(self):
        """
        If there is exactly one point selected in the glyph, return it.

        Else return None.
        """

        candidates = set()
        for contour in self._glyph:
            sel = contour.selection
            if len(sel) > 1:
                return None
            elif not len(sel):
                continue
            pt = next(iter(sel))
            candidates.add((pt, contour))
        if len(candidates) == 1:
            return next(iter(candidates))
        return None

    def _getOffCurveSiblingPoint(self, contour, point):
        index = contour.index(point)
        for d in (-1, 1):
            sibling = contour.getPoint(index + d)
            if sibling.segmentType is not None:
                return sibling
        raise IndexError

    def _moveOnCurveAlongHandles(self, contour, pt, x, y):
        # TODO: offCurves
        if pt.segmentType is not None and pt.smooth and len(contour) >= 3:
            index = contour.index(pt)
            prevCP = contour.getPoint(index - 1)
            nextCP = contour.getPoint(index + 1)
            # we need at least one offCurve so that it makes sense
            # slide the onCurve around
            if prevCP.segmentType is None or nextCP.segmentType is None:
                projX, projY, _ = bezierMath.lineProjection(
                    prevCP.x, prevCP.y, nextCP.x, nextCP.y, x, y, False)
                # short-circuit UIMove because we're only moving this point
                pt.x = projX
                pt.y = projY
                contour.dirty = True
                return True
        return False

    def _computeLineClick(self, pos, insert=False):
        scale = self.parent().inverseScale()
        for contour in self._glyph:
            for index, point in enumerate(contour):
                if point.segmentType == "line":
                    prev = contour.getPoint(index-1)
                    dist = bezierMath.lineDistance(
                        prev.x, prev.y, point.x, point.y, pos.x(), pos.y())
                    # TODO: somewhat arbitrary
                    if dist < 5 * scale:
                        if insert:
                            self._glyph.prepareUndo()
                            contour.holdNotifications()
                            for i, t in enumerate((.35, .65)):
                                xt = prev.x + t * (point.x - prev.x)
                                yt = prev.y + t * (point.y - prev.y)
                                contour.insertPoint(
                                    index+i, point.__class__((xt, yt)))
                            point.segmentType = "curve"
                            contour.releaseHeldNotifications()
                        else:
                            prev.selected = point.selected = True
                            contour.postNotification(
                                notification="Contour.SelectionChanged")
                            self._shouldMove = self._shouldPrepareUndo = True
                        return

    def _moveForEvent(self, event):
        key = event.key()
        modifiers = event.modifiers()
        dx, dy = 0, 0
        if key == Qt.Key_Left:
            dx = -1
        elif key == Qt.Key_Up:
            dy = 1
        elif key == Qt.Key_Right:
            dx = 1
        elif key == Qt.Key_Down:
            dy = -1
        if modifiers & Qt.ShiftModifier:
            dx *= 10
            dy *= 10
            if modifiers & Qt.ControlModifier:
                dx *= 10
                dy *= 10
        return (dx, dy)

    def _renameAnchor(self, anchor):
        widget = self.parent()
        newAnchorName, ok = AddAnchorDialog.getNewAnchorName(
            widget, None, anchor.name)
        if ok:
            anchor.name = newAnchorName

    def _reverse(self):
        selectedContours = set()
        for contour in self._glyph:
            if contour.selection:
                selectedContours.add(contour)
        target = selectedContours or self._glyph
        for contour in target:
            contour.reverse()

    def _setStartPoint(self, point, contour):
        index = contour.index(point)
        contour.setStartPoint(index)

    # actions

    def showContextMenu(self, event):
        widget = self.parent()
        self._cachedPos = event.globalPos()
        menu = QMenu(widget)
        menu.addAction(self.tr("Add Anchor…"), self._createAnchor)
        menu.addAction(self.tr("Add Component…"), self._createComponent)
        menu.addAction(self.tr("Reverse"), self._reverse)
        itemTuple = widget.itemAt(event.localPos())
        if itemTuple is not None:
            item, parent = itemTuple
            if parent is not None and item.segmentType:
                menu.addSeparator()
                menu.addAction(self.tr("Set Start Point"),
                               lambda: self._setStartPoint(item, parent))
        menu.exec_(self._cachedPos)
        self._cachedPos = None

    # events

    def keyPressEvent(self, event):
        key = event.key()
        if event.matches(QKeySequence.Delete):
            glyph = self._glyph
            # TODO: prune
            glyph.prepareUndo()
            preserveShape = not event.modifiers() & Qt.ShiftModifier
            for anchor in glyph.anchors:
                if anchor.selected:
                    glyph.removeAnchor(anchor)
            for contour in reversed(glyph):
                removeUISelection(contour, preserveShape)
            for component in glyph.components:
                if component.selected:
                    glyph.removeComponent(component)
            if glyph.image.selected:
                glyph.image = None
        elif key in arrowKeys:
            # TODO: prune
            self._glyph.prepareUndo()
            delta = self._moveForEvent(event)
            # TODO: seems weird that glyph.selection and selected don't incl.
            # anchors and components while glyph.move does... see what glyphs
            # does
            hadSelection = False
            for anchor in self._glyph.anchors:
                if anchor.selected:
                    anchor.move(delta)
                    hadSelection = True
            for contour in self._glyph:
                moveUISelection(contour, delta)
                # XXX: shouldn't have to recalc this
                if contour.selection:
                    hadSelection = True
            for component in self._glyph.components:
                if component.selected:
                    component.move(delta)
                    hadSelection = True
            image = self._glyph.image
            if image.selected:
                image.move(delta)
                hadSelection = True
            if not hadSelection:
                event.ignore()
        elif key in navKeys:
            pack = self._getSelectedCandidatePoint()
            if pack is not None:
                point, contour = pack
                point.selected = False
                index = contour.index(point)
                offset = int(key == Qt.Key_Greater) or -1
                newPoint = contour.getPoint(index + offset)
                newPoint.selected = True
                contour.postNotification(
                    notification="Contour.SelectionChanged")

    def mousePressEvent(self, event):
        if event.button() & Qt.RightButton:
            self.showContextMenu(event)
            return
        widget = self.parent()
        addToSelection = event.modifiers() & Qt.ControlModifier
        self._origin = self.magnetPos(event.localPos())
        self._itemTuple = widget.itemAt(self._origin)
        if self._itemTuple is not None:
            itemUnderMouse, parentContour = self._itemTuple
            if not (itemUnderMouse.selected or addToSelection):
                for anchor in self._glyph.anchors:
                    anchor.selected = False
                for component in self._glyph.components:
                    component.selected = False
                self._glyph.selected = False
                self._glyph.image.selected = False
            itemUnderMouse.selected = True
            if parentContour is not None:
                parentContour.postNotification(
                    notification="Contour.SelectionChanged")
            self._shouldPrepareUndo = True
        else:
            if addToSelection:
                self._oldSelection = self._glyph.selection
            else:
                for anchor in self._glyph.anchors:
                    anchor.selected = False
                for component in self._glyph.components:
                    component.selected = False
                self._glyph.selected = False
                self._glyph.image.selected = False
            self._computeLineClick(event.localPos())
        widget.update()

    def mouseMoveEvent(self, event):
        canvasPos = event.localPos()
        widget = self.parent()
        if self._shouldMove or self._itemTuple is not None:
            if self._shouldPrepareUndo:
                self._glyph.prepareUndo()
                self._shouldPrepareUndo = False
            modifiers = event.modifiers()
            # Alt: move point along handles
            if modifiers & Qt.AltModifier and len(self._glyph.selection) == 1:
                item, parent = self._itemTuple
                if parent is not None:
                    x, y = canvasPos.x(), canvasPos.y()
                    didMove = self._moveOnCurveAlongHandles(parent, item, x, y)
                    if didMove:
                        return
            # Shift: clamp pos on axis
            elif modifiers & Qt.ShiftModifier:
                item, parent = self._itemTuple
                if parent is not None:
                    if item.segmentType is None:
                        onCurve = self._getOffCurveSiblingPoint(parent, item)
                        canvasPos = self.clampToOrigin(
                            canvasPos, QPointF(onCurve.x, onCurve.y))
            dx = canvasPos.x() - self._origin.x()
            dy = canvasPos.y() - self._origin.y()
            for anchor in self._glyph.anchors:
                if anchor.selected:
                    anchor.move((dx, dy))
            for contour in self._glyph:
                moveUISelection(contour, (dx, dy))
            for component in self._glyph.components:
                if component.selected:
                    component.move((dx, dy))
            image = self._glyph.image
            if image.selected:
                image.move((dx, dy))
            self._origin = canvasPos
        else:
            self._rubberBandRect = QRectF(self._origin, canvasPos).normalized()
            items = widget.items(self._rubberBandRect)
            points = set(items["points"])
            if event.modifiers() & Qt.ControlModifier:
                points ^= self._oldSelection
            # TODO: fine-tune this more, maybe add optional args to items...
            if event.modifiers() & Qt.AltModifier:
                points = set(pt for pt in points if pt.segmentType)
            if points != self._glyph.selection:
                # TODO: doing this takes more time than by-contour
                # discrimination for large point count
                self._glyph.selection = points
        widget.update()

    def mouseReleaseEvent(self, event):
        self._itemTuple = None
        self._oldSelection = set()
        self._rubberBandRect = None
        self._shouldMove = False
        self.parent().update()

    def mouseDoubleClickEvent(self, event):
        widget = self.parent()
        self._itemTuple = widget.itemAt(self._origin)
        if self._itemTuple is not None:
            item, parent = self._itemTuple
            if parent is None:
                if isinstance(item, TAnchor):
                    self._renameAnchor(item)
            else:
                point, contour = item, parent
                if point.segmentType is not None:
                    self._glyph.prepareUndo()
                    point.smooth = not point.smooth
                contour.dirty = True
        else:
            self._computeLineClick(event.localPos(), True)

    # custom painting

    def paint(self, painter):
        if self._rubberBandRect is None:
            return
        widget = self.parent()
        # okay, OS-native rubber band does not support painting with
        # floating-point coordinates
        # paint directly on the widget with unscaled context
        widgetOrigin = widget.mapFromCanvas(self._rubberBandRect.bottomLeft())
        widgetMove = widget.mapFromCanvas(self._rubberBandRect.topRight())
        option = QStyleOptionRubberBand()
        option.initFrom(widget)
        option.opaque = False
        option.rect = QRectF(widgetOrigin, widgetMove).toRect()
        option.shape = QRubberBand.Rectangle
        painter.save()
        painter.setRenderHint(QPainter.Antialiasing, False)
        painter.setTransform(QTransform())
        widget.style().drawControl(
            QStyle.CE_RubberBand, option, painter, widget)
        painter.restore()
Ejemplo n.º 21
0
class RoundRectItem(QGraphicsObject):
    """Base class for most graphic objects in our scene"""
    def __init__(self, bounds, color=None, parent=None):
        """
        Args:
            bounds - QRectF, geometry of object
            color - QColor or None
            parent - widget to contain this graphic item or None
        """
        super(RoundRectItem, self).__init__(parent)

        self._fillRect = False
        self._bounds = QRectF(bounds)
        self._pix = QPixmap()
        self._color = color

        self.setCacheMode(QGraphicsItem.ItemCoordinateCache)

    def setFill(self, fill: bool):
        """
        Changes the property of how the cell is filled.

        Args:
          fill: bool
        """
        self._fillRect = fill
        self.update()

    @property
    def _gradient(self):
        gradient = QLinearGradient()
        gradient.setStart(
            (self._bounds.topLeft() + self._bounds.topRight()) / 2)
        gradient.setFinalStop(
            (self._bounds.bottomLeft() + self._bounds.bottomRight()) / 2)
        gradient.setColorAt(0, self._color)
        gradient.setColorAt(1, self._color.darker(200))
        return gradient

    def paint(self, painter, option, widget):
        """Standard Qt paint event."""
        if self._color:
            painter.setPen(Qt.NoPen)
            painter.setBrush(QColor(0, 0, 0, 64))
            painter.drawRoundedRect(self._bounds.translated(2, 2), 25.0, 25.0)

            if self._fillRect:
                painter.setBrush(QApplication.palette().brush(QPalette.Window))
            else:
                painter.setBrush(self._gradient)

            painter.setPen(QPen(Qt.black, 1))
            painter.drawRoundedRect(self._bounds, 25.0, 25.0)

        if not self._pix.isNull():
            if self._rounded_pixmap:
                painter.setRenderHint(QPainter.Antialiasing, True)
                brush = QBrush(
                    self._pix.scaled(self._bounds.width(),
                                     self._bounds.height()))
                painter.setBrush(brush)
                painter.drawRoundedRect(self._bounds, 25.0, 25.0)
            else:
                painter.scale(self._bounds.width() / self._pix.width(),
                              self._bounds.height() / self._pix.height())
                painter.drawPixmap(-self._pix.width() / 2,
                                   -self._pix.height() / 2, self._pix)

    def boundingRect(self):
        """returns bounding rectangle"""
        return self._bounds.adjusted(0, 0, 2, 2)

    def setPixmap(self, pixmap_path: str, rounded_pixmap=False):
        """
        Sets new pixmap for this graphic object.

        Args:
          pixmap_path: path to image for pixmap
          rounded_pixmap: make the picture rounded (used, e.g., for lava in the cells)
        """
        self._rounded_pixmap = rounded_pixmap
        self._pix = QPixmap(pixmap_path)
        self.update()
Ejemplo n.º 22
0
class SelectionTool(BaseTool):
    name = QApplication.translate("SelectionTool", "Selection")
    iconPath = ":/resources/cursor.svg"

    def __init__(self, parent=None):
        super().__init__(parent)
        self._itemTuple = None
        self._oldSelection = set()
        self._rubberBandRect = None
        self._shouldPrepareUndo = False

    # helpers

    def _createAnchor(self, *args):
        widget = self.parent()
        pos = widget.mapToCanvas(widget.mapFromGlobal(self._cachedPos))
        newAnchorName, ok = AddAnchorDialog.getNewAnchorName(widget, pos)
        if ok:
            anchor = TAnchor()
            anchor.x = pos.x()
            anchor.y = pos.y()
            anchor.name = newAnchorName
            self._glyph.appendAnchor(anchor)

    def _createComponent(self, *args):
        widget = self.parent()
        newGlyph, ok = AddComponentDialog.getNewGlyph(widget, self._glyph)
        if ok and newGlyph is not None:
            component = TComponent()
            component.baseGlyph = newGlyph.name
            self._glyph.appendComponent(component)

    def _getSelectedCandidatePoint(self):
        """
        If there is exactly one point selected in the glyph, return it.

        Else return None.
        """

        candidates = set()
        for contour in self._glyph:
            sel = contour.selection
            if len(sel) > 1:
                return None
            elif not len(sel):
                continue
            pt = next(iter(sel))
            candidates.add((pt, contour))
        if len(candidates) == 1:
            return next(iter(candidates))
        return None

    def _getOffCurveSiblingPoint(self, contour, point):
        index = contour.index(point)
        for d in (-1, 1):
            sibling = contour.getPoint(index + d)
            if sibling.segmentType is not None:
                return sibling
        raise IndexError

    def _moveOnCurveAlongHandles(self, contour, pt, x, y):
        # TODO: offCurves
        if pt.segmentType is not None and pt.smooth and len(contour) >= 3:
            index = contour.index(pt)
            prevCP = contour.getPoint(index - 1)
            nextCP = contour.getPoint(index + 1)
            # we need at least one offCurve so that it makes sense
            # slide the onCurve around
            if prevCP.segmentType is None or nextCP.segmentType is None:
                projX, projY = bezierMath.lineProjection(
                    prevCP.x, prevCP.y, nextCP.x, nextCP.y, x, y, False)
                # short-circuit UIMove because we're only moving this point
                pt.x = projX
                pt.y = projY
                contour.dirty = True
                return True
        return False

    def _moveForEvent(self, event):
        key = event.key()
        modifiers = event.modifiers()
        dx, dy = 0, 0
        if key == Qt.Key_Left:
            dx = -1
        elif key == Qt.Key_Up:
            dy = 1
        elif key == Qt.Key_Right:
            dx = 1
        elif key == Qt.Key_Down:
            dy = -1
        if modifiers & Qt.ShiftModifier:
            dx *= 10
            dy *= 10
            if modifiers & Qt.ControlModifier:
                dx *= 10
                dy *= 10
        return (dx, dy)

    def _renameAnchor(self, anchor):
        widget = self.parent()
        newAnchorName, ok = AddAnchorDialog.getNewAnchorName(
            widget, None, anchor.name)
        if ok:
            anchor.name = newAnchorName

    def _reverse(self):
        selectedContours = set()
        for contour in self._glyph:
            if contour.selection:
                selectedContours.add(contour)
        target = selectedContours or self._glyph
        for contour in target:
            contour.reverse()

    def _setStartPoint(self, point, contour):
        index = contour.index(point)
        contour.setStartPoint(index)

    # actions

    def showContextMenu(self, event):
        widget = self.parent()
        self._cachedPos = event.globalPos()
        menu = QMenu(widget)
        menu.addAction(self.tr("Add Anchor…"), self._createAnchor)
        menu.addAction(self.tr("Add Component…"), self._createComponent)
        menu.addAction(self.tr("Reverse"), self._reverse)
        itemTuple = widget.itemAt(event.localPos())
        if itemTuple is not None:
            item, parent = itemTuple
            if parent is not None and item.segmentType:
                menu.addSeparator()
                menu.addAction(self.tr("Set Start Point"),
                               lambda: self._setStartPoint(item, parent))
        menu.exec_(self._cachedPos)
        self._cachedPos = None

    # events

    def keyPressEvent(self, event):
        key = event.key()
        if key == platformSpecific.deleteKey:
            glyph = self._glyph
            # TODO: prune
            glyph.prepareUndo()
            preserveShape = not event.modifiers() & Qt.ShiftModifier
            for anchor in glyph.anchors:
                if anchor.selected:
                    glyph.removeAnchor(anchor)
            for contour in reversed(glyph):
                removeUISelection(contour, preserveShape)
            for component in glyph.components:
                if component.selected:
                    glyph.removeComponent(component)
        elif key in arrowKeys:
            # TODO: prune
            self._glyph.prepareUndo()
            delta = self._moveForEvent(event)
            # TODO: seems weird that glyph.selection and selected don't incl.
            # anchors and components while glyph.move does... see what glyphs
            # does
            hadSelection = False
            for anchor in self._glyph.anchors:
                if anchor.selected:
                    anchor.move(delta)
                    hadSelection = True
            for contour in self._glyph:
                moveUISelection(contour, delta)
                # XXX: shouldn't have to recalc this
                if contour.selection:
                    hadSelection = True
            for component in self._glyph.components:
                if component.selected:
                    component.move(delta)
                    hadSelection = True
            if not hadSelection:
                event.ignore()
        elif key in navKeys:
            pack = self._getSelectedCandidatePoint()
            if pack is not None:
                point, contour = pack
                point.selected = False
                index = contour.index(point)
                offset = int(key == Qt.Key_Greater) or -1
                newPoint = contour.getPoint(index + offset)
                newPoint.selected = True
                contour.postNotification(
                    notification="Contour.SelectionChanged")

    def mousePressEvent(self, event):
        if event.button() & Qt.RightButton:
            self.showContextMenu(event)
            return
        widget = self.parent()
        addToSelection = event.modifiers() & Qt.ControlModifier
        self._origin = self.magnetPos(event.localPos())
        self._itemTuple = widget.itemAt(self._origin)
        if self._itemTuple is not None:
            itemUnderMouse, parentContour = self._itemTuple
            if not (itemUnderMouse.selected or addToSelection):
                for anchor in self._glyph.anchors:
                    anchor.selected = False
                for component in self._glyph.components:
                    component.selected = False
                self._glyph.selected = False
            itemUnderMouse.selected = True
            if parentContour is not None:
                parentContour.postNotification(
                    notification="Contour.SelectionChanged")
            self._shouldPrepareUndo = True
        else:
            if addToSelection:
                self._oldSelection = self._glyph.selection
            else:
                for anchor in self._glyph.anchors:
                    anchor.selected = False
                for component in self._glyph.components:
                    component.selected = False
                self._glyph.selected = False
        widget.update()

    def mouseMoveEvent(self, event):
        canvasPos = event.localPos()
        widget = self.parent()
        if self._itemTuple is not None:
            if self._shouldPrepareUndo:
                self._glyph.prepareUndo()
                self._shouldPrepareUndo = False
            modifiers = event.modifiers()
            # Alt: move point along handles
            if modifiers & Qt.AltModifier and len(self._glyph.selection) == 1:
                item, parent = self._itemTuple
                if parent is not None:
                    x, y = canvasPos.x(), canvasPos.y()
                    didMove = self._moveOnCurveAlongHandles(parent, item, x, y)
                    if didMove:
                        return
            # Shift: clamp pos on axis
            elif modifiers & Qt.ShiftModifier:
                item, parent = self._itemTuple
                if parent is not None:
                    if item.segmentType is None:
                        onCurve = self._getOffCurveSiblingPoint(parent, item)
                        canvasPos = self.clampToOrigin(
                            canvasPos, QPointF(onCurve.x, onCurve.y))
            dx = canvasPos.x() - self._origin.x()
            dy = canvasPos.y() - self._origin.y()
            for anchor in self._glyph.anchors:
                if anchor.selected:
                    anchor.move((dx, dy))
            for contour in self._glyph:
                moveUISelection(contour, (dx, dy))
            for component in self._glyph.components:
                if component.selected:
                    component.move((dx, dy))
            self._origin = canvasPos
        else:
            self._rubberBandRect = QRectF(self._origin, canvasPos).normalized()
            items = widget.items(self._rubberBandRect)
            points = set(items["points"])
            if event.modifiers() & Qt.ControlModifier:
                points ^= self._oldSelection
            # TODO: fine-tune this more, maybe add optional args to items...
            if event.modifiers() & Qt.AltModifier:
                points = set(pt for pt in points if pt.segmentType)
            if points != self._glyph.selection:
                # TODO: doing this takes more time than by-contour
                # discrimination for large point count
                self._glyph.selection = points
        widget.update()

    def mouseReleaseEvent(self, event):
        self._itemTuple = None
        self._oldSelection = set()
        self._rubberBandRect = None
        self.parent().update()

    def mouseDoubleClickEvent(self, event):
        widget = self.parent()
        self._itemTuple = widget.itemAt(self._origin)
        if self._itemTuple is not None:
            item, parent = self._itemTuple
            if parent is None:
                if isinstance(item, TAnchor):
                    self._renameAnchor(item)
            else:
                point, contour = item, parent
                if point.segmentType is not None:
                    self._glyph.prepareUndo()
                    point.smooth = not point.smooth
                contour.dirty = True

    # custom painting

    def paint(self, painter):
        if self._rubberBandRect is None:
            return
        widget = self.parent()
        # okay, OS-native rubber band does not support painting with
        # floating-point coordinates
        # paint directly on the widget with unscaled context
        widgetOrigin = widget.mapToWidget(self._rubberBandRect.bottomLeft())
        widgetMove = widget.mapToWidget(self._rubberBandRect.topRight())
        option = QStyleOptionRubberBand()
        option.initFrom(widget)
        option.opaque = False
        option.rect = QRectF(widgetOrigin, widgetMove).toRect()
        option.shape = QRubberBand.Rectangle
        painter.save()
        painter.setRenderHint(QPainter.Antialiasing, False)
        painter.setTransform(QTransform())
        widget.style().drawControl(QStyle.CE_RubberBand, option, painter,
                                   widget)
        painter.restore()
Ejemplo n.º 23
0
class TestPathFinding(TestCase):

################################################################################

    def setUp(self):
        """

        """

        #       --left     --right
        #       '          '
        #   I   |    II    |  III
        # ------+==========+------   --top
        #  VIII |  IX (in) |  IV
        # ------+==========+------   --bottom
        #  VII  |    VI    |   V

        self._offset = 10
        self._rect = QRectF(QPointF(-20, -10), QPointF(20, 10))
        self._pointI = QPointF(self._rect.left()-self._offset, self._rect.top()-self._offset)
        self._pointII = QPointF(self._rect.center().x(), self._rect.top()-self._offset)
        self._pointIII = QPointF(self._rect.right()+ self._offset, self._rect.top()- self._offset)
        self._pointIV = QPointF(self._rect.right()+self._offset, self._rect.center().y())
        self._pointV = QPointF(self._rect.right()+self._offset, self._rect.bottom()+self._offset)
        self._pointVI = QPointF(self._rect.center().x(), self._rect.bottom()+self._offset)
        self._pointVII = QPointF(self._rect.left()-self._offset, self._rect.bottom()+self._offset)
        self._pointVIII = QPointF(self._rect.left()-self._offset, self._rect.center().y())
        self._pointIX = self._rect.center()

        self._lineI_VII = QLineF(self._pointI, self._pointVII)
        self._lineI_V = QLineF(self._pointI, self._pointV)
        self._lineII = QLineF(self._pointII, QPointF(self._rect.center().x(), self._rect.top()))
        self._lineII_IV = QLineF(QPointF(self._rect.right()-self._offset, self._rect.top()-self._offset),
                                 QPointF(self._rect.right()+self._offset, self._rect.top()+self._offset))

################################################################################

    def testPointRectDist(self):
        """

        """

        lineI = PathFinding.pointRectDist(self._pointI, self._rect)
        self.assertEqual(lineI.p2(), self._rect.topLeft())
        self.assertEqual(lineI.length(), pow(pow(self._offset, 2)+pow(self._offset, 2), 0.5))
        lineII = PathFinding.pointRectDist(self._pointII, self._rect)
        self.assertEqual(lineII.p2(), QPointF(self._rect.center().x(), self._rect.top()))
        self.assertEqual(lineII.length(), self._offset)
        lineIII = PathFinding.pointRectDist(self._pointIII, self._rect)
        self.assertEqual(lineIII.p2(), self._rect.topRight())
        self.assertEqual(lineIII.length(), pow(pow(self._offset, 2)+pow(self._offset, 2), 0.5))
        lineIV = PathFinding.pointRectDist(self._pointIV, self._rect)
        self.assertEqual(lineIV.p2(), QPointF(self._rect.right(), self._rect.center().y()))
        self.assertEqual(lineIV.length(), self._offset)
        lineV = PathFinding.pointRectDist(self._pointV, self._rect)
        self.assertEqual(lineV.p2(), self._rect.bottomRight())
        self.assertEqual(lineV.length(), pow(pow(self._offset, 2)+pow(self._offset, 2), 0.5))
        lineVI = PathFinding.pointRectDist(self._pointVI, self._rect)
        self.assertEqual(lineVI.p2(), QPointF(self._rect.center().x(), self._rect.bottom()))
        self.assertEqual(lineVI.length(), self._offset)
        lineVII = PathFinding.pointRectDist(self._pointVII, self._rect)
        self.assertEqual(lineVII.p2(), self._rect.bottomLeft())
        self.assertEqual(lineVII.length(), pow(pow(self._offset, 2)+pow(self._offset, 2), 0.5))
        lineVIII = PathFinding.pointRectDist(self._pointVIII, self._rect)
        self.assertEqual(lineVIII.p2(), QPointF(self._rect.left(), self._rect.center().y()))
        self.assertEqual(lineVIII.length(), self._offset)
        lineIX = PathFinding.pointRectDist(self._pointIX, self._rect)
        self.assertEqual(lineIX.p2(), self._pointIX)
        self.assertEqual(lineIX.length(), 0)

################################################################################

    def testIntersects(self):
        """

        """

        rect = QRectF(QPointF(-50, -10), QPointF(50, 10))
        # line completely outside of the rectangle
        self.assertFalse(PathFinding.intersects(rect, QLineF(QPointF(-100, -50), QPointF(-100, 50))))
        # the line is a top side of the rectangle
        self.assertFalse(PathFinding.intersects(rect, QLineF(rect.topLeft(), rect.topRight())))
        # the line starts at the left corner of the rectangle and is not perpendicular to any of the rectangle sides;
        # the line ends outside of the rectangle, not going through it
        self.assertFalse(PathFinding.intersects(rect, QLineF(rect.topLeft(), QPointF(-100, -100))))
        # the line starts at the left corner of the rectangle and is perpendicular to the top side of the rectangle;
        # the line ends outside of the rectangle, not going through it
        self.assertFalse(PathFinding.intersects(rect, QLineF(rect.topLeft(), QPointF(rect.left(), rect.top() - 100))))
        # the line is horizontal and goes straight through the center of the rectangle
        self.assertTrue(PathFinding.intersects(rect, QLineF(QPointF(-100, 0), QPointF(100, 0))))
        # the line is vertical and goes straight through the center of the rectangle
        self.assertTrue(PathFinding.intersects(rect, QLineF(QPointF(0, -100), QPointF(0, 100))))
        # the line is vertical and goes up from the bottom right corner of the rectangle
        self.assertFalse(PathFinding.intersects(rect, QLineF(rect.bottomRight(), QPointF(rect.right(), rect.top()-100))))
        # the line is diagonal of the rectangle
        self.assertTrue(PathFinding.intersects(rect, QLineF(rect.topLeft(), rect.bottomRight())))