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 | |
51 | PdfViewer::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 * = 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 * = menuBar()->addMenu(title: tr(s: "&View" )); |
68 | |
69 | QMenu * = 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 * = 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 * = 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 | |
162 | PdfViewer::~PdfViewer() |
163 | { |
164 | closeDocument(); |
165 | } |
166 | |
167 | QSize PdfViewer::sizeHint() const |
168 | { |
169 | return QSize(500, 600); |
170 | } |
171 | |
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(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 | |
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 | |
231 | void 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 | |
241 | void 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 | |
251 | void 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 | |
270 | void 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 | |
275 | void PdfViewer::slotAboutQt() |
276 | { |
277 | QMessageBox::aboutQt(parent: this); |
278 | } |
279 | |
280 | void 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 | |
293 | void 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 | |
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(page: m_currentPage); |
316 | } |
317 | } |
318 | |
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 | |
328 | int PdfViewer::page() const |
329 | { |
330 | return m_currentPage; |
331 | } |
332 | |