1/*
2 * Copyright (C) 2008-2009, Pino Toscano <pino@kde.org>
3 * Copyright (C) 2008, 2019, 2020, Albert Astals Cid <aacid@kde.org>
4 * Copyright (C) 2009, Shawn Rutledge <shawn.t.rutledge@gmail.com>
5 * Copyright (C) 2013, Fabio D'Urso <fabiodurso@hotmail.it>
6 * Copyright (C) 2020, 2021, Oliver Sander <oliver.sander@tu-dresden.de>
7 * Copyright (C) 2021, Mahmoud Khalil <mahmoudkhalil11@gmail.com>
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2, or (at your option)
12 * any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
22 */
23
24#include "viewer.h"
25
26#include "embeddedfiles.h"
27#include "fonts.h"
28#include "info.h"
29#include "metadata.h"
30#include "navigationtoolbar.h"
31#include "optcontent.h"
32#include "pageview.h"
33#include "permissions.h"
34#include "thumbnails.h"
35#include "toc.h"
36
37#include <poppler-qt6.h>
38
39#include <QAction>
40#include <QActionGroup>
41#include <QApplication>
42#include <QDir>
43#include <QFileDialog>
44#include <QInputDialog>
45#include <QMenu>
46#include <QMenuBar>
47#include <QMessageBox>
48
49#include <functional>
50
51PdfViewer::PdfViewer(QWidget *parent) : QMainWindow(parent), m_currentPage(0), m_doc(nullptr)
52{
53 setWindowTitle(tr(s: "Poppler-Qt6 Demo"));
54
55 // setup the menus
56 QMenu *fileMenu = menuBar()->addMenu(title: tr(s: "&File"));
57 m_fileOpenAct = fileMenu->addAction(text: tr(s: "&Open"), args: this, args: &PdfViewer::slotOpenFile);
58 m_fileOpenAct->setShortcut(Qt::CTRL | Qt::Key_O);
59 fileMenu->addSeparator();
60 m_fileSaveCopyAct = fileMenu->addAction(text: tr(s: "&Save a Copy..."), args: this, args: &PdfViewer::slotSaveCopy);
61 m_fileSaveCopyAct->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_S);
62 m_fileSaveCopyAct->setEnabled(false);
63 fileMenu->addSeparator();
64 QAction *act = fileMenu->addAction(text: tr(s: "&Quit"), qApp, args: &QApplication::closeAllWindows);
65 act->setShortcut(Qt::CTRL | Qt::Key_Q);
66
67 QMenu *viewMenu = menuBar()->addMenu(title: tr(s: "&View"));
68
69 QMenu *settingsMenu = menuBar()->addMenu(title: tr(s: "&Settings"));
70 m_settingsTextAAAct = settingsMenu->addAction(text: tr(s: "Text Antialias"));
71 m_settingsTextAAAct->setCheckable(true);
72 connect(sender: m_settingsTextAAAct, signal: &QAction::toggled, context: this, slot: &PdfViewer::slotToggleTextAA);
73 m_settingsGfxAAAct = settingsMenu->addAction(text: tr(s: "Graphics Antialias"));
74 m_settingsGfxAAAct->setCheckable(true);
75 connect(sender: m_settingsGfxAAAct, signal: &QAction::toggled, context: this, slot: &PdfViewer::slotToggleGfxAA);
76 QMenu *settingsRenderMenu = settingsMenu->addMenu(title: tr(s: "Render Backend"));
77 m_settingsRenderBackendGrp = new QActionGroup(settingsRenderMenu);
78 m_settingsRenderBackendGrp->setExclusive(true);
79 act = settingsRenderMenu->addAction(text: tr(s: "Splash"));
80 act->setCheckable(true);
81 act->setChecked(true);
82 act->setData(QVariant::fromValue(value: 0));
83 m_settingsRenderBackendGrp->addAction(a: act);
84 act = settingsRenderMenu->addAction(text: tr(s: "QPainter"));
85 act->setCheckable(true);
86 act->setData(QVariant::fromValue(value: 1));
87 m_settingsRenderBackendGrp->addAction(a: act);
88 connect(sender: m_settingsRenderBackendGrp, signal: &QActionGroup::triggered, context: this, slot: &PdfViewer::slotRenderBackend);
89
90 QMenu *helpMenu = menuBar()->addMenu(title: tr(s: "&Help"));
91 act = helpMenu->addAction(text: tr(s: "&About"), args: this, args: &PdfViewer::slotAbout);
92 act = helpMenu->addAction(text: tr(s: "About &Qt"), args: this, args: &PdfViewer::slotAboutQt);
93
94 NavigationToolBar *navbar = new NavigationToolBar(this);
95 addToolBar(toolbar: navbar);
96 m_observers.append(t: navbar);
97
98 PageView *view = new PageView(this);
99 setCentralWidget(view);
100 m_observers.append(t: view);
101
102 InfoDock *infoDock = new InfoDock(this);
103 addDockWidget(area: Qt::LeftDockWidgetArea, dockwidget: infoDock);
104 infoDock->hide();
105 viewMenu->addAction(action: infoDock->toggleViewAction());
106 m_observers.append(t: infoDock);
107
108 TocDock *tocDock = new TocDock(this);
109 addDockWidget(area: Qt::LeftDockWidgetArea, dockwidget: tocDock);
110 tocDock->hide();
111 viewMenu->addAction(action: tocDock->toggleViewAction());
112 m_observers.append(t: tocDock);
113
114 FontsDock *fontsDock = new FontsDock(this);
115 addDockWidget(area: Qt::LeftDockWidgetArea, dockwidget: fontsDock);
116 fontsDock->hide();
117 viewMenu->addAction(action: fontsDock->toggleViewAction());
118 m_observers.append(t: fontsDock);
119
120 PermissionsDock *permissionsDock = new PermissionsDock(this);
121 addDockWidget(area: Qt::LeftDockWidgetArea, dockwidget: permissionsDock);
122 permissionsDock->hide();
123 viewMenu->addAction(action: permissionsDock->toggleViewAction());
124 m_observers.append(t: permissionsDock);
125
126 ThumbnailsDock *thumbnailsDock = new ThumbnailsDock(this);
127 addDockWidget(area: Qt::LeftDockWidgetArea, dockwidget: thumbnailsDock);
128 thumbnailsDock->hide();
129 viewMenu->addAction(action: thumbnailsDock->toggleViewAction());
130 m_observers.append(t: thumbnailsDock);
131
132 EmbeddedFilesDock *embfilesDock = new EmbeddedFilesDock(this);
133 addDockWidget(area: Qt::BottomDockWidgetArea, dockwidget: embfilesDock);
134 embfilesDock->hide();
135 viewMenu->addAction(action: embfilesDock->toggleViewAction());
136 m_observers.append(t: embfilesDock);
137
138 MetadataDock *metadataDock = new MetadataDock(this);
139 addDockWidget(area: Qt::BottomDockWidgetArea, dockwidget: metadataDock);
140 metadataDock->hide();
141 viewMenu->addAction(action: metadataDock->toggleViewAction());
142 m_observers.append(t: metadataDock);
143
144 OptContentDock *optContentDock = new OptContentDock(this);
145 addDockWidget(area: Qt::LeftDockWidgetArea, dockwidget: optContentDock);
146 optContentDock->hide();
147 viewMenu->addAction(action: optContentDock->toggleViewAction());
148 m_observers.append(t: optContentDock);
149
150 Q_FOREACH (DocumentObserver *obs, m_observers) {
151 obs->m_viewer = this;
152 }
153
154 connect(sender: navbar, signal: &NavigationToolBar::zoomChanged, context: view, slot: &PageView::slotZoomChanged);
155 connect(sender: navbar, signal: &NavigationToolBar::rotationChanged, context: view, slot: &PageView::slotRotationChanged);
156
157 // activate AA by default
158 m_settingsTextAAAct->setChecked(true);
159 m_settingsGfxAAAct->setChecked(true);
160}
161
162PdfViewer::~PdfViewer()
163{
164 closeDocument();
165}
166
167QSize PdfViewer::sizeHint() const
168{
169 return QSize(500, 600);
170}
171
172void PdfViewer::loadDocument(const QString &file)
173{
174 // resetting xrefReconstructed each time we load new document
175 xrefReconstructed = false;
176 std::unique_ptr<Poppler::Document> newdoc = Poppler::Document::load(filePath: file);
177 if (!newdoc) {
178 QMessageBox msgbox(QMessageBox::Critical, tr(s: "Open Error"), tr(s: "Cannot open:\n") + file, QMessageBox::Ok, this);
179 msgbox.exec();
180 return;
181 }
182
183 while (newdoc->isLocked()) {
184 bool ok = true;
185 QString password = QInputDialog::getText(parent: this, title: tr(s: "Document Password"), label: tr(s: "Please insert the password of the document:"), echo: QLineEdit::Password, text: QString(), ok: &ok);
186 if (!ok) {
187 return;
188 }
189 newdoc->unlock(ownerPassword: password.toLatin1(), userPassword: password.toLatin1());
190 }
191
192 closeDocument();
193
194 m_doc = std::move(newdoc);
195
196 m_doc->setRenderHint(hint: Poppler::Document::TextAntialiasing, on: m_settingsTextAAAct->isChecked());
197 m_doc->setRenderHint(hint: Poppler::Document::Antialiasing, on: m_settingsGfxAAAct->isChecked());
198 m_doc->setRenderBackend((Poppler::Document::RenderBackend)m_settingsRenderBackendGrp->checkedAction()->data().toInt());
199 if (m_doc->xrefWasReconstructed()) {
200 xrefReconstructedHandler();
201 } else {
202 std::function<void()> cb = [this]() { xrefReconstructedHandler(); };
203
204 m_doc->setXRefReconstructedCallback(cb);
205 }
206
207 Q_FOREACH (DocumentObserver *obs, m_observers) {
208 obs->documentLoaded();
209 obs->pageChanged(page: 0);
210 }
211
212 m_fileSaveCopyAct->setEnabled(true);
213}
214
215void PdfViewer::closeDocument()
216{
217 if (!m_doc) {
218 return;
219 }
220
221 Q_FOREACH (DocumentObserver *obs, m_observers) {
222 obs->documentClosed();
223 }
224
225 m_currentPage = 0;
226 m_doc = nullptr;
227
228 m_fileSaveCopyAct->setEnabled(false);
229}
230
231void PdfViewer::xrefReconstructedHandler()
232{
233 if (!xrefReconstructed) {
234 QMessageBox msgbox(QMessageBox::Critical, tr(s: "File may be corrupted"), tr(s: "The PDF may be broken but we're still showing something, contents may not be correct"), QMessageBox::Ok, this);
235 msgbox.exec();
236
237 xrefReconstructed = true;
238 }
239}
240
241void PdfViewer::slotOpenFile()
242{
243 QString fileName = QFileDialog::getOpenFileName(parent: this, caption: tr(s: "Open PDF Document"), dir: QDir::homePath(), filter: tr(s: "PDF Documents (*.pdf)"));
244 if (fileName.isEmpty()) {
245 return;
246 }
247
248 loadDocument(file: fileName);
249}
250
251void PdfViewer::slotSaveCopy()
252{
253 if (!m_doc) {
254 return;
255 }
256
257 QString fileName = QFileDialog::getSaveFileName(parent: this, caption: tr(s: "Save Copy"), dir: QDir::homePath(), filter: tr(s: "PDF Documents (*.pdf)"));
258 if (fileName.isEmpty()) {
259 return;
260 }
261
262 std::unique_ptr<Poppler::PDFConverter> converter = m_doc->pdfConverter();
263 converter->setOutputFileName(fileName);
264 converter->setPDFOptions(converter->pdfOptions() & ~Poppler::PDFConverter::WithChanges);
265 if (!converter->convert()) {
266 QMessageBox msgbox(QMessageBox::Critical, tr(s: "Save Error"), tr(s: "Cannot export to:\n%1").arg(a: fileName), QMessageBox::Ok, this);
267 }
268}
269
270void PdfViewer::slotAbout()
271{
272 QMessageBox::about(parent: this, title: tr(s: "About Poppler-Qt6 Demo"), text: tr(s: "This is a demo of the Poppler-Qt6 library."));
273}
274
275void PdfViewer::slotAboutQt()
276{
277 QMessageBox::aboutQt(parent: this);
278}
279
280void PdfViewer::slotToggleTextAA(bool value)
281{
282 if (!m_doc) {
283 return;
284 }
285
286 m_doc->setRenderHint(hint: Poppler::Document::TextAntialiasing, on: value);
287
288 Q_FOREACH (DocumentObserver *obs, m_observers) {
289 obs->pageChanged(page: m_currentPage);
290 }
291}
292
293void PdfViewer::slotToggleGfxAA(bool value)
294{
295 if (!m_doc) {
296 return;
297 }
298
299 m_doc->setRenderHint(hint: Poppler::Document::Antialiasing, on: value);
300
301 Q_FOREACH (DocumentObserver *obs, m_observers) {
302 obs->pageChanged(page: m_currentPage);
303 }
304}
305
306void PdfViewer::slotRenderBackend(QAction *act)
307{
308 if (!m_doc || !act) {
309 return;
310 }
311
312 m_doc->setRenderBackend((Poppler::Document::RenderBackend)act->data().toInt());
313
314 Q_FOREACH (DocumentObserver *obs, m_observers) {
315 obs->pageChanged(page: m_currentPage);
316 }
317}
318
319void PdfViewer::setPage(int page)
320{
321 Q_FOREACH (DocumentObserver *obs, m_observers) {
322 obs->pageChanged(page);
323 }
324
325 m_currentPage = page;
326}
327
328int PdfViewer::page() const
329{
330 return m_currentPage;
331}
332

source code of poppler/qt6/demos/viewer.cpp