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