├── README.md ├── pyqt6_pdf_viewer.py └── requirements.txt /README.md: -------------------------------------------------------------------------------- 1 | # PyQt6 PDF Viewer 2 | Simple PDF viewer made with PyQt6. Scoured the Internet and for days and finally found a way to do it so I'm posting it here! Check out my other repository if you want to use Pyside6 instead. 3 | 4 | # Installation 5 | 6 | > First, make sure you're running [Python 3.10+](https://www.python.org/downloads/release/python-31011/) 7 | 8 | ### Setup 9 | Download the latest releast and unzip the folder to somewhere on your computer. Then, open the folder containing my repository files, create a command prompt, and create a virtual environment: 10 | ``` 11 | python -m venv . 12 | ``` 13 | Activate the virtual environment: 14 | ``` 15 | .\Scripts\activate 16 | ``` 17 | Upgrade pip 18 | ``` 19 | python -m pip install --upgrade pip 20 | ``` 21 | Install Dependencies 22 | ``` 23 | pip install -r requirements.txt 24 | ``` 25 | 26 | ### Run Program 27 | ``` 28 | python pyqt6_pdf_viewer.py 29 | ``` 30 | 31 | ## Please STAR if you enjoy so it's easier for other people to find this repository! 32 | -------------------------------------------------------------------------------- /pyqt6_pdf_viewer.py: -------------------------------------------------------------------------------- 1 | from PyQt6.QtCore import QUrl, Qt, QDir, QFileInfo 2 | from PyQt6.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout, QLineEdit, QFileDialog, QPushButton, QHBoxLayout, QTreeView, QSplitter, QLabel, QSizePolicy 3 | from PyQt6.QtGui import QAction, QStandardItemModel, QStandardItem, QIcon 4 | from PyQt6.QtWebEngineWidgets import QWebEngineView 5 | from PyQt6.QtWebEngineCore import QWebEnginePage 6 | import os 7 | 8 | class PDFFileSystemModel(QStandardItemModel): 9 | def __init__(self, parent=None): 10 | super().__init__(parent) 11 | self.setHorizontalHeaderLabels(["PDF Files and Folders"]) 12 | self.root_path = os.path.dirname(os.path.abspath(__file__)) 13 | self.current_path = self.root_path 14 | self.populate_model(self.invisibleRootItem(), self.current_path) 15 | 16 | def populate_model(self, parent_item, directory_path): 17 | parent_item.removeRows(0, parent_item.rowCount()) 18 | directory = QDir(directory_path) 19 | up_item = QStandardItem("") 20 | up_item.setData(os.path.dirname(directory_path), Qt.ItemDataRole.UserRole) 21 | up_item.setIcon(QIcon.fromTheme("go-up")) 22 | parent_item.appendRow(up_item) 23 | directories = directory.entryInfoList(QDir.Filter.Dirs | QDir.Filter.NoDotAndDotDot, QDir.SortFlag.Name) 24 | for dir_info in directories: 25 | dir_item = QStandardItem(dir_info.fileName()) 26 | dir_item.setData(dir_info.filePath(), Qt.ItemDataRole.UserRole) 27 | dir_item.setIcon(QIcon.fromTheme("folder")) 28 | parent_item.appendRow(dir_item) 29 | placeholder = QStandardItem("Loading...") 30 | dir_item.appendRow(placeholder) 31 | pdf_files = directory.entryInfoList(["*.pdf"], QDir.Filter.Files, QDir.SortFlag.Name) 32 | for file_info in pdf_files: 33 | file_item = QStandardItem(file_info.fileName()) 34 | file_item.setData(file_info.filePath(), Qt.ItemDataRole.UserRole) 35 | file_item.setIcon(QIcon.fromTheme("application-pdf")) 36 | parent_item.appendRow(file_item) 37 | 38 | def navigate_to(self, path): 39 | self.current_path = path 40 | self.populate_model(self.invisibleRootItem(), path) 41 | 42 | class MainWindow(QMainWindow): 43 | def __init__(self): 44 | super().__init__() 45 | self.setWindowTitle("PDF Viewer") 46 | self.setGeometry(0, 28, 1200, 750) 47 | self.central_widget = QWidget() 48 | self.setCentralWidget(self.central_widget) 49 | self.main_layout = QVBoxLayout(self.central_widget) 50 | self.tree_model = PDFFileSystemModel() 51 | self.path_label = QLabel() 52 | self.path_label.setText(self.tree_model.current_path) 53 | self.path_label.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed) 54 | self.main_layout.addWidget(self.path_label) 55 | self.splitter = QSplitter(Qt.Orientation.Horizontal) 56 | self.main_layout.addWidget(self.splitter) 57 | self.nav_widget = QWidget() 58 | self.nav_layout = QVBoxLayout(self.nav_widget) 59 | self.tree_view = QTreeView() 60 | self.tree_view.setModel(self.tree_model) 61 | self.tree_view.setHeaderHidden(True) 62 | self.tree_view.clicked.connect(self.on_tree_clicked) 63 | self.tree_view.expanded.connect(self.on_tree_expanded) 64 | self.nav_layout.addWidget(self.tree_view) 65 | self.pdf_widget = QWidget() 66 | self.pdf_layout = QVBoxLayout(self.pdf_widget) 67 | self.webView = QWebEngineView() 68 | self.webView.settings().setAttribute(self.webView.settings().WebAttribute.PluginsEnabled, True) 69 | self.webView.settings().setAttribute(self.webView.settings().WebAttribute.PdfViewerEnabled, True) 70 | self.pdf_layout.addWidget(self.webView) 71 | self.search_input = QLineEdit() 72 | self.search_input.setPlaceholderText("Enter text to search...") 73 | self.search_input.returnPressed.connect(lambda: self.search_text(self.search_input.text())) 74 | self.pdf_layout.addWidget(self.search_input) 75 | self.splitter.addWidget(self.nav_widget) 76 | self.splitter.addWidget(self.pdf_widget) 77 | self.splitter.setSizes([250, 750]) 78 | self.create_file_menu() 79 | 80 | def on_tree_clicked(self, index): 81 | item = self.tree_model.itemFromIndex(index) 82 | file_path = item.data(Qt.ItemDataRole.UserRole) 83 | if item.text() == "": 84 | self.tree_model.navigate_to(file_path) 85 | self.path_label.setText(file_path) 86 | return 87 | if os.path.isdir(file_path): 88 | self.tree_model.navigate_to(file_path) 89 | self.path_label.setText(file_path) 90 | return 91 | if file_path and file_path.lower().endswith('.pdf'): 92 | self.path_label.setText(file_path) 93 | pdf_url = QUrl.fromLocalFile(file_path) 94 | pdf_url.setFragment("zoom=page-width") 95 | self.webView.setUrl(pdf_url) 96 | 97 | def on_tree_expanded(self, index): 98 | item = self.tree_model.itemFromIndex(index) 99 | file_path = item.data(Qt.ItemDataRole.UserRole) 100 | if item.rowCount() == 1 and item.child(0).text() == "Loading...": 101 | item.removeRow(0) 102 | self.tree_model.populate_model(item, file_path) 103 | 104 | def create_file_menu(self): 105 | menubar = self.menuBar() 106 | file_menu = menubar.addMenu('Choose PDF') 107 | open_action = QAction('Open', self) 108 | open_action.triggered.connect(self.open_file_dialog) 109 | file_menu.addAction(open_action) 110 | 111 | def open_file_dialog(self): 112 | file_dialog = QFileDialog() 113 | filename, _ = file_dialog.getOpenFileName(self, "Open PDF", "", "PDF Files (*.pdf)") 114 | if filename: 115 | pdf_url = QUrl.fromLocalFile(filename) 116 | pdf_url.setFragment("zoom=page-width") 117 | self.webView.setUrl(pdf_url) 118 | 119 | def search_text(self, text): 120 | flag = QWebEnginePage.FindFlag.FindCaseSensitively 121 | if text: 122 | self.webView.page().findText(text, flag) 123 | else: 124 | self.webView.page().stopFinding() 125 | 126 | if __name__ == '__main__': 127 | import sys 128 | app = QApplication(sys.argv) 129 | app.setStyle("Fusion") 130 | win = MainWindow() 131 | win.show() 132 | sys.exit(app.exec()) 133 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | PyQt6 2 | PyQt6-WebEngine --------------------------------------------------------------------------------