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...')