PyQt6, drawing shapes over loaded PDF file

125 Views Asked by At

My goal is to create a PDF viewer that allows to draw a rectangle in front of the text. Right now the viewer part is done as well as the drawing rectangles part. The problem is, they don't work together. As soon as I load a PDF file, I can no longer draw my shapes over it. I highly suspect that the problem is wrong QPixmap or QPainter argument somewhere. Unfortunately, I'm very much nooby with PyQt6, so I don't really understand how those classes work (I've read the docs). I'll be really grateful if some noble soul would help me with this problem. Screenshot - Left: I can draw rectangles. Right: I open PDF and rectangles disappear and I can't draw new ones.

import sys
from pathlib import Path

from PyQt6 import QtWidgets
from PyQt6.QtCore import QPointF, QRect, QPoint, Qt
from PyQt6.QtGui import QAction, QPainter, QPixmap
from PyQt6.QtPdf import QPdfDocument
from PyQt6.QtPdfWidgets import QPdfView
from PyQt6.QtWidgets import QFileDialog, QApplication


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        self.init_ui()

        # Painting settings
        self.pix = QPixmap(self.rect().size())
        self.pix.fill(Qt.GlobalColor.transparent)
        self.point1, self.point2 = QPoint(), QPoint()

        # PDF settings
        self.pdf_document = QPdfDocument(self)
        self.pdf_view = QPdfView(self)
        self.m_fileDialog = None
        self.pdf_view.setDocument(self.pdf_document)

    def init_ui(self):
        self.setGeometry(100, 100, 800, 600)
        self.setWindowTitle('RectanglePDF')

        # Buttons
        open_file = QAction('Open PDF file...', self)
        open_file.triggered.connect(self.open_pdf)

        close_program = QAction('Exit', self)
        close_program.triggered.connect(self.exit_program)

        p_page = QAction('Previous Page', self)
        p_page.triggered.connect(self.previous_page)

        n_page = QAction('Next Page', self)
        n_page.triggered.connect(self.next_page)

        # Menubar
        menubar = self.menuBar()
        file_menu = menubar.addMenu('File')
        file_menu.addAction(open_file)
        file_menu.addAction(close_program)
        menubar.addAction(p_page)
        menubar.addAction(n_page)

        self.show()

    def paintEvent(self, event):
        painter = QPainter(self)
        painter.drawPixmap(QPoint(), self.pix)

        if not self.point1.isNull() and not self.point2.isNull():
            rect = QRect(self.point1, self.point2)
            painter.drawRect(rect.normalized())

    def mousePressEvent(self, event):
        if event.buttons() & Qt.MouseButton.LeftButton:
            self.point1 = event.pos()
            self.point2 = self.point1
            self.update()

    def mouseMoveEvent(self, event):
        if event.buttons() & Qt.MouseButton.LeftButton:
            self.point2 = event.pos()
            self.update()

    def mouseReleaseEvent(self, event):
        if event.button() & Qt.MouseButton.LeftButton:
            rect = QRect(self.point1, self.point2)
            painter = QPainter(self.pix)
            painter.drawRect(rect.normalized())

            self.point1, self.point2 = QPoint(), QPoint()
            self.update()

    def next_page(self):
        nav = self.pdf_view.pageNavigator()
        if nav.currentPage() < self.pdf_document.pageCount() - 1:
            nav.jump(nav.currentPage() + 1, QPointF(), nav.currentZoom())

    def previous_page(self):
        nav = self.pdf_view.pageNavigator()
        if nav.currentPage() > 0:
            nav.jump(nav.currentPage() - 1, QPointF(), nav.currentZoom())

    def exit_program(self):
        sys.exit(app.exec())

    def open_pdf(self):
        home_dir = str(Path.home())
        doc_location = QFileDialog.getOpenFileName(
            self,
            'Open File',
            home_dir,
            "PDF files (*.pdf)"
        )

        self.pdf_document.load(doc_location[0])

        self.pdf_view.setDocument(self.pdf_document)
        self.pdf_view.setPageMode(QPdfView.PageMode.SinglePage)
        self.pdf_view.setZoomMode(QPdfView.ZoomMode.FitInView)
        self.setCentralWidget(self.pdf_view)

        self.update()
        self.pdf_view.show()  # old


if __name__ == '__main__':
    app = QApplication(sys.argv)

    myApp = MainWindow()
    myApp.show()

    try:
        sys.exit(app.exec())
    except SystemExit:
        print('Closing Window...')

Update. I tried to use Stacked Layout, but result is still unsatisfactory.

import sys
from pathlib import Path

from PyQt6 import QtCore, QtWidgets
from PyQt6.QtCore import QPointF, QRect, QPoint, Qt
from PyQt6.QtGui import QAction, QPainter, QPixmap, QIcon
from PyQt6.QtPdf import QPdfDocument
from PyQt6.QtPdfWidgets import QPdfView
from PyQt6.QtWidgets import (QFileDialog,
                             QApplication,
                             QStackedLayout,
                             QVBoxLayout,
                             QWidget,
                             )


class RectWindow(QWidget):
    def __init__(self):
        super().__init__()

        layout = QVBoxLayout()
        self.setLayout(layout)

        self.pix = QPixmap(self.rect().size())  # TODO get screen resolution
        self.pix.fill(Qt.GlobalColor.transparent)

        self.begin, self.destination = QPoint(), QPoint()

        # TODO Transparent background to reveal PDF underneath
        # self.setWindowFlags(Qt.WindowType.FramelessWindowHint)
        # self.setWindowFlags(Qt.WindowType.WindowTransparentForInput)
        # self.setStyleSheet('QWidget {background: transparent')
        # self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground, True)
        # self.setWindowOpacity(0.5)

    def paintEvent(self, event):
        painter = QPainter(self)
        painter.drawPixmap(QPoint(), self.pix)

        if not self.begin.isNull() and not self.destination.isNull():
            rect = QRect(self.begin, self.destination)
            painter.drawRect(rect.normalized())

    def mousePressEvent(self, event):
        if event.buttons() & Qt.MouseButton.LeftButton:
            self.begin = event.pos()
            self.destination = self.begin
            self.update()

    def mouseMoveEvent(self, event):
        if event.buttons() & Qt.MouseButton.LeftButton:
            self.destination = event.pos()
            self.update()

    def mouseReleaseEvent(self, event):
        if event.button() & Qt.MouseButton.LeftButton:
            rect = QRect(self.begin, self.destination)
            painter = QPainter(self.pix)
            painter.drawRect(rect.normalized())

            self.begin, self.destination = QPoint(), QPoint()
            self.update()


class PdfWindow(QWidget):
    def __init__(self):
        super().__init__()

        self.pdf_document = QPdfDocument(self)
        self.pdf_view = QPdfView(self)
        self.m_fileDialog = None
        self.pdf_view.setDocument(self.pdf_document)

    def open_pdf(self):
        home_dir = str(Path.home())
        doc_location = QFileDialog.getOpenFileName(
            self,
            'Open File',
            home_dir,
            "PDF files (*.pdf)"
        )

        self.pdf_document.load(doc_location[0])

        self.pdf_view.setDocument(self.pdf_document)
        self.pdf_view.setPageMode(QPdfView.PageMode.SinglePage)
        self.pdf_view.setZoomMode(QPdfView.ZoomMode.FitInView)

        self.update()
        self.pdf_view.showMaximized()

    def next_page(self):
        nav = self.pdf_view.pageNavigator()
        if nav.currentPage() < self.pdf_document.pageCount() - 1:
            nav.jump(nav.currentPage() + 1, QPointF(), nav.currentZoom())

    def previous_page(self):
        nav = self.pdf_view.pageNavigator()
        if nav.currentPage() > 0:
            nav.jump(nav.currentPage() - 1, QPointF(), nav.currentZoom())


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('RectanglePDF')

        # Widgets
        base_widget = QWidget()
        rect_widget = RectWindow()
        pdf_widget = PdfWindow()

        # Layout settings
        main_layout = QVBoxLayout()
        self.stacklayout = QStackedLayout()
        self.setLayout(main_layout)
        base_widget.setLayout(self.stacklayout)

        # Layout ordering
        self.stacklayout.addWidget(pdf_widget)
        self.stacklayout.addWidget(rect_widget)

        self.setCentralWidget(base_widget)

        # Buttons
        open_file = QAction('Open PDF file...', self)
        open_file.triggered.connect(pdf_widget.open_pdf)

        close_program = QAction('Exit', self)
        close_program.triggered.connect(self.exit_program)

        p_page = QAction('Previous Page', self)
        p_page.triggered.connect(pdf_widget.previous_page)

        n_page = QAction('Next Page', self)
        n_page.triggered.connect(pdf_widget.next_page)

        # Menubar
        menubar = self.menuBar()
        file_menu = menubar.addMenu('File')
        file_menu.addAction(open_file)
        file_menu.addAction(close_program)
        menubar.addAction(p_page)
        menubar.addAction(n_page)

        # DEBUG
        r_pdf = QAction('Raise PDF', self)
        r_pdf.triggered.connect(self.raise_pdf)
        menubar.addAction(r_pdf)

        r_rect = QAction('Raise Rect', self)
        r_rect.triggered.connect(self.raise_rect)
        menubar.addAction(r_rect)

    def raise_pdf(self):  # for DEBUG
        self.stacklayout.setCurrentIndex(0)

    def raise_rect(self):  # for DEBUG
        self.stacklayout.setCurrentIndex(1)

    def exit_program(self):
        sys.exit(app.exec())


if __name__ == '__main__':
    app = QApplication(sys.argv)

    mainApp = MainWindow()
    mainApp.showMaximized()

    try:
        sys.exit(app.exec())
    except SystemExit:
        print('Closing Window...')
0

There are 0 best solutions below