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
PdfViewer(QWidget * parent)51 PdfViewer::PdfViewer(QWidget *parent) : QMainWindow(parent), m_currentPage(0), m_doc(nullptr)
52 {
53 setWindowTitle(tr("Poppler-Qt6 Demo"));
54
55 // setup the menus
56 QMenu *fileMenu = menuBar()->addMenu(tr("&File"));
57 m_fileOpenAct = fileMenu->addAction(tr("&Open"), this, &PdfViewer::slotOpenFile);
58 m_fileOpenAct->setShortcut(Qt::CTRL | Qt::Key_O);
59 fileMenu->addSeparator();
60 m_fileSaveCopyAct = fileMenu->addAction(tr("&Save a Copy..."), this, &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(tr("&Quit"), qApp, &QApplication::closeAllWindows);
65 act->setShortcut(Qt::CTRL | Qt::Key_Q);
66
67 QMenu *viewMenu = menuBar()->addMenu(tr("&View"));
68
69 QMenu *settingsMenu = menuBar()->addMenu(tr("&Settings"));
70 m_settingsTextAAAct = settingsMenu->addAction(tr("Text Antialias"));
71 m_settingsTextAAAct->setCheckable(true);
72 connect(m_settingsTextAAAct, &QAction::toggled, this, &PdfViewer::slotToggleTextAA);
73 m_settingsGfxAAAct = settingsMenu->addAction(tr("Graphics Antialias"));
74 m_settingsGfxAAAct->setCheckable(true);
75 connect(m_settingsGfxAAAct, &QAction::toggled, this, &PdfViewer::slotToggleGfxAA);
76 QMenu *settingsRenderMenu = settingsMenu->addMenu(tr("Render Backend"));
77 m_settingsRenderBackendGrp = new QActionGroup(settingsRenderMenu);
78 m_settingsRenderBackendGrp->setExclusive(true);
79 act = settingsRenderMenu->addAction(tr("Splash"));
80 act->setCheckable(true);
81 act->setChecked(true);
82 act->setData(QVariant::fromValue(0));
83 m_settingsRenderBackendGrp->addAction(act);
84 act = settingsRenderMenu->addAction(tr("QPainter"));
85 act->setCheckable(true);
86 act->setData(QVariant::fromValue(1));
87 m_settingsRenderBackendGrp->addAction(act);
88 connect(m_settingsRenderBackendGrp, &QActionGroup::triggered, this, &PdfViewer::slotRenderBackend);
89
90 QMenu *helpMenu = menuBar()->addMenu(tr("&Help"));
91 act = helpMenu->addAction(tr("&About"), this, &PdfViewer::slotAbout);
92 act = helpMenu->addAction(tr("About &Qt"), this, &PdfViewer::slotAboutQt);
93
94 NavigationToolBar *navbar = new NavigationToolBar(this);
95 addToolBar(navbar);
96 m_observers.append(navbar);
97
98 PageView *view = new PageView(this);
99 setCentralWidget(view);
100 m_observers.append(view);
101
102 InfoDock *infoDock = new InfoDock(this);
103 addDockWidget(Qt::LeftDockWidgetArea, infoDock);
104 infoDock->hide();
105 viewMenu->addAction(infoDock->toggleViewAction());
106 m_observers.append(infoDock);
107
108 TocDock *tocDock = new TocDock(this);
109 addDockWidget(Qt::LeftDockWidgetArea, tocDock);
110 tocDock->hide();
111 viewMenu->addAction(tocDock->toggleViewAction());
112 m_observers.append(tocDock);
113
114 FontsDock *fontsDock = new FontsDock(this);
115 addDockWidget(Qt::LeftDockWidgetArea, fontsDock);
116 fontsDock->hide();
117 viewMenu->addAction(fontsDock->toggleViewAction());
118 m_observers.append(fontsDock);
119
120 PermissionsDock *permissionsDock = new PermissionsDock(this);
121 addDockWidget(Qt::LeftDockWidgetArea, permissionsDock);
122 permissionsDock->hide();
123 viewMenu->addAction(permissionsDock->toggleViewAction());
124 m_observers.append(permissionsDock);
125
126 ThumbnailsDock *thumbnailsDock = new ThumbnailsDock(this);
127 addDockWidget(Qt::LeftDockWidgetArea, thumbnailsDock);
128 thumbnailsDock->hide();
129 viewMenu->addAction(thumbnailsDock->toggleViewAction());
130 m_observers.append(thumbnailsDock);
131
132 EmbeddedFilesDock *embfilesDock = new EmbeddedFilesDock(this);
133 addDockWidget(Qt::BottomDockWidgetArea, embfilesDock);
134 embfilesDock->hide();
135 viewMenu->addAction(embfilesDock->toggleViewAction());
136 m_observers.append(embfilesDock);
137
138 MetadataDock *metadataDock = new MetadataDock(this);
139 addDockWidget(Qt::BottomDockWidgetArea, metadataDock);
140 metadataDock->hide();
141 viewMenu->addAction(metadataDock->toggleViewAction());
142 m_observers.append(metadataDock);
143
144 OptContentDock *optContentDock = new OptContentDock(this);
145 addDockWidget(Qt::LeftDockWidgetArea, optContentDock);
146 optContentDock->hide();
147 viewMenu->addAction(optContentDock->toggleViewAction());
148 m_observers.append(optContentDock);
149
150 Q_FOREACH (DocumentObserver *obs, m_observers) {
151 obs->m_viewer = this;
152 }
153
154 connect(navbar, &NavigationToolBar::zoomChanged, view, &PageView::slotZoomChanged);
155 connect(navbar, &NavigationToolBar::rotationChanged, view, &PageView::slotRotationChanged);
156
157 // activate AA by default
158 m_settingsTextAAAct->setChecked(true);
159 m_settingsGfxAAAct->setChecked(true);
160 }
161
~PdfViewer()162 PdfViewer::~PdfViewer()
163 {
164 closeDocument();
165 }
166
sizeHint() const167 QSize PdfViewer::sizeHint() const
168 {
169 return QSize(500, 600);
170 }
171
loadDocument(const QString & file)172 void 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(file);
177 if (!newdoc) {
178 QMessageBox msgbox(QMessageBox::Critical, tr("Open Error"), tr("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(this, tr("Document Password"), tr("Please insert the password of the document:"), QLineEdit::Password, QString(), &ok);
186 if (!ok) {
187 return;
188 }
189 newdoc->unlock(password.toLatin1(), password.toLatin1());
190 }
191
192 closeDocument();
193
194 m_doc = std::move(newdoc);
195
196 m_doc->setRenderHint(Poppler::Document::TextAntialiasing, m_settingsTextAAAct->isChecked());
197 m_doc->setRenderHint(Poppler::Document::Antialiasing, 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(0);
210 }
211
212 m_fileSaveCopyAct->setEnabled(true);
213 }
214
closeDocument()215 void 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
xrefReconstructedHandler()231 void PdfViewer::xrefReconstructedHandler()
232 {
233 if (!xrefReconstructed) {
234 QMessageBox msgbox(QMessageBox::Critical, tr("File may be corrupted"), tr("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
slotOpenFile()241 void PdfViewer::slotOpenFile()
242 {
243 QString fileName = QFileDialog::getOpenFileName(this, tr("Open PDF Document"), QDir::homePath(), tr("PDF Documents (*.pdf)"));
244 if (fileName.isEmpty()) {
245 return;
246 }
247
248 loadDocument(fileName);
249 }
250
slotSaveCopy()251 void PdfViewer::slotSaveCopy()
252 {
253 if (!m_doc) {
254 return;
255 }
256
257 QString fileName = QFileDialog::getSaveFileName(this, tr("Save Copy"), QDir::homePath(), tr("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("Save Error"), tr("Cannot export to:\n%1").arg(fileName), QMessageBox::Ok, this);
267 }
268 }
269
slotAbout()270 void PdfViewer::slotAbout()
271 {
272 QMessageBox::about(this, tr("About Poppler-Qt6 Demo"), tr("This is a demo of the Poppler-Qt6 library."));
273 }
274
slotAboutQt()275 void PdfViewer::slotAboutQt()
276 {
277 QMessageBox::aboutQt(this);
278 }
279
slotToggleTextAA(bool value)280 void PdfViewer::slotToggleTextAA(bool value)
281 {
282 if (!m_doc) {
283 return;
284 }
285
286 m_doc->setRenderHint(Poppler::Document::TextAntialiasing, value);
287
288 Q_FOREACH (DocumentObserver *obs, m_observers) {
289 obs->pageChanged(m_currentPage);
290 }
291 }
292
slotToggleGfxAA(bool value)293 void PdfViewer::slotToggleGfxAA(bool value)
294 {
295 if (!m_doc) {
296 return;
297 }
298
299 m_doc->setRenderHint(Poppler::Document::Antialiasing, value);
300
301 Q_FOREACH (DocumentObserver *obs, m_observers) {
302 obs->pageChanged(m_currentPage);
303 }
304 }
305
slotRenderBackend(QAction * act)306 void 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(m_currentPage);
316 }
317 }
318
setPage(int page)319 void PdfViewer::setPage(int page)
320 {
321 Q_FOREACH (DocumentObserver *obs, m_observers) {
322 obs->pageChanged(page);
323 }
324
325 m_currentPage = page;
326 }
327
page() const328 int PdfViewer::page() const
329 {
330 return m_currentPage;
331 }
332