Exemple #1
0
class PageViewer(QGraphicsView):
    mouseRelaseEvent = pyqtSignal()
    wheelZoomEvent = pyqtSignal()

    @property
    def drawing(self):
        return self._drawing

    @property
    def tool_mode(self):
        return self._toolMode

    @property
    def empty(self):
        return self._page is None

    @property
    def page_layer(self):
        return self._pageLayer

    @property
    def drawing_layer(self):
        return self._drawingLayer

    @property
    def page(self):
        return self._page

    @property
    def pen_size(self):
        return self._pen.width()

    @property
    def eraser_size(self):
        return self._eraserSize

    def __init__(self, parent):
        super(PageViewer, self).__init__(parent)

        self._page = None

        # todo: add all these constants to preferences dialog
        self._zoom = 0
        self._zoomMaxDistance = 10
        self._zoomInFactor = 1.25
        self._zoomOutFactor = 0.8
        self._panDivisor = 2
        self._toolMode = ToolMode.NOTHING
        self._previousToolModeDrag = ToolMode.NOTHING
        self._drawing = False
        self._dragStart = None
        self._drawPoint = None
        self._lastPenPos = None

        self._scene = QGraphicsScene(self)
        self._pageLayer = None
        self._drawingLayer = None

        self._predrawPixmap = None

        self._eraserSize = 100
        self._eraserEllipse = self.getEraserEllipse()

        pen_brush = QBrush(QColor('red'), Qt.SolidPattern)
        self._pen = QPen(pen_brush, 5, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)

        self._increaseSizeAction = QAction('Increase tool size', self)
        self._increaseSizeAction.setEnabled(False)
        self._increaseSizeAction.triggered.connect(
            self.onIncreaseSizeActionTriggered)

        self._decreaseSizeAction = QAction('Decrease tool size', self)
        self._decreaseSizeAction.setEnabled(False)
        self._decreaseSizeAction.triggered.connect(
            self.onDecreaseSizeActionTriggered)

        self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)
        self.setResizeAnchor(QGraphicsView.AnchorUnderMouse)
        self.setMouseTracking(True)

    def reset(self):
        self.setInteractive(False)
        self.setToolMode(ToolMode.NOTHING)
        self.setScene(QGraphicsScene(self))

        self._page = None
        self._zoom = 0

    def getEraserEllipse(self):
        brush = QBrush(QColor(0, 0, 0, alpha=64), Qt.SolidPattern)
        pen = QPen(brush, 2, Qt.DashDotLine, Qt.RoundCap, Qt.RoundJoin)
        pen.setCosmetic(True)
        ellipse = QGraphicsEllipseItem(0, 0, self._eraserSize,
                                       self._eraserSize)
        ellipse.setPen(pen)
        return ellipse

    def fitPage(self):
        if not self.empty:
            rect = QRectF(self._pageLayer.pixmap().rect())
            if not rect.isNull():
                self.setSceneRect(rect)
                unity = self.transform().mapRect(QRectF(0, 0, 1, 1))
                self.scale(1 / unity.width(), 1 / unity.height())
                viewrect = self.viewport().rect()
                scenerect = self.transform().mapRect(rect)
                factor = min(viewrect.width() / scenerect.width(),
                             viewrect.height() / scenerect.height())
                self.scale(factor, factor)
                self._zoom = 0

    def setPage(self, page):
        self._page = page

        if not self._page.page_image.isNull():
            self._scene.clear()

            self._pageLayer = QGraphicsPixmapItem(self._page.page_image)
            self._scene.addItem(self._pageLayer)
            self._drawingLayer = QGraphicsPixmapItem(self._page.drawing_image)
            self._scene.addItem(self._drawingLayer)

            self.fitPage()
            self._eraserEllipse = self.getEraserEllipse()
            self.setScene(self._scene)
            self.setToolMode(self._toolMode)
            self.setInteractive(True)

    def canZoomIn(self):
        return self._zoom < self._zoomMaxDistance

    def canZoomOut(self):
        return self._zoom > 0

    def zoomIn(self):
        if self.canZoomIn():
            self._zoom += 1
            self._applyZoom(self._zoomInFactor)
            return True
        return False

    def zoomOut(self):
        if self.canZoomOut():
            self._zoom -= 1
            self._applyZoom(self._zoomOutFactor)
            return True
        return False

    def _applyZoom(self, factor):
        if self._zoom > 0:
            self.scale(factor, factor)
        else:
            self._zoom = 0
            self.fitPage()

    def wheelEvent(self, event):
        if not self.empty:
            if event.angleDelta().y() > 0:
                factor = self._zoomInFactor
                if not self.zoomIn():
                    return
            else:
                factor = self._zoomOutFactor
                self.zoomOut()

            self._applyZoom(factor)
            self.wheelZoomEvent.emit()

    def _penDraw(self, pos):
        self._drawing = True
        self._lastPenPos = self._drawPoint
        self._drawPoint = pos
        self.update()

    def _eraseDraw(self, pos):
        self._drawing = True
        self._drawPoint = pos
        self.update()

    def mousePressEvent(self, event):
        if not self.empty:
            button = event.button()
            scenePos = self.mapToScene(event.pos())

            if button == Qt.MiddleButton:
                self._previousToolModeDrag = self._toolMode
                self.setToolMode(ToolMode.DRAGGING)
                self._dragStart = scenePos
            elif button == Qt.LeftButton:
                if self._toolMode == ToolMode.NOTHING:
                    self.setToolMode(ToolMode.DRAGGING)
                else:
                    sceneRect = self._drawingLayer.mapRectToScene(
                        QRectF(self._page.drawing_image.rect()))

                    if sceneRect.contains(scenePos.x(), scenePos.y()):
                        self._predrawPixmap = copyPixmap(
                            self._page.drawing_image)
                        if self._toolMode == ToolMode.PEN:
                            self._penDraw(scenePos)
                        if self._toolMode == ToolMode.ERASER:
                            self._eraseDraw(scenePos)

        super(PageViewer, self).mousePressEvent(event)

    def mouseMoveEvent(self, event):
        scenePos = self.mapToScene(event.pos())

        if self._toolMode == ToolMode.ERASER:
            r = self._eraserEllipse.rect()
            x = scenePos.x() - (r.width() / 2)
            y = scenePos.y() - (r.height() / 2)
            self._eraserEllipse.setPos(x, y)

        if not self.empty:
            if self._toolMode == ToolMode.DRAGGING and self._dragStart is not None:
                delta = self.mapToScene(event.pos())
                self.translate(delta.x() - self._dragStart.x(),
                               delta.y() - self._dragStart.y())
            else:
                if self._drawing:
                    if self._toolMode == ToolMode.PEN:
                        self._penDraw(scenePos)

        super(PageViewer, self).mouseMoveEvent(event)

    def mouseReleaseEvent(self, event):
        if not self.empty:
            button = event.button()
            scenePos = self.mapToScene(event.pos())

            if button == Qt.MiddleButton:
                self.setToolMode(self._previousToolModeDrag)
            elif button == Qt.LeftButton:
                if self._toolMode == ToolMode.DRAGGING:
                    self.setToolMode(ToolMode.NOTHING)
                elif self._toolMode == ToolMode.PEN:
                    self._penDraw(scenePos)
                    self._page.pushCommand(
                        DrawCommand(self, self._predrawPixmap))
                    self._lastPenPos = None
                elif self._toolMode == ToolMode.ERASER:
                    self._eraseDraw(scenePos)
                    self._page.pushCommand(
                        DrawCommand(self, self._predrawPixmap))

                self._drawPoint = None
                self._drawing = False

        self.mouseRelaseEvent.emit()

    def paintEvent(self, event):
        if self._drawing:
            painter = QPainter(self._page.drawing_image)
            painter.setRenderHint(QPainter.HighQualityAntialiasing, True)

            if self._toolMode == ToolMode.ERASER:
                painter.setPen(Qt.NoPen)
                painter.setBrush(painter.background())

                r = int(self._eraserSize / 2)
                painter.drawEllipse(self._drawPoint, r, r)

            if self._toolMode == ToolMode.PEN:
                painter.setPen(self._pen)

                if self._lastPenPos is None:
                    painter.drawPoint(self._drawPoint)
                else:
                    painter.drawLine(self._lastPenPos, self._drawPoint)

            painter.end()
            self.updateDrawingLayer()

        super(PageViewer, self).paintEvent(event)

    def setToolMode(self, mode: ToolMode):
        self._toolMode = mode

        self._increaseSizeAction.setEnabled(False)
        self._decreaseSizeAction.setEnabled(False)

        for item in self._scene.items():
            if isinstance(item, QGraphicsEllipseItem):
                self._scene.removeItem(item)

        if self._toolMode == ToolMode.DRAGGING:
            self.setCursor(Qt.ClosedHandCursor)
        else:
            if self._toolMode == ToolMode.ERASER:
                self._scene.addItem(self._eraserEllipse)

                self._increaseSizeAction.setText('Increase Eraser width')
                self._decreaseSizeAction.setText('Decrease Eraser width')
                self._increaseSizeAction.setEnabled(True)
                self._decreaseSizeAction.setEnabled(True)
            elif self._toolMode == ToolMode.PEN:
                self.setDragMode(QGraphicsView.NoDrag)
                self.setCursor(Qt.CrossCursor)

                self._increaseSizeAction.setText('Increase Pen width')
                self._decreaseSizeAction.setText('Decrease Pen width')
                self._increaseSizeAction.setEnabled(True)
                self._decreaseSizeAction.setEnabled(True)
            else:
                self.setDragMode(QGraphicsView.NoDrag)
                self.setCursor(Qt.ArrowCursor)

    def setEraserSize(self, v):
        if 500 >= v >= 10:
            self._eraserEllipse.setRect(0, 0, v, v)
            self._eraserSize = v

    def setPenSize(self, v):
        if 30 >= v > 0:
            self._pen.setWidth(v)

    def onIncreaseSizeActionTriggered(self):
        if self._toolMode == ToolMode.ERASER:
            v = self._eraserSize + 1
            self.setEraserSize(v)
        elif self._toolMode == ToolMode.PEN:
            v = self._pen.width() + 1
            self.setPenSize(v)

    def onDecreaseSizeActionTriggered(self):
        if self._toolMode == ToolMode.ERASER:
            v = self._eraserSize - 1
            self.setEraserSize(v)
        elif self._toolMode == ToolMode.PEN:
            v = self._pen.width() - 1
            self.setPenSize(v)

    def getActiveToolSizeText(self):
        if self._toolMode == ToolMode.ERASER:
            return '{:03d}px'.format(self._eraserSize)
        elif self._toolMode == ToolMode.PEN:
            return '{:02d}px'.format(self._pen.width())
        else:
            return ''

    def getIncreaseSizeAction(self):
        return self._increaseSizeAction

    def getDecreaseSizeAction(self):
        return self._decreaseSizeAction

    def updateDrawingLayer(self):
        self._drawingLayer.setPixmap(self._page.drawing_image)