Implement a PDF Viewer Dialog

This is based on the example in pyside6 changed to use a QDialog and
not a QMainWindow.  The MenuBar is gone, the Status Bar is gone.
This commit is contained in:
Christopher T. Johnson
2025-02-20 10:09:32 -05:00
parent 50b14562fc
commit 9f1c54c1e7
16 changed files with 4040 additions and 21 deletions

View File

@@ -1,31 +1,173 @@
from PySide6.QtCore import QFile import math
from PySide6.QtPdf import QPdfDocument
from PySide6.QtCore import QFile, QModelIndex, QPoint, Signal, Slot
from PySide6.QtPdf import QPdfBookmarkModel, QPdfDocument
from PySide6.QtPdfWidgets import QPdfView from PySide6.QtPdfWidgets import QPdfView
from PySide6.QtWidgets import QDialog, QVBoxLayout, QWidget from PySide6.QtWidgets import QComboBox, QDialog, QMenuBar, QSpinBox, QWidget
from ui.pdfViewer import Ui_pdfViewer
ZOOM_MULTIPLIER = math.sqrt(2.0)
class ZoomSelector(QComboBox):
zoom_mode_changed = Signal(QPdfView.ZoomMode)
zoom_factor_changed = Signal(float)
def __init__(self, parent: QWidget|None = None) -> None:
super(ZoomSelector, self).__init__(parent)
self.setEditable(True)
for text in [ 'Fit Width', 'Fit Page', '12%', '25%', '33%', '50%', '66%', '75%', '100%', '125%', '150%', '200%', '400%']:
self.addItem(text)
self.currentTextChanged.connect(self.on_current_text_changed)
lineEdit = self.lineEdit()
assert lineEdit is not None
lineEdit.editingFinished.connect(self._editing_finished)
return
@Slot(float) # type: ignore
def set_zoom_factor(self, zoomFactor: float) -> None:
percent = int(zoomFactor * 100)
self.setCurrentText(f"{percent}%")
return
@Slot()
def reset(self) -> None:
self.setCurrentIndex(8) # 100%
return
@Slot(str) # type: ignore
def on_current_text_changed(self, text: str) -> None:
if text == "Fit Width":
self.zoom_mode_changed.emit(QPdfView.ZoomMode.FitToWidth)
elif text == "Fit Page":
self.zoom_mode_changed.emit(QPdfView.ZoomMode.FitInView)
elif text.endswith("%"):
zoom_level = int(text[:-1])
factor = zoom_level / 100.0
self.zoom_mode_changed.emit(QPdfView.ZoomMode.Custom)
self.zoom_factor_changed.emit(factor)
return
@Slot()
def _editing_finished(self) -> None:
lineEdit = self.lineEdit()
assert lineEdit is not None
self.on_current_text_changed(lineEdit.text())
return
class PDFViewer(QDialog): class PDFViewer(QDialog, Ui_pdfViewer):
pdf_view: QPdfView menubar: QMenuBar
pdf_document: QPdfDocument zoomSelector: ZoomSelector
pageSelector: QSpinBox
document: QPdfDocument
def __init__(self, parent: QWidget) -> None: def __init__(self, parent: QWidget | None) -> None:
super(PDFViewer, self).__init__(parent) super(PDFViewer, self).__init__(parent)
self.setupUi(self)
self.pdf_view = QPdfView() self.zoomSelector = ZoomSelector(self)
self.pdf_document = QPdfDocument() self.pageSelector = QSpinBox(self)
self.document = QPdfDocument(self)
layout = QVBoxLayout(self) self.zoomSelector.setMaximumWidth(150)
layout.addWidget(self.pdf_view) self.mainToolBar.insertWidget(self.actionZoom_In, self.zoomSelector)
self.setLayout(layout) self.mainToolBar.insertWidget(self.actionForward, self.pageSelector)
self.pageSelector.valueChanged.connect(self.page_selected)
nav = self.pdfView.pageNavigator()
nav.currentPageChanged.connect(self.pageSelector.setValue)
nav.backAvailableChanged.connect(self.actionBack.setEnabled)
nav.forwardAvailableChanged.connect(self.actionForward.setEnabled)
self.zoomSelector.zoom_mode_changed.connect(self.pdfView.setZoomMode)
self.zoomSelector.zoom_factor_changed.connect(self.pdfView.setZoomFactor)
self.zoomSelector.reset()
bookmark_model = QPdfBookmarkModel(self)
bookmark_model.setDocument(self.document)
self.bookmarkView.setModel(bookmark_model)
self.bookmarkView.activated.connect(self.bookmark_selected)
self.tabWidget.setTabEnabled(1, False) # disable pages tabwidget
self.pdfView.setDocument(self.document)
self.pdfView.zoomFactorChanged.connect(self.zoomSelector.set_zoom_factor)
return return
def load_pdf(self, file: QFile) -> None: @Slot(QFile) # type: ignore
if not file.isOpen(): def open(self, file: QFile) -> None:
file.open(file.OpenModeFlag.ReadOnly) #assert file.exists()
self.pdf_document.load(file) #if not file.isOpen():
self.pdf_view.setDocument(self.pdf_document) #file.open(QFile.OpenModeFlag.ReadOnly)
self.pdf_view.setPageMode(QPdfView.PageMode.MultiPage) #self.document.load(file)
size = self.pdf_document.pagePointSize(1) self.document.load(file.fileName())
self.resize(size.toSize()) document_title = self.document.metaData(QPdfDocument.MetaDataField.Title)
print(size) self.setWindowTitle(document_title if document_title else "PDF Viewer")
self.page_selected(0)
self.pageSelector.setMaximum(self.document.pageCount() -1 )
return return
@Slot(QModelIndex) # type: ignore
def bookmark_selected(self, index: QModelIndex) -> None:
if not index.isValid():
return
page = index.data(int(QPdfBookmarkModel.Role.Page))
zoom_level = index.data(int(QPdfBookmarkModel.Role.Level))
nav = self.pdfView.pageNavigator()
assert nav is not None
nav.jump(page, QPoint(), zoom_level)
return
@Slot(int) # type: ignore
def page_selected(self, page: int) -> None:
nav = self.pdfView.pageNavigator()
nav.jump(page, QPoint(), nav.currentZoom())
return
@Slot()
def on_actionOpen_triggered(self) -> None:
print("What are you doing in actionOpen_triggered?")
return
@Slot()
def on_actionQuit_triggered(self) -> None:
self.close()
return
@Slot()
def on_actionZoom_In_triggered(self) -> None:
factor = self.pdfView.zoomFactor() * ZOOM_MULTIPLIER
self.pdfView.setZoomFactor(factor)
return
@Slot()
def on_actionZoom_Out_triggered(self) -> None:
factor = self.pdfView.zoomFactor() / ZOOM_MULTIPLIER
self.pdfView.setZoomFactor(factor)
return
@Slot()
def on_actionPrevious_Page_triggered(self) -> None:
nav = self.pdfView.pageNavigator()
nav.jump(nav.currentPage() - 1, QPoint(), nav.currentZoom())
return
@Slot()
def on_actionNext_Page_triggered(self) -> None:
nav = self.pdfView.pageNavigator()
nav.jump(nav.currentPage() + 1, QPoint(), nav.currentZoom())
return
@Slot()
def on_actionContinuous_triggered(self) -> None:
cont_checked = self.actionContinuous.isChecked()
mode = QPdfView.PageMode.MultiPage if cont_checked else QPdfView.PageMode.SinglePage
self.pdfView.setPageMode(mode)
return
@Slot()
def on_actionBack_triggered(self) -> None:
self.pdfView.pageNavigator().back()
return
@Slot()
def on_actionForward_triggered(self) -> None:
self.pdfView.pageNavigator().forward()
return

Binary file not shown.

Binary file not shown.

BIN
ui/images/go-next-view.svgz Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
ui/images/zoom-in.svgz Normal file

Binary file not shown.

Binary file not shown.

BIN
ui/images/zoom-out.svgz Normal file

Binary file not shown.

Binary file not shown.

171
ui/pdfViewer.py Normal file
View File

@@ -0,0 +1,171 @@
# -*- coding: utf-8 -*-
################################################################################
## Form generated from reading UI file 'pdfViewer.ui'
##
## Created by: Qt User Interface Compiler version 6.8.2
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
QMetaObject, QObject, QPoint, QRect,
QSize, QTime, QUrl, Qt)
from PySide6.QtGui import (QAction, QBrush, QColor, QConicalGradient,
QCursor, QFont, QFontDatabase, QGradient,
QIcon, QImage, QKeySequence, QLinearGradient,
QPainter, QPalette, QPixmap, QRadialGradient,
QTransform)
from PySide6.QtPdfWidgets import QPdfView
from PySide6.QtWidgets import (QApplication, QDialog, QHeaderView, QSizePolicy,
QSplitter, QTabWidget, QToolBar, QTreeView,
QVBoxLayout, QWidget)
import ui.resources_rc
class Ui_pdfViewer(object):
def setupUi(self, pdfViewer):
if not pdfViewer.objectName():
pdfViewer.setObjectName(u"pdfViewer")
pdfViewer.resize(700, 513)
self.actionZoom_In = QAction(pdfViewer)
self.actionZoom_In.setObjectName(u"actionZoom_In")
icon = QIcon(QIcon.fromTheme(QIcon.ThemeIcon.ZoomIn))
self.actionZoom_In.setIcon(icon)
self.actionZoom_In.setMenuRole(QAction.MenuRole.NoRole)
self.actionZoom_Out = QAction(pdfViewer)
self.actionZoom_Out.setObjectName(u"actionZoom_Out")
icon1 = QIcon(QIcon.fromTheme(QIcon.ThemeIcon.ZoomOut))
self.actionZoom_Out.setIcon(icon1)
self.actionZoom_Out.setMenuRole(QAction.MenuRole.NoRole)
self.actionPrevious_Page = QAction(pdfViewer)
self.actionPrevious_Page.setObjectName(u"actionPrevious_Page")
icon2 = QIcon()
icon2.addFile(u":/icons/images/go-previous-view-page.svgz", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionPrevious_Page.setIcon(icon2)
self.actionPrevious_Page.setMenuRole(QAction.MenuRole.NoRole)
self.actionNext_Page = QAction(pdfViewer)
self.actionNext_Page.setObjectName(u"actionNext_Page")
icon3 = QIcon()
icon3.addFile(u":/icons/images/go-next-view-page.svgz", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionNext_Page.setIcon(icon3)
self.actionNext_Page.setMenuRole(QAction.MenuRole.NoRole)
self.actionContinuous = QAction(pdfViewer)
self.actionContinuous.setObjectName(u"actionContinuous")
self.actionContinuous.setCheckable(True)
self.actionContinuous.setMenuRole(QAction.MenuRole.NoRole)
self.actionBack = QAction(pdfViewer)
self.actionBack.setObjectName(u"actionBack")
icon4 = QIcon()
icon4.addFile(u":/icons/images/go-previous-view.svgz", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionBack.setIcon(icon4)
self.actionBack.setMenuRole(QAction.MenuRole.NoRole)
self.actionForward = QAction(pdfViewer)
self.actionForward.setObjectName(u"actionForward")
icon5 = QIcon()
icon5.addFile(u":/icons/images/go-next-view.svgz", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.actionForward.setIcon(icon5)
self.actionForward.setMenuRole(QAction.MenuRole.NoRole)
self.verticalLayout = QVBoxLayout(pdfViewer)
self.verticalLayout.setObjectName(u"verticalLayout")
self.mainToolBar = QToolBar(pdfViewer)
self.mainToolBar.setObjectName(u"mainToolBar")
self.verticalLayout.addWidget(self.mainToolBar)
self.widget = QWidget(pdfViewer)
self.widget.setObjectName(u"widget")
self.verticalLayout_2 = QVBoxLayout(self.widget)
self.verticalLayout_2.setSpacing(0)
self.verticalLayout_2.setObjectName(u"verticalLayout_2")
self.verticalLayout_2.setContentsMargins(0, 0, 0, 0)
self.splitter = QSplitter(self.widget)
self.splitter.setObjectName(u"splitter")
self.splitter.setOrientation(Qt.Orientation.Horizontal)
self.tabWidget = QTabWidget(self.splitter)
self.tabWidget.setObjectName(u"tabWidget")
sizePolicy = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.tabWidget.sizePolicy().hasHeightForWidth())
self.tabWidget.setSizePolicy(sizePolicy)
self.tabWidget.setTabPosition(QTabWidget.TabPosition.West)
self.bookmarkTab = QWidget()
self.bookmarkTab.setObjectName(u"bookmarkTab")
self.verticalLayout_3 = QVBoxLayout(self.bookmarkTab)
self.verticalLayout_3.setSpacing(0)
self.verticalLayout_3.setObjectName(u"verticalLayout_3")
self.verticalLayout_3.setContentsMargins(2, 2, 2, 2)
self.bookmarkView = QTreeView(self.bookmarkTab)
self.bookmarkView.setObjectName(u"bookmarkView")
sizePolicy.setHeightForWidth(self.bookmarkView.sizePolicy().hasHeightForWidth())
self.bookmarkView.setSizePolicy(sizePolicy)
self.verticalLayout_3.addWidget(self.bookmarkView)
self.tabWidget.addTab(self.bookmarkTab, "")
self.pagesTab = QWidget()
self.pagesTab.setObjectName(u"pagesTab")
self.tabWidget.addTab(self.pagesTab, "")
self.splitter.addWidget(self.tabWidget)
self.pdfView = QPdfView(self.splitter)
self.pdfView.setObjectName(u"pdfView")
sizePolicy1 = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
sizePolicy1.setHorizontalStretch(10)
sizePolicy1.setVerticalStretch(0)
sizePolicy1.setHeightForWidth(self.pdfView.sizePolicy().hasHeightForWidth())
self.pdfView.setSizePolicy(sizePolicy1)
self.splitter.addWidget(self.pdfView)
self.verticalLayout_2.addWidget(self.splitter)
self.verticalLayout.addWidget(self.widget)
self.mainToolBar.addAction(self.actionZoom_Out)
self.mainToolBar.addAction(self.actionZoom_In)
self.mainToolBar.addSeparator()
self.mainToolBar.addAction(self.actionBack)
self.mainToolBar.addAction(self.actionForward)
self.mainToolBar.addAction(self.actionPrevious_Page)
self.mainToolBar.addAction(self.actionNext_Page)
self.retranslateUi(pdfViewer)
self.tabWidget.setCurrentIndex(0)
QMetaObject.connectSlotsByName(pdfViewer)
# setupUi
def retranslateUi(self, pdfViewer):
pdfViewer.setWindowTitle(QCoreApplication.translate("pdfViewer", u"PDF Viewer", None))
self.actionZoom_In.setText(QCoreApplication.translate("pdfViewer", u"Zoom In", None))
#if QT_CONFIG(tooltip)
self.actionZoom_In.setToolTip(QCoreApplication.translate("pdfViewer", u"Zoom In", None))
#endif // QT_CONFIG(tooltip)
#if QT_CONFIG(shortcut)
self.actionZoom_In.setShortcut(QCoreApplication.translate("pdfViewer", u"Ctrl+=", None))
#endif // QT_CONFIG(shortcut)
self.actionZoom_Out.setText(QCoreApplication.translate("pdfViewer", u"Zoom Out", None))
#if QT_CONFIG(shortcut)
self.actionZoom_Out.setShortcut(QCoreApplication.translate("pdfViewer", u"Ctrl+-", None))
#endif // QT_CONFIG(shortcut)
self.actionPrevious_Page.setText(QCoreApplication.translate("pdfViewer", u"Previous Page", None))
#if QT_CONFIG(shortcut)
self.actionPrevious_Page.setShortcut(QCoreApplication.translate("pdfViewer", u"PgUp", None))
#endif // QT_CONFIG(shortcut)
self.actionNext_Page.setText(QCoreApplication.translate("pdfViewer", u"Next Page", None))
#if QT_CONFIG(shortcut)
self.actionNext_Page.setShortcut(QCoreApplication.translate("pdfViewer", u"PgDown", None))
#endif // QT_CONFIG(shortcut)
self.actionContinuous.setText(QCoreApplication.translate("pdfViewer", u"Continuous", None))
#if QT_CONFIG(tooltip)
self.actionContinuous.setToolTip(QCoreApplication.translate("pdfViewer", u"Continuous", None))
#endif // QT_CONFIG(tooltip)
self.actionBack.setText(QCoreApplication.translate("pdfViewer", u"Back", None))
self.actionForward.setText(QCoreApplication.translate("pdfViewer", u"Forward", None))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.bookmarkTab), QCoreApplication.translate("pdfViewer", u"Bookmarks", None))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.pagesTab), QCoreApplication.translate("pdfViewer", u"Pages", None))
# retranslateUi

227
ui/pdfViewer.ui Normal file
View File

@@ -0,0 +1,227 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>pdfViewer</class>
<widget class="QDialog" name="pdfViewer">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>700</width>
<height>513</height>
</rect>
</property>
<property name="windowTitle">
<string>PDF Viewer</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QToolBar" name="mainToolBar">
<addaction name="actionZoom_Out"/>
<addaction name="actionZoom_In"/>
<addaction name="separator"/>
<addaction name="actionBack"/>
<addaction name="actionForward"/>
<addaction name="actionPrevious_Page"/>
<addaction name="actionNext_Page"/>
</widget>
</item>
<item>
<widget class="QWidget" name="widget" native="true">
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<widget class="QTabWidget" name="tabWidget">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="tabPosition">
<enum>QTabWidget::TabPosition::West</enum>
</property>
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="bookmarkTab">
<attribute name="title">
<string>Bookmarks</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_3">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>2</number>
</property>
<property name="topMargin">
<number>2</number>
</property>
<property name="rightMargin">
<number>2</number>
</property>
<property name="bottomMargin">
<number>2</number>
</property>
<item>
<widget class="QTreeView" name="bookmarkView">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="pagesTab">
<attribute name="title">
<string>Pages</string>
</attribute>
</widget>
</widget>
<widget class="QPdfView" name="pdfView" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>10</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
<action name="actionZoom_In">
<property name="icon">
<iconset theme="QIcon::ThemeIcon::ZoomIn"/>
</property>
<property name="text">
<string>Zoom In</string>
</property>
<property name="toolTip">
<string>Zoom In</string>
</property>
<property name="shortcut">
<string>Ctrl+=</string>
</property>
<property name="menuRole">
<enum>QAction::MenuRole::NoRole</enum>
</property>
</action>
<action name="actionZoom_Out">
<property name="icon">
<iconset theme="QIcon::ThemeIcon::ZoomOut"/>
</property>
<property name="text">
<string>Zoom Out</string>
</property>
<property name="shortcut">
<string>Ctrl+-</string>
</property>
<property name="menuRole">
<enum>QAction::MenuRole::NoRole</enum>
</property>
</action>
<action name="actionPrevious_Page">
<property name="icon">
<iconset resource="resources.qrc">
<normaloff>:/icons/images/go-previous-view-page.svgz</normaloff>:/icons/images/go-previous-view-page.svgz</iconset>
</property>
<property name="text">
<string>Previous Page</string>
</property>
<property name="shortcut">
<string>PgUp</string>
</property>
<property name="menuRole">
<enum>QAction::MenuRole::NoRole</enum>
</property>
</action>
<action name="actionNext_Page">
<property name="icon">
<iconset resource="resources.qrc">
<normaloff>:/icons/images/go-next-view-page.svgz</normaloff>:/icons/images/go-next-view-page.svgz</iconset>
</property>
<property name="text">
<string>Next Page</string>
</property>
<property name="shortcut">
<string>PgDown</string>
</property>
<property name="menuRole">
<enum>QAction::MenuRole::NoRole</enum>
</property>
</action>
<action name="actionContinuous">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Continuous</string>
</property>
<property name="toolTip">
<string>Continuous</string>
</property>
<property name="menuRole">
<enum>QAction::MenuRole::NoRole</enum>
</property>
</action>
<action name="actionBack">
<property name="icon">
<iconset resource="resources.qrc">
<normaloff>:/icons/images/go-previous-view.svgz</normaloff>:/icons/images/go-previous-view.svgz</iconset>
</property>
<property name="text">
<string>Back</string>
</property>
<property name="menuRole">
<enum>QAction::MenuRole::NoRole</enum>
</property>
</action>
<action name="actionForward">
<property name="icon">
<iconset resource="resources.qrc">
<normaloff>:/icons/images/go-next-view.svgz</normaloff>:/icons/images/go-next-view.svgz</iconset>
</property>
<property name="text">
<string>Forward</string>
</property>
<property name="menuRole">
<enum>QAction::MenuRole::NoRole</enum>
</property>
</action>
</widget>
<customwidgets>
<customwidget>
<class>QPdfView</class>
<extends>QWidget</extends>
<header>qpdfview.h</header>
</customwidget>
</customwidgets>
<resources>
<include location="resources.qrc"/>
</resources>
<connections/>
</ui>

11
ui/resources.qrc Normal file
View File

@@ -0,0 +1,11 @@
<RCC>
<qresource prefix="/icons">
<file>images/document-open.svgz</file>
<file>images/go-next-view.svgz</file>
<file>images/go-previous-view.svgz</file>
<file>images/go-next-view-page.svgz</file>
<file>images/go-previous-view-page.svgz</file>
<file>images/zoom-in.svgz</file>
<file>images/zoom-out.svgz</file>
</qresource>
</RCC>

3468
ui/resources_rc.py Normal file

File diff suppressed because it is too large Load Diff